'use client'; import { useState, useEffect } from 'react'; import { workersAPI, Worker, settingsAPI, Settings } from '@/lib/api'; export default function Home() { const [workers, setWorkers] = useState([]); const [newWorkerName, setNewWorkerName] = useState(''); const [newPresenceDays, setNewPresenceDays] = useState(''); const [nameError, setNameError] = useState(false); const [daysError, setDaysError] = useState(false); const [editingId, setEditingId] = useState(null); const [editingDays, setEditingDays] = useState(''); const [loading, setLoading] = useState(false); const [settings, setSettings] = useState({ id: 1, totalPoints: 13, totalDays: 10 }); const [showSettings, setShowSettings] = useState(false); const [showResetConfirm, setShowResetConfirm] = useState(false); const [editTotalPoints, setEditTotalPoints] = useState(''); const [editTotalDays, setEditTotalDays] = useState(''); const TOTAL_POINTS = settings.totalPoints; const TOTAL_DAYS = settings.totalDays; const POINTS_PER_DAY = TOTAL_POINTS / TOTAL_DAYS; useEffect(() => { loadWorkers(); loadSettings(); }, []); 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 loadSettings = async () => { try { const data = await settingsAPI.get(); setSettings(data); } catch (error) { console.error('Failed to load settings:', error); } }; const openSettings = () => { setEditTotalPoints(settings.totalPoints.toString()); setEditTotalDays(settings.totalDays.toString()); setShowSettings(true); }; const closeSettings = () => { setShowSettings(false); setEditTotalPoints(''); setEditTotalDays(''); }; const saveSettings = async () => { const points = parseInt(editTotalPoints); const days = parseInt(editTotalDays); if (isNaN(points) || points <= 0) { alert('Please enter a valid number of points greater than 0'); return; } if (isNaN(days) || days <= 0) { alert('Please enter a valid number of days greater than 0'); return; } try { setLoading(true); const updatedSettings = await settingsAPI.update({ totalPoints: points, totalDays: days }); setSettings(updatedSettings); closeSettings(); } catch (error) { console.error('Failed to save settings:', error); alert('Failed to save settings'); } finally { setLoading(false); } }; const addWorker = async () => { // Validate required fields const isNameEmpty = !newWorkerName.trim(); const isDaysEmpty = !newPresenceDays.trim(); setNameError(isNameEmpty); setDaysError(isDaysEmpty); if (isNameEmpty || isDaysEmpty) 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}`); setDaysError(true); return; } try { setLoading(true); await workersAPI.create({ name: newWorkerName.trim(), presenceDays: days, }); await loadWorkers(); setNewWorkerName(''); setNewPresenceDays(''); setNameError(false); setDaysError(false); } 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 confirmResetAllPresences = async () => { try { setLoading(true); setShowResetConfirm(false); 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 Manager

Sprint duration: {TOTAL_DAYS} working days | Total points per worker: {TOTAL_POINTS}

{/* Add Worker Form */}

Add Worker

{ setNewWorkerName(e.target.value); if (nameError) setNameError(false); }} className={`flex-1 px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 ${ nameError ? 'border-red-500 focus:ring-red-500' : 'border-gray-300 focus:ring-blue-500' }`} onKeyPress={(e) => e.key === 'Enter' && addWorker()} /> { setNewPresenceDays(e.target.value); if (daysError) setDaysError(false); }} min="0" max={TOTAL_DAYS} className={`w-full sm:w-48 px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 ${ daysError ? 'border-red-500 focus:ring-red-500' : 'border-gray-300 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={TOTAL_DAYS} 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 Points in the sprint: {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 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
{/* Settings Modal */} {showSettings && (

Settings

setEditTotalPoints(e.target.value)} min="1" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="e.g., 13" />

Total points allocated to each worker for the sprint

setEditTotalDays(e.target.value)} min="1" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="e.g., 10" />

Total working days in the sprint

Points per day:{' '} {editTotalPoints && editTotalDays && parseInt(editTotalPoints) > 0 && parseInt(editTotalDays) > 0 ? (parseInt(editTotalPoints) / parseInt(editTotalDays)).toFixed(2) : 'N/A'}

)} {/* Reset Confirmation Modal */} {showResetConfirm && (

Reset All Presences

Are you sure you want to reset all worker presences to 0? This action cannot be undone.

)}
); }