diff --git a/docs/Design/kbx-foundation-v36/README.md b/docs/Design/kbx-foundation-v36/README.md deleted file mode 100644 index 1d3662ee..00000000 --- a/docs/Design/kbx-foundation-v36/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# KBX Foundation v36 — Recipe Verification · Generated Test Plan · Deterministic Home Workbench - -KBX v36는 v35의 Screen Recipe / Secure Scaffolder 기반을 실제 검증계약까지 닫는 Foundation iteration이다. 목표는 화면·컴포넌트 수를 더 늘리는 것이 아니라, T01~T09를 선택한 순간부터 표준 UX, 보안, 복구, 테스트 증적이 함께 따라오도록 만드는 것이다. - -## v36 핵심 - -- T01~T09 `testProfile` 추가 - - 필수 Scenario Kind - - 필수 Behavioral Tag - - 필수 Evidence - - 필수 Recovery/Interaction Check -- `generated/screen-recipe-verification-manifest.json` 추가 -- T01~T09 Recipe Verification **9/9 자동 폐쇄성** -- 각 Recipe는 실제 해당 Template을 사용하는 Screen의 Canonical E2E Scenario를 최소 1개 요구 -- T02 Master / T06 Queue / T07 Reconcile Canonical E2E Recovery Scenario 추가 -- Scaffolder가 신규 Vertical Slice에 `*.test-plan.ts` 자동 생성 -- 생성 Test Plan은 Canonical Scenario / Required Check / Required Evidence를 타입 계약으로 보존 -- 기존 Master / Transaction / Fast Entry의 명시적 `--write-permission` fail-closed 정책 유지 -- Home Attention Queue를 동일 우선순위 내 최신 발생 순으로 정렬 -- Home은 표시 상한과 별개로 권한 필터 후 실제 전체 건수 및 overflow를 정확히 표시 -- `buildKbxHomeAttentionQueue()` public API 추가, 기존 `buildKbxHomeAttention()` 호환 유지 -- Screen Recipe `testProfile` / `canonicalScenarioIds` 변경을 Release Impact에서 추적 -- Design Debt ratchet **131 유지** - -## 검증 - -```bash -node scripts/validate-kbx.mjs -``` - -최종 검증 기준: - -- 299 TS/Vue script units -- 20 Screen Definitions -- 84 Component Definitions -- 66 Component Catalog entries -- 153 Design Tokens -- 9 Screen Recipes -- 30 Canonical Test Scenarios -- Recipe Verification 9/9 -- 16 Navigation Entries -- Design Debt 131 <= 131 -- Release Impact major / major - -## 문서 - -- `docs/screen-recipe-verification-home-attention-v36.md` -- `docs/kbx-v36-standard-traceability.md` -- `docs/validation-report-v36.md` -- `docs/release/migration-guide.md` diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/erp/inventory-move/InventoryMovePage.vue b/docs/Design/kbx-foundation-v36/apps/web/src/modules/erp/inventory-move/InventoryMovePage.vue deleted file mode 100644 index c9fb0caf..00000000 --- a/docs/Design/kbx-foundation-v36/apps/web/src/modules/erp/inventory-move/InventoryMovePage.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/erp/items/ItemMasterPage.vue b/docs/Design/kbx-foundation-v36/apps/web/src/modules/erp/items/ItemMasterPage.vue deleted file mode 100644 index aee08f2f..00000000 --- a/docs/Design/kbx-foundation-v36/apps/web/src/modules/erp/items/ItemMasterPage.vue +++ /dev/null @@ -1,34 +0,0 @@ - - diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/oms/orders/register/OrderRegisterPage.vue b/docs/Design/kbx-foundation-v36/apps/web/src/modules/oms/orders/register/OrderRegisterPage.vue deleted file mode 100644 index 1fd5c723..00000000 --- a/docs/Design/kbx-foundation-v36/apps/web/src/modules/oms/orders/register/OrderRegisterPage.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/oms/orders/search/OrderListPage.vue b/docs/Design/kbx-foundation-v36/apps/web/src/modules/oms/orders/search/OrderListPage.vue deleted file mode 100644 index 1f5f3ea1..00000000 --- a/docs/Design/kbx-foundation-v36/apps/web/src/modules/oms/orders/search/OrderListPage.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/oms/orders/search/useOrderSearch.ts b/docs/Design/kbx-foundation-v36/apps/web/src/modules/oms/orders/search/useOrderSearch.ts deleted file mode 100644 index 6a9aef38..00000000 --- a/docs/Design/kbx-foundation-v36/apps/web/src/modules/oms/orders/search/useOrderSearch.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { computed, reactive, ref } from 'vue' -import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query' -import { useRouter } from 'vue-router' -import type { KbxSelectionState } from '@kbx/contracts' -import { toKbxBulkSelectionRequest } from '@kbx/ui' -import { orderApi, type OrderBulkFilter, type OrderSearchFilter } from './orderApi' -import { kbxTelemetry, startKbxTask } from '../../../telemetry/kbxTelemetryClient' -import { orderListScreen } from './order-list.definition' - -function todayIso() { return new Date().toISOString().slice(0, 10) } - -export function useOrderSearch() { - const router = useRouter() - const queryClient = useQueryClient() - const selectionState = ref>({ mode:'explicit', selectedIds:[] }) - - const search = reactive({ - from: todayIso(), to: todayIso(), channelId: null, status: null, - exceptionOnly: false, keyword: '', page: 1, pageSize: 100, - }) - const appliedSearch = ref({ ...search }) - - const query = useQuery({ - queryKey: computed(() => ['oms', 'orders', appliedSearch.value]), - queryFn: () => orderApi.search({ ...appliedSearch.value }), - enabled: false, - }) - - const shipMutation = useMutation({ - mutationFn: (selection: ReturnType) => orderApi.ship(selection), - onSuccess: async () => { - selectionState.value = { mode:'explicit', selectedIds:[] } - await queryClient.invalidateQueries({ queryKey: ['oms', 'orders'] }) - await query.refetch() - }, - }) - - const selectionCount = computed(() => selectionState.value.mode === 'all-filtered' - ? Math.max((query.data.value?.totalCount ?? 0) - (selectionState.value.excludedIds?.length ?? 0), 0) - : selectionState.value.selectedIds.length) - - function currentBulkFilter(): OrderBulkFilter { - return { - from: appliedSearch.value.from, to: appliedSearch.value.to, channelId: appliedSearch.value.channelId, - status: appliedSearch.value.status, exceptionOnly: appliedSearch.value.exceptionOnly, keyword: appliedSearch.value.keyword, - } - } - - function currentBulkSelection() { - return toKbxBulkSelectionRequest(selectionState.value, currentBulkFilter()) - } - - async function executeSearch() { - const started=performance.now();selectionState.value={mode:'explicit',selectedIds:[]};appliedSearch.value={...search} - kbxTelemetry.track('command.execute',{screenId:orderListScreen.id,screenVersion:orderListScreen.version,attributes:{commandId:'search',operationKind:'query'}}) - kbxTelemetry.track('search.execute',{screenId:orderListScreen.id,screenVersion:orderListScreen.version,attributes:{resultBucket:'requested'}}) - await query.refetch() - kbxTelemetry.track('command.succeeded',{screenId:orderListScreen.id,screenVersion:orderListScreen.version,durationMs:Math.round(performance.now()-started),attributes:{commandId:'search',operationKind:'query'}}) - } - - async function executeCommand(commandId: string) { - const handlers: Record unknown | Promise> = { - search: executeSearch, - new: () => router.push({ name: 'oms-order-new' }), - ship: async () => { - const task=startKbxTask(orderListScreen.id,orderListScreen.version,'ship-orders');task.interaction('bulk-command','ship') - kbxTelemetry.track('grid.bulk_action',{screenId:orderListScreen.id,screenVersion:orderListScreen.version,taskSessionId:task.id,attributes:{commandId:'ship',countBucket:selectionCount.value<10?'1-9':selectionCount.value<100?'10-99':'100+',selectionMode:selectionState.value.mode}}) - try{await shipMutation.mutateAsync(currentBulkSelection());task.complete('success')}catch(error){task.abandon('failed');throw error} - }, - hold: () => Promise.resolve(), - excel: () => Promise.resolve(), - } - return handlers[commandId]?.() - } - - return { - search, - appliedSearch, - rows: computed(() => query.data.value?.items ?? []), - result: computed(() => query.data.value), - loading: computed(() => query.isFetching.value), - error: computed(() => query.error.value), - selectionState, - selectionCount, - executeSearch, - executeCommand, - } -} diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/wms/counting/WmsCountingPage.vue b/docs/Design/kbx-foundation-v36/apps/web/src/modules/wms/counting/WmsCountingPage.vue deleted file mode 100644 index 0d5a3cc1..00000000 --- a/docs/Design/kbx-foundation-v36/apps/web/src/modules/wms/counting/WmsCountingPage.vue +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/wms/putaway/WmsPutawayPage.vue b/docs/Design/kbx-foundation-v36/apps/web/src/modules/wms/putaway/WmsPutawayPage.vue deleted file mode 100644 index cb1d1f50..00000000 --- a/docs/Design/kbx-foundation-v36/apps/web/src/modules/wms/putaway/WmsPutawayPage.vue +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/wms/receiving/WmsReceivingPage.vue b/docs/Design/kbx-foundation-v36/apps/web/src/modules/wms/receiving/WmsReceivingPage.vue deleted file mode 100644 index 88c579ca..00000000 --- a/docs/Design/kbx-foundation-v36/apps/web/src/modules/wms/receiving/WmsReceivingPage.vue +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/wms/work/WmsWorkPage.vue b/docs/Design/kbx-foundation-v36/apps/web/src/modules/wms/work/WmsWorkPage.vue deleted file mode 100644 index 1cce9a92..00000000 --- a/docs/Design/kbx-foundation-v36/apps/web/src/modules/wms/work/WmsWorkPage.vue +++ /dev/null @@ -1,18 +0,0 @@ - - diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/wms/work/workApi.ts b/docs/Design/kbx-foundation-v36/apps/web/src/modules/wms/work/workApi.ts deleted file mode 100644 index 0b925245..00000000 --- a/docs/Design/kbx-foundation-v36/apps/web/src/modules/wms/work/workApi.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { WmsWorkRow } from './work.definition' -export interface WmsWorkSearch { taskType?: string; status?: string; owner?: string; keyword?: string } -export async function searchWmsWork(_search: WmsWorkSearch): Promise { return [] } -export function routeForWmsTask(row: WmsWorkRow): string { - switch (row.taskType) { - case 'RECEIVING': return `/wms/receiving/${row.taskId}` - case 'PUTAWAY': return `/wms/putaway/${row.taskId}` - case 'PICKING': return `/wms/picking/${row.taskId}` - case 'COUNTING': return `/wms/counting/${row.taskId}` - default: return `/wms/work?task=${encodeURIComponent(row.taskId)}` - } -} diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/preferences/screenPreferenceStore.ts b/docs/Design/kbx-foundation-v36/apps/web/src/preferences/screenPreferenceStore.ts deleted file mode 100644 index 77f3c020..00000000 --- a/docs/Design/kbx-foundation-v36/apps/web/src/preferences/screenPreferenceStore.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { KbxScreenPreference } from '@kbx/contracts' - -const PREFIX = 'kbx.screen.preference.' - -export function loadScreenPreference(screenId: string): KbxScreenPreference | null { - const raw = localStorage.getItem(PREFIX + screenId) - if (!raw) return null - try { return JSON.parse(raw) as KbxScreenPreference } catch { return null } -} - -export function saveScreenPreference(preference: KbxScreenPreference) { - localStorage.setItem(PREFIX + preference.screenId, JSON.stringify(preference)) -} - -export function clearScreenPreference(screenId: string) { - localStorage.removeItem(PREFIX + screenId) -} diff --git a/docs/Design/kbx-foundation-v36/governance/release.json b/docs/Design/kbx-foundation-v36/governance/release.json deleted file mode 100644 index e05cce2f..00000000 --- a/docs/Design/kbx-foundation-v36/governance/release.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "foundationIteration": 36, - "contractVersion": "1.22.0", - "baseline": "v21", - "declaredChangeLevel": "major", - "releaseMode": "recipe-verification-generated-test-plan-home-attention-ordering-hardening", - "notes": "v36 closes T01-T09 recipes to executable verification evidence. Every recipe now declares required scenario kinds, behavioral tags, evidence classes, and stable verification checks; generated verification proves 9/9 recipe coverage and requires a representative E2E scenario on a screen that actually uses each template. T02/T06/T07 gain canonical E2E recovery scenarios. The secure scaffolder now emits a typed per-screen recipe test plan beside each new Vertical Slice, while keeping explicit write-permission fail-closed behavior. Home attention becomes deterministic and operationally accurate: business priority remains dirty -> failed/partial job -> urgent notification -> running job -> ordinary notification, items within one priority are newest-first, and overflow reports the real authorized total instead of only the visible slice. Screen recipe test-profile and canonical-scenario changes are now release-impact tracked." -} diff --git a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/components/KbxExcelMenu.vue b/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/components/KbxExcelMenu.vue deleted file mode 100644 index d39a1fc9..00000000 --- a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/components/KbxExcelMenu.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - - - diff --git a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/components/KbxMasterPage.vue b/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/components/KbxMasterPage.vue deleted file mode 100644 index 3ccc19b8..00000000 --- a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/components/KbxMasterPage.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - - - diff --git a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/components/KbxQueuePage.vue b/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/components/KbxQueuePage.vue deleted file mode 100644 index 8a5453e0..00000000 --- a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/components/KbxQueuePage.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - - - diff --git a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/components/KbxTransactionPage.vue b/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/components/KbxTransactionPage.vue deleted file mode 100644 index 3a1c9f69..00000000 --- a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/components/KbxTransactionPage.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - - - diff --git a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/shell/KbxGlobalHeader.vue b/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/shell/KbxGlobalHeader.vue deleted file mode 100644 index 58daf34d..00000000 --- a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/shell/KbxGlobalHeader.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/shell/KbxHomePage.vue b/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/shell/KbxHomePage.vue deleted file mode 100644 index a01acab7..00000000 --- a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/shell/KbxHomePage.vue +++ /dev/null @@ -1,121 +0,0 @@ - - - - - diff --git a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/shell/KbxSideNavigation.vue b/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/shell/KbxSideNavigation.vue deleted file mode 100644 index de8a2add..00000000 --- a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/shell/KbxSideNavigation.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - - - diff --git a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/shell/KbxUnsavedChangesDialog.vue b/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/shell/KbxUnsavedChangesDialog.vue deleted file mode 100644 index 9c15ff02..00000000 --- a/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/shell/KbxUnsavedChangesDialog.vue +++ /dev/null @@ -1,18 +0,0 @@ - - - diff --git a/docs/Design/kbx-foundation-v36/scripts/generate-design-tokens.mjs b/docs/Design/kbx-foundation-v36/scripts/generate-design-tokens.mjs deleted file mode 100644 index d3ba1512..00000000 --- a/docs/Design/kbx-foundation-v36/scripts/generate-design-tokens.mjs +++ /dev/null @@ -1,63 +0,0 @@ -import fs from 'node:fs' -import path from 'node:path' - -const root = process.cwd() -const sourcePath = path.join(root, 'packages/kbx-ui/src/tokens/source/kbx.tokens.json') -const cssPath = path.join(root, 'packages/kbx-ui/src/tokens/kbx.css') -const manifestPath = path.join(root, 'generated/token-manifest.json') -const figmaPath = path.join(root, 'design/figma/variables.contract.json') -const source = JSON.parse(fs.readFileSync(sourcePath, 'utf8')) -const byPath = new Map(source.tokens.map(t => [t.path, t])) - -function cssValue(value) { - const ref = /^\{(.+)\}$/.exec(value) - if (!ref) return value - const token = byPath.get(ref[1]) - if (!token) throw new Error(`Unknown token reference: ${value}`) - return `var(${token.css})` -} - -const roles = ['primitive', 'semantic', 'component'] -const lines = [':root {'] -for (const role of roles) { - lines.push(` /* ${role[0].toUpperCase()}${role.slice(1)} tokens */`) - for (const token of source.tokens.filter(t => t.role === role)) { - lines.push(` ${token.css}: ${cssValue(token.value)};`) - } - lines.push('') -} -lines.push('}') -lines.push('') -lines.push('html, body, button, input, select, textarea {') -lines.push(' font-family: var(--kbx-font-family);') -lines.push('}') -lines.push('') -for (const [mode, overrides] of Object.entries(source.modes ?? {})) { - if (mode === 'compact') continue - lines.push(`[data-kbx-density="${mode}"] {`) - for (const [tokenPath, value] of Object.entries(overrides)) { - const token = byPath.get(tokenPath) - if (!token) throw new Error(`Unknown mode token: ${tokenPath}`) - lines.push(` ${token.css}: ${value};`) - } - lines.push('}') - lines.push('') -} -lines.push(':focus-visible {') -lines.push(' outline: 2px solid var(--kbx-color-focus);') -lines.push(' outline-offset: 2px;') -lines.push('}') -lines.push('') - -fs.mkdirSync(path.dirname(manifestPath), { recursive: true }) -fs.mkdirSync(path.dirname(figmaPath), { recursive: true }) -fs.writeFileSync(cssPath, lines.join('\n')) -fs.writeFileSync(manifestPath, JSON.stringify({version: source.version, tokens: source.tokens, modes: source.modes}, null, 2) + '\n') - -const collections = { - 'KBX Primitive': source.tokens.filter(t => t.role === 'primitive').map(t => ({name:t.path, type:t.type, value:t.value, description:t.description ?? ''})), - 'KBX Semantic': source.tokens.filter(t => t.role === 'semantic').map(t => ({name:t.path, type:t.type, value:t.value, description:t.description ?? ''})), - 'KBX Component': source.tokens.filter(t => t.role === 'component').map(t => ({name:t.path, type:t.type, value:t.value, description:t.description ?? '', modes:Object.fromEntries(Object.entries(source.modes ?? {}).filter(([,o]) => t.path in o).map(([mode,o]) => [mode,o[t.path]]))})) -} -fs.writeFileSync(figmaPath, JSON.stringify({schemaVersion:'1.0', tokenVersion:source.version, collections}, null, 2) + '\n') -console.log(`Generated ${source.tokens.length} design tokens -> CSS / token manifest / Figma variable contract`) diff --git a/docs/Design/kbx-foundation-v36/.gitea/pull_request_template.md b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/.gitea/pull_request_template.md similarity index 100% rename from docs/Design/kbx-foundation-v36/.gitea/pull_request_template.md rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/.gitea/pull_request_template.md diff --git a/docs/Design/kbx-foundation-v36/.gitea/workflows/kbx-quality-gate.yaml b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/.gitea/workflows/kbx-quality-gate.yaml similarity index 100% rename from docs/Design/kbx-foundation-v36/.gitea/workflows/kbx-quality-gate.yaml rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/.gitea/workflows/kbx-quality-gate.yaml diff --git a/docs/Design/kbx-foundation-v36/.gitea/workflows/kbx-release-readiness.yaml b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/.gitea/workflows/kbx-release-readiness.yaml similarity index 100% rename from docs/Design/kbx-foundation-v36/.gitea/workflows/kbx-release-readiness.yaml rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/.gitea/workflows/kbx-release-readiness.yaml diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/README.md b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/README.md new file mode 100644 index 00000000..278e8f79 --- /dev/null +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/README.md @@ -0,0 +1,355 @@ +# KBX Foundation v50 — FE Operational Truth · Retryability · Home Overflow · Theme Hardening + +## v50 핵심 + +- T01~T09 주요 Template에 `errorRetryable` 계약을 전파해 영구 오류에서 무의미한 `[다시 조회]` 제거 +- `KbxTemplateStateBoundary 1.3.0`: transient error와 non-retryable error를 UI 계약으로 분리 +- `WMS-WORK-001 1.2.0`: `WMS_WORK_QUERY_NOT_CONNECTED`를 `retryable:false`로 수정, API 미연결에 무한 Retry 착시 제거 +- Home Attention: 기본 8개 유지, 총 건수 >24이면 `전체`가 아니라 `우선 24개 보기`로 정확히 표현 +- 24개 이후 남은 Attention은 `작업 센터 전체 / 알림 센터 전체`로 직접 연결 +- `KbxHomePage 1.10.0`, `KbxQueuePage 1.9.0`, 기타 T01~T09 Template 공개 Retry 계약 Minor Upgrade +- Reference HTML/JavaScript도 28개 fixture로 8→24 bounded disclosure와 Center navigation 재현 +- KBX Business Light/Dark + Forced Colors semantic theme 유지, Theme는 Retry/Permission/Domain truth를 결정하지 않음 +- Browser Reference QA **16/16 PASS**, 전체 `validate:kbx` PASS, Design Debt **116 <= 131** + +상세: `docs/frontend/KBX-FE-QA-Hardening-v50.md` +검증: `docs/validation-report-v50.md` + +--- + +# KBX Foundation v49 — FE Workbench · WMS Truthful Boundary · Theme Hardening + +## v49 핵심 + +- Home `확인 필요`가 상위 8개만 숨긴 채 끝나지 않도록 `8개 ↔ 전체(최대 24개)` 접근성 Disclosure 추가 +- Home 우선업무 출처를 미저장/실패/중요알림/진행/알림으로 요약해 AX 판단량 감소 +- `KbxQueuePage 1.8.0`: T06 Empty/Error/Retry copy와 Empty recovery Action 계약 추가 +- `WMS-WORK-001 1.1.0`: Demo에서는 실제 필터 가능한 Queue, 일반 Runtime에서는 미연결 API를 `0건`으로 가장하지 않고 Integration Problem으로 fail-explicit +- Receiving/Putaway/Counting `1.1.0`: client-only 성공 상태를 Production에서 차단하고 Demo Mode에서만 FE scanner state machine 검증 +- `KbxWmsMobilePage 1.7.0`: Production execution 차단 시 빈 sticky action footer를 숨기는 `actionsVisible` 계약 +- WMS 네트워크 표시를 `useKbxNetworkState()`로 통일하고 hardcoded online 제거 +- Reference Home도 동일한 8→전체 Priority disclosure와 KBX Business Light/Dark semantic theme 적용 +- Browser Reference QA **10/10 PASS**: Attention disclosure, keyboard collapse, Dark theme, 1280×720 overflow 0, runtime error 0 +- WMS에서 Server validation / Idempotency / Audit 이전의 성공표시는 금지한다는 경계를 FE Gate로 고정 + +상세: `docs/frontend/KBX-FE-QA-Hardening-v49.md` +검증: `docs/validation-report-v49.md` + +--- + +# KBX Foundation v48 — FE Template Completion · Real Search/File · Theme-safe Recovery Hardening + +## v48 핵심 + +- 실제 Vue T04/T06/T07/T08에 `KbxProblemFeedback` 기반 Actionable Problem Recovery 확장 +- T04~T08 Template 계층의 `problem / retry / dismiss` 계약 통일 +- `KbxProblemFeedback 1.1.0`: 상위 Validation Problem의 bounded summary 추가, Field/Cell Validation 역할은 유지 +- Reference T05/T06/T07: Toast/표시 위주의 조회를 실제 검색 → Loading → 결과/Empty → 복구 상태 전이로 교체 +- Chromium QA에서 발견한 Search Panel Mouse no-op 결함 수정: T05/T06/T07 `[조회]` click과 Enter/F3 동일 실행 +- Reference T08: 샘플 파일명 주입 제거, 실제 Browser File input/Drop + `.xlsx/.xls` + 20MB guard +- T08 6단계(`파일→매핑→검증→미리보기→반영→결과`)와 CSS 6-column 정합화 +- KBX Business Light/Dark semantic theme, Home/Workspace, Permission/Domain server-authority 경계 유지 +- Browser Reference QA **30/30 PASS**: T05~T08 completion loops, Dark theme, 1280px overflow 0, runtime error 0 + +상세: `docs/frontend/KBX-FE-QA-Hardening-v48.md` +검증: `docs/validation-report-v48.md` + +--- + +# KBX Foundation v47 — Actionable Problem Recovery · Theme-safe FE Hardening + +## v47 핵심 + +- 실제 T01/T02/T03에서 Command 실패를 조회 Error와 분리: `business-rule / permission / not-found / integration / system` 인라인 Problem Surface +- `KbxProblemFeedback 1.0.0`: 문제 유형 텍스트, 원인, 참조번호, retryable 재시도, 닫기 +- Server `problem.actions`는 기본 미노출 + 명시적 allow-list만 렌더링하여 임의 Action ID → FE Command 실행 차단 +- `OMS-ORD-001` Bulk 출고, `OMS-ORD-002` 저장/확정, `ERP-MST-ITEM-001` 목록 로드/저장/사용중지 실패 시 현재 화면·선택·입력 Context 유지 +- `KbxListPage 1.8.0`, `KbxMasterPage 1.7.0`, `KbxTransactionPage 1.7.0` +- Reference HTML/JavaScript: 예외 주문 출고지시 → `업무 규칙` 인라인 오류 → 문제 주문만 선택 복구 +- Browser Reference QA **12/12 PASS**: Dark Theme, 문제표시/복구/닫기, 1280px overflow 0, runtime error 0 +- 전체 Problem 계약에서도 Validation은 Field/Cell, Conflict는 최신값 비교, Command Problem은 인라인 복구로 역할 분리 + +상세: `docs/frontend/KBX-FE-QA-Hardening-v47.md` +검증: `docs/validation-report-v47.md` + +--- + +# KBX Foundation v46 — Safe Workspace Resume · T09 Recovery · Theme-preserving FE Hardening + +## v46 핵심 + +- 열린 Workspace의 **clean 업무 위치만 sessionStorage**에 복원, Query/Hash/Dirty Form·Grid 값은 저장 금지 +- 복원 전 Permission + Screen Registry + Safe Route 재검증, `resumed` 상태는 실제 업무 진입 시 제거 +- Home `바로 시작`에서 `복원` 업무를 구분하고 미저장 편집은 안전상 복원 제외 사실만 표시 +- Home에서 active tab이 없어도 첫 Workspace Tab을 keyboard roving entry로 제공 +- Navigation Preference의 최초 localStorage read까지 fail-soft로 통일 +- `KbxWorkspaceTabs 1.3.0`, `KbxHomePage 1.8.0`, `KbxWmsMobilePage 1.6.0` +- T09 WMS Picking: TanStack Query Loading/Error/Ready → 공통 Mobile State Boundary, 오류 `[작업 다시 불러오기]` 실제 refetch 연결 +- Reference HTML/CSS/JavaScript Browser QA **20/20 PASS**: clean resume, dirty exclusion, storage-blocked Shell, Dark Theme, 1280px overflow +- Theme/Permission/Domain 경계는 유지: Theme/Resume는 FE 표현·생산성 상태이며 업무 truth가 아님 +- 전체 `validate:kbx` PASS: 153 tokens / 20 screens / 84 components / 69 catalog entries / 306 TS·Vue units / Design Debt **116 <= 131** + +상세: `docs/frontend/KBX-FE-QA-Hardening-v46.md` +검증: `docs/validation-report-v46.md` + +--- + +# KBX Foundation v45 — Safe Preference · Home Context · Theme Hardening + +## v45 핵심 + +- 실제 Vue `OMS-ORD-001`에 `기본조건 저장 / 마지막 조회조건 기억`을 연결하되 자유검색어는 localStorage 저장에서 제외 +- Screen Preference key를 `Tenant/User scope + ScreenId + ScreenVersion`으로 격리하고 size/type 검증 + fail-soft 적용 +- Home의 실패/진행 작업 클릭 시 Runtime Center 전체가 아니라 해당 Operation/Notification으로 Focus +- `KbxSearchPanel 1.4.0`, `KbxOperationCenter 1.1.0`, `KbxNotificationCenter 1.1.0` +- HTML/JavaScript Reference Lab에도 동일한 안전한 조회조건 기억과 Light/Dark Theme parity 적용 +- Browser QA 14/14: 저장/복원, 자유검색어 비저장, Dark Theme, 1280 Home overflow, runtime error 0 +- Domain Rule·Permission·Concurrency·Audit은 여전히 Server authority이며 Preference는 표현/생산성 상태에 한정 + +상세: `docs/frontend/KBX-FE-QA-Hardening-v45.md` +검증: `docs/validation-report-v45.md` + + +## v44 핵심 + +- 실제 App artifact version `v44-fe-theme-state`로 정합화 +- Header 작업/알림 trigger ↔ runtime panel `aria-expanded/aria-controls` 연결 +- runtime panel: 명시 제목/닫기, Esc 닫기, open 시 panel focus, close 시 trigger focus restore +- route 전환/메뉴검색/Utility Rail 진입 시 non-modal runtime panel을 정리해 surface stacking 방지 +- Windows Forced Colors에서 Light/Dark palette 대신 OS system color로 fail-safe +- Forced Colors에서도 현재 화면/선택/Focus를 색상 하나가 아니라 outline/텍스트 상태로 유지 +- T01 Empty → `[조회조건 초기화]` → 즉시 재조회 공통 Recovery contract +- FE Reference Lab 작업/알림을 실제 Vue와 동일한 non-modal System Panel로 정렬 +- FE Reference Lab에도 동일 high-contrast bridge 적용 +- Browser matrix **28/28 PASS**: T01~T09 Light/Dark, T01 Empty recovery, 1280px, Forced Colors, runtime error 0 +- v44 전용 static QA gate를 전체 `validate:kbx`에 편입 + +상세: `docs/frontend/KBX-FE-QA-Hardening-v44.md` +검증: `docs/validation-report-v44.md` + +--- + +# KBX Foundation v43 — Semantic Theme · Home Navigation Hierarchy · Light/Dark FE Hardening + +이번 iteration은 v42의 first-class Vue runtime 위에 **KBX Business Light / Dark를 실제 업무 Semantic Theme으로 통합**한다. Theme를 장식용 팔레트로 만들지 않고 `surface / text / border / primary / success / warning / danger / focus / AI` 의미 계약을 Application Shell, Home, PrimeVue, AG Grid, 공통 Component, FE Reference Lab까지 공유한다. + +## v43 핵심 + +- Design Token source 1.9.0: `KBX Business Light / Dark` semantic override 계약 +- Theme runtime API + `light|dark` allow-list/fail-safe preference persistence +- Global Header에 명시적 Light/Dark 전환 + 접근성 label + narrow-header reflow +- PrimeVue dark selector와 KBX theme root 연결 +- AG Grid Quartz foreground/background/accent/border/header/hover/selection을 KBX semantic token으로 연결 +- Home: `확인 필요 → 바로 시작 → 모듈 업무`의 시각 우선순위를 warning/danger/info semantic으로 강화하되 Dashboard card 증식 금지 +- Side Navigation 현재 화면 primary inset marker +- Network/Dialog/Toast/Reference surface 하드코딩 감소 +- FE Reference Lab Light/Dark parity + 실제 Dark inheritance 결함 발견/수정 +- Theme Browser QA **16/16 PASS**: T01~T09 Dark render, T09 WMS mobile mode, 1280px overflow 0, runtime error 0 +- 전체 `validate:kbx` PASS / 305 TS·Vue script units / Design Debt **116 (ratchet 131 대비 감소)** + +### v43에서 완료로 계산하지 않는 항목 + +첨부 표준은 Semantic Color/Density/Accessibility를 요구하지만 Light/Dark 자체를 명시적으로 강제하지 않는다. Dark는 사용자 요청에 따른 구현 확장이다. 또한 현재 실행환경은 package registry 접근이 차단되어 **실제 Vite + PrimeVue + AG Grid live-runtime Theme E2E를 통과했다고 주장하지 않는다.** Browser 증적은 실제 FE Reference HTML/CSS/JS asset 기준이며 production CSP는 별도 정적 Gate로 검증한다. Server-side full-query XLSX 역시 기존 잔여 P1이다. + +상세: `docs/frontend/KBX-FE-QA-Hardening-v43.md` +검증: `docs/validation-report-v43.md` + +--- + +# KBX Foundation v42 — First-class Vue Runtime · Secure Demo Transport · Home Context · Excel Menu · Zoom Hardening + +이번 iteration은 v41의 Golden Screen parity를 넘어 **실제 FE 제품을 독립적으로 기동·빌드·검증할 수 있는 Runtime 경계**를 우선 완성한다. BE 결합을 더 늘리지 않고 `apps/web`을 Vite 8 application으로 명시하고, Demo Mode도 별도 Mock UI가 아니라 실제 Axios/kbxApi 경계의 transport adapter로 구성했다. + +## v42 핵심 + +- `apps/web` first-class Vite 8 package / `index.html` / `main.ts` / `App.vue` / typecheck-build scripts +- Vue + Pinia + Router + TanStack Query + PrimeVue + `installKbx()` 실제 application bootstrap +- `--mode demo`에서만 fixture HTTP adapter 사용, 일반 runtime은 real transport + permission fail-closed +- Demo unknown operation은 성공 위장이 아니라 `DEMO_OPERATION_UNSUPPORTED` 명시 실패 +- Home/Notification의 OMS 예외 Context를 `useOrderSearch()`가 allow-list로 hydrate하고 자동 조회 +- `KbxCommandBar` ↔ `KbxExcelMenu` 실제 통합, Excel 5개 표준 진입점 + keyboard/focus contract +- 일부 row만 로드된 경우 local CSV를 전체 Export로 오인하지 않도록 다운로드 차단 +- 56rem/40rem Shell pressure hardening: 200% Zoom에서 Workspace 우선 보존 +- v42 runtime-readiness Gate를 전체 `validate:kbx`에 편입 +- 304 TS/Vue syntax + 전체 Governance PASS / Design Debt **122 (ratchet 131 대비 감소)** + +### v42에서 완료로 계산하지 않는 항목 + +현재 실행환경은 npm registry 접근이 차단되어 pnpm 의존성 설치가 불가능하다. 따라서 **실제 Vite build/server와 Vue/PrimeVue/AG Grid Playwright를 통과했다고 주장하지 않는다.** 또한 T01의 전체 검색조건 XLSX/Background Export API는 아직 미연결이며, 부분 row CSV는 전체 Export로 오인될 수 있어 의도적으로 차단한다. + +상세: `docs/frontend/KBX-FE-QA-Hardening-v42.md` +검증: `docs/validation-report-v42.md` + +--- + +# KBX Foundation v41 — Actual Vue Golden Screen Parity · Controlled Grid Selection · Drawer/Dirty/Focus Hardening + +이번 iteration은 v40의 FE Reference Lab을 더 확장하지 않고, **실제 Vue/PrimeVue/AG Grid Golden Screen이 동일한 업무 UX 계약을 지키는지**를 우선 검수한다. 대상은 `OMS-ORD-001`, `OMS-ORD-002`, `ERP-MST-ITEM-001`, `KbxDataGrid`, `KbxLookup`, `KbxUnsavedChangesDialog`, Application Shell navigation이다. + +## v41 핵심 + +- OMS-ORD-001 주문번호 Link의 no-op 제거: 실제 Order Detail Drawer 연결 +- 행 Double Click도 Drawer 우선으로 통일하여 List 조회 Context 보존 +- Drawer async request sequence guard로 늦게 도착한 응답의 stale overwrite 방지 +- KbxDataGrid: AG Grid 33+ multiRow selection contract, checkbox/header checkbox, 외부 Selection State ↔ 내부 Grid selection 동기화 +- Grid error navigation이 Header/Form 오류를 Cell 오류로 잘못 세지 않도록 grid-scoped error 분리 +- KbxDataGrid에 `focusCell`, `focusNextEditableCell`, `selectRowByKey`, `clearSelection` 제어 API 추가 +- KbxLookup: 직접 코드 Enter 성공도 다음 Control로 Focus 이동, 선택/취소 Focus 정책 분리, 접근성 label 고유화 +- ERP-MST-ITEM-001: Dirty 상태에서 다른 Master 선택/New 전환 시 3-way Guard로 편집값 덮어쓰기 방지 +- OMS-ORD-002: F2 품목 선택 후 다음 Editable Cell Focus, New/Copy 내부 전환에도 Dirty Guard 적용 +- KbxUnsavedChangesDialog: Esc/Tab/Shift+Tab Focus Trap + 닫힌 후 Focus Restore +- OMS 주문관리의 구현되지 않은 `보류` no-op Command 제거 +- Side Navigation 즐겨찾기 접근성 이름과 현재 화면 상태 보강 +- Component version: KbxDataGrid 1.5.0 / KbxLookup 1.4.0 / UnsavedChangesDialog 1.1.0 / SideNavigation 1.3.1 +- 전체 `validate:kbx` PASS / Design Debt **131 유지** + +### v41에서 완료로 계산하지 않는 항목 + +현재 OMS 주문조회 `엑셀` 동작은 AG Grid CSV export 수준이다. KBX 표준의 Excel 메뉴 전체 계약(현재 조회결과 Excel, 양식, 업로드, 붙여넣기, 최근 업로드)은 아직 완료가 아니므로 **잔여 P1**로 유지한다. 또한 이 산출물에는 `node_modules`가 없어 실제 Vue runtime E2E/visual regression을 실행하지 못했다. 이번 v41 증적은 TypeScript/Vue syntax transpile + 전체 Foundation governance/static parity gate이며, 실제 앱 런타임 E2E는 다음 Gate다. + +상세: `docs/frontend/KBX-FE-QA-Hardening-v41.md` +검증: `docs/validation-report-v41.md` + +--- + +# KBX Foundation v40 — FE Completion Loop · Validation-safe Shortcut · Master/Transaction/Queue/Reconcile Hardening + +이번 iteration은 BE 구조나 새 Metadata Framework를 늘리지 않고, 실제 HTML/CSS/JavaScript FE에서 **T01~T09의 최소 업무 완료 루프가 닫히는지**를 기준으로 고도화한다. v39의 Navigation/Lookup/Drawer/Job/WMS 기반 위에서 T01 실제 검색, T02 Master CRUD, T03 Header+Detail 저장/확정, T06 Queue Action, T07 Reconcile Resolution을 실제 상태 전이로 연결했다. + +## v40 핵심 + +- T01 검색조건(판매채널/상태/키워드/예외) ↔ 실제 Grid 결과 결합 +- T02 Master 신규/수정/복사/검증/F2 Lookup/저장/상태/Dirty 전환 +- T03 Header+Detail 입력/계산/Validation/F8 저장/작성→확정 +- T06 선택 기반 작업자 배정/Wave 생성 실제 Row 갱신 +- T07 선택 기반 재처리 후 Expected/Actual/Difference 및 mismatch 목록 갱신 +- Global F8이 실제 화면 Save Handler를 호출하도록 수정 — Validation 우회 제거 +- Hash/Refresh/Close까지 Dirty 보호 확대 +- Browser QA **49/49 PASS**, Runtime Error 0 +- 1280×720 document overflow 0, WMS 390×844 Full View +- Design Debt ratchet **131 유지** + +상세: `docs/frontend/KBX-FE-QA-Hardening-v40.md` +검증: `docs/validation-report-v40.md` + +--- + +# KBX Foundation v39 — FE Product Parity · Lookup · Dirty Navigation · Job/WMS Hardening + +이번 iteration은 BE 구조를 늘리지 않고 실제 HTML/CSS/JavaScript와 핵심 Vue Component의 사용성을 고도화한다. v38의 상태전이 기반 위에 **Context-aware Home, 화면 이동 Dirty Guard, 실제 F2 Lookup, Utility Drawer, T08 Background Job UX, T09 Mobile Full View/중복 Scan Guard**를 추가했다. + +## v39 핵심 + +- Home 예외 → 실제 대상 필터/Context 적용 후 이동 +- Dirty 상태에서 Tab Close뿐 아니라 Side Nav/Home/다른 Tab 이동도 3-way Guard +- T02/T03 F2 Lookup: Search/Arrow/Enter/Esc/Focus Next +- 실제 `KbxLookup.vue`: 선택 후 다음 Control, 취소 시 Trigger Focus 복원 +- Help/AI/Suggestion/Job/Notification 실제 Drawer + inert/Focus Trap +- T08 6단계 + Background Progress + 화면 이탈/복귀 +- T09 중복 Barcode Guard + Network Failure No-Commit UX +- 767px 이하 WMS Mobile Full View +- 1280×720 및 720px zoom-pressure document overflow 없음 +- Browser QA 31/31 PASS · Runtime Error 0 +- Design Debt ratchet 131 유지 + +상세: `docs/frontend/KBX-FE-QA-Hardening-v39.md` + +--- + +# KBX Foundation v38 — FE QA Hardening · Interaction-complete Reference Lab + +이 패키지는 기존 v36 Foundation 거버넌스를 유지하면서 v37 FE Reference Lab을 **실제 상태 전이·복구·키보드·보안 실패 격리** 기준으로 고도화한 v38 작업본이다. 이번 iteration은 BE 구조를 확장하지 않고 실제 HTML/CSS/JavaScript FE 완성도에 집중한다. + +## v38 핵심 + +- Home: 예외 우선 → 이어서 처리 → 모듈별 업무 → Ctrl+K 검색 +- T01: Quick Filter/전체선택/Bulk/Drawer/Data State/선택 기반 Command disable +- T02: Master→Detail 실제 전환 + Dirty 보호 +- T03: Header/Detail edit + Enter 흐름 + Dirty/Save +- T04: Fast Entry input + TSV paste + Add/Duplicate/Fill Down + error gate +- T05: 품목→Location→재고이력 동시 문맥 갱신 +- T06/T07: Queue/불일치 필터 실제 적용 +- T08: 파일 gate + 이전/다음 + Mapping/Validation/Commit/Result +- T09: keyboard-wedge Scan + wrong barcode recovery + quantity/exception dialog +- CSP + dynamic HTML escape + localStorage fail-soft + prompt/eval/document.write 금지 +- Headless Chromium interaction evidence 및 1440×900/390px 화면 증적 +- Design Debt ratchet **131 유지** + +## v38 검증 + +```bash +node scripts/validate-fe-reference.mjs +node scripts/validate-kbx.mjs +``` + +상세: `docs/frontend/KBX-FE-QA-Hardening-v38.md` + +--- + +# KBX Foundation v36 — Recipe Verification · Generated Test Plan · Deterministic Home Workbench + +KBX v36는 v35의 Screen Recipe / Secure Scaffolder 기반을 실제 검증계약까지 닫는 Foundation iteration이다. 목표는 화면·컴포넌트 수를 더 늘리는 것이 아니라, T01~T09를 선택한 순간부터 표준 UX, 보안, 복구, 테스트 증적이 함께 따라오도록 만드는 것이다. + +## v36 핵심 + +- T01~T09 `testProfile` 추가 + - 필수 Scenario Kind + - 필수 Behavioral Tag + - 필수 Evidence + - 필수 Recovery/Interaction Check +- `generated/screen-recipe-verification-manifest.json` 추가 +- T01~T09 Recipe Verification **9/9 자동 폐쇄성** +- 각 Recipe는 실제 해당 Template을 사용하는 Screen의 Canonical E2E Scenario를 최소 1개 요구 +- T02 Master / T06 Queue / T07 Reconcile Canonical E2E Recovery Scenario 추가 +- Scaffolder가 신규 Vertical Slice에 `*.test-plan.ts` 자동 생성 +- 생성 Test Plan은 Canonical Scenario / Required Check / Required Evidence를 타입 계약으로 보존 +- 기존 Master / Transaction / Fast Entry의 명시적 `--write-permission` fail-closed 정책 유지 +- Home Attention Queue를 동일 우선순위 내 최신 발생 순으로 정렬 +- Home은 표시 상한과 별개로 권한 필터 후 실제 전체 건수 및 overflow를 정확히 표시 +- `buildKbxHomeAttentionQueue()` public API 추가, 기존 `buildKbxHomeAttention()` 호환 유지 +- Screen Recipe `testProfile` / `canonicalScenarioIds` 변경을 Release Impact에서 추적 +- Design Debt ratchet **131 유지** + +## 검증 + +```bash +node scripts/validate-kbx.mjs +``` + +최종 검증 기준: + +- 299 TS/Vue script units +- 20 Screen Definitions +- 84 Component Definitions +- 66 Component Catalog entries +- 153 Design Tokens +- 9 Screen Recipes +- 30 Canonical Test Scenarios +- Recipe Verification 9/9 +- 16 Navigation Entries +- Design Debt 131 <= 131 +- Release Impact major / major + +## 문서 + +- `docs/screen-recipe-verification-home-attention-v36.md` +- `docs/kbx-v36-standard-traceability.md` +- `docs/validation-report-v36.md` +- `docs/release/migration-guide.md` + +## FE Reference Lab (v37) + +Backend 없이 실제 브라우저에서 Application Shell, 홈 네비게이션과 T01~T09를 검증하려면 `apps/web/fe-reference/`를 사용한다. + +```bash +cd apps/web/fe-reference +python3 -m http.server 4173 +``` + +FE 정적 계약 검증: + +```bash +pnpm validate:fe-reference +``` + +상세 설계 기록: `docs/frontend/KBX-FE-Completion-v37.md` diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/.env.demo b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/.env.demo new file mode 100644 index 00000000..2899fc2f --- /dev/null +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/.env.demo @@ -0,0 +1 @@ +VITE_KBX_DEMO_MODE=true diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/README.md b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/README.md new file mode 100644 index 00000000..900ddaeb --- /dev/null +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/README.md @@ -0,0 +1,113 @@ +# KBX FE Reference Lab v50 + +Backend/API 없이 브라우저에서 즉시 실행하는 KBX 업무 UI 원형이다. 목적은 별도 Demo UI를 만드는 것이 아니라 **T01~T09의 실제 HTML/CSS/JavaScript 상호작용, Theme State, Keyboard, Recovery, WMS Mobile**을 빠르게 재현하는 것이다. + +## 실행 + +```bash +cd apps/web/fe-reference +python3 -m http.server 4173 +``` + +운영 Domain을 흉내 내는 제품이 아니다. Permission, Domain Validation, DB Commit, Audit, Concurrency, Idempotency는 Server 최종 책임이다. + +## v50 Operational Truth / Home Overflow QA + +- Home Attention 기본 8개, 최대 priority surface 24개 +- 총 건수 >24이면 `전체 보기`라고 과장하지 않고 `우선 24개 보기` +- 남은 항목은 `작업 센터 전체 / 알림 센터 전체`로 이동 +- Work Center open → Esc close → Home trigger Focus restore +- KBX Business Light/Dark에서 동일한 Label/Action/Focus 유지 +- 실제 Template 코드의 non-retryable error는 `[다시 시도]`를 숨기는 공통 계약을 사용 +- 증적: `docs/evidence/v50-browser-operational-truth.json` (16/16) +- Preview: `previews/v50-home-overflow-dark-1440x900.png` + + +## v46 검증 범위 + +- KBX Business Light / Dark + Forced Colors 의미 체계 유지 +- clean Workspace 위치만 `sessionStorage`에 복원 +- Query/Hash/Dirty Form·Grid 값은 session snapshot에서 제외 +- 복원 tab `↺` + Home `복원` 표시, 실제 진입 시 resumed marker 제거 +- Home/no-active-tab 상태에서도 첫 Workspace Tab keyboard entry 보장 +- Storage API 차단 시 Preference/Resume 없이 fail-soft 기동 +- T09 WMS common state boundary의 Error → `[작업 다시 불러오기]` recovery loop +- Browser Reference QA 20/20: clean resume, dirty exclusion, Dark Theme, 1280px overflow, runtime error 0 + +## v44 검증 범위 + +- KBX Business Light / Dark semantic theme +- Windows Forced Colors system-color fallback +- Home: 확인 필요 → 바로 시작 → 모듈별 업무 +- Header: 작업/알림 **비모달 System Panel**, Esc, Focus Restore +- Help/AI/Suggestion/Detail: Drawer contract 유지 +- T01: 검색 → Loading/Empty/Error/Ready → 선택/Bulk/Drawer → Empty 복구 +- T02: Master CRUD + Dirty Guard + Lookup +- T03: Header/Detail + Validation-safe F8 +- T04: Fast Entry/Paste/Fill Down +- T05: Master→Location→재고이력 +- T06: Actionable Queue +- T07: Reconcile/Resolution +- T08: Excel Import + Background Job +- T09: 390×844 WMS Mobile + Scanner/Network/Duplicate Guard + +## Theme 원칙 + +Light/Dark는 표현 모드이며 업무 상태나 Permission의 Source of Truth가 아니다. Forced Colors가 활성화되면 OS `Canvas`, `CanvasText`, `Highlight`, `LinkText`를 우선한다. Theme 변경으로 Layout/Density/Keyboard 문법을 변경하지 않는다. + +## QA 증적 + +- `previews/v44-home-light-1440x900.png` +- `previews/v44-home-dark-1440x900.png` +- `previews/v44-runtime-panel-dark-1440x900.png` +- `previews/v44-t01-dark-1440x900.png` +- `previews/v44-t01-forced-colors-1440x900.png` +- `previews/v44-t09-dark-390x844.png` +- `docs/evidence/v44-theme-state-browser.json` — 14/14 +- `docs/evidence/v44-browser-matrix.json` — 28/28 + +실행환경의 localhost/file navigation 제약 때문에 Browser QA는 동일 HTML/CSS/JS asset을 Playwright inline harness로 실행했다. 실제 Vue/Vite/PrimeVue/AG Grid runtime E2E와 혼동하지 않는다. + +## 검증 + +```bash +node scripts/validate-fe-reference.mjs +node scripts/validate-fe-state-navigation-v44.mjs +node scripts/validate-kbx.mjs +``` + + +## v45 Safe Preference QA + +- T01 `기본조건 저장`, `마지막 조회조건 기억` 실제 JavaScript 동작 +- 판매채널/상태/오류유형만 저장하고 자유검색어는 저장하지 않음 +- 저장값 allow-list 복원, 잘못된 값은 기본값으로 복구 +- Light/Dark Theme와 개인화 UI 동시 검증 +- 증적: `docs/evidence/v45-browser-preference-theme.json` (14/14) + +## v46 Safe Workspace Resume QA + +- clean Workspace tab만 `sessionStorage` 복원 +- Query/Hash, Dirty Form/Grid 값은 복원 snapshot에서 제외 +- Home `바로 시작`에 `복원` 상태 표시 +- active tab이 없는 Home에서도 첫 Workspace tab `tabindex=0` +- Storage API 차단 시 Home/Shell fail-soft +- KBX Business Light/Dark와 resume marker semantic parity +- 증적: `docs/evidence/v46-browser-resume-theme.json` (20/20) +- Preview: `previews/v46-home-resume-dark-1440x900.png` + + +## v47 Actionable Problem + +T01에서 예외 주문을 선택해 출고지시하면 업무규칙 Problem을 인라인으로 표시하고 `문제 N건만 선택`으로 복구한다. 단순 성공 Toast로 거짓 완료시키지 않는다. Light/Dark는 동일 semantic state를 사용한다. + +## v48 Template Completion QA + +- T05 재고현황: 실제 창고/품목 검색, Loading, Empty, 조건 초기화, Master→Location→History +- T06 작업 Queue: 실제 작업자/통합검색/빠른필터, Selection, Empty Recovery +- T07 Reconcile: 실제 검색/불일치 필터, Selection, Reference 재처리 상태변경 +- T08 Excel Import: 실제 Browser File input/Drop, `.xlsx/.xls`/20MB guard, 6-step visual flow +- Search Panel의 조회 버튼은 Enter/F3뿐 아니라 Mouse click도 동일 실행 경로를 사용한다. +- KBX Business Light/Dark semantic theme는 동일 업무 상태와 조작 계약을 유지한다. +- Browser 증적: `docs/evidence/v48-browser-template-completion.json` (30/30) +- Preview: `previews/v48-t05-inventory-dark-1440x900.png`, `previews/v48-t08-file-dark-1440x900.png` diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/index.html b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/index.html new file mode 100644 index 00000000..c6def3e6 --- /dev/null +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/index.html @@ -0,0 +1,123 @@ + + + + + + + + + KBX FE Reference Lab v52 · Operational Navigation · Screen Anatomy · T01~T09 + + + +
+ + + + +
+ + +
+ +
+
+
+ + +
+
업무 화면 검색
+ + +
↑↓ 이동 · Enter 열기 · Esc 닫기화면코드 검색 지원
+
+
+ + +
+
저장하지 않은 변경사항이 있습니다.
+

변경사항을 저장하거나 버린 뒤 화면을 닫을 수 있습니다.

+
+ + + +
+
+
+ + + +
+
업무 조회F2 조회
+ +
+
↑↓ 이동 · Enter 선택 · Esc 닫기
+
+
+ + +
+
피킹 수량 입력
+ +

필요수량을 초과할 수 없습니다.

+
+
+
+ + +
+
재고부족 신고
+
사유
+ +
+
+
+ + + + + +
+
+
+ + + diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/kbx-fe.css b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/kbx-fe.css new file mode 100644 index 00000000..9457c942 --- /dev/null +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/kbx-fe.css @@ -0,0 +1,151 @@ +:root{ + --blue-50:#eff6ff;--blue-100:#dbeafe;--blue-600:#2563eb;--blue-700:#1d4ed8; + --gray-0:#fff;--gray-25:#fcfcfd;--gray-50:#f8fafc;--gray-100:#f1f5f9;--gray-200:#e2e8f0;--gray-300:#cbd5e1;--gray-400:#94a3b8;--gray-500:#64748b;--gray-600:#475569;--gray-700:#334155;--gray-800:#1e293b;--gray-900:#0f172a; + --red-50:#fff1f2;--red-200:#fecdd3;--red-600:#e11d48;--amber-50:#fffbeb;--amber-200:#fde68a;--amber-700:#b45309;--green-50:#f0fdf4;--green-200:#bbf7d0;--green-700:#15803d; + --surface:var(--gray-0);--muted:var(--gray-50);--border:var(--gray-200);--border-strong:var(--gray-300);--text:var(--gray-900);--text-muted:var(--gray-500);--primary:var(--blue-600);--danger:var(--red-600);--warning:var(--amber-700);--success:var(--green-700); + --space-1:4px;--space-2:8px;--space-3:12px;--space-4:16px;--space-5:20px;--space-6:24px;--radius:4px;--shadow:0 12px 34px rgba(15,23,42,.14); + --header-h:56px;--tabs-h:40px;--nav-w:220px;--nav-collapsed:56px;--control-h:34px;--row-h:34px;--font:14px;--grid-font:13px; +} +.kbx-app[data-theme="dark"]{ + color-scheme:dark; + --surface:#111827;--muted:#0b1220;--border:#334155;--border-strong:#64748b;--text:#e5e7eb;--text-muted:#94a3b8;--primary:#60a5fa;--danger:#fb7185;--warning:#fbbf24;--success:#34d399; + --blue-50:#102a43;--blue-100:#153b5c;--gray-0:#111827;--gray-25:#111827;--gray-50:#0b1220;--gray-100:#172033;--gray-200:#334155;--gray-300:#475569;--gray-400:#64748b;--gray-500:#94a3b8;--gray-600:#cbd5e1;--gray-700:#e2e8f0;--gray-800:#e5e7eb;--gray-900:#f8fafc; + --red-50:#3b1118;--red-200:#9f1239;--red-600:#fb7185;--amber-50:#33270b;--amber-200:#854d0e;--amber-700:#fbbf24;--green-50:#062e24;--green-200:#166534;--green-700:#34d399;--shadow:0 18px 48px rgba(0,0,0,.42); +} +*{box-sizing:border-box}html,body,#app{height:100%;margin:0}body{font-family:Pretendard,"Apple SD Gothic Neo","Noto Sans KR",Arial,sans-serif;font-size:var(--font);color:var(--text);background:var(--muted)}button,input,select,textarea{font:inherit;color:inherit}button{cursor:pointer}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.skip-link{position:fixed;top:-60px;left:8px;z-index:999;padding:8px 12px;background:var(--text);color:#fff;border-radius:4px}.skip-link:focus{top:8px} +.kbx-app{height:100%;display:flex;flex-direction:column;color:var(--text);background:var(--muted)}.kbx-app[data-density="comfortable"]{--control-h:38px;--row-h:40px;--font:14px;--grid-font:14px} +.global-header{height:var(--header-h);display:grid;grid-template-columns:var(--nav-w) minmax(320px,620px) 1fr;align-items:center;gap:16px;padding:0 12px;border-bottom:1px solid var(--border);background:var(--surface);z-index:40}.brand-area{display:flex;align-items:center;gap:8px;min-width:0}.brand{height:36px;border:0;background:transparent;display:flex;align-items:baseline;gap:8px;padding:0 8px;border-radius:4px}.brand:hover{background:var(--muted)}.brand strong{font-size:17px}.brand span{font-size:12px;color:var(--text-muted)}.module-select{padding-left:8px;border-left:1px solid var(--border)}.module-select select{height:32px;width:88px;border:1px solid var(--border);border-radius:4px;background:var(--surface);padding:0 8px}.global-search{height:36px;display:flex;align-items:center;justify-content:space-between;border:1px solid var(--border-strong);border-radius:4px;background:var(--muted);color:var(--text-muted);padding:0 12px;text-align:left}.global-search:hover{border-color:var(--gray-400);background:var(--surface)}.global-search kbd,.side-search kbd{font-size:11px;border:1px solid var(--border);border-radius:3px;padding:2px 5px;background:var(--surface)}.header-actions{display:flex;justify-content:flex-end;gap:4px}.header-actions button{height:32px;border:0;background:transparent;border-radius:4px;padding:0 9px}.header-actions button:hover{background:var(--muted)}.header-actions b{color:var(--primary);margin-left:3px} +.shell-body{flex:1;min-height:0;display:flex}.side-nav{width:var(--nav-w);min-width:var(--nav-w);padding:8px;border-right:1px solid var(--border);background:var(--surface);overflow:auto;transition:width .16s ease,min-width .16s ease}.side-nav.collapsed{width:var(--nav-collapsed);min-width:var(--nav-collapsed);overflow:hidden}.side-nav-top{position:sticky;top:-8px;z-index:3;display:flex;gap:4px;padding:8px 0 8px;background:var(--surface)}.nav-home{flex:1;height:32px;display:flex;align-items:center;gap:8px;padding:0 8px;border:0;border-radius:4px;background:transparent;text-align:left}.nav-home:hover,.nav-home.active{background:var(--blue-50);color:var(--primary)}.collapse-button{width:28px;height:28px;border:1px solid var(--border);border-radius:4px;background:var(--surface)}.side-search{width:100%;height:34px;display:flex;align-items:center;justify-content:space-between;border:1px solid var(--border);border-radius:4px;background:var(--muted);color:var(--text-muted);padding:0 8px}.module-switcher{display:grid;grid-template-columns:repeat(4,1fr);gap:4px;margin-top:8px}.module-switcher button{height:28px;border:1px solid var(--border);border-radius:4px;background:var(--surface);font-size:12px}.module-switcher button.active{border-color:var(--primary);color:var(--primary);background:var(--blue-50);font-weight:600}.personal-nav,.menu-nav{margin-top:12px}.personal-nav h2,.menu-group h2{margin:0 8px 4px;font-size:12px;font-weight:600;color:var(--text-muted)}.nav-row{display:flex;align-items:center;border-radius:4px}.nav-row:hover{background:var(--muted)}.nav-row .nav-entry{min-width:0;flex:1;height:32px;border:0;background:transparent;text-align:left;padding:0 8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.nav-row .nav-entry.active{color:var(--primary);font-weight:600}.nav-row .star{width:28px;height:28px;border:0;background:transparent;color:var(--gray-400);border-radius:4px}.nav-row .star.on{color:var(--primary)}.collapsed .nav-label,.collapsed .personal-nav,.collapsed .menu-nav h2,.collapsed .nav-row .star{display:none}.collapsed .side-nav-top{flex-direction:column}.collapsed .nav-home{width:38px;flex:none;justify-content:center;padding:0}.collapsed .collapse-button{width:38px}.collapsed .side-search{width:38px;justify-content:center;padding:0}.collapsed .module-switcher{grid-template-columns:1fr}.collapsed .module-switcher button{width:38px;height:34px}.collapsed .menu-nav{display:block}.collapsed .menu-group{margin:0}.collapsed .nav-row .nav-entry{width:38px;flex:none;text-align:center;padding:0;font-size:0}.collapsed .nav-row .nav-entry::first-letter{font-size:12px}.collapsed-only{display:none}.collapsed .collapsed-only{display:inline} +.workspace-shell{flex:1;min-width:0;display:flex;flex-direction:column}.workspace-tabs{height:var(--tabs-h);display:flex;align-items:flex-end;gap:1px;padding:0 8px;border-bottom:1px solid var(--border);background:var(--muted);overflow-x:auto}.tab-shell{height:36px;display:flex;align-items:center;max-width:220px;border:1px solid transparent;border-bottom:0}.tab-shell.active{border-color:var(--border);background:var(--surface)}.tab-main{min-width:0;height:100%;display:flex;align-items:center;gap:6px;border:0;background:transparent;padding:0 8px;color:var(--text-muted)}.tab-shell.active .tab-main{color:var(--text);font-weight:500}.tab-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tab-close,.tab-pin{width:26px;height:100%;border:0;background:transparent;color:var(--text-muted)}.tab-close:hover,.tab-pin:hover{background:var(--muted)}.tab-dirty{color:var(--warning);font-size:10px}.workspace-tabs .empty-tabs{margin:auto;color:var(--text-muted);font-size:12px;padding-bottom:9px}.workspace{flex:1;min-height:0;overflow:auto;background:var(--surface)} +.home-page{max-width:1180px;margin:0 auto;padding:20px;display:flex;flex-direction:column;gap:16px}.home-heading{display:grid;grid-template-columns:1fr minmax(320px,520px);align-items:center;gap:24px;min-height:76px;border-bottom:1px solid var(--border)}.home-heading p,.home-heading small{margin:0;color:var(--text-muted);font-size:12px}.home-heading h1{margin:1px 0 3px;font-size:20px}.home-search{height:40px;display:flex;align-items:center;justify-content:space-between;border:1px solid var(--border-strong);background:var(--surface);border-radius:4px;padding:0 12px;color:var(--text-muted)}.attention-zone{border:1px solid var(--amber-200);background:var(--amber-50)}.section-head{min-height:42px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;border-bottom:1px solid var(--border);background:var(--muted)}.attention-zone .section-head{background:transparent;border-color:var(--amber-200)}.section-head h2{font-size:16px;margin:0}.section-head small{color:var(--text-muted)}.attention-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:4px;padding:8px}.attention-item{min-height:44px;display:grid;grid-template-columns:72px minmax(0,1fr) auto 14px;align-items:center;gap:8px;border:1px solid var(--border);border-radius:4px;background:var(--surface);padding:4px 8px;text-align:left}.attention-item.critical{border-color:var(--red-200);background:var(--red-50)}.attention-source{font-size:12px;font-weight:600;color:var(--text-muted)}.attention-item.critical .attention-source{color:var(--danger)}.attention-copy{min-width:0;display:grid}.attention-copy b,.attention-copy small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.attention-copy b{font-weight:500}.attention-copy small{font-size:12px;color:var(--text-muted)}.attention-action{font-size:12px;color:var(--text-muted)}.home-section{border:1px solid var(--border);background:var(--surface)}.continue-list{display:flex;flex-direction:column}.continue-item{min-height:42px;display:grid;grid-template-columns:72px minmax(0,1fr) auto 14px;align-items:center;gap:8px;border:0;border-bottom:1px solid var(--border);background:var(--surface);padding:0 12px;text-align:left}.continue-item:last-child{border-bottom:0}.continue-item:hover{background:var(--muted)}.continue-source{font-size:12px;color:var(--text-muted);font-weight:600}.continue-item[data-source="dirty"] .continue-source{color:var(--warning)}.continue-item[data-source="favorite"] .continue-source{color:var(--primary)}.continue-copy{display:grid;min-width:0}.continue-copy small{color:var(--text-muted);font-size:12px}.work-explorer{display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}.module-column{border-right:1px solid var(--border);border-bottom:1px solid var(--border)}.module-column:nth-child(2n){border-right:0}.module-title{height:44px;display:flex;align-items:center;gap:8px;padding:0 12px;border-bottom:1px solid var(--border)}.module-title strong{font-size:16px}.module-title span,.module-title small{font-size:12px;color:var(--text-muted)}.module-title small{margin-left:auto}.group-block{padding:8px}.group-block h3{font-size:12px;color:var(--text-muted);margin:0 4px 4px}.home-menu-row{display:flex;align-items:center;min-height:36px;border-radius:4px}.home-menu-row:hover{background:var(--muted)}.home-menu-row .open-screen{flex:1;min-width:0;height:36px;border:0;background:transparent;text-align:left;padding:0 8px;display:flex;align-items:center;justify-content:space-between}.home-menu-row .open-screen small{font-size:12px;color:var(--text-muted)}.home-menu-row .fav{width:28px;height:28px;border:0;background:transparent;color:var(--gray-400)}.home-menu-row .fav.on{color:var(--primary)} +.screen{height:100%;min-height:0;display:flex;flex-direction:column;background:var(--surface)}.page-header{min-height:48px;display:flex;align-items:center;justify-content:space-between;gap:16px;padding:4px 16px;border-bottom:1px solid var(--border)}.page-identity{min-width:0}.breadcrumb{font-size:12px;color:var(--text-muted);margin-bottom:2px}.title-row{display:flex;align-items:center;gap:8px}.title-row h1{font-size:20px;margin:0;font-weight:600}.status-chip,.dirty-chip{font-size:12px;border:1px solid var(--border);border-radius:4px;padding:2px 6px;background:var(--muted)}.dirty-chip{color:var(--warning);border-color:var(--amber-200);background:var(--amber-50)}.page-tools{display:flex;gap:4px}.page-tools button{height:28px;border:1px solid var(--border);border-radius:4px;background:var(--surface);padding:0 8px;font-size:12px}.command-bar{min-height:44px;display:flex;align-items:center;gap:8px;padding:0 16px;border-bottom:1px solid var(--border);background:var(--surface);position:sticky;top:0;z-index:6}.command-group{display:flex;gap:6px}.command-separator{height:22px;width:1px;background:var(--border)}.btn{height:var(--control-h);border:1px solid var(--border-strong);border-radius:4px;background:var(--surface);padding:0 12px}.btn:hover{background:var(--muted)}.btn.primary{border-color:var(--primary);background:var(--primary);color:#fff}.btn.danger{border-color:var(--red-200);color:var(--danger);background:var(--red-50)}.btn:disabled{opacity:.5;cursor:not-allowed}.screen-body{flex:1;min-height:0;display:flex;flex-direction:column;gap:8px;padding:8px 16px 12px}.search-panel{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px 12px;padding:8px;border:1px solid var(--border);background:var(--muted)}.search-fields{display:flex;align-items:center;gap:8px 16px;flex-wrap:wrap}.field-inline{display:flex;align-items:center;gap:6px}.field-inline>span{font-size:13px;color:var(--gray-700);white-space:nowrap}.control{height:var(--control-h);border:1px solid var(--border-strong);border-radius:4px;background:var(--surface);padding:0 8px;min-width:110px}.control.medium{min-width:180px}.control.wide{min-width:240px}.search-actions{display:flex;gap:4px}.quick-filter{display:flex;align-items:stretch;border:1px solid var(--border);background:var(--surface)}.quick-filter button{min-width:96px;min-height:44px;border:0;border-right:1px solid var(--border);background:var(--surface);text-align:left;padding:5px 10px}.quick-filter button:last-child{border-right:0}.quick-filter button.active{box-shadow:inset 0 -2px var(--primary);background:var(--blue-50)}.quick-filter b{display:block;font-size:16px}.quick-filter small{color:var(--text-muted)}.quick-filter .danger b{color:var(--danger)}.context-bar{min-height:34px;display:flex;align-items:center;gap:12px;padding:0 10px;border:1px solid var(--border);background:var(--muted);font-size:12px}.context-bar strong{font-size:13px}.bulk-bar[hidden]{display:none!important}.bulk-bar{min-height:42px;display:flex;align-items:center;gap:6px;padding:4px 8px;border:1px solid var(--blue-100);background:var(--blue-50)}.bulk-bar strong{margin-right:6px}.grid-wrap{flex:1;min-height:220px;border:1px solid var(--border);overflow:auto;background:var(--surface)}.data-grid{width:100%;border-collapse:separate;border-spacing:0;font-size:var(--grid-font)}.data-grid th,.data-grid td{height:var(--row-h);padding:0 8px;border-right:1px solid var(--border);border-bottom:1px solid var(--border);white-space:nowrap}.data-grid th{height:36px;position:sticky;top:0;z-index:2;background:var(--muted);font-weight:600;text-align:left}.data-grid th:last-child,.data-grid td:last-child{border-right:0}.data-grid tr:hover td{background:var(--blue-50)}.data-grid .num{text-align:right}.data-grid .center{text-align:center}.data-grid .link{color:var(--primary);text-decoration:none}.data-grid .warn{color:var(--warning);font-weight:600}.data-grid .error{color:var(--danger);font-weight:600}.data-grid td[contenteditable="true"]{background:var(--amber-50);outline:0}.data-grid td[contenteditable="true"]:focus{box-shadow:inset 0 0 0 2px var(--primary)}.summary-bar{min-height:34px;display:flex;align-items:center;gap:18px;padding:0 10px;border:1px solid var(--border);border-top:0;color:var(--text-muted);font-size:12px}.summary-bar strong{color:var(--text)} +.form-section{border:1px solid var(--border);background:var(--surface)}.form-section h2{height:38px;display:flex;align-items:center;margin:0;padding:0 12px;border-bottom:1px solid var(--border);background:var(--muted);font-size:15px}.form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 20px;padding:12px}.form-field{display:grid;grid-template-columns:96px minmax(0,1fr);align-items:center;gap:8px;min-height:34px}.form-field label{font-size:13px;color:var(--gray-700)}.form-field .required::after{content:" *";color:var(--danger)}.lookup{display:grid;grid-template-columns:100px minmax(120px,1fr) 58px;gap:4px}.lookup input,.form-field input,.form-field select{height:var(--control-h);border:1px solid var(--border-strong);border-radius:4px;background:var(--surface);padding:0 8px;min-width:0}.lookup button{height:var(--control-h);border:1px solid var(--border-strong);border-radius:4px;background:var(--surface)}.transaction-layout{display:flex;flex-direction:column;gap:8px;min-height:0;flex:1}.master-layout{display:grid;grid-template-columns:minmax(280px,32%) minmax(0,1fr);gap:8px;min-height:0;flex:1}.master-pane,.detail-pane{border:1px solid var(--border);min-height:0;overflow:auto}.master-pane .search-panel{border:0;border-bottom:1px solid var(--border)}.detail-pane{padding:12px;display:flex;flex-direction:column;gap:10px}.split-layout{display:grid;grid-template-columns:minmax(260px,40%) minmax(0,1fr);gap:8px;min-height:0;flex:1}.pane{border:1px solid var(--border);min-height:0;overflow:auto}.pane-header{height:38px;display:flex;align-items:center;justify-content:space-between;padding:0 10px;border-bottom:1px solid var(--border);background:var(--muted)}.queue-summary{display:grid;grid-template-columns:repeat(5,1fr);border:1px solid var(--border)}.queue-summary button{min-height:58px;border:0;border-right:1px solid var(--border);background:var(--surface);text-align:left;padding:7px 10px}.queue-summary button:last-child{border-right:0}.queue-summary b{display:block;font-size:18px}.queue-summary .danger b{color:var(--danger)}.reconcile-summary{display:flex;gap:20px;padding:8px 10px;border:1px solid var(--border);background:var(--muted);font-size:13px}.reconcile-summary .mismatch strong{color:var(--danger)}.import-steps{display:grid;grid-template-columns:repeat(6,1fr);border:1px solid var(--border)}.import-step{min-height:42px;display:flex;align-items:center;justify-content:center;gap:6px;border-right:1px solid var(--border);background:var(--muted);font-size:13px}.import-step:last-child{border-right:0}.import-step.active{background:var(--blue-50);color:var(--primary);font-weight:600}.drop-zone{min-height:230px;border:1px dashed var(--border-strong);display:grid;place-items:center;text-align:center;background:var(--gray-25)}.drop-zone strong{display:block;font-size:16px;margin-bottom:8px}.validation-cards{display:flex;gap:8px}.validation-card{min-width:120px;border:1px solid var(--border);padding:8px 10px}.validation-card b{display:block;font-size:18px}.validation-card.error b{color:var(--danger)} +.mobile-stage{background:var(--gray-100);padding:20px;min-height:100%;display:flex;justify-content:center;align-items:flex-start}.wms-phone{width:390px;min-height:720px;background:var(--surface);border:1px solid var(--border-strong);box-shadow:0 8px 24px rgba(15,23,42,.1);display:flex;flex-direction:column}.wms-header{min-height:64px;display:flex;align-items:center;justify-content:space-between;padding:10px 16px;border-bottom:1px solid var(--border)}.wms-header h1{font-size:20px;margin:0}.network{font-size:12px;color:var(--success)}.wms-content{flex:1;padding:16px;display:flex;flex-direction:column;gap:14px}.scan-location{font-size:36px;font-weight:700;letter-spacing:.02em}.scan-box{height:110px;border:2px solid var(--border-strong);display:grid;place-items:center;font-weight:700;background:var(--muted)}.qty-grid{display:grid;grid-template-columns:repeat(3,1fr);border:1px solid var(--border)}.qty-grid div{text-align:center;padding:10px;border-right:1px solid var(--border)}.qty-grid div:last-child{border-right:0}.qty-grid b{display:block;font-size:24px}.wms-actions{position:sticky;bottom:0;display:grid;grid-template-columns:1fr 1fr;gap:8px;padding:12px 16px;border-top:1px solid var(--border);background:var(--surface)}.wms-actions .btn{height:54px;font-size:16px;font-weight:600}.wms-actions .full{grid-column:1/-1}.error-panel{border:1px solid var(--red-200);background:var(--red-50);padding:12px}.error-panel strong{display:block;color:var(--danger);margin-bottom:4px} +.menu-search-dialog{width:min(680px,calc(100vw - 32px));padding:0;border:1px solid var(--border-strong);border-radius:6px;box-shadow:var(--shadow)}.menu-search-dialog::backdrop{background:rgba(15,23,42,.35)}.search-dialog-shell header{height:46px;display:flex;align-items:center;justify-content:space-between;padding:0 12px;border-bottom:1px solid var(--border)}.search-dialog-shell header button{width:30px;height:30px;border:0;background:transparent;font-size:20px}.search-input-wrap{display:block;padding:10px;border-bottom:1px solid var(--border)}.search-input-wrap input{width:100%;height:40px;border:1px solid var(--border-strong);border-radius:4px;padding:0 12px}.menu-search-results{max-height:420px;overflow:auto}.search-result{width:100%;min-height:48px;display:grid;grid-template-columns:82px minmax(0,1fr) auto;align-items:center;gap:10px;border:0;border-bottom:1px solid var(--border);background:var(--surface);padding:5px 12px;text-align:left}.search-result.active,.search-result:hover{background:var(--blue-50)}.search-result .code{font-size:11px;color:var(--text-muted)}.search-result .copy{display:grid}.search-result .copy small{color:var(--text-muted);font-size:12px}.search-result .module{font-size:12px;color:var(--text-muted)}.search-dialog-shell footer{height:34px;display:flex;align-items:center;justify-content:space-between;padding:0 12px;background:var(--muted);color:var(--text-muted);font-size:11px} +.drawer{position:fixed;z-index:90;top:56px;right:0;bottom:0;width:min(560px,92vw);background:var(--surface);border-left:1px solid var(--border);box-shadow:-10px 0 24px rgba(15,23,42,.1);transform:translateX(100%);transition:transform .18s ease;display:flex;flex-direction:column}.drawer.open{transform:translateX(0)}.drawer header{height:48px;display:flex;align-items:center;justify-content:space-between;padding:0 14px;border-bottom:1px solid var(--border)}.drawer header button{width:30px;height:30px;border:0;background:transparent;font-size:20px}.drawer-body{flex:1;overflow:auto;padding:14px}.detail-list{display:grid;gap:0;border:1px solid var(--border)}.detail-row{display:grid;grid-template-columns:110px 1fr;min-height:36px;border-bottom:1px solid var(--border);align-items:center}.detail-row:last-child{border-bottom:0}.detail-row span{color:var(--text-muted);padding:0 10px}.detail-row strong{font-weight:500}.toast-region{position:fixed;right:16px;bottom:16px;z-index:120;display:grid;gap:8px}.toast{min-width:260px;max-width:420px;padding:10px 12px;border:1px solid var(--border-strong);background:var(--surface);box-shadow:var(--shadow);border-radius:4px}.toast.success{border-color:var(--green-200);background:var(--green-50)}.toast.error{border-color:var(--red-200);background:var(--red-50)} +button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,[contenteditable="true"]:focus-visible{outline:2px solid var(--primary);outline-offset:1px} +@media(max-width:1100px){.global-header{grid-template-columns:var(--nav-w) minmax(260px,1fr) auto}.header-actions button:nth-child(-n+2){display:none}.work-explorer{grid-template-columns:1fr}.module-column{border-right:0}.attention-grid{grid-template-columns:1fr}.queue-summary{grid-template-columns:repeat(3,1fr)}} +@media(max-width:820px){.global-header{grid-template-columns:auto 1fr auto}.module-select,.brand span,.global-search kbd,.header-actions button:not(#densityButton):not(#themeButton){display:none}.side-nav{width:56px;min-width:56px}.side-nav .nav-label,.side-nav .personal-nav,.side-nav .menu-nav h2,.side-nav .nav-row .star{display:none}.side-nav .module-switcher{grid-template-columns:1fr}.home-heading{grid-template-columns:1fr}.home-page{padding:12px}.form-grid{grid-template-columns:1fr}.master-layout,.split-layout{grid-template-columns:1fr}.master-pane{max-height:280px}} + +/* v38 FE interaction hardening */ +.section-head>div{display:grid;gap:2px}.section-head>.section-actions{display:flex;grid-auto-flow:column;align-items:center;gap:8px}.section-count{font-size:12px;color:var(--text-muted);white-space:nowrap}.section-toggle{min-height:28px;padding:0 8px;border:1px solid var(--amber-200);border-radius:4px;background:var(--surface);color:var(--text);font-size:12px;font-weight:600}.section-toggle:hover{background:var(--muted)}.attention-centers{display:flex;align-items:center;gap:4px}.attention-centers .section-toggle{border-color:var(--border)}.page-tools button[aria-pressed="true"]{color:var(--primary);border-color:var(--blue-100);background:var(--blue-50)} +.search-panel.expanded{align-items:start}.search-advanced{grid-column:1/-1;display:flex;align-items:center;gap:8px 16px;padding-top:8px;border-top:1px solid var(--border)}.checkbox-inline{min-height:var(--control-h)} +.data-state{min-height:220px;display:grid;place-items:center;align-content:center;gap:7px;border:1px solid var(--border);background:var(--gray-25);text-align:center;padding:24px}.data-state strong{font-size:16px}.data-state span{max-width:560px;color:var(--text-muted);font-size:13px}.data-state.error{border-color:var(--red-200);background:var(--red-50)}.data-state.error strong{color:var(--danger)}.data-state.loading strong::before{content:"";display:inline-block;width:12px;height:12px;margin-right:8px;border:2px solid var(--gray-300);border-top-color:var(--primary);border-radius:50%;vertical-align:-2px;animation:kbx-spin .7s linear infinite}@keyframes kbx-spin{to{transform:rotate(360deg)}} +.row-link,.text-action{border:0;background:transparent;color:var(--primary);padding:0;text-align:left}.row-link:hover,.text-action:hover{text-decoration:underline}.selected-row td{background:var(--blue-50)!important}.checkbox-control{display:flex;align-items:center;gap:6px;font-size:13px}.detail-status{display:flex;gap:6px;margin-bottom:10px}.status-chip.danger{border-color:var(--red-200);background:var(--red-50);color:var(--danger)} +.workflow-strip{min-height:38px;display:flex;align-items:center;gap:0;border:1px solid var(--border);background:var(--muted);font-size:12px}.workflow-strip span{padding:0 10px;border-right:1px solid var(--border);color:var(--text-muted)}.workflow-strip span.active{color:var(--primary);font-weight:600}.workflow-strip strong{margin-left:auto;padding:0 10px;font-weight:500}.detail-grid-section{display:flex;min-height:230px;flex:1;flex-direction:column;border:1px solid var(--border)}.detail-grid-section .grid-wrap{border:0}.cell-editor,.fast-cell{width:100%;height:calc(var(--row-h) - 6px);border:1px solid transparent;background:transparent;padding:0 4px}.cell-editor:hover,.fast-cell:hover{border-color:var(--border-strong);background:var(--surface)}.cell-editor:focus,.fast-cell:focus{border-color:var(--primary);background:var(--surface);outline:1px solid var(--primary)}.cell-editor.num,.fast-cell.num{text-align:right}.fast-cell.invalid{border-color:var(--red-200);background:var(--red-50)} +.history-pane{min-height:190px}.queue-summary button.active{box-shadow:inset 0 -2px var(--primary);background:var(--blue-50)}.queue-summary button.danger.active{box-shadow:inset 0 -2px var(--danger);background:var(--red-50)}.context-bar .text-action{margin-left:auto} +.inline-actions,.wizard-actions{display:flex;align-items:center;gap:6px}.inline-actions{padding:8px;border:1px solid var(--border);border-top:0}.wizard-actions{justify-content:flex-end;padding-top:8px}.import-step.done{background:var(--green-50);color:var(--success)}.drop-zone.dragging{border-color:var(--primary);background:var(--blue-50)}.commit-panel{max-width:760px;width:100%;margin:8px auto 0}.commit-body{padding:16px}.commit-body dl{display:flex;gap:8px;margin:12px 0}.commit-body dl div{min-width:130px;border:1px solid var(--border);padding:8px}.commit-body dt{font-size:12px;color:var(--text-muted)}.commit-body dd{margin:2px 0 0;font-size:18px;font-weight:600}.warning-copy{padding:8px;border:1px solid var(--amber-200);background:var(--amber-50);color:var(--warning)} +.wms-label{font-size:12px;color:var(--text-muted);font-weight:600}.wms-item p{margin:4px 0 0;color:var(--text-muted)}.scan-box{cursor:text;text-align:center}.scan-box strong{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis}.scan-box small{display:block;margin-top:6px;color:var(--text-muted);font-weight:400}.scan-box.scan-error{border-color:var(--danger);background:var(--red-50)}.success-panel{display:grid;gap:4px;padding:10px 12px;border:1px solid var(--green-200);background:var(--green-50)}.success-panel strong{color:var(--success)} +.confirm-dialog{width:min(480px,calc(100vw - 32px));padding:0;border:1px solid var(--border-strong);border-radius:6px;box-shadow:var(--shadow)}.confirm-dialog::backdrop{background:rgba(15,23,42,.35)}.confirm-shell{margin:0}.confirm-shell>header{min-height:48px;display:flex;align-items:center;padding:0 14px;border-bottom:1px solid var(--border)}.confirm-shell>p,.dialog-field,.exception-options{margin:0;padding:14px}.confirm-shell>footer{display:flex;justify-content:flex-end;gap:6px;padding:10px 14px;border-top:1px solid var(--border);background:var(--muted)}.dialog-field{display:grid;gap:6px}.dialog-field>span,.dialog-help{font-size:13px;color:var(--text-muted)}.dialog-field input,.dialog-field textarea{width:100%;border:1px solid var(--border-strong);border-radius:4px;padding:8px}.exception-options{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;border:0}.exception-options legend{padding:0 0 8px;font-size:13px;color:var(--text-muted)}.exception-options label{display:flex;align-items:center;gap:6px}.dialog-help{padding:0 14px 14px!important} +.drawer-backdrop{position:fixed;z-index:85;inset:56px 0 0;background:rgba(15,23,42,.18)}.drawer-actions{position:sticky;bottom:0;display:flex;justify-content:flex-end;gap:6px;margin:16px -14px -14px;padding:10px 14px;border-top:1px solid var(--border);background:var(--surface)}.search-empty{padding:20px;color:var(--text-muted);text-align:center} + +@media(max-width:820px){.search-advanced{flex-direction:column;align-items:stretch}.workflow-strip{overflow:auto}.workflow-strip strong{display:none}.exception-options{grid-template-columns:1fr}} + +/* v39 FE product-parity hardening */ +.lookup-dialog{width:min(720px,calc(100vw - 32px));padding:0;border:1px solid var(--border-strong);border-radius:6px;box-shadow:var(--shadow)} +.lookup-dialog::backdrop{background:rgba(15,23,42,.35)} +.lookup-dialog-shell{display:grid;grid-template-rows:auto auto auto auto;background:var(--surface)} +.lookup-dialog-shell>header{min-height:48px;display:flex;align-items:center;justify-content:space-between;padding:0 12px;border-bottom:1px solid var(--border)} +.lookup-dialog-shell>header>div{display:grid;gap:2px}.lookup-dialog-shell>header small{font-size:12px;color:var(--text-muted)} +.lookup-dialog-shell>header button{width:32px;height:32px;border:0;background:transparent;font-size:20px} +.lookup-search{display:grid;grid-template-columns:1fr auto;gap:6px;padding:10px;border-bottom:1px solid var(--border);background:var(--muted)} +.lookup-search input{width:100%;height:var(--control-h);border:1px solid var(--border-strong);border-radius:4px;padding:0 10px;background:var(--surface)} +.lookup-results{min-height:180px;max-height:52vh;overflow:auto;background:var(--surface)}.lookup-result{width:100%;min-height:42px;display:grid;grid-template-columns:110px minmax(0,1fr);align-items:center;gap:10px;padding:4px 12px;border:0;border-bottom:1px solid var(--border);background:var(--surface);text-align:left} +.lookup-result:hover,.lookup-result.active{background:var(--blue-50)}.lookup-result .code{font-variant-numeric:tabular-nums;color:var(--text-muted)}.lookup-result .copy{display:grid;min-width:0}.lookup-result .copy b{font-weight:500}.lookup-result .copy small{font-size:12px;color:var(--text-muted)} +.lookup-dialog-shell>footer{min-height:44px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:6px 10px;border-top:1px solid var(--border);background:var(--muted);font-size:12px;color:var(--text-muted)} +.lookup input[readonly]{background:var(--muted);color:var(--text-muted)}.lookup-trigger{min-width:58px} +.order-data-row:focus{outline:2px solid var(--primary);outline-offset:-2px}.order-data-row:focus td{background:var(--blue-50)} +.utility-panel{display:grid;gap:12px}.utility-panel h2,.utility-panel h3,.utility-panel p{margin:0}.utility-panel h2{font-size:18px}.utility-panel h3{font-size:14px;margin-top:4px}.utility-panel ol{margin:0;padding-left:22px;display:grid;gap:6px}.utility-kicker{font-size:12px;color:var(--text-muted)} +.shortcut-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1px;margin:0;border:1px solid var(--border);background:var(--border)}.shortcut-list div{display:grid;grid-template-columns:52px 1fr;min-height:34px;align-items:center;background:var(--surface)}.shortcut-list dt{padding:0 8px;font-weight:600}.shortcut-list dd{margin:0;color:var(--text-muted)} +.utility-note{padding:10px 12px;border:1px solid var(--border);background:var(--muted);font-size:12px;color:var(--text-muted);line-height:1.5}.utility-actions{display:flex;flex-wrap:wrap;gap:6px} +.ai-attention{display:grid;gap:6px}.ai-attention button{min-height:48px;display:grid;grid-template-columns:90px 52px minmax(0,1fr);align-items:center;gap:8px;border:1px solid var(--border);background:var(--surface);padding:5px 10px;text-align:left}.ai-attention button:hover{background:var(--muted)}.ai-attention b{font-size:16px}.ai-attention small{color:var(--text-muted)} +.suggestion-types{display:flex;gap:12px;padding:10px;border:1px solid var(--border)}.suggestion-types legend{padding:0 4px;color:var(--text-muted);font-size:12px}.suggestion-types label{display:flex;align-items:center;gap:5px}.suggestion-copy{display:grid;gap:6px}.suggestion-copy span{font-size:13px;color:var(--text-muted)}.suggestion-copy textarea{width:100%;resize:vertical;border:1px solid var(--border-strong);border-radius:4px;padding:8px}.suggestion-context{display:grid;grid-template-columns:auto 1fr;align-items:start;gap:2px 7px}.suggestion-context small{grid-column:2;color:var(--text-muted)} +.system-row{width:100%;min-height:52px;display:flex;align-items:center;justify-content:space-between;gap:12px;border:1px solid var(--border);background:var(--surface);padding:6px 10px;text-align:left}.system-row+ .system-row{border-top:0}.system-row span:first-child{display:grid;gap:2px}.system-row small{font-size:12px;color:var(--text-muted)}.system-row.critical{border-color:var(--red-200);background:var(--red-50)}.system-row.static{cursor:default} +.import-steps.six{grid-template-columns:repeat(6,1fr)}.import-review{display:flex;flex-wrap:wrap;gap:8px 14px;padding:10px;border:1px solid var(--border);background:var(--muted);font-size:12px}.job-progress-head{display:flex;align-items:end;justify-content:space-between;gap:12px}.job-progress-head>div{display:grid;gap:3px}.job-progress-head small{color:var(--text-muted)}.job-progress-head>b{font-size:22px}.progress-track{height:12px;overflow:hidden;border:1px solid var(--border-strong);background:var(--gray-100)}.progress-track span{display:block;height:100%;background:var(--primary);transition:width .16s linear}.result-actions{display:flex;gap:6px;justify-content:flex-end;margin-top:12px} +.network.unstable{color:var(--warning)}.network.offline{color:var(--danger)} + +@media(max-width:1280px){ + .global-header{grid-template-columns:var(--nav-w) minmax(280px,1fr) auto;gap:8px}.header-actions button{padding:0 6px}.page-header{position:sticky;top:0;z-index:11}.command-bar{overflow-x:auto;white-space:nowrap}.screen-body{min-width:0}.search-panel{align-items:flex-start}.search-fields{flex-wrap:wrap}.home-page{max-width:none}.work-explorer{grid-template-columns:1fr 1fr}.grid-wrap{max-width:100%;overflow:auto} +} +@media(max-width:900px){ + .page-header{min-height:52px;height:auto;align-items:flex-start;gap:8px;padding-top:6px;padding-bottom:6px}.page-tools{flex-wrap:wrap;justify-content:flex-end}.command-bar{position:sticky;top:52px;z-index:10}.quick-filter{overflow-x:auto}.quick-filter button{min-width:96px}.context-bar,.summary-bar{overflow-x:auto;white-space:nowrap}.import-steps.six{overflow-x:auto;grid-template-columns:repeat(6,minmax(92px,1fr))}.validation-cards{overflow-x:auto}.lookup-dialog-shell>footer{align-items:flex-start;flex-direction:column} +} +@media(max-width:620px){ + .attention-item,.continue-item{grid-template-columns:64px minmax(0,1fr) 14px}.attention-action{display:none}.suggestion-types{flex-direction:column}.shortcut-list{grid-template-columns:1fr}.ai-attention button{grid-template-columns:78px 44px minmax(0,1fr)} +} +@media(prefers-reduced-motion:reduce){*,*::before,*::after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}} +.drawer[aria-hidden="true"]{visibility:hidden;pointer-events:none} +@media(max-width:767px){ + .kbx-app[data-mode="wms-mobile"] .global-header,.kbx-app[data-mode="wms-mobile"] .side-nav,.kbx-app[data-mode="wms-mobile"] .workspace-tabs{display:none} + .kbx-app[data-mode="wms-mobile"] .shell-body,.kbx-app[data-mode="wms-mobile"] .workspace-shell,.kbx-app[data-mode="wms-mobile"] .workspace{height:100%;min-height:100%;width:100%} + .kbx-app[data-mode="wms-mobile"] .mobile-stage{min-height:100%;padding:0;background:var(--surface)} + .kbx-app[data-mode="wms-mobile"] .wms-phone{width:100%;min-height:100%;border:0;box-shadow:none} + .kbx-app[data-mode="wms-mobile"] .wms-content{min-height:0} +} + +/* v40 FE completion-loop hardening */ +.validation-summary{display:flex;align-items:center;gap:6px;flex-wrap:wrap;padding:8px 10px;border:1px solid var(--red-200);background:var(--red-50);color:var(--danger)} +.validation-summary strong{margin-right:4px}.validation-summary button{min-height:28px;border:1px solid var(--red-200);border-radius:4px;background:var(--surface);color:var(--danger);padding:0 8px;text-align:left}.validation-summary button:hover,.validation-summary button:focus-visible{background:var(--red-50)} +.action-result{min-height:38px;display:flex;align-items:center;gap:10px;padding:6px 10px;border:1px solid var(--green-200);background:var(--green-50);color:var(--green-700)}.action-result span{min-width:0;flex:1}.action-result .text-action{margin-left:auto} +.master-pane .context-bar{border-top:0}.detail-pane>.pane-header{border-bottom:1px solid var(--border)} +.data-grid input[type="checkbox"]{width:16px;height:16px;vertical-align:middle}.detail-grid-section .pane-header .text-action{margin-left:auto} +@media(max-width:900px){.validation-summary{align-items:flex-start;flex-direction:column}.action-result{align-items:flex-start;flex-wrap:wrap}.action-result .text-action{margin-left:0}} + +/* v43 KBX Business Theme: hierarchy first, decoration second */ +.brand strong{color:var(--primary)}.nav-row .nav-entry.active{box-shadow:inset 4px 0 0 var(--primary);padding-left:10px}.attention-zone{border-color:var(--amber-200)}.attention-item{border-left:4px solid var(--amber-200)}.attention-item.critical{border-left-color:var(--danger)}.continue-source{justify-self:start;border:1px solid var(--border);border-radius:999px;background:var(--muted);padding:3px 8px}.continue-item[data-source="dirty"] .continue-source{background:var(--amber-50);border-color:var(--amber-200)}.continue-item[data-source="favorite"] .continue-source{background:var(--blue-50);border-color:var(--blue-100)} +.kbx-app[data-theme="dark"] .global-search:hover,.kbx-app[data-theme="dark"] .home-search:hover{background:var(--blue-50)} + +/* v44 OS high-contrast bridge: OS colors override Light/Dark without changing workflow geometry. */ +@media (forced-colors: active){ + :root,.kbx-app[data-theme="dark"],.kbx-app[data-theme="light"]{--surface:Canvas;--surface-muted:Canvas;--surface-subtle:Canvas;--surface-hover:Highlight;--text:CanvasText;--muted:CanvasText;--border:ButtonBorder;--border-strong:CanvasText;--primary:LinkText;--success:CanvasText;--warning:CanvasText;--danger:CanvasText;--info-surface:Canvas;--warning-surface:Canvas;--danger-surface:Canvas} + button,input,select,textarea,a{forced-color-adjust:auto}.active,[aria-current="page"],[aria-pressed="true"],[aria-selected="true"]{outline:1px solid Highlight;outline-offset:-2px} +} + +/* v44 non-modal runtime centers: preserve workspace context while inspecting jobs/notifications. */ +.system-panel{position:fixed;z-index:82;right:8px;top:60px;width:min(420px,calc(100vw - 24px));max-height:70vh;overflow:auto;border:1px solid var(--border);border-radius:6px;background:var(--surface);box-shadow:var(--shadow)} +.system-panel[hidden]{display:none}.system-panel>header{min-height:36px;display:flex;align-items:center;justify-content:space-between;padding:0 8px;border-bottom:1px solid var(--border)}.system-panel>header button{width:28px;height:28px;border:0;border-radius:4px;background:transparent;color:var(--text)}.system-panel>header button:hover{background:var(--surface-hover)}.system-panel-body{padding:8px} + +/* v45 safe screen preference: keep repetitive filters without persisting free-text business data. */ +.search-preference{min-height:34px;display:grid;grid-template-columns:18px auto;align-items:center;column-gap:6px;margin-left:auto;padding:0 4px;color:var(--text-muted);font-size:12px}.search-preference input{width:16px;height:16px;margin:0}.search-preference span{color:var(--text);font-size:13px}.search-preference small{grid-column:2;max-width:320px;color:var(--text-muted)} +@media(max-width:900px){.search-preference{margin-left:0}} + +.tab-resumed{font-size:var(--font-sm);font-weight:700;color:var(--primary);line-height:1} + + +.problem-feedback{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--space-3);padding:var(--space-3);border:1px solid var(--info-border);border-left:4px solid var(--primary);background:var(--info-surface)} +.problem-feedback.warning{border-color:var(--warning-border);border-left-color:var(--warning);background:var(--warning-surface)} +.problem-feedback.danger{border-color:var(--danger-border);border-left-color:var(--danger);background:var(--danger-surface)} +.problem-copy{display:grid;gap:4px;min-width:0}.problem-heading{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.problem-heading span{font-size:12px;font-weight:600;color:var(--text-muted)}.problem-copy p,.problem-copy small{margin:0;color:var(--text-muted);font-size:13px;line-height:1.5}.problem-actions{display:flex;align-items:center;justify-content:flex-end;gap:4px;flex-wrap:wrap} +@media(max-width:48rem){.problem-feedback{flex-direction:column}.problem-actions{width:100%;justify-content:flex-start}} + + +/* v51 FE presentation workbench: module identity is chrome-only; business status keeps semantic colors. */ +:root{--workspace:#f5f7fa;--shell:#fff;--nav-surface:#fbfcfe;--section-heading:#f7f9fc;--module-accent:#175cd3;--module-accent-surface:#eff8ff;--module-accent-border:#b2ddff;--shell-shadow:0 1px 2px rgba(16,24,40,.05)} +.kbx-app[data-theme="dark"]{--workspace:#0a1020;--shell:#111827;--nav-surface:#0d1524;--section-heading:#151f31;--module-accent:#84adff;--module-accent-surface:#102a43;--module-accent-border:#285b8f;--shell-shadow:0 1px 2px rgba(0,0,0,.42)} +[data-module="OMS"]{--module-accent:#175cd3;--module-accent-surface:#eff8ff;--module-accent-border:#b2ddff} +[data-module="ERP"]{--module-accent:#6938ef;--module-accent-surface:#f4f3ff;--module-accent-border:#d9d6fe} +[data-module="WMS"]{--module-accent:#087e8b;--module-accent-surface:#ecfdff;--module-accent-border:#a5f0fc} +[data-module="COMMON"]{--module-accent:#475467;--module-accent-surface:#f2f4f7;--module-accent-border:#d0d5dd} +.kbx-app[data-theme="dark"] [data-module="OMS"],.kbx-app[data-theme="dark"][data-module="OMS"]{--module-accent:#84adff;--module-accent-surface:#102a43;--module-accent-border:#285b8f} +.kbx-app[data-theme="dark"] [data-module="ERP"],.kbx-app[data-theme="dark"][data-module="ERP"]{--module-accent:#b9a2ff;--module-accent-surface:#241b45;--module-accent-border:#6d5bd0} +.kbx-app[data-theme="dark"] [data-module="WMS"],.kbx-app[data-theme="dark"][data-module="WMS"]{--module-accent:#63c5da;--module-accent-surface:#0d2f36;--module-accent-border:#1f6672} +.kbx-app[data-theme="dark"] [data-module="COMMON"],.kbx-app[data-theme="dark"][data-module="COMMON"]{--module-accent:#b3b8c4;--module-accent-surface:#172033;--module-accent-border:#475467} +.kbx-app{background:var(--workspace)}.global-header{background:var(--shell);box-shadow:var(--shell-shadow)}.brand strong,.header-actions b{color:var(--module-accent)}.global-search:hover{border-color:var(--module-accent);background:var(--module-accent-surface)}.side-nav{background:var(--nav-surface)}.side-nav-top{background:var(--nav-surface)}.nav-home:hover,.nav-home.active,.module-switcher button.active{background:var(--module-accent-surface);color:var(--module-accent);border-color:var(--module-accent-border)}.nav-row .nav-entry.active{color:var(--module-accent);box-shadow:inset 4px 0 0 var(--module-accent)}.nav-row .star.on{color:var(--module-accent)}.workspace-shell,.workspace{background:var(--workspace)}.tab-shell.active{box-shadow:inset 0 2px 0 var(--module-accent)} +.screen{background:var(--workspace)}.page-header{background:var(--shell);box-shadow:var(--shell-shadow);border-left:4px solid var(--module-accent)}.status-chip{border-color:var(--module-accent-border);background:var(--module-accent-surface);color:var(--module-accent)}.command-bar{background:var(--shell);box-shadow:var(--shell-shadow)}.screen-body{background:var(--workspace)} +.home-page{max-width:1280px}.home-pulse{min-height:40px;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));border:1px solid var(--border);background:var(--surface)}.home-pulse button{min-height:40px;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:0 8px;padding:4px 12px;border:0;border-right:1px solid var(--border);background:transparent;text-align:left}.home-pulse button:last-child{border-right:0}.home-pulse button:hover:not(:disabled){background:var(--muted)}.home-pulse button:disabled{cursor:default}.home-pulse span{font-size:12px;color:var(--text-muted)}.home-pulse b{grid-row:1 / span 2;grid-column:2;font-size:16px;font-variant-numeric:tabular-nums}.home-pulse small{font-size:12px;color:var(--module-accent)}.module-column .module-title{box-shadow:inset 4px 0 0 var(--module-accent);background:var(--section-heading)}.module-column .module-title strong{color:var(--module-accent)} +.screen[data-template="T01"] .search-panel{box-shadow:inset 4px 0 0 var(--module-accent)}.screen[data-template="T02"] .master-pane,.screen[data-template="T02"] .detail-pane,.screen[data-template="T03"] .form-section,.screen[data-template="T03"] .grid-wrap,.screen[data-template="T04"] .grid-wrap,.screen[data-template="T05"] .pane,.screen[data-template="T06"] .grid-wrap,.screen[data-template="T07"] .grid-wrap,.screen[data-template="T08"] .import-stage{box-shadow:var(--shell-shadow)}.screen[data-template="T02"] .detail-pane,.screen[data-template="T03"] .form-section,.screen[data-template="T04"] .fast-guide,.screen[data-template="T05"] .master-pane,.screen[data-template="T06"] .queue-summary,.screen[data-template="T07"] .reconcile-summary,.screen[data-template="T08"] .import-steps{border-top:2px solid var(--module-accent)}.screen[data-template="T06"] .queue-summary{background:var(--surface)}.screen[data-template="T07"] .reconcile-summary{background:var(--surface)}.screen[data-template="T08"] .import-steps{background:var(--section-heading)} +@media(max-width:820px){.home-pulse{grid-template-columns:repeat(2,minmax(0,1fr))}.home-pulse button:nth-child(2){border-right:0}.home-pulse button:nth-child(-n+2){border-bottom:1px solid var(--border)}} +@media(forced-colors:active){:root,.kbx-app[data-theme="dark"],.kbx-app[data-theme="light"]{--workspace:Canvas;--shell:Canvas;--nav-surface:Canvas;--section-heading:Canvas;--module-accent:Highlight;--module-accent-surface:Canvas;--module-accent-border:Highlight;--shell-shadow:none}} + +/* v52 operational navigation: Home is a launcher, resume point, exception ingress, and full work explorer. */ +.home-module-rail{display:grid;grid-template-columns:auto repeat(5,minmax(0,1fr));align-items:stretch;border:1px solid var(--border);background:var(--surface)} +.home-module-label{display:flex;align-items:center;padding:0 var(--space-3);border-right:1px solid var(--border);background:var(--section-heading);font-size:.75rem;font-weight:600;color:var(--text-muted)} +.home-module-rail button{min-width:0;min-height:var(--control-h);display:grid;grid-template-columns:minmax(0,1fr);align-content:center;gap:var(--space-1);padding:var(--space-2) var(--space-3);border:0;border-right:1px solid var(--border);background:transparent;text-align:left} +.home-module-rail button:last-child{border-right:0}.home-module-rail button:hover{background:var(--muted)} +.home-module-rail button b{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.875rem}.home-module-rail button small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.75rem;color:var(--text-muted);font-variant-numeric:tabular-nums} +.home-module-rail button[aria-pressed="true"]{box-shadow:inset 0 -.125rem 0 var(--module-accent);background:var(--module-accent-surface);color:var(--module-accent)} +.home-module-rail button[data-module="OMS"],.home-module-rail button[data-module="ERP"],.home-module-rail button[data-module="WMS"],.home-module-rail button[data-module="COMMON"]{border-top:.125rem solid var(--module-accent)} +#homeExplorer:focus-visible{outline:.125rem solid var(--module-accent);outline-offset:var(--space-1)} +@media(max-width:64rem){.home-module-rail{grid-template-columns:repeat(2,minmax(0,1fr))}.home-module-label{grid-column:1 / -1;min-height:var(--control-h);border-right:0;border-bottom:1px solid var(--border)}.home-module-rail button{border-bottom:1px solid var(--border)}.home-module-rail button:nth-last-child(-n+2){border-bottom:0}} +@media(max-width:40rem){.home-module-rail{grid-template-columns:1fr}.home-module-rail button{border-right:0}.home-module-rail button:nth-last-child(2){border-bottom:1px solid var(--border)}} +@media(forced-colors:active){.home-module-rail button[aria-pressed="true"]{outline:1px solid Highlight;outline-offset:-1px;box-shadow:none}} diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/kbx-fe.js b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/kbx-fe.js new file mode 100644 index 00000000..fc34121d --- /dev/null +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/kbx-fe.js @@ -0,0 +1,753 @@ +(() => { + 'use strict'; + + const screens = [ + { id:'HOME', title:'홈', module:'COMMON', group:'홈', type:'home', template:'HOME', path:'#home', keywords:['시작','메뉴'] }, + { id:'OMS-ORD-001', title:'주문관리', module:'OMS', group:'주문', type:'list', template:'T01', path:'#orders', keywords:['주문조회','출고대기','오류'] }, + { id:'ERP-MST-ITEM-001', title:'품목관리', module:'ERP', group:'기준정보', type:'master', template:'T02', path:'#items', keywords:['품목','상품','SKU'] }, + { id:'OMS-ORD-002', title:'주문등록', module:'OMS', group:'주문', type:'transaction', template:'T03', path:'#order-new', keywords:['신규주문','등록'] }, + { id:'ERP-PRICE-001', title:'품목단가 일괄입력', module:'ERP', group:'기준정보', type:'fast-entry', template:'T04', path:'#prices', keywords:['단가','Fast Entry','붙여넣기'] }, + { id:'ERP-INV-001', title:'재고현황', module:'ERP', group:'재고', type:'master-detail', template:'T05', path:'#inventory', keywords:['현재고','가용재고','로케이션'] }, + { id:'WMS-WORK-001', title:'오늘 물류작업', module:'WMS', group:'작업관리', type:'queue', template:'T06', path:'#wms-work', keywords:['피킹','입고','예외','Queue'] }, + { id:'COMMON-REC-001', title:'재고·출고 대사', module:'COMMON', group:'운영', type:'reconcile', template:'T07', path:'#reconcile', keywords:['대사','불일치','정합성'] }, + { id:'OMS-ORD-003', title:'주문 Excel 업로드', module:'OMS', group:'주문', type:'import', template:'T08', path:'#import', keywords:['엑셀','업로드','매핑'] }, + { id:'WMS-PICK-001', title:'출고 피킹', module:'WMS', group:'현장업무', type:'wms-mobile', template:'T09', path:'#picking', keywords:['PDA','스캔','바코드'] }, + ]; + + const orders = [ + {no:'ORD-260811-1024',channel:'쿠팡',time:'16:42',buyer:'김민수',item:'런닝화 외 1',qty:3,amount:128000,stock:'정상',status:'출고대기',exception:''}, + {no:'ORD-260811-1023',channel:'스마트스토어',time:'16:39',buyer:'이지은',item:'셔츠 BLACK',qty:1,amount:48000,stock:'⚠ 부족',status:'보류',exception:'재고부족'}, + {no:'ORD-260811-1022',channel:'자사몰',time:'16:31',buyer:'박서준',item:'운동화 270',qty:2,amount:196000,stock:'정상',status:'할당완료',exception:''}, + {no:'ORD-260811-1021',channel:'쿠팡',time:'16:28',buyer:'최유진',item:'양말 3PACK',qty:4,amount:36000,stock:'정상',status:'출고대기',exception:''}, + {no:'ORD-260811-1018',channel:'11번가',time:'16:12',buyer:'정하늘',item:'후드티 GRAY',qty:1,amount:69000,stock:'정상',status:'오류',exception:'송장실패'}, + {no:'ORD-260811-1014',channel:'쿠팡',time:'15:57',buyer:'한도윤',item:'운동화 BLACK',qty:2,amount:178000,stock:'정상',status:'출고대기',exception:''}, + {no:'ORD-260811-1007',channel:'자사몰',time:'15:43',buyer:'오서연',item:'스니커즈 WHITE',qty:1,amount:82000,stock:'정상',status:'신규',exception:''}, + {no:'ORD-260811-1002',channel:'스마트스토어',time:'15:31',buyer:'윤지호',item:'양말 WHITE',qty:5,amount:45000,stock:'정상',status:'출고대기',exception:'주소확인'}, + ]; + + const masterItems = [ + {id:'ABC001',name:'남성 운동화',group:'신발',spec:'BLACK / 270',unit:'EA',warehouse:'서울물류센터',warehouseCode:'WH01',barcode:'8801234567890',lot:true,expiry:false,status:'사용'}, + {id:'ABC002',name:'러닝화',group:'신발',spec:'WHITE / 265',unit:'EA',warehouse:'서울물류센터',warehouseCode:'WH01',barcode:'8801234567891',lot:false,expiry:false,status:'사용'}, + {id:'ABC003',name:'양말',group:'의류',spec:'WHITE / FREE',unit:'PACK',warehouse:'부산물류센터',warehouseCode:'WH02',barcode:'8801234567892',lot:false,expiry:false,status:'사용'}, + {id:'ABC004',name:'셔츠',group:'의류',spec:'BLACK / L',unit:'EA',warehouse:'서울물류센터',warehouseCode:'WH01',barcode:'8801234567893',lot:false,expiry:false,status:'사용'}, + {id:'ABC005',name:'후드티',group:'의류',spec:'GRAY / XL',unit:'EA',warehouse:'인천물류센터',warehouseCode:'WH03',barcode:'8801234567894',lot:false,expiry:false,status:'사용'}, + ]; + + const inventoryItems = [ + {id:'ABC001',name:'운동화',stock:1280,locations:[['서울센터','A-01-01',300,100,20],['서울센터','A-01-02',530,0,0],['부산센터','B-03-01',450,30,10]]}, + {id:'ABC002',name:'양말',stock:721,locations:[['서울센터','A-02-01',421,50,0],['부산센터','B-02-07',300,0,10]]}, + {id:'ABC003',name:'셔츠',stock:312,locations:[['서울센터','A-05-03',212,20,5],['인천센터','C-01-01',100,0,0]]}, + {id:'ABC004',name:'후드티',stock:187,locations:[['서울센터','A-06-02',87,10,0],['부산센터','B-06-01',100,0,0]]}, + ]; + + const queueRows = [ + {no:'WK-260811-184',type:'피킹',wave:'W260811-12',worker:'김작업',progress:'72%',sla:'17:10',status:'진행',exception:false}, + {no:'WK-260811-182',type:'검수',wave:'W260811-11',worker:'박물류',progress:'91%',sla:'16:55',status:'지연',exception:true}, + {no:'WK-260811-176',type:'입고',wave:'-',worker:'이현장',progress:'38%',sla:'17:30',status:'진행',exception:false}, + {no:'WK-260811-171',type:'피킹',wave:'W260811-10',worker:'최창고',progress:'100%',sla:'16:30',status:'완료',exception:false}, + ]; + + const reconcileRows = [ + {no:'ORD-001',expected:10,actual:10,diff:0,reason:'-',status:'정상'}, + {no:'ORD-002',expected:10,actual:9,diff:-1,reason:'피킹부족',status:'확인필요'}, + {no:'ORD-003',expected:5,actual:0,diff:-5,reason:'연계실패',status:'미처리'}, + {no:'ORD-004',expected:12,actual:12,diff:0,reason:'-',status:'정상'}, + ]; + + const state = { + activeId:'HOME', module:'ALL', collapsed:false, density:'compact', theme:'light', homeAttentionExpanded:false, + favorites:new Set(['OMS-ORD-001','ERP-INV-001','WMS-WORK-001']), recents:['OMS-ORD-001','ERP-MST-ITEM-001','ERP-INV-001'], tabs:[], + selectedOrders:new Set(), orderQuickFilter:'all', orderAdvanced:false, orderDataState:'ready', commandProblem:null, searchIndex:0, + orderSearch:{channel:'all',status:'all',keyword:'',exception:'all'}, orderRememberSearch:false, orderSavedDefaults:null, + selectedMasterId:'ABC001', masterQuery:'', masterMode:'edit', masterDraft:null, masterErrors:{}, pendingMasterId:null, + transactionStatus:'작성', transactionErrors:[], transactionHeader:{orderDate:'2026-08-11',channel:'직접주문',customerCode:'10001',customerName:'대한상사',owner:'홍길동',warehouseCode:'WH01',warehouseName:'서울물류센터',receiver:'홍길동',phone:'010-1234-5678',postal:'06236',address:'서울특별시 강남구 테헤란로'}, + transactionLines:[{code:'ABC001',name:'운동화',spec:'BLACK / 270',available:83,qty:2,price:98000,remark:''},{code:'XYZ003',name:'양말',spec:'WHITE',available:721,qty:3,price:9000,remark:''}], + inventoryItemId:'ABC001', inventorySearch:{warehouse:'all',keyword:''}, inventoryDataState:'ready', + queueFilter:'all', queueSearch:{worker:'',keyword:''}, queueDataState:'ready', selectedQueueRows:new Set(), queueActionResult:'', + mismatchOnly:true, reconcileSearch:{date:'2026-08-11',keyword:''}, reconcileDataState:'ready', selectedReconcileRows:new Set(), reconcileActionResult:'', + importStep:1, importFileName:'', importFileSize:0, importFileType:'', importJobStatus:'idle', importProgress:0, importProcessed:0, + pickingQty:4, pickingRequired:6, scanBuffer:'', scanError:'', networkStatus:'online', pendingSync:0, lastScanValue:'', lastScanAt:0, + pendingCloseId:null, pendingNavigationId:null, pendingNavigationOptions:null, pendingUnsavedMode:null, workspaceDirtyDiscarded:0, + lookupEntity:'', lookupField:'', lookupIndex:0, + fastRows:[ + {code:'ABC001',name:'운동화',price:'98,000',date:'2026-08-11',remark:''}, + {code:'ABC002',name:'러닝화',price:'89,000',date:'2026-08-11',remark:''}, + {code:'ABC003',name:'양말',price:'9,000',date:'2026-08-11',remark:''}, + {code:'ABC004',name:'셔츠',price:'48,000',date:'2026-08-11',remark:''}, + ...Array.from({length:8},()=>({code:'',name:'',price:'',date:'',remark:''})), + ], + }; + + const $ = (selector, root=document) => root.querySelector(selector); + const $$ = (selector, root=document) => [...root.querySelectorAll(selector)]; + const esc = value => String(value ?? '').replace(/[&<>"']/g, ch => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[ch])); + const knownIds = new Set(screens.map(x=>x.id)); + const app=$('#app'), workspace=$('#workspace'), sideNav=$('#sideNav'), tabs=$('#workspaceTabs'); + const searchDialog=$('#menuSearchDialog'), searchInput=$('#menuSearchInput'), searchResults=$('#menuSearchResults'); + const unsavedDialog=$('#unsavedDialog'), lookupDialog=$('#lookupDialog'), lookupSearchInput=$('#lookupSearchInput'), lookupResults=$('#lookupResults'), quantityDialog=$('#quantityDialog'), exceptionDialog=$('#exceptionDialog'); + const drawer=$('#drawer'), drawerTitle=$('#drawerTitle'), drawerBody=$('#drawerBody'), drawerBackdrop=$('#drawerBackdrop'); + const systemPanel=$('#systemPanel'),systemPanelTitle=$('#systemPanelTitle'),systemPanelBody=$('#systemPanelBody'); + let drawerRestoreFocus=null,systemPanelRestoreFocus=null; + let lookupRestoreFocus=null; + let importTimer=null; + const lookupData={ + customer:[ + {id:'c-10001',code:'10001',name:'대한상사',secondary:'서울 · 정상'}, + {id:'c-10002',code:'10002',name:'대한유통',secondary:'경기 · 정상'}, + {id:'c-10003',code:'10003',name:'대한물류',secondary:'인천 · 정상'}, + {id:'c-20001',code:'20001',name:'한빛상사',secondary:'부산 · 정상'}, + ], + warehouse:[ + {id:'w-WH01',code:'WH01',name:'서울물류센터',secondary:'상온 · 사용'}, + {id:'w-WH02',code:'WH02',name:'부산물류센터',secondary:'상온 · 사용'}, + {id:'w-WH03',code:'WH03',name:'인천물류센터',secondary:'상온 · 사용'}, + ], + }; + + function screen(id){ return screens.find(x=>x.id===id) || screens[0]; } + function labelModule(module){ return module==='COMMON'?'공통':module; } + function iconFor(item){ return ({T01:'조',T02:'기',T03:'등',T04:'입',T05:'재',T06:'작',T07:'대',T08:'엑',T09:'피'})[item.template] || '·'; } + function announce(message){ $('#liveRegion').textContent=message; } + function toast(message,tone='success'){ + const el=document.createElement('div'); el.className=`toast ${tone}`; el.textContent=message; $('#toastRegion').append(el); announce(message); setTimeout(()=>el.remove(),2600); + } + function persist(){ + try{ localStorage.setItem('kbx-fe-reference-state',JSON.stringify({favorites:[...state.favorites],density:state.density,collapsed:state.collapsed,theme:state.theme,orderRememberSearch:state.orderRememberSearch,orderSavedDefaults:state.orderSavedDefaults})); }catch{} + } + function persistWorkspaceSession(){ + const payload={tabs:state.tabs.filter(tab=>!tab.dirty).slice(0,10).map(tab=>({id:tab.id,pinned:Boolean(tab.pinned)})),dirtyDiscardedCount:Math.min(10,state.tabs.filter(tab=>tab.dirty).length)}; + try{sessionStorage.setItem('kbx-fe-reference-workspace-v1',JSON.stringify(payload));}catch{} + } + function restoreWorkspaceSession(){ + let raw='';try{raw=sessionStorage.getItem('kbx-fe-reference-workspace-v1')||'';}catch{return;} + if(!raw||raw.length>16000)return; + try{const value=JSON.parse(raw);if(!Array.isArray(value.tabs))return;const seen=new Set();state.tabs=value.tabs.slice(0,10).flatMap(item=>{const id=typeof item?.id==='string'&&knownIds.has(item.id)&&item.id!=='HOME'?item.id:'';if(!id||seen.has(id))return [];seen.add(id);return [{id,title:screen(id).title,pinned:Boolean(item.pinned),dirty:false,resumed:true}];});state.workspaceDirtyDiscarded=Math.min(10,Math.max(0,Number(value.dirtyDiscardedCount)||0));}catch{} + } + function restore(){ + let raw=''; + try{ raw=localStorage.getItem('kbx-fe-reference-state')||''; }catch{return;} + if(!raw)return; + try{ + const value=JSON.parse(raw); + if(Array.isArray(value.favorites))state.favorites=new Set(value.favorites.filter(id=>knownIds.has(id)&&id!=='HOME')); + if(['compact','comfortable'].includes(value.density))state.density=value.density; + if(typeof value.collapsed==='boolean')state.collapsed=value.collapsed; + if(['light','dark'].includes(value.theme))state.theme=value.theme; + if(typeof value.orderRememberSearch==='boolean')state.orderRememberSearch=value.orderRememberSearch; + if(value.orderSavedDefaults&&typeof value.orderSavedDefaults==='object'){ + const d=value.orderSavedDefaults; + const safe={channel:['all','쿠팡','스마트스토어','자사몰','11번가'].includes(d.channel)?d.channel:'all',status:['all','신규','출고대기','할당완료','보류','오류'].includes(d.status)?d.status:'all',exception:['all','재고부족','주소확인','송장실패'].includes(d.exception)?d.exception:'all'}; + state.orderSavedDefaults=safe;state.orderSearch={...state.orderSearch,...safe,keyword:''}; + } + }catch{} + } + function markDirty(id,dirty=true){ const tab=state.tabs.find(x=>x.id===id); if(tab){tab.dirty=dirty;tab.resumed=false;persistWorkspaceSession();renderTabs();} } + function addRecent(id){ if(id==='HOME')return; state.recents=[id,...state.recents.filter(x=>x!==id)].slice(0,6); } + function setHash(s){ if(location.hash!==s.path)location.hash=s.path; } + function openScreen(id,{focus=true,updateHash=true,force=false}={}){ + const target=screen(id); + const current=state.tabs.find(t=>t.id===state.activeId); + if(!force&&target.id!==state.activeId&¤t?.dirty){ + state.pendingNavigationId=target.id; + state.pendingNavigationOptions={focus,updateHash}; + state.pendingUnsavedMode='navigate'; + $('#unsavedMessage').textContent=`${current.title}에 저장하지 않은 변경사항이 있습니다. 이동하기 전에 저장하거나 변경을 버리세요.`; + if(!unsavedDialog.open)unsavedDialog.showModal(); + return false; + } + state.activeId=target.id; addRecent(target.id); + const existingTab=state.tabs.find(t=>t.id===target.id);if(existingTab)existingTab.resumed=false; + if(target.id!=='HOME'&&!existingTab)state.tabs.push({id:target.id,title:target.title,pinned:false,dirty:false,resumed:false}); + persistWorkspaceSession();if(updateHash)setHash(target); renderAll(); if(focus)workspace.focus({preventScroll:true}); + return true; + } + function closeTabNow(id){ + const index=state.tabs.findIndex(t=>t.id===id); if(index<0)return; + const wasActive=state.activeId===id; state.tabs.splice(index,1);persistWorkspaceSession(); + if(wasActive){ const fallback=state.tabs[index-1] || state.tabs[index] || null; openScreen(fallback?.id || 'HOME'); } + else renderTabs(); + } + function requestCloseTab(id){ + const tab=state.tabs.find(t=>t.id===id); if(!tab)return; + if(!tab.dirty){closeTabNow(id);return;} + state.pendingCloseId=id; state.pendingUnsavedMode='close'; + $('#unsavedMessage').textContent=`${tab.title}에 저장하지 않은 변경사항이 있습니다.`; + if(!unsavedDialog.open)unsavedDialog.showModal(); + } + + function groupScreens(list=screens.filter(x=>x.id!=='HOME')){ + const map=new Map(); + list.forEach(item=>{ if(!map.has(item.module))map.set(item.module,new Map());const groups=map.get(item.module);if(!groups.has(item.group))groups.set(item.group,[]);groups.get(item.group).push(item); }); + return map; + } + function isFav(id){return state.favorites.has(id)} + function toggleFavorite(id){ if(!knownIds.has(id)||id==='HOME')return; isFav(id)?state.favorites.delete(id):state.favorites.add(id);persist();renderNavigation();if(state.activeId==='HOME')renderHome(); } + + function navRow(item,favorite=true,icon=''){ + const collapsedLabel=icon||iconFor(item); + return ``; + } + function bindNavRow(row){ const id=row.dataset.screen; $('.nav-entry',row).onclick=()=>openScreen(id);const star=$('.star',row);if(star)star.onclick=e=>{e.stopPropagation();toggleFavorite(id)}; } + function renderNavigation(){ + app.dataset.density=state.density;app.dataset.theme=state.theme;app.dataset.nav=state.collapsed?'collapsed':'expanded';app.dataset.module=state.module==='ALL'?(screen(state.activeId).module||'COMMON'):state.module;document.documentElement.style.colorScheme=state.theme;sideNav.classList.toggle('collapsed',state.collapsed); + $('#collapseButton').textContent=state.collapsed?'›':'‹';$('#collapseButton').setAttribute('aria-label',state.collapsed?'메뉴 펼치기':'메뉴 접기');$('#collapseButton').setAttribute('aria-expanded',String(!state.collapsed)); + $('#densityButton').textContent=state.density==='compact'?'Compact':'Comfortable'; + const themeButton=$('#themeButton');if(themeButton){themeButton.innerHTML=state.theme==='dark'?' Dark':' Light';themeButton.setAttribute('aria-label',state.theme==='dark'?'밝은 테마로 변경':'어두운 테마로 변경');} + const modules=['OMS','ERP','WMS','COMMON']; + $('#moduleSwitcher').innerHTML=modules.map(m=>``).join(''); + $$('#moduleSwitcher button').forEach(button=>button.onclick=()=>{state.module=button.dataset.module;$('#moduleSelect').value=state.module;renderNavigation();if(state.activeId==='HOME')renderHome();}); + $('#moduleSelect').value=state.module; + const favoriteItems=screens.filter(x=>isFav(x.id)&&x.id!=='HOME'&&(state.module==='ALL'||x.module===state.module)); + $('#favoriteNav').innerHTML=favoriteItems.length?`

즐겨찾기

${favoriteItems.slice(0,6).map(item=>navRow(item)).join('')}`:''; + const recentItems=state.recents.map(screen).filter(x=>x.id!=='HOME'&&(state.module==='ALL'||x.module===state.module)); + $('#recentNav').innerHTML=recentItems.length?`

최근메뉴

${recentItems.slice(0,4).map(item=>navRow(item,false,'↺')).join('')}`:''; + const visible=screens.filter(x=>x.id!=='HOME'&&(state.module==='ALL'||x.module===state.module));const grouped=groupScreens(visible); + $('#menuNav').innerHTML=[...grouped.entries()].map(([module,groups])=>``).join(''); + $$('.nav-row',sideNav).forEach(bindNavRow);$('#sideHomeButton').classList.toggle('active',state.activeId==='HOME');$('#sideHomeButton').setAttribute('aria-current',state.activeId==='HOME'?'page':'false'); + } + + function renderTabs(){ + if(!state.tabs.length){tabs.innerHTML='
열린 업무가 없습니다.
';return;} + const focusableId=state.tabs.some(t=>t.id===state.activeId)?state.activeId:state.tabs[0]?.id; + tabs.innerHTML=state.tabs.map(t=>`
`).join(''); + const shells=$$('.tab-shell',tabs); + shells.forEach((shell,index)=>{ + const id=shell.dataset.tab;const main=$('.tab-main',shell); + main.onclick=()=>openScreen(id);$('.tab-close',shell).onclick=e=>{e.stopPropagation();requestCloseTab(id)}; + $('.tab-pin',shell).onclick=e=>{e.stopPropagation();const tab=state.tabs.find(x=>x.id===id);if(tab){tab.pinned=!tab.pinned;persistWorkspaceSession();renderTabs();}}; + main.onkeydown=e=>{let next=index;if(e.key==='ArrowRight')next=(index+1)%shells.length;else if(e.key==='ArrowLeft')next=(index-1+shells.length)%shells.length;else if(e.key==='Home')next=0;else if(e.key==='End')next=shells.length-1;else return;e.preventDefault();const target=shells[next]?.dataset.tab;if(target){openScreen(target,{focus:false});$('.tab-main',shells[next])?.focus();}}; + }); + } + + const btn=(label,cls='',id='',disabled=false)=>``; + function defaultCommands(primary='저장 F8',prefix='default'){ + return `
${btn('조회 F3','',''+prefix+'Search')}${btn('신규','',''+prefix+'New')}${btn(primary,'primary',''+prefix+'Save')}
${btn('엑셀 ▼')}${btn('더보기 ⋯')}
`; + } + function pageShell(s,body,commands=''){ + return `
${commands?`
${commands}
`:''}
${body}
`; + } + function setBackgroundInert(value){for(const el of [$('.global-header'),$('.shell-body')]){if(!el)continue;value?el.setAttribute('inert',''):el.removeAttribute('inert');}} + function openDrawerContent(title,html){ + drawerRestoreFocus=document.activeElement instanceof HTMLElement?document.activeElement:null;drawerTitle.textContent=title;drawerBody.innerHTML=html; + drawer.classList.add('open');drawer.setAttribute('aria-hidden','false');drawerBackdrop.hidden=false;setBackgroundInert(true);setTimeout(()=>$('#drawerClose').focus(),0); + } + function applyIntent(intent){ + if(intent==='order-stock'){state.orderQuickFilter='stock';state.selectedOrders.clear();state.orderDataState='ready';return openScreen('OMS-ORD-001');} + if(intent==='order-errors'){state.orderQuickFilter='error';state.selectedOrders.clear();state.orderDataState='ready';return openScreen('OMS-ORD-001');} + if(intent==='reconcile-mismatch'){state.mismatchOnly=true;return openScreen('COMMON-REC-001');} + if(intent==='import-progress'){state.importStep=state.importJobStatus==='completed'?6:Math.max(1,state.importStep);return openScreen('OMS-ORD-003');} + } + function showUtilityPanel(s,kind){ + if(kind==='help'){openDrawerContent(`${s.title} 도움말`,`

화면 도움말 · ${esc(s.id)}

${esc(s.title)}

이 화면은 ${esc(s.group)} 업무를 표준 ${esc(s.template)} 문법으로 처리합니다.

기본 사용법

  1. 조회 조건 또는 업무 대상을 확인합니다.
  2. 필요한 행이나 값을 선택합니다.
  3. Command Bar의 업무 동사를 실행합니다.

단축키

F2
Lookup
F3
조회
F8
저장
Esc
현재 Context 닫기
화면의 Disabled 상태는 UX 표현이며 최종 권한과 업무 규칙은 서버에서 재검증해야 합니다.
`);return;} + if(kind==='ai'){openDrawerContent(`${s.title} AI 도우미`,`

현재 화면 · ${esc(s.id)}

확인할 항목

빠른 질문

AI는 설명·제안만 수행합니다. 실제 변경은 구조화된 Proposal과 사용자 확인 후 Domain Command로 실행해야 합니다.
`);$$('[data-ai-intent]',drawerBody).forEach(button=>button.onclick=()=>{const intent=button.dataset.aiIntent;closeDrawer();setTimeout(()=>applyIntent(intent),0);});return;} + openDrawerContent(`${s.title} 제안`,`

사용자 제안 · ${esc(s.id)}

이 화면에 의견 보내기

유형
`);$('#suggestionForm').onsubmit=e=>{e.preventDefault();const value=$('#suggestionText').value.trim();if(!value){$('#suggestionText').focus();return;}closeDrawer();toast('화면 의견을 접수했습니다. 업무 민감데이터는 포함하지 않았습니다.');};setTimeout(()=>$('#suggestionText')?.focus(),0); + } + function closeSystemPanel(restoreFocus=true){ + if(systemPanel.hidden)return;systemPanel.hidden=true;$('#jobsButton').setAttribute('aria-expanded','false');$('#noticeButton').setAttribute('aria-expanded','false'); + const restore=systemPanelRestoreFocus;systemPanelRestoreFocus=null;if(restoreFocus)restore?.focus(); + } + function showSystemPanel(kind){ + const trigger=kind==='jobs'?$('#jobsButton'):$('#noticeButton'); + if(!systemPanel.hidden&&trigger.getAttribute('aria-expanded')==='true'){closeSystemPanel();return;} + closeDrawer();systemPanelRestoreFocus=document.activeElement instanceof HTMLElement?document.activeElement:null;systemPanelTitle.textContent=kind==='jobs'?'작업 센터':'알림 센터'; + $('#jobsButton').setAttribute('aria-expanded',String(kind==='jobs'));$('#noticeButton').setAttribute('aria-expanded',String(kind!=='jobs')); + if(kind==='jobs'){const progress=state.importJobStatus==='running'?`${state.importProgress}%`:'완료';systemPanelBody.innerHTML=`

백그라운드 작업

진행 중 / 최근 작업

송장 재처리대기 · 자동 재시도대기
`;}else{systemPanelBody.innerHTML=`

업무 알림

확인 필요 4건

`;} + systemPanel.hidden=false;$$('[data-system-intent]',systemPanelBody).forEach(button=>button.onclick=()=>{const intent=button.dataset.systemIntent;closeSystemPanel(false);setTimeout(()=>applyIntent(intent),0);});setTimeout(()=>systemPanel.focus(),0); + } + function bindPageTools(s){ + const fav=$('#pageFavorite');if(fav)fav.onclick=()=>toggleFavorite(s.id); + const help=$('#pageHelp');if(help)help.onclick=()=>showUtilityPanel(s,'help'); + const ai=$('#pageAi');if(ai)ai.onclick=()=>showUtilityPanel(s,'ai'); + const suggestion=$('#pageSuggestion');if(suggestion)suggestion.onclick=()=>showUtilityPanel(s,'suggestion'); + } + function grid(cols,rows,{caption='업무 데이터',className=''}={}){ + return `
${cols.map(c=>``).join('')}${rows}
${esc(caption)}
${esc(c)}
`; + } + function formField(label,value,{required=false,name='',readonly=false}={}){ + return `
`; + } + function lookupField(label,code,name,{field='',entity=field}={}){ + return `
`; + } + function checkboxField(label,checked,{field=''}={}){return `
`;} + + function lookupItems(){ + const list=lookupData[state.lookupEntity]||[];const q=(lookupSearchInput.value||'').trim().toLowerCase(); + return list.filter(item=>!q||`${item.code} ${item.name} ${item.secondary}`.toLowerCase().includes(q)); + } + function renderLookupResults(){ + const list=lookupItems();if(state.lookupIndex>=list.length)state.lookupIndex=Math.max(0,list.length-1); + lookupResults.innerHTML=list.length?list.map((item,index)=>``).join(''):'
조회된 항목이 없습니다.
'; + $('#lookupStatus').textContent=`${list.length}건 · ↑↓ 이동 · Enter 선택 · Esc 닫기`; + $$('.lookup-result',lookupResults).forEach((button,index)=>{button.onmousemove=()=>{state.lookupIndex=index;renderLookupResults();};button.onclick=()=>{state.lookupIndex=index;selectLookup();};}); + $('#lookupSelectButton').disabled=!list.length; + } + function openLookup(root){ + if(!root)return;state.lookupEntity=root.dataset.lookup||'';state.lookupField=root.dataset.lookupField||'';state.lookupIndex=0;lookupRestoreFocus=document.activeElement instanceof HTMLElement?document.activeElement:null; + const label=root.closest('.form-field')?.querySelector('label')?.textContent?.trim()||'업무';$('#lookupTitle').textContent=`${label} 검색`;$('#lookupSubtitle').textContent='F2 · 코드/명칭 조회';lookupSearchInput.value='';renderLookupResults(); + if(!lookupDialog.open)lookupDialog.showModal();setTimeout(()=>lookupSearchInput.focus(),0); + } + function focusAfterLookup(root){ + const focusables=$$('input:not([disabled]),select:not([disabled]),button:not([disabled]),textarea:not([disabled])',workspace); + const current=root?.querySelector('button.lookup-trigger');const index=focusables.indexOf(current);(focusables[index+1]||lookupRestoreFocus)?.focus(); + } + function applyLookupToActive(field,item){ + if(state.activeId==='ERP-MST-ITEM-001'&&field==='warehouse'&&state.masterDraft){state.masterDraft.warehouseCode=item.code;state.masterDraft.warehouse=item.name;} + if(state.activeId==='OMS-ORD-002'){ + if(field==='customer'){state.transactionHeader.customerCode=item.code;state.transactionHeader.customerName=item.name;} + if(field==='warehouse'){state.transactionHeader.warehouseCode=item.code;state.transactionHeader.warehouseName=item.name;} + } + } + function selectLookup(){ + const list=lookupItems();const item=list[state.lookupIndex];if(!item)return;const root=$(`.lookup[data-lookup-field="${CSS.escape(state.lookupField)}"]`,workspace); + if(root){const code=$(`[data-field="${CSS.escape(state.lookupField)}Code"]`,root);const name=$(`[data-field="${CSS.escape(state.lookupField)}Name"]`,root);if(code)code.value=item.code;if(name)name.value=item.name;applyLookupToActive(state.lookupField,item);markDirty(state.activeId,true);} + lookupDialog.close();announce(`${item.code} ${item.name}을 선택했습니다.`);toast(`${item.name}을 선택했습니다.`);setTimeout(()=>focusAfterLookup(root),0); + } + function bindLookups(){ + $$('.lookup',workspace).forEach(root=>{const trigger=$('.lookup-trigger',root);if(trigger)trigger.onclick=()=>openLookup(root);$$('input',root).forEach(input=>input.onkeydown=e=>{if(e.key==='F2'){e.preventDefault();openLookup(root);}});}); + } + function dataState(kind,title,detail,retryId=''){ + const action=retryId?btn('다시 조회','primary',retryId):''; + return `
${esc(title)}${esc(detail)}${action}
`; + } + + + function homeAttentionItems(){ + return [ + {source:'실패 작업',title:'송장 발급 실패 3건',detail:'오류 주문 필터로 바로 이동',id:'OMS-ORD-001',critical:true,intent:'order-errors'}, + {source:'예외',title:'재고부족 주문 7건',detail:'재고부족 필터를 적용해 이동',id:'OMS-ORD-001',critical:true,intent:'order-stock'}, + {source:'중요 알림',title:'대사 불일치 43건',detail:'불일치만 표시해 이동',id:'COMMON-REC-001',critical:false,intent:'reconcile-mismatch'}, + {source:'진행 작업',title:state.importJobStatus==='running'?`Excel 주문 반영 ${state.importProgress}%`:state.importJobStatus==='completed'?'Excel 주문 반영 완료':'Excel 주문 업로드 대기',detail:'작업상태 보기',id:'OMS-ORD-003',critical:false,intent:'import-progress'}, + {source:'미저장',title:'주문등록 수정 중',detail:'저장하지 않은 변경사항 이어서 편집',id:'OMS-ORD-002',critical:false,intent:''}, + {source:'알림',title:'주소 확인 필요 4건',detail:'배송지 오류 주문만 확인',id:'OMS-ORD-001',critical:false,intent:'order-errors'}, + {source:'알림',title:'SKU 매칭 실패 2건',detail:'상품 매핑 오류 주문만 확인',id:'OMS-ORD-001',critical:false,intent:'order-errors'}, + {source:'진행 작업',title:'피킹 작업 1건 지연',detail:'현재 물류작업 Queue에서 확인',id:'WMS-WORK-001',critical:false,intent:''}, + {source:'알림',title:'품목 바코드 확인 1건',detail:'품목 기준정보에서 확인',id:'ERP-MST-ITEM-001',critical:false,intent:''}, + {source:'진행 작업',title:'재고 실사 작업 확인',detail:'물류 작업 Queue에서 확인',id:'WMS-WORK-001',critical:false,intent:''}, + ...Array.from({length:18},(_,index)=>({source:index%3===0?'진행 작업':'알림',title:`추가 확인 업무 ${index+1}`,detail:index%3===0?'작업 센터에서 전체 진행상태 확인':'알림 센터에서 전체 알림 확인',id:index%3===0?'WMS-WORK-001':'OMS-ORD-001',critical:false,intent:''})), + ]; + } + + function attention(source,title,detail,id,critical=false,intent=''){return ``;} + function continueItem(source,title,detail,id,type){return ``;} + function homeMenuRow(item){return `
`;} + function renderHome(){ + const allScreens=screens.filter(x=>x.id!=='HOME');const visible=allScreens.filter(x=>state.module==='ALL'||x.module===state.module);const grouped=groupScreens(visible); + const moduleKeys=['OMS','ERP','WMS','COMMON'].filter(module=>allScreens.some(item=>item.module===module)); + const moduleRail=``; + const dirtyTab=state.tabs.find(t=>t.dirty);const resumedTab=state.tabs.find(t=>t.resumed&&!t.dirty);const regularOpen=state.tabs.find(t=>!t.dirty&&!t.resumed); + const allAttention=homeAttentionItems();const attentionCap=24;const visibleAttention=state.homeAttentionExpanded?allAttention.slice(0,attentionCap):allAttention.slice(0,8);const attentionOverflow=Math.max(allAttention.length-visibleAttention.length,0);const attentionToggleLabel=state.homeAttentionExpanded?'상위 8개만 보기':allAttention.length<=attentionCap?`전체 ${allAttention.length}개 보기`:`우선 ${attentionCap}개 보기`;const attentionCenters=state.homeAttentionExpanded&&attentionOverflow?`
`:''; + workspace.innerHTML=`

KBX 업무 시작

검색하고, 이어서 처리하고, 예외만 확인하는 한국형 업무 시작점${state.workspaceDirtyDiscarded?` · 이전 미저장 ${state.workspaceDirtyDiscarded}개는 안전상 복원 제외`:''}
+ + ${moduleRail} +

확인 필요

정상 8,224건은 자동 처리 · 사용자는 예외와 실패만 확인
전체 ${allAttention.length}개 · ${visibleAttention.length}개 표시${attentionCenters}
${visibleAttention.map(item=>attention(item.source,item.title,item.detail,item.id,item.critical,item.intent)).join('')}
+

바로 시작

미저장 → 열린 업무 → 즐겨찾기 → 최근 순으로 중복 없이 연결
업무 문맥 유지
${continueItem('미저장',dirtyTab?.title||'주문등록',dirtyTab?`${screen(dirtyTab.id).module} · 저장하지 않은 변경`:'OMS · 주문 · ORD 임시작성',dirtyTab?.id||'OMS-ORD-002','dirty')}${resumedTab?continueItem('복원',resumedTab.title,`${screen(resumedTab.id).module} · 이전 세션의 업무 위치`,resumedTab.id,'open'):continueItem('열림',regularOpen?.title||'품목관리',regularOpen?`${screen(regularOpen.id).module} · 열린 업무`:'ERP · 기준정보',regularOpen?.id||'ERP-MST-ITEM-001','open')}${continueItem('즐겨찾기','재고현황','ERP · 재고','ERP-INV-001','favorite')}${continueItem('최근','오늘 물류작업','WMS · 작업관리','WMS-WORK-001','recent')}
+

${state.module==='ALL'?'전체 업무':`${esc(labelModule(state.module))} 업무`}

신규 화면은 빈 Canvas가 아니라 T01~T09 원형에서 시작 · 검증된 업무 문법을 재사용
${state.module!=='ALL'?'':''}${visible.length}개 화면
${[...grouped.entries()].map(([module,groups])=>`
${esc(labelModule(module))}${module==='OMS'?'주문·채널':module==='ERP'?'기준정보·구매·재고':module==='WMS'?'물류 작업':'공통 운영'}${[...groups.values()].flat().length}개
${[...groups.entries()].map(([group,items])=>`

${esc(group)}

${items.map(homeMenuRow).join('')}
`).join('')}
`).join('')}
+
`; + $('#homeSearch').onclick=openSearch;$('#homePulseScreens').onclick=openSearch;const pulseDirty=$('#homePulseDirty');if(pulseDirty&&!pulseDirty.disabled)pulseDirty.onclick=()=>openScreen(dirtyTab.id);$('#homePulseJobs').onclick=()=>showSystemPanel('jobs');$('#homePulseNotices').onclick=()=>showSystemPanel('notices');$$('[data-home-module]',workspace).forEach(button=>button.onclick=()=>{state.module=button.dataset.homeModule;$('#moduleSelect').value=state.module;renderNavigation();renderHome();setTimeout(()=>$('#homeExplorer')?.scrollIntoView({block:'start',behavior:'smooth'}),0);});const showAll=$('#homeShowAll');if(showAll)showAll.onclick=()=>{state.module='ALL';$('#moduleSelect').value='ALL';renderNavigation();renderHome();setTimeout(()=>$('#homeExplorer')?.focus({preventScroll:true}),0);};const attentionToggle=$('#homeAttentionToggle');if(attentionToggle)attentionToggle.onclick=()=>{state.homeAttentionExpanded=!state.homeAttentionExpanded;renderHome();};const jobsAll=$('#homeJobsAll');if(jobsAll)jobsAll.onclick=()=>showSystemPanel('jobs');const noticesAll=$('#homeNoticesAll');if(noticesAll)noticesAll.onclick=()=>showSystemPanel('notices');$$('.attention-item',workspace).forEach(b=>b.onclick=()=>b.dataset.intent?applyIntent(b.dataset.intent):openScreen(b.dataset.screen));$$('.continue-item',workspace).forEach(b=>b.onclick=()=>openScreen(b.dataset.screen));$$('.home-menu-row',workspace).forEach(row=>{$('.open-screen',row).onclick=()=>openScreen(row.dataset.screen);$('.fav',row).onclick=()=>toggleFavorite(row.dataset.screen);}); + } + + function filteredOrders(){ + let visible=orders; + if(state.orderQuickFilter==='new')visible=visible.filter(x=>x.status==='신규'); + else if(state.orderQuickFilter==='ready')visible=visible.filter(x=>x.status==='출고대기'); + else if(state.orderQuickFilter==='stock')visible=visible.filter(x=>x.stock.includes('부족')); + else if(state.orderQuickFilter==='error')visible=visible.filter(x=>x.exception||x.status==='오류'); + if(state.orderSearch.channel!=='all')visible=visible.filter(x=>x.channel===state.orderSearch.channel); + if(state.orderSearch.status!=='all')visible=visible.filter(x=>x.status===state.orderSearch.status); + if(state.orderSearch.exception!=='all')visible=visible.filter(x=>x.exception===state.orderSearch.exception || (state.orderSearch.exception==='재고부족'&&x.stock.includes('부족'))); + const keyword=state.orderSearch.keyword.trim().toLowerCase(); + if(keyword)visible=visible.filter(x=>`${x.no} ${x.buyer} ${x.item} ${x.channel} ${x.exception}`.toLowerCase().includes(keyword)); + return visible; + } + function searchOption(value,label,current){return ``;} + function problemSurface(problem){ + if(!problem)return ''; + const kind={ 'business-rule':'업무 규칙', permission:'권한', 'not-found':'데이터', integration:'외부 연계', system:'시스템' }[problem.type]||'업무 오류'; + const tone=['permission','system'].includes(problem.type)?'danger':problem.type==='integration'?(problem.retryable?'warning':'danger'):'warning'; + const actions=(problem.actions||[]).map(action=>``).join(''); + return ``; + } + function clearProblem(){state.commandProblem=null;} + + function activeOrderSearchCount(){return [state.orderSearch.channel!=='all',state.orderSearch.status!=='all',Boolean(state.orderSearch.keyword.trim()),state.orderSearch.exception!=='all'].filter(Boolean).length;} + function safeOrderDefaults(){return {channel:['all','쿠팡','스마트스토어','자사몰','11번가'].includes(state.orderSearch.channel)?state.orderSearch.channel:'all',status:['all','신규','출고대기','할당완료','보류','오류'].includes(state.orderSearch.status)?state.orderSearch.status:'all',exception:['all','재고부족','주소확인','송장실패'].includes(state.orderSearch.exception)?state.orderSearch.exception:'all'};} + function saveOrderDefaults(message){state.orderSavedDefaults=safeOrderDefaults();persist();toast(message||'현재 조회조건을 기본조건으로 저장했습니다. 자유검색어는 저장하지 않습니다.');} + function orderSearchPanel(){ + const advanced=state.orderAdvanced?`
`:''; + const activeCount=activeOrderSearchCount(); + return `
${btn('조회','primary','searchExecute')}${btn(state.orderAdvanced?`상세조건 ${activeCount?`(${activeCount})`:''} 닫기`:`상세조건${activeCount?` ${activeCount}`:''}`,'','toggleAdvanced')}${btn('초기화','','searchReset')}${btn('기본조건 저장','','saveOrderDefaults')}
${advanced}
`; + } + function captureOrderSearch(){ + state.orderSearch.channel=$('#orderChannel')?.value||'all'; + state.orderSearch.status=$('#orderStatus')?.value||'all'; + state.orderSearch.keyword=$('#orderKeyword')?.value||''; + state.orderSearch.exception=$('#orderException')?.value||state.orderSearch.exception||'all'; + const remember=$('#rememberOrderSearch');if(remember)state.orderRememberSearch=remember.checked; + } + function orderRows(){ + return filteredOrders().map((order,index)=>`${esc(order.no)}${esc(order.channel)}${esc(order.time)}${esc(order.buyer)}${esc(order.item)}${order.qty.toLocaleString('ko-KR')}₩${order.amount.toLocaleString('ko-KR')}${esc(order.stock)}${esc(order.status)}${esc(order.exception||'-')}`).join(''); + } + function renderOrderData(){ + if(state.orderDataState==='loading')return dataState('loading','조회 중...','기존 조회조건을 유지한 채 데이터를 불러오고 있습니다.'); + if(state.orderDataState==='empty')return dataState('empty','조회된 주문이 없습니다.','조회조건을 변경하거나 초기화해 보세요.','orderRetry'); + if(state.orderDataState==='error')return dataState('error','주문을 조회하지 못했습니다.','네트워크 연결을 확인한 후 다시 시도하세요. 입력한 조회조건은 보존됩니다.','orderRetry'); + const visible=filteredOrders(); + if(!visible.length)return dataState('empty','현재 필터에 해당하는 주문이 없습니다.','다른 빠른 필터를 선택해 보세요.'); + const allSelected=visible.every(x=>state.selectedOrders.has(x.no)); + const table=grid([``,'주문번호','채널','주문시각','주문자','대표상품','수량','주문금액','재고','출고상태','오류'],orderRows(),{caption:'주문 조회 결과'}).replace('<input>',``); + return table; + } + function renderT01(s){ + const selection=state.selectedOrders.size; + const quick=[['all','전체','8,241'],['new','신규','2,138'],['ready','출고대기','927'],['stock','재고부족','7'],['error','처리오류','16']]; + const body=`${problemSurface(state.commandProblem)}${orderSearchPanel()}
${quick.map(([key,label,count])=>``).join('')}
오늘 주문조회 8,241건표시 ${filteredOrders().length}건선택 ${selection}오류 16건
${selection}건 선택${btn('출고지시','primary','bulkShip')}${btn('보류')}${btn('담당자 변경')}${btn('택배사 변경')}
${renderOrderData()}
전체 8,241건선택 ${selection}상품수량 12,824금액 ₩182,420,300
`; + const commands=`
${btn('조회 F3','','commandSearch')}${btn('신규','','commandNew')}${btn('주문복사')}
${btn('출고지시','primary','commandShip',selection===0)}${btn('보류','','commandHold',selection===0)}
${btn('인쇄')}${btn('엑셀 ▼')}
`; + workspace.innerHTML=pageShell(s,body,commands);bindPageTools(s);bindT01(); + } + function executeOrderSearch(){ clearProblem();captureOrderSearch();if(state.orderRememberSearch){state.orderSavedDefaults=safeOrderDefaults();persist();}state.selectedOrders.clear();state.orderDataState='loading';renderT01(screen('OMS-ORD-001'));setTimeout(()=>{state.orderDataState='ready';renderT01(screen('OMS-ORD-001'));toast(`현재 조건으로 ${filteredOrders().length}건을 조회했습니다.${state.orderRememberSearch?' 안전한 조회조건을 기억했습니다.':''}`);},260); } + function bindT01(){ + $('#orderSearchForm').onsubmit=e=>{e.preventDefault();executeOrderSearch()};$('#searchExecute').onclick=executeOrderSearch;$('#commandSearch').onclick=executeOrderSearch; + $('#toggleAdvanced').onclick=()=>{captureOrderSearch();state.orderAdvanced=!state.orderAdvanced;renderT01(screen('OMS-ORD-001'));}; + $('#searchReset').onclick=()=>{clearProblem();state.orderQuickFilter='all';state.orderAdvanced=false;state.orderSearch={channel:'all',status:'all',keyword:'',exception:'all'};state.selectedOrders.clear();state.orderDataState='ready';renderT01(screen('OMS-ORD-001'));toast('조회조건을 초기화했습니다.');}; + const saveDefaults=$('#saveOrderDefaults');if(saveDefaults)saveDefaults.onclick=()=>{captureOrderSearch();saveOrderDefaults();renderT01(screen('OMS-ORD-001'));}; + const remember=$('#rememberOrderSearch');if(remember)remember.onchange=()=>{captureOrderSearch();state.orderRememberSearch=remember.checked;if(remember.checked)state.orderSavedDefaults=safeOrderDefaults();persist();toast(remember.checked?'마지막 조회조건 기억을 켰습니다. 자유검색어는 저장하지 않습니다.':'마지막 조회조건 기억을 껐습니다.');renderT01(screen('OMS-ORD-001'));}; + const retry=$('#orderRetry');if(retry)retry.onclick=executeOrderSearch; + $$('[data-order-filter]',workspace).forEach(button=>button.onclick=()=>{clearProblem();state.orderQuickFilter=button.dataset.orderFilter;state.selectedOrders.clear();state.orderDataState='ready';renderT01(screen('OMS-ORD-001'));announce(`${button.textContent.trim()} 필터를 적용했습니다.`);}); + $$('input[data-order]',workspace).forEach(cb=>cb.onchange=()=>{cb.checked?state.selectedOrders.add(cb.dataset.order):state.selectedOrders.delete(cb.dataset.order);renderT01(screen('OMS-ORD-001'));}); + const all=$('#selectVisibleOrders');if(all)all.onchange=()=>{filteredOrders().forEach(order=>all.checked?state.selectedOrders.add(order.no):state.selectedOrders.delete(order.no));renderT01(screen('OMS-ORD-001'));}; + $$('.order-link',workspace).forEach(link=>link.onclick=e=>{e.preventDefault();showOrderDetail(link.dataset.order);}); + const orderRows=$$('.order-data-row',workspace);orderRows.forEach((row,index)=>row.onkeydown=e=>{ + if(['INPUT','A','BUTTON'].includes(e.target?.tagName))return; + if(e.key==='ArrowDown'||e.key==='ArrowUp'){e.preventDefault();const next=Math.max(0,Math.min(orderRows.length-1,index+(e.key==='ArrowDown'?1:-1)));orderRows.forEach((x,i)=>x.tabIndex=i===next?0:-1);orderRows[next]?.focus();} + else if(e.key===' '){e.preventDefault();const no=row.dataset.orderRow;no&&(state.selectedOrders.has(no)?state.selectedOrders.delete(no):state.selectedOrders.add(no));renderT01(screen('OMS-ORD-001'));setTimeout(()=>$$('.order-data-row',workspace)[index]?.focus(),0);} + else if(e.key==='Enter'){e.preventDefault();showOrderDetail(row.dataset.orderRow);} + }); + for(const id of ['bulkShip','commandShip']){const b=$('#'+id);if(b)b.onclick=()=>{if(!state.selectedOrders.size)return;const selected=orders.filter(order=>state.selectedOrders.has(order.no));const blocked=selected.filter(order=>Boolean(order.exception));if(blocked.length){state.commandProblem={type:'business-rule',title:'출고할 수 없는 주문이 포함되어 있습니다.',detail:`${blocked.length}건은 재고·주소·송장 예외를 먼저 해결해야 합니다. 정상 주문은 이 Reference Lab에서 임의 처리하지 않습니다.`,actions:[{id:'show-blocked',label:`문제 ${blocked.length}건만 선택`}],retryable:false};renderT01(screen('OMS-ORD-001'));return;}toast(`${state.selectedOrders.size}건 출고지시를 요청했습니다.`);};}const dismiss=$('#problemDismiss');if(dismiss)dismiss.onclick=()=>{clearProblem();renderT01(screen('OMS-ORD-001'));};$$('[data-problem-action="show-blocked"]',workspace).forEach(button=>button.onclick=()=>{const blocked=orders.filter(order=>state.selectedOrders.has(order.no)&&order.exception);state.selectedOrders=new Set(blocked.map(order=>order.no));state.orderQuickFilter='all';state.orderSearch.exception='all';clearProblem();renderT01(screen('OMS-ORD-001'));announce(`문제 주문 ${blocked.length}건만 선택했습니다.`);}); + const newButton=$('#commandNew');if(newButton)newButton.onclick=()=>openScreen('OMS-ORD-002'); + } + function showOrderDetail(orderNo){ + const order=orders.find(x=>x.no===orderNo);if(!order)return;drawerRestoreFocus=document.activeElement instanceof HTMLElement?document.activeElement:null; + openDrawerContent(`주문 ${order.no}`,`
${esc(order.status)}${order.exception?`${esc(order.exception)}`:''}
판매채널${esc(order.channel)}
주문자${esc(order.buyer)} / 010-****-1234
대표상품${esc(order.item)}
주문금액₩${order.amount.toLocaleString('ko-KR')}
예외${esc(order.exception||'없음')}

처리이력

16:42주문수집
16:43결제확인
16:45재고할당
${btn('전체 상세')}${btn('출고지시','primary','drawerShip',Boolean(order.exception))}
`); + } + function closeDrawer(){drawer.classList.remove('open');drawer.setAttribute('aria-hidden','true');drawerBackdrop.hidden=true;setBackgroundInert(false);const restore=drawerRestoreFocus;drawerRestoreFocus=null;restore?.focus();} + + function masterDraftFrom(item){return item?{...item}:{id:'',name:'',group:'',spec:'',unit:'EA',warehouse:'',warehouseCode:'',barcode:'',lot:false,expiry:false,status:'사용'};} + function loadMasterDraft(id){const item=masterItems.find(x=>x.id===id);state.selectedMasterId=item?.id||'';state.masterMode='edit';state.masterDraft=masterDraftFrom(item||masterItems[0]);state.masterErrors={};} + function validateMasterDraft(){ + const draft=state.masterDraft||masterDraftFrom();const errors={}; + if(!draft.id.trim())errors.id='품목코드를 입력하세요.'; + if(!draft.name.trim())errors.name='품목명을 입력하세요.'; + if(!draft.warehouseCode.trim())errors.warehouse='기본창고를 선택하세요.'; + if(state.masterMode==='new'&&masterItems.some(item=>item.id===draft.id.trim()))errors.id='이미 사용 중인 품목코드입니다.'; + state.masterErrors=errors;return Object.keys(errors).length===0; + } + function masterValidationSummary(){const entries=Object.entries(state.masterErrors);return entries.length?``:'';} + function performMasterTarget(target){ + if(target==='__NEW__'){state.selectedMasterId='';state.masterMode='new';state.masterDraft=masterDraftFrom();state.masterErrors={};renderT02(screen('ERP-MST-ITEM-001'));setTimeout(()=>$('[data-field="code"]',workspace)?.focus(),0);return;} + if(target==='__COPY__'){const source=state.masterDraft||masterItems.find(x=>x.id===state.selectedMasterId)||masterItems[0];state.selectedMasterId='';state.masterMode='new';state.masterDraft={...masterDraftFrom(source),id:'',barcode:'',status:'사용'};state.masterErrors={};renderT02(screen('ERP-MST-ITEM-001'));setTimeout(()=>$('[data-field="code"]',workspace)?.focus(),0);return;} + loadMasterDraft(target);renderT02(screen('ERP-MST-ITEM-001')); + } + function requestMasterTarget(target){const tab=state.tabs.find(x=>x.id==='ERP-MST-ITEM-001');if(tab?.dirty){state.pendingMasterId=target;state.pendingUnsavedMode='master-select';$('#unsavedMessage').textContent='현재 품목에 저장하지 않은 변경사항이 있습니다. 저장하거나 변경을 버린 뒤 다른 품목으로 전환하세요.';if(!unsavedDialog.open)unsavedDialog.showModal();return;}performMasterTarget(target);} + function saveMaster(){ + if(!validateMasterDraft()){renderT02(screen('ERP-MST-ITEM-001'));setTimeout(()=>$('[data-master-error]',workspace)?.focus(),0);return false;} + const draft={...state.masterDraft,id:state.masterDraft.id.trim(),name:state.masterDraft.name.trim(),warehouseCode:state.masterDraft.warehouseCode.trim()}; + if(state.masterMode==='new'){masterItems.push(draft);state.selectedMasterId=draft.id;state.masterMode='edit';} + else{const current=masterItems.find(x=>x.id===state.selectedMasterId);if(current)Object.assign(current,draft);} + state.masterDraft={...draft};state.masterErrors={};markDirty('ERP-MST-ITEM-001',false);renderT02(screen('ERP-MST-ITEM-001'));toast('품목을 저장했습니다.');return true; + } + function renderT02(s){ + if(!state.masterDraft)loadMasterDraft(state.selectedMasterId); + const draft=state.masterDraft; + const q=state.masterQuery.trim().toLowerCase();const visible=masterItems.filter(x=>!q||`${x.id} ${x.name} ${x.group}`.toLowerCase().includes(q)); + const rows=visible.map(x=>`${esc(x.name)}${esc(x.group)}${esc(x.status)}`).join(''); + const modeLabel=state.masterMode==='new'?'신규 품목':'품목 상세'; + const body=`${masterValidationSummary()}
${modeLabel}${state.masterMode==='new'?'고유 식별자와 Barcode는 새로 입력':'목록을 바꾸기 전에 미저장 상태를 확인'}

기본정보

${formField('품목코드',draft.id,{required:true,name:'code',readonly:state.masterMode!=='new'})}${formField('품목명',draft.name,{required:true,name:'name'})}${formField('품목그룹',draft.group,{name:'group'})}${formField('규격',draft.spec,{name:'spec'})}${formField('단위',draft.unit,{name:'unit'})}

물류정보

${lookupField('기본창고',draft.warehouseCode,draft.warehouse,{field:'warehouse'})}${formField('바코드',draft.barcode,{name:'barcode'})}${checkboxField('LOT 관리',draft.lot,{field:'lot'})}${checkboxField('유통기한 관리',draft.expiry,{field:'expiry'})}
상태 ${esc(draft.status)}Mode ${esc(state.masterMode)}Version 17
`; + const commands=`
${btn('조회 F3','','masterSearchTop')}${btn('신규','','masterNew')}${btn('저장 F8','primary','masterSave')}${btn('복사','','masterCopy')}
${btn(draft.status==='사용'?'사용중지':'사용재개','danger','masterSuspend')}${btn('엑셀 ▼')}
`; + workspace.innerHTML=pageShell(s,body,commands);bindPageTools(s);bindT02(); + } + function bindT02(){ + bindLookups(); + $$('.master-select',workspace).forEach(button=>button.onclick=()=>requestMasterTarget(button.dataset.item)); + $$('[data-field]',workspace).forEach(input=>{if(input.readOnly)return;const event=input.type==='checkbox'?'change':'input';input.addEventListener(event,()=>{if(!state.masterDraft)return;const key=input.dataset.field;if(key==='warehouseName')return;if(key==='code')state.masterDraft.id=input.value;else if(key==='warehouseCode'){state.masterDraft.warehouseCode=input.value;state.masterDraft.warehouse='';}else state.masterDraft[key]=input.type==='checkbox'?input.checked:input.value;state.masterErrors={};markDirty('ERP-MST-ITEM-001',true);});}); + $$('[data-master-error]',workspace).forEach(button=>button.onclick=()=>{const field=button.dataset.masterError;const target=field==='warehouse'?$('.lookup-trigger',workspace):$(`[data-field="${CSS.escape(field==='id'?'code':field)}"]`,workspace);target?.focus();}); + const runSearch=()=>{state.masterQuery=$('#masterQueryInput')?.value||'';renderT02(screen('ERP-MST-ITEM-001'));}; + $('#masterSearchForm').onsubmit=e=>{e.preventDefault();runSearch();};$('#masterSearch').onclick=runSearch;$('#masterSearchTop').onclick=runSearch; + $('#masterSave').onclick=saveMaster;$('#masterNew').onclick=()=>requestMasterTarget('__NEW__');$('#masterCopy').onclick=()=>requestMasterTarget('__COPY__'); + $('#masterSuspend').onclick=()=>{state.masterDraft.status=state.masterDraft.status==='사용'?'중지':'사용';markDirty('ERP-MST-ITEM-001',true);renderT02(screen('ERP-MST-ITEM-001'));}; + $('#auditButton').onclick=()=>showUtilityPanel(screen('ERP-MST-ITEM-001'),'help'); + } + + function transactionTotals(){const valid=state.transactionLines.filter(line=>line.code);return {items:valid.length,qty:valid.reduce((sum,line)=>sum+Number(line.qty||0),0),amount:valid.reduce((sum,line)=>sum+Number(line.qty||0)*Number(line.price||0),0)};} + function validateTransaction(){ + const h=state.transactionHeader;const errors=[]; + if(!h.orderDate.trim())errors.push({field:'orderDate',message:'주문일을 입력하세요.'}); + if(!h.customerCode.trim())errors.push({field:'customerCode',message:'거래처를 선택하세요.'}); + if(!h.warehouseCode.trim())errors.push({field:'warehouseCode',message:'출고창고를 선택하세요.'}); + if(!h.receiver.trim())errors.push({field:'receiver',message:'수취인을 입력하세요.'}); + if(!h.address.trim())errors.push({field:'address',message:'주소를 입력하세요.'}); + state.transactionLines.forEach((line,index)=>{if(!line.code)return;if(Number(line.qty)<=0)errors.push({row:index,field:'qty',message:`${index+1}행 수량은 0보다 커야 합니다.`});if(Number(line.price)<0)errors.push({row:index,field:'price',message:`${index+1}행 단가는 0 이상이어야 합니다.`});}); + if(!state.transactionLines.some(line=>line.code))errors.push({field:'lines',message:'품목을 1개 이상 입력하세요.'}); + state.transactionErrors=errors;return errors.length===0; + } + function transactionValidationSummary(){return state.transactionErrors.length?``:'';} + function resetTransaction(){state.transactionStatus='작성';state.transactionErrors=[];state.transactionHeader={orderDate:'2026-08-11',channel:'직접주문',customerCode:'',customerName:'',owner:'홍길동',warehouseCode:'WH01',warehouseName:'서울물류센터',receiver:'',phone:'',postal:'',address:''};state.transactionLines=[{code:'',name:'',spec:'',available:0,qty:1,price:0,remark:''}];markDirty('OMS-ORD-002',true);renderT03(screen('OMS-ORD-002'));setTimeout(()=>$('[data-field="customerCode"]',workspace)?.focus(),0);} + function renderT03(s){ + const h=state.transactionHeader;const totals=transactionTotals(); + const lineRows=state.transactionLines.map((line,rowIndex)=>`${esc(line.name||fastName(line.code))}${esc(line.spec)}${Number(line.available||0).toLocaleString('ko-KR')}${(Number(line.qty||0)*Number(line.price||0)).toLocaleString('ko-KR')}`).join(''); + const body=`${transactionValidationSummary()}
${['작성','확정','할당','피킹','검수','출고'].map(status=>`${status}`).join('')}현재: ${esc(state.transactionStatus)} · ${state.transactionStatus==='작성'?'다음: 저장/확정':'확정 이후 주요 값은 서버 업무 규칙으로 제한'}

기본정보

${formField('주문일',h.orderDate,{required:true,name:'orderDate'})}${formField('판매채널',h.channel,{name:'channel'})}${lookupField('거래처',h.customerCode,h.customerName,{field:'customer'})}${formField('담당자',h.owner,{name:'owner'})}${lookupField('출고창고',h.warehouseCode,h.warehouseName,{field:'warehouse'})}

배송정보

${formField('수취인',h.receiver,{required:true,name:'receiver'})}${formField('연락처',h.phone,{name:'phone'})}${formField('우편번호',h.postal,{name:'postal'})}${formField('주소',h.address,{required:true,name:'address'})}
상품코드·수량·단가 직접 편집 · Enter 다음 셀
${grid(['품목코드','품목명','규격','가용재고','수량','단가','금액','비고'],lineRows,{caption:'주문 품목 상세'})}
품목 ${totals.items}종총수량 ${totals.qty.toLocaleString('ko-KR')}주문금액 ₩${totals.amount.toLocaleString('ko-KR')}상태 ${esc(state.transactionStatus)}
`; + const commands=`
${btn('신규','','transactionNew')}${btn('저장 F8','primary','saveTransaction')}${btn('주문복사','','transactionCopy')}
${btn('확정','','confirmTransaction',state.transactionStatus!=='작성')}${btn('엑셀 ▼')}
`; + workspace.innerHTML=pageShell(s,body,commands);bindPageTools(s);bindT03(); + } + function bindT03(){ + bindLookups(); + $$('[data-field]',workspace).forEach(input=>{if(input.readOnly)return;input.addEventListener('input',()=>{const key=input.dataset.field;if(key==='customerCode'){state.transactionHeader.customerCode=input.value;state.transactionHeader.customerName='';}else if(key==='warehouseCode'){state.transactionHeader.warehouseCode=input.value;state.transactionHeader.warehouseName='';}else if(key in state.transactionHeader)state.transactionHeader[key]=input.value;state.transactionErrors=[];markDirty('OMS-ORD-002',true);});}); + const editors=$$('.cell-editor',workspace);editors.forEach((editor,index)=>{editor.oninput=()=>{const line=state.transactionLines[Number(editor.dataset.lineRow)];const field=editor.dataset.lineField;let value=editor.value;if(['qty','price'].includes(field))value=Number(String(value).replaceAll(',',''))||0;line[field]=value;if(field==='code')line.name=fastName(String(value).trim());state.transactionErrors=[];markDirty('OMS-ORD-002',true);const totals=transactionTotals();$('#transactionItemCount').textContent=`${totals.items}종`;$('#transactionQty').textContent=totals.qty.toLocaleString('ko-KR');$('#transactionAmount').textContent=`₩${totals.amount.toLocaleString('ko-KR')}`;};editor.onkeydown=e=>{if(e.key==='Enter'){e.preventDefault();editors[Math.min(index+1,editors.length-1)]?.focus();}};}); + $$('[data-transaction-error]',workspace).forEach(button=>button.onclick=()=>{const error=state.transactionErrors[Number(button.dataset.transactionError)];if(!error)return;if(Number.isInteger(error.row))$(`[data-line-row="${error.row}"][data-line-field="${CSS.escape(error.field)}"]`,workspace)?.focus();else if(error.field==='customerCode')$('.lookup[data-lookup-field="customer"] .lookup-trigger',workspace)?.focus();else if(error.field==='warehouseCode')$('.lookup[data-lookup-field="warehouse"] .lookup-trigger',workspace)?.focus();else $(`[data-field="${CSS.escape(error.field)}"]`,workspace)?.focus();}); + $('#transactionAddLine').onclick=()=>{state.transactionLines.push({code:'',name:'',spec:'',available:0,qty:1,price:0,remark:''});markDirty('OMS-ORD-002',true);renderT03(screen('OMS-ORD-002'));setTimeout(()=>$$('.cell-editor',workspace).at(-4)?.focus(),0);}; + $('#saveTransaction').onclick=()=>{if(!validateTransaction()){renderT03(screen('OMS-ORD-002'));return;}markDirty('OMS-ORD-002',false);toast('주문을 저장했습니다. 서버 최종 검증을 통과한 것으로 가정한 FE Reference 상태입니다.');}; + $('#confirmTransaction').onclick=()=>{const tab=state.tabs.find(x=>x.id==='OMS-ORD-002');if(tab?.dirty){toast('저장하지 않은 변경이 있습니다. 먼저 저장하세요.','error');return;}if(!validateTransaction()){renderT03(screen('OMS-ORD-002'));return;}state.transactionStatus='확정';renderT03(screen('OMS-ORD-002'));toast('주문 상태를 확정으로 전환했습니다. 실제 제품에서는 Domain이 최종 검증합니다.');}; + $('#transactionNew').onclick=()=>{const tab=state.tabs.find(x=>x.id==='OMS-ORD-002');if(tab?.dirty){toast('현재 주문의 미저장 변경을 먼저 저장하거나 버리세요.','error');return;}resetTransaction();}; + $('#transactionCopy').onclick=()=>{if(state.tabs.find(x=>x.id==='OMS-ORD-002')?.dirty){toast('미저장 변경을 먼저 처리하세요.','error');return;}state.transactionStatus='작성';state.transactionErrors=[];state.transactionHeader={...state.transactionHeader,receiver:'',phone:'',postal:'',address:''};state.transactionLines=state.transactionLines.map(line=>({...line}));markDirty('OMS-ORD-002',true);renderT03(screen('OMS-ORD-002'));toast('주문을 복사했습니다. 수취/배송정보를 새 주문에 맞게 입력하세요.');}; + } + + function fastName(code){return ({ABC001:'운동화',ABC002:'러닝화',ABC003:'양말',ABC004:'셔츠'})[code]||'';} + function fastErrorCount(){return state.fastRows.reduce((sum,row)=>sum+(row.code&&(!row.price||Number(String(row.price).replaceAll(',',''))<=0)?1:0),0);} + function renderT04(s){ + const rows=state.fastRows.map((row,index)=>`${index+1}${esc(row.name||fastName(row.code))}`).join(''); + const inputCount=state.fastRows.filter(x=>x.code).length;const errorCount=fastErrorCount(); + const body=`
Fast EntryEnter = 다음 입력 셀Ctrl+V = 다중 Cell 붙여넣기F2 = 품목 Lookup오류 ${errorCount}개
${grid(['#','품목코드','품목명','단가','적용일','비고'],rows,{caption:'품목단가 빠른 입력'})}
입력 ${inputCount}행오류 ${errorCount}빈 행 ${state.fastRows.length-inputCount}
`; + const commands=`
${btn('저장 F8','primary','fastSave',errorCount>0)}${btn('행추가','','fastAdd')}${btn('행복제','','fastDuplicate')}${btn('Fill Down','','fastFill')}
${btn('엑셀 붙여넣기')}${btn('엑셀 ▼')}
`; + workspace.innerHTML=pageShell(s,body,commands);bindPageTools(s);bindT04(); + } + function bindT04(){ + const cells=$$('.fast-cell',workspace); + cells.forEach((cell,index)=>{ + cell.oninput=()=>{const row=state.fastRows[Number(cell.dataset.row)];row[cell.dataset.field]=cell.value;if(cell.dataset.field==='code')row.name=fastName(cell.value.trim());markDirty('ERP-PRICE-001',true);}; + cell.onkeydown=e=>{if(e.key==='Enter'){e.preventDefault();cells[Math.min(index+1,cells.length-1)]?.focus();}}; + cell.onpaste=e=>{const text=e.clipboardData?.getData('text/plain');if(!text||(!text.includes('\t')&&!text.includes('\n')))return;e.preventDefault();const matrix=text.replace(/\r/g,'').split('\n').filter(Boolean).map(line=>line.split('\t'));const startRow=Number(cell.dataset.row);const fieldOrder=['code','price','date','remark'];const startField=Math.max(0,fieldOrder.indexOf(cell.dataset.field));matrix.forEach((values,rowOffset)=>values.forEach((value,colOffset)=>{const target=state.fastRows[startRow+rowOffset];const field=fieldOrder[startField+colOffset];if(target&&field){target[field]=value.trim();if(field==='code')target.name=fastName(target[field]);}}));markDirty('ERP-PRICE-001',true);renderT04(screen('ERP-PRICE-001'));toast(`${matrix.length}행 붙여넣기를 적용했습니다.`);}; + }); + $('#fastAdd').onclick=()=>{state.fastRows.push({code:'',name:'',price:'',date:'',remark:''});markDirty('ERP-PRICE-001',true);renderT04(screen('ERP-PRICE-001'));}; + $('#fastDuplicate').onclick=()=>{const last=[...state.fastRows].reverse().find(x=>x.code);if(last){state.fastRows.push({...last,code:'',name:''});markDirty('ERP-PRICE-001',true);renderT04(screen('ERP-PRICE-001'));}}; + $('#fastFill').onclick=()=>{const source=state.fastRows.find(x=>x.date)?.date||'2026-08-11';state.fastRows.forEach(row=>{if(row.code&&!row.date)row.date=source;});markDirty('ERP-PRICE-001',true);renderT04(screen('ERP-PRICE-001'));toast('적용일을 Fill Down 했습니다.');}; + $('#fastSave').onclick=()=>{markDirty('ERP-PRICE-001',false);toast('오류 없는 변경 행을 저장했습니다.');}; + } + + function inventoryVisibleItems(){ + const keyword=state.inventorySearch.keyword.trim().toLocaleLowerCase('ko-KR'); + return inventoryItems.filter(item=>{ + const matchesKeyword=!keyword||`${item.id} ${item.name}`.toLocaleLowerCase('ko-KR').includes(keyword); + const matchesWarehouse=state.inventorySearch.warehouse==='all'||item.locations.some(location=>location[0]===state.inventorySearch.warehouse); + return matchesKeyword&&matchesWarehouse; + }); + } + function executeInventorySearch(){ + const warehouse=$('#inventoryWarehouse')?.value??state.inventorySearch.warehouse;const keyword=$('#inventoryKeyword')?.value??state.inventorySearch.keyword; + state.inventorySearch={warehouse,keyword:keyword.trim()};state.inventoryDataState='loading';renderT05(screen('ERP-INV-001')); + setTimeout(()=>{state.inventoryDataState='ready';const visible=inventoryVisibleItems();if(!visible.some(item=>item.id===state.inventoryItemId))state.inventoryItemId=visible[0]?.id||'';renderT05(screen('ERP-INV-001'));announce(`재고현황 ${visible.length}개 품목을 조회했습니다.`);},220); + } + function renderT05(s){ + const visibleItems=inventoryVisibleItems();let item=visibleItems.find(x=>x.id===state.inventoryItemId)||visibleItems[0]||null;if(item&&state.inventoryItemId!==item.id)state.inventoryItemId=item.id; + const search=`
${btn('조회','primary','inventorySearchInline')}
`; + let content=''; + if(state.inventoryDataState==='loading')content=dataState('loading','재고 조회 중...','창고와 품목 조건을 유지한 채 재고 Projection을 조회하고 있습니다.'); + else if(!item)content=dataState('empty','조회된 재고 품목이 없습니다.','조건을 초기화하고 다시 조회해 보세요.','inventoryReset'); + else{ + const leftRows=visibleItems.map(x=>`${esc(x.name)}${x.stock.toLocaleString('ko-KR')}`).join(''); + const rightRows=item.locations.filter(row=>state.inventorySearch.warehouse==='all'||row[0]===state.inventorySearch.warehouse).map(row=>`${esc(row[0])}${esc(row[1])}${Number(row[2]).toLocaleString('ko-KR')}${Number(row[3]).toLocaleString('ko-KR')}${Number(row[4]).toLocaleString('ko-KR')}`).join(''); + const history=[['16:31','출고할당','','20',item.stock-20,'ORD-260811-1022'],['14:02','입고','100','',item.stock,'RCV-260811-0081'],['11:24','출고','','12',item.stock-100,'ORD-260811-0931']].map(row=>`${row.map((value,index)=>`${esc(value)}`).join('')}`).join(''); + content=`
현재 조회품목 ${visibleItems.length}창고 ${esc(state.inventorySearch.warehouse==='all'?'전체':state.inventorySearch.warehouse)}
품목현재고
${grid(['품목코드','품목명','현재고'],leftRows,{caption:'재고 품목 목록'})}
창고 / Location${esc(item.id)} ${esc(item.name)} · ${item.stock.toLocaleString('ko-KR')}개
${grid(['창고','Location','현재고','할당','보류'],rightRows||`선택한 창고의 재고 위치가 없습니다.`,{caption:'품목별 창고 로케이션 재고'})}
재고이력현재고 근거 추적 · ${esc(item.id)}
${grid(['일시','업무','입고','출고','잔량','참조번호'],history,{caption:'재고 이력'})}
`; + } + workspace.innerHTML=pageShell(s,`${search}${content}`,`
${btn('조회 F3','','inventorySearchButton')}
${btn('재고이동')}${btn('엑셀 ▼')}
`);bindPageTools(s); + $$('.inventory-select',workspace).forEach(button=>button.onclick=()=>{state.inventoryItemId=button.dataset.item;renderT05(s);announce(`${button.dataset.item} 재고 상세를 표시했습니다.`);}); + $('#inventorySearch').onsubmit=e=>{e.preventDefault();executeInventorySearch();};$('#inventorySearchButton').onclick=executeInventorySearch;const inventoryInline=$('#inventorySearchInline');if(inventoryInline)inventoryInline.onclick=executeInventorySearch; + const reset=$('#inventoryReset');if(reset)reset.onclick=()=>{state.inventorySearch={warehouse:'all',keyword:''};state.inventoryItemId=inventoryItems[0].id;state.inventoryDataState='ready';renderT05(s);announce('재고 조회조건을 초기화했습니다.');}; + } + + function queueVisibleRows(){ + const keyword=state.queueSearch.keyword.trim().toLocaleLowerCase('ko-KR');const worker=state.queueSearch.worker.trim().toLocaleLowerCase('ko-KR'); + return queueRows.filter(row=>{ + const quick=state.queueFilter==='all'||state.queueFilter==='exception'&&row.exception||state.queueFilter===row.type; + const workerMatch=!worker||row.worker.toLocaleLowerCase('ko-KR').includes(worker); + const keywordMatch=!keyword||`${row.no} ${row.type} ${row.wave} ${row.status}`.toLocaleLowerCase('ko-KR').includes(keyword); + return quick&&workerMatch&&keywordMatch; + }); + } + function executeQueueSearch(){ + state.queueSearch={worker:($('#queueWorker')?.value??state.queueSearch.worker).trim(),keyword:($('#queueKeyword')?.value??state.queueSearch.keyword).trim()};state.selectedQueueRows.clear();state.queueActionResult='';state.queueDataState='loading';renderT06(screen('WMS-WORK-001'));setTimeout(()=>{state.queueDataState='ready';renderT06(screen('WMS-WORK-001'));announce(`작업 Queue ${queueVisibleRows().length}건을 조회했습니다.`);},220); + } + function renderT06(s){ + const visible=queueVisibleRows(); + const selectedCount=state.selectedQueueRows.size;const allSelected=visible.length>0&&visible.every(row=>state.selectedQueueRows.has(row.no)); + const rows=visible.map(row=>`${esc(row.no)}${esc(row.type)}${esc(row.wave)}${esc(row.worker)}${esc(row.progress)}${esc(row.sla)}${esc(row.status)}`).join(''); + const table=grid(['선택','작업번호','작업유형','Wave','작업자','진행률','SLA','상태'],rows,{caption:'물류 작업 대기열'}).replace('선택',``); + const summary=[['입고','입고대기','218'],['적치','적치대기','43'],['피킹','피킹대기','927'],['검수','검수대기','122'],['exception','예외','12']]; + const result=state.queueActionResult?`
최근 처리${esc(state.queueActionResult)}
`:''; + const search=`
${btn('조회','primary','queueSearchInline')}
`; + let queueContent='';if(state.queueDataState==='loading')queueContent=dataState('loading','작업 Queue 조회 중...','현재 필터와 검색조건을 유지하고 최신 작업 상태를 조회합니다.');else if(!visible.length)queueContent=dataState('empty','처리할 작업이 없습니다.','검색조건 또는 예외 필터를 초기화해 보세요.','queueClearSearch');else queueContent=`
지금 처리할 업무표시 ${visible.length}건선택 ${selectedCount}지연 ${visible.filter(row=>row.status==='지연').length}건
${selectedCount?`
${selectedCount}건 선택${btn('작업자 배정','primary','queueAssign')}${btn('Wave 생성','','queueWave')}
`:''}${result}${table}`; + const body=`${search}
${summary.map(([key,label,count])=>``).join('')}
${queueContent}`; + const commands=`
${btn('조회 F3','','queueSearch')}
${btn('작업자 배정','primary','queueAssignTop',selectedCount===0)}${btn('Wave 생성','','queueWaveTop',selectedCount===0)}${btn('예외만 보기','','queueException')}
`; + workspace.innerHTML=pageShell(s,body,commands);bindPageTools(s);bindT06(); + } + function bindT06(){ + $$('[data-queue-filter]',workspace).forEach(button=>button.onclick=()=>{state.queueFilter=button.dataset.queueFilter;state.selectedQueueRows.clear();state.queueActionResult='';state.queueDataState='ready';renderT06(screen('WMS-WORK-001'));}); + const reset=$('#queueReset');if(reset)reset.onclick=()=>{state.queueFilter='all';state.selectedQueueRows.clear();renderT06(screen('WMS-WORK-001'));};$('#queueException').onclick=()=>{state.queueFilter='exception';state.selectedQueueRows.clear();state.queueDataState='ready';renderT06(screen('WMS-WORK-001'));};$('#queueSearch').onclick=executeQueueSearch;const inlineSearch=$('#queueSearchInline');if(inlineSearch)inlineSearch.onclick=executeQueueSearch;const form=$('#queueSearchForm');if(form)form.onsubmit=e=>{e.preventDefault();executeQueueSearch();}; + const clearSearch=$('#queueClearSearch');if(clearSearch)clearSearch.onclick=()=>{state.queueFilter='all';state.queueSearch={worker:'',keyword:''};state.queueDataState='ready';state.selectedQueueRows.clear();renderT06(screen('WMS-WORK-001'));}; + $$('[data-queue-row]',workspace).forEach(box=>box.onchange=()=>{box.checked?state.selectedQueueRows.add(box.dataset.queueRow):state.selectedQueueRows.delete(box.dataset.queueRow);renderT06(screen('WMS-WORK-001'));}); + const selectAll=$('#queueSelectAll');if(selectAll)selectAll.onchange=()=>{queueVisibleRows().forEach(row=>selectAll.checked?state.selectedQueueRows.add(row.no):state.selectedQueueRows.delete(row.no));renderT06(screen('WMS-WORK-001'));}; + const assign=()=>{if(!state.selectedQueueRows.size)return;for(const row of queueRows){if(state.selectedQueueRows.has(row.no))row.worker='홍길동';}state.queueActionResult=`${state.selectedQueueRows.size}건을 홍길동 작업자에게 배정했습니다.`;state.selectedQueueRows.clear();renderT06(screen('WMS-WORK-001'));}; + const wave=()=>{if(!state.selectedQueueRows.size)return;const waveNo='W260811-NEW';let changed=0;for(const row of queueRows){if(state.selectedQueueRows.has(row.no)&&row.wave==='-'){row.wave=waveNo;changed++;}}state.queueActionResult=changed?`${changed}건에 Wave ${waveNo}를 생성했습니다.`:'선택 작업에는 새 Wave를 생성할 대상이 없습니다.';state.selectedQueueRows.clear();renderT06(screen('WMS-WORK-001'));}; + for(const id of ['queueAssign','queueAssignTop']){const button=$('#'+id);if(button)button.onclick=assign;}for(const id of ['queueWave','queueWaveTop']){const button=$('#'+id);if(button)button.onclick=wave;}const clear=$('#queueResultClear');if(clear)clear.onclick=()=>{state.queueActionResult='';renderT06(screen('WMS-WORK-001'));}; + } + + function reconcileVisibleRows(){ + const keyword=state.reconcileSearch.keyword.trim().toLocaleLowerCase('ko-KR');return reconcileRows.filter(row=>(!state.mismatchOnly||row.diff!==0)&&(!keyword||`${row.no} ${row.reason} ${row.status}`.toLocaleLowerCase('ko-KR').includes(keyword))); + } + function executeReconcileSearch(){ + state.reconcileSearch={date:($('#reconcileDate')?.value??state.reconcileSearch.date).trim(),keyword:($('#reconcileKeyword')?.value??state.reconcileSearch.keyword).trim()};state.selectedReconcileRows.clear();state.reconcileActionResult='';state.reconcileDataState='loading';renderT07(screen('COMMON-REC-001'));setTimeout(()=>{state.reconcileDataState='ready';renderT07(screen('COMMON-REC-001'));announce(`대사 결과 ${reconcileVisibleRows().length}건을 표시했습니다.`);},220); + } + function renderT07(s){ + const visible=reconcileVisibleRows();const selectedCount=state.selectedReconcileRows.size;const allSelected=visible.length>0&&visible.every(row=>state.selectedReconcileRows.has(row.no)); + const rows=visible.map(row=>`${esc(row.no)}${row.expected}${row.actual}${row.diff}${esc(row.reason)}${esc(row.status)}`).join(''); + const table=grid(['선택','주문번호','OMS','WMS','차이','원인','처리상태'],rows,{caption:'OMS WMS 재고 출고 대사'}).replace('선택',``); + const result=state.reconcileActionResult?`
최근 처리${esc(state.reconcileActionResult)}
`:''; + const search=`
${btn('조회','primary','reconcileSearchInline')}
`; + let resultContent='';if(state.reconcileDataState==='loading')resultContent=dataState('loading','대사 결과 조회 중...','Expected/Actual/Difference Projection을 다시 조회하고 있습니다.');else if(!visible.length)resultContent=dataState('empty','조건에 맞는 대사 건이 없습니다.','불일치 필터 또는 검색어를 초기화해 보세요.','reconcileReset');else resultContent=`${selectedCount?`
${selectedCount}건 선택${btn('원인 재조회','','reconcileReason')}${btn('재처리','primary','reconcileRun')}${btn('처리사유 등록','','reconcileReasonNote')}
`:''}${result}${table}`; + const body=`${search}
전체 12,482정상 12,439불일치 43표시 ${visible.length}선택 ${selectedCount}
${resultContent}`; + const commands=`
${btn('조회 F3','','reconcileSearchButton')}
${btn('재처리','primary','reconcileRunTop',selectedCount===0)}${btn('엑셀 ▼')}
`; + workspace.innerHTML=pageShell(s,body,commands);bindPageTools(s);bindT07(); + } + function bindT07(){ + const mismatch=$('#mismatchOnly');if(mismatch)mismatch.onchange=e=>{state.mismatchOnly=e.target.checked;state.selectedReconcileRows.clear();state.reconcileDataState='ready';renderT07(screen('COMMON-REC-001'));};const form=$('#reconcileSearch');if(form)form.onsubmit=e=>{e.preventDefault();executeReconcileSearch();};const reconcileInline=$('#reconcileSearchInline');if(reconcileInline)reconcileInline.onclick=executeReconcileSearch;$('#reconcileSearchButton').onclick=executeReconcileSearch; + const reset=$('#reconcileReset');if(reset)reset.onclick=()=>{state.mismatchOnly=false;state.reconcileSearch={date:'2026-08-11',keyword:''};state.selectedReconcileRows.clear();state.reconcileDataState='ready';renderT07(screen('COMMON-REC-001'));}; + $$('[data-reconcile-row]',workspace).forEach(box=>box.onchange=()=>{box.checked?state.selectedReconcileRows.add(box.dataset.reconcileRow):state.selectedReconcileRows.delete(box.dataset.reconcileRow);renderT07(screen('COMMON-REC-001'));}); + const all=$('#reconcileSelectAll');if(all)all.onchange=()=>{reconcileVisibleRows().forEach(row=>all.checked?state.selectedReconcileRows.add(row.no):state.selectedReconcileRows.delete(row.no));renderT07(screen('COMMON-REC-001'));}; + const rerun=()=>{if(!state.selectedReconcileRows.size)return;const count=state.selectedReconcileRows.size;for(const row of reconcileRows){if(state.selectedReconcileRows.has(row.no)){row.actual=row.expected;row.diff=0;row.reason='재처리 완료';row.status='해결';}}state.reconcileActionResult=`${count}건을 재처리하여 현재 Reference 데이터 기준 차이를 0으로 맞췄습니다.`;state.selectedReconcileRows.clear();renderT07(screen('COMMON-REC-001'));}; + for(const id of ['reconcileRun','reconcileRunTop']){const button=$('#'+id);if(button)button.onclick=rerun;}const reason=$('#reconcileReason');if(reason)reason.onclick=()=>{state.reconcileActionResult=`${state.selectedReconcileRows.size}건의 원인 Projection을 다시 조회했습니다.`;renderT07(screen('COMMON-REC-001'));};const note=$('#reconcileReasonNote');if(note)note.onclick=()=>{state.reconcileActionResult=`${state.selectedReconcileRows.size}건에 처리사유가 필요합니다. 실제 제품에서는 사유 입력 Drawer를 사용합니다.`;renderT07(screen('COMMON-REC-001'));};const clear=$('#reconcileResultClear');if(clear)clear.onclick=()=>{state.reconcileActionResult='';renderT07(screen('COMMON-REC-001'));}; + } + + function applyImportFile(file){ + if(!file)return false;const name=String(file.name||'').trim();const size=Number(file.size||0);const validExt=/\.xlsx?$/i.test(name);const max=20*1024*1024; + if(!validExt){toast('Excel .xlsx 또는 .xls 파일을 선택하세요.','error');return false}if(size>max){toast('파일 크기는 20MB 이하여야 합니다.','error');return false} + state.importFileName=name;state.importFileSize=size;state.importFileType=file.type||'';state.importJobStatus='idle';state.importProgress=0;state.importProcessed=0;renderT08(screen('OMS-ORD-003'));announce(`${name} 파일을 선택했습니다.`);return true; + } + function renderT08(s){ + const step=state.importStep;const steps=['파일','매핑','검증','미리보기','반영','결과']; + let content=''; + if(step===1)content=`
${state.importFileName?esc(state.importFileName):'Excel 파일을 선택하세요'}

${state.importFileName?`${state.importFileSize?`${(state.importFileSize/1024/1024).toFixed(2)}MB · `:''}파일 선택 완료 · 다음 단계에서 필드 매핑을 확인합니다.`:'파일 선택은 필수 접근 경로이며 Drag & Drop은 편의 기능입니다. .xlsx/.xls · 최대 20MB'}

${btn(state.importFileName?'파일 다시 선택':'파일 선택','primary','fileSelect')} ${btn('업로드 양식 다운로드')}
`; + if(step===2)content=`
Excel ColumnSystem Field매핑 근거
${[['주문번호','주문번호','정확 일치'],['상품코드','품목코드','Alias 일치'],['수량','주문수량','Alias 일치'],['구매자','주문자명','Alias 일치'],['업체코드','거래처코드','사용자 확인 필요']].map(row=>`
${esc(row[0])}→ ${esc(row[1])}${esc(row[2])}
`).join('')}
`; + if(step===3)content=`
전체18,230
정상17,894
오류336
${grid(['행','필드','값','오류'],`8품목코드ABC99존재하지 않는 품목12수량-1수량은 0보다 커야 합니다.`,{caption:'Excel 검증 오류'})}`; + if(step===4)content=`
반영 미리보기오류 336건 제외

17,894건을 반영할 수 있습니다.

신규
17,201
수정
693
제외 오류
336

실제 제품은 Staging 데이터를 Domain 검증한 뒤 Commit합니다.

`; + if(step===5){const running=state.importJobStatus==='running';const completed=state.importJobStatus==='completed';content=`
Background 반영 작업${completed?'완료':running?'처리 중':'대기'}
${state.importProgress}%

${state.importProcessed.toLocaleString('ko-KR')} / 17,894 처리

다른 화면으로 이동해도 작업은 계속됩니다. 홈의 진행 작업에서 다시 들어올 수 있습니다.

${!running&&!completed?btn('17,894건 반영','primary','importCommit'):''}${completed?btn('결과 보기','primary','importResult'):''}
`;} + if(step===6)content=`
반영 결과완료
성공17,894
오류336

오류 데이터는 Domain DB에 반영하지 않았습니다.

${btn('오류 336건 다운로드')}${btn('새 업로드','primary','importReset')}
`; + const nav=`
${steps.map((label,index)=>`
${index+1}${label}
`).join('')}
`; + const buttons=step<5?`
${step>1?btn('이전','','importBack'):''}${step<4?btn('다음','primary','importNext',step===1&&!state.importFileName):step===4?btn('반영 단계','primary','importNext'):''}
`:''; + workspace.innerHTML=pageShell(s,`${nav}${content}${buttons}`,`
${btn('양식 다운로드')}
${btn('최근 업로드 결과')}
`);bindPageTools(s);bindT08(); + } + function startImportJob(){ + if(importTimer)clearInterval(importTimer);state.importStep=5;state.importJobStatus='running';state.importProgress=0;state.importProcessed=0;renderT08(screen('OMS-ORD-003')); + importTimer=setInterval(()=>{const increment=state.importProgress<60?11:state.importProgress<90?7:3;state.importProgress=Math.min(100,state.importProgress+increment);state.importProcessed=Math.floor(17894*state.importProgress/100);if(state.activeId==='OMS-ORD-003'&&state.importStep===5)renderT08(screen('OMS-ORD-003'));if(state.importProgress>=100){clearInterval(importTimer);importTimer=null;state.importJobStatus='completed';state.importProcessed=17894;if(state.activeId==='OMS-ORD-003'&&state.importStep===5)renderT08(screen('OMS-ORD-003'));toast('Excel 주문 Import가 완료되었습니다.');}},180); + } + function bindT08(){ + const trigger=$('#fileSelect');const input=$('#importFileInput');if(trigger&&input)trigger.onclick=()=>input.click();if(input)input.onchange=()=>{const file=input.files?.[0];if(file)applyImportFile(file);}; + const drop=$('#dropZone');if(drop){drop.ondragover=e=>{e.preventDefault();drop.classList.add('dragging')};drop.ondragleave=()=>drop.classList.remove('dragging');drop.ondrop=e=>{e.preventDefault();drop.classList.remove('dragging');applyImportFile(e.dataTransfer?.files?.[0]);};drop.onkeydown=e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();input?.click();}};} + const next=$('#importNext');if(next)next.onclick=()=>{if(state.importStep===1&&!state.importFileName)return;state.importStep=Math.min(5,state.importStep+1);renderT08(screen('OMS-ORD-003'));}; + const back=$('#importBack');if(back)back.onclick=()=>{state.importStep=Math.max(1,state.importStep-1);renderT08(screen('OMS-ORD-003'));}; + const commit=$('#importCommit');if(commit)commit.onclick=startImportJob;const result=$('#importResult');if(result)result.onclick=()=>{state.importStep=6;renderT08(screen('OMS-ORD-003'));}; + const reset=$('#importReset');if(reset)reset.onclick=()=>{state.importStep=1;state.importFileName='';state.importFileSize=0;state.importFileType='';state.importJobStatus='idle';state.importProgress=0;state.importProcessed=0;renderT08(screen('OMS-ORD-003'));}; + } + + function renderT09(s){ + const remain=Math.max(0,state.pickingRequired-state.pickingQty);const completed=remain===0; + const network=state.networkStatus==='online'?['●','온라인 · 동기화 완료','online']:state.networkStatus==='unstable'?['▲','연결 불안정 · 서버 확인 필요','unstable']:['○','오프라인 · 작업 반영 중지','offline']; + const errorTitle=state.scanError?.startsWith('NETWORK:')?'네트워크를 확인하세요.':'다른 상품입니다.';const errorCopy=(state.scanError||'').replace(/^NETWORK:/,''); + workspace.innerHTML=`

출고 피킹

23 / 138
${network[0]} ${network[1]}
LOCATION
A-03-02

위치 확인 완료 · 상품을 스캔하세요.

${state.scanBuffer?esc(state.scanBuffer):'SCAN READY'}스캐너 입력 후 Enter · 기대 바코드 8801234567890
ABC 운동화

BLACK / 270

필요${state.pickingRequired}
피킹${state.pickingQty}
남음${remain}
${completed?'
상품 완료다음 품목으로 자동 진행할 수 있습니다.
':''}${state.scanError?``:''}
${btn('+1 피킹','primary','pickOne',completed||state.networkStatus!=='online')}${btn('수량입력','','pickQty',state.networkStatus!=='online')}${btn('재고부족 신고','danger full','stockIssue')}
`; + bindT09(); + } + function applyScan(value){ + const normalized=value.trim();state.scanBuffer='';if(!normalized)return;const now=Date.now(); + if(normalized===state.lastScanValue&&now-state.lastScanAt<700){toast('중복 스캔을 감지해 무시했습니다. 수량은 변경되지 않았습니다.','error');setTimeout(()=>$('#scanBox')?.focus(),0);return;} + state.lastScanValue=normalized;state.lastScanAt=now; + if(state.networkStatus!=='online'){state.scanError=`NETWORK:${state.networkStatus==='unstable'?'연결이 불안정하여 서버 검증을 완료하지 못했습니다. 수량은 반영하지 않았습니다.':'오프라인에서는 서버 검증이 필요한 피킹 수량을 반영하지 않습니다.'}`;renderT09(screen('WMS-PICK-001'));setTimeout(()=>$('#scanBox')?.focus(),0);return;} + if(normalized!=='8801234567890'){state.scanError=`스캔 ${normalized} · 필요 ABC001 / 8801234567890`;renderT09(screen('WMS-PICK-001'));setTimeout(()=>$('#scanBox')?.focus(),0);return;} + state.scanError='';if(state.pickingQty$('#scanBox')?.focus(),0); + } + function bindT09(){ + const scan=$('#scanBox');if(scan){scan.onkeydown=e=>{if(e.key==='Enter'){e.preventDefault();applyScan(state.scanBuffer);return;}if(e.key==='Backspace'){state.scanBuffer=state.scanBuffer.slice(0,-1);}else if(e.key.length===1&&!e.ctrlKey&&!e.metaKey&&!e.altKey){state.scanBuffer+=e.key;}const strong=$('strong',scan);if(strong)strong.textContent=state.scanBuffer||'SCAN READY';};} + $('#pickOne').onclick=()=>{if(state.pickingQty{$('#quantityInput').value=String(state.pickingQty);$('#quantityInput').max=String(state.pickingRequired);quantityDialog.showModal();setTimeout(()=>$('#quantityInput').focus(),0);}; + $('#stockIssue').onclick=()=>{exceptionDialog.showModal();setTimeout(()=>$('input[name="exceptionReason"]',exceptionDialog)?.focus(),0);}; + } + + function renderActive(){ const s=screen(state.activeId);app.dataset.mode=s.template==='T09'?'wms-mobile':'desktop';if(s.id==='HOME')return renderHome();({T01:renderT01,T02:renderT02,T03:renderT03,T04:renderT04,T05:renderT05,T06:renderT06,T07:renderT07,T08:renderT08,T09:renderT09}[s.template]||renderHome)(s); } + function renderAll(){renderNavigation();renderTabs();renderActive();} + + function openSearch(){closeSystemPanel(false);state.searchIndex=0;searchInput.value='';searchInput.setAttribute('aria-expanded','true');renderSearchResults();if(!searchDialog.open)searchDialog.showModal();setTimeout(()=>searchInput.focus(),0);} + function searchMatches(){const q=searchInput.value.trim().toLowerCase();return screens.filter(x=>x.id!=='HOME').filter(x=>!q||[x.id,x.title,x.module,x.group,...x.keywords].join(' ').toLowerCase().includes(q));} + function renderSearchResults(){ + const list=searchMatches();if(state.searchIndex>=list.length)state.searchIndex=Math.max(0,list.length-1);searchInput.setAttribute('aria-activedescendant',list[state.searchIndex]?`menu-result-${list[state.searchIndex].id}`:''); + searchResults.innerHTML=list.length?list.map((item,index)=>``).join(''):'
검색 결과가 없습니다.
'; + $$('.search-result',searchResults).forEach((button,index)=>{button.onmousemove=()=>{state.searchIndex=index;$$('.search-result',searchResults).forEach((x,i)=>x.classList.toggle('active',i===index));};button.onclick=()=>{searchDialog.close();openScreen(button.dataset.screen);};}); + } + + searchInput.oninput=()=>{state.searchIndex=0;renderSearchResults();}; + searchInput.onkeydown=e=>{const list=searchMatches();if(e.key==='ArrowDown'){e.preventDefault();state.searchIndex=Math.min(list.length-1,state.searchIndex+1);renderSearchResults();$('.search-result.active',searchResults)?.scrollIntoView({block:'nearest'});}else if(e.key==='ArrowUp'){e.preventDefault();state.searchIndex=Math.max(0,state.searchIndex-1);renderSearchResults();$('.search-result.active',searchResults)?.scrollIntoView({block:'nearest'});}else if(e.key==='Enter'&&list[state.searchIndex]){e.preventDefault();searchDialog.close();openScreen(list[state.searchIndex].id);}}; + + $('#homeButton').onclick=()=>openScreen('HOME');$('#sideHomeButton').onclick=()=>openScreen('HOME');$('#globalSearchButton').onclick=openSearch;$('#sideSearchButton').onclick=openSearch;$('#searchCloseButton').onclick=()=>searchDialog.close(); + searchDialog.addEventListener('close',()=>searchInput.setAttribute('aria-expanded','false')); + $('#lookupCloseButton').onclick=()=>lookupDialog.close();$('#lookupSearchForm').onsubmit=e=>{e.preventDefault();state.lookupIndex=0;renderLookupResults();};lookupSearchInput.oninput=()=>{state.lookupIndex=0;renderLookupResults();}; + lookupSearchInput.onkeydown=e=>{const list=lookupItems();if(e.key==='ArrowDown'){e.preventDefault();state.lookupIndex=Math.min(Math.max(0,list.length-1),state.lookupIndex+1);renderLookupResults();$('.lookup-result.active',lookupResults)?.scrollIntoView({block:'nearest'});}else if(e.key==='ArrowUp'){e.preventDefault();state.lookupIndex=Math.max(0,state.lookupIndex-1);renderLookupResults();$('.lookup-result.active',lookupResults)?.scrollIntoView({block:'nearest'});}else if(e.key==='Enter'&&list[state.lookupIndex]){e.preventDefault();selectLookup();}};$('#lookupSelectButton').onclick=selectLookup;lookupDialog.addEventListener('close',()=>{if(document.activeElement===document.body)lookupRestoreFocus?.focus();}); + $('#collapseButton').onclick=()=>{state.collapsed=!state.collapsed;persist();renderNavigation();}; + $('#densityButton').onclick=()=>{state.density=state.density==='compact'?'comfortable':'compact';persist();renderNavigation();toast(`화면 밀도를 ${state.density==='compact'?'Compact':'Comfortable'}로 변경했습니다.`);}; + $('#themeButton').onclick=()=>{state.theme=state.theme==='dark'?'light':'dark';persist();renderNavigation();toast(`KBX Business ${state.theme==='dark'?'Dark':'Light'} 테마를 적용했습니다.`);}; + $('#moduleSelect').onchange=e=>{state.module=e.target.value;renderNavigation();if(state.activeId==='HOME')renderHome();}; + $('#drawerClose').onclick=closeDrawer;drawerBackdrop.onclick=closeDrawer;$('#systemPanelClose').onclick=()=>closeSystemPanel();$$('[data-panel]').forEach(button=>button.onclick=()=>showSystemPanel(button.dataset.panel)); + drawer.onkeydown=e=>{if(e.key==='Escape'){e.preventDefault();closeDrawer();return;}if(e.key!=='Tab')return;const focusables=$$('button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),a[href]',drawer);if(!focusables.length)return;const first=focusables[0],last=focusables[focusables.length-1];if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();}else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();}}; + + function clearUnsavedPending(){state.pendingCloseId=null;state.pendingNavigationId=null;state.pendingNavigationOptions=null;state.pendingMasterId=null;state.pendingUnsavedMode=null;} + function continuePendingNavigation(message){ + const target=state.pendingNavigationId;const options=state.pendingNavigationOptions||{};clearUnsavedPending();unsavedDialog.close(); + if(target){openScreen(target,{...options,force:true});toast(message);} + } + function trySaveDirtyScreen(id){ + if(id==='ERP-MST-ITEM-001'){if(!validateMasterDraft())return false;return saveMaster();} + if(id==='OMS-ORD-002'){if(!validateTransaction())return false;markDirty(id,false);toast('주문을 저장했습니다.');return true;} + if(id==='ERP-PRICE-001'){if(fastErrorCount()>0)return false;markDirty(id,false);toast('오류 없는 변경 행을 저장했습니다.');return true;} + markDirty(id,false);return true; + } + $('#unsavedStay').onclick=()=>{clearUnsavedPending();}; + $('#unsavedDiscard').onclick=()=>{ + if(state.pendingUnsavedMode==='master-select'){const target=state.pendingMasterId;markDirty('ERP-MST-ITEM-001',false);clearUnsavedPending();unsavedDialog.close();if(target)performMasterTarget(target);toast('저장하지 않은 품목 변경을 버리고 전환했습니다.');return;} + if(state.pendingUnsavedMode==='navigate'){markDirty(state.activeId,false);continuePendingNavigation('저장하지 않은 변경을 버리고 이동했습니다.');return;} + const id=state.pendingCloseId;clearUnsavedPending();unsavedDialog.close();if(id){markDirty(id,false);closeTabNow(id);toast('저장하지 않은 변경을 버리고 화면을 닫았습니다.');} + }; + $('#unsavedSave').onclick=()=>{ + if(state.pendingUnsavedMode==='master-select'){const target=state.pendingMasterId;if(!validateMasterDraft()){clearUnsavedPending();unsavedDialog.close();renderT02(screen('ERP-MST-ITEM-001'));setTimeout(()=>$('[data-master-error]',workspace)?.focus(),0);return;}saveMaster();clearUnsavedPending();unsavedDialog.close();if(target)performMasterTarget(target);return;} + if(state.pendingUnsavedMode==='navigate'){const activeId=state.activeId;if(!trySaveDirtyScreen(activeId)){const mode=state.pendingUnsavedMode;clearUnsavedPending();unsavedDialog.close();if(activeId==='ERP-MST-ITEM-001')renderT02(screen(activeId));if(activeId==='OMS-ORD-002')renderT03(screen(activeId));if(activeId==='ERP-PRICE-001')renderT04(screen(activeId));toast('입력 오류가 있어 저장 후 이동할 수 없습니다.','error');return;}continuePendingNavigation('변경사항을 저장하고 이동했습니다.');return;} + const id=state.pendingCloseId;if(id&&!trySaveDirtyScreen(id)){clearUnsavedPending();unsavedDialog.close();toast('입력 오류가 있어 저장 후 닫을 수 없습니다.','error');return;}clearUnsavedPending();unsavedDialog.close();if(id){closeTabNow(id);toast('변경사항을 저장하고 화면을 닫았습니다.');} + }; + $('#quantityApply').onclick=()=>{const value=Number($('#quantityInput').value);if(!Number.isInteger(value)||value<0||value>state.pickingRequired){$('#quantityHelp').textContent=`0 ~ ${state.pickingRequired} 사이 정수를 입력하세요.`;$('#quantityInput').focus();return;}state.pickingQty=value;state.scanError='';quantityDialog.close();renderT09(screen('WMS-PICK-001'));toast('피킹 수량을 변경했습니다.');}; + $('#exceptionApply').onclick=()=>{const reason=$('input[name="exceptionReason"]:checked',exceptionDialog)?.value||'기타';exceptionDialog.close();toast(`재고부족 사유 '${reason}'를 등록했습니다.`,'error');}; + + document.addEventListener('keydown',e=>{ + if(e.defaultPrevented)return; + const modalOpen=[searchDialog,lookupDialog,unsavedDialog,quantityDialog,exceptionDialog].some(dialog=>dialog.open); + if(modalOpen)return; + if(e.key==='Escape'&&!systemPanel.hidden){e.preventDefault();closeSystemPanel();return;} + if(e.key==='Escape'&&drawer.classList.contains('open')){e.preventDefault();closeDrawer();return;} + if(drawer.classList.contains('open'))return; + if((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()==='k'){e.preventDefault();openSearch();return;} + if(e.key==='F2'&&['T02','T03'].includes(screen(state.activeId).template)){const root=document.activeElement?.closest?.('.lookup')||$('.lookup',workspace);if(root){e.preventDefault();openLookup(root);return;}} + if(e.key==='F3'&&state.activeId!=='HOME'){e.preventDefault();const searchButton=state.activeId==='OMS-ORD-001'?$('#commandSearch'):state.activeId==='ERP-MST-ITEM-001'?$('#masterSearchTop'):state.activeId==='ERP-INV-001'?$('#inventorySearchButton'):state.activeId==='WMS-WORK-001'?$('#queueSearch'):state.activeId==='COMMON-REC-001'?$('#reconcileSearchButton'):null;if(searchButton)searchButton.click();else toast(`${screen(state.activeId).title} 조회를 실행했습니다.`);} + if(e.key==='F8'&&['T02','T03','T04'].includes(screen(state.activeId).template)){e.preventDefault();const saveButton=state.activeId==='ERP-MST-ITEM-001'?$('#masterSave'):state.activeId==='OMS-ORD-002'?$('#saveTransaction'):state.activeId==='ERP-PRICE-001'?$('#fastSave'):null;if(saveButton&&!saveButton.disabled)saveButton.click();} + }); + window.addEventListener('hashchange',()=>{const found=screens.find(x=>x.path===location.hash);if(!found||found.id===state.activeId)return;const opened=openScreen(found.id,{updateHash:false});if(!opened){state.pendingNavigationOptions={...(state.pendingNavigationOptions||{}),updateHash:true};history.replaceState(null,'',screen(state.activeId).path);}}); + window.addEventListener('beforeunload',event=>{if(state.tabs.some(tab=>tab.dirty)){event.preventDefault();event.returnValue='';}}); + + unsavedDialog.addEventListener('close',()=>{if(state.pendingUnsavedMode)clearUnsavedPending();}); + restore();restoreWorkspaceSession(); + const params=new URLSearchParams(location.search);const demoState=params.get('state');if(['ready','loading','empty','error'].includes(demoState))state.orderDataState=demoState;const network=params.get('network');if(['online','unstable','offline'].includes(network))state.networkStatus=network;const theme=params.get('theme');if(['light','dark'].includes(theme))state.theme=theme; + const byHash=screens.find(x=>x.path===location.hash);if(byHash)state.activeId=byHash.id; + if(state.activeId!=='HOME'&&!state.tabs.some(t=>t.id===state.activeId))state.tabs.push({id:state.activeId,title:screen(state.activeId).title,pinned:false,dirty:false,resumed:false});persistWorkspaceSession(); + renderAll(); +})(); diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/browser-interaction-v38.json b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/browser-interaction-v38.json new file mode 100644 index 00000000..1d6fe9bc --- /dev/null +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/browser-interaction-v38.json @@ -0,0 +1,20 @@ +{ + "viewport": "1440x900", + "method": "Headless Chromium interaction harness using the same index/css/js with resources inlined because loopback/file navigation is blocked by the execution sandbox.", + "runtimeErrors": 0, + "checks": { + "homeRendered": true, + "ctrlKSearchOpenedT01": true, + "t01QuickFilterChangedRows": true, + "t01SelectionEnabledBulkAction": true, + "drawerEscapeRecovery": true, + "dirtyTabCloseGuard": true, + "t05MasterDetailChanged": true, + "t06ExceptionFilter": true, + "t07MismatchToggle": true, + "t08FileGateAndStep2": true, + "t09WrongBarcodeError": true, + "t09CorrectBarcodeIncrement": true, + "t09QuantityDialog": true + } +} diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/browser-interaction-v39.json b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/browser-interaction-v39.json new file mode 100644 index 00000000..823998e8 --- /dev/null +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/browser-interaction-v39.json @@ -0,0 +1,162 @@ +{ + "version": "v39", + "passed": 31, + "total": 31, + "results": [ + { + "name": "home stock intent opens T01", + "ok": true, + "detail": "" + }, + { + "name": "home stock intent applies filter", + "ok": true, + "detail": "" + }, + { + "name": "stock filter narrows visible rows", + "ok": true, + "detail": "1" + }, + { + "name": "T01 Space selects focused row", + "ok": true, + "detail": "" + }, + { + "name": "T01 Enter opens detail drawer", + "ok": true, + "detail": "" + }, + { + "name": "drawer makes background inert", + "ok": true, + "detail": "" + }, + { + "name": "drawer Esc restores background", + "ok": true, + "detail": "" + }, + { + "name": "dirty navigation opens guard", + "ok": true, + "detail": "" + }, + { + "name": "dirty navigation keeps T03 until decision", + "ok": true, + "detail": "" + }, + { + "name": "discard continues pending navigation", + "ok": true, + "detail": "" + }, + { + "name": "T02 F2 opens lookup dialog", + "ok": true, + "detail": "" + }, + { + "name": "lookup Enter selects warehouse", + "ok": true, + "detail": "" + }, + { + "name": "lookup selection advances focus", + "ok": true, + "detail": "barcode" + }, + { + "name": "help opens actual drawer content", + "ok": true, + "detail": "" + }, + { + "name": "suggestion form submits and closes", + "ok": true, + "detail": "" + }, + { + "name": "T08 opens after save", + "ok": true, + "detail": "" + }, + { + "name": "T08 reaches preview step", + "ok": true, + "detail": "" + }, + { + "name": "T08 background job progresses", + "ok": true, + "detail": "22" + }, + { + "name": "T08 can leave while job runs", + "ok": true, + "detail": "" + }, + { + "name": "T08 return restores progress", + "ok": true, + "detail": "(22, 55)" + }, + { + "name": "T08 job completes", + "ok": true, + "detail": "" + }, + { + "name": "WMS valid scan increments once", + "ok": true, + "detail": "5" + }, + { + "name": "WMS duplicate scan does not increment", + "ok": true, + "detail": "(5, 5)" + }, + { + "name": "runtime console/page errors", + "ok": true, + "detail": "[]" + }, + { + "name": "1280x720 no document horizontal overflow", + "ok": true, + "detail": "[1280, 1280]" + }, + { + "name": "1280x720 grid remains in local scroll container", + "ok": true, + "detail": "" + }, + { + "name": "1280 runtime errors", + "ok": true, + "detail": "[]" + }, + { + "name": "narrow/zoom-pressure shell avoids document overflow", + "ok": true, + "detail": "[720, 720]" + }, + { + "name": "narrow shell collapses side nav", + "ok": true, + "detail": "56px" + }, + { + "name": "narrow runtime errors", + "ok": true, + "detail": "[]" + }, + { + "name": "WMS 390x844 uses full mobile viewport", + "ok": true, + "detail": "mode=wms-mobile, header=none, width=390px, scroll=[390, 390], errors=[]" + } + ] +} \ No newline at end of file diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/browser-interaction-v40.json b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/browser-interaction-v40.json new file mode 100644 index 00000000..fc3f784a --- /dev/null +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/browser-interaction-v40.json @@ -0,0 +1,254 @@ +{ + "version": "v40", + "passed": 49, + "total": 49, + "results": [ + { + "name": "home renders", + "pass": true, + "detail": "" + }, + { + "name": "home actionable attention 4", + "pass": true, + "detail": "" + }, + { + "name": "T01 channel+keyword filters actual rows", + "pass": true, + "detail": "rows=1" + }, + { + "name": "T01 query values preserved after loading render", + "pass": true, + "detail": "" + }, + { + "name": "T01 selection enables ship", + "pass": true, + "detail": "" + }, + { + "name": "T01 reset clears actual criteria", + "pass": true, + "detail": "" + }, + { + "name": "T02 F8 uses validation not dirty bypass", + "pass": true, + "detail": "" + }, + { + "name": "T02 lookup dialog opens", + "pass": true, + "detail": "" + }, + { + "name": "T02 lookup selected warehouse", + "pass": true, + "detail": "" + }, + { + "name": "T02 new master save adds list row", + "pass": true, + "detail": "" + }, + { + "name": "T02 save clears dirty indicator", + "pass": true, + "detail": "" + }, + { + "name": "T02 master selection dirty guard", + "pass": true, + "detail": "" + }, + { + "name": "T02 discard then select target", + "pass": true, + "detail": "" + }, + { + "name": "T03 F8 blocks invalid line", + "pass": true, + "detail": "" + }, + { + "name": "T03 valid save clears dirty", + "pass": true, + "detail": "" + }, + { + "name": "T03 explicit state transition", + "pass": true, + "detail": "" + }, + { + "name": "T03 confirmed disables confirm", + "pass": true, + "detail": "" + }, + { + "name": "T06 selection enables assign", + "pass": true, + "detail": "" + }, + { + "name": "T06 assignment changes row state", + "pass": true, + "detail": "" + }, + { + "name": "T06 action result visible", + "pass": true, + "detail": "" + }, + { + "name": "T07 reprocess resolves selected mismatch", + "pass": true, + "detail": "2->1" + }, + { + "name": "T07 result feedback persists", + "pass": true, + "detail": "" + }, + { + "name": "browser hash navigation dirty guard opens", + "pass": true, + "detail": "" + }, + { + "name": "browser hash rolled back while pending", + "pass": true, + "detail": "#items" + }, + { + "name": "discard completes pending route", + "pass": true, + "detail": "" + }, + { + "name": "T08 background job running", + "pass": true, + "detail": "" + }, + { + "name": "T08 can leave job", + "pass": true, + "detail": "" + }, + { + "name": "T09 mobile hides desktop shell", + "pass": true, + "detail": "" + }, + { + "name": "T09 validated scan increments", + "pass": true, + "detail": "" + }, + { + "name": "runtime console/page errors zero", + "pass": true, + "detail": "" + }, + { + "name": "Ctrl+K opens menu search", + "pass": true, + "detail": "" + }, + { + "name": "menu search Enter opens target", + "pass": true, + "detail": "" + }, + { + "name": "side nav collapse state", + "pass": true, + "detail": "" + }, + { + "name": "density toggle applies comfortable", + "pass": true, + "detail": "" + }, + { + "name": "utility drawer opens", + "pass": true, + "detail": "" + }, + { + "name": "drawer makes shell inert", + "pass": true, + "detail": "" + }, + { + "name": "drawer Esc restores shell", + "pass": true, + "detail": "" + }, + { + "name": "T04 invalid price disables save", + "pass": true, + "detail": "" + }, + { + "name": "T04 corrected price restores save", + "pass": true, + "detail": "" + }, + { + "name": "T04 save clears dirty before navigation", + "pass": true, + "detail": "" + }, + { + "name": "T05 master detail context changes", + "pass": true, + "detail": "" + }, + { + "name": "T08 job progresses while away", + "pass": true, + "detail": "11->33" + }, + { + "name": "T08 job completes asynchronously", + "pass": true, + "detail": "" + }, + { + "name": "T08 result step reachable", + "pass": true, + "detail": "" + }, + { + "name": "T09 duplicate scan does not double increment", + "pass": true, + "detail": "5->5" + }, + { + "name": "T09 wrong barcode recovery visible", + "pass": true, + "detail": "" + }, + { + "name": "T09 390 full viewport width", + "pass": true, + "detail": "" + }, + { + "name": "1280x720 document horizontal overflow none", + "pass": true, + "detail": "0" + }, + { + "name": "supplement runtime errors zero", + "pass": true, + "detail": "" + } + ], + "runtimeErrors": [], + "note": "Production DOM/CSS/JS tested in inline-asset browser harness because container browser navigation is administratively blocked." +} \ No newline at end of file diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-1440x900.png new file mode 100644 index 00000000..40f55a00 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v38-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v38-1440x900.png new file mode 100644 index 00000000..97223ed5 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v38-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v39-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v39-1440x900.png new file mode 100644 index 00000000..796027ff Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v39-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v39-zoom-pressure-720x450.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v39-zoom-pressure-720x450.png new file mode 100644 index 00000000..9dcb24f0 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v39-zoom-pressure-720x450.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v40-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v40-1440x900.png new file mode 100644 index 00000000..796027ff Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v40-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v43-dark-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v43-dark-1440x900.png new file mode 100644 index 00000000..77fea2cd Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v43-dark-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v43-light-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v43-light-1440x900.png new file mode 100644 index 00000000..64a63684 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/home-v43-light-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/lookup-v39-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/lookup-v39-1440x900.png new file mode 100644 index 00000000..6e3af3eb Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/lookup-v39-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t01-order-list-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t01-order-list-1440x900.png new file mode 100644 index 00000000..176e0268 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t01-order-list-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t01-order-list-v38-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t01-order-list-v38-1440x900.png new file mode 100644 index 00000000..4f281bed Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t01-order-list-v38-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t01-order-stock-v39-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t01-order-stock-v39-1440x900.png new file mode 100644 index 00000000..38adfc62 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t01-order-stock-v39-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t01-v39-1280x720.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t01-v39-1280x720.png new file mode 100644 index 00000000..d4aebb83 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t01-v39-1280x720.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t01-v43-dark-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t01-v43-dark-1440x900.png new file mode 100644 index 00000000..d348fc3b Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t01-v43-dark-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t02-lookup-v39-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t02-lookup-v39-1440x900.png new file mode 100644 index 00000000..4b111af6 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t02-lookup-v39-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t02-master-v40-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t02-master-v40-1440x900.png new file mode 100644 index 00000000..1641b7f0 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t02-master-v40-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t03-transaction-v40-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t03-transaction-v40-1440x900.png new file mode 100644 index 00000000..3c55407b Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t03-transaction-v40-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t07-reconcile-v40-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t07-reconcile-v40-1440x900.png new file mode 100644 index 00000000..b1e41b47 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t07-reconcile-v40-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t08-job-v39-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t08-job-v39-1440x900.png new file mode 100644 index 00000000..d00f718d Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t08-job-v39-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t09-picking-phone-v38.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t09-picking-phone-v38.png new file mode 100644 index 00000000..0ba476be Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t09-picking-phone-v38.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t09-picking-v39-390x844.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t09-picking-v39-390x844.png new file mode 100644 index 00000000..67ca28df Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t09-picking-v39-390x844.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t09-picking-v40-390x844.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t09-picking-v40-390x844.png new file mode 100644 index 00000000..6d6c0148 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/t09-picking-v40-390x844.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/theme-browser-v43.json b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/theme-browser-v43.json new file mode 100644 index 00000000..eeaddd39 --- /dev/null +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/theme-browser-v43.json @@ -0,0 +1,89 @@ +{ + "version": "v43", + "scope": "inline-asset browser QA for FE Reference Lab; production CSP remains statically validated", + "passed": 16, + "total": 16, + "checks": [ + { + "name": "initial-light", + "pass": true, + "detail": "light" + }, + { + "name": "toggle-dark", + "pass": true, + "detail": "dark" + }, + { + "name": "dark-text-recomputed", + "pass": true, + "detail": "rgb(229, 231, 235)" + }, + { + "name": "theme-aria", + "pass": true, + "detail": "밝은 테마로 변경" + }, + { + "name": "T01-dark-render", + "pass": true, + "detail": "OMS-ORD-001:T01" + }, + { + "name": "T02-dark-render", + "pass": true, + "detail": "ERP-MST-ITEM-001:T02" + }, + { + "name": "T03-dark-render", + "pass": true, + "detail": "OMS-ORD-002:T03" + }, + { + "name": "T04-dark-render", + "pass": true, + "detail": "ERP-PRICE-001:T04" + }, + { + "name": "T05-dark-render", + "pass": true, + "detail": "ERP-INV-001:T05" + }, + { + "name": "T06-dark-render", + "pass": true, + "detail": "WMS-WORK-001:T06" + }, + { + "name": "T07-dark-render", + "pass": true, + "detail": "COMMON-REC-001:T07" + }, + { + "name": "T08-dark-render", + "pass": true, + "detail": "OMS-ORD-003:T08" + }, + { + "name": "T09-dark-render", + "pass": true, + "detail": "WMS-PICK-001:wms-mobile:wms-mobile" + }, + { + "name": "1280-no-document-overflow", + "pass": true, + "detail": "False" + }, + { + "name": "toggle-light-back", + "pass": true, + "detail": "light" + }, + { + "name": "runtime-errors-zero", + "pass": true, + "detail": "" + } + ], + "pageErrors": [] +} \ No newline at end of file diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-home-dark-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-home-dark-1440x900.png new file mode 100644 index 00000000..f2e5eaba Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-home-dark-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-home-light-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-home-light-1440x900.png new file mode 100644 index 00000000..64a63684 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-home-light-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-runtime-panel-dark-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-runtime-panel-dark-1440x900.png new file mode 100644 index 00000000..92aecfab Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-runtime-panel-dark-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-t01-dark-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-t01-dark-1440x900.png new file mode 100644 index 00000000..4680fbd4 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-t01-dark-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-t01-forced-colors-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-t01-forced-colors-1440x900.png new file mode 100644 index 00000000..b38baf3f Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-t01-forced-colors-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-t09-dark-390x844.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-t09-dark-390x844.png new file mode 100644 index 00000000..14852c9d Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v44-t09-dark-390x844.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v45-home-dark-1280x720.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v45-home-dark-1280x720.png new file mode 100644 index 00000000..f0e62522 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v45-home-dark-1280x720.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v45-t01-preference-dark-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v45-t01-preference-dark-1440x900.png new file mode 100644 index 00000000..f8ec6acc Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v45-t01-preference-dark-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v46-home-resume-dark-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v46-home-resume-dark-1440x900.png new file mode 100644 index 00000000..10d3d918 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v46-home-resume-dark-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v47-t01-problem-dark-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v47-t01-problem-dark-1440x900.png new file mode 100644 index 00000000..d7e83e75 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v47-t01-problem-dark-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v48-t05-inventory-dark-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v48-t05-inventory-dark-1440x900.png new file mode 100644 index 00000000..8afa5425 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v48-t05-inventory-dark-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v48-t08-file-dark-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v48-t08-file-dark-1440x900.png new file mode 100644 index 00000000..656cc9f6 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v48-t08-file-dark-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v49-home-attention-dark-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v49-home-attention-dark-1440x900.png new file mode 100644 index 00000000..7f8a43f2 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v49-home-attention-dark-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v50-home-overflow-dark-1440x900.png b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v50-home-overflow-dark-1440x900.png new file mode 100644 index 00000000..30ceb1b1 Binary files /dev/null and b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/fe-reference/previews/v50-home-overflow-dark-1440x900.png differ diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/index.html b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/index.html new file mode 100644 index 00000000..84fef1e4 --- /dev/null +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/index.html @@ -0,0 +1,15 @@ + + + + + + + + KBX 업무 플랫폼 + + + +
+ + + diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/package.json b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/package.json new file mode 100644 index 00000000..33679df9 --- /dev/null +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/package.json @@ -0,0 +1,35 @@ +{ + "name": "@kbx/web", + "version": "0.45.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "dev:demo": "vite --mode demo --host 127.0.0.1", + "build": "vue-tsc --noEmit && vite build", + "build:demo": "vue-tsc --noEmit && vite build --mode demo", + "typecheck": "vue-tsc --noEmit", + "preview": "vite preview --host 127.0.0.1" + }, + "dependencies": { + "@kbx/contracts": "workspace:*", + "@kbx/ui": "workspace:*", + "@microsoft/signalr": "^10.0.0", + "@primeuix/themes": "^1.2.3", + "@tanstack/vue-query": "^5.85.5", + "ag-grid-community": "^34.1.2", + "ag-grid-vue3": "^34.1.2", + "axios": "^1.11.0", + "pinia": "^3.0.3", + "primevue": "^4.3.7", + "vue": "^3.5.18", + "vue-router": "^4.5.1", + "zod": "^4.0.17" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^7.0.0", + "typescript": "^5.9.2", + "vite": "^8.0.0", + "vue-tsc": "^3.0.5" + } +} diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/App.vue b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/App.vue new file mode 100644 index 00000000..8b2239e1 --- /dev/null +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/App.vue @@ -0,0 +1,18 @@ + + + diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/app/installKbx.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/app/installKbx.ts similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/app/installKbx.ts rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/app/installKbx.ts diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/experiments/createExperimentRolloutConnection.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/experiments/createExperimentRolloutConnection.ts similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/experiments/createExperimentRolloutConnection.ts rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/experiments/createExperimentRolloutConnection.ts diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/experiments/kbxExperimentClient.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/experiments/kbxExperimentClient.ts similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/experiments/kbxExperimentClient.ts rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/experiments/kbxExperimentClient.ts diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/help/helpRegistry.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/help/helpRegistry.ts similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/help/helpRegistry.ts rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/help/helpRegistry.ts diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/http/demo/installFrontendDemoApi.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/http/demo/installFrontendDemoApi.ts new file mode 100644 index 00000000..2f462f64 --- /dev/null +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/http/demo/installFrontendDemoApi.ts @@ -0,0 +1,217 @@ +import type { AxiosAdapter, AxiosRequestConfig, AxiosResponse } from 'axios' +import { kbxHttp } from '../http/kbxHttpClient' + +const now = () => new Date().toISOString() +const clone = (value: T): T => structuredClone(value) +const uid = () => crypto.randomUUID() + +const customers = [ + { id:'11111111-1111-4111-8111-111111111111', code:'10001', displayName:'대한상사', secondaryText:'서울 · 정상', status:'active', metadata:{ businessNumber:'123-45-67890', representative:'김대표' } }, + { id:'22222222-2222-4222-8222-222222222222', code:'10002', displayName:'한빛유통', secondaryText:'경기 · 정상', status:'active', metadata:{ businessNumber:'234-56-78901', representative:'이대표' } }, + { id:'33333333-3333-4333-8333-333333333333', code:'10003', displayName:'새봄물류', secondaryText:'인천 · 정상', status:'active', metadata:{ businessNumber:'345-67-89012', representative:'박대표' } }, +] +const warehouses = [ + { id:'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', code:'WH01', displayName:'서울물류센터', secondaryText:'수도권', status:'active' }, + { id:'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', code:'WH02', displayName:'인천물류센터', secondaryText:'수도권', status:'active' }, + { id:'cccccccc-cccc-4ccc-8ccc-cccccccccccc', code:'WH03', displayName:'부산물류센터', secondaryText:'영남권', status:'active' }, +] + +const items = [ + { id:'10000000-0000-4000-8000-000000000001', code:'ABC001', name:'운동화 BLACK 270', displayName:'운동화 BLACK 270', categoryName:'신발', specification:'BLACK / 270', unit:'EA', barcode:'8801234567001', defaultWarehouseId:warehouses[0].id, defaultWarehouseName:warehouses[0].displayName, lotManaged:false, expiryManaged:false, active:true, version:3 }, + { id:'10000000-0000-4000-8000-000000000002', code:'ABC002', name:'운동화 WHITE 265', displayName:'운동화 WHITE 265', categoryName:'신발', specification:'WHITE / 265', unit:'EA', barcode:'8801234567002', defaultWarehouseId:warehouses[0].id, defaultWarehouseName:warehouses[0].displayName, lotManaged:false, expiryManaged:false, active:true, version:2 }, + { id:'10000000-0000-4000-8000-000000000003', code:'DEF001', name:'기능성 양말', displayName:'기능성 양말', categoryName:'의류', specification:'FREE / BLACK', unit:'EA', barcode:'8801234567003', defaultWarehouseId:warehouses[1].id, defaultWarehouseName:warehouses[1].displayName, lotManaged:false, expiryManaged:false, active:true, version:5 }, + { id:'10000000-0000-4000-8000-000000000004', code:'FOOD01', name:'에너지바 40g', displayName:'에너지바 40g', categoryName:'식품', specification:'40g', unit:'EA', barcode:'8801234567004', defaultWarehouseId:warehouses[1].id, defaultWarehouseName:warehouses[1].displayName, lotManaged:true, expiryManaged:true, active:true, version:1 }, +] + +const orderDetails = new Map([ + ['20000000-0000-4000-8000-000000000001', { + orderId:'20000000-0000-4000-8000-000000000001', orderNo:'ORD-260811-0001', version:4, status:'출고대기', orderDate:'2026-08-11', customerId:customers[0].id, warehouseId:warehouses[0].id, + receiverName:'홍길동', phone:'010-1234-5678', postalCode:'06236', address1:'서울특별시 강남구 테헤란로 1', address2:'101호', + lines:[{id:'line-1',clientId:'line-1',itemId:items[0].id,itemCode:'ABC001',itemName:items[0].name,orderQty:2,unitPrice:89000,amount:178000,remark:''}], + }], + ['20000000-0000-4000-8000-000000000002', { + orderId:'20000000-0000-4000-8000-000000000002', orderNo:'ORD-260811-0002', version:2, status:'신규', orderDate:'2026-08-11', customerId:customers[1].id, warehouseId:warehouses[0].id, + receiverName:'김하나', phone:'010-2345-6789', postalCode:'04524', address1:'서울특별시 중구 세종대로 10', address2:'', + lines:[{id:'line-2',clientId:'line-2',itemId:items[2].id,itemCode:'DEF001',itemName:items[2].name,orderQty:5,unitPrice:12000,amount:60000,remark:'선물포장'}], + }], + ['20000000-0000-4000-8000-000000000003', { + orderId:'20000000-0000-4000-8000-000000000003', orderNo:'ORD-260811-0003', version:6, status:'출고대기', orderDate:'2026-08-11', customerId:customers[2].id, warehouseId:warehouses[1].id, + receiverName:'박현장', phone:'010-3456-7890', postalCode:'22382', address1:'인천광역시 중구 공항동로 1', address2:'물류동', + lines:[{id:'line-3',clientId:'line-3',itemId:items[1].id,itemCode:'ABC002',itemName:items[1].name,orderQty:1,unitPrice:92000,amount:92000,remark:''}], + }], +]) + +let orderRows = [ + { id:'20000000-0000-4000-8000-000000000001', orderNo:'ORD-260811-0001', channelName:'쿠팡', orderedAt:'2026-08-11T09:15:00+09:00', customerName:'홍길동', itemSummary:'운동화 BLACK 270', totalQty:2, amount:178000, allocationStatus:'부족', shipmentStatus:'출고대기', exceptionCount:1, status:'READY' }, + { id:'20000000-0000-4000-8000-000000000002', orderNo:'ORD-260811-0002', channelName:'스마트스토어', orderedAt:'2026-08-11T09:38:00+09:00', customerName:'김하나', itemSummary:'기능성 양말', totalQty:5, amount:60000, allocationStatus:'정상', shipmentStatus:'신규', exceptionCount:0, status:'NEW' }, + { id:'20000000-0000-4000-8000-000000000003', orderNo:'ORD-260811-0003', channelName:'자사몰', orderedAt:'2026-08-11T10:02:00+09:00', customerName:'박현장', itemSummary:'운동화 WHITE 265', totalQty:1, amount:92000, allocationStatus:'정상', shipmentStatus:'출고대기', exceptionCount:0, status:'READY' }, + { id:'20000000-0000-4000-8000-000000000004', orderNo:'ORD-260811-0004', channelName:'쿠팡', orderedAt:'2026-08-11T10:17:00+09:00', customerName:'이소라', itemSummary:'에너지바 40g', totalQty:12, amount:36000, allocationStatus:'정상', shipmentStatus:'보류', exceptionCount:1, status:'HOLD' }, + { id:'20000000-0000-4000-8000-000000000005', orderNo:'ORD-260811-0005', channelName:'스마트스토어', orderedAt:'2026-08-11T11:05:00+09:00', customerName:'정우진', itemSummary:'기능성 양말 외 1종', totalQty:4, amount:84000, allocationStatus:'정상', shipmentStatus:'출고완료', exceptionCount:0, status:'SHIPPED' }, +] + +const auditByEntity = new Map() +function audit(id:string, action:string, changes:any[] = []) { + const list = auditByEntity.get(id) ?? [] + list.unshift({ id:uid(), occurredAt:now(), actor:{type:'user',displayName:'QA 사용자'}, action, changes }) + auditByEntity.set(id, list) +} +for (const id of orderDetails.keys()) audit(id, '주문 조회용 데모 데이터 생성') +for (const item of items) audit(item.id, '품목 기준정보 생성') + +let notifications:any[] = [ + { id:'noti-1', severity:'warning', title:'재고부족 주문 7건', message:'출고 전 확인이 필요합니다.', createdAt:now(), screenId:'OMS-ORD-001', action:{id:'open',label:'주문 보기',route:'/oms/orders?exceptionOnly=true'} }, + { id:'noti-2', severity:'error', title:'OMS ↔ WMS 대사 불일치 3건', message:'원인 확인 후 재처리하세요.', createdAt:now(), screenId:'COMMON-REC-001', action:{id:'open',label:'대사 보기',route:'/operations/reconcile'} }, +] +let operations:any[] = [ + { id:'job-1', type:'excel-import', title:'주문 Excel Import', sourceScreenId:'OMS-ORD-003', status:'running', requestedAt:now(), startedAt:now(), processed:13421, total:17894, succeeded:13218, failed:203, resultMessage:'75% 처리 중' }, +] + +const workItems = [ + { id:'op-1', module:'OMS', category:'재고', code:'STOCK_SHORTAGE', title:'주문 재고 부족', detail:'ABC001 가용재고 부족', severity:'warning', status:'open', owner:'', occurredAt:now(), screenId:'OMS-ORD-001', entityId:orderRows[0].id }, + { id:'op-2', module:'WMS', category:'연계', code:'SHIPMENT_SYNC', title:'출고 연계 지연', detail:'WMS 전송 재시도 대기', severity:'error', status:'open', owner:'', occurredAt:now(), screenId:'COMMON-OPS-001', entityId:'sync-2' }, +] +const reconcileItems = [ + { id:'rec-1', reconcileType:'OMS_WMS_SHIPMENT', referenceNo:'ORD-260811-0001', expected:2, actual:1, difference:-1, reason:'피킹부족', status:'mismatch', resolutionStatus:'미처리' }, + { id:'rec-2', reconcileType:'OMS_WMS_SHIPMENT', referenceNo:'ORD-260811-0003', expected:1, actual:1, difference:0, reason:'', status:'matched', resolutionStatus:'정상' }, +] + +const pickingTask:any = { + taskId:'30000000-0000-4000-8000-000000000001', taskNo:'PICK-260811-001', status:'ready', stage:'ready', version:1, + currentLine:{ lineId:'pick-line-1', lineNo:23, locationCode:'A-03-02', itemId:items[0].id, itemCode:items[0].code, itemName:items[0].name, itemOption:items[0].specification, barcode:items[0].barcode, requiredQty:6, pickedQty:4, remainingQty:2 }, + completedLines:22, totalLines:138, completedQty:71, totalQty:427, message:'작업을 시작하세요.', +} + +const imports = new Map() + +function ok(config: AxiosRequestConfig, data: unknown, status = 200): AxiosResponse { + return { data, status, statusText: status === 204 ? 'No Content' : 'OK', headers:{}, config:config as any, request:{} } +} +function text(v:unknown){ return String(v ?? '').trim().toLowerCase() } +function body(config:AxiosRequestConfig):any { + if (typeof config.data === 'string') { try { return JSON.parse(config.data) } catch { return config.data } } + return config.data ?? {} +} +function params(config:AxiosRequestConfig):Record{ return (config.params ?? {}) as Record } +function path(config:AxiosRequestConfig){ return String(config.url ?? '').split('?')[0] } +function findLookup(list:any[], codeOrId:string){ return list.find(x=>x.id===codeOrId||x.code.toLowerCase()===codeOrId.toLowerCase()) ?? null } +function lookupResponse(list:any[], q:any){ + const keyword=text(q.query) + const filtered=keyword?list.filter(x=>`${x.code} ${x.displayName} ${x.secondaryText??''}`.toLowerCase().includes(keyword)):list + return { items:clone(filtered.slice(0,Number(q.pageSize??20))), totalCount:filtered.length } +} + +const demoAdapter:AxiosAdapter = async config => { + await new Promise(resolve => setTimeout(resolve, 90)) + const method=String(config.method??'get').toUpperCase(); const url=path(config); const q=params(config); const data=body(config) + + if(method==='GET'&&url==='/api/kbx/runtime/notice') return ok(config,null) + if(method==='GET'&&url==='/api/kbx/runtime/operations') return ok(config,clone(operations)) + if(method==='GET'&&url==='/api/kbx/runtime/notifications') return ok(config,clone(notifications)) + if(method==='POST'&&/^\/api\/kbx\/runtime\/notifications\/[^/]+\/read$/.test(url)){const id=url.split('/').at(-2)!;const n=notifications.find(x=>x.id===id);if(n)n.readAt=now();return ok(config,undefined,204)} + if(method==='POST'&&url==='/api/kbx/ux/events') return ok(config,undefined,204) + if(method==='POST'&&url==='/api/common/suggestions') return ok(config,{id:uid(),receivedAt:now()}) + if(method==='POST'&&url==='/api/common/ai/ask') return ok(config,{answer:'현재 화면의 예외와 선택 상태를 기준으로 확인할 항목을 정리했습니다.',actions:[]}) + + if(method==='GET'&&url==='/api/oms/orders'){ + let rows=[...orderRows]; const keyword=text(q.keyword) + if(q.status) rows=rows.filter(x=>x.status===q.status) + if(String(q.exceptionOnly)==='true'||q.exceptionOnly===true) rows=rows.filter(x=>x.exceptionCount>0) + if(keyword) rows=rows.filter(x=>`${x.orderNo} ${x.customerName} ${x.itemSummary} ${x.channelName}`.toLowerCase().includes(keyword)) + const counters={all:orderRows.length,new:orderRows.filter(x=>x.status==='NEW').length,readyToShip:orderRows.filter(x=>x.status==='READY').length,exceptions:orderRows.filter(x=>x.exceptionCount>0).length} + const pageSize=Number(q.pageSize??100); const page=Math.max(Number(q.page??1),1); const slice=rows.slice((page-1)*pageSize,page*pageSize) + return ok(config,{items:clone(slice),totalCount:rows.length,totalQty:rows.reduce((s,x)=>s+x.totalQty,0),totalAmount:rows.reduce((s,x)=>s+x.amount,0),counters}) + } + if(method==='GET'&&/^\/api\/oms\/orders\/[^/]+$/.test(url)){const id=url.split('/').at(-1)!;const existing=orderDetails.get(id);if(existing)return ok(config,clone(existing));const row=orderRows.find(x=>x.id===id);if(row)return ok(config,{orderId:id,orderNo:row.orderNo,version:1,status:row.shipmentStatus,orderDate:'2026-08-11',customerId:customers[0].id,warehouseId:warehouses[0].id,receiverName:row.customerName,phone:'010-0000-0000',address1:'서울특별시',lines:[]});return ok(config,null)} + if(method==='GET'&&/^\/api\/oms\/orders\/[^/]+\/audit$/.test(url)){const id=url.split('/').at(-2)!;return ok(config,clone(auditByEntity.get(id)??[]))} + if(method==='POST'&&url==='/api/oms/orders/ship'){ + const ids:Array=data.mode==='ids'?(data.ids??[]):orderRows.filter(row=>row.status==='READY').map(row=>row.id) + let accepted=0; for(const id of ids){const row=orderRows.find(x=>x.id===id);if(row&&row.status==='READY'){row.shipmentStatus='출고지시';accepted++}} + return ok(config,{requested:ids.length,accepted,rejected:ids.length-accepted}) + } + if(method==='POST'&&url==='/api/oms/orders/register'){ + const id=uid();const orderNo=`ORD-260811-${String(orderRows.length+1).padStart(4,'0')}`;const detail={...data,orderId:id,orderNo,version:1,status:'작성',lines:(data.lines??[]).map((line:any,index:number)=>{const item=items.find(x=>x.id===line.itemId);return{id:`${id}-${index+1}`,clientId:line.clientId,itemId:line.itemId,itemCode:item?.code??'',itemName:item?.name??'',orderQty:Number(line.orderQty),unitPrice:Number(line.unitPrice),amount:Number(line.orderQty)*Number(line.unitPrice),remark:line.remark??''}})};orderDetails.set(id,detail);orderRows.unshift({id,orderNo,channelName:'직접주문',orderedAt:now(),customerName:data.receiverName,itemSummary:detail.lines[0]?.itemName??'상품 없음',totalQty:detail.lines.reduce((s:number,x:any)=>s+x.orderQty,0),amount:detail.lines.reduce((s:number,x:any)=>s+x.amount,0),allocationStatus:'미확인',shipmentStatus:'작성',exceptionCount:0,status:'NEW'});audit(id,'주문 등록');return ok(config,{orderId:id,orderNo,version:1,status:'작성'}) + } + if(method==='PUT'&&/^\/api\/oms\/orders\/[^/]+$/.test(url)){const id=url.split('/').at(-1)!;const existing=orderDetails.get(id);const version=Number(existing?.version??0)+1;const next={...(existing??{}),...data,orderId:id,orderNo:existing?.orderNo??`ORD-${id.slice(0,8)}`,version,status:existing?.status??'작성',lines:(data.lines??existing?.lines??[]).map((line:any,index:number)=>{const item=items.find(x=>x.id===line.itemId);return{...line,id:line.id??`${id}-${index+1}`,itemCode:item?.code??line.itemCode??'',itemName:item?.name??line.itemName??'',amount:Number(line.orderQty)*Number(line.unitPrice)}})};orderDetails.set(id,next);audit(id,'주문 수정');return ok(config,{orderId:id,orderNo:next.orderNo,version,status:next.status})} + if(method==='POST'&&/^\/api\/oms\/orders\/[^/]+\/confirm$/.test(url)){const id=url.split('/').at(-2)!;const existing=orderDetails.get(id);if(existing){existing.version=Number(existing.version)+1;existing.status='확정';audit(id,'주문 확정');return ok(config,{orderId:id,orderNo:existing.orderNo,version:existing.version,status:'확정'})}return ok(config,{orderId:id,orderNo:'',version:1,status:'확정'})} + + if(url.startsWith('/api/lookups/customers')){ + if(method==='GET'&&url==='/api/lookups/customers')return ok(config,lookupResponse(customers,q)) + const key=decodeURIComponent(url.split('/').at(-1)!);return ok(config,clone(findLookup(customers,key))) + } + if(url.startsWith('/api/lookups/warehouses')){ + if(method==='GET'&&url==='/api/lookups/warehouses')return ok(config,lookupResponse(warehouses,q)) + const key=decodeURIComponent(url.split('/').at(-1)!);return ok(config,clone(findLookup(warehouses,key))) + } + if(url.startsWith('/api/lookups/items')){ + const lookupItems=items.map(x=>({id:x.id,code:x.code,displayName:x.name,secondaryText:`${x.specification} · ${x.unit}`,status:x.active?'active':'disabled',metadata:{availableQty:x.code==='ABC001'?8:120}})) + if(method==='GET'&&url==='/api/lookups/items')return ok(config,lookupResponse(lookupItems,q)) + const key=decodeURIComponent(url.split('/').at(-1)!);return ok(config,clone(findLookup(lookupItems,key))) + } + + if(method==='GET'&&url==='/api/erp/items'){const keyword=text(q.keyword);const found=keyword?items.filter(x=>`${x.code} ${x.name}`.toLowerCase().includes(keyword)):items;return ok(config,{items:clone(found),totalCount:found.length})} + if(method==='GET'&&/^\/api\/erp\/items\/[^/]+$/.test(url)){const id=url.split('/').at(-1)!;return ok(config,clone(items.find(x=>x.id===id)??null))} + if(method==='GET'&&/^\/api\/erp\/items\/[^/]+\/audit$/.test(url)){const id=url.split('/').at(-2)!;return ok(config,clone(auditByEntity.get(id)??[]))} + if(method==='POST'&&url==='/api/erp/items'){const id=uid();const created={id,version:1,active:true,defaultWarehouseName:warehouses.find(x=>x.id===data.defaultWarehouseId)?.displayName??'',...data};items.push(created);audit(id,'품목 등록');return ok(config,{id,version:1,status:'사용'})} + if(method==='PUT'&&/^\/api\/erp\/items\/[^/]+$/.test(url)){const id=url.split('/').at(-1)!;const target=items.find(x=>x.id===id);if(target){Object.assign(target,data,{version:Number(target.version)+1,defaultWarehouseName:warehouses.find(x=>x.id===data.defaultWarehouseId)?.displayName??target.defaultWarehouseName});audit(id,'품목 수정');return ok(config,{id,version:target.version,status:target.active?'사용':'사용중지'})}return ok(config,{id,version:1,status:'사용'})} + if(method==='POST'&&/^\/api\/erp\/items\/[^/]+\/deactivate$/.test(url)){const id=url.split('/').at(-2)!;const target=items.find(x=>x.id===id);if(target){target.active=false;target.version=Number(target.version)+1;audit(id,'품목 사용중지');return ok(config,{id,version:target.version,status:'사용중지'})}return ok(config,{id,version:1,status:'사용중지'})} + + if(method==='GET'&&url==='/api/erp/inventory'){ + const inv=items.map((x,index)=>({itemId:x.id,itemCode:x.code,itemName:x.name,specification:x.specification,unit:x.unit,totalQty:[1280,721,312,96][index]??0,availableQty:[830,650,290,80][index]??0,allocatedQty:[430,61,12,8][index]??0,holdQty:[20,10,10,8][index]??0})) + const keyword=text(q.keyword);const found=keyword?inv.filter(x=>`${x.itemCode} ${x.itemName}`.toLowerCase().includes(keyword)):inv;return ok(config,{items:found,totalCount:found.length}) + } + if(method==='GET'&&/^\/api\/erp\/inventory\/[^/]+\/locations$/.test(url)){return ok(config,{items:[{warehouseCode:'WH01',warehouseName:'서울물류센터',locationCode:'A-01-01',onHandQty:300,availableQty:260},{warehouseCode:'WH01',warehouseName:'서울물류센터',locationCode:'A-01-02',onHandQty:530,availableQty:500}]})} + if(method==='GET'&&/^\/api\/erp\/inventory\/[^/]+\/history$/.test(url)){return ok(config,{items:[{occurredAt:'2026-08-11T10:21:00+09:00',businessType:'출고',inQty:0,outQty:2,balanceQty:830,referenceNo:'ORD-260811-0001'},{occurredAt:'2026-08-11T08:40:00+09:00',businessType:'입고',inQty:100,outQty:0,balanceQty:832,referenceNo:'GR-260811-001'}]})} + if(method==='POST'&&url==='/api/erp/item-prices/bulk'){return ok(config,{requested:data.rows?.length??0,saved:data.rows?.length??0,created:data.rows?.length??0,updated:0})} + + if(method==='GET'&&url==='/api/operations/work-items'){return ok(config,{items:clone(workItems),totalCount:workItems.length,counters:{all:workItems.length,open:workItems.filter(x=>x.status==='open').length,critical:workItems.filter(x=>x.severity==='error').length}})} + if(method==='POST'&&url==='/api/operations/work-items/claim'){for(const id of data.ids??[]){const item=workItems.find(x=>x.id===id);if(item)item.owner='QA 사용자'}return ok(config,{updated:(data.ids??[]).length})} + if(method==='POST'&&url==='/api/operations/work-items/resolve'){for(const id of data.ids??[]){const item=workItems.find(x=>x.id===id);if(item)item.status='resolved'}return ok(config,{updated:(data.ids??[]).length})} + if(method==='POST'&&/^\/api\/operations\/work-items\/[^/]+\/retry$/.test(url))return ok(config,{accepted:true}) + if(method==='GET'&&url==='/api/reconcile/items'){return ok(config,{items:clone(reconcileItems),totalCount:reconcileItems.length,mismatchCount:reconcileItems.filter(x=>x.difference!==0).length})} + if(method==='POST'&&url==='/api/reconcile/items/create-exceptions')return ok(config,{created:(data.ids??[]).length}) + + if(method==='GET'&&url==='/api/oms/claims')return ok(config,{items:[{id:'claim-1',claimNo:'CLM-260811-01',orderNo:'ORD-260811-0001',type:'RETURN',reason:'사이즈 교환',status:'접수',customerName:'홍길동',requestedAt:now(),version:1}],totalCount:1}) + if(method==='POST'&&/^\/api\/oms\/claims\/[^/]+\/(approve|hold|start|complete)$/.test(url)){const transition=url.split('/').at(-1)!;return ok(config,{id:url.split('/').at(-2),status:{approve:'승인',hold:'보류',start:'처리중',complete:'완료'}[transition],version:2})} + + if(method==='GET'&&/^\/api\/wms\/picking\/tasks\/[^/]+$/.test(url))return ok(config,clone(pickingTask)) + if(method==='POST'&&/^\/api\/wms\/picking\/tasks\/[^/]+\/start$/.test(url)){pickingTask.status='picking';pickingTask.stage='await-location';pickingTask.message='A-03-02 위치를 스캔하세요.';pickingTask.version++;return ok(config,clone(pickingTask))} + if(method==='POST'&&/^\/api\/wms\/picking\/tasks\/[^/]+\/scan$/.test(url)){ + const raw=String(data.normalizedValue??data.rawValue??data.barcode??'') + if(pickingTask.stage==='await-location'){ + const accepted=raw===pickingTask.currentLine?.locationCode + if(accepted){pickingTask.stage='await-item';pickingTask.message='상품을 스캔하세요.';pickingTask.version++} + return ok(config,{accepted,duplicate:false,task:clone(pickingTask),feedback:accepted?'success':'error',message:accepted?'위치를 확인했습니다.':`잘못된 위치입니다. ${pickingTask.currentLine?.locationCode}로 이동하세요.`}) + } + const expected=pickingTask.currentLine?.barcode + if(raw===expected&&pickingTask.currentLine){pickingTask.currentLine.pickedQty++;pickingTask.currentLine.remainingQty=Math.max(pickingTask.currentLine.requiredQty-pickingTask.currentLine.pickedQty,0);pickingTask.completedQty++;pickingTask.version++;pickingTask.message=pickingTask.currentLine.remainingQty===0?'현재 상품 피킹을 완료했습니다.':'수량을 반영했습니다.';return ok(config,{accepted:true,duplicate:false,task:clone(pickingTask),feedback:'success',message:pickingTask.message})} + return ok(config,{accepted:false,duplicate:false,task:clone(pickingTask),feedback:'error',message:'다른 상품입니다.'}) + } + if(method==='POST'&&/^\/api\/wms\/picking\/tasks\/[^/]+\/quantity$/.test(url)){if(pickingTask.currentLine){const before=pickingTask.currentLine.pickedQty;pickingTask.currentLine.pickedQty=Number(data.pickedQty??0);pickingTask.currentLine.remainingQty=Math.max(pickingTask.currentLine.requiredQty-pickingTask.currentLine.pickedQty,0);pickingTask.completedQty+=pickingTask.currentLine.pickedQty-before;pickingTask.version++;pickingTask.message='피킹 수량을 변경했습니다.'}return ok(config,{accepted:true,duplicate:false,task:clone(pickingTask),feedback:'success',message:pickingTask.message})} + if(method==='POST'&&/^\/api\/wms\/picking\/tasks\/[^/]+\/exceptions$/.test(url)){pickingTask.status='exception';pickingTask.version++;return ok(config,clone(pickingTask))} + + if(method==='POST'&&url==='/api/imports/sessions'){ + const id=uid();const session={id,importType:'oms.orders.v1',screenId:'OMS-ORD-003',fileName:'demo-orders.xlsx',status:'mapping-required',totalRows:128,validRows:0,invalidRows:0,warningRows:0,createdRows:0,updatedRows:0,progressPercent:10,mapping:[],sourceColumns:['주문번호','상품코드','수량','구매자'],createdAt:now()};imports.set(id,session);operations=[{id:`import:${id}`,type:'excel-import',title:'주문 Excel Import',sourceScreenId:'OMS-ORD-003',status:'running',requestedAt:now(),startedAt:now(),processed:0,total:128,succeeded:0,failed:0},...operations];return ok(config,clone(session)) + } + if(method==='GET'&&/^\/api\/imports\/sessions\/[^/]+$/.test(url)){const id=url.split('/').at(-1)!;return ok(config,clone(imports.get(id)??null))} + if(method==='PUT'&&/^\/api\/imports\/sessions\/[^/]+\/mapping$/.test(url)){const id=url.split('/').at(-2)!;const s=imports.get(id);if(s){s.mapping=data.mappings??[];s.status='uploaded';s.progressPercent=20}return ok(config,clone(s))} + if(method==='POST'&&/^\/api\/imports\/sessions\/[^/]+\/validate$/.test(url)){const id=url.split('/').at(-2)!;const s=imports.get(id);if(s){Object.assign(s,{status:'validated',validRows:125,invalidRows:3,warningRows:2,progressPercent:60,errors:[{rowNumber:8,field:'itemCode',sourceColumn:'상품코드',code:'ITEM_NOT_FOUND',message:'존재하지 않는 품목코드입니다.',severity:'error'}]})}return ok(config,clone(s))} + if(method==='POST'&&/^\/api\/imports\/sessions\/[^/]+\/commit$/.test(url)){const id=url.split('/').at(-2)!;const s=imports.get(id);if(s){Object.assign(s,{status:'partially-completed',createdRows:120,updatedRows:5,progressPercent:100,completedAt:now()});const op=operations.find(x=>x.id===`import:${id}`);if(op)Object.assign(op,{status:'partially-completed',processed:128,succeeded:125,failed:3,completedAt:now(),resultMessage:'125건 반영 / 3건 오류'})}return ok(config,clone(s))} + if(method==='POST'&&/^\/api\/imports\/sessions\/[^/]+\/saved-mappings$/.test(url))return ok(config,undefined,204) + if(method==='GET'&&/^\/api\/imports\/oms.orders.v1\/template$/.test(url))return ok(config,new Blob(['KBX demo workbook'],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'})) + if(method==='GET'&&/^\/api\/imports\/sessions\/[^/]+\/errors.xlsx$/.test(url))return ok(config,new Blob(['KBX demo errors'],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'})) + + if(method==='GET'&&url==='/api/kbx/external-data/status')return ok(config,{items:[{datasetId:'krx.daily',name:'KRX 일별 시세',provider:'KRX OPEN API',status:'healthy',lastSucceededAt:now(),freshness:{observedAt:now(),source:'KRX'}},{datasetId:'dart.corp',name:'DART 기업정보',provider:'OPENDART',status:'healthy',lastSucceededAt:now(),freshness:{observedAt:now(),source:'DART'}}]}) + if(method==='POST'&&/^\/api\/kbx\/external-data\/[^/]+\/refresh$/.test(url))return ok(config,{accepted:true}) + if(method==='GET'&&url==='/api/kbx/ux/metrics')return ok(config,{summary:{taskCompletionRate:0.96,manualInterventionRate:0.0318,validationFailureRate:0.012},screens:[]}) + if(method==='GET'&&url==='/api/kbx/experiments')return ok(config,{items:[]}) + if(method==='GET'&&url==='/api/kbx/experiments/assignments')return ok(config,{assignments:[]}) + + // Frontend demo mode never fabricates a successful write for an unknown operation. + // Returning a visible unsupported response makes missing FE/API coverage explicit during QA. + console.warn(`[KBX demo API] unsupported ${method} ${url}`) + return ok(config,{ type:'system', code:'DEMO_OPERATION_UNSUPPORTED', title:'이 화면의 Demo API가 아직 준비되지 않았습니다.', detail:`${method} ${url}` },501) +} + +export function installFrontendDemoApi() { + kbxHttp.defaults.adapter = demoAdapter +} diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/http/generated/kbxApiClient.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/http/generated/kbxApiClient.ts similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/http/generated/kbxApiClient.ts rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/http/generated/kbxApiClient.ts diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/http/kbxHttpClient.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/http/kbxHttpClient.ts similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/http/kbxHttpClient.ts rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/http/kbxHttpClient.ts diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/http/useKbxProblemHandler.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/http/useKbxProblemHandler.ts similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/http/useKbxProblemHandler.ts rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/http/useKbxProblemHandler.ts diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/imports/createImportProgressConnection.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/imports/createImportProgressConnection.ts similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/imports/createImportProgressConnection.ts rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/imports/createImportProgressConnection.ts diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/lookups/lookupRegistry.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/lookups/lookupRegistry.ts similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/lookups/lookupRegistry.ts rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/lookups/lookupRegistry.ts diff --git a/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/main.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/main.ts new file mode 100644 index 00000000..4c0bdc12 --- /dev/null +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/main.ts @@ -0,0 +1,33 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query' +import { createRouter, createWebHistory } from 'vue-router' +import PrimeVue from 'primevue/config' +import Aura from '@primeuix/themes/aura' +import App from './App.vue' +import { appRoutes } from './router/appRoutes' +import { installKbx } from './app/installKbx' +import { installFrontendDemoApi } from './http/demo/installFrontendDemoApi' +import './styles/app.css' + +if (import.meta.env.VITE_KBX_DEMO_MODE === 'true') installFrontendDemoApi() + +const router = createRouter({ + history: createWebHistory(), + routes: appRoutes, + scrollBehavior: () => ({ top: 0 }), +}) +const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: 1, refetchOnWindowFocus: false }, + mutations: { retry: 0 }, + }, +}) + +const app = createApp(App) +app.use(createPinia()) +app.use(router) +app.use(VueQueryPlugin, { queryClient }) +app.use(PrimeVue, { theme: { preset: Aura, options: { darkModeSelector: '[data-kbx-theme=\"dark\"]' } }, ripple: false }) +installKbx(app) +app.mount('#app') diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/common/design-system/ComponentCatalogPage.vue b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/design-system/ComponentCatalogPage.vue similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/modules/common/design-system/ComponentCatalogPage.vue rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/design-system/ComponentCatalogPage.vue diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/common/design-system/design-system.definition.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/design-system/design-system.definition.ts similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/modules/common/design-system/design-system.definition.ts rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/design-system/design-system.definition.ts diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/common/experiments/ExperimentsPage.vue b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/experiments/ExperimentsPage.vue similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/modules/common/experiments/ExperimentsPage.vue rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/experiments/ExperimentsPage.vue diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/common/experiments/experimentApi.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/experiments/experimentApi.ts similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/modules/common/experiments/experimentApi.ts rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/experiments/experimentApi.ts diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/common/experiments/experiments.definition.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/experiments/experiments.definition.ts similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/modules/common/experiments/experiments.definition.ts rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/experiments/experiments.definition.ts diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/common/external-data/ExternalDataStatusPage.vue b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/external-data/ExternalDataStatusPage.vue similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/modules/common/external-data/ExternalDataStatusPage.vue rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/external-data/ExternalDataStatusPage.vue diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/common/external-data/external-data.definition.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/external-data/external-data.definition.ts similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/modules/common/external-data/external-data.definition.ts rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/external-data/external-data.definition.ts diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/common/external-data/externalDataApi.ts b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/external-data/externalDataApi.ts similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/modules/common/external-data/externalDataApi.ts rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/external-data/externalDataApi.ts diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/common/home/HomeRoutePage.vue b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/home/HomeRoutePage.vue similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/modules/common/home/HomeRoutePage.vue rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/home/HomeRoutePage.vue diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/common/not-found/NotFoundRoutePage.vue b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/not-found/NotFoundRoutePage.vue similarity index 100% rename from docs/Design/kbx-foundation-v36/apps/web/src/modules/common/not-found/NotFoundRoutePage.vue rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/not-found/NotFoundRoutePage.vue diff --git a/docs/Design/kbx-foundation-v36/apps/web/src/modules/common/operations/OperationsQueuePage.vue b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/operations/OperationsQueuePage.vue similarity index 70% rename from docs/Design/kbx-foundation-v36/apps/web/src/modules/common/operations/OperationsQueuePage.vue rename to docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/operations/OperationsQueuePage.vue index 0f24e576..a3c4fa07 100644 --- a/docs/Design/kbx-foundation-v36/apps/web/src/modules/common/operations/OperationsQueuePage.vue +++ b/docs/Design/kbx-foundation-v52-fe-operational-navigation-screen-anatomy/apps/web/src/modules/common/operations/OperationsQueuePage.vue @@ -1,5 +1,6 @@