182 lines
7.5 KiB
Vue
182 lines
7.5 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref } from 'vue';
|
|
import Card from 'primevue/card';
|
|
import Button from 'primevue/button';
|
|
import InputText from 'primevue/inputtext';
|
|
import type { ContentCondition, StructureContentAsset } from '../../types/fixedAssetStructure';
|
|
|
|
interface Props {
|
|
modelValue: StructureContentAsset[];
|
|
readOnly?: boolean;
|
|
}
|
|
|
|
const props = defineProps<Props>();
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'update:modelValue', value: StructureContentAsset[]): void;
|
|
}>();
|
|
|
|
const serialQuery = ref('');
|
|
|
|
const availableAssets: StructureContentAsset[] = [
|
|
{
|
|
id: 'ACT-5001',
|
|
name: 'Monitor Curvo 34"',
|
|
category: 'Perifericos',
|
|
serial: 'MON-CV-4491',
|
|
condition: 'EXCELENTE',
|
|
value: 9850
|
|
},
|
|
{
|
|
id: 'ACT-5002',
|
|
name: 'Docking Station USB-C',
|
|
category: 'Perifericos',
|
|
serial: 'DCK-USB-112',
|
|
condition: 'BUENO',
|
|
value: 3150
|
|
},
|
|
{
|
|
id: 'ACT-5003',
|
|
name: 'Gabinete de Red 24U',
|
|
category: 'Infraestructura TI',
|
|
serial: 'NET-CAB-240',
|
|
condition: 'REGULAR',
|
|
value: 6250
|
|
}
|
|
];
|
|
|
|
const conditionClasses: Record<ContentCondition, string> = {
|
|
EXCELENTE: 'bg-emerald-100 text-emerald-700',
|
|
BUENO: 'bg-amber-100 text-amber-700',
|
|
REGULAR: 'bg-orange-100 text-orange-700'
|
|
};
|
|
|
|
const totalValue = computed(() =>
|
|
props.modelValue.reduce((sum, item) => sum + item.value, 0)
|
|
);
|
|
|
|
const formatCurrency = (value: number) =>
|
|
`$${value.toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
|
|
const addAsset = () => {
|
|
const query = serialQuery.value.trim().toLowerCase();
|
|
if (!query) return;
|
|
|
|
const candidate = availableAssets.find((asset) =>
|
|
asset.serial.toLowerCase().includes(query) || asset.id.toLowerCase().includes(query)
|
|
);
|
|
|
|
if (!candidate) return;
|
|
const exists = props.modelValue.some((item) => item.id === candidate.id);
|
|
if (exists) return;
|
|
|
|
emit('update:modelValue', [...props.modelValue, candidate]);
|
|
serialQuery.value = '';
|
|
};
|
|
|
|
const removeAsset = (assetId: string) => {
|
|
emit('update:modelValue', props.modelValue.filter((item) => item.id !== assetId));
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<Card class="shadow-sm">
|
|
<template #title>
|
|
<div class="flex flex-wrap items-center justify-between gap-2 text-xl">
|
|
<div class="flex items-center gap-2">
|
|
<i class="pi pi-list-check text-primary"></i>
|
|
<span>Inventario de Activos Contenidos</span>
|
|
</div>
|
|
<span class="rounded-full bg-surface-100 px-3 py-1 text-xs font-semibold text-surface-600 dark:bg-surface-800 dark:text-surface-200">
|
|
{{ modelValue.length }} ELEMENTOS
|
|
</span>
|
|
</div>
|
|
</template>
|
|
<template #content>
|
|
<div class="space-y-4">
|
|
<div class="flex flex-col gap-2 md:flex-row">
|
|
<InputText
|
|
v-model="serialQuery"
|
|
class="w-full"
|
|
:disabled="readOnly"
|
|
placeholder="Escriba o escanee numero de serie para anadir..."
|
|
@keyup.enter="addAsset"
|
|
/>
|
|
<Button
|
|
label="Agregar"
|
|
icon="pi pi-plus"
|
|
class="min-w-40"
|
|
:disabled="readOnly"
|
|
@click="addAsset"
|
|
/>
|
|
</div>
|
|
<p class="text-xs text-surface-500 dark:text-surface-400">
|
|
Tip: puede ingresar serie o ID del activo para anexarlo rapidamente.
|
|
</p>
|
|
|
|
<div class="overflow-x-auto rounded-xl border border-surface-200 dark:border-surface-700">
|
|
<table class="min-w-full border-collapse">
|
|
<thead>
|
|
<tr class="bg-surface-50 text-left text-xs font-semibold uppercase tracking-wide text-surface-500 dark:bg-surface-800 dark:text-surface-300">
|
|
<th class="px-4 py-3">Imagen</th>
|
|
<th class="px-4 py-3">Activo</th>
|
|
<th class="px-4 py-3">Serie</th>
|
|
<th class="px-4 py-3">Condicion</th>
|
|
<th class="px-4 py-3">Valor</th>
|
|
<th class="px-4 py-3 text-right">Accion</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr
|
|
v-for="asset in modelValue"
|
|
:key="asset.id"
|
|
class="border-t border-surface-200 text-sm dark:border-surface-700"
|
|
>
|
|
<td class="px-4 py-3">
|
|
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-surface-100 dark:bg-surface-800">
|
|
<i class="pi pi-image text-surface-400"></i>
|
|
</div>
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<p class="font-semibold text-surface-900 dark:text-surface-0">{{ asset.name }}</p>
|
|
<p class="text-xs text-surface-500 dark:text-surface-400">{{ asset.category }}</p>
|
|
</td>
|
|
<td class="px-4 py-3 font-mono text-xs text-surface-600 dark:text-surface-300">{{ asset.serial }}</td>
|
|
<td class="px-4 py-3">
|
|
<span class="inline-flex rounded-full px-2 py-1 text-xs font-semibold" :class="conditionClasses[asset.condition]">
|
|
{{ asset.condition }}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-3 font-semibold text-surface-900 dark:text-surface-0">
|
|
{{ formatCurrency(asset.value) }}
|
|
</td>
|
|
<td class="px-4 py-3 text-right">
|
|
<Button
|
|
icon="pi pi-trash"
|
|
text
|
|
rounded
|
|
severity="danger"
|
|
size="small"
|
|
:disabled="readOnly"
|
|
@click="removeAsset(asset.id)"
|
|
/>
|
|
</td>
|
|
</tr>
|
|
<tr v-if="modelValue.length === 0">
|
|
<td colspan="6" class="px-4 py-8 text-center text-sm text-surface-500 dark:text-surface-400">
|
|
No hay activos vinculados a la estructura.
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="text-right">
|
|
<span class="text-sm text-surface-500 dark:text-surface-400">Valor acumulado de contenidos: </span>
|
|
<strong class="text-lg text-surface-900 dark:text-surface-0">{{ formatCurrency(totalValue) }}</strong>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</Card>
|
|
</template>
|