|
@@ -13,13 +13,13 @@ const METHODS = [
|
|
|
|
|
|
|
|
function Modal({ title, onClose, children }) {
|
|
function Modal({ title, onClose, children }) {
|
|
|
return (
|
|
return (
|
|
|
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
|
|
|
|
- <div className="bg-white rounded-2xl shadow-xl w-full max-w-lg">
|
|
|
|
|
- <div className="flex items-center justify-between p-6 border-b">
|
|
|
|
|
|
|
+ <div className="fixed inset-0 bg-black/50 flex items-end sm:items-center justify-center z-50 p-0 sm:p-4">
|
|
|
|
|
+ <div className="bg-white rounded-t-2xl sm:rounded-2xl shadow-xl w-full sm:max-w-lg max-h-[92vh] sm:max-h-[90vh] flex flex-col">
|
|
|
|
|
+ <div className="flex items-center justify-between p-4 sm:p-6 border-b shrink-0">
|
|
|
<h2 className="text-lg font-semibold">{title}</h2>
|
|
<h2 className="text-lg font-semibold">{title}</h2>
|
|
|
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 text-2xl leading-none">×</button>
|
|
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 text-2xl leading-none">×</button>
|
|
|
</div>
|
|
</div>
|
|
|
- <div className="p-6">{children}</div>
|
|
|
|
|
|
|
+ <div className="p-4 sm:p-6 overflow-y-auto">{children}</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
)
|
|
)
|
|
@@ -231,22 +231,22 @@ export default function LeaseDetail() {
|
|
|
|
|
|
|
|
{/* Bail info */}
|
|
{/* Bail info */}
|
|
|
<div className="card mb-6">
|
|
<div className="card mb-6">
|
|
|
- <div className="flex items-start justify-between">
|
|
|
|
|
|
|
+ <div className="flex flex-col sm:flex-row sm:items-start justify-between gap-3">
|
|
|
<div>
|
|
<div>
|
|
|
- <h1 className="text-2xl font-bold text-gray-900">
|
|
|
|
|
|
|
+ <h1 className="text-xl sm:text-2xl font-bold text-gray-900">
|
|
|
{lease.tenant_first_name} {lease.tenant_last_name}
|
|
{lease.tenant_first_name} {lease.tenant_last_name}
|
|
|
</h1>
|
|
</h1>
|
|
|
<p className="text-gray-600 mt-1">{lease.property_name}</p>
|
|
<p className="text-gray-600 mt-1">{lease.property_name}</p>
|
|
|
<p className="text-gray-500 text-sm">{lease.property_address}, {lease.property_zip} {lease.property_city}</p>
|
|
<p className="text-gray-500 text-sm">{lease.property_address}, {lease.property_zip} {lease.property_city}</p>
|
|
|
</div>
|
|
</div>
|
|
|
- <div className="flex items-center gap-3">
|
|
|
|
|
|
|
+ <div className="flex flex-wrap items-center gap-2">
|
|
|
{lease.active && (
|
|
{lease.active && (
|
|
|
<button onClick={() => {
|
|
<button onClick={() => {
|
|
|
setRevForm({ effective_date: now.toISOString().slice(0, 10), rent_amount: String(lease.rent_amount), charges_amount: String(lease.charges_amount), notes: '' })
|
|
setRevForm({ effective_date: now.toISOString().slice(0, 10), rent_amount: String(lease.rent_amount), charges_amount: String(lease.charges_amount), notes: '' })
|
|
|
setShowRevisionModal(true)
|
|
setShowRevisionModal(true)
|
|
|
- }} className="btn-secondary text-sm">📈 Réviser le loyer</button>
|
|
|
|
|
|
|
+ }} className="btn-secondary text-sm">📈 Réviser</button>
|
|
|
)}
|
|
)}
|
|
|
- <button onClick={openEditLease} className="btn-secondary text-sm">✏️ Modifier le bail</button>
|
|
|
|
|
|
|
+ <button onClick={openEditLease} className="btn-secondary text-sm">✏️ Modifier</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>
|
|
@@ -342,10 +342,10 @@ export default function LeaseDetail() {
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
{/* Paiements */}
|
|
{/* Paiements */}
|
|
|
- <div className="flex items-center justify-between mb-4">
|
|
|
|
|
|
|
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-4">
|
|
|
<h2 className="text-xl font-bold text-gray-900">💰 Historique des paiements</h2>
|
|
<h2 className="text-xl font-bold text-gray-900">💰 Historique des paiements</h2>
|
|
|
{lease.active && (
|
|
{lease.active && (
|
|
|
- <button onClick={openNewPayment} className="btn-primary">+ Enregistrer un paiement</button>
|
|
|
|
|
|
|
+ <button onClick={openNewPayment} className="btn-primary w-full sm:w-auto">+ Enregistrer un paiement</button>
|
|
|
)}
|
|
)}
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -372,53 +372,95 @@ export default function LeaseDetail() {
|
|
|
<p className="text-gray-400">Aucun paiement enregistré pour ce bail.</p>
|
|
<p className="text-gray-400">Aucun paiement enregistré pour ce bail.</p>
|
|
|
</div>
|
|
</div>
|
|
|
) : (
|
|
) : (
|
|
|
- <div className="card overflow-hidden p-0">
|
|
|
|
|
- <table className="w-full">
|
|
|
|
|
- <thead className="bg-gray-50 border-b">
|
|
|
|
|
- <tr>
|
|
|
|
|
- <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Période</th>
|
|
|
|
|
- <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Date paiement</th>
|
|
|
|
|
- <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Mode</th>
|
|
|
|
|
- <th className="text-right px-6 py-3 text-sm font-semibold text-gray-600">Loyer</th>
|
|
|
|
|
- <th className="text-right px-6 py-3 text-sm font-semibold text-gray-600">Charges</th>
|
|
|
|
|
- <th className="text-right px-6 py-3 text-sm font-semibold text-gray-600">Régul.</th>
|
|
|
|
|
- <th className="text-right px-6 py-3 text-sm font-semibold text-gray-600">Net</th>
|
|
|
|
|
- <th className="px-6 py-3"></th>
|
|
|
|
|
- </tr>
|
|
|
|
|
- </thead>
|
|
|
|
|
- <tbody className="divide-y divide-gray-50">
|
|
|
|
|
- {payments.map(p => {
|
|
|
|
|
- const reg = p.charge_regularization || 0
|
|
|
|
|
- const net = p.rent_paid + p.charges_paid - reg
|
|
|
|
|
- return (
|
|
|
|
|
- <tr key={p.id} className="hover:bg-gray-50">
|
|
|
|
|
- <td className="px-6 py-4 font-medium text-gray-900">{MONTHS[p.period_month-1]} {p.period_year}</td>
|
|
|
|
|
- <td className="px-6 py-4 text-gray-600">{fmtDate(p.payment_date)}</td>
|
|
|
|
|
- <td className="px-6 py-4 text-gray-600 capitalize">{p.payment_method}</td>
|
|
|
|
|
- <td className="px-6 py-4 text-right text-gray-900">
|
|
|
|
|
- {fmt(p.rent_paid)}
|
|
|
|
|
- {p.is_prorata ? <span className="ml-1 text-xs text-amber-600" title={`Prorata ${p.prorata_days}/${p.prorata_total_days} j`}>⅟</span> : null}
|
|
|
|
|
- </td>
|
|
|
|
|
- <td className="px-6 py-4 text-right text-gray-600">{fmt(p.charges_paid)}</td>
|
|
|
|
|
- <td className="px-6 py-4 text-right text-sm">
|
|
|
|
|
- {reg !== 0
|
|
|
|
|
- ? <span className={`font-semibold ${reg > 0 ? 'text-green-600' : 'text-orange-600'}`}
|
|
|
|
|
- title={reg > 0 ? 'Trop-perçu déduit' : 'Complément appelé'}>
|
|
|
|
|
- {reg > 0 ? `−${fmt(reg)}` : `+${fmt(Math.abs(reg))}`}
|
|
|
|
|
- </span>
|
|
|
|
|
- : <span className="text-gray-300">—</span>}
|
|
|
|
|
- </td>
|
|
|
|
|
- <td className="px-6 py-4 text-right font-semibold text-green-700">{fmt(net)}</td>
|
|
|
|
|
- <td className="px-6 py-4 text-right space-x-2">
|
|
|
|
|
- <button onClick={() => downloadReceipt(p.id)} className="text-blue-600 hover:text-blue-800 text-sm font-medium" title="Télécharger la quittance PDF">📄 Quittance</button>
|
|
|
|
|
- <button onClick={() => openEditPayment(p)} className="text-gray-500 hover:text-indigo-600 ml-2" title="Modifier">✏️</button>
|
|
|
|
|
- <button onClick={() => handleDeletePayment(p.id)} className="text-gray-400 hover:text-red-600 ml-2">🗑️</button>
|
|
|
|
|
- </td>
|
|
|
|
|
- </tr>
|
|
|
|
|
- )
|
|
|
|
|
- })}
|
|
|
|
|
- </tbody>
|
|
|
|
|
- </table>
|
|
|
|
|
|
|
+ <div>
|
|
|
|
|
+ {/* Desktop table */}
|
|
|
|
|
+ <div className="card overflow-hidden p-0 hidden md:block">
|
|
|
|
|
+ <table className="w-full">
|
|
|
|
|
+ <thead className="bg-gray-50 border-b">
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Période</th>
|
|
|
|
|
+ <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Date paiement</th>
|
|
|
|
|
+ <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Mode</th>
|
|
|
|
|
+ <th className="text-right px-6 py-3 text-sm font-semibold text-gray-600">Loyer</th>
|
|
|
|
|
+ <th className="text-right px-6 py-3 text-sm font-semibold text-gray-600">Charges</th>
|
|
|
|
|
+ <th className="text-right px-6 py-3 text-sm font-semibold text-gray-600">Régul.</th>
|
|
|
|
|
+ <th className="text-right px-6 py-3 text-sm font-semibold text-gray-600">Net</th>
|
|
|
|
|
+ <th className="px-6 py-3"></th>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody className="divide-y divide-gray-50">
|
|
|
|
|
+ {payments.map(p => {
|
|
|
|
|
+ const reg = p.charge_regularization || 0
|
|
|
|
|
+ const net = p.rent_paid + p.charges_paid - reg
|
|
|
|
|
+ return (
|
|
|
|
|
+ <tr key={p.id} className="hover:bg-gray-50">
|
|
|
|
|
+ <td className="px-6 py-4 font-medium text-gray-900">{MONTHS[p.period_month-1]} {p.period_year}</td>
|
|
|
|
|
+ <td className="px-6 py-4 text-gray-600">{fmtDate(p.payment_date)}</td>
|
|
|
|
|
+ <td className="px-6 py-4 text-gray-600 capitalize">{p.payment_method}</td>
|
|
|
|
|
+ <td className="px-6 py-4 text-right text-gray-900">
|
|
|
|
|
+ {fmt(p.rent_paid)}
|
|
|
|
|
+ {p.is_prorata ? <span className="ml-1 text-xs text-amber-600" title={`Prorata ${p.prorata_days}/${p.prorata_total_days} j`}>⅟</span> : null}
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td className="px-6 py-4 text-right text-gray-600">{fmt(p.charges_paid)}</td>
|
|
|
|
|
+ <td className="px-6 py-4 text-right text-sm">
|
|
|
|
|
+ {reg !== 0
|
|
|
|
|
+ ? <span className={`font-semibold ${reg > 0 ? 'text-green-600' : 'text-orange-600'}`}>
|
|
|
|
|
+ {reg > 0 ? `−${fmt(reg)}` : `+${fmt(Math.abs(reg))}`}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ : <span className="text-gray-300">—</span>}
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td className="px-6 py-4 text-right font-semibold text-green-700">{fmt(net)}</td>
|
|
|
|
|
+ <td className="px-6 py-4 text-right space-x-2 whitespace-nowrap">
|
|
|
|
|
+ <button onClick={() => downloadReceipt(p.id)} className="text-blue-600 hover:text-blue-800 text-sm font-medium">📄 Quittance</button>
|
|
|
|
|
+ <button onClick={() => openEditPayment(p)} className="text-gray-500 hover:text-indigo-600 ml-2">✏️</button>
|
|
|
|
|
+ <button onClick={() => handleDeletePayment(p.id)} className="text-gray-400 hover:text-red-600 ml-2">🗑️</button>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ )
|
|
|
|
|
+ })}
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ </table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Mobile cards */}
|
|
|
|
|
+ <div className="md:hidden space-y-3">
|
|
|
|
|
+ {payments.map(p => {
|
|
|
|
|
+ const reg = p.charge_regularization || 0
|
|
|
|
|
+ const net = p.rent_paid + p.charges_paid - reg
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div key={p.id} className="card">
|
|
|
|
|
+ <div className="flex items-start justify-between mb-2">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <p className="font-semibold text-gray-900">{MONTHS[p.period_month-1]} {p.period_year}</p>
|
|
|
|
|
+ <p className="text-sm text-gray-500">{fmtDate(p.payment_date)} · {p.payment_method}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <p className="font-bold text-green-700 text-lg">{fmt(net)}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="text-sm text-gray-500 space-y-0.5">
|
|
|
|
|
+ <div className="flex justify-between">
|
|
|
|
|
+ <span>Loyer{p.is_prorata ? ` (prorata ${p.prorata_days}j)` : ''}</span>
|
|
|
|
|
+ <span>{fmt(p.rent_paid)}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex justify-between">
|
|
|
|
|
+ <span>Charges</span>
|
|
|
|
|
+ <span>{fmt(p.charges_paid)}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ {reg !== 0 && (
|
|
|
|
|
+ <div className={`flex justify-between font-medium ${reg > 0 ? 'text-green-600' : 'text-orange-600'}`}>
|
|
|
|
|
+ <span>Régularisation</span>
|
|
|
|
|
+ <span>{reg > 0 ? `−${fmt(reg)}` : `+${fmt(Math.abs(reg))}`}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex gap-3 mt-3 pt-3 border-t border-gray-50">
|
|
|
|
|
+ <button onClick={() => downloadReceipt(p.id)} className="text-blue-600 text-sm font-medium flex-1 text-center">📄 Quittance</button>
|
|
|
|
|
+ <button onClick={() => openEditPayment(p)} className="text-gray-500 text-sm flex-1 text-center">✏️ Modifier</button>
|
|
|
|
|
+ <button onClick={() => handleDeletePayment(p.id)} className="text-red-400 text-sm flex-1 text-center">🗑️ Suppr.</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )
|
|
|
|
|
+ })}
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
|
|
|