Skip to content

Commit d4c79be

Browse files
committed
feat: ปรับการเข้าถึง role staff
1 parent e848120 commit d4c79be

File tree

12 files changed

+216
-109
lines changed

12 files changed

+216
-109
lines changed

client/my-app/src/components/features/cameras/CameraCardButton.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export default function BottomCameraCard({
3030
}: Props) {
3131
const router = useRouter();
3232
const { me, error: meError } = useMe();
33+
const restrictedRole = "staff";
3334

3435
const [open, setOpen] = useState(false);
3536
const [busyDelete, setBusyDelete] = useState(false);
@@ -144,7 +145,8 @@ export default function BottomCameraCard({
144145
<span className={active === "view" ? "text-[var(--color-primary)]" : ""}>View</span>
145146
</button>
146147

147-
{/* Edit */}
148+
{me?.usr_role !== restrictedRole && (
149+
// {/* Edit */}
148150
<button
149151
onClick={goEdit}
150152
type="button"
@@ -157,6 +159,7 @@ export default function BottomCameraCard({
157159
Settings
158160
</span>
159161
</button>
162+
)}
160163

161164
<EditCameraModal camId={camId} open={open} setOpen={setOpen} />
162165

@@ -174,7 +177,8 @@ export default function BottomCameraCard({
174177
</span>
175178
</button>
176179

177-
{/* Delete */}
180+
{me?.usr_role !== restrictedRole && (
181+
// {/* Delete */}
178182
<DeleteConfirmModal
179183
title={`Delete Camera – ${camName} (#${camCode})`}
180184
description={`This will remove this camera from the system. This action cannot be undone.`}
@@ -195,6 +199,7 @@ export default function BottomCameraCard({
195199
</button>
196200
}
197201
/>
202+
)}
198203
</div>
199204
);
200205
}

client/my-app/src/components/features/cameras/CameraTable.tsx

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export default function CameraTable({
119119
const statusParam = searchParams.get("status");
120120

121121
const { me, loading: loadingMe, error: meError } = useMe();
122+
const restrictedRole = "staff";
122123

123124
const [sortKey, setSortKey] = useState<SortKey | null>(null);
124125
const [sortOrder, setSortOrder] = useState<SortOrder>(null);
@@ -395,11 +396,12 @@ export default function CameraTable({
395396
>
396397
<Eye className="h-4 w-4" />
397398
</IconAction>
398-
399-
{/* Setting (เปิด modal เดิม) */}
400-
<IconAction label="Settings" variant="primary" onClick={() => goEdit(cam.camera_id)}>
401-
<Pencil className="h-4 w-4" />
402-
</IconAction>
399+
{me?.usr_role !== restrictedRole && (
400+
// {/* Setting (เปิด modal เดิม) */}
401+
<IconAction label="Settings" variant="primary" onClick={() => goEdit(cam.camera_id)}>
402+
<Pencil className="h-4 w-4" />
403+
</IconAction>
404+
)}
403405

404406
{/* Details */}
405407
<IconAction
@@ -411,16 +413,17 @@ export default function CameraTable({
411413
>
412414
<CircleAlert className="h-4 w-4" />
413415
</IconAction>
414-
415-
{/* Delete */}
416-
<IconAction
417-
label="Delete"
418-
variant="danger"
419-
disabled={busyId === cam.camera_id}
420-
onClick={() => openDelete(cam)}
421-
>
422-
<Trash2 className="h-4 w-4" />
423-
</IconAction>
416+
{me?.usr_role !== restrictedRole && (
417+
// {/* Delete */}
418+
<IconAction
419+
label="Delete"
420+
variant="danger"
421+
disabled={busyId === cam.camera_id}
422+
onClick={() => openDelete(cam)}
423+
>
424+
<Trash2 className="h-4 w-4" />
425+
</IconAction>
426+
)}
424427
</div>
425428
</TableCell>
426429
</TableRow>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"use client";
2+
3+
import { useMe } from "@/hooks/useMe";
4+
import CreateCameraForm from "@/components/forms/cameras/CreateCameraForm";
5+
import ToggleViewButton from "./ToggleViewButton";
6+
import RefreshButton from "./RefreshCamerasButton";
7+
8+
export default function CameraToolbar() {
9+
const { me } = useMe();
10+
const restrictedRole = "staff";
11+
12+
return (
13+
<div className="flex flex-col sm:flex-row gap-3 sm:items-center sm:justify-end">
14+
<ToggleViewButton />
15+
{me?.usr_role !== restrictedRole && <CreateCameraForm />}
16+
<RefreshButton />
17+
</div>
18+
);
19+
}

client/my-app/src/components/features/cameras/CameraView.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ import {
55
CameraSummaryProvider,
66
DashboardSummaryCameraSection,
77
} from "../../provider/CameraSummaryProvider";
8-
import ToggleViewButton from "@/components/features/cameras/ToggleViewButton";
9-
import CreateCameraForm from "@/components/forms/cameras/CreateCameraForm";
108
import { Separator } from "@/components/ui/separator";
11-
import RefreshButton from "@/components/features/cameras/RefreshCamerasButton";
9+
import CameraToolbar from "./CameraToolbar";
10+
1211
import { fetchWithAuth } from "@/lib/fetchWithAuth";
1312

1413
type ViewMode = "grid" | "list";
@@ -121,11 +120,7 @@ export default async function CameraView({
121120
Camera Management
122121
</label>
123122

124-
<div className="flex flex-col sm:flex-row gap-3 sm:items-center sm:justify-end">
125-
<ToggleViewButton />
126-
<CreateCameraForm />
127-
<RefreshButton />
128-
</div>
123+
<CameraToolbar />
129124
</div>
130125

131126
<Separator className="bg-[var(--color-primary-bg)] my-3" />

client/my-app/src/components/features/cameras/Details/CameraAccess.tsx

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

44
import { ReactNode, useEffect, useMemo, useState } from "react";
5+
import { useMe } from "@/hooks/useMe";
56
import {
67
Shield, LockKeyhole, UserLock, Clock, UserStar, ShieldUser, Users,
78
} from "lucide-react";
@@ -88,6 +89,9 @@ type Props =
8889

8990
/* ---------- Main ---------- */
9091
export default function CameraAccess(props: Props) {
92+
const { me } = useMe();
93+
const restrictedRole = "staff";
94+
9195
const camId = useMemo(() => {
9296
if ("camId" in props && typeof props.camId === "number") return props.camId;
9397
if ("camera" in props && props.camera) return Number(props.camera.camera_id);
@@ -216,21 +220,21 @@ export default function CameraAccess(props: Props) {
216220
setRequireAuth(v);
217221
void putPermission({ require_auth: v }, "auth");
218222
}}
219-
disabled={loading || !!saving}
223+
disabled={loading || !!saving || me?.usr_role === restrictedRole}
220224
/>
221225

222226
<Toggle
223227
label={<span className="flex items-center gap-2">
224228
<UserLock className="w-5 h-5 text-[var(--color-primary)]" />
225-
Restrict to Security Personnel
229+
Security Team Only
226230
</span>}
227-
hint="Only users in the Security group can access control operations."
231+
hint="If enabled, only Security users or higher can control. If disabled, all users can manage it."
228232
enabled={restrict}
229233
setEnabled={(v) => {
230234
setRestrict(v);
231235
void putPermission({ restrict: v }, "restrict");
232236
}}
233-
disabled={loading || !!saving}
237+
disabled={loading || !!saving || me?.usr_role === restrictedRole}
234238
/>
235239

236240
<Toggle
@@ -244,7 +248,7 @@ export default function CameraAccess(props: Props) {
244248
setLogAccess(v);
245249
void putPermission({ log: v }, "log");
246250
}}
247-
disabled={loading || !!saving}
251+
disabled={loading || !!saving || me?.usr_role === restrictedRole}
248252
/>
249253
</SectionCard>
250254

client/my-app/src/components/features/cameras/Details/CameraDetails.tsx

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,7 @@
11
'use client';
22

33
import { useState } from "react";
4-
import {
5-
Table,
6-
TableBody,
7-
TableCaption,
8-
TableCell,
9-
TableFooter,
10-
TableHead,
11-
TableHeader,
12-
TableRow,
13-
} from "@/components/ui/table"
4+
145
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
156
import { Button } from "@/components/ui/button";
167
import { Camera } from "@/app/models/cameras.model";
@@ -24,9 +15,6 @@ export default function CameraDetails({ camera }: { camera: Camera }) {
2415

2516
const [currentCamera, setCurrentCamera] = useState(camera);
2617

27-
const imageSrc = "/library-room.jpg";
28-
const videoSrc = "/footage-library-room.mp4";
29-
3018
const camCode = `CAM${String(currentCamera.camera_id).padStart(3, "0")}`;
3119

3220
function onBack() {

client/my-app/src/components/features/cameras/Details/EventDetection.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"use client";
22

33
import * as React from "react";
4+
import { useMe } from "@/hooks/useMe";
5+
import { useCameraPermission } from "@/hooks/useCameraPermission";
46
import * as Lucide from "lucide-react";
57
import {
68
Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
@@ -101,8 +103,13 @@ type ApiRow = {
101103
};
102104

103105
export default function EventDetectionTable({ camera }: { camera: Camera }) {
106+
const { me } = useMe();
107+
const restrictedRole = "staff";
108+
104109
const camId = Number(camera?.camera_id ?? 0);
105110

111+
const { permission } = useCameraPermission(camId);
112+
106113
const [events, setEvents] = React.useState<EventRow[]>([]);
107114
const [loading, setLoading] = React.useState(true);
108115
const [err, setErr] = React.useState<string>("");
@@ -273,7 +280,7 @@ export default function EventDetectionTable({ camera }: { camera: Camera }) {
273280
<Select
274281
value={ev.sensitivity}
275282
onValueChange={(v) => updateOptimistic(ev.id, "sensitivity", v as UiLevel)}
276-
disabled={disabled}
283+
disabled={disabled || (permission?.permission_restrict && me?.usr_role === restrictedRole)}
277284
>
278285
<SelectTrigger className="w-fit p-0 border-0 bg-transparent shadow-none focus:ring-0 focus:outline-none focus:border-0">
279286
<div className="py-1">
@@ -299,7 +306,7 @@ export default function EventDetectionTable({ camera }: { camera: Camera }) {
299306
<Select
300307
value={ev.priority}
301308
onValueChange={(v) => updateOptimistic(ev.id, "priority", v as UiLevel)}
302-
disabled={disabled}
309+
disabled={disabled || (permission?.permission_restrict && me?.usr_role === restrictedRole)}
303310
>
304311
<SelectTrigger className="w-fit p-0 border-0 bg-transparent shadow-none focus:ring-0 focus:outline-none focus:border-0">
305312
<div className="py-1">
@@ -326,7 +333,7 @@ export default function EventDetectionTable({ camera }: { camera: Camera }) {
326333
<Switch
327334
checked={ev.status}
328335
onCheckedChange={(v) => updateOptimistic(ev.id, "status", v)}
329-
disabled={disabled}
336+
disabled={disabled || (permission?.permission_restrict && me?.usr_role === restrictedRole)}
330337
className="[--track:theme(colors.slate.300)] data-[state=checked]:[--track:var(--color-primary)]"
331338
/>
332339
<span className="ml-2 text-sm text-slate-700">

client/my-app/src/components/features/cameras/Details/MaintenanceHistoryTable.tsx

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"use client";
22

33
import React, { useEffect, useMemo, useState } from "react";
4+
import { useMe } from "@/hooks/useMe";
5+
import { useCameraPermission } from "@/hooks/useCameraPermission";
46
import {
57
Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
68
} from "@/components/ui/table";
@@ -470,8 +472,13 @@ function EditMaintenanceModal({
470472
Main: CameraMaintenance
471473
========================================================= */
472474
export default function CameraMaintenance({ camera }: { camera: Camera }) {
475+
const { me } = useMe();
476+
const restrictedRole = "staff";
477+
473478
const camId = Number(camera?.camera_id ?? 0);
474479

480+
const { permission } = useCameraPermission(camId);
481+
475482
const [records, setRecords] = useState<Row[]>([]);
476483
const [loading, setLoading] = useState(true);
477484
const [err, setErr] = useState("");
@@ -647,7 +654,9 @@ export default function CameraMaintenance({ camera }: { camera: Camera }) {
647654
<h3 className="font-semibold text-[var(--color-primary)]">
648655
Maintenance
649656
</h3>
650-
<AddMaintenanceModal camId={camId} onAdded={onAdded} />
657+
{me?.usr_role !== restrictedRole && (
658+
<AddMaintenanceModal camId={camId} onAdded={onAdded} />
659+
)}
651660
</div>
652661

653662
{loading && (
@@ -706,9 +715,11 @@ export default function CameraMaintenance({ camera }: { camera: Camera }) {
706715
{renderSortIcon("notes")}
707716
</div>
708717
</TableHead>
709-
<TableHead className="w-[96px] text-[var(--color-primary)] text-left font-medium">
710-
Actions
711-
</TableHead>
718+
{!(permission?.permission_restrict && me?.usr_role === restrictedRole) && (
719+
<TableHead className="w-[96px] text-[var(--color-primary)] text-left font-medium">
720+
Actions
721+
</TableHead>
722+
)}
712723
</TableRow>
713724
</TableHeader>
714725

@@ -751,26 +762,28 @@ export default function CameraMaintenance({ camera }: { camera: Camera }) {
751762
{rec.notes || "-"}
752763
</TableCell>
753764

754-
<TableCell className="px-2 py-3 align-top text-left">
755-
<div className="flex items-center gap-2">
756-
{/* Edit (ghost→full primary) */}
757-
<EditMaintenanceModal row={rec} onUpdated={onUpdated} />
758-
759-
{/* Delete (ghost→full danger) */}
760-
<IconGhostFullBtn
761-
label="Delete"
762-
variant="danger"
763-
disabled={busyDelete}
764-
onClick={() => {
765-
if (busyDelete) return;
766-
setDeleteTarget(rec);
767-
setDeleteOpen(true);
768-
}}
769-
>
770-
<Trash2 className="h-4 w-4" />
771-
</IconGhostFullBtn>
772-
</div>
773-
</TableCell>
765+
{!(permission?.permission_restrict && me?.usr_role === restrictedRole) && (
766+
<TableCell className="px-2 py-3 align-top text-left">
767+
<div className="flex items-center gap-2">
768+
{/* Edit (ghost→full primary) */}
769+
<EditMaintenanceModal row={rec} onUpdated={onUpdated} />
770+
771+
{/* Delete (ghost→full danger) */}
772+
<IconGhostFullBtn
773+
label="Delete"
774+
variant="danger"
775+
disabled={busyDelete}
776+
onClick={() => {
777+
if (busyDelete) return;
778+
setDeleteTarget(rec);
779+
setDeleteOpen(true);
780+
}}
781+
>
782+
<Trash2 className="h-4 w-4" />
783+
</IconGhostFullBtn>
784+
</div>
785+
</TableCell>
786+
)}
774787
</TableRow>
775788
);
776789
})}

0 commit comments

Comments
 (0)