281 lines
8.6 KiB
TypeScript
281 lines
8.6 KiB
TypeScript
import { useMemo, useState } from "react";
|
|
import {
|
|
useReactTable,
|
|
getCoreRowModel,
|
|
getPaginationRowModel,
|
|
getSortedRowModel,
|
|
getFilteredRowModel,
|
|
flexRender,
|
|
createColumnHelper,
|
|
SortingState,
|
|
} from "@tanstack/react-table";
|
|
import Button from "@/components/actions/Button";
|
|
import Card from "@/components/data_display/Card/Card";
|
|
import { useDevices } from "@/features/devices/api/devices";
|
|
import { DeviceDataTable } from "@/features/devices/types";
|
|
import Loading from "@/components/feedback/Loading";
|
|
import Alert from "@/components/feedback/Alert";
|
|
import { Pencil } from "lucide-react";
|
|
|
|
import dayjs from "dayjs";
|
|
import id from "dayjs/locale/id";
|
|
import Dropdown from "@/components/actions/Dropdown/Dropdown";
|
|
|
|
dayjs.locale(id);
|
|
|
|
export default function DevicePage() {
|
|
const { data: devices, isLoading, isError, error } = useDevices();
|
|
// State untuk sorting dan filtering
|
|
const [sorting, setSorting] = useState<SortingState>([]);
|
|
const [globalFilter, setGlobalFilter] = useState("");
|
|
const [pagination, setPagination] = useState({
|
|
pageIndex: 0,
|
|
pageSize: 10, // Default 10 data per halaman
|
|
});
|
|
|
|
const columnHelper = createColumnHelper<DeviceDataTable>();
|
|
|
|
const columns = useMemo(
|
|
() => [
|
|
columnHelper.display({
|
|
id: "actions",
|
|
header: "",
|
|
cell: ({ row }) => (
|
|
<Dropdown hover horizontal="right">
|
|
<Dropdown.Toggle
|
|
button={false}
|
|
className="btn btn-ghost btn-sm"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth={2}
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
className="w-4 h-4"
|
|
>
|
|
<circle cx={12} cy={12} r={1} />
|
|
<circle cx={12} cy={5} r={1} />
|
|
<circle cx={12} cy={19} r={1} />
|
|
</svg>
|
|
</Dropdown.Toggle>
|
|
<Dropdown.Menu className="shadow-2xl border p-1 bg-base-100 w-32">
|
|
<Dropdown.Item>
|
|
<Pencil className="w-4 h-4 mr-2 text-info" />
|
|
Edit
|
|
</Dropdown.Item>
|
|
</Dropdown.Menu>
|
|
</Dropdown>
|
|
),
|
|
size: 10,
|
|
}),
|
|
columnHelper.accessor("device_code", {
|
|
header: "Device Code",
|
|
cell: (info) => info.getValue(),
|
|
size: 120,
|
|
}),
|
|
columnHelper.accessor("device_type", {
|
|
header: "Device Type",
|
|
cell: (info) => info.getValue(),
|
|
size: 100,
|
|
}),
|
|
columnHelper.accessor("longitude", {
|
|
header: "Longitude",
|
|
cell: (info) => info.getValue().toFixed(4),
|
|
size: 100,
|
|
}),
|
|
columnHelper.accessor("latitude", {
|
|
header: "Latitude",
|
|
cell: (info) => info.getValue().toFixed(4),
|
|
size: 100,
|
|
}),
|
|
columnHelper.accessor("address", {
|
|
header: "Address",
|
|
cell: (info) => info.getValue(),
|
|
size: 200,
|
|
}),
|
|
columnHelper.accessor("port_amount", {
|
|
header: "Total Ports",
|
|
cell: (info) => info.getValue(),
|
|
size: 100,
|
|
}),
|
|
columnHelper.accessor("total_used_port", {
|
|
header: "Used Ports",
|
|
cell: (info) => info.getValue(),
|
|
size: 100,
|
|
}),
|
|
columnHelper.accessor("status", {
|
|
header: "Status",
|
|
cell: (info) => (
|
|
<span
|
|
className={`badge ${
|
|
info.getValue() === "active" ? "badge-success" : "badge-error"
|
|
}`}
|
|
>
|
|
{info.getValue()}
|
|
</span>
|
|
),
|
|
size: 100,
|
|
}),
|
|
columnHelper.accessor("created_at", {
|
|
header: "Created At",
|
|
cell: (info) => dayjs(info.getValue()).format("DD MMMM YYYY, HH:mm"),
|
|
size: 150,
|
|
}),
|
|
],
|
|
[]
|
|
);
|
|
|
|
const table = useReactTable({
|
|
data: devices || [],
|
|
columns,
|
|
state: {
|
|
sorting,
|
|
globalFilter,
|
|
pagination,
|
|
},
|
|
getCoreRowModel: getCoreRowModel(),
|
|
getPaginationRowModel: getPaginationRowModel(),
|
|
getSortedRowModel: getSortedRowModel(),
|
|
getFilteredRowModel: getFilteredRowModel(),
|
|
});
|
|
|
|
return (
|
|
<div>
|
|
<div className="bg-base-100 shadow-md p-4 rounded-lg flex justify-between items-center">
|
|
<h1 className="text-2xl font-bold">Devices</h1>
|
|
<Button color="primary">Add Device</Button>
|
|
</div>
|
|
|
|
<Card className="bg-base-100 shadow-lg mt-8">
|
|
<Card.Body>
|
|
{isError && (
|
|
<Alert
|
|
status="error"
|
|
icon={
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className="h-6 w-6 shrink-0 stroke-current"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="2"
|
|
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
}
|
|
>
|
|
{error?.message || "Failed to load devices"}
|
|
</Alert>
|
|
)}
|
|
|
|
<div className="flex justify-between items-center mb-4">
|
|
{/* Search Input */}
|
|
<input
|
|
type="text"
|
|
placeholder="Search devices..."
|
|
value={globalFilter}
|
|
onChange={(e) => setGlobalFilter(e.target.value)}
|
|
className="input input-bordered w-full max-w-xs"
|
|
/>
|
|
|
|
{/* Page Size Dropdown */}
|
|
<select
|
|
value={pagination.pageSize}
|
|
onChange={(e) =>
|
|
setPagination((prev) => ({
|
|
...prev,
|
|
pageSize: Number(e.target.value),
|
|
}))
|
|
}
|
|
className="select select-bordered"
|
|
>
|
|
{[5, 10, 25, 50].map((pageSize) => (
|
|
<option key={pageSize} value={pageSize}>
|
|
Show {pageSize}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
<div className="overflow-x-auto">
|
|
<table className="table table-zebra w-full">
|
|
<thead>
|
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
<tr key={headerGroup.id}>
|
|
{headerGroup.headers.map((header) => (
|
|
<th
|
|
key={header.id}
|
|
onClick={header.column.getToggleSortingHandler()}
|
|
className="cursor-pointer"
|
|
>
|
|
{header.isPlaceholder
|
|
? null
|
|
: flexRender(
|
|
header.column.columnDef.header,
|
|
header.getContext()
|
|
)}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</thead>
|
|
<tbody>
|
|
{isLoading ? (
|
|
<tr>
|
|
<td colSpan={columns.length} className="text-center">
|
|
<Loading />
|
|
</td>
|
|
</tr>
|
|
) : null}
|
|
{table.getRowModel().rows.map((row) => (
|
|
<tr key={row.id}>
|
|
{row.getVisibleCells().map((cell) => (
|
|
<td key={cell.id} className="w-fit">
|
|
{flexRender(
|
|
cell.column.columnDef.cell,
|
|
cell.getContext()
|
|
)}
|
|
</td>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* Pagination */}
|
|
<div className="flex items-center justify-end space-x-2 py-4">
|
|
<button
|
|
className="btn btn-sm"
|
|
onClick={() => table.previousPage()}
|
|
disabled={!table.getCanPreviousPage()}
|
|
>
|
|
Previous
|
|
</button>
|
|
<span>
|
|
Page{" "}
|
|
<strong>
|
|
{table.getState().pagination.pageIndex + 1} of{" "}
|
|
{table.getPageCount()}
|
|
</strong>
|
|
</span>
|
|
<button
|
|
className="btn btn-sm"
|
|
onClick={() => table.nextPage()}
|
|
disabled={!table.getCanNextPage()}
|
|
>
|
|
Next
|
|
</button>
|
|
</div>
|
|
</Card.Body>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|