ERP-node/frontend/components/report/designer/ReportDesignerRightPanel.tsx

433 lines
20 KiB
TypeScript

"use client";
import { useState } from "react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Trash2, Settings, Database, Link2 } from "lucide-react";
import { useReportDesigner } from "@/contexts/ReportDesignerContext";
import { QueryManager } from "./QueryManager";
export function ReportDesignerRightPanel() {
const context = useReportDesigner();
const { selectedComponentId, components, updateComponent, removeComponent, queries } = context;
const [activeTab, setActiveTab] = useState<string>("properties");
const selectedComponent = components.find((c) => c.id === selectedComponentId);
// 선택된 쿼리의 결과 필드 가져오기
const getQueryFields = (queryId: string): string[] => {
const result = context.getQueryResult(queryId);
return result ? result.fields : [];
};
return (
<div className="w-[450px] border-l bg-white">
<Tabs value={activeTab} onValueChange={setActiveTab} className="h-full">
<div className="border-b p-2">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="properties" className="gap-2">
<Settings className="h-4 w-4" />
</TabsTrigger>
<TabsTrigger value="queries" className="gap-2">
<Database className="h-4 w-4" />
</TabsTrigger>
</TabsList>
</div>
{/* 속성 탭 */}
<TabsContent value="properties" className="mt-0 h-[calc(100vh-120px)]">
<ScrollArea className="h-full">
<div className="space-y-4 p-4">
{!selectedComponent ? (
<div className="flex h-full items-center justify-center p-4 text-center text-sm text-gray-500">
</div>
) : (
<Card>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-sm"> </CardTitle>
<Button
variant="ghost"
size="sm"
onClick={() => removeComponent(selectedComponent.id)}
className="text-destructive hover:bg-destructive/10 h-8 w-8 p-0"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</CardHeader>
<CardContent className="space-y-3">
{/* 타입 */}
<div>
<Label className="text-xs"></Label>
<div className="mt-1 text-sm font-medium capitalize">{selectedComponent.type}</div>
</div>
{/* 위치 */}
<div className="grid grid-cols-2 gap-2">
<div>
<Label className="text-xs">X</Label>
<Input
type="number"
value={Math.round(selectedComponent.x)}
onChange={(e) =>
updateComponent(selectedComponent.id, {
x: parseInt(e.target.value) || 0,
})
}
className="h-8"
/>
</div>
<div>
<Label className="text-xs">Y</Label>
<Input
type="number"
value={Math.round(selectedComponent.y)}
onChange={(e) =>
updateComponent(selectedComponent.id, {
y: parseInt(e.target.value) || 0,
})
}
className="h-8"
/>
</div>
</div>
{/* 크기 */}
<div className="grid grid-cols-2 gap-2">
<div>
<Label className="text-xs"></Label>
<Input
type="number"
value={Math.round(selectedComponent.width)}
onChange={(e) =>
updateComponent(selectedComponent.id, {
width: parseInt(e.target.value) || 50,
})
}
className="h-8"
/>
</div>
<div>
<Label className="text-xs"></Label>
<Input
type="number"
value={Math.round(selectedComponent.height)}
onChange={(e) =>
updateComponent(selectedComponent.id, {
height: parseInt(e.target.value) || 30,
})
}
className="h-8"
/>
</div>
</div>
{/* 스타일링 섹션 */}
<div className="space-y-3 rounded-md border border-gray-200 bg-gray-50 p-3">
<h4 className="text-xs font-semibold text-gray-700"></h4>
{/* 글꼴 크기 */}
<div>
<Label className="text-xs"> </Label>
<Input
type="number"
value={selectedComponent.fontSize || 13}
onChange={(e) =>
updateComponent(selectedComponent.id, {
fontSize: parseInt(e.target.value) || 13,
})
}
className="h-8"
/>
</div>
{/* 글꼴 색상 */}
<div>
<Label className="text-xs"> </Label>
<div className="flex gap-2">
<Input
type="color"
value={selectedComponent.fontColor || "#000000"}
onChange={(e) =>
updateComponent(selectedComponent.id, {
fontColor: e.target.value,
})
}
className="h-8 w-16"
/>
<Input
type="text"
value={selectedComponent.fontColor || "#000000"}
onChange={(e) =>
updateComponent(selectedComponent.id, {
fontColor: e.target.value,
})
}
className="h-8 flex-1 font-mono text-xs"
/>
</div>
</div>
{/* 텍스트 정렬 (텍스트/라벨만) */}
{(selectedComponent.type === "text" || selectedComponent.type === "label") && (
<div>
<Label className="text-xs"> </Label>
<Select
value={selectedComponent.textAlign || "left"}
onValueChange={(value) =>
updateComponent(selectedComponent.id, {
textAlign: value as "left" | "center" | "right",
})
}
>
<SelectTrigger className="h-8">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="left"></SelectItem>
<SelectItem value="center"></SelectItem>
<SelectItem value="right"></SelectItem>
</SelectContent>
</Select>
</div>
)}
{/* 글꼴 굵기 (텍스트/라벨만) */}
{(selectedComponent.type === "text" || selectedComponent.type === "label") && (
<div>
<Label className="text-xs"> </Label>
<Select
value={selectedComponent.fontWeight || "normal"}
onValueChange={(value) =>
updateComponent(selectedComponent.id, {
fontWeight: value as "normal" | "bold",
})
}
>
<SelectTrigger className="h-8">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="normal"></SelectItem>
<SelectItem value="bold"></SelectItem>
</SelectContent>
</Select>
</div>
)}
{/* 배경 색상 */}
<div>
<Label className="text-xs"> </Label>
<div className="flex gap-2">
<Input
type="color"
value={selectedComponent.backgroundColor || "#ffffff"}
onChange={(e) =>
updateComponent(selectedComponent.id, {
backgroundColor: e.target.value,
})
}
className="h-8 w-16"
/>
<Input
type="text"
value={selectedComponent.backgroundColor || "#ffffff"}
onChange={(e) =>
updateComponent(selectedComponent.id, {
backgroundColor: e.target.value,
})
}
placeholder="transparent"
className="h-8 flex-1 font-mono text-xs"
/>
<Button
variant="outline"
size="sm"
onClick={() =>
updateComponent(selectedComponent.id, {
backgroundColor: "transparent",
})
}
className="h-8 px-2 text-xs"
>
</Button>
</div>
</div>
{/* 테두리 */}
<div className="space-y-2">
<Label className="text-xs"></Label>
<div className="grid grid-cols-2 gap-2">
<div>
<Label className="text-xs text-gray-600"></Label>
<Input
type="number"
min="0"
max="10"
value={selectedComponent.borderWidth || 0}
onChange={(e) =>
updateComponent(selectedComponent.id, {
borderWidth: parseInt(e.target.value) || 0,
})
}
className="h-8"
/>
</div>
<div>
<Label className="text-xs text-gray-600"></Label>
<Input
type="color"
value={selectedComponent.borderColor || "#cccccc"}
onChange={(e) =>
updateComponent(selectedComponent.id, {
borderColor: e.target.value,
})
}
className="h-8"
/>
</div>
</div>
</div>
</div>
{/* 데이터 바인딩 (텍스트/라벨/테이블 컴포넌트) */}
{(selectedComponent.type === "text" ||
selectedComponent.type === "label" ||
selectedComponent.type === "table") && (
<Card className="mt-4 border-blue-200 bg-blue-50">
<CardHeader className="pb-3">
<div className="flex items-center gap-2">
<Link2 className="h-4 w-4 text-blue-600" />
<CardTitle className="text-sm text-blue-900"> </CardTitle>
</div>
</CardHeader>
<CardContent className="space-y-3">
{/* 쿼리 선택 */}
<div>
<Label className="text-xs"></Label>
<Select
value={selectedComponent.queryId || "none"}
onValueChange={(value) =>
updateComponent(selectedComponent.id, {
queryId: value === "none" ? undefined : value,
fieldName: undefined,
})
}
>
<SelectTrigger className="h-8">
<SelectValue placeholder="쿼리 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"> </SelectItem>
{queries.map((query) => (
<SelectItem key={query.id} value={query.id}>
{query.name} ({query.type})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 필드 선택 (텍스트/라벨만) */}
{selectedComponent.queryId &&
(selectedComponent.type === "text" || selectedComponent.type === "label") && (
<div>
<Label className="text-xs"></Label>
<Select
value={selectedComponent.fieldName || "none"}
onValueChange={(value) =>
updateComponent(selectedComponent.id, {
fieldName: value === "none" ? undefined : value,
})
}
>
<SelectTrigger className="h-8">
<SelectValue placeholder="필드 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"> </SelectItem>
{getQueryFields(selectedComponent.queryId).length > 0 ? (
getQueryFields(selectedComponent.queryId).map((field) => (
<SelectItem key={field} value={field}>
{field}
</SelectItem>
))
) : (
<SelectItem value="no-result" disabled>
</SelectItem>
)}
</SelectContent>
</Select>
</div>
)}
{/* 테이블 안내 메시지 */}
{selectedComponent.queryId && selectedComponent.type === "table" && (
<div className="rounded-md bg-blue-100 p-2 text-xs text-blue-800">
.
</div>
)}
{/* 기본값 (텍스트/라벨만) */}
{(selectedComponent.type === "text" || selectedComponent.type === "label") && (
<div>
<Label className="text-xs"></Label>
<Input
value={selectedComponent.defaultValue || ""}
onChange={(e) =>
updateComponent(selectedComponent.id, {
defaultValue: e.target.value,
})
}
placeholder="데이터가 없을 때 표시할 값"
className="h-8"
/>
</div>
)}
{/* 포맷 (텍스트/라벨만) */}
{(selectedComponent.type === "text" || selectedComponent.type === "label") && (
<div>
<Label className="text-xs"></Label>
<Input
value={selectedComponent.format || ""}
onChange={(e) =>
updateComponent(selectedComponent.id, {
format: e.target.value,
})
}
placeholder="예: YYYY-MM-DD, #,###"
className="h-8"
/>
</div>
)}
</CardContent>
</Card>
)}
</CardContent>
</Card>
)}
</div>
</ScrollArea>
</TabsContent>
{/* 쿼리 탭 */}
<TabsContent value="queries" className="mt-0 h-[calc(100vh-120px)]">
<QueryManager />
</TabsContent>
</Tabs>
</div>
);
}