'use client'; import { useState, useEffect } from 'react'; import { useTranslations } from 'next-intl'; import { getAllOrganizations, verifySiret, approveSiret, rejectSiret } from '@/lib/api/admin'; import { createOrganization, updateOrganization } from '@/lib/api/organizations'; import { PageHeader } from '@/components/ui/PageHeader'; interface Organization { id: string; name: string; type: string; scac?: string; siren?: string; siret?: string; siretVerified?: boolean; statusBadge?: string; eori?: string; contact_phone?: string; contact_email?: string; address: { street: string; city: string; state?: string; postalCode: string; country: string; }; logoUrl?: string; isActive: boolean; createdAt: string; } interface FormData { name: string; type: string; scac: string; siren: string; siret: string; eori: string; contact_phone: string; contact_email: string; address: { street: string; city: string; state: string; postalCode: string; country: string; }; logoUrl: string; isActive: boolean; } const EMPTY_FORM: FormData = { name: '', type: 'FREIGHT_FORWARDER', scac: '', siren: '', siret: '', eori: '', contact_phone: '', contact_email: '', address: { street: '', city: '', state: '', postalCode: '', country: 'FR' }, logoUrl: '', isActive: true, }; function FieldHint({ children }: { children: React.ReactNode }) { return

{children}

; } function FieldError({ message }: { message?: string }) { if (!message) return null; return

{message}

; } export default function AdminOrganizationsPage() { const t = useTranslations('dashboard.admin.organizations'); const [organizations, setOrganizations] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selectedOrg, setSelectedOrg] = useState(null); const [showCreateModal, setShowCreateModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [verifyingId, setVerifyingId] = useState(null); const [formData, setFormData] = useState(EMPTY_FORM); const [formError, setFormError] = useState(null); const [fieldErrors, setFieldErrors] = useState>>({}); const [submitting, setSubmitting] = useState(false); useEffect(() => { fetchOrganizations(); }, []); const fetchOrganizations = async () => { try { setLoading(true); const response = await getAllOrganizations(); setOrganizations(response.organizations || []); setError(null); } catch (err: any) { setError(err.message || t('loadError')); } finally { setLoading(false); } }; const validateForm = (isCreate: boolean): boolean => { const errors: Partial> = {}; if (!formData.name.trim() || formData.name.trim().length < 2) { errors.name = t('validation.nameTooShort'); } if (isCreate && formData.type === 'CARRIER') { if (!formData.scac) { errors.scac = t('validation.scacRequired'); } else if (!/^[A-Z]{4}$/.test(formData.scac)) { errors.scac = t('validation.scacFormat'); } } if (formData.siren && !/^[0-9]{9}$/.test(formData.siren)) { errors.siren = t('validation.sirenFormat'); } if (formData.siret && !/^[0-9]{14}$/.test(formData.siret)) { errors.siret = t('validation.siretFormat'); } if (!formData.address.street.trim()) errors.street = t('validation.required'); if (!formData.address.city.trim()) errors.city = t('validation.required'); if (!formData.address.postalCode.trim()) errors.postalCode = t('validation.required'); if (!/^[A-Z]{2}$/.test(formData.address.country)) { errors.country = t('validation.countryFormat'); } setFieldErrors(errors); return Object.keys(errors).length === 0; }; const buildApiAddress = () => ({ street: formData.address.street, city: formData.address.city, postalCode: formData.address.postalCode, country: formData.address.country, ...(formData.address.state ? { state: formData.address.state } : {}), }); const handleCreate = async (e: React.FormEvent) => { e.preventDefault(); setFormError(null); if (!validateForm(true)) return; setSubmitting(true); try { await createOrganization({ name: formData.name.trim(), type: formData.type as any, address: buildApiAddress(), contact_email: formData.contact_email || undefined, contact_phone: formData.contact_phone || undefined, logoUrl: formData.logoUrl || undefined, ...(formData.siren ? { siren: formData.siren } : {}), ...(formData.siret ? { siret: formData.siret } : {}), ...(formData.eori ? { eori: formData.eori } : {}), ...(formData.scac ? { scac: formData.scac } : {}), }); await fetchOrganizations(); setShowCreateModal(false); resetForm(); } catch (err: any) { setFormError(err.message || t('createError')); } finally { setSubmitting(false); } }; const handleUpdate = async (e: React.FormEvent) => { e.preventDefault(); if (!selectedOrg) return; setFormError(null); if (!validateForm(false)) return; setSubmitting(true); try { await updateOrganization(selectedOrg.id, { name: formData.name.trim(), address: buildApiAddress(), contact_email: formData.contact_email || undefined, contact_phone: formData.contact_phone || undefined, logoUrl: formData.logoUrl || undefined, isActive: formData.isActive, ...(formData.siren ? { siren: formData.siren } : {}), ...(formData.siret ? { siret: formData.siret } : {}), ...(formData.eori ? { eori: formData.eori } : {}), }); await fetchOrganizations(); setShowEditModal(false); setSelectedOrg(null); resetForm(); } catch (err: any) { setFormError(err.message || t('updateError')); } finally { setSubmitting(false); } }; const resetForm = () => { setFormData(EMPTY_FORM); setFormError(null); setFieldErrors({}); }; const handleTypeChange = (newType: string) => { setFormData(prev => ({ ...prev, type: newType, scac: newType !== 'CARRIER' ? '' : prev.scac, })); setFieldErrors(prev => ({ ...prev, scac: undefined })); }; const handleVerifySiret = async (orgId: string) => { try { setVerifyingId(orgId); const result = await verifySiret(orgId); if (result.verified) { alert( t('siretVerified', { companyName: result.companyName || 'N/A', address: result.address || 'N/A', }) ); await fetchOrganizations(); } else { alert(result.message || t('siretInvalid')); } } catch (err: any) { alert(err.message || t('siretError')); } finally { setVerifyingId(null); } }; const handleApproveSiret = async (orgId: string) => { if (!confirm(t('confirmApprove'))) return; try { setVerifyingId(orgId); const result = await approveSiret(orgId); alert(result.message); await fetchOrganizations(); } catch (err: any) { alert(err.message || t('siretApproveError')); } finally { setVerifyingId(null); } }; const handleRejectSiret = async (orgId: string) => { if (!confirm(t('confirmReject'))) return; try { setVerifyingId(orgId); const result = await rejectSiret(orgId); alert(result.message); await fetchOrganizations(); } catch (err: any) { alert(err.message || t('siretRejectError')); } finally { setVerifyingId(null); } }; const openEditModal = (org: Organization) => { setSelectedOrg(org); setFormData({ name: org.name, type: org.type, scac: org.scac || '', siren: org.siren || '', siret: org.siret || '', eori: org.eori || '', contact_phone: org.contact_phone || '', contact_email: org.contact_email || '', address: { street: org.address.street, city: org.address.city, state: org.address.state || '', postalCode: org.address.postalCode, country: org.address.country, }, logoUrl: org.logoUrl || '', isActive: org.isActive, }); setFormError(null); setFieldErrors({}); setShowEditModal(true); }; const closeModal = () => { setShowCreateModal(false); setShowEditModal(false); setSelectedOrg(null); resetForm(); }; const getTypeLabel = (type: string) => { const allowed = ['FREIGHT_FORWARDER', 'CARRIER', 'SHIPPER']; if (allowed.includes(type)) return t(`types.${type}` as any); return type.replace('_', ' '); }; const inputClass = (hasError?: string) => `mt-1 block w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none text-sm ${ hasError ? 'border-red-400 focus:border-red-500 focus:ring-red-500' : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500' }`; if (loading) { return (

{t('loading')}

); } return (
setShowCreateModal(true)} className="px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors" > {t('create')} } /> {error && (
{error}
)} {/* Organizations Grid */}
{organizations.map(org => (

{org.name}

{getTypeLabel(org.type)}
{org.isActive ? t('active') : t('inactive')}
{org.scac && (
{t('scac')}: {org.scac}
)} {org.siren && (
{t('siren')}: {org.siren}
)}
{t('siret')}: {org.siret ? ( <> {org.siret} {org.siretVerified ? ( {t('verified')} ) : ( {t('notVerified')} )} ) : ( {t('notProvided')} )}
{org.contact_email && (
{t('email')}: {org.contact_email}
)}
{t('location')}: {org.address.city},{' '} {org.address.country}
{org.siret && !org.siretVerified && ( )}
{(org.siret || org.siren) && (
{!org.siretVerified ? ( ) : ( )}
)}
))}
{/* Create / Edit Modal */} {(showCreateModal || showEditModal) && (

{showCreateModal ? t('modal.createTitle') : t('modal.editTitle')}

{/* ── Informations générales ── */}

{t('modal.sectionGeneral')}

{ setFormData({ ...formData, name: e.target.value }); setFieldErrors(prev => ({ ...prev, name: undefined })); }} minLength={2} maxLength={200} className={inputClass(fieldErrors.name)} placeholder="Acme Freight Forwarding" />
{showCreateModal ? ( ) : (
{getTypeLabel(formData.type)} {t('modal.typeReadOnly')}
)}
{/* SCAC — création uniquement, visible en lecture seule en édition si CARRIER */} {showCreateModal && formData.type === 'CARRIER' && (
{ const v = e.target.value.toUpperCase().replace(/[^A-Z]/g, ''); setFormData({ ...formData, scac: v }); setFieldErrors(prev => ({ ...prev, scac: undefined })); }} maxLength={4} minLength={4} className={inputClass(fieldErrors.scac)} placeholder="MAEU" /> {t('modal.scacHint')}
)} {showEditModal && formData.type === 'CARRIER' && formData.scac && (
{formData.scac}
{t('modal.typeReadOnly')}
)}
{/* ── Identifiants légaux ── */}

{t('modal.sectionLegal')}

{ const v = e.target.value.replace(/\D/g, '').slice(0, 9); setFormData({ ...formData, siren: v }); setFieldErrors(prev => ({ ...prev, siren: undefined })); }} maxLength={9} className={inputClass(fieldErrors.siren)} placeholder="123456789" /> {t('modal.sirenHint')}
{ const v = e.target.value.replace(/\D/g, '').slice(0, 14); setFormData({ ...formData, siret: v }); setFieldErrors(prev => ({ ...prev, siret: undefined })); }} maxLength={14} className={inputClass(fieldErrors.siret)} placeholder="12345678901234" /> {t('modal.siretHint')}
setFormData({ ...formData, eori: e.target.value })} className={inputClass()} placeholder="FR123456789" /> {t('modal.eoriHint')}
{/* ── Contact ── */}

{t('modal.sectionContact')}

setFormData({ ...formData, contact_email: e.target.value })} className={inputClass()} placeholder="contact@exemple.com" />
setFormData({ ...formData, contact_phone: e.target.value })} className={inputClass()} placeholder="+33 6 00 00 00 00" />
{/* ── Adresse ── */}

{t('modal.sectionAddress')}

{ setFormData({ ...formData, address: { ...formData.address, street: e.target.value } }); setFieldErrors(prev => ({ ...prev, street: undefined })); }} className={inputClass(fieldErrors.street)} placeholder="123 Rue de la République" />
{ setFormData({ ...formData, address: { ...formData.address, city: e.target.value } }); setFieldErrors(prev => ({ ...prev, city: undefined })); }} className={inputClass(fieldErrors.city)} placeholder="Paris" />
{ setFormData({ ...formData, address: { ...formData.address, postalCode: e.target.value } }); setFieldErrors(prev => ({ ...prev, postalCode: undefined })); }} className={inputClass(fieldErrors.postalCode)} placeholder="75001" />
setFormData({ ...formData, address: { ...formData.address, state: e.target.value } }) } className={inputClass()} placeholder="Île-de-France" />
{ const v = e.target.value.toUpperCase().replace(/[^A-Z]/g, '').slice(0, 2); setFormData({ ...formData, address: { ...formData.address, country: v } }); setFieldErrors(prev => ({ ...prev, country: undefined })); }} maxLength={2} minLength={2} className={inputClass(fieldErrors.country)} placeholder="FR" /> {t('modal.countryHint')}
{/* ── Autres ── */}

{t('modal.sectionOther')}

setFormData({ ...formData, logoUrl: e.target.value })} className={inputClass()} placeholder="https://exemple.com/logo.png" />
{showEditModal && (
setFormData({ ...formData, isActive: e.target.checked })} className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
)}
{/* Erreur globale */} {formError && (
{formError}
)}
)}
); }