Skip to content

Commit 31c901f

Browse files
committed
Merge branch 'develop'
2 parents 89c1cff + ddddec1 commit 31c901f

30 files changed

+1906
-251
lines changed

client/my-app/package-lock.json

Lines changed: 82 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/my-app/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
"@radix-ui/react-label": "^2.1.7",
1616
"@radix-ui/react-popover": "^1.1.15",
1717
"@radix-ui/react-scroll-area": "^1.2.10",
18+
"@radix-ui/react-select": "^2.2.6",
1819
"@radix-ui/react-separator": "^1.1.7",
1920
"@radix-ui/react-slot": "^1.2.3",
2021
"class-variance-authority": "^0.7.1",
2122
"clsx": "^2.1.1",
22-
"date-fns": "^4.1.0",
2323
"cmdk": "^1.1.1",
24+
"date-fns": "^4.1.0",
2425
"lucide-react": "^0.539.0",
2526
"next": "15.4.5",
2627
"react": "19.1.0",

client/my-app/src/app/(with-layout)/alerts/page.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import AlertTable, { type Alert } from "../../components/AlertTable";
22
import * as StatusCard from "../../components/StatusCard";
3+
import { Separator } from "@/components/ui/separator";
34

45
const base = process.env.NEXT_PUBLIC_APP_URL!;
56

@@ -21,10 +22,64 @@ export default async function AlertsPage() {
2122
<StatusCard.DashboardSummaryAlertSection></StatusCard.DashboardSummaryAlertSection>
2223

2324
<div className="rounded-lg bg-[var(--color-white)] shadow-md p-6">
25+
<div className="flex flex-col sm:flex-row sm:flex-wrap sm:items-start gap-3 justify-center mb-3">
26+
<label
27+
htmlFor="AlertManagement"
28+
className="min-w-0 flex-1 font-bold text-lg text-[var(--color-primary)]"
29+
>
30+
Alert Management
31+
</label>
32+
33+
{/* <div className="flex gap-3">
34+
<ToggleViewButton />
35+
<CreateEventForm />
36+
</div> */}
37+
</div>
38+
39+
<Separator className="bg-[var(--color-primary-bg)] my-3" />
40+
41+
2442
<div className="grid grid-cols-[repeat(auto-fit,minmax(320px,1fr))] gap-6">
2543
<AlertTable alerts={alerts} />
2644
</div>
2745
</div>
46+
47+
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
48+
<div className="rounded-lg bg-[var(--color-white)] shadow-md p-6 ">
49+
<div className="flex flex-wrap items-start gap-3 justify-center mb-3">
50+
<label
51+
htmlFor="AlertTrends"
52+
className="min-w-0 flex-1 font-bold text-lg text-[var(--color-primary)]"
53+
>
54+
Alert Trends
55+
</label>
56+
</div>
57+
<Separator className="bg-[var(--color-primary-bg)]" />
58+
</div>
59+
<div className="rounded-lg bg-[var(--color-white)] shadow-md p-6 ">
60+
<div className="flex flex-wrap items-start gap-3 justify-center mb-3">
61+
<label
62+
htmlFor="AlertDistribution"
63+
className="min-w-0 flex-1 font-bold text-lg text-[var(--color-primary)]"
64+
>
65+
Alert Distribution by Event Type
66+
</label>
67+
</div>
68+
<Separator className="bg-[var(--color-primary-bg)]" />
69+
</div>
70+
</div>
71+
72+
<div className="rounded-lg bg-[var(--color-white)] shadow-md p-6 ">
73+
<div className="flex flex-wrap items-start gap-3 justify-center mb-3">
74+
<label
75+
htmlFor="cameraName"
76+
className="min-w-0 flex-1 font-bold text-lg text-[var(--color-primary)]"
77+
>
78+
Recent Camera Activity
79+
</label>
80+
</div>
81+
<Separator className="bg-[var(--color-primary-bg)]" />
82+
</div>
2883
</div>
2984
);
3085
}

client/my-app/src/app/(with-layout)/cameras/page.tsx

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,29 @@ import CreateEventForm from "@/app/components/CreateEventForm";
33
import { Separator } from "@/components/ui/separator";
44
import ToggleViewButton from "@/app/components/ToggleViewButton";
55
import CameraView from "@/app/components/CameraView";
6+
import SearchCamerasInput from "@/app/components/SearchCamerasInput";
7+
import CameraFilters from "@/app/components/CameraFilters";
8+
import CreateCameraForm from "@/app/components/CreateCameraForm";
69

710
type ViewMode = "grid" | "list";
811

912
export default async function CamerasPage({
1013
searchParams,
1114
}: {
12-
searchParams?: { view?: ViewMode };
15+
searchParams?: {
16+
view?: ViewMode;
17+
q?: string;
18+
status?: 'Active' | 'Inactive';
19+
location?: string;
20+
type?: string;
21+
};
1322
}) {
23+
1424
const viewMode: ViewMode = searchParams?.view === "list" ? "list" : "grid";
25+
const q = searchParams?.q ?? "";
26+
const status = searchParams?.status;
27+
const location = searchParams?.location;
28+
const type = searchParams?.type;
1529

1630
return (
1731
<div className="space-y-6">
@@ -28,13 +42,28 @@ export default async function CamerasPage({
2842

2943
<div className="flex gap-3">
3044
<ToggleViewButton />
31-
<CreateEventForm />
45+
<CreateCameraForm />
3246
</div>
3347
</div>
3448

35-
<Separator className="bg-[var(--color-primary-bg)] mb-3" />
49+
<Separator className="bg-[var(--color-primary-bg)] my-3" />
50+
51+
<div className="grid grid-cols-1 sm:grid-cols-[1fr_auto] items-start gap-2 sm:gap-3 mt-3">
52+
<div className="w-full">
53+
<SearchCamerasInput />
54+
</div>
55+
<div className="w-full sm:w-auto">
56+
<CameraFilters />
57+
</div>
58+
</div>
3659

37-
<CameraView viewMode={viewMode} />
60+
<CameraView
61+
viewMode={viewMode}
62+
search={q}
63+
status={status}
64+
location={location}
65+
type={type}
66+
/>
3867
</div>
3968

4069
<div className="rounded-lg bg-[var(--color-white)] shadow-md p-6 ">

client/my-app/src/app/components/AlertTable.tsx

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,45 @@ import { Eye, CheckCircle2, XCircle, MapPin, ArrowUpDown, ArrowUp, ArrowDown } f
77
import * as Icons from "lucide-react";
88
import type { LucideProps } from "lucide-react";
99

10+
const SEVERITY_STYLES = {
11+
critical: { pill: "bg-rose-50 text-rose-700 ring-rose-200", Icon: Icons.TriangleAlert },
12+
high: { pill: "bg-orange-50 text-orange-700 ring-orange-200", Icon: Icons.CircleAlert },
13+
medium: { pill: "bg-yellow-50 text-yellow-700 ring-yellow-200", Icon: Icons.Minus },
14+
low: { pill: "bg-emerald-50 text-emerald-700 ring-emerald-200", Icon: Icons.ArrowDown },
15+
default: { pill: "bg-slate-50 text-slate-700 ring-slate-200", Icon: Icons.CircleAlert },
16+
} as const;
17+
18+
function SeverityBadge({ value }: { value?: string }) {
19+
const key = (value ?? "").trim().toLowerCase() as keyof typeof SEVERITY_STYLES;
20+
const { pill, Icon } = SEVERITY_STYLES[key] ?? SEVERITY_STYLES.default;
21+
return (
22+
<span className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium ring-1 ring-inset ${pill}`}>
23+
<Icon className="w-3.5 h-3.5" aria-hidden="true" />
24+
<span className="capitalize">{value ?? "Unknown"}</span>
25+
</span>
26+
);
27+
}
28+
29+
const STATUS_STYLES = {
30+
active: "bg-emerald-50 text-emerald-700 ring-emerald-200",
31+
resolved: "bg-sky-50 text-sky-700 ring-sky-200",
32+
dismissed: "bg-rose-50 text-rose-700 ring-rose-200",
33+
// pending: "bg-amber-50 text-amber-700 ring-amber-200",
34+
// acknowledged: "bg-indigo-50 text-indigo-700 ring-indigo-200",
35+
// inactive: "bg-slate-100 text-slate-700 ring-slate-300",
36+
default: "bg-slate-50 text-slate-700 ring-slate-200",
37+
} as const;
38+
39+
function StatusBadge({ value }: { value?: string }) {
40+
const key = (value ?? "").trim().toLowerCase() as keyof typeof STATUS_STYLES;
41+
const pill = STATUS_STYLES[key] ?? STATUS_STYLES.default;
42+
return (
43+
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ring-1 ring-inset ${pill}`}>
44+
<span className="capitalize">{value ?? "Unknown"}</span>
45+
</span>
46+
);
47+
}
48+
1049
export type Alert = {
1150
id: number;
1251
severity: string;
@@ -126,12 +165,31 @@ export default function AlertTable({ alerts }: Props) {
126165
<TableBody>
127166
{sortedAlerts.map((alr) => {
128167
const EventIcon = iconFromName(alr.event?.icon);
168+
const alrCode = `ALT${String(alr.id).padStart(3, "0")}`;
169+
129170
return (
130171
<TableRow key={alr.id}>
131-
<TableCell>{alr.severity}</TableCell>
132-
<TableCell>{alr.id}</TableCell>
133-
<TableCell>{alr.create_date} {alr.create_time}</TableCell>
134-
<TableCell>{alr.camera.name}</TableCell>
172+
<TableCell>
173+
<SeverityBadge value={alr.severity} />
174+
</TableCell>
175+
176+
<TableCell>{alrCode}</TableCell>
177+
178+
<TableCell>
179+
<div className="flex items-center gap-2">
180+
<Icons.Clock3 className="h-4 w-4 text-[var(--color-primary)]" aria-hidden="true" />
181+
<span>{alr.create_date} {alr.create_time}</span>
182+
</div>
183+
</TableCell>
184+
{/* <TableCell>{alr.create_date} {alr.create_time}</TableCell> */}
185+
186+
<TableCell>
187+
<div className="flex items-center gap-2">
188+
<Icons.Camera className="h-4 w-4 text-[var(--color-primary)]" aria-hidden="true" />
189+
<span>{alr.camera.name}</span>
190+
</div>
191+
</TableCell>
192+
{/* <TableCell>{alr.camera.name}</TableCell> */}
135193

136194
{/* Event Type + icon */}
137195
<TableCell>
@@ -149,21 +207,30 @@ export default function AlertTable({ alerts }: Props) {
149207
</div>
150208
</TableCell>
151209

152-
<TableCell>{alr.status}</TableCell>
210+
<TableCell>
211+
<StatusBadge value={alr.status} />
212+
</TableCell>
153213

154214
<TableCell className="flex gap-2">
155-
<button className="inline-flex items-center gap-2 bg-[var(--color-primary)] text-white px-4 py-1 rounded-sm hover:opacity-90">
215+
{/* ปุ่ม View แสดงเสมอ */}
216+
<button className="inline-flex items-center justify-center gap-2 px-3 py-1 rounded-sm bg-white border border-[var(--color-primary)] text-[var(--color-primary)] hover:bg-[var(--color-primary)] hover:border-[var(--color-primary)] hover:text-white transition focus:outline-none focus:ring-2 focus:ring-offset-2">
156217
<Eye className="h-4 w-4" aria-hidden="true" />
157218
<span>View</span>
158219
</button>
159-
<button className="inline-flex items-center gap-2 bg-[var(--color-success)] text-white px-4 py-1 rounded-sm hover:opacity-90">
160-
<CheckCircle2 className="h-4 w-4" aria-hidden="true" />
161-
<span>Resolved</span>
162-
</button>
163-
<button className="inline-flex items-center gap-2 bg-[var(--color-danger)] text-white px-4 py-1 rounded-sm hover:opacity-90">
164-
<XCircle className="h-4 w-4" aria-hidden="true" />
165-
<span>Dismiss</span>
166-
</button>
220+
221+
{/* แสดงปุ่ม Resolved / Dismiss เฉพาะเมื่อสถานะเป็น Active */}
222+
{alr.status?.trim().toLowerCase() === "active" && (
223+
<>
224+
<button className="inline-flex items-center justify-center gap-2 px-3 py-1 rounded-sm bg-white border border-[var(--color-success)] text-[var(--color-success)] hover:bg-[var(--color-success)] hover:border-[var(--color-success)] hover:text-white transition focus:outline-none focus:ring-2 focus:ring-offset-2">
225+
<CheckCircle2 className="h-4 w-4" aria-hidden="true" />
226+
<span>Resolved</span>
227+
</button>
228+
<button className="inline-flex items-center justify-center gap-2 px-3 py-1 rounded-sm bg-white border border-[var(--color-danger)] text-[var(--color-danger)] hover:bg-[var(--color-danger)] hover:border-[var(--color-danger)] hover:text-white transition focus:outline-none focus:ring-2 focus:ring-offset-2">
229+
<XCircle className="h-4 w-4" aria-hidden="true" />
230+
<span>Dismiss</span>
231+
</button>
232+
</>
233+
)}
167234
</TableCell>
168235
</TableRow>
169236
);

0 commit comments

Comments
 (0)