import React, { useState, useEffect } from 'react'; import Toast from '../Components/Toast'; import { Shield, ChevronLeft, Save, History, Calculator, Clock, CheckCircle2, X, Plus, Trash2 } from 'lucide-react'; export default function StaffEdit({ id }) { const isReceptionist = window.__APP_DATA__?.role === 'receptionist'; const basePath = isReceptionist ? '/receptionist' : '/owner'; const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [branches, setBranches] = useState([]); const [roles, setRoles] = useState([]); const [history, setHistory] = useState([]); const [showHistory, setShowHistory] = useState(false); const [loadingHistory, setLoadingHistory] = useState(false); const [toast, setToast] = useState(null); const [initialCommission, setInitialCommission] = useState({ amount: '', count: '' }); const [commissionChanged, setCommissionChanged] = useState(false); const [periodSelected, setPeriodSelected] = useState(false); const [isAdvanceConfirmOpen, setIsAdvanceConfirmOpen] = useState(false); const [advanceHistory, setAdvanceHistory] = useState([]); const [loadingAdvanceHistory, setLoadingAdvanceHistory] = useState(false); const [formData, setFormData] = useState({ full_name: '', email: '', phone: '', role: 'Trainer', branch_id: '', joining_date: '', status: 'Active', salary_type: 'Fixed', salary_amount: '', cycle_effective_date: '', advance_enabled: false, advance_amount: '', advance_repayment_mode: 'Full Next Month', advance_months: '', commission_enabled: false, commission_amount: '', commission_member_count: '', apply_from: 'this_month', emirates_id: '', emirates_id_expiry: '', emirates_id_reminder_days: 30, salary_reminder_enabled: true, document_expiry_enabled: true, family_members: [], documents: [] }); useEffect(() => { const fetchData = async () => { try { // Fetch Branches and Roles const [bRes, rRes] = await Promise.all([ fetch('/api/branches?status=Active'), fetch('/api/masters/staff_role') ]); const bData = await bRes.json(); const rData = await rRes.json(); setBranches(bData); setRoles(rData.filter(r => r.status === 'Active')); // Fetch Staff Details const sRes = await fetch(`/api/staff/${id}`); const sData = await sRes.json(); if (sData) { const urlParams = new URLSearchParams(window.location.search); const autoGetAdvance = urlParams.get('get_advance') === 'true'; setFormData({ ...formData, full_name: sData.full_name || '', email: sData.email || '', phone: sData.phone || '', role: sData.role || 'Trainer', branch_id: sData.branch_id || '', joining_date: sData.joining_date || '', status: sData.status || 'Active', salary_type: sData.salary_type || 'Monthly', salary_amount: sData.salary_amount || '', cycle_effective_date: sData.cycle_effective_date || '', advance_enabled: autoGetAdvance ? true : !!sData.advance_enabled, advance_amount: sData.advance_amount || '', advance_repayment_mode: sData.advance_repayment_mode || 'Full Next Month', advance_months: sData.advance_months || '', commission_enabled: !!sData.commission_enabled, commission_amount: sData.commission_amount || '', commission_member_count: sData.commission_member_count || '', emirates_id: sData.emirates_id || '', emirates_id_expiry: sData.emirates_id_expiry || '', emirates_id_reminder_days: sData.emirates_id_reminder_days || 30, family_members: sData.family_members && sData.family_members.length > 0 ? sData.family_members : [{ name: '', relation: '', contact: '' }], documents: sData.documents || [], apply_from: '' // Reset to force choice if changed }); setInitialCommission({ amount: sData.commission_amount || '', count: sData.commission_member_count || '' }); } } catch (error) { console.error('Error fetching data:', error); setToast({ message: 'Error loading staff details. Please refresh.', type: 'error' }); } finally { setLoading(false); } }; fetchData(); fetchAdvanceHistory(); }, [id]); const fetchAdvanceHistory = async () => { setLoadingAdvanceHistory(true); try { const res = await fetch(`/api/staff/${id}/advance-history`); const data = await res.json(); setAdvanceHistory(data); } catch (error) { console.error('Error fetching advance history:', error); } finally { setLoadingAdvanceHistory(false); } }; const fetchHistory = async () => { setLoadingHistory(true); setShowHistory(true); try { const res = await fetch(`/api/staff/${id}/commission-history`); const data = await res.json(); setHistory(data); } catch (error) { console.error('Error fetching history:', error); } finally { setLoadingHistory(false); } }; const handleChange = (e) => { const { name, value, type, checked } = e.target; const newFormData = { ...formData, [name]: type === 'checkbox' ? checked : value }; // Detect commission changes if (name === 'commission_amount' || name === 'commission_member_count' || name === 'commission_enabled') { const hasChanged = newFormData.commission_enabled !== (initialCommission.enabled ?? false) || newFormData.commission_amount !== initialCommission.amount || newFormData.commission_member_count !== initialCommission.count; setCommissionChanged(hasChanged); if (!hasChanged) setPeriodSelected(false); } setFormData(newFormData); }; const handleDocumentChange = (index, e) => { const { name, value, files } = e.target; const newDocs = [...formData.documents]; if (name === 'file') { newDocs[index][name] = files[0]; } else { newDocs[index][name] = value; } setFormData({ ...formData, documents: newDocs }); }; const addDocumentRow = () => { setFormData({ ...formData, documents: [...formData.documents, { name: '', document_number: '', expiry_date: '', reminder_days: 30, file: null }] }); }; const removeDocumentRow = (index) => { const newDocs = formData.documents.filter((_, i) => i !== index); setFormData({ ...formData, documents: newDocs }); }; const handleFamilyMemberChange = (index, e) => { const { name, value } = e.target; const newMembers = [...formData.family_members]; newMembers[index][name] = value; setFormData({ ...formData, family_members: newMembers }); }; const addFamilyMemberRow = () => { setFormData({ ...formData, family_members: [...formData.family_members, { name: '', relation: '', contact: '' }] }); }; const removeFamilyMemberRow = (index) => { const newMembers = formData.family_members.filter((_, i) => i !== index); setFormData({ ...formData, family_members: newMembers }); }; const handleSave = async (e) => { if (e) e.preventDefault(); // Validation for commission change if (commissionChanged && !formData.apply_from) { setToast({ message: 'Please select when to apply commission changes (This Month or Next Month).', type: 'error' }); // Scroll to commission section const el = document.getElementById('commission-section'); if (el) el.scrollIntoView({ behavior: 'smooth' }); return; } // Branch Start Date Validation - REMOVED AS PER USER REQUEST /* const selectedBranch = branches.find(b => b.id == formData.branch_id); if (selectedBranch && formData.joining_date && selectedBranch.operational_start_date) { if (formData.joining_date < selectedBranch.operational_start_date) { setToast({ message: `Error: Joining date (${formData.joining_date}) cannot be before branch start date (${selectedBranch.operational_start_date}).`, type: 'error' }); return; } } */ // Phone Validation if (formData.phone && !/^(\+91|91|0)?[6-9]\d{9}$|^(\+971|971|0)?5[024568]\d{7}$/.test(formData.phone.replace(/[\s-]/g, ''))) { setToast({ message: 'Error: Invalid Phone format. Only Indian (+91) and UAE (+971) numbers are allowed.', type: 'error' }); return; } // Family Contact Validation for (let i = 0; i < formData.family_members.length; i++) { const member = formData.family_members[i]; if (member.contact && !/^(\+91|91|0)?[6-9]\d{9}$|^(\+971|971|0)?5[024568]\d{7}$/.test(member.contact.replace(/[\s-]/g, ''))) { setToast({ message: `Error: Invalid Family Contact format for ${member.name || (i+1)}. Only Indian (+91) and UAE (+971) numbers are allowed.`, type: 'error' }); return; } } // Document Validations for (let i = 0; i < formData.documents.length; i++) { const doc = formData.documents[i]; if (doc.name || doc.document_number || doc.expiry_date || doc.file) { if (!doc.name) { setToast({ message: `Document ${i + 1}: Name is required.`, type: 'error' }); return; } if (!doc.document_number) { setToast({ message: `Document ${i + 1}: Document Number is required.`, type: 'error' }); return; } if (!doc.expiry_date) { setToast({ message: `Document ${i + 1}: Expiry Date is required.`, type: 'error' }); return; } // Only require file if it's a new document row (no id) and no existing path if (!doc.id && !doc.file && !doc.path) { setToast({ message: `Document ${i + 1}: Please upload a file.`, type: 'error' }); return; } } } setSaving(true); const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content'); const data = new FormData(); Object.keys(formData).forEach(key => { if (key === 'documents') { formData.documents.forEach((doc, index) => { if (doc.id) data.append(`documents[${index}][id]`, doc.id); data.append(`documents[${index}][name]`, doc.name); data.append(`documents[${index}][document_number]`, doc.document_number || ''); data.append(`documents[${index}][expiry_date]`, doc.expiry_date || ''); data.append(`documents[${index}][reminder_days]`, doc.reminder_days || 30); if (doc.file) { data.append(`documents[${index}][file]`, doc.file); } }); } else if (key === 'family_members') { formData.family_members.forEach((member, index) => { data.append(`family_members[${index}][name]`, member.name); data.append(`family_members[${index}][relation]`, member.relation); data.append(`family_members[${index}][contact]`, member.contact); }); } else if (formData[key] !== null && formData[key] !== undefined) { let value = formData[key]; if (typeof value === 'boolean') { value = value ? '1' : '0'; } data.append(key, value); } }); // Use POST with _method=PUT for multipart support in Laravel data.append('_method', 'PUT'); try { const response = await fetch(`/api/staff/${id}`, { method: 'POST', headers: { 'Accept': 'application/json', 'X-CSRF-TOKEN': csrfToken }, body: data }); if (response.ok) { setToast({ message: 'Staff updated successfully!', type: 'success' }); setTimeout(() => window.location.href = `${basePath}/staff`, 1500); } else { const errorData = await response.json().catch(() => ({})); if (errorData.errors) { const message = Object.entries(errorData.errors) .map(([field, msgs]) => `• ${field.replace(/_/g, ' ')}: ${msgs.join(', ')}`) .join('\n'); setToast({ message: 'Validation Error:\n' + message, type: 'error' }); } else { setToast({ message: 'Error updating staff details: ' + (errorData.message || response.statusText), type: 'error' }); } } } catch (error) { console.error('API Error:', error); setToast({ message: 'Failed to update staff.', type: 'error' }); } finally { setSaving(false); } }; if (loading) return
Past member counts and commissions
| Month | Members | Per Head | Total |
|---|---|---|---|
| {new Date(row.effective_month + "-01").toLocaleString('default', { month: 'long', year: 'numeric' })} | {row.member_count} | AED {row.amount_per_head} | AED {row.total_amount} |
No history found for this staff member.
Are you sure you want to disable the current advance? This will stop future deductions. {advanceHistory.find(h => h.status === 'Active') && (
Warning: There is a pending balance of {(advanceHistory.find(h => h.status === 'Active').advance_amount - advanceHistory.find(h => h.status === 'Active').paid_amount).toLocaleString()} AED.