카드 컴포넌트 중간커밋

This commit is contained in:
kjs
2025-12-01 18:39:01 +09:00
parent 617655a42a
commit fb16e224f0
8 changed files with 374 additions and 47 deletions

View File

@@ -9,11 +9,13 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { Settings, Layout, ArrowRight, Database, Loader2, Check, ChevronsUpDown } from "lucide-react";
import { Settings, Layout, ArrowRight, Database, Loader2, Check, ChevronsUpDown, Plus, Trash2, Link2 } from "lucide-react";
import { screenApi } from "@/lib/api/screen";
import { getTableColumns } from "@/lib/api/tableManagement";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import type { ParentDataMapping } from "@/contexts/SplitPanelContext";
interface ScreenSplitPanelConfigPanelProps {
config: any;
@@ -29,6 +31,10 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
const [leftOpen, setLeftOpen] = useState(false);
const [rightOpen, setRightOpen] = useState(false);
// 좌측 화면의 테이블 컬럼 목록
const [leftScreenColumns, setLeftScreenColumns] = useState<Array<{ columnName: string; columnLabel: string }>>([]);
const [isLoadingColumns, setIsLoadingColumns] = useState(false);
const [localConfig, setLocalConfig] = useState({
screenId: config.screenId || 0,
leftScreenId: config.leftScreenId || 0,
@@ -37,6 +43,7 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
resizable: config.resizable ?? true,
buttonLabel: config.buttonLabel || "데이터 전달",
buttonPosition: config.buttonPosition || "center",
parentDataMapping: config.parentDataMapping || [] as ParentDataMapping[],
...config,
});
@@ -51,10 +58,51 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
resizable: config.resizable ?? true,
buttonLabel: config.buttonLabel || "데이터 전달",
buttonPosition: config.buttonPosition || "center",
parentDataMapping: config.parentDataMapping || [],
...config,
});
}, [config]);
// 좌측 화면이 변경되면 해당 화면의 테이블 컬럼 로드
useEffect(() => {
const loadLeftScreenColumns = async () => {
if (!localConfig.leftScreenId) {
setLeftScreenColumns([]);
return;
}
try {
setIsLoadingColumns(true);
// 좌측 화면 정보 조회
const screenData = await screenApi.getScreen(localConfig.leftScreenId);
if (!screenData?.tableName) {
console.warn("좌측 화면에 테이블이 설정되지 않았습니다.");
setLeftScreenColumns([]);
return;
}
// 테이블 컬럼 조회
const columnsResponse = await getTableColumns(screenData.tableName);
if (columnsResponse.success && columnsResponse.data?.columns) {
const columns = columnsResponse.data.columns.map((col: any) => ({
columnName: col.column_name || col.columnName,
columnLabel: col.column_label || col.columnLabel || col.column_name || col.columnName,
}));
setLeftScreenColumns(columns);
console.log("📋 좌측 화면 컬럼 로드 완료:", columns.length);
}
} catch (error) {
console.error("좌측 화면 컬럼 로드 실패:", error);
setLeftScreenColumns([]);
} finally {
setIsLoadingColumns(false);
}
};
loadLeftScreenColumns();
}, [localConfig.leftScreenId]);
// 화면 목록 로드
useEffect(() => {
const loadScreens = async () => {
@@ -94,10 +142,36 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
}
};
// 부모 데이터 매핑 추가
const addParentDataMapping = () => {
const newMapping: ParentDataMapping = {
sourceColumn: "",
targetColumn: "",
};
const newMappings = [...(localConfig.parentDataMapping || []), newMapping];
updateConfig("parentDataMapping", newMappings);
};
// 부모 데이터 매핑 수정
const updateParentDataMapping = (index: number, field: keyof ParentDataMapping, value: string) => {
const newMappings = [...(localConfig.parentDataMapping || [])];
newMappings[index] = {
...newMappings[index],
[field]: value,
};
updateConfig("parentDataMapping", newMappings);
};
// 부모 데이터 매핑 삭제
const removeParentDataMapping = (index: number) => {
const newMappings = (localConfig.parentDataMapping || []).filter((_: any, i: number) => i !== index);
updateConfig("parentDataMapping", newMappings);
};
return (
<div className="space-y-4">
<Tabs defaultValue="layout" className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="layout" className="gap-2">
<Layout className="h-4 w-4" />
@@ -106,6 +180,10 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
<Database className="h-4 w-4" />
</TabsTrigger>
<TabsTrigger value="dataMapping" className="gap-2">
<Link2 className="h-4 w-4" />
</TabsTrigger>
</TabsList>
{/* 레이아웃 탭 */}
@@ -295,7 +373,7 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
<div className="rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-900 dark:bg-amber-950">
<p className="text-xs text-amber-800 dark:text-amber-200">
💡 <strong> :</strong> ,
방법: ,
"transferData" .
<br />
(), , .
@@ -306,6 +384,119 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
</CardContent>
</Card>
</TabsContent>
{/* 데이터 전달 탭 */}
<TabsContent value="dataMapping" className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="text-base"> </CardTitle>
<CardDescription className="text-xs">
, / .
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{!localConfig.leftScreenId ? (
<div className="rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-900 dark:bg-amber-950">
<p className="text-xs text-amber-800 dark:text-amber-200">
"화면 설정" .
</p>
</div>
) : isLoadingColumns ? (
<div className="flex items-center justify-center py-4">
<Loader2 className="text-muted-foreground h-5 w-5 animate-spin" />
<span className="text-muted-foreground ml-2 text-xs"> ...</span>
</div>
) : leftScreenColumns.length === 0 ? (
<div className="rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-900 dark:bg-amber-950">
<p className="text-xs text-amber-800 dark:text-amber-200">
.
</p>
</div>
) : (
<>
{/* 매핑 목록 */}
<div className="space-y-3">
{(localConfig.parentDataMapping || []).map((mapping: ParentDataMapping, index: number) => (
<div key={index} className="flex items-center gap-2 rounded-lg border bg-gray-50 p-3 dark:bg-gray-900">
<div className="flex-1 space-y-2">
<div className="flex items-center gap-2">
<div className="flex-1">
<Label className="text-xs text-gray-600"> ()</Label>
<Select
value={mapping.sourceColumn}
onValueChange={(value) => updateParentDataMapping(index, "sourceColumn", value)}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="컬럼 선택" />
</SelectTrigger>
<SelectContent>
{leftScreenColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName} className="text-xs">
{col.columnLabel} ({col.columnName})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<ArrowRight className="mt-5 h-4 w-4 text-gray-400" />
<div className="flex-1">
<Label className="text-xs text-gray-600"> ( )</Label>
<Input
value={mapping.targetColumn}
onChange={(e) => updateParentDataMapping(index, "targetColumn", e.target.value)}
placeholder="저장할 컬럼명"
className="h-8 text-xs"
/>
</div>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => removeParentDataMapping(index)}
className="h-8 w-8 p-0 text-red-500 hover:bg-red-50 hover:text-red-600"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
))}
</div>
{/* 매핑 추가 버튼 */}
<Button
variant="outline"
size="sm"
onClick={addParentDataMapping}
className="w-full"
>
<Plus className="mr-2 h-4 w-4" />
</Button>
{/* 안내 메시지 */}
<div className="rounded-lg border border-blue-200 bg-blue-50 p-3 dark:border-blue-900 dark:bg-blue-950">
<p className="text-xs text-blue-800 dark:text-blue-200">
<strong> :</strong>
<br />
좌측: 설비 (equipment_mng)
<br />
우측: 점검항목
<br />
<br />
:
<br />
- 소스: equipment_code 타겟: equipment_code
<br />
<br />
,
equipment_code가 .
</p>
</div>
</>
)}
</CardContent>
</Card>
</TabsContent>
</Tabs>
{/* 설정 요약 */}
@@ -343,6 +534,14 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
<span className="text-muted-foreground"> :</span>
<span className="font-medium">{localConfig.resizable ? "가능" : "불가능"}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground"> :</span>
<span className="font-medium">
{(localConfig.parentDataMapping || []).length > 0
? `${localConfig.parentDataMapping.length}개 설정`
: "미설정"}
</span>
</div>
</div>
</CardContent>
</Card>