ERP-node/frontend/lib/meta-components/Modal/ModalRenderer.tsx

170 lines
4.5 KiB
TypeScript

"use client";
/**
* Modal 메타 컴포넌트 렌더러
* - 트리거: shadcn Button (config.triggerLabel)
* - 모달: shadcn Dialog
* - 모달 내용: 간단한 폼 구조 (config.content.fields → Label + Input)
* - 저장/취소 버튼 (DialogFooter)
* - 디자인 모드: 트리거 버튼만 비활성 표시
*/
import React, { useState } from "react";
import { cn } from "@/lib/utils";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
interface ModalField {
columnName: string;
label: string;
type?: "text" | "number" | "date" | "textarea";
}
interface ModalRendererProps {
id: string;
config: {
triggerLabel?: string;
title?: string;
description?: string;
content: {
fields: ModalField[];
};
size?: "sm" | "md" | "lg" | "xl";
};
isDesignMode?: boolean;
disabled?: boolean;
className?: string;
}
export function ModalRenderer({
id,
config,
isDesignMode = false,
disabled = false,
className,
}: ModalRendererProps) {
const {
triggerLabel = "모달 열기",
title = "모달",
description,
content,
size = "md",
} = config;
const [isOpen, setIsOpen] = useState(false);
const [formData, setFormData] = useState<Record<string, any>>({});
// 모달 크기 매핑
const sizeClass = {
sm: "sm:max-w-[400px]",
md: "sm:max-w-[500px]",
lg: "sm:max-w-[700px]",
xl: "sm:max-w-[900px]",
}[size];
const handleOpen = () => {
if (isDesignMode || disabled) return;
setIsOpen(true);
};
const handleClose = () => {
setIsOpen(false);
setFormData({});
};
const handleSave = () => {
// TODO: 실제 저장 로직 (API 호출)
console.log("Modal save:", formData);
handleClose();
};
const handleFieldChange = (columnName: string, value: any) => {
setFormData((prev) => ({ ...prev, [columnName]: value }));
};
return (
<>
{/* 트리거 버튼 */}
<Button
onClick={handleOpen}
disabled={isDesignMode || disabled}
className={cn("h-8 sm:h-10", className)}
>
{triggerLabel}
{isDesignMode && (
<span className="ml-2 text-xs">( )</span>
)}
</Button>
{/* 모달 */}
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent className={cn("max-w-[95vw]", sizeClass)}>
<DialogHeader>
<DialogTitle className="text-base sm:text-lg">
{title}
</DialogTitle>
{description && (
<DialogDescription className="text-xs sm:text-sm">
{description}
</DialogDescription>
)}
</DialogHeader>
{/* 폼 필드들 */}
<div className="space-y-3 sm:space-y-4">
{content.fields.map((field) => (
<div key={field.columnName}>
<Label htmlFor={field.columnName} className="text-xs sm:text-sm">
{field.label}
</Label>
{field.type === "textarea" ? (
<textarea
id={field.columnName}
value={formData[field.columnName] || ""}
onChange={(e) => handleFieldChange(field.columnName, e.target.value)}
className="mt-1 h-20 w-full rounded-md border border-input bg-background px-3 py-2 text-xs sm:text-sm"
/>
) : (
<Input
id={field.columnName}
type={field.type || "text"}
value={formData[field.columnName] || ""}
onChange={(e) => handleFieldChange(field.columnName, e.target.value)}
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
)}
</div>
))}
</div>
{/* 푸터 버튼 */}
<DialogFooter className="gap-2 sm:gap-0">
<Button
variant="outline"
onClick={handleClose}
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
>
</Button>
<Button
onClick={handleSave}
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
}