ADM-008: Puntos de Venta (CRUD fundacional) #19
@@ -0,0 +1,96 @@
|
|||||||
|
import { toast } from 'sonner'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||||
|
import { TipoComprobante } from '../types'
|
||||||
|
import { useReservarNumero, useProximoNumero } from '../hooks/useReservarNumero'
|
||||||
|
|
||||||
|
const TIPOS: Array<{ value: TipoComprobante; label: string }> = [
|
||||||
|
{ value: TipoComprobante.FacturaA, label: 'Factura A' },
|
||||||
|
{ value: TipoComprobante.FacturaB, label: 'Factura B' },
|
||||||
|
{ value: TipoComprobante.FacturaC, label: 'Factura C' },
|
||||||
|
{ value: TipoComprobante.NotaCreditoA, label: 'Nota Crédito A' },
|
||||||
|
{ value: TipoComprobante.NotaCreditoB, label: 'Nota Crédito B' },
|
||||||
|
{ value: TipoComprobante.NotaCreditoC, label: 'Nota Crédito C' },
|
||||||
|
]
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
puntoDeVentaId: number
|
||||||
|
disabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SecuenciasPanel({ puntoDeVentaId, disabled }: Props) {
|
||||||
|
return (
|
||||||
|
<div className="rounded-md border border-border">
|
||||||
|
<div className="border-b border-border px-4 py-3">
|
||||||
|
<h2 className="text-sm font-semibold">Reserva de números de comprobante</h2>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Cada reserva incrementa el correlativo y devuelve el número asignado.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Tipo</TableHead>
|
||||||
|
<TableHead className="text-right">Próximo número</TableHead>
|
||||||
|
<TableHead className="text-right">Acción</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{TIPOS.map((tipo) => (
|
||||||
|
<SecuenciaRow
|
||||||
|
key={tipo.value}
|
||||||
|
puntoDeVentaId={puntoDeVentaId}
|
||||||
|
tipoValue={tipo.value}
|
||||||
|
tipoLabel={tipo.label}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RowProps {
|
||||||
|
puntoDeVentaId: number
|
||||||
|
tipoValue: TipoComprobante
|
||||||
|
tipoLabel: string
|
||||||
|
disabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function SecuenciaRow({ puntoDeVentaId, tipoValue, tipoLabel, disabled }: RowProps) {
|
||||||
|
const proximo = useProximoNumero(puntoDeVentaId, tipoValue)
|
||||||
|
const reservar = useReservarNumero(puntoDeVentaId)
|
||||||
|
|
||||||
|
const handleReservar = () => {
|
||||||
|
reservar.mutate(tipoValue, {
|
||||||
|
onSuccess: (data) => {
|
||||||
|
toast.success(`${tipoLabel}: número ${data.numeroReservado} reservado`)
|
||||||
|
},
|
||||||
|
onError: (err: unknown) => {
|
||||||
|
const apiError = err as { response?: { data?: { error?: string } } }
|
||||||
|
const code = apiError.response?.data?.error ?? 'error'
|
||||||
|
toast.error(`No se pudo reservar: ${code}`)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{tipoLabel}</TableCell>
|
||||||
|
<TableCell className="text-right font-mono">
|
||||||
|
{proximo.isLoading ? '…' : proximo.data?.proximoNumero ?? '—'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
disabled={disabled || reservar.isPending}
|
||||||
|
onClick={handleReservar}
|
||||||
|
>
|
||||||
|
{reservar.isPending ? 'Reservando…' : 'Reservar'}
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import { useMedio } from '../../medios/hooks/useMedio'
|
|||||||
import { DeactivatePuntoDeVentaModal } from '../components/DeactivatePuntoDeVentaModal'
|
import { DeactivatePuntoDeVentaModal } from '../components/DeactivatePuntoDeVentaModal'
|
||||||
import { MedioInactivoBanner } from '../components/MedioInactivoBanner'
|
import { MedioInactivoBanner } from '../components/MedioInactivoBanner'
|
||||||
import { PdvInactivoBanner } from '../components/PdvInactivoBanner'
|
import { PdvInactivoBanner } from '../components/PdvInactivoBanner'
|
||||||
|
import { SecuenciasPanel } from '../components/SecuenciasPanel'
|
||||||
|
|
||||||
function formatDate(iso: string | null): string {
|
function formatDate(iso: string | null): string {
|
||||||
if (!iso) return '—'
|
if (!iso) return '—'
|
||||||
@@ -103,6 +104,13 @@ export function PuntoDeVentaDetailPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CanPerform>
|
</CanPerform>
|
||||||
|
|
||||||
|
<CanPerform permission="administracion:puntos_de_venta:gestionar">
|
||||||
|
<SecuenciasPanel
|
||||||
|
puntoDeVentaId={puntoDeVentaId}
|
||||||
|
disabled={pdvInactivo || medioInactivo}
|
||||||
|
/>
|
||||||
|
</CanPerform>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user