[Feature] Add Status Filtering to Systems Table (#927)

This commit is contained in:
Sven van Ginkel
2025-08-28 23:30:44 +02:00
committed by GitHub
parent 50d2406423
commit a989d121d3
6 changed files with 255 additions and 98 deletions

View File

@@ -36,26 +36,43 @@ import {
ArrowUpIcon,
Settings2Icon,
EyeIcon,
PenBoxIcon,
ClockIcon,
FilterIcon,
HourglassIcon,
} from "lucide-react"
import { memo, useEffect, useMemo, useState } from "react"
import { $systems } from "@/lib/stores"
import { useStore } from "@nanostores/react"
import { cn, useLocalStorage } from "@/lib/utils"
import { $router, Link } from "../router"
import { useLingui, Trans } from "@lingui/react/macro"
import {
cn,
copyToClipboard,
isReadOnlyUser,
useLocalStorage,
formatTemperature,
decimalString,
formatBytes,
formatUptimeString,
} from "@/lib/utils"
import AlertsButton from "../alerts/alert-button"
import { $router, Link, navigate } from "../router"
import { EthernetIcon, GpuIcon, ThermometerIcon } from "../ui/icons"
import { useLingui, Trans, Plural } from "@lingui/react/macro"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
import { Input } from "../ui/input"
import { Input } from "@/components/ui/input"
import { getPagePath } from "@nanostores/router"
import SystemsTableColumns, { ActionsButton, IndicatorDot } from "./systems-table-columns"
import AlertButton from "../alerts/alert-button"
import { SystemStatus } from "@/lib/enums"
type ViewMode = "table" | "grid"
type StatusFilter = "all" | "up" | "down" | "paused"
export default function SystemsTable() {
const data = useStore($systems)
const { i18n, t } = useLingui()
const [filter, setFilter] = useState<string>()
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all")
const [sorting, setSorting] = useState<SortingState>([{ id: "system", desc: false }])
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const [columnVisibility, setColumnVisibility] = useLocalStorage<VisibilityState>("cols", {})
@@ -63,6 +80,14 @@ export default function SystemsTable() {
const locale = i18n.locale
// Filter data based on status filter
const filteredData = useMemo(() => {
if (statusFilter === "all") {
return data
}
return data.filter((system) => system.status === statusFilter)
}, [data, statusFilter])
useEffect(() => {
if (filter !== undefined) {
table.getColumn("system")?.setFilterValue(filter)
@@ -72,7 +97,7 @@ export default function SystemsTable() {
const columnDefs = useMemo(() => SystemsTableColumns(viewMode), [viewMode])
const table = useReactTable({
data,
data: filteredData,
columns: columnDefs,
getCoreRowModel: getCoreRowModel(),
onSortingChange: setSorting,
@@ -111,8 +136,8 @@ export default function SystemsTable() {
<Trans>Updated in real time. Click on a system to view information.</Trans>
</CardDescription>
</div>
<div className="flex gap-2 ms-auto w-full md:w-80">
<Input placeholder={t`Filter...`} onChange={(e) => setFilter(e.target.value)} className="px-4" />
<div className="flex gap-2 ms-auto w-full md:w-auto">
<Input placeholder={t`Filter...`} onChange={(e) => setFilter(e.target.value)} className="px-4 w-full md:w-80" />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
@@ -121,7 +146,7 @@ export default function SystemsTable() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="h-72 md:h-auto min-w-48 md:min-w-auto overflow-y-auto">
<div className="grid grid-cols-1 md:grid-cols-3 divide-y md:divide-s md:divide-y-0">
<div className="grid grid-cols-1 md:grid-cols-4 divide-y md:divide-s md:divide-y-0">
<div>
<DropdownMenuLabel className="pt-2 px-3.5 flex items-center gap-2">
<LayoutGridIcon className="size-4" />
@@ -144,6 +169,32 @@ export default function SystemsTable() {
</DropdownMenuRadioGroup>
</div>
<div>
<DropdownMenuLabel className="pt-2 px-3.5 flex items-center gap-2">
<FilterIcon className="size-4" />
<Trans>Status</Trans>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioGroup
className="px-1 pb-1"
value={statusFilter}
onValueChange={(value) => setStatusFilter(value as StatusFilter)}
>
<DropdownMenuRadioItem value="all" onSelect={(e) => e.preventDefault()} className="gap-2">
<Trans>All Systems</Trans>
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="up" onSelect={(e) => e.preventDefault()} className="gap-2">
<Trans>Up</Trans>
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="down" onSelect={(e) => e.preventDefault()} className="gap-2">
<Trans>Down</Trans>
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="paused" onSelect={(e) => e.preventDefault()} className="gap-2">
<Trans>Paused</Trans>
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</div>
<div>
<DropdownMenuLabel className="pt-2 px-3.5 flex items-center gap-2">
<ArrowUpDownIcon className="size-4" />
@@ -210,7 +261,7 @@ export default function SystemsTable() {
</div>
</CardHeader>
)
}, [visibleColumns.length, sorting, viewMode, locale])
}, [visibleColumns.length, sorting, viewMode, locale, statusFilter])
return (
<Card>
@@ -331,7 +382,7 @@ const SystemCard = memo(
>
<CardHeader className="py-1 ps-5 pe-3 bg-muted/30 border-b border-border/60">
<div className="flex items-center justify-between gap-2">
<CardTitle className="text-base tracking-normal shrink-1 text-primary/90 flex items-center min-h-10 gap-2.5 min-w-0">
<CardTitle className="text-base tracking-normal shrink-1 text-primary/90 flex items-center min-w-0 gap-2.5">
<div className="flex items-center gap-2.5 min-w-0">
<IndicatorDot system={system} />
<CardTitle className="text-[.95em]/normal tracking-normal truncate text-primary/90">
@@ -356,7 +407,9 @@ const SystemCard = memo(
const { Icon, name } = column.columnDef as ColumnDef<SystemRecord, unknown>
return (
<div key={column.id} className="flex items-center gap-3">
{Icon && <Icon className="size-4 text-muted-foreground" />}
{column.id === "lastSeen" ? (
<EyeIcon className="size-4 text-muted-foreground" />
) : Icon && <Icon className="size-4 text-muted-foreground" />}
<div className="flex items-center gap-3 flex-1">
<span className="text-muted-foreground min-w-16">{name()}:</span>
<div className="flex-1">{flexRender(cell.column.columnDef.cell, cell.getContext())}</div>