feature/digitalizacion-api
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
const parentData = await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
return {
|
||||
error: 'No authenticated',
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 50
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const page = parseInt(url.searchParams.get('page') || '1');
|
||||
const pageSize = parseInt(url.searchParams.get('page_size') || '50');
|
||||
const search = url.searchParams.get('search') || '';
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId ? parseInt(cookieCompanyId) : parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return {
|
||||
error: 'No se encontró una compañía seleccionada',
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
page_size: pageSize
|
||||
};
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString()
|
||||
});
|
||||
|
||||
if (search) {
|
||||
params.append('search', search);
|
||||
}
|
||||
|
||||
const response = await authenticatedFetch(
|
||||
`v1/a76/document-types-digitization/?${params.toString()}`,
|
||||
{},
|
||||
cookies,
|
||||
fetch
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('📊 [Document Types Digitization] API Error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText
|
||||
});
|
||||
|
||||
return {
|
||||
error: `Error ${response.status}: ${response.statusText}`,
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
page_size: pageSize
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
return {
|
||||
items: data.items || [],
|
||||
total: data.total || 0,
|
||||
page: data.page || page,
|
||||
page_size: data.page_size || pageSize,
|
||||
error: null
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('📊 [Document Types Digitization] Load error:', error);
|
||||
return {
|
||||
error: 'Error loading data',
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 50
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,183 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { page } from '$app/stores';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { RefreshCw } from 'lucide-svelte';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import {
|
||||
documentTypesDigitizationApi,
|
||||
type DocumentTypeDigitization
|
||||
} from '$lib/api/dashboard/reference_data/document_types_digitization';
|
||||
import DataTable from '$lib/components/dashboard/reference_data/invoice_types/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/reference_data/document_types_digitization/columns';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
onMount(() => {
|
||||
if (browser) {
|
||||
const getCookie = (name: string): string | null => {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop()?.split(';').shift() || null;
|
||||
return null;
|
||||
};
|
||||
const cookieToken = getCookie('access_token');
|
||||
const localToken = localStorage.getItem('access_token');
|
||||
if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken);
|
||||
}
|
||||
});
|
||||
|
||||
let allItems = $state<DocumentTypeDigitization[]>(data.items || []);
|
||||
let currentPage = $state(data.page || 1);
|
||||
let pageSize = $state(data.page_size || 50);
|
||||
let totalItems = $state(data.total || 0);
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
let searchQuery = $state($page.url.searchParams.get('search') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
let hasMore = $derived(allItems.length < totalItems);
|
||||
|
||||
function getActiveCompanyId(): number | null {
|
||||
const fromStore = companyStore.activeCompany?.id;
|
||||
if (fromStore) return fromStore;
|
||||
if (!browser) return null;
|
||||
const cookie = document.cookie
|
||||
.split('; ')
|
||||
.find((row) => row.startsWith('active_company_id='))
|
||||
?.split('=')[1];
|
||||
if (!cookie) return null;
|
||||
const parsed = Number(cookie);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(async () => {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const companyId = getActiveCompanyId();
|
||||
if (!companyId) {
|
||||
error = 'No hay empresa activa seleccionada';
|
||||
return;
|
||||
}
|
||||
const response = await documentTypesDigitizationApi.list(1, pageSize, companyId, searchQuery);
|
||||
if (!response.error && response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
totalItems = response.data.total;
|
||||
}
|
||||
} catch (err) {
|
||||
error = 'Error aplicando filtros';
|
||||
console.error('Error applying filters:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
|
||||
const url = new URL($page.url);
|
||||
if (searchQuery) url.searchParams.set('search', searchQuery);
|
||||
else url.searchParams.delete('search');
|
||||
history.replaceState(history.state, '', url);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
async function loadMore() {
|
||||
if (loading || !hasMore) return;
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const companyId = getActiveCompanyId();
|
||||
if (!companyId) {
|
||||
error = 'No hay empresa activa seleccionada';
|
||||
return;
|
||||
}
|
||||
const response = await documentTypesDigitizationApi.list(
|
||||
currentPage + 1,
|
||||
pageSize,
|
||||
companyId,
|
||||
searchQuery
|
||||
);
|
||||
if (response.error) {
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
error = 'Sesión expirada. Recargando página...';
|
||||
setTimeout(() => window.location.reload(), 2000);
|
||||
} else {
|
||||
error = response.error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (response.data?.items) {
|
||||
allItems = [...allItems, ...response.data.items];
|
||||
currentPage += 1;
|
||||
totalItems = response.data.total;
|
||||
}
|
||||
} catch (err) {
|
||||
error = 'Error cargando más datos';
|
||||
console.error('Error loading more document types:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function reloadData() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
useShortcuts('Tipos de documento para digitalización', [
|
||||
{ key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData }
|
||||
]);
|
||||
|
||||
const columns = createColumns();
|
||||
</script>
|
||||
|
||||
<div class="flex h-[calc(100svh-4rem)] flex-col gap-6 overflow-hidden p-6 group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)]">
|
||||
<div class="flex-none flex items-center justify-between">
|
||||
<div class="space-y-1">
|
||||
<h1 class="text-2xl font-bold tracking-tight">Tipos de documento para digitalización</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Consulta el catálogo fijo de solo lectura utilizado por digitalización y pedimentos.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2 h-4 w-4" />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<div class="flex-none rounded-md border border-destructive px-4 py-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Card.Root class="border bg-background flex min-h-0 flex-1 flex-col">
|
||||
<Card.Header>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<Card.Title>Listado de tipos de documento</Card.Title>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<Input
|
||||
placeholder="Buscar por código o descripción"
|
||||
bind:value={searchQuery}
|
||||
oninput={handleSearch}
|
||||
class="h-9 w-56 bg-card lg:w-72"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content class="min-h-0 p-0">
|
||||
<div class="rounded-md border bg-background overflow-hidden flex min-h-0 flex-1 flex-col">
|
||||
<DataTable data={allItems} {columns} {loading} {hasMore} {loadMore} />
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user