fix/catalogos-fijos

This commit is contained in:
2026-04-22 10:13:34 -06:00
parent 9884cf44c3
commit 8c1dd07d76
27 changed files with 387 additions and 40 deletions

View File

@@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Tuple
from fastapi import HTTPException
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from sqlalchemy import or_
from . import dto, models
@@ -33,6 +34,14 @@ class SectorService:
)
if filters:
if filters.get("search"):
search_term = f"%{filters['search']}%"
query = query.filter(
or_(
models.Sector.key.ilike(search_term),
models.Sector.description.ilike(search_term)
)
)
if filters.get("key"):
query = query.filter(
models.Sector.key.ilike(f"%{filters['key']}%")

View File

@@ -18,12 +18,25 @@ async def list_agency_tariff_codes(
page_size: int = Query(50, ge=1, le=1000, description="Tamaño de página"),
agency: str = Query(None, description="Filtrar por código de agencia"),
q: str = Query(None, description="Búsqueda general"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(AgencyTariffCode)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
AgencyTariffCode.tariff_flag_code.ilike(search_filter),
AgencyTariffCode.agency_code.ilike(search_filter),
AgencyTariffCode.requirement_level.ilike(search_filter),
AgencyTariffCode.program_code.ilike(search_filter),
AgencyTariffCode.definition.ilike(search_filter)
)
)
if agency:
query = query.filter(AgencyTariffCode.agency_code == agency)

View File

@@ -18,12 +18,22 @@ async def list_carta_porte(
page: int = Query(1, ge=1),
page_size: int = Query(50, ge=1, le=100),
q: str = Query(None, description="Buscar por código o descripción"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(CartaPorte)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
CartaPorte.code.ilike(search_filter),
CartaPorte.description.ilike(search_filter)
)
)
if q:
search_filter = f"%{q}%"
query = query.filter(

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user, has_role
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import CodePedimentoRegimenDTO
from .models import CodePedimentoRegimen
@@ -18,11 +19,20 @@ def list_code_pedimento_regimens(
code: str = Query(None, description="Filter by code"),
regime: str = Query(None, description="Filter by regime"),
type: str = Query(None, description="Filter by type"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(CodePedimentoRegimen)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
CodePedimentoRegimen.pedimento_code.ilike(search_filter)
)
)
if code is not None:
query = query.filter(CodePedimentoRegimen.pedimento_code == code)

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user, has_role
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import ContainerDTO
from .models import Container
@@ -15,11 +16,21 @@ router = APIRouter(prefix="/containers")
async def list_containers(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(Container)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
Container.key.ilike(search_filter),
Container.description.ilike(search_filter)
)
)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user, has_role
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import CurrencyTypeDTO
from .models import CurrencyType
@@ -15,11 +16,22 @@ router = APIRouter(prefix="/currency-types")
async def list_currency_types(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(CurrencyType)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
CurrencyType.code.ilike(search_filter),
CurrencyType.currency_name.ilike(search_filter),
CurrencyType.country_description.ilike(search_filter)
)
)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user, has_role
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import CustomsSectionDTO
from .models import CustomsSection
@@ -15,11 +16,21 @@ router = APIRouter(prefix="/customs-sections")
def list_customs_sections(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(CustomsSection)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
CustomsSection.code.ilike(search_filter),
CustomsSection.description.ilike(search_filter)
)
)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user, has_role
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import CustomsWarehouseDTO
from .models import CustomsWarehouse
@@ -15,11 +16,22 @@ router = APIRouter(prefix="/customs-warehouses")
def list_customs_warehouses(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(CustomsWarehouse)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
CustomsWarehouse.key.ilike(search_filter),
CustomsWarehouse.customs.ilike(search_filter),
CustomsWarehouse.fiscalized_warehouse.ilike(search_filter)
)
)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {

View File

@@ -18,12 +18,24 @@ async def list_identifiers(
page_size: int = Query(50, ge=1, le=1000, description="Tamaño de página"),
level: str = Query(None, description="Filtrar por nivel (G o P)"),
q: str = Query(None, description="Búsqueda general"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(IdentifierCatalog)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
IdentifierCatalog.key.ilike(search_filter),
IdentifierCatalog.description.ilike(search_filter),
IdentifierCatalog.level.ilike(search_filter),
IdentifierCatalog.complement.ilike(search_filter)
)
)
if level:
query = query.filter(IdentifierCatalog.nivel == level)

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user, has_role
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import IncotermDTO
from .models import Incoterm
@@ -17,12 +18,23 @@ async def list_incoterms(
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
code: str = Query(None, description="Filtrar por clave"),
description: str = Query(None, description="Filtrar por descripción"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(Incoterm)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
Incoterm.code.ilike(search_filter),
Incoterm.description_es.ilike(search_filter),
Incoterm.description_en.ilike(search_filter)
)
)
if code:
query = query.filter(Incoterm.code.ilike(f"%{code}%"))
if description:

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user, has_role
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import InvoiceTypeDTO
from .models import InvoiceType
@@ -17,9 +18,22 @@ def list_invoice_types(
page_size: int = Query(50, ge=1, le=100),
type: Optional[str] = Query(None, description="Filter by type"),
operation: Optional[str] = Query(None, description="Filter by operation type (imp, exp, both)"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
):
query = db.query(InvoiceType)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
InvoiceType.key.ilike(search_filter),
InvoiceType.description.ilike(search_filter),
InvoiceType.note.ilike(search_filter),
InvoiceType.type.ilike(search_filter),
InvoiceType.operation.ilike(search_filter)
)
)
# Filter by operation if provided
if operation:

View File

@@ -17,12 +17,22 @@ async def list_license_exceptions(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=1000, description="Tamaño de página"),
q: str = Query(None, description="Búsqueda general"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(LicenseException)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
LicenseException.key.ilike(search_filter),
LicenseException.description.ilike(search_filter)
)
)
if q:
search_term = f"%{q}%"
query = query.filter(

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user, has_role
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import MaterialTypeDTO
from .models import MaterialType
@@ -16,12 +17,23 @@ async def list_material_types(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=1000, description="Tamaño de página"),
type: str = Query(None, description="Filtrar por tipo (ACTIVO FIJO, MATERIALES, PRODUCTOS)"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(MaterialType)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
MaterialType.key.ilike(search_filter),
MaterialType.type.ilike(search_filter),
MaterialType.description.ilike(search_filter)
)
)
# Aplicar filtro por tipo si se proporciona
if type:
query = query.filter(MaterialType.type == type)

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user, has_role
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import PaymentMethodDTO
from .models import PaymentMethod
@@ -15,11 +16,21 @@ router = APIRouter(prefix="/payment-methods")
def list_payment_methods(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(PaymentMethod)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
PaymentMethod.key.ilike(search_filter),
PaymentMethod.description.ilike(search_filter)
)
)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user, has_role
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import PedimentoCodeDTO
from .models import PedimentoCode
@@ -15,11 +16,21 @@ router = APIRouter(prefix="/pedimento-codes")
def list_pedimento_codes(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=1000, description="Tamaño de página"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(PedimentoCode)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
PedimentoCode.code.ilike(search_filter),
PedimentoCode.description.ilike(search_filter)
)
)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user, has_role
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import RegimenPedimentoDTO
from .models import RegimenPedimento
@@ -15,11 +16,21 @@ router = APIRouter(prefix="/pedimento-regimens")
def list_pedimento_regimens(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(RegimenPedimento)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
RegimenPedimento.code.ilike(search_filter),
RegimenPedimento.description.ilike(search_filter)
)
)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import PedimentoTransportCatalogDTO
from .models import PedimentoTransportCatalog
@@ -15,10 +16,22 @@ router = APIRouter(prefix="/pedimento-transport-catalog")
async def list_pedimento_transport_catalog(
page: int = Query(1, ge=1, description="Numero de pagina"),
page_size: int = Query(100, ge=1, le=200, description="Tamano de pagina"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
):
skip = (page - 1) * page_size
query = db.query(PedimentoTransportCatalog).order_by(PedimentoTransportCatalog.code.asc())
query = db.query(PedimentoTransportCatalog)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
PedimentoTransportCatalog.code.ilike(search_filter),
PedimentoTransportCatalog.transport_en.ilike(search_filter),
PedimentoTransportCatalog.transport_es.ilike(search_filter),
PedimentoTransportCatalog.payment_date_code.ilike(search_filter)
)
).order_by(PedimentoTransportCatalog.code.asc())
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user, has_role
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import StateDTO
from .models import State
@@ -15,11 +16,21 @@ router = APIRouter(prefix="/states")
async def list_states(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(State)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
State.m3_key.ilike(search_filter),
State.description.ilike(search_filter)
)
)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {

View File

@@ -13,10 +13,11 @@ router = APIRouter()
def list_trailer_types(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
):
skip = (page - 1) * page_size
items, total = services.TrailerTypeService.list_trailer_types(db, skip, page_size)
items, total = services.TrailerTypeService.list_trailer_types(db, skip, page_size, search)
return {
"items": [dto.TrailerTypeListItemDTO.model_validate(obj) for obj in items],
"total": total,

View File

@@ -1,6 +1,7 @@
from typing import List, Tuple
from sqlalchemy.orm import Session
from sqlalchemy import or_
from . import dto, models
@@ -8,9 +9,19 @@ from . import dto, models
class TrailerTypeService:
@staticmethod
def list_trailer_types(
db: Session, skip: int = 0, limit: int = 50
db: Session, skip: int = 0, limit: int = 50, search: str = None
) -> Tuple[List[models.TrailerType], int]:
q = db.query(models.TrailerType).order_by(models.TrailerType.trailer_type_key)
if search:
search_filter = f"%{search}%"
q = q.filter(
or_(
models.TrailerType.trailer_type_key.ilike(search_filter),
models.TrailerType.description.ilike(search_filter)
)
)
total = q.count()
items = q.offset(skip).limit(limit).all()
return items, total

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import TransportModeDTO
from .models import TransportMode
@@ -15,10 +16,20 @@ router = APIRouter(prefix="/transport-modes")
async def list_transport_modes(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
):
skip = (page - 1) * page_size
query = db.query(TransportMode)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
TransportMode.key.ilike(search_filter),
TransportMode.name.ilike(search_filter)
)
)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import TransportTypeDTO
from .models import TransportType
@@ -15,10 +16,20 @@ router = APIRouter(prefix="/transport-types")
def list_transport_types(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
):
skip = (page - 1) * page_size
query = db.query(TransportType)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
TransportType.transport_code.ilike(search_filter),
TransportType.description.ilike(search_filter)
)
)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {

View File

@@ -4,6 +4,7 @@ from core.database import get_core_db
from core.security import get_current_user, has_role
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import or_
from .dto import ValuationMethodDTO
from .models import ValuationMethod
@@ -15,11 +16,21 @@ router = APIRouter(prefix="/valuation-methods")
async def list_valuation_methods(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
search: str = Query(None, description="Término de búsqueda"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(ValuationMethod)
if search:
search_filter = f"%{search}%"
query = query.filter(
or_(
ValuationMethod.key.ilike(search_filter),
ValuationMethod.description.ilike(search_filter)
)
)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {

View File

@@ -40,7 +40,7 @@ export const sectorsApi = {
page_size: pageSize.toString(),
company_id: companyId.toString()
});
if (search) params.append('key', search);
if (search) params.append('search', search);
return api.get<SectorListResponse>(`/v1/a76/sectors/?${params.toString()}`);
},

View File

@@ -1,7 +1,6 @@
import type { ColumnDef } from "@tanstack/table-core";
import { renderComponent, renderSnippet } from "$lib/components/ui/data-table/index.js";
import { renderSnippet } from "$lib/components/ui/data-table/index.js";
import { createRawSnippet } from "svelte";
import DataTableActions from "./data-table-actions.svelte";
export type Sector = {
id: number;
@@ -56,12 +55,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Sector>[] {
});
return renderSnippet(authSnippet, { authorized: row.original.authorized });
}
},
{
id: "actions",
cell: ({ row }) => {
return renderComponent(DataTableActions, { item: row.original, onSuccess });
}
}
];
}

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
import PencilIcon from "@lucide/svelte/icons/pencil";
import { Button } from "$lib/components/ui/button/index.js";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
import type { Sector } from "./columns.js";
@@ -30,27 +31,34 @@
}
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisIcon class="size-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Group>
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Item onclick={handleCopyId}>
Copiar Clave
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<div class="flex items-center gap-2 justify-end">
<Button variant="ghost" size="icon" class="relative size-8 p-0" onclick={handleEdit} title="Editar">
<span class="sr-only">Editar</span>
<PencilIcon class="size-4" />
</Button>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
<span class="sr-only">Abrir menú</span>
<EllipsisIcon class="size-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Group>
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Item onclick={handleCopyId}>
Copiar Clave
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
<!-- Dialogs -->
<DetailsDialog bind:open={showDetailsDialog} {item} />

View File

@@ -2,14 +2,15 @@
import { onMount } from 'svelte';
import { sectorsApi, type Sector } from '$lib/api/dashboard/reference_data/sectors';
import { companyStore } from '$lib/stores/company.svelte';
import DataTable from '$lib/components/dashboard/reference_data/sectors/data-table.svelte';
import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte';
import CreateEditDialog from '$lib/components/dashboard/reference_data/sectors/create-edit-dialog.svelte';
import { createColumns } from '$lib/components/dashboard/reference_data/sectors/columns.js';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { page } from '$app/stores';
import { browser } from '$app/environment';
import { RefreshCw } from 'lucide-svelte';
import { RefreshCw, Plus, Pencil } from 'lucide-svelte';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import type { PageData } from './$types';
@@ -31,7 +32,7 @@
}
});
// Estado para infinite scroll
// Estado para infinite scroll y seleccion
let allItems = $state<Sector[]>(data.items || []);
let currentPage = $state(data.page || 1);
let pageSize = $state(50);
@@ -39,6 +40,13 @@
let loading = $state(false);
let hasMore = $derived(allItems.length < totalItems);
let error = $state<string | null>(data.error || null);
let createDialogOpen = $state(false);
let editingItem = $state<Sector | null>(null);
let selectedIds = $state<(string | number)[]>([]);
const selectedSector = $derived(
selectedIds.length === 1 ? allItems.find((s) => s.id === selectedIds[0]) : null
);
// Filtros
let searchQuery = $state($page.url.searchParams.get('search') || '');
@@ -123,11 +131,48 @@
}
function handleSuccess() {
createDialogOpen = false;
editingItem = null;
reloadData();
}
function handleRowDoubleClick(row: Sector) {
editingItem = row;
createDialogOpen = true;
}
function handleRowClick(row: Sector) {
const id = row.id;
if (selectedIds.includes(id)) {
selectedIds = selectedIds.filter((i) => i !== id);
} else {
selectedIds = [id]; // Selección única
}
}
function handleEditSelected() {
if (selectedSector) {
editingItem = selectedSector;
createDialogOpen = true;
}
}
$effect(() => {
if (!createDialogOpen) {
editingItem = null;
}
});
useShortcuts('Sectores', [
{ key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData }
{ key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData },
{
key: 'Ctrl+S',
description: 'Guardar',
action: () => {
if (!createDialogOpen) return;
document.dispatchEvent(new CustomEvent('save-form'));
}
}
]);
const columns = createColumns(handleSuccess);
@@ -144,6 +189,12 @@
Gestiona los sectores económicos del sistema
</p>
</div>
<div class="flex items-center gap-3">
<Button class="h-9" onclick={() => { editingItem = null; createDialogOpen = true; }}>
<Plus class="mr-2 h-4 w-4" />
Nuevo Sector
</Button>
</div>
</div>
{#if error}
@@ -152,10 +203,55 @@
</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 Sectores</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Buscar" bind:value={searchQuery} oninput={handleSearch} class="h-9 w-44 bg-card lg:w-64" /></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 class="border bg-background flex flex-col min-h-0 flex-1">
<Card.Header>
<div class="flex flex-wrap items-center justify-between gap-3">
<Card.Title>Listado de Sectores</Card.Title>
<div class="flex flex-wrap items-center gap-2">
<Input placeholder="Buscar" bind:value={searchQuery} oninput={handleSearch} class="h-9 w-44 bg-card lg:w-64" />
</div>
</div>
</Card.Header>
<Card.Content class="min-h-0 p-0 flex-1 flex flex-col">
<div class="rounded-md border bg-background overflow-hidden flex min-h-0 flex-1 flex-col">
<InfiniteDataTable
data={allItems}
{columns}
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
<div class="h-20 flex-none"></div>
<!-- Footer fijo con botones de acción -->
<div
id="sectors-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
</div>
</div>
</div>
<CreateEditDialog bind:open={createDialogOpen} item={editingItem} onSuccess={handleSuccess} />
</div>