NAM-APJATEL/src/pages/device/DevicePage.tsx

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>
);
}