lhj #282
|
|
@ -32,7 +32,7 @@ export const uploadFiles = async (params: {
|
|||
files: FileList | File[];
|
||||
tableName?: string;
|
||||
fieldName?: string;
|
||||
recordId?: string;
|
||||
recordId?: string | number;
|
||||
docType?: string;
|
||||
docTypeName?: string;
|
||||
targetObjid?: string;
|
||||
|
|
@ -43,6 +43,7 @@ export const uploadFiles = async (params: {
|
|||
columnName?: string;
|
||||
isVirtualFileColumn?: boolean;
|
||||
companyCode?: string; // 🔒 멀티테넌시: 회사 코드
|
||||
isRecordMode?: boolean; // 🆕 레코드 모드 플래그
|
||||
}): Promise<FileUploadResponse> => {
|
||||
const formData = new FormData();
|
||||
|
||||
|
|
@ -55,7 +56,7 @@ export const uploadFiles = async (params: {
|
|||
// 추가 파라미터들 추가
|
||||
if (params.tableName) formData.append("tableName", params.tableName);
|
||||
if (params.fieldName) formData.append("fieldName", params.fieldName);
|
||||
if (params.recordId) formData.append("recordId", params.recordId);
|
||||
if (params.recordId) formData.append("recordId", String(params.recordId));
|
||||
if (params.docType) formData.append("docType", params.docType);
|
||||
if (params.docTypeName) formData.append("docTypeName", params.docTypeName);
|
||||
if (params.targetObjid) formData.append("targetObjid", params.targetObjid);
|
||||
|
|
@ -66,6 +67,8 @@ export const uploadFiles = async (params: {
|
|||
if (params.columnName) formData.append("columnName", params.columnName);
|
||||
if (params.isVirtualFileColumn !== undefined) formData.append("isVirtualFileColumn", params.isVirtualFileColumn.toString());
|
||||
if (params.companyCode) formData.append("companyCode", params.companyCode); // 🔒 멀티테넌시
|
||||
// 🆕 레코드 모드 플래그 추가 (백엔드에서 attachments 컬럼 자동 업데이트용)
|
||||
if (params.isRecordMode !== undefined) formData.append("isRecordMode", params.isRecordMode.toString());
|
||||
|
||||
const response = await apiClient.post("/files/upload", formData, {
|
||||
headers: {
|
||||
|
|
|
|||
|
|
@ -602,6 +602,9 @@ export const AccordionBasicComponent: React.FC<AccordionBasicComponentProps> = (
|
|||
isInModal: _isInModal,
|
||||
isPreview: _isPreview,
|
||||
originalData: _originalData,
|
||||
_originalData: __originalData,
|
||||
_initialData: __initialData,
|
||||
_groupedData: __groupedData,
|
||||
allComponents: _allComponents,
|
||||
selectedRows: _selectedRows,
|
||||
selectedRowsData: _selectedRowsData,
|
||||
|
|
|
|||
|
|
@ -984,6 +984,9 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
|||
flowSelectedStepId: _flowSelectedStepId, // 플로우 선택 스텝 ID 필터링
|
||||
onFlowRefresh: _onFlowRefresh, // 플로우 새로고침 콜백 필터링
|
||||
originalData: _originalData, // 부분 업데이트용 원본 데이터 필터링
|
||||
_originalData: __originalData, // DOM 필터링
|
||||
_initialData: __initialData, // DOM 필터링
|
||||
_groupedData: __groupedData, // DOM 필터링
|
||||
refreshKey: _refreshKey, // 필터링 추가
|
||||
isInModal: _isInModal, // 필터링 추가
|
||||
mode: _mode, // 필터링 추가
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@ export const DividerLineComponent: React.FC<DividerLineComponentProps> = ({
|
|||
isInModal: _isInModal,
|
||||
readonly: _readonly,
|
||||
originalData: _originalData,
|
||||
_originalData: __originalData,
|
||||
_initialData: __initialData,
|
||||
_groupedData: __groupedData,
|
||||
allComponents: _allComponents,
|
||||
onUpdateLayout: _onUpdateLayout,
|
||||
selectedRows: _selectedRows,
|
||||
|
|
|
|||
|
|
@ -603,10 +603,16 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
|
|||
targetObjid,
|
||||
});
|
||||
|
||||
// 🔑 레코드 모드일 때는 effectiveTableName을 우선 사용
|
||||
// formData.linkedTable이 'screen_files' 같은 기본값일 수 있으므로 레코드 모드에서는 무시
|
||||
const finalLinkedTable = effectiveIsRecordMode
|
||||
? effectiveTableName
|
||||
: (formData?.linkedTable || effectiveTableName);
|
||||
|
||||
const uploadData = {
|
||||
// 🎯 formData에서 백엔드 API 설정 가져오기
|
||||
autoLink: formData?.autoLink || true,
|
||||
linkedTable: formData?.linkedTable || effectiveTableName,
|
||||
linkedTable: finalLinkedTable,
|
||||
recordId: effectiveRecordId || `temp_${component.id}`,
|
||||
columnName: effectiveColumnName,
|
||||
isVirtualFileColumn: formData?.isVirtualFileColumn || true,
|
||||
|
|
@ -620,13 +626,27 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
|
|||
// 🆕 레코드 모드 플래그
|
||||
isRecordMode: effectiveIsRecordMode,
|
||||
};
|
||||
|
||||
console.log("📤 [FileUploadComponent] uploadData 최종:", {
|
||||
isRecordMode: effectiveIsRecordMode,
|
||||
linkedTable: finalLinkedTable,
|
||||
recordId: effectiveRecordId,
|
||||
columnName: effectiveColumnName,
|
||||
targetObjid,
|
||||
});
|
||||
|
||||
|
||||
console.log("🚀 [FileUploadComponent] uploadFiles API 호출 직전:", {
|
||||
filesCount: filesToUpload.length,
|
||||
uploadData,
|
||||
});
|
||||
|
||||
const response = await uploadFiles({
|
||||
files: filesToUpload,
|
||||
...uploadData,
|
||||
});
|
||||
|
||||
console.log("📥 [FileUploadComponent] uploadFiles API 응답:", response);
|
||||
|
||||
if (response.success) {
|
||||
// FileUploadResponse 타입에 맞게 files 배열 사용
|
||||
|
|
|
|||
|
|
@ -52,11 +52,15 @@ export function RepeatScreenModalComponent({
|
|||
config,
|
||||
className,
|
||||
groupedData: propsGroupedData, // EditModal에서 전달받는 그룹 데이터
|
||||
// DynamicComponentRenderer에서 전달되는 props (DOM 전달 방지)
|
||||
_initialData,
|
||||
_originalData: _propsOriginalData,
|
||||
_groupedData,
|
||||
...props
|
||||
}: RepeatScreenModalComponentProps) {
|
||||
}: RepeatScreenModalComponentProps & { _initialData?: any; _originalData?: any; _groupedData?: any }) {
|
||||
// props에서도 groupedData를 추출 (DynamicWebTypeRenderer에서 전달될 수 있음)
|
||||
// DynamicComponentRenderer에서는 _groupedData로 전달됨
|
||||
const groupedData = propsGroupedData || (props as any).groupedData || (props as any)._groupedData;
|
||||
const groupedData = propsGroupedData || (props as any).groupedData || _groupedData;
|
||||
const componentConfig = {
|
||||
...config,
|
||||
...component?.config,
|
||||
|
|
|
|||
|
|
@ -4065,30 +4065,54 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
}
|
||||
|
||||
// 📎 첨부파일 타입: 파일 아이콘과 개수 표시
|
||||
if (inputType === "file" || inputType === "attachment" || column.columnName === "attachments") {
|
||||
// 컬럼명이 'attachments'를 포함하거나, inputType이 file/attachment인 경우
|
||||
const isAttachmentColumn =
|
||||
inputType === "file" ||
|
||||
inputType === "attachment" ||
|
||||
column.columnName === "attachments" ||
|
||||
column.columnName?.toLowerCase().includes("attachment") ||
|
||||
column.columnName?.toLowerCase().includes("file");
|
||||
|
||||
if (isAttachmentColumn) {
|
||||
// JSONB 배열 또는 JSON 문자열 파싱
|
||||
let files: any[] = [];
|
||||
try {
|
||||
if (typeof value === "string") {
|
||||
files = JSON.parse(value);
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
const parsed = JSON.parse(value);
|
||||
files = Array.isArray(parsed) ? parsed : [];
|
||||
} else if (Array.isArray(value)) {
|
||||
files = value;
|
||||
} else if (value && typeof value === "object") {
|
||||
// 단일 객체인 경우 배열로 변환
|
||||
files = [value];
|
||||
}
|
||||
} catch {
|
||||
} catch (e) {
|
||||
// 파싱 실패 시 빈 배열
|
||||
console.warn("📎 [TableList] 첨부파일 파싱 실패:", { columnName: column.columnName, value, error: e });
|
||||
}
|
||||
|
||||
if (!files || files.length === 0) {
|
||||
return <span className="text-muted-foreground text-xs">-</span>;
|
||||
}
|
||||
|
||||
// 파일 개수와 아이콘 표시
|
||||
// 파일 이름 표시 (여러 개면 쉼표로 구분)
|
||||
const { Paperclip } = require("lucide-react");
|
||||
const fileNames = files.map((f: any) => f.realFileName || f.real_file_name || f.name || "파일").join(", ");
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1 text-sm">
|
||||
<Paperclip className="h-4 w-4 text-gray-500" />
|
||||
<span className="text-blue-600 font-medium">{files.length}</span>
|
||||
<span className="text-muted-foreground text-xs">개</span>
|
||||
<div className="flex items-center gap-1.5 text-sm max-w-full">
|
||||
<Paperclip className="h-4 w-4 text-gray-500 flex-shrink-0" />
|
||||
<span
|
||||
className="text-blue-600 truncate"
|
||||
title={fileNames}
|
||||
>
|
||||
{fileNames}
|
||||
</span>
|
||||
{files.length > 1 && (
|
||||
<span className="text-muted-foreground text-xs flex-shrink-0">
|
||||
({files.length})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -224,6 +224,9 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
|
|||
isInModal: _isInModal,
|
||||
isPreview: _isPreview,
|
||||
originalData: _originalData,
|
||||
_originalData: __originalData,
|
||||
_initialData: __initialData,
|
||||
_groupedData: __groupedData,
|
||||
allComponents: _allComponents,
|
||||
selectedRows: _selectedRows,
|
||||
selectedRowsData: _selectedRowsData,
|
||||
|
|
|
|||
|
|
@ -126,11 +126,13 @@ export function UniversalFormModalComponent({
|
|||
initialData: propInitialData,
|
||||
// DynamicComponentRenderer에서 전달되는 props (DOM 전달 방지를 위해 _ prefix 사용)
|
||||
_initialData,
|
||||
_originalData,
|
||||
_groupedData,
|
||||
onSave,
|
||||
onCancel,
|
||||
onChange,
|
||||
...restProps // 나머지 props는 DOM에 전달하지 않음
|
||||
}: UniversalFormModalComponentProps & { _initialData?: any }) {
|
||||
}: UniversalFormModalComponentProps & { _initialData?: any; _originalData?: any; _groupedData?: any }) {
|
||||
// initialData 우선순위: 직접 전달된 prop > DynamicComponentRenderer에서 전달된 prop
|
||||
const initialData = propInitialData || _initialData;
|
||||
// 설정 병합
|
||||
|
|
|
|||
Loading…
Reference in New Issue