refactor(frontend): complete full transparent wrapper refactoring for primitives, fields, and subcomponents under /components
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 20s
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

This commit is contained in:
2026-07-26 03:01:53 +09:00
parent 7cb3be3d15
commit e1f1aec04f
5 changed files with 101 additions and 55 deletions
@@ -1,27 +1,36 @@
<!-- Typed Field Layer: DateField (PrimeVue DatePicker Adapter Wrapper) --> <!-- Typed Field Layer: DateField (Quant Transparent 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">
<label v-if="label" class="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>
<button <button
v-if="showTodayBtn && !readonly && !disabled" v-if="showTodayBtn && !disabled"
type="button" type="button"
class="text-[11px] font-bold text-blue-600 hover:text-blue-800 underline" class="text-[10px] text-blue-600 hover:text-blue-800 font-bold hover:underline"
@click="setToday" @click="setToday"
> >
오늘 오늘 날짜
</button> </button>
</div> </div>
<DatePicker <DatePicker
:modelValue="parsedDate" v-bind="$attrs"
:id="id"
:modelValue="dateValue"
dateFormat="yy-mm-dd" dateFormat="yy-mm-dd"
:readonly="readonly" :placeholder="placeholder || 'YYYY-MM-DD'"
:disabled="disabled" :disabled="disabled"
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" class="w-full text-xs"
@update:modelValue="handleChange" inputClass="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 disabled:bg-slate-100"
/> showIcon
iconDisplay="input"
@update:modelValue="onDateChange"
>
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps || {}"></slot>
</template>
</DatePicker>
</div> </div>
</template> </template>
@@ -29,45 +38,45 @@
import { computed } from 'vue'; import { computed } from 'vue';
import DatePicker from 'primevue/datepicker'; import DatePicker from 'primevue/datepicker';
defineOptions({
inheritAttrs: false
});
const props = defineProps<{ const props = defineProps<{
id?: string;
label?: string; label?: string;
modelValue?: string; modelValue?: string | Date;
placeholder?: string;
required?: boolean; required?: boolean;
readonly?: boolean;
disabled?: boolean; disabled?: boolean;
showTodayBtn?: boolean; showTodayBtn?: boolean;
}>(); }>();
const emit = defineEmits(['update:modelValue', 'change']); const emit = defineEmits(['update:modelValue', 'change']);
const parsedDate = computed(() => { const dateValue = computed(() => {
if (!props.modelValue) return null; if (!props.modelValue) return null;
if (props.modelValue instanceof Date) return props.modelValue;
const d = new Date(props.modelValue); const d = new Date(props.modelValue);
return isNaN(d.getTime()) ? null : d; return isNaN(d.getTime()) ? null : d;
}); });
const handleChange = (val: any) => { const onDateChange = (val: Date | null) => {
if (!val) { if (!val) {
emit('update:modelValue', ''); emit('update:modelValue', '');
emit('change', ''); emit('change', '');
return; return;
} }
let dateStr = ''; const yyyy = val.getFullYear();
if (val instanceof Date) { const mm = String(val.getMonth() + 1).padStart(2, '0');
const yyyy = val.getFullYear(); const dd = String(val.getDate()).padStart(2, '0');
const mm = String(val.getMonth() + 1).padStart(2, '0'); const strVal = `${yyyy}-${mm}-${dd}`;
const dd = String(val.getDate()).padStart(2, '0'); emit('update:modelValue', strVal);
dateStr = `${yyyy}-${mm}-${dd}`; emit('change', strVal);
} else {
dateStr = String(val);
}
emit('update:modelValue', dateStr);
emit('change', dateStr);
}; };
const setToday = () => { const setToday = () => {
const today = new Date().toISOString().substring(0, 10); const d = new Date();
emit('update:modelValue', today); onDateChange(d);
emit('change', today);
}; };
</script> </script>
@@ -1,40 +1,48 @@
<!-- Typed Field Component Layer: StringField --> <!-- Typed Field Layer: StringField (Quant Transparent Adapter Wrapper) -->
<template> <template>
<div class="typed-string-field flex flex-col gap-1 w-full"> <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 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 <InputText
v-bind="$attrs"
:id="id" :id="id"
type="text" :modelValue="modelValue"
:value="modelValue"
:placeholder="placeholder" :placeholder="placeholder"
:readonly="readonly"
:disabled="disabled" :disabled="disabled"
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" :readonly="readonly"
@input="handleInput" 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 disabled:bg-slate-100 readonly:bg-slate-50"
/> @update:modelValue="onInput"
<div v-if="errorMessage" class="text-[11px] text-rose-600 font-semibold mt-0.5">{{ errorMessage }}</div> >
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps || {}"></slot>
</template>
</InputText>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import InputText from 'primevue/inputtext';
defineOptions({
inheritAttrs: false
});
const props = defineProps<{ const props = defineProps<{
id?: string; id?: string;
label?: string; label?: string;
modelValue?: string; modelValue?: string;
placeholder?: string; placeholder?: string;
required?: boolean; required?: boolean;
readonly?: boolean;
disabled?: boolean; disabled?: boolean;
errorMessage?: string; readonly?: boolean;
}>(); }>();
const emit = defineEmits(['update:modelValue', 'change']); const emit = defineEmits(['update:modelValue', 'change']);
const handleInput = (e: Event) => { const onInput = (val: string | undefined) => {
const val = (e.target as HTMLInputElement).value; const finalVal = val ?? '';
emit('update:modelValue', val); emit('update:modelValue', finalVal);
emit('change', val); emit('change', finalVal);
}; };
</script> </script>
@@ -1,8 +1,9 @@
<!-- Primitive Layer: DialogModal (PrimeVue Dialog Adapter Wrapper) --> <!-- Primitive Layer: DialogModal (Quant Transparent Adapter Wrapper) -->
<template> <template>
<Dialog <Dialog
v-bind="$attrs"
:visible="isOpen" :visible="isOpen"
:header="title || '확인'" :header="title"
:modal="true" :modal="true"
:closable="true" :closable="true"
:dismissableMask="true" :dismissableMask="true"
@@ -16,7 +17,7 @@
<template #footer> <template #footer>
<div class="flex justify-end gap-2 text-xs pt-2 border-t border-slate-200"> <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 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>
<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 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')">
확인 확인
@@ -29,9 +30,13 @@
<script setup lang="ts"> <script setup lang="ts">
import Dialog from 'primevue/dialog'; import Dialog from 'primevue/dialog';
defineOptions({
inheritAttrs: false
});
const props = defineProps<{ const props = defineProps<{
isOpen: boolean; isOpen: boolean;
title?: string; title: string;
}>(); }>();
const emit = defineEmits(['close', 'confirm']); const emit = defineEmits(['close', 'confirm']);
@@ -1,16 +1,24 @@
<!-- Primitive Layer: SelectInput (PrimeVue Select Adapter Wrapper) --> <!-- Primitive Layer: SelectInput (Quant Transparent Adapter Wrapper) -->
<template> <template>
<div class="primitive-select-wrapper w-full"> <div class="primitive-select-wrapper w-full">
<Select <Select
v-bind="$attrs"
:id="id" :id="id"
:modelValue="modelValue" :modelValue="modelValue"
:options="formattedOptions" :options="formattedOptions"
optionLabel="label" optionLabel="label"
optionValue="value" optionValue="value"
:placeholder="placeholder || '선택하세요'"
:disabled="disabled" :disabled="disabled"
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" :showClear="showClear !== false"
:filter="filter !== false"
class="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"
@update:modelValue="onSelectChange" @update:modelValue="onSelectChange"
/> >
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps || {}"></slot>
</template>
</Select>
</div> </div>
</template> </template>
@@ -18,11 +26,18 @@
import { computed } from 'vue'; import { computed } from 'vue';
import Select from 'primevue/select'; import Select from 'primevue/select';
defineOptions({
inheritAttrs: false
});
const props = defineProps<{ const props = defineProps<{
id?: string; id?: string;
modelValue?: string | number; modelValue?: string | number;
options: Array<string | { label: string; value: string | number }>; options: Array<string | { label: string; value: string | number }>;
placeholder?: string;
disabled?: boolean; disabled?: boolean;
showClear?: boolean;
filter?: boolean;
}>(); }>();
const emit = defineEmits(['update:modelValue', 'change']); const emit = defineEmits(['update:modelValue', 'change']);
@@ -1,7 +1,8 @@
<!-- Primitive Layer: TextInput (PrimeVue Adapter Wrapper) --> <!-- Primitive Layer: TextInput (Quant Transparent 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">
<InputText <InputText
v-bind="$attrs"
:id="id" :id="id"
:type="type || 'text'" :type="type || 'text'"
:modelValue="modelValue" :modelValue="modelValue"
@@ -10,12 +11,16 @@
: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"
@update:modelValue="onUpdateValue" @update:modelValue="onUpdateValue"
/> >
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps || {}"></slot>
</template>
</InputText>
<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 transition" 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="1-Click 삭제"
@click="onClear" @click="onClear"
> >
@@ -26,6 +31,10 @@
<script setup lang="ts"> <script setup lang="ts">
import InputText from 'primevue/inputtext'; import InputText from 'primevue/inputtext';
defineOptions({
inheritAttrs: false
});
const props = defineProps<{ const props = defineProps<{
id?: string; id?: string;
type?: string; type?: string;