ERP-node/frontend/components/screen/config-panels/FileConfigPanel.tsx

401 lines
16 KiB
TypeScript

"use client";
import React, { useState, useEffect } from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Upload, File, X } from "lucide-react";
import { WebTypeConfigPanelProps } from "@/lib/registry/types";
import { WidgetComponent, FileTypeConfig } from "@/types/screen";
export const FileConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
component,
onUpdateComponent,
onUpdateProperty,
}) => {
const widget = component as WidgetComponent;
const config = (widget.webTypeConfig as FileTypeConfig) || {};
// 로컬 상태
const [localConfig, setLocalConfig] = useState<FileTypeConfig>({
multiple: config.multiple || false,
maxFileSize: config.maxFileSize || 10, // MB
maxFiles: config.maxFiles || 1,
acceptedTypes: config.acceptedTypes || [],
showPreview: config.showPreview !== false, // 기본값 true
showProgress: config.showProgress !== false, // 기본값 true
dragAndDrop: config.dragAndDrop !== false, // 기본값 true
required: config.required || false,
readonly: config.readonly || false,
uploadText: config.uploadText || "파일을 선택하거나 여기에 드래그하세요",
browseText: config.browseText || "파일 선택",
});
// 새 파일 타입 추가용 상태
const [newFileType, setNewFileType] = useState("");
// 컴포넌트 변경 시 로컬 상태 동기화
useEffect(() => {
const currentConfig = (widget.webTypeConfig as FileTypeConfig) || {};
setLocalConfig({
multiple: currentConfig.multiple || false,
maxFileSize: currentConfig.maxFileSize || 10,
maxFiles: currentConfig.maxFiles || 1,
acceptedTypes: currentConfig.acceptedTypes || [],
showPreview: currentConfig.showPreview !== false,
showProgress: currentConfig.showProgress !== false,
dragAndDrop: currentConfig.dragAndDrop !== false,
required: currentConfig.required || false,
readonly: currentConfig.readonly || false,
uploadText: currentConfig.uploadText || "파일을 선택하거나 여기에 드래그하세요",
browseText: currentConfig.browseText || "파일 선택",
});
}, [widget.webTypeConfig]);
// 설정 업데이트 핸들러
const updateConfig = (field: keyof FileTypeConfig, value: any) => {
const newConfig = { ...localConfig, [field]: value };
setLocalConfig(newConfig);
onUpdateProperty("webTypeConfig", newConfig);
};
// 파일 타입 추가
const addFileType = () => {
if (!newFileType.trim()) return;
let extension = newFileType.trim();
if (!extension.startsWith(".")) {
extension = "." + extension;
}
if (!localConfig.acceptedTypes.includes(extension)) {
const newTypes = [...localConfig.acceptedTypes, extension];
updateConfig("acceptedTypes", newTypes);
}
setNewFileType("");
};
// 파일 타입 제거
const removeFileType = (typeToRemove: string) => {
const newTypes = localConfig.acceptedTypes.filter((type) => type !== typeToRemove);
updateConfig("acceptedTypes", newTypes);
};
// 기본 파일 타입 세트
const defaultFileTypeSets = {
images: [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"],
documents: [".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".txt"],
archives: [".zip", ".rar", ".7z", ".tar", ".gz"],
media: [".mp4", ".avi", ".mov", ".wmv", ".mp3", ".wav", ".flac"],
web: [".html", ".css", ".js", ".json", ".xml"],
all: ["*"],
};
const applyFileTypeSet = (setName: keyof typeof defaultFileTypeSets) => {
updateConfig("acceptedTypes", defaultFileTypeSets[setName]);
};
// 파일 크기 단위 변환
const formatFileSize = (sizeInMB: number) => {
if (sizeInMB < 1) {
return `${(sizeInMB * 1024).toFixed(0)}KB`;
} else if (sizeInMB >= 1024) {
return `${(sizeInMB / 1024).toFixed(1)}GB`;
} else {
return `${sizeInMB}MB`;
}
};
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-xs" style={{ fontSize: "12px" }}>
<Upload className="h-4 w-4" />
</CardTitle>
<CardDescription className="text-xs"> .</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* 기본 설정 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="space-y-2">
<Label htmlFor="uploadText" className="text-xs">
</Label>
<Input
id="uploadText"
value={localConfig.uploadText || ""}
onChange={(e) => updateConfig("uploadText", e.target.value)}
placeholder="파일을 선택하거나 여기에 드래그하세요"
className="text-xs" style={{ fontSize: "12px" }}
/>
</div>
<div className="space-y-2">
<Label htmlFor="browseText" className="text-xs">
</Label>
<Input
id="browseText"
value={localConfig.browseText || ""}
onChange={(e) => updateConfig("browseText", e.target.value)}
placeholder="파일 선택"
className="text-xs" style={{ fontSize: "12px" }}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="multiple" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="multiple"
checked={localConfig.multiple || false}
onCheckedChange={(checked) => updateConfig("multiple", checked)}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="dragAndDrop" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="dragAndDrop"
checked={localConfig.dragAndDrop !== false}
onCheckedChange={(checked) => updateConfig("dragAndDrop", checked)}
/>
</div>
</div>
{/* 파일 제한 설정 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="space-y-2">
<Label htmlFor="maxFileSize" className="text-xs">
: {formatFileSize(localConfig.maxFileSize || 10)}
</Label>
<div className="flex items-center gap-2">
<Input
id="maxFileSize"
type="number"
value={localConfig.maxFileSize || 10}
onChange={(e) => updateConfig("maxFileSize", parseFloat(e.target.value))}
min={0.1}
max={1024}
step={0.1}
className="flex-1 text-xs" style={{ fontSize: "12px" }}
/>
<span className="text-muted-foreground text-xs">MB</span>
</div>
</div>
{localConfig.multiple && (
<div className="space-y-2">
<Label htmlFor="maxFiles" className="text-xs">
</Label>
<Input
id="maxFiles"
type="number"
value={localConfig.maxFiles || 1}
onChange={(e) => updateConfig("maxFiles", parseInt(e.target.value))}
min={1}
max={100}
className="text-xs" style={{ fontSize: "12px" }}
/>
</div>
)}
</div>
{/* 허용된 파일 타입 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
{/* 기본 파일 타입 세트 */}
<div className="space-y-2">
<Label className="text-xs"> </Label>
<div className="grid grid-cols-3 gap-2">
<Button size="sm" variant="outline" onClick={() => applyFileTypeSet("images")} className="text-xs">
</Button>
<Button size="sm" variant="outline" onClick={() => applyFileTypeSet("documents")} className="text-xs">
</Button>
<Button size="sm" variant="outline" onClick={() => applyFileTypeSet("archives")} className="text-xs">
</Button>
<Button size="sm" variant="outline" onClick={() => applyFileTypeSet("media")} className="text-xs">
</Button>
<Button size="sm" variant="outline" onClick={() => applyFileTypeSet("web")} className="text-xs">
</Button>
<Button size="sm" variant="outline" onClick={() => applyFileTypeSet("all")} className="text-xs">
</Button>
</div>
</div>
{/* 개별 파일 타입 추가 */}
<div className="space-y-2">
<Label className="text-xs"> </Label>
<div className="flex gap-2">
<Input
value={newFileType}
onChange={(e) => setNewFileType(e.target.value)}
placeholder=".pdf 또는 pdf"
className="flex-1 text-xs" style={{ fontSize: "12px" }}
/>
<Button size="sm" onClick={addFileType} disabled={!newFileType.trim()} className="text-xs">
</Button>
</div>
</div>
{/* 현재 허용된 타입 목록 */}
<div className="space-y-2">
<Label className="text-xs"> ({localConfig.acceptedTypes.length})</Label>
<div className="flex min-h-8 flex-wrap gap-1 rounded-md border p-2">
{localConfig.acceptedTypes.length === 0 ? (
<span className="text-muted-foreground text-xs"> </span>
) : (
localConfig.acceptedTypes.map((type, index) => (
<Badge key={index} variant="secondary" className="text-xs">
{type}
<button type="button" onClick={() => removeFileType(type)} className="hover:text-destructive ml-1">
<X className="h-3 w-3" />
</button>
</Badge>
))
)}
</div>
</div>
</div>
{/* UI 설정 */}
<div className="space-y-3">
<h4 className="text-sm font-medium">UI </h4>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="showPreview" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="showPreview"
checked={localConfig.showPreview !== false}
onCheckedChange={(checked) => updateConfig("showPreview", checked)}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="showProgress" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="showProgress"
checked={localConfig.showProgress !== false}
onCheckedChange={(checked) => updateConfig("showProgress", checked)}
/>
</div>
</div>
{/* 상태 설정 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="required" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="required"
checked={localConfig.required || false}
onCheckedChange={(checked) => updateConfig("required", checked)}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="readonly" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="readonly"
checked={localConfig.readonly || false}
onCheckedChange={(checked) => updateConfig("readonly", checked)}
/>
</div>
</div>
{/* 미리보기 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"></h4>
<div className="bg-muted/50 rounded-md border p-3">
<div
className={`space-y-2 rounded-md border-2 border-dashed p-4 text-center ${
localConfig.dragAndDrop && !localConfig.readonly
? "border-gray-300 hover:border-gray-400"
: "border-gray-200"
}`}
>
<File className="mx-auto h-8 w-8 text-gray-400" />
<p className="text-xs text-muted-foreground">{localConfig.uploadText}</p>
<Button size="sm" variant="outline" disabled={localConfig.readonly} className="text-xs">
{localConfig.browseText}
</Button>
</div>
<div className="text-muted-foreground mt-2 space-y-1 text-xs">
<div>
: {formatFileSize(localConfig.maxFileSize || 10)}
{localConfig.multiple && ` • 최대 ${localConfig.maxFiles}개 파일`}
</div>
<div>
:{" "}
{localConfig.acceptedTypes.length === 0
? "모든 파일"
: localConfig.acceptedTypes.slice(0, 3).join(", ") +
(localConfig.acceptedTypes.length > 3 ? `${localConfig.acceptedTypes.length - 3}` : "")}
</div>
<div>
{localConfig.dragAndDrop && "드래그 앤 드롭 • "}
{localConfig.showPreview && "미리보기 • "}
{localConfig.showProgress && "진행률 표시 • "}
{localConfig.required && "필수"}
</div>
</div>
</div>
</div>
</CardContent>
</Card>
);
};
FileConfigPanel.displayName = "FileConfigPanel";