Files
time_tracker/frontend/src/components/Supervisor/SupervisorDashboard.jsx
Kris Forbes 366253ce19
All checks were successful
Docker Build and Publish / build-and-push (push) Successful in 33s
feat(frontend): add delete entry and update standby entry logic
2026-01-11 20:07:00 -05:00

123 lines
5.7 KiB
JavaScript

import React, { useState, useEffect, useContext } from 'react';
import { pb } from '../../lib/pocketbase';
import TimeList from '../TimeEntry/TimeList';
import BalanceCard from '../TimeEntry/BalanceCard';
import { LanguageContext } from '../../contexts/LanguageContext';
const SupervisorDashboard = ({ user }) => {
const { t } = useContext(LanguageContext);
const [employees, setEmployees] = useState([]);
const [selectedEmployeeId, setSelectedEmployeeId] = useState(null);
const [employeeEntries, setEmployeeEntries] = useState([]);
useEffect(() => {
const loadEmployees = async () => {
try {
// Find users who have assigned ME as supervisor
const result = await pb.collection('users').getList(1, 50, {
filter: `supervisor = "${user.id}"`,
});
setEmployees(result.items);
} catch (err) {
console.error("Error loading employees", err);
}
};
if (user.is_supervisor) {
loadEmployees();
}
}, [user.id, user.is_supervisor]);
useEffect(() => {
if (selectedEmployeeId) {
const loadEntries = async () => {
try {
const result = await pb.collection('time_entries').getList(1, 50, {
filter: `user = "${selectedEmployeeId}"`,
sort: '-date',
});
setEmployeeEntries(result.items);
} catch (err) {
console.error("Error loading employee entries", err);
}
};
loadEntries();
} else {
setEmployeeEntries([]);
}
}, [selectedEmployeeId]);
if (!user.is_supervisor) return null;
return (
<div className="bg-white p-6 rounded-lg shadow-md mb-6">
<h2 className="text-xl font-bold mb-4 text-indigo-700">{t('sup.title')}</h2>
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
{/* Employee List */}
<div className="md:col-span-1 border-r border-gray-200 pr-4">
<h3 className="font-semibold text-gray-700 mb-2">{t('sup.my_team')}</h3>
{employees.length === 0 ? (
<p className="text-sm text-gray-500">{t('sup.no_employees')}</p>
) : (
<ul className="space-y-2">
{employees.map(emp => (
<li key={emp.id}>
<button
onClick={() => setSelectedEmployeeId(emp.id)}
className={`w-full text-left px-3 py-2 rounded-md text-sm transition ${selectedEmployeeId === emp.id
? 'bg-indigo-100 text-indigo-700 font-medium'
: 'hover:bg-gray-50 text-gray-600'
}`}
>
{emp.name || emp.email}
</button>
</li>
))}
</ul>
)}
</div>
{/* Employee Details */}
<div className="md:col-span-3">
{selectedEmployeeId ? (
<div className="space-y-6">
<div className="flex justify-between items-center bg-gray-50 p-4 rounded-lg">
<h3 className="font-bold text-gray-800">
{t('dash.employee_viewing')}: {employees.find(e => e.id === selectedEmployeeId)?.name || 'Employee'}
</h3>
</div>
<BalanceCard userId={selectedEmployeeId} />
<TimeList
entries={employeeEntries}
onEntryDeleted={() => {
// Trigger reload of this employee's entries
// We can just toggle selectedEmployeeId momentarily or just use another state?
// Better: add a refresh dependency to the effect.
// Actually, let's keep it simple: just re-set the ID to trigger effect? No that's ugly.
// Let's just re-fetch in place or add a version state.
// For now, if supervisor deletes, we might want to refresh.
// But Wait, does supervisor have permission to delete?
// User said "delete a time entry owned by the user".
// Assuming supervisor can too, or we just rely on PB permissions.
// I'll add a simple force refresh mechanism.
const currentId = selectedEmployeeId;
setSelectedEmployeeId(null);
setTimeout(() => setSelectedEmployeeId(currentId), 50);
}}
/>
</div>
) : (
<div className="h-full flex items-center justify-center text-gray-400 border-2 border-dashed border-gray-200 rounded-lg">
{t('sup.select_prompt')}
</div>
)}
</div>
</div>
</div>
);
};
export default SupervisorDashboard;