170 lines
4.5 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|