Skip to content

Commit aeb72ba

Browse files
committed
0.1.9
1 parent 41d7f07 commit aeb72ba

File tree

8 files changed

+211
-57
lines changed

8 files changed

+211
-57
lines changed

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## [0.1.9] - 08-15-2025
4+
5+
- Support for MQTT as plate notification for HA
6+
- Staged code for AI vehicle descriptions and deep research agent
7+
- More UI improvements
8+
- Database setup fixes
9+
- Improved session management
10+
- Improved management of known plates and flags
11+
- Error handling for non-plate objects in AI dump
12+
- Hardened integration tests
313

414
## [0.1.8] - 03-19-2025
515

@@ -14,7 +24,6 @@ See release notes for more detail on how to update the other systems: https://gi
1424
- Several bug fixes and other improvements
1525
- Foundation for soon-to-come RF fingerprinting functionality
1626

17-
1827
## [0.1.7] - 02-11-2025
1928

2029
- Complete overhaul of image storage system

app/actions.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
editMqttNotification,
5353
toggleMqttNotificationEnabled,
5454
deleteMqttNotification,
55+
addUnseenPlate,
5556
} from "@/lib/db";
5657
import {
5758
getNotificationPlates as getNotificationPlatesDB,
@@ -750,7 +751,7 @@ export async function loginAction(formData) {
750751
path: "/",
751752
});
752753

753-
redirect("/");
754+
return { success: true };
754755
} catch (error) {
755756
console.error("Login error:", error);
756757

@@ -1374,3 +1375,14 @@ export async function validatePlateRecord(readId, value) {
13741375
return { success: false, error: "Failed to validate plate record" };
13751376
}
13761377
}
1378+
1379+
export async function addDBPlate(plate_number, flagged = false) {
1380+
try {
1381+
await addUnseenPlate(plate_number, flagged);
1382+
revalidatePath("/flagged");
1383+
return { success: true };
1384+
} catch (error) {
1385+
console.error("Error adding plate:", error);
1386+
return { success: false, error: "Failed to add plate" };
1387+
}
1388+
}

app/flagged/page.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { getFlagged } from "@/app/actions";
22
import { FlaggedPlatesTable } from "@/components/FlaggedPlatesTable";
33
import DashboardLayout from "@/components/layout/MainLayout";
44
import BasicTitle from "@/components/layout/BasicTitle";
5+
56
export const dynamic = "force-dynamic";
67

78
export default async function FlaggedPlatesPage() {

app/login/page.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"use client";
33

44
import { useState, useTransition } from "react";
5+
import { useRouter } from "next/navigation";
56
import { loginAction } from "@/app/actions";
67
import { Input } from "@/components/ui/input";
78
import { Button } from "@/components/ui/button";
@@ -11,6 +12,7 @@ import Image from "next/image";
1112
export default function LoginPage() {
1213
const [error, setError] = useState("");
1314
const [isPending, startTransition] = useTransition();
15+
const router = useRouter();
1416

1517
async function handleSubmit(event) {
1618
event.preventDefault();
@@ -24,6 +26,9 @@ export default function LoginPage() {
2426

2527
if (result && result.error) {
2628
setError(result.error);
29+
} else if (result && result.success) {
30+
// Login successful, navigate to dashboard
31+
router.push("/");
2732
}
2833
} catch (e) {
2934
setError(

components/FlaggedPlatesTable.jsx

Lines changed: 158 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
"use client";
22

3-
import { useState } from "react";
3+
import { useState, useEffect, useTransition } from "react";
44
import { Badge } from "@/components/ui/badge";
5+
import { Button } from "@/components/ui/button";
6+
import { Input } from "@/components/ui/input";
7+
import { Label } from "@/components/ui/label";
58
import { Card, CardContent } from "@/components/ui/card";
9+
import {
10+
Dialog,
11+
DialogContent,
12+
DialogDescription,
13+
DialogFooter,
14+
DialogHeader,
15+
DialogTitle,
16+
DialogTrigger,
17+
} from "@/components/ui/dialog";
618
import {
719
Table,
820
TableBody,
@@ -11,62 +23,158 @@ import {
1123
TableHeader,
1224
TableRow,
1325
} from "@/components/ui/table";
26+
import { Plus } from "lucide-react";
27+
import { addDBPlate } from "@/app/actions";
1428

1529
export function FlaggedPlatesTable({ initialData }) {
16-
const [data] = useState(initialData);
30+
const [data, setData] = useState(initialData);
31+
const [open, setOpen] = useState(false);
32+
const [plateNumber, setPlateNumber] = useState("");
33+
const [isPending, startTransition] = useTransition();
34+
35+
// Update data when initialData prop changes (after revalidation)
36+
useEffect(() => {
37+
setData(initialData);
38+
}, [initialData]);
39+
40+
const handleSubmit = async (e) => {
41+
e.preventDefault();
42+
43+
const trimmedPlate = plateNumber.trim();
44+
if (!trimmedPlate) return;
45+
46+
if (trimmedPlate.length > 10) {
47+
alert("Plate number cannot exceed 10 characters");
48+
return;
49+
}
50+
51+
startTransition(async () => {
52+
try {
53+
const result = await addDBPlate(trimmedPlate.toUpperCase(), true);
54+
if (result.success) {
55+
setData((prev) => [
56+
{
57+
plate_number: trimmedPlate.toUpperCase(),
58+
tags: [],
59+
},
60+
...prev,
61+
]);
62+
setPlateNumber("");
63+
setOpen(false);
64+
}
65+
} catch (error) {
66+
console.error("Error adding flagged plate:", error);
67+
}
68+
});
69+
};
1770

1871
return (
19-
<Card className="mt-4 sm:mt-0 dark:bg-[#0e0e10]">
20-
<CardContent className="py-4 ">
21-
<Table>
22-
<TableHeader>
23-
<TableRow>
24-
<TableHead>Plate Number</TableHead>
25-
<TableHead>Tags</TableHead>
26-
</TableRow>
27-
</TableHeader>
28-
<TableBody>
29-
{data.length === 0 ? (
72+
<>
73+
<Card className="mt-4 sm:mt-0 dark:bg-[#0e0e10]">
74+
<CardContent className="py-4 ">
75+
<Table>
76+
<TableHeader>
3077
<TableRow>
31-
<TableCell colSpan={2} className="text-center py-4">
32-
No flagged plates found
33-
</TableCell>
78+
<TableHead>Plate Number</TableHead>
79+
<TableHead>Tags</TableHead>
3480
</TableRow>
35-
) : (
36-
data.map((plate) => (
37-
<TableRow key={plate.plate_number}>
38-
<TableCell className="font-medium font-mono text-[#F31260]">
39-
{plate.plate_number}
40-
</TableCell>
41-
<TableCell>
42-
<div className="flex flex-wrap items-center gap-1.5">
43-
{plate.tags?.length > 0 ? (
44-
plate.tags.map((tag) => (
45-
<Badge
46-
key={tag.name}
47-
variant="secondary"
48-
className="text-xs py-0.5 px-2"
49-
style={{
50-
backgroundColor: tag.color,
51-
color: "#fff",
52-
}}
53-
>
54-
{tag.name}
55-
</Badge>
56-
))
57-
) : (
58-
<div className="text-sm text-gray-500 dark:text-gray-400 italic">
59-
No tags
60-
</div>
61-
)}
62-
</div>
81+
</TableHeader>
82+
<TableBody>
83+
{data.length === 0 ? (
84+
<TableRow>
85+
<TableCell colSpan={2} className="text-center py-4">
86+
No flagged plates found
6387
</TableCell>
6488
</TableRow>
65-
))
66-
)}
67-
</TableBody>
68-
</Table>
69-
</CardContent>
70-
</Card>
89+
) : (
90+
data.map((plate) => (
91+
<TableRow key={plate.plate_number}>
92+
<TableCell className="font-medium font-mono text-[#F31260]">
93+
{plate.plate_number}
94+
</TableCell>
95+
<TableCell>
96+
<div className="flex flex-wrap items-center gap-1.5">
97+
{plate.tags?.length > 0 ? (
98+
plate.tags.map((tag) => (
99+
<Badge
100+
key={tag.name}
101+
variant="secondary"
102+
className="text-xs py-0.5 px-2"
103+
style={{
104+
backgroundColor: tag.color,
105+
color: "#fff",
106+
}}
107+
>
108+
{tag.name}
109+
</Badge>
110+
))
111+
) : (
112+
<div className="text-sm text-gray-500 dark:text-gray-400 italic">
113+
No tags
114+
</div>
115+
)}
116+
</div>
117+
</TableCell>
118+
</TableRow>
119+
))
120+
)}
121+
</TableBody>
122+
</Table>
123+
</CardContent>
124+
</Card>
125+
126+
<div className="flex justify-end items-center mt-4">
127+
<Dialog open={open} onOpenChange={setOpen}>
128+
<DialogTrigger asChild>
129+
<Button>
130+
<Plus className="h-4 w-4 mr-2" />
131+
Add Flagged Plate
132+
</Button>
133+
</DialogTrigger>
134+
<DialogContent className="sm:max-w-[425px]">
135+
<DialogHeader>
136+
<DialogTitle>Add Flagged Plate</DialogTitle>
137+
<DialogDescription>
138+
Add a new plate number to the flagged list for monitoring.
139+
</DialogDescription>
140+
</DialogHeader>
141+
<form onSubmit={handleSubmit}>
142+
<div className="grid gap-4 py-4">
143+
<div className="grid w-full items-center gap-2">
144+
<Label htmlFor="plate-number">Plate Number</Label>
145+
<Input
146+
id="plate-number"
147+
value={plateNumber}
148+
onChange={(e) => setPlateNumber(e.target.value)}
149+
onBlur={(e) => setPlateNumber(e.target.value.toUpperCase())}
150+
className="font-mono text-base p-2 h-10 w-full uppercase"
151+
placeholder="Enter Plate Number"
152+
maxLength={10}
153+
disabled={isPending}
154+
autoFocus
155+
/>
156+
</div>
157+
</div>
158+
<DialogFooter>
159+
<Button
160+
type="button"
161+
variant="outline"
162+
onClick={() => setOpen(false)}
163+
disabled={isPending}
164+
>
165+
Cancel
166+
</Button>
167+
<Button
168+
type="submit"
169+
disabled={isPending || !plateNumber.trim()}
170+
>
171+
{isPending ? "Adding..." : "Add Plate"}
172+
</Button>
173+
</DialogFooter>
174+
</form>
175+
</DialogContent>
176+
</Dialog>
177+
</div>
178+
</>
71179
);
72180
}

components/PlateTable.jsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1894,13 +1894,19 @@ export default function PlateTable({
18941894
<Input
18951895
id="new-plate"
18961896
value={correction?.newPlateNumber || ""}
1897-
onChange={(e) =>
1897+
onChange={(e) => {
1898+
setCorrection((curr) => ({
1899+
...curr,
1900+
newPlateNumber: e.target.value,
1901+
}));
1902+
}}
1903+
onBlur={(e) => {
18981904
setCorrection((curr) => ({
18991905
...curr,
19001906
newPlateNumber: e.target.value.toUpperCase(),
1901-
}))
1902-
}
1903-
className="font-mono text-base p-2 h-10 w-full" // Increased text size, padding, height, and added w-full
1907+
}));
1908+
}}
1909+
className="font-mono text-base p-2 h-10 w-full uppercase" // Increased text size, padding, height, and added w-full
19041910
placeholder="ENTER NEW PLATE NUMBER"
19051911
/>
19061912
</div>

lib/db.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,3 +1506,16 @@ export async function deleteMqttNotification(id) {
15061506
return { success: true };
15071507
});
15081508
}
1509+
1510+
export async function addUnseenPlate(plate_number, flagged = false) {
1511+
return withClient(async (client) => {
1512+
const result = await client.query(
1513+
`INSERT INTO plates (plate_number, flagged) VALUES ($1, $2)
1514+
ON CONFLICT (plate_number)
1515+
DO UPDATE SET flagged = $2
1516+
RETURNING *`,
1517+
[plate_number, flagged]
1518+
);
1519+
return result.rows[0];
1520+
});
1521+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "blueiris-alpr-database",
3-
"version": "0.1.8",
3+
"version": "0.1.9",
44
"private": true,
55
"scripts": {
66
"dev": "next dev",

0 commit comments

Comments
 (0)