diff --git a/.gitignore b/.gitignore index 471d46fd..2d9ff71d 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ wheels/ *.egg-info/ .installed.cfg *.egg +.pnpm-store/ # Environment .env diff --git a/frontend/package.json b/frontend/package.json index 53787930..02dc6bfc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -29,6 +29,7 @@ "@tailwindcss/forms": "^0.5.10", "@tailwindcss/typography": "^0.5.19", "@tailwindcss/vite": "^4.1.14", + "@tanstack/table-core": "^8.21.3", "@types/node": "^20", "@vitest/browser": "^3.2.4", "bits-ui": "^2.14.2", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index eca68e3c..b514b82d 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -48,6 +48,9 @@ importers: '@tailwindcss/vite': specifier: ^4.1.14 version: 4.1.14(vite@7.1.10(@types/node@20.19.22)(jiti@2.6.1)(lightningcss@1.30.1)) + '@tanstack/table-core': + specifier: ^8.21.3 + version: 8.21.3 '@types/node': specifier: ^20 version: 20.19.22 @@ -723,6 +726,10 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 + '@tanstack/table-core@8.21.3': + resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} + engines: {node: '>=12'} + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -2492,6 +2499,8 @@ snapshots: tailwindcss: 4.1.14 vite: 7.1.10(@types/node@20.19.22)(jiti@2.6.1)(lightningcss@1.30.1) + '@tanstack/table-core@8.21.3': {} + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.27.1 diff --git a/frontend/src/app.css b/frontend/src/app.css index 236c256b..8d9a66ab 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -42,36 +42,36 @@ .dark { --background: oklch(0.141 0.005 285.823); - --foreground: oklch(0.985 0 0); - --card: oklch(0.21 0.006 285.885); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.21 0.006 285.885); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.546 0.245 262.881); - --primary-foreground: oklch(0.379 0.146 265.522); - --secondary: oklch(0.274 0.006 286.033); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.274 0.006 286.033); - --muted-foreground: oklch(0.705 0.015 286.067); - --accent: oklch(0.274 0.006 286.033); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.488 0.243 264.376); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.21 0.006 285.885); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.546 0.245 262.881); - --sidebar-primary-foreground: oklch(0.379 0.146 265.522); - --sidebar-accent: oklch(0.274 0.006 286.033); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.488 0.243 264.376); + --foreground: oklch(0.985 0 0); + --card: oklch(0.21 0.006 285.885); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.21 0.006 285.885); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.546 0.245 262.881); + --primary-foreground: oklch(0.98 0.01 262.881); + --secondary: oklch(0.274 0.006 286.033); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.274 0.006 286.033); + --muted-foreground: oklch(0.705 0.015 286.067); + --accent: oklch(0.274 0.006 286.033); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.488 0.243 264.376); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.21 0.006 285.885); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.546 0.245 262.881); + --sidebar-primary-foreground: oklch(0.379 0.146 265.522); + --sidebar-accent: oklch(0.274 0.006 286.033); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.488 0.243 264.376); } diff --git a/frontend/src/app.html b/frontend/src/app.html index 35bd8b2c..e52450df 100644 --- a/frontend/src/app.html +++ b/frontend/src/app.html @@ -1,5 +1,5 @@ - + diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 4ae6cdf7..a0a996d1 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -32,7 +32,8 @@ async function fetchApi( try { const response = await fetch(`${API_BASE_URL}${endpoint}`, { ...options, - headers + headers, + credentials: 'include' // Importante: envía cookies con cada request }); const data = await response.json(); diff --git a/frontend/src/lib/api/dashboard/refrence_data/code_pedimento_regimens.ts b/frontend/src/lib/api/dashboard/refrence_data/code_pedimento_regimens.ts index e69de29b..837216f8 100644 --- a/frontend/src/lib/api/dashboard/refrence_data/code_pedimento_regimens.ts +++ b/frontend/src/lib/api/dashboard/refrence_data/code_pedimento_regimens.ts @@ -0,0 +1,73 @@ +/** + * API Client para Code Pedimento Regimens + * Gestiona las operaciones CRUD para las relaciones entre códigos de pedimento y regímenes + */ +import { api } from '$lib/api'; + +export interface CodePedimentoRegimen { + id: number; + pedimento_code: string; + regimen_code: string | null; + type_code: string | null; +} + +export interface CodePedimentoRegimenListResponse { + items: CodePedimentoRegimen[]; + total: number; + page: number; + page_size: number; +} + +export interface CreateCodePedimentoRegimenData { + pedimento_code: string; + regimen_code?: string; + type_code?: string; +} + +export interface UpdateCodePedimentoRegimenData { + pedimento_code?: string; + regimen_code?: string; + type_code?: string; +} + +/** + * API para Code Pedimento Regimens + */ +export const codePedimentoRegimensApi = { + /** + * Lista todos los code pedimento regimens con paginación + * @param page - Número de página (por defecto 1) + * @param pageSize - Tamaño de página (por defecto 50) + */ + list: (page = 1, pageSize = 50) => + api.get( + `/v1/code-pedimento-regimens?page=${page}&page_size=${pageSize}` + ), + + /** + * Obtiene un code pedimento regimen por ID + * @param id - ID del code pedimento regimen + */ + get: (id: number) => api.get(`/v1/code-pedimento-regimens/${id}`), + + /** + * Crea un nuevo code pedimento regimen + * @param data - Datos del code pedimento regimen a crear + */ + create: (data: CreateCodePedimentoRegimenData) => + api.post('/v1/code-pedimento-regimens', data), + + /** + * Actualiza un code pedimento regimen existente + * @param id - ID del code pedimento regimen a actualizar + * @param data - Datos a actualizar + */ + update: (id: number, data: UpdateCodePedimentoRegimenData) => + api.put(`/v1/code-pedimento-regimens/${id}`, data), + + /** + * Elimina un code pedimento regimen + * @param id - ID del code pedimento regimen a eliminar + */ + delete: (id: number) => api.delete(`/v1/code-pedimento-regimens/${id}`) +}; diff --git a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/columns.ts b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/columns.ts new file mode 100644 index 00000000..7686563b --- /dev/null +++ b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/columns.ts @@ -0,0 +1,85 @@ +import type { ColumnDef } from "@tanstack/table-core"; +import { renderComponent, renderSnippet } from "$lib/components/ui/data-table/index.js"; +import { createRawSnippet } from "svelte"; +import DataTableActions from "./data-table-actions.svelte"; + +export type CodePedimentoRegimen = { + id: number; + pedimento_code: string; + regimen_code: string | null; + type_code: string | null; +}; + +export const columns: ColumnDef[] = [ + { + accessorKey: "id", + header: "ID", + cell: ({ row }) => { + const idSnippet = createRawSnippet<[{ id: number }]>((getId) => { + const { id } = getId(); + return { + render: () => `
${id}
` + }; + }); + return renderSnippet(idSnippet, { id: row.original.id }); + } + }, + { + accessorKey: "pedimento_code", + header: "Código Pedimento", + cell: ({ row }) => { + const codeSnippet = createRawSnippet<[{ code: string }]>((getCode) => { + const { code } = getCode(); + return { + render: () => + `${code}` + }; + }); + return renderSnippet(codeSnippet, { code: row.original.pedimento_code }); + } + }, + { + accessorKey: "regimen_code", + header: "Código Régimen", + cell: ({ row }) => { + const regimenSnippet = createRawSnippet<[{ code: string | null }]>((getCode) => { + const { code } = getCode(); + if (code) { + return { + render: () => + `${code}` + }; + } + return { + render: () => `N/A` + }; + }); + return renderSnippet(regimenSnippet, { code: row.original.regimen_code }); + } + }, + { + accessorKey: "type_code", + header: "Tipo", + cell: ({ row }) => { + const typeSnippet = createRawSnippet<[{ type: string | null }]>((getType) => { + const { type } = getType(); + if (type) { + return { + render: () => + `${type}` + }; + } + return { + render: () => `N/A` + }; + }); + return renderSnippet(typeSnippet, { type: row.original.type_code }); + } + }, + { + id: "actions", + cell: ({ row }) => { + return renderComponent(DataTableActions, { item: row.original }); + } + } +]; diff --git a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table-actions.svelte new file mode 100644 index 00000000..22b42007 --- /dev/null +++ b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table-actions.svelte @@ -0,0 +1,32 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + + + Acciones + navigator.clipboard.writeText(item.id.toString())}> + Copiar ID + + + + Ver detalles + Editar + + Eliminar + + diff --git a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte new file mode 100644 index 00000000..c16b5315 --- /dev/null +++ b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte @@ -0,0 +1,139 @@ + + +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + + No hay resultados. + + + {/each} + + +
+
+
+ Mostrando {data.length} de {totalItems} registro(s) +
+
+
+ Página {currentPage} de {Math.ceil(totalItems / pageSize)} +
+ + +
+
+
diff --git a/frontend/src/lib/components/sidebar/modules.ts b/frontend/src/lib/components/sidebar/modules.ts index 829156eb..8017eb00 100644 --- a/frontend/src/lib/components/sidebar/modules.ts +++ b/frontend/src/lib/components/sidebar/modules.ts @@ -68,7 +68,7 @@ export const sidebarData: SidebarData = { icon: SquareTerminalIcon, items: [ { - title: "", + title: "Codigos de Pedimento y Régimen", url: "/dashboard/reference_data/code_pedimento_regimens", }, { diff --git a/frontend/src/lib/components/ui/data-table/data-table.svelte.ts b/frontend/src/lib/components/ui/data-table/data-table.svelte.ts new file mode 100644 index 00000000..5b7985e7 --- /dev/null +++ b/frontend/src/lib/components/ui/data-table/data-table.svelte.ts @@ -0,0 +1,142 @@ +import { + type RowData, + type TableOptions, + type TableOptionsResolved, + type TableState, + createTable, +} from "@tanstack/table-core"; + +/** + * Creates a reactive TanStack table object for Svelte. + * @param options Table options to create the table with. + * @returns A reactive table object. + * @example + * ```svelte + * + * + * + * + * {#each table.getHeaderGroups() as headerGroup} + * + * {#each headerGroup.headers as header} + * + * {/each} + * + * {/each} + * + * + *
+ * + *
+ * ``` + */ +export function createSvelteTable(options: TableOptions) { + const resolvedOptions: TableOptionsResolved = mergeObjects( + { + state: {}, + onStateChange() {}, + renderFallbackValue: null, + mergeOptions: ( + defaultOptions: TableOptions, + options: Partial> + ) => { + return mergeObjects(defaultOptions, options); + }, + }, + options + ); + + const table = createTable(resolvedOptions); + let state = $state>(table.initialState); + + function updateOptions() { + table.setOptions((prev) => { + return mergeObjects(prev, options, { + state: mergeObjects(state, options.state || {}), + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onStateChange: (updater: any) => { + if (updater instanceof Function) state = updater(state); + else state = mergeObjects(state, updater); + + options.onStateChange?.(updater); + }, + }); + }); + } + + updateOptions(); + + $effect.pre(() => { + updateOptions(); + }); + + return table; +} + +type MaybeThunk = T | (() => T | null | undefined); +type Intersection = (T extends [infer H, ...infer R] + ? H & Intersection + : unknown) & {}; + +/** + * Lazily merges several objects (or thunks) while preserving + * getter semantics from every source. + * + * Proxy-based to avoid known WebKit recursion issue. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function mergeObjects[]>( + ...sources: Sources +): Intersection<{ [K in keyof Sources]: Sources[K] }> { + const resolve = (src: MaybeThunk): T | undefined => + typeof src === "function" ? (src() ?? undefined) : src; + + const findSourceWithKey = (key: PropertyKey) => { + for (let i = sources.length - 1; i >= 0; i--) { + const obj = resolve(sources[i]); + if (obj && key in obj) return obj; + } + return undefined; + }; + + return new Proxy(Object.create(null), { + get(_, key) { + const src = findSourceWithKey(key); + + return src?.[key as never]; + }, + + has(_, key) { + return !!findSourceWithKey(key); + }, + + ownKeys(): (string | symbol)[] { + // eslint-disable-next-line svelte/prefer-svelte-reactivity + const all = new Set(); + for (const s of sources) { + const obj = resolve(s); + if (obj) { + for (const k of Reflect.ownKeys(obj) as (string | symbol)[]) { + all.add(k); + } + } + } + return [...all]; + }, + + getOwnPropertyDescriptor(_, key) { + const src = findSourceWithKey(key); + if (!src) return undefined; + return { + configurable: true, + enumerable: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: (src as any)[key], + writable: true, + }; + }, + }) as Intersection<{ [K in keyof Sources]: Sources[K] }>; +} diff --git a/frontend/src/lib/components/ui/data-table/flex-render.svelte b/frontend/src/lib/components/ui/data-table/flex-render.svelte new file mode 100644 index 00000000..ac82a581 --- /dev/null +++ b/frontend/src/lib/components/ui/data-table/flex-render.svelte @@ -0,0 +1,40 @@ + + +{#if typeof content === "string"} + {content} +{:else if content instanceof Function} + + + {@const result = content(context as any)} + {#if result instanceof RenderComponentConfig} + {@const { component: Component, props } = result} + + {:else if result instanceof RenderSnippetConfig} + {@const { snippet, params } = result} + {@render snippet({ ...params, attach })} + {:else} + {result} + {/if} +{/if} diff --git a/frontend/src/lib/components/ui/data-table/index.ts b/frontend/src/lib/components/ui/data-table/index.ts new file mode 100644 index 00000000..5f4e77ea --- /dev/null +++ b/frontend/src/lib/components/ui/data-table/index.ts @@ -0,0 +1,3 @@ +export { default as FlexRender } from "./flex-render.svelte"; +export { renderComponent, renderSnippet } from "./render-helpers.js"; +export { createSvelteTable } from "./data-table.svelte.js"; diff --git a/frontend/src/lib/components/ui/data-table/render-helpers.ts b/frontend/src/lib/components/ui/data-table/render-helpers.ts new file mode 100644 index 00000000..fa036d62 --- /dev/null +++ b/frontend/src/lib/components/ui/data-table/render-helpers.ts @@ -0,0 +1,111 @@ +import type { Component, ComponentProps, Snippet } from "svelte"; + +/** + * A helper class to make it easy to identify Svelte components in + * `columnDef.cell` and `columnDef.header` properties. + * + * > NOTE: This class should only be used internally by the adapter. If you're + * reading this and you don't know what this is for, you probably don't need it. + * + * @example + * ```svelte + * {@const result = content(context as any)} + * {#if result instanceof RenderComponentConfig} + * {@const { component: Component, props } = result} + * + * {/if} + * ``` + */ +export class RenderComponentConfig { + component: TComponent; + props: ComponentProps | Record; + constructor( + component: TComponent, + props: ComponentProps | Record = {} + ) { + this.component = component; + this.props = props; + } +} + +/** + * A helper class to make it easy to identify Svelte Snippets in `columnDef.cell` and `columnDef.header` properties. + * + * > NOTE: This class should only be used internally by the adapter. If you're + * reading this and you don't know what this is for, you probably don't need it. + * + * @example + * ```svelte + * {@const result = content(context as any)} + * {#if result instanceof RenderSnippetConfig} + * {@const { snippet, params } = result} + * {@render snippet(params)} + * {/if} + * ``` + */ +export class RenderSnippetConfig { + snippet: Snippet<[TProps]>; + params: TProps; + constructor(snippet: Snippet<[TProps]>, params: TProps) { + this.snippet = snippet; + this.params = params; + } +} + +/** + * A helper function to help create cells from Svelte components through ColumnDef's `cell` and `header` properties. + * + * This is only to be used with Svelte Components - use `renderSnippet` for Svelte Snippets. + * + * @param component A Svelte component + * @param props The props to pass to `component` + * @returns A `RenderComponentConfig` object that helps svelte-table know how to render the header/cell component. + * @example + * ```ts + * // +page.svelte + * const defaultColumns = [ + * columnHelper.accessor('name', { + * header: header => renderComponent(SortHeader, { label: 'Name', header }), + * }), + * columnHelper.accessor('state', { + * header: header => renderComponent(SortHeader, { label: 'State', header }), + * }), + * ] + * ``` + * @see {@link https://tanstack.com/table/latest/docs/guide/column-defs} + */ +export function renderComponent< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + T extends Component, + Props extends ComponentProps, +>(component: T, props: Props = {} as Props) { + return new RenderComponentConfig(component, props); +} + +/** + * A helper function to help create cells from Svelte Snippets through ColumnDef's `cell` and `header` properties. + * + * The snippet must only take one parameter. + * + * This is only to be used with Snippets - use `renderComponent` for Svelte Components. + * + * @param snippet + * @param params + * @returns - A `RenderSnippetConfig` object that helps svelte-table know how to render the header/cell snippet. + * @example + * ```ts + * // +page.svelte + * const defaultColumns = [ + * columnHelper.accessor('name', { + * cell: cell => renderSnippet(nameSnippet, { name: cell.row.name }), + * }), + * columnHelper.accessor('state', { + * cell: cell => renderSnippet(stateSnippet, { state: cell.row.state }), + * }), + * ] + * ``` + * @see {@link https://tanstack.com/table/latest/docs/guide/column-defs} + */ +export function renderSnippet(snippet: Snippet<[TProps]>, params: TProps = {} as TProps) { + return new RenderSnippetConfig(snippet, params); +} diff --git a/frontend/src/lib/components/ui/table/index.ts b/frontend/src/lib/components/ui/table/index.ts new file mode 100644 index 00000000..14695c81 --- /dev/null +++ b/frontend/src/lib/components/ui/table/index.ts @@ -0,0 +1,28 @@ +import Root from "./table.svelte"; +import Body from "./table-body.svelte"; +import Caption from "./table-caption.svelte"; +import Cell from "./table-cell.svelte"; +import Footer from "./table-footer.svelte"; +import Head from "./table-head.svelte"; +import Header from "./table-header.svelte"; +import Row from "./table-row.svelte"; + +export { + Root, + Body, + Caption, + Cell, + Footer, + Head, + Header, + Row, + // + Root as Table, + Body as TableBody, + Caption as TableCaption, + Cell as TableCell, + Footer as TableFooter, + Head as TableHead, + Header as TableHeader, + Row as TableRow, +}; diff --git a/frontend/src/lib/components/ui/table/table-body.svelte b/frontend/src/lib/components/ui/table/table-body.svelte new file mode 100644 index 00000000..29e96875 --- /dev/null +++ b/frontend/src/lib/components/ui/table/table-body.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/frontend/src/lib/components/ui/table/table-caption.svelte b/frontend/src/lib/components/ui/table/table-caption.svelte new file mode 100644 index 00000000..4696cff5 --- /dev/null +++ b/frontend/src/lib/components/ui/table/table-caption.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/frontend/src/lib/components/ui/table/table-cell.svelte b/frontend/src/lib/components/ui/table/table-cell.svelte new file mode 100644 index 00000000..1a2f033f --- /dev/null +++ b/frontend/src/lib/components/ui/table/table-cell.svelte @@ -0,0 +1,23 @@ + + + + {@render children?.()} + diff --git a/frontend/src/lib/components/ui/table/table-footer.svelte b/frontend/src/lib/components/ui/table/table-footer.svelte new file mode 100644 index 00000000..b9b14ebf --- /dev/null +++ b/frontend/src/lib/components/ui/table/table-footer.svelte @@ -0,0 +1,20 @@ + + +tr]:last:border-b-0", className)} + {...restProps} +> + {@render children?.()} + diff --git a/frontend/src/lib/components/ui/table/table-head.svelte b/frontend/src/lib/components/ui/table/table-head.svelte new file mode 100644 index 00000000..e9dd2378 --- /dev/null +++ b/frontend/src/lib/components/ui/table/table-head.svelte @@ -0,0 +1,23 @@ + + + + {@render children?.()} + diff --git a/frontend/src/lib/components/ui/table/table-header.svelte b/frontend/src/lib/components/ui/table/table-header.svelte new file mode 100644 index 00000000..f47d2597 --- /dev/null +++ b/frontend/src/lib/components/ui/table/table-header.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/frontend/src/lib/components/ui/table/table-row.svelte b/frontend/src/lib/components/ui/table/table-row.svelte new file mode 100644 index 00000000..0df769e0 --- /dev/null +++ b/frontend/src/lib/components/ui/table/table-row.svelte @@ -0,0 +1,23 @@ + + +svelte-css-wrapper]:[&>th,td]:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", + className + )} + {...restProps} +> + {@render children?.()} + diff --git a/frontend/src/lib/components/ui/table/table.svelte b/frontend/src/lib/components/ui/table/table.svelte new file mode 100644 index 00000000..a3349563 --- /dev/null +++ b/frontend/src/lib/components/ui/table/table.svelte @@ -0,0 +1,22 @@ + + +
+ + {@render children?.()} +
+
diff --git a/frontend/src/routes/dashboard/+layout.svelte b/frontend/src/routes/dashboard/+layout.svelte index 8b96419d..2d21f0d2 100644 --- a/frontend/src/routes/dashboard/+layout.svelte +++ b/frontend/src/routes/dashboard/+layout.svelte @@ -1,6 +1,10 @@ -{@render children()} + + + +
+
+ + + + + + + +
+
+
+ + {@render children()} +
+
+
diff --git a/frontend/src/routes/dashboard/+page.svelte b/frontend/src/routes/dashboard/+page.svelte index a0031a88..316f62b4 100644 --- a/frontend/src/routes/dashboard/+page.svelte +++ b/frontend/src/routes/dashboard/+page.svelte @@ -1,39 +1,129 @@ - - - -
-
- - - - - - - +
+ +
+

Bienvenido al Dashboard

+

+ Sistema de gestión de comercio exterior conforme a Anexos 24, 31 y 22 del SAT +

+
+ + +
+ + + Total de Pedimentos + + +
0
+

Registros activos

+
+
+ + + + Datos de Referencia + + +
12
+

Catálogos disponibles

+
+
+ + + + Licencia Activa + + +
+

Cuenta verificada

+
+
+
+ + + + + Accesos Rápidos + Accede a las funciones más utilizadas del sistema + + +
+ + + + + + + + + Código Pedimento - Regímenes + Gestionar relaciones + + +
+ + + + + + Catálogo de Tipos + Próximamente +
+ +
+ + + + + + Reportes + Próximamente +
-
-
-
-
-
-
-
-
-
-
-
+ + + diff --git a/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.server.ts b/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.server.ts new file mode 100644 index 00000000..3aabfdd4 --- /dev/null +++ b/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.server.ts @@ -0,0 +1,89 @@ +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ cookies, fetch, url }) => { + const token = cookies.get('access_token'); + + if (!token) { + return { + error: 'No authenticated', + items: [], + total: 0, + page: 1, + page_size: 50 + }; + } + + try { + // Obtener parámetros de paginación de la URL + const page = parseInt(url.searchParams.get('page') || '1'); + const pageSize = parseInt(url.searchParams.get('page_size') || '50'); + + // Configurar la URL de la API para SSR + let apiUrl = process.env.INTERNAL_API_URL; + if (!apiUrl) { + apiUrl = import.meta.env.VITE_API_URL; + // Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR) + apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend'); + } + + // Normalizar la URL + const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`; + + console.log('📊 [Code Pedimento Regimens] Fetching data:', { + url: `${baseUrl}v1/code-pedimento-regimens`, + page, + pageSize + }); + + const response = await fetch( + `${baseUrl}v1/code-pedimento-regimens?page=${page}&page_size=${pageSize}`, + { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + } + ); + + if (!response.ok) { + const errorText = await response.text(); + console.error('📊 [Code Pedimento Regimens] API Error:', { + status: response.status, + statusText: response.statusText, + error: errorText + }); + + return { + error: `Error ${response.status}: ${response.statusText}`, + items: [], + total: 0, + page: page, + page_size: pageSize + }; + } + + const data = await response.json(); + + console.log('📊 [Code Pedimento Regimens] Data loaded:', { + total: data.total, + itemsCount: data.items?.length + }); + + return { + items: data.items || [], + total: data.total || 0, + page: data.page || page, + page_size: data.page_size || pageSize, + error: null + }; + } catch (error) { + console.error('📊 [Code Pedimento Regimens] Load error:', error); + return { + error: 'Error loading data', + items: [], + total: 0, + page: 1, + page_size: 50 + }; + } +}; diff --git a/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte b/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte index e69de29b..85e4e473 100644 --- a/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte @@ -0,0 +1,113 @@ + + +
+ +
+
+

Código Pedimento - Regímenes

+

+ Gestiona las relaciones entre códigos de pedimento y regímenes +

+
+ +
+ + + {#if error} + + + Error + {error} + + + {/if} + + + + +
+
+ Listado de Relaciones + Total de registros: {totalItems} +
+ +
+
+ + + + +
+
diff --git a/package.json b/package.json new file mode 100644 index 00000000..24a8af56 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "lucide-svelte": "^0.552.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..47270527 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,157 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + lucide-svelte: + specifier: ^0.552.0 + version: 0.552.0(svelte@5.43.2) + +packages: + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@sveltejs/acorn-typescript@1.0.6': + resolution: {integrity: sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==} + peerDependencies: + acorn: ^8.9.0 + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + + esrap@2.1.2: + resolution: {integrity: sha512-DgvlIQeowRNyvLPWW4PT7Gu13WznY288Du086E751mwwbsgr29ytBiYeLzAGIo0qk3Ujob0SDk8TiSaM5WQzNg==} + + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + + lucide-svelte@0.552.0: + resolution: {integrity: sha512-zynJ64KOsuQG3I4tSqfvvl7Kc9x4mWkppbxsuyrbegQwma9HFhBp4aE6HuQNF4c3pS0AHWHki5CAMs5m3QXA5w==} + peerDependencies: + svelte: ^3 || ^4 || ^5.0.0-next.42 + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + svelte@5.43.2: + resolution: {integrity: sha512-ro1umEzX8rT5JpCmlf0PPv7ncD8MdVob9e18bhwqTKNoLjS8kDvhVpaoYVPc+qMwDAOfcwJtyY7ZFSDbOaNPgA==} + engines: {node: '>=18'} + + zimmerframe@1.1.4: + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} + +snapshots: + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@sveltejs/acorn-typescript@1.0.6(acorn@8.15.0)': + dependencies: + acorn: 8.15.0 + + '@types/estree@1.0.8': {} + + acorn@8.15.0: {} + + aria-query@5.3.2: {} + + axobject-query@4.1.0: {} + + clsx@2.1.1: {} + + esm-env@1.2.2: {} + + esrap@2.1.2: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + locate-character@3.0.0: {} + + lucide-svelte@0.552.0(svelte@5.43.2): + dependencies: + svelte: 5.43.2 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + svelte@5.43.2: + dependencies: + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.5 + '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) + '@types/estree': 1.0.8 + acorn: 8.15.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + clsx: 2.1.1 + esm-env: 1.2.2 + esrap: 2.1.2 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.21 + zimmerframe: 1.1.4 + + zimmerframe@1.1.4: {}