286 lines
17 KiB
JavaScript
286 lines
17 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import Toast from '../Components/Toast';
|
|
import { ChevronLeft, Upload, X, Check, FileText } from 'lucide-react';
|
|
|
|
export default function InvestorEdit({ id }) {
|
|
const isReceptionist = window.__APP_DATA__?.role === 'receptionist';
|
|
const basePath = isReceptionist ? '/receptionist/investors' : '/owner/investors';
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
const [saving, setSaving] = useState(false);
|
|
const [branches, setBranches] = useState([]);
|
|
const [toast, setToast] = useState(null);
|
|
|
|
const [formData, setFormData] = useState({
|
|
name: '',
|
|
investment_date: '',
|
|
investment_amount: '',
|
|
applicable_to_all_branches: false,
|
|
branch_ids: [],
|
|
roi_type: 'Percentage',
|
|
roi_value: '',
|
|
roi_period: 'Monthly',
|
|
security_proof_document: null,
|
|
existing_document: null
|
|
});
|
|
|
|
const [branchDropdownOpen, setBranchDropdownOpen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const fetchData = async () => {
|
|
try {
|
|
const bRes = await fetch('/api/branches');
|
|
const bData = await bRes.json();
|
|
setBranches(bData);
|
|
|
|
const iRes = await fetch(`/api/investors/${id}`);
|
|
const iData = await iRes.json();
|
|
|
|
setFormData({
|
|
...formData,
|
|
...iData,
|
|
applicable_to_all_branches: !!iData.applicable_to_all_branches,
|
|
branch_ids: iData.branches.map(b => b.id),
|
|
existing_document: iData.security_proof_document,
|
|
security_proof_document: null
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching data:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
fetchData();
|
|
}, [id]);
|
|
|
|
const handleChange = (e) => {
|
|
const { name, value, type, checked } = e.target;
|
|
setFormData({
|
|
...formData,
|
|
[name]: type === 'checkbox' ? checked : value
|
|
});
|
|
};
|
|
|
|
const handleFileChange = (e) => {
|
|
setFormData({ ...formData, security_proof_document: e.target.files[0] });
|
|
};
|
|
|
|
const toggleBranch = (id) => {
|
|
const newBranchIds = formData.branch_ids.includes(id)
|
|
? formData.branch_ids.filter(bid => bid !== id)
|
|
: [...formData.branch_ids, id];
|
|
setFormData({ ...formData, branch_ids: newBranchIds });
|
|
};
|
|
|
|
const handleSave = async (e) => {
|
|
e.preventDefault();
|
|
setSaving(true);
|
|
|
|
const data = new FormData();
|
|
data.append('_method', 'PUT'); // For Laravel multipart PUT
|
|
Object.keys(formData).forEach(key => {
|
|
if (key === 'branch_ids') {
|
|
formData.branch_ids.forEach(id => data.append('branch_ids[]', id));
|
|
} else if (key === 'security_proof_document' && formData[key]) {
|
|
data.append('security_proof_document', formData[key]);
|
|
} else if (key === 'applicable_to_all_branches') {
|
|
data.append(key, formData[key] ? '1' : '0');
|
|
} else if (key !== 'existing_document' && key !== 'branches') {
|
|
data.append(key, formData[key] || '');
|
|
}
|
|
});
|
|
|
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
|
|
|
try {
|
|
const response = await fetch(`/api/investors/${id}`, {
|
|
method: 'POST', // Multipart must be POST in PHP/Laravel with _method=PUT
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'X-CSRF-TOKEN': csrfToken
|
|
},
|
|
body: data
|
|
});
|
|
|
|
if (response.ok) {
|
|
setToast({ message: 'Investor updated successfully!', type: 'success' });
|
|
setTimeout(() => window.location.href = basePath, 1500);
|
|
} else {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
if (errorData.errors) {
|
|
const message = Object.entries(errorData.errors)
|
|
.map(([field, msgs]) => `${field.replace('_', ' ')}: ${msgs.join(', ')}`)
|
|
.join('\n');
|
|
setToast({ message: 'Validation Error:\n' + message, type: 'error' });
|
|
} else {
|
|
setToast({ message: 'Error updating investor: ' + (errorData.message || response.statusText), type: 'error' });
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('API Error:', error);
|
|
setToast({ message: 'Failed to update investor.', type: 'error' });
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
if (loading) return <div className="p-10 text-center font-medium text-gray-400">Loading details...</div>;
|
|
|
|
const selectedBranchNames = branches
|
|
.filter(b => formData.branch_ids.includes(b.id))
|
|
.map(b => b.name)
|
|
.join(', ');
|
|
|
|
return (
|
|
<>
|
|
{toast && <Toast message={toast.message} type={toast.type} onClose={() => setToast(null)} />}
|
|
|
|
<main className="p-8 max-w-4xl mx-auto">
|
|
<div className="flex items-center gap-4 mb-8">
|
|
<button onClick={() => window.location.href = basePath} className="p-2 hover:bg-gray-100 rounded-full transition-all">
|
|
<ChevronLeft size={24} className="text-gray-600" />
|
|
</button>
|
|
<h1 className="text-2xl font-bold text-[#101828]">Edit Investor</h1>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-3xl border border-gray-100 shadow-sm p-10">
|
|
<form onSubmit={handleSave} className="space-y-8">
|
|
<div className="grid grid-cols-2 gap-8">
|
|
<div className="space-y-2">
|
|
<label className="text-[13px] font-semibold text-gray-600">Investor Name</label>
|
|
<input required type="text" name="name" value={formData.name} onChange={handleChange} className="w-full border border-gray-200 rounded-xl px-4 py-3.5 text-sm focus:ring-2 focus:ring-emerald-500 transition-all outline-none" />
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-[13px] font-semibold text-gray-600">Investment Date</label>
|
|
<input required type="date" name="investment_date" value={formData.investment_date} onChange={handleChange} className="w-full border border-gray-200 rounded-xl px-4 py-3.5 text-sm focus:ring-2 focus:ring-emerald-500 transition-all outline-none" />
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-[13px] font-semibold text-gray-600">Investment Amount (AED)</label>
|
|
<input required type="number" name="investment_amount" value={formData.investment_amount} onChange={handleChange} className="w-full border border-gray-200 rounded-xl px-4 py-3.5 text-sm focus:ring-2 focus:ring-emerald-500 transition-all outline-none" />
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<label className="text-[13px] font-semibold text-gray-600">Branch Selection</label>
|
|
<div className="flex items-center justify-between p-4 bg-gray-50/50 rounded-xl border border-gray-100">
|
|
<span className="text-sm text-gray-600 font-medium">Applicable to All Branches</span>
|
|
<button
|
|
type="button"
|
|
disabled={isReceptionist}
|
|
onClick={() => setFormData({...formData, applicable_to_all_branches: !formData.applicable_to_all_branches})}
|
|
className={`w-12 h-6 rounded-full transition-all relative ${formData.applicable_to_all_branches ? 'bg-emerald-500' : 'bg-gray-300'} ${isReceptionist ? 'opacity-50 cursor-not-allowed' : ''}`}
|
|
>
|
|
<div className={`absolute top-1 w-4 h-4 bg-white rounded-full transition-all ${formData.applicable_to_all_branches ? 'left-7' : 'left-1'}`}></div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{!formData.applicable_to_all_branches && !isReceptionist && (
|
|
<div className="col-span-2 space-y-2 animate-in slide-in-from-top-2 duration-300">
|
|
<label className="text-[13px] font-semibold text-gray-600">Select Specific Branches</label>
|
|
<div className="relative">
|
|
<button
|
|
type="button"
|
|
onClick={() => setBranchDropdownOpen(!branchDropdownOpen)}
|
|
className="w-full border border-gray-200 rounded-xl px-4 py-3.5 text-sm text-left flex items-center justify-between bg-white focus:ring-2 focus:ring-emerald-500 transition-all"
|
|
>
|
|
<span className={selectedBranchNames ? 'text-gray-900' : 'text-gray-400'}>
|
|
{selectedBranchNames || 'Select Specific Branches...'}
|
|
</span>
|
|
<ChevronLeft size={18} className={`text-gray-400 transform transition-transform ${branchDropdownOpen ? 'rotate-90' : '-rotate-90'}`} />
|
|
</button>
|
|
|
|
{branchDropdownOpen && (
|
|
<div className="absolute z-10 w-full mt-2 bg-white border border-gray-100 rounded-xl shadow-xl max-h-60 overflow-y-auto p-2">
|
|
{branches.map(branch => (
|
|
<div
|
|
key={branch.id}
|
|
onClick={() => toggleBranch(branch.id)}
|
|
className="flex items-center justify-between p-3 hover:bg-gray-50 rounded-lg cursor-pointer transition-colors"
|
|
>
|
|
<span className="text-sm font-medium text-gray-700">{branch.name}</span>
|
|
{formData.branch_ids.includes(branch.id) && <Check size={16} className="text-emerald-600" />}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-[13px] font-semibold text-gray-600">ROI Type</label>
|
|
<select name="roi_type" value={formData.roi_type} onChange={handleChange} className="w-full border border-gray-200 rounded-xl px-4 py-3.5 text-sm focus:ring-2 focus:ring-emerald-500 transition-all outline-none appearance-none bg-white">
|
|
<option value="Percentage">Percentage</option>
|
|
<option value="Fixed Amount">Fixed Amount</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-[13px] font-semibold text-gray-600">
|
|
{formData.roi_type === 'Percentage' ? 'ROI (%)' : 'Fixed Amount (AED)'}
|
|
</label>
|
|
<input type="number" name="roi_value" value={formData.roi_value} onChange={handleChange} className="w-full border border-gray-200 rounded-xl px-4 py-3.5 text-sm focus:ring-2 focus:ring-emerald-500 transition-all outline-none" />
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-[13px] font-semibold text-gray-600">ROI Period</label>
|
|
<select name="roi_period" value={formData.roi_period} onChange={handleChange} className="w-full border border-gray-200 rounded-xl px-4 py-3.5 text-sm focus:ring-2 focus:ring-emerald-500 transition-all outline-none appearance-none bg-white">
|
|
<option value="Monthly">Monthly</option>
|
|
<option value="Quarterly">Quarterly</option>
|
|
<option value="Yearly">Yearly</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<label className="text-[13px] font-semibold text-gray-600">Security Proof Document</label>
|
|
|
|
{formData.existing_document && !formData.security_proof_document && (
|
|
<div className="flex items-center justify-between p-4 bg-emerald-50/50 rounded-xl border border-emerald-100">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-emerald-100 rounded-lg text-emerald-600">
|
|
<FileText size={20} />
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-bold text-emerald-900">Current Document</p>
|
|
<p className="text-xs text-emerald-600">Existing proof attached</p>
|
|
</div>
|
|
</div>
|
|
<button type="button" onClick={() => document.getElementById('fileInput').click()} className="text-xs font-bold text-emerald-700 hover:underline">Change File</button>
|
|
</div>
|
|
)}
|
|
|
|
<div className="border-2 border-dashed border-gray-200 rounded-3xl p-10 flex flex-col items-center justify-center space-y-4 hover:border-emerald-300 transition-all group bg-gray-50/20 relative">
|
|
<input
|
|
id="fileInput"
|
|
type="file"
|
|
className="absolute inset-0 opacity-0 cursor-pointer"
|
|
onChange={handleFileChange}
|
|
/>
|
|
<div className="w-12 h-12 rounded-xl bg-gray-50 flex items-center justify-center text-gray-400 group-hover:bg-emerald-50 group-hover:text-emerald-500 transition-all">
|
|
<Upload size={24} />
|
|
</div>
|
|
<div className="text-center">
|
|
<p className="text-sm font-semibold text-gray-900">
|
|
{formData.security_proof_document ? formData.security_proof_document.name : <><span className="text-emerald-600">Upload new file</span> or drag and drop</>}
|
|
</p>
|
|
<p className="text-xs text-gray-400 mt-1 uppercase tracking-wider font-bold">PDF, PNG, JPG up to 10MB</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-end gap-3 pt-4">
|
|
<button type="button" onClick={() => window.location.href = basePath} className="px-8 py-3.5 text-gray-500 font-bold text-sm hover:bg-gray-100 rounded-xl transition-all">Cancel</button>
|
|
<button type="submit" disabled={saving} className="px-10 py-3.5 bg-emerald-600 text-white font-bold text-sm rounded-xl hover:bg-emerald-700 shadow-lg shadow-emerald-100 transition-all flex items-center gap-2">
|
|
<span>{saving ? 'Updating...' : 'Update Investor'}</span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</main>
|
|
</>
|
|
);
|
|
}
|