636 lines
40 KiB
JavaScript
636 lines
40 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import Toast from '../Components/Toast';
|
|
import { Shield, ChevronLeft, Save, Plus, Trash2 } from 'lucide-react';
|
|
|
|
export default function StaffAdd() {
|
|
const [loading, setLoading] = useState(true);
|
|
const [saving, setSaving] = useState(false);
|
|
const [branches, setBranches] = useState([]);
|
|
const [roles, setRoles] = useState([]);
|
|
const [toast, setToast] = useState(null);
|
|
|
|
const isReceptionist = window.__APP_DATA__?.role === 'receptionist';
|
|
const receptionistBranchId = window.__APP_DATA__?.branch?.id;
|
|
const basePath = isReceptionist ? '/receptionist' : '/owner';
|
|
|
|
const today = new Date().toISOString().split("T")[0];
|
|
|
|
const [formData, setFormData] = useState({
|
|
full_name: '',
|
|
email: '',
|
|
phone: '',
|
|
role: 'Trainer',
|
|
branch_id: isReceptionist ? (receptionistBranchId?.toString() || '') : '',
|
|
joining_date: today,
|
|
status: 'Active',
|
|
salary_type: 'Fixed',
|
|
salary_amount: '',
|
|
cycle_effective_date: today,
|
|
advance_enabled: false,
|
|
advance_amount: '',
|
|
advance_months: '',
|
|
advance_repayment_mode: 'Full Next Month',
|
|
commission_enabled: false,
|
|
commission_amount: '',
|
|
commission_member_count: '',
|
|
emirates_id: '',
|
|
emirates_id_expiry: '',
|
|
emirates_id_reminder_days: 30,
|
|
salary_reminder_enabled: true,
|
|
document_expiry_enabled: true,
|
|
family_members: [{ name: '', relation: '', contact: '' }],
|
|
documents: [{ name: '', document_number: '', expiry_date: '', reminder_days: 30, file: null }]
|
|
});
|
|
|
|
useEffect(() => {
|
|
const fetchData = async () => {
|
|
try {
|
|
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);
|
|
const activeRoles = rData.filter(r => r.status === 'Active');
|
|
setRoles(activeRoles);
|
|
if (activeRoles.length > 0) {
|
|
setFormData(prev => ({ ...prev, role: prev.role || activeRoles[0].name }));
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching data:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
fetchData();
|
|
}, []);
|
|
|
|
const handleChange = (e) => {
|
|
const { name, value, type, checked } = e.target;
|
|
setFormData({
|
|
...formData,
|
|
[name]: type === 'checkbox' ? checked : value
|
|
});
|
|
};
|
|
|
|
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();
|
|
|
|
// Comprehensive Validations
|
|
const errors = [];
|
|
if (!formData.full_name.trim()) errors.push('Full Name is required.');
|
|
if (!formData.email.trim()) errors.push('Email is required.');
|
|
else if (!/\S+@\S+\.\S+/.test(formData.email)) errors.push('Invalid Email format.');
|
|
|
|
if (!formData.phone.trim()) errors.push('Phone number is required.');
|
|
else if (!/^(\+91|91|0)?[6-9]\d{9}$|^(\+971|971|0)?5[024568]\d{7}$/.test(formData.phone.replace(/[\s-]/g, ''))) {
|
|
errors.push('Invalid Phone format. Only Indian (+91) and UAE (+971) numbers are allowed.');
|
|
}
|
|
if (!formData.branch_id) errors.push('Please select a Branch.');
|
|
if (!formData.joining_date) errors.push('Joining Date is required.');
|
|
if (!formData.role) errors.push('Please select a Role.');
|
|
|
|
if (!formData.salary_amount || parseFloat(formData.salary_amount) <= 0) {
|
|
errors.push('Please enter a valid Salary Amount.');
|
|
}
|
|
|
|
// 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) {
|
|
errors.push(`Joining date (${formData.joining_date}) cannot be before branch start date (${selectedBranch.operational_start_date}).`);
|
|
}
|
|
}
|
|
*/
|
|
|
|
if (formData.advance_enabled) {
|
|
if (!formData.advance_amount || parseFloat(formData.advance_amount) <= 0) errors.push('Please enter a valid Advance Amount.');
|
|
if (formData.advance_repayment_mode === 'Divide by Months' && (!formData.advance_months || parseInt(formData.advance_months) <= 0)) {
|
|
errors.push('Please enter a valid number of months for repayment.');
|
|
}
|
|
}
|
|
|
|
if (formData.commission_enabled) {
|
|
if (!formData.commission_amount || parseFloat(formData.commission_amount) <= 0) errors.push('Please enter a valid Commission Amount.');
|
|
if (!formData.commission_member_count || parseInt(formData.commission_member_count) <= 0) errors.push('Please enter a valid Member Count for commission.');
|
|
}
|
|
|
|
// Family Details Validation
|
|
formData.family_members.forEach((member, idx) => {
|
|
if (member.name || member.relation || member.contact) {
|
|
if (!member.name) errors.push(`Family Member ${idx + 1}: Name is required.`);
|
|
if (!member.relation) errors.push(`Family Member ${idx + 1}: Relation is required.`);
|
|
if (!member.contact) errors.push(`Family Member ${idx + 1}: Contact is required.`);
|
|
else if (!/^(\+91|91|0)?[6-9]\d{9}$|^(\+971|971|0)?5[024568]\d{7}$/.test(member.contact.replace(/[\s-]/g, ''))) {
|
|
errors.push(`Family Member ${idx + 1}: Invalid Contact format. Only Indian (+91) and UAE (+971) numbers are allowed.`);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Document Validations (Check if any document rows have partial data)
|
|
formData.documents.forEach((doc, idx) => {
|
|
if (doc.name || doc.document_number || doc.expiry_date || doc.file) {
|
|
if (!doc.name) errors.push(`Document ${idx + 1}: Name is required.`);
|
|
if (!doc.document_number) errors.push(`Document ${idx + 1}: Document Number is required.`);
|
|
if (!doc.expiry_date) errors.push(`Document ${idx + 1}: Expiry Date is required.`);
|
|
if (!doc.file) errors.push(`Document ${idx + 1}: Please upload a file.`);
|
|
}
|
|
});
|
|
|
|
if (errors.length > 0) {
|
|
setToast({ message: 'Validation Errors:\n• ' + errors.join('\n• '), type: 'error' });
|
|
return;
|
|
}
|
|
|
|
setSaving(true);
|
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
|
const data = new FormData();
|
|
// ... (rest of the method remains similar but with the validation above)
|
|
Object.keys(formData).forEach(key => {
|
|
if (key === 'documents') {
|
|
formData.documents.forEach((doc, index) => {
|
|
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);
|
|
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 {
|
|
let value = formData[key];
|
|
if (typeof value === 'boolean') {
|
|
value = value ? '1' : '0';
|
|
}
|
|
data.append(key, value);
|
|
}
|
|
});
|
|
|
|
try {
|
|
const response = await fetch('/api/staff', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'X-CSRF-TOKEN': csrfToken
|
|
},
|
|
body: data
|
|
});
|
|
|
|
if (response.ok) {
|
|
setToast({ message: 'Staff member added 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 saving staff member: ' + (errorData.message || response.statusText), type: 'error' });
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('API Error:', error);
|
|
setToast({ message: 'Failed to save staff member.', type: 'error' });
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
if (loading) return <div className="p-10 text-center font-medium text-gray-400">Loading form...</div>;
|
|
|
|
return (
|
|
<div className="pb-20">
|
|
{toast && <Toast message={toast.message} type={toast.type} onClose={() => setToast(null)} />}
|
|
<main className="p-8 max-w-5xl mx-auto space-y-8">
|
|
{/* Top Actions */}
|
|
<div className="flex items-center justify-between">
|
|
<button onClick={() => window.location.href = `${basePath}/staff`} className="flex items-center gap-2 text-sm font-bold text-gray-500 hover:text-gray-900 transition-colors">
|
|
<ChevronLeft size={18} />
|
|
<span>Back to Staff List</span>
|
|
</button>
|
|
<button
|
|
onClick={handleSave}
|
|
disabled={saving}
|
|
className="flex items-center gap-2 px-6 py-2.5 bg-red-500 text-white rounded-xl font-bold text-sm hover:hover:scale-[1.02] active:scale-[0.98] transition-all shadow-lg shadow-red-100 disabled:opacity-50"
|
|
>
|
|
<Save size={18} />
|
|
<span>{saving ? 'Adding...' : 'Create Staff Member'}</span>
|
|
</button>
|
|
</div>
|
|
|
|
<form onSubmit={handleSave} className="grid grid-cols-1 gap-8">
|
|
{/* Card 1: Basic Details */}
|
|
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden">
|
|
<div className="p-6 border-b border-gray-50 bg-gray-50/30">
|
|
<h3 className="text-lg font-bold text-gray-900">Basic Details</h3>
|
|
</div>
|
|
<div className="p-8 grid grid-cols-2 gap-6">
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Full Name *</label>
|
|
<input type="text" name="full_name" value={formData.full_name} onChange={handleChange} className="w-full bg-gray-50 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-red-500 transition-all font-medium" placeholder="Ex: Alex Johnson" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Email *</label>
|
|
<input type="text" name="email" value={formData.email} onChange={handleChange} className="w-full bg-gray-50 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-red-500 transition-all font-medium" placeholder="Ex: alex@gym.com" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Phone (India/UAE) *</label>
|
|
<input type="text" name="phone" value={formData.phone} onChange={handleChange} className="w-full bg-gray-50 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-red-500 transition-all font-medium" placeholder="Ex: +91 98765 43210 or +971 50 123 4567" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Role *</label>
|
|
<select name="role" value={formData.role} onChange={handleChange} className="w-full bg-gray-50 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-red-500 transition-all font-medium appearance-none">
|
|
{roles.length === 0 && <option value="">No roles configured</option>}
|
|
{roles.map(r => (
|
|
<option key={r.id} value={r.name}>{r.name}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
{!isReceptionist && (
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Branch *</label>
|
|
<select name="branch_id" value={formData.branch_id} onChange={handleChange} className="w-full bg-gray-50 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-red-500 transition-all font-medium appearance-none">
|
|
<option value="">Select Branch...</option>
|
|
{branches.map(branch => (
|
|
<option key={branch.id} value={branch.id}>{branch.name}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
)}
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Joining Date *</label>
|
|
<input type="date" name="joining_date" value={formData.joining_date} onChange={handleChange} className="w-full bg-gray-50 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-red-500 transition-all font-medium" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Status</label>
|
|
<select name="status" value={formData.status} onChange={handleChange} className="w-full bg-gray-50 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-red-500 transition-all font-medium appearance-none">
|
|
<option value="Active">Active</option>
|
|
<option value="Inactive">Inactive</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Salary Details */}
|
|
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden">
|
|
<div className="p-6 border-b border-gray-50 bg-gray-50/30">
|
|
<h3 className="text-lg font-bold text-gray-900">Salary Details</h3>
|
|
</div>
|
|
<div className="p-8 grid grid-cols-2 gap-6">
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Salary Type</label>
|
|
<select name="salary_type" value={formData.salary_type} onChange={handleChange} className="w-full bg-gray-50 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-red-500 transition-all font-medium appearance-none">
|
|
<option value="Monthly">Monthly</option>
|
|
<option value="Daily">Daily</option>
|
|
<option value="Weekly">Weekly</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Amount (AED) *</label>
|
|
<input type="number" name="salary_amount" value={formData.salary_amount} onChange={handleChange} className="w-full bg-gray-50 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-red-500 transition-all font-medium" placeholder="0.00" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Salary Advance */}
|
|
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden">
|
|
<div className="p-6 border-b border-gray-50 bg-gray-50/30">
|
|
<h3 className="text-lg font-bold text-gray-900">Salary Advance</h3>
|
|
</div>
|
|
<div className="p-8 space-y-6">
|
|
{!formData.advance_enabled ? (
|
|
<div className="py-8 text-center bg-gray-50 rounded-2xl border-2 border-dashed border-gray-100">
|
|
<button
|
|
type="button"
|
|
onClick={() => setFormData({...formData, advance_enabled: true})}
|
|
className="px-6 py-2.5 bg-white border border-gray-200 text-gray-700 rounded-xl text-sm font-bold hover:bg-gray-50 transition-all shadow-sm flex items-center gap-2 mx-auto"
|
|
>
|
|
Get Advance
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="animate-in slide-in-from-top-2 duration-300 space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-2 h-2 rounded-full bg-orange-500 shadow-[0_0_10px_rgba(249,115,22,0.5)]" />
|
|
<span className="text-[#344054] font-bold text-sm">Advance Configuration Active</span>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => setFormData({...formData, advance_enabled: false, advance_amount: '', advance_months: ''})}
|
|
className="text-xs font-bold text-red-500 hover:text-red-600 transition-colors uppercase tracking-widest"
|
|
>
|
|
Remove Advance
|
|
</button>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-6 pt-4">
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Advance Amount (AED)</label>
|
|
<input type="number" name="advance_amount" value={formData.advance_amount} onChange={handleChange} className="w-full bg-gray-50 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-red-500 transition-all font-medium" placeholder="e.g. 3000" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Repayment Mode</label>
|
|
<div className="flex gap-2">
|
|
<button
|
|
type="button"
|
|
onClick={() => setFormData({...formData, advance_repayment_mode: 'Full Next Month'})}
|
|
className={`flex-1 py-3 text-sm font-bold rounded-xl border transition-all ${formData.advance_repayment_mode === 'Full Next Month' ? 'border-orange-500 bg-orange-50/50 text-orange-600' : 'border-gray-200 text-gray-500 bg-white'}`}
|
|
>
|
|
Full Next Month
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => setFormData({...formData, advance_repayment_mode: 'Divide by Months'})}
|
|
className={`flex-1 py-3 text-sm font-bold rounded-xl border transition-all ${formData.advance_repayment_mode === 'Divide by Months' ? 'border-orange-500 bg-orange-50/50 text-orange-600' : 'border-gray-200 text-gray-500 bg-white'}`}
|
|
>
|
|
Divide by Months
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{formData.advance_repayment_mode === 'Divide by Months' && (
|
|
<div className="grid grid-cols-2 gap-6 p-6 bg-gray-50 rounded-2xl border border-gray-100 animate-in zoom-in-95 duration-200">
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Number of Months</label>
|
|
<input
|
|
type="number"
|
|
name="advance_months"
|
|
value={formData.advance_months}
|
|
onChange={handleChange}
|
|
className="w-full bg-white border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-red-500 transition-all font-medium"
|
|
placeholder="e.g. 3"
|
|
/>
|
|
</div>
|
|
<div className="flex flex-col justify-center">
|
|
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-1">Monthly Deduction</p>
|
|
<p className="text-xl font-black text-orange-600">
|
|
AED {((parseFloat(formData.advance_amount) || 0) / (parseInt(formData.advance_months) || 1)).toFixed(2)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Commission Card */}
|
|
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden text-purple-600">
|
|
<div className="p-6 border-b border-gray-50 bg-gray-50/30 font-bold">
|
|
Commission Settings
|
|
</div>
|
|
<div className="p-8 space-y-6">
|
|
<div className="flex items-center gap-3">
|
|
<input
|
|
type="checkbox"
|
|
name="commission_enabled"
|
|
checked={formData.commission_enabled}
|
|
onChange={handleChange}
|
|
className="w-5 h-5 rounded border-gray-300 text-purple-600 focus:ring-purple-500 cursor-pointer"
|
|
/>
|
|
<span className="text-[#344054] font-medium text-sm">Enable per-person commission</span>
|
|
</div>
|
|
|
|
{formData.commission_enabled && (
|
|
<div className="animate-in slide-in-from-top-2 duration-300 space-y-6">
|
|
<div className="grid grid-cols-2 gap-6">
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Number of Members</label>
|
|
<input type="number" name="commission_member_count" value={formData.commission_member_count} onChange={handleChange} className="w-full bg-gray-50 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-purple-500 transition-all font-medium" placeholder="Ex: 15" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Amount per Person (AED)</label>
|
|
<input type="number" name="commission_amount" value={formData.commission_amount} onChange={handleChange} className="w-full bg-gray-50 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-purple-500 transition-all font-medium" placeholder="e.g. 195" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-6 p-6 bg-purple-50 rounded-2xl border border-purple-100 animate-in zoom-in-95 duration-200">
|
|
<div>
|
|
<p className="text-[10px] font-bold text-purple-400 uppercase tracking-widest mb-1">Total Commission</p>
|
|
<p className="text-2xl font-black text-purple-600">
|
|
AED {((parseFloat(formData.commission_member_count) || 0) * (parseFloat(formData.commission_amount) || 0)).toFixed(2)}
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center text-[10px] text-purple-400 font-medium leading-relaxed italic">
|
|
* This amount will be added to the monthly settlement automatically.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-8">
|
|
{/* Documentation Card */}
|
|
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden text-blue-600">
|
|
<div className="p-6 border-b border-gray-50 bg-gray-50/30 flex items-center justify-between font-bold">
|
|
<span>Documentation</span>
|
|
<button
|
|
type="button"
|
|
onClick={addDocumentRow}
|
|
className="flex items-center gap-2 px-3 py-1.5 bg-blue-50 text-blue-600 rounded-lg text-xs hover:bg-blue-100 transition-all border border-blue-100"
|
|
>
|
|
<Plus size={14} />
|
|
<span>Add Document</span>
|
|
</button>
|
|
</div>
|
|
<div className="p-8 space-y-8">
|
|
{formData.documents.map((doc, index) => (
|
|
<div key={index} className="relative p-6 bg-gray-50 rounded-2xl border border-gray-100 animate-in slide-in-from-top-2 duration-300">
|
|
{formData.documents.length > 1 && (
|
|
<button
|
|
type="button"
|
|
onClick={() => removeDocumentRow(index)}
|
|
className="absolute top-4 right-4 text-red-400 hover:text-red-600 transition-colors"
|
|
>
|
|
<Trash2 size={18} />
|
|
</button>
|
|
)}
|
|
<div className="grid grid-cols-2 gap-6">
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Document Name</label>
|
|
<input
|
|
type="text"
|
|
name="name"
|
|
value={doc.name}
|
|
onChange={(e) => handleDocumentChange(index, e)}
|
|
className="w-full bg-white border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-blue-500 transition-all font-medium"
|
|
placeholder="Ex: Visa, Emirates ID, Passport"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Document Number</label>
|
|
<input
|
|
type="text"
|
|
name="document_number"
|
|
value={doc.document_number}
|
|
onChange={(e) => handleDocumentChange(index, e)}
|
|
className="w-full bg-white border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-blue-500 transition-all font-medium"
|
|
placeholder="Number / ID"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Expiry Date</label>
|
|
<input
|
|
type="date"
|
|
name="expiry_date"
|
|
value={doc.expiry_date}
|
|
onChange={(e) => handleDocumentChange(index, e)}
|
|
className="w-full bg-white border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-blue-500 transition-all font-medium"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Days Before</label>
|
|
<input
|
|
type="number"
|
|
name="reminder_days"
|
|
value={doc.reminder_days}
|
|
onChange={(e) => handleDocumentChange(index, e)}
|
|
className="w-full bg-white border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-blue-500 transition-all font-medium"
|
|
placeholder="30"
|
|
/>
|
|
</div>
|
|
<div className="col-span-2">
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Upload Document</label>
|
|
<input
|
|
type="file"
|
|
name="file"
|
|
onChange={(e) => handleDocumentChange(index, e)}
|
|
className="w-full bg-white border-none rounded-xl px-4 py-2.5 text-xs focus:ring-2 focus:ring-blue-500 transition-all font-medium file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-xs file:font-black file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Family Members Card */}
|
|
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden text-emerald-600">
|
|
<div className="p-6 border-b border-gray-50 bg-gray-50/30 flex items-center justify-between font-bold">
|
|
<span>Family Members</span>
|
|
<button
|
|
type="button"
|
|
onClick={addFamilyMemberRow}
|
|
className="flex items-center gap-2 px-3 py-1.5 bg-emerald-50 text-emerald-600 rounded-lg text-xs hover:bg-emerald-100 transition-all border border-emerald-100"
|
|
>
|
|
<Plus size={14} />
|
|
<span>Add Member</span>
|
|
</button>
|
|
</div>
|
|
<div className="p-8 space-y-6">
|
|
{formData.family_members.map((member, index) => (
|
|
<div key={index} className="relative p-6 bg-gray-50 rounded-2xl border border-gray-100 animate-in slide-in-from-top-2 duration-300">
|
|
{formData.family_members.length > 1 && (
|
|
<button
|
|
type="button"
|
|
onClick={() => removeFamilyMemberRow(index)}
|
|
className="absolute top-4 right-4 text-red-400 hover:text-red-600 transition-colors"
|
|
>
|
|
<Trash2 size={18} />
|
|
</button>
|
|
)}
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Member Name</label>
|
|
<input
|
|
type="text"
|
|
name="name"
|
|
value={member.name}
|
|
onChange={(e) => handleFamilyMemberChange(index, e)}
|
|
className="w-full bg-white border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-emerald-500 transition-all font-medium"
|
|
placeholder="e.g. Jane Doe"
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Relation</label>
|
|
<input
|
|
type="text"
|
|
name="relation"
|
|
value={member.relation}
|
|
onChange={(e) => handleFamilyMemberChange(index, e)}
|
|
className="w-full bg-white border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-emerald-500 transition-all font-medium"
|
|
placeholder="e.g. Spouse"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Contact Number</label>
|
|
<input
|
|
type="text"
|
|
name="contact"
|
|
value={member.contact}
|
|
onChange={(e) => handleFamilyMemberChange(index, e)}
|
|
className="w-full bg-white border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-emerald-500 transition-all font-medium"
|
|
placeholder="05XXXXXXXX"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</form>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|