- Added InvoiceTopFieldsFormData to handle additional invoice fields. - Updated saveInvoice function to validate and process new fields. - Integrated transport modes fetching in the invoice edit page. - Refactored financials and compliance data handling to accommodate new structure. - Improved user feedback with toast notifications instead of alerts.
251 lines
7.3 KiB
TypeScript
251 lines
7.3 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 { Invoice } from '$lib/api/dashboard/a76/invoices';
|
|
|
|
/**
|
|
* Formatea un número como moneda MXN
|
|
*/
|
|
function formatCurrencyMXN(value?: number | null): string {
|
|
if (value === null || value === undefined) return '-';
|
|
return new Intl.NumberFormat('es-MX', {
|
|
style: 'currency',
|
|
currency: 'MXN',
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
}).format(value);
|
|
}
|
|
|
|
/**
|
|
* Formatea un número como moneda USD
|
|
*/
|
|
function formatCurrencyUSD(value?: number | null): string {
|
|
if (value === null || value === undefined) return '-';
|
|
return new Intl.NumberFormat('es-MX', {
|
|
style: 'currency',
|
|
currency: 'USD',
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
}).format(value);
|
|
}
|
|
|
|
/**
|
|
* 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'
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Obtiene el color del badge según el tipo de operación
|
|
*/
|
|
function getOperationTypeColor(type?: string | null): string {
|
|
if (!type) return 'bg-gray-100 text-gray-800';
|
|
return type === 'imp' ? 'bg-blue-100 text-blue-800' : 'bg-green-100 text-green-800';
|
|
}
|
|
|
|
/**
|
|
* Obtiene el color del badge según el semáforo fiscal
|
|
*/
|
|
function getTrafficLightColor(status?: string | null): string {
|
|
if (!status) return 'bg-gray-100 text-gray-800';
|
|
|
|
const statusLower = status.toLowerCase();
|
|
if (statusLower.includes('verde') || statusLower === 'green') return 'bg-green-100 text-green-800';
|
|
if (statusLower.includes('amarillo') || statusLower === 'yellow') return 'bg-yellow-100 text-yellow-800';
|
|
if (statusLower.includes('rojo') || statusLower === 'red') return 'bg-red-100 text-red-800';
|
|
|
|
return 'bg-gray-100 text-gray-800';
|
|
}
|
|
|
|
export function createColumns(onSuccess?: () => void): ColumnDef<Invoice>[] {
|
|
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: "operation_type",
|
|
header: "Operación",
|
|
cell: ({ row }) => {
|
|
const type = row.original.operation_type;
|
|
const colorClass = getOperationTypeColor(type);
|
|
const label = type === 'imp' ? 'IMP' : type === 'exp' ? 'EXP' : 'N/A';
|
|
|
|
const typeSnippet = createRawSnippet<[{ label: string; colorClass: string }]>((getType) => {
|
|
const { label, colorClass } = getType();
|
|
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(typeSnippet, { label, colorClass });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "invoice_type",
|
|
header: "Tipo",
|
|
cell: ({ row }) => {
|
|
const typeSnippet = createRawSnippet<[{ type?: string | null }]>((getType) => {
|
|
const { type } = getType();
|
|
return {
|
|
render: () =>
|
|
`<div class="capitalize">${type || '-'}</div>`
|
|
};
|
|
});
|
|
return renderSnippet(typeSnippet, { type: row.original.invoice_type });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "invoice_number",
|
|
header: "Número de Factura",
|
|
cell: ({ row }) => {
|
|
const numberSnippet = createRawSnippet<[{ number?: string | null }]>((getNumber) => {
|
|
const { number } = getNumber();
|
|
return {
|
|
render: () =>
|
|
`<code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold">${number || 'N/A'}</code>`
|
|
};
|
|
});
|
|
return renderSnippet(numberSnippet, { number: row.original.invoice_number });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "project_number",
|
|
header: "Proyecto",
|
|
cell: ({ row }) => {
|
|
const projectSnippet = createRawSnippet<[{ project?: string | null }]>((getProject) => {
|
|
const { project } = getProject();
|
|
return {
|
|
render: () =>
|
|
`<div>${project || '-'}</div>`
|
|
};
|
|
});
|
|
return renderSnippet(projectSnippet, { project: row.original.project_number });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "compliance_mx.pedimento",
|
|
header: "Pedimento",
|
|
cell: ({ row }) => {
|
|
const pedimento = row.original.compliance_mx?.pedimento;
|
|
|
|
const pedimentoSnippet = createRawSnippet<[{ pedimento?: string | null }]>((getPedimento) => {
|
|
const { pedimento } = getPedimento();
|
|
return {
|
|
render: () =>
|
|
`<div class="text-sm">${pedimento || '-'}</div>`
|
|
};
|
|
});
|
|
return renderSnippet(pedimentoSnippet, { pedimento });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "financials.value_mn",
|
|
header: () => {
|
|
const headerSnippet = createRawSnippet(() => {
|
|
return {
|
|
render: () => `<div class="text-right">Valor MN</div>`
|
|
};
|
|
});
|
|
return renderSnippet(headerSnippet, {});
|
|
},
|
|
cell: ({ row }) => {
|
|
const valueMN = row.original.financials?.value_mn;
|
|
|
|
const valueSnippet = createRawSnippet<[{ value: string }]>((getValue) => {
|
|
const { value } = getValue();
|
|
return {
|
|
render: () =>
|
|
`<div class="text-right font-medium">${value}</div>`
|
|
};
|
|
});
|
|
return renderSnippet(valueSnippet, { value: formatCurrencyMXN(valueMN) });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "financials.value_me",
|
|
header: () => {
|
|
const headerSnippet = createRawSnippet(() => {
|
|
return {
|
|
render: () => `<div class="text-right">Valor ME</div>`
|
|
};
|
|
});
|
|
return renderSnippet(headerSnippet, {});
|
|
},
|
|
cell: ({ row }) => {
|
|
const valueME = row.original.financials?.value_me;
|
|
|
|
const valueSnippet = createRawSnippet<[{ value: string }]>((getValue) => {
|
|
const { value } = getValue();
|
|
return {
|
|
render: () =>
|
|
`<div class="text-right font-medium">${value}</div>`
|
|
};
|
|
});
|
|
return renderSnippet(valueSnippet, { value: formatCurrencyUSD(valueME) });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "traffic_light_status",
|
|
header: "Semáforo",
|
|
cell: ({ row }) => {
|
|
const status = row.original.traffic_light_status;
|
|
const colorClass = getTrafficLightColor(status);
|
|
|
|
const statusSnippet = createRawSnippet<[{ status?: string | null; colorClass: string }]>((getStatus) => {
|
|
const { status, colorClass } = getStatus();
|
|
return {
|
|
render: () =>
|
|
`<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClass}">
|
|
${status || '-'}
|
|
</span>`
|
|
};
|
|
});
|
|
return renderSnippet(statusSnippet, { status, colorClass });
|
|
}
|
|
},
|
|
{
|
|
accessorKey: "invoice_date",
|
|
header: "Fecha Factura",
|
|
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.invoice_date) });
|
|
}
|
|
},
|
|
{
|
|
id: "actions",
|
|
cell: ({ row }) => {
|
|
return renderComponent(DataTableActions, { invoice: row.original, onSuccess });
|
|
}
|
|
}
|
|
];
|
|
}
|
|
|
|
// Mantener compatibilidad hacia atrás
|
|
export const columns = createColumns();
|