| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- import { useEffect, useState } from 'react'
- import api from '../api'
- import toast from 'react-hot-toast'
- function Modal({ title, onClose, children }) {
- return (
- <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">×</button>
- </div>
- <div className="p-4 sm:p-6 overflow-y-auto">{children}</div>
- </div>
- </div>
- )
- }
- const emptyForm = {
- type: 'physique',
- first_name: '', last_name: '',
- company_name: '', siret: '',
- address: '', zip_code: '', city: '',
- email: '', phone: '',
- }
- export function ownerLabel(o) {
- if (!o) return '—'
- return o.type === 'morale'
- ? o.company_name || '—'
- : `${o.first_name || ''} ${o.last_name || ''}`.trim() || '—'
- }
- export default function Owners() {
- const [owners, setOwners] = useState([])
- const [showModal, setShowModal] = useState(false)
- const [form, setForm] = useState(emptyForm)
- const [editing, setEditing] = useState(null)
- const [loading, setLoading] = useState(false)
- const load = () => api.get('/owners').then(r => setOwners(r.data))
- useEffect(() => { load() }, [])
- const openAdd = () => { setForm(emptyForm); setEditing(null); setShowModal(true) }
- const openEdit = o => {
- setForm({
- type: o.type || 'physique',
- first_name: o.first_name || '', last_name: o.last_name || '',
- company_name: o.company_name || '', siret: o.siret || '',
- address: o.address || '', zip_code: o.zip_code || '', city: o.city || '',
- email: o.email || '', phone: o.phone || '',
- })
- setEditing(o.id); setShowModal(true)
- }
- const handleSubmit = async e => {
- e.preventDefault(); setLoading(true)
- try {
- if (editing) await api.put(`/owners/${editing}`, form)
- else await api.post('/owners', form)
- toast.success(editing ? 'Propriétaire modifié !' : 'Propriétaire ajouté !')
- setShowModal(false); load()
- } catch (err) { toast.error(err.response?.data?.error || 'Erreur') }
- finally { setLoading(false) }
- }
- const handleDelete = async id => {
- if (!confirm('Supprimer ce propriétaire ?')) return
- try { await api.delete(`/owners/${id}`); toast.success('Propriétaire supprimé'); load() }
- catch (err) { toast.error(err.response?.data?.error || 'Erreur') }
- }
- const set = field => e => setForm(f => ({ ...f, [field]: e.target.value }))
- return (
- <div>
- <div className="flex items-center justify-between mb-8">
- <div>
- <h1 className="text-2xl font-bold text-gray-900">🏢 Propriétaires</h1>
- <p className="text-gray-500 mt-1">{owners.length} propriétaire(s) enregistré(s)</p>
- </div>
- <button onClick={openAdd} className="btn-primary">+ Ajouter un propriétaire</button>
- </div>
- {owners.length === 0 ? (
- <div className="card text-center py-16">
- <div className="text-5xl mb-4">🏢</div>
- <p className="text-gray-500">Aucun propriétaire enregistré.</p>
- </div>
- ) : (
- <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>
- </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>
- )}
- {showModal && (
- <Modal title={editing ? 'Modifier le propriétaire' : 'Ajouter un propriétaire'} onClose={() => setShowModal(false)}>
- <form onSubmit={handleSubmit} className="space-y-4">
- {/* Type */}
- <div className="flex gap-3">
- {['physique', 'morale'].map(t => (
- <label key={t} className={`flex-1 flex items-center justify-center gap-2 p-3 rounded-xl border-2 cursor-pointer transition-colors ${form.type === t ? 'border-blue-500 bg-blue-50 text-blue-700' : 'border-gray-200 text-gray-600 hover:border-gray-300'}`}>
- <input type="radio" className="hidden" value={t} checked={form.type === t}
- onChange={() => setForm(f => ({ ...f, type: t }))} />
- <span className="text-lg">{t === 'physique' ? '👤' : '🏢'}</span>
- <span className="text-sm font-medium">{t === 'physique' ? 'Personne physique' : 'Personne morale'}</span>
- </label>
- ))}
- </div>
- {/* Identité selon type */}
- {form.type === 'physique' ? (
- <div className="grid grid-cols-2 gap-4">
- <div>
- <label className="label">Prénom *</label>
- <input className="input" required value={form.first_name} onChange={set('first_name')} />
- </div>
- <div>
- <label className="label">Nom *</label>
- <input className="input" required value={form.last_name} onChange={set('last_name')} />
- </div>
- </div>
- ) : (
- <div className="grid grid-cols-2 gap-4">
- <div className="col-span-2">
- <label className="label">Raison sociale *</label>
- <input className="input" required value={form.company_name} onChange={set('company_name')} />
- </div>
- <div className="col-span-2">
- <label className="label">SIRET</label>
- <input className="input" value={form.siret} onChange={set('siret')} placeholder="XXX XXX XXX XXXXX" />
- </div>
- </div>
- )}
- {/* Coordonnées communes */}
- <div className="grid grid-cols-2 gap-4">
- <div className="col-span-2">
- <label className="label">Adresse</label>
- <input className="input" value={form.address} onChange={set('address')} />
- </div>
- <div>
- <label className="label">Code postal</label>
- <input className="input" value={form.zip_code} onChange={set('zip_code')} />
- </div>
- <div>
- <label className="label">Ville</label>
- <input className="input" value={form.city} onChange={set('city')} />
- </div>
- <div>
- <label className="label">Email</label>
- <input className="input" type="email" value={form.email} onChange={set('email')} />
- </div>
- <div>
- <label className="label">Téléphone</label>
- <input className="input" value={form.phone} onChange={set('phone')} />
- </div>
- </div>
- <div className="flex gap-3 pt-2">
- <button type="button" onClick={() => setShowModal(false)} className="btn-secondary flex-1">Annuler</button>
- <button type="submit" disabled={loading} className="btn-primary flex-1">{loading ? '...' : 'Enregistrer'}</button>
- </div>
- </form>
- </Modal>
- )}
- </div>
- )
- }
|