feat: 파일 정보 조회 API 추가 및 파일 업로드 컴포넌트 개선
- 파일 정보 조회를 위한 getFileInfo 함수를 추가하여, 파일의 메타데이터를 공개 접근으로 조회할 수 있도록 하였습니다. - 파일 업로드 컴포넌트에서 파일 아이콘 매핑 및 파일 미리보기 기능을 개선하여 사용자 경험을 향상시켰습니다. - V2 파일 업로드 컴포넌트의 설정 패널을 추가하여, 파일 업로드 관련 설정을 보다 쉽게 관리할 수 있도록 하였습니다. - 파일 뷰어 모달을 추가하여 다양한 파일 형식의 미리보기를 지원합니다.
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
"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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user