refactor(arch): apply Adapter Pattern to wrap PrimeVue 4 components (InputText, Select, Dialog, InputNumber, DatePicker) isolating UI framework dependency
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 12s
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 11s
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 9s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 10s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 19s

This commit is contained in:
2026-07-26 02:42:59 +09:00
parent 9553df4f14
commit 64009419b3
5 changed files with 136 additions and 72 deletions
@@ -1,4 +1,4 @@
<!-- Typed Field Component Layer: DateField (WBS-COMP-2.3) --> <!-- Typed Field Layer: DateField (PrimeVue DatePicker Adapter Wrapper) -->
<template> <template>
<div class="typed-date-field flex flex-col gap-1 w-full"> <div class="typed-date-field flex flex-col gap-1 w-full">
<div class="flex justify-between items-center text-xs"> <div class="flex justify-between items-center text-xs">
@@ -14,18 +14,21 @@
오늘 오늘
</button> </button>
</div> </div>
<input <DatePicker
type="date" :modelValue="parsedDate"
:value="modelValue" dateFormat="yy-mm-dd"
:readonly="readonly" :readonly="readonly"
:disabled="disabled" :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" inputClass="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 disabled:bg-slate-100 disabled:text-slate-500 readonly:bg-slate-50 readonly:text-slate-700"
@change="handleChange" @update:modelValue="handleChange"
/> />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue';
import DatePicker from 'primevue/datepicker';
const props = defineProps<{ const props = defineProps<{
label?: string; label?: string;
modelValue?: string; modelValue?: string;
@@ -35,14 +38,36 @@ const props = defineProps<{
showTodayBtn?: boolean; showTodayBtn?: boolean;
}>(); }>();
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue', 'change']);
const handleChange = (e: Event) => { const parsedDate = computed(() => {
emit('update:modelValue', (e.target as HTMLInputElement).value); if (!props.modelValue) return null;
const d = new Date(props.modelValue);
return isNaN(d.getTime()) ? null : d;
});
const handleChange = (val: any) => {
if (!val) {
emit('update:modelValue', '');
emit('change', '');
return;
}
let dateStr = '';
if (val instanceof Date) {
const yyyy = val.getFullYear();
const mm = String(val.getMonth() + 1).padStart(2, '0');
const dd = String(val.getDate()).padStart(2, '0');
dateStr = `${yyyy}-${mm}-${dd}`;
} else {
dateStr = String(val);
}
emit('update:modelValue', dateStr);
emit('change', dateStr);
}; };
const setToday = () => { const setToday = () => {
const today = new Date().toISOString().substring(0, 10); const today = new Date().toISOString().substring(0, 10);
emit('update:modelValue', today); emit('update:modelValue', today);
emit('change', today);
}; };
</script> </script>
@@ -1,23 +1,26 @@
<!-- Typed Field Component Layer: NumberField (WBS-COMP-2.2) --> <!-- Typed Field Layer: NumberField (PrimeVue InputNumber Adapter Wrapper) -->
<template> <template>
<div class="typed-number-field flex flex-col gap-1 w-full"> <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 v-if="label" class="text-xs font-bold text-slate-800">
{{ label }} <span v-if="required" class="text-rose-600">*</span> {{ label }} <span v-if="required" class="text-rose-600">*</span>
</label> </label>
<input <InputNumber
type="text" :modelValue="numericValue"
:value="formattedValue"
:placeholder="placeholder || '0'" :placeholder="placeholder || '0'"
:readonly="readonly" :readonly="readonly"
:disabled="disabled" :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" locale="ko-KR"
@input="handleInput" :minFractionDigits="0"
:maxFractionDigits="2"
inputClass="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 readonly:bg-slate-50 readonly:text-slate-700"
@update:modelValue="handleInput"
/> />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import InputNumber from 'primevue/inputnumber';
const props = defineProps<{ const props = defineProps<{
label?: string; label?: string;
@@ -28,17 +31,17 @@ const props = defineProps<{
disabled?: boolean; disabled?: boolean;
}>(); }>();
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue', 'change']);
const formattedValue = computed(() => { const numericValue = computed(() => {
if (props.modelValue === undefined || props.modelValue === null || props.modelValue === '') return ''; if (props.modelValue === undefined || props.modelValue === null || props.modelValue === '') return null;
const num = Number(String(props.modelValue).replace(/,/g, '')); const num = Number(String(props.modelValue).replace(/,/g, ''));
if (isNaN(num)) return String(props.modelValue); return isNaN(num) ? null : num;
return num.toLocaleString('ko-KR');
}); });
const handleInput = (e: Event) => { const handleInput = (val: number | null) => {
const raw = (e.target as HTMLInputElement).value.replace(/,/g, ''); const finalVal = val === null ? '' : String(val);
emit('update:modelValue', raw); emit('update:modelValue', finalVal);
emit('change', finalVal);
}; };
</script> </script>
@@ -1,38 +1,44 @@
<!-- Primitive UI Component: DialogModal (WBS-COMP-1.3) --> <!-- Primitive Layer: DialogModal (PrimeVue Dialog Adapter Wrapper) -->
<template> <template>
<Teleport to="body"> <Dialog
<div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/50 backdrop-blur-xs select-none"> :visible="isOpen"
<div class="bg-white rounded-lg border border-slate-300 shadow-xl w-full max-w-md overflow-hidden flex flex-col animate-in fade-in zoom-in-95 duration-150"> :header="title || '확인'"
<!-- Header --> :modal="true"
<div class="bg-slate-900 text-white px-4 py-3 flex justify-between items-center text-sm font-bold"> :closable="true"
<span>{{ title || '확인' }}</span> :dismissableMask="true"
<button type="button" class="text-slate-400 hover:text-white font-bold" @click="$emit('close')"></button> class="primitive-dialog-modal max-w-md w-full"
</div> @update:visible="onVisibleChange"
>
<!-- Body --> <div class="p-4 text-xs text-slate-800 leading-relaxed">
<div class="p-5 text-xs text-slate-800 leading-relaxed"> <slot></slot>
<slot></slot>
</div>
<!-- Footer -->
<div class="bg-slate-100 px-4 py-2.5 border-t border-slate-200 flex justify-end gap-2 text-xs">
<button type="button" class="px-3 py-1.5 bg-slate-200 hover:bg-slate-300 text-slate-800 font-bold rounded transition" @click="$emit('close')">
취소
</button>
<button type="button" class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded transition shadow-xs" @click="$emit('confirm')">
확인
</button>
</div>
</div>
</div> </div>
</Teleport>
<template #footer>
<div class="flex justify-end gap-2 text-xs pt-2 border-t border-slate-200">
<button type="button" class="px-3 py-1.5 bg-slate-200 hover:bg-slate-300 text-slate-800 font-bold rounded transition" @click="$emit('close')">
취소
</button>
<button type="button" class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded transition shadow-xs" @click="$emit('confirm')">
확인
</button>
</div>
</template>
</Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
defineProps<{ import Dialog from 'primevue/dialog';
const props = defineProps<{
isOpen: boolean; isOpen: boolean;
title?: string; title?: string;
}>(); }>();
defineEmits(['close', 'confirm']); const emit = defineEmits(['close', 'confirm']);
const onVisibleChange = (val: boolean) => {
if (!val) {
emit('close');
}
};
</script> </script>
@@ -1,27 +1,44 @@
<!-- Primitive UI Component: SelectInput (WBS-COMP-1.2) --> <!-- Primitive Layer: SelectInput (PrimeVue Select Adapter Wrapper) -->
<template> <template>
<div class="primitive-select-wrapper w-full"> <div class="primitive-select-wrapper w-full">
<select <Select
:id="id" :id="id"
:value="modelValue" :modelValue="modelValue"
:options="formattedOptions"
optionLabel="label"
optionValue="value"
:disabled="disabled" :disabled="disabled"
class="primitive-select w-full h-9 px-3 border border-slate-300 rounded text-xs text-slate-900 font-medium 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" class="primitive-select w-full h-9 border border-slate-300 rounded text-xs text-slate-900 font-medium bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-slate-100 disabled:text-slate-500"
@change="$emit('update:modelValue', ($event.target as HTMLSelectElement).value)" @update:modelValue="onSelectChange"
> />
<option v-for="opt in options" :key="opt" :value="opt">
{{ opt }}
</option>
</select>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
defineProps<{ import { computed } from 'vue';
import Select from 'primevue/select';
const props = defineProps<{
id?: string; id?: string;
modelValue?: string | number; modelValue?: string | number;
options: string[]; options: Array<string | { label: string; value: string | number }>;
disabled?: boolean; disabled?: boolean;
}>(); }>();
defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue', 'change']);
const formattedOptions = computed(() => {
if (!props.options) return [];
return props.options.map(opt => {
if (typeof opt === 'string') {
return { label: opt, value: opt };
}
return opt;
});
});
const onSelectChange = (val: any) => {
emit('update:modelValue', val);
emit('change', val);
};
</script> </script>
@@ -1,22 +1,22 @@
<!-- Primitive UI Components Layer: TextInput --> <!-- Primitive Layer: TextInput (PrimeVue Adapter Wrapper) -->
<template> <template>
<div class="primitive-input-wrapper relative w-full flex items-center"> <div class="primitive-input-wrapper relative w-full flex items-center">
<input <InputText
:id="id" :id="id"
:type="type || 'text'" :type="type || 'text'"
:value="modelValue" :modelValue="modelValue"
:placeholder="placeholder" :placeholder="placeholder"
:disabled="disabled" :disabled="disabled"
:readonly="readonly" :readonly="readonly"
class="primitive-input w-full h-9 pl-3 pr-8 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 transition shadow-xs" class="primitive-input w-full h-9 pl-3 pr-8 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 transition shadow-xs"
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)" @update:modelValue="onUpdateValue"
/> />
<button <button
v-if="modelValue && !disabled && !readonly" v-if="modelValue && !disabled && !readonly"
type="button" type="button"
class="absolute right-2 text-slate-400 hover:text-slate-600 text-xs font-bold w-4 h-4 rounded-full flex items-center justify-center bg-slate-100 hover:bg-slate-200" class="absolute right-2 text-slate-400 hover:text-slate-600 text-xs font-bold w-4 h-4 rounded-full flex items-center justify-center bg-slate-100 hover:bg-slate-200 transition"
title="Clear text" title="Clear text"
@click="$emit('update:modelValue', '')" @click="onClear"
> >
</button> </button>
@@ -24,7 +24,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
defineProps<{ import InputText from 'primevue/inputtext';
const props = defineProps<{
id?: string; id?: string;
type?: string; type?: string;
modelValue?: string | number; modelValue?: string | number;
@@ -33,5 +35,16 @@ defineProps<{
readonly?: boolean; readonly?: boolean;
}>(); }>();
defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue', 'change', 'clear']);
const onUpdateValue = (val: string | undefined) => {
const finalVal = val ?? '';
emit('update:modelValue', finalVal);
emit('change', finalVal);
};
const onClear = () => {
emit('update:modelValue', '');
emit('clear');
};
</script> </script>