From ce65be4f9ac375023f9a892f60eedafd8957d9cf Mon Sep 17 00:00:00 2001 From: Basichy Date: Fri, 31 Jul 2026 00:32:01 +1000 Subject: [PATCH 1/2] Feature Admin Panel, Add read-only shift oversight table --- app-frontend/admin-panel/src/pages/Shifts.jsx | 180 +++++++++++++++++- 1 file changed, 178 insertions(+), 2 deletions(-) diff --git a/app-frontend/admin-panel/src/pages/Shifts.jsx b/app-frontend/admin-panel/src/pages/Shifts.jsx index 88f6fb050..89d34a8dc 100644 --- a/app-frontend/admin-panel/src/pages/Shifts.jsx +++ b/app-frontend/admin-panel/src/pages/Shifts.jsx @@ -1,8 +1,184 @@ +import { useEffect, useMemo, useState } from 'react'; +import { getShifts } from '../service/adminAPI'; +import DataTable from '../components/DataTable'; +import LoadingComponent from '../components/LoadingComponent'; +import SearchFilter from '../components/SearchFilter'; + +// List of status options for the filter +const STATUS_OPTIONS = ['draft', 'open', 'applied', 'assigned', 'completed']; + +// Number of shifts shown per page +const PAGE_SIZE = 20; + +// Format the shift date +function formatDate(d) { + if (!d) return '\u2014'; + const parsed = new Date(d); + return Number.isNaN(parsed.getTime()) ? '\u2014' : parsed.toLocaleDateString(); +} + +// Format the shift start and end times +function formatTimes(r) { + if (!r.startTime && !r.endTime) return '\u2014'; + return `${r.startTime || '\u2014'} \u2013 ${r.endTime || '\u2014'}`; +} + +// Show a person's name or email if no name exists +function personLabel(person) { + if (!person) return '\u2014'; + return person.name || person.email || '\u2014'; +} + +// Read-only admin oversight of all shifts export default function Shifts() { + const [shifts, setShifts] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [query, setQuery] = useState(''); + const [status, setStatus] = useState(''); + const [page, setPage] = useState(1); + + // Load all shifts when the page opens + useEffect(() => { + let mounted = true; + (async () => { + try { + setLoading(true); + setError(''); + + // Get shifts from the backend + const data = await getShifts(); + const list = Array.isArray(data) ? data : data.shifts || data.data || []; + if (mounted) setShifts(list); + } catch (err) { + if (mounted) setError(err?.response?.data?.message || 'Failed to load shifts'); + } finally { + if (mounted) setLoading(false); + } + })(); + return () => { + mounted = false; + }; + }, []); + + // Go back to page 1 when the search or filter changes + useEffect(() => { + setPage(1); + }, [query, status]); + + // Filter shifts by search text and status + const filtered = useMemo(() => { + const q = query.trim().toLowerCase(); + return shifts.filter((s) => { + if (status && s.status !== status) return false; + if (!q) return true; + const haystack = [ + s.title, + s.status, + personLabel(s.createdBy), + s.createdBy?.email, + personLabel(s.acceptedBy), + s.acceptedBy?.email, + ] + .filter(Boolean) + .join(' ') + .toLowerCase(); + return haystack.includes(q); + }); + }, [shifts, query, status]); + + // Calculate pagination + const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)); + const currentPage = Math.min(page, totalPages); + const paged = filtered.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE); + + // Column shown in the table + const columns = [ + { key: 'title', header: 'Title' }, + { key: 'date', header: 'Date', render: (r) => formatDate(r.date) }, + { key: 'times', header: 'Time', render: (r) => formatTimes(r) }, + { key: 'status', header: 'Status' }, + { key: 'employer', header: 'Employer', render: (r) => personLabel(r.createdBy) }, + { key: 'guard', header: 'Guard', render: (r) => personLabel(r.acceptedBy) }, + ]; + + // Display the admin shifts page return (

Shifts

-

Planned (Sprint 2). Backend: GET /admin/shifts.

+

Read-only oversight of all shifts.

+ +
+ + +
+ + {loading ? ( + + ) : error ? ( +

{error}

+ ) : ( + <> + + {filtered.length > 0 && ( +
+ + Showing {(currentPage - 1) * PAGE_SIZE + 1} + {'\u2013'} + {Math.min(currentPage * PAGE_SIZE, filtered.length)} of {filtered.length} + +
+ + + Page {currentPage} of {totalPages} + + +
+
+ )} + + )}
); -} +} \ No newline at end of file From 60ddb09e96ceaedc38e9437aeb54a49e2957b48c Mon Sep 17 00:00:00 2001 From: Ethan <128652728+Basichy@users.noreply.github.com> Date: Fri, 31 Jul 2026 00:50:32 +1000 Subject: [PATCH 2/2] Fix lint formatting --- app-frontend/admin-panel/src/pages/Shifts.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-frontend/admin-panel/src/pages/Shifts.jsx b/app-frontend/admin-panel/src/pages/Shifts.jsx index 89d34a8dc..56ef61478 100644 --- a/app-frontend/admin-panel/src/pages/Shifts.jsx +++ b/app-frontend/admin-panel/src/pages/Shifts.jsx @@ -181,4 +181,4 @@ export default function Shifts() { )} ); -} \ No newline at end of file +}