Skip to content

Commit 7c74768

Browse files
authored
Merge pull request #51 from algertc/dev
1.8 Release
2 parents 96713e2 + 42d89f0 commit 7c74768

File tree

10 files changed

+422
-82
lines changed

10 files changed

+422
-82
lines changed

Dockerfile

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,49 @@
1-
FROM node:18-bullseye AS builder
1+
FROM node:20-bullseye AS builder
22
WORKDIR /app
33
COPY package.json next.config.js ./
4-
RUN yarn install
4+
5+
RUN apt-get update && apt-get install -y \
6+
build-essential \
7+
libcairo2-dev \
8+
libpango1.0-dev \
9+
libjpeg-dev \
10+
libgif-dev \
11+
librsvg2-dev \
12+
python3 \
13+
&& rm -rf /var/lib/apt/lists/*
14+
15+
16+
# A bunch of canvas bs
17+
ENV npm_config_canvas_binary_host_mirror=https://github.com/Automattic/node-canvas/releases/download/
18+
ENV CXXFLAGS="-DSYZX_FEATURE_FLAG=1"
19+
20+
COPY package.json yarn.lock* ./
21+
RUN yarn install --network-timeout 100000 || \
22+
(echo "Retrying with canvas workaround..." && \
23+
yarn add canvas@2.11.2 --network-timeout 100000 && \
24+
yarn install --network-timeout 100000)
25+
26+
527
COPY . .
628
RUN yarn build
729

830

9-
FROM node:18-bullseye
31+
FROM node:20-bullseye
1032
WORKDIR /app
33+
34+
RUN apt-get update && apt-get install -y \
35+
libcairo2 \
36+
libpango-1.0-0 \
37+
libpangocairo-1.0-0 \
38+
libjpeg62-turbo \
39+
libgif7 \
40+
librsvg2-2 \
41+
&& rm -rf /var/lib/apt/lists/*
42+
1143
COPY --from=builder /app/.next ./.next
1244
COPY --from=builder /app/public ./public
1345
COPY --from=builder /app /app
46+
COPY --from=builder /app/node_modules ./node_modules
1447
COPY --from=builder /app/package.json ./package.json
1548
EXPOSE 3000
1649
RUN mkdir -p /auth

app/actions.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
markUpdateComplete,
4646
updateTagName,
4747
getTrainingRecordCount,
48+
confirmPlateRecord,
4849
} from "@/lib/db";
4950
import {
5051
getNotificationPlates as getNotificationPlatesDB,
@@ -1149,3 +1150,14 @@ export async function processTrainingData() {
11491150
return { success: false, error: error.message };
11501151
}
11511152
}
1153+
1154+
export async function validatePlateRecord(readId, value) {
1155+
try {
1156+
await confirmPlateRecord(readId, value);
1157+
1158+
return { success: true };
1159+
} catch (error) {
1160+
console.error("Error validating plate record:", error);
1161+
return { success: false, error: "Failed to validate plate record" };
1162+
}
1163+
}

app/dashboard/CameraChart.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export function CameraReadsChart({ data, loading }) {
7979
}}
8080
barGap={4}
8181
>
82-
<CartesianGrid vertical={false} strokeWidth={0.2} />
82+
<CartesianGrid vertical={false} strokeWidth={0.25} />
8383
<XAxis
8484
dataKey="camera"
8585
tickLine={false}
@@ -143,7 +143,7 @@ export function CameraReadsChart({ data, loading }) {
143143
</div>
144144
)}
145145
</CardContent>
146-
<CardFooter className="flex-col items-start gap-1 text-xs pt-2 md:pb-0">
146+
<CardFooter className="flex-col items-start gap-1 text-xs pt-2">
147147
<div className="flex gap-2 font-medium leading-none items-center">
148148
Most Active: {mostActiveCamera}
149149
<Camera className="h-4 w-4" />

components/LiveRecognitionViewer.jsx

Lines changed: 62 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ export default function LiveRecognitionViewer({
328328
<div className="flex flex-col gap-4">
329329
{/* Plate number - largest and most prominent */}
330330
<div className="bg-background dark:bg-[#0e0e10] rounded-lg border p-5 flex-grow-0">
331-
<div className="flex justify-between items-start">
331+
<div className="flex justify-between items-start w-80 2xl:w-[22rem]">
332332
<div>
333333
<div className="text-4xl font-mono font-bold tracking-wider">
334334
{latestPlate.plate_number}
@@ -351,9 +351,9 @@ export default function LiveRecognitionViewer({
351351
<Calendar className="h-4 w-4 mr-2 text-muted-foreground" />
352352
{formatTimestamp(latestPlate.timestamp)}
353353
</div>
354-
<div className="font-medium">
354+
{/* <div className="font-medium">
355355
Confidence: {formatConfidence(latestPlate.confidence)}
356-
</div>
356+
</div> */}
357357
</div>
358358
</div>
359359

@@ -363,31 +363,64 @@ export default function LiveRecognitionViewer({
363363
<div className="bg-background dark:bg-[#0e0e10] rounded-lg border p-4">
364364
<h3 className="text-sm font-medium mb-3">Cropped Plate</h3>
365365
<div className="flex items-center justify-center bg-black/5 rounded overflow-hidden p-1">
366-
<div
367-
style={{
368-
position: "relative",
369-
width: `${
370-
latestPlate.crop_coordinates[2] -
371-
latestPlate.crop_coordinates[0]
372-
}px`,
373-
height: `${
374-
latestPlate.crop_coordinates[3] -
375-
latestPlate.crop_coordinates[1]
376-
}px`,
377-
overflow: "hidden",
378-
}}
379-
>
380-
<img
381-
src={getImageSrc(latestPlate)}
382-
alt={`License plate ${latestPlate.plate_number}`}
383-
style={{
384-
position: "absolute",
385-
left: `-${latestPlate.crop_coordinates[0]}px`,
386-
top: `-${latestPlate.crop_coordinates[1]}px`,
387-
maxWidth: "none",
388-
}}
389-
/>
390-
</div>
366+
{(() => {
367+
// Original crop dimensions
368+
const cropWidth =
369+
latestPlate.crop_coordinates[2] -
370+
latestPlate.crop_coordinates[0];
371+
const cropHeight =
372+
latestPlate.crop_coordinates[3] -
373+
latestPlate.crop_coordinates[1];
374+
375+
// Calculate aspect ratio
376+
const aspectRatio = cropWidth / cropHeight;
377+
378+
// Minimum dimensions (as percentages of container width)
379+
const minWidthPercent = 50; // 50% of container width
380+
const containerWidth = 80 * 4 - 32; // w-80 = 20rem = 320px, minus padding
381+
382+
// Calculate minimum width in pixels (based on container)
383+
const minWidth = (containerWidth * minWidthPercent) / 100;
384+
385+
// Calculate scaling based on minimum width
386+
const scale =
387+
cropWidth < minWidth ? minWidth / cropWidth : 1;
388+
389+
// Calculate dimensions respecting aspect ratio
390+
const finalWidth = Math.round(cropWidth * scale);
391+
const finalHeight = Math.round(cropHeight * scale);
392+
393+
return (
394+
<div className="relative w-full flex justify-center">
395+
<div
396+
style={{
397+
position: "relative",
398+
width: `${finalWidth}px`,
399+
height: `${finalHeight}px`,
400+
maxWidth: "100%",
401+
overflow: "hidden",
402+
}}
403+
>
404+
<img
405+
src={getImageSrc(latestPlate)}
406+
alt={`License plate ${latestPlate.plate_number}`}
407+
style={{
408+
position: "absolute",
409+
left: `-${
410+
latestPlate.crop_coordinates[0] * scale
411+
}px`,
412+
top: `-${
413+
latestPlate.crop_coordinates[1] * scale
414+
}px`,
415+
maxWidth: "none",
416+
transform: `scale(${scale})`,
417+
transformOrigin: "top left",
418+
}}
419+
/>
420+
</div>
421+
</div>
422+
);
423+
})()}
391424
</div>
392425
</div>
393426
)}
@@ -461,7 +494,7 @@ export default function LiveRecognitionViewer({
461494
</CardTitle>
462495
</CardHeader>
463496
<CardContent>
464-
<div className="max-h-[350px] overflow-y-auto pr-2">
497+
<div className="h-full overflow-y-auto pr-2">
465498
{plateInsights.recentReads?.length > 0 ? (
466499
<div className="space-y-4">
467500
{plateInsights.recentReads.map((read, index) => (

components/PlateTable.jsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import {
2323
ZoomIn,
2424
MoreHorizontal,
2525
SlidersHorizontal,
26+
CircleCheck,
27+
Check,
2628
} from "lucide-react";
2729
import { Badge } from "@/components/ui/badge";
2830
import { Button } from "@/components/ui/button";
@@ -121,6 +123,7 @@ export default function PlateTable({
121123
onRemoveTag,
122124
onAddKnownPlate,
123125
onDeleteRecord,
126+
onValidate,
124127
availableCameras,
125128
onCorrectPlate,
126129
timeFormat = 12,
@@ -247,6 +250,7 @@ export default function PlateTable({
247250
thumbnail: thumbnailUrl,
248251
plateNumber: plate.plate_number,
249252
id: plate.id,
253+
validated: plate.validated,
250254
bi_path: bi_url,
251255
crop_coordinates: plate.crop_coordinates,
252256
});
@@ -256,6 +260,19 @@ export default function PlateTable({
256260
setPosition({ x: 0, y: 0 });
257261
};
258262

263+
useEffect(() => {
264+
if (selectedImage && data && data.length > 0) {
265+
const currentPlate = data.find((plate) => plate.id === selectedImage.id);
266+
267+
if (currentPlate && currentPlate.validated !== selectedImage.validated) {
268+
setSelectedImage((prev) => ({
269+
...prev,
270+
validated: currentPlate.validated,
271+
}));
272+
}
273+
}
274+
}, [data, selectedImage]);
275+
259276
const handleDownloadImage = async () => {
260277
if (!selectedImage) return;
261278

@@ -1304,6 +1321,24 @@ export default function PlateTable({
13041321
<ExternalLink className="h-4 w-4" />
13051322
</Button>
13061323
)}
1324+
<Button
1325+
variant="ghost"
1326+
size="icon"
1327+
className={
1328+
plate?.validated
1329+
? "text-green-500 hover:text-green-700"
1330+
: ""
1331+
}
1332+
onClick={() => {
1333+
onValidate(plate.id, !plate.validated);
1334+
}}
1335+
>
1336+
{plate?.validated ? (
1337+
<CircleCheck className="h-4 w-4" />
1338+
) : (
1339+
<Check className="h-4 w-4" />
1340+
)}
1341+
</Button>
13071342

13081343
<Button
13091344
variant="ghost"
@@ -1692,6 +1727,21 @@ export default function PlateTable({
16921727
))}
16931728
</DropdownMenuContent>
16941729
</DropdownMenu>
1730+
<Button
1731+
variant="outline"
1732+
size="sm"
1733+
className={
1734+
selectedImage?.validated
1735+
? "text-xs sm:text-sm text-green-500"
1736+
: "text-xs sm:text-sm"
1737+
}
1738+
onClick={() => {
1739+
onValidate(selectedImage.id, !selectedImage.validated);
1740+
}}
1741+
>
1742+
<Check className="h-3 w-3 sm:h-4 sm:w-4 mr-1 sm:mr-2" />
1743+
<span className="whitespace-nowrap">Confirm AI Label</span>
1744+
</Button>
16951745
</div>
16961746
<div className="flex justify-end space-x-2">
16971747
{biHost && selectedImage?.bi_path && (

components/PlateTableWrapper.jsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
deletePlateRead,
1010
tagPlate,
1111
untagPlate,
12+
validatePlateRecord,
1213
} from "@/app/actions";
1314

1415
export default function PlateTableWrapper({
@@ -109,6 +110,14 @@ export default function PlateTableWrapper({
109110
return result;
110111
};
111112

113+
const handleValidatePlate = async (id, value) => {
114+
const result = await validatePlateRecord(id, value);
115+
if (result.success) {
116+
router.refresh();
117+
}
118+
return result;
119+
};
120+
112121
const handleSort = (field) => {
113122
const currentSortField = params.get("sortField") || "";
114123
const currentSortDirection = params.get("sortDirection") || "desc";
@@ -170,6 +179,7 @@ export default function PlateTableWrapper({
170179
onAddKnownPlate={handleAddKnownPlate}
171180
onDeleteRecord={handleDeleteRecord}
172181
onCorrectPlate={handleCorrectPlate}
182+
onValidate={handleValidatePlate}
173183
onLiveChange={(isLive) => {
174184
if (isLive) {
175185
const interval = setInterval(() => {

0 commit comments

Comments
 (0)