From afc66a4971cc55e505b5b529251ff23f64edf31d Mon Sep 17 00:00:00 2001 From: DDD1542 Date: Thu, 26 Feb 2026 17:07:53 +0900 Subject: [PATCH] feat: Enhance SelectedItemsDetailInputComponent with improved FK mapping and performance optimizations - Implemented automatic detection of sourceKeyField based on component configuration, enhancing data handling flexibility. - Updated SelectedItemsDetailInputConfigPanel to support automatic FK detection and mapping, streamlining configuration. - Improved database connection logic for DATE types to prevent timezone-related issues. - Optimized memoization and state management for better overall component performance and user experience. --- bom-restore-verify.mjs | 85 +++++++++++++++++++ .../app/(main)/screen/[screenCode]/page.tsx | 14 +-- 2 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 bom-restore-verify.mjs diff --git a/bom-restore-verify.mjs b/bom-restore-verify.mjs new file mode 100644 index 00000000..15407bde --- /dev/null +++ b/bom-restore-verify.mjs @@ -0,0 +1,85 @@ +/** + * BOM Screen - Restoration Verification + * Screen 4168 - verify split panel, BOM list, and tree with child items + */ +import { chromium } from 'playwright'; +import { mkdirSync, existsSync } from 'fs'; +import { join } from 'path'; + +const SCREENSHOT_DIR = join(process.cwd(), 'bom-detail-test-screenshots'); + +async function ensureDir(dir) { + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); +} + +async function screenshot(page, name) { + ensureDir(SCREENSHOT_DIR); + await page.screenshot({ path: join(SCREENSHOT_DIR, `${name}.png`), fullPage: true }); + console.log(` [Screenshot] ${name}.png`); +} + +async function sleep(ms) { + return new Promise((r) => setTimeout(r, ms)); +} + +async function main() { + const browser = await chromium.launch({ headless: true }); + const page = await browser.newPage({ viewport: { width: 1400, height: 900 } }); + + try { + console.log('\n--- Step 1-2: Login ---'); + await page.goto('http://localhost:9771/login', { waitUntil: 'load', timeout: 45000 }); + await page.locator('input[type="text"], input[placeholder*="ID"]').first().fill('topseal_admin'); + await page.locator('input[type="password"]').first().fill('qlalfqjsgh11'); + await Promise.all([ + page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 20000 }).catch(() => {}), + page.locator('button:has-text("로그인")').first().click(), + ]); + await sleep(3000); + + console.log('\n--- Step 4-5: Navigate to screen 4168 ---'); + await page.goto('http://localhost:9771/screens/4168', { waitUntil: 'load', timeout: 45000 }); + await sleep(5000); + + console.log('\n--- Step 6: Screenshot after load ---'); + await screenshot(page, '10-bom-4168-initial'); + + const hasBomList = (await page.locator('text="BOM 목록"').count()) > 0; + const hasSplitPanel = (await page.locator('text="BOM 상세정보"').count()) > 0 || hasBomList; + const rowCount = await page.locator('table tbody tr').count(); + const hasBomRows = rowCount > 0; + + console.log('\n========== INITIAL STATE (Step 7) =========='); + console.log('BOM management screen loaded:', hasBomList || hasSplitPanel ? 'YES' : 'CHECK'); + console.log('Split panel (BOM list left):', hasSplitPanel ? 'YES' : 'NO'); + console.log('BOM data rows visible:', hasBomRows ? `YES (${rowCount} rows)` : 'NO'); + + if (hasBomRows) { + console.log('\n--- Step 8-9: Click first row ---'); + await page.locator('table tbody tr').first().click(); + await sleep(5000); + + console.log('\n--- Step 10: Screenshot after row click ---'); + await screenshot(page, '11-bom-4168-after-click'); + + const noDataMsg = (await page.locator('text="등록된 하위 품목이 없습니다"').count()) > 0; + const treeArea = page.locator('div:has-text("BOM 구성"), div:has-text("BOM 상세정보")').first(); + const treeText = (await treeArea.textContent().catch(() => '') || '').substring(0, 600); + const hasChildItems = !noDataMsg && (treeText.includes('품번') || treeText.includes('레벨') || treeText.length > 150); + + console.log('\n========== AFTER ROW CLICK (Step 11) =========='); + console.log('BOM tree shows child items:', hasChildItems ? 'YES' : noDataMsg ? 'NO (empty message)' : 'CHECK'); + console.log('Tree preview:', treeText.substring(0, 300) + (treeText.length > 300 ? '...' : '')); + } else { + console.log('\n--- No BOM rows to click ---'); + } + + } catch (err) { + console.error('Error:', err.message); + try { await page.screenshot({ path: join(SCREENSHOT_DIR, '99-error.png'), fullPage: true }); } catch (e) {} + } finally { + await browser.close(); + } +} + +main(); diff --git a/frontend/app/(main)/screen/[screenCode]/page.tsx b/frontend/app/(main)/screen/[screenCode]/page.tsx index 0817065e..64c1bb34 100644 --- a/frontend/app/(main)/screen/[screenCode]/page.tsx +++ b/frontend/app/(main)/screen/[screenCode]/page.tsx @@ -6,7 +6,7 @@ import { Loader2 } from "lucide-react"; import { apiClient } from "@/lib/api/client"; /** - * /screen/COMPANY_7_167 → /screens/4153 리다이렉트 + * /screen/{screenCode} → /screens/{screenId} 리다이렉트 * 메뉴 URL이 screenCode 기반이므로, screenId로 변환 후 이동 */ export default function ScreenCodeRedirectPage() { @@ -26,12 +26,14 @@ export default function ScreenCodeRedirectPage() { const resolve = async () => { try { const res = await apiClient.get("/screen-management/screens", { - params: { screenCode }, + params: { searchTerm: screenCode, size: 50 }, }); - const screens = res.data?.data || []; - if (screens.length > 0) { - const id = screens[0].screenId || screens[0].screen_id; - router.replace(`/screens/${id}`); + const items = res.data?.data?.data || res.data?.data || []; + const arr = Array.isArray(items) ? items : []; + const exact = arr.find((s: any) => s.screenCode === screenCode); + const target = exact || arr[0]; + if (target) { + router.replace(`/screens/${target.screenId || target.screen_id}`); } else { router.replace("/"); }