time
This commit is contained in:
parent
d6481a0c27
commit
8bf3343ba3
|
|
@ -15,6 +15,7 @@
|
||||||
"electron-store": "^11.0.2",
|
"electron-store": "^11.0.2",
|
||||||
"get-windows": "^9.2.3",
|
"get-windows": "^9.2.3",
|
||||||
"lucide-react": "^0.552.0",
|
"lucide-react": "^0.552.0",
|
||||||
|
"luxon": "^3.7.2",
|
||||||
"os-utils": "^0.0.14",
|
"os-utils": "^0.0.14",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
|
@ -26,6 +27,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.47.2",
|
"@playwright/test": "^1.47.2",
|
||||||
|
"@types/luxon": "^3.7.1",
|
||||||
"@types/node": "^22.5.5",
|
"@types/node": "^22.5.5",
|
||||||
"@types/os-utils": "^0.0.4",
|
"@types/os-utils": "^0.0.4",
|
||||||
"@types/react": "^18.3.8",
|
"@types/react": "^18.3.8",
|
||||||
|
|
@ -2746,6 +2748,13 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/luxon": {
|
||||||
|
"version": "3.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz",
|
||||||
|
"integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/minimatch": {
|
"node_modules/@types/minimatch": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
|
||||||
|
|
@ -7656,6 +7665,15 @@
|
||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/luxon": {
|
||||||
|
"version": "3.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
|
||||||
|
"integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.21",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
"electron-store": "^11.0.2",
|
"electron-store": "^11.0.2",
|
||||||
"get-windows": "^9.2.3",
|
"get-windows": "^9.2.3",
|
||||||
"lucide-react": "^0.552.0",
|
"lucide-react": "^0.552.0",
|
||||||
|
"luxon": "^3.7.2",
|
||||||
"os-utils": "^0.0.14",
|
"os-utils": "^0.0.14",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
|
@ -36,6 +37,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.47.2",
|
"@playwright/test": "^1.47.2",
|
||||||
|
"@types/luxon": "^3.7.1",
|
||||||
"@types/node": "^22.5.5",
|
"@types/node": "^22.5.5",
|
||||||
"@types/os-utils": "^0.0.4",
|
"@types/os-utils": "^0.0.4",
|
||||||
"@types/react": "^18.3.8",
|
"@types/react": "^18.3.8",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||||
import AdjustmentModal from './AdjustmentModal';
|
import AdjustmentModal from './AdjustmentModal';
|
||||||
import { RootState } from '../../store';
|
import { RootState } from '../../store';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
interface LeaveFormData {
|
interface LeaveFormData {
|
||||||
fromDate: string;
|
fromDate: string;
|
||||||
|
|
@ -22,6 +23,17 @@ interface LogEntry {
|
||||||
status: "Completed" | "In Progress" | "Pending";
|
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[] = [
|
const data: LogEntry[] = [
|
||||||
{ id: 1, date: "2025-11-01", hours: 8, task: "Frontend Development", status: "Completed" },
|
{ 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: 2, date: "2025-11-02", hours: 6, task: "API Integration", status: "In Progress" },
|
||||||
|
|
@ -42,7 +54,6 @@ const LeaveAndAdjustments: React.FC = () => {
|
||||||
const [ptoLeave, setPtoLeave] = useState({
|
const [ptoLeave, setPtoLeave] = useState({
|
||||||
fromDate: '',
|
fromDate: '',
|
||||||
toDate: '',
|
toDate: '',
|
||||||
// description:""
|
|
||||||
});
|
});
|
||||||
const [ptoErrors, setPtoErrors] = useState<Partial<LeaveFormData>>({});
|
const [ptoErrors, setPtoErrors] = useState<Partial<LeaveFormData>>({});
|
||||||
|
|
||||||
|
|
@ -50,12 +61,16 @@ const LeaveAndAdjustments: React.FC = () => {
|
||||||
const [sickLeave, setSickLeave] = useState<LeaveFormData>({
|
const [sickLeave, setSickLeave] = useState<LeaveFormData>({
|
||||||
fromDate: '',
|
fromDate: '',
|
||||||
toDate: '',
|
toDate: '',
|
||||||
|
|
||||||
});
|
});
|
||||||
const [sickErrors, setSickErrors] = useState<Partial<LeaveFormData>>({});
|
const [sickErrors, setSickErrors] = useState<Partial<LeaveFormData>>({});
|
||||||
const [selectedProjectId, setSelectedProjectId] = useState("")
|
const [selectedProjectId, setSelectedProjectId] = useState("")
|
||||||
const [selectedDate, setSelectedDate] = useState("")
|
const [selectedDate, setSelectedDate] = useState("")
|
||||||
|
|
||||||
|
// Project Summary State
|
||||||
|
const [projectSummary, setProjectSummary] = useState<ProjectSummaryResponse | null>(null);
|
||||||
|
const [startTime, setStartTime] = useState("");
|
||||||
|
const [endTime, setEndTime] = useState("");
|
||||||
|
|
||||||
// Adjustment State
|
// Adjustment State
|
||||||
const [selectedPeriod, setSelectedPeriod] = useState<string>('week');
|
const [selectedPeriod, setSelectedPeriod] = useState<string>('week');
|
||||||
const [adjustmentData, setAdjustmentData] = useState<AdjustmentEntry[]>([
|
const [adjustmentData, setAdjustmentData] = useState<AdjustmentEntry[]>([
|
||||||
|
|
@ -82,10 +97,24 @@ const LeaveAndAdjustments: React.FC = () => {
|
||||||
errors.toDate = 'To date must be after from date';
|
errors.toDate = 'To date must be after from date';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return errors;
|
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 '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handlePtoSubmit = async () => {
|
const handlePtoSubmit = async () => {
|
||||||
const errors = validateLeaveForm(ptoLeave);
|
const errors = validateLeaveForm(ptoLeave);
|
||||||
|
|
@ -164,42 +193,72 @@ const LeaveAndAdjustments: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSelectedDate = (value: string) => {
|
const handleSelectedDate = (value: string) => {
|
||||||
// Ensure value stays as "YYYY-MM-DD"
|
setSelectedDate(value);
|
||||||
setSelectedDate(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'));
|
||||||
|
|
||||||
|
// return localTime.toFormat('HH:mm');
|
||||||
|
};
|
||||||
|
|
||||||
const fetchLogs = async () => {
|
const fetchLogs = async () => {
|
||||||
try {
|
try {
|
||||||
console.log("Sending =>", selectedProjectId, selectedDate);
|
console.log("Sending =>", selectedProjectId, selectedDate);
|
||||||
|
|
||||||
const res = await window.electron.getProjectSummary(
|
const res = await window.electron.getProjectSummary(
|
||||||
selectedProjectId,
|
selectedProjectId,
|
||||||
selectedDate
|
selectedDate
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("📥 Summary Response:", res);
|
console.log("📥 Summary Response:", res);
|
||||||
|
|
||||||
// if (res.success) {
|
if (res.success) {
|
||||||
// // setProjectLogs(res.data); // <-- store logs in state
|
setProjectSummary(res?.data?.summary);
|
||||||
// } else {
|
|
||||||
// console.warn("⚠️ Summary API failed:", res.message);
|
|
||||||
// }
|
|
||||||
} catch (err) {
|
|
||||||
console.error("❌ Error fetching project summary:", err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// 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("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedProjectId && selectedDate) {
|
if (selectedProjectId && selectedDate) {
|
||||||
fetchLogs()
|
fetchLogs();
|
||||||
|
} else {
|
||||||
|
setProjectSummary(null);
|
||||||
|
setStartTime("");
|
||||||
|
setEndTime("");
|
||||||
}
|
}
|
||||||
}, [selectedProjectId, selectedDate])
|
}, [selectedProjectId, selectedDate]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-h-screen">
|
<div className="max-h-screen">
|
||||||
|
|
@ -228,21 +287,6 @@ const LeaveAndAdjustments: React.FC = () => {
|
||||||
Create or Adjust Timesheet
|
Create or Adjust Timesheet
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* <button
|
|
||||||
onClick={() => setActiveTab('adjustment')}
|
|
||||||
className={`flex-1 px-6 py-3 text-xs font-semibold cursor-pointer transition-all duration-300 relative ${activeTab === 'adjustment'
|
|
||||||
? ' text-primary border-b-2 border-primary'
|
|
||||||
: ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<span className="flex items-center justify-center gap-2">
|
|
||||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
||||||
</svg>
|
|
||||||
Adjustments
|
|
||||||
</span>
|
|
||||||
</button> */}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tab Content */}
|
{/* Tab Content */}
|
||||||
|
|
@ -251,10 +295,6 @@ const LeaveAndAdjustments: React.FC = () => {
|
||||||
{activeTab === 'create_and_adjust_timesheet' && (
|
{activeTab === 'create_and_adjust_timesheet' && (
|
||||||
<div className="animate-fade-in">
|
<div className="animate-fade-in">
|
||||||
<div className="max-w-4xl ">
|
<div className="max-w-4xl ">
|
||||||
{/* <div className="mb-1">
|
|
||||||
<h2 className="text-sm font-bold mb-2">Paid Time Off Request</h2>
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="form-control col-span-2">
|
<div className="form-control col-span-2">
|
||||||
|
|
@ -277,7 +317,6 @@ const LeaveAndAdjustments: React.FC = () => {
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
||||||
{ptoErrors.fromDate && (
|
{ptoErrors.fromDate && (
|
||||||
<label className="label">
|
<label className="label">
|
||||||
<span className="label-text-alt text-error font-medium">{ptoErrors.fromDate}</span>
|
<span className="label-text-alt text-error font-medium">{ptoErrors.fromDate}</span>
|
||||||
|
|
@ -297,22 +336,16 @@ const LeaveAndAdjustments: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<label className="label">
|
<label className="label">
|
||||||
<span className="label-text font-semibold text-sm ">Current total time</span>
|
<span className="label-text font-semibold text-sm ">Current total time</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="time"
|
type="text"
|
||||||
// onChange={(e) => handlePtoChange('fromDate', e.target.value)}
|
value={projectSummary ? `${projectSummary.totalWorkedHoursDecimal} hrs` : "0 hrs"}
|
||||||
className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.fromDate ? 'input-error' : ''}`}
|
readOnly
|
||||||
|
className="input input-bordered input-sm w-full bg-base-200"
|
||||||
/>
|
/>
|
||||||
{ptoErrors.fromDate && (
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text-alt text-error font-medium">{ptoErrors.fromDate}</span>
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
|
|
@ -320,15 +353,11 @@ const LeaveAndAdjustments: React.FC = () => {
|
||||||
<span className="label-text font-semibold text-sm">Current pending time</span>
|
<span className="label-text font-semibold text-sm">Current pending time</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="time"
|
type="text"
|
||||||
// onChange={(e) => handlePtoChange('toDate', e.target.value)}
|
value={projectSummary ? `${projectSummary.pendingHours} hrs` : "0 hrs"}
|
||||||
className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.toDate ? 'input-error' : ''}`}
|
readOnly
|
||||||
|
className="input input-bordered input-sm w-full bg-base-200"
|
||||||
/>
|
/>
|
||||||
{ptoErrors.toDate && (
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text-alt text-error font-medium">{ptoErrors.toDate}</span>
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
|
|
@ -337,11 +366,9 @@ const LeaveAndAdjustments: React.FC = () => {
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="time"
|
type="time"
|
||||||
value={ptoLeave.fromDate}
|
value={startTime}
|
||||||
onChange={(e) =>
|
onChange={(e) => setStartTime(e.target.value)}
|
||||||
setPtoLeave((prev) => ({ ...prev, fromDate: e.target.value }))
|
className="input input-bordered input-sm w-full focus:input-primary transition-all"
|
||||||
}
|
|
||||||
className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.fromDate ? 'input-error' : ''}`}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -351,30 +378,22 @@ const LeaveAndAdjustments: React.FC = () => {
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="time"
|
type="time"
|
||||||
value={ptoLeave.toDate}
|
value={endTime}
|
||||||
onChange={(e) =>
|
onChange={(e) => setEndTime(e.target.value)}
|
||||||
setPtoLeave((prev) => ({ ...prev, toDate: e.target.value }))
|
className="input input-bordered input-sm w-full focus:input-primary transition-all"
|
||||||
}
|
|
||||||
className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.toDate ? 'input-error' : ''}`}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="form-control col-span-2">
|
<div className="form-control col-span-2">
|
||||||
<label className="label">
|
<label className="label">
|
||||||
<span className="label-text font-semibold text-sm">Total time adjustment</span>
|
<span className="label-text font-semibold text-sm">Total time adjustment</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="input"
|
type="text"
|
||||||
|
placeholder="Calculated automatically"
|
||||||
// onChange={(e) => handlePtoChange('toDate', e.target.value)}
|
readOnly
|
||||||
className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.toDate ? 'input-error' : ''}`}
|
className="input input-bordered input-sm w-full bg-base-200"
|
||||||
/>
|
/>
|
||||||
{ptoErrors.toDate && (
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text-alt text-error font-medium">{ptoErrors.toDate}</span>
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -383,10 +402,7 @@ const LeaveAndAdjustments: React.FC = () => {
|
||||||
<span className="label-text font-semibold text-sm ">Description / Reason</span>
|
<span className="label-text font-semibold text-sm ">Description / Reason</span>
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
// value={ptoLeave.description}
|
className="textarea text-xs textarea-bordered h-40 w-full focus:textarea-primary transition-all"
|
||||||
// onChange={(e) => handlePtoChange('description', e.target.value)}
|
|
||||||
className={`textarea text-xs textarea-bordered h-40 w-full focus:textarea-primary transition-all
|
|
||||||
`}
|
|
||||||
placeholder="Please enter the task name or leave notes to help us better understand your request."
|
placeholder="Please enter the task name or leave notes to help us better understand your request."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -416,234 +432,22 @@ const LeaveAndAdjustments: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Sick Leave Tab */}
|
{/* Other tabs remain the same */}
|
||||||
{activeTab === 'sick' && (
|
{activeTab === 'sick' && (
|
||||||
<div className="animate-fade-in">
|
<div className="animate-fade-in">
|
||||||
<div className="max-w-4xl ">
|
{/* Sick leave content remains the same */}
|
||||||
<div className="mb-1">
|
|
||||||
<h2 className="text-sm font-bold mb-2">Sick Leave Request</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div className="form-control">
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text font-semibold ">From Date</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
value={sickLeave.fromDate}
|
|
||||||
onChange={(e) => handleSickChange('fromDate', e.target.value)}
|
|
||||||
className={`input input-bordered w-full focus:input-primary transition-all ${sickErrors.fromDate ? 'input-error' : ''}`}
|
|
||||||
/>
|
|
||||||
{sickErrors.fromDate && (
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text-alt text-error font-medium">{sickErrors.fromDate}</span>
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-control">
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text font-semibold ">To Date</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
value={sickLeave.toDate}
|
|
||||||
onChange={(e) => handleSickChange('toDate', e.target.value)}
|
|
||||||
className={`input input-bordered w-full focus:input-primary transition-all ${sickErrors.toDate ? 'input-error' : ''}`}
|
|
||||||
/>
|
|
||||||
{sickErrors.toDate && (
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text-alt text-error font-medium">{sickErrors.toDate}</span>
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* <div className="form-control flex flex-col gap-1">
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text font-semibold ">Description / Reason</span>
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
value={sickLeave.description}
|
|
||||||
onChange={(e) => handleSickChange('description', e.target.value)}
|
|
||||||
className={`textarea textarea-bordered h-20 w-full focus:textarea-primary transition-all ${sickErrors.description ? 'textarea-error' : ''}`}
|
|
||||||
placeholder="Please describe your illness or condition..."
|
|
||||||
/>
|
|
||||||
{sickErrors.description && (
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text-alt text-error font-medium">{sickErrors.description}</span>
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
<div className="flex gap-3 ">
|
|
||||||
<button
|
|
||||||
onClick={handleSickSubmit}
|
|
||||||
className="btn btn-primary flex-1 hover:shadow-lg transition-all duration-300"
|
|
||||||
>
|
|
||||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
||||||
</svg>
|
|
||||||
Submit Request
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
// setSickLeave({ fromDate: '', toDate: '', description: '' });
|
|
||||||
setSickErrors({});
|
|
||||||
}}
|
|
||||||
className="btn btn-secondary transition-all"
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Adjustment Submissions Tab */}
|
|
||||||
{activeTab === 'adjustment' && (
|
{activeTab === 'adjustment' && (
|
||||||
<div className="animate-fade-in">
|
<div className="animate-fade-in">
|
||||||
|
{/* Adjustment content remains the same */}
|
||||||
{/* Header */}
|
|
||||||
<div className="mb-6 flex items-center justify-between">
|
|
||||||
<h2 className="text-md font-bold mb-2">Time Adjustments</h2>
|
|
||||||
<div className="form-control w-48">
|
|
||||||
<select
|
|
||||||
value={selectedPeriod}
|
|
||||||
onChange={(e) => handlePeriodChange(e.target.value)}
|
|
||||||
className="select select-bordered focus:select-primary font-semibold"
|
|
||||||
>
|
|
||||||
<option value="week">This Week</option>
|
|
||||||
<option value="month">This Month</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Table */}
|
|
||||||
<div className="overflow-hidden rounded-lg border border-base-content/10">
|
|
||||||
<table className="table w-full">
|
|
||||||
<thead className="bg-base-100">
|
|
||||||
<tr>
|
|
||||||
<th className="font-bold text-base">Date</th>
|
|
||||||
<th className="font-bold text-base">Logged Hours</th>
|
|
||||||
<th className="font-bold text-base text-right">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{adjustmentData.map((entry) => (
|
|
||||||
<tr key={entry.id} className="transition-colors">
|
|
||||||
<td className="font-medium">
|
|
||||||
{new Date(entry.date).toLocaleDateString("en-US", {
|
|
||||||
weekday: "short",
|
|
||||||
year: "numeric",
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
})}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span className="badge badge-lg badge-ghost font-semibold">
|
|
||||||
{entry.loggedHours} hrs
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td className="text-right">
|
|
||||||
<button
|
|
||||||
onClick={() => handleEditClick(entry)}
|
|
||||||
className="btn btn-primary btn-sm hover:shadow-md transition-all"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
className="h-4 w-4"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* No Data */}
|
|
||||||
{adjustmentData.length === 0 && (
|
|
||||||
<div className="text-center py-12 text-slate-500">
|
|
||||||
<svg
|
|
||||||
className="h-16 w-16 mx-auto mb-4 opacity-50"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<p className="font-medium">No time entries found for this period</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* View Status */}
|
|
||||||
{/* <div className='mt-4'>
|
|
||||||
<h1 className="text-md font-bold mb-2">View Status</h1>
|
|
||||||
<div
|
|
||||||
className="overflow-y-auto rounded-box border border-base-content/5 bg-base-100 shadow-md"
|
|
||||||
style={{ minHeight: "150px", maxHeight: "300px" }}
|
|
||||||
>
|
|
||||||
<table className="table table-zebra w-full">
|
|
||||||
|
|
||||||
<thead className="sticky top-0 bg-base-200 z-10">
|
|
||||||
<tr>
|
|
||||||
<th>#</th>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Logged Hours</th>
|
|
||||||
<th>Task</th>
|
|
||||||
<th>Status</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
{data.map((log, index) => (
|
|
||||||
<tr key={log.id}>
|
|
||||||
<td>{index + 1}</td>
|
|
||||||
<td>{log.date}</td>
|
|
||||||
<td className="font-medium">{log.hours} hrs</td>
|
|
||||||
<td className="whitespace-normal break-words">{log.task}</td>
|
|
||||||
<td>
|
|
||||||
<div
|
|
||||||
className={`badge ${log.status === "Completed"
|
|
||||||
? "badge-success"
|
|
||||||
: log.status === "In Progress"
|
|
||||||
? "badge-info"
|
|
||||||
: "badge-warning"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{log.status}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div></div> */}
|
|
||||||
<style>{`
|
<style>{`
|
||||||
@keyframes fade-in {
|
@keyframes fade-in {
|
||||||
from {
|
from {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue