2025-09-05 21:52:19 +09:00
|
|
|
"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";
|
2025-09-06 00:16:27 +09:00
|
|
|
import { FileComponent, TableInfo } from "@/types/screen";
|
2025-09-05 21:52:19 +09:00
|
|
|
import { Plus, X } from "lucide-react";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
|
|
|
|
|
interface FileComponentConfigPanelProps {
|
|
|
|
|
component: FileComponent;
|
|
|
|
|
onUpdateProperty: (componentId: string, path: string, value: any) => void;
|
2025-09-06 00:16:27 +09:00
|
|
|
currentTable?: TableInfo; // 현재 화면의 테이블 정보
|
|
|
|
|
currentTableName?: string; // 현재 화면의 테이블명
|
2025-09-05 21:52:19 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-06 00:16:27 +09:00
|
|
|
export const FileComponentConfigPanel: React.FC<FileComponentConfigPanelProps> = ({
|
|
|
|
|
component,
|
|
|
|
|
onUpdateProperty,
|
|
|
|
|
currentTable,
|
|
|
|
|
currentTableName,
|
|
|
|
|
}) => {
|
2025-09-05 21:52:19 +09:00
|
|
|
// 로컬 상태
|
|
|
|
|
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: "", // 새 파일 타입 추가용
|
2025-09-06 00:16:27 +09:00
|
|
|
linkedTable: component.fileConfig.linkedTable || "", // 연결 테이블
|
|
|
|
|
linkedField: component.fileConfig.linkedField || "", // 연결 필드
|
2025-09-05 21:52:19 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const [localValues, setLocalValues] = useState({
|
|
|
|
|
multiple: component.fileConfig.multiple ?? true,
|
|
|
|
|
showPreview: component.fileConfig.showPreview ?? true,
|
|
|
|
|
showProgress: component.fileConfig.showProgress ?? true,
|
2025-09-06 00:16:27 +09:00
|
|
|
autoLink: component.fileConfig.autoLink ?? false, // 자동 연결
|
2025-09-05 21:52:19 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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: "",
|
2025-09-06 00:16:27 +09:00
|
|
|
linkedTable: component.fileConfig.linkedTable || "",
|
|
|
|
|
linkedField: component.fileConfig.linkedField || "",
|
2025-09-05 21:52:19 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
setLocalValues({
|
|
|
|
|
multiple: component.fileConfig.multiple ?? true,
|
|
|
|
|
showPreview: component.fileConfig.showPreview ?? true,
|
|
|
|
|
showProgress: component.fileConfig.showProgress ?? true,
|
2025-09-06 00:16:27 +09:00
|
|
|
autoLink: component.fileConfig.autoLink ?? false,
|
2025-09-05 21:52:19 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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>
|
2025-09-06 00:16:27 +09:00
|
|
|
|
|
|
|
|
{/* 테이블 연결 설정 섹션 */}
|
|
|
|
|
<div className="mt-6 rounded-lg border bg-blue-50 p-4">
|
|
|
|
|
<h4 className="mb-3 text-sm font-semibold text-blue-900">📎 테이블 연결 설정</h4>
|
|
|
|
|
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<div className="flex items-center space-x-2">
|
|
|
|
|
<Checkbox
|
|
|
|
|
id="autoLink"
|
|
|
|
|
checked={localValues.autoLink}
|
|
|
|
|
onCheckedChange={(checked) => {
|
|
|
|
|
setLocalValues((prev) => ({ ...prev, autoLink: checked as boolean }));
|
|
|
|
|
onUpdateProperty(component.id, "fileConfig.autoLink", checked);
|
|
|
|
|
|
|
|
|
|
// 자동 연결이 활성화되면 현재 화면의 테이블 정보를 자동 설정
|
|
|
|
|
if (checked && currentTableName && currentTable) {
|
|
|
|
|
// 기본키 추정 로직 (일반적인 패턴들)
|
|
|
|
|
const primaryKeyGuesses = [
|
|
|
|
|
`${currentTableName}_id`, // table_name + "_id"
|
|
|
|
|
`${currentTableName.replace(/_/g, "")}_id`, // undercore 제거 + "_id"
|
|
|
|
|
currentTableName.endsWith("_info") || currentTableName.endsWith("_mng")
|
|
|
|
|
? currentTableName.replace(/_(info|mng)$/, "_code") // _info, _mng -> _code
|
|
|
|
|
: `${currentTableName}_code`, // table_name + "_code"
|
|
|
|
|
"id", // 단순 "id"
|
|
|
|
|
"objid", // "objid"
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// 실제 테이블 컬럼에서 기본키로 추정되는 컬럼 찾기
|
|
|
|
|
let detectedPrimaryKey = "";
|
|
|
|
|
for (const guess of primaryKeyGuesses) {
|
|
|
|
|
const foundColumn = currentTable.columns.find(
|
|
|
|
|
(col) => col.columnName.toLowerCase() === guess.toLowerCase(),
|
|
|
|
|
);
|
|
|
|
|
if (foundColumn) {
|
|
|
|
|
detectedPrimaryKey = foundColumn.columnName;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 찾지 못한 경우 첫 번째 컬럼을 기본키로 사용
|
|
|
|
|
if (!detectedPrimaryKey && currentTable.columns.length > 0) {
|
|
|
|
|
detectedPrimaryKey = currentTable.columns[0].columnName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log("🔗 자동 테이블 연결 설정:", {
|
|
|
|
|
tableName: currentTableName,
|
|
|
|
|
detectedPrimaryKey,
|
|
|
|
|
availableColumns: currentTable.columns.map((c) => c.columnName),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 자동으로 테이블명과 기본키 설정
|
|
|
|
|
setLocalInputs((prev) => ({
|
|
|
|
|
...prev,
|
|
|
|
|
linkedTable: currentTableName,
|
|
|
|
|
linkedField: detectedPrimaryKey,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
onUpdateProperty(component.id, "fileConfig.linkedTable", currentTableName);
|
|
|
|
|
onUpdateProperty(component.id, "fileConfig.linkedField", detectedPrimaryKey);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<Label htmlFor="autoLink">다른 테이블과 자동 연결</Label>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{localValues.autoLink && (
|
|
|
|
|
<>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="linkedTable">연결할 테이블명</Label>
|
|
|
|
|
<Input
|
|
|
|
|
id="linkedTable"
|
|
|
|
|
value={localInputs.linkedTable}
|
|
|
|
|
readOnly
|
|
|
|
|
className="bg-gray-50 text-gray-700"
|
|
|
|
|
placeholder="자동으로 설정됩니다"
|
|
|
|
|
/>
|
|
|
|
|
<div className="text-xs text-green-600">✅ 현재 화면의 테이블이 자동으로 설정됩니다</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="linkedField">연결할 필드명 (기본키)</Label>
|
|
|
|
|
<Input
|
|
|
|
|
id="linkedField"
|
|
|
|
|
value={localInputs.linkedField}
|
|
|
|
|
readOnly
|
|
|
|
|
className="bg-gray-50 text-gray-700"
|
|
|
|
|
placeholder="자동으로 감지됩니다"
|
|
|
|
|
/>
|
|
|
|
|
<div className="text-xs text-green-600">✅ 테이블의 기본키가 자동으로 감지됩니다</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="rounded bg-blue-100 p-2 text-xs text-blue-600">
|
|
|
|
|
💡 이 설정을 활성화하면 파일이 현재 레코드와 자동으로 연결됩니다.
|
|
|
|
|
<br />
|
|
|
|
|
{currentTableName && localInputs.linkedField ? (
|
|
|
|
|
<>
|
|
|
|
|
예: {currentTableName} 테이블의 {localInputs.linkedField}가 "값123"인 레코드에 파일을 업로드하면
|
|
|
|
|
<br />
|
|
|
|
|
target_objid가 "{currentTableName}:값123"로 설정됩니다.
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>테이블과 기본키 정보가 자동으로 설정되면 연결 예시가 표시됩니다.</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-09-05 21:52:19 +09:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|