|
@@ -34,10 +34,16 @@ export default function LeaseDetail() {
|
|
|
const [revisions, setRevisions] = useState([])
|
|
const [revisions, setRevisions] = useState([])
|
|
|
const [pendingReg, setPendingReg] = useState({ pending: [], total: 0 })
|
|
const [pendingReg, setPendingReg] = useState({ pending: [], total: 0 })
|
|
|
const [showModal, setShowModal] = useState(false)
|
|
const [showModal, setShowModal] = useState(false)
|
|
|
- const [editPayment, setEditPayment] = useState(null) // null = création, sinon paiement existant
|
|
|
|
|
|
|
+ const [editPayment, setEditPayment] = useState(null)
|
|
|
const [showRevisionModal, setShowRevisionModal] = useState(false)
|
|
const [showRevisionModal, setShowRevisionModal] = useState(false)
|
|
|
|
|
+ const [showEditLeaseModal, setShowEditLeaseModal] = useState(false)
|
|
|
const [loading, setLoading] = useState(false)
|
|
const [loading, setLoading] = useState(false)
|
|
|
const [revLoading, setRevLoading] = useState(false)
|
|
const [revLoading, setRevLoading] = useState(false)
|
|
|
|
|
+ const [leaseEditLoading, setLeaseEditLoading] = useState(false)
|
|
|
|
|
+ const [properties, setProperties] = useState([])
|
|
|
|
|
+ const [tenants, setTenants] = useState([])
|
|
|
|
|
+ const [owners, setOwners] = useState([])
|
|
|
|
|
+ const [leaseForm, setLeaseForm] = useState({})
|
|
|
|
|
|
|
|
const now = new Date()
|
|
const now = new Date()
|
|
|
const [form, setForm] = useState({
|
|
const [form, setForm] = useState({
|
|
@@ -62,16 +68,22 @@ export default function LeaseDetail() {
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const load = async () => {
|
|
const load = async () => {
|
|
|
- const [l, p, r, reg] = await Promise.all([
|
|
|
|
|
|
|
+ const [l, p, r, reg, props, ten, own] = await Promise.all([
|
|
|
api.get(`/leases/${id}`),
|
|
api.get(`/leases/${id}`),
|
|
|
api.get(`/payments?lease_id=${id}`),
|
|
api.get(`/payments?lease_id=${id}`),
|
|
|
api.get(`/leases/${id}/revisions`),
|
|
api.get(`/leases/${id}/revisions`),
|
|
|
api.get(`/payments/regularization/${id}`),
|
|
api.get(`/payments/regularization/${id}`),
|
|
|
|
|
+ api.get('/properties'),
|
|
|
|
|
+ api.get('/tenants'),
|
|
|
|
|
+ api.get('/owners'),
|
|
|
])
|
|
])
|
|
|
setLease(l.data)
|
|
setLease(l.data)
|
|
|
setPayments(p.data)
|
|
setPayments(p.data)
|
|
|
setRevisions(r.data)
|
|
setRevisions(r.data)
|
|
|
setPendingReg(reg.data)
|
|
setPendingReg(reg.data)
|
|
|
|
|
+ setProperties(props.data)
|
|
|
|
|
+ setTenants(ten.data)
|
|
|
|
|
+ setOwners(own.data)
|
|
|
setForm(f => ({ ...f, rent_paid: l.data.rent_amount, charges_paid: l.data.charges_amount }))
|
|
setForm(f => ({ ...f, rent_paid: l.data.rent_amount, charges_paid: l.data.charges_amount }))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -134,6 +146,42 @@ export default function LeaseDetail() {
|
|
|
catch { toast.error('Erreur') }
|
|
catch { toast.error('Erreur') }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ const openEditLease = () => {
|
|
|
|
|
+ setLeaseForm({
|
|
|
|
|
+ property_id: String(lease.property_id),
|
|
|
|
|
+ tenant_id: String(lease.tenant_id),
|
|
|
|
|
+ owner_id: lease.owner_id ? String(lease.owner_id) : '',
|
|
|
|
|
+ start_date: lease.start_date ? lease.start_date.slice(0, 10) : '',
|
|
|
|
|
+ end_date: lease.end_date ? lease.end_date.slice(0, 10) : '',
|
|
|
|
|
+ rent_amount: String(lease.rent_amount),
|
|
|
|
|
+ charges_amount: String(lease.charges_amount),
|
|
|
|
|
+ deposit_amount: String(lease.deposit_amount),
|
|
|
|
|
+ payment_day: String(lease.payment_day),
|
|
|
|
|
+ })
|
|
|
|
|
+ setShowEditLeaseModal(true)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handleEditLease = async e => {
|
|
|
|
|
+ e.preventDefault(); setLeaseEditLoading(true)
|
|
|
|
|
+ try {
|
|
|
|
|
+ await api.put(`/leases/${id}`, {
|
|
|
|
|
+ ...leaseForm,
|
|
|
|
|
+ property_id: Number(leaseForm.property_id),
|
|
|
|
|
+ tenant_id: Number(leaseForm.tenant_id),
|
|
|
|
|
+ owner_id: leaseForm.owner_id ? Number(leaseForm.owner_id) : null,
|
|
|
|
|
+ rent_amount: parseFloat(leaseForm.rent_amount),
|
|
|
|
|
+ charges_amount: parseFloat(leaseForm.charges_amount || 0),
|
|
|
|
|
+ deposit_amount: parseFloat(leaseForm.deposit_amount || 0),
|
|
|
|
|
+ payment_day: parseInt(leaseForm.payment_day || 1),
|
|
|
|
|
+ active: lease.active,
|
|
|
|
|
+ })
|
|
|
|
|
+ toast.success('Bail mis à jour !')
|
|
|
|
|
+ setShowEditLeaseModal(false)
|
|
|
|
|
+ load()
|
|
|
|
|
+ } catch (err) { toast.error(err.response?.data?.error || 'Erreur') }
|
|
|
|
|
+ finally { setLeaseEditLoading(false) }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
const handleRevision = async e => {
|
|
const handleRevision = async e => {
|
|
|
e.preventDefault(); setRevLoading(true)
|
|
e.preventDefault(); setRevLoading(true)
|
|
|
try {
|
|
try {
|
|
@@ -198,6 +246,7 @@ export default function LeaseDetail() {
|
|
|
setShowRevisionModal(true)
|
|
setShowRevisionModal(true)
|
|
|
}} className="btn-secondary text-sm">📈 Réviser le loyer</button>
|
|
}} className="btn-secondary text-sm">📈 Réviser le loyer</button>
|
|
|
)}
|
|
)}
|
|
|
|
|
+ <button onClick={openEditLease} className="btn-secondary text-sm">✏️ Modifier le bail</button>
|
|
|
<span className={lease.active ? 'badge-green' : 'badge-red'}>{lease.active ? 'Actif' : 'Clôturé'}</span>
|
|
<span className={lease.active ? 'badge-green' : 'badge-red'}>{lease.active ? 'Actif' : 'Clôturé'}</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -232,6 +281,26 @@ export default function LeaseDetail() {
|
|
|
<p className="font-semibold text-gray-900">Le {lease.payment_day} du mois</p>
|
|
<p className="font-semibold text-gray-900">Le {lease.payment_day} du mois</p>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Propriétaire bailleur */}
|
|
|
|
|
+ {lease.owner_type && (
|
|
|
|
|
+ <div className="mt-4 pt-4 border-t">
|
|
|
|
|
+ <p className="text-sm text-gray-500 mb-1">🏢 Propriétaire bailleur</p>
|
|
|
|
|
+ <p className="font-medium text-gray-900">
|
|
|
|
|
+ {lease.owner_type === 'morale'
|
|
|
|
|
+ ? lease.owner_company_name
|
|
|
|
|
+ : `${lease.owner_first_name || ''} ${lease.owner_last_name || ''}`.trim()}
|
|
|
|
|
+ {lease.owner_siret && <span className="ml-2 text-xs text-gray-400 font-normal">SIRET {lease.owner_siret}</span>}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ {lease.owner_address && (
|
|
|
|
|
+ <p className="text-sm text-gray-500">{lease.owner_address}{lease.owner_zip ? `, ${lease.owner_zip}` : ''}{lease.owner_city ? ` ${lease.owner_city}` : ''}</p>
|
|
|
|
|
+ )}
|
|
|
|
|
+ <div className="flex gap-4 mt-1 text-sm text-gray-500">
|
|
|
|
|
+ {lease.owner_email && <span>✉️ {lease.owner_email}</span>}
|
|
|
|
|
+ {lease.owner_phone && <span>📞 {lease.owner_phone}</span>}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
{/* Historique des révisions */}
|
|
{/* Historique des révisions */}
|
|
@@ -540,6 +609,85 @@ export default function LeaseDetail() {
|
|
|
</form>
|
|
</form>
|
|
|
</Modal>
|
|
</Modal>
|
|
|
)}
|
|
)}
|
|
|
|
|
+
|
|
|
|
|
+ {/* Modal édition bail */}
|
|
|
|
|
+ {showEditLeaseModal && (
|
|
|
|
|
+ <Modal title="✏️ Modifier le bail" onClose={() => setShowEditLeaseModal(false)}>
|
|
|
|
|
+ <form onSubmit={handleEditLease} className="space-y-4 max-h-[70vh] overflow-y-auto pr-1">
|
|
|
|
|
+
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <label className="label">Bien *</label>
|
|
|
|
|
+ <select className="input" required value={leaseForm.property_id}
|
|
|
|
|
+ onChange={e => setLeaseForm(f => ({ ...f, property_id: e.target.value }))}>
|
|
|
|
|
+ <option value="">Sélectionner un bien</option>
|
|
|
|
|
+ {properties.map(p => <option key={p.id} value={p.id}>{p.name} — {p.city}</option>)}
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <label className="label">Locataire *</label>
|
|
|
|
|
+ <select className="input" required value={leaseForm.tenant_id}
|
|
|
|
|
+ onChange={e => setLeaseForm(f => ({ ...f, tenant_id: e.target.value }))}>
|
|
|
|
|
+ <option value="">Sélectionner un locataire</option>
|
|
|
|
|
+ {tenants.map(t => <option key={t.id} value={t.id}>{t.first_name} {t.last_name}</option>)}
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <label className="label">Propriétaire bailleur</label>
|
|
|
|
|
+ <select className="input" value={leaseForm.owner_id}
|
|
|
|
|
+ onChange={e => setLeaseForm(f => ({ ...f, owner_id: e.target.value }))}>
|
|
|
|
|
+ <option value="">— Aucun / À renseigner —</option>
|
|
|
|
|
+ {owners.map(o => (
|
|
|
|
|
+ <option key={o.id} value={o.id}>
|
|
|
|
|
+ {o.type === 'morale' ? `🏢 ${o.company_name}` : `👤 ${o.first_name} ${o.last_name}`}
|
|
|
|
|
+ </option>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div className="grid grid-cols-2 gap-4">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <label className="label">Date de début *</label>
|
|
|
|
|
+ <input className="input" type="date" required value={leaseForm.start_date}
|
|
|
|
|
+ onChange={e => setLeaseForm(f => ({ ...f, start_date: e.target.value }))} />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <label className="label">Date de fin</label>
|
|
|
|
|
+ <input className="input" type="date" value={leaseForm.end_date}
|
|
|
|
|
+ onChange={e => setLeaseForm(f => ({ ...f, end_date: e.target.value }))} />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <label className="label">Loyer HC (€) *</label>
|
|
|
|
|
+ <input className="input" type="number" step="0.01" min="0" required value={leaseForm.rent_amount}
|
|
|
|
|
+ onChange={e => setLeaseForm(f => ({ ...f, rent_amount: e.target.value }))} />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <label className="label">Charges (€)</label>
|
|
|
|
|
+ <input className="input" type="number" step="0.01" min="0" value={leaseForm.charges_amount}
|
|
|
|
|
+ onChange={e => setLeaseForm(f => ({ ...f, charges_amount: e.target.value }))} />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <label className="label">Dépôt de garantie (€)</label>
|
|
|
|
|
+ <input className="input" type="number" step="0.01" min="0" value={leaseForm.deposit_amount}
|
|
|
|
|
+ onChange={e => setLeaseForm(f => ({ ...f, deposit_amount: e.target.value }))} />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <label className="label">Jour de paiement</label>
|
|
|
|
|
+ <input className="input" type="number" min="1" max="28" value={leaseForm.payment_day}
|
|
|
|
|
+ onChange={e => setLeaseForm(f => ({ ...f, payment_day: e.target.value }))} />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div className="flex gap-3 pt-2">
|
|
|
|
|
+ <button type="button" onClick={() => setShowEditLeaseModal(false)} className="btn-secondary flex-1">Annuler</button>
|
|
|
|
|
+ <button type="submit" disabled={leaseEditLoading} className="btn-primary flex-1">
|
|
|
|
|
+ {leaseEditLoading ? '...' : 'Enregistrer'}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </Modal>
|
|
|
|
|
+ )}
|
|
|
</div>
|
|
</div>
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|