175 lines
6.9 KiB
TypeScript
175 lines
6.9 KiB
TypeScript
"use client";
|
|
|
|
import React, { useMemo } from "react";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import { Layers, Trash2, ArrowRight, ArrowDown, Info } from "lucide-react";
|
|
import { ComponentData } from "@/types/screen";
|
|
import { findAllButtonGroups, getButtonGroupInfo, ButtonGroupInfo } from "@/lib/utils/flowButtonGroupUtils";
|
|
|
|
interface FlowButtonGroupPanelProps {
|
|
components: ComponentData[];
|
|
onSelectGroup: (buttonIds: string[]) => void;
|
|
onDeleteGroup: (groupId: string) => void;
|
|
}
|
|
|
|
/**
|
|
* FlowButtonGroupPanel
|
|
*
|
|
* 화면의 모든 플로우 버튼 그룹을 관리하는 패널
|
|
* - 그룹 목록 표시
|
|
* - 그룹 선택 (해당 그룹의 버튼들 선택)
|
|
* - 그룹 삭제
|
|
*/
|
|
export const FlowButtonGroupPanel: React.FC<FlowButtonGroupPanelProps> = ({
|
|
components,
|
|
onSelectGroup,
|
|
onDeleteGroup,
|
|
}) => {
|
|
// 모든 버튼 그룹 찾기
|
|
const buttonGroups = useMemo(() => findAllButtonGroups(components), [components]);
|
|
|
|
// 그룹 정보 배열
|
|
const groupInfos = useMemo(() => {
|
|
return Object.entries(buttonGroups)
|
|
.map(([groupId, buttons]) => getButtonGroupInfo(groupId, buttons))
|
|
.filter((info): info is ButtonGroupInfo => info !== null);
|
|
}, [buttonGroups]);
|
|
|
|
// 그룹이 없을 때
|
|
if (groupInfos.length === 0) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<Layers className="h-5 w-5" />
|
|
플로우 버튼 그룹
|
|
</CardTitle>
|
|
<CardDescription className="text-xs">화면에 생성된 플로우 버튼 그룹이 없습니다</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Alert>
|
|
<Info className="h-4 w-4" />
|
|
<AlertDescription className="text-xs">
|
|
<p className="mb-2">플로우 버튼 그룹을 만들려면:</p>
|
|
<ol className="ml-4 list-decimal space-y-1 text-[11px]">
|
|
<li>2개 이상의 버튼을 선택하세요 (Shift + 클릭)</li>
|
|
<li>우측 하단의 "플로우 그룹 생성" 버튼을 클릭하세요</li>
|
|
<li>같은 그룹의 버튼들이 자동으로 정렬됩니다</li>
|
|
</ol>
|
|
</AlertDescription>
|
|
</Alert>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<Layers className="h-5 w-5" />
|
|
플로우 버튼 그룹
|
|
<Badge variant="secondary" className="ml-auto text-xs">
|
|
{groupInfos.length}개
|
|
</Badge>
|
|
</CardTitle>
|
|
<CardDescription className="text-xs">화면의 플로우 버튼 그룹 관리</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
{groupInfos.map((groupInfo, index) => (
|
|
<div key={groupInfo.groupId}>
|
|
<div className="rounded-lg border border-gray-200 bg-gray-50 p-3 space-y-2">
|
|
{/* 그룹 헤더 */}
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<Badge variant="outline" className="text-xs font-mono">
|
|
그룹 #{index + 1}
|
|
</Badge>
|
|
<span className="text-xs text-muted-foreground">
|
|
{groupInfo.buttonCount}개 버튼
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-1">
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={() => onSelectGroup(groupInfo.buttons.map((b) => b.id))}
|
|
className="h-7 px-2 text-xs" style={{ fontSize: "12px" }}
|
|
>
|
|
선택
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={() => onDeleteGroup(groupInfo.groupId)}
|
|
className="h-7 px-2 text-xs text-red-600 hover:bg-red-50 hover:text-red-700"
|
|
>
|
|
<Trash2 className="h-3 w-3" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 그룹 설정 정보 */}
|
|
<div className="flex flex-wrap items-center gap-2 text-[11px]">
|
|
<div className="flex items-center gap-1">
|
|
{groupInfo.direction === "horizontal" ? (
|
|
<ArrowRight className="h-3 w-3 text-blue-600" />
|
|
) : (
|
|
<ArrowDown className="h-3 w-3 text-blue-600" />
|
|
)}
|
|
<span className="text-gray-600">
|
|
{groupInfo.direction === "horizontal" ? "가로" : "세로"}
|
|
</span>
|
|
</div>
|
|
<span className="text-gray-400">•</span>
|
|
<span className="text-gray-600">간격 {groupInfo.gap}px</span>
|
|
<span className="text-gray-400">•</span>
|
|
<span className="text-gray-600">
|
|
{groupInfo.align === "start" && "시작점"}
|
|
{groupInfo.align === "center" && "중앙"}
|
|
{groupInfo.align === "end" && "끝점"}
|
|
{groupInfo.align === "space-between" && "양끝"}
|
|
{groupInfo.align === "space-around" && "균등배분"}
|
|
</span>
|
|
</div>
|
|
|
|
{/* 그룹 ID (디버깅용) */}
|
|
<details className="text-[10px]">
|
|
<summary className="cursor-pointer text-gray-500 hover:text-gray-700">
|
|
그룹 ID 보기
|
|
</summary>
|
|
<code className="mt-1 block rounded bg-gray-200 px-2 py-1 text-[9px]">
|
|
{groupInfo.groupId}
|
|
</code>
|
|
</details>
|
|
|
|
{/* 버튼 목록 */}
|
|
<div className="mt-2 space-y-1">
|
|
{groupInfo.buttons.map((button) => (
|
|
<div
|
|
key={button.id}
|
|
className="flex items-center gap-2 rounded bg-white px-2 py-1.5 text-xs" style={{ fontSize: "12px" }}
|
|
>
|
|
<div className="h-2 w-2 rounded-full bg-blue-500" />
|
|
<span className="flex-1 truncate font-medium">
|
|
{button.label || button.text || "버튼"}
|
|
</span>
|
|
<code className="text-[10px] text-gray-400">{button.id.slice(-8)}</code>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{index < groupInfos.length - 1 && <Separator className="my-3" />}
|
|
</div>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
};
|
|
|