Timesheet

import React, { useState, useEffect } from ‘react’; import { Calendar, Clock, Send, CheckCircle, XCircle, Eye, Download, Plus, Trash2 } from ‘lucide-react’; const TimesheetApp = () => { const [currentView, setCurrentView] = useState(‘create’); const [timesheets, setTimesheets] = useState([]); const [currentTimesheet, setCurrentTimesheet] = useState({ weekEnding: ”, clientName: ”, projectName: ”, entries: [] }); // Load timesheets from storage on mount useEffect(() => { loadTimesheets(); }, []); const loadTimesheets = async () => { try { const result = await window.storage.list(‘timesheet:’); if (result && result.keys) { const loadedSheets = await Promise.all( result.keys.map(async (key) => { const data = await window.storage.get(key); return data ? JSON.parse(data.value) : null; }) ); setTimesheets(loadedSheets.filter(Boolean).sort((a, b) => new Date(b.submittedDate) – new Date(a.submittedDate) )); } } catch (error) { console.log(‘No existing timesheets found’); } }; const addEntry = () => { setCurrentTimesheet({ …currentTimesheet, entries: [ …currentTimesheet.entries, { date: ”, startTime: ”, endTime: ”, breakMinutes: 30, description: ” } ] }); }; const updateEntry = (index, field, value) => { const updatedEntries = […currentTimesheet.entries]; updatedEntries[index][field] = value; setCurrentTimesheet({ …currentTimesheet, entries: updatedEntries }); }; const removeEntry = (index) => { const updatedEntries = currentTimesheet.entries.filter((_, i) => i !== index); setCurrentTimesheet({ …currentTimesheet, entries: updatedEntries }); }; const calculateHours = (start, end, breakMins) => { if (!start || !end) return 0; const [startH, startM] = start.split(‘:’).map(Number); const [endH, endM] = end.split(‘:’).map(Number); const totalMinutes = (endH * 60 + endM) – (startH * 60 + startM) – breakMins; return (totalMinutes / 60).toFixed(2); }; const getTotalHours = () => { return currentTimesheet.entries.reduce((total, entry) => { return total + parseFloat(calculateHours(entry.startTime, entry.endTime, entry.breakMinutes) || 0); }, 0).toFixed(2); }; const submitTimesheet = async () => { if (!currentTimesheet.weekEnding || !currentTimesheet.clientName || currentTimesheet.entries.length === 0) { alert(‘Please fill in all required fields and add at least one entry’); return; } const newTimesheet = { id: `timesheet:${Date.now()}`, …currentTimesheet, status: ‘pending’, submittedDate: new Date().toISOString(), totalHours: getTotalHours() }; try { await window.storage.set(newTimesheet.id, JSON.stringify(newTimesheet)); await loadTimesheets(); setCurrentTimesheet({ weekEnding: ”, clientName: ”, projectName: ”, entries: [] }); setCurrentView(‘history’); alert(‘Timesheet submitted successfully!’); } catch (error) { alert(‘Failed to save timesheet’); } }; const updateTimesheetStatus = async (id, status, comments = ”) => { try { const result = await window.storage.get(id); if (result) { const timesheet = JSON.parse(result.value); timesheet.status = status; timesheet.approvalDate = new Date().toISOString(); if (comments) timesheet.comments = comments; await window.storage.set(id, JSON.stringify(timesheet)); await loadTimesheets(); } } catch (error) { alert(‘Failed to update status’); } }; const exportToCSV = (timesheet) => { const headers = [‘Date’, ‘Start Time’, ‘End Time’, ‘Break (mins)’, ‘Hours’, ‘Description’]; const rows = timesheet.entries.map(entry => [ entry.date, entry.startTime, entry.endTime, entry.breakMinutes, calculateHours(entry.startTime, entry.endTime, entry.breakMinutes), entry.description ]); const csvContent = [ `Timesheet for ${timesheet.clientName}`, `Project: ${timesheet.projectName}`, `Week Ending: ${timesheet.weekEnding}`, `Total Hours: ${timesheet.totalHours}`, ”, headers.join(‘,’), …rows.map(row => row.join(‘,’)) ].join(‘\n’); const blob = new Blob([csvContent], { type: ‘text/csv’ }); const url = window.URL.createObjectURL(blob); const a = document.createElement(‘a’); a.href = url; a.download = `timesheet_${timesheet.weekEnding}_${timesheet.clientName}.csv`; a.click(); }; const StatusBadge = ({ status }) => { const styles = { pending: ‘bg-yellow-100 text-yellow-800 border-yellow-300’, approved: ‘bg-green-100 text-green-800 border-green-300’, rejected: ‘bg-red-100 text-red-800 border-red-300’ }; const icons = { pending: , approved: , rejected: }; return ( {icons[status]} {status.charAt(0).toUpperCase() + status.slice(1)} ); }; return (
{/* Header */}

Timesheet Management

Track, submit and manage your timesheets

{/* Navigation */}
{/* Create Timesheet View */} {currentView === ‘create’ && (

New Timesheet

setCurrentTimesheet({ …currentTimesheet, weekEnding: e.target.value })} className=”w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent” />
setCurrentTimesheet({ …currentTimesheet, clientName: e.target.value })} placeholder=”Enter client name” className=”w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent” />
setCurrentTimesheet({ …currentTimesheet, projectName: e.target.value })} placeholder=”Enter project name” className=”w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent” />

Time Entries

{currentTimesheet.entries.map((entry, index) => (
updateEntry(index, ‘date’, e.target.value)} className=”w-full px-3 py-2 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500″ />
updateEntry(index, ‘startTime’, e.target.value)} className=”w-full px-3 py-2 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500″ />
updateEntry(index, ‘endTime’, e.target.value)} className=”w-full px-3 py-2 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500″ />
updateEntry(index, ‘breakMinutes’, parseInt(e.target.value) || 0)} className=”w-full px-3 py-2 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500″ />
updateEntry(index, ‘description’, e.target.value)} placeholder=”Work description…” className=”w-full px-3 py-2 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500″ />
))}
{currentTimesheet.entries.length > 0 && (
Total Hours: {getTotalHours()}
)}
)} {/* History View */} {currentView === ‘history’ && (

Timesheet History

{timesheets.length === 0 ? (

No timesheets yet

Create your first timesheet to get started

) : (
{timesheets.map((timesheet) => (

{timesheet.clientName}

{timesheet.projectName && (

{timesheet.projectName}

)}

Week Ending: {new Date(timesheet.weekEnding).toLocaleDateString()}

Total Hours

{timesheet.totalHours}

Entries

{timesheet.entries.length}

Submitted

{new Date(timesheet.submittedDate).toLocaleDateString()}

{timesheet.approvalDate && (

Processed

{new Date(timesheet.approvalDate).toLocaleDateString()}

)}
{timesheet.comments && (

Comments:

{timesheet.comments}

)}
{timesheet.status === ‘pending’ && ( <> )}
))}
)}
)}
); }; export default TimesheetApp;
Scroll to Top