ERP-node/frontend/components/screen/panels/FileComponentConfigPanel.tsx

339 lines
13 KiB
TypeScript

"use client";
import React, { useState, useEffect, useCallback } from "react";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { Badge } from "@/components/ui/badge";
import { FileComponent } from "@/types/screen";
import { Plus, X } from "lucide-react";
import { Button } from "@/components/ui/button";
interface FileComponentConfigPanelProps {
component: FileComponent;
onUpdateProperty: (componentId: string, path: string, value: any) => void;
}
export const FileComponentConfigPanel: React.FC<FileComponentConfigPanelProps> = ({ component, onUpdateProperty }) => {
// 로컬 상태
const [localInputs, setLocalInputs] = useState({
docType: component.fileConfig.docType || "DOCUMENT",
docTypeName: component.fileConfig.docTypeName || "일반 문서",
dragDropText: component.fileConfig.dragDropText || "파일을 드래그하거나 클릭하여 업로드하세요",
maxSize: component.fileConfig.maxSize || 10,
maxFiles: component.fileConfig.maxFiles || 5,
newAcceptType: "", // 새 파일 타입 추가용
});
const [localValues, setLocalValues] = useState({
multiple: component.fileConfig.multiple ?? true,
showPreview: component.fileConfig.showPreview ?? true,
showProgress: component.fileConfig.showProgress ?? true,
});
const [acceptTypes, setAcceptTypes] = useState<string[]>(component.fileConfig.accept || []);
// 컴포넌트 변경 시 로컬 상태 동기화
useEffect(() => {
setLocalInputs({
docType: component.fileConfig.docType || "DOCUMENT",
docTypeName: component.fileConfig.docTypeName || "일반 문서",
dragDropText: component.fileConfig.dragDropText || "파일을 드래그하거나 클릭하여 업로드하세요",
maxSize: component.fileConfig.maxSize || 10,
maxFiles: component.fileConfig.maxFiles || 5,
newAcceptType: "",
});
setLocalValues({
multiple: component.fileConfig.multiple ?? true,
showPreview: component.fileConfig.showPreview ?? true,
showProgress: component.fileConfig.showProgress ?? true,
});
setAcceptTypes(component.fileConfig.accept || []);
}, [component.fileConfig]);
// 미리 정의된 문서 타입들
const docTypeOptions = [
{ value: "CONTRACT", label: "계약서" },
{ value: "DRAWING", label: "도면" },
{ value: "PHOTO", label: "사진" },
{ value: "DOCUMENT", label: "일반 문서" },
{ value: "REPORT", label: "보고서" },
{ value: "SPECIFICATION", label: "사양서" },
{ value: "MANUAL", label: "매뉴얼" },
{ value: "CERTIFICATE", label: "인증서" },
{ value: "OTHER", label: "기타" },
];
// 미리 정의된 파일 타입들
const commonFileTypes = [
{ value: "image/*", label: "모든 이미지 파일" },
{ value: ".pdf", label: "PDF 파일" },
{ value: ".doc,.docx", label: "Word 문서" },
{ value: ".xls,.xlsx", label: "Excel 파일" },
{ value: ".ppt,.pptx", label: "PowerPoint 파일" },
{ value: ".txt", label: "텍스트 파일" },
{ value: ".zip,.rar", label: "압축 파일" },
{ value: ".dwg,.dxf", label: "CAD 파일" },
];
// 파일 타입 추가
const addAcceptType = useCallback(() => {
const newType = localInputs.newAcceptType.trim();
if (newType && !acceptTypes.includes(newType)) {
const newAcceptTypes = [...acceptTypes, newType];
setAcceptTypes(newAcceptTypes);
onUpdateProperty(component.id, "fileConfig.accept", newAcceptTypes);
setLocalInputs((prev) => ({ ...prev, newAcceptType: "" }));
}
}, [localInputs.newAcceptType, acceptTypes, component.id, onUpdateProperty]);
// 파일 타입 제거
const removeAcceptType = useCallback(
(typeToRemove: string) => {
const newAcceptTypes = acceptTypes.filter((type) => type !== typeToRemove);
setAcceptTypes(newAcceptTypes);
onUpdateProperty(component.id, "fileConfig.accept", newAcceptTypes);
},
[acceptTypes, component.id, onUpdateProperty],
);
// 미리 정의된 파일 타입 추가
const addCommonFileType = useCallback(
(fileType: string) => {
const types = fileType.split(",");
const newAcceptTypes = [...acceptTypes];
types.forEach((type) => {
if (!newAcceptTypes.includes(type.trim())) {
newAcceptTypes.push(type.trim());
}
});
setAcceptTypes(newAcceptTypes);
onUpdateProperty(component.id, "fileConfig.accept", newAcceptTypes);
},
[acceptTypes, component.id, onUpdateProperty],
);
return (
<div className="space-y-6">
{/* 문서 분류 설정 */}
<div className="space-y-4">
<h4 className="text-sm font-medium text-gray-900"> </h4>
<div className="space-y-2">
<Label htmlFor="docType"> </Label>
<Select
value={localInputs.docType}
onValueChange={(value) => {
setLocalInputs((prev) => ({ ...prev, docType: value }));
onUpdateProperty(component.id, "fileConfig.docType", value);
// 문서 타입 변경 시 자동으로 타입명도 업데이트
const selectedOption = docTypeOptions.find((opt) => opt.value === value);
if (selectedOption) {
setLocalInputs((prev) => ({ ...prev, docTypeName: selectedOption.label }));
onUpdateProperty(component.id, "fileConfig.docTypeName", selectedOption.label);
}
}}
>
<SelectTrigger>
<SelectValue placeholder="문서 타입을 선택하세요" />
</SelectTrigger>
<SelectContent>
{docTypeOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="docTypeName"> </Label>
<Input
id="docTypeName"
value={localInputs.docTypeName}
onChange={(e) => {
const newValue = e.target.value;
setLocalInputs((prev) => ({ ...prev, docTypeName: newValue }));
onUpdateProperty(component.id, "fileConfig.docTypeName", newValue);
}}
placeholder="문서 타입 표시명"
/>
</div>
</div>
{/* 파일 업로드 제한 설정 */}
<div className="space-y-4">
<h4 className="text-sm font-medium text-gray-900"> </h4>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="maxSize"> (MB)</Label>
<Input
id="maxSize"
type="number"
min="1"
max="100"
value={localInputs.maxSize}
onChange={(e) => {
const newValue = parseInt(e.target.value) || 10;
setLocalInputs((prev) => ({ ...prev, maxSize: newValue }));
onUpdateProperty(component.id, "fileConfig.maxSize", newValue);
}}
/>
</div>
<div className="space-y-2">
<Label htmlFor="maxFiles"> </Label>
<Input
id="maxFiles"
type="number"
min="1"
max="20"
value={localInputs.maxFiles}
onChange={(e) => {
const newValue = parseInt(e.target.value) || 5;
setLocalInputs((prev) => ({ ...prev, maxFiles: newValue }));
onUpdateProperty(component.id, "fileConfig.maxFiles", newValue);
}}
/>
</div>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="multiple"
checked={localValues.multiple}
onCheckedChange={(checked) => {
setLocalValues((prev) => ({ ...prev, multiple: checked as boolean }));
onUpdateProperty(component.id, "fileConfig.multiple", checked);
}}
/>
<Label htmlFor="multiple"> </Label>
</div>
</div>
{/* 허용 파일 타입 설정 */}
<div className="space-y-4">
<h4 className="text-sm font-medium text-gray-900"> </h4>
{/* 미리 정의된 파일 타입 버튼들 */}
<div className="space-y-2">
<Label> </Label>
<div className="flex flex-wrap gap-2">
{commonFileTypes.map((fileType) => (
<Button
key={fileType.value}
variant="outline"
size="sm"
onClick={() => addCommonFileType(fileType.value)}
className="text-xs"
>
<Plus className="mr-1 h-3 w-3" />
{fileType.label}
</Button>
))}
</div>
</div>
{/* 현재 설정된 파일 타입들 */}
<div className="space-y-2">
<Label> </Label>
<div className="flex flex-wrap gap-2">
{acceptTypes.map((type, index) => (
<Badge key={index} variant="secondary" className="flex items-center space-x-1">
<span>{type}</span>
<Button
variant="ghost"
size="sm"
onClick={() => removeAcceptType(type)}
className="h-4 w-4 p-0 hover:bg-transparent"
>
<X className="h-3 w-3" />
</Button>
</Badge>
))}
{acceptTypes.length === 0 && <span className="text-sm text-gray-500"> </span>}
</div>
</div>
{/* 사용자 정의 파일 타입 추가 */}
<div className="space-y-2">
<Label htmlFor="newAcceptType"> </Label>
<div className="flex space-x-2">
<Input
id="newAcceptType"
value={localInputs.newAcceptType}
onChange={(e) => {
setLocalInputs((prev) => ({ ...prev, newAcceptType: e.target.value }));
}}
placeholder="예: .dwg, image/*, .custom"
onKeyPress={(e) => {
if (e.key === "Enter") {
addAcceptType();
}
}}
/>
<Button onClick={addAcceptType} disabled={!localInputs.newAcceptType.trim()} size="sm">
</Button>
</div>
</div>
</div>
{/* UI 설정 */}
<div className="space-y-4">
<h4 className="text-sm font-medium text-gray-900">UI </h4>
<div className="space-y-2">
<Label htmlFor="dragDropText"> </Label>
<Textarea
id="dragDropText"
value={localInputs.dragDropText}
onChange={(e) => {
const newValue = e.target.value;
setLocalInputs((prev) => ({ ...prev, dragDropText: newValue }));
onUpdateProperty(component.id, "fileConfig.dragDropText", newValue);
}}
placeholder="파일을 드래그하거나 클릭하여 업로드하세요"
rows={2}
/>
</div>
<div className="space-y-3">
<div className="flex items-center space-x-2">
<Checkbox
id="showPreview"
checked={localValues.showPreview}
onCheckedChange={(checked) => {
setLocalValues((prev) => ({ ...prev, showPreview: checked as boolean }));
onUpdateProperty(component.id, "fileConfig.showPreview", checked);
}}
/>
<Label htmlFor="showPreview"> </Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="showProgress"
checked={localValues.showProgress}
onCheckedChange={(checked) => {
setLocalValues((prev) => ({ ...prev, showProgress: checked as boolean }));
onUpdateProperty(component.id, "fileConfig.showProgress", checked);
}}
/>
<Label htmlFor="showProgress"> </Label>
</div>
</div>
</div>
</div>
);
};