ERP-node/frontend/components/v2/V2Biz.tsx

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;