diff --git a/src/features/admin/OrganizationDetailPage.tsx b/src/features/admin/OrganizationDetailPage.tsx index 9c4fa01..d02474a 100644 --- a/src/features/admin/OrganizationDetailPage.tsx +++ b/src/features/admin/OrganizationDetailPage.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react' import { useParams, useNavigate, Link } from '@tanstack/react-router' -import { ArrowLeft, FileText, Users, FolderX, UserX, GitBranch } from 'lucide-react' +import { ArrowLeft, FileText, Users, FolderX, UserX, GitBranch, FolderOpen, Plus, Pencil, Trash2 } from 'lucide-react' import { supabase } from '@/lib/supabase' import { Button } from '@/components/ui/button' import { Skeleton } from '@/components/ui/skeleton' @@ -20,14 +20,25 @@ import { SelectValue, } from '@/components/ui/select' import { RoleBadge } from './RoleBadge' +import { supabaseAreaRepo } from '@/persistence/supabase/area-repo' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' interface OrgProcess { id: string name: string client: string | null + area_id: string | null updated_at: string } +interface AreaWithCount { + id: string + name: string + description: string | null + processCount: number +} + interface OrgUser { id: string name: string | null @@ -66,16 +77,53 @@ export function OrganizationDetailPage() { const [assigning, setAssigning] = useState(false) const [assignError, setAssignError] = useState(null) + // Áreas de la org + const [areas, setAreas] = useState([]) + const [areasLoading, setAreasLoading] = useState(false) + + // Sheet crear/editar área + const [areaSheetOpen, setAreaSheetOpen] = useState(false) + const [editingAreaId, setEditingAreaId] = useState(null) + const [areaNameInput, setAreaNameInput] = useState('') + const [savingArea, setSavingArea] = useState(false) + const [areaFormError, setAreaFormError] = useState(null) + + // Confirmación de delete + const [deleteConfirmId, setDeleteConfirmId] = useState(null) + const [deletingAreaId, setDeletingAreaId] = useState(null) + const fetchDetail = async () => { const [orgRes, procRes, userRes] = await Promise.all([ supabase.from('organizations').select('name').eq('id', orgId).single(), - supabase.from('processes').select('id, name, client, updated_at').eq('org_id', orgId).order('updated_at', { ascending: false }), + supabase + .from('processes') + .select('id, name, client, area_id, updated_at') + .eq('org_id', orgId) + .order('updated_at', { ascending: false }), supabase.from('users').select('id, name, company, platform_role, avatar_url').eq('org_id', orgId).order('name'), ]) if (orgRes.data) setOrgName((orgRes.data as { name: string }).name) - setProcesses((procRes.data as OrgProcess[] | null) ?? []) + const loadedProcesses = (procRes.data as OrgProcess[] | null) ?? [] + setProcesses(loadedProcesses) setUsers((userRes.data as OrgUser[] | null) ?? []) setLoading(false) + + // Cargar áreas y calcular conteo de procesos + setAreasLoading(true) + try { + const rawAreas = await supabaseAreaRepo.getByOrg(orgId) + const withCount: AreaWithCount[] = rawAreas.map((a) => ({ + id: a.id, + name: a.name, + description: a.description, + processCount: loadedProcesses.filter((p) => p.area_id === a.id).length, + })) + setAreas(withCount) + } catch { + setAreas([]) + } finally { + setAreasLoading(false) + } } useEffect(() => { void fetchDetail() }, [orgId]) @@ -114,6 +162,53 @@ export function OrganizationDetailPage() { } } + const openCreateArea = () => { + setEditingAreaId(null) + setAreaNameInput('') + setAreaFormError(null) + setAreaSheetOpen(true) + } + + const openEditArea = (area: AreaWithCount) => { + setEditingAreaId(area.id) + setAreaNameInput(area.name) + setAreaFormError(null) + setAreaSheetOpen(true) + } + + const handleSaveArea = async () => { + const name = areaNameInput.trim() + if (!name) { setAreaFormError('El nombre del área es requerido'); return } + setSavingArea(true) + setAreaFormError(null) + try { + if (editingAreaId) { + await supabaseAreaRepo.update(editingAreaId, { name }) + } else { + await supabaseAreaRepo.create({ orgId, name }) + } + setAreaSheetOpen(false) + void fetchDetail() + } catch (e) { + setAreaFormError(e instanceof Error ? e.message : 'Error al guardar el área') + } finally { + setSavingArea(false) + } + } + + const handleDeleteArea = async (areaId: string) => { + setDeletingAreaId(areaId) + try { + await supabaseAreaRepo.delete(areaId) + setDeleteConfirmId(null) + void fetchDetail() + } catch (e) { + console.error('Error eliminando área:', e) + } finally { + setDeletingAreaId(null) + } + } + if (loading) { return (
@@ -148,6 +243,10 @@ export function OrganizationDetailPage() { Usuarios ({users.length}) + + + Áreas ({areas.length}) + {/* ─── Tab: Procesos ─────────────────────────────────────────────── */} @@ -240,6 +339,105 @@ export function OrganizationDetailPage() {
)} + {/* ─── Tab: Áreas ──────────────────────────────────────────────── */} + +
+

+ {areasLoading + ? 'Cargando áreas…' + : areas.length === 0 + ? 'No hay áreas en esta organización.' + : `${areas.length} área${areas.length !== 1 ? 's' : ''}.`} +

+ +
+ + {areasLoading ? ( +
+ + +
+ ) : areas.length === 0 ? ( + } + message="Las áreas agrupan procesos dentro de una organización (ej: Operaciones, RR.HH., Finanzas)." + /> + ) : ( +
+ + + + + + + + + {areas.map((area) => ( + + + + + + ))} + +
ÁreaProcesos +
{area.name} + {area.processCount} {area.processCount === 1 ? 'proceso' : 'procesos'} + + {deleteConfirmId === area.id ? ( +
+ + {area.processCount > 0 + ? `${area.processCount} proceso${area.processCount !== 1 ? 's' : ''} quedarán sin área.` + : '¿Eliminar esta área?'} + + + +
+ ) : ( +
+ + +
+ )} +
+
+ )} +
{/* ─── Sheet: Asignar proceso ─────────────────────────────────────── */} @@ -277,6 +475,40 @@ export function OrganizationDetailPage() { + {/* ─── Sheet: Crear/editar área ────────────────────────────────── */} + + + + + {editingAreaId ? 'Renombrar área' : `Nueva área en ${orgName}`} + + +
+
+ + { setAreaNameInput(e.target.value); setAreaFormError(null) }} + placeholder="ej. Operaciones, RR.HH., Finanzas" + className="h-9" + onKeyDown={(e) => { if (e.key === 'Enter') void handleSaveArea() }} + autoFocus + /> + {areaFormError && ( +

{areaFormError}

+ )} +
+ + + +
+
+
) }