diff --git a/backend-node/src/services/entityJoinService.ts b/backend-node/src/services/entityJoinService.ts index df2823c8..6877fedd 100644 --- a/backend-node/src/services/entityJoinService.ts +++ b/backend-node/src/services/entityJoinService.ts @@ -49,6 +49,33 @@ export class EntityJoinService { const joinConfigs: EntityJoinConfig[] = []; + // ๐ŸŽฏ writer ์ปฌ๋Ÿผ ์ž๋™ ๊ฐ์ง€ ๋ฐ ์กฐ์ธ ์„ค์ • ์ถ”๊ฐ€ + const tableColumns = await query<{ column_name: string }>( + `SELECT column_name + FROM information_schema.columns + WHERE table_name = $1 + AND table_schema = 'public' + AND column_name = 'writer'`, + [tableName] + ); + + if (tableColumns.length > 0) { + const writerJoinConfig: EntityJoinConfig = { + sourceTable: tableName, + sourceColumn: "writer", + referenceTable: "user_info", + referenceColumn: "user_id", + displayColumns: ["user_name"], + displayColumn: "user_name", + aliasColumn: "writer_name", + separator: " - ", + }; + + if (await this.validateJoinConfig(writerJoinConfig)) { + joinConfigs.push(writerJoinConfig); + } + } + for (const column of entityColumns) { logger.info(`๐Ÿ” Entity ์ปฌ๋Ÿผ ์ƒ์„ธ ์ •๋ณด:`, { column_name: column.column_name, diff --git a/docs/์—‘์…€_๋‹ค์šด๋กœ๋“œ_๊ฐœ์„ _๊ณ„ํš.md b/docs/์—‘์…€_๋‹ค์šด๋กœ๋“œ_๊ฐœ์„ _๊ณ„ํš.md new file mode 100644 index 00000000..f15db82d --- /dev/null +++ b/docs/์—‘์…€_๋‹ค์šด๋กœ๋“œ_๊ฐœ์„ _๊ณ„ํš.md @@ -0,0 +1,656 @@ +# ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ๊ธฐ๋Šฅ ๊ฐœ์„  ๊ณ„ํš์„œ + +## ๐Ÿ“‹ ๋ฌธ์„œ ์ •๋ณด + +- **์ž‘์„ฑ์ผ**: 2025-01-10 +- **์ž‘์„ฑ์ž**: AI Developer +- **์ƒํƒœ**: ๊ณ„ํš ๋‹จ๊ณ„ +- **์šฐ์„ ์ˆœ์œ„**: ๐Ÿ”ด ๋†’์Œ (๋ณด์•ˆ ์ทจ์•ฝ์  ํฌํ•จ) + +--- + +## ๐Ÿšจ ํ˜„์žฌ ๋ฌธ์ œ์  + +### 1. ๋ณด์•ˆ ์ทจ์•ฝ์  (Critical) + +- โŒ **๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ๊ทœ์น™ ์œ„๋ฐ˜**: ๋ชจ๋“  ํšŒ์‚ฌ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ด +- โŒ **ํšŒ์‚ฌ ํ•„ํ„ฐ๋ง ์—†์Œ**: `dynamicFormApi.getTableData` ํ˜ธ์ถœ ์‹œ `autoFilter` ๋ฏธ์ ์šฉ +- โŒ **๋ฐ์ดํ„ฐ ์œ ์ถœ ์œ„ํ—˜**: ํšŒ์‚ฌ A ์‚ฌ์šฉ์ž๊ฐ€ ํšŒ์‚ฌ B, C, D์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์šด๋กœ๋“œ ๊ฐ€๋Šฅ +- โŒ **๊ทœ์ • ์œ„๋ฐ˜**: GDPR, ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ๋ฒ• ๋“ฑ ๋ฒ•์  ๋ฌธ์ œ + +**๊ด€๋ จ ์ฝ”๋“œ**: + +```typescript +// frontend/lib/utils/buttonActions.ts (2043-2048 ๋ผ์ธ) +const response = await dynamicFormApi.getTableData(context.tableName, { + page: 1, + pageSize: 10000, // ์ตœ๋Œ€ 10,000๊ฐœ ํ–‰ + sortBy: context.sortBy || "id", + sortOrder: context.sortOrder || "asc", + // โŒ autoFilter ์—†์Œ - company_code ํ•„ํ„ฐ๋ง ์•ˆ๋จ + // โŒ search ์—†์Œ - ์‚ฌ์šฉ์ž ํ•„ํ„ฐ ์กฐ๊ฑด ๋ฌด์‹œ +}); +``` + +### 2. ๊ธฐ๋Šฅ ๋ฌธ์ œ + +- โŒ **๋ชจ๋“  ์ปฌ๋Ÿผ ํฌํ•จ**: ํ™”๋ฉด์— ํ‘œ์‹œ๋˜์ง€ ์•Š๋Š” ์ปฌ๋Ÿผ๋„ ๋‹ค์šด๋กœ๋“œ๋จ +- โŒ **ํ•„ํ„ฐ ์กฐ๊ฑด ๋ฌด์‹œ**: ์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•œ ๊ฒ€์ƒ‰/ํ•„ํ„ฐ๊ฐ€ ์ ์šฉ๋˜์ง€ ์•Š์Œ +- โŒ **DB ์ปฌ๋Ÿผ๋ช… ์‚ฌ์šฉ**: ์‚ฌ์šฉ์ž ์นœํ™”์ ์ด์ง€ ์•Š์Œ (์˜ˆ: `user_id` ๋Œ€์‹  `์‚ฌ์šฉ์ž ID`) +- โŒ **์ปฌ๋Ÿผ ์ˆœ์„œ ๋ถˆ์ผ์น˜**: ํ™”๋ฉด ํ‘œ์‹œ ์ˆœ์„œ์™€ ๋‹ค๋ฆ„ + +### 3. ์šฐ์„ ์ˆœ์œ„ ๋ฌธ์ œ + +ํ˜„์žฌ ๋‹ค์šด๋กœ๋“œ ๋ฐ์ดํ„ฐ ์šฐ์„ ์ˆœ์œ„: + +1. โœ… ์„ ํƒ๋œ ํ–‰ ๋ฐ์ดํ„ฐ (`context.selectedRowsData`) +2. โœ… ํ™”๋ฉด ํ‘œ์‹œ ๋ฐ์ดํ„ฐ (`context.tableDisplayData`) +3. โœ… ์ „์—ญ ์ €์žฅ์†Œ ๋ฐ์ดํ„ฐ (`tableDisplayStore`) +4. โŒ **ํ…Œ์ด๋ธ” ์ „์ฒด ๋ฐ์ดํ„ฐ** (API ํ˜ธ์ถœ) โ† **๋ณด์•ˆ ์œ„ํ—˜!** + +--- + +## ๐ŸŽฏ ๊ฐœ์„  ๋ชฉํ‘œ + +### 1. ๋ณด์•ˆ ๊ฐ•ํ™” + +- โœ… **๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ์ค€์ˆ˜**: ํ˜„์žฌ ์‚ฌ์šฉ์ž์˜ ํšŒ์‚ฌ ๋ฐ์ดํ„ฐ๋งŒ ๋‹ค์šด๋กœ๋“œ +- โœ… **ํ•„ํ„ฐ ์กฐ๊ฑด ์ ์šฉ**: ์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•œ ๊ฒ€์ƒ‰/ํ•„ํ„ฐ ์กฐ๊ฑด ๋ฐ˜์˜ +- โœ… **๊ถŒํ•œ ๊ฒ€์ฆ**: ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ถŒํ•œ ํ™•์ธ +- โœ… **๊ฐ์‚ฌ ๋กœ๊ทธ**: ๋‹ค์šด๋กœ๋“œ ์ด๋ ฅ ๊ธฐ๋ก + +### 2. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„  + +- โœ… **ํ™”๋ฉด ํ‘œ์‹œ ์ปฌ๋Ÿผ๋งŒ**: ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ์ปฌ๋Ÿผ๋งŒ ๋‹ค์šด๋กœ๋“œ +- โœ… **์ปฌ๋Ÿผ ์ˆœ์„œ ์œ ์ง€**: ํ™”๋ฉด ํ‘œ์‹œ ์ˆœ์„œ์™€ ๋™์ผ +- โœ… **๋ผ๋ฒจ๋ช… ์‚ฌ์šฉ**: ํ•œ๊ธ€ ์ปฌ๋Ÿผ๋ช… (์˜ˆ: `์‚ฌ์šฉ์ž ID`, `๋ถ€์„œ๋ช…`) +- โœ… **์ •๋ ฌ ์œ ์ง€**: ํ™”๋ฉด ์ •๋ ฌ ์ƒํƒœ ๋ฐ˜์˜ + +### 3. ๋ฐ์ดํ„ฐ ์ •ํ™•์„ฑ + +- โœ… **ํ•„ํ„ฐ๋ง๋œ ๋ฐ์ดํ„ฐ**: ํ™”๋ฉด์— ๋ณด์ด๋Š” ์กฐ๊ฑด๊ณผ ๋™์ผํ•œ ๋ฐ์ดํ„ฐ +- โœ… **์„ ํƒ ์šฐ์„ **: ์‚ฌ์šฉ์ž๊ฐ€ ํ–‰์„ ์„ ํƒํ–ˆ์œผ๋ฉด ์„ ํƒ๋œ ํ–‰๋งŒ +- โœ… **๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ**: ํ™”๋ฉด โ†” ์—‘์…€ ๋ฐ์ดํ„ฐ ์ผ์น˜ + +--- + +## ๐Ÿ“ ๊ฐœ์„  ๊ณ„ํš + +### Phase 1: ๋ฐ์ดํ„ฐ ์†Œ์Šค ์šฐ์„ ์ˆœ์œ„ ์žฌ์ •์˜ + +#### ์ƒˆ๋กœ์šด ์šฐ์„ ์ˆœ์œ„ + +``` +1. ์„ ํƒ๋œ ํ–‰ ๋ฐ์ดํ„ฐ (๊ฐ€์žฅ ๋†’์€ ์šฐ์„ ์ˆœ์œ„) + - ์ถœ์ฒ˜: context.selectedRowsData + - ์„ค๋ช…: ์‚ฌ์šฉ์ž๊ฐ€ ์ฒดํฌ๋ฐ•์Šค๋กœ ์„ ํƒํ•œ ํ–‰ + - ํŠน์ง•: ํ•„ํ„ฐ/์ •๋ ฌ ์ด๋ฏธ ์ ์šฉ๋จ, ๊ฐ€์žฅ ๋ช…ํ™•ํ•œ ์˜๋„ + - ์ฒ˜๋ฆฌ: ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ + +2. ํ™”๋ฉด ํ‘œ์‹œ ๋ฐ์ดํ„ฐ (๋‘ ๋ฒˆ์งธ ์šฐ์„ ์ˆœ์œ„) + - ์ถœ์ฒ˜: tableDisplayStore.getTableData(tableName) + - ์„ค๋ช…: ํ˜„์žฌ ํ™”๋ฉด์— ํ‘œ์‹œ ์ค‘์ธ ๋ฐ์ดํ„ฐ + - ํŠน์ง•: ํ•„ํ„ฐ/์ •๋ ฌ/ํŽ˜์ด์ง• ์ ์šฉ๋จ, ๊ฐ€์žฅ ์•ˆ์ „ + - ์ฒ˜๋ฆฌ: + - ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ๋งŒ (๊ธฐ๋ณธ) + - ๋˜๋Š” ์ „์ฒด ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ (์˜ต์…˜) + +3. API ํ˜ธ์ถœ - ํ•„ํ„ฐ ์กฐ๊ฑด ํฌํ•จ (์ตœํ›„ ์ˆ˜๋‹จ) + - ์ถœ์ฒ˜: entityJoinApi.getTableDataWithJoins() + - ์„ค๋ช…: ์œ„์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์„ ๋•Œ๋งŒ + - ํŠน์ง•: + - โœ… company_code ์ž๋™ ํ•„ํ„ฐ๋ง (autoFilter: true) + - โœ… ๊ฒ€์ƒ‰/ํ•„ํ„ฐ ์กฐ๊ฑด ์ „๋‹ฌ + - โœ… ์ •๋ ฌ ์กฐ๊ฑด ์ „๋‹ฌ + - ์ œํ•œ: ์ตœ๋Œ€ 10,000๊ฐœ ํ–‰ + +4. โŒ ํ…Œ์ด๋ธ” ์ „์ฒด ๋ฐ์ดํ„ฐ (์ œ๊ฑฐ) + - ๋ณด์•ˆ์ƒ ์œ„ํ—˜ํ•˜๋ฏ€๋กœ ์™„์ „ ์ œ๊ฑฐ + - ๋Œ€์‹  ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ +``` + +### Phase 2: ButtonActionContext ํ™•์žฅ + +#### ํ˜„์žฌ ๊ตฌ์กฐ + +```typescript +interface ButtonActionContext { + tableName?: string; + formData?: Record; + selectedRowsData?: any[]; + tableDisplayData?: any[]; + columnOrder?: string[]; + sortBy?: string; + sortOrder?: "asc" | "desc"; +} +``` + +#### ์ถ”๊ฐ€ ํ•„๋“œ + +```typescript +interface ButtonActionContext { + // ... ๊ธฐ์กด ํ•„๋“œ + + // ๐Ÿ†• ํ•„ํ„ฐ ๋ฐ ๊ฒ€์ƒ‰ ์กฐ๊ฑด + filterConditions?: Record; // ํ•„ํ„ฐ ์กฐ๊ฑด (์˜ˆ: { status: "active", dept: "dev" }) + searchTerm?: string; // ๊ฒ€์ƒ‰์–ด + searchColumn?: string; // ๊ฒ€์ƒ‰ ๋Œ€์ƒ ์ปฌ๋Ÿผ + + // ๐Ÿ†• ์ปฌ๋Ÿผ ์ •๋ณด + visibleColumns?: string[]; // ํ™”๋ฉด์— ํ‘œ์‹œ ์ค‘์ธ ์ปฌ๋Ÿผ ๋ชฉ๋ก (์ˆœ์„œ ํฌํ•จ) + columnLabels?: Record; // ์ปฌ๋Ÿผ๋ช… โ†’ ๋ผ๋ฒจ๋ช… ๋งคํ•‘ + + // ๐Ÿ†• ํŽ˜์ด์ง• ์ •๋ณด + currentPage?: number; // ํ˜„์žฌ ํŽ˜์ด์ง€ + pageSize?: number; // ํŽ˜์ด์ง€ ํฌ๊ธฐ + totalItems?: number; // ์ „์ฒด ํ•ญ๋ชฉ ์ˆ˜ + + // ๐Ÿ†• ์—‘์…€ ์˜ต์…˜ + excelScope?: "selected" | "current-page" | "all-filtered"; // ๋‹ค์šด๋กœ๋“œ ๋ฒ”์œ„ +} +``` + +### Phase 3: TableListComponent ์ˆ˜์ • + +#### ์œ„์น˜ + +`frontend/lib/registry/components/table-list/TableListComponent.tsx` + +#### ๋ณ€๊ฒฝ ์‚ฌํ•ญ + +```typescript +// ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ context ์ƒ์„ฑ +const buttonContext: ButtonActionContext = { + tableName: tableConfig.selectedTable, + + // ๊ธฐ์กด + selectedRowsData: selectedRows, + tableDisplayData: data, // ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ + columnOrder: visibleColumns.map((col) => col.columnName), + sortBy: sortColumn, + sortOrder: sortDirection, + + // ๐Ÿ†• ์ถ”๊ฐ€ + filterConditions: searchValues, // ํ•„ํ„ฐ ์กฐ๊ฑด + searchTerm: searchTerm, // ๊ฒ€์ƒ‰์–ด + visibleColumns: visibleColumns.map((col) => col.columnName), // ํ‘œ์‹œ ์ปฌ๋Ÿผ + columnLabels: columnLabels, // ์ปฌ๋Ÿผ ๋ผ๋ฒจ (ํ•œ๊ธ€) + currentPage: currentPage, // ํ˜„์žฌ ํŽ˜์ด์ง€ + pageSize: localPageSize, // ํŽ˜์ด์ง€ ํฌ๊ธฐ + totalItems: totalItems, // ์ „์ฒด ํ•ญ๋ชฉ ์ˆ˜ + excelScope: selectedRows.length > 0 ? "selected" : "current-page", // ๊ธฐ๋ณธ: ํ˜„์žฌ ํŽ˜์ด์ง€ +}; +``` + +### Phase 4: handleExcelDownload ์ˆ˜์ • + +#### 4-1. ๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ ํƒ ๋กœ์ง + +```typescript +private static async handleExcelDownload( + config: ButtonActionConfig, + context: ButtonActionContext +): Promise { + try { + let dataToExport: any[] = []; + let dataSource: string = "unknown"; + + // 1์ˆœ์œ„: ์„ ํƒ๋œ ํ–‰ ๋ฐ์ดํ„ฐ + if (context.selectedRowsData && context.selectedRowsData.length > 0) { + dataToExport = context.selectedRowsData; + dataSource = "selected"; + console.log("โœ… ์„ ํƒ๋œ ํ–‰ ์‚ฌ์šฉ:", dataToExport.length); + } + // 2์ˆœ์œ„: ํ™”๋ฉด ํ‘œ์‹œ ๋ฐ์ดํ„ฐ + else if (context.tableDisplayData && context.tableDisplayData.length > 0) { + dataToExport = context.tableDisplayData; + dataSource = "current-page"; + console.log("โœ… ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ:", dataToExport.length); + } + // 3์ˆœ์œ„: ์ „์—ญ ์ €์žฅ์†Œ ๋ฐ์ดํ„ฐ + else if (context.tableName) { + const { tableDisplayStore } = await import("@/stores/tableDisplayStore"); + const storedData = tableDisplayStore.getTableData(context.tableName); + + if (storedData && storedData.data.length > 0) { + dataToExport = storedData.data; + dataSource = "store"; + console.log("โœ… ์ €์žฅ์†Œ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ:", dataToExport.length); + } + } + + // 4์ˆœ์œ„: API ํ˜ธ์ถœ (ํ•„ํ„ฐ ์กฐ๊ฑด ํฌํ•จ) - ์ตœํ›„ ์ˆ˜๋‹จ + if (dataToExport.length === 0 && context.tableName) { + console.log("โš ๏ธ ํ™”๋ฉด ๋ฐ์ดํ„ฐ ์—†์Œ - API ํ˜ธ์ถœ ํ•„์š”"); + + // ์‚ฌ์šฉ์ž ํ™•์ธ (์„ ํƒ์‚ฌํ•ญ) + const confirmed = await this.confirmLargeDownload(context.totalItems || 0); + if (!confirmed) { + return false; + } + + dataToExport = await this.fetchFilteredData(context); + dataSource = "api"; + } + + // ๋ฐ์ดํ„ฐ ์—†์Œ + if (dataToExport.length === 0) { + toast.error("๋‹ค์šด๋กœ๋“œํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); + return false; + } + + // ... ๊ณ„์† + } +} +``` + +#### 4-2. API ํ˜ธ์ถœ ๋ฉ”์„œ๋“œ (ํ•„ํ„ฐ ์กฐ๊ฑด ํฌํ•จ) + +```typescript +private static async fetchFilteredData( + context: ButtonActionContext +): Promise { + try { + console.log("๐Ÿ”„ ํ•„ํ„ฐ๋œ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์ค‘...", { + tableName: context.tableName, + filterConditions: context.filterConditions, + searchTerm: context.searchTerm, + sortBy: context.sortBy, + sortOrder: context.sortOrder, + }); + + const { entityJoinApi } = await import("@/lib/api/entityJoin"); + + // ๐Ÿ”’ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ์ค€์ˆ˜: autoFilter๋กœ company_code ์ž๋™ ์ ์šฉ + const response = await entityJoinApi.getTableDataWithJoins( + context.tableName!, + { + page: 1, + size: 10000, // ์ตœ๋Œ€ 10,000๊ฐœ + sortBy: context.sortBy || "id", + sortOrder: context.sortOrder || "asc", + search: context.filterConditions, // โœ… ํ•„ํ„ฐ ์กฐ๊ฑด + enableEntityJoin: true, // โœ… Entity ์กฐ์ธ + autoFilter: true, // โœ… company_code ์ž๋™ ํ•„ํ„ฐ๋ง + } + ); + + if (response.success && response.data) { + console.log("โœ… API ๋ฐ์ดํ„ฐ ์กฐํšŒ ์™„๋ฃŒ:", { + count: response.data.length, + total: response.total, + }); + return response.data; + } else { + console.error("โŒ API ์‘๋‹ต ์‹คํŒจ:", response); + return []; + } + } catch (error) { + console.error("โŒ API ํ˜ธ์ถœ ์˜ค๋ฅ˜:", error); + toast.error("๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); + return []; + } +} +``` + +#### 4-3. ์ปฌ๋Ÿผ ํ•„ํ„ฐ๋ง ๋ฐ ๋ผ๋ฒจ ์ ์šฉ + +```typescript +private static applyColumnFiltering( + data: any[], + context: ButtonActionContext +): any[] { + // ํ‘œ์‹œ ์ปฌ๋Ÿผ์ด ์ง€์ •๋˜์ง€ ์•Š์•˜์œผ๋ฉด ๋ชจ๋“  ์ปฌ๋Ÿผ ์‚ฌ์šฉ + const visibleColumns = context.visibleColumns || Object.keys(data[0] || {}); + const columnLabels = context.columnLabels || {}; + + console.log("๐Ÿ”ง ์ปฌ๋Ÿผ ํ•„ํ„ฐ๋ง ๋ฐ ๋ผ๋ฒจ ์ ์šฉ:", { + totalColumns: Object.keys(data[0] || {}).length, + visibleColumns: visibleColumns.length, + hasLabels: Object.keys(columnLabels).length > 0, + }); + + return data.map(row => { + const filteredRow: Record = {}; + + visibleColumns.forEach(columnName => { + // ๋ผ๋ฒจ ์šฐ์„  ์‚ฌ์šฉ, ์—†์œผ๋ฉด ์ปฌ๋Ÿผ๋ช… ์‚ฌ์šฉ + const label = columnLabels[columnName] || columnName; + filteredRow[label] = row[columnName]; + }); + + return filteredRow; + }); +} +``` + +#### 4-4. ๋Œ€์šฉ๋Ÿ‰ ๋‹ค์šด๋กœ๋“œ ํ™•์ธ + +```typescript +private static async confirmLargeDownload(totalItems: number): Promise { + if (totalItems === 0) { + return true; // ๋ฐ์ดํ„ฐ ์—†์œผ๋ฉด ํ™•์ธ ๋ถˆํ•„์š” + } + + if (totalItems > 1000) { + const confirmed = window.confirm( + `์ด ${totalItems.toLocaleString()}๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.\n` + + `(์ตœ๋Œ€ 10,000๊ฐœ๊นŒ์ง€๋งŒ ๋‹ค์šด๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค)\n\n` + + `๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?` + ); + return confirmed; + } + + return true; // 1000๊ฐœ ์ดํ•˜๋Š” ์ž๋™ ์ง„ํ–‰ +} +``` + +#### 4-5. ์ „์ฒด ํ๋ฆ„ + +```typescript +private static async handleExcelDownload( + config: ButtonActionConfig, + context: ButtonActionContext +): Promise { + try { + // 1. ๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ ํƒ + let dataToExport = await this.selectDataSource(context); + + if (dataToExport.length === 0) { + toast.error("๋‹ค์šด๋กœ๋“œํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); + return false; + } + + // 2. ์ตœ๋Œ€ ํ–‰ ์ˆ˜ ์ œํ•œ + const MAX_ROWS = 10000; + if (dataToExport.length > MAX_ROWS) { + toast.warning(`์ตœ๋Œ€ ${MAX_ROWS.toLocaleString()}๊ฐœ ํ–‰๊นŒ์ง€๋งŒ ๋‹ค์šด๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.`); + dataToExport = dataToExport.slice(0, MAX_ROWS); + } + + // 3. ์ปฌ๋Ÿผ ํ•„ํ„ฐ๋ง ๋ฐ ๋ผ๋ฒจ ์ ์šฉ + dataToExport = this.applyColumnFiltering(dataToExport, context); + + // 4. ์ •๋ ฌ ์ ์šฉ (ํ•„์š” ์‹œ) + if (context.sortBy) { + dataToExport = this.applySorting(dataToExport, context.sortBy, context.sortOrder); + } + + // 5. ์—‘์…€ ํŒŒ์ผ ์ƒ์„ฑ + const { exportToExcel } = await import("@/lib/utils/excelExport"); + + const fileName = config.excelFileName || + `${context.tableName}_${new Date().toISOString().split("T")[0]}.xlsx`; + const sheetName = config.excelSheetName || "Sheet1"; + const includeHeaders = config.excelIncludeHeaders !== false; + + await exportToExcel(dataToExport, fileName, sheetName, includeHeaders); + + toast.success(`${dataToExport.length}๊ฐœ ํ–‰์ด ๋‹ค์šด๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`); + + // 6. ๊ฐ์‚ฌ ๋กœ๊ทธ (์„ ํƒ์‚ฌํ•ญ) + this.logExcelDownload(context, dataToExport.length); + + return true; + } catch (error) { + console.error("โŒ ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ์‹คํŒจ:", error); + toast.error("์—‘์…€ ๋‹ค์šด๋กœ๋“œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); + return false; + } +} +``` + +--- + +## ๐Ÿ”ง ๊ตฌํ˜„ ๋‹จ๊ณ„ + +### Step 1: ํƒ€์ž… ์ •์˜ ์—…๋ฐ์ดํŠธ + +**ํŒŒ์ผ**: `frontend/lib/utils/buttonActions.ts` + +- [ ] `ButtonActionContext` ์ธํ„ฐํŽ˜์ด์Šค์— ์ƒˆ ํ•„๋“œ ์ถ”๊ฐ€ +- [ ] `ExcelDownloadScope` ํƒ€์ž… ์ •์˜ ์ถ”๊ฐ€ +- [ ] JSDoc ์ฃผ์„ ์ถ”๊ฐ€ + +**์˜ˆ์ƒ ์ž‘์—… ์‹œ๊ฐ„**: 10๋ถ„ + +--- + +### Step 2: TableListComponent ์ˆ˜์ • + +**ํŒŒ์ผ**: `frontend/lib/registry/components/table-list/TableListComponent.tsx` + +- [ ] ๋ฒ„ํŠผ context ์ƒ์„ฑ ์‹œ ํ•„ํ„ฐ/์ปฌ๋Ÿผ/๋ผ๋ฒจ ์ •๋ณด ์ถ”๊ฐ€ +- [ ] `columnLabels` ์ƒ์„ฑ ๋กœ์ง ์ถ”๊ฐ€ +- [ ] `visibleColumns` ๋ชฉ๋ก ์ƒ์„ฑ + +**์˜ˆ์ƒ ์ž‘์—… ์‹œ๊ฐ„**: 20๋ถ„ + +--- + +### Step 3: handleExcelDownload ๋ฆฌํŒฉํ† ๋ง + +**ํŒŒ์ผ**: `frontend/lib/utils/buttonActions.ts` + +- [ ] ๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ ํƒ ๋กœ์ง ๋ถ„๋ฆฌ (`selectDataSource`) +- [ ] API ํ˜ธ์ถœ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€ (`fetchFilteredData`) +- [ ] ์ปฌ๋Ÿผ ํ•„ํ„ฐ๋ง ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€ (`applyColumnFiltering`) +- [ ] ๋Œ€์šฉ๋Ÿ‰ ํ™•์ธ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€ (`confirmLargeDownload`) +- [ ] ์ •๋ ฌ ๋ฉ”์„œ๋“œ ๊ฐœ์„  (`applySorting`) +- [ ] ๊ธฐ์กด ์ฝ”๋“œ ์ •๋ฆฌ (๋ถˆํ•„์š”ํ•œ ๋กœ๊ทธ ์ œ๊ฑฐ) + +**์˜ˆ์ƒ ์ž‘์—… ์‹œ๊ฐ„**: 40๋ถ„ + +--- + +### Step 4: ํ…Œ์ŠคํŠธ + +**ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค**: + +1. **์„ ํƒ๋œ ํ–‰ ๋‹ค์šด๋กœ๋“œ** + + - ์ฒดํฌ๋ฐ•์Šค๋กœ ์—ฌ๋Ÿฌ ํ–‰ ์„ ํƒ + - ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ํด๋ฆญ + - ์˜ˆ์ƒ: ์„ ํƒ๋œ ํ–‰๋งŒ ๋‹ค์šด๋กœ๋“œ + - ํ™•์ธ: ๋ผ๋ฒจ๋ช…, ์ปฌ๋Ÿผ ์ˆœ์„œ, ๋ฐ์ดํ„ฐ ์ •ํ™•์„ฑ + +2. **ํ˜„์žฌ ํŽ˜์ด์ง€ ๋‹ค์šด๋กœ๋“œ** + + - ํ–‰ ์„ ํƒ ์—†์ด ์—‘์…€ ๋‹ค์šด๋กœ๋“œ + - ์˜ˆ์ƒ: ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ๋งŒ + - ํ™•์ธ: ํŽ˜์ด์ง€ ํฌ๊ธฐ๋งŒํผ ๋‹ค์šด๋กœ๋“œ + +3. **ํ•„ํ„ฐ ์ ์šฉ ๋‹ค์šด๋กœ๋“œ** + + - ๊ฒ€์ƒ‰์–ด ์ž…๋ ฅ ๋˜๋Š” ํ•„ํ„ฐ ์„ค์ • + - ์—‘์…€ ๋‹ค์šด๋กœ๋“œ + - ์˜ˆ์ƒ: ํ•„ํ„ฐ๋œ ๊ฒฐ๊ณผ๋งŒ + - ํ™•์ธ: ํ™”๋ฉด ๋ฐ์ดํ„ฐ์™€ ์ผ์น˜ + +4. **๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ํ…Œ์ŠคํŠธ** + + - ํšŒ์‚ฌ A๋กœ ๋กœ๊ทธ์ธ + - ์—‘์…€ ๋‹ค์šด๋กœ๋“œ + - ํ™•์ธ: ํšŒ์‚ฌ A ๋ฐ์ดํ„ฐ๋งŒ + - ํšŒ์‚ฌ B๋กœ ๋กœ๊ทธ์ธ + - ์—‘์…€ ๋‹ค์šด๋กœ๋“œ + - ํ™•์ธ: ํšŒ์‚ฌ B ๋ฐ์ดํ„ฐ๋งŒ + +5. **๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ํ…Œ์ŠคํŠธ** + + - 10,000๊ฐœ ์ด์ƒ ๋ฐ์ดํ„ฐ ์กฐํšŒ + - ์—‘์…€ ๋‹ค์šด๋กœ๋“œ + - ์˜ˆ์ƒ: 10,000๊ฐœ๊นŒ์ง€๋งŒ + ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€ + +6. **์ปฌ๋Ÿผ ๋ผ๋ฒจ ํ…Œ์ŠคํŠธ** + - ์—‘์…€ ํŒŒ์ผ ์—ด๊ธฐ + - ํ™•์ธ: DB ์ปฌ๋Ÿผ๋ช…์ด ์•„๋‹Œ ํ•œ๊ธ€ ๋ผ๋ฒจ๋ช… + +**์˜ˆ์ƒ ์ž‘์—… ์‹œ๊ฐ„**: 30๋ถ„ + +--- + +### Step 5: ๋ฌธ์„œํ™” ๋ฐ ์ปค๋ฐ‹ + +- [ ] ์ฝ”๋“œ ์ฃผ์„ ์ถ”๊ฐ€ +- [ ] README ์—…๋ฐ์ดํŠธ (์žˆ๋‹ค๋ฉด) +- [ ] ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์ž‘์„ฑ + +**์˜ˆ์ƒ ์ž‘์—… ์‹œ๊ฐ„**: 10๋ถ„ + +--- + +## โฑ๏ธ ์ด ์˜ˆ์ƒ ์‹œ๊ฐ„ + +**์•ฝ 2์‹œ๊ฐ„** (์ฝ”๋”ฉ + ํ…Œ์ŠคํŠธ) + +--- + +## โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ + +### 1. ํ•˜์œ„ ํ˜ธํ™˜์„ฑ + +- ๊ธฐ์กด `context.tableDisplayData`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Œ +- ์ƒˆ ํ•„๋“œ๋Š” ๋ชจ๋‘ ์„ ํƒ์‚ฌํ•ญ(`?`)์œผ๋กœ ์ •์˜ +- ๊ธฐ์กด ๋™์ž‘์€ ์œ ์ง€ํ•˜๋ฉด์„œ ์ ์ง„์ ์œผ๋กœ ๊ฐœ์„  + +### 2. ์„ฑ๋Šฅ + +- API ํ˜ธ์ถœ ์‹œ ์ตœ๋Œ€ 10,000๊ฐœ ์ œํ•œ +- ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ๋Š” ํŽ˜์ด์ง• ๊ถŒ์žฅ +- ๋ธŒ๋ผ์šฐ์ € ๋ฉ”๋ชจ๋ฆฌ ์ œํ•œ ๊ณ ๋ ค + +### 3. ๋ณด์•ˆ + +- **์ ˆ๋Œ€ `autoFilter: false` ์‚ฌ์šฉ ๊ธˆ์ง€** +- ๋ชจ๋“  API ํ˜ธ์ถœ์— `autoFilter: true` ํ•„์ˆ˜ +- ๊ฐ์‚ฌ ๋กœ๊ทธ ๊ธฐ๋ก ๊ถŒ์žฅ + +### 4. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ + +- ๋‹ค์šด๋กœ๋“œ ์ค‘ ๋กœ๋”ฉ ํ‘œ์‹œ +- ์™„๋ฃŒ/์‹คํŒจ ํ† ์ŠคํŠธ ๋ฉ”์‹œ์ง€ +- ๋Œ€์šฉ๋Ÿ‰ ๋‹ค์šด๋กœ๋“œ ์‹œ ํ™•์ธ ์ฐฝ + +--- + +## ๐Ÿ“Š ์˜ˆ์ƒ ๊ฒฐ๊ณผ + +### Before (ํ˜„์žฌ) + +``` +์—‘์…€ ๋‹ค์šด๋กœ๋“œ: +โŒ ๋ชจ๋“  ํšŒ์‚ฌ์˜ ๋ฐ์ดํ„ฐ (๋ณด์•ˆ ์œ„ํ—˜!) +โŒ ๋ชจ๋“  ์ปฌ๋Ÿผ ํฌํ•จ (๋ถˆํ•„์š”ํ•œ ์ •๋ณด) +โŒ ํ•„ํ„ฐ ์กฐ๊ฑด ๋ฌด์‹œ +โŒ DB ์ปฌ๋Ÿผ๋ช… (user_id, dept_code) +โŒ ์ •๋ ฌ ์ƒํƒœ ๋ฌด์‹œ +``` + +### After (๊ฐœ์„ ) + +``` +์—‘์…€ ๋‹ค์šด๋กœ๋“œ: +โœ… ํ˜„์žฌ ํšŒ์‚ฌ ๋ฐ์ดํ„ฐ๋งŒ (๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ์ค€์ˆ˜) +โœ… ํ™”๋ฉด ํ‘œ์‹œ ์ปฌ๋Ÿผ๋งŒ (์‚ฌ์šฉ์ž ์„ ํƒ) +โœ… ํ•„ํ„ฐ ์กฐ๊ฑด ์ ์šฉ (๊ฒ€์ƒ‰/ํ•„ํ„ฐ ๋ฐ˜์˜) +โœ… ํ•œ๊ธ€ ๋ผ๋ฒจ๋ช… (์‚ฌ์šฉ์ž ID, ๋ถ€์„œ๋ช…) +โœ… ์ •๋ ฌ ์ƒํƒœ ์œ ์ง€ (ํ™”๋ฉด๊ณผ ๋™์ผ) +โœ… ์ปฌ๋Ÿผ ์ˆœ์„œ ์œ ์ง€ (ํ™”๋ฉด๊ณผ ๋™์ผ) +``` + +--- + +## ๐Ÿ”— ๊ด€๋ จ ํŒŒ์ผ + +### ์ˆ˜์ • ๋Œ€์ƒ + +1. `frontend/lib/utils/buttonActions.ts` + + - `ButtonActionContext` ์ธํ„ฐํŽ˜์ด์Šค + - `handleExcelDownload` ๋ฉ”์„œ๋“œ + +2. `frontend/lib/registry/components/table-list/TableListComponent.tsx` + - ๋ฒ„ํŠผ context ์ƒ์„ฑ ๋กœ์ง + +### ์ฐธ๊ณ  ํŒŒ์ผ + +1. `frontend/lib/api/entityJoin.ts` + + - `getTableDataWithJoins` API + +2. `frontend/lib/utils/excelExport.ts` + + - `exportToExcel` ํ•จ์ˆ˜ + +3. `.cursor/rules/multi-tenancy-guide.mdc` + - ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ๊ทœ์น™ + +--- + +## ๐Ÿ“ ํ›„์† ์ž‘์—… (์„ ํƒ์‚ฌํ•ญ) + +### 1. ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ์˜ต์…˜ UI + +์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค์šด๋กœ๋“œ ๋ฒ”์œ„๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋‹ฌ: + +``` +[ ] ์„ ํƒ๋œ ํ–‰๋งŒ (N๊ฐœ) +[x] ํ˜„์žฌ ํŽ˜์ด์ง€ (20๊ฐœ) +[ ] ํ•„ํ„ฐ๋œ ์ „์ฒด ๋ฐ์ดํ„ฐ (์ตœ๋Œ€ 10,000๊ฐœ) +``` + +### 2. ์—‘์…€ ์Šคํƒ€์ผ๋ง + +- ํ—ค๋” ๋ฐฐ๊ฒฝ์ƒ‰ +- ์ž๋™ ๋„ˆ๋น„ ์กฐ์ • +- ํ•„ํ„ฐ ๋ฒ„ํŠผ ์ถ”๊ฐ€ + +### 3. CSV ๋‚ด๋ณด๋‚ด๊ธฐ + +- ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ์— ์ ํ•ฉ +- ๊ฐ€๋ฒผ์šด ํŒŒ์ผ ํฌ๊ธฐ + +### 4. ๊ฐ์‚ฌ ๋กœ๊ทธ + +- ๋ˆ„๊ฐ€, ์–ธ์ œ, ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์šด๋กœ๋“œํ–ˆ๋Š”์ง€ ๊ธฐ๋ก +- ๋ณด์•ˆ ๊ฐ์‚ฌ ์ถ”์  + +--- + +## โœ… ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +### ๊ณ„ํš ๋‹จ๊ณ„ + +- [x] ๊ณ„ํš์„œ ์ž‘์„ฑ ์™„๋ฃŒ +- [x] ์‚ฌ์šฉ์ž ๊ฒ€ํ†  ๋ฐ ์Šน์ธ +- [x] ์ˆ˜์ • ์‚ฌํ•ญ ๋ฐ˜์˜ + +### ๊ตฌํ˜„ ๋‹จ๊ณ„ + +- [x] Step 1: ํƒ€์ž… ์ •์˜ ์—…๋ฐ์ดํŠธ +- [x] Step 2: TableListComponent ์ˆ˜์ • +- [x] Step 3: handleExcelDownload ๋ฆฌํŒฉํ† ๋ง +- [ ] Step 4: ํ…Œ์ŠคํŠธ ์™„๋ฃŒ (์‚ฌ์šฉ์ž ํ…Œ์ŠคํŠธ ํ•„์š”) +- [ ] Step 5: ๋ฌธ์„œํ™” ๋ฐ ์ปค๋ฐ‹ (๋Œ€๊ธฐ ์ค‘) + +### ๋ฐฐํฌ ๋‹จ๊ณ„ + +- [ ] ์ฝ”๋“œ ๋ฆฌ๋ทฐ +- [ ] QA ํ…Œ์ŠคํŠธ +- [ ] ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ +- [ ] ๋ชจ๋‹ˆํ„ฐ๋ง + +--- + +## ๐Ÿค ์Šน์ธ + +- [ ] ๊ฐœ๋ฐœํŒ€ ๋ฆฌ๋ทฐ +- [ ] ๋ณด์•ˆํŒ€ ๊ฒ€ํ†  +- [ ] ์‚ฌ์šฉ์ž ์Šน์ธ +- [ ] ์ตœ์ข… ์Šน์ธ + +--- + +**์ž‘์„ฑ ์™„๋ฃŒ**: 2025-01-10 +**๋‹ค์Œ ์—…๋ฐ์ดํŠธ**: ๊ตฌํ˜„ ์™„๋ฃŒ ํ›„ diff --git a/docs/์—‘์…€_๋‹ค์šด๋กœ๋“œ_๊ฐœ์„ _๊ณ„ํš_v2.md b/docs/์—‘์…€_๋‹ค์šด๋กœ๋“œ_๊ฐœ์„ _๊ณ„ํš_v2.md new file mode 100644 index 00000000..17139109 --- /dev/null +++ b/docs/์—‘์…€_๋‹ค์šด๋กœ๋“œ_๊ฐœ์„ _๊ณ„ํš_v2.md @@ -0,0 +1,275 @@ +# ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ๊ฐœ์„  ๊ณ„ํš v2 (์ˆ˜์ •) + +## ๐Ÿ“‹ ๋ฌธ์„œ ์ •๋ณด + +- **์ž‘์„ฑ์ผ**: 2025-01-10 +- **์ž‘์„ฑ์ž**: AI Developer +- **๋ฒ„์ „**: 2.0 (์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ ๋ฐ˜์˜) +- **์ƒํƒœ**: ๊ตฌํ˜„ ๋Œ€๊ธฐ + +--- + +## ๐ŸŽฏ ๋ณ€๊ฒฝ๋œ ์š”๊ตฌ์‚ฌํ•ญ (์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ) + +### ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ๋™์ž‘ + +1. โŒ **์„ ํƒ๋œ ํ–‰๋งŒ ๋‹ค์šด๋กœ๋“œ ๊ธฐ๋Šฅ ์ œ๊ฑฐ** (๋ถˆํ•„์š”) +2. โœ… **ํ•ญ์ƒ ํ•„ํ„ฐ๋ง๋œ ์ „์ฒด ๋ฐ์ดํ„ฐ ๋‹ค์šด๋กœ๋“œ** (ํ˜„์žฌ ํ™”๋ฉด ๊ธฐ์ค€) +3. โœ… **ํ™”๋ฉด์— ํ‘œ์‹œ๋œ ์ปฌ๋Ÿผ๋งŒ** ๋‹ค์šด๋กœ๋“œ +4. โœ… **์ปฌ๋Ÿผ ๋ผ๋ฒจ(ํ•œ๊ธ€) ์šฐ์„ ** ์‚ฌ์šฉ +5. โœ… **๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ์ค€์ˆ˜** (company_code ํ•„ํ„ฐ๋ง) + +### ํ˜„์žฌ ๋ฌธ์ œ + +1. ๐Ÿ› **ํ–‰ ์„ ํƒ ์•ˆ ํ–ˆ์„ ๋•Œ**: "๋‹ค์šด๋กœ๋“œํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค" ์—๋Ÿฌ +2. โŒ **์„ ํƒ๋œ ํ–‰๋งŒ ๋‹ค์šด๋กœ๋“œ**: ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜์ง€ ์•Š๋Š” ๋™์ž‘ +3. โŒ **๋ชจ๋“  ์ปฌ๋Ÿผ ํฌํ•จ**: ํ™”๋ฉด์— ํ‘œ์‹œ๋˜์ง€ ์•Š๋Š” ์ปฌ๋Ÿผ๋„ ๋‹ค์šด๋กœ๋“œ๋จ +4. โŒ **ํ•„ํ„ฐ ์กฐ๊ฑด ๋ฌด์‹œ**: ์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•œ ๊ฒ€์ƒ‰/ํ•„ํ„ฐ๊ฐ€ ์ ์šฉ๋˜์ง€ ์•Š์Œ +5. โŒ **๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ์œ„๋ฐ˜**: ๋ชจ๋“  ํšŒ์‚ฌ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๊ฐ€๋Šฅ์„ฑ + +--- + +## ๐Ÿ”„ ์ˆ˜์ •๋œ ๋‹ค์šด๋กœ๋“œ ๋™์ž‘ ํ๋ฆ„ + +### Before (ํ˜„์žฌ - ์ž˜๋ชป๋œ ๋™์ž‘) + +``` +์—‘์…€ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ํด๋ฆญ +โ†“ +1. ์„ ํƒ๋œ ํ–‰์ด ์žˆ๋Š”๊ฐ€? + โ”œโ”€ Yes โ†’ ์„ ํƒ๋œ ํ–‰๋งŒ ๋‹ค์šด๋กœ๋“œ โŒ (์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜์ง€ ์•Š์Œ) + โ””โ”€ No โ†’ ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ๋งŒ (10๊ฐœ ๋“ฑ) โŒ (์ „์ฒด๊ฐ€ ์•„๋‹˜) +``` + +### After (์ˆ˜์ • - ์˜ฌ๋ฐ”๋ฅธ ๋™์ž‘) + +``` +์—‘์…€ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ํด๋ฆญ +โ†“ +๐Ÿ”’ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ: company_code ์ž๋™ ํ•„ํ„ฐ๋ง +โ†“ +๐Ÿ” ํ•„ํ„ฐ ์กฐ๊ฑด: ์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•œ ๊ฒ€์ƒ‰/ํ•„ํ„ฐ ์ ์šฉ +โ†“ +๐Ÿ“Š ๋ฐ์ดํ„ฐ ์กฐํšŒ: ์ „์ฒด ํ•„ํ„ฐ๋ง๋œ ๋ฐ์ดํ„ฐ (์ตœ๋Œ€ 10,000๊ฐœ) +โ†“ +๐ŸŽจ ์ปฌ๋Ÿผ ํ•„ํ„ฐ๋ง: ํ™”๋ฉด์— ํ‘œ์‹œ๋œ ์ปฌ๋Ÿผ๋งŒ +โ†“ +๐Ÿท๏ธ ๋ผ๋ฒจ ์ ์šฉ: ์ปฌ๋Ÿผ๋ช… โ†’ ํ•œ๊ธ€ ๋ผ๋ฒจ๋ช… +โ†“ +๐Ÿ’พ ์—‘์…€ ๋‹ค์šด๋กœ๋“œ +``` + +--- + +## ๐ŸŽฏ ์ˆ˜์ •๋œ ๋ฐ์ดํ„ฐ ์šฐ์„ ์ˆœ์œ„ + +### โŒ ์ œ๊ฑฐ: ์„ ํƒ๋œ ํ–‰ ๋‹ค์šด๋กœ๋“œ + +```typescript +// โŒ ์‚ญ์ œํ•  ์ฝ”๋“œ +if (context.selectedRowsData && context.selectedRowsData.length > 0) { + dataToExport = context.selectedRowsData; // ๋ถˆํ•„์š”! +} +``` + +### โœ… ์ƒˆ๋กœ์šด ์šฐ์„ ์ˆœ์œ„ + +```typescript +// โœ… ํ•ญ์ƒ API ํ˜ธ์ถœ๋กœ ์ „์ฒด ํ•„ํ„ฐ๋ง๋œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ +const response = await entityJoinApi.getTableDataWithJoins(context.tableName, { + page: 1, + size: 10000, // ์ตœ๋Œ€ 10,000๊ฐœ + sortBy: context.sortBy || "id", + sortOrder: (context.sortOrder || "asc") as "asc" | "desc", + search: context.filterConditions, // โœ… ํ•„ํ„ฐ ์กฐ๊ฑด + searchTerm: context.searchTerm, // โœ… ๊ฒ€์ƒ‰์–ด + autoFilter: true, // โœ… company_code ์ž๋™ ํ•„ํ„ฐ๋ง (๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ) + enableEntityJoin: true, // โœ… Entity ์กฐ์ธ (writer_name ๋“ฑ) +}); + +dataToExport = response.data; // ํ•„ํ„ฐ๋ง๋œ ์ „์ฒด ๋ฐ์ดํ„ฐ +``` + +--- + +## ๐Ÿ“ ์ˆ˜์ • ์‚ฌํ•ญ + +### 1. `buttonActions.ts` - handleExcelDownload ๋ฆฌํŒฉํ† ๋ง + +**ํŒŒ์ผ**: `frontend/lib/utils/buttonActions.ts` + +#### ๋ณ€๊ฒฝ ์ „ + +```typescript +// โŒ ์ž˜๋ชป๋œ ์šฐ์„ ์ˆœ์œ„ +if (context.selectedRowsData && context.selectedRowsData.length > 0) { + dataToExport = context.selectedRowsData; // ์„ ํƒ๋œ ํ–‰๋งŒ +} +else if (context.tableDisplayData && context.tableDisplayData.length > 0) { + dataToExport = context.tableDisplayData; // ํ˜„์žฌ ํŽ˜์ด์ง€๋งŒ +} +``` + +#### ๋ณ€๊ฒฝ ํ›„ + +```typescript +private static async handleExcelDownload( + config: ButtonActionConfig, + context: ButtonActionContext +): Promise { + try { + let dataToExport: any[] = []; + + // โœ… ํ•ญ์ƒ API ํ˜ธ์ถœ๋กœ ํ•„ํ„ฐ๋ง๋œ ์ „์ฒด ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ + if (context.tableName) { + const { entityJoinApi } = await import("@/lib/api/entityJoin"); + + // ๐Ÿ”’ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ์ค€์ˆ˜: autoFilter๋กœ company_code ์ž๋™ ์ ์šฉ + const response = await entityJoinApi.getTableDataWithJoins(context.tableName, { + page: 1, + size: 10000, // ์ตœ๋Œ€ 10,000๊ฐœ + sortBy: context.sortBy || "id", + sortOrder: (context.sortOrder || "asc") as "asc" | "desc", + search: context.filterConditions, // โœ… ํ•„ํ„ฐ ์กฐ๊ฑด + searchTerm: context.searchTerm, // โœ… ๊ฒ€์ƒ‰์–ด + autoFilter: true, // โœ… company_code ์ž๋™ ํ•„ํ„ฐ๋ง (๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ) + enableEntityJoin: true, // โœ… Entity ์กฐ์ธ + }); + + if (response.success && response.data) { + dataToExport = response.data; + } else { + toast.error("๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); + return false; + } + } else { + toast.error("ํ…Œ์ด๋ธ” ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); + return false; + } + + // ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ์ข…๋ฃŒ + if (dataToExport.length === 0) { + toast.error("๋‹ค์šด๋กœ๋“œํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); + return false; + } + + // ๐ŸŽจ ์ปฌ๋Ÿผ ํ•„ํ„ฐ๋ง ๋ฐ ๋ผ๋ฒจ ์ ์šฉ + if (context.visibleColumns && context.visibleColumns.length > 0) { + const visibleColumns = context.visibleColumns; + const columnLabels = context.columnLabels || {}; + + dataToExport = dataToExport.map((row) => { + const filteredRow: Record = {}; + + visibleColumns.forEach((columnName) => { + // ๋ผ๋ฒจ ์šฐ์„  ์‚ฌ์šฉ, ์—†์œผ๋ฉด ์ปฌ๋Ÿผ๋ช… ์‚ฌ์šฉ + const label = columnLabels[columnName] || columnName; + filteredRow[label] = row[columnName]; + }); + + return filteredRow; + }); + } + + // ๐Ÿ’พ ์—‘์…€ ํŒŒ์ผ ์ƒ์„ฑ + const { exportToExcel } = await import("@/lib/utils/excelExport"); + + const fileName = + config.excelFileName || `${context.tableName}_${new Date().toISOString().split("T")[0]}.xlsx`; + const sheetName = config.excelSheetName || "Sheet1"; + + await exportToExcel(dataToExport, fileName, { + sheetName, + includeHeaders: config.excelIncludeHeaders !== false, + }); + + toast.success(`${dataToExport.length}๊ฐœ ํ–‰์ด ๋‹ค์šด๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`); + + return true; + } catch (error) { + console.error("์—‘์…€ ๋‹ค์šด๋กœ๋“œ ์˜ค๋ฅ˜:", error); + toast.error("์—‘์…€ ๋‹ค์šด๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); + return false; + } +} +``` + +--- + +## ๐Ÿ”’ ๋ณด์•ˆ ๊ฐ•ํ™” (๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ) + +### Before (์œ„ํ—˜) + +```typescript +// โŒ ๋ชจ๋“  ํšŒ์‚ฌ ๋ฐ์ดํ„ฐ ๋…ธ์ถœ +await dynamicFormApi.getTableData(tableName, { + pageSize: 10000, // ํ•„ํ„ฐ ์—†์Œ! +}); +``` + +### After (์•ˆ์ „) + +```typescript +// โœ… ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ์ค€์ˆ˜ +await entityJoinApi.getTableDataWithJoins(tableName, { + size: 10000, + search: filterConditions, // ํ•„ํ„ฐ ์กฐ๊ฑด + searchTerm: searchTerm, // ๊ฒ€์ƒ‰์–ด + autoFilter: true, // company_code ์ž๋™ ํ•„ํ„ฐ๋ง โœ… + enableEntityJoin: true, // Entity ์กฐ์ธ โœ… +}); +``` + +--- + +## โœ… ๊ตฌํ˜„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +### Step 1: handleExcelDownload ๋‹จ์ˆœํ™” + +- [ ] ์„ ํƒ๋œ ํ–‰ ๋‹ค์šด๋กœ๋“œ ๋กœ์ง ์ œ๊ฑฐ (`context.selectedRowsData` ์ฒดํฌ ์‚ญ์ œ) +- [ ] ํ™”๋ฉด ํ‘œ์‹œ ๋ฐ์ดํ„ฐ ๋กœ์ง ์ œ๊ฑฐ (`context.tableDisplayData` ์ฒดํฌ ์‚ญ์ œ) +- [ ] ํ•ญ์ƒ API ํ˜ธ์ถœ๋กœ ๋ณ€๊ฒฝ (entityJoinApi.getTableDataWithJoins) +- [ ] ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ํ•„์ˆ˜ ์ ์šฉ (`autoFilter: true`) +- [ ] ํ•„ํ„ฐ ์กฐ๊ฑด ์ „๋‹ฌ (`search`, `searchTerm`) + +### Step 2: ์ปฌ๋Ÿผ ํ•„ํ„ฐ๋ง ๋ฐ ๋ผ๋ฒจ ์ ์šฉ + +- [ ] `context.visibleColumns`๋กœ ํ•„ํ„ฐ๋ง +- [ ] `context.columnLabels`๋กœ ๋ผ๋ฒจ ๋ณ€ํ™˜ +- [ ] ๋ผ๋ฒจ ์šฐ์„ , ์—†์œผ๋ฉด ์ปฌ๋Ÿผ๋ช… ์‚ฌ์šฉ + +### Step 3: ํ…Œ์ŠคํŠธ + +- [ ] ํ•„ํ„ฐ ์—†์ด ๋‹ค์šด๋กœ๋“œ โ†’ ์ „์ฒด ๋ฐ์ดํ„ฐ (company_code ํ•„ํ„ฐ๋ง) +- [ ] ๊ฒ€์ƒ‰์–ด ์ž…๋ ฅ ํ›„ ๋‹ค์šด๋กœ๋“œ โ†’ ๊ฒ€์ƒ‰๋œ ๋ฐ์ดํ„ฐ๋งŒ +- [ ] ํ•„ํ„ฐ ์„ค์ • ํ›„ ๋‹ค์šด๋กœ๋“œ โ†’ ํ•„ํ„ฐ๋ง๋œ ๋ฐ์ดํ„ฐ๋งŒ +- [ ] ์ปฌ๋Ÿผ ์ˆจ๊ธฐ๊ธฐ ํ›„ ๋‹ค์šด๋กœ๋“œ โ†’ ํ‘œ์‹œ๋œ ์ปฌ๋Ÿผ๋งŒ +- [ ] ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ํ…Œ์ŠคํŠธ โ†’ ๋‹ค๋ฅธ ํšŒ์‚ฌ ๋ฐ์ดํ„ฐ ์•ˆ ๋ณด์ž„ +- [ ] 10,000๊ฐœ ์ œํ•œ ํ™•์ธ + +### Step 4: ๋ฌธ์„œํ™” + +- [ ] ์ฃผ์„ ์ถ”๊ฐ€ +- [ ] ๊ณ„ํš์„œ ์—…๋ฐ์ดํŠธ +- [ ] ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์ž‘์„ฑ + +--- + +## ๐Ÿš€ ์˜ˆ์ƒ ํšจ๊ณผ + +1. **๋ณด์•ˆ ๊ฐ•ํ™”**: ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ 100% ์ค€์ˆ˜ +2. **์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„ **: ํ•„ํ„ฐ๋ง๋œ ์ „์ฒด ๋ฐ์ดํ„ฐ ๋‹ค์šด๋กœ๋“œ +3. **์ง๊ด€์ ์ธ ๋™์ž‘**: ํ™”๋ฉด์— ๋ณด์ด๋Š” ๋Œ€๋กœ ๋‹ค์šด๋กœ๋“œ +4. **ํ•œ๊ธ€ ์ง€์›**: ์ปฌ๋Ÿผ ๋ผ๋ฒจ๋ช…์œผ๋กœ ์—‘์…€ ์ƒ์„ฑ + +--- + +## ๐Ÿค ์Šน์ธ + +**์‚ฌ์šฉ์ž ์Šน์ธ**: โฌœ ๋Œ€๊ธฐ ์ค‘ + +--- + +**์ž‘์„ฑ ์™„๋ฃŒ**: 2025-01-10 +**๋‹ค์Œ ์—…๋ฐ์ดํŠธ**: ๊ตฌํ˜„ ์™„๋ฃŒ ํ›„ + diff --git a/frontend/components/screen/RealtimePreview.tsx b/frontend/components/screen/RealtimePreview.tsx index ab8cc3ae..81f8dc21 100644 --- a/frontend/components/screen/RealtimePreview.tsx +++ b/frontend/components/screen/RealtimePreview.tsx @@ -563,7 +563,7 @@ export const RealtimePreviewDynamic: React.FC = ({ {/* ์œ„์ ฏ ํƒ€์ž… - ๋™์  ๋ Œ๋”๋ง (ํŒŒ์ผ ์ปดํฌ๋„ŒํŠธ ์ œ์™ธ) */} {type === "widget" && !isFileComponent(component) && ( -
+
= ({ } : component; + // componentStyle์—์„œ width, height ์ œ๊ฑฐ (size.width, size.height๋งŒ ์‚ฌ์šฉ) + const { width: _styleWidth, height: _styleHeight, ...restComponentStyle } = componentStyle || {}; + const baseStyle = { left: `${position.x}px`, top: `${position.y}px`, - width: getWidth(), // getWidth()๊ฐ€ ๋ชจ๋“  ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ฒ˜๋ฆฌ - height: getHeight(), + ...restComponentStyle, // width/height ์ œ์™ธํ•œ ์Šคํƒ€์ผ ๋จผ์ € ์ ์šฉ + width: getWidth(), // size.width๋กœ ๋ฎ์–ด์“ฐ๊ธฐ + height: getHeight(), // size.height๋กœ ๋ฎ์–ด์“ฐ๊ธฐ zIndex: component.type === "layout" ? 1 : position.z || 2, - ...componentStyle, right: undefined, }; diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index 92d3f560..54f26a8d 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -25,18 +25,44 @@ import { restoreAbsolutePositions, } from "@/lib/utils/groupingUtils"; import { - calculateGridInfo, - snapToGrid, - snapSizeToGrid, - generateGridLines, - updateSizeFromGridColumns, adjustGridColumnsFromSize, - alignGroupChildrenToGrid, - calculateOptimalGroupSize, - normalizeGroupChildPositions, + updateSizeFromGridColumns, calculateWidthFromColumns, - GridSettings as GridUtilSettings, + snapSizeToGrid, + snapToGrid, } from "@/lib/utils/gridUtils"; + +// 10px ๋‹จ์œ„ ์Šค๋ƒ… ํ•จ์ˆ˜ +const snapTo10px = (value: number): number => { + return Math.round(value / 10) * 10; +}; + +const snapPositionTo10px = (position: Position): Position => { + return { + x: snapTo10px(position.x), + y: snapTo10px(position.y), + z: position.z, + }; +}; + +const snapSizeTo10px = (size: { width: number; height: number }): { width: number; height: number } => { + return { + width: snapTo10px(size.width), + height: snapTo10px(size.height), + }; +}; + +// calculateGridInfo ๋”๋ฏธ ํ•จ์ˆ˜ (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด ์œ ์ง€) +const calculateGridInfo = (width: number, height: number, settings: any) => { + return { + columnWidth: 10, + totalWidth: width, + totalHeight: height, + columns: settings.columns || 12, + gap: settings.gap || 0, + padding: settings.padding || 0, + }; +}; import { GroupingToolbar } from "./GroupingToolbar"; import { screenApi, tableTypeApi } from "@/lib/api/screen"; import { tableManagementApi } from "@/lib/api/tableManagement"; @@ -57,7 +83,6 @@ import { TemplatesPanel, TemplateComponent } from "./panels/TemplatesPanel"; import { ComponentsPanel } from "./panels/ComponentsPanel"; import PropertiesPanel from "./panels/PropertiesPanel"; import DetailSettingsPanel from "./panels/DetailSettingsPanel"; -import GridPanel from "./panels/GridPanel"; import ResolutionPanel from "./panels/ResolutionPanel"; import { usePanelState, PanelConfig } from "@/hooks/usePanelState"; import { FlowButtonGroup } from "./widgets/FlowButtonGroup"; @@ -281,55 +306,24 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD const canvasRef = useRef(null); - // ๊ฒฉ์ž ์ •๋ณด ๊ณ„์‚ฐ - const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 }); - - const gridInfo = useMemo(() => { - if (!layout.gridSettings) return null; - - // ์บ”๋ฒ„์Šค ํฌ๊ธฐ ๊ณ„์‚ฐ (ํ•ด์ƒ๋„ ์„ค์ • ์šฐ์„ ) - let width = screenResolution.width; - let height = screenResolution.height; - - // ํ•ด์ƒ๋„๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ - if (!width || !height) { - width = canvasSize.width || window.innerWidth - 100; - height = canvasSize.height || window.innerHeight - 200; - } - - const newGridInfo = calculateGridInfo(width, height, { - columns: layout.gridSettings.columns, - gap: layout.gridSettings.gap, - padding: layout.gridSettings.padding, - snapToGrid: layout.gridSettings.snapToGrid || false, - }); - - return newGridInfo; - }, [layout.gridSettings, screenResolution]); - - // ๊ฒฉ์ž ๋ผ์ธ ์ƒ์„ฑ + // 10px ๊ฒฉ์ž ๋ผ์ธ ์ƒ์„ฑ (์‹œ๊ฐ์  ๊ฐ€์ด๋“œ์šฉ) const gridLines = useMemo(() => { - if (!gridInfo || !layout.gridSettings?.showGrid) return []; + if (!layout.gridSettings?.showGrid) return []; - // ์บ”๋ฒ„์Šค ํฌ๊ธฐ๋Š” ํ•ด์ƒ๋„ ํฌ๊ธฐ ์‚ฌ์šฉ const width = screenResolution.width; const height = screenResolution.height; + const lines: Array<{ type: "vertical" | "horizontal"; position: number }> = []; - const lines = generateGridLines(width, height, { - columns: layout.gridSettings.columns, - gap: layout.gridSettings.gap, - padding: layout.gridSettings.padding, - snapToGrid: layout.gridSettings.snapToGrid || false, - }); + // 10px ๋‹จ์œ„๋กœ ๊ฒฉ์ž ๋ผ์ธ ์ƒ์„ฑ + for (let x = 0; x <= width; x += 10) { + lines.push({ type: "vertical", position: x }); + } + for (let y = 0; y <= height; y += 10) { + lines.push({ type: "horizontal", position: y }); + } - // ์ˆ˜์ง์„ ๊ณผ ์ˆ˜ํ‰์„ ์„ ํ•˜๋‚˜์˜ ๋ฐฐ์—ด๋กœ ํ•ฉ์น˜๊ธฐ - const allLines = [ - ...lines.verticalLines.map((pos) => ({ type: "vertical" as const, position: pos })), - ...lines.horizontalLines.map((pos) => ({ type: "horizontal" as const, position: pos })), - ]; - - return allLines; - }, [gridInfo, layout.gridSettings, screenResolution]); + return lines; + }, [layout.gridSettings?.showGrid, screenResolution.width, screenResolution.height]); // ํ•„ํ„ฐ๋œ ํ…Œ์ด๋ธ” ๋ชฉ๋ก const filteredTables = useMemo(() => { @@ -527,64 +521,61 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD const finalKey = pathParts[pathParts.length - 1]; current[finalKey] = value; - // gridColumns ๋ณ€๊ฒฝ ์‹œ ํฌ๊ธฐ ์ž๋™ ์—…๋ฐ์ดํŠธ - if (path === "gridColumns" && gridInfo) { - const updatedSize = updateSizeFromGridColumns(newComp, gridInfo, layout.gridSettings as GridUtilSettings); - newComp.size = updatedSize; - } + // gridColumns ๋ณ€๊ฒฝ ์‹œ ํฌ๊ธฐ ์ž๋™ ์—…๋ฐ์ดํŠธ ์ œ๊ฑฐ (๊ฒฉ์ž ์‹œ์Šคํ…œ ์ œ๊ฑฐ๋จ) + // if (path === "gridColumns" && prevLayout.gridSettings) { + // const updatedSize = updateSizeFromGridColumns(newComp, prevLayout.gridSettings as GridUtilSettings); + // newComp.size = updatedSize; + // } - // ํฌ๊ธฐ ๋ณ€๊ฒฝ ์‹œ ๊ฒฉ์ž ์Šค๋ƒ… ์ ์šฉ (๊ทธ๋ฃน ์ปดํฌ๋„ŒํŠธ ์ œ์™ธ) - if ( - (path === "size.width" || path === "size.height") && - prevLayout.gridSettings?.snapToGrid && - gridInfo && - newComp.type !== "group" - ) { - // ํ˜„์žฌ ํ•ด์ƒ๋„์— ๋งž๋Š” ๊ฒฉ์ž ์ •๋ณด๋กœ ์Šค๋ƒ… ์ ์šฉ - const currentGridInfo = calculateGridInfo(screenResolution.width, screenResolution.height, { - columns: prevLayout.gridSettings.columns, - gap: prevLayout.gridSettings.gap, - padding: prevLayout.gridSettings.padding, - snapToGrid: prevLayout.gridSettings.snapToGrid || false, - }); - const snappedSize = snapSizeToGrid( - newComp.size, - currentGridInfo, - prevLayout.gridSettings as GridUtilSettings, - ); - newComp.size = snappedSize; + // ํฌ๊ธฐ ๋ณ€๊ฒฝ ์‹œ ๊ฒฉ์ž ์Šค๋ƒ… ์ ์šฉ ์ œ๊ฑฐ (์ง์ ‘ ์ž…๋ ฅ ์‹œ ๋ถˆํ•„์š”) + // ๋“œ๋ž˜๊ทธ/๋ฆฌ์‚ฌ์ด์ฆˆ ์‹œ์—๋Š” ๋ณ„๋„ ๋กœ์ง์—์„œ ์ฒ˜๋ฆฌ๋จ + // if ( + // (path === "size.width" || path === "size.height") && + // prevLayout.gridSettings?.snapToGrid && + // newComp.type !== "group" + // ) { + // const currentGridInfo = calculateGridInfo(screenResolution.width, screenResolution.height, { + // columns: prevLayout.gridSettings.columns, + // gap: prevLayout.gridSettings.gap, + // padding: prevLayout.gridSettings.padding, + // snapToGrid: prevLayout.gridSettings.snapToGrid || false, + // }); + // const snappedSize = snapSizeToGrid( + // newComp.size, + // currentGridInfo, + // prevLayout.gridSettings as GridUtilSettings, + // ); + // newComp.size = snappedSize; + // + // const adjustedColumns = adjustGridColumnsFromSize( + // newComp, + // currentGridInfo, + // prevLayout.gridSettings as GridUtilSettings, + // ); + // if (newComp.gridColumns !== adjustedColumns) { + // newComp.gridColumns = adjustedColumns; + // } + // } - // ํฌ๊ธฐ ๋ณ€๊ฒฝ ์‹œ gridColumns๋„ ์ž๋™ ์กฐ์ • - const adjustedColumns = adjustGridColumnsFromSize( - newComp, - currentGridInfo, - prevLayout.gridSettings as GridUtilSettings, - ); - if (newComp.gridColumns !== adjustedColumns) { - newComp.gridColumns = adjustedColumns; - } - } - - // gridColumns ๋ณ€๊ฒฝ ์‹œ ํฌ๊ธฐ๋ฅผ ๊ฒฉ์ž์— ๋งž๊ฒŒ ์ž๋™ ์กฐ์ • - if (path === "gridColumns" && prevLayout.gridSettings?.snapToGrid && newComp.type !== "group") { - const currentGridInfo = calculateGridInfo(screenResolution.width, screenResolution.height, { - columns: prevLayout.gridSettings.columns, - gap: prevLayout.gridSettings.gap, - padding: prevLayout.gridSettings.padding, - snapToGrid: prevLayout.gridSettings.snapToGrid || false, - }); - - // gridColumns์— ๋งž๋Š” ์ •ํ™•ํ•œ ๋„ˆ๋น„ ๊ณ„์‚ฐ - const newWidth = calculateWidthFromColumns( - newComp.gridColumns, - currentGridInfo, - prevLayout.gridSettings as GridUtilSettings, - ); - newComp.size = { - ...newComp.size, - width: newWidth, - }; - } + // gridColumns ๋ณ€๊ฒฝ ์‹œ ํฌ๊ธฐ๋ฅผ ๊ฒฉ์ž์— ๋งž๊ฒŒ ์ž๋™ ์กฐ์ • ์ œ๊ฑฐ (๊ฒฉ์ž ์‹œ์Šคํ…œ ์ œ๊ฑฐ๋จ) + // if (path === "gridColumns" && prevLayout.gridSettings?.snapToGrid && newComp.type !== "group") { + // const currentGridInfo = calculateGridInfo(screenResolution.width, screenResolution.height, { + // columns: prevLayout.gridSettings.columns, + // gap: prevLayout.gridSettings.gap, + // padding: prevLayout.gridSettings.padding, + // snapToGrid: prevLayout.gridSettings.snapToGrid || false, + // }); + // + // const newWidth = calculateWidthFromColumns( + // newComp.gridColumns, + // currentGridInfo, + // prevLayout.gridSettings as GridUtilSettings, + // ); + // newComp.size = { + // ...newComp.size, + // width: newWidth, + // }; + // } // ์œ„์น˜ ๋ณ€๊ฒฝ ์‹œ ๊ฒฉ์ž ์Šค๋ƒ… ์ ์šฉ (๊ทธ๋ฃน ๋‚ด๋ถ€ ์ปดํฌ๋„ŒํŠธ ํฌํ•จ) if ( @@ -634,7 +625,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD }; } else if (newComp.type !== "group") { // ๊ทธ๋ฃน์ด ์•„๋‹Œ ์ผ๋ฐ˜ ์ปดํฌ๋„ŒํŠธ๋งŒ ๊ฒฉ์ž ์Šค๋ƒ… ์ ์šฉ - const snappedPosition = snapToGrid( + const snappedPosition = snapPositionTo10px( newComp.position, currentGridInfo, layout.gridSettings as GridUtilSettings, @@ -684,7 +675,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD return newLayout; }); }, - [gridInfo, saveToHistory], // ๐Ÿ”ง layout, selectedComponent ์ œ๊ฑฐ! + [saveToHistory], ); // ์ปดํฌ๋„ŒํŠธ ์‹œ์Šคํ…œ ์ดˆ๊ธฐํ™” @@ -1093,7 +1084,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD columns: newGridSettings.columns, gap: newGridSettings.gap, padding: newGridSettings.padding, - snapToGrid: newGridSettings.snapToGrid, + snapToGrid: true, // ํ•ญ์ƒ 10px ์Šค๋ƒ… ํ™œ์„ฑํ™” }; const adjustedComponents = layout.components.map((comp) => { @@ -1208,7 +1199,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD columns: layout.gridSettings.columns, gap: layout.gridSettings.gap, padding: layout.gridSettings.padding, - snapToGrid: layout.gridSettings.snapToGrid, + snapToGrid: true, }; finalComponents = scaledComponents.map((comp) => { @@ -1278,7 +1269,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD columns: layout.gridSettings.columns, gap: layout.gridSettings.gap, padding: layout.gridSettings.padding, - snapToGrid: layout.gridSettings.snapToGrid, + snapToGrid: true, }; const adjustedComponents = layout.components.map((comp) => { @@ -1450,7 +1441,11 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD // ๊ฒฉ์ž ์Šค๋ƒ… ์ ์šฉ const finalPosition = layout.gridSettings?.snapToGrid && currentGridInfo - ? snapToGrid({ x: absoluteX, y: absoluteY, z: 1 }, currentGridInfo, layout.gridSettings as GridUtilSettings) + ? snapPositionTo10px( + { x: absoluteX, y: absoluteY, z: 1 }, + currentGridInfo, + layout.gridSettings as GridUtilSettings, + ) : { x: absoluteX, y: absoluteY, z: 1 }; if (templateComp.type === "container") { @@ -1516,7 +1511,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD templateSize: templateComp.size, calculatedSize, hasGridInfo: !!currentGridInfo, - hasGridSettings: !!layout.gridSettings?.snapToGrid, + hasGridSettings: !!layout.gridSettings, }); return { @@ -1807,7 +1802,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD toast.success(`${template.name} ํ…œํ”Œ๋ฆฟ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`); }, - [layout, gridInfo, selectedScreen, snapToGrid, saveToHistory], + [layout, selectedScreen, saveToHistory], ); // ๋ ˆ์ด์•„์›ƒ ๋“œ๋ž˜๊ทธ ์ฒ˜๋ฆฌ @@ -1877,7 +1872,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD toast.success(`${layoutData.label} ๋ ˆ์ด์•„์›ƒ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`); }, - [layout, gridInfo, screenResolution, snapToGrid, saveToHistory, zoomLevel], + [layout, screenResolution, saveToHistory, zoomLevel], ); // handleZoneComponentDrop์€ handleComponentDrop์œผ๋กœ ๋Œ€์ฒด๋จ @@ -2022,7 +2017,11 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD // ๊ฒฉ์ž ์Šค๋ƒ… ์ ์šฉ const snappedPosition = layout.gridSettings?.snapToGrid && currentGridInfo - ? snapToGrid({ x: boundedX, y: boundedY, z: 1 }, currentGridInfo, layout.gridSettings as GridUtilSettings) + ? snapPositionTo10px( + { x: boundedX, y: boundedY, z: 1 }, + currentGridInfo, + layout.gridSettings as GridUtilSettings, + ) : { x: boundedX, y: boundedY, z: 1 }; console.log("๐Ÿงฉ ์ปดํฌ๋„ŒํŠธ ๋“œ๋กญ:", { @@ -2131,21 +2130,12 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD }); } - // ๊ทธ๋ฆฌ๋“œ ์‹œ์Šคํ…œ์ด ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ gridColumns์— ๋งž์ถฐ ๋„ˆ๋น„ ์žฌ๊ณ„์‚ฐ - if (layout.gridSettings?.snapToGrid && gridInfo) { - // gridColumns์— ๋งž๋Š” ์ •ํ™•ํ•œ ๋„ˆ๋น„ ๊ณ„์‚ฐ - const calculatedWidth = calculateWidthFromColumns( - gridColumns, - gridInfo, - layout.gridSettings as GridUtilSettings, - ); - - // ์ปดํฌ๋„ŒํŠธ๋ณ„ ์ตœ์†Œ ํฌ๊ธฐ ๋ณด์žฅ - const minWidth = isTableList ? 120 : isCardDisplay ? 400 : component.defaultSize.width; - + // 10px ๋‹จ์œ„๋กœ ๋„ˆ๋น„ ์Šค๋ƒ… + if (layout.gridSettings?.snapToGrid) { componentSize = { ...component.defaultSize, - width: Math.max(calculatedWidth, minWidth), + width: snapTo10px(component.defaultSize.width), + height: snapTo10px(component.defaultSize.height), }; } @@ -2234,7 +2224,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD toast.success(`${component.name} ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`); }, - [layout, gridInfo, selectedScreen, snapToGrid, saveToHistory], + [layout, selectedScreen, saveToHistory], ); // ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ ์ฒ˜๋ฆฌ @@ -2309,74 +2299,44 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD }; } else if (type === "column") { // console.log("๐Ÿ”„ ์ปฌ๋Ÿผ ๋“œ๋กญ ์ฒ˜๋ฆฌ:", { webType: column.widgetType, columnName: column.columnName }); - // ํ˜„์žฌ ํ•ด์ƒ๋„์— ๋งž๋Š” ๊ฒฉ์ž ์ •๋ณด๋กœ ๊ธฐ๋ณธ ํฌ๊ธฐ ๊ณ„์‚ฐ - const currentGridInfo = layout.gridSettings - ? calculateGridInfo(screenResolution.width, screenResolution.height, { - columns: layout.gridSettings.columns, - gap: layout.gridSettings.gap, - padding: layout.gridSettings.padding, - snapToGrid: layout.gridSettings.snapToGrid || false, - }) - : null; - // ๊ฒฉ์ž ์Šค๋ƒ…์ด ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ ์ •ํ™•ํ•œ ๊ฒฉ์ž ํฌ๊ธฐ๋กœ ์ƒ์„ฑ, ์•„๋‹ˆ๋ฉด ๊ธฐ๋ณธ๊ฐ’ - const defaultWidth = - currentGridInfo && layout.gridSettings?.snapToGrid - ? calculateWidthFromColumns(1, currentGridInfo, layout.gridSettings as GridUtilSettings) - : 200; - - console.log("๐ŸŽฏ ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ ์‹œ ํฌ๊ธฐ ๊ณ„์‚ฐ:", { - screenResolution: `${screenResolution.width}x${screenResolution.height}`, - gridSettings: layout.gridSettings, - currentGridInfo: currentGridInfo - ? { - columnWidth: currentGridInfo.columnWidth.toFixed(2), - totalWidth: currentGridInfo.totalWidth, - } - : null, - defaultWidth: defaultWidth.toFixed(2), - snapToGrid: layout.gridSettings?.snapToGrid, - }); - - // ์›นํƒ€์ž…๋ณ„ ๊ธฐ๋ณธ ๊ทธ๋ฆฌ๋“œ ์ปฌ๋Ÿผ ์ˆ˜ ๊ณ„์‚ฐ - const getDefaultGridColumns = (widgetType: string): number => { + // ์›นํƒ€์ž…๋ณ„ ๊ธฐ๋ณธ ๋„ˆ๋น„ ๊ณ„์‚ฐ (10px ๋‹จ์œ„ ๊ณ ์ •) + const getDefaultWidth = (widgetType: string): number => { const widthMap: Record = { - // ํ…์ŠคํŠธ ์ž…๋ ฅ ๊ณ„์—ด (๋„“๊ฒŒ) - text: 4, // 1/3 (33%) - email: 4, // 1/3 (33%) - tel: 3, // 1/4 (25%) - url: 4, // 1/3 (33%) - textarea: 6, // ์ ˆ๋ฐ˜ (50%) + // ํ…์ŠคํŠธ ์ž…๋ ฅ ๊ณ„์—ด + text: 200, + email: 200, + tel: 150, + url: 250, + textarea: 300, - // ์ˆซ์ž/๋‚ ์งœ ์ž…๋ ฅ (์ค‘๊ฐ„) - number: 2, // 2/12 (16.67%) - decimal: 2, // 2/12 (16.67%) - date: 3, // 1/4 (25%) - datetime: 3, // 1/4 (25%) - time: 2, // 2/12 (16.67%) + // ์ˆซ์ž/๋‚ ์งœ ์ž…๋ ฅ + number: 120, + decimal: 120, + date: 150, + datetime: 180, + time: 120, - // ์„ ํƒ ์ž…๋ ฅ (์ค‘๊ฐ„) - select: 3, // 1/4 (25%) - radio: 3, // 1/4 (25%) - checkbox: 2, // 2/12 (16.67%) - boolean: 2, // 2/12 (16.67%) + // ์„ ํƒ ์ž…๋ ฅ + select: 180, + radio: 180, + checkbox: 120, + boolean: 120, - // ์ฝ”๋“œ/์ฐธ์กฐ (๋„“๊ฒŒ) - code: 3, // 1/4 (25%) - entity: 4, // 1/3 (33%) + // ์ฝ”๋“œ/์ฐธ์กฐ + code: 180, + entity: 200, - // ํŒŒ์ผ/์ด๋ฏธ์ง€ (๋„“๊ฒŒ) - file: 4, // 1/3 (33%) - image: 3, // 1/4 (25%) + // ํŒŒ์ผ/์ด๋ฏธ์ง€ + file: 250, + image: 200, // ๊ธฐํƒ€ - button: 2, // 2/12 (16.67%) - label: 2, // 2/12 (16.67%) + button: 100, + label: 100, }; - const defaultColumns = widthMap[widgetType] || 3; // ๊ธฐ๋ณธ๊ฐ’ 3 (1/4, 25%) - console.log("๐ŸŽฏ [ScreenDesigner] getDefaultGridColumns:", { widgetType, defaultColumns }); - return defaultColumns; + return widthMap[widgetType] || 200; // ๊ธฐ๋ณธ๊ฐ’ 200px }; // ์›นํƒ€์ž…๋ณ„ ๊ธฐ๋ณธ ๋†’์ด ๊ณ„์‚ฐ @@ -2544,24 +2504,12 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD const componentId = getComponentIdFromWebType(column.widgetType); // console.log(`๐Ÿ”„ ํผ ์ปจํ…Œ์ด๋„ˆ ๋“œ๋กญ: ${column.widgetType} โ†’ ${componentId}`); - // ์›นํƒ€์ž…๋ณ„ ์ ์ ˆํ•œ gridColumns ๊ณ„์‚ฐ - const calculatedGridColumns = getDefaultGridColumns(column.widgetType); - - // gridColumns์— ๋งž๋Š” ์‹ค์ œ ๋„ˆ๋น„ ๊ณ„์‚ฐ - const componentWidth = - currentGridInfo && layout.gridSettings?.snapToGrid - ? calculateWidthFromColumns( - calculatedGridColumns, - currentGridInfo, - layout.gridSettings as GridUtilSettings, - ) - : defaultWidth; + // ์›นํƒ€์ž…๋ณ„ ๊ธฐ๋ณธ ๋„ˆ๋น„ ๊ณ„์‚ฐ (10px ๋‹จ์œ„ ๊ณ ์ •) + const componentWidth = getDefaultWidth(column.widgetType); console.log("๐ŸŽฏ ํผ ์ปจํ…Œ์ด๋„ˆ ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:", { widgetType: column.widgetType, - calculatedGridColumns, componentWidth, - defaultWidth, }); newComponent = { @@ -2576,7 +2524,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD componentType: componentId, // DynamicComponentRenderer์šฉ ์ปดํฌ๋„ŒํŠธ ํƒ€์ž… position: { x: relativeX, y: relativeY, z: 1 } as Position, size: { width: componentWidth, height: getDefaultHeight(column.widgetType) }, - gridColumns: calculatedGridColumns, // ์ฝ”๋“œ ํƒ€์ž…์ธ ๊ฒฝ์šฐ ์ฝ”๋“œ ์นดํ…Œ๊ณ ๋ฆฌ ์ •๋ณด ์ถ”๊ฐ€ ...(column.widgetType === "code" && column.codeCategory && { @@ -2588,7 +2535,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD labelColor: "#212121", labelFontWeight: "500", labelMarginBottom: "6px", - width: `${(calculatedGridColumns / (layout.gridSettings?.columns || 12)) * 100}%`, // ํผ์„ผํŠธ ๋„ˆ๋น„ }, componentConfig: { type: componentId, // text-input, number-input ๋“ฑ @@ -2611,36 +2557,14 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD const componentId = getComponentIdFromWebType(column.widgetType); // console.log(`๐Ÿ”„ ์บ”๋ฒ„์Šค ๋“œ๋กญ: ${column.widgetType} โ†’ ${componentId}`); - // ์›นํƒ€์ž…๋ณ„ ์ ์ ˆํ•œ gridColumns ๊ณ„์‚ฐ - const calculatedGridColumns = getDefaultGridColumns(column.widgetType); - - // gridColumns์— ๋งž๋Š” ์‹ค์ œ ๋„ˆ๋น„ ๊ณ„์‚ฐ - const componentWidth = - currentGridInfo && layout.gridSettings?.snapToGrid - ? calculateWidthFromColumns( - calculatedGridColumns, - currentGridInfo, - layout.gridSettings as GridUtilSettings, - ) - : defaultWidth; + // ์›นํƒ€์ž…๋ณ„ ๊ธฐ๋ณธ ๋„ˆ๋น„ ๊ณ„์‚ฐ (10px ๋‹จ์œ„ ๊ณ ์ •) + const componentWidth = getDefaultWidth(column.widgetType); console.log("๐ŸŽฏ ์บ”๋ฒ„์Šค ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:", { widgetType: column.widgetType, - calculatedGridColumns, componentWidth, - defaultWidth, }); - // ๐Ÿ” ์ด๋ฏธ์ง€ ํƒ€์ž… ๋“œ๋ž˜๊ทธ์•ค๋“œ๋กญ ๋””๋ฒ„๊น… - // if (column.widgetType === "image") { - // console.log("๐Ÿ–ผ๏ธ ์ด๋ฏธ์ง€ ์ปฌ๋Ÿผ ๋“œ๋ž˜๊ทธ์•ค๋“œ๋กญ:", { - // columnName: column.columnName, - // widgetType: column.widgetType, - // componentId, - // column, - // }); - // } - newComponent = { id: generateComponentId(), type: "component", // โœ… ์ƒˆ๋กœ์šด ์ปดํฌ๋„ŒํŠธ ์‹œ์Šคํ…œ ์‚ฌ์šฉ @@ -2652,7 +2576,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD componentType: componentId, // DynamicComponentRenderer์šฉ ์ปดํฌ๋„ŒํŠธ ํƒ€์ž… position: { x, y, z: 1 } as Position, size: { width: componentWidth, height: getDefaultHeight(column.widgetType) }, - gridColumns: calculatedGridColumns, // ์ฝ”๋“œ ํƒ€์ž…์ธ ๊ฒฝ์šฐ ์ฝ”๋“œ ์นดํ…Œ๊ณ ๋ฆฌ ์ •๋ณด ์ถ”๊ฐ€ ...(column.widgetType === "code" && column.codeCategory && { @@ -2664,7 +2587,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD labelColor: "#000000", // ์ˆœ์ˆ˜ํ•œ ๊ฒ€์ • labelFontWeight: "500", labelMarginBottom: "8px", - width: `${(calculatedGridColumns / (layout.gridSettings?.columns || 12)) * 100}%`, // ํผ์„ผํŠธ ๋„ˆ๋น„ }, componentConfig: { type: componentId, // text-input, number-input ๋“ฑ @@ -2684,31 +2606,15 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD return; } - // ๊ฒฉ์ž ์Šค๋ƒ… ์ ์šฉ (๊ทธ๋ฃน ์ปดํฌ๋„ŒํŠธ ์ œ์™ธ) + // 10px ๋‹จ์œ„ ์Šค๋ƒ… ์ ์šฉ (๊ทธ๋ฃน ์ปดํฌ๋„ŒํŠธ ์ œ์™ธ) if (layout.gridSettings?.snapToGrid && newComponent.type !== "group") { - // ํ˜„์žฌ ํ•ด์ƒ๋„์— ๋งž๋Š” ๊ฒฉ์ž ์ •๋ณด ๊ณ„์‚ฐ - const currentGridInfo = calculateGridInfo(screenResolution.width, screenResolution.height, { - columns: layout.gridSettings.columns, - gap: layout.gridSettings.gap, - padding: layout.gridSettings.padding, - snapToGrid: layout.gridSettings.snapToGrid || false, - }); + newComponent.position = snapPositionTo10px(newComponent.position); + newComponent.size = snapSizeTo10px(newComponent.size); - const gridUtilSettings = { - columns: layout.gridSettings.columns, - gap: layout.gridSettings.gap, - padding: layout.gridSettings.padding, - snapToGrid: layout.gridSettings.snapToGrid || false, - }; - newComponent.position = snapToGrid(newComponent.position, currentGridInfo, gridUtilSettings); - newComponent.size = snapSizeToGrid(newComponent.size, currentGridInfo, gridUtilSettings); - - console.log("๐Ÿงฒ ์ƒˆ ์ปดํฌ๋„ŒํŠธ ๊ฒฉ์ž ์Šค๋ƒ… ์ ์šฉ:", { + console.log("๐Ÿงฒ ์ƒˆ ์ปดํฌ๋„ŒํŠธ 10px ์Šค๋ƒ… ์ ์šฉ:", { type: newComponent.type, - resolution: `${screenResolution.width}x${screenResolution.height}`, snappedPosition: newComponent.position, snappedSize: newComponent.size, - columnWidth: currentGridInfo.columnWidth, }); } @@ -2735,7 +2641,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD // console.error("๋“œ๋กญ ์ฒ˜๋ฆฌ ์‹คํŒจ:", error); } }, - [layout, gridInfo, saveToHistory], + [layout, saveToHistory], ); // ํŒŒ์ผ ์ปดํฌ๋„ŒํŠธ ์—…๋ฐ์ดํŠธ ์ฒ˜๋ฆฌ @@ -3014,7 +2920,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD // ์ผ๋ฐ˜ ์ปดํฌ๋„ŒํŠธ ๋ฐ ํ”Œ๋กœ์šฐ ๋ฒ„ํŠผ ๊ทธ๋ฃน์— ๊ฒฉ์ž ์Šค๋ƒ… ์ ์šฉ (์ผ๋ฐ˜ ๊ทธ๋ฃน ์ œ์™ธ) if (draggedComponent?.type !== "group" && layout.gridSettings?.snapToGrid && currentGridInfo) { - finalPosition = snapToGrid( + finalPosition = snapPositionTo10px( { x: dragState.currentPosition.x, y: dragState.currentPosition.y, @@ -3174,7 +3080,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD justFinishedDrag: false, })); }, 100); - }, [dragState, layout, gridInfo, saveToHistory]); + }, [dragState, layout, saveToHistory]); // ๋“œ๋ž˜๊ทธ ์„ ํƒ ์‹œ์ž‘ const startSelectionDrag = useCallback( @@ -3669,8 +3575,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD console.log("๐Ÿ”ง ๊ทธ๋ฃน ์ƒ์„ฑ ์‹œ์ž‘:", { selectedCount: selectedComponents.length, - snapToGrid: layout.gridSettings?.snapToGrid, - gridInfo: currentGridInfo, + snapToGrid: true, }); // ์ปดํฌ๋„ŒํŠธ ํฌ๊ธฐ ์กฐ์ • ๊ธฐ๋ฐ˜ ๊ทธ๋ฃน ํฌ๊ธฐ ๊ณ„์‚ฐ @@ -3834,12 +3739,12 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD size: optimizedGroupSize, gridColumns: groupComponent.gridColumns, componentsScaled: !!scaledComponents.length, - gridAligned: layout.gridSettings?.snapToGrid, + gridAligned: true, }); toast.success(`๊ทธ๋ฃน์ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค (${finalChildren.length}๊ฐœ ์ปดํฌ๋„ŒํŠธ)`); }, - [layout, saveToHistory, gridInfo], + [layout, saveToHistory], ); // ๊ทธ๋ฃน ์ƒ์„ฑ ํ•จ์ˆ˜ (๋‹ค์ด์–ผ๋กœ๊ทธ ํ‘œ์‹œ) @@ -3935,36 +3840,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD endSelectionDrag, ]); - // ์บ”๋ฒ„์Šค ํฌ๊ธฐ ์ดˆ๊ธฐํ™” ๋ฐ ๋ฆฌ์‚ฌ์ด์ฆˆ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ - useEffect(() => { - const updateCanvasSize = () => { - if (canvasRef.current) { - const rect = canvasRef.current.getBoundingClientRect(); - setCanvasSize({ width: rect.width, height: rect.height }); - } - }; - - // ์ดˆ๊ธฐ ํฌ๊ธฐ ์„ค์ • - updateCanvasSize(); - - // ๋ฆฌ์‚ฌ์ด์ฆˆ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ - window.addEventListener("resize", updateCanvasSize); - - return () => window.removeEventListener("resize", updateCanvasSize); - }, []); - - // ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ํ›„ ์บ”๋ฒ„์Šค ํฌ๊ธฐ ์—…๋ฐ์ดํŠธ - useEffect(() => { - const timer = setTimeout(() => { - if (canvasRef.current) { - const rect = canvasRef.current.getBoundingClientRect(); - setCanvasSize({ width: rect.width, height: rect.height }); - } - }, 100); - - return () => clearTimeout(timer); - }, [selectedScreen]); - // ํ‚ค๋ณด๋“œ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ (๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ณธ ๊ธฐ๋Šฅ ์™„์ „ ์ฐจ๋‹จ) useEffect(() => { const handleKeyDown = async (e: KeyboardEvent) => { @@ -4253,7 +4128,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD } return ( - +
{/* ์ƒ๋‹จ ์Šฌ๋ฆผ ํˆด๋ฐ” */} void; onResetGrid: () => void; - onForceGridUpdate?: () => void; // ๊ฐ•์ œ ๊ฒฉ์ž ์žฌ์กฐ์ • ์ถ”๊ฐ€ - screenResolution?: ScreenResolution; // ํ•ด์ƒ๋„ ์ •๋ณด ์ถ”๊ฐ€ + screenResolution?: ScreenResolution; } export const GridPanel: React.FC = ({ gridSettings, onGridSettingsChange, onResetGrid, - onForceGridUpdate, screenResolution, }) => { const updateSetting = (key: keyof GridSettings, value: any) => { @@ -33,32 +30,6 @@ export const GridPanel: React.FC = ({ }); }; - // ์ตœ๋Œ€ ์ปฌ๋Ÿผ ์ˆ˜ ๊ณ„์‚ฐ (์ตœ์†Œ ์ปฌ๋Ÿผ ๋„ˆ๋น„ 30px ๊ธฐ์ค€) - const MIN_COLUMN_WIDTH = 30; - const maxColumns = screenResolution - ? Math.floor((screenResolution.width - gridSettings.padding * 2 + gridSettings.gap) / (MIN_COLUMN_WIDTH + gridSettings.gap)) - : 24; - const safeMaxColumns = Math.max(1, Math.min(maxColumns, 100)); // ์ตœ๋Œ€ 100๊ฐœ๋กœ ์ œํ•œ - - // ์‹ค์ œ ๊ฒฉ์ž ์ •๋ณด ๊ณ„์‚ฐ - const actualGridInfo = screenResolution - ? calculateGridInfo(screenResolution.width, screenResolution.height, { - columns: gridSettings.columns, - gap: gridSettings.gap, - padding: gridSettings.padding, - snapToGrid: gridSettings.snapToGrid || false, - }) - : null; - - // ์‹ค์ œ ํ‘œ์‹œ๋˜๋Š” ์ปฌ๋Ÿผ ์ˆ˜ ๊ณ„์‚ฐ (ํ•ญ์ƒ ์„ค์ •๋œ ๊ฐœ์ˆ˜๋ฅผ ํ‘œ์‹œํ•˜๋˜, ๋„ˆ๋น„๊ฐ€ ๋„ˆ๋ฌด ์ž‘์œผ๋ฉด ๊ฒฝ๊ณ ) - const actualColumns = gridSettings.columns; - - // ์ปฌ๋Ÿผ์ด ๋„ˆ๋ฌด ์ž‘์€์ง€ ํ™•์ธ - const isColumnsTooSmall = - screenResolution && actualGridInfo - ? actualGridInfo.columnWidth < MIN_COLUMN_WIDTH - : false; - return (
{/* ํ—ค๋” */} @@ -69,25 +40,10 @@ export const GridPanel: React.FC = ({

๊ฒฉ์ž ์„ค์ •

-
- {onForceGridUpdate && ( - - )} - - -
+
{/* ์ฃผ์š” ํ† ๊ธ€๋“ค */} @@ -128,87 +84,14 @@ export const GridPanel: React.FC = ({ {/* ์„ค์ • ์˜์—ญ */}
- {/* ๊ฒฉ์ž ๊ตฌ์กฐ */} + {/* 10px ๋‹จ์œ„ ์Šค๋ƒ… ์•ˆ๋‚ด */}
-

๊ฒฉ์ž ๊ตฌ์กฐ

+

๊ฒฉ์ž ์‹œ์Šคํ…œ

-
- -
- { - const value = parseInt(e.target.value, 10); - if (!isNaN(value) && value >= 1 && value <= safeMaxColumns) { - updateSetting("columns", value); - } - }} - className="h-8 text-xs" - /> - / {safeMaxColumns} -
- updateSetting("columns", value)} - className="w-full" - /> -
- 1์—ด - {safeMaxColumns}์—ด -
- {isColumnsTooSmall && ( -

- โš ๏ธ ์ปฌ๋Ÿผ ๋„ˆ๋น„๊ฐ€ ๋„ˆ๋ฌด ์ž‘์Šต๋‹ˆ๋‹ค (์ตœ์†Œ {MIN_COLUMN_WIDTH}px ๊ถŒ์žฅ) -

- )} -
- -
- - updateSetting("gap", value)} - className="w-full" - /> -
- 0px - 40px -
-
- -
- - updateSetting("padding", value)} - className="w-full" - /> -
- 0px - 60px -
+
+

+ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋Š” 10px ๋‹จ์œ„๋กœ ์ž๋™ ๋ฐฐ์น˜๋ฉ๋‹ˆ๋‹ค. +

@@ -216,10 +99,10 @@ export const GridPanel: React.FC = ({ {/* ๊ฒฉ์ž ์Šคํƒ€์ผ */}
-

๊ฒฉ์ž ์Šคํƒ€์ผ

+

๊ฒฉ์ž ์Šคํƒ€์ผ

-
-
)} - {/* Grid Columns + Z-Index (๊ฐ™์€ ํ–‰) */} + {/* Width + Z-Index (๊ฐ™์€ ํ–‰) */}
- {(selectedComponent as any).gridColumns !== undefined && ( -
- -
- { - const value = parseInt(e.target.value, 10); - const maxColumns = gridSettings?.columns || 12; - if (!isNaN(value) && value >= 1 && value <= maxColumns) { - handleUpdate("gridColumns", value); - - // width๋ฅผ ํผ์„ผํŠธ๋กœ ๊ณ„์‚ฐํ•˜์—ฌ ์—…๋ฐ์ดํŠธ - const widthPercent = (value / maxColumns) * 100; - handleUpdate("style.width", `${widthPercent}%`); +
+ +
+ { + // ์ž…๋ ฅ ์ค‘์—๋Š” ๋กœ์ปฌ ์ƒํƒœ๋งŒ ์—…๋ฐ์ดํŠธ (์ž์œ  ์ž…๋ ฅ) + setLocalWidth(e.target.value); + }} + onBlur={(e) => { + // ํฌ์ปค์Šค๋ฅผ ์žƒ์„ ๋•Œ 10px ๋‹จ์œ„๋กœ ์Šค๋ƒ… + const value = parseInt(e.target.value, 10); + if (!isNaN(value) && value >= 10) { + const snappedValue = Math.round(value / 10) * 10; + handleUpdate("size.width", snappedValue); + setLocalWidth(String(snappedValue)); + } + }} + onKeyDown={(e) => { + // Enter ํ‚ค๋ฅผ ๋ˆ„๋ฅด๋ฉด ์ฆ‰์‹œ ์ ์šฉ (10px ๋‹จ์œ„๋กœ ์Šค๋ƒ…) + if (e.key === "Enter") { + const value = parseInt(e.currentTarget.value, 10); + if (!isNaN(value) && value >= 10) { + const snappedValue = Math.round(value / 10) * 10; + handleUpdate("size.width", snappedValue); + setLocalWidth(String(snappedValue)); } - }} - className="h-6 w-full px-2 py-0 text-xs" - style={{ fontSize: "12px" }} - /> - - /{gridSettings?.columns || 12} - -
+ e.currentTarget.blur(); // ํฌ์ปค์Šค ์ œ๊ฑฐ + } + }} + className="h-6 w-full px-2 py-0 text-xs" + style={{ fontSize: "12px" }} + />
- )} +
= ({ required, className, style, + isDesignMode = false, // ๋””์ž์ธ ๋ชจ๋“œ ํ”Œ๋ž˜๊ทธ + ...restProps }) => { - const handleClick = () => { + const handleClick = (e: React.MouseEvent) => { + // ๋””์ž์ธ ๋ชจ๋“œ์—์„œ๋Š” ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๊ณ  ๊ทธ๋ƒฅ ์ด๋ฒคํŠธ ์ „ํŒŒ + if (isDesignMode) { + return; + } + // ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ๋™์ž‘ (์ถ”ํ›„ ๋ฒ„ํŠผ ์•ก์…˜ ์‹œ์Šคํ…œ๊ณผ ์—ฐ๋™) - // console.log("Button clicked:", config); + console.log("Button clicked:", config); // onChange๋ฅผ ํ†ตํ•ด ํด๋ฆญ ์ด๋ฒคํŠธ ์ „๋‹ฌ if (onChange) { @@ -25,6 +32,25 @@ export const ButtonWidget: React.FC = ({ } }; + // ๋””์ž์ธ ๋ชจ๋“œ์—์„œ๋Š” div๋กœ ๋ Œ๋”๋งํ•˜์—ฌ ๋ฒ„ํŠผ ๋™์ž‘ ์™„์ „ ์ฐจ๋‹จ + if (isDesignMode) { + return ( +
+ {config?.label || config?.text || value || placeholder || "๋ฒ„ํŠผ"} +
+ ); + } + return ( + {isDesignMode ? ( + // ๋””์ž์ธ ๋ชจ๋“œ: div๋กœ ๋ Œ๋”๋งํ•˜์—ฌ ์„ ํƒ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•จ +
+ {buttonContent} +
+ ) : ( + // ์ผ๋ฐ˜ ๋ชจ๋“œ: button์œผ๋กœ ๋ Œ๋”๋ง + + )}
{/* ํ™•์ธ ๋‹ค์ด์–ผ๋กœ๊ทธ - EditModal๋ณด๋‹ค ์œ„์— ํ‘œ์‹œํ•˜๋„๋ก z-index ์ตœ์ƒ์œ„๋กœ ์„ค์ • */} diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index 30756d09..6e03a9d0 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -323,16 +323,29 @@ export const TableListComponent: React.FC = ({ return reordered; }); - console.log("๐Ÿ“Š ์ดˆ๊ธฐ ํ™”๋ฉด ํ‘œ์‹œ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ:", { count: initialData.length, firstRow: initialData[0] }); - // ์ „์—ญ ์ €์žฅ์†Œ์— ๋ฐ์ดํ„ฐ ์ €์žฅ if (tableConfig.selectedTable) { + // ์ปฌ๋Ÿผ ๋ผ๋ฒจ ๋งคํ•‘ ์ƒ์„ฑ + const labels: Record = {}; + visibleColumns.forEach((col) => { + labels[col.columnName] = columnLabels[col.columnName] || col.columnName; + }); + tableDisplayStore.setTableData( tableConfig.selectedTable, initialData, parsedOrder.filter((col) => col !== "__checkbox__"), sortColumn, sortDirection, + { + filterConditions: Object.keys(searchValues).length > 0 ? searchValues : undefined, + searchTerm: searchTerm || undefined, + visibleColumns: visibleColumns.map((col) => col.columnName), + columnLabels: labels, + currentPage: currentPage, + pageSize: localPageSize, + totalItems: totalItems, + }, ); } @@ -624,33 +637,44 @@ export const TableListComponent: React.FC = ({ referenceTable: col.additionalJoinInfo!.referenceTable, })); - const hasEntityJoins = entityJoinColumns.length > 0; - - let response; - if (hasEntityJoins) { - response = await entityJoinApi.getTableDataWithJoins(tableConfig.selectedTable, { - page, - size: pageSize, - sortBy, - sortOrder, - search: filters, - enableEntityJoin: true, - additionalJoinColumns: entityJoinColumns, - }); - } else { - response = await tableTypeApi.getTableData(tableConfig.selectedTable, { - page, - size: pageSize, - sortBy, - sortOrder, - search: filters, - }); - } + // ๐ŸŽฏ ํ•ญ์ƒ entityJoinApi ์‚ฌ์šฉ (writer ์ปฌ๋Ÿผ ์ž๋™ ์กฐ์ธ ์ง€์›) + const response = await entityJoinApi.getTableDataWithJoins(tableConfig.selectedTable, { + page, + size: pageSize, + sortBy, + sortOrder, + search: filters, + enableEntityJoin: true, + additionalJoinColumns: entityJoinColumns.length > 0 ? entityJoinColumns : undefined, + }); setData(response.data || []); setTotalPages(response.totalPages || 0); setTotalItems(response.total || 0); setError(null); + + // ๐ŸŽฏ Store์— ํ•„ํ„ฐ ์กฐ๊ฑด ์ €์žฅ (์—‘์…€ ๋‹ค์šด๋กœ๋“œ์šฉ) + const labels: Record = {}; + visibleColumns.forEach((col) => { + labels[col.columnName] = columnLabels[col.columnName] || col.columnName; + }); + + tableDisplayStore.setTableData( + tableConfig.selectedTable, + response.data || [], + visibleColumns.map((col) => col.columnName), + sortBy, + sortOrder, + { + filterConditions: filters, + searchTerm: search, + visibleColumns: visibleColumns.map((col) => col.columnName), + columnLabels: labels, + currentPage: page, + pageSize: pageSize, + totalItems: response.total || 0, + } + ); } catch (err: any) { console.error("๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํŒจ:", err); setData([]); @@ -788,12 +812,28 @@ export const TableListComponent: React.FC = ({ const cleanColumnOrder = ( columnOrder.length > 0 ? columnOrder : visibleColumns.map((c) => c.columnName) ).filter((col) => col !== "__checkbox__"); + + // ์ปฌ๋Ÿผ ๋ผ๋ฒจ ์ •๋ณด๋„ ํ•จ๊ป˜ ์ €์žฅ + const labels: Record = {}; + visibleColumns.forEach((col) => { + labels[col.columnName] = columnLabels[col.columnName] || col.columnName; + }); + tableDisplayStore.setTableData( tableConfig.selectedTable, reorderedData, cleanColumnOrder, newSortColumn, newSortDirection, + { + filterConditions: Object.keys(searchValues).length > 0 ? searchValues : undefined, + searchTerm: searchTerm || undefined, + visibleColumns: visibleColumns.map((col) => col.columnName), + columnLabels: labels, + currentPage: currentPage, + pageSize: localPageSize, + totalItems: totalItems, + }, ); } } else { @@ -1062,6 +1102,11 @@ export const TableListComponent: React.FC = ({ (value: any, column: ColumnConfig, rowData?: Record) => { if (value === null || value === undefined) return "-"; + // ๐ŸŽฏ writer ์ปฌ๋Ÿผ ์ž๋™ ๋ณ€ํ™˜: user_id -> user_name + if (column.columnName === "writer" && rowData && rowData.writer_name) { + return rowData.writer_name; + } + // ๐ŸŽฏ ์—”ํ‹ฐํ‹ฐ ์ปฌ๋Ÿผ ํ‘œ์‹œ ์„ค์ •์ด ์žˆ๋Š” ๊ฒฝ์šฐ if (column.entityDisplayConfig && rowData) { // displayColumns ๋˜๋Š” selectedColumns ๋‘˜ ๋‹ค ์ฒดํฌ @@ -1155,6 +1200,22 @@ export const TableListComponent: React.FC = ({ return String(value); } + // ๋‚ ์งœ ํƒ€์ž… ํฌ๋งทํŒ… (yyyy-mm-dd) + if (inputType === "date" || inputType === "datetime") { + if (value) { + try { + const date = new Date(value); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + } catch { + return String(value); + } + } + return "-"; + } + // ์ˆซ์ž ํƒ€์ž… ํฌ๋งทํŒ… if (inputType === "number" || inputType === "decimal") { if (value !== null && value !== undefined && value !== "") { @@ -1179,7 +1240,10 @@ export const TableListComponent: React.FC = ({ if (value) { try { const date = new Date(value); - return date.toLocaleDateString("ko-KR"); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; } catch { return value; } @@ -2144,7 +2208,7 @@ export const TableListComponent: React.FC = ({ handleRowClick(row, index, e)} > @@ -2173,8 +2237,8 @@ export const TableListComponent: React.FC = ({ = ({ handleRowClick(row, index, e)} > @@ -2239,8 +2303,8 @@ export const TableListComponent: React.FC = ({ ; // ํ•„ํ„ฐ ์กฐ๊ฑด (์˜ˆ: { status: "active", dept: "dev" }) + searchTerm?: string; // ๊ฒ€์ƒ‰์–ด + searchColumn?: string; // ๊ฒ€์ƒ‰ ๋Œ€์ƒ ์ปฌ๋Ÿผ + visibleColumns?: string[]; // ํ™”๋ฉด์— ํ‘œ์‹œ ์ค‘์ธ ์ปฌ๋Ÿผ ๋ชฉ๋ก (์ˆœ์„œ ํฌํ•จ) + columnLabels?: Record; // ์ปฌ๋Ÿผ๋ช… โ†’ ๋ผ๋ฒจ๋ช… ๋งคํ•‘ (ํ•œ๊ธ€) + currentPage?: number; // ํ˜„์žฌ ํŽ˜์ด์ง€ + pageSize?: number; // ํŽ˜์ด์ง€ ํฌ๊ธฐ + totalItems?: number; // ์ „์ฒด ํ•ญ๋ชฉ ์ˆ˜ } /** @@ -1936,162 +1946,74 @@ export class ButtonActionExecutor { */ private static async handleExcelDownload(config: ButtonActionConfig, context: ButtonActionContext): Promise { try { - console.log("๐Ÿ“ฅ ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ์‹œ์ž‘:", { config, context }); - console.log("๐Ÿ” context.columnOrder ํ™•์ธ:", { - hasColumnOrder: !!context.columnOrder, - columnOrderLength: context.columnOrder?.length, - columnOrder: context.columnOrder, - }); - console.log("๐Ÿ” context.tableDisplayData ํ™•์ธ:", { - hasTableDisplayData: !!context.tableDisplayData, - tableDisplayDataLength: context.tableDisplayData?.length, - tableDisplayDataFirstRow: context.tableDisplayData?.[0], - tableDisplayDataColumns: context.tableDisplayData?.[0] ? Object.keys(context.tableDisplayData[0]) : [], - }); - // ๋™์  import๋กœ ์—‘์…€ ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋กœ๋“œ const { exportToExcel } = await import("@/lib/utils/excelExport"); let dataToExport: any[] = []; - // 1์ˆœ์œ„: ์„ ํƒ๋œ ํ–‰ ๋ฐ์ดํ„ฐ - if (context.selectedRowsData && context.selectedRowsData.length > 0) { - dataToExport = context.selectedRowsData; - console.log("โœ… ์„ ํƒ๋œ ํ–‰ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ:", dataToExport.length); - - // ์„ ํƒ๋œ ํ–‰๋„ ์ •๋ ฌ ์ ์šฉ - if (context.sortBy) { - console.log("๐Ÿ”„ ์„ ํƒ๋œ ํ–‰ ๋ฐ์ดํ„ฐ ์ •๋ ฌ ์ ์šฉ:", { - sortBy: context.sortBy, - sortOrder: context.sortOrder, - }); - - dataToExport = [...dataToExport].sort((a, b) => { - const aVal = a[context.sortBy!]; - const bVal = b[context.sortBy!]; - - // null/undefined ์ฒ˜๋ฆฌ - if (aVal == null && bVal == null) return 0; - if (aVal == null) return 1; - if (bVal == null) return -1; - - // ์ˆซ์ž ๋น„๊ต (๋ฌธ์ž์—ด์ด์–ด๋„ ์ˆซ์ž๋กœ ๋ณ€ํ™˜ ๊ฐ€๋Šฅํ•˜๋ฉด ์ˆซ์ž๋กœ ๋น„๊ต) - const aNum = Number(aVal); - const bNum = Number(bVal); - - // ๋‘˜ ๋‹ค ์œ ํšจํ•œ ์ˆซ์ž์ด๊ณ , ์›๋ณธ ๊ฐ’์ด ๋นˆ ๋ฌธ์ž์—ด์ด ์•„๋‹Œ ๊ฒฝ์šฐ - if (!isNaN(aNum) && !isNaN(bNum) && aVal !== "" && bVal !== "") { - return context.sortOrder === "desc" ? bNum - aNum : aNum - bNum; - } - - // ๋ฌธ์ž์—ด ๋น„๊ต (๋Œ€์†Œ๋ฌธ์ž ๊ตฌ๋ถ„ ์—†์ด, ์ˆซ์ž ํฌํ•จ ๋ฌธ์ž์—ด๋„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ •๋ ฌ) - const aStr = String(aVal).toLowerCase(); - const bStr = String(bVal).toLowerCase(); - - // ์ž์—ฐ์Šค๋Ÿฌ์šด ์ •๋ ฌ (์ˆซ์ž ํฌํ•จ ๋ฌธ์ž์—ด) - const comparison = aStr.localeCompare(bStr, undefined, { numeric: true, sensitivity: 'base' }); - return context.sortOrder === "desc" ? -comparison : comparison; - }); - - console.log("โœ… ์ •๋ ฌ ์™„๋ฃŒ:", { - firstRow: dataToExport[0], - lastRow: dataToExport[dataToExport.length - 1], - firstSortValue: dataToExport[0]?.[context.sortBy], - lastSortValue: dataToExport[dataToExport.length - 1]?.[context.sortBy], - }); - } - } - // 2์ˆœ์œ„: ํ™”๋ฉด ํ‘œ์‹œ ๋ฐ์ดํ„ฐ (์ปฌ๋Ÿผ ์ˆœ์„œ ํฌํ•จ, ์ •๋ ฌ ์ ์šฉ๋จ) - else if (context.tableDisplayData && context.tableDisplayData.length > 0) { - dataToExport = context.tableDisplayData; - console.log("โœ… ํ™”๋ฉด ํ‘œ์‹œ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ (context):", { - count: dataToExport.length, - firstRow: dataToExport[0], - columns: Object.keys(dataToExport[0] || {}), - }); - } - // 2.5์ˆœ์œ„: ์ „์—ญ ์ €์žฅ์†Œ์—์„œ ํ™”๋ฉด ํ‘œ์‹œ ๋ฐ์ดํ„ฐ ์กฐํšŒ - else if (context.tableName) { + // โœ… ํ•ญ์ƒ API ํ˜ธ์ถœ๋กœ ํ•„ํ„ฐ๋ง๋œ ์ „์ฒด ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ + if (context.tableName) { const { tableDisplayStore } = await import("@/stores/tableDisplayStore"); const storedData = tableDisplayStore.getTableData(context.tableName); - if (storedData && storedData.data.length > 0) { - dataToExport = storedData.data; - console.log("โœ… ํ™”๋ฉด ํ‘œ์‹œ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ (์ „์—ญ ์ €์žฅ์†Œ):", { - tableName: context.tableName, - count: dataToExport.length, - firstRow: dataToExport[0], - lastRow: dataToExport[dataToExport.length - 1], - columns: Object.keys(dataToExport[0] || {}), - columnOrder: storedData.columnOrder, - sortBy: storedData.sortBy, - sortOrder: storedData.sortOrder, - // ์ •๋ ฌ ์ปฌ๋Ÿผ์˜ ์ฒซ/๋งˆ์ง€๋ง‰ ๊ฐ’ ํ™•์ธ - firstSortValue: storedData.sortBy ? dataToExport[0]?.[storedData.sortBy] : undefined, - lastSortValue: storedData.sortBy ? dataToExport[dataToExport.length - 1]?.[storedData.sortBy] : undefined, - }); - } - // 3์ˆœ์œ„: ํ…Œ์ด๋ธ” ์ „์ฒด ๋ฐ์ดํ„ฐ (API ํ˜ธ์ถœ) - else { - console.log("๐Ÿ”„ ํ…Œ์ด๋ธ” ์ „์ฒด ๋ฐ์ดํ„ฐ ์กฐํšŒ ์ค‘...", context.tableName); - console.log("๐Ÿ“Š ์ •๋ ฌ ์ •๋ณด:", { - sortBy: context.sortBy, - sortOrder: context.sortOrder, - }); + // ํ•„ํ„ฐ ์กฐ๊ฑด์€ ์ €์žฅ์†Œ ๋˜๋Š” context์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ + const filterConditions = storedData?.filterConditions || context.filterConditions; + const searchTerm = storedData?.searchTerm || context.searchTerm; + try { - const { dynamicFormApi } = await import("@/lib/api/dynamicForm"); - const response = await dynamicFormApi.getTableData(context.tableName, { + const { entityJoinApi } = await import("@/lib/api/entityJoin"); + + const apiParams = { page: 1, - pageSize: 10000, // ์ตœ๋Œ€ 10,000๊ฐœ ํ–‰ - sortBy: context.sortBy || "id", // ํ™”๋ฉด ์ •๋ ฌ ๋˜๋Š” ๊ธฐ๋ณธ ์ •๋ ฌ - sortOrder: context.sortOrder || "asc", // ํ™”๋ฉด ์ •๋ ฌ ๋ฐฉํ–ฅ ๋˜๋Š” ์˜ค๋ฆ„์ฐจ์ˆœ - }); + size: 10000, // ์ตœ๋Œ€ 10,000๊ฐœ + sortBy: context.sortBy || storedData?.sortBy || "id", + sortOrder: (context.sortOrder || storedData?.sortOrder || "asc") as "asc" | "desc", + search: filterConditions, // โœ… ํ•„ํ„ฐ ์กฐ๊ฑด + enableEntityJoin: true, // โœ… Entity ์กฐ์ธ + autoFilter: true, // โœ… company_code ์ž๋™ ํ•„ํ„ฐ๋ง (๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ) + }; - console.log("๐Ÿ“ฆ API ์‘๋‹ต ๊ตฌ์กฐ:", { - response, - responseSuccess: response.success, - responseData: response.data, - responseDataType: typeof response.data, - responseDataIsArray: Array.isArray(response.data), - responseDataLength: Array.isArray(response.data) ? response.data.length : "N/A", - }); - - if (response.success && response.data) { + // ๐Ÿ”’ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ์ค€์ˆ˜: autoFilter๋กœ company_code ์ž๋™ ์ ์šฉ + const response = await entityJoinApi.getTableDataWithJoins(context.tableName, apiParams); + + // ๐Ÿ”’ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ํ™•์ธ + const allData = Array.isArray(response) ? response : response?.data || []; + const companyCodesInData = [...new Set(allData.map((row: any) => row.company_code))]; + + if (companyCodesInData.length > 1) { + console.error("โŒ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ์œ„๋ฐ˜! ์—ฌ๋Ÿฌ ํšŒ์‚ฌ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์„ž์—ฌ์žˆ์Šต๋‹ˆ๋‹ค:", companyCodesInData); + } + + // entityJoinApi๋Š” EntityJoinResponse ๋˜๋Š” data ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ + if (Array.isArray(response)) { + // ๋ฐฐ์—ด๋กœ ์ง์ ‘ ๋ฐ˜ํ™˜๋œ ๊ฒฝ์šฐ + dataToExport = response; + } else if (response && 'data' in response) { + // EntityJoinResponse ๊ฐ์ฒด์ธ ๊ฒฝ์šฐ dataToExport = response.data; - console.log("โœ… ํ…Œ์ด๋ธ” ์ „์ฒด ๋ฐ์ดํ„ฐ ์กฐํšŒ ์™„๋ฃŒ:", { - count: dataToExport.length, - firstRow: dataToExport[0], - }); } else { - console.error("โŒ API ์‘๋‹ต์— ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค:", response); + console.error("โŒ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์‘๋‹ต ํ˜•์‹:", response); + toast.error("๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); + return false; } } catch (error) { - console.error("โŒ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹คํŒจ:", error); - } + console.error("์—‘์…€ ๋‹ค์šด๋กœ๋“œ: ๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹คํŒจ:", error); + toast.error("๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); + return false; } } - // 4์ˆœ์œ„: ํผ ๋ฐ์ดํ„ฐ + // ํด๋ฐฑ: ํผ ๋ฐ์ดํ„ฐ else if (context.formData && Object.keys(context.formData).length > 0) { dataToExport = [context.formData]; - console.log("โœ… ํผ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ:", dataToExport); } - - console.log("๐Ÿ“Š ์ตœ์ข… ๋‹ค์šด๋กœ๋“œ ๋ฐ์ดํ„ฐ:", { - selectedRowsData: context.selectedRowsData, - selectedRowsLength: context.selectedRowsData?.length, - formData: context.formData, - tableName: context.tableName, - dataToExport, - dataToExportType: typeof dataToExport, - dataToExportIsArray: Array.isArray(dataToExport), - dataToExportLength: Array.isArray(dataToExport) ? dataToExport.length : "N/A", - }); + // ํ…Œ์ด๋ธ”๋ช…๋„ ์—†๊ณ  ํผ ๋ฐ์ดํ„ฐ๋„ ์—†์œผ๋ฉด ์—๋Ÿฌ + else { + toast.error("๋‹ค์šด๋กœ๋“œํ•  ๋ฐ์ดํ„ฐ ์†Œ์Šค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); + return false; + } // ๋ฐฐ์—ด์ด ์•„๋‹ˆ๋ฉด ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ if (!Array.isArray(dataToExport)) { - console.warn("โš ๏ธ dataToExport๊ฐ€ ๋ฐฐ์—ด์ด ์•„๋‹™๋‹ˆ๋‹ค. ๋ณ€ํ™˜ ์‹œ๋„:", dataToExport); - - // ๊ฐ์ฒด์ธ ๊ฒฝ์šฐ ๋ฐฐ์—ด๋กœ ๊ฐ์‹ธ๊ธฐ if (typeof dataToExport === "object" && dataToExport !== null) { dataToExport = [dataToExport]; } else { @@ -2110,66 +2032,196 @@ export class ButtonActionExecutor { const sheetName = config.excelSheetName || "Sheet1"; const includeHeaders = config.excelIncludeHeaders !== false; - // ๐Ÿ†• ์ปฌ๋Ÿผ ์ˆœ์„œ ์žฌ์ •๋ ฌ (ํ™”๋ฉด์— ํ‘œ์‹œ๋œ ์ˆœ์„œ๋Œ€๋กœ) - let columnOrder: string[] | undefined = context.columnOrder; - - // columnOrder๊ฐ€ ์—†์œผ๋ฉด tableDisplayData์—์„œ ์ถ”์ถœ ์‹œ๋„ - if (!columnOrder && context.tableDisplayData && context.tableDisplayData.length > 0) { - columnOrder = Object.keys(context.tableDisplayData[0]); - console.log("๐Ÿ“Š tableDisplayData์—์„œ ์ปฌ๋Ÿผ ์ˆœ์„œ ์ถ”์ถœ:", columnOrder); - } - - if (columnOrder && columnOrder.length > 0 && dataToExport.length > 0) { - console.log("๐Ÿ”„ ์ปฌ๋Ÿผ ์ˆœ์„œ ์žฌ์ •๋ ฌ ์‹œ์ž‘:", { - columnOrder, - originalColumns: Object.keys(dataToExport[0] || {}), - }); - - dataToExport = dataToExport.map((row: any) => { - const reorderedRow: any = {}; - - // 1. columnOrder์— ์žˆ๋Š” ์ปฌ๋Ÿผ๋“ค์„ ์ˆœ์„œ๋Œ€๋กœ ์ถ”๊ฐ€ - columnOrder!.forEach((colName: string) => { - if (colName in row) { - reorderedRow[colName] = row[colName]; + // ๐ŸŽจ ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ์—์„œ ํ…Œ์ด๋ธ” ๋ฆฌ์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ์˜ ์ปฌ๋Ÿผ ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ + let visibleColumns: string[] | undefined = undefined; + let columnLabels: Record | undefined = undefined; + + try { + // ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ (๋ณ„๋„ API ์‚ฌ์šฉ) + const { apiClient } = await import("@/lib/api/client"); + const layoutResponse = await apiClient.get(`/screen-management/screens/${context.screenId}/layout`); + + if (layoutResponse.data?.success && layoutResponse.data?.data) { + let layoutData = layoutResponse.data.data; + + // components๊ฐ€ ๋ฌธ์ž์—ด์ด๋ฉด ํŒŒ์‹ฑ + if (typeof layoutData.components === 'string') { + layoutData.components = JSON.parse(layoutData.components); + } + + // ํ…Œ์ด๋ธ” ๋ฆฌ์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ ์ฐพ๊ธฐ + const findTableListComponent = (components: any[]): any => { + if (!Array.isArray(components)) return null; + + for (const comp of components) { + // componentType์ด 'table-list'์ธ์ง€ ํ™•์ธ + const isTableList = comp.componentType === 'table-list'; + + // componentConfig ์•ˆ์—์„œ ํ…Œ์ด๋ธ”๋ช… ํ™•์ธ + const matchesTable = + comp.componentConfig?.selectedTable === context.tableName || + comp.componentConfig?.tableName === context.tableName; + + if (isTableList && matchesTable) { + return comp; + } + if (comp.children && comp.children.length > 0) { + const found = findTableListComponent(comp.children); + if (found) return found; + } + } + return null; + }; + + const tableListComponent = findTableListComponent(layoutData.components || []); + + if (tableListComponent && tableListComponent.componentConfig?.columns) { + const columns = tableListComponent.componentConfig.columns; + + // visible์ด true์ธ ์ปฌ๋Ÿผ๋งŒ ์ถ”์ถœ + visibleColumns = columns + .filter((col: any) => col.visible !== false) + .map((col: any) => col.columnName); + + // ๐ŸŽฏ column_labels ํ…Œ์ด๋ธ”์—์„œ ์‹ค์ œ ๋ผ๋ฒจ ๊ฐ€์ ธ์˜ค๊ธฐ + try { + const columnsResponse = await apiClient.get(`/table-management/tables/${context.tableName}/columns`, { + params: { page: 1, size: 9999 } + }); + + if (columnsResponse.data?.success && columnsResponse.data?.data) { + let columnData = columnsResponse.data.data; + + // data๊ฐ€ ๊ฐ์ฒด์ด๊ณ  columns ํ•„๋“œ๊ฐ€ ์žˆ์œผ๋ฉด ์ถ”์ถœ + if (columnData.columns && Array.isArray(columnData.columns)) { + columnData = columnData.columns; + } + + if (Array.isArray(columnData)) { + columnLabels = {}; + + // API์—์„œ ๊ฐ€์ ธ์˜จ ๋ผ๋ฒจ๋กœ ๋งคํ•‘ + columnData.forEach((colData: any) => { + const colName = colData.column_name || colData.columnName; + // ์šฐ์„ ์ˆœ์œ„: column_label > label > displayName > columnName + const labelValue = colData.column_label || colData.label || colData.displayName || colName; + if (colName && labelValue) { + columnLabels![colName] = labelValue; + } + }); + } + } + } catch (error) { + // ์‹คํŒจ ์‹œ ์ปดํฌ๋„ŒํŠธ ์„ค์ •์˜ displayName ์‚ฌ์šฉ + columnLabels = {}; + columns.forEach((col: any) => { + if (col.columnName) { + columnLabels![col.columnName] = col.displayName || col.label || col.columnName; + } + }); + } + } else { + console.warn("โš ๏ธ ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ์—์„œ ํ…Œ์ด๋ธ” ๋ฆฌ์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + } + } + } catch (error) { + console.error("โŒ ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ์กฐํšŒ ์‹คํŒจ:", error); } - }); + + + // ๐ŸŽจ ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐ’๋“ค ์กฐํšŒ (ํ•œ ๋ฒˆ๋งŒ) + const categoryMap: Record> = {}; + let categoryColumns: string[] = []; + + // ๋ฐฑ์—”๋“œ์—์„œ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ + if (context.tableName) { + try { + const { getCategoryColumns, getCategoryValues } = await import("@/lib/api/tableCategoryValue"); - // 2. columnOrder์— ์—†๋Š” ๋‚˜๋จธ์ง€ ์ปฌ๋Ÿผ๋“ค ์ถ”๊ฐ€ (๋์— ๋ฐฐ์น˜) - Object.keys(row).forEach((key) => { - if (!(key in reorderedRow)) { - reorderedRow[key] = row[key]; + const categoryColumnsResponse = await getCategoryColumns(context.tableName); + + if (categoryColumnsResponse.success && categoryColumnsResponse.data) { + // ๋ฐฑ์—”๋“œ์—์„œ ์ •์˜๋œ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ๋“ค + categoryColumns = categoryColumnsResponse.data.map((col: any) => + col.column_name || col.columnName || col.name + ).filter(Boolean); // undefined ์ œ๊ฑฐ + + // ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ์˜ ๊ฐ’๋“ค ์กฐํšŒ + for (const columnName of categoryColumns) { + try { + const valuesResponse = await getCategoryValues(context.tableName, columnName, false); + + if (valuesResponse.success && valuesResponse.data) { + // valueCode โ†’ valueLabel ๋งคํ•‘ + categoryMap[columnName] = {}; + valuesResponse.data.forEach((catValue: any) => { + const code = catValue.valueCode || catValue.category_value_id; + const label = catValue.valueLabel || catValue.label || code; + if (code) { + categoryMap[columnName][code] = label; + } + }); + + } + } catch (error) { + console.error(`โŒ ์นดํ…Œ๊ณ ๋ฆฌ "${columnName}" ์กฐํšŒ ์‹คํŒจ:`, error); + } } - }); - - return reorderedRow; - }); - - console.log("โœ… ์ปฌ๋Ÿผ ์ˆœ์„œ ์žฌ์ •๋ ฌ ์™„๋ฃŒ:", { - reorderedColumns: Object.keys(dataToExport[0] || {}), - }); - } else { - console.log("โญ๏ธ ์ปฌ๋Ÿผ ์ˆœ์„œ ์žฌ์ •๋ ฌ ์Šคํ‚ต:", { - hasColumnOrder: !!columnOrder, - columnOrderLength: columnOrder?.length, - hasTableDisplayData: !!context.tableDisplayData, - dataToExportLength: dataToExport.length, - }); + } + } catch (error) { + console.error("โŒ ์นดํ…Œ๊ณ ๋ฆฌ ์ •๋ณด ์กฐํšŒ ์‹คํŒจ:", error); + } } - console.log("๐Ÿ“ฅ ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ์‹คํ–‰:", { - fileName, - sheetName, - includeHeaders, - dataCount: dataToExport.length, - firstRow: dataToExport[0], - columnOrder: context.columnOrder, - }); + // ๐ŸŽจ ์ปฌ๋Ÿผ ํ•„ํ„ฐ๋ง ๋ฐ ๋ผ๋ฒจ ์ ์šฉ (ํ•ญ์ƒ ์‹คํ–‰) + if (visibleColumns && visibleColumns.length > 0 && dataToExport.length > 0) { + dataToExport = dataToExport.map((row: any) => { + const filteredRow: Record = {}; + + visibleColumns.forEach((columnName: string) => { + // __checkbox__ ์ปฌ๋Ÿผ์€ ์ œ์™ธ + if (columnName === "__checkbox__") return; + + if (columnName in row) { + // ๋ผ๋ฒจ ์šฐ์„  ์‚ฌ์šฉ, ์—†์œผ๋ฉด ์ปฌ๋Ÿผ๋ช… ์‚ฌ์šฉ + const label = columnLabels?.[columnName] || columnName; + + // ๐ŸŽฏ Entity ์กฐ์ธ๋œ ๊ฐ’ ์šฐ์„  ์‚ฌ์šฉ + let value = row[columnName]; + + // writer โ†’ writer_name ์‚ฌ์šฉ + if (columnName === 'writer' && row['writer_name']) { + value = row['writer_name']; + } + // ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ ํ•„๋“œ๋“ค๋„ _name ์šฐ์„  ์‚ฌ์šฉ + else if (row[`${columnName}_name`]) { + value = row[`${columnName}_name`]; + } + // ์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž… ํ•„๋“œ๋Š” ๋ผ๋ฒจ๋กœ ๋ณ€ํ™˜ (๋ฐฑ์—”๋“œ์—์„œ ์ •์˜๋œ ์ปฌ๋Ÿผ๋งŒ) + else if (categoryMap[columnName] && typeof value === 'string' && categoryMap[columnName][value]) { + value = categoryMap[columnName][value]; + } + + filteredRow[label] = value; + } + }); + + return filteredRow; + }); + + } + + // ์ตœ๋Œ€ ํ–‰ ์ˆ˜ ์ œํ•œ + const MAX_ROWS = 10000; + if (dataToExport.length > MAX_ROWS) { + toast.warning(`์ตœ๋Œ€ ${MAX_ROWS.toLocaleString()}๊ฐœ ํ–‰๊นŒ์ง€๋งŒ ๋‹ค์šด๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.`); + dataToExport = dataToExport.slice(0, MAX_ROWS); + } // ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ์‹คํ–‰ await exportToExcel(dataToExport, fileName, sheetName, includeHeaders); - toast.success(config.successMessage || "์—‘์…€ ํŒŒ์ผ์ด ๋‹ค์šด๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); + toast.success(`${dataToExport.length}๊ฐœ ํ–‰์ด ๋‹ค์šด๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`); return true; } catch (error) { console.error("โŒ ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ์‹คํŒจ:", error); diff --git a/frontend/lib/utils/gridUtils.ts b/frontend/lib/utils/gridUtils.ts index 7ea3f6b4..cb4d2652 100644 --- a/frontend/lib/utils/gridUtils.ts +++ b/frontend/lib/utils/gridUtils.ts @@ -107,9 +107,9 @@ export function snapSizeToGrid(size: Size, gridInfo: GridInfo, gridSettings: Gri const rowHeight = 10; const snappedHeight = Math.max(10, Math.round(size.height / rowHeight) * rowHeight); - console.log( - `๐Ÿ“ ํฌ๊ธฐ ์Šค๋ƒ…: ${size.width}px โ†’ ${snappedWidth}px (${gridColumns}์ปฌ๋Ÿผ, ์ปฌ๋Ÿผ๋„ˆ๋น„:${columnWidth}px, ๊ฐ„๊ฒฉ:${gap}px)`, - ); + // console.log( + // `๐Ÿ“ ํฌ๊ธฐ ์Šค๋ƒ…: ${size.width}px โ†’ ${snappedWidth}px (${gridColumns}์ปฌ๋Ÿผ, ์ปฌ๋Ÿผ๋„ˆ๋น„:${columnWidth}px, ๊ฐ„๊ฒฉ:${gap}px)`, + // ); return { width: Math.max(columnWidth, snappedWidth), diff --git a/frontend/stores/tableDisplayStore.ts b/frontend/stores/tableDisplayStore.ts index 570f41f0..38ea7c6b 100644 --- a/frontend/stores/tableDisplayStore.ts +++ b/frontend/stores/tableDisplayStore.ts @@ -9,6 +9,15 @@ interface TableDisplayState { sortBy: string | null; sortOrder: "asc" | "desc"; tableName: string; + + // ๐Ÿ†• ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ๊ฐœ์„ ์„ ์œ„ํ•œ ์ถ”๊ฐ€ ํ•„๋“œ + filterConditions?: Record; // ํ•„ํ„ฐ ์กฐ๊ฑด + searchTerm?: string; // ๊ฒ€์ƒ‰์–ด + visibleColumns?: string[]; // ํ™”๋ฉด ํ‘œ์‹œ ์ปฌ๋Ÿผ + columnLabels?: Record; // ์ปฌ๋Ÿผ ๋ผ๋ฒจ + currentPage?: number; // ํ˜„์žฌ ํŽ˜์ด์ง€ + pageSize?: number; // ํŽ˜์ด์ง€ ํฌ๊ธฐ + totalItems?: number; // ์ „์ฒด ํ•ญ๋ชฉ ์ˆ˜ } class TableDisplayStore { @@ -22,13 +31,23 @@ class TableDisplayStore { * @param columnOrder ์ปฌ๋Ÿผ ์ˆœ์„œ * @param sortBy ์ •๋ ฌ ์ปฌ๋Ÿผ * @param sortOrder ์ •๋ ฌ ๋ฐฉํ–ฅ + * @param options ์ถ”๊ฐ€ ์˜ต์…˜ (ํ•„ํ„ฐ, ํŽ˜์ด์ง• ๋“ฑ) */ setTableData( tableName: string, data: any[], columnOrder: string[], sortBy: string | null, - sortOrder: "asc" | "desc" + sortOrder: "asc" | "desc", + options?: { + filterConditions?: Record; + searchTerm?: string; + visibleColumns?: string[]; + columnLabels?: Record; + currentPage?: number; + pageSize?: number; + totalItems?: number; + } ) { this.state.set(tableName, { data, @@ -36,15 +55,7 @@ class TableDisplayStore { sortBy, sortOrder, tableName, - }); - - console.log("๐Ÿ“ฆ [TableDisplayStore] ๋ฐ์ดํ„ฐ ์ €์žฅ:", { - tableName, - dataCount: data.length, - columnOrderLength: columnOrder.length, - sortBy, - sortOrder, - firstRow: data[0], + ...options, }); this.notifyListeners(); @@ -55,15 +66,7 @@ class TableDisplayStore { * @param tableName ํ…Œ์ด๋ธ”๋ช… */ getTableData(tableName: string): TableDisplayState | undefined { - const state = this.state.get(tableName); - - console.log("๐Ÿ“ค [TableDisplayStore] ๋ฐ์ดํ„ฐ ์กฐํšŒ:", { - tableName, - found: !!state, - dataCount: state?.data.length, - }); - - return state; + return this.state.get(tableName); } /**