Merge pull request 'feature/alerta-no-registros-agentes-clientes' (#179) from fix/svelte-a11y-warnings into development
Reviewed-on: ADUANASOFT/anexo76#179
This commit is contained in:
BIN
backend/celerybeat-schedule
Normal file
BIN
backend/celerybeat-schedule
Normal file
Binary file not shown.
@@ -265,12 +265,7 @@ services:
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
reservations:
|
||||
memory: 512M
|
||||
|
||||
|
||||
# celery
|
||||
celery_worker:
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<script lang="ts">
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
import { CircleAlert } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
agentsCount = 0,
|
||||
clientsCount = 0,
|
||||
onAccept,
|
||||
onCancel
|
||||
}: {
|
||||
open?: boolean;
|
||||
agentsCount?: number;
|
||||
clientsCount?: number;
|
||||
onAccept?: () => void;
|
||||
onCancel?: () => void;
|
||||
} = $props();
|
||||
|
||||
const showModal = $derived(agentsCount === 0 || clientsCount === 0);
|
||||
|
||||
const message = $derived(
|
||||
agentsCount === 0 && clientsCount === 0
|
||||
? 'No hay Agentes aduanales ni Clientes registrados. Debes darlos de alta para poder trabajar en este módulo.'
|
||||
: agentsCount === 0
|
||||
? 'No hay Agentes aduanales registrados. Debes darlos de alta para poder trabajar en este módulo.'
|
||||
: 'No hay Clientes registrados. Debes darlos de alta para poder trabajar en este módulo.'
|
||||
);
|
||||
|
||||
function handleOpenChange(newOpen: boolean) {
|
||||
open = newOpen;
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
onCancel?.();
|
||||
}
|
||||
|
||||
function handleAccept() {
|
||||
onAccept?.();
|
||||
}
|
||||
</script>
|
||||
|
||||
<AlertDialog.Root bind:open onOpenChange={handleOpenChange}>
|
||||
<AlertDialog.Content>
|
||||
<AlertDialog.Header>
|
||||
<div class="flex items-center gap-3">
|
||||
<CircleAlert class="h-6 w-6 shrink-0 text-amber-500" />
|
||||
<AlertDialog.Title>Aviso</AlertDialog.Title>
|
||||
</div>
|
||||
<AlertDialog.Description class="space-y-3 pt-1">
|
||||
<p>{message}</p>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Puedes registrarlos en
|
||||
<a
|
||||
href="/dashboard/customs_brokers"
|
||||
class="font-medium text-primary underline underline-offset-4 hover:no-underline"
|
||||
>
|
||||
Agentes Aduanales
|
||||
</a>
|
||||
y
|
||||
<a
|
||||
href="/dashboard/clients_and_providers"
|
||||
class="font-medium text-primary underline underline-offset-4 hover:no-underline"
|
||||
>
|
||||
Clientes y Proveedores
|
||||
</a>.
|
||||
</p>
|
||||
</AlertDialog.Description>
|
||||
</AlertDialog.Header>
|
||||
<AlertDialog.Footer>
|
||||
<AlertDialog.Cancel onclick={handleCancel}>Cancelar</AlertDialog.Cancel>
|
||||
<AlertDialog.Action onclick={handleAccept}>Aceptar</AlertDialog.Action>
|
||||
</AlertDialog.Footer>
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Root>
|
||||
@@ -121,7 +121,7 @@
|
||||
</script>
|
||||
|
||||
<Sheet.Root bind:open={helpStore.isOpen}>
|
||||
<Sheet.Trigger>
|
||||
<Sheet.Trigger asChild>
|
||||
<button
|
||||
class="fixed right-6 bottom-6 z-50 flex h-14 w-14 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg transition-transform hover:scale-110 active:scale-95"
|
||||
aria-label="Ayuda"
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
// Group local shortcuts
|
||||
let localShortcuts = $derived($store.shortcuts);
|
||||
|
||||
let globalList: HTMLDivElement;
|
||||
let localList: HTMLDivElement;
|
||||
let modalRef: HTMLDivElement;
|
||||
let globalList = $state<HTMLDivElement | undefined>();
|
||||
let localList = $state<HTMLDivElement | undefined>();
|
||||
let modalRef = $state<HTMLDivElement | undefined>();
|
||||
|
||||
function handleArrowScroll(event: KeyboardEvent, target: HTMLDivElement) {
|
||||
function handleArrowScroll(event: KeyboardEvent, target: HTMLDivElement | undefined) {
|
||||
if (!target) return;
|
||||
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
||||
event.preventDefault();
|
||||
@@ -51,6 +51,7 @@
|
||||
>
|
||||
<div
|
||||
class="w-full max-w-2xl rounded-xl bg-white p-6 shadow-2xl dark:bg-gray-900 text-gray-900 dark:text-gray-100 max-h-[80vh] overflow-y-auto"
|
||||
role="document"
|
||||
bind:this={modalRef}
|
||||
onkeydown={handleFocusTrap}
|
||||
>
|
||||
@@ -88,6 +89,8 @@
|
||||
</h3>
|
||||
<div
|
||||
class="space-y-2 max-h-64 overflow-y-auto pr-1"
|
||||
role="region"
|
||||
aria-label="Global Navigation shortcuts"
|
||||
bind:this={globalList}
|
||||
tabindex="0"
|
||||
onkeydown={(event) => handleArrowScroll(event, globalList)}
|
||||
@@ -124,6 +127,8 @@
|
||||
{:else}
|
||||
<div
|
||||
class="space-y-2 max-h-64 overflow-y-auto pr-1"
|
||||
role="region"
|
||||
aria-label="Active Actions shortcuts"
|
||||
bind:this={localList}
|
||||
tabindex="0"
|
||||
onkeydown={(event) => handleArrowScroll(event, localList)}
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
{#if item.icon}
|
||||
<item.icon />
|
||||
{:else}
|
||||
<div class="size-4" />
|
||||
<div class="size-4"></div>
|
||||
{/if}
|
||||
<!-- Ocultamos el texto en modo colapsado para asegurar que solo sea el icono -->
|
||||
<span class="sr-only">{item.title}</span>
|
||||
@@ -127,7 +127,7 @@
|
||||
{#if item.icon}
|
||||
<item.icon class="size-4 shrink-0" />
|
||||
{:else}
|
||||
<div class="size-4 shrink-0" />
|
||||
<div class="size-4 shrink-0"></div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -85,7 +85,6 @@
|
||||
bind:this={searchInputRef}
|
||||
value={searchQuery}
|
||||
placeholder={searchPlaceholder}
|
||||
autofocus={autoFocusSearch}
|
||||
class="placeholder:text-muted-foreground flex h-8 w-full rounded-md border border-input bg-background px-3 py-1 text-sm outline-none focus:border-ring focus:ring-1 focus:ring-ring"
|
||||
oninput={(event) => updateQuery((event.currentTarget as HTMLInputElement).value)}
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
import { getAuthTokens, getActiveCompanyId, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ params, cookies, fetch }) => {
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
}
|
||||
|
||||
const companyId = await getActiveCompanyId(cookies, fetch);
|
||||
if (!companyId) {
|
||||
throw error(400, 'No se encontró una compañía seleccionada');
|
||||
}
|
||||
|
||||
let agentsCount = 0;
|
||||
let clientsCount = 0;
|
||||
|
||||
try {
|
||||
const [brokersRes, clientsRes] = await Promise.all([
|
||||
authenticatedFetch(
|
||||
`v1/a76/customs-brokers?company_id=${companyId}&page=1&page_size=1`,
|
||||
{},
|
||||
cookies,
|
||||
fetch
|
||||
),
|
||||
authenticatedFetch(
|
||||
`v1/a76/clients-providers?company_id=${companyId}&page=1&page_size=1`,
|
||||
{},
|
||||
cookies,
|
||||
fetch
|
||||
)
|
||||
]);
|
||||
|
||||
if (brokersRes.ok) {
|
||||
const brokersData = await brokersRes.json();
|
||||
agentsCount = brokersData.total ?? 0;
|
||||
}
|
||||
if (clientsRes.ok) {
|
||||
const clientsData = await clientsRes.json();
|
||||
clientsCount = clientsData.total ?? 0;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error fetching prerequisites count for parts:', e);
|
||||
}
|
||||
|
||||
const isCreate = params.id === 'new' || params.id === undefined;
|
||||
|
||||
return {
|
||||
agentsCount,
|
||||
clientsCount,
|
||||
isCreate
|
||||
};
|
||||
};
|
||||
@@ -1,9 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import PartForm from '$lib/components/dashboard/goods/parts/partForm.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import PartForm from '$lib/components/dashboard/goods/parts/partForm.svelte';
|
||||
import PrerequisitesModal from '$lib/components/dashboard/PrerequisitesModal.svelte';
|
||||
|
||||
let id = $derived($page.params.id === 'new' ? null : Number($page.params.id));
|
||||
let type: 'inv' | 'fa' = 'fa';
|
||||
let id = $derived($page.params.id === 'new' ? null : Number($page.params.id));
|
||||
let type: 'inv' | 'fa' = 'fa';
|
||||
|
||||
let { data }: { data: { agentsCount?: number; clientsCount?: number; isCreate?: boolean } } = $props();
|
||||
|
||||
let showPrerequisitesModal = $state(false);
|
||||
let hasTriedToShowPrerequisitesModal = $state(false);
|
||||
$effect(() => {
|
||||
if (hasTriedToShowPrerequisitesModal) return;
|
||||
if (!data?.isCreate) return;
|
||||
const agentsCount = data.agentsCount ?? 0;
|
||||
const clientsCount = data.clientsCount ?? 0;
|
||||
if (agentsCount === 0 || clientsCount === 0) {
|
||||
hasTriedToShowPrerequisitesModal = true;
|
||||
showPrerequisitesModal = true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if data?.isCreate}
|
||||
<PrerequisitesModal
|
||||
bind:open={showPrerequisitesModal}
|
||||
agentsCount={data?.agentsCount ?? 0}
|
||||
clientsCount={data?.clientsCount ?? 0}
|
||||
onAccept={() => (showPrerequisitesModal = false)}
|
||||
onCancel={() => goto('/dashboard/goods/parts')}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<PartForm partId={id} formType={type} />
|
||||
@@ -39,6 +39,7 @@
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosEdicionFactura } from '$lib/config/shortcuts/dashboard/invoices/edit';
|
||||
import { api } from '$lib/api';
|
||||
import PrerequisitesModal from '$lib/components/dashboard/PrerequisitesModal.svelte';
|
||||
|
||||
// Cargar companyStore solo en el cliente - no usamos sidebar en esta página
|
||||
let companyStore: any = $state(undefined);
|
||||
@@ -793,6 +794,19 @@
|
||||
manejarRegresar: handleBack
|
||||
})
|
||||
);
|
||||
|
||||
// Prerrequisitos: modal cuando no hay agentes o clientes
|
||||
const agentsCount = $derived(data.customsBrokers?.length ?? 0);
|
||||
const clientsCount = $derived(data.clients?.length ?? 0);
|
||||
let showPrerequisitesModal = $state(false);
|
||||
let hasTriedToShowPrerequisitesModal = $state(false);
|
||||
$effect(() => {
|
||||
if (hasTriedToShowPrerequisitesModal) return;
|
||||
if (agentsCount === 0 || clientsCount === 0) {
|
||||
hasTriedToShowPrerequisitesModal = true;
|
||||
showPrerequisitesModal = true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="space-y-3">
|
||||
@@ -1042,3 +1056,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PrerequisitesModal
|
||||
bind:open={showPrerequisitesModal}
|
||||
{agentsCount}
|
||||
{clientsCount}
|
||||
onAccept={() => (showPrerequisitesModal = false)}
|
||||
onCancel={() => goto('/dashboard/invoices')}
|
||||
/>
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
import { getExchangeRateByDate } from '$lib/api/dashboard/a76/exchange-rate';
|
||||
import ExchangeRateDialog from '$lib/components/dashboard/exchange_rate/create-edit-dialog.svelte';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import PrerequisitesModal from '$lib/components/dashboard/PrerequisitesModal.svelte';
|
||||
|
||||
let { data }: { data: ExtendedPageData } = $props();
|
||||
|
||||
@@ -118,6 +119,20 @@
|
||||
let showExchangeRateDialog = $state(false);
|
||||
let missingExchangeRateDate = $state('');
|
||||
|
||||
// Prerrequisitos: modal solo en creación cuando no hay agentes o clientes
|
||||
const agentsCount = $derived(data.customsBrokers?.length ?? 0);
|
||||
const clientsCount = $derived(data.clients?.length ?? 0);
|
||||
let showPrerequisitesModal = $state(false);
|
||||
let hasTriedToShowPrerequisitesModal = $state(false);
|
||||
$effect(() => {
|
||||
if (hasTriedToShowPrerequisitesModal) return;
|
||||
if (!data.isCreate) return;
|
||||
if (agentsCount === 0 || clientsCount === 0) {
|
||||
hasTriedToShowPrerequisitesModal = true;
|
||||
showPrerequisitesModal = true;
|
||||
}
|
||||
});
|
||||
|
||||
async function checkPaymentDateRate(date: string): Promise<boolean> {
|
||||
if (!date || !companyStore.activeCompany?.id) return true;
|
||||
|
||||
@@ -1249,4 +1264,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Root>
|
||||
|
||||
<!-- Modal de prerrequisitos: agentes aduanales y clientes -->
|
||||
{#if data.isCreate}
|
||||
<PrerequisitesModal
|
||||
bind:open={showPrerequisitesModal}
|
||||
{agentsCount}
|
||||
{clientsCount}
|
||||
onAccept={() => (showPrerequisitesModal = false)}
|
||||
onCancel={() => goto('/dashboard/pedimentos')}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user