Cambios en backend: - Actualizado ClassResponseDTO con campos de tenant y timestamps - Agregados métodos estáticos en servicio (get_all, get_by_id, create, update, delete) - Reemplazados endpoints de rutas con inicialización de TenantCRUDRoutes - Corregida ruta de importación del modelo Company en security.py - Actualizados nombres de campos en DTO para coincidir con modelo (description_es/en) - Agregada validación para constraint de foreign key de material_key - Agregada validación de class_code duplicado en método create - Agregado TimestampMixin al modelo Class - Creada migración de Alembic para agregar columnas timestamp a tabla classes - Corregido orden de parámetros en firma del método update Cambios en frontend: - Actualizados componentes de UI para módulo de clases
178 lines
5.1 KiB
TypeScript
178 lines
5.1 KiB
TypeScript
import type { ColumnDef } from "@tanstack/table-core";
|
|
import { renderComponent, renderSnippet } from "$lib/components/ui/data-table/index.js";
|
|
import { createRawSnippet } from "svelte";
|
|
import DataTableActions from "./data-table-actions.svelte";
|
|
import type { A76Class } from "$lib/api/dashboard/a76/classes";
|
|
|
|
/**
|
|
* Formatea una fecha
|
|
*/
|
|
function formatDate(date?: string | null): string {
|
|
if (!date) return '-';
|
|
return new Date(date).toLocaleDateString('es-MX', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Obtiene el color del badge según el tipo de revisión física
|
|
*/
|
|
function getReviewColor(physicalReview: number): string {
|
|
return physicalReview === 1
|
|
? 'bg-yellow-100 text-yellow-800'
|
|
: 'bg-green-100 text-green-800';
|
|
}
|
|
|
|
export function createColumns(onSuccess?: () => void): ColumnDef<A76Class>[] {
|
|
return [
|
|
{
|
|
accessorKey: "id",
|
|
header: "ID",
|
|
cell: ({ row }) => {
|
|
const idSnippet = createRawSnippet<[{ id: number }]>((getId) => {
|
|
const { id } = getId();
|
|
return {
|
|
render: () =>
|
|
`<div class="font-medium">#${id}</div>`
|
|
};
|
|
});
|
|
return renderSnippet(idSnippet, { id: row.original.id });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "class_code",
|
|
header: "Código de Clase",
|
|
cell: ({ row }) => {
|
|
const codeSnippet = createRawSnippet<[{ code: string }]>((getCode) => {
|
|
const { code } = getCode();
|
|
return {
|
|
render: () =>
|
|
`<code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold">${code}</code>`
|
|
};
|
|
});
|
|
return renderSnippet(codeSnippet, { code: row.original.class_code });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "description_es",
|
|
header: "Descripción",
|
|
cell: ({ row }) => {
|
|
const description = row.original.description_es || row.original.description_en || '-';
|
|
const descSnippet = createRawSnippet<[{ desc: string }]>((getDesc) => {
|
|
const { desc } = getDesc();
|
|
return {
|
|
render: () =>
|
|
`<div class="max-w-xs truncate" title="${desc}">${desc}</div>`
|
|
};
|
|
});
|
|
return renderSnippet(descSnippet, { desc: description });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "fraction",
|
|
header: "Fracción",
|
|
cell: ({ row }) => {
|
|
const fractionSnippet = createRawSnippet<[{ fraction: string }]>((getFraction) => {
|
|
const { fraction } = getFraction();
|
|
return {
|
|
render: () =>
|
|
`<div class="font-mono text-sm">${fraction}</div>`
|
|
};
|
|
});
|
|
return renderSnippet(fractionSnippet, { fraction: row.original.fraction });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "us_fraction",
|
|
header: "Fracción US",
|
|
cell: ({ row }) => {
|
|
const usFractionSnippet = createRawSnippet<[{ usFraction: string }]>((getUsFraction) => {
|
|
const { usFraction } = getUsFraction();
|
|
return {
|
|
render: () =>
|
|
`<div class="font-mono text-sm">${usFraction}</div>`
|
|
};
|
|
});
|
|
return renderSnippet(usFractionSnippet, { usFraction: row.original.us_fraction });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "unit_of_measure",
|
|
header: "Unidad",
|
|
cell: ({ row }) => {
|
|
const unitSnippet = createRawSnippet<[{ unit: string }]>((getUnit) => {
|
|
const { unit } = getUnit();
|
|
return {
|
|
render: () =>
|
|
`<span class="inline-flex items-center rounded-full bg-blue-100 text-blue-800 px-2.5 py-0.5 text-xs font-medium">
|
|
${unit}
|
|
</span>`
|
|
};
|
|
});
|
|
return renderSnippet(unitSnippet, { unit: row.original.unit_of_measure });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "physical_review",
|
|
header: "Rev. Física",
|
|
cell: ({ row }) => {
|
|
const review = row.original.physical_review;
|
|
const colorClass = getReviewColor(review);
|
|
const label = review === 1 ? 'Sí' : 'No';
|
|
|
|
const reviewSnippet = createRawSnippet<[{ label: string; colorClass: string }]>((getReview) => {
|
|
const { label, colorClass } = getReview();
|
|
return {
|
|
render: () =>
|
|
`<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClass}">
|
|
${label}
|
|
</span>`
|
|
};
|
|
});
|
|
return renderSnippet(reviewSnippet, { label, colorClass });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "client_id",
|
|
header: "Cliente",
|
|
cell: ({ row }) => {
|
|
const clientSnippet = createRawSnippet<[{ clientId: number }]>((getClient) => {
|
|
const { clientId } = getClient();
|
|
return {
|
|
render: () =>
|
|
`<div class="text-sm">Cliente #${clientId}</div>`
|
|
};
|
|
});
|
|
return renderSnippet(clientSnippet, { clientId: row.original.client_id });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "created_at",
|
|
header: "Fecha de Creación",
|
|
cell: ({ row }) => {
|
|
const dateSnippet = createRawSnippet<[{ date: string }]>((getDate) => {
|
|
const { date } = getDate();
|
|
return {
|
|
render: () =>
|
|
`<div class="text-sm text-muted-foreground">${date}</div>`
|
|
};
|
|
});
|
|
return renderSnippet(dateSnippet, { date: formatDate(row.original.created_at) });
|
|
}
|
|
},
|
|
{
|
|
id: "actions",
|
|
cell: ({ row }) => {
|
|
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
|
}
|
|
}
|
|
];
|
|
}
|
|
|
|
// Mantener compatibilidad hacia atrás
|
|
export const columns = createColumns();
|