'use client'; import { useState, useEffect } from 'react'; import { workersAPI, Worker } from '@/lib/api'; export default function Home() { const [workers, setWorkers] = useState([]); const [newWorkerName, setNewWorkerName] = useState(''); const [newPresenceDays, setNewPresenceDays] = useState(''); const [editingId, setEditingId] = useState(null); const [editingDays, setEditingDays] = useState(''); const [loading, setLoading] = useState(false); const TOTAL_POINTS = 13; const TOTAL_DAYS = 10; const POINTS_PER_DAY = TOTAL_POINTS / TOTAL_DAYS; useEffect(() => { loadWorkers(); }, []); const loadWorkers = async () => { try { const data = await workersAPI.getAll(); setWorkers(data); } catch (error) { console.error('Failed to load workers:', error); alert('Failed to load workers. Make sure json-server is running.'); } }; const addWorker = async () => { if (!newWorkerName.trim() || !newPresenceDays.trim()) return; const days = parseInt(newPresenceDays); if (isNaN(days) || days < 0 || days > TOTAL_DAYS) { alert(`Please enter a valid number of days between 0 and ${TOTAL_DAYS}`); return; } try { setLoading(true); await workersAPI.create({ name: newWorkerName.trim(), presenceDays: days, }); await loadWorkers(); setNewWorkerName(''); setNewPresenceDays(''); } catch (error) { console.error('Failed to add worker:', error); alert('Failed to add worker'); } finally { setLoading(false); } }; const removeWorker = async (id: string) => { try { setLoading(true); await workersAPI.delete(id); await loadWorkers(); } catch (error) { console.error('Failed to remove worker:', error); alert('Failed to remove worker'); } finally { setLoading(false); } }; const startEditing = (worker: Worker) => { setEditingId(worker.id); setEditingDays(worker.presenceDays.toString()); }; const cancelEditing = () => { setEditingId(null); setEditingDays(''); }; const saveEditing = async (id: string) => { const days = parseInt(editingDays); if (isNaN(days) || days < 0 || days > TOTAL_DAYS) { alert(`Please enter a valid number of days between 0 and ${TOTAL_DAYS}`); return; } try { setLoading(true); await workersAPI.update(id, { presenceDays: days }); await loadWorkers(); setEditingId(null); setEditingDays(''); } catch (error) { console.error('Failed to update worker:', error); alert('Failed to update worker'); } finally { setLoading(false); } }; const resetAllPresences = async () => { if (!confirm('Are you sure you want to reset all worker presences to 0?')) { return; } try { setLoading(true); await workersAPI.resetAllPresences(); await loadWorkers(); } catch (error) { console.error('Failed to reset presences:', error); alert('Failed to reset presences'); } finally { setLoading(false); } }; const calculatePoints = (days: number): number => { return days * POINTS_PER_DAY; }; const calculateUsedPoints = (days: number): number => { return TOTAL_POINTS - calculatePoints(days); }; return (

Worker Presence Tracker

Sprint duration: 2 weeks (10 working days) | Total points per worker: {TOTAL_POINTS}

{workers.length > 0 && ( )}
{/* Add Worker Form */}

Add Worker

setNewWorkerName(e.target.value)} className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" onKeyPress={(e) => e.key === 'Enter' && addWorker()} /> setNewPresenceDays(e.target.value)} min="0" max="10" className="w-full sm:w-48 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" onKeyPress={(e) => e.key === 'Enter' && addWorker()} />
{/* Workers List */} {workers.length > 0 ? (
{workers.map((worker) => { const pointsEarned = calculatePoints(worker.presenceDays); const pointsUsed = calculateUsedPoints(worker.presenceDays); const remainingPoints = TOTAL_POINTS - pointsUsed; const isEditing = editingId === worker.id; return ( ); })}
Worker Name Presence Days Points Earned Points Used Remaining Points Actions
{worker.name} {isEditing ? ( setEditingDays(e.target.value)} min="0" max="10" className="w-20 px-2 py-1 border border-blue-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500" onKeyPress={(e) => { if (e.key === 'Enter') saveEditing(worker.id); if (e.key === 'Escape') cancelEditing(); }} autoFocus /> ) : ( `${worker.presenceDays} / ${TOTAL_DAYS}` )} {pointsEarned.toFixed(2)} {pointsUsed.toFixed(2)} {remainingPoints.toFixed(2)} / {TOTAL_POINTS} {isEditing ? (
) : (
)}
{/* Summary */}
Total Workers: {workers.length}
Avg Presence Days: {workers.length > 0 ? (workers.reduce((sum, w) => sum + w.presenceDays, 0) / workers.length).toFixed(1) : '0'}
Total Presence Days: {workers.reduce((sum, w) => sum + w.presenceDays, 0)}
Total Remaining Points: {workers.reduce((sum, w) => sum + calculatePoints(w.presenceDays), 0).toFixed(2)}
) : (

No workers added yet. Add your first worker above.

)} {/* Info Box */}

How it works:

  • • Each worker has {TOTAL_POINTS} points for a 2-week sprint ({TOTAL_DAYS} working days)
  • • Each presence day equals {POINTS_PER_DAY.toFixed(2)} points
  • • Points Earned = Presence Days × {POINTS_PER_DAY.toFixed(2)}
  • • Points Used = {TOTAL_POINTS} - Points Earned (days not present)
  • • Remaining Points = Points Earned
); }