Merge branch 'feature/v2-unified-renewal' of http://39.117.244.52:3000/kjs/ERP-node into gbpark-node
; Please enter a commit message to explain why this merge is necessary, ; especially if it merges an updated upstream into a topic branch. ; ; Lines starting with ';' will be ignored, and an empty message aborts ; the commit.
This commit is contained in:
commit
0ac2d78ad3
|
|
@ -431,7 +431,7 @@ export const deleteFile = async (
|
|||
// 파일 정보 조회
|
||||
const fileRecord = await queryOne<any>(
|
||||
`SELECT * FROM attach_file_info WHERE objid = $1`,
|
||||
[parseInt(objid)]
|
||||
[objid]
|
||||
);
|
||||
|
||||
if (!fileRecord) {
|
||||
|
|
@ -460,7 +460,7 @@ export const deleteFile = async (
|
|||
// 파일 상태를 DELETED로 변경 (논리적 삭제)
|
||||
await query<any>(
|
||||
"UPDATE attach_file_info SET status = $1 WHERE objid = $2",
|
||||
["DELETED", parseInt(objid)]
|
||||
["DELETED", objid]
|
||||
);
|
||||
|
||||
// 🆕 레코드 모드: 해당 행의 attachments 컬럼 자동 업데이트
|
||||
|
|
@ -708,6 +708,40 @@ export const getComponentFiles = async (
|
|||
);
|
||||
}
|
||||
|
||||
// 3. 레코드의 컬럼 값으로 파일 직접 조회 (수정 모달에서 기존 파일 로드)
|
||||
// target_objid 매칭이 안 될 때, 테이블 레코드의 컬럼 값(파일 objid)으로 직접 찾기
|
||||
if (dataFiles.length === 0 && templateFiles.length === 0 && tableName && recordId && columnName) {
|
||||
try {
|
||||
// 레코드에서 해당 컬럼 값 조회 (파일 objid가 저장되어 있을 수 있음)
|
||||
const safeTable = String(tableName).replace(/[^a-zA-Z0-9_]/g, "");
|
||||
const safeColumn = String(columnName).replace(/[^a-zA-Z0-9_]/g, "");
|
||||
const recordResult = await query<any>(
|
||||
`SELECT "${safeColumn}" FROM "${safeTable}" WHERE id = $1 LIMIT 1`,
|
||||
[recordId]
|
||||
);
|
||||
|
||||
if (recordResult.length > 0 && recordResult[0][safeColumn]) {
|
||||
const columnValue = String(recordResult[0][safeColumn]);
|
||||
// 숫자값인 경우 파일 objid로 간주하고 조회
|
||||
if (/^\d+$/.test(columnValue)) {
|
||||
console.log("🔍 [getComponentFiles] 레코드 컬럼 값으로 파일 조회:", { table: safeTable, column: safeColumn, fileObjid: columnValue });
|
||||
const directFiles = await query<any>(
|
||||
`SELECT * FROM attach_file_info
|
||||
WHERE objid = $1 AND status = $2
|
||||
ORDER BY regdate DESC`,
|
||||
[columnValue, "ACTIVE"]
|
||||
);
|
||||
if (directFiles.length > 0) {
|
||||
console.log("✅ [getComponentFiles] 레코드 컬럼 값으로 파일 찾음:", directFiles.length, "건");
|
||||
dataFiles = directFiles;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (lookupError) {
|
||||
console.warn("⚠️ [getComponentFiles] 레코드 컬럼 값 조회 실패:", lookupError);
|
||||
}
|
||||
}
|
||||
|
||||
// 파일 정보 포맷팅 함수
|
||||
const formatFileInfo = (file: any, isTemplate: boolean = false) => ({
|
||||
objid: file.objid.toString(),
|
||||
|
|
@ -782,7 +816,7 @@ export const previewFile = async (
|
|||
|
||||
const fileRecord = await queryOne<any>(
|
||||
"SELECT * FROM attach_file_info WHERE objid = $1 LIMIT 1",
|
||||
[parseInt(objid)]
|
||||
[objid]
|
||||
);
|
||||
|
||||
if (!fileRecord || fileRecord.status !== "ACTIVE") {
|
||||
|
|
@ -921,7 +955,7 @@ export const downloadFile = async (
|
|||
|
||||
const fileRecord = await queryOne<any>(
|
||||
`SELECT * FROM attach_file_info WHERE objid = $1`,
|
||||
[parseInt(objid)]
|
||||
[objid]
|
||||
);
|
||||
|
||||
if (!fileRecord || fileRecord.status !== "ACTIVE") {
|
||||
|
|
@ -1212,7 +1246,7 @@ export const setRepresentativeFile = async (
|
|||
// 파일 존재 여부 및 권한 확인
|
||||
const fileRecord = await queryOne<any>(
|
||||
`SELECT * FROM attach_file_info WHERE objid = $1 AND status = $2`,
|
||||
[parseInt(objid), "ACTIVE"]
|
||||
[objid, "ACTIVE"]
|
||||
);
|
||||
|
||||
if (!fileRecord) {
|
||||
|
|
@ -1237,7 +1271,7 @@ export const setRepresentativeFile = async (
|
|||
`UPDATE attach_file_info
|
||||
SET is_representative = false
|
||||
WHERE target_objid = $1 AND objid != $2`,
|
||||
[fileRecord.target_objid, parseInt(objid)]
|
||||
[fileRecord.target_objid, objid]
|
||||
);
|
||||
|
||||
// 선택한 파일을 대표 파일로 설정
|
||||
|
|
@ -1245,7 +1279,7 @@ export const setRepresentativeFile = async (
|
|||
`UPDATE attach_file_info
|
||||
SET is_representative = true
|
||||
WHERE objid = $1`,
|
||||
[parseInt(objid)]
|
||||
[objid]
|
||||
);
|
||||
|
||||
res.json({
|
||||
|
|
@ -1281,7 +1315,7 @@ export const getFileInfo = async (req: Request, res: Response) => {
|
|||
`SELECT objid, real_file_name, file_size, file_ext, file_path, regdate, is_representative
|
||||
FROM attach_file_info
|
||||
WHERE objid = $1 AND status = 'ACTIVE'`,
|
||||
[parseInt(objid)]
|
||||
[objid]
|
||||
);
|
||||
|
||||
if (!fileRecord) {
|
||||
|
|
|
|||
|
|
@ -2266,6 +2266,9 @@ export class TableManagementService {
|
|||
? `WHERE ${whereConditions.join(" AND ")}`
|
||||
: "";
|
||||
|
||||
// 안전한 테이블명 검증
|
||||
const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, "");
|
||||
|
||||
// ORDER BY 조건 구성
|
||||
let orderClause = "";
|
||||
if (sortBy) {
|
||||
|
|
@ -2273,11 +2276,17 @@ export class TableManagementService {
|
|||
const safeSortOrder =
|
||||
sortOrder.toLowerCase() === "desc" ? "DESC" : "ASC";
|
||||
orderClause = `ORDER BY ${safeSortBy} ${safeSortOrder}`;
|
||||
} else {
|
||||
// sortBy가 없으면 created_date 컬럼이 있는 경우에만 기본 정렬 적용
|
||||
const hasCreatedDate = await query<any>(
|
||||
`SELECT 1 FROM information_schema.columns WHERE table_name = $1 AND column_name = 'created_date' LIMIT 1`,
|
||||
[safeTableName]
|
||||
);
|
||||
if (hasCreatedDate.length > 0) {
|
||||
orderClause = `ORDER BY main.created_date DESC`;
|
||||
}
|
||||
}
|
||||
|
||||
// 안전한 테이블명 검증
|
||||
const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, "");
|
||||
|
||||
// 전체 개수 조회 (main 별칭 추가 - buildWhereClause가 main. 접두사를 사용하므로 필요)
|
||||
const countQuery = `SELECT COUNT(*) as count FROM ${safeTableName} main ${whereClause}`;
|
||||
const countResult = await query<any>(countQuery, searchValues);
|
||||
|
|
@ -3185,9 +3194,13 @@ export class TableManagementService {
|
|||
}
|
||||
|
||||
// ORDER BY 절 구성
|
||||
// sortBy가 없으면 created_date 컬럼이 있는 경우에만 기본 정렬 적용
|
||||
const hasCreatedDateColumn = selectColumns.includes("created_date");
|
||||
const orderBy = options.sortBy
|
||||
? `main.${options.sortBy} ${options.sortOrder === "desc" ? "DESC" : "ASC"}`
|
||||
: "";
|
||||
? `main."${options.sortBy}" ${options.sortOrder === "desc" ? "DESC" : "ASC"}`
|
||||
: hasCreatedDateColumn
|
||||
? `main."created_date" DESC`
|
||||
: "";
|
||||
|
||||
// 페이징 계산
|
||||
const offset = (options.page - 1) * options.size;
|
||||
|
|
@ -3397,14 +3410,17 @@ export class TableManagementService {
|
|||
const entitySearchColumns: string[] = [];
|
||||
|
||||
// Entity 조인 쿼리 생성하여 별칭 매핑 얻기
|
||||
const hasCreatedDateForSearch = selectColumns.includes("created_date");
|
||||
const joinQueryResult = entityJoinService.buildJoinQuery(
|
||||
tableName,
|
||||
joinConfigs,
|
||||
selectColumns,
|
||||
"", // WHERE 절은 나중에 추가
|
||||
options.sortBy
|
||||
? `main.${options.sortBy} ${options.sortOrder || "ASC"}`
|
||||
: undefined,
|
||||
? `main."${options.sortBy}" ${options.sortOrder || "ASC"}`
|
||||
: hasCreatedDateForSearch
|
||||
? `main."created_date" DESC`
|
||||
: undefined,
|
||||
options.size,
|
||||
(options.page - 1) * options.size
|
||||
);
|
||||
|
|
@ -3590,9 +3606,12 @@ export class TableManagementService {
|
|||
}
|
||||
|
||||
const whereClause = whereConditions.join(" AND ");
|
||||
const hasCreatedDateForOrder = selectColumns.includes("created_date");
|
||||
const orderBy = options.sortBy
|
||||
? `main.${options.sortBy} ${options.sortOrder === "desc" ? "DESC" : "ASC"}`
|
||||
: "";
|
||||
? `main."${options.sortBy}" ${options.sortOrder === "desc" ? "DESC" : "ASC"}`
|
||||
: hasCreatedDateForOrder
|
||||
? `main."created_date" DESC`
|
||||
: "";
|
||||
|
||||
// 페이징 계산
|
||||
const offset = (options.page - 1) * options.size;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,214 @@
|
|||
# 이미지/파일 저장 방식 가이드
|
||||
|
||||
## 개요
|
||||
|
||||
WACE 솔루션에서 이미지 및 파일은 **attach_file_info 테이블**에 메타데이터를 저장하고, 실제 파일은 **서버 디스크**에 저장하는 이중 구조를 사용합니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 데이터 흐름
|
||||
|
||||
```
|
||||
[사용자 업로드] → [백엔드 API] → [디스크 저장] + [DB 메타데이터 저장]
|
||||
↓ ↓
|
||||
/uploads/COMPANY_7/ attach_file_info 테이블
|
||||
2026/02/06/ (objid, file_path, ...)
|
||||
1770346704685_5.png
|
||||
```
|
||||
|
||||
### 저장 과정
|
||||
|
||||
1. 사용자가 파일 업로드 → `POST /api/files/upload`
|
||||
2. 백엔드가 파일을 디스크에 저장: `/uploads/{company_code}/{YYYY}/{MM}/{DD}/{timestamp}_{filename}`
|
||||
3. `attach_file_info` 테이블에 메타데이터 INSERT (objid, file_path, target_objid 등)
|
||||
4. 비즈니스 테이블의 이미지 컬럼에 **파일 objid** 저장 (예: `item_info.image = '433765011963536400'`)
|
||||
|
||||
### 조회 과정
|
||||
|
||||
1. 비즈니스 테이블에서 이미지 컬럼 값(objid) 로드
|
||||
2. `GET /api/files/preview/{objid}` 로 이미지 프리뷰 요청
|
||||
3. 백엔드가 `attach_file_info`에서 objid로 파일 정보 조회
|
||||
4. 디스크에서 실제 파일을 읽어 응답
|
||||
|
||||
---
|
||||
|
||||
## 2. 테이블 구조
|
||||
|
||||
### attach_file_info (파일 메타데이터)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| objid | numeric | 파일 고유 ID (PK, 큰 숫자) |
|
||||
| real_file_name | varchar | 원본 파일명 |
|
||||
| saved_file_name | varchar | 저장된 파일명 (timestamp_원본명) |
|
||||
| file_path | varchar | 저장 경로 (/uploads/COMPANY_7/2026/02/06/...) |
|
||||
| file_ext | varchar | 파일 확장자 |
|
||||
| file_size | numeric | 파일 크기 (bytes) |
|
||||
| target_objid | varchar | 연결 대상 (아래 패턴 참조) |
|
||||
| company_code | varchar | 회사 코드 (멀티테넌시) |
|
||||
| status | varchar | 상태 (ACTIVE, DELETED) |
|
||||
| writer | varchar | 업로더 ID |
|
||||
| regdate | timestamp | 등록일시 |
|
||||
| is_representative | boolean | 대표 이미지 여부 |
|
||||
|
||||
### 비즈니스 테이블 (예: item_info, company_mng)
|
||||
|
||||
이미지 컬럼에 `attach_file_info.objid` 값을 문자열로 저장합니다.
|
||||
|
||||
```sql
|
||||
-- item_info.image = '433765011963536400'
|
||||
-- company_mng.company_image = '413276787660035200'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. target_objid 패턴
|
||||
|
||||
`attach_file_info.target_objid`는 파일이 어디에 연결되어 있는지를 나타냅니다.
|
||||
|
||||
| 패턴 | 예시 | 설명 |
|
||||
|------|------|------|
|
||||
| 템플릿 모드 | `screen_files:140:comp_z4yffowb:image` | 화면 설계 시 업로드 (screenId:componentId:columnName) |
|
||||
| 레코드 모드 | `item_info:uuid-xxx:image` | 특정 레코드에 연결 (tableName:recordId:columnName) |
|
||||
|
||||
---
|
||||
|
||||
## 4. 파일 조회 API
|
||||
|
||||
### GET /api/files/preview/{objid}
|
||||
|
||||
이미지 프리뷰 (공개 접근 허용).
|
||||
|
||||
```
|
||||
GET /api/files/preview/433765011963536400
|
||||
→ 200 OK (이미지 바이너리)
|
||||
```
|
||||
|
||||
**주의: objid를 parseInt()로 변환하면 안 됩니다.** JavaScript의 `Number.MAX_SAFE_INTEGER`(9007199254740991)를 초과하는 큰 숫자이므로 **정밀도 손실**이 발생합니다. 반드시 **문자열**로 전달해야 합니다.
|
||||
|
||||
```typescript
|
||||
// 잘못된 방법
|
||||
const fileRecord = await query("SELECT * FROM attach_file_info WHERE objid = $1", [parseInt(objid)]);
|
||||
// → parseInt("433765011963536400") = 433765011963536416 (16 차이!)
|
||||
// → DB에서 찾을 수 없음 → 404
|
||||
|
||||
// 올바른 방법
|
||||
const fileRecord = await query("SELECT * FROM attach_file_info WHERE objid = $1", [objid]);
|
||||
// → PostgreSQL이 문자열 → numeric 자동 캐스팅
|
||||
```
|
||||
|
||||
### GET /api/files/component-files
|
||||
|
||||
컴포넌트별 파일 목록 조회 (인증 필요).
|
||||
|
||||
```
|
||||
GET /api/files/component-files?screenId=149&componentId=comp_z4yffowb&tableName=item_info&recordId=uuid-xxx&columnName=image
|
||||
```
|
||||
|
||||
**조회 우선순위:**
|
||||
1. **데이터 파일**: `target_objid = '{tableName}:{recordId}:{columnName}'` 패턴으로 조회
|
||||
2. **템플릿 파일**: `target_objid = 'screen_files:{screenId}:{componentId}:{columnName}'` 패턴으로 조회
|
||||
3. **레코드 컬럼 값 조회 (fallback)**: 위 두 방법으로 파일을 찾지 못하면, 비즈니스 테이블의 레코드에서 해당 컬럼 값(파일 objid)을 읽어 직접 조회
|
||||
|
||||
```sql
|
||||
-- fallback: 레코드의 image 컬럼에 저장된 objid로 직접 조회
|
||||
SELECT "image" FROM "item_info" WHERE id = $1;
|
||||
-- → '433765011963536400'
|
||||
SELECT * FROM attach_file_info WHERE objid = '433765011963536400' AND status = 'ACTIVE';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 프론트엔드 컴포넌트
|
||||
|
||||
### v2-file-upload (FileUploadComponent.tsx)
|
||||
|
||||
현재 사용되는 V2 파일 업로드 컴포넌트입니다.
|
||||
|
||||
**파일 경로**: `frontend/lib/registry/components/v2-file-upload/FileUploadComponent.tsx`
|
||||
|
||||
#### 이미지 로드 방식
|
||||
|
||||
1. **formData의 컬럼 값으로 로드**: `formData[columnName]`에 파일 objid가 있으면 `/api/files/preview/{objid}`로 이미지 표시
|
||||
2. **getComponentFiles API로 로드**: target_objid 패턴으로 서버에서 파일 목록 조회
|
||||
|
||||
#### 상태 관리
|
||||
|
||||
- `uploadedFiles` state: 현재 표시 중인 파일 목록
|
||||
- `localStorage` 백업: `fileUpload_{componentId}_{columnName}` 키로 저장
|
||||
- `window.globalFileState`: 전역 파일 상태 (컴포넌트 간 동기화)
|
||||
|
||||
#### 등록/수정 모드 구분
|
||||
|
||||
- **수정 모드** (isRecordMode=true, recordId 있음): localStorage/서버에서 기존 파일 복원
|
||||
- **등록 모드** (isRecordMode=false, recordId 없음): localStorage 복원 스킵, 빈 상태로 시작
|
||||
- **단일 폼 화면** (회사정보 등): `formData[columnName]`의 objid 값으로 이미지 자동 로드
|
||||
|
||||
### file-upload (레거시)
|
||||
|
||||
**파일 경로**: `frontend/lib/registry/components/file-upload/FileUploadComponent.tsx`
|
||||
|
||||
V2MediaRenderer에서 사용하는 레거시 컴포넌트. v2-file-upload와 유사하지만 별도 파일입니다.
|
||||
|
||||
### ImageWidget
|
||||
|
||||
**파일 경로**: `frontend/components/screen/widgets/types/ImageWidget.tsx`
|
||||
|
||||
단순 이미지 표시용 위젯. 파일 업로드 기능은 있으나, `getFullImageUrl()`로 URL을 변환하여 `<img>` 태그로 직접 표시합니다. 파일 관리(목록, 삭제 등) 기능은 없습니다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 디스크 저장 구조
|
||||
|
||||
```
|
||||
backend-node/uploads/
|
||||
├── COMPANY_7/ # 회사별 격리
|
||||
│ ├── 2026/
|
||||
│ │ ├── 01/
|
||||
│ │ │ └── 08/
|
||||
│ │ │ └── 1767863580718_img.jpg
|
||||
│ │ └── 02/
|
||||
│ │ └── 06/
|
||||
│ │ ├── 1770346704685_5.png
|
||||
│ │ └── 1770352493105_5.png
|
||||
├── COMPANY_9/
|
||||
│ └── ...
|
||||
└── company_*/ # 최고 관리자 전용
|
||||
└── ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 수정 이력 (2026-02-06)
|
||||
|
||||
### parseInt 정밀도 손실 수정
|
||||
|
||||
**파일**: `backend-node/src/controllers/fileController.ts`
|
||||
|
||||
`attach_file_info.objid`는 `numeric` 타입으로 `433765011963536400` 같은 매우 큰 숫자입니다. JavaScript의 `parseInt()`는 `Number.MAX_SAFE_INTEGER`(약 9 * 10^15)를 초과하면 정밀도 손실이 발생합니다.
|
||||
|
||||
| objid (원본) | parseInt 결과 | 차이 |
|
||||
|:---|:---|:---:|
|
||||
| 396361999644927100 | 396361999644927104 | -4 |
|
||||
| 433765011963536400 | 433765011963536384 | +16 |
|
||||
| 1128460590844245000 | 1128460590844244992 | +8 |
|
||||
|
||||
**수정**: `parseInt(objid)` → `objid` (문자열 직접 전달, 8곳)
|
||||
|
||||
### getComponentFiles fallback 추가
|
||||
|
||||
**파일**: `backend-node/src/controllers/fileController.ts`
|
||||
|
||||
수정 모달에서 이미지가 안 보이는 문제. `target_objid` 패턴이 일치하지 않을 때, 비즈니스 테이블의 레코드 컬럼 값으로 파일을 직접 조회하는 fallback 로직 추가.
|
||||
|
||||
### v2-file-upload 등록 모드 파일 잔존 방지
|
||||
|
||||
**파일**: `frontend/lib/registry/components/v2-file-upload/FileUploadComponent.tsx`
|
||||
|
||||
연속 등록 시 이전 등록의 이미지가 남아있는 문제. `loadComponentFiles`와 fallback 로직에서 등록 모드(recordId 없음)일 때 파일 복원을 스킵하도록 수정.
|
||||
|
||||
### ORDER BY 기본 정렬 추가
|
||||
|
||||
**파일**: `backend-node/src/services/tableManagementService.ts`
|
||||
|
||||
`sortBy` 파라미터가 없을 때 `ORDER BY created_date DESC`를 기본값으로 적용. 4곳 수정.
|
||||
|
|
@ -177,10 +177,18 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
|
|||
// 🔑 레코드별 고유 키 사용
|
||||
const backupKey = getUniqueKey();
|
||||
const backupFiles = localStorage.getItem(backupKey);
|
||||
console.log("🔎 [DEBUG-MOUNT] localStorage 확인:", {
|
||||
backupKey,
|
||||
hasBackup: !!backupFiles,
|
||||
componentId: component.id,
|
||||
recordId: recordId,
|
||||
formDataId: formData?.id,
|
||||
stackTrace: new Error().stack?.split('\n').slice(1, 4).join(' <- '),
|
||||
});
|
||||
if (backupFiles) {
|
||||
const parsedFiles = JSON.parse(backupFiles);
|
||||
if (parsedFiles.length > 0) {
|
||||
console.log("🚀 컴포넌트 마운트 시 파일 즉시 복원:", {
|
||||
console.log("🚀 [DEBUG-MOUNT] 파일 즉시 복원:", {
|
||||
uniqueKey: backupKey,
|
||||
componentId: component.id,
|
||||
recordId: recordId,
|
||||
|
|
@ -203,6 +211,50 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
|
|||
}
|
||||
}, [component.id, getUniqueKey, recordId]); // 레코드별 고유 키 변경 시 재실행
|
||||
|
||||
// 🆕 모달 닫힘/저장 성공 시 localStorage 파일 캐시 정리 (등록 후 재등록 시 이전 파일 잔존 방지)
|
||||
useEffect(() => {
|
||||
const handleClearFileCache = (event: Event) => {
|
||||
const backupKey = getUniqueKey();
|
||||
const eventType = event.type;
|
||||
console.log("🧹 [DEBUG-CLEAR] 파일 캐시 정리 이벤트 수신:", {
|
||||
eventType,
|
||||
backupKey,
|
||||
componentId: component.id,
|
||||
currentFiles: uploadedFiles.length,
|
||||
hasLocalStorage: !!localStorage.getItem(backupKey),
|
||||
});
|
||||
try {
|
||||
localStorage.removeItem(backupKey);
|
||||
setUploadedFiles([]);
|
||||
setRepresentativeImageUrl(null);
|
||||
if (typeof window !== "undefined") {
|
||||
const globalFileState = (window as any).globalFileState || {};
|
||||
delete globalFileState[backupKey];
|
||||
(window as any).globalFileState = globalFileState;
|
||||
}
|
||||
console.log("🧹 [DEBUG-CLEAR] 정리 완료:", backupKey);
|
||||
} catch (e) {
|
||||
console.warn("파일 캐시 정리 실패:", e);
|
||||
}
|
||||
};
|
||||
|
||||
// EditModal 닫힘, ScreenModal 연속 등록 저장 성공, 일반 저장 성공 모두 처리
|
||||
window.addEventListener("closeEditModal", handleClearFileCache);
|
||||
window.addEventListener("saveSuccess", handleClearFileCache);
|
||||
window.addEventListener("saveSuccessInModal", handleClearFileCache);
|
||||
|
||||
console.log("🔎 [DEBUG-CLEAR] 이벤트 리스너 등록 완료:", {
|
||||
componentId: component.id,
|
||||
backupKey: getUniqueKey(),
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("closeEditModal", handleClearFileCache);
|
||||
window.removeEventListener("saveSuccess", handleClearFileCache);
|
||||
window.removeEventListener("saveSuccessInModal", handleClearFileCache);
|
||||
};
|
||||
}, [getUniqueKey]);
|
||||
|
||||
// 🎯 화면설계 모드에서 실제 화면으로의 실시간 동기화 이벤트 리스너
|
||||
useEffect(() => {
|
||||
const handleDesignModeFileChange = (event: CustomEvent) => {
|
||||
|
|
@ -363,6 +415,12 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
|
|||
console.warn("파일 병합 중 오류:", e);
|
||||
}
|
||||
|
||||
console.log("🔎 [DEBUG-LOAD] API 응답 후 파일 설정:", {
|
||||
componentId: component.id,
|
||||
serverFiles: formattedFiles.length,
|
||||
finalFiles: finalFiles.length,
|
||||
files: finalFiles.map((f: any) => ({ objid: f.objid, name: f.realFileName })),
|
||||
});
|
||||
setUploadedFiles(finalFiles);
|
||||
|
||||
// 전역 상태에도 저장 (레코드별 고유 키 사용)
|
||||
|
|
|
|||
|
|
@ -431,6 +431,11 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
|
|||
return; // DB 로드 성공 시 localStorage 무시
|
||||
}
|
||||
|
||||
// 🆕 등록 모드(새 레코드)인 경우 fallback 로드도 스킵 - 항상 빈 상태 유지
|
||||
if (!isRecordMode || !recordId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// DB 로드 실패 시에만 기존 로직 사용 (하위 호환성)
|
||||
|
||||
// 전역 상태에서 최신 파일 정보 가져오기 (🆕 고유 키 사용)
|
||||
|
|
|
|||
Loading…
Reference in New Issue