fix/visual-bugs-laptop

This commit is contained in:
hreyes
2026-02-09 16:04:28 -06:00
parent efdbb404a6
commit adc5ab5cae
10 changed files with 797 additions and 760 deletions

View File

@@ -57,7 +57,7 @@ class PortsApi {
const queryParams = new URLSearchParams({
company_id: companyId.toString()
});
return api.get<Port>(`${this.baseUrl}/${id}/?${queryParams.toString()}`);
return api.get<Port>(`${this.baseUrl}/${id}?${queryParams.toString()}`);
}
async create(data: PortCreate, companyId: string | number): Promise<ApiResponse<Port>> {
@@ -78,7 +78,7 @@ class PortsApi {
const queryParams = new URLSearchParams({
company_id: companyId.toString()
});
return api.delete(`${this.baseUrl}/${id}/?${queryParams.toString()}`);
return api.delete(`${this.baseUrl}/${id}?${queryParams.toString()}`);
}
}

View File

@@ -83,7 +83,7 @@
</script>
<Dialog.Root bind:open>
<Dialog.Content class="!max-w-[70vw] w-[70vw] max-h-[90vh] p-0 flex flex-col">
<Dialog.Content class="max-w-4xl w-full max-h-[85vh] p-0 flex flex-col">
<Dialog.Header class="px-6 py-4 border-b">
<Dialog.Title class="text-lg font-semibold">CATALOGO DE PAISES</Dialog.Title>
</Dialog.Header>
@@ -91,11 +91,7 @@
<div class="px-6 py-3 border-b bg-zinc-50 dark:bg-zinc-900">
<div class="flex items-center gap-2">
<Search class="w-4 h-4 text-zinc-400" />
<Input
bind:value={searchTerm}
placeholder="Buscando..."
class="flex-1 h-9"
/>
<Input bind:value={searchTerm} placeholder="Buscando..." class="flex-1 h-9" />
</div>
</div>
@@ -113,9 +109,7 @@
<table class="w-full text-sm">
<thead class="bg-zinc-900 dark:bg-zinc-800 text-white">
<tr>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>Clave M3</th
>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700">Clave M3</th>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>Clave Mexicana</th
>
@@ -134,11 +128,11 @@
class="border-b hover:bg-zinc-100 dark:hover:bg-zinc-800 cursor-pointer transition-colors"
onclick={() => handleSelect(country)}
>
<td class="px-3 py-2 border-r">{country.m3_key || ''}</td>
<td class="px-3 py-2 border-r">{country.mex_key || ''}</td>
<td class="px-3 py-2 border-r">{country.description_es || ''}</td>
<td class="px-3 py-2 border-r text-center">{country.ame_key || ''}</td>
<td class="px-3 py-2">{country.description_en || ''}</td>
<td class="px-3 py-2 border-r">{country.m3_key || ''}</td>
<td class="px-3 py-2 border-r">{country.mex_key || ''}</td>
<td class="px-3 py-2 border-r">{country.description_es || ''}</td>
<td class="px-3 py-2 border-r text-center">{country.ame_key || ''}</td>
<td class="px-3 py-2">{country.description_en || ''}</td>
</tr>
{/each}
{#if filteredCountries.length === 0}
@@ -152,7 +146,9 @@
</table>
</div>
<div class="mt-4 flex items-center justify-between text-sm text-zinc-600 dark:text-zinc-400">
<div
class="mt-4 flex items-center justify-between text-sm text-zinc-600 dark:text-zinc-400"
>
<div class="flex items-center gap-4">
<button class="px-3 py-1 border rounded hover:bg-zinc-100 dark:hover:bg-zinc-800">
&lt;&lt;

View File

@@ -24,11 +24,12 @@
const filteredParts = $derived(
searchQuery
? parts.filter(p =>
p.part_number?.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.description_spanish?.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.description_english?.toLowerCase().includes(searchQuery.toLowerCase())
)
? parts.filter(
(p) =>
p.part_number?.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.description_spanish?.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.description_english?.toLowerCase().includes(searchQuery.toLowerCase())
)
: parts
);
@@ -52,15 +53,12 @@
isSearching = true;
try {
const response = await fetch(
`/api-sveltekit/parts?company_id=${activeCompanyId}&limit=100`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
const response = await fetch(`/api-sveltekit/parts?company_id=${activeCompanyId}&limit=100`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
);
});
if (!response.ok) {
throw new Error('Error al buscar números de parte');
@@ -87,8 +85,9 @@
function handleScroll(e: Event) {
const target = e.target as HTMLDivElement;
const threshold = 100;
const scrolledToBottom = target.scrollHeight - target.scrollTop - target.clientHeight < threshold;
const scrolledToBottom =
target.scrollHeight - target.scrollTop - target.clientHeight < threshold;
if (scrolledToBottom && displayedParts.length < filteredParts.length) {
currentPage++;
loadMoreParts();
@@ -104,12 +103,10 @@
</script>
<Dialog.Root bind:open>
<Dialog.Content class="!max-w-[50vw] w-[50vw] max-h-[80vh] flex flex-col">
<Dialog.Content class="max-w-5xl w-full max-h-[90vh] p-0 flex flex-col">
<Dialog.Header>
<Dialog.Title>Seleccionar Número de Parte</Dialog.Title>
<Dialog.Description>
Busca y selecciona un número de parte para la partida
</Dialog.Description>
<Dialog.Description>Busca y selecciona un número de parte para la partida</Dialog.Description>
</Dialog.Header>
<div class="flex gap-2 mb-4">
@@ -148,15 +145,18 @@
</Table.Row>
{:else}
{#each displayedParts as part}
<Table.Row class="cursor-pointer hover:bg-muted/50" onclick={() => handleSelect(part)}>
<Table.Row
class="cursor-pointer hover:bg-muted/50"
onclick={() => handleSelect(part)}
>
<Table.Cell class="font-medium">{part.part_number}</Table.Cell>
<Table.Cell>{part.description_spanish || '-'}</Table.Cell>
<Table.Cell class="text-muted-foreground">{part.description_english || '-'}</Table.Cell>
<Table.Cell class="text-muted-foreground"
>{part.description_english || '-'}</Table.Cell
>
<Table.Cell class="text-muted-foreground">{part.part_class || '-'}</Table.Cell>
<Table.Cell>
<Button variant="ghost" size="sm" class="h-8">
Seleccionar
</Button>
<Button variant="ghost" size="sm" class="h-8">Seleccionar</Button>
</Table.Cell>
</Table.Row>
{/each}

View File

@@ -17,7 +17,7 @@
let loadingMore = $state(false);
let searchTerm = $state('');
let error = $state('');
// Pagination state
let currentPage = $state(1);
let totalPages = $state(1);
@@ -34,7 +34,7 @@
loadingMore = true;
}
error = '';
try {
const params = new URLSearchParams({
page: page.toString(),
@@ -45,11 +45,11 @@
const response = await fetch(`/api-sveltekit/tariff-fractions?${params}`, {
credentials: 'include'
});
if (response.ok) {
const data = await response.json();
console.log('Tariff fractions data received:', data);
if (data.items && Array.isArray(data.items)) {
if (append) {
fractions = [...fractions, ...data.items];
@@ -79,10 +79,10 @@
function handleScroll(e: Event) {
if (!scrollContainer || loading || loadingMore || !hasMore) return;
const target = e.target as HTMLDivElement;
const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight;
// Load more when within 200px of bottom
if (scrollBottom < 200) {
loadFractions(currentPage + 1, true);
@@ -120,7 +120,7 @@
</script>
<Dialog.Root bind:open>
<Dialog.Content class="!max-w-[80vw] w-[80vw] max-h-[90vh] p-0 flex flex-col">
<Dialog.Content class="max-w-4xl w-full max-h-[80vh] flex flex-col px-6">
<Dialog.Header class="px-6 py-4 border-b">
<Dialog.Title class="text-lg font-semibold">FRACCIONES ARANCELARIAS</Dialog.Title>
</Dialog.Header>
@@ -139,11 +139,7 @@
</p>
</div>
<div
bind:this={scrollContainer}
onscroll={handleScroll}
class="flex-1 overflow-auto px-6 py-4"
>
<div bind:this={scrollContainer} onscroll={handleScroll} class="flex-1 overflow-auto px-6 py-4">
{#if loading}
<div class="flex items-center justify-center py-20">
<Loader2 class="w-8 h-8 animate-spin text-zinc-900 dark:text-zinc-100" />
@@ -157,18 +153,12 @@
<table class="w-full text-sm">
<thead class="bg-zinc-900 dark:bg-zinc-800 text-white sticky top-0">
<tr>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>Código</th
>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>Fracción</th
>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700">Código</th>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700">Fracción</th>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>Descripción</th
>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>NICO</th
>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700">NICO</th>
<th class="px-3 py-2 text-left font-semibold">UMT</th>
</tr>
</thead>
@@ -204,9 +194,7 @@
{/if}
{#if !hasMore && fractions.length > 0}
<div class="text-center py-4 text-sm text-zinc-500">
Todos los resultados cargados
</div>
<div class="text-center py-4 text-sm text-zinc-500">Todos los resultados cargados</div>
{/if}
{/if}
</div>

View File

@@ -84,7 +84,7 @@
</script>
<Dialog.Root bind:open>
<Dialog.Content class="!max-w-[70vw] w-[70vw] max-h-[90vh] p-0 flex flex-col">
<Dialog.Content class="max-w-4xl w-full max-h-[90vh] p-0 flex flex-col">
<Dialog.Header class="px-6 py-4 border-b">
<Dialog.Title class="text-lg font-semibold">CATALOGOS DE UNIDADES DE MEDIDA</Dialog.Title>
</Dialog.Header>
@@ -92,11 +92,7 @@
<div class="px-6 py-3 border-b bg-zinc-50 dark:bg-zinc-900">
<div class="flex items-center gap-2">
<Search class="w-4 h-4 text-zinc-400" />
<Input
bind:value={searchTerm}
placeholder="Buscando..."
class="flex-1 h-9"
/>
<Input bind:value={searchTerm} placeholder="Buscando..." class="flex-1 h-9" />
</div>
</div>
@@ -114,9 +110,7 @@
<table class="w-full text-sm">
<thead class="bg-zinc-900 dark:bg-zinc-800 text-white">
<tr>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>U.M.</th
>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700">U.M.</th>
<th class="px-3 py-2 text-left font-semibold border-r border-zinc-700"
>Descripción Español</th
>
@@ -157,7 +151,9 @@
</table>
</div>
<div class="mt-4 flex items-center justify-between text-sm text-zinc-600 dark:text-zinc-400">
<div
class="mt-4 flex items-center justify-between text-sm text-zinc-600 dark:text-zinc-400"
>
<div class="flex items-center gap-4">
<button class="px-3 py-1 border rounded hover:bg-zinc-100 dark:hover:bg-zinc-800">
&lt;&lt;

View File

@@ -36,6 +36,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Port>[] {
},
{
id: 'actions',
header: 'Acciones',
cell: ({ row }) => {
return renderComponent(DataTableActions, {
item: row.original,

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { deletePort, type Port } from "$lib/api/dashboard/a76/general_catalogs/ports";
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
import type { Port } from "$lib/api/dashboard/a76/general_catalogs/ports";
import { EllipsisVertical, Pencil, Trash2 } from 'lucide-svelte';
import CreateEditDialog from "./create-edit-dialog.svelte";
import { companyStore } from "$lib/stores/company.svelte";
import DeleteDialog from "./delete-dialog.svelte";
let {
item,
@@ -14,42 +14,8 @@
onSuccess?: () => void;
} = $props();
let loading = $state(false);
let error = $state<string | null>(null);
let dialogOpen = $state(false);
async function handleDelete() {
if (!confirm(`¿Estás seguro de eliminar el puerto "${item.port_code}"?`)) {
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, companyId);
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;
}
}
let editDialogOpen = $state(false);
let deleteDialogOpen = $state(false);
</script>
<DropdownMenu.Root>
@@ -63,24 +29,26 @@
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Item onclick={() => dialogOpen = true}>
<DropdownMenu.Item onclick={() => editDialogOpen = 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}
<DropdownMenu.Item onclick={() => deleteDialogOpen = true} class="text-destructive">
<Trash2 class="mr-2 h-4 w-4" />
Eliminar
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<CreateEditDialog
bind:open={dialogOpen}
bind:open={editDialogOpen}
mode="edit"
{item}
{onSuccess}
/>
<DeleteDialog
bind:open={deleteDialogOpen}
{item}
{onSuccess}
/>

View File

@@ -0,0 +1,117 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import * as AlertDialog from '$lib/components/ui/alert-dialog';
import { portsApi, type Port } from '$lib/api/dashboard/a76/general_catalogs/ports';
import { LoaderCircle } from 'lucide-svelte';
import { companyStore } from '$lib/stores/company.svelte';
let {
open = $bindable(false),
item,
onSuccess
}: {
open: boolean;
item: Port | null;
onSuccess?: () => void;
} = $props();
let loading = $state(false);
let error = $state<string | null>(null);
async function handleDelete() {
if (!item) return;
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
error = 'Selecciona una compañía antes de eliminar';
return;
}
loading = true;
error = null;
try {
const response = await portsApi.delete(item.id, companyId);
if (response.error) {
error = response.error;
return;
}
// Éxito
open = false;
if (onSuccess) {
onSuccess();
}
} catch (e) {
error = e instanceof Error ? e.message : 'Error al eliminar';
console.error('Error deleting:', e);
} finally {
loading = false;
}
}
function handleOpenChange(newOpen: boolean) {
if (!newOpen) {
error = null;
}
open = newOpen;
}
</script>
<AlertDialog.Root bind:open onOpenChange={handleOpenChange}>
<AlertDialog.Content>
<AlertDialog.Header>
<AlertDialog.Title>¿Estás seguro?</AlertDialog.Title>
<AlertDialog.Description class="space-y-2">
<p>Esta acción no se puede deshacer. Se eliminará permanentemente este puerto:</p>
{#if item}
<div class="mt-2 rounded-lg bg-muted p-3 space-y-2">
<div class="flex items-center justify-between text-sm">
<span class="font-medium">Código:</span>
<code class="font-mono font-semibold">{item.port_code}</code>
</div>
{#if item.description}
<div class="flex flex-col gap-1 text-sm">
<span class="font-medium">Descripción:</span>
<span class="text-xs">{item.description}</span>
</div>
{/if}
{#if item.location_code}
<div class="flex items-center justify-between text-sm">
<span class="font-medium">Ubicación:</span>
<code class="font-mono font-semibold">{item.location_code}</code>
</div>
{/if}
</div>
{:else}
<div class="mt-2 rounded-lg bg-muted p-3 text-sm italic">
Cargando información del puerto...
</div>
{/if}
{#if error}
<div
class="mt-2 rounded-lg border border-destructive bg-destructive/10 p-3 text-sm text-destructive"
>
{error}
</div>
{/if}
</AlertDialog.Description>
</AlertDialog.Header>
<AlertDialog.Footer>
<AlertDialog.Cancel disabled={loading}>Cancelar</AlertDialog.Cancel>
<AlertDialog.Action
onclick={handleDelete}
disabled={loading}
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
Eliminando...
{:else}
Eliminar
{/if}
</AlertDialog.Action>
</AlertDialog.Footer>
</AlertDialog.Content>
</AlertDialog.Root>

View File

@@ -660,7 +660,7 @@
<!-- Dialog para Insertar/Editar Clase de Activo Fijo -->
<Dialog.Root bind:open={showInsertDialog}>
<Dialog.Content class="!max-w-[1600px] !w-[1600px] !h-[90vh] p-0 overflow-hidden flex flex-col">
<Dialog.Content class="max-w-6xl w-full h-[90vh] p-0 overflow-hidden flex flex-col">
<Dialog.Header class="p-6 pb-4 border-b">
<Dialog.Title>{selectedClass ? 'Editar' : 'Nueva'} Clase de Activo Fijo</Dialog.Title>
</Dialog.Header>