refactor(frontend): implement Full Transparent Wrapper Pattern with v-bind=\ and \ passthrough to expose 100% PrimeVue native capabilities without feature loss
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 10s
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 11s
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 9s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 9s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped

This commit is contained in:
2026-07-26 02:54:06 +09:00
parent 79026960b4
commit e2b797c03d
4 changed files with 66 additions and 23 deletions
+23 -3
View File
@@ -1,7 +1,11 @@
<!-- PrimeVue Select Adapter Wrapper: QuantComboBox --> <!-- PrimeVue Select Adapter Wrapper: QuantComboBox (Full Transparent Passthrough Wrapper) -->
<template> <template>
<div class="quant-combobox-wrapper w-full"> <div class="quant-combobox-wrapper 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>
<Select <Select
v-bind="$attrs"
:id="id" :id="id"
:modelValue="modelValue" :modelValue="modelValue"
:options="formattedOptions" :options="formattedOptions"
@@ -9,9 +13,17 @@
optionValue="value" optionValue="value"
:placeholder="placeholder || '선택하세요'" :placeholder="placeholder || '선택하세요'"
:disabled="disabled" :disabled="disabled"
:showClear="showClear !== false"
:filter="filter !== false"
filterPlaceholder="검색어 입력..."
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" 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"
/> >
<!-- PrimeVue Select 슬롯 100% 투명 전파 -->
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps || {}"></slot>
</template>
</Select>
</div> </div>
</template> </template>
@@ -19,12 +31,20 @@
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;
label?: 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; placeholder?: string;
required?: boolean;
disabled?: boolean; disabled?: boolean;
showClear?: boolean;
filter?: boolean;
}>(); }>();
const emit = defineEmits(['update:modelValue', 'change']); const emit = defineEmits(['update:modelValue', 'change']);
+14 -2
View File
@@ -1,14 +1,17 @@
<!-- PrimeVue DataTable Adapter Wrapper: QuantDataGrid --> <!-- PrimeVue DataTable Adapter Wrapper: QuantDataGrid (Full Transparent Passthrough Wrapper) -->
<template> <template>
<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"
:value="items" :value="items"
:loading="loading" :loading="loading"
:paginator="paginator" :paginator="paginator !== false"
:rows="rows || 10" :rows="rows || 10"
:rowsPerPageOptions="[10, 25, 50, 100]" :rowsPerPageOptions="[10, 25, 50, 100]"
dataKey="id" dataKey="id"
responsiveLayout="scroll" responsiveLayout="scroll"
resizableColumns
columnResizeMode="fit"
class="p-datatable-sm w-full text-xs" class="p-datatable-sm w-full text-xs"
tableStyle="min-width: 50rem" tableStyle="min-width: 50rem"
> >
@@ -18,6 +21,11 @@
</div> </div>
</template> </template>
<!-- 사용자 정의 슬롯 100% 전파 -->
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps || {}"></slot>
</template>
<Column <Column
v-for="col in headers" v-for="col in headers"
:key="col.key" :key="col.key"
@@ -42,6 +50,10 @@
import DataTable from 'primevue/datatable'; import DataTable from 'primevue/datatable';
import Column from 'primevue/column'; import Column from 'primevue/column';
defineOptions({
inheritAttrs: false
});
export interface GridColumn { export interface GridColumn {
key: string; key: string;
label: string; label: string;
+15 -16
View File
@@ -1,10 +1,11 @@
<!-- PrimeVue InputText Adapter Wrapper: QuantInput (Strict Numeric Filter Support) --> <!-- PrimeVue InputText Adapter Wrapper: QuantInput (Full Transparent Passthrough Wrapper) -->
<template> <template>
<div class="quant-input-wrapper flex flex-col gap-1 w-full"> <div class="quant-input-wrapper 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>
<InputText <InputText
v-bind="$attrs"
:id="id" :id="id"
:type="inputType" :type="inputType"
:modelValue="displayValue" :modelValue="displayValue"
@@ -15,7 +16,12 @@
@keydown="onKeyDown" @keydown="onKeyDown"
@paste="onPaste" @paste="onPaste"
@update:modelValue="onInput" @update:modelValue="onInput"
/> >
<!-- PrimeVue 원본 슬롯 100% 투명 전파 -->
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps || {}"></slot>
</template>
</InputText>
</div> </div>
</template> </template>
@@ -23,6 +29,10 @@
import { computed } from 'vue'; import { computed } from 'vue';
import InputText from 'primevue/inputtext'; import InputText from 'primevue/inputtext';
defineOptions({
inheritAttrs: false
});
const props = defineProps<{ const props = defineProps<{
id?: string; id?: string;
label?: string; label?: string;
@@ -37,7 +47,6 @@ const props = defineProps<{
const emit = defineEmits(['update:modelValue', 'change']); const emit = defineEmits(['update:modelValue', 'change']);
const isNumeric = computed(() => props.type === 'number'); const isNumeric = computed(() => props.type === 'number');
const inputType = computed(() => (isNumeric.value ? 'text' : props.type || 'text')); const inputType = computed(() => (isNumeric.value ? 'text' : props.type || 'text'));
const displayValue = computed(() => { const displayValue = computed(() => {
@@ -47,37 +56,27 @@ const displayValue = computed(() => {
const onKeyDown = (e: KeyboardEvent) => { const onKeyDown = (e: KeyboardEvent) => {
if (!isNumeric.value) return; if (!isNumeric.value) return;
const allowedKeys = [ const allowedKeys = [
'Backspace', 'Delete', 'Tab', 'Escape', 'Enter', 'Backspace', 'Delete', 'Tab', 'Escape', 'Enter',
'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown',
'Home', 'End', '.', '-' 'Home', 'End', '.', '-'
]; ];
if (allowedKeys.includes(e.key) || e.ctrlKey || e.metaKey) return;
if (allowedKeys.includes(e.key) || e.ctrlKey || e.metaKey) { if (!/^[0-9]$/.test(e.key)) e.preventDefault();
return;
}
if (!/^[0-9]$/.test(e.key)) {
e.preventDefault();
}
}; };
const onPaste = (e: ClipboardEvent) => { const onPaste = (e: ClipboardEvent) => {
if (!isNumeric.value) return; if (!isNumeric.value) return;
const pasteData = e.clipboardData?.getData('text') || ''; const pasteData = e.clipboardData?.getData('text') || '';
if (/[^0-9.-]/g.test(pasteData)) { if (/[^0-9.-]/g.test(pasteData)) {
e.preventDefault(); e.preventDefault();
const sanitized = pasteData.replace(/[^0-9.-]/g, ''); onInput(pasteData.replace(/[^0-9.-]/g, ''));
onInput(sanitized);
} }
}; };
const onInput = (val: string | undefined) => { const onInput = (val: string | undefined) => {
let finalVal = val ?? ''; let finalVal = val ?? '';
if (isNumeric.value) { if (isNumeric.value) {
// 숫자가 아닌 모든 문자 원천 정제
finalVal = finalVal.replace(/[^0-9.-]/g, ''); finalVal = finalVal.replace(/[^0-9.-]/g, '');
} }
emit('update:modelValue', finalVal); emit('update:modelValue', finalVal);
@@ -1,10 +1,13 @@
<!-- PrimeVue DataTable Adapter Wrapper: QuantMasterGrid --> <!-- PrimeVue DataTable Adapter Wrapper: QuantMasterGrid (Full Transparent Passthrough Wrapper) -->
<template> <template>
<div class="quant-master-grid-wrapper w-full overflow-hidden border border-slate-300 rounded shadow-2xs bg-white"> <div class="quant-master-grid-wrapper w-full overflow-hidden border border-slate-300 rounded shadow-2xs bg-white">
<DataTable <DataTable
v-bind="$attrs"
:value="items || []" :value="items || []"
dataKey="id" dataKey="id"
responsiveLayout="scroll" responsiveLayout="scroll"
resizableColumns
columnResizeMode="fit"
class="p-datatable-sm w-full text-xs" class="p-datatable-sm w-full text-xs"
:style="{ maxHeight: height ? height + 'px' : '300px' }" :style="{ maxHeight: height ? height + 'px' : '300px' }"
> >
@@ -14,12 +17,18 @@
</div> </div>
</template> </template>
<!-- 사용자 정의 슬롯 100% 전파 -->
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps || {}"></slot>
</template>
<Column <Column
v-for="col in gridHeaders" v-for="col in gridHeaders"
:key="col.key" :key="col.key"
:field="col.key" :field="col.key"
:header="col.label" :header="col.label"
:style="{ width: col.width || 'auto', textAlign: col.align || 'left' }" :style="{ width: col.width || 'auto', textAlign: col.align || 'left' }"
sortable
> >
<template #body="slotProps"> <template #body="slotProps">
<slot :name="col.key" :item="slotProps.data" :value="slotProps.data[col.key]"> <slot :name="col.key" :item="slotProps.data" :value="slotProps.data[col.key]">
@@ -38,6 +47,10 @@ import { computed } from 'vue';
import DataTable from 'primevue/datatable'; import DataTable from 'primevue/datatable';
import Column from 'primevue/column'; import Column from 'primevue/column';
defineOptions({
inheritAttrs: false
});
export interface GridHeader { export interface GridHeader {
key: string; key: string;
label: string; label: string;
@@ -53,7 +66,6 @@ const props = defineProps<{
const gridHeaders = computed(() => { const gridHeaders = computed(() => {
if (props.headers && props.headers.length > 0) return props.headers; if (props.headers && props.headers.length > 0) return props.headers;
// Fallback defaults if not supplied
return [ return [
{ key: 'code', label: '코드', width: '120px' }, { key: 'code', label: '코드', width: '120px' },
{ key: 'name', label: '명칭' }, { key: 'name', label: '명칭' },