From 3a1fee74e7fadca4f2ada8b69cf631033fa02b13 Mon Sep 17 00:00:00 2001 From: Nurfog Date: Thu, 10 Sep 2026 14:47:15 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20p=C3=A1ginas=20de=20configuraci=C3=B3n?= =?UTF-8?q?=20(departamentos,=20equipo,=20SLA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/(app)/settings/departments/page.tsx | 228 +++++++++++++++ frontend/src/app/(app)/settings/sla/page.tsx | 256 +++++++++++++++++ frontend/src/app/(app)/settings/team/page.tsx | 267 ++++++++++++++++++ frontend/src/components/Modal.tsx | 60 ++++ frontend/src/components/Sidebar.tsx | 25 ++ frontend/src/lib/api.ts | 84 ++++++ 6 files changed, 920 insertions(+) create mode 100644 frontend/src/app/(app)/settings/departments/page.tsx create mode 100644 frontend/src/app/(app)/settings/sla/page.tsx create mode 100644 frontend/src/app/(app)/settings/team/page.tsx create mode 100644 frontend/src/components/Modal.tsx diff --git a/frontend/src/app/(app)/settings/departments/page.tsx b/frontend/src/app/(app)/settings/departments/page.tsx new file mode 100644 index 0000000..e35d8d8 --- /dev/null +++ b/frontend/src/app/(app)/settings/departments/page.tsx @@ -0,0 +1,228 @@ +"use client"; + +import { useEffect, useState, useCallback } from "react"; +import { + getGroups, + createGroup, + updateGroup, + deleteGroup, + type Group, +} from "@/lib/api"; +import Modal from "@/components/Modal"; + +export default function DepartmentsPage() { + const [groups, setGroups] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(""); + const [showModal, setShowModal] = useState(false); + const [editing, setEditing] = useState(null); + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [isActive, setIsActive] = useState(true); + const [saving, setSaving] = useState(false); + const [modalError, setModalError] = useState(""); + + const fetchGroups = useCallback(async () => { + setLoading(true); + setError(""); + try { + setGroups(await getGroups()); + } catch (err) { + setError(err instanceof Error ? err.message : "Error al cargar"); + setGroups([]); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchGroups(); + }, [fetchGroups]); + + const openCreate = () => { + setEditing(null); + setName(""); + setDescription(""); + setIsActive(true); + setModalError(""); + setShowModal(true); + }; + + const openEdit = (g: Group) => { + setEditing(g); + setName(g.name); + setDescription(g.description || ""); + setIsActive(g.isActive); + setModalError(""); + setShowModal(true); + }; + + const handleSubmit = async () => { + if (!name.trim()) { + setModalError("El nombre es requerido"); + return; + } + setSaving(true); + setModalError(""); + try { + if (editing) { + await updateGroup(editing.id, { + name: name.trim(), + description: description.trim() || undefined, + isActive, + }); + } else { + await createGroup({ name: name.trim(), description: description.trim() || undefined }); + } + setShowModal(false); + fetchGroups(); + } catch (err) { + setModalError(err instanceof Error ? err.message : "Error al guardar"); + } finally { + setSaving(false); + } + }; + + const handleDelete = async (g: Group) => { + if (!confirm(`¿Eliminar el departamento "${g.name}"?`)) return; + setError(""); + try { + await deleteGroup(g.id); + fetchGroups(); + } catch (err) { + setError(err instanceof Error ? err.message : "Error al eliminar"); + } + }; + + return ( +
+
+

Departamentos

+ +
+ + {error && ( +
+ + {error} +
+ )} + +
+
+
+ + + + + + + + + + + {loading ? ( + + + + ) : groups.length === 0 ? ( + + + + ) : ( + groups.map((g) => ( + + + + + + + )) + )} + +
NombreDescripciónEstadoAcciones
+
+
+ +
Sin departamentos
+

Crea el primero para organizar los tickets

+
{g.name}{g.description || "—"} + {g.isActive ? ( + Activo + ) : ( + Inactivo + )} + + + +
+
+
+
+ + setShowModal(false)} + onSubmit={handleSubmit} + submitting={saving} + > + {modalError && ( +
+ {modalError} +
+ )} +
+ + setName(e.target.value)} + placeholder="ej: Soporte Nivel 1" + autoFocus + /> +
+
+ +