From 86e692c537427a9738ad48b8b55862240bd8aec4 Mon Sep 17 00:00:00 2001 From: enlineawork Date: Mon, 7 Sep 2026 22:41:59 -0300 Subject: [PATCH] F4: add inventory function catalog admin --- web-v2/src/pages/InventoryFunctionsPage.tsx | 166 ++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 web-v2/src/pages/InventoryFunctionsPage.tsx diff --git a/web-v2/src/pages/InventoryFunctionsPage.tsx b/web-v2/src/pages/InventoryFunctionsPage.tsx new file mode 100644 index 0000000..2376953 --- /dev/null +++ b/web-v2/src/pages/InventoryFunctionsPage.tsx @@ -0,0 +1,166 @@ +import { useEffect, useState } from 'react'; +import type { FormEvent } from 'react'; +import { Alert, LoadingBlock, errorMessage } from '../components/Feedback'; +import { + createInventoryFunction, + listInventoryFunctions, + updateInventoryFunction, +} from '../lib/inventoryFunctionApi'; +import type { InventoryFunction } from '../lib/inventoryFunctionApi'; + +export function InventoryFunctionsPage() { + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(''); + const [code, setCode] = useState(''); + const [name, setName] = useState(''); + const [description, setDescription] = useState(''); + const [sortOrder, setSortOrder] = useState('0'); + const [editingId, setEditingId] = useState(null); + const [editName, setEditName] = useState(''); + const [editDescription, setEditDescription] = useState(''); + const [editSortOrder, setEditSortOrder] = useState('0'); + + const load = () => { + setLoading(true); + setError(''); + listInventoryFunctions(true) + .then(setItems) + .catch((requestError) => setError(errorMessage(requestError))) + .finally(() => setLoading(false)); + }; + + useEffect(() => { load(); }, []); + + const create = async (event: FormEvent) => { + event.preventDefault(); + if (!code.trim() || !name.trim()) return; + setBusy(true); + setError(''); + setSuccess(''); + try { + await createInventoryFunction({ + code, + name, + description: description.trim() || null, + sortOrder: Number(sortOrder || 0), + }); + setCode(''); + setName(''); + setDescription(''); + setSortOrder('0'); + setSuccess('Función incorporada al catálogo. Ya puede asignarse a Estaciones y Subestaciones.'); + load(); + } catch (requestError) { + setError(errorMessage(requestError)); + } finally { + setBusy(false); + } + }; + + const beginEdit = (item: InventoryFunction) => { + setEditingId(item.id); + setEditName(item.name); + setEditDescription(item.description ?? ''); + setEditSortOrder(String(item.sortOrder)); + setError(''); + setSuccess(''); + }; + + const saveEdit = async (event: FormEvent) => { + event.preventDefault(); + if (!editingId || !editName.trim()) return; + setBusy(true); + setError(''); + setSuccess(''); + try { + await updateInventoryFunction(editingId, { + name: editName, + description: editDescription.trim() || null, + sortOrder: Number(editSortOrder || 0), + }); + setEditingId(null); + setSuccess('Función actualizada. Los registros históricos conservan la referencia temporal correspondiente.'); + load(); + } catch (requestError) { + setError(errorMessage(requestError)); + } finally { + setBusy(false); + } + }; + + const toggleActive = async (item: InventoryFunction) => { + setBusy(true); + setError(''); + setSuccess(''); + try { + await updateInventoryFunction(item.id, { isActive: !item.isActive }); + setSuccess(item.isActive + ? 'Función desactivada. No podrá elegirse en nuevos cambios, pero permanece en el historial.' + : 'Función reactivada y disponible nuevamente para asignación.'); + load(); + } catch (requestError) { + setError(errorMessage(requestError)); + } finally { + setBusy(false); + } + }; + + if (loading) return ; + + return
+
+
ADMINISTRACIÓN

Catálogo de funciones

Funciones operativas que pueden asumir las Estaciones y Subestaciones. Desactivar una opción nunca borra su uso histórico.

+
+ + {error && {error}} + {success && {success}} + +
+
+

Nueva función

Ejemplos: Bombeo Mecánico AIB, Bombeo Mecánico Rotaflex, PCP.

+
+ + + +
+