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:
kjs
2026-04-01 12:12:15 +09:00
parent 250a83b581
commit ccb0c8df4c
112 changed files with 1165 additions and 11644 deletions

View File

@@ -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