From 6c2c07412f66ecc130ce1e6e9715f44502d02895 Mon Sep 17 00:00:00 2001 From: prsmith777 Date: Wed, 21 Oct 2026 21:05:54 -0600 Subject: [PATCH 1/2] Add resizable plate columns and adaptive action menus --- CHANGELOG.md | 6 + app/globals.css | 5 + components/PlateStateCountryField.jsx | 8 +- components/PlateTable.jsx | 297 ++++++-------------------- components/ResizablePlateTable.jsx | 124 +++++++++++ components/TableRowActions.jsx | 59 +++++ components/plateDbTable.jsx | 222 +++++-------------- lib/help-manual.mjs | 2 + lib/table-column-widths.mjs | 78 +++++++ test/action-tooltips.test.mjs | 17 +- test/table-column-widths.test.mjs | 71 ++++++ test/ui-table-enhancements.test.mjs | 26 ++- 12 files changed, 488 insertions(+), 427 deletions(-) create mode 100644 components/ResizablePlateTable.jsx create mode 100644 components/TableRowActions.jsx create mode 100644 lib/table-column-widths.mjs create mode 100644 test/table-column-widths.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bb9af5..d8e3613 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +- Resize Recognition Feed and Plate Database columns using draggable header dividers or the keyboard, with separate saved widths per browser and a reset option. +- Keep narrow text readable through ellipses and hover text, protect minimum control widths, and allow horizontal scrolling. +- Start Actions as a compact menu with full labels; widening the column restores the individual action icons. + ## 0.1.57 — 2026-10-21 - Enable batch correction and recurring OCR-misread aliases by default for administrators; retain the batch preview and unchanged-plate safeguards. Operators retain single-read correction. diff --git a/app/globals.css b/app/globals.css index 6867d27..5ea4a83 100644 --- a/app/globals.css +++ b/app/globals.css @@ -200,3 +200,8 @@ @apply bg-background text-foreground; } } + +/* Fixed column widths keep long content inside its own cell. */ +.resizable-plate-table td { + overflow: hidden; +} diff --git a/components/PlateStateCountryField.jsx b/components/PlateStateCountryField.jsx index 5388949..fec3637 100644 --- a/components/PlateStateCountryField.jsx +++ b/components/PlateStateCountryField.jsx @@ -10,7 +10,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { getPlateStateCountries, updatePlateStateCountry } from "@/app/actions"; -export default function PlateStateCountryField({ plateNumber, value, onSaved }) { +export default function PlateStateCountryField({ plateNumber, value, onSaved, compact = false }) { const { can } = useAccess(); const inputId = useId(); const optionsId = useId(); @@ -37,7 +37,7 @@ export default function PlateStateCountryField({ plateNumber, value, onSaved }) return () => { active = false; }; }, [open]); - if (!can("plate.review")) return {savedValue || "—"}; + if (!can("plate.review")) return {savedValue || "—"}; const save = async (event) => { event.preventDefault(); @@ -58,8 +58,8 @@ export default function PlateStateCountryField({ plateNumber, value, onSaved }) }; return <> - { if (!saving) setOpen(next); }}> diff --git a/components/PlateTable.jsx b/components/PlateTable.jsx index 87ea53d..b4bf512 100644 --- a/components/PlateTable.jsx +++ b/components/PlateTable.jsx @@ -47,10 +47,8 @@ import { SelectValue, } from "@/components/ui/select"; import { - Table, TableBody, TableCell, - TableHead, TableHeader, TableRow, } from "@/components/ui/table"; @@ -98,6 +96,8 @@ import { useRouter } from "next/navigation"; import PlateMatchModeSelect from "@/components/PlateMatchModeSelect"; import PlateImage from "@/components/PlateImage"; import PlateStateCountryField from "@/components/PlateStateCountryField"; +import ResizablePlateTable, { ResizableTableHead, TableValue } from "@/components/ResizablePlateTable"; +import TableRowActions from "@/components/TableRowActions"; import { plateCorrectionDefaults, isAliasOnlyCorrection } from "@/lib/plate-correction-defaults.mjs"; import MultiSelectFilter from "@/components/MultiSelectFilter"; import LiveFeedDateRangeFilter from "@/components/LiveFeedDateRangeFilter"; @@ -156,12 +156,12 @@ const SortButton = ({ label, field, sort, onSort }) => { ); }; @@ -187,7 +187,6 @@ const POPUP_ACTION_ICON_CLASS = "mr-1 h-3.5 w-3.5 shrink-0"; const POPUP_ACTION_LABEL_CLASS = "min-w-0 truncate whitespace-nowrap"; const POPUP_ACTION_GRID_CLASS = "grid w-full grid-cols-7 gap-2"; const POPUP_ACTION_SLOT_CLASS = "min-h-8 min-w-0"; -const TABLE_ACTION_BUTTON_CLASS = "h-8 w-8 p-0"; const CONFIRM_NEXT_SCAN_TIMEOUT_MS = 15000; const SEARCH_FILTER_DEBOUNCE_MS = 250; @@ -245,11 +244,11 @@ function PlateIdentity({ plate, compact = false }) { const wasResolved = observed !== plate.plate_number; return ( -
+
{plate.plate_number} @@ -262,14 +261,10 @@ function PlateIdentity({ plate, compact = false }) {
{wasResolved && ( -
- Camera read {observed} -
+ {`Camera read ${observed}`} )} {plate.known_name && ( -
- {plate.known_name} -
+ {plate.known_name} )}
); @@ -291,13 +286,14 @@ function DirectionBadge({ plate }) { return ( - {label} + {label} ); } @@ -312,15 +308,16 @@ function PlateTimestamp({ timestamp, timeFormat }) { return (