feat: seguimiento auxiliares UI con tabs Asignadas/Sin asignar
- Componente seguimiento-auxiliares.tsx con tabs Asignadas/Sin asignar - Tabs internos Obligaciones/Tareas en cada vista - API client y hooks para asignaciones - Fix: invalidar query sin-asignar al asignar/desasignar
This commit is contained in:
@@ -5,12 +5,13 @@ import {
|
||||
Button, Card, CardContent, CardHeader, CardTitle, Input, Label,
|
||||
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
|
||||
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
||||
Tabs, TabsList, TabsTrigger, TabsContent,
|
||||
cn,
|
||||
} from '@horux/shared-ui';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
FolderOpen, Plus, Trash2, ChevronDown, ChevronUp, X,
|
||||
Users, Building2, FolderPlus, UserCog,
|
||||
Users, Building2, FolderPlus, UserCog, ClipboardList,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
useCarteras, useCreateCartera, useDeleteCartera,
|
||||
@@ -25,14 +26,16 @@ import { useUsuarios } from '@/lib/hooks/use-usuarios';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import { DashboardShell } from '@/components/layouts/dashboard-shell';
|
||||
import type { Cartera } from '@/lib/api/carteras';
|
||||
import SeguimientoAuxiliares from './seguimiento-auxiliares';
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* SubcarteraCard */
|
||||
/* ------------------------------------------------------------------ */
|
||||
function SubcarteraCard({ sub, usuarios, contribuyentes, onDelete }: {
|
||||
function SubcarteraCard({ sub, usuarios, contribuyentes, parentEntidadIds, onDelete }: {
|
||||
sub: Cartera;
|
||||
usuarios: any[];
|
||||
contribuyentes: any[];
|
||||
parentEntidadIds: string[];
|
||||
onDelete: () => void;
|
||||
}) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
@@ -47,7 +50,7 @@ function SubcarteraCard({ sub, usuarios, contribuyentes, onDelete }: {
|
||||
);
|
||||
|
||||
const available = (contribuyentes ?? []).filter(
|
||||
(c: any) => !(entidadIds ?? []).includes(c.id)
|
||||
(c: any) => (parentEntidadIds ?? []).includes(c.id) && !(entidadIds ?? []).includes(c.id)
|
||||
);
|
||||
|
||||
const auxiliarUser = usuarios?.find((u: any) => u.id === sub.auxiliarUserId);
|
||||
@@ -319,6 +322,7 @@ function CarteraDetail({ cartera, canEdit = true, canManageSubcarteras = true }:
|
||||
sub={sub}
|
||||
usuarios={usuarios ?? []}
|
||||
contribuyentes={contribuyentes ?? []}
|
||||
parentEntidadIds={entidadIds ?? []}
|
||||
onDelete={() => handleDeleteSubcartera(sub.id)}
|
||||
/>
|
||||
))}
|
||||
@@ -396,6 +400,10 @@ export default function CarterasPage() {
|
||||
const canEditCartera = userRole === 'owner'; // Edit/delete top-level carteras + add/remove RFCs
|
||||
const canManageSubcarteras = userRole === 'owner' || userRole === 'supervisor'; // Create subcarteras
|
||||
const isAuxiliar = userRole === 'auxiliar';
|
||||
const isSupervisor = userRole === 'supervisor';
|
||||
const isOwner = userRole === 'owner';
|
||||
const puedeVerSeguimiento = isOwner || isSupervisor;
|
||||
const [activeTab, setActiveTab] = useState('carteras');
|
||||
const { data: carteras, isLoading } = useCarteras();
|
||||
const { data: supervisores } = useSupervisores();
|
||||
const { data: usuarios } = useUsuarios();
|
||||
@@ -440,9 +448,43 @@ export default function CarterasPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const CarterasList = () => (
|
||||
<>
|
||||
{isLoading ? (
|
||||
<p className="text-muted-foreground">Cargando...</p>
|
||||
) : !carteras || carteras.length === 0 ? (
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
||||
<FolderOpen className="h-12 w-12 text-muted-foreground mb-4" />
|
||||
<h3 className="text-lg font-semibold">Sin carteras</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1 mb-4">
|
||||
Crea la primera cartera para organizar tus contribuyentes.
|
||||
</p>
|
||||
<Button onClick={() => setShowCreate(true)}>Crear primera cartera</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{carteras.map(cartera => (
|
||||
<CarteraCard
|
||||
key={cartera.id}
|
||||
cartera={cartera}
|
||||
expanded={expandedId === cartera.id}
|
||||
onToggle={() => setExpandedId(expandedId === cartera.id ? null : cartera.id)}
|
||||
onDelete={() => handleDelete(cartera)}
|
||||
usuarios={usuarios ?? []}
|
||||
canEdit={canEditCartera}
|
||||
canManageSubcarteras={canManageSubcarteras}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<DashboardShell title="Carteras">
|
||||
<div className="max-w-3xl mx-auto space-y-6">
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
@@ -450,42 +492,34 @@ export default function CarterasPage() {
|
||||
{isAuxiliar ? 'Carteras asignadas a ti' : 'Organiza contribuyentes en carteras y asigna subcarteras a cada auxiliar'}
|
||||
</p>
|
||||
</div>
|
||||
{canCreate && (
|
||||
{canCreate && activeTab === 'carteras' && (
|
||||
<Button onClick={() => setShowCreate(true)} className="flex items-center gap-2">
|
||||
<Plus className="h-4 w-4" /> Nueva cartera
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* List */}
|
||||
{isLoading ? (
|
||||
<p className="text-muted-foreground">Cargando...</p>
|
||||
) : !carteras || carteras.length === 0 ? (
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
||||
<FolderOpen className="h-12 w-12 text-muted-foreground mb-4" />
|
||||
<h3 className="text-lg font-semibold">Sin carteras</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1 mb-4">
|
||||
Crea la primera cartera para organizar tus contribuyentes.
|
||||
</p>
|
||||
<Button onClick={() => setShowCreate(true)}>Crear primera cartera</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{puedeVerSeguimiento ? (
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} defaultValue="carteras" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="carteras">
|
||||
<FolderOpen className="h-4 w-4 mr-1.5" /> Carteras
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="seguimiento">
|
||||
<ClipboardList className="h-4 w-4 mr-1.5" /> Seguimiento de Auxiliares
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="carteras">
|
||||
<CarterasList />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="seguimiento">
|
||||
<SeguimientoAuxiliares />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{carteras.map(cartera => (
|
||||
<CarteraCard
|
||||
key={cartera.id}
|
||||
cartera={cartera}
|
||||
expanded={expandedId === cartera.id}
|
||||
onToggle={() => setExpandedId(expandedId === cartera.id ? null : cartera.id)}
|
||||
onDelete={() => handleDelete(cartera)}
|
||||
usuarios={usuarios ?? []}
|
||||
canEdit={canEditCartera}
|
||||
canManageSubcarteras={canManageSubcarteras}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<CarterasList />
|
||||
)}
|
||||
|
||||
{/* Create dialog */}
|
||||
|
||||
332
apps/web/app/(dashboard)/carteras/seguimiento-auxiliares.tsx
Normal file
332
apps/web/app/(dashboard)/carteras/seguimiento-auxiliares.tsx
Normal file
@@ -0,0 +1,332 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Button, Card, CardContent, CardHeader, CardTitle,
|
||||
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
|
||||
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
||||
Tabs, TabsList, TabsTrigger, TabsContent,
|
||||
} from '@horux/shared-ui';
|
||||
import {
|
||||
useAsignacionesSupervisor,
|
||||
useSinAsignar,
|
||||
useAsignarObligacion,
|
||||
useDesasignarObligacion,
|
||||
useAsignarTarea,
|
||||
useDesasignarTarea,
|
||||
} from '@/lib/hooks/use-asignaciones';
|
||||
import { useUsuarios } from '@/lib/hooks/use-usuarios';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import { UserCheck, UserX, UserCog, Plus } from 'lucide-react';
|
||||
|
||||
export default function SeguimientoAuxiliares() {
|
||||
const { user } = useAuthStore();
|
||||
const { data: asignaciones, isLoading: loadingAsignadas } = useAsignacionesSupervisor();
|
||||
const { data: sinAsignar, isLoading: loadingSinAsignar } = useSinAsignar();
|
||||
const { data: usuarios } = useUsuarios();
|
||||
const asignarObligacionMut = useAsignarObligacion();
|
||||
const desasignarObligacionMut = useDesasignarObligacion();
|
||||
const asignarTareaMut = useAsignarTarea();
|
||||
const desasignarTareaMut = useDesasignarTarea();
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [modalType, setModalType] = useState<'obligacion' | 'tarea'>('obligacion');
|
||||
const [modalItem, setModalItem] = useState<any>(null);
|
||||
const [selectedAuxiliar, setSelectedAuxiliar] = useState('');
|
||||
|
||||
const auxiliares = (usuarios ?? []).filter((u: any) => u.role === 'auxiliar');
|
||||
|
||||
const openAssignModal = (type: 'obligacion' | 'tarea', item: any) => {
|
||||
setModalType(type);
|
||||
setModalItem(item);
|
||||
setSelectedAuxiliar(item.auxiliarUserId || '');
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
const handleAssign = async () => {
|
||||
if (!selectedAuxiliar || !modalItem) return;
|
||||
try {
|
||||
if (modalType === 'obligacion') {
|
||||
await asignarObligacionMut.mutateAsync({
|
||||
contribuyenteId: modalItem.contribuyenteId,
|
||||
obligacionId: modalItem.obligacionId,
|
||||
auxiliarUserId: selectedAuxiliar,
|
||||
});
|
||||
} else {
|
||||
await asignarTareaMut.mutateAsync({
|
||||
tareaId: modalItem.tareaId,
|
||||
auxiliarUserId: selectedAuxiliar,
|
||||
});
|
||||
}
|
||||
setModalOpen(false);
|
||||
} catch (err: any) {
|
||||
alert(err.response?.data?.message || 'Error al asignar');
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnassign = async (type: 'obligacion' | 'tarea', item: any) => {
|
||||
if (!confirm('¿Eliminar la asignación?')) return;
|
||||
try {
|
||||
if (type === 'obligacion') {
|
||||
await desasignarObligacionMut.mutateAsync({
|
||||
contribuyenteId: item.contribuyenteId,
|
||||
obligacionId: item.obligacionId,
|
||||
});
|
||||
} else {
|
||||
await desasignarTareaMut.mutateAsync({ tareaId: item.tareaId });
|
||||
}
|
||||
} catch (err: any) {
|
||||
alert(err.response?.data?.message || 'Error al desasignar');
|
||||
}
|
||||
};
|
||||
|
||||
if (loadingAsignadas || loadingSinAsignar) {
|
||||
return <p className="text-muted-foreground">Cargando asignaciones...</p>;
|
||||
}
|
||||
|
||||
const obligacionesAsignadas = asignaciones?.obligaciones ?? [];
|
||||
const tareasAsignadas = asignaciones?.tareas ?? [];
|
||||
const obligacionesSinAsignar = sinAsignar?.obligaciones ?? [];
|
||||
const tareasSinAsignar = sinAsignar?.tareas ?? [];
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Tabs defaultValue="asignadas" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="asignadas">Asignadas</TabsTrigger>
|
||||
<TabsTrigger value="sin-asignar">Sin asignar</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="asignadas">
|
||||
<Tabs defaultValue="obligaciones" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="obligaciones">Obligaciones ({obligacionesAsignadas.length})</TabsTrigger>
|
||||
<TabsTrigger value="tareas">Tareas ({tareasAsignadas.length})</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="obligaciones">
|
||||
<AsignacionesTable
|
||||
items={obligacionesAsignadas}
|
||||
tipo="obligacion"
|
||||
modo="asignadas"
|
||||
auxiliares={auxiliares}
|
||||
onAssign={(item) => openAssignModal('obligacion', item)}
|
||||
onUnassign={(item) => handleUnassign('obligacion', item)}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="tareas">
|
||||
<AsignacionesTable
|
||||
items={tareasAsignadas}
|
||||
tipo="tarea"
|
||||
modo="asignadas"
|
||||
auxiliares={auxiliares}
|
||||
onAssign={(item) => openAssignModal('tarea', item)}
|
||||
onUnassign={(item) => handleUnassign('tarea', item)}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="sin-asignar">
|
||||
<Tabs defaultValue="obligaciones" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="obligaciones">Obligaciones ({obligacionesSinAsignar.length})</TabsTrigger>
|
||||
<TabsTrigger value="tareas">Tareas ({tareasSinAsignar.length})</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="obligaciones">
|
||||
<SinAsignarTable
|
||||
items={obligacionesSinAsignar}
|
||||
tipo="obligacion"
|
||||
auxiliares={auxiliares}
|
||||
onAssign={(item) => openAssignModal('obligacion', item)}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="tareas">
|
||||
<SinAsignarTable
|
||||
items={tareasSinAsignar}
|
||||
tipo="tarea"
|
||||
auxiliares={auxiliares}
|
||||
onAssign={(item) => openAssignModal('tarea', item)}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{/* Modal de asignación */}
|
||||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{modalItem?.auxiliarUserId ? 'Reasignar' : 'Asignar'} {modalType === 'obligacion' ? 'obligación' : 'tarea'}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
{modalType === 'obligacion' ? modalItem?.obligacionNombre : modalItem?.tareaNombre}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mb-4">
|
||||
Contribuyente: {modalItem?.contribuyenteRazonSocial} ({modalItem?.contribuyenteRfc})
|
||||
</p>
|
||||
<Select value={selectedAuxiliar} onValueChange={setSelectedAuxiliar}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Selecciona un auxiliar" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{auxiliares.map((a: any) => (
|
||||
<SelectItem key={a.id} value={a.id}>{a.nombre} ({a.email})</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setModalOpen(false)}>Cancelar</Button>
|
||||
<Button onClick={handleAssign} disabled={!selectedAuxiliar}>
|
||||
{modalItem?.auxiliarUserId ? 'Reasignar' : 'Asignar'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AsignacionesTable({
|
||||
items,
|
||||
tipo,
|
||||
modo,
|
||||
auxiliares,
|
||||
onAssign,
|
||||
onUnassign,
|
||||
}: {
|
||||
items: any[];
|
||||
tipo: 'obligacion' | 'tarea';
|
||||
modo: 'asignadas';
|
||||
auxiliares: any[];
|
||||
onAssign: (item: any) => void;
|
||||
onUnassign: (item: any) => void;
|
||||
}) {
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="py-8 text-center text-muted-foreground">
|
||||
No hay {tipo === 'obligacion' ? 'obligaciones' : 'tareas'} asignadas.
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-muted/50">
|
||||
<tr>
|
||||
<th className="text-left px-4 py-3 font-medium">Auxiliar</th>
|
||||
<th className="text-left px-4 py-3 font-medium">Contribuyente</th>
|
||||
<th className="text-left px-4 py-3 font-medium">{tipo === 'obligacion' ? 'Obligación' : 'Tarea'}</th>
|
||||
<th className="text-left px-4 py-3 font-medium">Asignado</th>
|
||||
<th className="text-right px-4 py-3 font-medium">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y">
|
||||
{items.map((item) => (
|
||||
<tr key={item.id} className="hover:bg-muted/30">
|
||||
<td className="px-4 py-3">
|
||||
{item.auxiliarNombre ? (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80 flex items-center gap-1 w-fit">
|
||||
<UserCheck className="h-3 w-3" /> {item.auxiliarNombre}
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-border text-muted-foreground hover:bg-secondary/80 flex items-center gap-1 w-fit">
|
||||
<UserX className="h-3 w-3" /> Sin asignar
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<div className="font-medium">{item.contribuyenteRazonSocial}</div>
|
||||
<div className="text-xs text-muted-foreground">{item.contribuyenteRfc}</div>
|
||||
</td>
|
||||
<td className="px-4 py-3">{tipo === 'obligacion' ? item.obligacionNombre : item.tareaNombre}</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">
|
||||
{item.asignadoAt ? new Date(item.asignadoAt).toLocaleDateString('es-MX') : '-'}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button variant="ghost" size="sm" onClick={() => onAssign(item)}>
|
||||
<UserCog className="h-4 w-4" />
|
||||
</Button>
|
||||
{item.auxiliarUserId && (
|
||||
<Button variant="ghost" size="sm" className="text-red-600" onClick={() => onUnassign(item)}>
|
||||
<UserX className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function SinAsignarTable({
|
||||
items,
|
||||
tipo,
|
||||
auxiliares,
|
||||
onAssign,
|
||||
}: {
|
||||
items: any[];
|
||||
tipo: 'obligacion' | 'tarea';
|
||||
auxiliares: any[];
|
||||
onAssign: (item: any) => void;
|
||||
}) {
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="py-8 text-center text-muted-foreground">
|
||||
No hay {tipo === 'obligacion' ? 'obligaciones' : 'tareas'} sin asignar.
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-muted/50">
|
||||
<tr>
|
||||
<th className="text-left px-4 py-3 font-medium">Contribuyente</th>
|
||||
<th className="text-left px-4 py-3 font-medium">{tipo === 'obligacion' ? 'Obligación' : 'Tarea'}</th>
|
||||
<th className="text-right px-4 py-3 font-medium">Acción</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y">
|
||||
{items.map((item, idx) => (
|
||||
<tr key={`${item.obligacionId || item.tareaId}-${idx}`} className="hover:bg-muted/30">
|
||||
<td className="px-4 py-3">
|
||||
<div className="font-medium">{item.contribuyenteRazonSocial}</div>
|
||||
<div className="text-xs text-muted-foreground">{item.contribuyenteRfc}</div>
|
||||
</td>
|
||||
<td className="px-4 py-3">{tipo === 'obligacion' ? item.obligacionNombre : item.tareaNombre}</td>
|
||||
<td className="px-4 py-3 text-right">
|
||||
<Button variant="ghost" size="sm" onClick={() => onAssign(item)}>
|
||||
<Plus className="h-4 w-4 mr-1" /> Asignar
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
58
apps/web/lib/api/asignaciones.ts
Normal file
58
apps/web/lib/api/asignaciones.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { apiClient } from './client';
|
||||
|
||||
export interface AsignacionObligacion {
|
||||
id: string;
|
||||
obligacionId: string;
|
||||
obligacionNombre: string;
|
||||
contribuyenteId: string;
|
||||
contribuyenteRfc: string;
|
||||
contribuyenteRazonSocial: string;
|
||||
auxiliarUserId: string;
|
||||
auxiliarNombre: string | null;
|
||||
asignadoPor: string;
|
||||
asignadoAt: string;
|
||||
}
|
||||
|
||||
export interface AsignacionTarea {
|
||||
id: string;
|
||||
tareaId: string;
|
||||
tareaNombre: string;
|
||||
contribuyenteId: string;
|
||||
contribuyenteRfc: string;
|
||||
contribuyenteRazonSocial: string;
|
||||
auxiliarUserId: string;
|
||||
auxiliarNombre: string | null;
|
||||
asignadoPor: string;
|
||||
asignadoAt: string;
|
||||
}
|
||||
|
||||
export interface AsignacionesResponse {
|
||||
obligaciones: AsignacionObligacion[];
|
||||
tareas: AsignacionTarea[];
|
||||
}
|
||||
|
||||
export const getAsignacionesPorSupervisor = () =>
|
||||
apiClient.get<AsignacionesResponse>('/carteras/asignaciones').then(r => r.data);
|
||||
|
||||
export const getAsignacionesPorAuxiliar = () =>
|
||||
apiClient.get<AsignacionesResponse>('/carteras/asignaciones/mias').then(r => r.data);
|
||||
|
||||
export interface SinAsignarResponse {
|
||||
obligaciones: Omit<AsignacionObligacion, 'id' | 'auxiliarUserId' | 'auxiliarNombre' | 'asignadoPor' | 'asignadoAt'>[];
|
||||
tareas: Omit<AsignacionTarea, 'id' | 'auxiliarUserId' | 'auxiliarNombre' | 'asignadoPor' | 'asignadoAt'>[];
|
||||
}
|
||||
|
||||
export const getSinAsignar = () =>
|
||||
apiClient.get<SinAsignarResponse>('/carteras/asignaciones/sin-asignar').then(r => r.data);
|
||||
|
||||
export const asignarObligacion = (contribuyenteId: string, obligacionId: string, auxiliarUserId: string) =>
|
||||
apiClient.post(`/contribuyentes/${contribuyenteId}/obligaciones/${obligacionId}/asignar`, { auxiliarUserId }).then(r => r.data);
|
||||
|
||||
export const desasignarObligacion = (contribuyenteId: string, obligacionId: string) =>
|
||||
apiClient.delete(`/contribuyentes/${contribuyenteId}/obligaciones/${obligacionId}/asignar`).then(r => r.data);
|
||||
|
||||
export const asignarTarea = (tareaId: string, auxiliarUserId: string) =>
|
||||
apiClient.post(`/tareas/${tareaId}/asignar`, { auxiliarUserId }).then(r => r.data);
|
||||
|
||||
export const desasignarTarea = (tareaId: string) =>
|
||||
apiClient.delete(`/tareas/${tareaId}/asignar`).then(r => r.data);
|
||||
89
apps/web/lib/hooks/use-asignaciones.ts
Normal file
89
apps/web/lib/hooks/use-asignaciones.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
getAsignacionesPorSupervisor,
|
||||
getAsignacionesPorAuxiliar,
|
||||
getSinAsignar,
|
||||
asignarObligacion,
|
||||
desasignarObligacion,
|
||||
asignarTarea,
|
||||
desasignarTarea,
|
||||
} from '../api/asignaciones';
|
||||
|
||||
export function useAsignacionesSupervisor() {
|
||||
return useQuery({
|
||||
queryKey: ['asignaciones-supervisor'],
|
||||
queryFn: getAsignacionesPorSupervisor,
|
||||
});
|
||||
}
|
||||
|
||||
export function useAsignacionesAuxiliar() {
|
||||
return useQuery({
|
||||
queryKey: ['asignaciones-auxiliar'],
|
||||
queryFn: getAsignacionesPorAuxiliar,
|
||||
});
|
||||
}
|
||||
|
||||
export function useSinAsignar() {
|
||||
return useQuery({
|
||||
queryKey: ['asignaciones-sin-asignar'],
|
||||
queryFn: getSinAsignar,
|
||||
});
|
||||
}
|
||||
|
||||
export function useAsignarObligacion() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ contribuyenteId, obligacionId, auxiliarUserId }: {
|
||||
contribuyenteId: string;
|
||||
obligacionId: string;
|
||||
auxiliarUserId: string;
|
||||
}) => asignarObligacion(contribuyenteId, obligacionId, auxiliarUserId),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['asignaciones-supervisor'] });
|
||||
qc.invalidateQueries({ queryKey: ['asignaciones-sin-asignar'] });
|
||||
qc.invalidateQueries({ queryKey: ['obligaciones'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDesasignarObligacion() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ contribuyenteId, obligacionId }: {
|
||||
contribuyenteId: string;
|
||||
obligacionId: string;
|
||||
}) => desasignarObligacion(contribuyenteId, obligacionId),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['asignaciones-supervisor'] });
|
||||
qc.invalidateQueries({ queryKey: ['asignaciones-sin-asignar'] });
|
||||
qc.invalidateQueries({ queryKey: ['obligaciones'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useAsignarTarea() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ tareaId, auxiliarUserId }: {
|
||||
tareaId: string;
|
||||
auxiliarUserId: string;
|
||||
}) => asignarTarea(tareaId, auxiliarUserId),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['asignaciones-supervisor'] });
|
||||
qc.invalidateQueries({ queryKey: ['asignaciones-sin-asignar'] });
|
||||
qc.invalidateQueries({ queryKey: ['tareas'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDesasignarTarea() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ tareaId }: { tareaId: string }) => desasignarTarea(tareaId),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['asignaciones-supervisor'] });
|
||||
qc.invalidateQueries({ queryKey: ['asignaciones-sin-asignar'] });
|
||||
qc.invalidateQueries({ queryKey: ['tareas'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user