Se acabo de realizar las tareas CRUD de catalogos, ademas de cambiar su estilo de tablas. Tambien se agrego la caracteristica de reactividad
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
"""increase_port_description_length
|
||||
|
||||
Revision ID: 3a012dff0274
|
||||
Revises: 7937209f9718
|
||||
Create Date: 2025-12-24 10:24:49.927020
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '3a012dff0274'
|
||||
down_revision: Union[str, Sequence[str], None] = '7937209f9718'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
pass
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
pass
|
||||
@@ -63,8 +63,8 @@ class PortService:
|
||||
def update(
|
||||
db: Session,
|
||||
id: int,
|
||||
data: PortUpdate,
|
||||
tenant_id: int,
|
||||
data: PortUpdate,
|
||||
company_id: int
|
||||
) -> Optional[Port]:
|
||||
db_obj = PortService.get_by_id(db, id, tenant_id, company_id)
|
||||
|
||||
@@ -164,7 +164,7 @@ services:
|
||||
- KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-dev-secret}
|
||||
- CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:5173,http://localhost:3000}
|
||||
ports:
|
||||
- "8001:8000"
|
||||
- "5050:8000"
|
||||
depends_on:
|
||||
postgres-a76:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -45,24 +45,30 @@ export interface PortListResponse {
|
||||
export async function getPorts(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
filters: Record<string, any> = {},
|
||||
companyId?: number
|
||||
): Promise<ApiResponse<PortListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/ports?${queryParams.toString()}`);
|
||||
|
||||
if (companyId) {
|
||||
queryParams.append('company_id', companyId.toString());
|
||||
}
|
||||
|
||||
return await api.get(`/v1/a76/ports?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function createPort(data: PortCreate): Promise<ApiResponse<Port>> {
|
||||
return await api.post('/a76/ports', data);
|
||||
export async function createPort(data: PortCreate, companyId: number): Promise<ApiResponse<Port>> {
|
||||
return await api.post(`/v1/a76/ports?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function updatePort(id: number, data: PortUpdate): Promise<ApiResponse<Port>> {
|
||||
return await api.put(`/a76/ports/${id}`, data);
|
||||
export async function updatePort(id: number, data: PortUpdate, companyId: number): Promise<ApiResponse<Port>> {
|
||||
return await api.put(`/v1/a76/ports/${id}?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function deletePort(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/ports/${id}`);
|
||||
export async function deletePort(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/ports/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import * as Select from "$lib/components/ui/select";
|
||||
import * as Select from "$lib/components/ui/select";
|
||||
import { createPort, updatePort, type Port, PortType } from "$lib/api/dashboard/a76/general_catalogs/ports";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
@@ -60,23 +61,29 @@
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
error = 'Selecciona una compañía para guardar el puerto';
|
||||
return;
|
||||
}
|
||||
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
response = await updatePort(item.id, {
|
||||
port_code: formData.port_code,
|
||||
description: formData.description || null,
|
||||
location_code: formData.location_code,
|
||||
location_description: formData.location_description || null,
|
||||
port_type: formData.port_type
|
||||
});
|
||||
location_code: formData.location_code,
|
||||
location_description: formData.location_description || null,
|
||||
port_type: formData.port_type
|
||||
}, companyId);
|
||||
} else {
|
||||
response = await createPort({
|
||||
port_code: formData.port_code,
|
||||
description: formData.description || null,
|
||||
location_code: formData.location_code,
|
||||
location_description: formData.location_description || null,
|
||||
port_type: formData.port_type
|
||||
});
|
||||
location_code: formData.location_code,
|
||||
location_description: formData.location_description || null,
|
||||
port_type: formData.port_type
|
||||
}, companyId);
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { deletePort, type Port } from "$lib/api/dashboard/a76/general_catalogs/ports";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -22,11 +23,17 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
alert('Selecciona una compañía antes de eliminar');
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deletePort(item.id);
|
||||
const response = await deletePort(item.id, companyId);
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { setContext, onMount } from 'svelte';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import type { LayoutData } from './$types';
|
||||
import AppSidebar from "$lib/components/sidebar/app-sidebar.svelte";
|
||||
import * as Breadcrumb from "$lib/components/ui/breadcrumb/index.js";
|
||||
@@ -17,6 +18,16 @@
|
||||
if (data.companies) {
|
||||
companyStore.initialize(data.companies);
|
||||
}
|
||||
|
||||
// Escuchar cambios de compañía y recargar datos
|
||||
const handleCompanyChange = () => {
|
||||
invalidateAll();
|
||||
};
|
||||
window.addEventListener('companyChanged', handleCompanyChange);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('companyChanged', handleCompanyChange);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { page } from '$app/stores';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/company/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/general_catalogs/company/columns.js';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/company/create-edit-dialog.svelte';
|
||||
//import CreateEditDialog from '$lib/components/dashboard/general_catalogs/company/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
@@ -43,12 +43,13 @@
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">company_information</h1> <!-- {m["sidebar.general_catalogs.company_information"]()} -->
|
||||
<h1 class="text-2xl font-bold tracking-tight">Información de Empresas</h1> <!-- {m["sidebar.general_catalogs.company_information"]()} -->
|
||||
<p class="text-muted-foreground">
|
||||
Gestión de información de empresas
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<!-- <Button onclick={() => dialogOpen = true}> -->
|
||||
<Button href="/dashboard/general_catalogs/company_information/new">
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Empresa
|
||||
</Button>
|
||||
@@ -80,8 +81,9 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
<!-- <CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
/> -->
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as Tabs from '$lib/components/ui/tabs';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { createCompany } from '$lib/api/dashboard/a76/general_catalogs/company';
|
||||
import { ArrowLeft, LoaderCircle, Save } from 'lucide-svelte';
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
let formData = $state({
|
||||
name: '',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
main_activity: '',
|
||||
program: '',
|
||||
program_number: '',
|
||||
prosec: 0,
|
||||
prosec_authorization: '',
|
||||
responsible_name: '',
|
||||
responsible_last_name: '',
|
||||
responsible_mother_last_name: '',
|
||||
responsible_rfc: '',
|
||||
position: '',
|
||||
manufacturer_id: '',
|
||||
has_express_line: false,
|
||||
is_service_company: false,
|
||||
order_format_type: '',
|
||||
ctpat_svi: '',
|
||||
trusted_exporter_number: ''
|
||||
});
|
||||
|
||||
const clean = (value: string) => value.trim() || null;
|
||||
|
||||
async function handleSubmit() {
|
||||
error = null;
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
if (!formData.name.trim()) throw new Error('El nombre es requerido');
|
||||
if (!formData.rfc.trim()) throw new Error('El RFC es requerido');
|
||||
|
||||
const payload = {
|
||||
name: formData.name.trim(),
|
||||
rfc: formData.rfc.trim(),
|
||||
curp: clean(formData.curp),
|
||||
main_activity: clean(formData.main_activity),
|
||||
program: clean(formData.program),
|
||||
program_number: clean(formData.program_number),
|
||||
prosec: formData.prosec || null,
|
||||
prosec_authorization: clean(formData.prosec_authorization),
|
||||
responsible: `${formData.responsible_name} ${formData.responsible_last_name}`.trim() || null,
|
||||
responsible_name: clean(formData.responsible_name),
|
||||
responsible_last_name: clean(formData.responsible_last_name),
|
||||
responsible_mother_last_name: clean(formData.responsible_mother_last_name),
|
||||
responsible_rfc: clean(formData.responsible_rfc),
|
||||
position: clean(formData.position),
|
||||
manufacturer_id: clean(formData.manufacturer_id),
|
||||
has_express_line: formData.has_express_line,
|
||||
is_service_company: formData.is_service_company,
|
||||
order_format_type: clean(formData.order_format_type),
|
||||
ctpat_svi: clean(formData.ctpat_svi),
|
||||
trusted_exporter_number: clean(formData.trusted_exporter_number)
|
||||
};
|
||||
|
||||
const response = await createCompany(payload);
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
await goto('/dashboard/general_catalogs/company_information');
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
goto('/dashboard/general_catalogs/company_information');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full mx-auto max-w-6xl py-6 px-4 space-y-6 pb-48">
|
||||
<div class="flex items-center gap-4">
|
||||
<Button variant="outline" size="icon" href="/dashboard/general_catalogs/company_information">
|
||||
<ArrowLeft class="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Nueva Empresa</h1>
|
||||
<p class="text-muted-foreground">Crea la información de la empresa.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<div class="p-4 rounded-md bg-destructive/10 text-destructive border border-destructive/20 text-sm font-medium">
|
||||
⚠️ {error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<form class="space-y-6" on:submit|preventDefault={handleSubmit}>
|
||||
<Card.Root>
|
||||
<Card.Content class="p-6">
|
||||
<Tabs.Root value="general" class="w-full">
|
||||
<Tabs.Content value="general" class="space-y-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="name">Razón Social <span class="text-destructive">*</span></Label>
|
||||
<Input id="name" bind:value={formData.name} placeholder="Nombre de la empresa" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="grid gap-2">
|
||||
<Label for="rfc">RFC <span class="text-destructive">*</span></Label>
|
||||
<Input id="rfc" bind:value={formData.rfc} maxlength={13} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="curp">CURP</Label>
|
||||
<Input id="curp" bind:value={formData.curp} maxlength={18} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="main_activity">Actividad Principal</Label>
|
||||
<Input id="main_activity" bind:value={formData.main_activity} />
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="programas" class="space-y-4">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="grid gap-2">
|
||||
<Label for="program">Programa (IMMEX)</Label>
|
||||
<Input id="program" bind:value={formData.program} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="program_number">No. Programa</Label>
|
||||
<Input id="program_number" bind:value={formData.program_number} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="grid gap-2">
|
||||
<Label for="prosec">Sector PROSEC (ID)</Label>
|
||||
<Input id="prosec" type="number" bind:value={formData.prosec} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="prosec_auth">Autorización PROSEC</Label>
|
||||
<Input id="prosec_auth" bind:value={formData.prosec_authorization} />
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="responsable" class="space-y-4">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="grid gap-2">
|
||||
<Label for="resp_name">Nombre</Label>
|
||||
<Input id="resp_name" bind:value={formData.responsible_name} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="resp_last">Apellido Paterno</Label>
|
||||
<Input id="resp_last" bind:value={formData.responsible_last_name} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="grid gap-2">
|
||||
<Label for="resp_mother">Apellido Materno</Label>
|
||||
<Input id="resp_mother" bind:value={formData.responsible_mother_last_name} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="resp_rfc">RFC Responsable</Label>
|
||||
<Input id="resp_rfc" bind:value={formData.responsible_rfc} maxlength={13} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="position">Puesto / Cargo</Label>
|
||||
<Input id="position" bind:value={formData.position} />
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="config" class="space-y-4">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<Switch id="express" bind:checked={formData.has_express_line} />
|
||||
<Label for="express">Carril Exprés (OEA/NEEC)</Label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Switch id="service" bind:checked={formData.is_service_company} />
|
||||
<Label for="service">Es Empresa de Servicios</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t" />
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="grid gap-2">
|
||||
<Label for="man_id">Manufacturer ID (MID)</Label>
|
||||
<Input id="man_id" bind:value={formData.manufacturer_id} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="order_format">Formato de Pedido</Label>
|
||||
<Input id="order_format" bind:value={formData.order_format_type} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="grid gap-2">
|
||||
<Label for="ctpat">C-TPAT / SVI</Label>
|
||||
<Input id="ctpat" bind:value={formData.ctpat_svi} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="exporter">No. Exportador Confiable</Label>
|
||||
<Input id="exporter" bind:value={formData.trusted_exporter_number} />
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.List class="grid grid-cols-4 fixed bottom-24 left-1/2 -translate-x-1/2 w-[90%] max-w-2xl z-40 shadow-xl bg-background border rounded-xl">
|
||||
<Tabs.Trigger value="general">General</Tabs.Trigger>
|
||||
<Tabs.Trigger value="programas">Programas</Tabs.Trigger>
|
||||
<Tabs.Trigger value="responsable">Responsable</Tabs.Trigger>
|
||||
<Tabs.Trigger value="config">Config</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
</Tabs.Root>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="fixed bottom-0 right-0 left-0 md:left-64 p-4 border-t bg-background/95 backdrop-blur z-50 flex justify-end gap-4 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)]">
|
||||
<div class="w-full mx-auto flex justify-end gap-4 px-4">
|
||||
<Button type="button" variant="outline" onclick={handleCancel}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
Guardando...
|
||||
{:else}
|
||||
<Save class="mr-2 h-4 w-4" />
|
||||
Guardar Empresa
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -43,7 +43,8 @@
|
||||
Gestión de Documentos de Operación de Aduana
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<!-- <Button onclick={() => dialogOpen = true}> -->
|
||||
<Button href="/dashboard/general_catalogs/doda/new">
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo DODA
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
import * as Tabs from '$lib/components/ui/tabs';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { createDoda } from '$lib/api/dashboard/a76/general_catalogs/doda';
|
||||
import { ArrowLeft, LoaderCircle, Save } from 'lucide-svelte';
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
let formData = $state({
|
||||
integration_number: '',
|
||||
doda_date: undefined as number | undefined,
|
||||
doda_time: undefined as number | undefined,
|
||||
dispatch_customs: '',
|
||||
customs_sections: '',
|
||||
patent: '',
|
||||
pedimentos: '',
|
||||
caat: '',
|
||||
transport_identification: '',
|
||||
fast_id: '',
|
||||
operation_type: '',
|
||||
selected: false,
|
||||
user_selected: '',
|
||||
last_user: '',
|
||||
responsible: '',
|
||||
carrier: '',
|
||||
shipments: '',
|
||||
pedimento_type: '',
|
||||
original_chain: '',
|
||||
serial_number: '',
|
||||
electronic_signature: '',
|
||||
transaction_number: '',
|
||||
status: '',
|
||||
linq_sat_qr: '',
|
||||
sat_certificate: '',
|
||||
sat_digital_seal: '',
|
||||
xml_doda_sent_path: '',
|
||||
xml_doda_response_path: '',
|
||||
sat_original_chain: '',
|
||||
customs_clearance: undefined as number | undefined,
|
||||
unique_badge_number: ''
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
error = null;
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
if (!formData.integration_number.trim()) throw new Error('El número de integración es requerido');
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) throw new Error('Selecciona una compañía para crear el DODA');
|
||||
|
||||
const payload = { ...formData, integration_number: formData.integration_number.trim() };
|
||||
await createDoda(payload, companyId);
|
||||
|
||||
await goto('/dashboard/general_catalogs/doda');
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
goto('/dashboard/general_catalogs/doda');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full mx-auto max-w-6xl py-6 px-4 space-y-6 pb-48">
|
||||
<div class="flex items-center gap-4">
|
||||
<Button variant="outline" size="icon" href="/dashboard/general_catalogs/doda">
|
||||
<ArrowLeft class="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Nuevo DODA</h1>
|
||||
<p class="text-muted-foreground">Captura la información del documento.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<div class="p-4 rounded-md bg-destructive/10 text-destructive border border-destructive/20 text-sm font-medium">
|
||||
⚠️ {error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<form class="space-y-6" on:submit|preventDefault={handleSubmit}>
|
||||
<Card.Root>
|
||||
<Card.Content class="p-6">
|
||||
<Tabs.Root value="general" class="w-full">
|
||||
<Tabs.Content value="general" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="integration_number">No. Integración <span class="text-destructive">*</span></Label>
|
||||
<Input id="integration_number" bind:value={formData.integration_number} maxlength={30} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="status">Estatus</Label>
|
||||
<Input id="status" bind:value={formData.status} maxlength={30} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="doda_date">Fecha (YYYYMMDD)</Label>
|
||||
<Input type="number" id="doda_date" bind:value={formData.doda_date} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="doda_time">Hora (HHMMSS)</Label>
|
||||
<Input type="number" id="doda_time" bind:value={formData.doda_time} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="operation_type">Tipo Operación</Label>
|
||||
<Input id="operation_type" bind:value={formData.operation_type} maxlength={1} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="pedimentos">Pedimentos</Label>
|
||||
<Input id="pedimentos" bind:value={formData.pedimentos} maxlength={80} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="pedimento_type">Tipo Pedimento</Label>
|
||||
<Input id="pedimento_type" bind:value={formData.pedimento_type} maxlength={30} />
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="transport" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="patent">Patente</Label>
|
||||
<Input id="patent" bind:value={formData.patent} maxlength={4} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="dispatch_customs">Aduana Despacho</Label>
|
||||
<Input id="dispatch_customs" bind:value={formData.dispatch_customs} maxlength={3} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="customs_sections">Sección Aduanera</Label>
|
||||
<Input id="customs_sections" bind:value={formData.customs_sections} maxlength={3} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="caat">CAAT</Label>
|
||||
<Input id="caat" bind:value={formData.caat} maxlength={10} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="carrier">Transportista (Carrier)</Label>
|
||||
<Input id="carrier" bind:value={formData.carrier} maxlength={8} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="transport_identification">Ident. Transporte</Label>
|
||||
<Input id="transport_identification" bind:value={formData.transport_identification} maxlength={20} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="fast_id">FAST ID</Label>
|
||||
<Input id="fast_id" bind:value={formData.fast_id} maxlength={20} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="shipments">Embarques (Shipments)</Label>
|
||||
<Input id="shipments" bind:value={formData.shipments} maxlength={80} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="customs_clearance">Despacho Aduanero (ID)</Label>
|
||||
<Input type="number" id="customs_clearance" bind:value={formData.customs_clearance} />
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="sat" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="serial_number">Número de Serie</Label>
|
||||
<Input id="serial_number" bind:value={formData.serial_number} maxlength={21} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="transaction_number">No. Transacción</Label>
|
||||
<Input id="transaction_number" bind:value={formData.transaction_number} maxlength={30} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="unique_badge_number">Número Único de Gafete</Label>
|
||||
<Input id="unique_badge_number" bind:value={formData.unique_badge_number} maxlength={250} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="original_chain">Cadena Original</Label>
|
||||
<Textarea id="original_chain" bind:value={formData.original_chain} class="h-20" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="electronic_signature">Firma Electrónica</Label>
|
||||
<Textarea id="electronic_signature" bind:value={formData.electronic_signature} class="h-20" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="sat_digital_seal">Sello Digital SAT</Label>
|
||||
<Textarea id="sat_digital_seal" bind:value={formData.sat_digital_seal} class="h-20" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="sat_original_chain">Cadena Original SAT</Label>
|
||||
<Textarea id="sat_original_chain" bind:value={formData.sat_original_chain} class="h-20" />
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="xml_doda_sent_path">Ruta XML Enviado</Label>
|
||||
<Input id="xml_doda_sent_path" bind:value={formData.xml_doda_sent_path} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="xml_doda_response_path">Ruta XML Respuesta</Label>
|
||||
<Input id="xml_doda_response_path" bind:value={formData.xml_doda_response_path} />
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="other" class="space-y-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<Switch id="selected" bind:checked={formData.selected} />
|
||||
<Label for="selected">Seleccionado</Label>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="user_selected">Usuario Selección</Label>
|
||||
<Input id="user_selected" bind:value={formData.user_selected} maxlength={30} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="last_user">Último Usuario</Label>
|
||||
<Input id="last_user" bind:value={formData.last_user} maxlength={30} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="responsible">Responsable</Label>
|
||||
<Input id="responsible" bind:value={formData.responsible} maxlength={14} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="linq_sat_qr">LINQ SAT QR</Label>
|
||||
<Input id="linq_sat_qr" bind:value={formData.linq_sat_qr} maxlength={1000} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="sat_certificate">Certificado SAT</Label>
|
||||
<Input id="sat_certificate" bind:value={formData.sat_certificate} maxlength={2001} />
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.List class="grid grid-cols-4 fixed bottom-24 left-1/2 -translate-x-1/2 w-[90%] max-w-3xl z-40 shadow-xl bg-background border rounded-xl">
|
||||
<Tabs.Trigger value="general">General</Tabs.Trigger>
|
||||
<Tabs.Trigger value="transport">Aduana/Transp.</Tabs.Trigger>
|
||||
<Tabs.Trigger value="sat">SAT / Digital</Tabs.Trigger>
|
||||
<Tabs.Trigger value="other">Otros</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
</Tabs.Root>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="fixed bottom-0 right-0 left-0 md:left-64 p-4 border-t bg-background/95 backdrop-blur z-50 flex justify-end gap-4 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)]">
|
||||
<div class="w-full mx-auto flex justify-end gap-4 px-4">
|
||||
<Button type="button" variant="outline" onclick={handleCancel}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
Guardando...
|
||||
{:else}
|
||||
<Save class="mr-2 h-4 w-4" />
|
||||
Guardar DODA
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -3,12 +3,19 @@
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import CreateDialog from '$lib/components/dashboard/general_catalogs/multi_currency_types/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchCode = $state($page.url.searchParams.get('currency_type_code') || '');
|
||||
let searchCountry = $state($page.url.searchParams.get('country_key') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = [
|
||||
{ key: 'currency_type_code', label: 'Código Moneda' },
|
||||
{ key: 'country_key', label: 'País' },
|
||||
@@ -20,6 +27,22 @@
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchCode) url.searchParams.set('currency_type_code', searchCode);
|
||||
else url.searchParams.delete('currency_type_code');
|
||||
|
||||
if (searchCountry) url.searchParams.set('country_key', searchCountry);
|
||||
else url.searchParams.delete('country_key');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
@@ -36,6 +59,23 @@
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por código..."
|
||||
bind:value={searchCode}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por país..."
|
||||
bind:value={searchCountry}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
data={data.types?.items || []}
|
||||
@@ -47,7 +87,6 @@
|
||||
|
||||
<CreateDialog
|
||||
bind:open={dialogOpen}
|
||||
on:success={handleSuccess}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/ace/data-table.svelte';
|
||||
import DataTable from '$lib/components/dashboard/packages/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/packages/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/packages/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
@@ -44,7 +44,7 @@
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">packages</h1> <!-- {m["sidebar.general_catalogs.packages"]()} -->
|
||||
<h1 class="text-2xl font-bold tracking-tight">Bultos y Embalajes</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestión del catálogo de bultos y embalajes
|
||||
</p>
|
||||
|
||||
@@ -4,17 +4,34 @@ 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 companyId = url.searchParams.get('company_id') || cookies.get('active_company_id');
|
||||
|
||||
if (!companyId) {
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: 'Selecciona una compañía para ver los puertos'
|
||||
};
|
||||
}
|
||||
|
||||
const apiUrl = getServerApiUrl();
|
||||
const endpoint = `${apiUrl}api/v1/a76/ports?page=${page}&page_size=${pageSize}`;
|
||||
|
||||
const query = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId
|
||||
});
|
||||
|
||||
const endpoint = `${apiUrl}v1/a76/ports?${query.toString()}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
headers: {
|
||||
|
||||
@@ -1,61 +1,92 @@
|
||||
<script lang="ts">
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/ports/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/ports/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/ace/data-table.svelte'; // Reusing generic data table
|
||||
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';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import { createColumns } from '$lib/components/dashboard/ports/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/ports/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/ace/data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let loading = $state(false);
|
||||
let createDialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchCode = $state($page.url.searchParams.get('port_code') || '');
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = createColumns(() => {
|
||||
refreshData();
|
||||
});
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchCode) url.searchParams.set('port_code', searchCode);
|
||||
else url.searchParams.delete('port_code');
|
||||
|
||||
if (searchDesc) url.searchParams.set('description', searchDesc);
|
||||
else url.searchParams.delete('description');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
}
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</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">Puertos</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo 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>
|
||||
<Button onclick={() => createDialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Puertos</h1>
|
||||
<p class="text-muted-foreground">Catálogo de puertos</p>
|
||||
</div>
|
||||
<Button onclick={() => createDialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Puerto
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<DataTable
|
||||
data={data.items}
|
||||
{columns}
|
||||
pageCount={data.pages}
|
||||
totalItems={data.total}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
{#if data.items?.error}
|
||||
<div class="rounded-md border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
||||
{data.items.error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={refreshData}
|
||||
/>
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por código..."
|
||||
bind:value={searchCode}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por descripción..."
|
||||
bind:value={searchDesc}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<DataTable
|
||||
data={data.items?.items || data.items || []}
|
||||
columns={createColumns(handleSuccess)}
|
||||
pageCount={data.items?.pages || data.items?.pageCount || 0}
|
||||
totalItems={data.items?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,40 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { goto, invalidateAll } from '$app/navigation';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { browser } from '$app/environment';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { RefreshCw, Plus, House } from 'lucide-svelte';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/prevalidators/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 * as Breadcrumb from "$lib/components/ui/breadcrumb";
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import * as Sidebar from "$lib/components/ui/sidebar";
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
let loading = $state(false);
|
||||
|
||||
let searchCode = $state($page.url.searchParams.get('code') || '');
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = createColumns(refreshData);
|
||||
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
const currentPage = Number($page.url.searchParams.get('page') || 1);
|
||||
if (currentPage !== 1) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', '1');
|
||||
await goto(url, { keepFocus: true, noScroll: true });
|
||||
} else {
|
||||
await invalidateAll();
|
||||
}
|
||||
loading = false;
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
@@ -46,56 +34,22 @@
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
dialogOpen = false;
|
||||
refreshData();
|
||||
}, 500);
|
||||
}
|
||||
</script>
|
||||
|
||||
<header class="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
|
||||
<div class="flex items-center gap-2 px-4">
|
||||
<Sidebar.Trigger class="-ml-1" />
|
||||
<Separator orientation="vertical" class="mr-2 h-4" />
|
||||
<Breadcrumb.Root>
|
||||
<Breadcrumb.List>
|
||||
<Breadcrumb.Item class="hidden md:block">
|
||||
<Breadcrumb.Link href="/dashboard">
|
||||
<House class="h-4 w-4" />
|
||||
</Breadcrumb.Link>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Separator class="hidden md:block" />
|
||||
<Breadcrumb.Item class="hidden md:block">
|
||||
<Breadcrumb.Link href="/dashboard/general_catalogs">Catálogos Generales</Breadcrumb.Link>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Separator class="hidden md:block" />
|
||||
<Breadcrumb.Item>
|
||||
<Breadcrumb.Page>Prevalidadores</Breadcrumb.Page>
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb.List>
|
||||
</Breadcrumb.Root>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4 pt-0">
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Prevalidadores</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestión del catálogo de prevalidadores
|
||||
Catálogo de prevalidadores
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<Button variant="outline" size="icon" onclick={refreshData} disabled={loading}>
|
||||
<RefreshCw class="h-4 w-4 {loading ? 'animate-spin' : ''}" />
|
||||
</Button>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo
|
||||
</Button>
|
||||
</div>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Prevalidador
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
@@ -115,16 +69,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
data={data.prevalidators.items}
|
||||
columns={columns}
|
||||
pageCount={data.prevalidators.pages}
|
||||
totalItems={data.prevalidators.total}
|
||||
/>
|
||||
<div class="rounded-md border">
|
||||
<DataTable
|
||||
data={data.prevalidators?.items || []}
|
||||
columns={createColumns(handleSuccess)}
|
||||
pageCount={data.prevalidators?.pages || 0}
|
||||
totalItems={data.prevalidators?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
open={dialogOpen}
|
||||
onOpenChange={(open) => dialogOpen = open}
|
||||
bind:open={dialogOpen}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Plus, Search } from 'lucide-svelte';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import DataTable from '$lib/components/dashboard/seal/data-table.svelte';
|
||||
import CreateEditDialog from '$lib/components/dashboard/seal/create-edit-dialog.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/seal/columns';
|
||||
@@ -124,15 +122,13 @@
|
||||
const columns = createColumns(handleSuccess);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Sellos - Anexo 76</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Sellos</h1>
|
||||
<p class="text-muted-foreground">Gestiona los sellos de tu empresa</p>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Sellos</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestiona los sellos de tu empresa
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreate}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
@@ -146,47 +142,29 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Card.Title>Lista de Sellos</Card.Title>
|
||||
<Card.Description>
|
||||
Total: {allItems.length} sello{allItems.length !== 1 ? 's' : ''}
|
||||
</Card.Description>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="flex gap-4 items-end pt-4">
|
||||
<div class="flex-1">
|
||||
<Label for="seal-filter">Buscar por Sello</Label>
|
||||
<Input
|
||||
id="seal-filter"
|
||||
bind:value={sealFilter}
|
||||
placeholder="Filtrar por sello..."
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<Button onclick={handleSearch} disabled={loading}>
|
||||
<Search class="mr-2 h-4 w-4" />
|
||||
Buscar
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<DataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Filtrar por sello..."
|
||||
bind:value={sealFilter}
|
||||
oninput={handleSearch}
|
||||
disabled={loading}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
<div class="rounded-md border">
|
||||
<DataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,36 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { goto, invalidateAll } from '$app/navigation';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { browser } from '$app/environment';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { RefreshCw, Plus } from 'lucide-svelte';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/signatures/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';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
let loading = $state(false);
|
||||
|
||||
let searchCode = $state($page.url.searchParams.get('code') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = createColumns(refreshData);
|
||||
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
const currentPage = Number($page.url.searchParams.get('page') || 1);
|
||||
if (currentPage !== 1) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', '1');
|
||||
await goto(url, { keepFocus: true, noScroll: true });
|
||||
} else {
|
||||
await invalidateAll();
|
||||
}
|
||||
loading = false;
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
@@ -38,12 +29,7 @@
|
||||
else url.searchParams.delete('code');
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
dialogOpen = false;
|
||||
refreshData();
|
||||
}, 500);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -55,15 +41,10 @@
|
||||
Gestión del catálogo de firmas electrónicas
|
||||
</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={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Firma
|
||||
</Button>
|
||||
</div>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Firma
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
@@ -76,12 +57,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
data={data.signatures?.items || []}
|
||||
{columns}
|
||||
pageCount={data.signatures?.pages || 0}
|
||||
totalItems={data.signatures?.total || 0}
|
||||
/>
|
||||
<div class="rounded-md border">
|
||||
<DataTable
|
||||
data={data.signatures?.items || []}
|
||||
columns={createColumns(handleSuccess)}
|
||||
pageCount={data.signatures?.pages || 0}
|
||||
totalItems={data.signatures?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
|
||||
@@ -1,62 +1,89 @@
|
||||
<script lang="ts">
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/general_catalogs/unit_conversions/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/unit_conversions/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/unit_conversions/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';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import { createColumns } from '$lib/components/dashboard/general_catalogs/unit_conversions/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/unit_conversions/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/unit_conversions/data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let loading = $state(false);
|
||||
let createDialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchFrom = $state($page.url.searchParams.get('from_unit_code') || '');
|
||||
let searchTo = $state($page.url.searchParams.get('to_unit_code') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = createColumns(() => {
|
||||
refreshData();
|
||||
});
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchFrom) url.searchParams.set('from_unit_code', searchFrom);
|
||||
else url.searchParams.delete('from_unit_code');
|
||||
|
||||
if (searchTo) url.searchParams.set('to_unit_code', searchTo);
|
||||
else url.searchParams.delete('to_unit_code');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
}
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</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">Conversiones de Unidades</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo de conversiones 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" />
|
||||
Nueva Conversión
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<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>
|
||||
<Button onclick={() => createDialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Conversión
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<DataTable
|
||||
data={data.conversions?.items || []}
|
||||
{columns}
|
||||
pageCount={data.conversions?.pages || 0}
|
||||
totalItems={data.conversions?.total || 0}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar desde código..."
|
||||
bind:value={searchFrom}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar hacia código..."
|
||||
bind:value={searchTo}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={refreshData}
|
||||
/>
|
||||
<div class="rounded-md border">
|
||||
<DataTable
|
||||
data={data.conversions?.items || []}
|
||||
columns={createColumns(handleSuccess)}
|
||||
pageCount={data.conversions?.pages || 0}
|
||||
totalItems={data.conversions?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import DataTable from '$lib/components/dashboard/reference_data/countries/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/reference_data/countries/columns.js';
|
||||
import CreateEditDialog from '$lib/components/dashboard/reference_data/countries/create-edit-dialog.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';
|
||||
@@ -111,59 +110,41 @@
|
||||
const columns = createColumns(handleSuccess);
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Países</h1>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Países</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestiona los países disponibles en el sistema
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo País
|
||||
</Button>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo País
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
{#if error}
|
||||
<Card.Root class="border-destructive">
|
||||
<Card.Header>
|
||||
<Card.Title class="text-destructive">Error</Card.Title>
|
||||
<Card.Description>{error}</Card.Description>
|
||||
</Card.Header>
|
||||
</Card.Root>
|
||||
<div class="rounded-md border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Data Table -->
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Card.Title>Listado de Países</Card.Title>
|
||||
<Card.Description>
|
||||
Mostrando {allItems.length} de {totalItems} registros
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<!-- TanStack DataTable con Infinite Scroll -->
|
||||
<DataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
<div class="rounded-md border">
|
||||
<DataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Diálogo de crear/editar -->
|
||||
<CreateEditDialog bind:open={showCreateDialog} onSuccess={handleSuccess} />
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import DataTable from '$lib/components/dashboard/reference_data/currency_types/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/reference_data/currency_types/columns.js';
|
||||
import CreateEditDialog from '$lib/components/dashboard/reference_data/currency_types/create-edit-dialog.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';
|
||||
@@ -111,59 +110,41 @@
|
||||
const columns = createColumns(handleSuccess);
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Tipos de Moneda</h1>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Tipos de Moneda</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestiona los tipos de moneda disponibles en el sistema
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Tipo de Moneda
|
||||
</Button>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Tipo de Moneda
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
{#if error}
|
||||
<Card.Root class="border-destructive">
|
||||
<Card.Header>
|
||||
<Card.Title class="text-destructive">Error</Card.Title>
|
||||
<Card.Description>{error}</Card.Description>
|
||||
</Card.Header>
|
||||
</Card.Root>
|
||||
<div class="rounded-md border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Data Table -->
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Card.Title>Listado de Tipos de Moneda</Card.Title>
|
||||
<Card.Description>
|
||||
Mostrando {allItems.length} de {totalItems} registros
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<!-- TanStack DataTable con Infinite Scroll -->
|
||||
<DataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
<div class="rounded-md border">
|
||||
<DataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Diálogo de crear/editar -->
|
||||
<CreateEditDialog bind:open={showCreateDialog} onSuccess={handleSuccess} />
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import DataTable from '$lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/reference_data/customs_warehouses/columns.js';
|
||||
import CreateEditDialog from '$lib/components/dashboard/reference_data/customs_warehouses/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
@@ -111,59 +110,41 @@
|
||||
const columns = createColumns(handleSuccess);
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Recintos Fiscalizados</h1>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Recintos Fiscalizados</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestiona los recintos fiscalizados del sistema aduanal
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Recinto
|
||||
</Button>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Recinto
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
{#if error}
|
||||
<Card.Root class="border-destructive">
|
||||
<Card.Header>
|
||||
<Card.Title class="text-destructive">Error</Card.Title>
|
||||
<Card.Description>{error}</Card.Description>
|
||||
</Card.Header>
|
||||
</Card.Root>
|
||||
<div class="rounded-md border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Data Table -->
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Card.Title>Listado de Recintos Fiscalizados</Card.Title>
|
||||
<Card.Description>
|
||||
Mostrando {allItems.length} de {totalItems} registros
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<!-- TanStack DataTable con Infinite Scroll -->
|
||||
<DataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
<div class="rounded-md border">
|
||||
<DataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Diálogo de crear/editar -->
|
||||
<CreateEditDialog bind:open={showCreateDialog} onSuccess={handleSuccess} />
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import DataTable from '$lib/components/dashboard/reference_data/invoice_types/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/reference_data/invoice_types/columns.js';
|
||||
import CreateEditDialog from '$lib/components/dashboard/reference_data/invoice_types/create-edit-dialog.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';
|
||||
@@ -111,59 +110,41 @@
|
||||
const columns = createColumns(handleSuccess);
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Tipos de Factura</h1>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Tipos de Factura</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestiona los tipos de facturas del sistema
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Tipo de Factura
|
||||
</Button>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nuevo Tipo de Factura
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
{#if error}
|
||||
<Card.Root class="border-destructive">
|
||||
<Card.Header>
|
||||
<Card.Title class="text-destructive">Error</Card.Title>
|
||||
<Card.Description>{error}</Card.Description>
|
||||
</Card.Header>
|
||||
</Card.Root>
|
||||
<div class="rounded-md border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Data Table -->
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Card.Title>Listado de Tipos de Factura</Card.Title>
|
||||
<Card.Description>
|
||||
Mostrando {allItems.length} de {totalItems} registros
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<!-- TanStack DataTable con Infinite Scroll -->
|
||||
<DataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
<div class="rounded-md border">
|
||||
<DataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Diálogo de crear/editar -->
|
||||
<CreateEditDialog bind:open={showCreateDialog} onSuccess={handleSuccess} />
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import DataTable from '$lib/components/dashboard/reference_data/valuation_methods/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/reference_data/valuation_methods/columns.js';
|
||||
import CreateEditDialog from '$lib/components/dashboard/reference_data/valuation_methods/create-edit-dialog.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';
|
||||
@@ -111,59 +110,41 @@
|
||||
const columns = createColumns(handleSuccess);
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Métodos de Valoración</h1>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Métodos de Valoración</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestiona los métodos de valoración aduanera
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<Plus size={16} class="mr-2" />
|
||||
Nuevo Método de Valoración
|
||||
</Button>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<RefreshCw size={16} class="mr-2" />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<Plus size={16} class="mr-2" />
|
||||
Nuevo Método de Valoración
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
{#if error}
|
||||
<Card.Root class="border-destructive">
|
||||
<Card.Header>
|
||||
<Card.Title class="text-destructive">Error</Card.Title>
|
||||
<Card.Description>{error}</Card.Description>
|
||||
</Card.Header>
|
||||
</Card.Root>
|
||||
<div class="rounded-md border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Data Table -->
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Card.Title>Listado de Métodos de Valoración</Card.Title>
|
||||
<Card.Description>
|
||||
Mostrando {allItems.length} de {totalItems} registros
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<RefreshCw size={16} class="mr-2" />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<!-- TanStack DataTable con Infinite Scroll -->
|
||||
<DataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
<div class="rounded-md border">
|
||||
<DataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Diálogo de crear/editar -->
|
||||
<CreateEditDialog bind:open={showCreateDialog} onSuccess={handleSuccess} />
|
||||
|
||||
Reference in New Issue
Block a user