handleBatchTypeSelect('db-to-db')}
>
-
-
-
-
+
-
DB → DB
-
데이터베이스 간 데이터 동기화
+
DB → DB
+
데이터베이스 간 데이터 동기화
{/* REST API → DB */}
handleBatchTypeSelect('restapi-to-db')}
>
-
-
-
-
+
-
REST API → DB
-
REST API에서 데이터베이스로 데이터 수집
+
REST API → DB
+
REST API에서 데이터베이스로 데이터 수집
diff --git a/frontend/components/common/EditableSpreadsheet.tsx b/frontend/components/common/EditableSpreadsheet.tsx
index 1c5078d5..a5d44b4f 100644
--- a/frontend/components/common/EditableSpreadsheet.tsx
+++ b/frontend/components/common/EditableSpreadsheet.tsx
@@ -894,7 +894,7 @@ export const EditableSpreadsheet: React.FC
= ({
const colName = columns[col];
const increment = columnIncrements.get(col);
- if (increment !== null) {
+ if (typeof increment === 'number') {
// 숫자 패턴 증가 모드
// 마지막 선택 행의 값을 기준으로 증가
const lastValue = String(data[norm.endRow]?.[colName] ?? "");
@@ -929,7 +929,7 @@ export const EditableSpreadsheet: React.FC = ({
const colName = columns[col];
const increment = columnIncrements.get(col);
- if (increment !== null) {
+ if (typeof increment === 'number') {
// 숫자 패턴 감소 모드
const firstValue = String(data[norm.startRow]?.[colName] ?? "");
const step = (targetRow - norm.startRow) * increment;
diff --git a/frontend/components/v2/V2Date.tsx b/frontend/components/v2/V2Date.tsx
index 04877b6a..f1a2da90 100644
--- a/frontend/components/v2/V2Date.tsx
+++ b/frontend/components/v2/V2Date.tsx
@@ -700,7 +700,7 @@ export const V2Date = forwardRef((props, ref) => {
}
};
- const showLabel = label && style?.labelDisplay !== false && style?.labelDisplay !== "false";
+ const showLabel = label && style?.labelDisplay !== false;
const componentWidth = size?.width || style?.width;
const componentHeight = size?.height || style?.height;
diff --git a/frontend/components/v2/V2Input.tsx b/frontend/components/v2/V2Input.tsx
index c2313f5d..eade1dfd 100644
--- a/frontend/components/v2/V2Input.tsx
+++ b/frontend/components/v2/V2Input.tsx
@@ -962,7 +962,7 @@ export const V2Input = forwardRef((props, ref) =>
};
const actualLabel = label || style?.labelText;
- const showLabel = actualLabel && style?.labelDisplay !== false && style?.labelDisplay !== "false";
+ const showLabel = actualLabel && style?.labelDisplay !== false;
const componentWidth = size?.width || style?.width;
const componentHeight = size?.height || style?.height;
diff --git a/frontend/components/v2/V2Select.tsx b/frontend/components/v2/V2Select.tsx
index b4e30ee4..a7939db2 100644
--- a/frontend/components/v2/V2Select.tsx
+++ b/frontend/components/v2/V2Select.tsx
@@ -1145,7 +1145,7 @@ export const V2Select = forwardRef(
}
};
- const showLabel = label && style?.labelDisplay !== false && style?.labelDisplay !== "false";
+ const showLabel = label && style?.labelDisplay !== false;
const componentWidth = size?.width || style?.width;
const componentHeight = size?.height || style?.height;
diff --git a/run-responsive-e2e.mjs b/run-responsive-e2e.mjs
new file mode 100644
index 00000000..d3e57f06
--- /dev/null
+++ b/run-responsive-e2e.mjs
@@ -0,0 +1,119 @@
+import { chromium } from '/Users/gbpark/ERP-node/node_modules/playwright/index.mjs';
+import { writeFileSync } from 'fs';
+
+const results = [];
+let passed = true;
+let failReason = '';
+
+async function login(page) {
+ await page.goto('http://localhost:9771/login');
+ await page.waitForLoadState('networkidle');
+
+ await page.getByPlaceholder('사용자 ID를 입력하세요').fill('wace');
+ await page.getByPlaceholder('비밀번호를 입력하세요').fill('qlalfqjsgh11');
+
+ await Promise.all([
+ page.waitForURL(url => !url.toString().includes('/login'), { timeout: 30000 }),
+ page.getByRole('button', { name: '로그인' }).click(),
+ ]);
+ await page.waitForLoadState('networkidle');
+}
+
+async function run() {
+ const browser = await chromium.launch({ headless: true });
+ const page = await browser.newPage();
+
+ try {
+ // 로그인
+ await login(page);
+ results.push('✅ 로그인 성공');
+
+ // ─── 1. batch-management 375px 카드 뷰 전환 확인 ───
+ await page.setViewportSize({ width: 375, height: 812 });
+ await page.goto('http://localhost:9771/admin/batch-management');
+ await page.waitForLoadState('domcontentloaded');
+ await page.waitForTimeout(3000);
+
+ const batchHasError = await page.locator('[id="__next"] .nextjs-container-errors-body').isVisible().catch(() => false);
+ if (batchHasError) throw new Error('batch-management 에러 오버레이 발견');
+ results.push('✅ batch-management 에러 없음');
+
+ // 페이지 정상 렌더링 확인
+ const batchBodyVisible = await page.locator('body').isVisible();
+ if (!batchBodyVisible) throw new Error('batch-management body 렌더링 실패');
+ results.push('✅ batch-management body 렌더링 정상');
+
+ // 가로 오버플로우 확인 (카드 뷰는 375px 내에 들어와야 함)
+ const batchScrollWidth = await page.evaluate(() => document.body.scrollWidth);
+ results.push(`ℹ️ batch-management scrollWidth: ${batchScrollWidth}px`);
+ if (batchScrollWidth > 420) throw new Error(`batch-management 가로 오버플로우: ${batchScrollWidth}px`);
+ results.push('✅ batch-management 375px 레이아웃 정상');
+
+ await page.screenshot({ path: '/Users/gbpark/ERP-node/.agent-pipeline/browser-tests/result-batch-375.png', fullPage: true });
+ results.push('✅ batch-management 스크린샷 저장');
+
+ // ─── 2. audit-log 375px 카드 뷰 전환 확인 ───
+ await page.goto('http://localhost:9771/admin/audit-log');
+ await page.waitForLoadState('domcontentloaded');
+ await page.waitForTimeout(3000);
+
+ const auditHasError = await page.locator('[id="__next"] .nextjs-container-errors-body').isVisible().catch(() => false);
+ if (auditHasError) throw new Error('audit-log 에러 오버레이 발견');
+ results.push('✅ audit-log 에러 없음');
+
+ const auditBodyVisible = await page.locator('body').isVisible();
+ if (!auditBodyVisible) throw new Error('audit-log body 렌더링 실패');
+ results.push('✅ audit-log body 렌더링 정상');
+
+ const auditScrollWidth = await page.evaluate(() => document.body.scrollWidth);
+ results.push(`ℹ️ audit-log scrollWidth: ${auditScrollWidth}px`);
+ if (auditScrollWidth > 420) throw new Error(`audit-log 가로 오버플로우: ${auditScrollWidth}px`);
+ results.push('✅ audit-log 375px 레이아웃 정상');
+
+ await page.screenshot({ path: '/Users/gbpark/ERP-node/.agent-pipeline/browser-tests/result-audit-375.png', fullPage: true });
+ results.push('✅ audit-log 스크린샷 저장');
+
+ // ─── 3. standards 375px 레이아웃 깨짐 확인 ───
+ await page.goto('http://localhost:9771/admin/standards');
+ await page.waitForLoadState('domcontentloaded');
+ await page.waitForTimeout(3000);
+
+ const standardsHasError = await page.locator('[id="__next"] .nextjs-container-errors-body').isVisible().catch(() => false);
+ if (standardsHasError) throw new Error('standards 에러 오버레이 발견');
+ results.push('✅ standards 에러 없음');
+
+ const standardsBodyVisible = await page.locator('body').isVisible();
+ if (!standardsBodyVisible) throw new Error('standards body 렌더링 실패');
+ results.push('✅ standards body 렌더링 정상');
+
+ const standardsScrollWidth = await page.evaluate(() => document.body.scrollWidth);
+ results.push(`ℹ️ standards scrollWidth: ${standardsScrollWidth}px`);
+ if (standardsScrollWidth > 420) throw new Error(`standards 가로 오버플로우: ${standardsScrollWidth}px`);
+ results.push('✅ standards 375px 레이아웃 정상');
+
+ await page.screenshot({ path: '/Users/gbpark/ERP-node/.agent-pipeline/browser-tests/result.png', fullPage: true });
+ results.push('✅ standards 스크린샷 저장');
+
+ } catch (err) {
+ passed = false;
+ failReason = err.message;
+ results.push(`❌ 실패: ${err.message}`);
+ await page.screenshot({ path: '/Users/gbpark/ERP-node/.agent-pipeline/browser-tests/result-fail.png', fullPage: true }).catch(() => {});
+ } finally {
+ await browser.close();
+ }
+}
+
+run().then(() => {
+ const output = results.join('\n');
+ console.log(output);
+ const finalResult = passed ? 'RESULT: PASS' : `RESULT: FAIL - ${failReason}`;
+ writeFileSync('/tmp/pw-responsive-result.txt', output + '\n' + finalResult);
+ console.log(finalResult);
+ process.exit(passed ? 0 : 1);
+}).catch(err => {
+ const msg = `치명적 오류: ${err.message}`;
+ console.error(msg);
+ writeFileSync('/tmp/pw-responsive-result.txt', msg + '\nRESULT: FAIL - ' + err.message);
+ process.exit(1);
+});