Files
coastal_timesheet/src/components/TimeSheet.jsx
T
Bizzle b5e7c8796f Initial commit: Coastal Timesheet App with comprehensive features
- Employee name validation for all submissions
- Complete entry validation (all fields required when any field has input)
- Dynamic auto-resizing work description fields
- Optimized field layout (Homeowner → Hours → Work Description)
- Professional PDF generation with validation
- Email/share functionality with validation
- Data import/export capabilities
- Dark/light theme support
- Mobile-responsive design
- Local data persistence
- Multiple entries per day support
- Custom homeowner management
- Comprehensive README with deployment instructions
- Production build ready for web hosting
2025-08-19 08:21:20 -04:00

159 lines
5.7 KiB
React

import DatePicker from './DatePicker';
import DayEntries from './DayEntries';
import PDFExport from './PDFExport';
import EmailPDF from './EmailPDF';
import officeContact from './OfficeContact.vcf?url';
import ImportExport from './ImportExport';
const TimeSheet = ({ timesheetData }) => {
const {
selectedDate,
setSelectedDate,
weekDays,
timeEntries,
updateTimeEntry,
addTimeEntry,
removeTimeEntry,
allHomeowners,
addCustomHomeowner,
customHomeowners,
importTimesheet,
userName,
setUserName,
getEntryValidation
} = timesheetData;
const totalHours = Object.values(timeEntries).reduce((total, dayEntries) => {
const dayTotal = dayEntries.reduce((daySum, entry) => {
return daySum + (parseFloat(entry.hoursWorked) || 0);
}, 0);
return total + dayTotal;
}, 0);
return (
<div className="max-w-6xl mx-auto p-6 space-y-6">
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
COASTAL CONTRACTING OF FL TIME SHEET
</h1>
<p className="text-gray-600 dark:text-gray-300">
Track your daily work hours and generate timesheets Sherry can actually read
</p>
</div>
</div>
<div className="flex flex-col sm:flex-row gap-4 items-start sm:items-end">
<div className="flex-1">
<label htmlFor="userName" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Employee Name <span className="text-red-500">*</span>
</label>
<input
type="text"
id="userName"
value={userName}
onChange={(e) => setUserName(e.target.value)}
placeholder="Enter your name"
className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white ${
userName.trim() === ''
? 'border-red-500 bg-red-50 dark:bg-red-900/20 dark:border-red-400'
: 'border-gray-300 dark:border-gray-600'
}`}
/>
{userName.trim() === '' && (
<p className="mt-1 text-sm text-red-600 dark:text-red-400">
Employee name is required for timesheet submission
</p>
)}
</div>
<div className="flex-1">
<DatePicker
selectedDate={selectedDate}
onDateChange={setSelectedDate}
weekDays={weekDays}
/>
</div>
</div>
<div className="space-y-4">
<div className="flex justify-between items-center">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Time Entries
</h2>
<div className="text-lg font-medium text-gray-900 dark:text-white">
Total: {totalHours.toFixed(1)} hours
</div>
</div>
{weekDays.map((day) => {
const dayKey = day.toISOString().split('T')[0];
const dayEntries = timeEntries[dayKey] || [{
id: `${dayKey}-0`,
workDescription: '',
hoursWorked: '',
homeowner: ''
}];
return (
<DayEntries
key={dayKey}
date={day}
entries={dayEntries}
onUpdate={updateTimeEntry}
onAdd={addTimeEntry}
onRemove={removeTimeEntry}
homeowners={allHomeowners}
onAddCustomHomeowner={addCustomHomeowner}
getEntryValidation={getEntryValidation}
/>
);
})}
</div>
<div className="flex flex-col items-center gap-2 pt-6">
<div className="flex flex-wrap justify-center items-center gap-3">
<ImportExport
weekDays={weekDays}
timeEntries={timeEntries}
customHomeowners={customHomeowners}
onImport={importTimesheet}
/>
<PDFExport
weekDays={weekDays}
timeEntries={timeEntries}
userName={userName}
getEntryValidation={getEntryValidation}
/>
<EmailPDF
weekDays={weekDays}
timeEntries={timeEntries}
userName={userName}
getEntryValidation={getEntryValidation}
/>
<a
href={officeContact}
download="Coastal_Office_Contact.vcf"
className="flex items-center gap-2 px-4 py-2 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-100 rounded-lg font-medium transition-colors"
title="Download and import the office contact to your phone so the To field is one tap away"
>
Import Office Contact
</a>
</div>
<div className="text-xs text-gray-600 dark:text-gray-400 text-center max-w-xl">
<p className="mb-1">
Export: Save a JSON backup that you can import later. Download PDF: Save a printable copy. Share/Email PDF: Attach the PDF via your device share sheet or download and open email with the subject pre-filled.
</p>
<p>
If all else fails: email your timesheet to <span className="font-medium">Office@CoastalContractingFL.com</span> with subject
<span className="font-medium"> "{userName || 'Employee'} - {weekDays && weekDays.length ? weekDays[0].toLocaleDateString() + ' - ' + weekDays[6].toLocaleDateString() : 'Timesheet'}"</span>.
</p>
<p className="mt-1">
Tip: You can add a contact for Office@CoastalContractingFL.com on your device so the To field is one tap away.
</p>
</div>
</div>
</div>
);
};
export default TimeSheet;