Ver código fonte

adaptation iphpne

jeremy 4 meses atrás
pai
commit
494d82ca7c

+ 3 - 1
frontend/index.html

@@ -2,7 +2,9 @@
 <html lang="fr">
   <head>
     <meta charset="UTF-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
+    <meta name="apple-mobile-web-app-capable" content="yes" />
+    <meta name="theme-color" content="#2563eb" />
     <title>Gestion Locative</title>
     <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🏠</text></svg>" />
   </head>

+ 65 - 34
frontend/src/components/Layout.jsx

@@ -1,3 +1,4 @@
+import { useState } from 'react'
 import { Outlet, NavLink, useNavigate } from 'react-router-dom'
 import { useAuth } from '../context/AuthContext'
 
@@ -15,48 +16,78 @@ const navItems = [
 export default function Layout() {
   const { user, logout } = useAuth()
   const navigate = useNavigate()
+  const [menuOpen, setMenuOpen] = useState(false)
 
   const handleLogout = () => { logout(); navigate('/login') }
 
+  const SidebarContent = () => (
+    <>
+      <div className="p-5 border-b border-gray-100">
+        <h1 className="text-lg font-bold text-blue-700">🏠 Gestion Locative</h1>
+        <p className="text-xs text-gray-500 mt-0.5 truncate">
+          {user?.first_name ? `${user.first_name} ${user.last_name || ''}`.trim() : user?.name}
+        </p>
+      </div>
+      <nav className="flex-1 p-3 space-y-0.5 overflow-y-auto">
+        {navItems.map(({ to, label, end }) => (
+          <NavLink
+            key={to}
+            to={to}
+            end={end}
+            onClick={() => setMenuOpen(false)}
+            className={({ isActive }) =>
+              `flex items-center px-3 py-2.5 rounded-lg text-sm font-medium transition-colors ${
+                isActive ? 'bg-blue-50 text-blue-700' : 'text-gray-600 hover:bg-gray-50'
+              }`
+            }
+          >
+            {label}
+          </NavLink>
+        ))}
+      </nav>
+      <div className="p-3 border-t border-gray-100">
+        <button onClick={handleLogout} className="w-full text-left px-3 py-2 text-sm text-red-600 hover:bg-red-50 rounded-lg transition-colors">
+          🚪 Déconnexion
+        </button>
+      </div>
+    </>
+  )
+
   return (
     <div className="min-h-screen bg-gray-50 flex">
-      {/* Sidebar */}
-      <aside className="w-64 bg-white border-r border-gray-200 flex flex-col">
-        <div className="p-6 border-b border-gray-100">
-          <h1 className="text-xl font-bold text-blue-700">🏠 Gestion Locative</h1>
-          <p className="text-sm text-gray-500 mt-1">
-            {user?.first_name ? `${user.first_name} ${user.last_name || ''}`.trim() : user?.name}
-          </p>
-        </div>
-        <nav className="flex-1 p-4 space-y-1">
-          {navItems.map(({ to, label, end }) => (
-            <NavLink
-              key={to}
-              to={to}
-              end={end}
-              className={({ isActive }) =>
-                `flex items-center px-3 py-2.5 rounded-lg text-sm font-medium transition-colors ${
-                  isActive ? 'bg-blue-50 text-blue-700' : 'text-gray-600 hover:bg-gray-50'
-                }`
-              }
-            >
-              {label}
-            </NavLink>
-          ))}
-        </nav>
-        <div className="p-4 border-t border-gray-100">
-          <button onClick={handleLogout} className="w-full text-left px-3 py-2 text-sm text-red-600 hover:bg-red-50 rounded-lg transition-colors">
-            🚪 Déconnexion
-          </button>
-        </div>
+      {/* Sidebar desktop */}
+      <aside className="hidden md:flex w-60 bg-white border-r border-gray-200 flex-col shrink-0">
+        <SidebarContent />
       </aside>
 
-      {/* Main */}
-      <main className="flex-1 overflow-auto">
-        <div className="max-w-6xl mx-auto p-8">
-          <Outlet />
+      {/* Mobile overlay */}
+      {menuOpen && (
+        <div className="fixed inset-0 z-40 flex md:hidden">
+          <div className="fixed inset-0 bg-black/40" onClick={() => setMenuOpen(false)} />
+          <aside className="relative z-50 w-64 bg-white flex flex-col shadow-xl">
+            <SidebarContent />
+          </aside>
         </div>
-      </main>
+      )}
+
+      {/* Main */}
+      <div className="flex-1 flex flex-col min-w-0">
+        {/* Mobile top bar */}
+        <header className="md:hidden flex items-center gap-3 px-4 py-3 bg-white border-b border-gray-200 sticky top-0 z-30">
+          <button onClick={() => setMenuOpen(true)} className="p-1.5 rounded-lg hover:bg-gray-100 text-gray-600" aria-label="Menu">
+            <svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
+              <path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h16" />
+            </svg>
+          </button>
+          <span className="font-bold text-blue-700 text-base">🏠 Gestion Locative</span>
+        </header>
+
+        <main className="flex-1 overflow-auto">
+          <div className="max-w-6xl mx-auto p-4 md:p-8">
+            <Outlet />
+          </div>
+        </main>
+      </div>
     </div>
   )
 }

+ 2 - 2
frontend/src/index.css

@@ -13,10 +13,10 @@
     @apply bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700 transition-colors font-medium;
   }
   .card {
-    @apply bg-white rounded-xl shadow-sm border border-gray-100 p-6;
+    @apply bg-white rounded-xl shadow-sm border border-gray-100 p-4 md:p-6;
   }
   .input {
-    @apply w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent;
+    @apply w-full border border-gray-300 rounded-lg px-3 py-2.5 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-base;
   }
   .label {
     @apply block text-sm font-medium text-gray-700 mb-1;

+ 93 - 60
frontend/src/pages/Charges.jsx

@@ -19,13 +19,13 @@ const YEARS = Array.from({ length: 6 }, (_, i) => CURRENT_YEAR - i)
 
 function Modal({ title, onClose, children }) {
   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 max-h-[90vh] overflow-y-auto">
-        <div className="flex items-center justify-between p-6 border-b sticky top-0 bg-white z-10">
+    <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] flex flex-col">
+        <div className="flex items-center justify-between p-4 sm:p-6 border-b shrink-0 sticky top-0 bg-white z-10">
           <h2 className="text-lg font-semibold">{title}</h2>
           <button onClick={onClose} className="text-gray-400 hover:text-gray-600 text-2xl leading-none">&times;</button>
         </div>
-        <div className="p-6">{children}</div>
+        <div className="p-4 sm:p-6 overflow-y-auto">{children}</div>
       </div>
     </div>
   )
@@ -341,17 +341,17 @@ export default function Charges() {
 
   return (
     <div>
-      <div className="flex items-center justify-between mb-6">
+      <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-6">
         <div>
           <h1 className="text-2xl font-bold text-gray-900">🧾 Gestion des charges</h1>
           <p className="text-gray-500 text-sm mt-1">Charges réelles par immeuble et bilan annuel</p>
         </div>
-        <div className="flex gap-2">
+        <div className="flex flex-wrap gap-2">
           <button onClick={loadBilan} disabled={!filterProperty || bilanLoading}
-            className="btn-secondary flex items-center gap-2">
+            className="btn-secondary flex items-center gap-2 text-sm">
             {bilanLoading ? '...' : '📊 Bilan annuel'}
           </button>
-          <button onClick={openAdd} className="btn-primary">+ Ajouter une charge</button>
+          <button onClick={openAdd} className="btn-primary text-sm">+ Ajouter une charge</button>
         </div>
       </div>
 
@@ -389,59 +389,92 @@ export default function Charges() {
           <button onClick={openAdd} className="btn-primary mt-4">Ajouter une première charge</button>
         </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">Date</th>
-                <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Catégorie</th>
-                <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Libellé</th>
-                <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Immeuble</th>
-                <th className="text-right px-6 py-3 text-sm font-semibold text-gray-600">Montant</th>
-                <th className="text-center px-6 py-3 text-sm font-semibold text-gray-600">Récup.</th>
-                <th className="text-center px-6 py-3 text-sm font-semibold text-gray-600">Facture</th>
-                <th className="px-6 py-3"></th>
-              </tr>
-            </thead>
-            <tbody className="divide-y divide-gray-50">
-              {charges.map(c => (
-                <tr key={c.id} className="hover:bg-gray-50">
-                  <td className="px-6 py-4 text-sm text-gray-600">{fmtDate(c.date)}</td>
-                  <td className="px-6 py-4"><CategoryBadge value={c.category} /></td>
-                  <td className="px-6 py-4 text-gray-900 font-medium">{c.label}</td>
-                  <td className="px-6 py-4 text-sm text-gray-500">{c.property_name}</td>
-                  <td className="px-6 py-4 text-right font-semibold text-gray-900">{fmt(c.amount)}</td>
-                  <td className="px-6 py-4 text-center">
-                    {c.recoverable !== 0
-                      ? <span className="text-xs bg-green-100 text-green-700 font-semibold px-2 py-0.5 rounded-full">✓ Oui</span>
-                      : <span className="text-xs bg-gray-100 text-gray-500 font-semibold px-2 py-0.5 rounded-full">✗ Non</span>
-                    }
-                  </td>
-                  <td className="px-6 py-4 text-center">
-                    {c.invoice_path ? (
-                      <button onClick={() => downloadInvoice(c)}
-                        className="text-green-600 hover:text-green-800 text-sm font-medium" title={c.invoice_name}>
-                        📎 {c.invoice_name?.length > 20 ? c.invoice_name.slice(0, 17) + '…' : c.invoice_name}
-                      </button>
-                    ) : (
-                      <span className="text-gray-300 text-xs">—</span>
-                    )}
-                  </td>
-                  <td className="px-6 py-4 text-right space-x-2 whitespace-nowrap">
-                    <button onClick={() => openEdit(c)} className="text-blue-500 hover:text-blue-700 text-sm">✏️</button>
-                    <button onClick={() => handleDelete(c.id)} className="text-gray-400 hover:text-red-600 ml-1">🗑️</button>
-                  </td>
+        <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">Date</th>
+                  <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Catégorie</th>
+                  <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Libellé</th>
+                  <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Immeuble</th>
+                  <th className="text-right px-6 py-3 text-sm font-semibold text-gray-600">Montant</th>
+                  <th className="text-center px-6 py-3 text-sm font-semibold text-gray-600">Récup.</th>
+                  <th className="text-center px-6 py-3 text-sm font-semibold text-gray-600">Facture</th>
+                  <th className="px-6 py-3"></th>
                 </tr>
-              ))}
-            </tbody>
-            <tfoot className="bg-gray-50 border-t-2 border-gray-200">
-              <tr>
-                <td colSpan={5} className="px-6 py-3 text-sm font-semibold text-gray-700">Total</td>
-                <td className="px-6 py-3 text-right font-bold text-gray-900">{fmt(totalAmount)}</td>
-                <td colSpan={2}></td>
-              </tr>
-            </tfoot>
-          </table>
+              </thead>
+              <tbody className="divide-y divide-gray-50">
+                {charges.map(c => (
+                  <tr key={c.id} className="hover:bg-gray-50">
+                    <td className="px-6 py-4 text-sm text-gray-600">{fmtDate(c.date)}</td>
+                    <td className="px-6 py-4"><CategoryBadge value={c.category} /></td>
+                    <td className="px-6 py-4 text-gray-900 font-medium">{c.label}</td>
+                    <td className="px-6 py-4 text-sm text-gray-500">{c.property_name}</td>
+                    <td className="px-6 py-4 text-right font-semibold text-gray-900">{fmt(c.amount)}</td>
+                    <td className="px-6 py-4 text-center">
+                      {c.recoverable !== 0
+                        ? <span className="text-xs bg-green-100 text-green-700 font-semibold px-2 py-0.5 rounded-full">✓ Oui</span>
+                        : <span className="text-xs bg-gray-100 text-gray-500 font-semibold px-2 py-0.5 rounded-full">✗ Non</span>}
+                    </td>
+                    <td className="px-6 py-4 text-center">
+                      {c.invoice_path ? (
+                        <button onClick={() => downloadInvoice(c)} className="text-green-600 hover:text-green-800 text-sm font-medium" title={c.invoice_name}>
+                          📎 {c.invoice_name?.length > 20 ? c.invoice_name.slice(0, 17) + '…' : c.invoice_name}
+                        </button>
+                      ) : <span className="text-gray-300 text-xs">—</span>}
+                    </td>
+                    <td className="px-6 py-4 text-right space-x-2 whitespace-nowrap">
+                      <button onClick={() => openEdit(c)} className="text-blue-500 hover:text-blue-700 text-sm">✏️</button>
+                      <button onClick={() => handleDelete(c.id)} className="text-gray-400 hover:text-red-600 ml-1">🗑️</button>
+                    </td>
+                  </tr>
+                ))}
+              </tbody>
+              <tfoot className="bg-gray-50 border-t-2 border-gray-200">
+                <tr>
+                  <td colSpan={5} className="px-6 py-3 text-sm font-semibold text-gray-700">Total</td>
+                  <td className="px-6 py-3 text-right font-bold text-gray-900">{fmt(totalAmount)}</td>
+                  <td colSpan={2}></td>
+                </tr>
+              </tfoot>
+            </table>
+          </div>
+
+          {/* Mobile cards */}
+          <div className="md:hidden space-y-3">
+            {charges.map(c => (
+              <div key={c.id} className="card">
+                <div className="flex items-start justify-between mb-1">
+                  <div className="flex-1 min-w-0">
+                    <p className="font-semibold text-gray-900 truncate">{c.label}</p>
+                    <div className="flex items-center gap-2 mt-1 flex-wrap">
+                      <CategoryBadge value={c.category} />
+                      {c.recoverable !== 0
+                        ? <span className="text-xs bg-green-100 text-green-700 px-1.5 py-0.5 rounded-full font-medium">Récup.</span>
+                        : <span className="text-xs bg-gray-100 text-gray-400 px-1.5 py-0.5 rounded-full font-medium">Non récup.</span>}
+                    </div>
+                  </div>
+                  <p className="font-bold text-gray-900 text-lg ml-3 shrink-0">{fmt(c.amount)}</p>
+                </div>
+                <p className="text-sm text-gray-500">{fmtDate(c.date)}</p>
+                <div className="flex items-center justify-between mt-3 pt-3 border-t border-gray-50">
+                  {c.invoice_path
+                    ? <button onClick={() => downloadInvoice(c)} className="text-green-600 text-sm">📎 Facture</button>
+                    : <span />}
+                  <div className="flex gap-3">
+                    <button onClick={() => openEdit(c)} className="text-blue-500 text-sm">✏️ Modifier</button>
+                    <button onClick={() => handleDelete(c.id)} className="text-red-400 text-sm">🗑️</button>
+                  </div>
+                </div>
+              </div>
+            ))}
+            <div className="card bg-gray-50 flex justify-between items-center">
+              <span className="font-semibold text-gray-700">Total</span>
+              <span className="font-bold text-gray-900">{fmt(totalAmount)}</span>
+            </div>
+          </div>
         </div>
       )}
 

+ 100 - 58
frontend/src/pages/LeaseDetail.jsx

@@ -13,13 +13,13 @@ const METHODS = [
 
 function Modal({ title, onClose, children }) {
   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>
           <button onClick={onClose} className="text-gray-400 hover:text-gray-600 text-2xl leading-none">&times;</button>
         </div>
-        <div className="p-6">{children}</div>
+        <div className="p-4 sm:p-6 overflow-y-auto">{children}</div>
       </div>
     </div>
   )
@@ -231,22 +231,22 @@ export default function LeaseDetail() {
 
       {/* Bail info */}
       <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>
-            <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}
             </h1>
             <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>
           </div>
-          <div className="flex items-center gap-3">
+          <div className="flex flex-wrap items-center gap-2">
             {lease.active && (
               <button onClick={() => {
                 setRevForm({ effective_date: now.toISOString().slice(0, 10), rent_amount: String(lease.rent_amount), charges_amount: String(lease.charges_amount), notes: '' })
                 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>
           </div>
         </div>
@@ -342,10 +342,10 @@ export default function LeaseDetail() {
       )}
 
       {/* 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>
         {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>
 
@@ -372,53 +372,95 @@ export default function LeaseDetail() {
           <p className="text-gray-400">Aucun paiement enregistré pour ce bail.</p>
         </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>
       )}
 

+ 15 - 14
frontend/src/pages/Leases.jsx

@@ -6,13 +6,13 @@ import { ownerLabel } from './Owners'
 
 function Modal({ title, onClose, children }) {
   return (
-    <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 overflow-y-auto">
-      <div className="bg-white rounded-2xl shadow-xl w-full max-w-lg my-4">
-        <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] 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>
           <button onClick={onClose} className="text-gray-400 hover:text-gray-600 text-2xl leading-none">&times;</button>
         </div>
-        <div className="p-6">{children}</div>
+        <div className="p-4 sm:p-6 overflow-y-auto">{children}</div>
       </div>
     </div>
   )
@@ -62,12 +62,12 @@ export default function Leases() {
 
   return (
     <div>
-      <div className="flex items-center justify-between mb-8">
+      <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-8">
         <div>
           <h1 className="text-2xl font-bold text-gray-900">📋 Baux</h1>
           <p className="text-gray-500 mt-1">{leases.filter(l => l.active).length} bail(s) actif(s)</p>
         </div>
-        <button onClick={() => { setForm(emptyForm); setShowModal(true) }} className="btn-primary">+ Nouveau bail</button>
+        <button onClick={() => { setForm(emptyForm); setShowModal(true) }} className="btn-primary w-full sm:w-auto">+ Nouveau bail</button>
       </div>
 
       {leases.length === 0 ? (
@@ -80,26 +80,27 @@ export default function Leases() {
           {leases.map(l => (
             <div key={l.id} className={`card hover:shadow-md transition-shadow ${!l.active ? 'opacity-60' : ''}`}>
               <div className="flex items-center justify-between">
-                <div className="flex items-center gap-4">
-                  <div>
-                    <div className="flex items-center gap-2">
+                <div className="flex items-center gap-4 flex-1 min-w-0">
+                  <div className="min-w-0">
+                    <div className="flex items-center gap-2 flex-wrap">
                       <h3 className="font-semibold text-gray-900">{l.tenant_first_name} {l.tenant_last_name}</h3>
                       <span className={l.active ? 'badge-green' : 'badge-red'}>{l.active ? 'Actif' : 'Clôturé'}</span>
                     </div>
-                    <p className="text-sm text-gray-500">{l.property_name} · {l.property_address}, {l.property_city}</p>
+                    <p className="text-sm text-gray-500 truncate">{l.property_name} · {l.property_address}, {l.property_city}</p>
                     <p className="text-sm text-gray-400">Du {fmtDate(l.start_date)}{l.end_date ? ` au ${fmtDate(l.end_date)}` : ''}</p>
                     {l.owner_type && (
                       <p className="text-xs text-gray-400 mt-0.5">
-                        🏢 Propriétaire : {l.owner_type === 'morale' ? l.owner_company_name : `${l.owner_first_name || ''} ${l.owner_last_name || ''}`.trim()}
+                        🏢 {l.owner_type === 'morale' ? l.owner_company_name : `${l.owner_first_name || ''} ${l.owner_last_name || ''}`.trim()}
                       </p>
                     )}
                   </div>
                 </div>
-                <div className="flex items-center gap-6">
-                  <div className="text-right">
+                <div className="flex flex-col sm:flex-row items-end sm:items-center gap-2 ml-3 shrink-0">
+                  <div className="text-right hidden sm:block">
                     <p className="font-semibold text-gray-900">{fmt(l.rent_amount + l.charges_amount)}/mois</p>
-                    <p className="text-xs text-gray-400">Loyer {fmt(l.rent_amount)} + Charges {fmt(l.charges_amount)}</p>
+                    <p className="text-xs text-gray-400">Loyer {fmt(l.rent_amount)} + Ch. {fmt(l.charges_amount)}</p>
                   </div>
+                  <p className="font-semibold text-gray-900 sm:hidden">{fmt(l.rent_amount + l.charges_amount)}/m</p>
                   <div className="flex gap-2">
                     <Link to={`/leases/${l.id}`} className="btn-secondary text-sm px-3 py-1.5">Détail</Link>
                     <button onClick={() => handleToggle(l)} className={`text-sm px-3 py-1.5 rounded-lg transition-colors ${l.active ? 'bg-red-50 text-red-600 hover:bg-red-100' : 'bg-green-50 text-green-600 hover:bg-green-100'}`}>

+ 68 - 40
frontend/src/pages/Owners.jsx

@@ -4,13 +4,13 @@ import toast from 'react-hot-toast'
 
 function Modal({ title, onClose, children }) {
   return (
-    <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 overflow-y-auto">
-      <div className="bg-white rounded-2xl shadow-xl w-full max-w-lg my-4">
-        <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] 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>
           <button onClick={onClose} className="text-gray-400 hover:text-gray-600 text-2xl leading-none">&times;</button>
         </div>
-        <div className="p-6">{children}</div>
+        <div className="p-4 sm:p-6 overflow-y-auto">{children}</div>
       </div>
     </div>
   )
@@ -88,43 +88,71 @@ export default function Owners() {
           <p className="text-gray-500">Aucun propriétaire enregistré.</p>
         </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">Identité</th>
-                <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Type</th>
-                <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Email</th>
-                <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Téléphone</th>
-                <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Adresse</th>
-                <th className="px-6 py-3"></th>
-              </tr>
-            </thead>
-            <tbody className="divide-y divide-gray-50">
-              {owners.map(o => (
-                <tr key={o.id} className="hover:bg-gray-50">
-                  <td className="px-6 py-4 font-medium text-gray-900">
-                    {ownerLabel(o)}
-                    {o.siret && <span className="ml-2 text-xs text-gray-400">SIRET {o.siret}</span>}
-                  </td>
-                  <td className="px-6 py-4">
-                    <span className={`text-xs px-2 py-0.5 rounded-full font-medium ${o.type === 'morale' ? 'bg-purple-100 text-purple-700' : 'bg-blue-100 text-blue-700'}`}>
-                      {o.type === 'morale' ? 'Personne morale' : 'Personne physique'}
-                    </span>
-                  </td>
-                  <td className="px-6 py-4 text-gray-600">{o.email || '—'}</td>
-                  <td className="px-6 py-4 text-gray-600">{o.phone || '—'}</td>
-                  <td className="px-6 py-4 text-gray-600 text-sm">
-                    {o.address ? `${o.address}${o.zip_code ? ', ' + o.zip_code : ''}${o.city ? ' ' + o.city : ''}` : '—'}
-                  </td>
-                  <td className="px-6 py-4 text-right space-x-3">
-                    <button onClick={() => openEdit(o)} className="text-gray-400 hover:text-blue-600">✏️</button>
-                    <button onClick={() => handleDelete(o.id)} className="text-gray-400 hover:text-red-600">🗑️</button>
-                  </td>
+        <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">Identité</th>
+                  <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Type</th>
+                  <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Email</th>
+                  <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Téléphone</th>
+                  <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Adresse</th>
+                  <th className="px-6 py-3"></th>
                 </tr>
-              ))}
-            </tbody>
-          </table>
+              </thead>
+              <tbody className="divide-y divide-gray-50">
+                {owners.map(o => (
+                  <tr key={o.id} className="hover:bg-gray-50">
+                    <td className="px-6 py-4 font-medium text-gray-900">
+                      {ownerLabel(o)}
+                      {o.siret && <span className="ml-2 text-xs text-gray-400">SIRET {o.siret}</span>}
+                    </td>
+                    <td className="px-6 py-4">
+                      <span className={`text-xs px-2 py-0.5 rounded-full font-medium ${o.type === 'morale' ? 'bg-purple-100 text-purple-700' : 'bg-blue-100 text-blue-700'}`}>
+                        {o.type === 'morale' ? 'Personne morale' : 'Personne physique'}
+                      </span>
+                    </td>
+                    <td className="px-6 py-4 text-gray-600">{o.email || '—'}</td>
+                    <td className="px-6 py-4 text-gray-600">{o.phone || '—'}</td>
+                    <td className="px-6 py-4 text-gray-600 text-sm">
+                      {o.address ? `${o.address}${o.zip_code ? ', ' + o.zip_code : ''}${o.city ? ' ' + o.city : ''}` : '—'}
+                    </td>
+                    <td className="px-6 py-4 text-right space-x-3">
+                      <button onClick={() => openEdit(o)} className="text-gray-400 hover:text-blue-600">✏️</button>
+                      <button onClick={() => handleDelete(o.id)} className="text-gray-400 hover:text-red-600">🗑️</button>
+                    </td>
+                  </tr>
+                ))}
+              </tbody>
+            </table>
+          </div>
+          {/* Mobile cards */}
+          <div className="md:hidden space-y-3">
+            {owners.map(o => (
+              <div key={o.id} className="card">
+                <div className="flex items-start justify-between">
+                  <div className="flex-1 min-w-0">
+                    <div className="flex items-center gap-2 flex-wrap">
+                      <p className="font-semibold text-gray-900">{ownerLabel(o)}</p>
+                      <span className={`text-xs px-2 py-0.5 rounded-full font-medium ${o.type === 'morale' ? 'bg-purple-100 text-purple-700' : 'bg-blue-100 text-blue-700'}`}>
+                        {o.type === 'morale' ? 'Morale' : 'Physique'}
+                      </span>
+                    </div>
+                    {o.siret && <p className="text-xs text-gray-400 mt-0.5">SIRET {o.siret}</p>}
+                    {o.email && <p className="text-sm text-gray-500 mt-1">✉️ {o.email}</p>}
+                    {o.phone && <p className="text-sm text-gray-500">📞 {o.phone}</p>}
+                    {o.address && <p className="text-sm text-gray-400">{o.address}{o.zip_code ? `, ${o.zip_code}` : ''}{o.city ? ` ${o.city}` : ''}</p>}
+                  </div>
+                  <div className="flex gap-3 ml-3">
+                    <button onClick={() => openEdit(o)} className="text-gray-400 hover:text-blue-600 text-lg">✏️</button>
+                    <button onClick={() => handleDelete(o.id)} className="text-gray-400 hover:text-red-600 text-lg">🗑️</button>
+                  </div>
+                </div>
+              </div>
+            ))}
+          </div>
         </div>
       )}
 

+ 53 - 29
frontend/src/pages/Payments.jsx

@@ -55,8 +55,8 @@ export default function Payments() {
         </div>
       </div>
 
-      <div className="flex items-center gap-4 mb-6">
-        <input className="input max-w-xs" placeholder="🔍 Rechercher..." value={filter} onChange={e => setFilter(e.target.value)} />
+      <div className="flex flex-col sm:flex-row sm:items-center gap-3 mb-6">
+        <input className="input sm:max-w-xs" placeholder="🔍 Rechercher..." value={filter} onChange={e => setFilter(e.target.value)} />
         {filtered.length > 0 && (
           <p className="text-sm text-gray-500">
             <strong>{filtered.length}</strong> résultat(s) — Total : <strong className="text-green-700">{fmt(total)}</strong>
@@ -72,34 +72,58 @@ export default function Payments() {
           <Link to="/leases" className="btn-primary inline-block mt-4">Voir les baux</Link>
         </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">Locataire</th>
-                <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Bien</th>
-                <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</th>
-                <th className="text-right px-6 py-3 text-sm font-semibold text-gray-600">Total</th>
-                <th className="px-6 py-3"></th>
-              </tr>
-            </thead>
-            <tbody className="divide-y divide-gray-50">
-              {filtered.map(p => (
-                <tr key={p.id} className="hover:bg-gray-50">
-                  <td className="px-6 py-4 font-medium text-gray-900">{p.tenant_first_name} {p.tenant_last_name}</td>
-                  <td className="px-6 py-4 text-gray-600">{p.property_name}</td>
-                  <td className="px-6 py-4 text-gray-600">{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-right font-semibold text-green-700">{fmt(p.rent_paid + p.charges_paid)}</td>
-                  <td className="px-6 py-4 text-right space-x-3">
-                    <button onClick={() => downloadReceipt(p.id)} className="text-blue-600 hover:text-blue-800 text-sm font-medium">📄 Quittance</button>
-                    <button onClick={() => handleDelete(p.id)} className="text-gray-400 hover:text-red-600">🗑️</button>
-                  </td>
+        <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">Locataire</th>
+                  <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Bien</th>
+                  <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</th>
+                  <th className="text-right px-6 py-3 text-sm font-semibold text-gray-600">Total</th>
+                  <th className="px-6 py-3"></th>
                 </tr>
-              ))}
-            </tbody>
-          </table>
+              </thead>
+              <tbody className="divide-y divide-gray-50">
+                {filtered.map(p => (
+                  <tr key={p.id} className="hover:bg-gray-50">
+                    <td className="px-6 py-4 font-medium text-gray-900">{p.tenant_first_name} {p.tenant_last_name}</td>
+                    <td className="px-6 py-4 text-gray-600">{p.property_name}</td>
+                    <td className="px-6 py-4 text-gray-600">{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-right font-semibold text-green-700">{fmt(p.rent_paid + p.charges_paid)}</td>
+                    <td className="px-6 py-4 text-right space-x-3">
+                      <button onClick={() => downloadReceipt(p.id)} className="text-blue-600 hover:text-blue-800 text-sm font-medium">📄 Quittance</button>
+                      <button onClick={() => handleDelete(p.id)} className="text-gray-400 hover:text-red-600">🗑️</button>
+                    </td>
+                  </tr>
+                ))}
+              </tbody>
+            </table>
+          </div>
+          {/* Mobile cards */}
+          <div className="md:hidden space-y-3">
+            {filtered.map(p => (
+              <div key={p.id} className="card">
+                <div className="flex items-start justify-between mb-2">
+                  <div>
+                    <p className="font-semibold text-gray-900">{p.tenant_first_name} {p.tenant_last_name}</p>
+                    <p className="text-sm text-gray-500">{p.property_name}</p>
+                  </div>
+                  <p className="font-bold text-green-700 text-lg">{fmt(p.rent_paid + p.charges_paid)}</p>
+                </div>
+                <div className="flex items-center justify-between text-sm text-gray-500">
+                  <span>{MONTHS[p.period_month-1]} {p.period_year} · {fmtDate(p.payment_date)}</span>
+                  <div className="flex gap-3">
+                    <button onClick={() => downloadReceipt(p.id)} className="text-blue-600 font-medium">📄</button>
+                    <button onClick={() => handleDelete(p.id)} className="text-gray-400 hover:text-red-600">🗑️</button>
+                  </div>
+                </div>
+              </div>
+            ))}
+          </div>
         </div>
       )}
     </div>

+ 4 - 4
frontend/src/pages/Properties.jsx

@@ -6,13 +6,13 @@ const TYPES = ['Appartement', 'Maison', 'Studio', 'Chambre', 'Local commercial',
 
 function Modal({ title, onClose, children }) {
   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] 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>
           <button onClick={onClose} className="text-gray-400 hover:text-gray-600 text-2xl leading-none">&times;</button>
         </div>
-        <div className="p-6">{children}</div>
+        <div className="p-4 sm:p-6 overflow-y-auto">{children}</div>
       </div>
     </div>
   )

+ 48 - 27
frontend/src/pages/Tenants.jsx

@@ -4,13 +4,13 @@ import toast from 'react-hot-toast'
 
 function Modal({ title, onClose, children }) {
   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] 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>
           <button onClick={onClose} className="text-gray-400 hover:text-gray-600 text-2xl leading-none">&times;</button>
         </div>
-        <div className="p-6">{children}</div>
+        <div className="p-4 sm:p-6 overflow-y-auto">{children}</div>
       </div>
     </div>
   )
@@ -67,30 +67,51 @@ export default function Tenants() {
           <p className="text-gray-500">Aucun locataire enregistré.</p>
         </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">Nom</th>
-                <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Email</th>
-                <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Téléphone</th>
-                <th className="px-6 py-3"></th>
-              </tr>
-            </thead>
-            <tbody className="divide-y divide-gray-50">
-              {tenants.map(t => (
-                <tr key={t.id} className="hover:bg-gray-50">
-                  <td className="px-6 py-4 font-medium text-gray-900">{t.first_name} {t.last_name}</td>
-                  <td className="px-6 py-4 text-gray-600">{t.email || '—'}</td>
-                  <td className="px-6 py-4 text-gray-600">{t.phone || '—'}</td>
-                  <td className="px-6 py-4 text-right space-x-3">
-                    <button onClick={() => openEdit(t)} className="text-gray-400 hover:text-blue-600">✏️</button>
-                    <button onClick={() => handleDelete(t.id)} className="text-gray-400 hover:text-red-600">🗑️</button>
-                  </td>
+        <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">Nom</th>
+                  <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Email</th>
+                  <th className="text-left px-6 py-3 text-sm font-semibold text-gray-600">Téléphone</th>
+                  <th className="px-6 py-3"></th>
                 </tr>
-              ))}
-            </tbody>
-          </table>
+              </thead>
+              <tbody className="divide-y divide-gray-50">
+                {tenants.map(t => (
+                  <tr key={t.id} className="hover:bg-gray-50">
+                    <td className="px-6 py-4 font-medium text-gray-900">{t.first_name} {t.last_name}</td>
+                    <td className="px-6 py-4 text-gray-600">{t.email || '—'}</td>
+                    <td className="px-6 py-4 text-gray-600">{t.phone || '—'}</td>
+                    <td className="px-6 py-4 text-right space-x-3">
+                      <button onClick={() => openEdit(t)} className="text-gray-400 hover:text-blue-600">✏️</button>
+                      <button onClick={() => handleDelete(t.id)} className="text-gray-400 hover:text-red-600">🗑️</button>
+                    </td>
+                  </tr>
+                ))}
+              </tbody>
+            </table>
+          </div>
+          {/* Mobile cards */}
+          <div className="md:hidden space-y-3">
+            {tenants.map(t => (
+              <div key={t.id} className="card">
+                <div className="flex items-start justify-between">
+                  <div>
+                    <p className="font-semibold text-gray-900">{t.first_name} {t.last_name}</p>
+                    {t.email && <p className="text-sm text-gray-500 mt-0.5">✉️ {t.email}</p>}
+                    {t.phone && <p className="text-sm text-gray-500">📞 {t.phone}</p>}
+                  </div>
+                  <div className="flex gap-3 ml-3">
+                    <button onClick={() => openEdit(t)} className="text-gray-400 hover:text-blue-600 text-lg">✏️</button>
+                    <button onClick={() => handleDelete(t.id)} className="text-gray-400 hover:text-red-600 text-lg">🗑️</button>
+                  </div>
+                </div>
+              </div>
+            ))}
+          </div>
         </div>
       )}