95 lines
4.4 KiB
TypeScript
95 lines
4.4 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import api from '../lib/api';
|
|
|
|
export default function TimeclockPage() {
|
|
const [logs, setLogs] = useState<any[]>([]);
|
|
const [status, setStatus] = useState<'CLOCKED_OUT' | 'CLOCKED_IN'>('CLOCKED_OUT');
|
|
|
|
useEffect(() => {
|
|
fetchLogs();
|
|
}, []);
|
|
|
|
const fetchLogs = async () => {
|
|
try {
|
|
const { data } = await api.get('/timeclock/logs');
|
|
setLogs(data);
|
|
// Determine status
|
|
const active = data.find((l: any) => !l.endTime);
|
|
setStatus(active ? 'CLOCKED_IN' : 'CLOCKED_OUT');
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
};
|
|
|
|
const handleClock = async (action: 'in' | 'out') => {
|
|
try {
|
|
if (action === 'in') {
|
|
await api.post('/timeclock/clock-in', { activityType: 'General' });
|
|
} else {
|
|
await api.post('/timeclock/clock-out', {});
|
|
}
|
|
await fetchLogs();
|
|
} catch (e: any) {
|
|
alert(e.response?.data?.message || 'Error clocking');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-8 max-w-2xl mx-auto">
|
|
<header className="text-center">
|
|
<h2 className="text-3xl font-bold text-neutral-800">Time Clock</h2>
|
|
<p className="text-neutral-500 mt-2">{new Date().toLocaleDateString()} {new Date().toLocaleTimeString()}</p>
|
|
</header>
|
|
|
|
<div className="bg-white p-8 rounded-2xl shadow-lg border border-neutral-200 text-center">
|
|
<div className="mb-8 p-4 bg-neutral-50 rounded-lg inline-block">
|
|
<span className="text-sm font-bold text-neutral-400 uppercase tracking-widest">Current Status</span>
|
|
<div className={`text-2xl font-bold mt-1 ${status === 'CLOCKED_IN' ? 'text-emerald-600' : 'text-neutral-600'}`}>
|
|
{status.replace('_', ' ')}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-4 justify-center">
|
|
<button
|
|
onClick={() => handleClock('in')}
|
|
disabled={status === 'CLOCKED_IN'}
|
|
className="w-40 h-40 rounded-full font-bold text-xl flex items-center justify-center transition-all disabled:opacity-50 disabled:cursor-not-allowed bg-emerald-600 text-white hover:bg-emerald-700 hover:scale-105 shadow-xl shadow-emerald-900/20"
|
|
>
|
|
CLOCK IN
|
|
</button>
|
|
<button
|
|
onClick={() => handleClock('out')}
|
|
disabled={status === 'CLOCKED_OUT'}
|
|
className="w-40 h-40 rounded-full font-bold text-xl flex items-center justify-center transition-all disabled:opacity-50 disabled:cursor-not-allowed bg-red-600 text-white hover:bg-red-700 hover:scale-105 shadow-xl shadow-red-900/20"
|
|
>
|
|
CLOCK OUT
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-xl shadow-sm border border-neutral-200 overflow-hidden">
|
|
<h3 className="text-lg font-bold text-neutral-800 p-6 border-b border-neutral-100">Recent Logs</h3>
|
|
<table className="w-full text-left text-sm">
|
|
<thead className="bg-neutral-50 text-neutral-500">
|
|
<tr>
|
|
<th className="p-4">Date</th>
|
|
<th className="p-4">Start</th>
|
|
<th className="p-4">End</th>
|
|
<th className="p-4">Activity</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-neutral-100">
|
|
{logs.map(log => (
|
|
<tr key={log.id}>
|
|
<td className="p-4">{new Date(log.startTime).toLocaleDateString()}</td>
|
|
<td className="p-4">{new Date(log.startTime).toLocaleTimeString()}</td>
|
|
<td className="p-4">{log.endTime ? new Date(log.endTime).toLocaleTimeString() : '-'}</td>
|
|
<td className="p-4">{log.activityType}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|