feat: Implement API clients and UI components for managing units of measure and locations
- Added API client for OMA units of measure with CRUD operations. - Created API client for general unit measures with CRUD functionality. - Developed UI components for displaying and managing locations in a data table format. - Implemented data table actions for unit measures, including create, edit, and delete functionalities. - Integrated pagination and refresh capabilities in the data tables for both customs and general unit measures. - Enhanced user experience with dialogs for creating and editing units of measure. - Added server-side loading logic for fetching locations and units of measure with error handling.
This commit is contained in:
21
frontend/src/lib/api/dashboard/a76/general_catalogs/index.ts
Normal file
21
frontend/src/lib/api/dashboard/a76/general_catalogs/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Índice de exportación para catálogos generales A76
|
||||
*/
|
||||
|
||||
// Unit Measures - Main
|
||||
export * from './unit-measures';
|
||||
|
||||
// Unit Measures - Customs (Mexican)
|
||||
export * from './um-customs-mex';
|
||||
|
||||
// Unit Measures - American
|
||||
export * from './um-customs-ame';
|
||||
|
||||
// Unit Measures - ACE
|
||||
export * from './um-ace';
|
||||
|
||||
// Unit Measures - OMA
|
||||
export * from './um-oma';
|
||||
|
||||
// Locations (from ports)
|
||||
export * from './locations';
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* API Client para Locations - Ubicaciones relacionadas con puertos
|
||||
* Basado en los campos location_code y location_description del módulo de puertos
|
||||
*/
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Location {
|
||||
location_code: string;
|
||||
location_description: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nota: Las ubicaciones están integradas en el módulo de puertos.
|
||||
* Este archivo proporciona tipos para trabajar con ubicaciones,
|
||||
* pero las operaciones se realizan a través del módulo de puertos.
|
||||
*
|
||||
* Ver: /a76/ports para operaciones relacionadas con ubicaciones
|
||||
*/
|
||||
|
||||
/**
|
||||
* Obtiene ubicaciones únicas de los puertos
|
||||
* Esta función extrae las ubicaciones únicas de la lista de puertos
|
||||
*/
|
||||
export async function getLocationsFromPorts(): Promise<ApiResponse<Location[]>> {
|
||||
const portsResponse = await api.get('/a76/ports?page_size=1000');
|
||||
|
||||
if (portsResponse.error || !portsResponse.data) {
|
||||
return {
|
||||
error: portsResponse.error || 'Error al obtener puertos',
|
||||
status: portsResponse.status
|
||||
};
|
||||
}
|
||||
|
||||
// Extraer ubicaciones únicas
|
||||
const locationMap = new Map<string, Location>();
|
||||
const ports = portsResponse.data.items || [];
|
||||
|
||||
ports.forEach((port: any) => {
|
||||
if (port.location_code && !locationMap.has(port.location_code)) {
|
||||
locationMap.set(port.location_code, {
|
||||
location_code: port.location_code,
|
||||
location_description: port.location_description
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
data: Array.from(locationMap.values()),
|
||||
status: 200
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* API Client para UM ACE - Unidades de medida ACE
|
||||
*/
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface UMACE {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface UMACECreate {
|
||||
code: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UMACEUpdate {
|
||||
code?: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UMACEListResponse {
|
||||
items: UMACE[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lista todas las unidades de medida ACE
|
||||
*/
|
||||
export async function getUMACE(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UMACEListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/units-of-measure/ace?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene una unidad de medida por ID
|
||||
*/
|
||||
export async function getUMACEById(id: number): Promise<ApiResponse<UMACE>> {
|
||||
return await api.get(`/a76/units-of-measure/ace/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crea una nueva unidad de medida
|
||||
*/
|
||||
export async function createUMACE(data: UMACECreate): Promise<ApiResponse<UMACE>> {
|
||||
return await api.post('/a76/units-of-measure/ace', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza una unidad de medida
|
||||
*/
|
||||
export async function updateUMACE(id: number, data: UMACEUpdate): Promise<ApiResponse<UMACE>> {
|
||||
return await api.put(`/a76/units-of-measure/ace/${id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina una unidad de medida
|
||||
*/
|
||||
export async function deleteUMACE(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/units-of-measure/ace/${id}`);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* API Client para UM American - Unidades de medida americanas
|
||||
*/
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface UMCustomsAme {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface UMCustomsAmeCreate {
|
||||
code: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UMCustomsAmeUpdate {
|
||||
code?: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UMCustomsAmeListResponse {
|
||||
items: UMCustomsAme[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lista todas las unidades de medida americanas
|
||||
*/
|
||||
export async function getUMCustomsAme(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UMCustomsAmeListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/units-of-measure/american?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene una unidad de medida por ID
|
||||
*/
|
||||
export async function getUMCustomsAmeById(id: number): Promise<ApiResponse<UMCustomsAme>> {
|
||||
return await api.get(`/a76/units-of-measure/american/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crea una nueva unidad de medida
|
||||
*/
|
||||
export async function createUMCustomsAme(data: UMCustomsAmeCreate): Promise<ApiResponse<UMCustomsAme>> {
|
||||
return await api.post('/a76/units-of-measure/american', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza una unidad de medida
|
||||
*/
|
||||
export async function updateUMCustomsAme(id: number, data: UMCustomsAmeUpdate): Promise<ApiResponse<UMCustomsAme>> {
|
||||
return await api.put(`/a76/units-of-measure/american/${id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina una unidad de medida
|
||||
*/
|
||||
export async function deleteUMCustomsAme(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/units-of-measure/american/${id}`);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* API Client para UM Customs (Mexican) - Unidades de medida para aduanas mexicanas
|
||||
*/
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface UMCustomsMex {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface UMCustomsMexCreate {
|
||||
code: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UMCustomsMexUpdate {
|
||||
code?: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UMCustomsMexListResponse {
|
||||
items: UMCustomsMex[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lista todas las unidades de medida para aduanas mexicanas
|
||||
*/
|
||||
export async function getUMCustomsMex(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UMCustomsMexListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/units-of-measure/customs?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene una unidad de medida por ID
|
||||
*/
|
||||
export async function getUMCustomsMexById(id: number): Promise<ApiResponse<UMCustomsMex>> {
|
||||
return await api.get(`/a76/units-of-measure/customs/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crea una nueva unidad de medida
|
||||
*/
|
||||
export async function createUMCustomsMex(data: UMCustomsMexCreate): Promise<ApiResponse<UMCustomsMex>> {
|
||||
return await api.post('/a76/units-of-measure/customs', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza una unidad de medida
|
||||
*/
|
||||
export async function updateUMCustomsMex(id: number, data: UMCustomsMexUpdate): Promise<ApiResponse<UMCustomsMex>> {
|
||||
return await api.put(`/a76/units-of-measure/customs/${id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina una unidad de medida
|
||||
*/
|
||||
export async function deleteUMCustomsMex(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/units-of-measure/customs/${id}`);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* API Client para UM OMA - Unidades de medida OMA
|
||||
*/
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface UMOMA {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface UMOMACreate {
|
||||
code: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UMOMAUpdate {
|
||||
code?: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UMOMAListResponse {
|
||||
items: UMOMA[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lista todas las unidades de medida OMA
|
||||
*/
|
||||
export async function getUMOMA(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UMOMAListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/units-of-measure/oma?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene una unidad de medida por ID
|
||||
*/
|
||||
export async function getUMOMAById(id: number): Promise<ApiResponse<UMOMA>> {
|
||||
return await api.get(`/a76/units-of-measure/oma/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crea una nueva unidad de medida
|
||||
*/
|
||||
export async function createUMOMA(data: UMOMACreate): Promise<ApiResponse<UMOMA>> {
|
||||
return await api.post('/a76/units-of-measure/oma', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza una unidad de medida
|
||||
*/
|
||||
export async function updateUMOMA(id: number, data: UMOMAUpdate): Promise<ApiResponse<UMOMA>> {
|
||||
return await api.put(`/a76/units-of-measure/oma/${id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina una unidad de medida
|
||||
*/
|
||||
export async function deleteUMOMA(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/units-of-measure/oma/${id}`);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* API Client para Unit Measures - Catálogo principal de unidades de medida
|
||||
*/
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface UnitMeasure {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface UnitMeasureCreate {
|
||||
code: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UnitMeasureUpdate {
|
||||
code?: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UnitMeasureListResponse {
|
||||
items: UnitMeasure[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lista todas las unidades de medida
|
||||
*/
|
||||
export async function getUnitMeasures(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UnitMeasureListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/units-of-measure?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene una unidad de medida por ID
|
||||
*/
|
||||
export async function getUnitMeasure(id: number): Promise<ApiResponse<UnitMeasure>> {
|
||||
return await api.get(`/a76/units-of-measure/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crea una nueva unidad de medida
|
||||
*/
|
||||
export async function createUnitMeasure(data: UnitMeasureCreate): Promise<ApiResponse<UnitMeasure>> {
|
||||
return await api.post('/a76/units-of-measure', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza una unidad de medida
|
||||
*/
|
||||
export async function updateUnitMeasure(id: number, data: UnitMeasureUpdate): Promise<ApiResponse<UnitMeasure>> {
|
||||
return await api.put(`/a76/units-of-measure/${id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina una unidad de medida
|
||||
*/
|
||||
export async function deleteUnitMeasure(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/units-of-measure/${id}`);
|
||||
}
|
||||
20
frontend/src/lib/components/dashboard/locations/columns.ts
Normal file
20
frontend/src/lib/components/dashboard/locations/columns.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
|
||||
export interface Location {
|
||||
location_code: string;
|
||||
location_description: string | null;
|
||||
}
|
||||
|
||||
export function createColumns(): ColumnDef<Location>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'location_code',
|
||||
header: 'Código',
|
||||
},
|
||||
{
|
||||
accessorKey: 'location_description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.location_description || '-'
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<script lang="ts" generics="TData, TValue">
|
||||
import {
|
||||
type ColumnDef,
|
||||
getCoreRowModel,
|
||||
} from "@tanstack/table-core";
|
||||
import { createSvelteTable, FlexRender } from "$lib/components/ui/data-table/index.js";
|
||||
import * as Table from "$lib/components/ui/table/index.js";
|
||||
|
||||
type DataTableProps<TData, TValue> = {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
pageCount: number;
|
||||
totalItems: number;
|
||||
};
|
||||
|
||||
let {
|
||||
data,
|
||||
columns,
|
||||
totalItems
|
||||
}: DataTableProps<TData, TValue> = $props();
|
||||
|
||||
const table = createSvelteTable({
|
||||
get data() {
|
||||
return data;
|
||||
},
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#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>
|
||||
<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}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} ubicaciones únicas
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export interface UMCustomsMex {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UMCustomsMex>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
header: 'Código',
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { createUMCustomsMex, updateUMCustomsMex, type UMCustomsMex } from "$lib/api/dashboard/a76/general_catalogs/um-customs-mex";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
mode = 'create',
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
mode?: 'create' | 'edit';
|
||||
item?: UMCustomsMex | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(mode === 'edit');
|
||||
const title = $derived(isEdit ? "Editar Unidad Customs MEX" : "Nueva Unidad Customs MEX");
|
||||
|
||||
let formData = $state({
|
||||
code: '',
|
||||
description: ''
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (isEdit && item) {
|
||||
formData = {
|
||||
code: item.code,
|
||||
description: item.description || ''
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
code: '',
|
||||
description: ''
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
response = await updateUMCustomsMex(item.id, {
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
});
|
||||
} else {
|
||||
response = await createUMCustomsMex({
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
});
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
return;
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = 'Error de conexión';
|
||||
console.error(e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="grid gap-4 py-4">
|
||||
{#if error}
|
||||
<div class="text-red-500 text-sm mb-2">{error}</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="code" class="text-right">Código</Label>
|
||||
<Input id="code" bind:value={formData.code} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="description" class="text-right">Descripción</Label>
|
||||
<Input id="description" bind:value={formData.description} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button variant="outline" onclick={() => open = false} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button onclick={handleSubmit} disabled={loading}>
|
||||
{loading ? 'Guardando...' : 'Guardar'}
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deleteUMCustomsMex, type UMCustomsMex } from "$lib/api/dashboard/a76/general_catalogs/um-customs-mex";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: UMCustomsMex;
|
||||
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 unidad "${item.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteUMCustomsMex(item.id);
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
alert(`Error al eliminar: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = 'Error de conexión';
|
||||
console.error(e);
|
||||
alert('Error de conexión al eliminar');
|
||||
} 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.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}
|
||||
mode="edit"
|
||||
{item}
|
||||
{onSuccess}
|
||||
/>
|
||||
@@ -0,0 +1,106 @@
|
||||
<script lang="ts" generics="TData, TValue">
|
||||
import {
|
||||
type ColumnDef,
|
||||
getCoreRowModel,
|
||||
} from "@tanstack/table-core";
|
||||
import { createSvelteTable, FlexRender } from "$lib/components/ui/data-table/index.js";
|
||||
import * as Table from "$lib/components/ui/table/index.js";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
|
||||
type DataTableProps<TData, TValue> = {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
pageCount: number;
|
||||
totalItems: number;
|
||||
};
|
||||
|
||||
let {
|
||||
data,
|
||||
columns,
|
||||
pageCount,
|
||||
totalItems
|
||||
}: DataTableProps<TData, TValue> = $props();
|
||||
|
||||
const table = createSvelteTable({
|
||||
get data() {
|
||||
return data;
|
||||
},
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
pageCount: pageCount,
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#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>
|
||||
<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}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems}
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) - 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) + 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export interface UnitMeasure {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitMeasure>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
header: 'Código',
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { createUnitMeasure, updateUnitMeasure, type UnitMeasure } from "$lib/api/dashboard/a76/general_catalogs/unit-measures";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
mode = 'create',
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
mode?: 'create' | 'edit';
|
||||
item?: UnitMeasure | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(mode === 'edit');
|
||||
const title = $derived(isEdit ? "Editar Unidad de Medida" : "Nueva Unidad de Medida");
|
||||
|
||||
let formData = $state({
|
||||
code: '',
|
||||
description: ''
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (isEdit && item) {
|
||||
formData = {
|
||||
code: item.code,
|
||||
description: item.description || ''
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
code: '',
|
||||
description: ''
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
response = await updateUnitMeasure(item.id, {
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
});
|
||||
} else {
|
||||
response = await createUnitMeasure({
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
});
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
return;
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = 'Error de conexión';
|
||||
console.error(e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="grid gap-4 py-4">
|
||||
{#if error}
|
||||
<div class="text-red-500 text-sm mb-2">{error}</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="code" class="text-right">Código</Label>
|
||||
<Input id="code" bind:value={formData.code} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="description" class="text-right">Descripción</Label>
|
||||
<Input id="description" bind:value={formData.description} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button variant="outline" onclick={() => open = false} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button onclick={handleSubmit} disabled={loading}>
|
||||
{loading ? 'Guardando...' : 'Guardar'}
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deleteUnitMeasure, type UnitMeasure } from "$lib/api/dashboard/a76/general_catalogs/unit-measures";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: UnitMeasure;
|
||||
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 unidad "${item.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteUnitMeasure(item.id);
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
alert(`Error al eliminar: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = 'Error de conexión';
|
||||
console.error(e);
|
||||
alert('Error de conexión al eliminar');
|
||||
} 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.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}
|
||||
mode="edit"
|
||||
{item}
|
||||
{onSuccess}
|
||||
/>
|
||||
@@ -0,0 +1,106 @@
|
||||
<script lang="ts" generics="TData, TValue">
|
||||
import {
|
||||
type ColumnDef,
|
||||
getCoreRowModel,
|
||||
} from "@tanstack/table-core";
|
||||
import { createSvelteTable, FlexRender } from "$lib/components/ui/data-table/index.js";
|
||||
import * as Table from "$lib/components/ui/table/index.js";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
|
||||
type DataTableProps<TData, TValue> = {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
pageCount: number;
|
||||
totalItems: number;
|
||||
};
|
||||
|
||||
let {
|
||||
data,
|
||||
columns,
|
||||
pageCount,
|
||||
totalItems
|
||||
}: DataTableProps<TData, TValue> = $props();
|
||||
|
||||
const table = createSvelteTable({
|
||||
get data() {
|
||||
return data;
|
||||
},
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
pageCount: pageCount,
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#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>
|
||||
<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}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems}
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) - 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) + 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -179,7 +179,7 @@ export function getSidebarData(): SidebarData {
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.incoterms"](),
|
||||
url: "#",
|
||||
url: "/dashboard/reference_data/incoterms",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.inpc"](),
|
||||
@@ -195,7 +195,7 @@ export function getSidebarData(): SidebarData {
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.valuation_methods"](),
|
||||
url: "#",
|
||||
url: "/dashboard/reference_data/valuation_methods",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.countries"](),
|
||||
@@ -207,23 +207,23 @@ export function getSidebarData(): SidebarData {
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.unit_measures"](),
|
||||
url: "/dashboard/general_catalogs/units_of_measures",
|
||||
url: "/dashboard/general_catalogs/units_of_measure/general",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.um_customs_mex"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/units_of_measure/customs",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.um_customs_ame"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/units_of_measure/american",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.um_ace"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/units_of_measure/ace",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.um_oma"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/units_of_measure/oma",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.conversions"](),
|
||||
@@ -239,7 +239,7 @@ export function getSidebarData(): SidebarData {
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.currency_types"](),
|
||||
url: "#",
|
||||
url: "/dashboard/reference_data/currency_types",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.multi_currency"](),
|
||||
@@ -247,7 +247,7 @@ export function getSidebarData(): SidebarData {
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.invoice_types"](),
|
||||
url: "#",
|
||||
url: "/dashboard/reference_data/invoice_types",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.electronic_signatures"](),
|
||||
@@ -259,20 +259,16 @@ export function getSidebarData(): SidebarData {
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.customs_warehouses"](),
|
||||
url: "#",
|
||||
url: "/dashboard/reference_data/customs_warehouses",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.locations"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/locations",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.doda"](),
|
||||
url: "/dashboard/general_catalogs/doda",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.packing_list"](),
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.prevalidators"](),
|
||||
url: "/dashboard/general_catalogs/prevalidators",
|
||||
@@ -281,10 +277,6 @@ export function getSidebarData(): SidebarData {
|
||||
title: m["sidebar.general_catalogs.electronic_notices"](),
|
||||
url: "/dashboard/general_catalogs/electronic_notices",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.back_flush"](),
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.crossing_notice"](),
|
||||
url: "#",
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
import { getClassificationConcepts } from '$lib/api/dashboard/a76/classification-concepts';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const classification = url.searchParams.get('classification');
|
||||
const description = url.searchParams.get('description');
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (classification) filters.classification = classification;
|
||||
if (description) filters.description = description;
|
||||
if (!accessToken) {
|
||||
return { error: 'No authenticated', classifications: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
const response = await getClassificationConcepts(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
classifications: response.data
|
||||
};
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const classification = url.searchParams.get('classification');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (classification) filters.classification = classification;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/classification-concepts?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', classifications: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
return { classifications: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading classification concepts:', error);
|
||||
return { error: 'Error loading', classifications: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,79 @@
|
||||
import { getConcepts } from '$lib/api/dashboard/a76/concepts';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
// Esperar a que el layout padre valide/refresque el token
|
||||
const parentData = await parent();
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
const response = await getConcepts(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
concepts: response.data
|
||||
};
|
||||
if (!accessToken) {
|
||||
return {
|
||||
error: 'No authenticated',
|
||||
concepts: {
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
pages: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, string> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
// Construir URL con parámetros
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await authenticatedFetch(
|
||||
`v1/a76/concepts?${queryParams.toString()}`,
|
||||
{ method: 'GET' },
|
||||
cookies,
|
||||
fetch
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
return {
|
||||
error: 'Failed to load concepts',
|
||||
concepts: {
|
||||
items: [],
|
||||
total: 0,
|
||||
page: page,
|
||||
page_size: pageSize,
|
||||
pages: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
return {
|
||||
concepts: data
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error loading concepts:', error);
|
||||
return {
|
||||
error: 'Error loading concepts',
|
||||
concepts: {
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
pages: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
import { getCustomsBrokerConcepts } from '$lib/api/dashboard/a76/customs-broker-concepts';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
if (!accessToken) {
|
||||
return { error: 'No authenticated', concepts: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
const response = await getCustomsBrokerConcepts(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
concepts: response.data
|
||||
};
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/customs-broker-concepts?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', concepts: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
return { concepts: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading customs broker concepts:', error);
|
||||
return { error: 'Error loading', concepts: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
import { getDODAs } from '$lib/api/dashboard/a76/doda';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
if (!accessToken) {
|
||||
return { error: 'No authenticated', dodas: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
const response = await getDODAs(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
dodas: response.data
|
||||
};
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/doda?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', dodas: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
return { dodas: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading DODAs:', error);
|
||||
return { error: 'Error loading', dodas: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
import { getElectronicNotices } from '$lib/api/dashboard/a76/electronic-notices';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
if (!accessToken) {
|
||||
return { error: 'No authenticated', notices: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
const response = await getElectronicNotices(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
notices: response.data
|
||||
};
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/electronic-notices?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', notices: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
return { notices: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading electronic notices:', error);
|
||||
return { error: 'Error loading', notices: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
import { getEquivalencies } from '$lib/api/dashboard/a76/equivalencies';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const fraccion_mex = url.searchParams.get('fraccion_mex');
|
||||
const fraccion_us = url.searchParams.get('fraccion_us');
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (fraccion_mex) filters.fraccion_mex = fraccion_mex;
|
||||
if (fraccion_us) filters.fraccion_us = fraccion_us;
|
||||
if (!accessToken) {
|
||||
return { error: 'No authenticated', equivalencies: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
const response = await getEquivalencies(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
equivalencies: response.data
|
||||
};
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const fraccion_mex = url.searchParams.get('fraccion_mex');
|
||||
const fraccion_us = url.searchParams.get('fraccion_us');
|
||||
|
||||
if (fraccion_mex) filters.fraccion_mex = fraccion_mex;
|
||||
if (fraccion_us) filters.fraccion_us = fraccion_us;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/equivalencies?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', equivalencies: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
return { equivalencies: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading equivalencies:', error);
|
||||
return { error: 'Error loading', equivalencies: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
import { getErrorCatalogs } from '$lib/api/dashboard/a76/error-catalogs';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
if (!accessToken) {
|
||||
return { error: 'No authenticated', errors: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
const response = await getErrorCatalogs(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
errors: response.data
|
||||
};
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/error-catalogs?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', errors: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
return { errors: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading error catalogs:', error);
|
||||
return { error: 'Error loading', errors: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
import { getINPCs } from '$lib/api/dashboard/a76/inpc';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const year = url.searchParams.get('year');
|
||||
const month = url.searchParams.get('month');
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (year) filters.year = year;
|
||||
if (month) filters.month = month;
|
||||
if (!accessToken) {
|
||||
return { error: 'No authenticated', inpcs: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
const response = await getINPCs(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
inpcs: response.data
|
||||
};
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const year = url.searchParams.get('year');
|
||||
const month = url.searchParams.get('month');
|
||||
|
||||
if (year) filters.year = year;
|
||||
if (month) filters.month = month;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/inpc?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', inpcs: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
return { inpcs: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading INPCs:', error);
|
||||
return { error: 'Error loading', inpcs: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
import { getLegends } from '$lib/api/dashboard/a76/legends';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
if (!accessToken) {
|
||||
return { error: 'No authenticated', legends: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
const response = await getLegends(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
legends: response.data
|
||||
};
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/legends?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', legends: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
return { legends: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading legends:', error);
|
||||
return { error: 'Error loading', legends: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { getServerApiUrl, getAuthTokens } from '$lib/server/api';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
}
|
||||
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('page_size')) || 1000;
|
||||
|
||||
const apiUrl = getServerApiUrl();
|
||||
// Get locations from ports
|
||||
const endpoint = `${apiUrl}api/v1/a76/ports?page=${page}&page_size=${pageSize}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Error fetching Ports for locations: ${response.status} ${response.statusText}`);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: `Error: ${response.statusText}`
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Extract unique locations
|
||||
const locationMap = new Map();
|
||||
data.items.forEach((port: any) => {
|
||||
if (port.location_code && !locationMap.has(port.location_code)) {
|
||||
locationMap.set(port.location_code, {
|
||||
location_code: port.location_code,
|
||||
location_description: port.location_description
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const locations = Array.from(locationMap.values());
|
||||
|
||||
return {
|
||||
items: locations,
|
||||
total: locations.length,
|
||||
page: 1,
|
||||
pageSize: locations.length,
|
||||
pages: 1
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching Locations:', error);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: 'Error al cargar datos'
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/locations/columns';
|
||||
import DataTable from '$lib/components/dashboard/locations/data-table.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
|
||||
const columns = createColumns();
|
||||
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Ubicaciones</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo de ubicaciones extraídas de puertos
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" onclick={refreshData} disabled={loading}>
|
||||
<RefreshCw class="h-4 w-4 {loading ? 'animate-spin' : ''}" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<DataTable
|
||||
data={data.items}
|
||||
{columns}
|
||||
pageCount={data.pages}
|
||||
totalItems={data.total}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
@@ -1,20 +1,34 @@
|
||||
import { getMultiCurrencyTypes } from '$lib/api/dashboard/a76/multi-currency-types';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const key = url.searchParams.get('key');
|
||||
const description = url.searchParams.get('description');
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (key) filters.key = key;
|
||||
if (description) filters.description = description;
|
||||
if (!accessToken) {
|
||||
return { error: 'No authenticated', types: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
const response = await getMultiCurrencyTypes(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
types: response.data
|
||||
};
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const key = url.searchParams.get('key');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (key) filters.key = key;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/multi-currency-types?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', types: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
return { types: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading multi currency types:', error);
|
||||
return { error: 'Error loading', types: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,79 @@
|
||||
import { getPackages } from '$lib/api/dashboard/a76/packages';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const key = url.searchParams.get('key');
|
||||
const description_es = url.searchParams.get('description_es');
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
// Esperar a que el layout padre valide/refresque el token
|
||||
const parentData = await parent();
|
||||
|
||||
if (key) filters.key = key;
|
||||
if (description_es) filters.description_es = description_es;
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
const response = await getPackages(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
packages: response.data
|
||||
};
|
||||
if (!accessToken) {
|
||||
return {
|
||||
error: 'No authenticated',
|
||||
packages: {
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
pages: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, string> = {};
|
||||
const key = url.searchParams.get('key');
|
||||
const description_es = url.searchParams.get('description_es');
|
||||
|
||||
if (key) filters.key = key;
|
||||
if (description_es) filters.description_es = description_es;
|
||||
|
||||
// Construir URL con parámetros
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await authenticatedFetch(
|
||||
`v1/a76/packages?${queryParams.toString()}`,
|
||||
{ method: 'GET' },
|
||||
cookies,
|
||||
fetch
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
return {
|
||||
error: 'Failed to load packages',
|
||||
packages: {
|
||||
items: [],
|
||||
total: 0,
|
||||
page: page,
|
||||
page_size: pageSize,
|
||||
pages: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
return {
|
||||
packages: data
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error loading packages:', error);
|
||||
return {
|
||||
error: 'Error loading packages',
|
||||
packages: {
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
pages: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
import { getPrevalidators } from '$lib/api/dashboard/a76/prevalidators';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
if (!accessToken) {
|
||||
return { error: 'No authenticated', prevalidators: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
const response = await getPrevalidators(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
prevalidators: response.data
|
||||
};
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/prevalidators?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', prevalidators: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
return { prevalidators: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading prevalidators:', error);
|
||||
return { error: 'Error loading', prevalidators: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
import { getSignatures } from '$lib/api/dashboard/a76/signatures';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const name = url.searchParams.get('name');
|
||||
const position = url.searchParams.get('position');
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (name) filters.name = name;
|
||||
if (position) filters.position = position;
|
||||
if (!accessToken) {
|
||||
return { error: 'No authenticated', signatures: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
const response = await getSignatures(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
signatures: response.data
|
||||
};
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const name = url.searchParams.get('name');
|
||||
const position = url.searchParams.get('position');
|
||||
|
||||
if (name) filters.name = name;
|
||||
if (position) filters.position = position;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/signatures?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', signatures: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
return { signatures: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading signatures:', error);
|
||||
return { error: 'Error loading', signatures: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
import { getUnitConversions } from '$lib/api/dashboard/a76/unit-conversions';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
const response = await getUnitConversions(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
conversions: response.data
|
||||
};
|
||||
if (!accessToken) {
|
||||
return { error: 'No authenticated', conversions: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/unit-conversions?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', conversions: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
return { conversions: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading unit conversions:', error);
|
||||
return { error: 'Error loading', conversions: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { getServerApiUrl, getAuthTokens } from '$lib/server/api';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
}
|
||||
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('page_size')) || 50;
|
||||
|
||||
const apiUrl = getServerApiUrl();
|
||||
const endpoint = `${apiUrl}api/v1/a76/units-of-measure/customs?page=${page}&page_size=${pageSize}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Error fetching Customs units: ${response.status} ${response.statusText}`);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: `Error: ${response.statusText}`
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
pageSize: data.page_size,
|
||||
pages: data.pages
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching Customs units:', error);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: 'Error al cargar datos'
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
<script lang="ts">
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/units_of_measure/customs/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/units_of_measure/customs/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/customs/data-table.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let loading = $state(false);
|
||||
|
||||
const columns = createColumns(() => {
|
||||
refreshData();
|
||||
});
|
||||
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Unidades de Medida Aduanas MEX</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo de unidades de medida para aduanas mexicanas
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" onclick={refreshData} disabled={loading}>
|
||||
<RefreshCw class="h-4 w-4 {loading ? 'animate-spin' : ''}" />
|
||||
</Button>
|
||||
<Button onclick={() => createDialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<DataTable
|
||||
data={data.items}
|
||||
{columns}
|
||||
pageCount={data.pages}
|
||||
totalItems={data.total}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={refreshData}
|
||||
/>
|
||||
</div>
|
||||
@@ -0,0 +1,56 @@
|
||||
import { getServerApiUrl, getAuthTokens } from '$lib/server/api';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
}
|
||||
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('page_size')) || 50;
|
||||
|
||||
const apiUrl = getServerApiUrl();
|
||||
const endpoint = `${apiUrl}api/v1/a76/units-of-measure?page=${page}&page_size=${pageSize}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Error fetching General units: ${response.status} ${response.statusText}`);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: `Error: ${response.statusText}`
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
pageSize: data.page_size,
|
||||
pages: data.pages
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching General units:', error);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: 'Error al cargar datos'
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
<script lang="ts">
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/units_of_measure/general/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/units_of_measure/general/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/general/data-table.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let loading = $state(false);
|
||||
|
||||
const columns = createColumns(() => {
|
||||
refreshData();
|
||||
});
|
||||
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Unidades de Medida Generales</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo general de unidades de medida
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" onclick={refreshData} disabled={loading}>
|
||||
<RefreshCw class="h-4 w-4 {loading ? 'animate-spin' : ''}" />
|
||||
</Button>
|
||||
<Button onclick={() => createDialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<DataTable
|
||||
data={data.items}
|
||||
{columns}
|
||||
pageCount={data.pages}
|
||||
totalItems={data.total}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={refreshData}
|
||||
/>
|
||||
</div>
|
||||
Reference in New Issue
Block a user