2026-03-13 10:08:46 +05:30

349 lines
20 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 [hasPayouts, setHasPayouts] = useState(false);
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?status=Active');
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
});
const pRes = await fetch(`/api/investors/${id}/payouts`);
const pData = await pRes.json();
setHasPayouts(pData.length > 0);
} 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>
{hasPayouts && (
<div className="mb-6 p-4 bg-amber-50 border border-amber-100 rounded-2xl flex items-start gap-4 animate-in fade-in slide-in-from-top-2 duration-300">
<div className="p-2 bg-amber-100 rounded-xl text-amber-600">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
</div>
<div className="space-y-1">
<p className="text-sm font-bold text-amber-900 uppercase italic">Financial Terms Locked</p>
<p className="text-xs text-amber-700 font-medium">Core investment terms (Amount, Date, ROI) cannot be modified because payments have already been processed for this investor to maintain accounting integrity.</p>
</div>
</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"
maxLength={30}
value={formData.name}
onChange={(e) => {
if (e.target.value.length <= 30) handleChange(e);
else setToast({ message: 'Maximum 30 characters allowed', type: 'error' });
}}
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"
disabled={hasPayouts}
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 ${hasPayouts ? 'bg-gray-50 text-gray-400 cursor-not-allowed border-gray-100' : ''}`}
/>
</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"
disabled={hasPayouts}
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 ${hasPayouts ? 'bg-gray-50 text-gray-400 cursor-not-allowed border-gray-100' : ''}`}
/>
</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"
disabled={hasPayouts}
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 ${hasPayouts ? 'bg-gray-50 text-gray-400 cursor-not-allowed border-gray-100' : ''}`}
>
<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"
disabled={hasPayouts}
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 ${hasPayouts ? 'bg-gray-50 text-gray-400 cursor-not-allowed border-gray-100' : ''}`}
/>
</div>
<div className="space-y-2">
<label className="text-[13px] font-semibold text-gray-600">ROI Period</label>
<select
name="roi_period"
disabled={hasPayouts}
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 ${hasPayouts ? 'bg-gray-50 text-gray-400 cursor-not-allowed border-gray-100' : ''}`}
>
<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>
</>
);
}