Add environment variable example and update .gitignore
- Created a new .env.example file to provide a template for environment variables, including database connection details, JWT settings, encryption keys, and external API keys. - Updated .gitignore to include additional test output directories and archive files, ensuring that unnecessary files are not tracked by Git. - Removed outdated approval test reports and scripts that are no longer needed, streamlining the project structure. These changes improve the clarity of environment configuration and maintain a cleaner repository.
This commit is contained in:
@@ -1233,22 +1233,34 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
|
||||
const strValue = String(value);
|
||||
|
||||
if (mapping && mapping[strValue]) {
|
||||
const categoryData = mapping[strValue];
|
||||
return categoryData.label || strValue;
|
||||
}
|
||||
|
||||
// 전역 폴백: 컬럼명으로 매핑을 못 찾았을 때, 전체 매핑에서 값 검색
|
||||
if (!mapping && (strValue.startsWith("CAT_") || strValue.startsWith("CATEGORY_"))) {
|
||||
// 카테고리 코드 라벨 변환 헬퍼
|
||||
const resolveLabel = (code: string): string | null => {
|
||||
if (mapping && mapping[code]) return mapping[code].label || code;
|
||||
for (const key of Object.keys(categoryMappings)) {
|
||||
const m = categoryMappings[key];
|
||||
if (m && m[strValue]) {
|
||||
const categoryData = m[strValue];
|
||||
return categoryData.label || strValue;
|
||||
}
|
||||
if (m && m[code]) return m[code].label || code;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// 콤마/세미콜론 구분 다중값 처리
|
||||
const looksLikeCatCode = (v: string) => v.startsWith("CAT_") || v.startsWith("CATEGORY_");
|
||||
if (looksLikeCatCode(strValue) || strValue.includes(",") || strValue.includes(";")) {
|
||||
const delimiter = strValue.includes(";") ? ";" : ",";
|
||||
const codes = strValue.includes(delimiter) ? strValue.split(delimiter).map(s => s.trim()).filter(Boolean) : [strValue];
|
||||
if (codes.some(c => looksLikeCatCode(c))) {
|
||||
const labels = codes.map(code => resolveLabel(code) || code);
|
||||
return labels.join(", ");
|
||||
}
|
||||
}
|
||||
|
||||
// 단일값 변환
|
||||
if (mapping && mapping[strValue]) {
|
||||
return mapping[strValue].label || strValue;
|
||||
}
|
||||
const resolved = resolveLabel(strValue);
|
||||
if (resolved) return resolved;
|
||||
|
||||
// 🆕 자동 날짜 감지 (ISO 8601 형식 또는 Date 객체)
|
||||
if (typeof value === "string" && value.match(/^\d{4}-\d{2}-\d{2}(T|\s)/)) {
|
||||
return formatDateValue(value, "YYYY-MM-DD");
|
||||
@@ -2429,6 +2441,31 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
}
|
||||
}
|
||||
|
||||
// 카테고리 타입이 아닌 컬럼 중 division/unit/type 등은 item_info에서 fallback 로드
|
||||
// 엔티티 조인 컬럼명은 "item_id_division" 형태이므로 끝부분으로 매칭
|
||||
const KNOWN_CAT_SUFFIXES = ["division", "unit", "type", "material"];
|
||||
const leftPanelCols = componentConfig.leftPanel?.columns || [];
|
||||
for (const col of leftPanelCols) {
|
||||
const colName = (col as any).name || (col as any).columnName || (col as any).column_name;
|
||||
if (!colName || mappings[colName]) continue;
|
||||
const suffix = KNOWN_CAT_SUFFIXES.find(s => colName === s || colName.endsWith(`_${s}`));
|
||||
if (!suffix) continue;
|
||||
try {
|
||||
const fbRes = await apiClient.get(`/table-categories/item_info/${suffix}/values?includeInactive=true`);
|
||||
if (fbRes.data.success && fbRes.data.data?.length > 0) {
|
||||
const fbMap: Record<string, { label: string; color?: string }> = {};
|
||||
const flatFb = (items: any[]) => {
|
||||
items.forEach((item: any) => {
|
||||
fbMap[item.value_code || item.valueCode] = { label: item.value_label || item.valueLabel, color: item.color };
|
||||
if (item.children?.length) flatFb(item.children);
|
||||
});
|
||||
};
|
||||
flatFb(fbRes.data.data);
|
||||
if (Object.keys(fbMap).length > 0) mappings[colName] = fbMap;
|
||||
}
|
||||
} catch { /* 무시 */ }
|
||||
}
|
||||
|
||||
setLeftCategoryMappings(mappings);
|
||||
} catch (error) {
|
||||
console.error("좌측 카테고리 매핑 로드 실패:", error);
|
||||
@@ -4036,7 +4073,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-border divide-y bg-white">
|
||||
<tbody className="divide-border divide-y bg-card">
|
||||
<tr className="hover:bg-muted cursor-pointer">
|
||||
<td className="px-3 py-2 text-sm whitespace-nowrap">데이터 1-1</td>
|
||||
<td className="px-3 py-2 text-sm whitespace-nowrap">데이터 1-2</td>
|
||||
@@ -4155,7 +4192,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-border divide-y bg-white">
|
||||
<tbody className="divide-border divide-y bg-card">
|
||||
{group.items.map((item, idx) => {
|
||||
const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id";
|
||||
const itemId = item[sourceColumn] || item.id || item.ID || idx;
|
||||
@@ -4167,9 +4204,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
<tr
|
||||
key={itemId}
|
||||
onClick={() => handleLeftItemSelect(item)}
|
||||
className={`group hover:bg-accent cursor-pointer transition-colors ${
|
||||
isSelected ? "bg-primary/10" : ""
|
||||
}`}
|
||||
className={`group hover:bg-accent cursor-pointer transition-colors ${isSelected ? "bg-primary/10" : ""}`}
|
||||
>
|
||||
{columnsToShow.map((col, colIdx) => (
|
||||
<td
|
||||
@@ -4190,7 +4225,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
</td>
|
||||
))}
|
||||
{hasGroupedLeftActions && (
|
||||
<td className="bg-card group-hover:bg-accent sticky right-0 z-10 px-3 py-2 text-right">
|
||||
<td className={`sticky right-0 z-10 px-3 py-2 text-right ${isSelected ? "bg-transparent" : "bg-card"}`}>
|
||||
<div className="flex items-center justify-end gap-1 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
{componentConfig.leftPanel?.showEdit !== false && (
|
||||
<button
|
||||
@@ -4275,7 +4310,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-border divide-y bg-white">
|
||||
<tbody className="divide-border divide-y bg-card">
|
||||
{filteredData.map((item, idx) => {
|
||||
const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id";
|
||||
const itemId = item[sourceColumn] || item.id || item.ID || idx;
|
||||
@@ -4310,7 +4345,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
</td>
|
||||
))}
|
||||
{hasLeftTableActions && (
|
||||
<td className="bg-card group-hover:bg-accent sticky right-0 z-10 px-3 py-2 text-right">
|
||||
<td className={`sticky right-0 z-10 px-3 py-2 text-right ${isSelected ? "bg-transparent" : "bg-card"}`}>
|
||||
<div className="flex items-center justify-end gap-1 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
{componentConfig.leftPanel?.showEdit !== false && (
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user