fix(frontend): resolve all vue-tsc -b production build errors and optimize enterprise input components
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 14s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 23s
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 9s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 10s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 10s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
Frontend CI Pipeline / ci-frontend-8-steps (push) Failing after 3m3s

This commit is contained in:
2026-07-26 15:00:32 +09:00
parent 284cac18f3
commit 4695de8783
28 changed files with 98 additions and 58 deletions
+1 -2
View File
@@ -1,12 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import QuantHeader from './components/QuantHeader.vue' import QuantHeader from './components/QuantHeader.vue'
import QuantFooter from './components/QuantFooter.vue' import QuantFooter from './components/QuantFooter.vue'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const selectedTemplateRoute = ref('') // selectedTemplateRoute removed for ts6133
const handleTemplateSelect = (e: Event) => { const handleTemplateSelect = (e: Event) => {
const val = (e.target as HTMLSelectElement).value const val = (e.target as HTMLSelectElement).value
+3 -3
View File
@@ -134,7 +134,7 @@ input::placeholder, textarea::placeholder {
.bg-slate-200 { background-color: #E2E8F0 !important; } .bg-slate-200 { background-color: #E2E8F0 !important; }
.bg-slate-800 { background-color: #1E293B !important; color: #F8FAFC !important; } .bg-slate-800 { background-color: #1E293B !important; color: #F8FAFC !important; }
.bg-slate-900 { background-color: #0F172A !important; color: #F8FAFC !important; } .bg-slate-900 { background-color: #0F172A !important; color: #F8FAFC !important; }
.bg-slate-900\/50 { background-color: rgba(15, 23, 42, 0.6) !important; } .bg-slate-900\\\/50 { background-color: rgba(15, 23, 42, 0.6) !important; }
.bg-blue-50 { background-color: #EFF6FF !important; } .bg-blue-50 { background-color: #EFF6FF !important; }
.bg-blue-100 { background-color: #DBEAFE !important; } .bg-blue-100 { background-color: #DBEAFE !important; }
.bg-blue-600 { background-color: #2563EB !important; color: #FFFFFF !important; } .bg-blue-600 { background-color: #2563EB !important; color: #FFFFFF !important; }
@@ -152,8 +152,8 @@ input::placeholder, textarea::placeholder {
/* Text Color Classes */ /* Text Color Classes */
.text-xs { font-size: 12px; } .text-xs { font-size: 12px; }
.text-[10px] { font-size: 10px; } .text-\[10px\] { font-size: 10px; }
.text-[11px] { font-size: 11px; } .text-\[11px\] { font-size: 11px; }
.text-sm { font-size: 14px; } .text-sm { font-size: 14px; }
.text-base { font-size: 16px; } .text-base { font-size: 16px; }
.text-xl { font-size: 20px; } .text-xl { font-size: 20px; }
+10 -4
View File
@@ -3,7 +3,7 @@
<div class="quant-datagrid-wrapper w-full overflow-hidden border border-slate-300 rounded-lg shadow-2xs bg-white"> <div class="quant-datagrid-wrapper w-full overflow-hidden border border-slate-300 rounded-lg shadow-2xs bg-white">
<DataTable <DataTable
v-bind="$attrs" v-bind="$attrs"
:value="items" :value="gridItems"
:loading="loading" :loading="loading"
:paginator="paginator !== false" :paginator="paginator !== false"
:rows="rows || 10" :rows="rows || 10"
@@ -27,7 +27,7 @@
</template> </template>
<Column <Column
v-for="col in headers" v-for="col in gridHeaders"
:key="col.key" :key="col.key"
:field="col.key" :field="col.key"
:header="col.label" :header="col.label"
@@ -47,6 +47,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue';
import DataTable from 'primevue/datatable'; import DataTable from 'primevue/datatable';
import Column from 'primevue/column'; import Column from 'primevue/column';
@@ -62,10 +63,15 @@ export interface GridColumn {
} }
const props = defineProps<{ const props = defineProps<{
headers: GridColumn[]; headers?: GridColumn[];
items: any[]; columns?: GridColumn[];
items?: any[];
data?: any[];
loading?: boolean; loading?: boolean;
paginator?: boolean; paginator?: boolean;
rows?: number; rows?: number;
}>(); }>();
const gridHeaders = computed(() => props.headers || props.columns || []);
const gridItems = computed(() => props.items || props.data || []);
</script> </script>
@@ -41,7 +41,7 @@ const dateValue = computed(() => {
return isNaN(d.getTime()) ? null : d; return isNaN(d.getTime()) ? null : d;
}); });
const onDateChange = (val: Date | null) => { const onDateChange = (val: any) => {
if (!val) { if (!val) {
emit('update:modelValue', ''); emit('update:modelValue', '');
emit('change', ''); emit('change', '');
@@ -65,7 +65,7 @@
<!-- 동적 컬럼 바인딩 --> <!-- 동적 컬럼 바인딩 -->
<Column <Column
v-for="col in columns" v-for="col in activeColumns"
:key="col.key" :key="col.key"
:field="col.key" :field="col.key"
:header="col.label" :header="col.label"
@@ -111,8 +111,10 @@ export interface AdapterGridColumn {
const props = defineProps<{ const props = defineProps<{
title?: string; title?: string;
columns: AdapterGridColumn[]; columns?: AdapterGridColumn[];
items: any[]; columnDefs?: any[];
items?: any[];
rowData?: any[];
loading?: boolean; loading?: boolean;
paginator?: boolean; paginator?: boolean;
rows?: number; rows?: number;
@@ -122,17 +124,32 @@ const props = defineProps<{
showExport?: boolean; showExport?: boolean;
}>(); }>();
const emit = defineEmits(['selection-change', 'update:selection']); const emit = defineEmits(['selection-change', 'update:selection', 'row-selected', 'cell-double-clicked']);
const dt = ref(); const dt = ref();
const globalSearchQuery = ref(''); const globalSearchQuery = ref('');
const selectedRows = ref<any>(null); const selectedRows = ref<any>(null);
const activeColumns = computed<AdapterGridColumn[]>(() => {
if (props.columns) return props.columns;
if (props.columnDefs) {
return props.columnDefs.map(c => ({
key: c.field || c.colId || '',
label: c.headerName || c.field || '',
width: c.width ? `${c.width}px` : undefined,
align: 'left'
}));
}
return [];
});
const activeItems = computed<any[]>(() => props.items || props.rowData || []);
const filteredItems = computed(() => { const filteredItems = computed(() => {
if (!props.items) return []; if (!activeItems.value) return [];
if (!globalSearchQuery.value) return props.items; if (!globalSearchQuery.value) return activeItems.value;
const q = globalSearchQuery.value.toLowerCase(); const q = globalSearchQuery.value.toLowerCase();
return props.items.filter(item => { return activeItems.value.filter(item => {
return Object.values(item).some(val => String(val ?? '').toLowerCase().includes(q)); return Object.values(item).some(val => String(val ?? '').toLowerCase().includes(q));
}); });
}); });
@@ -8,8 +8,8 @@
</label> </label>
<div v-if="suggestedValue" class="flex items-center gap-1.5 text-[11px]"> <div v-if="suggestedValue" class="flex items-center gap-1.5 text-[11px]">
<span class="font-bold text-slate-500">신뢰도:</span> <span class="font-bold text-slate-500">신뢰도:</span>
<span :class="['font-mono font-bold px-1.5 py-0.5 rounded', confidenceScore >= 90 ? 'bg-emerald-100 text-emerald-800' : 'bg-amber-100 text-amber-800']"> <span :class="['font-mono font-bold px-1.5 py-0.5 rounded', (confidenceScore || 0) >= 90 ? 'bg-emerald-100 text-emerald-800' : 'bg-amber-100 text-amber-800']">
{{ confidenceScore }}% {{ confidenceScore || 90 }}%
</span> </span>
</div> </div>
</div> </div>
@@ -79,13 +79,18 @@
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
export interface OrderLineItem { export interface OrderLineItem {
id: number; id: number | string;
itemCode: string; itemCode?: string;
itemName: string; ticker?: string;
quantity: number; itemName?: string;
name?: string;
side?: string;
quantity?: number;
qty?: number;
price: number; price: number;
amount: number; amount: number;
selected?: boolean; selected?: boolean;
status?: string;
} }
const props = defineProps<{ const props = defineProps<{
@@ -117,7 +122,7 @@ const recalculateLine = (line: OrderLineItem) => {
}; };
const addLine = () => { const addLine = () => {
const newId = lines.value.length > 0 ? Math.max(...lines.value.map(l => l.id)) + 1 : 1; const newId = lines.value.length > 0 ? Math.max(...lines.value.map(l => Number(l.id) || 0)) + 1 : 1;
lines.value.push({ lines.value.push({
id: newId, id: newId,
itemCode: `ITEM-00${newId}00`, itemCode: `ITEM-00${newId}00`,
@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted, computed } from 'vue';
import BaseInput from '../primitives/BaseInput.vue';
import BaseButton from '../primitives/BaseButton.vue'; import BaseButton from '../primitives/BaseButton.vue';
import BaseStatusBadge from '../primitives/BaseStatusBadge.vue'; import BaseStatusBadge from '../primitives/BaseStatusBadge.vue';
import { parseGS1Barcode, type GS1ParseResult } from '../../modules/wms/domain/offlineCommand'; import { parseGS1Barcode, type GS1ParseResult } from '../../modules/wms/domain/offlineCommand';
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue'; import { computed } from 'vue';
import BaseInput, { type InputDensity } from '../primitives/BaseInput.vue'; import BaseInput, { type InputDensity } from '../primitives/BaseInput.vue';
interface Props { interface Props {
@@ -2,7 +2,7 @@
import { computed } from 'vue'; import { computed } from 'vue';
import DecimalField from '../fields/DecimalField.vue'; import DecimalField from '../fields/DecimalField.vue';
import type { InputDensity } from '../primitives/BaseInput.vue'; import type { InputDensity } from '../primitives/BaseInput.vue';
import { createMoney, makeDecimalString, type DecimalString, type Quantity } from '../../shared/types/coreModels'; import { makeDecimalString, type Quantity } from '../../shared/types/coreModels';
interface Props { interface Props {
id?: string; id?: string;
@@ -1,6 +1,6 @@
<!-- Unified Component Re-export: SelectInput -> QuantComboBox --> <!-- Unified Component Re-export: SelectInput -> QuantComboBox -->
<template> <template>
<QuantComboBox v-bind="$attrs"> <QuantComboBox v-bind="{ options: [], ...$attrs }">
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps"> <template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps || {}"></slot> <slot :name="slotName" v-bind="slotProps || {}"></slot>
</template> </template>
@@ -10,6 +10,12 @@
<script setup lang="ts"> <script setup lang="ts">
import QuantComboBox from '../QuantComboBox.vue'; import QuantComboBox from '../QuantComboBox.vue';
withDefaults(defineProps<{
options?: (string | { label: string; value: string | number })[];
}>(), {
options: () => []
});
defineOptions({ defineOptions({
inheritAttrs: false inheritAttrs: false
}); });
@@ -22,6 +22,7 @@ export interface OfflineCommand {
export interface BarcodeScanResult { export interface BarcodeScanResult {
rawBarcode: string; rawBarcode: string;
itemCode?: string; itemCode?: string;
gtin?: string;
lotNumber?: string; lotNumber?: string;
expiryDate?: string; expiryDate?: string;
serialNumber?: string; serialNumber?: string;
@@ -29,6 +30,8 @@ export interface BarcodeScanResult {
isValid: boolean; isValid: boolean;
} }
export type GS1ParseResult = BarcodeScanResult;
/** GS1-128 Barcode Parser Simulator (<100ms parse requirement) */ /** GS1-128 Barcode Parser Simulator (<100ms parse requirement) */
export function parseGS1Barcode(barcode: string): BarcodeScanResult { export function parseGS1Barcode(barcode: string): BarcodeScanResult {
const startTime = performance.now(); const startTime = performance.now();
@@ -6,7 +6,6 @@ import BaseCheckbox from '../../components/primitives/BaseCheckbox.vue';
import BaseButton from '../../components/primitives/BaseButton.vue'; import BaseButton from '../../components/primitives/BaseButton.vue';
import BaseStatusBadge from '../../components/primitives/BaseStatusBadge.vue'; import BaseStatusBadge from '../../components/primitives/BaseStatusBadge.vue';
import CodeField from '../../components/fields/CodeField.vue'; import CodeField from '../../components/fields/CodeField.vue';
import DateField from '../../components/fields/DateField.vue';
import MoneyField from '../../components/domain-fields/MoneyField.vue'; import MoneyField from '../../components/domain-fields/MoneyField.vue';
import QuantityField from '../../components/domain-fields/QuantityField.vue'; import QuantityField from '../../components/domain-fields/QuantityField.vue';
@@ -4,10 +4,9 @@ import { ref } from 'vue';
import BaseStatusBadge from '../components/primitives/BaseStatusBadge.vue'; import BaseStatusBadge from '../components/primitives/BaseStatusBadge.vue';
import BaseButton from '../components/primitives/BaseButton.vue'; import BaseButton from '../components/primitives/BaseButton.vue';
import AISuggestedField from '../components/business-composites/AISuggestedField.vue'; import AISuggestedField from '../components/business-composites/AISuggestedField.vue';
import { getAIRiskPolicy, validateAIFormulaGuard, type AIRiskLevel, type AIDecisionLog } from '../shared/ai/aiGovernance'; import { validateAIFormulaGuard, type AIRiskLevel, type AIDecisionLog } from '../shared/ai/aiGovernance';
const customerCategory = ref('일반 거래처'); const customerCategory = ref('일반 거래처');
const orderRemarks = ref('');
// Decision Logs Stream // Decision Logs Stream
const decisionLogs = ref<AIDecisionLog[]>([]); const decisionLogs = ref<AIDecisionLog[]>([]);
@@ -11,7 +11,6 @@ import TextField from '../components/fields/TextField.vue';
import CodeField from '../components/fields/CodeField.vue'; import CodeField from '../components/fields/CodeField.vue';
import DecimalField from '../components/fields/DecimalField.vue'; import DecimalField from '../components/fields/DecimalField.vue';
import DateField from '../components/fields/DateField.vue'; import DateField from '../components/fields/DateField.vue';
import SelectField from '../components/fields/SelectField.vue';
import QuantityField from '../components/domain-fields/QuantityField.vue'; import QuantityField from '../components/domain-fields/QuantityField.vue';
import MoneyField from '../components/domain-fields/MoneyField.vue'; import MoneyField from '../components/domain-fields/MoneyField.vue';
import LotField from '../components/domain-fields/LotField.vue'; import LotField from '../components/domain-fields/LotField.vue';
@@ -91,7 +90,7 @@ const aiFieldVal = ref('일반 배송');
<CodeField v-model="sampleCode" label="L2 CodeField (대문자 정규화)" /> <CodeField v-model="sampleCode" label="L2 CodeField (대문자 정규화)" />
<DecimalField v-model="sampleDecimal" label="L2 DecimalField (부동소수점 오차 0)" /> <DecimalField v-model="sampleDecimal" label="L2 DecimalField (부동소수점 오차 0)" />
<DateField v-model="sampleDate" label="L2 DateField (ISO YYYY-MM-DD)" /> <DateField v-model="sampleDate" label="L2 DateField (ISO YYYY-MM-DD)" />
<TextField label="L2 TextField (오류 예시)" :field-state="{ value: '오류값', status: 'invalid', errors: [{ code: 'REQ', message: '입력 필수 항목입니다' }] }" /> <TextField label="L2 TextField (오류 예시)" :field-state="{ value: '오류값', initialValue: '초기값', status: 'invalid', source: 'user', touched: true, dirty: true, required: true, errors: [{ code: 'REQ', message: '입력 필수 항목입니다', severity: 'error', blocking: true }], warnings: [], information: [] }" />
</div> </div>
</div> </div>
@@ -84,7 +84,7 @@ const realQuantData = ref<Array<{ symbol: string; name: string; sector: string;
<td style="padding: 6px; border: 1px solid #CBD5E1; font-weight: bold;">{{ item.name }}</td> <td style="padding: 6px; border: 1px solid #CBD5E1; font-weight: bold;">{{ item.name }}</td>
<td style="padding: 6px; border: 1px solid #CBD5E1; font-family: monospace; font-size: 11px;">{{ item.source }}</td> <td style="padding: 6px; border: 1px solid #CBD5E1; font-family: monospace; font-size: 11px;">{{ item.source }}</td>
<td style="padding: 6px; border: 1px solid #CBD5E1; text-align: center;"> <td style="padding: 6px; border: 1px solid #CBD5E1; text-align: center;">
<QuantStatusChip :type="item.status" /> <QuantStatusChip :type="item.status === 'PASS' ? 'success' : (item.status === 'LIMIT' ? 'warning' : 'danger')" />
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -6,9 +6,8 @@ import BaseButton from '../components/primitives/BaseButton.vue';
import BaseInput from '../components/primitives/BaseInput.vue'; import BaseInput from '../components/primitives/BaseInput.vue';
import CodeField from '../components/fields/CodeField.vue'; import CodeField from '../components/fields/CodeField.vue';
import TextField from '../components/fields/TextField.vue'; import TextField from '../components/fields/TextField.vue';
import DecimalField from '../components/fields/DecimalField.vue';
import { makeUserId, makeDecimalString } from '../shared/types/coreModels'; import { makeUserId, makeDecimalString } from '../shared/types/coreModels';
import { validateJournalBalance, validateSoDApproval, createReverseJournalEntry, type JournalEntryModel, type JournalLineModel } from '../modules/accounting/domain/journalEntry'; import { validateSoDApproval, createReverseJournalEntry, type JournalEntryModel, type JournalLineModel } from '../modules/accounting/domain/journalEntry';
// Current User Context (Maker-Checker) // Current User Context (Maker-Checker)
const currentUserId = ref(makeUserId('USER-MAKER-01')); const currentUserId = ref(makeUserId('USER-MAKER-01'));
@@ -56,7 +56,7 @@ const timeSeriesRows = ref([
<td style="padding: 8px; border: 1px solid #CBD5E1; text-align: right; font-weight: bold; color: #2C3E50;">{{ row.close }}</td> <td style="padding: 8px; border: 1px solid #CBD5E1; text-align: right; font-weight: bold; color: #2C3E50;">{{ row.close }}</td>
<td style="padding: 8px; border: 1px solid #CBD5E1; text-align: right;">{{ row.volume }}</td> <td style="padding: 8px; border: 1px solid #CBD5E1; text-align: right;">{{ row.volume }}</td>
<td style="padding: 8px; border: 1px solid #CBD5E1; text-align: center; font-family: monospace; font-size: 11px;"> <td style="padding: 8px; border: 1px solid #CBD5E1; text-align: center; font-family: monospace; font-size: 11px;">
<QuantStatusChip type="PASS" :label="row.source" /> <QuantStatusChip type="success" :label="row.source" />
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -3,7 +3,7 @@
import { ref } from 'vue'; import { ref } from 'vue';
import BaseStatusBadge from '../components/primitives/BaseStatusBadge.vue'; import BaseStatusBadge from '../components/primitives/BaseStatusBadge.vue';
import BaseButton from '../components/primitives/BaseButton.vue'; import BaseButton from '../components/primitives/BaseButton.vue';
import { runReconciliationCheck, sanitizeInputString, isNewFeatureEnabled, type ReconciliationReport } from '../shared/nfr/migrationGovernance'; import { runReconciliationCheck, sanitizeInputString, type ReconciliationReport } from '../shared/nfr/migrationGovernance';
const legacyTotal = ref(150000000); const legacyTotal = ref(150000000);
const newTotal = ref(150000000); const newTotal = ref(150000000);
+3 -3
View File
@@ -59,9 +59,9 @@ const handleCreateOrder = () => {
}, },
lines: lineItems.value.map((l, idx) => ({ lines: lineItems.value.map((l, idx) => ({
lineNo: idx + 1, lineNo: idx + 1,
productId: l.ticker, productId: l.ticker || l.itemCode || 'ITEM-001',
productName: l.name, productName: l.name || l.itemName || '상세 품목',
qty: l.qty, qty: l.qty || l.quantity || 1,
unitPrice: l.price, unitPrice: l.price,
amount: l.amount amount: l.amount
})), })),
+2 -2
View File
@@ -1,7 +1,7 @@
<!-- SnapshotAdminView.vue --> <!-- SnapshotAdminView.vue -->
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import QuantDataGrid from '../components/QuantDataGrid.vue'; import QuantGridAdapter from '../components/QuantGridAdapter.vue';
import type { ColDef } from 'ag-grid-community'; import type { ColDef } from 'ag-grid-community';
const rowData = ref<any[]>([]); const rowData = ref<any[]>([]);
@@ -82,7 +82,7 @@ onMounted(loadGridData);
<!-- Grid Body --> <!-- Grid Body -->
<div class="grid-body-panel"> <div class="grid-body-panel">
<QuantDataGrid <QuantGridAdapter
:columnDefs="columnDefs" :columnDefs="columnDefs"
:rowData="rowData" :rowData="rowData"
rowSelection="multiple" rowSelection="multiple"
@@ -6,7 +6,7 @@ import QuantInput from '../components/QuantInput.vue'
import QuantComboBox from '../components/QuantComboBox.vue' import QuantComboBox from '../components/QuantComboBox.vue'
import QuantRadio from '../components/QuantRadio.vue' import QuantRadio from '../components/QuantRadio.vue'
import QuantTextArea from '../components/QuantTextArea.vue' import QuantTextArea from '../components/QuantTextArea.vue'
import QuantDataGrid from '../components/QuantDataGrid.vue' import QuantGridAdapter from '../components/QuantGridAdapter.vue'
import CrudToolbar from '../components/crud/CrudToolbar.vue' import CrudToolbar from '../components/crud/CrudToolbar.vue'
import AuditTimeline from '../components/crud/AuditTimeline.vue' import AuditTimeline from '../components/crud/AuditTimeline.vue'
import { useSystemSettings } from '../composables/useSystemSettings' import { useSystemSettings } from '../composables/useSystemSettings'
@@ -15,8 +15,8 @@ import type { ColDef } from 'ag-grid-community'
const { const {
activeDomain, searchKeyword, activeCategoryTab, lastDraftSavedTime, activeDomain, searchKeyword, activeCategoryTab, lastDraftSavedTime,
isOmsModalOpen, isWmsModalOpen, isErpModalOpen, isModalOpen, isGuideModalOpen, isEditMode, isOmsModalOpen, isWmsModalOpen, isErpModalOpen, isModalOpen, isGuideModalOpen, isEditMode,
masterPanelWidth, isDraggingSplitter, pingMs, isBatchRunning, batchProgress, dbConnections, aiRecommendation, masterPanelWidth, isDraggingSplitter, pingMs, isBatchRunning, batchProgress, dbConnections: _dbConnections, aiRecommendation,
telemetryMetrics, liveLogs, items, selectedItem, toastMessage, filteredItems, telemetryMetrics: _telemetryMetrics, liveLogs, items, selectedItem, toastMessage, filteredItems,
showToast, loadData, selectItemWithInsight, quickChangeStatus, cloneSelectedItem, runFactorBatch, saveDetailForm, deleteSelectedItem, setSplitRatio showToast, loadData, selectItemWithInsight, quickChangeStatus, cloneSelectedItem, runFactorBatch, saveDetailForm, deleteSelectedItem, setSplitRatio
} = useSystemSettings(); } = useSystemSettings();
@@ -189,7 +189,7 @@ const triggerExport = (fmt: string) => {
<h4 class="panel-title">📋 OMS/WMS/ERP 원장 목록 ({{ filteredItems.length }} / {{ items.length }})</h4> <h4 class="panel-title">📋 OMS/WMS/ERP 원장 목록 ({{ filteredItems.length }} / {{ items.length }})</h4>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<QuantDataGrid <QuantGridAdapter
:columnDefs="columnDefs" :columnDefs="columnDefs"
:rowData="filteredItems" :rowData="filteredItems"
rowSelection="single" rowSelection="single"
@@ -286,7 +286,8 @@ const triggerExport = (fmt: string) => {
<button class="status-toggle-btn blocked" @click="quickChangeStatus('BLOCKED')">🚨 BLOCKED로 변경</button> <button class="status-toggle-btn blocked" @click="quickChangeStatus('BLOCKED')">🚨 BLOCKED로 변경</button>
</div> </div>
<QuantRadio <QuantRadio
v-model="selectedItem.status" v-model="selectedItem.status"
:value="selectedItem.status"
name="statusRadio" name="statusRadio"
:options="[ :options="[
{ label: 'ACTIVE (정상)', value: 'ACTIVE' }, { label: 'ACTIVE (정상)', value: 'ACTIVE' },
@@ -188,7 +188,7 @@ onMounted(() => {
@click="selectUser(u)"> @click="selectUser(u)">
<div style="display: flex; justify-content: space-between; align-items: center;"> <div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: bold; color: #2C3E50;">{{ u.username }}</span> <span style="font-weight: bold; color: #2C3E50;">{{ u.username }}</span>
<QuantStatusChip :type="u.isActive ? 'PASS' : 'FAIL'" :label="u.isActive ? '활성' : '비활성'" /> <QuantStatusChip :type="u.isActive ? 'success' : 'danger'" :label="u.isActive ? '활성' : '비활성'" />
</div> </div>
<div style="font-size: 11px; color: #7F8C8D; margin-top: 4px;"> <div style="font-size: 11px; color: #7F8C8D; margin-top: 4px;">
권한: <strong style="color: #2980B9;">{{ u.role }}</strong> | 변경일: {{ u.updatedAt || u.createdAt }} 권한: <strong style="color: #2980B9;">{{ u.role }}</strong> | 변경일: {{ u.updatedAt || u.createdAt }}
@@ -20,8 +20,8 @@ const totalPickedCount = ref(150);
// Offline Command Queue State (WMS-04) // Offline Command Queue State (WMS-04)
const offlineQueue = ref<OfflineCommand[]>([]); const offlineQueue = ref<OfflineCommand[]>([]);
const handleBarcodeScanned = (barcode: string) => { const handleBarcodeScanned = (input: BarcodeScanResult | string) => {
const result = parseGS1Barcode(barcode); const result = typeof input === 'string' ? parseGS1Barcode(input) : input;
lastScanResult.value = result; lastScanResult.value = result;
if (result.isValid && result.itemCode) { if (result.isValid && result.itemCode) {
scannedItemCode.value = result.itemCode; scannedItemCode.value = result.itemCode;
@@ -78,7 +78,7 @@ const syncOfflineQueue = () => {
<div class="flex gap-2"> <div class="flex gap-2">
<BaseButton <BaseButton
:variant="isOnline ? 'outline' : 'warning'" :variant="isOnline ? 'outline' : 'secondary'"
density="touch" density="touch"
@click="isOnline = !isOnline" @click="isOnline = !isOnline"
> >
@@ -44,8 +44,8 @@ const columnDefs = ref<ColDef[]>([
cellStyle: params => { cellStyle: params => {
const val = params.value || 0; const val = params.value || 0;
if (Math.abs(val) >= 2.0) return { color: '#DC2626', fontWeight: 'bold', backgroundColor: '#FEE2E2' }; if (Math.abs(val) >= 2.0) return { color: '#DC2626', fontWeight: 'bold', backgroundColor: '#FEE2E2' };
if (Math.abs(val) >= 1.0) return { color: '#D97706', fontWeight: 'bold' }; if (Math.abs(val) >= 1.0) return { color: '#D97706', fontWeight: 'bold', backgroundColor: 'transparent' };
return { color: '#166534', fontWeight: 'normal' }; return { color: '#166534', fontWeight: 'normal', backgroundColor: 'transparent' };
} }
}, },
{ field: 'provenance', headerName: '데이터 출처 (Provenance)', editable: false, width: 220 } { field: 'provenance', headerName: '데이터 출처 (Provenance)', editable: false, width: 220 }
@@ -6,7 +6,6 @@ import BaseInput from '../../components/primitives/BaseInput.vue';
import BaseButton from '../../components/primitives/BaseButton.vue'; import BaseButton from '../../components/primitives/BaseButton.vue';
import BaseStatusBadge from '../../components/primitives/BaseStatusBadge.vue'; import BaseStatusBadge from '../../components/primitives/BaseStatusBadge.vue';
import QuantDataGrid from '../../components/QuantDataGrid.vue'; import QuantDataGrid from '../../components/QuantDataGrid.vue';
import CrudToolbar from '../../components/crud/CrudToolbar.vue';
import type { EnterpriseTemplateId } from '../../types/enterpriseTemplateContracts'; import type { EnterpriseTemplateId } from '../../types/enterpriseTemplateContracts';
const templateId: EnterpriseTemplateId = 'TPL-LIST-01'; const templateId: EnterpriseTemplateId = 'TPL-LIST-01';
@@ -60,6 +59,12 @@ const clearFilter = (type: string) => {
executeSearch(); executeSearch();
}; };
const resetFilters = () => {
searchKeyword.value = '';
statusFilter.value = 'ALL';
executeSearch();
};
const filteredRows = computed(() => { const filteredRows = computed(() => {
return mockRows.value.filter(row => { return mockRows.value.filter(row => {
const matchKey = !searchKeyword.value || row.orderId.includes(searchKeyword.value) || row.customerName.includes(searchKeyword.value); const matchKey = !searchKeyword.value || row.orderId.includes(searchKeyword.value) || row.customerName.includes(searchKeyword.value);
@@ -84,7 +89,9 @@ onMounted(() => {
</div> </div>
<p class="text-xs text-slate-500 mt-1">TPL-LIST-01: 표준 목록, 다중 조건 검색, Active Filter Chips URL 동기화</p> <p class="text-xs text-slate-500 mt-1">TPL-LIST-01: 표준 목록, 다중 조건 검색, Active Filter Chips URL 동기화</p>
</div> </div>
<CrudToolbar :is-edit-mode="false" /> <div class="flex gap-2">
<BaseButton variant="primary" density="compact" @click="resetFilters">🔄 필터 초기화</BaseButton>
</div>
</div> </div>
<!-- Search Filter Panel --> <!-- Search Filter Panel -->
+1
View File
@@ -4,6 +4,7 @@
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"types": ["vite/client"], "types": ["vite/client"],
"allowArbitraryExtensions": true, "allowArbitraryExtensions": true,
"ignoreDeprecations": "6.0",
/* Strict Type-Checking Options */ /* Strict Type-Checking Options */
"strict": true, "strict": true,
+2 -2
View File
@@ -26,8 +26,8 @@ export default defineConfig({
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
configure: (proxy) => { configure: (proxy) => {
proxy.on('error', (err, req, res) => { proxy.on('error', (_err, _req, res: any) => {
if (!res.headersSent) { if (res && !res.headersSent) {
res.writeHead(200, { 'Content-Type': 'application/json' }); res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'OFFLINE_MOCK_ACTIVE', data: [], versions: [] })); res.end(JSON.stringify({ status: 'OFFLINE_MOCK_ACTIVE', data: [], versions: [] }));
} }