Merge pull request 'fix/catalogos-generales' (#303) from fix/catalogos-generales into development

Reviewed-on: ADUANASOFT/anexo76#303
This commit is contained in:
2026-04-22 23:15:52 +00:00
84 changed files with 2748 additions and 1994 deletions

View File

@@ -43,7 +43,7 @@ export async function getClassificationConcepts(
}
export async function getClassificationConcept(id: number, companyId: number): Promise<ApiResponse<ClassificationConcept>> {
return await api.get(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`);
return await api.get(`/v1/a76/classification-concepts/${id}?company_id=${companyId}`);
}
export async function createClassificationConcept(
@@ -62,5 +62,5 @@ export async function updateClassificationConcept(
}
export async function deleteClassificationConcept(id: number, companyId: number): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`);
return await api.delete(`/v1/a76/classification-concepts/${id}?company_id=${companyId}`);
}

View File

@@ -426,7 +426,7 @@ export async function getCompanies(
page_size: pageSize.toString(),
...filters
});
return await api.get(`/v1/a76/company/?${queryParams.toString()}`);
return await api.get(`/v1/a76/company?${queryParams.toString()}`);
}
export async function getCompany(id: number): Promise<ApiResponse<Company>> {
@@ -434,7 +434,7 @@ export async function getCompany(id: number): Promise<ApiResponse<Company>> {
}
export async function createCompany(data: CompanyCreate): Promise<ApiResponse<Company>> {
return await api.post(`/v1/a76/company/`, data);
return await api.post(`/v1/a76/company`, data);
}
export async function updateCompany(id: number, data: CompanyUpdate): Promise<ApiResponse<Company>> {
@@ -442,7 +442,7 @@ export async function updateCompany(id: number, data: CompanyUpdate): Promise<Ap
}
export async function deleteCompany(id: number): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/company/${id}/`);
return await api.delete(`/v1/a76/company/${id}`);
}
export async function uploadCompanyLogo(id: number, file: File): Promise<ApiResponse<{ message: string; logo_path: string; company_id: number }>> {

View File

@@ -62,7 +62,7 @@ export async function getConcepts(
export async function getConcept(id: number, companyId: number): Promise<ApiResponse<Concept>> {
return await api.get(`/v1/a76/concepts/${id}/?company_id=${companyId}`);
return await api.get(`/v1/a76/concepts/${id}?company_id=${companyId}`);
}
@@ -77,5 +77,5 @@ export async function updateConcept(id: number, data: ConceptUpdate, companyId:
export async function deleteConcept(id: number, companyId: number): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/concepts/${id}/?company_id=${companyId}`);
return await api.delete(`/v1/a76/concepts/${id}?company_id=${companyId}`);
}

View File

@@ -47,7 +47,7 @@ export async function getCustomsBrokerConcepts(
}
export async function getCustomsBrokerConcept(id: number, companyId: number): Promise<ApiResponse<CustomsBrokerConcept>> {
return await api.get(`/v1/a76/customs-broker-concepts/${id}/?company_id=${companyId}`);
return await api.get(`/v1/a76/customs-broker-concepts/${id}?company_id=${companyId}`);
}
export async function createCustomsBrokerConcept(data: CustomsBrokerConceptCreate, companyId: number): Promise<ApiResponse<CustomsBrokerConcept>> {

View File

@@ -1,5 +1,4 @@
import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
export interface ElectronicNotice {
id: number;
@@ -52,7 +51,7 @@ export async function getElectronicNotices(
pageSize: number = 50,
filters: Record<string, any> = {},
companyId?: number
): Promise<ApiResponse<ElectronicNoticeListResponse>> {
): Promise<ElectronicNoticeListResponse> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
@@ -62,7 +61,13 @@ export async function getElectronicNotices(
params.append('company_id', companyId.toString());
}
const response = await api.get(`/v1/a76/electronic-notices/?${params.toString()}`);
const response = await api.get<ElectronicNoticeListResponse>(`/v1/a76/electronic-notices/?${params.toString()}`);
if (response.error) {
throw new Error(response.error);
}
if (!response.data) {
throw new Error('No se pudieron obtener los avisos electrónicos');
}
return response.data;
}
@@ -71,20 +76,41 @@ export async function getElectronicNotice(id: number, companyId?: number): Promi
if (companyId) {
params.append('company_id', companyId.toString());
}
const response = await api.get(`/v1/a76/electronic-notices/${id}?${params.toString()}`);
const response = await api.get<ElectronicNotice>(`/v1/a76/electronic-notices/${id}?${params.toString()}`);
if (response.error) {
throw new Error(response.error);
}
if (!response.data) {
throw new Error('No se pudo obtener el aviso electrónico');
}
return response.data;
}
export async function createElectronicNotice(data: ElectronicNoticeCreate, companyId: number): Promise<ElectronicNotice> {
const response = await api.post(`/v1/a76/electronic-notices/?company_id=${companyId}`, data);
const response = await api.post<ElectronicNotice>(`/v1/a76/electronic-notices/?company_id=${companyId}`, data);
if (response.error) {
throw new Error(response.error);
}
if (!response.data) {
throw new Error('No se pudo crear el aviso electrónico');
}
return response.data;
}
export async function updateElectronicNotice(id: number, data: ElectronicNoticeUpdate, companyId: number): Promise<ElectronicNotice> {
const response = await api.put(`/v1/a76/electronic-notices/${id}?company_id=${companyId}`, data);
const response = await api.put<ElectronicNotice>(`/v1/a76/electronic-notices/${id}/?company_id=${companyId}`, data);
if (response.error) {
throw new Error(response.error);
}
if (!response.data) {
throw new Error('No se pudo actualizar el aviso electrónico');
}
return response.data;
}
export async function deleteElectronicNotice(id: number, companyId: number): Promise<void> {
await api.delete(`/v1/a76/electronic-notices/${id}?company_id=${companyId}`);
const response = await api.delete(`/v1/a76/electronic-notices/${id}?company_id=${companyId}`);
if (response.error) {
throw new Error(response.error);
}
}

View File

@@ -93,7 +93,7 @@ export async function createErrorClassification(data: ErrorClassificationCreate,
export async function updateErrorClassification(id: number, data: ErrorClassificationUpdate, companyId: number): Promise<ErrorClassification> {
const params = new URLSearchParams({ company_id: companyId.toString() });
const response = await api.put(`/v1/a76/error-catalogs/classifications/${id}?${params.toString()}`, data);
const response = await api.put(`/v1/a76/error-catalogs/classifications/${id}/?${params.toString()}`, data);
return response.data;
}
@@ -135,7 +135,7 @@ export async function createErrorCatalog(data: ErrorCatalogCreate, companyId: nu
export async function updateErrorCatalog(id: number, data: ErrorCatalogUpdate, companyId: number): Promise<ErrorCatalog> {
const params = new URLSearchParams({ company_id: companyId.toString() });
const response = await api.put(`/v1/a76/error-catalogs/${id}?${params.toString()}`, data);
const response = await api.put(`/v1/a76/error-catalogs/${id}/?${params.toString()}`, data);
return response.data;
}

View File

@@ -97,7 +97,7 @@ export async function deleteIdentifier(
id: number,
companyId: number
): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/identifiers/${id}/?company_id=${companyId}`);
return await api.delete(`/v1/a76/identifiers/${id}?company_id=${companyId}`);
}
/**
@@ -122,5 +122,5 @@ export async function deleteIdentifierDetail(
id: number,
companyId: number
): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/identifiers/details/${id}/?company_id=${companyId}`);
return await api.delete(`/v1/a76/identifiers/details/${id}?company_id=${companyId}`);
}

View File

@@ -45,7 +45,7 @@ export async function getINPCs(
}
export async function getINPC(id: number, companyId: number): Promise<ApiResponse<INPC>> {
return await api.get(`/v1/a76/inpc/${id}/?company_id=${companyId}`);
return await api.get(`/v1/a76/inpc/${id}?company_id=${companyId}`);
}
export async function createINPC(
@@ -64,5 +64,5 @@ export async function updateINPC(
}
export async function deleteINPC(id: number, companyId: number): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/inpc/${id}/?company_id=${companyId}`);
return await api.delete(`/v1/a76/inpc/${id}?company_id=${companyId}`);
}

View File

@@ -42,7 +42,7 @@ export async function getLegends(
}
export async function getLegend(id: number, companyId: number): Promise<ApiResponse<Legend>> {
return await api.get(`/v1/a76/legends/${id}/?company_id=${companyId}`);
return await api.get(`/v1/a76/legends/${id}?company_id=${companyId}`);
}
export async function createLegend(
@@ -61,5 +61,5 @@ export async function updateLegend(
}
export async function deleteLegend(id: number, companyId: number): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/legends/${id}/?company_id=${companyId}`);
return await api.delete(`/v1/a76/legends/${id}?company_id=${companyId}`);
}

View File

@@ -75,7 +75,7 @@ export async function updateMultiCurrencyType(
): Promise<ApiResponse<MultiCurrencyType>> {
const params = new URLSearchParams({ company_id: companyId.toString() });
return api.put<MultiCurrencyType>(
`/v1/a76/multi-currency-types/${multiCurrencyTypeId}?${params.toString()}`,
`/v1/a76/multi-currency-types/${multiCurrencyTypeId}/?${params.toString()}`,
data
);
}

View File

@@ -57,7 +57,7 @@ export async function getPackages(
export async function getPackage(id: number, companyId: number): Promise<ApiResponse<Package>> {
return await api.get(`/v1/a76/packages/${id}/?company_id=${companyId}`);
return await api.get(`/v1/a76/packages/${id}?company_id=${companyId}`);
}
export async function createPackage(

View File

@@ -37,6 +37,10 @@ export interface PortUpdate {
export interface PortListResponse {
items: Port[];
total: number;
page?: number;
page_size?: number;
pageSize?: number;
pages?: number;
}
class PortsApi {

View File

@@ -65,7 +65,7 @@ export async function createPrevalidator(data: PrevalidatorCreate, companyId: nu
}
export async function updatePrevalidator(id: number, data: PrevalidatorUpdate, companyId: number): Promise<Prevalidator> {
const response = await api.put(`/v1/a76/prevalidators/${id}?company_id=${companyId}`, data);
const response = await api.put(`/v1/a76/prevalidators/${id}/?company_id=${companyId}`, data);
return response.data;
}

View File

@@ -53,7 +53,7 @@ export async function getSignatures(
export async function getSignature(id: number, companyId: number): Promise<Signature> {
const response = await api.get(`/v1/a76/signatures/${id}/?company_id=${companyId}`);
const response = await api.get(`/v1/a76/signatures/${id}?company_id=${companyId}`);
if (response.error) throw new Error(response.error);
return response.data;
}
@@ -79,6 +79,6 @@ export async function updateSignature(
}
export async function deleteSignature(id: number, companyId: number): Promise<void> {
const response = await api.delete(`/v1/a76/signatures/${id}/?company_id=${companyId}`);
const response = await api.delete(`/v1/a76/signatures/${id}?company_id=${companyId}`);
if (response.error) throw new Error(response.error);
}

View File

@@ -1,5 +1,4 @@
import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
export interface UnitConversion {
id: number;
@@ -35,7 +34,7 @@ export async function getUnitConversions(
pageSize: number = 50,
companyId: number, // 👈 Obligatorio
filters: Record<string, any> = {}
): Promise<ApiResponse<UnitConversionListResponse>> {
): Promise<UnitConversionListResponse> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
@@ -45,13 +44,16 @@ export async function getUnitConversions(
// Agregamos /v1 y prefijo.
// NOTA: Revisa si en tu router definiste "unit_conversions" o "unit-conversions"
const response = await api.get(`/v1/a76/unit-conversions/?${params.toString()}`);
const response = await api.get<UnitConversionListResponse>(`/v1/a76/unit-conversions/?${params.toString()}`);
if (response.error) throw new Error(response.error);
if (!response.data) throw new Error('No se pudieron obtener las conversiones de unidades');
return response.data;
}
export async function getUnitConversion(id: number, companyId: number): Promise<UnitConversion> {
const response = await api.get(`/v1/a76/unit-conversions/${id}/?company_id=${companyId}`);
const response = await api.get<UnitConversion>(`/v1/a76/unit-conversions/${id}?company_id=${companyId}`);
if (response.error) throw new Error(response.error);
if (!response.data) throw new Error('No se pudo obtener la conversión de unidades');
return response.data;
}
@@ -59,8 +61,9 @@ export async function createUnitConversion(
data: UnitConversionCreate,
companyId: number
): Promise<UnitConversion> {
const response = await api.post(`/v1/a76/unit-conversions/?company_id=${companyId}`, data);
const response = await api.post<UnitConversion>(`/v1/a76/unit-conversions/?company_id=${companyId}`, data);
if (response.error) throw new Error(response.error);
if (!response.data) throw new Error('No se pudo crear la conversión de unidades');
return response.data;
}
@@ -70,8 +73,9 @@ export async function updateUnitConversion(
data: UnitConversionUpdate,
companyId: number
): Promise<UnitConversion> {
const response = await api.put(`/v1/a76/unit-conversions/${id}/?company_id=${companyId}`, data);
const response = await api.put<UnitConversion>(`/v1/a76/unit-conversions/${id}/?company_id=${companyId}`, data);
if (response.error) throw new Error(response.error);
if (!response.data) throw new Error('No se pudo actualizar la conversión de unidades');
return response.data;
}

View File

@@ -52,7 +52,7 @@ export async function updateUnitOfMeasureACE(id: number, data: UnitOfMeasureACEU
}
export async function deleteUnitOfMeasureACE(id: number, companyId: number): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/units-of-measure/ace/${id}/?company_id=${companyId}`);
return await api.delete(`/v1/a76/units-of-measure/ace/${id}?company_id=${companyId}`);
}
// --- OMA ---
@@ -106,7 +106,7 @@ export async function updateUnitOfMeasureOMA(id: number, data: UnitOfMeasureOMAU
}
export async function deleteUnitOfMeasureOMA(id: number, companyId: number): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/units-of-measure/oma/${id}/?company_id=${companyId}`);
return await api.delete(`/v1/a76/units-of-measure/oma/${id}?company_id=${companyId}`);
}
// --- American ---
@@ -160,7 +160,7 @@ export async function updateUnitOfMeasureAmerican(id: number, data: UnitOfMeasur
}
export async function deleteUnitOfMeasureAmerican(id: number, companyId: number): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/units-of-measure/american/${id}/?company_id=${companyId}`);
return await api.delete(`/v1/a76/units-of-measure/american/${id}?company_id=${companyId}`);
}
// --- General ---
@@ -271,7 +271,7 @@ export async function updateUnitOfMeasureCustoms(id: number, data: UnitOfMeasure
}
export async function deleteUnitOfMeasureCustoms(id: number, companyId: number): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/units-of-measure/customs/${id}/?company_id=${companyId}`);
return await api.delete(`/v1/a76/units-of-measure/customs/${id}?company_id=${companyId}`);
}
export interface UnitOfMeasure {

View File

@@ -1,9 +1,7 @@
import type { ColumnDef } from '@tanstack/table-core';
import type { ExchangeRate } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
export function createColumns(onSuccess?: () => void): ColumnDef<ExchangeRate>[] {
export function createColumns(): ColumnDef<ExchangeRate>[] {
return [
{
accessorKey: 'date',
@@ -33,16 +31,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ExchangeRate>[]
accessorKey: 'foreign_currency',
header: 'Moneda Extranjera',
cell: ({ row }) => row.original.foreign_currency ?? 'N/A'
},
{
id: 'actions',
header: 'Acciones',
cell: ({ row }) => {
return renderComponent(DataTableActions, {
item: row.original,
onSuccess
});
}
}
];
}

View File

@@ -1,9 +1,7 @@
import type { ClassificationConcept } from '$lib/api/dashboard/a76/general_catalogs/classification-concepts';
import type { ColumnDef } from '@tanstack/table-core';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
export function createColumns(onSuccess?: () => void): ColumnDef<ClassificationConcept>[] {
export function createColumns(): ColumnDef<ClassificationConcept>[] {
return [
{
accessorKey: 'classification',
@@ -14,16 +12,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ClassificationC
accessorKey: 'description',
header: 'Descripción',
cell: ({ row }) => row.original.description || '-'
},
{
id: 'actions',
header: 'Acciones',
cell: ({ row }) => {
return renderComponent(DataTableActions, {
item: row.original,
onSuccess
});
}
}
];
}

View File

@@ -1,85 +0,0 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { deleteClassificationConcept, type ClassificationConcept } from "$lib/api/dashboard/a76/general_catalogs/classification-concepts";
import { companyStore } from "$lib/stores/company.svelte";
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
import CreateEditDialog from "$lib/components/dashboard/general_catalogs/classification/create-edit-dialog.svelte";
let {
item,
onSuccess
}: {
item: ClassificationConcept;
onSuccess?: () => void;
} = $props();
let loading = $state(false);
let error = $state<string | null>(null);
let dialogOpen = $state(false);
async function handleDelete() {
if (!confirm(`¿Estás seguro de eliminar la clasificación "${item.classification}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
return;
}
if (!companyStore.activeCompany) {
return;
}
loading = true;
error = null;
try {
const response = await deleteClassificationConcept(item.id, companyStore.activeCompany.id);
if (response.error) {
return;
}
// Éxito
if (response.status === 204 || response.status === 200 || !response.error) {
if (onSuccess) {
onSuccess();
}
}
} catch (e) {
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
} finally {
loading = false;
}
}
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisVertical class="h-4 w-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Item onclick={() => dialogOpen = true}>
<Pencil class="mr-2 h-4 w-4" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<Trash2 class="mr-2 h-4 w-4" />
{/if}
Eliminar
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<CreateEditDialog
bind:open={dialogOpen}
item={item}
onSuccess={onSuccess}
/>

View File

@@ -1,9 +1,7 @@
import type { Company } from '$lib/api/dashboard/a76/general_catalogs/company';
import type { ColumnDef } from '@tanstack/table-core';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
export function createColumns(onSuccess?: () => void): ColumnDef<Company>[] {
export function createColumns(): ColumnDef<Company>[] {
return [
{
accessorKey: 'name',
@@ -24,15 +22,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Company>[] {
accessorKey: 'program_number',
header: 'No. Programa',
cell: ({ row }) => row.original.program_number || '-'
},
{
id: 'actions',
cell: ({ row }) => {
return renderComponent(DataTableActions, {
item: row.original,
onSuccess
});
}
}
];
}

View File

@@ -1,9 +1,7 @@
import type { Concept } from '$lib/api/dashboard/a76/general_catalogs/concepts';
import type { ColumnDef } from '@tanstack/table-core';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
export function createColumns(onSuccess?: () => void): ColumnDef<Concept>[] {
export function createColumns(): ColumnDef<Concept>[] {
return [
{
accessorKey: 'code',
@@ -24,16 +22,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Concept>[] {
accessorKey: 'section',
header: 'Sección',
cell: ({ row }) => row.original.section?.toString() || '-'
},
{
id: 'actions',
header: 'Acciones',
cell: ({ row }) => {
return renderComponent(DataTableActions, {
item: row.original,
onSuccess
});
}
}
];
}

View File

@@ -1,85 +0,0 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { deleteConcept, type Concept } from "$lib/api/dashboard/a76/general_catalogs/concepts";
import { companyStore } from "$lib/stores/company.svelte";
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
import CreateEditDialog from "./create-edit-dialog.svelte";
let {
item,
onSuccess
}: {
item: Concept;
onSuccess?: () => void;
} = $props();
let loading = $state(false);
let error = $state<string | null>(null);
let dialogOpen = $state(false);
async function handleDelete() {
if (!confirm(`¿Estás seguro de eliminar el concepto "${item.code}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
return;
}
if (!companyStore.activeCompany) {
return;
}
loading = true;
error = null;
try {
const response = await deleteConcept(item.id, companyStore.activeCompany.id);
if (response.error) {
return;
}
// Éxito
if (response.status === 204 || response.status === 200 || !response.error) {
if (onSuccess) {
onSuccess();
}
}
} catch (e) {
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
} finally {
loading = false;
}
}
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisVertical class="h-4 w-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Item onclick={() => dialogOpen = true}>
<Pencil class="mr-2 h-4 w-4" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<Trash2 class="mr-2 h-4 w-4" />
{/if}
Eliminar
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<CreateEditDialog
bind:open={dialogOpen}
item={item}
onSuccess={onSuccess}
/>

View File

@@ -1,9 +1,7 @@
import type { CustomsBrokerConcept } from '$lib/api/dashboard/a76/general_catalogs/customs-broker-concepts';
import type { ColumnDef } from '@tanstack/table-core';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
export function createColumns(onSuccess?: () => void): ColumnDef<CustomsBrokerConcept>[] {
export function createColumns(): ColumnDef<CustomsBrokerConcept>[] {
return [
{
accessorKey: 'broker_key',
@@ -24,16 +22,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<CustomsBrokerCo
accessorKey: 'priority',
header: 'Prioridad',
cell: ({ row }) => row.original.priority?.toString() || '-'
},
{
id: 'actions',
header: 'Acciones',
cell: ({ row }) => {
return renderComponent(DataTableActions, {
item: row.original,
onSuccess
});
}
}
];
}

View File

@@ -1,8 +1,7 @@
import type { ColumnDef } from '@tanstack/table-core';
import type { Doda } from '$lib/api/dashboard/a76/general_catalogs/doda';
import { renderComponent, renderSnippet } from '$lib/components/ui/data-table';
import { renderSnippet } from '$lib/components/ui/data-table';
import { createRawSnippet } from 'svelte';
import DataTableActions from './data-table-actions.svelte';
function formatDate(date?: string | null): string {
if (!date) return '-';
@@ -18,7 +17,7 @@ function formatDate(date?: string | null): string {
}
}
export function createColumns(onSuccess?: () => void): ColumnDef<Doda>[] {
export function createColumns(): ColumnDef<Doda>[] {
return [
{
accessorKey: 'id',
@@ -119,16 +118,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Doda>[] {
});
return renderSnippet(statusSnippet, { status });
}
},
{
id: 'actions',
header: 'Acciones',
cell: ({ row }) => {
return renderComponent(DataTableActions, {
item: row.original,
onSuccess
});
}
}
];
}

View File

@@ -1,10 +1,8 @@
import type { ColumnDef } from '@tanstack/table-core';
import type { ElectronicNotice } from '$lib/api/dashboard/a76/general_catalogs/electronic-notices';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
import { Head } from '$lib/components/ui/table';
export function createColumns(onSuccess?: () => void): ColumnDef<ElectronicNotice>[] {
export function createColumns(): ColumnDef<ElectronicNotice>[] {
return [
{
accessorKey: 'notice_number',
@@ -30,16 +28,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ElectronicNotic
accessorKey: 'status',
header: 'Estatus',
cell: ({ row }) => row.original.status || 'N/A'
},
{
id: 'actions',
Headers: 'Acciones',
cell: ({ row }) => {
return renderComponent(DataTableActions, {
item: row.original,
onSuccess
});
}
}
];
}

View File

@@ -3,6 +3,11 @@
import * as Dialog from '$lib/components/ui/dialog';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import {
createElectronicNotice,
updateElectronicNotice,
type ElectronicNotice
} from '$lib/api/dashboard/a76/general_catalogs/electronic-notices';
import { companyStore } from '$lib/stores/company.svelte';
import { obtenerAtajosFormularioAvisosElectrónicos } from '$lib/config/shortcuts/dashboard/general_catalogs/electronic_notices/edit';
@@ -89,10 +94,15 @@
error = null;
try {
let savedNotice: ElectronicNotice | undefined;
if (isEdit && item) {
await updateElectronicNotice(item.id, formData, companyId);
savedNotice = await updateElectronicNotice(item.id, formData, companyId);
} else {
await createElectronicNotice(formData, companyId);
savedNotice = await createElectronicNotice(formData, companyId);
}
if (!savedNotice) {
throw new Error('No se pudo guardar el aviso electrónico');
}
open = false;

View File

@@ -1,82 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
import type { ElectronicNotice } from '$lib/api/dashboard/a76/general_catalogs/electronic-notices';
import { deleteElectronicNotice } from '$lib/api/dashboard/a76/general_catalogs/electronic-notices';
import { companyStore } from '$lib/stores/company.svelte';
import CreateEditDialog from './create-edit-dialog.svelte';
let {
item,
onSuccess
}: {
item: ElectronicNotice;
onSuccess?: () => void;
} = $props();
let loading = $state(false);
let error = $state<string | null>(null);
let dialogOpen = $state(false);
let selectedItem = $state<ElectronicNotice | null>(null);
async function handleDelete() {
if (!confirm('¿Está seguro de eliminar este aviso electrónico?')) {
return;
}
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
return;
}
loading = true;
try {
await deleteElectronicNotice(item.id, companyId);
if (onSuccess) onSuccess();
} catch (err: any) {
error = err.message || 'Error al eliminar el aviso electrónico';
console.error('Error deleting electronic notice:', err);
} finally {
loading = false;
}
}
function handleEdit() {
selectedItem = item;
dialogOpen = true;
}
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisVertical class="h-4 w-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Item onclick={handleEdit}>
<Pencil class="mr-2 h-4 w-4" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<Trash2 class="mr-2 h-4 w-4" />
{/if}
Eliminar
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<CreateEditDialog
bind:open={dialogOpen}
item={selectedItem}
onSuccess={onSuccess}
/>

View File

@@ -1,17 +1,7 @@
import type { ColumnDef } from '@tanstack/table-core';
import type { Equivalency } from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
import { renderComponent } from '$lib/components/ui/data-table';
import CatalogDataTableActions from './catalog-data-table-actions.svelte';
export function createCatalogColumns({
onInsertItems,
onEdit,
onSuccess
}: {
onInsertItems: (equivalency: Equivalency) => void;
onEdit: (equivalency: Equivalency) => void;
onSuccess?: () => void;
}): ColumnDef<Equivalency>[] {
export function createCatalogColumns(): ColumnDef<Equivalency>[] {
return [
{
accessorKey: 'identifier',
@@ -22,18 +12,6 @@ export function createCatalogColumns({
accessorKey: 'description',
header: 'Descripción',
cell: ({ row }) => row.original.description || ''
},
{
id: 'actions',
Header: 'Acciones',
cell: ({ row }) => {
return renderComponent(CatalogDataTableActions, {
item: row.original,
onInsertItems,
onEdit,
onSuccess
});
}
}
];
}

View File

@@ -1,71 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
import { companyStore } from '$lib/stores/company.svelte';
import type { Equivalency } from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
import { deleteEquivalency } from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
let {
item,
onInsertItems,
onEdit,
onSuccess
}: {
item: Equivalency;
onInsertItems: (equivalency: Equivalency) => void;
onEdit: (equivalency: Equivalency) => void;
onSuccess?: () => void;
} = $props();
let loading = $state(false);
async function handleDelete() {
if (!confirm('¿Está seguro de eliminar esta equivalencia?')) return;
const companyId = companyStore.activeCompany?.id;
if (!companyId) return;
loading = true;
try {
const response = await deleteEquivalency(item.id, companyId);
if (response.error) throw new Error(response.error);
onSuccess?.();
} catch (err: any) {
console.error('Error deleting equivalency:', err);
alert(err?.message || 'Error al eliminar la equivalencia');
} finally {
loading = false;
}
}
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisVertical class="h-4 w-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Item onclick={() => onInsertItems(item)}>
<span>Insertar items</span>
</DropdownMenu.Item>
<DropdownMenu.Item onclick={() => onEdit(item)}>
<Pencil class="mr-2 h-4 w-4" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleDelete} class="text-red-600" disabled={loading}>
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<Trash2 class="mr-2 h-4 w-4" />
{/if}
Borrar
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>

View File

@@ -1,87 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
import type { EquivalencyItem } from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
import { deleteEquivalencyItem } from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
import { companyStore } from '$lib/stores/company.svelte';
import CreateEditDialog from './create-edit-dialog.svelte';
let {
item,
onSuccess
}: {
item: EquivalencyItem;
onSuccess?: () => void;
} = $props();
let loading = $state(false);
let error = $state<string | null>(null);
let dialogOpen = $state(false);
let selectedItem = $state<EquivalencyItem | null>(null);
async function handleDelete() {
if (!confirm('¿Está seguro de eliminar esta equivalencia?')) {
return;
}
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
return;
}
loading = true;
try {
const response = await deleteEquivalencyItem(item.id, companyId);
if (response.error) {
throw new Error(response.error);
}
if (onSuccess) onSuccess();
} catch (err: any) {
error = err.message || 'Error al eliminar la equivalencia';
console.error('Error deleting equivalency:', err);
} finally {
loading = false;
}
}
function handleEdit() {
selectedItem = item;
dialogOpen = true;
}
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisVertical class="h-4 w-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Item onclick={handleEdit}>
<Pencil class="mr-2 h-4 w-4" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<Trash2 class="mr-2 h-4 w-4" />
{/if}
Eliminar
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<CreateEditDialog
bind:open={dialogOpen}
item={selectedItem}
onSuccess={onSuccess}
/>

View File

@@ -1,9 +1,7 @@
import type { ColumnDef } from '@tanstack/table-core';
import type { ErrorCatalog } from '$lib/api/dashboard/a76/general_catalogs/error-catalogs';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
export function createColumns(onSuccess?: () => void): ColumnDef<ErrorCatalog>[] {
export function createColumns(): ColumnDef<ErrorCatalog>[] {
return [
{
accessorKey: 'code',
@@ -18,14 +16,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ErrorCatalog>[]
accessorKey: 'classification_id',
header: 'Clasificación',
cell: ({ row }) => row.original.classification_id ?? '—'
},
{
id: 'actions',
header: 'Acciones',
cell: ({ row }) => renderComponent(DataTableActions, {
item: row.original,
onSuccess
})
}
];
}

View File

@@ -1,94 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
import type { ErrorCatalog } from '$lib/api/dashboard/a76/general_catalogs/error-catalogs';
import { deleteErrorCatalog } from '$lib/api/dashboard/a76/general_catalogs/error-catalogs';
import { companyStore } from '$lib/stores/company.svelte';
import CreateEditDialog from './create-edit-dialog.svelte';
let {
item,
onSuccess
}: {
item: ErrorCatalog;
onSuccess?: () => void;
} = $props();
let loading = $state(false);
let error = $state<string | null>(null);
let dialogOpen = $state(false);
let selectedItem = $state<ErrorCatalog | null>(null);
async function handleDelete() {
if (!confirm('¿Está seguro de que desea eliminar este error?')) {
return;
}
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
return;
}
loading = true;
try {
await deleteErrorCatalog(item.id, companyId);
if (onSuccess) {
onSuccess();
}
} catch (err: any) {
error = err.message || 'Error al eliminar el error';
console.error('Error deleting:', err);
} finally {
loading = false;
}
}
function handleEdit() {
selectedItem = item;
dialogOpen = true;
}
function handleDialogSuccess() {
dialogOpen = false;
selectedItem = null;
if (onSuccess) {
onSuccess();
}
}
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisVertical size={16} />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" class="w-[160px]">
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleEdit}>
<Pencil size={16} class="mr-2" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<Trash2 size={16} class="mr-2" />
{/if}
Eliminar
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<CreateEditDialog
bind:open={dialogOpen}
item={selectedItem}
onSuccess={handleDialogSuccess}
/>

View File

@@ -1,10 +1,7 @@
import type { ColumnDef } from '@tanstack/table-core';
import type { Identifier } from '$lib/api/dashboard/a76/general_catalogs/identifiers';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
import { Header } from '$lib/components/ui/alert-dialog';
export function createColumns(onSuccess?: () => void): ColumnDef<Identifier>[] {
export function createColumns(): ColumnDef<Identifier>[] {
return [
{
accessorKey: 'code',
@@ -25,16 +22,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Identifier>[] {
accessorKey: 'complement',
header: 'Complemento',
cell: ({ row }) => row.original.complement || 'N/A'
},
{
id: 'actions',
Header: 'Acciones',
cell: ({ row }) => {
return renderComponent(DataTableActions, {
item: row.original,
onSuccess
});
}
}
];
}

View File

@@ -1,82 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
import type { Identifier } from '$lib/api/dashboard/a76/general_catalogs/identifiers';
import { deleteIdentifier } from '$lib/api/dashboard/a76/general_catalogs/identifiers';
import { companyStore } from '$lib/stores/company.svelte';
import CreateEditDialog from './create-edit-dialog.svelte';
let {
item,
onSuccess
}: {
item: Identifier;
onSuccess?: () => void;
} = $props();
let loading = $state(false);
let error = $state<string | null>(null);
let dialogOpen = $state(false);
let selectedItem = $state<Identifier | null>(null);
async function handleDelete() {
if (!confirm('¿Está seguro de eliminar este identificador?')) {
return;
}
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
return;
}
loading = true;
try {
await deleteIdentifier(item.id, companyId);
if (onSuccess) onSuccess();
} catch (err: any) {
error = err.message || 'Error al eliminar el identificador';
console.error('Error deleting identifier:', err);
} finally {
loading = false;
}
}
function handleEdit() {
selectedItem = item;
dialogOpen = true;
}
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisVertical class="h-4 w-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Item onclick={handleEdit}>
<Pencil class="mr-2 h-4 w-4" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<Trash2 class="mr-2 h-4 w-4" />
{/if}
Eliminar
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<CreateEditDialog
bind:open={dialogOpen}
item={selectedItem}
onSuccess={onSuccess}
/>

View File

@@ -1,9 +1,7 @@
import type { ColumnDef } from '@tanstack/table-core';
import type { INPC } from '$lib/api/dashboard/a76/general_catalogs/inpc';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
export function createColumns(onSuccess?: () => void): ColumnDef<INPC>[] {
export function createColumns(): ColumnDef<INPC>[] {
return [
{
accessorKey: 'year',
@@ -19,16 +17,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<INPC>[] {
accessorKey: 'value',
header: 'Valor',
cell: ({ row }) => row.original.value?.toString() || 'N/A'
},
{
id: 'actions',
header: 'Acciones',
cell: ({ row }) => {
return renderComponent(DataTableActions, {
item: row.original,
onSuccess
});
}
}
];
}

View File

@@ -1,82 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
import type { INPC } from '$lib/api/dashboard/a76/general_catalogs/inpc';
import { deleteINPC } from '$lib/api/dashboard/a76/general_catalogs/inpc';
import { companyStore } from '$lib/stores/company.svelte';
import CreateEditDialog from './create-edit-dialog.svelte';
let {
item,
onSuccess
}: {
item: INPC;
onSuccess?: () => void;
} = $props();
let loading = $state(false);
let error = $state<string | null>(null);
let dialogOpen = $state(false);
let selectedItem = $state<INPC | null>(null);
async function handleDelete() {
if (!confirm('¿Está seguro de eliminar este registro de INPC?')) {
return;
}
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
return;
}
loading = true;
try {
await deleteINPC(item.id, companyId);
if (onSuccess) onSuccess();
} catch (err: any) {
error = err.message || 'Error al eliminar el registro';
console.error('Error deleting INPC:', err);
} finally {
loading = false;
}
}
function handleEdit() {
selectedItem = item;
dialogOpen = true;
}
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisVertical class="h-4 w-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Item onclick={handleEdit}>
<Pencil class="mr-2 h-4 w-4" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<Trash2 class="mr-2 h-4 w-4" />
{/if}
Eliminar
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<CreateEditDialog
bind:open={dialogOpen}
item={selectedItem}
onSuccess={onSuccess}
/>

View File

@@ -1,9 +1,7 @@
import type { Legend } from '$lib/api/dashboard/a76/general_catalogs/legends';
import type { ColumnDef } from '@tanstack/table-core';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
export function createColumns(onSuccess?: () => void): ColumnDef<Legend>[] {
export function createColumns(): ColumnDef<Legend>[] {
return [
{
accessorKey: 'code',
@@ -14,16 +12,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Legend>[] {
accessorKey: 'description',
header: 'Descripción',
cell: ({ row }) => row.original.description || '-'
},
{
id: 'actions',
header: 'Acciones',
cell: ({ row }) => {
return renderComponent(DataTableActions, {
item: row.original,
onSuccess
});
}
}
];
}

View File

@@ -1,86 +0,0 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { deleteLegend, type Legend } from "$lib/api/dashboard/a76/general_catalogs/legends";
import { companyStore } from "$lib/stores/company.svelte";
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
import CreateDialog from './create-edit-dialog.svelte';
let {
item,
onSuccess
}: {
item: Legend;
onSuccess?: () => void;
} = $props();
let loading = $state(false);
let error = $state<string | null>(null);
let dialogOpen = $state(false);
async function handleDelete() {
if (!confirm(`¿Estás seguro de eliminar la leyenda "${item.code}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
return;
}
if (!companyStore.activeCompany) {
return;
}
loading = true;
error = null;
try {
const response = await deleteLegend(item.id, companyStore.activeCompany.id);
if (response.error) {
return;
}
// Éxito
if (response.status === 204 || response.status === 200 || !response.error) {
if (onSuccess) {
onSuccess();
}
}
} catch (e) {
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
} finally {
loading = false;
}
}
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisVertical class="h-4 w-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Item onclick={() => dialogOpen = true}>
<Pencil class="mr-2 h-4 w-4" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<Trash2 class="mr-2 h-4 w-4" />
{/if}
Eliminar
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<CreateDialog
bind:open={dialogOpen}
item={item}
onSuccess={onSuccess}
/>

View File

@@ -96,10 +96,15 @@
publication_date: dateInt // Mandamos el INT que espera Python
};
let response;
if (isEdit && item) {
await updateMultiCurrencyType(item.id, dataToSend, companyId);
response = await updateMultiCurrencyType(item.id, dataToSend, companyId);
} else {
await createMultiCurrencyType(dataToSend, companyId);
response = await createMultiCurrencyType(dataToSend, companyId);
}
if (response?.error) {
throw new Error(response.error);
}
open = false;

View File

@@ -1,10 +1,7 @@
import type { ColumnDef } from '@tanstack/table-core';
import type { Prevalidator } from '$lib/api/dashboard/a76/general_catalogs/prevalidators';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
import { Header } from '$lib/components/ui/alert-dialog';
export function createColumns(onSuccess?: () => void): ColumnDef<Prevalidator>[] {
export function createColumns(): ColumnDef<Prevalidator>[] {
return [
{
accessorKey: 'code',
@@ -25,16 +22,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Prevalidator>[]
accessorKey: 'patent_prevalidator',
header: 'Patente',
cell: ({ row }) => row.original.patent_prevalidator || 'N/A'
},
{
id: 'actions',
Header: 'Acciones',
cell: ({ row }) => {
return renderComponent(DataTableActions, {
item: row.original,
onSuccess
});
}
}
];
}

View File

@@ -1,82 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
import type { Prevalidator } from '$lib/api/dashboard/a76/general_catalogs/prevalidators';
import { deletePrevalidator } from '$lib/api/dashboard/a76/general_catalogs/prevalidators';
import { companyStore } from '$lib/stores/company.svelte';
import CreateEditDialog from './create-edit-dialog.svelte';
let {
item,
onSuccess
}: {
item: Prevalidator;
onSuccess?: () => void;
} = $props();
let loading = $state(false);
let error = $state<string | null>(null);
let dialogOpen = $state(false);
let selectedItem = $state<Prevalidator | null>(null);
async function handleDelete() {
if (!confirm('¿Está seguro de eliminar este prevalidador?')) {
return;
}
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
return;
}
loading = true;
try {
await deletePrevalidator(item.id, companyId);
if (onSuccess) onSuccess();
} catch (err: any) {
error = err.message || 'Error al eliminar el prevalidador';
console.error('Error deleting prevalidator:', err);
} finally {
loading = false;
}
}
function handleEdit() {
selectedItem = item;
dialogOpen = true;
}
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisVertical class="h-4 w-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Item onclick={handleEdit}>
<Pencil class="mr-2 h-4 w-4" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<Trash2 class="mr-2 h-4 w-4" />
{/if}
Eliminar
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<CreateEditDialog
bind:open={dialogOpen}
item={selectedItem}
onSuccess={onSuccess}
/>

View File

@@ -1,9 +1,7 @@
import type { ColumnDef } from '@tanstack/table-core';
import type { Signature } from '$lib/api/dashboard/a76/general_catalogs/signatures';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
export function createColumns(onSuccess?: () => void): ColumnDef<Signature>[] {
export function createColumns(): ColumnDef<Signature>[] {
return [
{
accessorKey: 'code',
@@ -19,14 +17,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Signature>[] {
accessorKey: 'photo_path',
header: 'Ruta Foto',
cell: ({ row }) => row.original.photo_path ?? 'N/A'
},
{
id: 'actions',
header: 'Acciones',
cell: ({ row }) => renderComponent(DataTableActions, {
item: row.original,
onSuccess
})
}
];
}

View File

@@ -1,90 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
import type { Signature } from '$lib/api/dashboard/a76/general_catalogs/signatures';
import { deleteSignature } from '$lib/api/dashboard/a76/general_catalogs/signatures';
import { companyStore } from '$lib/stores/company.svelte';
import CreateEditDialog from './create-edit-dialog.svelte';
let {
item,
onSuccess
}: {
item: Signature;
onSuccess?: () => void;
} = $props();
let loading = $state(false);
let error = $state<string | null>(null);
let dialogOpen = $state(false);
let selectedItem = $state<Signature | null>(null);
async function handleDelete() {
if (!confirm('¿Está seguro de eliminar esta firma electrónica?')) {
return;
}
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
return;
}
loading = true;
try {
await deleteSignature(item.id, companyId);
if (onSuccess) onSuccess();
} catch (err: any) {
error = err.message || 'Error al eliminar la firma';
console.error('Error deleting signature:', err);
} finally {
loading = false;
}
}
function handleEdit() {
selectedItem = item;
dialogOpen = true;
}
function handleDialogSuccess() {
dialogOpen = false;
selectedItem = null;
if (onSuccess) onSuccess();
}
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisVertical size={16} />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" class="w-[160px]">
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleEdit}>
<Pencil size={16} class="mr-2" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<Trash2 size={16} class="mr-2" />
{/if}
Eliminar
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<CreateEditDialog
bind:open={dialogOpen}
item={selectedItem}
onSuccess={handleDialogSuccess}
/>

View File

@@ -1,10 +1,7 @@
import type { ColumnDef } from '@tanstack/table-core';
import type { UnitConversion } from '$lib/api/dashboard/a76/general_catalogs/unit-conversions';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
import { Header } from '$lib/components/ui/alert-dialog';
export function createColumns(onSuccess?: () => void): ColumnDef<UnitConversion>[] {
export function createColumns(): ColumnDef<UnitConversion>[] {
return [
{
accessorKey: 'from_unit_code',
@@ -17,15 +14,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitConversion>
{
accessorKey: 'conversion_factor',
header: 'Factor de conversión'
},
{
id: 'actions',
header: 'Acciones',
cell: ({ row }) =>
renderComponent(DataTableActions, {
conversion: row.original,
onSuccess
})
}
];
}

View File

@@ -15,11 +15,13 @@
let {
open = $bindable(false),
item = null,
conversion = null,
mode = 'create',
onSuccess
}: {
open: boolean;
item?: UnitConversion | null;
conversion?: UnitConversion | null;
mode?: 'create' | 'edit';
onSuccess?: () => void;
@@ -27,7 +29,8 @@
// Atajos
const isEdit = $derived(mode === 'edit');
const currentConversion = $derived(item ?? conversion);
const isEdit = $derived(mode === 'edit' || !!currentConversion);
const title = $derived(isEdit ? 'Editar Conversión' : 'Nueva Conversión');
let formData = $state({
@@ -40,26 +43,15 @@
let error = $state<string | null>(null);
let showFromUomModal = $state(false);
let showToUomModal = $state(false);
let wasOpen = $state(false);
function resetForm() {
formData = {
from_unit_code: '',
to_unit_code: '',
conversion_factor: ''
};
error = null;
loading = false;
showFromUomModal = false;
showToUomModal = false;
}
$effect(() => {
if (conversion) {
if (!open) return;
if (currentConversion) {
formData = {
from_unit_code: conversion.from_unit_code || '',
to_unit_code: conversion.to_unit_code || '',
conversion_factor: conversion.conversion_factor.toString() || ''
from_unit_code: currentConversion.from_unit_code || '',
to_unit_code: currentConversion.to_unit_code || '',
conversion_factor: currentConversion.conversion_factor.toString() || ''
};
} else {
formData = {
@@ -68,14 +60,11 @@
conversion_factor: ''
};
}
});
$effect(() => {
// Reset each time the dialog is opened in create mode
if (open && !wasOpen && !isEdit) {
resetForm();
}
wasOpen = open;
error = null;
loading = false;
showFromUomModal = false;
showToUomModal = false;
});
async function handleSubmit() {
@@ -101,8 +90,8 @@
throw new Error('El factor de conversión debe ser un número válido');
}
if (isEdit && conversion) {
await updateUnitConversion(conversion.id, dataToSend, companyId);
if (isEdit && currentConversion) {
await updateUnitConversion(currentConversion.id, dataToSend, companyId);
} else {
await createUnitConversion(dataToSend, companyId);
}

View File

@@ -1,82 +0,0 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { deleteUnitConversion, type UnitConversion } from "$lib/api/dashboard/a76/general_catalogs/unit-conversions";
import { companyStore } from "$lib/stores/company.svelte";
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
import CreateEditDialog from "./create-edit-dialog.svelte";
import { toast } from "svelte-sonner";
let {
conversion,
onSuccess
}: {
conversion: UnitConversion;
onSuccess?: () => void;
} = $props();
let loading = $state(false);
let dialogOpen = $state(false);
async function handleDelete() {
if (!confirm(`¿Estás seguro de eliminar la conversión "${conversion.from_unit_code}${conversion.to_unit_code}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
return;
}
if (!companyStore.activeCompany) {
toast.error('Selecciona una compañía', {
description: 'No se puede eliminar sin una compañía activa.'
});
return;
}
loading = true;
try {
await deleteUnitConversion(conversion.id, companyStore.activeCompany.id);
toast.success('Conversión eliminada');
if (onSuccess) {
onSuccess();
}
} catch (e) {
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
toast.error('No se pudo eliminar', { description: errorMsg });
} finally {
loading = false;
}
}
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisVertical class="h-4 w-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Item onclick={() => dialogOpen = true}>
<Pencil class="mr-2 h-4 w-4" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<Trash2 class="mr-2 h-4 w-4" />
{/if}
Eliminar
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<CreateEditDialog
bind:open={dialogOpen}
conversion={conversion}
mode="edit"
{onSuccess}
/>

View File

@@ -1,9 +1,7 @@
import type { UnitOfMeasureACE } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
import type { ColumnDef } from '@tanstack/table-core';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureACE>[] {
export function createColumns(): ColumnDef<UnitOfMeasureACE>[] {
return [
{
accessorKey: 'code',
@@ -14,16 +12,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureAC
accessorKey: 'description',
header: 'Descripción',
cell: ({ row }) => row.original.description || '-'
},
{
id: 'actions',
header: 'Acciones',
cell: ({ row }) => {
return renderComponent(DataTableActions, {
item: row.original,
onSuccess
});
}
}
];
}

View File

@@ -1,9 +1,7 @@
import type { ColumnDef } from "@tanstack/table-core";
import type { UnitOfMeasureAmerican } from "$lib/api/dashboard/a76/general_catalogs/units-of-measure";
import { renderComponent } from "$lib/components/ui/data-table/index.js";
import DataTableActions from "./data-table-actions.svelte";
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureAmerican>[] {
export function createColumns(): ColumnDef<UnitOfMeasureAmerican>[] {
return [
{
accessorKey: "code",
@@ -13,14 +11,5 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureAm
accessorKey: "description",
header: "Descripción",
},
{
id: "actions",
header: "Acciones",
cell: ({ row }) =>
renderComponent(DataTableActions, {
unit: row.original,
onSuccess
}),
},
];
}

View File

@@ -17,11 +17,11 @@
interface Props {
open: boolean;
unit?: UnitOfMeasureAmerican;
item?: UnitOfMeasureAmerican | null;
onSuccess?: () => void;
}
let { open = $bindable(), unit, onSuccess }: Props = $props();
let { open = $bindable(), item, onSuccess }: Props = $props();
// Atajos
@@ -29,41 +29,64 @@
code: '',
description: ''
});
let loading = $state(false);
let error = $state<string | null>(null);
$effect(() => {
if (open) {
if (unit) {
if (item) {
formData = {
code: unit.code,
description: unit.description || ''
code: item.code,
description: item.description || ''
};
} else {
formData = { code: '', description: '' };
}
error = null;
}
});
async function handleSubmit(e: Event) {
e.preventDefault();
loading = true;
error = null;
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
return;
}
try {
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
throw new Error('No hay una compañía seleccionada');
}
const data: UnitOfMeasureAmericanCreate | UnitOfMeasureAmericanUpdate = {
code: formData.code,
description: formData.description || null
};
let response;
if (item) {
response = await updateUnitOfMeasureAmerican(
item.id,
{
code: formData.code,
description: formData.description || null
} satisfies UnitOfMeasureAmericanUpdate,
activeCompanyId
);
} else {
response = await createUnitOfMeasureAmerican(
{
code: formData.code,
description: formData.description || null
} satisfies UnitOfMeasureAmericanCreate,
activeCompanyId
);
}
const response = unit
? await updateUnitOfMeasureAmerican(unit.id, data, activeCompanyId)
: await createUnitOfMeasureAmerican(data, activeCompanyId);
if (response.error) {
throw new Error(response.error);
}
if (response.error) {
} else {
open = false;
onSuccess?.();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar la unidad';
} finally {
loading = false;
}
}
</script>
@@ -71,20 +94,25 @@
<Dialog.Root bind:open>
<Dialog.Content class="sm:max-w-[425px]">
<Dialog.Header>
<Dialog.Title>{unit ? 'Editar' : 'Crear'} Unidad Americana</Dialog.Title>
<Dialog.Title>{item ? 'Editar' : 'Crear'} Unidad Americana</Dialog.Title>
</Dialog.Header>
<form onsubmit={handleSubmit} class="space-y-4">
{#if error}
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">{error}</div>
{/if}
<div class="space-y-2">
<Label for="code">Código * (máx. 3 caracteres)</Label>
<Input id="code" bind:value={formData.code} required maxlength="3" />
<Input id="code" bind:value={formData.code} required maxlength={3} disabled={loading} />
</div>
<div class="space-y-2">
<Label for="description">Descripción (máx. 40 caracteres)</Label>
<Input id="description" bind:value={formData.description} maxlength="40" />
<Input id="description" bind:value={formData.description} maxlength={40} disabled={loading} />
</div>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => (open = false)}>Cancelar</Button>
<Button type="submit">Guardar</Button>
<Button type="button" variant="outline" onclick={() => (open = false)} disabled={loading}
>Cancelar</Button
>
<Button type="submit" disabled={loading}>{loading ? 'Guardando...' : 'Guardar'}</Button>
</Dialog.Footer>
</form>
</Dialog.Content>

View File

@@ -1,9 +1,7 @@
import type { ColumnDef } from "@tanstack/table-core";
import type { UnitOfMeasureCustoms } from "$lib/api/dashboard/a76/general_catalogs/units-of-measure";
import { renderComponent } from "$lib/components/ui/data-table/index.js";
import DataTableActions from "./data-table-actions.svelte";
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureCustoms>[] {
export function createColumns(): ColumnDef<UnitOfMeasureCustoms>[] {
return [
{
accessorKey: "code",
@@ -17,14 +15,5 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureCu
accessorKey: "a76_unit_code",
header: "Código A76 / SCAII",
},
{
id: "actions",
header: "Acciones",
cell: ({ row }) =>
renderComponent(DataTableActions, {
unit: row.original,
onSuccess
}),
},
];
}

View File

@@ -17,11 +17,11 @@
interface Props {
open: boolean;
unit?: UnitOfMeasureCustoms;
item?: UnitOfMeasureCustoms | null;
onSuccess?: () => void;
}
let { open = $bindable(), unit, onSuccess }: Props = $props();
let { open = $bindable(), item, onSuccess }: Props = $props();
// Atajos
@@ -30,50 +30,64 @@
description: '',
a76_unit_code: ''
});
let loading = $state(false);
let error = $state<string | null>(null);
$effect(() => {
if (open) {
if (unit) {
if (item) {
formData = {
code: unit.code,
description: unit.description || '',
a76_unit_code: unit.a76_unit_code || ''
code: item.code,
description: item.description || '',
a76_unit_code: item.a76_unit_code || ''
};
} else {
formData = { code: '', description: '', a76_unit_code: '' };
}
error = null;
}
});
async function handleSubmit(e: Event) {
e.preventDefault();
loading = true;
error = null;
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
return;
}
try {
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
throw new Error('No hay una compañía seleccionada');
}
const response = unit
? await updateUnitOfMeasureCustoms(unit.id, {
code: formData.code,
description: formData.description || null,
a76_unit_code: formData.a76_unit_code || null
} satisfies UnitOfMeasureCustomsUpdate,
activeCompanyId
)
: await createUnitOfMeasureCustoms(
{
code: formData.code,
description: formData.description || null,
a76_unit_code: formData.a76_unit_code || null
} satisfies UnitOfMeasureCustomsCreate,
activeCompanyId
);
const response = item
? await updateUnitOfMeasureCustoms(
item.id,
{
code: formData.code,
description: formData.description || null,
a76_unit_code: formData.a76_unit_code || null
} satisfies UnitOfMeasureCustomsUpdate,
activeCompanyId
)
: await createUnitOfMeasureCustoms(
{
code: formData.code,
description: formData.description || null,
a76_unit_code: formData.a76_unit_code || null
} satisfies UnitOfMeasureCustomsCreate,
activeCompanyId
);
if (response.error) {
throw new Error(response.error);
}
if (response.error) {
} else {
open = false;
onSuccess?.();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar la unidad';
} finally {
loading = false;
}
}
</script>
@@ -81,24 +95,29 @@
<Dialog.Root bind:open>
<Dialog.Content class="sm:max-w-[425px]">
<Dialog.Header>
<Dialog.Title>{unit ? 'Editar' : 'Crear'} Unidad Aduanas MEX</Dialog.Title>
<Dialog.Title>{item ? 'Editar' : 'Crear'} Unidad Aduanas MEX</Dialog.Title>
</Dialog.Header>
<form onsubmit={handleSubmit} class="space-y-4">
{#if error}
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">{error}</div>
{/if}
<div class="space-y-2">
<Label for="code">Código * (máx. 10 caracteres)</Label>
<Input id="code" bind:value={formData.code} required maxlength={10} />
<Input id="code" bind:value={formData.code} required maxlength={10} disabled={loading} />
</div>
<div class="space-y-2">
<Label for="description">Descripción (máx. 20 caracteres)</Label>
<Input id="description" bind:value={formData.description} maxlength={20} />
<Input id="description" bind:value={formData.description} maxlength={20} disabled={loading} />
</div>
<div class="space-y-2">
<Label for="a76_unit_code">Código A76 / SCAII</Label>
<Input id="a76_unit_code" bind:value={formData.a76_unit_code} maxlength={5} />
<Input id="a76_unit_code" bind:value={formData.a76_unit_code} maxlength={5} disabled={loading} />
</div>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => (open = false)}>Cancelar</Button>
<Button type="submit">Guardar</Button>
<Button type="button" variant="outline" onclick={() => (open = false)} disabled={loading}
>Cancelar</Button
>
<Button type="submit" disabled={loading}>{loading ? 'Guardando...' : 'Guardar'}</Button>
</Dialog.Footer>
</form>
</Dialog.Content>

View File

@@ -1,9 +1,7 @@
import type { ColumnDef } from '@tanstack/table-core';
import type { UnitOfMeasureGeneral } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureGeneral>[] {
export function createColumns(): ColumnDef<UnitOfMeasureGeneral>[] {
return [
{
accessorKey: 'code',
@@ -14,16 +12,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureGe
accessorKey: 'description',
header: 'Descripción',
cell: (info) => info.getValue() || '-'
},
{
id: 'actions',
header: 'Acciones',
cell: ({ row }) => {
return renderComponent(DataTableActions, {
unit: row.original,
onSuccess
});
}
}
];
}

View File

@@ -17,11 +17,11 @@
interface Props {
open: boolean;
unit?: UnitOfMeasureGeneral;
item?: UnitOfMeasureGeneral | null;
onSuccess?: () => void;
}
let { open = $bindable(), unit, onSuccess }: Props = $props();
let { open = $bindable(), item, onSuccess }: Props = $props();
// Atajos
@@ -29,41 +29,64 @@
code: '',
description: ''
});
let loading = $state(false);
let error = $state<string | null>(null);
$effect(() => {
if (open) {
if (unit) {
if (item) {
formData = {
code: unit.code,
description: unit.description || ''
code: item.code,
description: item.description || ''
};
} else {
formData = { code: '', description: '' };
}
error = null;
}
});
async function handleSubmit(e: Event) {
e.preventDefault();
loading = true;
error = null;
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
return;
}
try {
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
throw new Error('No hay una compañía seleccionada');
}
const data: UnitOfMeasureGeneralCreate | UnitOfMeasureGeneralUpdate = {
code: formData.code,
description: formData.description || null
};
let response;
if (item) {
response = await updateUnitOfMeasureGeneral(
item.id,
{
code: formData.code,
description: formData.description || null
} satisfies UnitOfMeasureGeneralUpdate,
activeCompanyId
);
} else {
response = await createUnitOfMeasureGeneral(
{
code: formData.code,
description: formData.description || null
} satisfies UnitOfMeasureGeneralCreate,
activeCompanyId
);
}
const response = unit
? await updateUnitOfMeasureGeneral(unit.id, data, activeCompanyId)
: await createUnitOfMeasureGeneral(data, activeCompanyId);
if (response.error) {
throw new Error(response.error);
}
if (response.error) {
} else {
open = false;
onSuccess?.();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar la unidad';
} finally {
loading = false;
}
}
</script>
@@ -71,20 +94,25 @@
<Dialog.Root bind:open>
<Dialog.Content class="sm:max-w-[425px]">
<Dialog.Header>
<Dialog.Title>{unit ? 'Editar' : 'Crear'} Unidad General</Dialog.Title>
<Dialog.Title>{item ? 'Editar' : 'Crear'} Unidad General</Dialog.Title>
</Dialog.Header>
<form onsubmit={handleSubmit} class="space-y-4">
{#if error}
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">{error}</div>
{/if}
<div class="space-y-2">
<Label for="code">Código *</Label>
<Input id="code" bind:value={formData.code} required />
<Input id="code" bind:value={formData.code} required disabled={loading} />
</div>
<div class="space-y-2">
<Label for="description">Descripción</Label>
<Input id="description" bind:value={formData.description} />
<Input id="description" bind:value={formData.description} disabled={loading} />
</div>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => (open = false)}>Cancelar</Button>
<Button type="submit">Guardar</Button>
<Button type="button" variant="outline" onclick={() => (open = false)} disabled={loading}
>Cancelar</Button
>
<Button type="submit" disabled={loading}>{loading ? 'Guardando...' : 'Guardar'}</Button>
</Dialog.Footer>
</form>
</Dialog.Content>

View File

@@ -1,9 +1,7 @@
import type { ColumnDef } from "@tanstack/table-core";
import type { UnitOfMeasureOMA } from "$lib/api/dashboard/a76/general_catalogs/units-of-measure";
import { renderComponent } from "$lib/components/ui/data-table/index.js";
import DataTableActions from "./data-table-actions.svelte";
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureOMA>[] {
export function createColumns(): ColumnDef<UnitOfMeasureOMA>[] {
return [
{
accessorKey: "code",
@@ -13,14 +11,5 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureOM
accessorKey: "description",
header: "Descripción",
},
{
id: "actions",
header: "Acciones",
cell: ({ row }) =>
renderComponent(DataTableActions, {
unit: row.original,
onSuccess
}),
},
];
}

View File

@@ -17,11 +17,11 @@
interface Props {
open: boolean;
unit?: UnitOfMeasureOMA;
item?: UnitOfMeasureOMA | null;
onSuccess?: () => void;
}
let { open = $bindable(), unit, onSuccess }: Props = $props();
let { open = $bindable(), item, onSuccess }: Props = $props();
// Atajos
@@ -29,41 +29,64 @@
code: '',
description: ''
});
let loading = $state(false);
let error = $state<string | null>(null);
$effect(() => {
if (open) {
if (unit) {
if (item) {
formData = {
code: unit.code,
description: unit.description || ''
code: item.code,
description: item.description || ''
};
} else {
formData = { code: '', description: '' };
}
error = null;
}
});
async function handleSubmit(e: Event) {
e.preventDefault();
loading = true;
error = null;
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
return;
}
try {
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
throw new Error('No hay una compañía seleccionada');
}
const data: UnitOfMeasureOMACreate | UnitOfMeasureOMAUpdate = {
code: formData.code,
description: formData.description || null
};
let response;
if (item) {
response = await updateUnitOfMeasureOMA(
item.id,
{
code: formData.code,
description: formData.description || null
} satisfies UnitOfMeasureOMAUpdate,
activeCompanyId
);
} else {
response = await createUnitOfMeasureOMA(
{
code: formData.code,
description: formData.description || null
} satisfies UnitOfMeasureOMACreate,
activeCompanyId
);
}
const response = unit
? await updateUnitOfMeasureOMA(unit.id, data, activeCompanyId)
: await createUnitOfMeasureOMA(data, activeCompanyId);
if (response.error) {
throw new Error(response.error);
}
if (response.error) {
} else {
open = false;
onSuccess?.();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar la unidad';
} finally {
loading = false;
}
}
</script>
@@ -71,20 +94,25 @@
<Dialog.Root bind:open>
<Dialog.Content class="sm:max-w-[425px]">
<Dialog.Header>
<Dialog.Title>{unit ? 'Editar' : 'Crear'} Unidad OMA</Dialog.Title>
<Dialog.Title>{item ? 'Editar' : 'Crear'} Unidad OMA</Dialog.Title>
</Dialog.Header>
<form onsubmit={handleSubmit} class="space-y-4">
{#if error}
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">{error}</div>
{/if}
<div class="space-y-2">
<Label for="code">Código * (máx. 10 caracteres)</Label>
<Input id="code" bind:value={formData.code} required maxlength="10" />
<Input id="code" bind:value={formData.code} required maxlength={10} disabled={loading} />
</div>
<div class="space-y-2">
<Label for="description">Descripción (máx. 200 caracteres)</Label>
<Input id="description" bind:value={formData.description} maxlength="200" />
<Input id="description" bind:value={formData.description} maxlength={200} disabled={loading} />
</div>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => (open = false)}>Cancelar</Button>
<Button type="submit">Guardar</Button>
<Button type="button" variant="outline" onclick={() => (open = false)} disabled={loading}
>Cancelar</Button
>
<Button type="submit" disabled={loading}>{loading ? 'Guardando...' : 'Guardar'}</Button>
</Dialog.Footer>
</form>
</Dialog.Content>

View File

@@ -1,9 +1,7 @@
import type { Port } from '$lib/api/dashboard/a76/general_catalogs/ports';
import type { ColumnDef } from '@tanstack/table-core';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
export function createColumns(onSuccess?: () => void): ColumnDef<Port>[] {
export function createColumns(): ColumnDef<Port>[] {
return [
{
accessorKey: 'port_code',
@@ -33,16 +31,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Port>[] {
if (type === 'BOTH') return 'Ambos';
return type;
}
},
{
id: 'actions',
header: 'Acciones',
cell: ({ row }) => {
return renderComponent(DataTableActions, {
item: row.original,
onSuccess
});
}
}
];
}

View File

@@ -25,7 +25,7 @@
onSuccess?: () => void;
} = $props();
const isEdit = $derived(mode === 'edit');
const isEdit = $derived(mode === 'edit' || !!item);
const title = $derived(isEdit ? 'Editar Puerto' : 'Nuevo Puerto');
// Atajos

View File

@@ -1,54 +0,0 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import type { Port } from "$lib/api/dashboard/a76/general_catalogs/ports";
import { EllipsisVertical, Pencil, Trash2 } from 'lucide-svelte';
import CreateEditDialog from "./create-edit-dialog.svelte";
import DeleteDialog from "./delete-dialog.svelte";
let {
item,
onSuccess
}: {
item: Port;
onSuccess?: () => void;
} = $props();
let editDialogOpen = $state(false);
let deleteDialogOpen = $state(false);
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisVertical class="h-4 w-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Item onclick={() => editDialogOpen = true}>
<Pencil class="mr-2 h-4 w-4" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Item onclick={() => deleteDialogOpen = true} class="text-destructive">
<Trash2 class="mr-2 h-4 w-4" />
Eliminar
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<CreateEditDialog
bind:open={editDialogOpen}
mode="edit"
{item}
{onSuccess}
/>
<DeleteDialog
bind:open={deleteDialogOpen}
{item}
{onSuccess}
/>

View File

@@ -1,26 +1,12 @@
import type { ColumnDef } from '@tanstack/table-core';
import type { Seal } from '$lib/api/dashboard/a76/general_catalogs/seal';
import { renderComponent } from '$lib/components/ui/data-table';
import DataTableActions from './data-table-actions.svelte';
export function createColumns(onSuccess?: () => void): ColumnDef<Seal>[] {
export function createColumns(): ColumnDef<Seal>[] {
return [
{
accessorKey: 'seal',
header: 'Sello',
cell: ({ row }) => row.original.seal,
},
{
id: 'actions',
header: 'Acciones',
meta: {
class: 'w-[100px] text-right'
},
cell: ({ row }) => renderComponent(DataTableActions, {
item: row.original,
onSuccess
})
}
];
}

View File

@@ -1,98 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
import type { Seal } from '$lib/api/dashboard/a76/general_catalogs/seal';
import { deleteSeal } from '$lib/api/dashboard/a76/general_catalogs/seal';
import { companyStore } from '$lib/stores/company.svelte';
import CreateEditDialog from './create-edit-dialog.svelte';
let {
item,
onSuccess
}: {
item: Seal;
onSuccess?: () => void;
} = $props();
let loading = $state(false);
let error = $state<string | null>(null);
let dialogOpen = $state(false);
let selectedItem = $state<Seal | null>(null);
async function handleDelete() {
if (!confirm('¿Está seguro de que desea eliminar este sello?')) {
return;
}
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
alert('No hay compañía seleccionada');
return;
}
loading = true;
error = null;
try {
await deleteSeal(item.id, companyId);
if (onSuccess) {
onSuccess();
}
} catch (err: any) {
error = err.message || 'Error al eliminar el sello';
alert(`Error: ${error}`);
console.error('Error deleting:', err);
} finally {
loading = false;
}
}
function handleEdit() {
selectedItem = item;
dialogOpen = true;
}
function handleDialogSuccess() {
dialogOpen = false;
selectedItem = null;
if (onSuccess) {
onSuccess();
}
}
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisVertical size={16} />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" class="w-[160px]">
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleEdit}>
<Pencil size={16} class="mr-2" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<Trash2 size={16} class="mr-2" />
{/if}
Eliminar
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<CreateEditDialog
bind:open={dialogOpen}
item={selectedItem}
onSuccess={handleDialogSuccess}
/>

View File

@@ -1,122 +0,0 @@
<script lang="ts" generics="TData, TValue">
import { onMount } from 'svelte';
import type { ColumnDef } from '@tanstack/table-core';
import {
getCoreRowModel,
type TableOptions
} from '@tanstack/table-core';
import { createSvelteTable, FlexRender } from '$lib/components/ui/data-table/index.js';
import * as Table from '$lib/components/ui/table';
type Props = {
data: TData[];
columns: ColumnDef<TData, TValue>[];
loading?: boolean;
hasMore?: boolean;
loadMore?: () => void;
};
let { data, columns, loading = false, hasMore = false, loadMore }: Props = $props();
let scrollContainer = $state<HTMLDivElement>();
let loadingTrigger = $state<HTMLDivElement>();
let options = $derived<TableOptions<TData>>({
get data() {
return data;
},
columns,
getCoreRowModel: getCoreRowModel()
});
let table = $derived(createSvelteTable(options));
onMount(() => {
if (!loadMore) return;
const observer = new IntersectionObserver(
(entries) => {
const [entry] = entries;
if (entry.isIntersecting && hasMore && !loading && loadMore) {
loadMore();
}
},
{
root: scrollContainer,
threshold: 0.1
}
);
if (loadingTrigger) {
observer.observe(loadingTrigger);
}
return () => {
observer.disconnect();
};
});
</script>
<div class="flex h-full w-full flex-col overflow-hidden">
<div class="relative flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
<Table.Root>
<Table.Header class="sticky top-0 z-20 border-b bg-background/95 shadow-sm backdrop-blur-md">
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
{#each headerGroup.headers as header (header.id)}
<Table.Head class="whitespace-nowrap">
{#if !header.isPlaceholder}
<FlexRender
content={header.column.columnDef.header}
context={header.getContext()}
/>
{/if}
</Table.Head>
{/each}
</Table.Row>
{/each}
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell class="whitespace-nowrap">
<FlexRender
content={cell.column.columnDef.cell}
context={cell.getContext()}
/>
</Table.Cell>
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>
</Table.Row>
{/each}
{#if hasMore}
<Table.Row>
<Table.Cell colspan={columns.length} class="h-24 p-0 text-center">
<div bind:this={loadingTrigger} class="flex h-full w-full items-center justify-center">
{#if loading}
<div class="flex items-center justify-center gap-3 rounded-full border bg-muted/30 px-6 py-2 shadow-sm">
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
<span class="text-sm font-medium text-foreground">Cargando más registros...</span>
</div>
{:else}
<div class="flex items-center gap-2 text-sm text-muted-foreground">
<span class="h-px w-8 bg-border"></span>
<span>Desplázate para cargar más</span>
<span class="h-px w-8 bg-border"></span>
</div>
{/if}
</div>
</Table.Cell>
</Table.Row>
{/if}
</Table.Body>
</Table.Root>
</div>
</div>

View File

@@ -6,23 +6,31 @@
import CreateDialog from '$lib/components/dashboard/general_catalogs/classification/create-edit-dialog.svelte';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaClasificacion } from '$lib/config/shortcuts/dashboard/general_catalogs/classification/list';
import { getClassificationConcepts } from '$lib/api/dashboard/a76/general_catalogs/classification-concepts';
import { getClassificationConcepts, deleteClassificationConcept, type ClassificationConcept } from '$lib/api/dashboard/a76/general_catalogs/classification-concepts';
import { companyStore } from '$lib/stores/company.svelte';
let { data } = $props();
let dialogOpen = $state(false);
let editingItem = $state<ClassificationConcept | null>(null);
let error = $state<string | null>(data.error || null);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
);
// Atajos
useShortcuts(
'Lista Clasificaciones',
obtenerAtajosListaClasificacion({
manejarNuevo: () => (dialogOpen = true),
manejarNuevo: () => {
editingItem = null;
dialogOpen = true;
},
manejarActualizar: handleSuccess
})
);
@@ -32,7 +40,7 @@
let searchDesc = $state($page.url.searchParams.get('description') || '');
let timeout: ReturnType<typeof setTimeout>;
const columns = $derived(createColumns(handleSuccess));
const columns = createColumns();
let allItems = $state(data.classifications?.items || []);
let currentPage = $state(data.classifications?.page || 1);
@@ -148,8 +156,55 @@
}
function handleSuccess() {
dialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: ClassificationConcept) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: ClassificationConcept) {
editingItem = row;
dialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
dialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar la clasificación "${selectedItem.classification}"?`)) {
return;
}
loading = true;
error = null;
try {
const response = await deleteClassificationConcept(selectedItem.id, companyStore.activeCompany.id);
if (response.error) {
error = response.error;
return;
}
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar la clasificación';
console.error('Error deleting classification:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -159,11 +214,13 @@
<p class="text-muted-foreground">Gestión del catálogo de clasificaciones de conceptos</p>
</div>
<div class="flex items-center gap-3">
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
<RefreshCw class="mr-2 h-4 w-4" />
Actualizar
</Button>
<Button class="h-9" onclick={() => (dialogOpen = true)}>
<Button
class="h-9"
onclick={() => {
editingItem = null;
dialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nueva Clasificación
</Button>
@@ -204,6 +261,10 @@
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
@@ -212,8 +273,41 @@
<div class="flex-none text-sm text-muted-foreground">
Mostrando {allItems.length} de {totalItems} registros
</div>
<div class="h-20"></div>
<div
id="classification-concepts-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateDialog
bind:open={dialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -6,26 +6,29 @@
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import * as m from '$lib/paraglide/messages.js';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaEmpresa } from '$lib/config/shortcuts/dashboard/general_catalogs/company_information/list';
import { getCompanies } from '$lib/api/dashboard/a76/general_catalogs/company';
import { getCompanies, deleteCompany, type Company } from '$lib/api/dashboard/a76/general_catalogs/company';
let { data } = $props();
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
);
// Atajos
useShortcuts(
'Lista Empresas',
obtenerAtajosListaEmpresa({
manejarNuevo: () => goto('/dashboard/general_catalogs/company_information/edit'),
manejarActualizar: () => goto($page.url, { invalidateAll: true })
manejarActualizar: handleSuccess
})
);
let dialogOpen = $state(false);
let error = $state<string | null>(data.error || null);
// Filtros
@@ -130,8 +133,42 @@
}
function handleSuccess() {
selectedIds = [];
reloadData();
}
function handleRowClick(row: Company) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleEditSelected() {
if (!selectedItem) return;
goto(`/dashboard/general_catalogs/company_information/edit/${selectedItem.id}`);
}
async function handleDeleteSelected() {
if (!selectedItem) return;
if (!confirm(`¿Estás seguro de eliminar la empresa "${selectedItem.name}"?`)) {
return;
}
loading = true;
error = null;
try {
await deleteCompany(selectedItem.id);
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar la empresa';
console.error('Error deleting company:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -141,11 +178,7 @@
<p class="text-muted-foreground">Gestión de información de empresas</p>
</div>
<div class="flex items-center gap-3">
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
<RefreshCw class="mr-2 h-4 w-4" />
Actualizar
</Button>
<Button class="h-9" href="/dashboard/general_catalogs/company_information/edit">
<Button class="h-9" onclick={() => goto('/dashboard/general_catalogs/company_information/edit')}>
<Plus class="mr-2 h-4 w-4" />
Nueva Empresa
</Button>
@@ -182,17 +215,50 @@
<div class="rounded-md border bg-background overflow-hidden">
<InfiniteDataTable
data={allItems}
columns={createColumns(handleSuccess)}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
onRowDoubleClick={(item) => goto(`/dashboard/general_catalogs/company_information/edit/${item.id}`)}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={(item) =>
goto(`/dashboard/general_catalogs/company_information/edit/${item.id}`)}
/>
</div>
</Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">
Mostrando {allItems.length} de {totalItems} registros
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<div class="h-20"></div>
<div
id="company-info-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
</div>

View File

@@ -6,26 +6,33 @@
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/concepts/create-edit-dialog.svelte';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import type { PageData } from './$types';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaConceptos } from '$lib/config/shortcuts/dashboard/general_catalogs/concepts/list';
import { getConcepts } from '$lib/api/dashboard/a76/general_catalogs/concepts';
import { getConcepts, deleteConcept, type Concept } from '$lib/api/dashboard/a76/general_catalogs/concepts';
import { companyStore } from '$lib/stores/company.svelte';
let { data }: { data: PageData } = $props();
let createDialogOpen = $state(false);
let dialogOpen = $state(false);
let editingItem = $state<Concept | null>(null);
let error = $state<string | null>(data.error || null);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
);
// Atajos
useShortcuts(
'Lista Conceptos',
obtenerAtajosListaConceptos({
manejarNuevo: () => (createDialogOpen = true),
manejarNuevo: () => {
editingItem = null;
dialogOpen = true;
},
manejarActualizar: () => goto($page.url, { invalidateAll: true })
})
);
@@ -139,8 +146,55 @@
}
function handleSuccess() {
dialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: Concept) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: Concept) {
editingItem = row;
dialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
dialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar el concepto "${selectedItem.code}"?`)) {
return;
}
loading = true;
error = null;
try {
const response = await deleteConcept(selectedItem.id, companyStore.activeCompany.id);
if (response.error) {
error = response.error;
return;
}
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar el concepto';
console.error('Error deleting concept:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -150,11 +204,13 @@
<p class="text-muted-foreground">Define los conceptos que se usan para capturar y clasificar cargos en la pestaña Cuenta de Gastos y A.A. del pedimento.</p>
</div>
<div class="flex items-center gap-3">
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
<RefreshCw class="mr-2 h-4 w-4" />
Actualizar
</Button>
<Button class="h-9" onclick={() => (dialogOpen = true)}>
<Button
class="h-9"
onclick={() => {
editingItem = null;
dialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nuevo Concepto
</Button>
@@ -191,10 +247,14 @@
<div class="rounded-md border bg-background overflow-hidden">
<InfiniteDataTable
data={allItems}
columns={createColumns(handleSuccess)}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
@@ -204,7 +264,38 @@
Mostrando {allItems.length} de {totalItems} registros
</div>
<div class="h-20"></div>
<div
id="concepts-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
{#if dialogOpen}
<CreateEditDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
<CreateEditDialog bind:open={dialogOpen} item={editingItem} onSuccess={handleSuccess} />
{/if}
</div>

View File

@@ -42,7 +42,7 @@
let searchConcept = $state($page.url.searchParams.get('concept') || '');
let timeout: ReturnType<typeof setTimeout>;
const columns = $derived(createColumns(handleSuccess));
const columns = $derived(createColumns());
let allItems = $state(data.concepts?.items || []);
let currentPage = $state(data.concepts?.page || 1);

View File

@@ -50,8 +50,10 @@
let error = $state<string | null>(data.error || null);
// Selection
let selectedId = $state<number | null>(null);
const selectedDoda = $derived(selectedId ? allItems.find((i) => i.id === selectedId) : null);
let selectedIds = $state<(string | number)[]>([]);
const selectedDoda = $derived(
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
);
// Sincronizar con datos del servidor al cargar (primera carga)
$effect(() => {
@@ -144,7 +146,7 @@
allItems = res.data.items;
currentPage = 1;
totalItems = res.data.total;
selectedId = null;
selectedIds = [];
}
} catch (e) {
console.error('Error reloading DODAs:', e);
@@ -169,18 +171,19 @@
}
function handleEdit() {
if (selectedId) {
goto(`/dashboard/general_catalogs/doda/edit/${selectedId}`);
if (selectedDoda) {
goto(`/dashboard/general_catalogs/doda/edit/${selectedDoda.id}`);
}
}
async function handleDelete() {
if (!selectedId || !companyStore.activeCompany) return;
if (!selectedDoda || !companyStore.activeCompany) return;
if (confirm('¿Estás seguro de eliminar este DODA?')) {
try {
await deleteDoda(selectedId, companyStore.activeCompany.id);
await deleteDoda(selectedDoda.id, companyStore.activeCompany.id);
toast.success('DODA eliminado correctamente');
selectedIds = [];
reloadData();
} catch (e) {
toast.error('Error al eliminar DODA');
@@ -188,8 +191,9 @@
}
}
function handleRowClick(doda: Doda) {
selectedId = selectedId === doda.id ? null : doda.id;
function handleRowClick(row: Doda) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
</script>
@@ -200,10 +204,6 @@
<p class="text-muted-foreground">Gestiona tus Documentos de Operación Aduanera (DODA)</p>
</div>
<div class="flex items-center gap-3">
<Button variant="outline" size="sm" class="h-9" onclick={reloadData} disabled={loading}>
<RefreshCw class="mr-2 h-4 w-4 {loading ? 'animate-spin' : ''}" />
Actualizar
</Button>
<Button class="h-9" onclick={handleCreateClick}>
<Plus class="mr-2 h-4 w-4" />
Nuevo DODA
@@ -270,11 +270,11 @@
<div class="rounded-md border bg-background">
<DataTable
data={allItems}
columns={createColumns(reloadData)}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedId}
selectedId={selectedIds.length === 1 ? selectedIds[0] : null}
onRowClick={handleRowClick}
onRowDoubleClick={(item) => goto(`/dashboard/general_catalogs/doda/edit/${item.id}`)}
/>
@@ -292,42 +292,35 @@
<!-- Footer fijo de acciones (estilo Facturas) -->
<div
id="doda-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex items-center justify-between">
<div class="text-sm text-muted-foreground">
{#if selectedDoda}
Seleccionado: <span class="font-medium text-foreground"
>{selectedDoda.integration_number || 'S/N'}</span
>
{:else}
Selecciona un registro para ver acciones
{/if}
</div>
<div class="flex gap-2">
<Button variant="outline" size="sm" onclick={reloadData} disabled={loading}>
<RefreshCw class="mr-2 h-4 w-4 {loading ? 'animate-spin' : ''}" />
Actualizar
</Button>
<Button variant="outline" size="sm" onclick={handleEdit} disabled={!selectedId}>
<Pencil class="mr-2 h-4 w-4" />
Editar
</Button>
<Button variant="destructive" size="sm" onclick={handleDelete} disabled={!selectedId}>
<Trash2 class="mr-2 h-4 w-4" />
Eliminar
</Button>
<Separator orientation="vertical" class="mx-1 h-8" />
<Button variant="secondary" size="sm" disabled={!selectedId}>
<Printer class="mr-2 h-4 w-4" />
Imprimir
</Button>
</div>
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEdit}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDelete}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
<Separator orientation="vertical" class="mx-1 h-8" />
<Button variant="secondary" size="sm" disabled={selectedIds.length !== 1}>
<Printer size={16} class="mr-2" />
Imprimir
</Button>
</div>
</div>
</div>

View File

@@ -8,21 +8,25 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaAvisosElectrónicos } from '$lib/config/shortcuts/dashboard/general_catalogs/electronic_notices/list';
import { getElectronicNotices } from '$lib/api/dashboard/a76/general_catalogs/electronic-notices';
import { getElectronicNotices, deleteElectronicNotice, type ElectronicNotice } from '$lib/api/dashboard/a76/general_catalogs/electronic-notices';
import { companyStore } from '$lib/stores/company.svelte';
let { data } = $props();
let dialogOpen = $state(false);
let editingItem = $state<ElectronicNotice | null>(null);
let error = $state<string | null>(data.error || null);
// Atajos
useShortcuts(
'Lista Avisos Electrónicos',
obtenerAtajosListaAvisosElectrónicos({
manejarNuevo: () => (dialogOpen = true),
manejarNuevo: () => {
editingItem = null;
dialogOpen = true;
},
manejarRefrescar: handleSuccess
})
);
@@ -32,12 +36,18 @@
let searchPedimento = $state($page.url.searchParams.get('pedimento') || '');
let timeout: ReturnType<typeof setTimeout>;
let allItems = $state(data.notices?.items || []);
let allItems = $state<ElectronicNotice[]>(data.notices?.items || []);
let currentPage = $state(data.notices?.page || 1);
let pageSize = $state(data.notices?.page_size || 50);
let totalItems = $state(data.notices?.total || 0);
let loading = $state(false);
let hasMore = $derived(allItems.length < totalItems);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
: null
);
$effect(() => {
if (data.notices) {
@@ -146,8 +156,50 @@
}
function handleSuccess() {
dialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: ElectronicNotice) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: ElectronicNotice) {
editingItem = row;
dialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
dialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar el aviso "${selectedItem.notice_number}"?`)) {
return;
}
loading = true;
error = null;
try {
await deleteElectronicNotice(selectedItem.id, companyStore.activeCompany.id);
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar el aviso';
console.error('Error deleting notice:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -156,7 +208,7 @@
<h1 class="text-2xl font-bold tracking-tight">Avisos Electrónicos</h1>
<p class="text-muted-foreground">Gestión del catálogo de avisos electrónicos</p>
</div>
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (dialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nuevo Aviso</Button></div>
<div class="flex items-center gap-3"><Button class="h-9" onclick={() => { editingItem = null; dialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />Nuevo Aviso</Button></div>
</div>
{#if error}
@@ -167,10 +219,41 @@
<Card.Root class="border bg-background flex flex-col">
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Avisos</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="No. aviso" bind:value={searchNotice} oninput={handleSearch} class="h-9 w-40 bg-card lg:w-52" /><Input placeholder="Pedimento" bind:value={searchPedimento} oninput={handleSearch} class="h-9 w-40 bg-card lg:w-52" /></div></div></Card.Header>
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns(handleSuccess)} {loading} {hasMore} {loadMore} /></div></Card.Content>
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns()} {loading} {hasMore} {loadMore} {selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} /></div></Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<CreateEditDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="electronic-notices-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateEditDialog bind:open={dialogOpen} item={editingItem} onSuccess={handleSuccess} />
</div>

View File

@@ -8,12 +8,12 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaEquivalencias } from '$lib/config/shortcuts/dashboard/general_catalogs/equivalencies/list';
import DataEquivalenciesDialog from '$lib/components/dashboard/general_catalogs/equivalencies/data-equivalencies-dialog.svelte';
import type { PageData } from './$types';
import { getEquivalencies } from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
import { getEquivalencies, deleteEquivalency } from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
import { companyStore } from '$lib/stores/company.svelte';
let { data }: { data: PageData } = $props();
@@ -22,6 +22,11 @@
let dataDialogOpen = $state(false);
let selectedEquivalency = $state<Equivalency | null>(null);
let dataMode = $state<'create' | 'edit'>('edit');
let selectedIds = $state<(string | number)[]>([]);
const singleSelected = $derived(
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
);
// Atajos
useShortcuts(
@@ -148,20 +153,51 @@
}
function handleSuccess() {
dataDialogOpen = false;
selectedEquivalency = null;
selectedIds = [];
reloadData();
}
function handleOpenInsertItems(equivalency: Equivalency) {
selectedEquivalency = equivalency;
function handleRowClick(row: Equivalency) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: Equivalency) {
selectedEquivalency = row;
dataMode = 'edit';
dataDialogOpen = true;
}
function handleOpenEditCatalog(equivalency: Equivalency) {
selectedEquivalency = equivalency;
function handleEditSelected() {
if (!singleSelected) return;
selectedEquivalency = singleSelected;
dataMode = 'edit';
dataDialogOpen = true;
}
async function handleDeleteSelected() {
if (!singleSelected || !companyStore.activeCompany) return;
if (!confirm('¿Estás seguro de eliminar la equivalencia seleccionada?')) {
return;
}
loading = true;
error = null;
try {
await deleteEquivalency(singleSelected.id, companyStore.activeCompany.id);
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar la equivalencia';
console.error('Error deleting equivalency:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -170,7 +206,7 @@
<h1 class="text-2xl font-bold tracking-tight">Equivalencias</h1>
<p class="text-muted-foreground">Catálogo de equivalencias</p>
</div>
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9"
<div class="flex items-center gap-3"><Button class="h-9"
onclick={() => {
selectedEquivalency = null;
dataMode = 'create';
@@ -190,11 +226,42 @@
<Card.Root class="border bg-background flex flex-col">
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Equivalencias</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Código origen" bind:value={searchFrom} oninput={handleSearch} class="h-9 w-40 bg-card lg:w-52" /></div></div></Card.Header>
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createCatalogColumns({ onInsertItems: handleOpenInsertItems, onEdit: handleOpenEditCatalog, onSuccess: handleSuccess })} {loading} {hasMore} {loadMore} /></div></Card.Content>
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createCatalogColumns()} {loading} {hasMore} {loadMore} {selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} /></div></Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<div class="h-20"></div>
<div
id="equivalencies-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<DataEquivalenciesDialog
bind:open={dataDialogOpen}
equivalency={selectedEquivalency}

View File

@@ -8,22 +8,30 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaErrores } from '$lib/config/shortcuts/dashboard/general_catalogs/error_catalogs/list';
import type { PageData } from './$types';
import { getErrorCatalogs } from '$lib/api/dashboard/a76/general_catalogs/error-catalogs';
import { getErrorCatalogs, deleteErrorCatalog, type ErrorCatalog } from '$lib/api/dashboard/a76/general_catalogs/error-catalogs';
import { companyStore } from '$lib/stores/company.svelte';
let { data }: { data: PageData } = $props();
let dialogOpen = $state(false);
let editingItem = $state<ErrorCatalog | null>(null);
let error = $state<string | null>(data.error || null);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
);
// Atajos
useShortcuts(
'Lista Errores',
obtenerAtajosListaErrores({
manejarNuevo: () => (dialogOpen = true),
manejarNuevo: () => {
editingItem = null;
dialogOpen = true;
},
manejarActualizar: handleSuccess
})
);
@@ -147,8 +155,50 @@
}
function handleSuccess() {
dialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: ErrorCatalog) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: ErrorCatalog) {
editingItem = row;
dialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
dialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar el error "${selectedItem.code}"?`)) {
return;
}
loading = true;
error = null;
try {
await deleteErrorCatalog(selectedItem.id, companyStore.activeCompany.id);
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar el error';
console.error('Error deleting error catalog:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -157,7 +207,18 @@
<h1 class="text-2xl font-bold tracking-tight">Errores de Facturación</h1>
<p class="text-muted-foreground">Gestión del catálogo de errores de facturación</p>
</div>
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (dialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nuevo Error</Button></div>
<div class="flex items-center gap-3">
<Button
class="h-9"
onclick={() => {
editingItem = null;
dialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nuevo Error
</Button>
</div>
</div>
{#if error}
@@ -168,10 +229,59 @@
<Card.Root class="border bg-background flex flex-col">
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Errores</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Código" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="Descripción" bind:value={searchDesc} oninput={handleSearch} class="h-9 w-44 bg-card lg:w-64" /></div></div></Card.Header>
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns(handleSuccess)} {loading} {hasMore} {loadMore} /></div></Card.Content>
<Card.Content class="p-0">
<div class="rounded-md border bg-background overflow-hidden">
<InfiniteDataTable
data={allItems}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<CreateDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="error-catalogs-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateDialog
bind:open={dialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -8,23 +8,31 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaTiposCambio } from '$lib/config/shortcuts/dashboard/general_catalogs/exchange_rate/list';
import type { PageData } from './$types';
import { getExchangeRates } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
import { getExchangeRates, deleteExchangeRate, type ExchangeRate } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
import { companyStore } from '$lib/stores/company.svelte';
let { data }: { data: PageData } = $props();
let createDialogOpen = $state(false);
let editingItem = $state<ExchangeRate | null>(null);
let error = $state<string | null>(data.error || null);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
);
// Atajos
useShortcuts(
'Lista Tipos de Cambio',
obtenerAtajosListaTiposCambio({
manejarNuevo: () => (createDialogOpen = true),
manejarNuevo: () => {
editingItem = null;
createDialogOpen = true;
},
manejarActualizar: handleSuccess
})
);
@@ -136,8 +144,50 @@
}
function handleSuccess() {
createDialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: ExchangeRate) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: ExchangeRate) {
editingItem = row;
createDialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
createDialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar este tipo de cambio?`)) {
return;
}
loading = true;
error = null;
try {
await deleteExchangeRate(selectedItem.id, companyStore.activeCompany.id);
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar el tipo de cambio';
console.error('Error deleting exchange rate:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -146,7 +196,18 @@
<h1 class="text-2xl font-bold tracking-tight">Tipos de Cambio</h1>
<p class="text-muted-foreground">Catálogo de tipos de cambio</p>
</div>
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nuevo Tipo de Cambio</Button></div>
<div class="flex items-center gap-3">
<Button
class="h-9"
onclick={() => {
editingItem = null;
createDialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nuevo Tipo de Cambio
</Button>
</div>
</div>
{#if error}
@@ -157,10 +218,59 @@
<Card.Root class="border bg-background flex flex-col">
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Tipos de Cambio</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Moneda" bind:value={searchCurrency} oninput={handleSearch} class="h-9 w-40 bg-card lg:w-52" /></div></div></Card.Header>
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns(handleSuccess)} {loading} {hasMore} {loadMore} /></div></Card.Content>
<Card.Content class="p-0">
<div class="rounded-md border bg-background overflow-hidden">
<InfiniteDataTable
data={allItems}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="exchange-rate-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateEditDialog
bind:open={createDialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -8,23 +8,31 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import type { PageData } from './$types';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaIdentificadores } from '$lib/config/shortcuts/dashboard/general_catalogs/identifiers/list';
import { getIdentifiers } from '$lib/api/dashboard/a76/general_catalogs/identifiers';
import { getIdentifiers, deleteIdentifier, type Identifier } from '$lib/api/dashboard/a76/general_catalogs/identifiers';
import { companyStore } from '$lib/stores/company.svelte';
let { data }: { data: PageData } = $props();
let createDialogOpen = $state(false);
let editingItem = $state<Identifier | null>(null);
let error = $state<string | null>(data.error || null);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
);
// Atajos
useShortcuts(
'Lista Identificadores',
obtenerAtajosListaIdentificadores({
manejarNuevo: () => (createDialogOpen = true),
manejarNuevo: () => {
editingItem = null;
createDialogOpen = true;
},
manejarRefrescar: handleSuccess
})
);
@@ -138,8 +146,50 @@
}
function handleSuccess() {
createDialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: Identifier) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: Identifier) {
editingItem = row;
createDialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
createDialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar el identificador "${selectedItem.code}"?`)) {
return;
}
loading = true;
error = null;
try {
await deleteIdentifier(selectedItem.id, companyStore.activeCompany.id);
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar el identificador';
console.error('Error deleting identifier:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -149,11 +199,13 @@
<p class="text-muted-foreground">Catálogo de identificadores</p>
</div>
<div class="flex items-center gap-3">
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
<RefreshCw class="mr-2 h-4 w-4" />
Actualizar
</Button>
<Button class="h-9" onclick={() => (createDialogOpen = true)}>
<Button
class="h-9"
onclick={() => {
editingItem = null;
createDialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nuevo Identificador
</Button>
@@ -190,10 +242,14 @@
<div class="rounded-md border bg-background overflow-hidden">
<InfiniteDataTable
data={allItems}
columns={createColumns(handleSuccess)}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
@@ -203,5 +259,40 @@
Mostrando {allItems.length} de {totalItems} registros
</div>
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="identifiers-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateEditDialog
bind:open={createDialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -8,24 +8,32 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import type { PageData } from './$types';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaInpc } from '$lib/config/shortcuts/dashboard/general_catalogs/inpc/list';
import { getINPCs } from '$lib/api/dashboard/a76/general_catalogs/inpc';
import { getINPCs, deleteINPC, type INPC } from '$lib/api/dashboard/a76/general_catalogs/inpc';
import { companyStore } from '$lib/stores/company.svelte';
let { data }: { data: PageData } = $props();
let createDialogOpen = $state(false);
let editingItem = $state<INPC | null>(null);
let error = $state<string | null>(data.error || null);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
);
// Atajos
useShortcuts(
'Lista INPC',
obtenerAtajosListaInpc({
manejarNuevo: () => (createDialogOpen = true),
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
manejarNuevo: () => {
editingItem = null;
createDialogOpen = true;
},
manejarActualizar: handleSuccess
})
);
@@ -139,8 +147,55 @@
}
function handleSuccess() {
createDialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: INPC) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: INPC) {
editingItem = row;
createDialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
createDialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar el INPC del periodo ${selectedItem.year}-${selectedItem.month}?`)) {
return;
}
loading = true;
error = null;
try {
const response = await deleteINPC(selectedItem.id, companyStore.activeCompany.id);
if (response.error) {
error = response.error;
return;
}
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar el INPC';
console.error('Error deleting INPC:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -150,11 +205,12 @@
<p class="text-muted-foreground">Índice Nacional de Precios al Consumidor</p>
</div>
<div class="flex items-center gap-3">
<Button variant="outline" onclick={reloadData}>
<RefreshCw class="mr-2 h-4 w-4" />
Actualizar
</Button>
<Button onclick={() => (createDialogOpen = true)}>
<Button
onclick={() => {
editingItem = null;
createDialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nuevo INPC
</Button>
@@ -191,10 +247,14 @@
<div class="flex h-full min-h-0 rounded-md border bg-background overflow-hidden flex-col">
<InfiniteDataTable
data={allItems}
columns={createColumns(handleSuccess)}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
@@ -204,5 +264,40 @@
Mostrando {allItems.length} de {totalItems} registros
</div>
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="inpc-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateEditDialog
bind:open={createDialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -6,24 +6,28 @@
import CreateDialog from '$lib/components/dashboard/general_catalogs/legends/create-edit-dialog.svelte';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaLeyendas } from '$lib/config/shortcuts/dashboard/general_catalogs/legends/list';
import { getLegends } from '$lib/api/dashboard/a76/general_catalogs/legends';
import { getLegends, deleteLegend, type Legend } from '$lib/api/dashboard/a76/general_catalogs/legends';
import { companyStore } from '$lib/stores/company.svelte';
let { data } = $props();
let dialogOpen = $state(false);
let editingItem = $state<Legend | null>(null);
let error = $state<string | null>(data.error || null);
// Atajos
useShortcuts(
'Lista Leyendas',
obtenerAtajosListaLeyendas({
manejarNuevo: () => (dialogOpen = true),
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
manejarNuevo: () => {
editingItem = null;
dialogOpen = true;
},
manejarActualizar: handleSuccess
})
);
@@ -32,14 +36,20 @@
let searchDesc = $state($page.url.searchParams.get('description') || '');
let timeout: ReturnType<typeof setTimeout>;
const columns = $derived(createColumns(handleSuccess));
let allItems = $state(data.legends?.items || []);
let allItems = $state<Legend[]>(data.legends?.items || []);
let currentPage = $state(data.legends?.page || 1);
let pageSize = $state(data.legends?.page_size || 50);
let totalItems = $state(data.legends?.total || 0);
let loading = $state(false);
let hasMore = $derived(allItems.length < totalItems);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
: null
);
$effect(() => {
if (data.legends) {
@@ -63,6 +73,9 @@
if (searchDesc) filters.description = searchDesc;
const response = await getLegends(1, pageSize, companyStore.activeCompany.id, filters);
if (response.error) {
throw new Error(response.error);
}
if (response.data) {
allItems = response.data.items;
currentPage = 1;
@@ -101,6 +114,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data?.items) {
allItems = [...allItems, ...response.data.items];
currentPage += 1;
@@ -124,6 +140,9 @@
if (searchDesc) filters.description = searchDesc;
const response = await getLegends(1, pageSize, companyStore.activeCompany.id, filters);
if (response.error) {
throw new Error(response.error);
}
if (response.data) {
allItems = response.data.items;
currentPage = 1;
@@ -138,8 +157,53 @@
}
function handleSuccess() {
dialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: Legend) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: Legend) {
editingItem = row;
dialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
dialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar la leyenda "${selectedItem.code}"?`)) {
return;
}
loading = true;
error = null;
try {
const response = await deleteLegend(selectedItem.id, companyStore.activeCompany.id);
if (response.error) {
throw new Error(response.error);
}
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar la leyenda';
console.error('Error deleting legend:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -149,11 +213,13 @@
<p class="text-muted-foreground">Gestión del catálogo de leyendas fijas</p>
</div>
<div class="flex items-center gap-3">
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
<RefreshCw class="mr-2 h-4 w-4" />
Actualizar
</Button>
<Button class="h-9" onclick={() => (dialogOpen = true)}>
<Button
class="h-9"
onclick={() => {
editingItem = null;
dialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nueva Leyenda
</Button>
@@ -190,10 +256,14 @@
<div class="flex h-full min-h-0 rounded-md border bg-background overflow-hidden flex-col">
<InfiniteDataTable
data={allItems}
{columns}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
@@ -203,5 +273,40 @@
Mostrando {allItems.length} de {totalItems} registros
</div>
<CreateDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="legends-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateDialog
bind:open={dialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -6,27 +6,32 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaMonedas } from '$lib/config/shortcuts/dashboard/general_catalogs/multi_currency_types/list';
import {
getMultiCurrencyTypes,
deleteMultiCurrencyType,
type MultiCurrencyType
} from '$lib/api/dashboard/a76/general_catalogs/multi-currency-types';
import { companyStore } from '$lib/stores/company.svelte';
let { data } = $props();
let dialogOpen = $state(false);
let editingItem = $state<MultiCurrencyType | null>(null);
let error = $state<string | null>(data.error || null);
// Atajos
useShortcuts(
'Lista Monedas',
obtenerAtajosListaMonedas({
manejarNuevo: () => (dialogOpen = true),
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
manejarNuevo: () => {
editingItem = null;
dialogOpen = true;
},
manejarActualizar: handleSuccess
})
);
@@ -58,12 +63,18 @@
}
];
let allItems = $state(data.types?.items || []);
let allItems = $state<MultiCurrencyType[]>(data.types?.items || []);
let currentPage = $state(data.types?.page || 1);
let pageSize = $state(data.types?.page_size || 50);
let totalItems = $state(data.types?.total || 0);
let loading = $state(false);
let hasMore = $derived(allItems.length < totalItems);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
: null
);
$effect(() => {
if (data.types) {
@@ -91,6 +102,9 @@
country_key: searchCountry || undefined
}
);
if (response.error) {
throw new Error(response.error);
}
if (response.data?.items) {
allItems = response.data.items;
currentPage = response.data.page || 1;
@@ -128,6 +142,9 @@
country_key: searchCountry || undefined
}
);
if (response.error) {
throw new Error(response.error);
}
if (response.data?.items) {
allItems = [...allItems, ...response.data.items];
currentPage += 1;
@@ -155,6 +172,9 @@
country_key: searchCountry || undefined
}
);
if (response.error) {
throw new Error(response.error);
}
if (response.data?.items) {
allItems = response.data.items;
currentPage = response.data.page || 1;
@@ -169,8 +189,53 @@
}
function handleSuccess() {
dialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: MultiCurrencyType) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: MultiCurrencyType) {
editingItem = row;
dialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
dialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar el tipo de moneda "${selectedItem.currency_type_code}"?`)) {
return;
}
loading = true;
error = null;
try {
const response = await deleteMultiCurrencyType(selectedItem.id, companyStore.activeCompany.id);
if (response.error) {
throw new Error(response.error);
}
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar el tipo de moneda';
console.error('Error deleting multi-currency type:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -179,7 +244,18 @@
<h1 class="text-2xl font-bold tracking-tight">Tipos de Moneda Múltiple</h1>
<p class="text-muted-foreground">Gestión del catálogo de tipos de moneda múltiple</p>
</div>
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (dialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nuevo Tipo</Button></div>
<div class="flex items-center gap-3">
<Button
class="h-9"
onclick={() => {
editingItem = null;
dialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nuevo Tipo
</Button>
</div>
</div>
{#if error}
@@ -190,10 +266,59 @@
<Card.Root class="border bg-background flex flex-col">
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Monedas</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Código" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="País" bind:value={searchCountry} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /></div></div></Card.Header>
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={columns} {loading} {hasMore} {loadMore} /></div></Card.Content>
<Card.Content class="p-0">
<div class="rounded-md border bg-background overflow-hidden">
<InfiniteDataTable
data={allItems}
{columns}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<CreateDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="multi-currency-types-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateDialog
bind:open={dialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -8,24 +8,28 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import type { PageData } from './$types';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaPuertos } from '$lib/config/shortcuts/dashboard/general_catalogs/ports/list';
import { portsApi } from '$lib/api/dashboard/a76/general_catalogs/ports';
import { portsApi, type Port } from '$lib/api/dashboard/a76/general_catalogs/ports';
import { companyStore } from '$lib/stores/company.svelte';
let { data }: { data: PageData } = $props();
let createDialogOpen = $state(false);
let editingItem = $state<Port | null>(null);
let error = $state<string | null>(data.items?.error || null);
// Atajos
useShortcuts(
'Lista Puertos',
obtenerAtajosListaPuertos({
manejarNuevo: () => (createDialogOpen = true),
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
manejarNuevo: () => {
editingItem = null;
createDialogOpen = true;
},
manejarActualizar: handleSuccess
})
);
@@ -34,12 +38,18 @@
let searchDesc = $state($page.url.searchParams.get('description') || '');
let timeout: ReturnType<typeof setTimeout>;
let allItems = $state(data.items?.items || data.items || []);
let allItems = $state<Port[]>(data.items?.items || data.items || []);
let currentPage = $state(data.items?.page || 1);
let pageSize = $state(data.items?.pageSize || data.items?.page_size || 50);
let totalItems = $state(data.items?.total || 0);
let loading = $state(false);
let hasMore = $derived(allItems.length < totalItems);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
: null
);
$effect(() => {
if (data.items?.items) {
@@ -142,8 +152,50 @@
}
function handleSuccess() {
createDialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: Port) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: Port) {
editingItem = row;
createDialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
createDialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar el puerto "${selectedItem.port_code}"?`)) {
return;
}
loading = true;
error = null;
try {
await portsApi.delete(selectedItem.id, companyStore.activeCompany.id);
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar el puerto';
console.error('Error deleting port:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -153,11 +205,13 @@
<p class="text-muted-foreground">Catálogo de puertos</p>
</div>
<div class="flex items-center gap-3">
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
<RefreshCw class="mr-2 h-4 w-4" />
Actualizar
</Button>
<Button class="h-9" onclick={() => (createDialogOpen = true)}>
<Button
class="h-9"
onclick={() => {
editingItem = null;
createDialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nuevo Puerto
</Button>
@@ -186,10 +240,14 @@
<div class="rounded-md border bg-background overflow-hidden">
<InfiniteDataTable
data={allItems}
columns={createColumns(handleSuccess)}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
@@ -197,5 +255,41 @@
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="ports-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateEditDialog
bind:open={createDialogOpen}
mode={editingItem ? 'edit' : 'create'}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -5,25 +5,33 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte';
import { createColumns } from '$lib/components/dashboard/general_catalogs/prevalidators/columns';
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/prevalidators/create-edit-dialog.svelte';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaPrevalidadores } from '$lib/config/shortcuts/dashboard/general_catalogs/prevalidators/list';
import { getPrevalidators } from '$lib/api/dashboard/a76/general_catalogs/prevalidators';
import { getPrevalidators, deletePrevalidator, type Prevalidator } from '$lib/api/dashboard/a76/general_catalogs/prevalidators';
import { companyStore } from '$lib/stores/company.svelte';
let { data } = $props();
let dialogOpen = $state(false);
let editingItem = $state<Prevalidator | null>(null);
let error = $state<string | null>(data.error || null);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
);
// Atajos
useShortcuts(
'Lista Prevalidadores',
obtenerAtajosListaPrevalidadores({
manejarNuevo: () => (dialogOpen = true),
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
manejarNuevo: () => {
editingItem = null;
dialogOpen = true;
},
manejarActualizar: handleSuccess
})
);
@@ -135,8 +143,50 @@
}
function handleSuccess() {
dialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: Prevalidator) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: Prevalidator) {
editingItem = row;
dialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
dialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar el prevalidador "${selectedItem.code}"?`)) {
return;
}
loading = true;
error = null;
try {
await deletePrevalidator(selectedItem.id, companyStore.activeCompany.id);
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar el prevalidador';
console.error('Error deleting prevalidator:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -146,8 +196,16 @@
<p class="text-muted-foreground">Catálogo de prevalidadores</p>
</div>
<div class="flex items-center gap-3">
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button>
<Button class="h-9" onclick={() => (dialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nuevo Prevalidador</Button>
<Button
class="h-9"
onclick={() => {
editingItem = null;
dialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nuevo Prevalidador
</Button>
</div>
</div>
@@ -167,10 +225,59 @@
</div>
</div>
</Card.Header>
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns(handleSuccess)} {loading} {hasMore} {loadMore} /></div></Card.Content>
<Card.Content class="p-0">
<div class="rounded-md border bg-background overflow-hidden">
<InfiniteDataTable
data={allItems}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<CreateEditDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="prevalidators-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateEditDialog
bind:open={dialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -3,12 +3,11 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import DataTable from '$lib/components/dashboard/seal/data-table.svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte';
import CreateEditDialog from '$lib/components/dashboard/seal/create-edit-dialog.svelte';
import { createColumns } from '$lib/components/dashboard/seal/columns';
import type { Seal } from '$lib/api/dashboard/a76/general_catalogs/seal';
import { getSeals } from '$lib/api/dashboard/a76/general_catalogs/seal';
import { getSeals, deleteSeal, type Seal } from '$lib/api/dashboard/a76/general_catalogs/seal';
import { companyStore } from '$lib/stores/company.svelte';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaSellos } from '$lib/config/shortcuts/dashboard/general_catalogs/seal/list';
@@ -21,13 +20,21 @@
let totalItems = $state(0);
let dialogOpen = $state(false);
let editingItem = $state<Seal | null>(null);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
);
// Atajos
useShortcuts(
'Lista Sellos',
obtenerAtajosListaSellos({
manejarNuevo: handleCreate,
manejarActualizar: loadInitialData
manejarNuevo: () => {
editingItem = null;
dialogOpen = true;
},
manejarActualizar: handleSuccess
})
);
@@ -125,19 +132,55 @@
loadInitialData();
}
function handleCreate() {
function handleSuccess() {
dialogOpen = false;
editingItem = null;
selectedIds = [];
loadInitialData();
}
function handleRowClick(row: Seal) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: Seal) {
editingItem = row;
dialogOpen = true;
}
function handleSuccess() {
loadInitialData();
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
dialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar el sello "${selectedItem.seal}"?`)) {
return;
}
loading = true;
error = null;
try {
await deleteSeal(selectedItem.id, companyStore.activeCompany.id);
selectedIds = [];
await loadInitialData();
} catch (err) {
error = 'Error al eliminar el sello';
console.error('Error deleting seal:', err);
} finally {
loading = false;
}
}
function reloadData() {
loadInitialData();
}
const columns = createColumns(handleSuccess);
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -147,11 +190,13 @@
<p class="text-muted-foreground">Gestiona los sellos de tu empresa</p>
</div>
<div class="flex items-center gap-3">
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
<RefreshCw class="mr-2 h-4 w-4" />
Actualizar
</Button>
<Button class="h-9" onclick={handleCreate}>
<Button
class="h-9"
onclick={() => {
editingItem = null;
dialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nuevo Sello
</Button>
@@ -181,7 +226,17 @@
</Card.Header>
<Card.Content class="min-h-0 flex-1 p-0">
<div class="flex h-full min-h-0 rounded-md border bg-background overflow-hidden flex-col">
<DataTable data={allItems} {columns} {loading} {hasMore} {loadMore} />
<InfiniteDataTable
data={allItems}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
</Card.Root>
@@ -190,5 +245,40 @@
Mostrando {allItems.length} de {totalItems || allItems.length} registros
</div>
<CreateEditDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="seal-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateEditDialog
bind:open={dialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -5,25 +5,33 @@
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import * as Card from '$lib/components/ui/card';
import { Plus, RefreshCw } from 'lucide-svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte';
import { createColumns } from '$lib/components/dashboard/general_catalogs/signatures/columns';
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/signatures/create-edit-dialog.svelte';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaFirmas } from '$lib/config/shortcuts/dashboard/general_catalogs/signatures/list';
import { getSignatures } from '$lib/api/dashboard/a76/general_catalogs/signatures';
import { getSignatures, deleteSignature, type Signature } from '$lib/api/dashboard/a76/general_catalogs/signatures';
import { companyStore } from '$lib/stores/company.svelte';
let { data } = $props();
let dialogOpen = $state(false);
let editingItem = $state<Signature | null>(null);
let error = $state<string | null>(data.error || null);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
);
// Atajos
useShortcuts(
'Lista Firmas',
obtenerAtajosListaFirmas({
manejarNuevo: () => (dialogOpen = true),
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
manejarNuevo: () => {
editingItem = null;
dialogOpen = true;
},
manejarActualizar: handleSuccess
})
);
@@ -137,8 +145,50 @@
}
function handleSuccess() {
dialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: Signature) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: Signature) {
editingItem = row;
dialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
dialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar la firma "${selectedItem.code}"?`)) {
return;
}
loading = true;
error = null;
try {
await deleteSignature(selectedItem.id, companyStore.activeCompany.id);
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar la firma';
console.error('Error deleting signature:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -147,7 +197,18 @@
<h1 class="text-2xl font-bold tracking-tight">Firmas Electrónicas</h1>
<p class="text-muted-foreground">Gestión del catálogo de firmas electrónicas</p>
</div>
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (dialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nueva Firma</Button></div>
<div class="flex items-center gap-3">
<Button
class="h-9"
onclick={() => {
editingItem = null;
dialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nueva Firma
</Button>
</div>
</div>
{#if error}
@@ -158,10 +219,59 @@
<Card.Root class="border bg-background flex flex-col">
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Firmas</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Código" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /></div></div></Card.Header>
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns(handleSuccess)} {loading} {hasMore} {loadMore} /></div></Card.Content>
<Card.Content class="p-0">
<div class="rounded-md border bg-background overflow-hidden">
<InfiniteDataTable
data={allItems}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<CreateEditDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="signatures-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateEditDialog
bind:open={dialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -8,23 +8,27 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import type { PageData } from './$types';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaConversiones } from '$lib/config/shortcuts/dashboard/general_catalogs/unit_conversions/list';
import { getUnitConversions } from '$lib/api/dashboard/a76/general_catalogs/unit-conversions';
import { getUnitConversions, deleteUnitConversion, type UnitConversion } from '$lib/api/dashboard/a76/general_catalogs/unit-conversions';
import { companyStore } from '$lib/stores/company.svelte';
let { data }: { data: PageData } = $props();
let createDialogOpen = $state(false);
let editingItem = $state<UnitConversion | null>(null);
let error = $state<string | null>(data.error || null);
// Atajos
useShortcuts(
'Lista Conversiones',
obtenerAtajosListaConversiones({
manejarNuevo: () => (createDialogOpen = true),
manejarNuevo: () => {
editingItem = null;
createDialogOpen = true;
},
manejarRefrescar: handleSuccess
})
);
@@ -34,12 +38,18 @@
let searchTo = $state($page.url.searchParams.get('to_unit_code') || '');
let timeout: ReturnType<typeof setTimeout>;
let allItems = $state(data.conversions?.items || []);
let allItems = $state<UnitConversion[]>(data.conversions?.items || []);
let currentPage = $state(data.conversions?.page || 1);
let pageSize = $state(data.conversions?.page_size || 50);
let totalItems = $state(data.conversions?.total || 0);
let loading = $state(false);
let hasMore = $derived(allItems.length < totalItems);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
: null
);
$effect(() => {
if (data.conversions) {
@@ -148,8 +158,50 @@
}
function handleSuccess() {
createDialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: UnitConversion) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: UnitConversion) {
editingItem = row;
createDialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
createDialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar esta conversión?`)) {
return;
}
loading = true;
error = null;
try {
await deleteUnitConversion(selectedItem.id, companyStore.activeCompany.id);
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar la conversión';
console.error('Error deleting unit conversion:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -158,7 +210,18 @@
<h1 class="text-2xl font-bold tracking-tight">Conversiones de Unidades</h1>
<p class="text-muted-foreground">Catálogo de conversiones de unidades de medida</p>
</div>
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nueva Conversión</Button></div>
<div class="flex items-center gap-3">
<Button
class="h-9"
onclick={() => {
editingItem = null;
createDialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nueva Conversión
</Button>
</div>
</div>
{#if error}
@@ -169,10 +232,60 @@
<Card.Root class="border bg-background flex flex-col">
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Conversiones</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Desde" bind:value={searchFrom} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="Hacia" bind:value={searchTo} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /></div></div></Card.Header>
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns(handleSuccess)} {loading} {hasMore} {loadMore} /></div></Card.Content>
<Card.Content class="p-0">
<div class="rounded-md border bg-background overflow-hidden">
<InfiniteDataTable
data={allItems}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="unit-conversions-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateEditDialog
bind:open={createDialogOpen}
mode={editingItem ? 'edit' : 'create'}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -8,24 +8,28 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import type { PageData } from './$types';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaUnidadesACE } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/ace/list';
import { getUnitsOfMeasureACE } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
import { getUnitsOfMeasureACE, deleteUnitOfMeasureACE, type UnitOfMeasureACE } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
import { companyStore } from '$lib/stores/company.svelte';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
let createDialogOpen = $state(false);
let editingItem = $state<UnitOfMeasureACE | null>(null);
let error = $state<string | null>(data.error || null);
// Atajos
useShortcuts(
'Lista Unidades ACE',
obtenerAtajosListaUnidadesACE({
manejarNuevo: () => (createDialogOpen = true),
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
manejarNuevo: () => {
editingItem = null;
createDialogOpen = true;
},
manejarActualizar: handleSuccess
})
);
@@ -34,12 +38,18 @@
let searchDesc = $state($page.url.searchParams.get('description') || '');
let timeout: ReturnType<typeof setTimeout>;
let allItems = $state(data.ace_units?.items || []);
let allItems = $state<UnitOfMeasureACE[]>(data.ace_units?.items || []);
let currentPage = $state(data.ace_units?.page || 1);
let pageSize = $state(data.ace_units?.page_size || 50);
let totalItems = $state(data.ace_units?.total || 0);
let loading = $state(false);
let hasMore = $derived(allItems.length < totalItems);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
: null
);
$effect(() => {
if (data.ace_units) {
@@ -68,6 +78,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data) {
allItems = response.data.items;
currentPage = 1;
@@ -106,6 +119,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data?.items) {
allItems = [...allItems, ...response.data.items];
currentPage += 1;
@@ -134,6 +150,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data) {
allItems = response.data.items;
currentPage = 1;
@@ -148,8 +167,53 @@
}
function handleSuccess() {
createDialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: UnitOfMeasureACE) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: UnitOfMeasureACE) {
editingItem = row;
createDialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
createDialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar la unidad "${selectedItem.code}"?`)) {
return;
}
loading = true;
error = null;
try {
const response = await deleteUnitOfMeasureACE(selectedItem.id, companyStore.activeCompany.id);
if (response.error) {
throw new Error(response.error);
}
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar la unidad';
console.error('Error deleting ACE unit:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -158,7 +222,18 @@
<h1 class="text-2xl font-bold tracking-tight">Unidades de Medida ACE</h1>
<p class="text-muted-foreground">Catálogo de unidades de medida ACE</p>
</div>
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nueva Unidad</Button></div>
<div class="flex items-center gap-3">
<Button
class="h-9"
onclick={() => {
editingItem = null;
createDialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nueva Unidad
</Button>
</div>
</div>
{#if error}
@@ -169,10 +244,59 @@
<Card.Root class="border bg-background flex flex-col">
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Unidades ACE</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Clave" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="Descripción" bind:value={searchDesc} oninput={handleSearch} class="h-9 w-44 bg-card lg:w-64" /></div></div></Card.Header>
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns(handleSuccess)} {loading} {hasMore} {loadMore} /></div></Card.Content>
<Card.Content class="p-0">
<div class="rounded-md border bg-background overflow-hidden">
<InfiniteDataTable
data={allItems}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="ace-units-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateEditDialog
bind:open={createDialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -8,24 +8,28 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import type { PageData } from './$types';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaUnidadesAmericanas } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/american/list';
import { getUnitsOfMeasureAmerican } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
import { getUnitsOfMeasureAmerican, deleteUnitOfMeasureAmerican, type UnitOfMeasureAmerican } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
import { companyStore } from '$lib/stores/company.svelte';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
let createDialogOpen = $state(false);
let editingItem = $state<UnitOfMeasureAmerican | null>(null);
let error = $state<string | null>(data.error || null);
// Atajos
useShortcuts(
'Lista Unidades Americanas',
obtenerAtajosListaUnidadesAmericanas({
manejarNuevo: () => (createDialogOpen = true),
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
manejarNuevo: () => {
editingItem = null;
createDialogOpen = true;
},
manejarActualizar: handleSuccess
})
);
@@ -34,12 +38,18 @@
let searchDesc = $state($page.url.searchParams.get('description') || '');
let timeout: ReturnType<typeof setTimeout>;
let allItems = $state(data.american_units?.items || []);
let allItems = $state<UnitOfMeasureAmerican[]>(data.american_units?.items || []);
let currentPage = $state(data.american_units?.page || 1);
let pageSize = $state(data.american_units?.page_size || 50);
let totalItems = $state(data.american_units?.total || 0);
let loading = $state(false);
let hasMore = $derived(allItems.length < totalItems);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
: null
);
$effect(() => {
if (data.american_units) {
@@ -68,6 +78,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data) {
allItems = response.data.items;
currentPage = 1;
@@ -106,6 +119,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data?.items) {
allItems = [...allItems, ...response.data.items];
currentPage += 1;
@@ -134,6 +150,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data) {
allItems = response.data.items;
currentPage = 1;
@@ -148,8 +167,53 @@
}
function handleSuccess() {
createDialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: UnitOfMeasureAmerican) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: UnitOfMeasureAmerican) {
editingItem = row;
createDialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
createDialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar la unidad "${selectedItem.code}"?`)) {
return;
}
loading = true;
error = null;
try {
const response = await deleteUnitOfMeasureAmerican(selectedItem.id, companyStore.activeCompany.id);
if (response.error) {
throw new Error(response.error);
}
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar la unidad';
console.error('Error deleting American unit:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -158,7 +222,18 @@
<h1 class="text-2xl font-bold tracking-tight">Unidades de Medida Americanas</h1>
<p class="text-muted-foreground">Catálogo de unidades de medida Americanas</p>
</div>
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nueva Unidad</Button></div>
<div class="flex items-center gap-3">
<Button
class="h-9"
onclick={() => {
editingItem = null;
createDialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nueva Unidad
</Button>
</div>
</div>
{#if error}
@@ -169,10 +244,59 @@
<Card.Root class="border bg-background flex flex-col">
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Unidades Americanas</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Clave" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="Descripción" bind:value={searchDesc} oninput={handleSearch} class="h-9 w-44 bg-card lg:w-64" /></div></div></Card.Header>
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable columns={createColumns(handleSuccess)} data={allItems} {loading} {hasMore} {loadMore} /></div></Card.Content>
<Card.Content class="p-0">
<div class="rounded-md border bg-background overflow-hidden">
<InfiniteDataTable
data={allItems}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="american-units-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateEditDialog
bind:open={createDialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -8,24 +8,28 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import type { PageData } from './$types';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaUnidadesAduanas } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/customs/list';
import { getUnitsOfMeasureCustoms } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
import { getUnitsOfMeasureCustoms, deleteUnitOfMeasureCustoms, type UnitOfMeasureCustoms } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
import { companyStore } from '$lib/stores/company.svelte';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
let createDialogOpen = $state(false);
let editingItem = $state<UnitOfMeasureCustoms | null>(null);
let error = $state<string | null>(data.error || null);
// Atajos
useShortcuts(
'Lista Unidades Aduanas',
obtenerAtajosListaUnidadesAduanas({
manejarNuevo: () => (createDialogOpen = true),
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
manejarNuevo: () => {
editingItem = null;
createDialogOpen = true;
},
manejarActualizar: handleSuccess
})
);
@@ -34,12 +38,18 @@
let searchDesc = $state($page.url.searchParams.get('description') || '');
let timeout: ReturnType<typeof setTimeout>;
let allItems = $state(data.customs_units?.items || []);
let allItems = $state<UnitOfMeasureCustoms[]>(data.customs_units?.items || []);
let currentPage = $state(data.customs_units?.page || 1);
let pageSize = $state(data.customs_units?.page_size || 50);
let totalItems = $state(data.customs_units?.total || 0);
let loading = $state(false);
let hasMore = $derived(allItems.length < totalItems);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
: null
);
$effect(() => {
if (data.customs_units) {
@@ -68,6 +78,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data) {
allItems = response.data.items;
currentPage = 1;
@@ -106,6 +119,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data?.items) {
allItems = [...allItems, ...response.data.items];
currentPage += 1;
@@ -134,6 +150,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data) {
allItems = response.data.items;
currentPage = 1;
@@ -148,8 +167,53 @@
}
function handleSuccess() {
createDialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: UnitOfMeasureCustoms) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: UnitOfMeasureCustoms) {
editingItem = row;
createDialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
createDialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar la unidad "${selectedItem.code}"?`)) {
return;
}
loading = true;
error = null;
try {
const response = await deleteUnitOfMeasureCustoms(selectedItem.id, companyStore.activeCompany.id);
if (response.error) {
throw new Error(response.error);
}
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar la unidad';
console.error('Error deleting customs unit:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -158,7 +222,18 @@
<h1 class="text-2xl font-bold tracking-tight">Unidades de Medida Aduanas MEX</h1>
<p class="text-muted-foreground">Catálogo de unidades de medida para aduanas mexicanas</p>
</div>
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nueva Unidad</Button></div>
<div class="flex items-center gap-3">
<Button
class="h-9"
onclick={() => {
editingItem = null;
createDialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nueva Unidad
</Button>
</div>
</div>
{#if error}
@@ -169,10 +244,59 @@
<Card.Root class="border bg-background flex flex-col">
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Unidades Aduanas MX</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Clave" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="Descripción" bind:value={searchDesc} oninput={handleSearch} class="h-9 w-44 bg-card lg:w-64" /></div></div></Card.Header>
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable columns={createColumns(handleSuccess)} data={allItems} {loading} {hasMore} {loadMore} /></div></Card.Content>
<Card.Content class="p-0">
<div class="rounded-md border bg-background overflow-hidden">
<InfiniteDataTable
data={allItems}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="customs-units-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateEditDialog
bind:open={createDialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -8,24 +8,28 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import type { PageData } from './$types';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaUnidadesGeneral } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/general/list';
import { getUnitsOfMeasureGeneral } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
import { getUnitsOfMeasureGeneral, deleteUnitOfMeasureGeneral, type UnitOfMeasureGeneral } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
import { companyStore } from '$lib/stores/company.svelte';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
let createDialogOpen = $state(false);
let editingItem = $state<UnitOfMeasureGeneral | null>(null);
let error = $state<string | null>(data.error || null);
// Atajos
useShortcuts(
'Lista Unidades de Medida',
obtenerAtajosListaUnidadesGeneral({
manejarNuevo: () => (createDialogOpen = true),
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
manejarNuevo: () => {
editingItem = null;
createDialogOpen = true;
},
manejarActualizar: handleSuccess
})
);
@@ -34,12 +38,18 @@
let searchDesc = $state($page.url.searchParams.get('description') || '');
let timeout: ReturnType<typeof setTimeout>;
let allItems = $state(data.general_units?.items || []);
let allItems = $state<UnitOfMeasureGeneral[]>(data.general_units?.items || []);
let currentPage = $state(data.general_units?.page || 1);
let pageSize = $state(data.general_units?.page_size || 50);
let totalItems = $state(data.general_units?.total || 0);
let loading = $state(false);
let hasMore = $derived(allItems.length < totalItems);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
: null
);
$effect(() => {
if (data.general_units) {
@@ -68,6 +78,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data) {
allItems = response.data.items;
currentPage = 1;
@@ -106,6 +119,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data?.items) {
allItems = [...allItems, ...response.data.items];
currentPage += 1;
@@ -134,6 +150,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data) {
allItems = response.data.items;
currentPage = 1;
@@ -148,8 +167,53 @@
}
function handleSuccess() {
createDialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: UnitOfMeasureGeneral) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: UnitOfMeasureGeneral) {
editingItem = row;
createDialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
createDialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar la unidad "${selectedItem.code}"?`)) {
return;
}
loading = true;
error = null;
try {
const response = await deleteUnitOfMeasureGeneral(selectedItem.id, companyStore.activeCompany.id);
if (response.error) {
throw new Error(response.error);
}
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar la unidad';
console.error('Error deleting general unit:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -158,7 +222,18 @@
<h1 class="text-2xl font-bold tracking-tight">Unidades de Medida</h1>
<p class="text-muted-foreground">Catálogo general de unidades de medida</p>
</div>
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nueva Unidad</Button></div>
<div class="flex items-center gap-3">
<Button
class="h-9"
onclick={() => {
editingItem = null;
createDialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nueva Unidad
</Button>
</div>
</div>
{#if error}
@@ -169,10 +244,59 @@
<Card.Root class="border bg-background flex flex-col">
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Unidades</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Clave" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="Descripción" bind:value={searchDesc} oninput={handleSearch} class="h-9 w-44 bg-card lg:w-64" /></div></div></Card.Header>
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable columns={createColumns(handleSuccess)} data={allItems} {loading} {hasMore} {loadMore} /></div></Card.Content>
<Card.Content class="p-0">
<div class="rounded-md border bg-background overflow-hidden">
<InfiniteDataTable
data={allItems}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="general-units-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateEditDialog
bind:open={createDialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -8,24 +8,28 @@
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import type { PageData } from './$types';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaUnidadesOMA } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/oma/list';
import { getUnitsOfMeasureOMA } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
import { getUnitsOfMeasureOMA, deleteUnitOfMeasureOMA, type UnitOfMeasureOMA } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
import { companyStore } from '$lib/stores/company.svelte';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
let createDialogOpen = $state(false);
let editingItem = $state<UnitOfMeasureOMA | null>(null);
let error = $state<string | null>(data.error || null);
// Atajos
useShortcuts(
'Lista Unidades OMA',
obtenerAtajosListaUnidadesOMA({
manejarNuevo: () => (createDialogOpen = true),
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
manejarNuevo: () => {
editingItem = null;
createDialogOpen = true;
},
manejarActualizar: handleSuccess
})
);
@@ -34,12 +38,18 @@
let searchDesc = $state($page.url.searchParams.get('description') || '');
let timeout: ReturnType<typeof setTimeout>;
let allItems = $state(data.oma_units?.items || []);
let allItems = $state<UnitOfMeasureOMA[]>(data.oma_units?.items || []);
let currentPage = $state(data.oma_units?.page || 1);
let pageSize = $state(data.oma_units?.page_size || 50);
let totalItems = $state(data.oma_units?.total || 0);
let loading = $state(false);
let hasMore = $derived(allItems.length < totalItems);
let selectedIds = $state<(string | number)[]>([]);
const selectedItem = $derived(
selectedIds.length === 1
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
: null
);
$effect(() => {
if (data.oma_units) {
@@ -68,6 +78,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data) {
allItems = response.data.items;
currentPage = 1;
@@ -106,6 +119,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data?.items) {
allItems = [...allItems, ...response.data.items];
currentPage += 1;
@@ -134,6 +150,9 @@
companyStore.activeCompany.id,
filters
);
if (response.error) {
throw new Error(response.error);
}
if (response.data) {
allItems = response.data.items;
currentPage = 1;
@@ -148,8 +167,53 @@
}
function handleSuccess() {
createDialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: UnitOfMeasureOMA) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: UnitOfMeasureOMA) {
editingItem = row;
createDialogOpen = true;
}
function handleEditSelected() {
if (!selectedItem) return;
editingItem = selectedItem;
createDialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedItem || !companyStore.activeCompany) return;
if (!confirm(`¿Estás seguro de eliminar la unidad "${selectedItem.code}"?`)) {
return;
}
loading = true;
error = null;
try {
const response = await deleteUnitOfMeasureOMA(selectedItem.id, companyStore.activeCompany.id);
if (response.error) {
throw new Error(response.error);
}
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar la unidad';
console.error('Error deleting OMA unit:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -158,7 +222,18 @@
<h1 class="text-2xl font-bold tracking-tight">Unidades de Medida OMA</h1>
<p class="text-muted-foreground">Catálogo de unidades de medida OMA</p>
</div>
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nueva Unidad</Button></div>
<div class="flex items-center gap-3">
<Button
class="h-9"
onclick={() => {
editingItem = null;
createDialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nueva Unidad
</Button>
</div>
</div>
{#if error}
@@ -169,10 +244,59 @@
<Card.Root class="border bg-background flex flex-col">
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Unidades OMA</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Clave" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="Descripción" bind:value={searchDesc} oninput={handleSearch} class="h-9 w-44 bg-card lg:w-64" /></div></div></Card.Header>
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable columns={createColumns(handleSuccess)} data={allItems} {loading} {hasMore} {loadMore} /></div></Card.Content>
<Card.Content class="p-0">
<div class="rounded-md border bg-background overflow-hidden">
<InfiniteDataTable
data={allItems}
columns={createColumns()}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
<div class="h-20"></div>
<div
id="oma-units-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
<CreateEditDialog
bind:open={createDialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>