348 lines
10 KiB
TypeScript
348 lines
10 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* V2Biz
|
|
*
|
|
* 통합 비즈니스 컴포넌트
|
|
* - flow: 플로우/워크플로우
|
|
* - rack: 랙 구조
|
|
* - map: 맵/위치
|
|
* - numbering: 채번 규칙
|
|
* - category: 카테고리 관리
|
|
* - mapping: 데이터 매핑
|
|
* - related-buttons: 관련 데이터 버튼
|
|
*/
|
|
|
|
import React, { forwardRef } from "react";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { cn } from "@/lib/utils";
|
|
import { V2BizProps } from "@/types/v2-components";
|
|
import { GitBranch, LayoutGrid, MapPin, Hash, FolderTree, Link2, FileText, ArrowRight } from "lucide-react";
|
|
|
|
/**
|
|
* 플로우 컴포넌트 (플레이스홀더)
|
|
* 실제 구현은 기존 FlowWidget과 연동
|
|
*/
|
|
const FlowBiz = forwardRef<
|
|
HTMLDivElement,
|
|
{
|
|
config?: Record<string, unknown>;
|
|
className?: string;
|
|
}
|
|
>(({ config, className }, ref) => {
|
|
return (
|
|
<Card ref={ref} className={className}>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<GitBranch className="h-4 w-4" />
|
|
플로우
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-muted-foreground flex h-48 items-center justify-center rounded-lg border-2 border-dashed">
|
|
<div className="text-center">
|
|
<GitBranch className="mx-auto mb-2 h-8 w-8" />
|
|
<p className="text-sm">플로우 디자이너</p>
|
|
<p className="text-xs">기존 FlowWidget과 연동</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
});
|
|
FlowBiz.displayName = "FlowBiz";
|
|
|
|
/**
|
|
* 랙 구조 컴포넌트 (플레이스홀더)
|
|
* 실제 구현은 기존 RackStructure와 연동
|
|
*/
|
|
const RackBiz = forwardRef<
|
|
HTMLDivElement,
|
|
{
|
|
config?: Record<string, unknown>;
|
|
className?: string;
|
|
}
|
|
>(({ config, className }, ref) => {
|
|
return (
|
|
<Card ref={ref} className={className}>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<LayoutGrid className="h-4 w-4" />랙 구조
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-muted-foreground flex h-48 items-center justify-center rounded-lg border-2 border-dashed">
|
|
<div className="text-center">
|
|
<LayoutGrid className="mx-auto mb-2 h-8 w-8" />
|
|
<p className="text-sm">랙 구조 뷰어</p>
|
|
<p className="text-xs">기존 RackStructure와 연동</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
});
|
|
RackBiz.displayName = "RackBiz";
|
|
|
|
/**
|
|
* 맵 컴포넌트 (플레이스홀더)
|
|
*/
|
|
const MapBiz = forwardRef<
|
|
HTMLDivElement,
|
|
{
|
|
config?: Record<string, unknown>;
|
|
className?: string;
|
|
}
|
|
>(({ config, className }, ref) => {
|
|
return (
|
|
<Card ref={ref} className={className}>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<MapPin className="h-4 w-4" />
|
|
위치 맵
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-muted-foreground flex h-48 items-center justify-center rounded-lg border-2 border-dashed">
|
|
<div className="text-center">
|
|
<MapPin className="mx-auto mb-2 h-8 w-8" />
|
|
<p className="text-sm">위치 맵 뷰어</p>
|
|
<p className="text-xs">지도 라이브러리 연동 예정</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
});
|
|
MapBiz.displayName = "MapBiz";
|
|
|
|
/**
|
|
* 채번 규칙 컴포넌트 (플레이스홀더)
|
|
* 실제 구현은 기존 NumberingRuleComponent와 연동
|
|
*/
|
|
const NumberingBiz = forwardRef<
|
|
HTMLDivElement,
|
|
{
|
|
config?: Record<string, unknown>;
|
|
className?: string;
|
|
}
|
|
>(({ config, className }, ref) => {
|
|
return (
|
|
<Card ref={ref} className={className}>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<Hash className="h-4 w-4" />
|
|
채번 규칙
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-3">
|
|
<div className="bg-muted/50 flex items-center justify-between rounded-lg p-3">
|
|
<div>
|
|
<p className="text-sm font-medium">자동 채번</p>
|
|
<p className="text-muted-foreground text-xs">규칙에 따라 자동 생성</p>
|
|
</div>
|
|
<div className="bg-background rounded border px-2 py-1 font-mono text-sm">PO-2024-0001</div>
|
|
</div>
|
|
<p className="text-muted-foreground text-center text-xs">기존 NumberingRuleComponent와 연동</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
});
|
|
NumberingBiz.displayName = "NumberingBiz";
|
|
|
|
/**
|
|
* 카테고리 관리 컴포넌트 (플레이스홀더)
|
|
* 실제 구현은 기존 CategoryManager와 연동
|
|
*/
|
|
const CategoryBiz = forwardRef<
|
|
HTMLDivElement,
|
|
{
|
|
config?: Record<string, unknown>;
|
|
className?: string;
|
|
}
|
|
>(({ config, className }, ref) => {
|
|
return (
|
|
<Card ref={ref} className={className}>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<FolderTree className="h-4 w-4" />
|
|
카테고리
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-2">
|
|
<div className="bg-muted/50 rounded px-2 py-1 pl-0">
|
|
<span className="text-sm">대분류</span>
|
|
</div>
|
|
<div className="text-muted-foreground px-2 py-1 pl-4 text-sm">└ 중분류</div>
|
|
<div className="text-muted-foreground px-2 py-1 pl-8 text-sm">└ 소분류</div>
|
|
<p className="text-muted-foreground mt-3 text-center text-xs">기존 CategoryManager와 연동</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
});
|
|
CategoryBiz.displayName = "CategoryBiz";
|
|
|
|
/**
|
|
* 데이터 매핑 컴포넌트 (플레이스홀더)
|
|
*/
|
|
const MappingBiz = forwardRef<
|
|
HTMLDivElement,
|
|
{
|
|
config?: Record<string, unknown>;
|
|
className?: string;
|
|
}
|
|
>(({ config, className }, ref) => {
|
|
return (
|
|
<Card ref={ref} className={className}>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<Link2 className="h-4 w-4" />
|
|
데이터 매핑
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex items-center justify-center gap-4 p-4">
|
|
<div className="text-center">
|
|
<div className="mb-2 flex h-20 w-20 items-center justify-center rounded-lg border-2">
|
|
<FileText className="text-muted-foreground h-6 w-6" />
|
|
</div>
|
|
<p className="text-muted-foreground text-xs">소스</p>
|
|
</div>
|
|
<ArrowRight className="text-muted-foreground h-6 w-6" />
|
|
<div className="text-center">
|
|
<div className="mb-2 flex h-20 w-20 items-center justify-center rounded-lg border-2">
|
|
<FileText className="text-muted-foreground h-6 w-6" />
|
|
</div>
|
|
<p className="text-muted-foreground text-xs">대상</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
});
|
|
MappingBiz.displayName = "MappingBiz";
|
|
|
|
/**
|
|
* 관련 데이터 버튼 컴포넌트 (플레이스홀더)
|
|
*/
|
|
const RelatedButtonsBiz = forwardRef<
|
|
HTMLDivElement,
|
|
{
|
|
config?: Record<string, unknown>;
|
|
className?: string;
|
|
}
|
|
>(({ config, className }, ref) => {
|
|
const buttons = (config?.buttons as Array<{ label: string; icon?: string }>) || [
|
|
{ label: "관련 주문" },
|
|
{ label: "관련 출고" },
|
|
{ label: "이력 보기" },
|
|
];
|
|
|
|
return (
|
|
<div ref={ref} className={cn("flex flex-wrap gap-2", className)}>
|
|
{buttons.map((button, index) => (
|
|
<Button key={index} variant="outline" size="sm">
|
|
{button.label}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
);
|
|
});
|
|
RelatedButtonsBiz.displayName = "RelatedButtonsBiz";
|
|
|
|
/**
|
|
* 메인 V2Biz 컴포넌트
|
|
*/
|
|
export const V2Biz = forwardRef<HTMLDivElement, V2BizProps>((props, ref) => {
|
|
const { id, label, style, size, config: configProp } = props;
|
|
|
|
// config가 없으면 기본값 사용
|
|
const config = configProp || { type: "flow" as const };
|
|
|
|
// 타입별 비즈니스 컴포넌트 렌더링
|
|
const renderBiz = () => {
|
|
const bizConfig = config.config || {};
|
|
const bizType = config.type || "flow";
|
|
|
|
switch (bizType) {
|
|
case "flow":
|
|
return <FlowBiz config={bizConfig} />;
|
|
|
|
case "rack":
|
|
return <RackBiz config={bizConfig} />;
|
|
|
|
case "map":
|
|
return <MapBiz config={bizConfig} />;
|
|
|
|
case "numbering":
|
|
return <NumberingBiz config={bizConfig} />;
|
|
|
|
case "category":
|
|
return <CategoryBiz config={bizConfig} />;
|
|
|
|
case "mapping":
|
|
return <MappingBiz config={bizConfig} />;
|
|
|
|
case "related-buttons":
|
|
return <RelatedButtonsBiz config={bizConfig} />;
|
|
|
|
default:
|
|
return (
|
|
<div className="text-muted-foreground rounded border p-4 text-center">
|
|
알 수 없는 비즈니스 타입: {config.type}
|
|
</div>
|
|
);
|
|
}
|
|
};
|
|
|
|
const showLabel = label && style?.labelDisplay !== false;
|
|
const componentWidth = size?.width || style?.width;
|
|
const componentHeight = size?.height || style?.height;
|
|
|
|
// 라벨 높이 계산 (기본 20px, 사용자 설정에 따라 조정)
|
|
const labelFontSize = style?.labelFontSize ? parseInt(String(style.labelFontSize)) : 14;
|
|
const labelMarginBottom = style?.labelMarginBottom ? parseInt(String(style.labelMarginBottom)) : 4;
|
|
const estimatedLabelHeight = labelFontSize + labelMarginBottom + 2;
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
id={id}
|
|
className="relative"
|
|
style={{
|
|
width: componentWidth,
|
|
height: componentHeight,
|
|
}}
|
|
>
|
|
{/* 🔧 라벨을 absolute로 컴포넌트 위에 배치 */}
|
|
{showLabel && (
|
|
<Label
|
|
htmlFor={id}
|
|
style={{
|
|
position: "absolute",
|
|
top: `-${estimatedLabelHeight}px`,
|
|
left: 0,
|
|
fontSize: style?.labelFontSize || "14px",
|
|
color: style?.labelColor || "#64748b",
|
|
fontWeight: style?.labelFontWeight || "500",
|
|
}}
|
|
className="text-sm font-medium whitespace-nowrap"
|
|
>
|
|
{label}
|
|
</Label>
|
|
)}
|
|
<div className="h-full w-full">{renderBiz()}</div>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
V2Biz.displayName = "V2Biz";
|
|
|
|
export default V2Biz;
|