import React, { useEffect, useState } from 'react'; import AdjustmentModal from './AdjustmentModal'; import { RootState } from '../../store'; import { useSelector } from 'react-redux'; import { DateTime } from 'luxon'; interface LeaveFormData { fromDate: string; toDate: string; } interface AdjustmentEntry { id: number; date: string; loggedHours: number; } interface LogEntry { id: number; date: string; hours: number; task: string; status: "Completed" | "In Progress" | "Pending"; } interface ProjectSummaryResponse { totalWorkedHours: string; totalWorkedHoursDetailed: string; totalWorkedHoursDecimal: string; totalWorkedMinutes: string; dedicatedHours: number; pendingHours: string; firstLoginTime: string; lastLogoutTime: string; } const data: LogEntry[] = [ { id: 1, date: "2025-11-01", hours: 8, task: "Frontend Development", status: "Completed" }, { id: 2, date: "2025-11-02", hours: 6, task: "API Integration", status: "In Progress" }, { id: 3, date: "2025-11-03", hours: 7.5, task: "UI Testing", status: "Pending" }, { id: 4, date: "2025-11-04", hours: 9, task: "Bug Fixing", status: "Completed" }, { id: 5, date: "2025-11-05", hours: 5.5, task: "Code Review", status: "Completed" }, ]; type TabType = 'create_and_adjust_timesheet' | 'sick' | 'adjustment'; // Validation interface interface FormErrors { selectedProjectId?: string; selectedDate?: string; startTime?: string; endTime?: string; general?: string; } const LeaveAndAdjustments: React.FC = () => { const [activeTab, setActiveTab] = useState('create_and_adjust_timesheet'); const projects = useSelector((state: RootState) => state.projects.projectsData); console.log("LeaveAndAdjustments", projects) // PTO State const [ptoLeave, setPtoLeave] = useState({ fromDate: '', toDate: '', }); const [ptoErrors, setPtoErrors] = useState>({}); // Sick Leave State const [sickLeave, setSickLeave] = useState({ fromDate: '', toDate: '', }); const [sickErrors, setSickErrors] = useState>({}); const [selectedProjectId, setSelectedProjectId] = useState("") const [selectedDate, setSelectedDate] = useState("") const [totalAdjustment, setTotalAdjustment] = useState("0h 0m"); const [pendingOverride, setPendingOverride] = useState(null); const [loadingSummary, setLoadingSummary] = useState(false); // Form Validation State const [formErrors, setFormErrors] = useState({}); // Project Summary State const [projectSummary, setProjectSummary] = useState(null); const [startTime, setStartTime] = useState(""); const [endTime, setEndTime] = useState(""); // Adjustment State const [selectedPeriod, setSelectedPeriod] = useState('week'); const [adjustmentData, setAdjustmentData] = useState([ { id: 1, date: '2025-11-01', loggedHours: 8 }, { id: 2, date: '2025-11-02', loggedHours: 7.5 }, { id: 3, date: '2025-11-03', loggedHours: 8 }, ]); const [editingId, setEditingId] = useState(null); const [editHours, setEditHours] = useState(''); // Validation function const validateLeaveForm = (data: LeaveFormData): Partial => { const errors: Partial = {}; if (!data.fromDate) { errors.fromDate = 'From date is required'; } if (!data.toDate) { errors.toDate = 'To date is required'; } if (data.fromDate && data.toDate && new Date(data.fromDate) > new Date(data.toDate)) { errors.toDate = 'To date must be after from date'; } return errors; }; // Function to convert UTC time to local time in HH:mm format const convertToLocalTime = (utcTimeString: string): string => { if (!utcTimeString) return ''; try { // Parse the UTC time and convert to local timezone const localTime = DateTime.fromISO(utcTimeString, { zone: 'utc' }).toLocal(); // Format to HH:mm (24-hour format) return localTime.toFormat('HH:mm'); } catch (error) { console.error('Error converting time:', error); return ''; } }; // Field validation functions const validateProjectId = (projectId: string): string | undefined => { if (!projectId) return 'Project selection is required'; return undefined; }; const validateDate = (date: string): string | undefined => { if (!date) return 'Date is required'; const selectedDateObj = new Date(date); const today = new Date(); today.setHours(0, 0, 0, 0); if (selectedDateObj > today) return 'Cannot select future dates'; return undefined; }; const validateTime = (time: string, fieldName: string): string | undefined => { if (!time) return `${fieldName} is required`; const timeRegex = /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/; if (!timeRegex.test(time)) return 'Invalid time format'; return undefined; }; const validateTimeRange = (start: string, end: string): string | undefined => { if (!start || !end) return undefined; const startTime = DateTime.fromFormat(start, "HH:mm"); const endTime = DateTime.fromFormat(end, "HH:mm"); if (!startTime.isValid || !endTime.isValid) return 'Invalid time values'; if (endTime <= startTime) return 'End time must be after start time'; return undefined; }; const validateDedicatedHours = (start: string, end: string): string | undefined => { if (!start || !end || !projectSummary) return undefined; const startTime = DateTime.fromFormat(start, "HH:mm"); const endTime = DateTime.fromFormat(end, "HH:mm"); if (!startTime.isValid || !endTime.isValid) return undefined; const diff = endTime.diff(startTime, ["hours", "minutes"]).toObject(); const workedHours = (diff.hours || 0) + (diff.minutes || 0) / 60; if (workedHours > projectSummary.dedicatedHours) { return `Cannot exceed project's dedicated hours (${projectSummary.dedicatedHours} hrs)`; } return undefined; }; // Real-time field validation const validateField = (field: keyof FormErrors, value: string) => { let error: string | undefined; switch (field) { case 'selectedProjectId': error = validateProjectId(value); break; case 'selectedDate': error = validateDate(value); break; case 'startTime': error = validateTime(value, 'Start time'); if (!error && endTime) { error = validateTimeRange(value, endTime) || validateDedicatedHours(value, endTime); } break; case 'endTime': error = validateTime(value, 'End time'); if (!error && startTime) { error = validateTimeRange(startTime, value) || validateDedicatedHours(startTime, value); } break; } setFormErrors(prev => ({ ...prev, [field]: error })); return !error; // Return true if valid }; // Comprehensive form validation const validateForm = (): boolean => { const errors: FormErrors = {}; // Validate individual fields errors.selectedProjectId = validateProjectId(selectedProjectId); errors.selectedDate = validateDate(selectedDate); errors.startTime = validateTime(startTime, 'Start time'); errors.endTime = validateTime(endTime, 'End time'); // Validate time range and dedicated hours if (!errors.startTime && !errors.endTime) { errors.startTime = validateTimeRange(startTime, endTime); if (!errors.startTime) { errors.startTime = validateDedicatedHours(startTime, endTime); } } // Filter out undefined errors const filteredErrors = Object.fromEntries( Object.entries(errors).filter(([_, value]) => value !== undefined) ) as FormErrors; setFormErrors(filteredErrors); return Object.keys(filteredErrors).length === 0; }; const handlePtoSubmit = async () => { const errors = validateLeaveForm(ptoLeave); if (Object.keys(errors).length > 0) { setPtoErrors(errors); return; } try { console.log('Submitting PTO:', ptoLeave); alert('PTO request submitted successfully!'); setPtoLeave({ fromDate: '', toDate: '' }); setPtoErrors({}); } catch (error) { alert('Failed to submit request. Please try again.'); console.error('Error:', error); } }; // Sick Leave Handlers const handleSickChange = (field: keyof LeaveFormData, value: string) => { setSickLeave(prev => ({ ...prev, [field]: value })); if (sickErrors[field]) { setSickErrors(prev => ({ ...prev, [field]: undefined })); } }; const handleSickSubmit = async () => { const errors = validateLeaveForm(sickLeave); if (Object.keys(errors).length > 0) { setSickErrors(errors); return; } try { console.log('Submitting Sick Leave:', sickLeave); alert('Sick Leave request submitted successfully!'); setSickLeave({ fromDate: '', toDate: '' }); setSickErrors({}); } catch (error) { alert('Failed to submit request. Please try again.'); console.error('Error:', error); } }; const [editingEntry, setEditingEntry] = useState(null); const [newHours, setNewHours] = useState(0); const [comment, setComment] = useState("") const [isSubmitting, setIsSubmitting] = useState(false); const handlePeriodChange = (value: string) => setSelectedPeriod(value); const handleEditClick = (entry: AdjustmentEntry) => { setEditingEntry(entry); const modal = document.getElementById("editModal") as HTMLDialogElement; modal?.showModal(); }; const handleCloseModal = () => { setEditingEntry(null); setNewHours(0) const modal = document.getElementById("editModal") as HTMLDialogElement; modal?.close(); }; const handleSave = () => { console.log("Saving:", { ...editingEntry, updatedHours: newHours }); handleCloseModal(); }; const handleProjectSelect = (projectId: string) => { console.log("handleProjectSelect", projectId) setSelectedProjectId(projectId); validateField('selectedProjectId', projectId); } const handleSelectedDate = (value: string) => { setSelectedDate(value); validateField('selectedDate', value); }; const debugTimezoneInfo = (utcTimeString: string, check: string) => { const utcTime = DateTime.fromISO(utcTimeString, { zone: 'utc' }); const localTime = utcTime.toLocal(); console.log(`-------------------------Timezone Debug Info: ${check}----------------------`); console.log('UTC Time:', utcTime.toString()); console.log('Local Time:', localTime.toString()); console.log('System Timezone:', localTime.zoneName); console.log('Formatted Local Time:', localTime.toFormat('HH:mm')); }; const fetchLogs = async () => { try { console.log("Sending =>", selectedProjectId, selectedDate); setLoadingSummary(true); const res = await window.electron.getProjectSummary( selectedProjectId, selectedDate ); console.log("📥 Summary Response:", res); if (res.success) { setProjectSummary(res?.data?.summary); // Convert UTC times to local timezone and set them if (res?.data?.summary?.firstLoginTime) { const localStartTime = convertToLocalTime(res?.data?.summary?.firstLoginTime); debugTimezoneInfo(localStartTime, "start"); setStartTime(localStartTime); } if (res?.data?.summary?.lastLogoutTime) { const localEndTime = convertToLocalTime(res?.data?.summary?.lastLogoutTime); debugTimezoneInfo(localEndTime, "end"); setEndTime(localEndTime); } } else { console.warn("⚠️ Summary API failed:", res.message); setProjectSummary(null); setStartTime(""); setEndTime(""); } } catch (err) { console.error("❌ Error fetching project summary:", err); setProjectSummary(null); setStartTime(""); setEndTime(""); } finally { setLoadingSummary(false); } }; useEffect(() => { if (selectedProjectId && selectedDate) { fetchLogs(); } else { setProjectSummary(null); setStartTime(""); setEndTime(""); } }, [selectedProjectId, selectedDate]); // Auto calculations --- derived state useEffect(() => { if (!projectSummary) return; // No API summary yet if (!startTime || !endTime) return; // Clear previous time range errors when times change if (formErrors.startTime?.includes('time') || formErrors.startTime?.includes('dedicated')) { setFormErrors(prev => ({ ...prev, startTime: undefined })); } // 1. Validation: prevent end < start const start = DateTime.fromFormat(startTime, "HH:mm"); const end = DateTime.fromFormat(endTime, "HH:mm"); if (!start.isValid || !end.isValid) return; if (end < start) { setTotalAdjustment("0h 0m"); return; } // 2. Calculate worked minutes from UI input const diff = end.diff(start, ["hours", "minutes"]).toObject(); const workedHours = diff.hours || 0; const workedMinutes = diff.minutes || 0; const totalWorkedDecimal = workedHours + workedMinutes / 60; // 3. Compare with server original total const originalWorkedDecimal = Number(projectSummary.totalWorkedHoursDecimal); const adjustment = totalWorkedDecimal - originalWorkedDecimal; // 4. Build readable format (always positive for display) const adjH = Math.floor(Math.abs(adjustment)); const adjM = Math.round((Math.abs(adjustment) % 1) * 60); const formatted = `${adjustment >= 0 ? "+" : "-"}${adjH}h ${adjM}m`; setTotalAdjustment(formatted); // 5. Update Pending Hours -> dedicated - new worked const newPending = projectSummary.dedicatedHours - totalWorkedDecimal; const newPendingRounded = newPending.toFixed(2); setPendingOverride(newPendingRounded); }, [startTime, endTime, projectSummary]); const handleStartTimeChange = (value: string) => { console.log("Start changed:", value); setStartTime(value); validateField('startTime', value); }; const handleEndTimeChange = (value: string) => { console.log("End changed:", value); setEndTime(value); validateField('endTime', value); }; const handleSubmit = async () => { if (isSubmitting) return; // Clear previous errors setFormErrors({}); // Validate all fields if (!validateForm()) { return; } // Final dedicated hours check const dedicatedHoursError = validateDedicatedHours(startTime, endTime); if (dedicatedHoursError) { setFormErrors(prev => ({ ...prev, startTime: dedicatedHoursError })); return; } const payload = { projectId: selectedProjectId, date: selectedDate, loginTime: `${startTime}:00`, logoutTime: `${endTime}:00`, comment }; console.log("Submitting payload:", payload); setIsSubmitting(true); try { // Add await here to properly wait for the response const res = await window.electron.createTimesheet(payload); console.log("API Response:", res); if (res?.success) { console.log("Timesheet Created Successfully"); // Show success message alert(res.message); // Reset form or navigate away setSelectedProjectId(""); setSelectedDate(""); setStartTime(""); setEndTime(""); setComment(""); setFormErrors({}); setTotalAdjustment("0h 0m"); } else { setFormErrors(prev => ({ ...prev, general: res?.message || "Failed to create timesheet" })); } } catch (err: any) { console.error("Submission error:", err); setFormErrors(prev => ({ ...prev, general: err.message || "Something went wrong while saving timesheet" })); } finally { setIsSubmitting(false); } }; const handleDescription = (value: string) => { setComment(value); }; return (
{/* Header */}

Leave & Adjust Timesheet

{/* General Error Alert */} {formErrors.general && (
{formErrors.general}
)} {/* Main Container */}
{/* Tabs Navigation */}
{/* Tab Content */}
{/* PTO Tab */} {activeTab === 'create_and_adjust_timesheet' && (
{/* Project Selection */}
{formErrors.selectedProjectId && ( )}
{/* Date Selection */}
handleSelectedDate(e.target.value)} className={`input input-bordered input-sm w-full focus:input-primary transition-all ${formErrors.selectedDate ? 'input-error' : ''}`} /> {formErrors.selectedDate && ( )}
{/* Current Total Time */}
{loadingSummary ? (
) : }
{/* Current Pending Time */}
{loadingSummary ? (
) : }
{/* Start Time */}
{loadingSummary ? (
) : handleStartTimeChange(e.target.value)} className={`input input-bordered input-sm w-full focus:input-primary transition-all ${formErrors.startTime ? 'input-error' : ''}`} /> } {formErrors.startTime && ( )}
{/* End Time */}
{loadingSummary ? (
) : handleEndTimeChange(e.target.value)} className={`input input-bordered input-sm w-full focus:input-primary transition-all ${formErrors.endTime ? 'input-error' : ''}`} /> } {formErrors.endTime && ( )}
{/* Total Time Adjustment */}
{/* Description */}