- 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
574 lines
24 KiB
TypeScript
574 lines
24 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
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, ClipboardList,
|
|
} from 'lucide-react';
|
|
import {
|
|
useCarteras, useCreateCartera, useDeleteCartera,
|
|
useCarteraEntidades, useSubcarteras, useCreateSubcartera,
|
|
useSupervisores,
|
|
} from '@/lib/hooks/use-carteras';
|
|
import {
|
|
addEntidadToCartera, removeEntidadFromCartera,
|
|
} from '@/lib/api/carteras';
|
|
import { useContribuyentes } from '@/lib/hooks/use-contribuyentes';
|
|
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, parentEntidadIds, onDelete }: {
|
|
sub: Cartera;
|
|
usuarios: any[];
|
|
contribuyentes: any[];
|
|
parentEntidadIds: string[];
|
|
onDelete: () => void;
|
|
}) {
|
|
const [expanded, setExpanded] = useState(false);
|
|
const qc = useQueryClient();
|
|
const { data: entidadIds, isLoading } = useCarteraEntidades(expanded ? sub.id : null);
|
|
const [addingEntidad, setAddingEntidad] = useState(false);
|
|
const [selectedEntidadId, setSelectedEntidadId] = useState('');
|
|
const [busy, setBusy] = useState(false);
|
|
|
|
const entidadMap = Object.fromEntries(
|
|
(contribuyentes ?? []).map((c: any) => [c.id, { rfc: c.rfc, nombre: c.nombre }])
|
|
);
|
|
|
|
const available = (contribuyentes ?? []).filter(
|
|
(c: any) => (parentEntidadIds ?? []).includes(c.id) && !(entidadIds ?? []).includes(c.id)
|
|
);
|
|
|
|
const auxiliarUser = usuarios?.find((u: any) => u.id === sub.auxiliarUserId);
|
|
|
|
const invalidate = () => {
|
|
qc.invalidateQueries({ queryKey: ['cartera-entidades', sub.id] });
|
|
qc.invalidateQueries({ queryKey: ['subcarteras'] });
|
|
qc.invalidateQueries({ queryKey: ['carteras'] });
|
|
};
|
|
|
|
const handleAddEntidad = async () => {
|
|
if (!selectedEntidadId) return;
|
|
setBusy(true);
|
|
try {
|
|
await addEntidadToCartera(sub.id, selectedEntidadId);
|
|
setSelectedEntidadId('');
|
|
setAddingEntidad(false);
|
|
invalidate();
|
|
} finally { setBusy(false); }
|
|
};
|
|
|
|
const handleRemoveEntidad = async (entidadId: string) => {
|
|
setBusy(true);
|
|
try {
|
|
await removeEntidadFromCartera(sub.id, entidadId);
|
|
invalidate();
|
|
} finally { setBusy(false); }
|
|
};
|
|
|
|
return (
|
|
<div className="border rounded-lg p-3 bg-muted/20">
|
|
<div className="flex items-center justify-between">
|
|
<button onClick={() => setExpanded(!expanded)} className="flex items-center gap-2 flex-1 text-left">
|
|
<UserCog className="h-4 w-4 text-muted-foreground" />
|
|
<div>
|
|
<span className="font-medium text-sm">{sub.nombre}</span>
|
|
{auxiliarUser && (
|
|
<span className="text-xs text-muted-foreground ml-2">({auxiliarUser.nombre})</span>
|
|
)}
|
|
</div>
|
|
<span className="text-xs text-muted-foreground ml-auto mr-2">{sub.entidadesCount} RFCs</span>
|
|
{expanded ? <ChevronUp className="h-3 w-3" /> : <ChevronDown className="h-3 w-3" />}
|
|
</button>
|
|
<button onClick={onDelete} className="text-muted-foreground hover:text-destructive p-1">
|
|
<Trash2 className="h-3.5 w-3.5" />
|
|
</button>
|
|
</div>
|
|
|
|
{expanded && (
|
|
<div className="mt-3 space-y-2">
|
|
{!addingEntidad && (
|
|
<Button variant="ghost" size="sm" onClick={() => setAddingEntidad(true)} className="h-7 gap-1 text-xs">
|
|
<Plus className="h-3 w-3" /> Asignar RFC
|
|
</Button>
|
|
)}
|
|
{addingEntidad && (
|
|
<div className="flex items-center gap-2">
|
|
<select className="flex-1 rounded-md border bg-background px-2 py-1 text-sm" value={selectedEntidadId} onChange={e => setSelectedEntidadId(e.target.value)}>
|
|
<option value="">-- Seleccionar RFC --</option>
|
|
{available.map((c: any) => <option key={c.id} value={c.id}>{c.rfc} — {c.nombre}</option>)}
|
|
</select>
|
|
<Button size="sm" onClick={handleAddEntidad} disabled={!selectedEntidadId || busy}>Agregar</Button>
|
|
<Button size="sm" variant="ghost" onClick={() => { setAddingEntidad(false); setSelectedEntidadId(''); }}>Cancelar</Button>
|
|
</div>
|
|
)}
|
|
{isLoading ? (
|
|
<p className="text-xs text-muted-foreground">Cargando...</p>
|
|
) : !entidadIds || entidadIds.length === 0 ? (
|
|
<p className="text-xs text-muted-foreground">Sin RFCs asignados a esta subcartera.</p>
|
|
) : (
|
|
<ul className="space-y-1">
|
|
{entidadIds.map(id => {
|
|
const info = entidadMap[id];
|
|
return (
|
|
<li key={id} className="flex items-center justify-between bg-background rounded px-2 py-1 text-sm">
|
|
<span>{info ? <><span className="font-mono text-xs">{info.rfc}</span> <span className="text-muted-foreground">{info.nombre}</span></> : id}</span>
|
|
<button onClick={() => handleRemoveEntidad(id)} disabled={busy} className="text-muted-foreground hover:text-destructive"><X className="h-3 w-3" /></button>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* CarteraDetail */
|
|
/* ------------------------------------------------------------------ */
|
|
function CarteraDetail({ cartera, canEdit = true, canManageSubcarteras = true }: { cartera: Cartera; canEdit?: boolean; canManageSubcarteras?: boolean }) {
|
|
const qc = useQueryClient();
|
|
const { data: contribuyentes } = useContribuyentes();
|
|
const { data: usuarios } = useUsuarios();
|
|
const { data: entidadIds, isLoading: loadingEntidades } = useCarteraEntidades(cartera.id);
|
|
const { data: subcarteras, isLoading: loadingSubs } = useSubcarteras(cartera.id);
|
|
const createSub = useCreateSubcartera();
|
|
|
|
const [addingEntidad, setAddingEntidad] = useState(false);
|
|
const [selectedEntidadId, setSelectedEntidadId] = useState('');
|
|
const [showCreateSub, setShowCreateSub] = useState(false);
|
|
const [subForm, setSubForm] = useState({ nombre: '', auxiliarUserId: '' });
|
|
const [busy, setBusy] = useState(false);
|
|
|
|
const entidadMap = Object.fromEntries(
|
|
(contribuyentes ?? []).map((c) => [c.id, { rfc: c.rfc, nombre: c.nombre }])
|
|
);
|
|
|
|
const available = (contribuyentes ?? []).filter(
|
|
(c) => !(entidadIds ?? []).includes(c.id)
|
|
);
|
|
|
|
// Auxiliares available for subcarteras (those assigned to this supervisor)
|
|
const auxiliares = (usuarios ?? []).filter((u: any) => u.role === 'auxiliar');
|
|
|
|
const supervisorUser = usuarios?.find((u: any) => u.id === cartera.supervisorUserId);
|
|
|
|
const invalidate = () => {
|
|
qc.invalidateQueries({ queryKey: ['cartera-entidades', cartera.id] });
|
|
qc.invalidateQueries({ queryKey: ['subcarteras', cartera.id] });
|
|
qc.invalidateQueries({ queryKey: ['carteras'] });
|
|
};
|
|
|
|
const handleAddEntidad = async () => {
|
|
if (!selectedEntidadId) return;
|
|
setBusy(true);
|
|
try {
|
|
await addEntidadToCartera(cartera.id, selectedEntidadId);
|
|
setSelectedEntidadId('');
|
|
setAddingEntidad(false);
|
|
invalidate();
|
|
} finally { setBusy(false); }
|
|
};
|
|
|
|
const handleRemoveEntidad = async (entidadId: string) => {
|
|
setBusy(true);
|
|
try {
|
|
await removeEntidadFromCartera(cartera.id, entidadId);
|
|
invalidate();
|
|
} finally { setBusy(false); }
|
|
};
|
|
|
|
const handleCreateSubcartera = async () => {
|
|
if (!subForm.nombre.trim() || !subForm.auxiliarUserId) return;
|
|
try {
|
|
await createSub.mutateAsync({
|
|
carteraId: cartera.id,
|
|
nombre: subForm.nombre.trim(),
|
|
auxiliarUserId: subForm.auxiliarUserId,
|
|
});
|
|
setSubForm({ nombre: '', auxiliarUserId: '' });
|
|
setShowCreateSub(false);
|
|
} catch (err: any) {
|
|
alert(err.response?.data?.message || 'Error al crear subcartera');
|
|
}
|
|
};
|
|
|
|
const handleDeleteSubcartera = async (subId: string) => {
|
|
if (!confirm('¿Eliminar esta subcartera?')) return;
|
|
try {
|
|
const { deleteCartera } = await import('@/lib/api/carteras');
|
|
await deleteCartera(subId);
|
|
invalidate();
|
|
} catch (err: any) {
|
|
alert(err.response?.data?.message || 'Error al eliminar');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="border-t mt-4 pt-4 space-y-6">
|
|
{/* Supervisor info */}
|
|
{supervisorUser && (
|
|
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
<UserCog className="h-3.5 w-3.5" />
|
|
Supervisor: <span className="font-medium text-foreground">{supervisorUser.nombre}</span> ({supervisorUser.email})
|
|
</div>
|
|
)}
|
|
|
|
{/* ---- Contribuyentes ---- */}
|
|
<div>
|
|
<div className="flex items-center justify-between mb-2">
|
|
<h3 className="text-sm font-semibold flex items-center gap-1.5">
|
|
<Building2 className="h-4 w-4 text-muted-foreground" />
|
|
Contribuyentes ({entidadIds?.length || 0})
|
|
</h3>
|
|
{canEdit && !addingEntidad && (
|
|
<Button variant="ghost" size="sm" onClick={() => setAddingEntidad(true)} className="h-7 gap-1 text-xs">
|
|
<Plus className="h-3 w-3" /> Agregar
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
{canEdit && addingEntidad && (
|
|
<div className="flex items-center gap-2 mb-3">
|
|
<select className="flex-1 rounded-md border bg-background px-3 py-1.5 text-sm" value={selectedEntidadId} onChange={e => setSelectedEntidadId(e.target.value)}>
|
|
<option value="">-- Seleccionar RFC --</option>
|
|
{available.map(c => <option key={c.id} value={c.id}>{c.rfc} — {c.nombre}</option>)}
|
|
</select>
|
|
<Button size="sm" onClick={handleAddEntidad} disabled={!selectedEntidadId || busy}>Agregar</Button>
|
|
<Button size="sm" variant="ghost" onClick={() => { setAddingEntidad(false); setSelectedEntidadId(''); }}>Cancelar</Button>
|
|
</div>
|
|
)}
|
|
|
|
{loadingEntidades ? (
|
|
<p className="text-xs text-muted-foreground">Cargando...</p>
|
|
) : !entidadIds || entidadIds.length === 0 ? (
|
|
<p className="text-xs text-muted-foreground">Sin contribuyentes asignados.</p>
|
|
) : (
|
|
<ul className="space-y-1">
|
|
{entidadIds.map(id => {
|
|
const info = entidadMap[id];
|
|
return (
|
|
<li key={id} className="flex items-center justify-between rounded-md bg-muted/40 px-3 py-1.5 text-sm">
|
|
<span>{info ? <><span className="font-mono text-xs">{info.rfc}</span> <span className="text-muted-foreground ml-2">{info.nombre}</span></> : <span className="font-mono text-xs">{id}</span>}</span>
|
|
{canEdit && <button onClick={() => handleRemoveEntidad(id)} disabled={busy} className="text-muted-foreground hover:text-destructive ml-2"><X className="h-3.5 w-3.5" /></button>}
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
)}
|
|
</div>
|
|
|
|
{/* ---- Subcarteras ---- */}
|
|
<div>
|
|
<div className="flex items-center justify-between mb-2">
|
|
<h3 className="text-sm font-semibold flex items-center gap-1.5">
|
|
<Users className="h-4 w-4 text-muted-foreground" />
|
|
Subcarteras ({subcarteras?.length || 0})
|
|
</h3>
|
|
{canManageSubcarteras && !showCreateSub && (
|
|
<Button variant="ghost" size="sm" onClick={() => setShowCreateSub(true)} className="h-7 gap-1 text-xs">
|
|
<FolderPlus className="h-3 w-3" /> Nueva subcartera
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
{canManageSubcarteras && showCreateSub && (
|
|
<div className="border rounded-lg p-3 mb-3 space-y-3 bg-muted/20">
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<Label className="text-xs">Nombre</Label>
|
|
<Input value={subForm.nombre} onChange={e => setSubForm(p => ({ ...p, nombre: e.target.value }))} placeholder="Ej. Cartera de María" className="h-8 text-sm mt-1" />
|
|
</div>
|
|
<div>
|
|
<Label className="text-xs">Auxiliar</Label>
|
|
<select className="w-full h-8 rounded-md border bg-background px-2 text-sm mt-1" value={subForm.auxiliarUserId} onChange={e => setSubForm(p => ({ ...p, auxiliarUserId: e.target.value }))}>
|
|
<option value="">-- Seleccionar auxiliar --</option>
|
|
{auxiliares.map((u: any) => <option key={u.id} value={u.id}>{u.nombre} ({u.email})</option>)}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button size="sm" onClick={handleCreateSubcartera} disabled={!subForm.nombre.trim() || !subForm.auxiliarUserId || createSub.isPending}>Crear</Button>
|
|
<Button size="sm" variant="ghost" onClick={() => { setShowCreateSub(false); setSubForm({ nombre: '', auxiliarUserId: '' }); }}>Cancelar</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{loadingSubs ? (
|
|
<p className="text-xs text-muted-foreground">Cargando...</p>
|
|
) : !subcarteras || subcarteras.length === 0 ? (
|
|
<p className="text-xs text-muted-foreground">Sin subcarteras. Crea una para asignar RFCs a un auxiliar.</p>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{subcarteras.map(sub => (
|
|
<SubcarteraCard
|
|
key={sub.id}
|
|
sub={sub}
|
|
usuarios={usuarios ?? []}
|
|
contribuyentes={contribuyentes ?? []}
|
|
parentEntidadIds={entidadIds ?? []}
|
|
onDelete={() => handleDeleteSubcartera(sub.id)}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* CarteraCard */
|
|
/* ------------------------------------------------------------------ */
|
|
function CarteraCard({ cartera, expanded, onToggle, onDelete, usuarios, canEdit, canManageSubcarteras }: {
|
|
cartera: Cartera;
|
|
expanded: boolean;
|
|
onToggle: () => void;
|
|
onDelete: () => void;
|
|
usuarios: any[];
|
|
canEdit: boolean;
|
|
canManageSubcarteras: boolean;
|
|
}) {
|
|
const supervisorUser = usuarios?.find((u: any) => u.id === cartera.supervisorUserId);
|
|
return (
|
|
<Card className={cn('transition-shadow', expanded && 'ring-1 ring-primary/30 shadow-md')}>
|
|
<CardHeader className="pb-2">
|
|
<div className="flex items-start justify-between gap-4">
|
|
<button onClick={onToggle} className="flex-1 text-left flex items-center gap-2 group">
|
|
<FolderOpen className={cn('h-5 w-5 flex-shrink-0 transition-colors', expanded ? 'text-primary' : 'text-muted-foreground group-hover:text-foreground')} />
|
|
<div>
|
|
<CardTitle className="text-base">{cartera.nombre}</CardTitle>
|
|
{cartera.descripcion && <p className="text-xs text-muted-foreground mt-0.5">{cartera.descripcion}</p>}
|
|
</div>
|
|
{expanded ? <ChevronUp className="h-4 w-4 text-muted-foreground ml-auto" /> : <ChevronDown className="h-4 w-4 text-muted-foreground ml-auto" />}
|
|
</button>
|
|
{canEdit && (
|
|
<Button variant="ghost" size="sm" onClick={onDelete} className="text-destructive hover:text-destructive flex-shrink-0 h-8 w-8 p-0">
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
)}
|
|
</div>
|
|
<div className="flex gap-4 mt-1 pl-7">
|
|
{supervisorUser && (
|
|
<span className="text-xs text-muted-foreground">
|
|
<UserCog className="inline h-3 w-3 mr-1" />
|
|
{supervisorUser.nombre}
|
|
</span>
|
|
)}
|
|
<span className="text-xs text-muted-foreground">
|
|
<Building2 className="inline h-3 w-3 mr-1" />
|
|
{cartera.entidadesCount} RFCs
|
|
</span>
|
|
<span className="text-xs text-muted-foreground">
|
|
<Users className="inline h-3 w-3 mr-1" />
|
|
{cartera.subcarterasCount} subcarteras
|
|
</span>
|
|
</div>
|
|
</CardHeader>
|
|
{expanded && (
|
|
<CardContent className="pt-0">
|
|
<CarteraDetail cartera={cartera} canEdit={canEdit} canManageSubcarteras={canManageSubcarteras} />
|
|
</CardContent>
|
|
)}
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Page */
|
|
/* ------------------------------------------------------------------ */
|
|
export default function CarterasPage() {
|
|
const { user } = useAuthStore();
|
|
const userRole = user?.role || 'visor';
|
|
const canCreate = userRole === 'owner'; // Create top-level carteras
|
|
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();
|
|
const createMut = useCreateCartera();
|
|
const deleteMut = useDeleteCartera();
|
|
|
|
const [expandedId, setExpandedId] = useState<string | null>(null);
|
|
const [showCreate, setShowCreate] = useState(false);
|
|
const [form, setForm] = useState({ nombre: '', descripcion: '', supervisorUserId: '' });
|
|
|
|
const hasSupervisores = supervisores && supervisores.length > 0;
|
|
|
|
const resetForm = () => {
|
|
setForm({ nombre: '', descripcion: '', supervisorUserId: '' });
|
|
setShowCreate(false);
|
|
};
|
|
|
|
const handleCreate = async () => {
|
|
if (!form.nombre.trim()) return;
|
|
try {
|
|
const supervisorUserId = form.supervisorUserId && form.supervisorUserId !== '__self__'
|
|
? form.supervisorUserId : undefined;
|
|
const cartera = await createMut.mutateAsync({
|
|
nombre: form.nombre.trim(),
|
|
descripcion: form.descripcion.trim() || undefined,
|
|
supervisorUserId,
|
|
});
|
|
resetForm();
|
|
setExpandedId(cartera.id);
|
|
} catch (err: any) {
|
|
alert(err.response?.data?.message || 'Error al crear cartera');
|
|
}
|
|
};
|
|
|
|
const handleDelete = async (cartera: Cartera) => {
|
|
if (!confirm(`¿Eliminar la cartera "${cartera.nombre}"? Se eliminarán también sus subcarteras.`)) return;
|
|
try {
|
|
await deleteMut.mutateAsync(cartera.id);
|
|
if (expandedId === cartera.id) setExpandedId(null);
|
|
} catch (err: any) {
|
|
alert(err.response?.data?.message || 'Error al eliminar cartera');
|
|
}
|
|
};
|
|
|
|
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-4xl mx-auto space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-muted-foreground">
|
|
{isAuxiliar ? 'Carteras asignadas a ti' : 'Organiza contribuyentes en carteras y asigna subcarteras a cada auxiliar'}
|
|
</p>
|
|
</div>
|
|
{canCreate && activeTab === 'carteras' && (
|
|
<Button onClick={() => setShowCreate(true)} className="flex items-center gap-2">
|
|
<Plus className="h-4 w-4" /> Nueva cartera
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
{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>
|
|
) : (
|
|
<CarterasList />
|
|
)}
|
|
|
|
{/* Create dialog */}
|
|
<Dialog open={showCreate} onOpenChange={open => { if (!open) resetForm(); }}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Nueva cartera</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="space-y-4 py-4">
|
|
<div>
|
|
<Label>Nombre *</Label>
|
|
<Input value={form.nombre} onChange={e => setForm(p => ({ ...p, nombre: e.target.value }))} placeholder="Ej. Clientes CDMX" autoFocus />
|
|
</div>
|
|
<div>
|
|
<Label>Descripcion (opcional)</Label>
|
|
<Input value={form.descripcion} onChange={e => setForm(p => ({ ...p, descripcion: e.target.value }))} placeholder="Descripcion breve" />
|
|
</div>
|
|
{hasSupervisores ? (
|
|
<div>
|
|
<Label>Asignar a supervisor</Label>
|
|
<Select value={form.supervisorUserId} onValueChange={v => setForm(p => ({ ...p, supervisorUserId: v }))}>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Yo mismo (Owner)" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="__self__">Yo mismo (Owner)</SelectItem>
|
|
{supervisores!.map(s => (
|
|
<SelectItem key={s.userId} value={s.userId}>{s.nombre} ({s.email})</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<p className="text-xs text-muted-foreground mt-1">Si no seleccionas, la cartera se asigna a ti.</p>
|
|
</div>
|
|
) : (
|
|
<p className="text-xs text-muted-foreground border rounded-md p-3 bg-muted/30">
|
|
No hay supervisores registrados. La cartera se asignará a ti como owner.
|
|
</p>
|
|
)}
|
|
</div>
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={resetForm}>Cancelar</Button>
|
|
<Button onClick={handleCreate} disabled={!form.nombre.trim() || createMut.isPending}>
|
|
{createMut.isPending ? 'Creando...' : 'Crear'}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
</DashboardShell>
|
|
);
|
|
}
|