682 lines
27 KiB
TypeScript
682 lines
27 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import AdjustmentModal from './AdjustmentModal';
|
|
import { RootState } from '../../store';
|
|
import { useSelector } from 'react-redux';
|
|
|
|
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";
|
|
}
|
|
|
|
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';
|
|
|
|
const LeaveAndAdjustments: React.FC = () => {
|
|
const [activeTab, setActiveTab] = useState<TabType>('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<Partial<LeaveFormData>>({});
|
|
|
|
// Sick Leave State
|
|
const [sickLeave, setSickLeave] = useState<LeaveFormData>({
|
|
fromDate: '',
|
|
toDate: '',
|
|
});
|
|
const [sickErrors, setSickErrors] = useState<Partial<LeaveFormData>>({});
|
|
const [selectedProjectId, setSelectedProjectId] = useState("")
|
|
const [selectedDate, setSelectedDate] = useState("")
|
|
|
|
// Adjustment State
|
|
const [selectedPeriod, setSelectedPeriod] = useState<string>('week');
|
|
const [adjustmentData, setAdjustmentData] = useState<AdjustmentEntry[]>([
|
|
{ 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<number | null>(null);
|
|
const [editHours, setEditHours] = useState<string>('');
|
|
|
|
// Validation function
|
|
const validateLeaveForm = (data: LeaveFormData): Partial<LeaveFormData> => {
|
|
const errors: Partial<LeaveFormData> = {};
|
|
|
|
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';
|
|
}
|
|
|
|
if (!data.description.trim()) {
|
|
errors.description = 'Description/Reason is required';
|
|
}
|
|
|
|
return errors;
|
|
};
|
|
|
|
|
|
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: '', description: '' });
|
|
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: '', description: '' });
|
|
setSickErrors({});
|
|
} catch (error) {
|
|
alert('Failed to submit request. Please try again.');
|
|
console.error('Error:', error);
|
|
}
|
|
};
|
|
|
|
const [editingEntry, setEditingEntry] = useState<AdjustmentEntry | null>(null);
|
|
const [newHours, setNewHours] = useState<number>(0);
|
|
|
|
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)
|
|
}
|
|
|
|
const handleSelectedDate = (value: string) => {
|
|
// Ensure value stays as "YYYY-MM-DD"
|
|
setSelectedDate(value);
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const fetchLogs = async () => {
|
|
const res = await window.electron.getProjectLogs(
|
|
selectedProjectId,
|
|
selectedDate || undefined,
|
|
selectedDate || undefined,
|
|
);
|
|
|
|
if (res.success) {
|
|
console.log("✅ Logs: fetchLogs", res);
|
|
|
|
// Safely extract time if from/to exist
|
|
const fromTime = res.from ? new Date(res.from).toISOString().slice(11, 16) : "";
|
|
const toTime = res.to ? new Date(res.to).toISOString().slice(11, 16) : "";
|
|
|
|
setPtoLeave({
|
|
fromDate: fromTime,
|
|
toDate: toTime,
|
|
});
|
|
} else {
|
|
console.warn("⚠️ Failed to fetch project logs");
|
|
}
|
|
};
|
|
|
|
|
|
useEffect(() => {
|
|
if (selectedProjectId && selectedDate) {
|
|
fetchLogs()
|
|
}
|
|
}, [selectedProjectId, selectedDate])
|
|
|
|
|
|
return (
|
|
<div className="max-h-screen">
|
|
<div className="w-full mx-auto">
|
|
{/* Header */}
|
|
<div className=" ">
|
|
<h1 className="text-mb font-bold mb-2">Adjust Timesheet</h1>
|
|
</div>
|
|
|
|
{/* Main Container */}
|
|
<div className=" rounded-lg bg-base-100 overflow-hidden">
|
|
{/* Tabs Navigation */}
|
|
<div className="flex ">
|
|
<button
|
|
onClick={() => setActiveTab('create_and_adjust_timesheet')}
|
|
className={`flex-1 px-6 py-3 text-xs font-semibold transition-all duration-300 relative ${activeTab === 'create_and_adjust_timesheet'
|
|
? ' text-primary border-b-2 border-primary'
|
|
: ''
|
|
}`}
|
|
>
|
|
<span className="flex items-center justify-center gap-2 cursor-pointer">
|
|
<svg
|
|
className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
</svg>
|
|
Create or Adjust Timesheet
|
|
</span>
|
|
</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>
|
|
|
|
{/* Tab Content */}
|
|
<div className="px-8 py-4 overflow-y-auto no-scrollbar max-h-[81vh]">
|
|
{/* PTO Tab */}
|
|
{activeTab === 'create_and_adjust_timesheet' && (
|
|
<div className="animate-fade-in">
|
|
<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="grid grid-cols-2 gap-4">
|
|
<div className="form-control col-span-2">
|
|
<label className="label">
|
|
<span className="label-text font-semibold text-sm">Select project</span>
|
|
</label>
|
|
<select
|
|
className="select select-sm"
|
|
defaultValue=""
|
|
onChange={(e) => handleProjectSelect(e.target.value)}
|
|
>
|
|
<option value="" disabled>
|
|
Pick a project
|
|
</option>
|
|
|
|
{projects.map((item, index) => (
|
|
<option key={item.projectId || index} value={item.projectId}>
|
|
{item.projectName}
|
|
</option>
|
|
))}
|
|
</select>
|
|
|
|
|
|
{ptoErrors.fromDate && (
|
|
<label className="label">
|
|
<span className="label-text-alt text-error font-medium">{ptoErrors.fromDate}</span>
|
|
</label>
|
|
)}
|
|
</div>
|
|
|
|
<div className="form-control col-span-2">
|
|
<label className="label">
|
|
<span className="label-text font-semibold text-sm">Select timesheet date</span>
|
|
</label>
|
|
<input
|
|
type="date"
|
|
value={selectedDate || ""}
|
|
onChange={(e) => handleSelectedDate(e.target.value)}
|
|
className="input input-bordered input-sm w-full focus:input-primary transition-all"
|
|
/>
|
|
</div>
|
|
|
|
|
|
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text font-semibold text-sm ">Current total time</span>
|
|
</label>
|
|
<input
|
|
type="time"
|
|
// onChange={(e) => handlePtoChange('fromDate', e.target.value)}
|
|
className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.fromDate ? 'input-error' : ''}`}
|
|
/>
|
|
{ptoErrors.fromDate && (
|
|
<label className="label">
|
|
<span className="label-text-alt text-error font-medium">{ptoErrors.fromDate}</span>
|
|
</label>
|
|
)}
|
|
</div>
|
|
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text font-semibold text-sm">Current pending time</span>
|
|
</label>
|
|
<input
|
|
type="time"
|
|
// onChange={(e) => handlePtoChange('toDate', e.target.value)}
|
|
className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.toDate ? 'input-error' : ''}`}
|
|
/>
|
|
{ptoErrors.toDate && (
|
|
<label className="label">
|
|
<span className="label-text-alt text-error font-medium">{ptoErrors.toDate}</span>
|
|
</label>
|
|
)}
|
|
</div>
|
|
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text font-semibold text-sm">Start time</span>
|
|
</label>
|
|
<input
|
|
type="time"
|
|
value={ptoLeave.fromDate}
|
|
onChange={(e) =>
|
|
setPtoLeave((prev) => ({ ...prev, fromDate: e.target.value }))
|
|
}
|
|
className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.fromDate ? 'input-error' : ''}`}
|
|
/>
|
|
</div>
|
|
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text font-semibold text-sm">End time</span>
|
|
</label>
|
|
<input
|
|
type="time"
|
|
value={ptoLeave.toDate}
|
|
onChange={(e) =>
|
|
setPtoLeave((prev) => ({ ...prev, toDate: e.target.value }))
|
|
}
|
|
className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.toDate ? 'input-error' : ''}`}
|
|
/>
|
|
</div>
|
|
|
|
|
|
<div className="form-control col-span-2">
|
|
<label className="label">
|
|
<span className="label-text font-semibold text-sm">Total time adjustment</span>
|
|
</label>
|
|
<input
|
|
type="input"
|
|
|
|
// onChange={(e) => handlePtoChange('toDate', e.target.value)}
|
|
className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.toDate ? 'input-error' : ''}`}
|
|
/>
|
|
{ptoErrors.toDate && (
|
|
<label className="label">
|
|
<span className="label-text-alt text-error font-medium">{ptoErrors.toDate}</span>
|
|
</label>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="form-control flex flex-col gap-1">
|
|
<label className="label">
|
|
<span className="label-text font-semibold text-sm ">Description / Reason</span>
|
|
</label>
|
|
<textarea
|
|
value={ptoLeave.description}
|
|
// onChange={(e) => handlePtoChange('description', e.target.value)}
|
|
className={`textarea text-xs textarea-bordered h-40 w-full focus:textarea-primary transition-all ${ptoErrors.description ? 'textarea-error' : ''}`}
|
|
placeholder="Please enter the task name or leave notes to help us better understand your request."
|
|
/>
|
|
{ptoErrors.description && (
|
|
<label className="label">
|
|
<span className="label-text-alt text-error font-medium">{ptoErrors.description}</span>
|
|
</label>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex gap-3">
|
|
<button
|
|
onClick={handlePtoSubmit}
|
|
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={() => {
|
|
setPtoLeave({ fromDate: '', toDate: '', description: '' });
|
|
setPtoErrors({});
|
|
}}
|
|
className="btn btn-secondary transition-all"
|
|
>
|
|
Clear
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Sick Leave Tab */}
|
|
{activeTab === 'sick' && (
|
|
<div className="animate-fade-in">
|
|
<div className="max-w-4xl ">
|
|
<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>
|
|
)}
|
|
|
|
{/* Adjustment Submissions Tab */}
|
|
{activeTab === 'adjustment' && (
|
|
<div className="animate-fade-in">
|
|
|
|
{/* 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>
|
|
|
|
{/* 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>{`
|
|
@keyframes fade-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.animate-fade-in {
|
|
animation: fade-in 0.3s ease-out;
|
|
}
|
|
`}</style>
|
|
|
|
<AdjustmentModal
|
|
editingEntry={editingEntry}
|
|
newHours={newHours}
|
|
setNewHours={setNewHours}
|
|
handleCloseModal={handleCloseModal}
|
|
handleSave={handleSave}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LeaveAndAdjustments; |