import express from 'express' import cors from 'cors' import { Parser } from 'json2csv' import { v4 as uuidv4 } from 'uuid' const app = express() const port = 3000 app.use(cors()) app.use(express.json()) // --- In-memory database for MVP --- const users = [ { id: 1, username: 'worker', password: 'password', role: 'worker', fullName: 'John Doe' }, { id: 2, username: 'worker2', password: 'password', role: 'worker', fullName: 'Jane Smith' }, { id: 3, username: 'manager', password: 'password', role: 'manager', fullName: 'Manager Bob' }, ] let qrCodes = [ { id: 'FACTORY-MAIN-ENTRANCE', name: 'Factory Main Entrance', isActive: true, createdAt: new Date().toISOString(), }, { id: 'WAREHOUSE-SECTION-A', name: 'Warehouse Section A', isActive: true, createdAt: new Date().toISOString(), }, { id: 'ASSEMBLY-LINE-1', name: 'Assembly Line 1', isActive: false, createdAt: new Date().toISOString(), }, ] let clockEvents = [ // Sample data for testing reports { id: 1, userId: 1, eventType: 'clock_in', timestamp: '2025-06-10T09:00:00.000Z', qrCodeUsedId: 'FACTORY-MAIN-ENTRANCE', qrCodeUsedName: 'Factory Main Entrance', }, { id: 2, userId: 1, eventType: 'clock_out', timestamp: '2025-06-10T17:30:00.000Z', qrCodeUsedId: 'FACTORY-MAIN-ENTRANCE', qrCodeUsedName: 'Factory Main Entrance', }, { id: 3, userId: 2, eventType: 'clock_in', timestamp: '2025-06-10T09:05:00.000Z', qrCodeUsedId: 'WAREHOUSE-SECTION-A', qrCodeUsedName: 'Warehouse Section A', }, { id: 4, userId: 2, eventType: 'clock_out', timestamp: '2025-06-10T17:35:00.000Z', qrCodeUsedId: 'WAREHOUSE-SECTION-A', qrCodeUsedName: 'Warehouse Section A', }, { id: 5, userId: 1, eventType: 'clock_in', timestamp: '2025-06-11T08:58:00.000Z', qrCodeUsedId: 'FACTORY-MAIN-ENTRANCE', qrCodeUsedName: 'Factory Main Entrance', }, // Missing clock out ] let eventId = clockEvents.length + 1 // --- Helper Functions --- const calculateHours = (events) => { const userHours = {} const pairedEvents = {} // Group events by user events.forEach((event) => { if (!pairedEvents[event.userId]) { pairedEvents[event.userId] = [] } pairedEvents[event.userId].push(event) }) for (const userId in pairedEvents) { const userEvents = pairedEvents[userId].sort( (a, b) => new Date(a.timestamp) - new Date(b.timestamp), ) let totalHours = 0 let clockInTime = null userEvents.forEach((event) => { if (event.eventType === 'clock_in' && !clockInTime) { clockInTime = new Date(event.timestamp) } else if (event.eventType === 'clock_out' && clockInTime) { const clockOutTime = new Date(event.timestamp) const diffMs = clockOutTime - clockInTime totalHours += diffMs / (1000 * 60 * 60) clockInTime = null // Reset for next pair } }) const worker = users.find((u) => u.id === parseInt(userId)) userHours[userId] = { userId, fullName: worker ? worker.fullName : `User ${userId}`, totalHours: parseFloat(totalHours.toFixed(2)), hasIncomplete: clockInTime !== null, // Mark if there's a pending clock-in } } return Object.values(userHours) } // --- API Endpoints --- // Auth Endpoint app.post('/api/auth/login', (req, res) => { const { username, password } = req.body const user = users.find((u) => u.username === username && u.password === password) if (user) { res.json({ message: 'Login successful', role: user.role, userId: user.id }) } else { res.status(401).json({ message: 'Invalid credentials' }) } }) // Worker Clock In/Out Endpoint app.post('/api/clock', (req, res) => { const { userId, eventType, qrCodeValue, latitude, longitude } = req.body const validQrCode = qrCodes.find((qr) => qr.id === qrCodeValue && qr.isActive) if (!validQrCode) return res.status(400).json({ message: 'Invalid or inactive QR Code.' }) const lastEvent = clockEvents.filter((e) => e.userId === userId).pop() if (lastEvent && lastEvent.eventType === eventType) return res .status(400) .json({ message: `You are already clocked ${eventType === 'clock_in' ? 'in' : 'out'}.` }) const newEvent = { id: eventId++, userId, eventType, timestamp: new Date().toISOString(), qrCodeUsedId: qrCodeValue, qrCodeUsedName: validQrCode.name, // Add human-readable name latitude, longitude, } clockEvents.push(newEvent) res.status(201).json(newEvent) }) // Worker Status Endpoint app.get('/api/worker/status/:userId', (req, res) => { const userId = parseInt(req.params.userId, 10) const lastEvent = clockEvents.filter((event) => event.userId === userId).pop() if (lastEvent) { res.json(lastEvent) } else { res.json({ eventType: 'clock_out' }) } }) // Worker History Endpoint app.get('/api/worker/clock-history/:userId', (req, res) => { const userId = parseInt(req.params.userId, 10) const userEvents = clockEvents.filter((event) => event.userId === userId) res.json(userEvents) }) // --- Manager Endpoints --- // Reporting Endpoint app.get('/api/managers/hours-report', (req, res) => { const { startDate, endDate, format } = req.query let filteredEvents = clockEvents if (startDate && endDate) { const endOfDay = new Date(endDate) endOfDay.setHours(23, 59, 59, 999) // Include the whole end day filteredEvents = clockEvents.filter((event) => { const eventDate = new Date(event.timestamp) return eventDate >= new Date(startDate) && eventDate <= endOfDay }) } const reportData = calculateHours(filteredEvents) if (format === 'csv') { const json2csvParser = new Parser({ fields: ['userId', 'fullName', 'totalHours', 'hasIncomplete'], }) const csv = json2csvParser.parse(reportData) res.header('Content-Type', 'text/csv') res.attachment(`hours-report-${new Date().toISOString().split('T')[0]}.csv`) return res.send(csv) } res.json(reportData) }) // GET QR Codes Endpoint app.get('/api/managers/qr-codes', (req, res) => { res.json(qrCodes.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))) }) // POST (Add new) QR Code Endpoint app.post('/api/managers/qr-codes', (req, res) => { const { name } = req.body if (!name) { return res.status(400).json({ message: 'QR Code name is required.' }) } const newQrCode = { id: uuidv4(), name, isActive: true, createdAt: new Date().toISOString(), } qrCodes.push(newQrCode) res.status(201).json(newQrCode) }) // PUT (Update) QR Code Endpoint app.put('/api/managers/qr-codes/:id', (req, res) => { const { id } = req.params const { isActive } = req.body const qrCodeIndex = qrCodes.findIndex((qr) => qr.id === id) if (qrCodeIndex === -1) { return res.status(404).json({ message: 'QR Code not found.' }) } if (typeof isActive !== 'boolean') { return res.status(400).json({ message: 'isActive must be a boolean.' }) } qrCodes[qrCodeIndex].isActive = isActive res.json(qrCodes[qrCodeIndex]) }) // DELETE QR Code Endpoint app.delete('/api/managers/qr-codes/:id', (req, res) => { const { id } = req.params const initialLength = qrCodes.length qrCodes = qrCodes.filter((qr) => qr.id !== id) if (qrCodes.length === initialLength) { return res.status(404).json({ message: 'QR Code not found.' }) } res.status(204).send() }) // --- Server Start --- app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`) })