Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions app-frontend/employer-panel/src/pages/EmployerDashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,18 @@ body {
flex-wrap: wrap;
}

.ss-request-reason {
font-size: 13px;
color: var(--ss-muted);
}

.ss-request-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
}

/* Incident Management */
.ss-incident-toolbar {
display: grid;
grid-template-columns: 2fr repeat(3, minmax(130px, 1fr)) auto;
Expand Down Expand Up @@ -842,4 +854,82 @@ body {
.form-grid {
grid-template-columns: 1fr;
}

.ss-priority-topstats {
grid-template-columns: 1fr;
}
}

.ss-requests-list {
display: flex;
flex-direction: column;
gap: 12px;
}

.ss-request-row {
display: grid;
grid-template-columns: 2fr 1fr 1.2fr;
gap: 16px;
align-items: center;
background: #fff;
border-radius: 14px;
padding: 16px;
box-shadow: var(--shadow-soft);
}

.ss-request-left {
display: flex;
flex-direction: column;
gap: 6px;
}

.ss-request-type {
font-size: 14px;
font-weight: 700;
color: #162447;
}

.ss-request-employee {
font-size: 13px;
color: #4f5b73;
}

.ss-request-reason {
font-size: 12px;
color: var(--ss-muted);
}

.ss-request-middle {
display: flex;
align-items: center;
justify-content: center;
}

.ss-request-actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
flex-wrap: wrap;
}

.ss-secondary {
background: #f0f2f7;
color: var(--ss-blue-800);
border: none;
padding: 8px 14px;
border-radius: 999px;
font-weight: 600;
cursor: pointer;
}

.ss-secondary--danger {
background: #fdeaea;
color: var(--ss-red);
}

.ss-primary:disabled,
.ss-secondary:disabled {
opacity: 0.6;
cursor: not-allowed;
}
129 changes: 125 additions & 4 deletions app-frontend/employer-panel/src/pages/EmployerDashboard.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useMemo, useRef, useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import "./EmployerDashboard.css";
import translations from '../i18n/translations';

/* --- icons --- */
const IconCalendar = (props) => (
Expand Down Expand Up @@ -139,6 +138,45 @@ export default function EmployerDashboard() {
const [incidentSeverityFilter, setIncidentSeverityFilter] = useState("All");
const [incidentSort, setIncidentSort] = useState("Newest");

const [requests, setRequests] = useState([
{
id: "REQ-1001",
type: "Shift Swap",
employee: "John Doe",
shift: "Crowd Control - Marvel Stadium",
requestDate: "28-04-2026",
reason: "Requested shift swap due to university exam.",
status: "Pending",
},
{
id: "REQ-1002",
type: "Leave",
employee: "Amy Huggins",
shift: "Shopping Centre Security - Chadstone",
requestDate: "29-04-2026",
reason: "Medical appointment.",
status: "Pending",
},
{
id: "REQ-1003",
type: "Shift Swap",
employee: "Andrew Goddard",
shift: "Event Security - Rod Laver Arena",
requestDate: "30-04-2026",
reason: "Requested swap with another available guard.",
status: "Approved",
},
{
id: "REQ-1004",
type: "Leave",
employee: "Amy Huggins",
shift: "Shopping Centre Security - Chadstone",
requestDate: "01-05-2026",
reason: "Family commitment.",
status: "Rejected",
},
]);

const [incidents, setIncidents] = useState([
{
id: "INC-9921",
Expand Down Expand Up @@ -226,7 +264,7 @@ export default function EmployerDashboard() {

return {
id: shift._id || shift.id || idx,
title: shift.title || shift.role || "Shift 1",
title: shift.title || shift.role || "Shift",
location: formatLocation(shift.location || shift.venue),
date: formatShiftDate(shift.date || shift.shiftDate),
time:
Expand All @@ -237,6 +275,7 @@ export default function EmployerDashboard() {
payRate: shift.payRate ?? shift.rate ?? shift.hourlyRate ?? 0,
priority:
shift.priority || (idx % 3 === 0 ? "High" : idx % 3 === 1 ? "Medium" : "Low"),
assignedGuards: shift.assignedGuards ?? shift.guardsAssigned ?? 0,
};
});

Expand All @@ -254,6 +293,7 @@ export default function EmployerDashboard() {
status: { text: "Open", tone: "confirmed" },
payRate: 23,
priority: "High",
assignedGuards: 2,
},
{
id: 2,
Expand All @@ -264,6 +304,7 @@ export default function EmployerDashboard() {
status: { text: "Open", tone: "confirmed" },
payRate: 23,
priority: "High",
assignedGuards: 1,
},
{
id: 3,
Expand All @@ -274,6 +315,7 @@ export default function EmployerDashboard() {
status: { text: "Open", tone: "confirmed" },
payRate: 23,
priority: "High",
assignedGuards: 3,
},
{
id: 4,
Expand All @@ -284,6 +326,7 @@ export default function EmployerDashboard() {
status: { text: "Open", tone: "confirmed" },
payRate: 23,
priority: "High",
assignedGuards: 2,
},
{
id: 5,
Expand All @@ -294,6 +337,7 @@ export default function EmployerDashboard() {
status: { text: "Open", tone: "confirmed" },
payRate: 23,
priority: "High",
assignedGuards: 2,
},
{
id: 6,
Expand All @@ -304,6 +348,7 @@ export default function EmployerDashboard() {
status: { text: "Pending", tone: "pending" },
payRate: 25,
priority: "Medium",
assignedGuards: 1,
},
{
id: 7,
Expand All @@ -314,6 +359,7 @@ export default function EmployerDashboard() {
status: { text: "Completed", tone: "completed" },
payRate: 24,
priority: "Low",
assignedGuards: 4,
},
]);
} finally {
Expand Down Expand Up @@ -441,6 +487,11 @@ export default function EmployerDashboard() {
);
}, [incidents]);

const pendingRequestCount = useMemo(
() => requests.filter((req) => req.status === "Pending").length,
[requests]
);

const updateIncident = (id, newStatus, newSeverity, newComments) => {
setIncidents((prev) =>
prev.map((inc) =>
Expand All @@ -458,6 +509,12 @@ export default function EmployerDashboard() {
});
};

const updateRequestStatus = (id, newStatus) => {
setRequests((prev) =>
prev.map((req) => (req.id === id ? { ...req, status: newStatus } : req))
);
};

const scrollByAmount = (ref, amt) => {
if (!ref.current) return;
ref.current.scrollBy({ left: amt, behavior: "smooth" });
Expand Down Expand Up @@ -661,6 +718,62 @@ export default function EmployerDashboard() {
</div>
</div>

<div className="ss-section-head">
<h2 className="ss-section-title">Shift Swap / Leave Requests</h2>
<p className="ss-section-subtitle">
{pendingRequestCount} pending · {requests.length} total
</p>
</div>

<div className="ss-dashboard-card">
<div className="ss-requests-list">
{requests.map((req) => (
<div className="ss-request-row" key={req.id}>
<div className="ss-request-left">
<div className="ss-request-type">{req.type}</div>
<div className="ss-request-employee">
{req.employee} — {req.shift}
</div>
<div className="ss-request-reason">{req.reason}</div>
</div>

<div className="ss-request-middle">
<div className="ss-datetime-line">
<IconCalendar className="ss-ico" />
{req.requestDate}
</div>
</div>

<div className="ss-request-actions">
<span
className={`ss-badge ss-badge--status-${String(req.status).toLowerCase()}`}
>
{req.status}
</span>

<button
type="button"
className="ss-primary"
onClick={() => updateRequestStatus(req.id, "Approved")}
disabled={req.status === "Approved"}
>
Approve
</button>

<button
type="button"
className="ss-secondary ss-secondary--danger"
onClick={() => updateRequestStatus(req.id, "Rejected")}
disabled={req.status === "Rejected"}
>
Reject
</button>
</div>
</div>
))}
</div>
</div>

<div className="ss-section-head">
<h2 className="ss-section-title">Incident Reports</h2>
<p className="ss-section-subtitle">
Expand Down Expand Up @@ -807,7 +920,11 @@ export default function EmployerDashboard() {

{selectedIncident && (
<div className="create-shift-modal-backdrop" onClick={() => setSelectedIncident(null)}>
<div className="create-shift-card" onClick={(e) => e.stopPropagation()} style={{ maxWidth: "700px" }}>
<div
className="create-shift-card"
onClick={(e) => e.stopPropagation()}
style={{ maxWidth: "700px" }}
>
<div className="create-shift-header">
<div>
<h1>
Expand Down Expand Up @@ -902,7 +1019,11 @@ export default function EmployerDashboard() {
>
Save as Pending
</button>
<button className="secondary" style={{ color: "#666" }} onClick={() => setSelectedIncident(null)}>
<button
className="secondary"
style={{ color: "#666" }}
onClick={() => setSelectedIncident(null)}
>
Close
</button>
</div>
Expand Down