ERP-node/frontend/lib/registry/components/v2-process-work-standard/components/WorkItemAddModal.tsx

351 lines
11 KiB
TypeScript

"use client";
import React, { useState, useEffect } from "react";
import { Plus, Trash2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { DetailTypeDefinition, WorkItem } from "../types";
interface ModalDetail {
id: string;
detail_type: string;
content: string;
is_required: string;
sort_order: number;
}
interface WorkItemAddModalProps {
open: boolean;
onClose: () => void;
onSave: (data: {
work_phase: string;
title: string;
is_required: string;
description?: string;
details?: Array<{
detail_type?: string;
content: string;
is_required: string;
sort_order: number;
}>;
}) => void;
phaseKey: string;
phaseLabel: string;
detailTypes: DetailTypeDefinition[];
editItem?: WorkItem | null;
}
export function WorkItemAddModal({
open,
onClose,
onSave,
phaseKey,
phaseLabel,
detailTypes,
editItem,
}: WorkItemAddModalProps) {
const [title, setTitle] = useState("");
const [isRequired, setIsRequired] = useState("Y");
const [description, setDescription] = useState("");
const [details, setDetails] = useState<ModalDetail[]>([]);
useEffect(() => {
if (open && editItem) {
setTitle(editItem.title || "");
setIsRequired(editItem.is_required || "Y");
setDescription(editItem.description || "");
} else if (open && !editItem) {
setTitle("");
setIsRequired("Y");
setDescription("");
setDetails([]);
}
}, [open, editItem]);
const resetForm = () => {
setTitle("");
setIsRequired("Y");
setDescription("");
setDetails([]);
};
const handleSave = () => {
if (!title.trim()) return;
onSave({
work_phase: phaseKey,
title: title.trim(),
is_required: isRequired,
description: description.trim() || undefined,
details: details
.filter((d) => d.content.trim())
.map((d, idx) => ({
detail_type: d.detail_type || undefined,
content: d.content.trim(),
is_required: d.is_required,
sort_order: idx + 1,
})),
});
resetForm();
onClose();
};
const addDetail = () => {
setDetails((prev) => [
...prev,
{
id: crypto.randomUUID(),
detail_type: detailTypes[0]?.value || "",
content: "",
is_required: "N",
sort_order: prev.length + 1,
},
]);
};
const removeDetail = (id: string) => {
setDetails((prev) => prev.filter((d) => d.id !== id));
};
const updateDetailField = (
id: string,
field: keyof ModalDetail,
value: string | number
) => {
setDetails((prev) =>
prev.map((d) => (d.id === id ? { ...d, [field]: value } : d))
);
};
return (
<Dialog
open={open}
onOpenChange={(v) => {
if (!v) {
resetForm();
onClose();
}
}}
>
<DialogContent className="max-w-[95vw] sm:max-w-[600px]">
<DialogHeader>
<DialogTitle className="text-base sm:text-lg">
{editItem ? "수정" : "추가"}
</DialogTitle>
<DialogDescription className="text-xs sm:text-sm">
{phaseLabel} {editItem ? "항목을 수정" : "새 항목을 추가"}.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
{/* 기본 정보 */}
<div className="space-y-3 rounded-lg border p-3">
<p className="text-xs font-medium text-muted-foreground">
</p>
<div className="grid grid-cols-2 gap-3">
<div>
<Label className="text-xs">
<span className="text-destructive">*</span>
</Label>
<Input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="예: 장비 점검, 품질 검사"
className="mt-1 h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div>
<Label className="text-xs"> </Label>
<Select value={isRequired} onValueChange={setIsRequired}>
<SelectTrigger className="mt-1 h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="Y" className="text-xs sm:text-sm">
</SelectItem>
<SelectItem value="N" className="text-xs sm:text-sm">
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div>
<Label className="text-xs"></Label>
<Textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="추가 설명이나 주의사항"
className="mt-1 min-h-[60px] text-xs sm:text-sm"
rows={2}
/>
</div>
</div>
{/* 상세 항목 (신규 추가 시에만) */}
{!editItem && (
<div className="space-y-2 rounded-lg border p-3">
<div className="flex items-center justify-between">
<p className="text-xs font-medium text-muted-foreground">
</p>
<Button
variant="outline"
size="sm"
className="h-6 gap-1 text-[10px]"
onClick={addDetail}
>
<Plus className="h-3 w-3" />
</Button>
</div>
{details.length > 0 ? (
<table className="w-full text-xs">
<thead>
<tr className="border-b">
<th className="w-10 py-1.5 text-center font-medium text-muted-foreground">
</th>
<th className="w-20 px-1 py-1.5 text-left font-medium text-muted-foreground">
</th>
<th className="px-1 py-1.5 text-left font-medium text-muted-foreground">
</th>
<th className="w-16 px-1 py-1.5 text-center font-medium text-muted-foreground">
</th>
<th className="w-10 py-1.5 text-center font-medium text-muted-foreground">
</th>
</tr>
</thead>
<tbody>
{details.map((detail, idx) => (
<tr key={detail.id} className="border-b">
<td className="py-1 text-center text-muted-foreground">
{idx + 1}
</td>
<td className="px-1 py-1">
<Select
value={detail.detail_type}
onValueChange={(v) =>
updateDetailField(detail.id, "detail_type", v)
}
>
<SelectTrigger className="h-7 text-[10px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
{detailTypes.map((t) => (
<SelectItem
key={t.value}
value={t.value}
className="text-xs"
>
{t.label}
</SelectItem>
))}
</SelectContent>
</Select>
</td>
<td className="px-1 py-1">
<Input
value={detail.content}
onChange={(e) =>
updateDetailField(
detail.id,
"content",
e.target.value
)
}
placeholder="상세 내용"
className="h-7 text-[10px]"
/>
</td>
<td className="px-1 py-1">
<Select
value={detail.is_required}
onValueChange={(v) =>
updateDetailField(detail.id, "is_required", v)
}
>
<SelectTrigger className="h-7 text-[10px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="Y" className="text-xs">
</SelectItem>
<SelectItem value="N" className="text-xs">
</SelectItem>
</SelectContent>
</Select>
</td>
<td className="py-1 text-center">
<Button
variant="ghost"
size="icon"
className="h-6 w-6 text-destructive hover:text-destructive"
onClick={() => removeDetail(detail.id)}
>
<Trash2 className="h-3 w-3" />
</Button>
</td>
</tr>
))}
</tbody>
</table>
) : (
<p className="py-3 text-center text-[10px] text-muted-foreground">
. &quot; &quot; .
</p>
)}
</div>
)}
</div>
<DialogFooter className="gap-2 sm:gap-0">
<Button
variant="outline"
onClick={() => {
resetForm();
onClose();
}}
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
>
</Button>
<Button
onClick={handleSave}
disabled={!title.trim()}
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}