feat: add toast notifications to EmployeeModal for all actions
- Toast success/error on PDF download, negate, restore, hard delete - Toast success on employee edit and violation amendment via modal callbacks - Error details from API responses included in error toasts
This commit is contained in:
@@ -6,6 +6,7 @@ import EditEmployeeModal from './EditEmployeeModal';
|
|||||||
import AmendViolationModal from './AmendViolationModal';
|
import AmendViolationModal from './AmendViolationModal';
|
||||||
import ExpirationTimeline from './ExpirationTimeline';
|
import ExpirationTimeline from './ExpirationTimeline';
|
||||||
import EmployeeNotes from './EmployeeNotes';
|
import EmployeeNotes from './EmployeeNotes';
|
||||||
|
import { useToast } from './ToastProvider';
|
||||||
|
|
||||||
const s = {
|
const s = {
|
||||||
overlay: {
|
overlay: {
|
||||||
@@ -97,6 +98,8 @@ export default function EmployeeModal({ employeeId, onClose }) {
|
|||||||
const [editingEmp, setEditingEmp] = useState(false);
|
const [editingEmp, setEditingEmp] = useState(false);
|
||||||
const [amending, setAmending] = useState(null); // violation object
|
const [amending, setAmending] = useState(null); // violation object
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
const load = useCallback(() => {
|
const load = useCallback(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
Promise.all([
|
Promise.all([
|
||||||
@@ -116,34 +119,54 @@ export default function EmployeeModal({ employeeId, onClose }) {
|
|||||||
useEffect(() => { load(); }, [load]);
|
useEffect(() => { load(); }, [load]);
|
||||||
|
|
||||||
const handleDownloadPdf = async (violId, empName, date) => {
|
const handleDownloadPdf = async (violId, empName, date) => {
|
||||||
const response = await axios.get(`/api/violations/${violId}/pdf`, { responseType: 'blob' });
|
try {
|
||||||
const url = window.URL.createObjectURL(new Blob([response.data], { type: 'application/pdf' }));
|
const response = await axios.get(`/api/violations/${violId}/pdf`, { responseType: 'blob' });
|
||||||
const link = document.createElement('a');
|
const url = window.URL.createObjectURL(new Blob([response.data], { type: 'application/pdf' }));
|
||||||
link.href = url;
|
const link = document.createElement('a');
|
||||||
link.download = `CPAS_${(empName || '').replace(/[^a-z0-9]/gi, '_')}_${date}.pdf`;
|
link.href = url;
|
||||||
document.body.appendChild(link);
|
link.download = `CPAS_${(empName || '').replace(/[^a-z0-9]/gi, '_')}_${date}.pdf`;
|
||||||
link.click();
|
document.body.appendChild(link);
|
||||||
link.remove();
|
link.click();
|
||||||
window.URL.revokeObjectURL(url);
|
link.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
toast.success('PDF downloaded.');
|
||||||
|
} catch (err) {
|
||||||
|
toast.error('PDF generation failed: ' + (err.response?.data?.error || err.message));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleHardDelete = async (id) => {
|
const handleHardDelete = async (id) => {
|
||||||
await axios.delete(`/api/violations/${id}`);
|
try {
|
||||||
setConfirmDel(null);
|
await axios.delete(`/api/violations/${id}`);
|
||||||
load();
|
toast.success('Violation permanently deleted.');
|
||||||
|
setConfirmDel(null);
|
||||||
|
load();
|
||||||
|
} catch (err) {
|
||||||
|
toast.error('Delete failed: ' + (err.response?.data?.error || err.message));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRestore = async (id) => {
|
const handleRestore = async (id) => {
|
||||||
await axios.patch(`/api/violations/${id}/restore`);
|
try {
|
||||||
setConfirmDel(null);
|
await axios.patch(`/api/violations/${id}/restore`);
|
||||||
load();
|
toast.success('Violation restored to active.');
|
||||||
|
setConfirmDel(null);
|
||||||
|
load();
|
||||||
|
} catch (err) {
|
||||||
|
toast.error('Restore failed: ' + (err.response?.data?.error || err.message));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNegate = async ({ resolution_type, details, resolved_by }) => {
|
const handleNegate = async ({ resolution_type, details, resolved_by }) => {
|
||||||
await axios.patch(`/api/violations/${negating.id}/negate`, { resolution_type, details, resolved_by });
|
try {
|
||||||
setNegating(null);
|
await axios.patch(`/api/violations/${negating.id}/negate`, { resolution_type, details, resolved_by });
|
||||||
setConfirmDel(null);
|
toast.success('Violation negated.');
|
||||||
load();
|
setNegating(null);
|
||||||
|
setConfirmDel(null);
|
||||||
|
load();
|
||||||
|
} catch (err) {
|
||||||
|
toast.error('Negate failed: ' + (err.response?.data?.error || err.message));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const tier = score ? getTier(score.active_points) : null;
|
const tier = score ? getTier(score.active_points) : null;
|
||||||
@@ -203,7 +226,7 @@ export default function EmployeeModal({ employeeId, onClose }) {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ ...s.scoreCard, minWidth: '140px' }}>
|
<div style={{ ...s.scoreCard, minWidth: '140px' }}>
|
||||||
<div style={{ fontSize: '13px', fontWeight: 700, color: tier?.color || '#f8f9fa' }}>
|
<div style={{ fontSize: '13px', fontWeight: 700, color: tier?.color || '#f8f9fa' }}>
|
||||||
{tier ? tier.label : '–'}
|
{tier ? tier.label : '—'}
|
||||||
</div>
|
</div>
|
||||||
<div style={s.scoreLbl}>Current Tier</div>
|
<div style={s.scoreLbl}>Current Tier</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -405,14 +428,14 @@ export default function EmployeeModal({ employeeId, onClose }) {
|
|||||||
<EditEmployeeModal
|
<EditEmployeeModal
|
||||||
employee={employee}
|
employee={employee}
|
||||||
onClose={() => setEditingEmp(false)}
|
onClose={() => setEditingEmp(false)}
|
||||||
onSaved={load}
|
onSaved={() => { toast.success('Employee updated.'); load(); }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{amending && (
|
{amending && (
|
||||||
<AmendViolationModal
|
<AmendViolationModal
|
||||||
violation={amending}
|
violation={amending}
|
||||||
onClose={() => setAmending(null)}
|
onClose={() => setAmending(null)}
|
||||||
onSaved={load}
|
onSaved={() => { toast.success('Violation amended.'); load(); }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user