연쇄관계 관리

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

@@ -7,9 +7,12 @@ import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Plus, Trash2, ChevronDown, List } from "lucide-react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Plus, Trash2, List, Link2, ExternalLink } from "lucide-react";
import { WebTypeConfigPanelProps } from "@/lib/registry/types";
import { WidgetComponent, SelectTypeConfig } from "@/types/screen";
import { cascadingRelationApi, CascadingRelation } from "@/lib/api/cascadingRelation";
import Link from "next/link";
interface SelectOption {
label: string;
@@ -38,7 +41,18 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
required: config.required || false,
readonly: config.readonly || false,
emptyMessage: config.emptyMessage || "선택 가능한 옵션이 없습니다",
cascadingRelationCode: config.cascadingRelationCode,
cascadingParentField: config.cascadingParentField,
});
// 연쇄 드롭다운 설정 상태
const [cascadingEnabled, setCascadingEnabled] = useState(!!config.cascadingRelationCode);
const [selectedRelationCode, setSelectedRelationCode] = useState(config.cascadingRelationCode || "");
const [selectedParentField, setSelectedParentField] = useState(config.cascadingParentField || "");
// 연쇄 관계 목록
const [relationList, setRelationList] = useState<CascadingRelation[]>([]);
const [loadingRelations, setLoadingRelations] = useState(false);
// 새 옵션 추가용 상태
const [newOptionLabel, setNewOptionLabel] = useState("");
@@ -66,6 +80,7 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
required: currentConfig.required || false,
readonly: currentConfig.readonly || false,
emptyMessage: currentConfig.emptyMessage || "선택 가능한 옵션이 없습니다",
cascadingRelationCode: currentConfig.cascadingRelationCode,
});
// 입력 필드 로컬 상태도 동기화
@@ -73,7 +88,34 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
placeholder: currentConfig.placeholder || "",
emptyMessage: currentConfig.emptyMessage || "",
});
// 연쇄 드롭다운 설정 동기화
setCascadingEnabled(!!currentConfig.cascadingRelationCode);
setSelectedRelationCode(currentConfig.cascadingRelationCode || "");
setSelectedParentField(currentConfig.cascadingParentField || "");
}, [widget.webTypeConfig]);
// 연쇄 관계 목록 로드
useEffect(() => {
if (cascadingEnabled && relationList.length === 0) {
loadRelationList();
}
}, [cascadingEnabled]);
// 연쇄 관계 목록 로드 함수
const loadRelationList = async () => {
setLoadingRelations(true);
try {
const response = await cascadingRelationApi.getList("Y");
if (response.success && response.data) {
setRelationList(response.data);
}
} catch (error) {
console.error("연쇄 관계 목록 로드 실패:", error);
} finally {
setLoadingRelations(false);
}
};
// 설정 업데이트 핸들러
const updateConfig = (field: keyof SelectTypeConfig, value: any) => {
@@ -82,6 +124,38 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
onUpdateProperty("webTypeConfig", newConfig);
};
// 연쇄 드롭다운 활성화/비활성화
const handleCascadingToggle = (enabled: boolean) => {
setCascadingEnabled(enabled);
if (!enabled) {
// 비활성화 시 관계 코드 제거
setSelectedRelationCode("");
const newConfig = { ...localConfig, cascadingRelationCode: undefined };
setLocalConfig(newConfig);
onUpdateProperty("webTypeConfig", newConfig);
} else {
// 활성화 시 관계 목록 로드
loadRelationList();
}
};
// 연쇄 관계 선택
const handleRelationSelect = (code: string) => {
setSelectedRelationCode(code);
const newConfig = { ...localConfig, cascadingRelationCode: code || undefined };
setLocalConfig(newConfig);
onUpdateProperty("webTypeConfig", newConfig);
};
// 부모 필드 선택
const handleParentFieldChange = (field: string) => {
setSelectedParentField(field);
const newConfig = { ...localConfig, cascadingParentField: field || undefined };
setLocalConfig(newConfig);
onUpdateProperty("webTypeConfig", newConfig);
};
// 옵션 추가
const addOption = () => {
if (!newOptionLabel.trim() || !newOptionValue.trim()) return;
@@ -167,6 +241,9 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
updateConfig("options", defaultOptionSets[setName]);
};
// 선택된 관계 정보
const selectedRelation = relationList.find(r => r.relation_code === selectedRelationCode);
return (
<Card>
<CardHeader>
@@ -238,23 +315,122 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
</div>
</div>
{/* 기본 옵션 세트 */}
{/* 연쇄 드롭다운 설정 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="flex flex-wrap gap-2">
<Button size="sm" variant="outline" onClick={() => applyDefaultSet("yesno")} className="text-xs">
/
</Button>
<Button size="sm" variant="outline" onClick={() => applyDefaultSet("status")} className="text-xs">
</Button>
<Button size="sm" variant="outline" onClick={() => applyDefaultSet("priority")} className="text-xs">
</Button>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Link2 className="h-4 w-4" />
<h4 className="text-sm font-medium"> </h4>
</div>
<Switch
checked={cascadingEnabled}
onCheckedChange={handleCascadingToggle}
/>
</div>
<p className="text-muted-foreground text-xs">
. (: 창고 )
</p>
{cascadingEnabled && (
<div className="space-y-3 rounded-md border p-3">
{/* 관계 선택 */}
<div className="space-y-2">
<Label className="text-xs"> </Label>
<Select
value={selectedRelationCode}
onValueChange={handleRelationSelect}
>
<SelectTrigger className="text-xs">
<SelectValue placeholder={loadingRelations ? "로딩 중..." : "관계 선택"} />
</SelectTrigger>
<SelectContent>
{relationList.map((relation) => (
<SelectItem key={relation.relation_code} value={relation.relation_code}>
<div className="flex flex-col">
<span>{relation.relation_name}</span>
<span className="text-muted-foreground text-xs">
{relation.parent_table} {relation.child_table}
</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-muted-foreground text-xs">
.
</p>
</div>
{/* 부모 필드 설정 */}
{selectedRelationCode && (
<div className="space-y-2">
<Label className="text-xs"> ( )</Label>
<Input
value={selectedParentField}
onChange={(e) => handleParentFieldChange(e.target.value)}
placeholder="예: warehouse_code"
className="text-xs"
/>
<p className="text-muted-foreground text-xs">
.
</p>
</div>
)}
{/* 선택된 관계 정보 표시 */}
{selectedRelation && (
<div className="bg-muted/50 space-y-2 rounded-md p-2">
<div className="text-xs">
<span className="text-muted-foreground"> :</span>{" "}
<span className="font-medium">{selectedRelation.parent_table}</span>
<span className="text-muted-foreground"> ({selectedRelation.parent_value_column})</span>
</div>
<div className="text-xs">
<span className="text-muted-foreground"> :</span>{" "}
<span className="font-medium">{selectedRelation.child_table}</span>
<span className="text-muted-foreground">
{" "}({selectedRelation.child_filter_column} {selectedRelation.child_value_column})
</span>
</div>
{selectedRelation.description && (
<div className="text-muted-foreground text-xs">{selectedRelation.description}</div>
)}
</div>
)}
{/* 관계 관리 페이지 링크 */}
<div className="flex justify-end">
<Link href="/admin/cascading-relations" target="_blank">
<Button variant="link" size="sm" className="h-auto p-0 text-xs">
<ExternalLink className="mr-1 h-3 w-3" />
</Button>
</Link>
</div>
</div>
)}
</div>
{/* 옵션 관리 */}
{/* 기본 옵션 세트 (연쇄 드롭다운 비활성화 시에만 표시) */}
{!cascadingEnabled && (
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="flex flex-wrap gap-2">
<Button size="sm" variant="outline" onClick={() => applyDefaultSet("yesno")} className="text-xs">
/
</Button>
<Button size="sm" variant="outline" onClick={() => applyDefaultSet("status")} className="text-xs">
</Button>
<Button size="sm" variant="outline" onClick={() => applyDefaultSet("priority")} className="text-xs">
</Button>
</div>
</div>
)}
{/* 옵션 관리 (연쇄 드롭다운 비활성화 시에만 표시) */}
{!cascadingEnabled && (
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
@@ -337,8 +513,10 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
</div>
</div>
</div>
)}
{/* 기본값 설정 */}
{/* 기본값 설정 (연쇄 드롭다운 비활성화 시에만 표시) */}
{!cascadingEnabled && (
<div className="space-y-3">
<h4 className="text-sm font-medium"></h4>
@@ -361,6 +539,7 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
</select>
</div>
</div>
)}
{/* 상태 설정 */}
<div className="space-y-3">
@@ -395,7 +574,8 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
</div>
</div>
{/* 미리보기 */}
{/* 미리보기 (연쇄 드롭다운 비활성화 시에만 표시) */}
{!cascadingEnabled && (
<div className="space-y-3">
<h4 className="text-sm font-medium"></h4>
<div className="bg-muted/50 rounded-md border p-3">
@@ -422,11 +602,10 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
</div>
</div>
</div>
)}
</CardContent>
</Card>
);
};
SelectConfigPanel.displayName = "SelectConfigPanel";