lhj #282

Merged
hjlee merged 5 commits from lhj into main 2025-12-12 13:45:31 +09:00
9 changed files with 80 additions and 15 deletions

View File

@ -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: {

View File

@ -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,

View File

@ -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, // 필터링 추가

View File

@ -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,

View File

@ -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 배열 사용

View File

@ -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,

View File

@ -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>
);
}

View File

@ -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,

View File

@ -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;
// 설정 병합