연쇄관계 관리

This commit is contained in:
kjs
2025-12-10 13:53:44 +09:00
parent ba817980f0
commit c71b958a05
20 changed files with 3313 additions and 35 deletions

View File

@@ -19,11 +19,13 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { ChevronDown, ChevronUp, Plus, Trash2, RefreshCw } from "lucide-react";
import { ChevronDown, ChevronUp, Plus, Trash2, RefreshCw, Loader2 } from "lucide-react";
import { toast } from "sonner";
import { cn } from "@/lib/utils";
import { apiClient } from "@/lib/api/client";
import { generateNumberingCode } from "@/lib/api/numberingRule";
import { useCascadingDropdown } from "@/hooks/useCascadingDropdown";
import { CascadingDropdownConfig } from "@/types/screen-management";
import {
UniversalFormModalComponentProps,
@@ -36,6 +38,83 @@ import {
} from "./types";
import { defaultConfig, generateUniqueId } from "./config";
/**
* 🔗 연쇄 드롭다운 Select 필드 컴포넌트
*/
interface CascadingSelectFieldProps {
fieldId: string;
config: CascadingDropdownConfig;
parentValue?: string | number | null;
value?: string;
onChange: (value: string) => void;
placeholder?: string;
disabled?: boolean;
}
const CascadingSelectField: React.FC<CascadingSelectFieldProps> = ({
fieldId,
config,
parentValue,
value,
onChange,
placeholder,
disabled,
}) => {
const { options, loading } = useCascadingDropdown({
config,
parentValue,
});
const getPlaceholder = () => {
if (!parentValue) {
return config.emptyParentMessage || "상위 항목을 먼저 선택하세요";
}
if (loading) {
return config.loadingMessage || "로딩 중...";
}
if (options.length === 0) {
return config.noOptionsMessage || "선택 가능한 항목이 없습니다";
}
return placeholder || "선택하세요";
};
const isDisabled = disabled || !parentValue || loading;
return (
<Select
value={value || ""}
onValueChange={onChange}
disabled={isDisabled}
>
<SelectTrigger id={fieldId} className="w-full">
{loading ? (
<div className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
<span className="text-muted-foreground text-sm"> ...</span>
</div>
) : (
<SelectValue placeholder={getPlaceholder()} />
)}
</SelectTrigger>
<SelectContent>
{options.length === 0 ? (
<div className="text-muted-foreground px-2 py-4 text-center text-sm">
{!parentValue
? config.emptyParentMessage || "상위 항목을 먼저 선택하세요"
: config.noOptionsMessage || "선택 가능한 항목이 없습니다"}
</div>
) : (
options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))
)}
</SelectContent>
</Select>
);
};
/**
* 범용 폼 모달 컴포넌트
*
@@ -837,6 +916,24 @@ export function UniversalFormModalComponent({
);
case "select": {
// 🆕 연쇄 드롭다운 처리
if (field.cascading?.enabled) {
const cascadingConfig = field.cascading;
const parentValue = formData[cascadingConfig.parentField];
return (
<CascadingSelectField
fieldId={fieldKey}
config={cascadingConfig as CascadingDropdownConfig}
parentValue={parentValue}
value={value}
onChange={onChangeHandler}
placeholder={field.placeholder || "선택하세요"}
disabled={isDisabled}
/>
);
}
// 다중 컬럼 저장이 활성화된 경우
const lfgMappings = field.linkedFieldGroup?.mappings;
if (field.linkedFieldGroup?.enabled && field.linkedFieldGroup?.sourceTable && lfgMappings && lfgMappings.length > 0) {