mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 14:13:18 +00:00
fix: concepto de presupuesto con Select filtrable de PrimeVue
El buscador pasa a Select con filtro, el mismo patrón que proveedor y unidad, para que overlay y tema coincidan con el resto del formulario. Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
This commit is contained in:
parent
7a31453dfd
commit
4b5d9572ee
2 changed files with 42 additions and 64 deletions
|
|
@ -2166,7 +2166,8 @@ html.dark .p-select-label.p-placeholder {
|
||||||
|
|
||||||
html.dark .p-select-overlay,
|
html.dark .p-select-overlay,
|
||||||
html.dark .p-datepicker-panel,
|
html.dark .p-datepicker-panel,
|
||||||
html.dark .p-multiselect-overlay {
|
html.dark .p-multiselect-overlay,
|
||||||
|
html.dark .p-autocomplete-overlay {
|
||||||
background: #111b2e !important;
|
background: #111b2e !important;
|
||||||
border-color: #243044 !important;
|
border-color: #243044 !important;
|
||||||
color: #e2e8f0 !important;
|
color: #e2e8f0 !important;
|
||||||
|
|
@ -2238,11 +2239,13 @@ html.dark .p-dialog .p-dialog-header-icon {
|
||||||
.cost-critical { background: #ffebee !important; color: #b42318 !important; }
|
.cost-critical { background: #ffebee !important; color: #b42318 !important; }
|
||||||
.cost-critical-text { color: #b42318; font-weight: 600; }
|
.cost-critical-text { color: #b42318; font-weight: 600; }
|
||||||
|
|
||||||
|
.budget-item-picker-overlay.p-select-overlay,
|
||||||
.budget-item-picker-overlay.p-autocomplete-overlay,
|
.budget-item-picker-overlay.p-autocomplete-overlay,
|
||||||
.budget-item-picker-overlay {
|
.budget-item-picker-overlay {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
.budget-item-picker-overlay .p-select-list,
|
||||||
.budget-item-picker-overlay .p-autocomplete-list,
|
.budget-item-picker-overlay .p-autocomplete-list,
|
||||||
.budget-item-picker-overlay ul {
|
.budget-item-picker-overlay ul {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -2250,12 +2253,26 @@ html.dark .p-dialog .p-dialog-header-icon {
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
.budget-item-picker-overlay .p-select-option,
|
||||||
.budget-item-picker-overlay .p-autocomplete-option,
|
.budget-item-picker-overlay .p-autocomplete-option,
|
||||||
.budget-item-picker-overlay li {
|
.budget-item-picker-overlay li {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
.budget-item-picker .budget-item-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.budget-item-picker .budget-item-value-desc {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #5b6573;
|
||||||
|
}
|
||||||
.budget-item-option {
|
.budget-item-option {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,35 @@
|
||||||
<template>
|
<template>
|
||||||
<AutoComplete
|
<Select
|
||||||
v-model="selected"
|
:model-value="modelValue ?? null"
|
||||||
:suggestions="filtered"
|
:options="items"
|
||||||
option-label="label"
|
option-label="label"
|
||||||
|
option-value="id"
|
||||||
placeholder="Buscar concepto del presupuesto..."
|
placeholder="Buscar concepto del presupuesto..."
|
||||||
class="budget-item-picker w-full"
|
filter
|
||||||
input-class="w-full"
|
filter-placeholder="Clave o descripción"
|
||||||
|
show-clear
|
||||||
|
fluid
|
||||||
|
class="w-full budget-item-picker"
|
||||||
overlay-class="budget-item-picker-overlay"
|
overlay-class="budget-item-picker-overlay"
|
||||||
panel-class="budget-item-picker-overlay"
|
panel-class="budget-item-picker-overlay"
|
||||||
dropdown
|
empty-filter-message="Sin conceptos que coincidan"
|
||||||
fluid
|
empty-message="Esta obra no tiene partidas cargadas"
|
||||||
force-selection
|
@update:model-value="onUpdate"
|
||||||
complete-on-focus
|
|
||||||
scroll-height="16rem"
|
|
||||||
empty-search-message="Sin conceptos que coincidan"
|
|
||||||
@complete="onSearch"
|
|
||||||
@item-select="onSelect"
|
|
||||||
@option-select="onSelect"
|
|
||||||
@clear="onClear"
|
|
||||||
@show="alignOverlay"
|
|
||||||
>
|
>
|
||||||
|
<template #value="{ value, placeholder }">
|
||||||
|
<span v-if="selected" class="budget-item-value">
|
||||||
|
<span class="identifier">{{ selected.code || "—" }}</span>
|
||||||
|
<span class="budget-item-value-desc">{{ selected.description }}</span>
|
||||||
|
</span>
|
||||||
|
<span v-else class="p-select-placeholder">{{ placeholder }}</span>
|
||||||
|
</template>
|
||||||
<template #option="{ option }">
|
<template #option="{ option }">
|
||||||
<div class="budget-item-option">
|
<div class="budget-item-option">
|
||||||
<span class="identifier">{{ option.code || "—" }}</span>
|
<span class="identifier">{{ option.code || "—" }}</span>
|
||||||
<span class="budget-item-option-desc">{{ option.description }}</span>
|
<span class="budget-item-option-desc">{{ option.description }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</AutoComplete>
|
</Select>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -47,8 +50,8 @@ type BudgetOption = {
|
||||||
|
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
const items = ref<BudgetOption[]>([]);
|
const items = ref<BudgetOption[]>([]);
|
||||||
const selected = ref<BudgetOption | null>(null);
|
|
||||||
const filtered = ref<BudgetOption[]>([]);
|
const selected = computed(() => items.value.find((i) => i.id === props.modelValue) ?? null);
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
if (!props.projectId) {
|
if (!props.projectId) {
|
||||||
|
|
@ -67,55 +70,13 @@ async function load() {
|
||||||
amount: Number(row.amount ?? 0),
|
amount: Number(row.amount ?? 0),
|
||||||
label: [row.code, row.description].filter(Boolean).join(" — "),
|
label: [row.code, row.description].filter(Boolean).join(" — "),
|
||||||
}));
|
}));
|
||||||
filtered.value = items.value.slice(0, 30);
|
|
||||||
if (props.modelValue) {
|
|
||||||
selected.value = items.value.find((i) => i.id === props.modelValue) ?? null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSearch(event: { query: string }) {
|
function onUpdate(id: number | null) {
|
||||||
const q = (event.query || "").toLowerCase().trim();
|
const option = id == null ? null : items.value.find((i) => i.id === id) ?? null;
|
||||||
filtered.value = items.value
|
|
||||||
.filter((i) => !q || i.label.toLowerCase().includes(q) || i.code.toLowerCase().includes(q))
|
|
||||||
.slice(0, 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSelect(event: { value?: BudgetOption; option?: BudgetOption }) {
|
|
||||||
const option = event.value ?? event.option ?? null;
|
|
||||||
emit("update:modelValue", option?.id ?? null);
|
emit("update:modelValue", option?.id ?? null);
|
||||||
emit("select", option);
|
emit("select", option);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClear() {
|
|
||||||
emit("update:modelValue", null);
|
|
||||||
emit("select", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
function alignOverlay() {
|
|
||||||
nextTick(() => {
|
|
||||||
const input = document.querySelector(".budget-item-picker input") as HTMLElement | null;
|
|
||||||
const overlay = document.querySelector(".budget-item-picker-overlay") as HTMLElement | null;
|
|
||||||
if (!input || !overlay) return;
|
|
||||||
const width = Math.round(input.getBoundingClientRect().width);
|
|
||||||
overlay.style.width = `${width}px`;
|
|
||||||
overlay.style.minWidth = `${width}px`;
|
|
||||||
overlay.style.maxWidth = `${width}px`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => props.projectId, load, { immediate: true });
|
watch(() => props.projectId, load, { immediate: true });
|
||||||
watch(
|
|
||||||
() => props.modelValue,
|
|
||||||
(id) => {
|
|
||||||
if (!id) selected.value = null;
|
|
||||||
else selected.value = items.value.find((i) => i.id === id) ?? null;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.budget-item-picker :deep(.p-autocomplete),
|
|
||||||
.budget-item-picker :deep(.p-autocomplete-input) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue