183 lines
9.0 KiB
Vue
183 lines
9.0 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted, ref } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
import Card from 'primevue/card';
|
|
import Button from 'primevue/button';
|
|
import InputText from 'primevue/inputtext';
|
|
import IconField from 'primevue/iconfield';
|
|
import InputIcon from 'primevue/inputicon';
|
|
import Select from 'primevue/select';
|
|
import Paginator from 'primevue/paginator';
|
|
import { fixedAssetStructuresService } from '../../services/fixedAssetStructuresService';
|
|
import type { FixedAssetStructure, StructureStatus } from '../../types/fixedAssetStructure';
|
|
|
|
const router = useRouter();
|
|
|
|
const loading = ref(false);
|
|
const first = ref(0);
|
|
const rows = ref(6);
|
|
const searchTerm = ref('');
|
|
const selectedStatus = ref<'all' | StructureStatus>('all');
|
|
const structures = ref<FixedAssetStructure[]>([]);
|
|
|
|
const statusOptions = [
|
|
{ label: 'Todos los estatus', value: 'all' },
|
|
{ label: 'Borrador', value: 'BORRADOR' },
|
|
{ label: 'Activa', value: 'ACTIVA' },
|
|
{ label: 'En revision', value: 'EN_REVISION' },
|
|
{ label: 'Inactiva', value: 'INACTIVA' }
|
|
];
|
|
|
|
const statusClasses: Record<StructureStatus, string> = {
|
|
BORRADOR: 'bg-surface-200 text-surface-700',
|
|
ACTIVA: 'bg-emerald-100 text-emerald-700',
|
|
EN_REVISION: 'bg-amber-100 text-amber-700',
|
|
INACTIVA: 'bg-red-100 text-red-700'
|
|
};
|
|
|
|
const formatCurrency = (value: number) =>
|
|
`$${value.toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
|
|
const filteredStructures = computed(() => {
|
|
const query = searchTerm.value.trim().toLowerCase();
|
|
return structures.value.filter((structure) => {
|
|
const matchesQuery = !query
|
|
|| structure.code.toLowerCase().includes(query)
|
|
|| structure.name.toLowerCase().includes(query)
|
|
|| structure.containerSerial.toLowerCase().includes(query);
|
|
const matchesStatus = selectedStatus.value === 'all' || structure.status === selectedStatus.value;
|
|
return matchesQuery && matchesStatus;
|
|
});
|
|
});
|
|
|
|
const paginatedStructures = computed(() =>
|
|
filteredStructures.value.slice(first.value, first.value + rows.value)
|
|
);
|
|
|
|
const loadStructures = async () => {
|
|
loading.value = true;
|
|
structures.value = await fixedAssetStructuresService.getStructures();
|
|
loading.value = false;
|
|
};
|
|
|
|
const onPage = (event: { first: number; rows: number }) => {
|
|
first.value = event.first;
|
|
rows.value = event.rows;
|
|
};
|
|
|
|
const goToCreate = () => router.push('/fixed-assets/structures/create');
|
|
const goToDetails = (id: string) => router.push(`/fixed-assets/structures/${id}`);
|
|
const goToEdit = (id: string) => router.push(`/fixed-assets/structures/${id}/edit`);
|
|
|
|
onMounted(loadStructures);
|
|
</script>
|
|
|
|
<template>
|
|
<section class="space-y-6">
|
|
<div class="flex flex-wrap items-center justify-between gap-3">
|
|
<div>
|
|
<h1 class="text-3xl font-black tracking-tight text-surface-900 dark:text-surface-0">
|
|
Estructuras de Activos Fijos
|
|
</h1>
|
|
<p class="mt-1 text-surface-500 dark:text-surface-400">
|
|
Define, organiza y monitorea activos compuestos.
|
|
</p>
|
|
</div>
|
|
<Button label="Crear estructura" icon="pi pi-plus" @click="goToCreate" />
|
|
</div>
|
|
|
|
<Card class="shadow-sm">
|
|
<template #content>
|
|
<div class="space-y-4">
|
|
<div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
|
<IconField iconPosition="left" class="w-full md:max-w-lg">
|
|
<InputIcon class="pi pi-search" />
|
|
<InputText
|
|
v-model="searchTerm"
|
|
class="w-full"
|
|
placeholder="Buscar por codigo, nombre o numero de serie..."
|
|
/>
|
|
</IconField>
|
|
<Select
|
|
v-model="selectedStatus"
|
|
:options="statusOptions"
|
|
optionLabel="label"
|
|
optionValue="value"
|
|
class="w-full md:w-56"
|
|
/>
|
|
</div>
|
|
|
|
<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">Codigo</th>
|
|
<th class="px-4 py-3">Estructura</th>
|
|
<th class="px-4 py-3">Categoria</th>
|
|
<th class="px-4 py-3">Ubicacion</th>
|
|
<th class="px-4 py-3">Contenidos</th>
|
|
<th class="px-4 py-3">Valor Total</th>
|
|
<th class="px-4 py-3">Estatus</th>
|
|
<th class="px-4 py-3 text-right">Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-if="loading">
|
|
<td colspan="8" class="px-4 py-8 text-center text-sm text-surface-500">
|
|
Cargando estructuras...
|
|
</td>
|
|
</tr>
|
|
<tr
|
|
v-for="structure in paginatedStructures"
|
|
:key="structure.id"
|
|
class="border-t border-surface-200 text-sm dark:border-surface-700"
|
|
>
|
|
<td class="px-4 py-3 font-mono text-xs text-primary">{{ structure.code }}</td>
|
|
<td class="px-4 py-3">
|
|
<p class="font-semibold text-surface-900 dark:text-surface-0">{{ structure.name }}</p>
|
|
<p class="text-xs text-surface-500 dark:text-surface-400">Serie: {{ structure.containerSerial }}</p>
|
|
</td>
|
|
<td class="px-4 py-3">{{ structure.containerCategory }}</td>
|
|
<td class="px-4 py-3">{{ structure.location }}</td>
|
|
<td class="px-4 py-3 text-center font-semibold">{{ structure.contents.length }}</td>
|
|
<td class="px-4 py-3 font-semibold">{{ formatCurrency(structure.containerValue + structure.contents.reduce((sum, item) => sum + item.value, 0)) }}</td>
|
|
<td class="px-4 py-3">
|
|
<span class="inline-flex rounded-full px-2 py-1 text-xs font-semibold" :class="statusClasses[structure.status]">
|
|
{{ structure.status }}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<div class="flex items-center justify-end gap-1">
|
|
<Button icon="pi pi-eye" text rounded size="small" @click="goToDetails(structure.id)" />
|
|
<Button icon="pi pi-pencil" text rounded size="small" @click="goToEdit(structure.id)" />
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr v-if="!loading && paginatedStructures.length === 0">
|
|
<td colspan="8" class="px-4 py-8 text-center text-sm text-surface-500 dark:text-surface-400">
|
|
No hay estructuras para mostrar.
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
|
|
<p class="text-sm text-surface-500 dark:text-surface-400">
|
|
Mostrando {{ paginatedStructures.length }} de {{ filteredStructures.length }} estructuras
|
|
</p>
|
|
<Paginator
|
|
:first="first"
|
|
:rows="rows"
|
|
:totalRecords="filteredStructures.length"
|
|
:rowsPerPageOptions="[6, 12, 18]"
|
|
template="PrevPageLink PageLinks NextPageLink"
|
|
@page="onPage"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</Card>
|
|
</section>
|
|
</template>
|