style(ui): standardize font colors across 4-layer components to slate-800 labels and slate-900 input values
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 11s
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 12s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 19s
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 9s

This commit is contained in:
2026-07-26 02:09:49 +09:00
parent fa12511a2c
commit 27096f0d3b
6 changed files with 104 additions and 134 deletions
@@ -1,8 +1,8 @@
<!-- Domain Field Component Layer: MoneyField (WBS-COMP-3.2) -->
<template>
<div class="domain-money-field flex flex-col gap-1">
<label v-if="label" class="text-xs font-bold text-slate-700">
{{ label }} <span v-if="required" class="text-red-500">*</span>
<div class="domain-money-field flex flex-col gap-1 w-full">
<label v-if="label" class="text-xs font-bold text-slate-800">
{{ label }} <span v-if="required" class="text-rose-600">*</span>
</label>
<div class="flex gap-2">
<input
@@ -11,13 +11,13 @@
placeholder="0"
:readonly="readonly"
:disabled="disabled"
class="flex-1 h-9 px-3 border border-slate-300 rounded text-right font-mono text-xs text-slate-800 font-bold focus:ring-2 focus:ring-blue-500 focus:outline-none"
class="flex-1 h-9 px-3 border border-slate-300 rounded text-right font-mono text-xs text-slate-900 font-bold bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500 focus:outline-none disabled:bg-slate-100 disabled:text-slate-500 readonly:bg-slate-50 readonly:text-slate-700"
@input="handleInput"
/>
<select
:value="currencyCode || 'KRW'"
:disabled="currencyReadonly || disabled"
class="h-9 px-2 border border-slate-300 rounded bg-slate-50 text-xs font-bold text-slate-700"
class="h-9 px-2 border border-slate-300 rounded bg-slate-50 text-xs font-bold text-slate-800"
@change="handleCurrencyChange"
>
<option value="KRW">KRW ()</option>
@@ -26,7 +26,7 @@
<option value="JPY">JPY (¥)</option>
</select>
</div>
<div v-if="formattedKrwSummary" class="text-[11px] text-blue-600 font-medium mt-0.5">
<div v-if="formattedKrwSummary" class="text-[11px] text-blue-700 font-bold mt-0.5">
한화 환산: {{ formattedKrwSummary }}
</div>
</div>
@@ -58,7 +58,7 @@ const formattedKrwSummary = computed(() => {
if (!props.amount || props.currencyCode === 'KRW') return '';
const num = Number(props.amount.replace(/,/g, ''));
if (isNaN(num)) return '';
return (num * 1350).toLocaleString('ko-KR'); // 예시 환율
return (num * 1350).toLocaleString('ko-KR');
});
const handleInput = (e: Event) => {
@@ -1,46 +1,72 @@
<!-- Domain Field Component Layer -->
<!-- Domain Field Component Layer: QuantityField (WBS-COMP-3.1) -->
<template>
<div class="domain-quantity-field">
<label class="domain-label">{{ label || '수량' }} <span v-if="required" class="req">*</span></label>
<div class="input-unit-group flex gap-2">
<div class="domain-quantity-field flex flex-col gap-1 w-full">
<div class="flex justify-between items-center text-xs">
<label v-if="label" class="font-bold text-slate-800">
{{ label }} <span v-if="required" class="text-rose-600">*</span>
</label>
<span v-if="availableStock !== undefined" class="text-[11px] text-emerald-700 font-bold">
가용재고: {{ availableStock.toLocaleString() }} {{ selectedUnit }}
</span>
</div>
<div class="flex gap-2">
<input
type="text"
:value="amount"
:value="formattedAmount"
placeholder="0"
class="quantity-input flex-1 border rounded px-3 py-1 text-right font-mono"
@input="handleAmountInput"
:readonly="readonly"
:disabled="disabled"
class="flex-1 h-9 px-3 border border-slate-300 rounded text-right font-mono text-xs text-slate-900 font-bold bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500 focus:outline-none disabled:bg-slate-100 disabled:text-slate-500 readonly:bg-slate-50 readonly:text-slate-700"
@input="handleInput"
/>
<select :value="unitCode" class="unit-select border rounded px-2" @change="handleUnitChange">
<option v-for="unit in availableUnits" :key="unit" :value="unit">{{ unit }}</option>
<select
:value="selectedUnit"
:disabled="unitReadonly || disabled"
class="h-9 px-2 border border-slate-300 rounded bg-slate-50 text-xs font-bold text-slate-800"
@change="handleUnitChange"
>
<option v-for="unit in availableUnits || ['EA', 'BOX', 'PALLET']" :key="unit" :value="unit">
{{ unit }}
</option>
</select>
</div>
<div v-if="stockContext" class="stock-info text-xs text-slate-500 mt-1">
가용재고: <strong>{{ stockContext.availableAmount }} {{ unitCode }}</strong>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
const props = defineProps<{
label?: string;
amount: string | number;
unitCode?: string;
availableUnits?: string[];
availableStock?: number;
required?: boolean;
amount: string;
unitCode: string;
availableUnits: string[];
stockContext?: { availableAmount: string };
readonly?: boolean;
disabled?: boolean;
unitReadonly?: boolean;
}>();
const emit = defineEmits(['update:amount', 'update:unitCode']);
const handleAmountInput = (e: Event) => {
emit('update:amount', (e.target as HTMLInputElement).value);
const selectedUnit = ref(props.unitCode || 'EA');
const formattedAmount = computed(() => {
if (!props.amount) return '';
const num = Number(String(props.amount).replace(/,/g, ''));
if (isNaN(num)) return String(props.amount);
return num.toLocaleString('ko-KR');
});
const handleInput = (e: Event) => {
const raw = (e.target as HTMLInputElement).value.replace(/,/g, '');
emit('update:amount', raw);
};
const handleUnitChange = (e: Event) => {
emit('update:unitCode', (e.target as HTMLSelectElement).value);
const newUnit = (e.target as HTMLSelectElement).value;
selectedUnit.value = newUnit;
emit('update:unitCode', newUnit);
};
</script>
<style scoped>
.req { color: #DC2626; }
</style>
@@ -1,59 +1,48 @@
<!-- Typed Field Component Layer: DateField (WBS-COMP-2.3) -->
<template>
<div class="typed-date-field flex flex-col gap-1">
<label v-if="label" :for="id" class="text-xs font-bold text-slate-700">
{{ label }} <span v-if="required" class="text-red-500">*</span>
</label>
<div class="flex gap-2 items-center">
<input
:id="id"
type="date"
:value="modelValue"
:min="minDate"
:max="maxDate"
:readonly="readonly"
:disabled="disabled"
class="h-9 px-3 border border-slate-300 rounded text-xs text-slate-800 focus:ring-2 focus:ring-blue-500 focus:outline-none"
@change="handleChange"
/>
<div class="typed-date-field flex flex-col gap-1 w-full">
<div class="flex justify-between items-center text-xs">
<label v-if="label" class="font-bold text-slate-800">
{{ label }} <span v-if="required" class="text-rose-600">*</span>
</label>
<button
v-if="showTodayBtn && !readonly && !disabled"
type="button"
class="h-9 px-2.5 bg-slate-100 hover:bg-slate-200 border border-slate-300 rounded text-xs text-slate-700 font-medium transition"
class="text-[11px] font-bold text-blue-600 hover:text-blue-800 underline"
@click="setToday"
>
오늘
</button>
</div>
<div v-if="errorMessage" class="text-[11px] text-red-600 mt-0.5">{{ errorMessage }}</div>
<input
type="date"
:value="modelValue"
:readonly="readonly"
:disabled="disabled"
class="w-full h-9 px-3 border border-slate-300 rounded text-xs text-slate-900 font-mono font-bold bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500 focus:outline-none disabled:bg-slate-100 disabled:text-slate-500 disabled:cursor-not-allowed readonly:bg-slate-50 readonly:text-slate-700"
@change="handleChange"
/>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
id?: string;
label?: string;
modelValue?: string;
minDate?: string;
maxDate?: string;
required?: boolean;
readonly?: boolean;
disabled?: boolean;
showTodayBtn?: boolean;
errorMessage?: string;
}>();
const emit = defineEmits(['update:modelValue', 'change']);
const emit = defineEmits(['update:modelValue']);
const handleChange = (e: Event) => {
const val = (e.target as HTMLInputElement).value;
emit('update:modelValue', val);
emit('change', val);
emit('update:modelValue', (e.target as HTMLInputElement).value);
};
const setToday = () => {
const todayIso = new Date().toISOString().substring(0, 10);
emit('update:modelValue', todayIso);
emit('change', todayIso);
const today = new Date().toISOString().substring(0, 10);
emit('update:modelValue', today);
};
</script>
@@ -1,59 +1,44 @@
<!-- Typed Field Component Layer: NumberField (WBS-COMP-2.2) -->
<template>
<div class="typed-number-field flex flex-col gap-1">
<label v-if="label" :for="id" class="text-xs font-bold text-slate-700">
{{ label }} <span v-if="required" class="text-red-500">*</span>
<div class="typed-number-field flex flex-col gap-1 w-full">
<label v-if="label" class="text-xs font-bold text-slate-800">
{{ label }} <span v-if="required" class="text-rose-600">*</span>
</label>
<div class="relative flex items-center">
<input
:id="id"
type="text"
:value="displayFormattedValue"
:placeholder="placeholder || '0'"
:readonly="readonly"
:disabled="disabled"
class="w-full h-9 px-3 border border-slate-300 rounded text-right font-mono text-xs focus:ring-2 focus:ring-blue-500 focus:outline-none"
@input="handleInput"
@blur="handleBlur"
/>
</div>
<div v-if="errorMessage" class="text-[11px] text-red-600 mt-0.5">{{ errorMessage }}</div>
<input
type="text"
:value="formattedValue"
:placeholder="placeholder || '0'"
:readonly="readonly"
:disabled="disabled"
class="w-full h-9 px-3 border border-slate-300 rounded text-right font-mono text-xs text-slate-900 font-bold bg-white placeholder:text-slate-400 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 focus:outline-none disabled:bg-slate-100 disabled:text-slate-500 disabled:cursor-not-allowed readonly:bg-slate-50 readonly:text-slate-700"
@input="handleInput"
/>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { computed } from 'vue';
const props = defineProps<{
id?: string;
label?: string;
modelValue?: string | number;
placeholder?: string;
required?: boolean;
readonly?: boolean;
disabled?: boolean;
grouping?: boolean;
errorMessage?: string;
}>();
const emit = defineEmits(['update:modelValue', 'change']);
const isFocused = ref(false);
const emit = defineEmits(['update:modelValue']);
const displayFormattedValue = computed(() => {
if (props.modelValue === null || props.modelValue === undefined || props.modelValue === '') return '';
if (isFocused.value) return String(props.modelValue);
const num = Number(props.modelValue);
const formattedValue = computed(() => {
if (props.modelValue === undefined || props.modelValue === null || props.modelValue === '') return '';
const num = Number(String(props.modelValue).replace(/,/g, ''));
if (isNaN(num)) return String(props.modelValue);
return props.grouping !== false ? num.toLocaleString('ko-KR') : String(num);
return num.toLocaleString('ko-KR');
});
const handleInput = (e: Event) => {
const val = (e.target as HTMLInputElement).value.replace(/,/g, '');
emit('update:modelValue', val);
emit('change', val);
};
const handleBlur = () => {
isFocused.value = false;
const raw = (e.target as HTMLInputElement).value.replace(/,/g, '');
emit('update:modelValue', raw);
};
</script>
@@ -1,7 +1,9 @@
<!-- Typed Field Component Layer -->
<!-- Typed Field Component Layer: StringField -->
<template>
<div class="typed-string-field">
<label v-if="label" :for="id" class="field-label">{{ label }}</label>
<div class="typed-string-field flex flex-col gap-1 w-full">
<label v-if="label" :for="id" class="text-xs font-bold text-slate-800">
{{ label }} <span v-if="required" class="text-rose-600">*</span>
</label>
<input
:id="id"
type="text"
@@ -9,10 +11,10 @@
:placeholder="placeholder"
:readonly="readonly"
:disabled="disabled"
class="string-input"
class="w-full h-9 px-3 border border-slate-300 rounded text-xs text-slate-900 font-medium bg-white placeholder:text-slate-400 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 focus:outline-none disabled:bg-slate-100 disabled:text-slate-500 disabled:cursor-not-allowed readonly:bg-slate-50 readonly:text-slate-700"
@input="handleInput"
/>
<div v-if="errorMessage" class="field-error-msg">{{ errorMessage }}</div>
<div v-if="errorMessage" class="text-[11px] text-rose-600 font-semibold mt-0.5">{{ errorMessage }}</div>
</div>
</template>
@@ -22,6 +24,7 @@ const props = defineProps<{
label?: string;
modelValue?: string;
placeholder?: string;
required?: boolean;
readonly?: boolean;
disabled?: boolean;
errorMessage?: string;
@@ -35,25 +38,3 @@ const handleInput = (e: Event) => {
emit('change', val);
};
</script>
<style scoped>
.field-label {
display: block;
font-size: 12px;
font-weight: 700;
color: #334155;
margin-bottom: 4px;
}
.string-input {
width: 100%;
height: 36px;
padding: 0 10px;
border: 1px solid #CBD5E1;
border-radius: 4px;
}
.field-error-msg {
font-size: 11px;
color: #DC2626;
margin-top: 2px;
}
</style>
@@ -1,6 +1,6 @@
<!-- Primitive UI Components Layer -->
<template>
<div class="primitive-input-wrapper">
<div class="primitive-input-wrapper w-full">
<input
:id="id"
:type="type || 'text'"
@@ -8,7 +8,7 @@
:placeholder="placeholder"
:disabled="disabled"
:readonly="readonly"
class="primitive-input"
class="primitive-input w-full h-9 px-3 border border-slate-300 rounded text-xs text-slate-900 font-medium bg-white placeholder:text-slate-400 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 focus:outline-none disabled:bg-slate-100 disabled:text-slate-500 disabled:cursor-not-allowed readonly:bg-slate-50 readonly:text-slate-700"
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
/>
</div>
@@ -26,14 +26,3 @@ defineProps<{
defineEmits(['update:modelValue']);
</script>
<style scoped>
.primitive-input {
width: 100%;
height: 36px;
padding: 0 12px;
border: 1px solid #CBD5E1;
border-radius: 4px;
font-size: 13px;
}
</style>