Device management table edit and delete logic
This commit is contained in:
@@ -37,6 +37,7 @@ export default function DeviceManagement() {
|
|||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const [editingId, setEditingId] = useState<string | null>(null);
|
const [editingId, setEditingId] = useState<string | null>(null);
|
||||||
const [activeDevice, setActiveDevice] = useState<Device | null>(null);
|
const [activeDevice, setActiveDevice] = useState<Device | null>(null);
|
||||||
|
const [selectedRows, setSelectedRows] = useState<Device[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const emptyDevice: Omit<Device, "id"> = {
|
const emptyDevice: Omit<Device, "id"> = {
|
||||||
@@ -70,6 +71,7 @@ export default function DeviceManagement() {
|
|||||||
const data: ApiResponse = await response.json();
|
const data: ApiResponse = await response.json();
|
||||||
setDevices(data.records);
|
setDevices(data.records);
|
||||||
setActiveDevice(null);
|
setActiveDevice(null);
|
||||||
|
setSelectedRows([]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading devices:", error);
|
console.error("Error loading devices:", error);
|
||||||
const mockData: Device[] = [
|
const mockData: Device[] = [
|
||||||
@@ -115,6 +117,7 @@ export default function DeviceManagement() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
setDevices(mockData);
|
setDevices(mockData);
|
||||||
|
setSelectedRows([]);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -152,12 +155,17 @@ export default function DeviceManagement() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
if (!activeDevice) return;
|
if (selectedRows.length === 0) return;
|
||||||
|
|
||||||
if (confirm("¿Deseas eliminar este dispositivo?")) {
|
const message = selectedRows.length === 1
|
||||||
|
? "¿Deseas eliminar este dispositivo?"
|
||||||
|
: `¿Deseas eliminar ${selectedRows.length} dispositivos?`;
|
||||||
|
|
||||||
|
if (confirm(message)) {
|
||||||
setDevices((prev) =>
|
setDevices((prev) =>
|
||||||
prev.filter((device) => device.id !== activeDevice.id)
|
prev.filter((device) => !selectedRows.some(selected => selected.id === device.id))
|
||||||
);
|
);
|
||||||
|
setSelectedRows([]);
|
||||||
setActiveDevice(null);
|
setActiveDevice(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -203,20 +211,25 @@ export default function DeviceManagement() {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleEdit}
|
onClick={handleEdit}
|
||||||
disabled={!activeDevice}
|
disabled={selectedRows.length !== 1}
|
||||||
className={`flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg
|
className={`flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg
|
||||||
${!activeDevice ? "opacity-70 cursor-not-allowed" : "hover:bg-white/10"}`}
|
${
|
||||||
|
selectedRows.length !== 1 ? "opacity-70 cursor-not-allowed" : "hover:bg-white/10"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<Pencil size={16} /> Edit
|
<Pencil size={16} /> Edit
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
disabled={!activeDevice}
|
disabled={selectedRows.length === 0}
|
||||||
className={`flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg
|
className={`flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg
|
||||||
${!activeDevice ? "opacity-70 cursor-not-allowed" : "hover:bg-white/10"}`}
|
${
|
||||||
|
selectedRows.length === 0 ? "opacity-70 cursor-not-allowed" : "hover:bg-white/10"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<Trash2 size={16} /> Delete
|
<Trash2 size={16} />
|
||||||
|
{selectedRows.length > 0 ? `Delete (${selectedRows.length})` : "Delete"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -236,7 +249,7 @@ export default function DeviceManagement() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<MaterialTable
|
<MaterialTable
|
||||||
title="Devices"
|
title={`Devices ${selectedRows.length > 0 ? `(${selectedRows.length} selected)` : ""}`}
|
||||||
columns={[
|
columns={[
|
||||||
{ title: "Area Name", field: "Area Name" },
|
{ title: "Area Name", field: "Area Name" },
|
||||||
{ title: "Account Number", field: "Account Number" },
|
{ title: "Account Number", field: "Account Number" },
|
||||||
@@ -275,8 +288,11 @@ export default function DeviceManagement() {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
data={filteredDevices}
|
data={filteredDevices}
|
||||||
onRowClick={(_event, rowData) => {
|
onSelectionChange={(rows) => {
|
||||||
setActiveDevice(rowData as Device);
|
const selectedDevices = rows as Device[];
|
||||||
|
setSelectedRows(selectedDevices);
|
||||||
|
// Set active device to the first selected item for editing
|
||||||
|
setActiveDevice(selectedDevices.length > 0 ? selectedDevices[0] : null);
|
||||||
}}
|
}}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
@@ -303,18 +319,13 @@ export default function DeviceManagement() {
|
|||||||
search: false,
|
search: false,
|
||||||
paging: true,
|
paging: true,
|
||||||
sorting: true,
|
sorting: true,
|
||||||
|
selection: true,
|
||||||
headerStyle: {
|
headerStyle: {
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
},
|
},
|
||||||
maxBodyHeight: "500px",
|
maxBodyHeight: "500px",
|
||||||
tableLayout: "fixed",
|
tableLayout: "fixed",
|
||||||
rowStyle: (rowData) => ({
|
|
||||||
backgroundColor:
|
|
||||||
activeDevice?.id === (rowData as Device).id
|
|
||||||
? "#EEF2FF"
|
|
||||||
: "#FFFFFF",
|
|
||||||
}),
|
|
||||||
}}
|
}}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import MaterialTable from "@material-table/core";
|
import MaterialTable from "@material-table/core";
|
||||||
|
|
||||||
|
|
||||||
/* ================= TYPES ================= */
|
/* ================= TYPES ================= */
|
||||||
interface Operator {
|
interface Operator {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -98,16 +97,16 @@ export default function OperatorManagement() {
|
|||||||
|
|
||||||
/* ================= TREE ================= */
|
/* ================= TREE ================= */
|
||||||
const toggleExpand = (id: number) => {
|
const toggleExpand = (id: number) => {
|
||||||
setExpandedIds(prev =>
|
setExpandedIds((prev) =>
|
||||||
prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]
|
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateArea = (list: Area[]): Area[] =>
|
const updateArea = (list: Area[]): Area[] =>
|
||||||
list.map(area => {
|
list.map((area) => {
|
||||||
if (area.id === selectedArea?.id) {
|
if (area.id === selectedArea?.id) {
|
||||||
const operators = editingId
|
const operators = editingId
|
||||||
? area.operators.map(op =>
|
? area.operators.map((op) =>
|
||||||
op.id === editingId ? { ...op, ...form } : op
|
op.id === editingId ? { ...op, ...form } : op
|
||||||
)
|
)
|
||||||
: [...area.operators, { id: Date.now(), ...form }];
|
: [...area.operators, { id: Date.now(), ...form }];
|
||||||
@@ -122,13 +121,12 @@ export default function OperatorManagement() {
|
|||||||
|
|
||||||
/* ================= CRUD ================= */
|
/* ================= CRUD ================= */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
setAreas(prev => {
|
setAreas((prev) => {
|
||||||
const updated = updateArea(prev);
|
const updated = updateArea(prev);
|
||||||
|
|
||||||
// 🔑 volver a apuntar al área actual actualizada
|
// 🔑 volver a apuntar al área actual actualizada
|
||||||
const refreshedArea = updated.find(
|
const refreshedArea =
|
||||||
a => a.id === selectedArea?.id
|
updated.find((a) => a.id === selectedArea?.id) || null;
|
||||||
) || null;
|
|
||||||
|
|
||||||
setSelectedArea(refreshedArea);
|
setSelectedArea(refreshedArea);
|
||||||
return updated;
|
return updated;
|
||||||
@@ -150,12 +148,12 @@ export default function OperatorManagement() {
|
|||||||
if (!selectedArea || !activeOperator) return;
|
if (!selectedArea || !activeOperator) return;
|
||||||
|
|
||||||
const deleteFromTree = (list: Area[]): Area[] =>
|
const deleteFromTree = (list: Area[]): Area[] =>
|
||||||
list.map(area => {
|
list.map((area) => {
|
||||||
if (area.id === selectedArea.id) {
|
if (area.id === selectedArea.id) {
|
||||||
return {
|
return {
|
||||||
...area,
|
...area,
|
||||||
operators: area.operators.filter(
|
operators: area.operators.filter(
|
||||||
op => op.id !== activeOperator.id
|
(op) => op.id !== activeOperator.id
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -165,14 +163,14 @@ export default function OperatorManagement() {
|
|||||||
return area;
|
return area;
|
||||||
});
|
});
|
||||||
|
|
||||||
setAreas(prev => deleteFromTree(prev));
|
setAreas((prev) => deleteFromTree(prev));
|
||||||
setActiveOperator(null);
|
setActiveOperator(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ================= FILTER ================= */
|
/* ================= FILTER ================= */
|
||||||
const filtered =
|
const filtered =
|
||||||
selectedArea?.operators.filter(
|
selectedArea?.operators.filter(
|
||||||
op =>
|
(op) =>
|
||||||
op.loginName.toLowerCase().includes(search.toLowerCase()) ||
|
op.loginName.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
op.userName.toLowerCase().includes(search.toLowerCase())
|
op.userName.toLowerCase().includes(search.toLowerCase())
|
||||||
) || [];
|
) || [];
|
||||||
@@ -195,28 +193,22 @@ export default function OperatorManagement() {
|
|||||||
>
|
>
|
||||||
{area.children && (
|
{area.children && (
|
||||||
<button onClick={() => toggleExpand(area.id)}>
|
<button onClick={() => toggleExpand(area.id)}>
|
||||||
{expanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
{expanded ? (
|
||||||
|
<ChevronDown size={14} />
|
||||||
|
) : (
|
||||||
|
<ChevronRight size={14} />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{area.name}
|
{area.name}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{expanded &&
|
{expanded &&
|
||||||
area.children?.map(child => renderTree(child, level + 1))}
|
area.children?.map((child) => renderTree(child, level + 1))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredOperators: Operator[] =
|
|
||||||
selectedArea?.operators.filter(op => {
|
|
||||||
const q = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
op.loginName.toLowerCase().includes(q) ||
|
|
||||||
op.userName.toLowerCase().includes(q) ||
|
|
||||||
op.cellPhone.toLowerCase().includes(q)
|
|
||||||
);
|
|
||||||
}) || [];
|
|
||||||
|
|
||||||
/* ================= UI ================= */
|
/* ================= UI ================= */
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-6 p-6 w-full bg-gray-100">
|
<div className="flex gap-6 p-6 w-full bg-gray-100">
|
||||||
@@ -225,7 +217,7 @@ export default function OperatorManagement() {
|
|||||||
<h3 className="text-xs font-semibold text-gray-500 mb-3">
|
<h3 className="text-xs font-semibold text-gray-500 mb-3">
|
||||||
Organizational Structure
|
Organizational Structure
|
||||||
</h3>
|
</h3>
|
||||||
{areas.map(a => renderTree(a))}
|
{areas.map((a) => renderTree(a))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* MAIN */}
|
{/* MAIN */}
|
||||||
@@ -264,9 +256,7 @@ export default function OperatorManagement() {
|
|||||||
disabled={!activeOperator}
|
disabled={!activeOperator}
|
||||||
className={`flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg
|
className={`flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg
|
||||||
${
|
${
|
||||||
!activeOperator
|
!activeOperator ? "opacity-70 cursor-not-allowed" : "hover:bg-white/10"
|
||||||
? "opacity-70 cursor-not-allowed"
|
|
||||||
: "hover:bg-white/10"
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Pencil size={16} /> Edit
|
<Pencil size={16} /> Edit
|
||||||
@@ -278,9 +268,7 @@ export default function OperatorManagement() {
|
|||||||
disabled={!activeOperator}
|
disabled={!activeOperator}
|
||||||
className={`flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg
|
className={`flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg
|
||||||
${
|
${
|
||||||
!activeOperator
|
!activeOperator ? "opacity-70 cursor-not-allowed" : "hover:bg-white/10"
|
||||||
? "opacity-70 cursor-not-allowed"
|
|
||||||
: "hover:bg-white/10"
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Trash2 size={16} /> Delete
|
<Trash2 size={16} /> Delete
|
||||||
@@ -294,8 +282,6 @@ export default function OperatorManagement() {
|
|||||||
<RefreshCcw size={16} /> Refresh
|
<RefreshCcw size={16} /> Refresh
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* SEARCH */}
|
{/* SEARCH */}
|
||||||
@@ -303,18 +289,17 @@ export default function OperatorManagement() {
|
|||||||
className="bg-white rounded-lg shadow px-4 py-2 text-sm"
|
className="bg-white rounded-lg shadow px-4 py-2 text-sm"
|
||||||
placeholder="Search operator..."
|
placeholder="Search operator..."
|
||||||
value={search}
|
value={search}
|
||||||
onChange={e => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MaterialTable
|
<MaterialTable
|
||||||
title={selectedArea?.name || "Operators"}
|
title={selectedArea?.name || "Operators"}
|
||||||
|
|
||||||
columns={[
|
columns={[
|
||||||
{ title: "Login", field: "loginName" },
|
{ title: "Login", field: "loginName" },
|
||||||
{
|
{
|
||||||
title: "Super Admin",
|
title: "Super Admin",
|
||||||
field: "isSuperAdmin",
|
field: "isSuperAdmin",
|
||||||
render: rowData => (
|
render: (rowData) => (
|
||||||
<span
|
<span
|
||||||
className={`px-3 py-1 rounded-full text-xs font-semibold border ${
|
className={`px-3 py-1 rounded-full text-xs font-semibold border ${
|
||||||
rowData.isSuperAdmin
|
rowData.isSuperAdmin
|
||||||
@@ -329,7 +314,7 @@ export default function OperatorManagement() {
|
|||||||
{
|
{
|
||||||
title: "Status",
|
title: "Status",
|
||||||
field: "isDisabled",
|
field: "isDisabled",
|
||||||
render: rowData => (
|
render: (rowData) => (
|
||||||
<span
|
<span
|
||||||
className={`px-3 py-1 rounded-full text-xs font-semibold border ${
|
className={`px-3 py-1 rounded-full text-xs font-semibold border ${
|
||||||
rowData.isDisabled
|
rowData.isDisabled
|
||||||
@@ -346,7 +331,7 @@ export default function OperatorManagement() {
|
|||||||
{ title: "Created", field: "createdAt", type: "date" },
|
{ title: "Created", field: "createdAt", type: "date" },
|
||||||
]}
|
]}
|
||||||
data={filtered}
|
data={filtered}
|
||||||
onRowClick={(event, rowData) => {
|
onRowClick={(_event, rowData) => {
|
||||||
setActiveOperator(rowData as Operator);
|
setActiveOperator(rowData as Operator);
|
||||||
}}
|
}}
|
||||||
actions={[
|
actions={[
|
||||||
@@ -363,7 +348,7 @@ export default function OperatorManagement() {
|
|||||||
{
|
{
|
||||||
icon: () => <Pencil size={16} />,
|
icon: () => <Pencil size={16} />,
|
||||||
tooltip: "Edit Operator",
|
tooltip: "Edit Operator",
|
||||||
onClick: (event, rowData) => {
|
onClick: (_event, rowData) => {
|
||||||
setActiveOperator(rowData as Operator);
|
setActiveOperator(rowData as Operator);
|
||||||
setEditingId((rowData as Operator).id);
|
setEditingId((rowData as Operator).id);
|
||||||
setForm({ ...(rowData as Operator) });
|
setForm({ ...(rowData as Operator) });
|
||||||
@@ -373,7 +358,7 @@ export default function OperatorManagement() {
|
|||||||
{
|
{
|
||||||
icon: () => <Trash2 size={16} />,
|
icon: () => <Trash2 size={16} />,
|
||||||
tooltip: "Delete Operator",
|
tooltip: "Delete Operator",
|
||||||
onClick: (event, rowData) => {
|
onClick: (_event, rowData) => {
|
||||||
setActiveOperator(rowData as Operator);
|
setActiveOperator(rowData as Operator);
|
||||||
handleDelete();
|
handleDelete();
|
||||||
},
|
},
|
||||||
@@ -388,12 +373,9 @@ export default function OperatorManagement() {
|
|||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
},
|
},
|
||||||
cellStyle: {
|
|
||||||
textAlign: "center",
|
|
||||||
},
|
|
||||||
maxBodyHeight: "400px",
|
maxBodyHeight: "400px",
|
||||||
tableLayout: "fixed",
|
tableLayout: "fixed",
|
||||||
rowStyle: rowData => ({
|
rowStyle: (rowData) => ({
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
activeOperator?.id === (rowData as Operator).id
|
activeOperator?.id === (rowData as Operator).id
|
||||||
? "#EEF2FF"
|
? "#EEF2FF"
|
||||||
@@ -401,9 +383,6 @@ export default function OperatorManagement() {
|
|||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* MODAL */}
|
{/* MODAL */}
|
||||||
@@ -418,9 +397,7 @@ export default function OperatorManagement() {
|
|||||||
className="w-full border px-3 py-2 rounded"
|
className="w-full border px-3 py-2 rounded"
|
||||||
placeholder="Login Name"
|
placeholder="Login Name"
|
||||||
value={form.loginName}
|
value={form.loginName}
|
||||||
onChange={e =>
|
onChange={(e) => setForm({ ...form, loginName: e.target.value })}
|
||||||
setForm({ ...form, loginName: e.target.value })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -437,9 +414,7 @@ export default function OperatorManagement() {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() => setForm({ ...form, isDisabled: !form.isDisabled })}
|
||||||
setForm({ ...form, isDisabled: !form.isDisabled })
|
|
||||||
}
|
|
||||||
className={`w-full border rounded px-3 py-2 ${
|
className={`w-full border rounded px-3 py-2 ${
|
||||||
form.isDisabled
|
form.isDisabled
|
||||||
? "text-red-600 border-red-600"
|
? "text-red-600 border-red-600"
|
||||||
@@ -453,27 +428,21 @@ export default function OperatorManagement() {
|
|||||||
className="w-full border px-3 py-2 rounded"
|
className="w-full border px-3 py-2 rounded"
|
||||||
placeholder="User Name"
|
placeholder="User Name"
|
||||||
value={form.userName}
|
value={form.userName}
|
||||||
onChange={e =>
|
onChange={(e) => setForm({ ...form, userName: e.target.value })}
|
||||||
setForm({ ...form, userName: e.target.value })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
className="w-full border px-3 py-2 rounded"
|
className="w-full border px-3 py-2 rounded"
|
||||||
placeholder="Cell Phone"
|
placeholder="Cell Phone"
|
||||||
value={form.cellPhone}
|
value={form.cellPhone}
|
||||||
onChange={e =>
|
onChange={(e) => setForm({ ...form, cellPhone: e.target.value })}
|
||||||
setForm({ ...form, cellPhone: e.target.value })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
className="w-full border px-3 py-2 rounded"
|
className="w-full border px-3 py-2 rounded"
|
||||||
value={form.createdAt}
|
value={form.createdAt}
|
||||||
onChange={e =>
|
onChange={(e) => setForm({ ...form, createdAt: e.target.value })}
|
||||||
setForm({ ...form, createdAt: e.target.value })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end gap-2 pt-3">
|
<div className="flex justify-end gap-2 pt-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user