feat: enhance TableManagementPage and ExcelUploadModal for improved functionality
- Added handling for unique and nullable column toggles in TableManagementPage, allowing for better column configuration. - Updated ExcelUploadModal to include depth and ancestors in valid options for category values, enhancing the categorization process. - Improved user feedback in ExcelUploadModal by clarifying success messages and ensuring proper handling of duplicate actions. - Refactored category value flattening logic to maintain depth and ancestor information, improving data structure for better usability. These enhancements aim to provide users with a more flexible and intuitive experience when managing table configurations and uploading Excel data.
This commit is contained in:
@@ -28,7 +28,11 @@ import {
|
||||
Zap,
|
||||
Download,
|
||||
Loader2,
|
||||
Check,
|
||||
ChevronsUpDown,
|
||||
} from "lucide-react";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
|
||||
import { importFromExcel, getExcelSheetNames, exportToExcel } from "@/lib/utils/excelExport";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { EditableSpreadsheet } from "./EditableSpreadsheet";
|
||||
@@ -51,17 +55,31 @@ interface ColumnMapping {
|
||||
targetColumn: string | null;
|
||||
}
|
||||
|
||||
interface FlatCategoryValue {
|
||||
valueCode: string;
|
||||
valueLabel: string;
|
||||
depth: number;
|
||||
ancestors: string[];
|
||||
}
|
||||
|
||||
function flattenCategoryValues(
|
||||
values: Array<{ valueCode: string; valueLabel: string; children?: any[] }>
|
||||
): Array<{ valueCode: string; valueLabel: string }> {
|
||||
const result: Array<{ valueCode: string; valueLabel: string }> = [];
|
||||
const traverse = (items: any[]) => {
|
||||
): FlatCategoryValue[] {
|
||||
const result: FlatCategoryValue[] = [];
|
||||
const traverse = (items: any[], depth: number, ancestors: string[]) => {
|
||||
for (const item of items) {
|
||||
result.push({ valueCode: item.valueCode, valueLabel: item.valueLabel });
|
||||
if (item.children?.length > 0) traverse(item.children);
|
||||
result.push({
|
||||
valueCode: item.valueCode,
|
||||
valueLabel: item.valueLabel,
|
||||
depth,
|
||||
ancestors,
|
||||
});
|
||||
if (item.children?.length > 0) {
|
||||
traverse(item.children, depth + 1, [...ancestors, item.valueLabel]);
|
||||
}
|
||||
}
|
||||
};
|
||||
traverse(values);
|
||||
traverse(values, 0, []);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -102,7 +120,7 @@ export const MultiTableExcelUploadModal: React.FC<MultiTableExcelUploadModalProp
|
||||
Record<string, Array<{
|
||||
invalidValue: string;
|
||||
replacement: string | null;
|
||||
validOptions: Array<{ code: string; label: string }>;
|
||||
validOptions: Array<{ code: string; label: string; depth: number; ancestors: string[] }>;
|
||||
rowIndices: number[];
|
||||
}>>
|
||||
>({});
|
||||
@@ -398,6 +416,8 @@ export const MultiTableExcelUploadModal: React.FC<MultiTableExcelUploadModalProp
|
||||
const options = validValues.map((v) => ({
|
||||
code: v.valueCode,
|
||||
label: v.valueLabel,
|
||||
depth: v.depth,
|
||||
ancestors: v.ancestors,
|
||||
}));
|
||||
|
||||
const key = `${catColName}|||[${level.label}] ${catDisplayName}`;
|
||||
@@ -475,8 +495,7 @@ export const MultiTableExcelUploadModal: React.FC<MultiTableExcelUploadModalProp
|
||||
setDisplayData(newData);
|
||||
setShowCategoryValidation(false);
|
||||
setCategoryMismatches({});
|
||||
toast.success("카테고리 값이 대체되었습니다.");
|
||||
setCurrentStep(3);
|
||||
toast.success("카테고리 값이 대체되었습니다. '다음'을 눌러 진행해주세요.");
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -543,7 +562,7 @@ export const MultiTableExcelUploadModal: React.FC<MultiTableExcelUploadModalProp
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<Dialog open={open} onOpenChange={(v) => { if (!showCategoryValidation) onOpenChange(v); }}>
|
||||
<DialogContent
|
||||
className="max-h-[95vh] max-w-[95vw] sm:max-w-[1200px]"
|
||||
style={{ width: "1000px", height: "700px", minWidth: "700px", minHeight: "500px", maxWidth: "1400px", maxHeight: "900px" }}
|
||||
@@ -1020,33 +1039,63 @@ export const MultiTableExcelUploadModal: React.FC<MultiTableExcelUploadModalProp
|
||||
</span>
|
||||
</div>
|
||||
<ArrowRight className="h-4 w-4 text-muted-foreground" />
|
||||
<Select
|
||||
value={item.replacement || ""}
|
||||
onValueChange={(val) => {
|
||||
setCategoryMismatches((prev) => {
|
||||
const updated = { ...prev };
|
||||
updated[key] = updated[key].map((it, i) =>
|
||||
i === idx ? { ...it, replacement: val } : it
|
||||
);
|
||||
return updated;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs sm:h-9 sm:text-sm">
|
||||
<SelectValue placeholder="대체 값 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{item.validOptions.map((opt) => (
|
||||
<SelectItem
|
||||
key={opt.code}
|
||||
value={opt.code}
|
||||
className="text-xs sm:text-sm"
|
||||
>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className="h-8 w-full justify-between text-xs sm:h-9 sm:text-sm"
|
||||
>
|
||||
<span className="truncate">
|
||||
{item.replacement
|
||||
? item.validOptions.find((o) => o.code === item.replacement)?.label || item.replacement
|
||||
: "대체 값 선택"}
|
||||
</span>
|
||||
<ChevronsUpDown className="ml-1 h-3 w-3 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[260px] p-0" align="start">
|
||||
<Command
|
||||
filter={(value, search) => {
|
||||
const opt = item.validOptions.find((o) => o.code === value);
|
||||
if (!opt) return 0;
|
||||
const s = search.toLowerCase();
|
||||
if (opt.label.toLowerCase().includes(s)) return 1;
|
||||
if (opt.ancestors.some((a) => a.toLowerCase().includes(s))) return 1;
|
||||
return 0;
|
||||
}}
|
||||
>
|
||||
<CommandInput placeholder="카테고리 검색..." className="text-xs" />
|
||||
<CommandList className="max-h-52">
|
||||
<CommandEmpty className="py-3 text-xs">찾을 수 없습니다</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{item.validOptions.map((opt) => (
|
||||
<CommandItem
|
||||
key={opt.code}
|
||||
value={opt.code}
|
||||
onSelect={(val) => {
|
||||
setCategoryMismatches((prev) => {
|
||||
const updated = { ...prev };
|
||||
updated[key] = updated[key].map((it, i) =>
|
||||
i === idx ? { ...it, replacement: val } : it
|
||||
);
|
||||
return updated;
|
||||
});
|
||||
}}
|
||||
className="text-xs sm:text-sm"
|
||||
>
|
||||
<Check className={cn("mr-2 h-3 w-3", item.replacement === opt.code ? "opacity-100" : "opacity-0")} />
|
||||
<span style={{ paddingLeft: `${opt.depth * 12}px` }}>
|
||||
{opt.depth > 0 && <span className="mr-1 text-muted-foreground">ㄴ</span>}
|
||||
{opt.label}
|
||||
</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -1065,17 +1114,6 @@ export const MultiTableExcelUploadModal: React.FC<MultiTableExcelUploadModalProp
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setShowCategoryValidation(false);
|
||||
setCategoryMismatches({});
|
||||
setCurrentStep(3);
|
||||
}}
|
||||
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
|
||||
>
|
||||
무시하고 진행
|
||||
</Button>
|
||||
<Button
|
||||
onClick={applyCategoryReplacements}
|
||||
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
|
||||
|
||||
Reference in New Issue
Block a user