From 222a81cc7e0af2faef6d05c01864e4ecd64b873e Mon Sep 17 00:00:00 2001 From: Poseidon5114 <141444788+Poseidon5114@users.noreply.github.com> Date: Fri, 5 Jun 2026 13:43:02 +1000 Subject: [PATCH 1/5] Create Timesheet.js --- app-backend/src/models/Timesheet.js | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 app-backend/src/models/Timesheet.js diff --git a/app-backend/src/models/Timesheet.js b/app-backend/src/models/Timesheet.js new file mode 100644 index 000000000..c06c29311 --- /dev/null +++ b/app-backend/src/models/Timesheet.js @@ -0,0 +1,45 @@ +// This model stores timesheet records that are automatically +import mongoose from "mongoose"; //Created for when a guard checks in/out of a shift + +const timesheetSchema = new mongoose.Schema( + { + guardId: { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + required: true, + }, + shiftId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Shift", + required: true, + }, + date: { + type: Date, + required: true, + }, + checkInTime: { + type: Date, + required: true, + }, + checkOutTime: { + type: Date, + required: true, + }, + hoursWorked: { + type: Number, + required: true, + }, + status: { + type: String, + enum: ["completed", "pending"], + default: "completed", + }, + }, + { timestamps: true } +); + +// Prevent duplicate timesheet for same guard + shift +timesheetSchema.index({ guardId: 1, shiftId: 1 }, { unique: true }); + +const Timesheet = mongoose.model("Timesheet", timesheetSchema); +export default Timesheet; From b2489583dcfdffbc593f326113a061dc0e6e20d7 Mon Sep 17 00:00:00 2001 From: Poseidon5114 <141444788+Poseidon5114@users.noreply.github.com> Date: Fri, 5 Jun 2026 13:57:18 +1000 Subject: [PATCH 2/5] Update shiftattendance.routes.js --- app-backend/src/routes/shiftattendance.routes.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app-backend/src/routes/shiftattendance.routes.js b/app-backend/src/routes/shiftattendance.routes.js index 493967b25..8b8bb367f 100644 --- a/app-backend/src/routes/shiftattendance.routes.js +++ b/app-backend/src/routes/shiftattendance.routes.js @@ -1,5 +1,5 @@ import express from "express"; -import { checkIn, checkOut, getAttendanceByUserId } from "../controllers/shiftattendance.controller.js"; +import { checkIn, checkOut, getAttendanceByUserId, getTimesheets } from "../controllers/shiftattendance.controller.js"; import auth from "../middleware/auth.js"; const router = express.Router(); @@ -136,6 +136,9 @@ router.post("/checkout/:shiftId", auth, checkOut); * 500: * description: Server error */ +// Get all timesheets (with optional filters) +router.get("/timesheets", auth, getTimesheets); // This endpoint is used for the employer panel timesheet page router.get("/:userId", auth, getAttendanceByUserId); -export default router; \ No newline at end of file + +export default router; From a2d9f47ee84c8f01f650c93aeb630288572aa4f4 Mon Sep 17 00:00:00 2001 From: Poseidon5114 <141444788+Poseidon5114@users.noreply.github.com> Date: Fri, 5 Jun 2026 13:58:32 +1000 Subject: [PATCH 3/5] Update shiftattendance.controller.js --- .../controllers/shiftattendance.controller.js | 128 +++++++++++++++++- 1 file changed, 121 insertions(+), 7 deletions(-) diff --git a/app-backend/src/controllers/shiftattendance.controller.js b/app-backend/src/controllers/shiftattendance.controller.js index e9905ffbe..64eec63be 100644 --- a/app-backend/src/controllers/shiftattendance.controller.js +++ b/app-backend/src/controllers/shiftattendance.controller.js @@ -119,26 +119,140 @@ export const checkOut = async (req, res) => { return res.status(403).json({ message: "Not assigned to this shift" }); } - const attendance = await ShiftAttendance.findOne({ guard: guardId, shift: shiftId }); + const attendance = await ShiftAttendance.findOne({ + guardId: guardId, + shiftId: shiftId + }); + if (!attendance) { return res.status(404).json({ message: "No check-in record found" }); } - if (attendance.clockOut) { + if (attendance.checkOutTime) { return res.status(400).json({ message: "Already checked out" }); } - attendance.clockOut = new Date(); - attendance.recordedBy = guardId; + attendance.checkOutTime = new Date(); await attendance.save(); - return res.status(200).json({ message: "Check-out recorded", attendance }); + // AUTO TIMESHEET GENERATION + // When a guard checks out, we automatically: + // 1. Calculate hours worked + // 2. Create a new Timesheet record + // 3. Save it to the database + const checkInTime = new Date(attendance.checkInTime); + const checkOutTime = new Date(attendance.checkOutTime); + const hoursWorked = (checkOutTime - checkInTime) / (1000 * 60 * 60); // in hours + + const Timesheet = (await import("../models/Timesheet.js")).default; + + const timesheet = new Timesheet({ + guardId, + shiftId, + date: checkInTime, // Store full date + checkInTime: checkInTime, + checkOutTime: checkOutTime, + hoursWorked: parseFloat(hoursWorked.toFixed(2)), + }); + + await timesheet.save(); + + + return res.status(200).json({ + message: "Check-out recorded successfully", + attendance, + timesheet: { + id: timesheet._id, + hoursWorked: timesheet.hoursWorked + } + }); } catch (error) { + console.error("Checkout Error:", error); return res.status(500).json({ message: error.message }); } }; -// GET /api/v1/attendance/:userId + +// GET /api/v1/timesheets +export const getTimesheets = async (req, res) => { + try { + const { guardId, startDate, endDate } = req.query; + const Timesheet = (await import("../models/Timesheet.js")).default; + + let filter = {}; + + if (guardId) { + filter.guardId = guardId; + } + if (startDate) { + filter.date = { $gte: new Date(startDate) }; + } + if (endDate) { + filter.date = filter.date || {}; + filter.date.$lte = new Date(endDate); + } + + const timesheets = await Timesheet.find(filter) + .populate('guardId', 'name email') // Optional: show guard info + .populate('shiftId', 'date startTime endTime') + .sort({ date: -1 }); + + res.status(200).json({ + message: "Timesheets retrieved successfully", + count: timesheets.length, + timesheets + }); + } catch (error) { + console.error("Get Timesheets Error:", error); + res.status(500).json({ message: error.message }); + } +}; +// export const checkOut = async (req, res) => { +// try { +// const { latitude, longitude } = req.body; +// const { shiftId } = req.params; +// const guardId = req.user?._id || req.user?.id; + +// if (latitude === undefined || longitude === undefined) { +// return res.status(400).json({ message: "Invalid location coordinates" }); +// } + +// const latNum = Number(latitude); +// const lngNum = Number(longitude); + +// if (!Number.isFinite(latNum) || !Number.isFinite(lngNum)) { +// return res.status(400).json({ message: "Invalid location coordinates" }); +// } + +// const shift = await Shift.findById(shiftId); +// if (!shift) { +// return res.status(404).json({ message: "Shift not found" }); +// } + +// if (String(shift.assignedGuard) !== String(guardId)) { +// return res.status(403).json({ message: "Not assigned to this shift" }); +// } + +// const attendance = await ShiftAttendance.findOne({ guard: guardId, shift: shiftId }); +// if (!attendance) { +// return res.status(404).json({ message: "No check-in record found" }); +// } + +// if (attendance.clockOut) { +// return res.status(400).json({ message: "Already checked out" }); +// } + +// attendance.clockOut = new Date(); +// attendance.recordedBy = guardId; + +// await attendance.save(); + +// return res.status(200).json({ message: "Check-out recorded", attendance }); +// } catch (error) { +// return res.status(500).json({ message: error.message }); +// } +// }; +// // GET /api/v1/attendance/:userId export const getAttendanceByUserId = async (req, res) => { try { const { userId } = req.params; @@ -160,4 +274,4 @@ export const getAttendanceByUserId = async (req, res) => { } catch (error) { res.status(500).json({ message: error.message }); } -}; \ No newline at end of file +}; From cfd90d02afb776805424f6ba8792101d5f22c56f Mon Sep 17 00:00:00 2001 From: Poseidon5114 <141444788+Poseidon5114@users.noreply.github.com> Date: Fri, 5 Jun 2026 14:00:22 +1000 Subject: [PATCH 4/5] Update Timesheet.js --- .../employer-panel/src/pages/Timesheet.js | 72 +++++++++++-------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/app-frontend/employer-panel/src/pages/Timesheet.js b/app-frontend/employer-panel/src/pages/Timesheet.js index 837bf736c..a901dd424 100644 --- a/app-frontend/employer-panel/src/pages/Timesheet.js +++ b/app-frontend/employer-panel/src/pages/Timesheet.js @@ -103,36 +103,48 @@ export default function Timesheet({ language }) { const [sortBy, setSortBy] = useState(Sort.DateDesc); const [page, setPage] = useState(1); - useEffect(() => { - let cancelled = false; - (async () => { - try { - const { data } = await http.get("/shifts/myshifts"); - const list = Array.isArray(data) ? data : []; - const assignedShifts = list.filter((s) => s.acceptedBy); - const attendanceLists = await Promise.all( - assignedShifts.map((s) => - http - .get(`/payroll/attendance/${s._id}`) - .then((r) => r.data) - .catch(() => ({ records: [] })) - ) - ); - if (cancelled) return; - setRows(buildRows(assignedShifts, attendanceLists)); - } catch (err) { - if (cancelled) return; - setError( - err?.response?.data?.message || - err.message || - "Failed to load timesheets" - ); - } finally { - if (!cancelled) setLoading(false); - } - })(); - return () => { cancelled = true; }; - }, []); + useEffect(() => { //Changes made + let cancelled = false; + + (async () => { + try { + setLoading(true); + setError(null); + // This was changed from the old endpoint to /attendance/timesheets + // as part of the Auto Timesheet Generation feature. + // This makes the Timesheet tab display real auto-generated data. + const { data } = await http.get("/attendance/timesheets"); // NEW ENDPOINT + + if (cancelled) return; + + // Transform data to match the table structure + const formattedRows = Array.isArray(data.timesheets) + ? data.timesheets.map(ts => ({ + id: ts._id, + guard: ts.guardId?.name || "Unknown Guard", + shiftDate: formatDate(ts.date), + location: "N/A", // You can improve later + clockIn: formatTime(ts.checkInTime), + clockOut: formatTime(ts.checkOutTime), + totalHours: formatHoursWorked(ts.hoursWorked), + payRate: "--", + status: "Completed", + totalPayment: ts.hoursWorked ? `$${(ts.hoursWorked * 25).toFixed(2)}` : "--", // assuming $25/hr + })) + : []; + + setRows(formattedRows); + } catch (err) { + if (cancelled) return; + setError(err?.response?.data?.message || "Failed to load timesheets"); + console.error(err); + } finally { + if (!cancelled) setLoading(false); + } + })(); + + return () => { cancelled = true; }; +}, []); const summary = useMemo(() => { const counts = { Active: 0, Late: 0, Absent: 0, Completed: 0 }; From f49d2b52c841092ba79e65768ad95c6ae5a2b6f0 Mon Sep 17 00:00:00 2001 From: Poseidon5114 <141444788+Poseidon5114@users.noreply.github.com> Date: Fri, 5 Jun 2026 14:39:27 +1000 Subject: [PATCH 5/5] Update Timesheet.js --- app-frontend/employer-panel/src/pages/Timesheet.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app-frontend/employer-panel/src/pages/Timesheet.js b/app-frontend/employer-panel/src/pages/Timesheet.js index a901dd424..a5171e0c0 100644 --- a/app-frontend/employer-panel/src/pages/Timesheet.js +++ b/app-frontend/employer-panel/src/pages/Timesheet.js @@ -352,3 +352,4 @@ const sortSelectStyle = { cursor: "pointer", }; +//test commit fix