init
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
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}`)
|
||||
})
|
||||
Reference in New Issue
Block a user