Files
vexplor/frontend/lib/registry/components/v2-file-upload/FileUploadConfigPanel.tsx
kjs ad7c5923a6 feat: 파일 정보 조회 API 추가 및 파일 업로드 컴포넌트 개선
- 파일 정보 조회를 위한 getFileInfo 함수를 추가하여, 파일의 메타데이터를 공개 접근으로 조회할 수 있도록 하였습니다.
- 파일 업로드 컴포넌트에서 파일 아이콘 매핑 및 파일 미리보기 기능을 개선하여 사용자 경험을 향상시켰습니다.
- V2 파일 업로드 컴포넌트의 설정 패널을 추가하여, 파일 업로드 관련 설정을 보다 쉽게 관리할 수 있도록 하였습니다.
- 파일 뷰어 모달을 추가하여 다양한 파일 형식의 미리보기를 지원합니다.
2026-02-05 13:45:23 +09:00

288 lines
9.8 KiB
TypeScript

"use client";
import React, { useMemo, useCallback } from "react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { FileUploadConfig } from "./types";
import { V2FileUploadDefaultConfig } from "./config";
export interface FileUploadConfigPanelProps {
config: FileUploadConfig;
onChange: (config: Partial<FileUploadConfig>) => void;
screenTableName?: string;
}
/**
* V2 FileUpload 설정 패널
* 컴포넌트의 설정값들을 편집할 수 있는 UI 제공
*/
export const FileUploadConfigPanel: React.FC<FileUploadConfigPanelProps> = ({
config: propConfig,
onChange,
screenTableName,
}) => {
// config 안전하게 초기화 (useMemo)
const config = useMemo(() => ({
...V2FileUploadDefaultConfig,
...propConfig,
}), [propConfig]);
// 핸들러
const handleChange = useCallback(<K extends keyof FileUploadConfig>(
key: K,
value: FileUploadConfig[K]
) => {
onChange({ [key]: value });
}, [onChange]);
// 파일 크기를 MB 단위로 변환
const maxSizeMB = useMemo(() => {
return (config.maxSize || 10 * 1024 * 1024) / (1024 * 1024);
}, [config.maxSize]);
const handleMaxSizeChange = useCallback((value: string) => {
const mb = parseFloat(value) || 10;
handleChange("maxSize", mb * 1024 * 1024);
}, [handleChange]);
return (
<div className="space-y-4 p-4">
<div className="text-sm font-medium text-muted-foreground">
V2
</div>
<Separator />
{/* 기본 설정 */}
<div className="space-y-3">
<Label className="text-xs font-semibold uppercase text-muted-foreground">
</Label>
<div className="space-y-2">
<Label htmlFor="placeholder" className="text-xs"></Label>
<Input
id="placeholder"
value={config.placeholder || ""}
onChange={(e) => handleChange("placeholder", e.target.value)}
placeholder="파일을 선택하세요"
className="h-8 text-xs"
/>
</div>
<div className="space-y-2">
<Label htmlFor="accept" className="text-xs"> </Label>
<Select
value={config.accept || "*/*"}
onValueChange={(value) => handleChange("accept", value)}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="파일 형식 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="*/*"> </SelectItem>
<SelectItem value="image/*"></SelectItem>
<SelectItem value=".pdf,.doc,.docx,.xls,.xlsx"></SelectItem>
<SelectItem value="image/*,.pdf"> + PDF</SelectItem>
<SelectItem value=".zip,.rar,.7z"> </SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-2 gap-2">
<div className="space-y-2">
<Label htmlFor="maxSize" className="text-xs"> (MB)</Label>
<Input
id="maxSize"
type="number"
min={1}
max={100}
value={maxSizeMB}
onChange={(e) => handleMaxSizeChange(e.target.value)}
className="h-8 text-xs"
/>
</div>
<div className="space-y-2">
<Label htmlFor="maxFiles" className="text-xs"> </Label>
<Input
id="maxFiles"
type="number"
min={1}
max={50}
value={config.maxFiles || 10}
onChange={(e) => handleChange("maxFiles", parseInt(e.target.value) || 10)}
className="h-8 text-xs"
/>
</div>
</div>
</div>
<Separator />
{/* 동작 설정 */}
<div className="space-y-3">
<Label className="text-xs font-semibold uppercase text-muted-foreground">
</Label>
<div className="flex items-center space-x-2">
<Checkbox
id="multiple"
checked={config.multiple !== false}
onCheckedChange={(checked) => handleChange("multiple", checked as boolean)}
/>
<Label htmlFor="multiple" className="text-xs"> </Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="allowDelete"
checked={config.allowDelete !== false}
onCheckedChange={(checked) => handleChange("allowDelete", checked as boolean)}
/>
<Label htmlFor="allowDelete" className="text-xs"> </Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="allowDownload"
checked={config.allowDownload !== false}
onCheckedChange={(checked) => handleChange("allowDownload", checked as boolean)}
/>
<Label htmlFor="allowDownload" className="text-xs"> </Label>
</div>
</div>
<Separator />
{/* 표시 설정 */}
<div className="space-y-3">
<Label className="text-xs font-semibold uppercase text-muted-foreground">
</Label>
<div className="flex items-center space-x-2">
<Checkbox
id="showPreview"
checked={config.showPreview !== false}
onCheckedChange={(checked) => handleChange("showPreview", checked as boolean)}
/>
<Label htmlFor="showPreview" className="text-xs"> </Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="showFileList"
checked={config.showFileList !== false}
onCheckedChange={(checked) => handleChange("showFileList", checked as boolean)}
/>
<Label htmlFor="showFileList" className="text-xs"> </Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="showFileSize"
checked={config.showFileSize !== false}
onCheckedChange={(checked) => handleChange("showFileSize", checked as boolean)}
/>
<Label htmlFor="showFileSize" className="text-xs"> </Label>
</div>
</div>
<Separator />
{/* 상태 설정 */}
<div className="space-y-3">
<Label className="text-xs font-semibold uppercase text-muted-foreground">
</Label>
<div className="flex items-center space-x-2">
<Checkbox
id="required"
checked={config.required || false}
onCheckedChange={(checked) => handleChange("required", checked as boolean)}
/>
<Label htmlFor="required" className="text-xs"> </Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="readonly"
checked={config.readonly || false}
onCheckedChange={(checked) => handleChange("readonly", checked as boolean)}
/>
<Label htmlFor="readonly" className="text-xs"> </Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="disabled"
checked={config.disabled || false}
onCheckedChange={(checked) => handleChange("disabled", checked as boolean)}
/>
<Label htmlFor="disabled" className="text-xs"></Label>
</div>
</div>
<Separator />
{/* 스타일 설정 */}
<div className="space-y-3">
<Label className="text-xs font-semibold uppercase text-muted-foreground">
</Label>
<div className="space-y-2">
<Label htmlFor="variant" className="text-xs"> </Label>
<Select
value={config.variant || "default"}
onValueChange={(value) => handleChange("variant", value as "default" | "outlined" | "filled")}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="스타일 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="default"></SelectItem>
<SelectItem value="outlined"></SelectItem>
<SelectItem value="filled"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="size" className="text-xs"></Label>
<Select
value={config.size || "md"}
onValueChange={(value) => handleChange("size", value as "sm" | "md" | "lg")}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="크기 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="sm"></SelectItem>
<SelectItem value="md"></SelectItem>
<SelectItem value="lg"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
{/* 도움말 */}
<div className="space-y-2">
<Label htmlFor="helperText" className="text-xs"></Label>
<Input
id="helperText"
value={config.helperText || ""}
onChange={(e) => handleChange("helperText", e.target.value)}
placeholder="파일 업로드에 대한 안내 문구"
className="h-8 text-xs"
/>
</div>
</div>
);
};