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
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user