ConstructionProcectmanagement / components /AttendanceManager.tsx
Codex Deploy
Prepare local Hugging Face deployment
191b322
import React, { useState } from 'react';
import { AttendanceRecord } from '../types';
import { UserCheck, Search, Filter, Calendar, Clock, MapPin, MoreVertical, Loader2 } from 'lucide-react';
interface AttendanceManagerProps {
attendance: AttendanceRecord[];
}
const AttendanceManager: React.FC<AttendanceManagerProps> = ({ attendance }) => {
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
const [isCheckingIn, setIsCheckingIn] = useState(false);
const dailyAttendance = attendance.filter(a => a.date === selectedDate);
const stats = {
present: dailyAttendance.filter(a => a.status === 'PRESENT').length,
absent: dailyAttendance.filter(a => a.status === 'ABSENT').length,
total: dailyAttendance.length
};
const handleGpsCheckIn = () => {
setIsCheckingIn(true);
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition(
(position) => {
setIsCheckingIn(false);
const { latitude, longitude } = position.coords;
alert(`GPS Check-in Successful!\nLat: ${latitude.toFixed(6)}\nLng: ${longitude.toFixed(6)}\nLocation verified with site geofence.`);
},
(error) => {
setIsCheckingIn(false);
alert(`Check-in failed: ${error.message}. Please ensure GPS is enabled.`);
},
{ enableHighAccuracy: true, timeout: 5000, maximumAge: 0 }
);
} else {
setIsCheckingIn(false);
alert('Geolocation is not supported by your browser.');
}
};
return (
<div className="space-y-6">
<div className="flex justify-between items-center bg-white p-4 rounded-2xl border border-slate-200 shadow-sm">
<div>
<h2 className="text-xl font-bold text-slate-800">Attendance & Labor</h2>
<p className="text-sm text-slate-500">Track workforce productivity and geofenced attendance.</p>
</div>
<button
onClick={handleGpsCheckIn}
disabled={isCheckingIn}
className="flex items-center gap-2 bg-slate-800 text-white px-4 py-2 rounded-xl text-sm font-bold shadow-lg hover:bg-slate-900 transition-all disabled:opacity-50"
>
{isCheckingIn ? <Loader2 className="w-4 h-4 animate-spin" /> : <MapPin className="w-4 h-4" />}
{isCheckingIn ? 'Locating...' : 'GPS Check-In'}
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 bg-emerald-50 text-emerald-600 rounded-xl flex items-center justify-center">
<UserCheck className="w-5 h-5" />
</div>
<div>
<p className="text-xs font-bold text-slate-400 uppercase">Present Today</p>
<p className="text-2xl font-black text-slate-800">{stats.present}</p>
</div>
</div>
<div className="w-full bg-slate-100 h-2 rounded-full overflow-hidden">
<div
className="h-full bg-emerald-500"
style={{ width: `${(stats.present / (stats.total || 1)) * 100}%` }}
/>
</div>
</div>
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 bg-blue-50 text-blue-600 rounded-xl flex items-center justify-center">
<Calendar className="w-5 h-5" />
</div>
<div>
<p className="text-xs font-bold text-slate-400 uppercase">Total Workforce</p>
<p className="text-2xl font-black text-slate-800">{stats.total}</p>
</div>
</div>
<p className="text-xs text-slate-500 font-medium">Across all categories</p>
</div>
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 bg-amber-50 text-amber-600 rounded-xl flex items-center justify-center">
<Clock className="w-5 h-5" />
</div>
<div>
<p className="text-xs font-bold text-slate-400 uppercase">Avg. Productivity</p>
<p className="text-2xl font-black text-slate-800">84%</p>
</div>
</div>
<p className="text-xs text-slate-500 font-medium">Based on DPR work achieved</p>
</div>
</div>
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden">
<div className="p-6 border-b border-slate-100 flex flex-wrap items-center justify-between gap-4">
<h3 className="font-bold text-slate-800">Daily Attendance Log</h3>
<div className="flex items-center gap-3">
<input
type="date"
value={selectedDate}
onChange={(e) => setSelectedDate(e.target.value)}
className="px-3 py-2 bg-slate-50 border border-slate-200 rounded-xl text-sm font-medium outline-none focus:ring-2 focus:ring-blue-500"
/>
<button className="p-2 text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded-lg transition-all">
<Filter className="w-5 h-5" />
</button>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-slate-50/50">
<th className="px-6 py-4 text-[10px] font-bold text-slate-400 uppercase tracking-wider">Worker Name</th>
<th className="px-6 py-4 text-[10px] font-bold text-slate-400 uppercase tracking-wider">Category</th>
<th className="px-6 py-4 text-[10px] font-bold text-slate-400 uppercase tracking-wider">Check In</th>
<th className="px-6 py-4 text-[10px] font-bold text-slate-400 uppercase tracking-wider">Check Out</th>
<th className="px-6 py-4 text-[10px] font-bold text-slate-400 uppercase tracking-wider">Status</th>
<th className="px-6 py-4 text-[10px] font-bold text-slate-400 uppercase tracking-wider"></th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{dailyAttendance.length === 0 ? (
<tr>
<td colSpan={6} className="px-6 py-12 text-center text-slate-500 text-sm italic">
No attendance records for this date
</td>
</tr>
) : (
dailyAttendance.map(record => (
<tr key={record.id} className="hover:bg-slate-50/50 transition-colors">
<td className="px-6 py-4">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-slate-100 rounded-full flex items-center justify-center text-xs font-bold text-slate-600">
{record.workerName.charAt(0)}
</div>
<span className="text-sm font-bold text-slate-800">{record.workerName}</span>
</div>
</td>
<td className="px-6 py-4">
<span className="text-xs font-medium text-slate-500">{record.category}</span>
</td>
<td className="px-6 py-4 text-sm text-slate-600">{record.checkIn}</td>
<td className="px-6 py-4 text-sm text-slate-600">{record.checkOut || '--:--'}</td>
<td className="px-6 py-4">
<span className={`px-2 py-1 rounded-full text-[10px] font-bold uppercase ${
record.status === 'PRESENT' ? 'text-emerald-600 bg-emerald-50' :
record.status === 'ABSENT' ? 'text-red-600 bg-red-50' : 'text-amber-600 bg-amber-50'
}`}>
{record.status}
</span>
</td>
<td className="px-6 py-4 text-right">
<button className="p-1 text-slate-400 hover:text-slate-600">
<MoreVertical className="w-4 h-4" />
</button>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
</div>
);
};
export default AttendanceManager;