espace-paie-odentas/app/(app)/staff/clients/[id]/page.tsx
odentas 8edb624330 fix: Corriger logos cassés dans PDFs PDFMonkey
- Conserver data URI complète (data:image/png;base64,...) lors de l'upload
- Ajout script migration SQL pour logos existants
- Compatible avec affichage et génération PDF PDFMonkey
2025-11-27 14:09:16 +01:00

1154 lines
No EOL
47 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import Link from "next/link";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Send, Loader2 } from "lucide-react";
type Organization = {
id: string;
name: string;
subscription_plan?: string;
subscription_status?: string;
monthly_fee?: number;
setup_fee?: number;
created_at?: string;
updated_at?: string;
};
type StructureInfos = {
raison_sociale?: string;
code_employeur?: string; // Remplace structure_api
siret?: string;
siren?: string;
forme_juridique?: string;
declaration?: string;
convention_collective?: string;
ccn_raccourci?: string;
code_ape?: string;
tva?: string;
rna?: string;
adresse_siege?: string;
adresse?: string;
cp?: string;
ville?: string;
presidente?: string;
tresoriere?: string;
structure_a_spectacles?: boolean;
entree_en_relation?: string;
logo_base64?: string;
// Nouveaux champs abonnement
statut?: string;
ouverture_compte?: string;
offre_speciale?: string;
notes?: string;
// Gestion paie
virements_salaires?: string;
agrement_aem?: string;
contact_principal?: string;
email?: string;
email_cc?: string; // Nouveau champ
email_signature?: string; // Nouveau champ
telephone?: string;
signataire_contrats?: string;
signataire_delegation?: boolean; // Boolean au lieu de string
// Responsable de traitement (RGPD)
nom_responsable_traitement?: string;
qualite_responsable_traitement?: string;
email_responsable_traitement?: string;
licence_spectacles?: string;
urssaf?: string;
audiens?: string;
conges_spectacles?: string;
pole_emploi_spectacle?: string;
recouvrement_pe_spectacle?: string;
afdas?: string;
fnas?: string;
fcap?: string;
};
type ClientData = {
organization: Organization;
structureInfos: StructureInfos;
details: any;
};
type Referrer = {
id: string;
code: string;
name: string;
contact_name: string;
};
function Line({ label, value }: { label: string; value?: string | number | null }) {
return (
<div className="grid grid-cols-3 gap-2 border-b last:border-b-0 py-2">
<div className="text-slate-500">{label}</div>
<div className="col-span-2">{value ?? "—"}</div>
</div>
);
}
function LogoLine({ label, value }: { label: string; value?: string | null }) {
return (
<div className="grid grid-cols-3 gap-2 border-b last:border-b-0 py-2">
<div className="text-slate-500">{label}</div>
<div className="col-span-2">
{value ? (
<img
src={value.startsWith('data:') ? value : `data:image/png;base64,${value}`}
alt="Logo"
className="max-w-32 max-h-32 object-contain border rounded"
/>
) : (
"—"
)}
</div>
</div>
);
}
function EditableLine({
label,
value,
onChange,
type = "text",
options
}: {
label: string;
value?: string | number | null;
onChange: (value: string) => void;
type?: "text" | "email" | "number" | "date" | "select";
options?: { value: string; label: string }[];
}) {
return (
<div className="grid grid-cols-3 gap-2 border-b last:border-b-0 py-2">
<div className="text-slate-500">{label}</div>
<div className="col-span-2">
{type === "select" && options ? (
<select
value={value || ""}
onChange={(e) => onChange(e.target.value)}
className="w-full px-2 py-1 text-sm border rounded"
>
<option value=""></option>
{options.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
) : (
<input
type={type}
value={value || ""}
onChange={(e) => onChange(e.target.value)}
className="w-full px-2 py-1 text-sm border rounded"
/>
)}
</div>
</div>
);
}
function ImageUpload({
label,
value,
onChange
}: {
label: string;
value?: string | null;
onChange: (base64: string) => void;
}) {
const [preview, setPreview] = useState<string | null>(null);
useEffect(() => {
if (value) {
// Si la valeur commence par "data:", c'est déjà un data URL complet
// Sinon, on ajoute le préfixe pour les images
const imageUrl = value.startsWith('data:') ? value : `data:image/png;base64,${value}`;
setPreview(imageUrl);
} else {
setPreview(null);
}
}, [value]);
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
// Vérifier que c'est bien une image
if (!file.type.startsWith('image/')) {
alert('Veuillez sélectionner un fichier image');
return;
}
// Vérifier la taille (max 5MB)
if (file.size > 5 * 1024 * 1024) {
alert('Le fichier est trop volumineux (max 5MB)');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const result = e.target?.result as string;
if (result) {
// Conserver la data URI complète (avec le préfixe data:image/...;base64,)
// pour compatibilité avec PDFMonkey et l'affichage
setPreview(result);
onChange(result);
}
};
reader.readAsDataURL(file);
};
const handleRemove = () => {
setPreview(null);
onChange('');
};
return (
<div className="grid grid-cols-3 gap-2 border-b last:border-b-0 py-2">
<div className="text-slate-500">{label}</div>
<div className="col-span-2 space-y-2">
{preview && (
<div className="relative inline-block">
<img
src={preview}
alt="Logo"
className="max-w-32 max-h-32 object-contain border rounded"
/>
<button
type="button"
onClick={handleRemove}
className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-xs hover:bg-red-600"
>
×
</button>
</div>
)}
<input
type="file"
accept="image/*"
onChange={handleFileChange}
className="w-full px-2 py-1 text-sm border rounded file:mr-2 file:py-1 file:px-2 file:border-0 file:text-sm file:bg-slate-100 file:text-slate-700 file:rounded"
/>
<div className="text-xs text-slate-500">
Formats acceptés: JPG, PNG, GIF (max 5MB)
</div>
</div>
</div>
);
}
function formatDate(d?: string | null) {
if (!d) return "—";
try {
return new Intl.DateTimeFormat("fr-FR", {
dateStyle: "medium",
timeStyle: "short"
}).format(new Date(d));
} catch {
return d;
}
}
export default function ClientDetailPage() {
const params = useParams();
const router = useRouter();
const queryClient = useQueryClient();
const clientId = params.id as string;
const [isEditing, setIsEditing] = useState(false);
const [editData, setEditData] = useState<Partial<Organization & StructureInfos & any>>({});
const [showSepaMandateModal, setShowSepaMandateModal] = useState(false);
// Récupération de la liste des apporteurs
const { data: referrers = [] } = useQuery<Referrer[]>({
queryKey: ["referrers"],
queryFn: async () => {
const res = await fetch("/api/staff/referrers", {
cache: "no-store",
credentials: "include"
});
if (!res.ok) return [];
return res.json();
},
});
// Récupération des données du client
const {
data: clientData,
isLoading,
error
} = useQuery<ClientData>({
queryKey: ["staff-client", clientId],
queryFn: async () => {
const res = await fetch(`/api/staff/clients/${clientId}`, {
cache: "no-store",
credentials: "include"
});
if (!res.ok) {
throw new Error("Erreur lors de la récupération du client");
}
return res.json();
},
enabled: !!clientId,
});
// Mutation pour la mise à jour
const updateMutation = useMutation({
mutationFn: async (data: any) => {
console.log("📤 [CLIENT UPDATE] Données envoyées:", data);
const res = await fetch(`/api/staff/clients/${clientId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
body: JSON.stringify(data),
});
if (!res.ok) {
const errorData = await res.json().catch(() => ({}));
console.error("❌ [CLIENT UPDATE] Erreur API:", {
status: res.status,
statusText: res.statusText,
errorData
});
throw new Error(`Erreur lors de la mise à jour: ${errorData.error || res.statusText}`);
}
return res.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["staff-client", clientId] });
setIsEditing(false);
setEditData({});
},
});
// Mutation pour envoyer la demande de mandat SEPA
const sendSepaMandateMutation = useMutation({
mutationFn: async () => {
const res = await fetch(`/api/staff/clients/${clientId}/request-sepa-mandate`, {
method: "POST",
credentials: "include",
});
if (!res.ok) {
const errorData = await res.json().catch(() => ({}));
throw new Error(errorData.message || "Erreur lors de l'envoi de la demande");
}
return res.json();
},
onSuccess: () => {
setShowSepaMandateModal(false);
alert("Demande de mandat SEPA envoyée avec succès !");
},
onError: (error: any) => {
alert(`Erreur: ${error.message}`);
},
});
// Initialiser les données d'édition
useEffect(() => {
if (clientData && isEditing) {
const { organization, structureInfos, details } = clientData;
setEditData({
// Organisation
name: organization.name,
statut: structureInfos.statut,
ouverture_compte: structureInfos.ouverture_compte,
offre_speciale: structureInfos.offre_speciale,
notes: structureInfos.notes,
// Gestion paie
virements_salaires: structureInfos.virements_salaires,
agrement_aem: structureInfos.agrement_aem,
// Apporteur d'affaires
is_referred: details.is_referred,
referrer_code: details.referrer_code,
commission_rate: details.commission_rate,
// Structure infos
code_employeur: structureInfos.code_employeur, // Remplace structure_api
siret: structureInfos.siret,
siren: structureInfos.siren,
forme_juridique: structureInfos.forme_juridique,
declaration: structureInfos.declaration,
ccn: structureInfos.convention_collective,
ccn_raccourci: structureInfos.ccn_raccourci,
ape: structureInfos.code_ape,
tva: structureInfos.tva,
rna: structureInfos.rna,
adresse_siege: structureInfos.adresse_siege,
adresse: structureInfos.adresse,
cp: structureInfos.cp,
ville: structureInfos.ville,
president: structureInfos.presidente,
tresorier: structureInfos.tresoriere,
structure_a_spectacles: structureInfos.structure_a_spectacles,
entree_en_relation: structureInfos.entree_en_relation,
logo_base64: structureInfos.logo_base64,
// Contact
nom_contact: details.nom_contact,
prenom_contact: details.prenom_contact,
email_notifs: structureInfos.email, // Modifié pour utiliser email_notifs
email_notifs_cc: structureInfos.email_cc, // Nouveau champ
email_signature: structureInfos.email_signature, // Nouveau champ
tel_contact: structureInfos.telephone,
prenom_signataire: details.prenom_signataire,
nom_signataire: details.nom_signataire,
qualite_signataire: details.qualite_signataire,
delegation_signature: structureInfos.signataire_delegation, // Boolean
// Responsable de traitement (RGPD)
nom_responsable_traitement: structureInfos.nom_responsable_traitement,
qualite_responsable_traitement: structureInfos.qualite_responsable_traitement,
email_responsable_traitement: structureInfos.email_responsable_traitement,
// Facturation (SEPA)
iban: details.iban,
bic: details.bic,
id_mandat_sepa: details.id_mandat_sepa,
// Caisses
licence_spectacles: structureInfos.licence_spectacles,
urssaf: structureInfos.urssaf,
audiens: structureInfos.audiens,
conges_spectacles_id: structureInfos.conges_spectacles,
pole_emploi_id: structureInfos.pole_emploi_spectacle,
recouvrement_pe_id: structureInfos.recouvrement_pe_spectacle,
afdas_id: structureInfos.afdas,
fnas_id: structureInfos.fnas,
fcap_id: structureInfos.fcap,
});
}
}, [clientData, isEditing]);
const handleSave = () => {
updateMutation.mutate(editData);
};
const handleCancel = () => {
setIsEditing(false);
setEditData({});
};
if (isLoading) {
return (
<main className="p-4 md:p-6">
<div className="text-slate-500">Chargement...</div>
</main>
);
}
if (error) {
return (
<main className="p-4 md:p-6">
<div className="text-red-600">Erreur: {error.message}</div>
<Link href="/staff/clients" className="text-blue-600 underline">
Retour à la liste des clients
</Link>
</main>
);
}
if (!clientData) {
return (
<main className="p-4 md:p-6">
<div className="text-slate-500">Client non trouvé</div>
<Link href="/staff/clients" className="text-blue-600 underline">
Retour à la liste des clients
</Link>
</main>
);
}
const { organization, structureInfos } = clientData;
return (
<main className="p-4 md:p-6 space-y-4">
<div className="flex items-center justify-between">
<div>
<Link
href="/staff/clients"
className="text-blue-600 hover:text-blue-800 text-sm mb-2 inline-block"
>
Retour aux clients
</Link>
<h1 className="text-lg font-semibold">Détail client : {organization.name}</h1>
</div>
<div className="flex items-center gap-2">
{isEditing ? (
<>
<button
onClick={handleCancel}
className="px-3 py-2 text-sm border rounded-lg hover:bg-slate-50"
disabled={updateMutation.isPending}
>
Annuler
</button>
<button
onClick={handleSave}
className="px-3 py-2 text-sm bg-emerald-600 text-white rounded-lg hover:bg-emerald-700"
disabled={updateMutation.isPending}
>
{updateMutation.isPending ? "Sauvegarde..." : "Sauvegarder"}
</button>
</>
) : (
<button
onClick={() => setIsEditing(true)}
className="px-3 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
Modifier
</button>
)}
</div>
</div>
{updateMutation.error && (
<div className="rounded-lg border border-red-200 bg-red-50 p-4 text-red-800">
Erreur lors de la sauvegarde : {updateMutation.error.message}
</div>
)}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 items-start">
<div className="space-y-4">
{/* Informations principales + Votre structure */}
<section className="rounded-2xl border bg-white">
<div className="px-4 py-3 border-b">
<h2 className="font-medium">Informations principales</h2>
</div>
<div className="p-4 text-sm">
{isEditing ? (
<div className="space-y-2">
<EditableLine
label="Nom"
value={editData.name}
onChange={(value) => setEditData(prev => ({ ...prev, name: value }))}
/>
<EditableLine
label="Code employeur"
value={editData.code_employeur}
onChange={(value) => setEditData(prev => ({ ...prev, code_employeur: value }))}
/>
<EditableLine
label="SIRET"
value={editData.siret}
onChange={(value) => setEditData(prev => ({ ...prev, siret: value }))}
/>
<EditableLine
label="SIREN"
value={editData.siren}
onChange={(value) => setEditData(prev => ({ ...prev, siren: value }))}
/>
<EditableLine
label="Forme juridique"
value={editData.forme_juridique}
onChange={(value) => setEditData(prev => ({ ...prev, forme_juridique: value }))}
/>
<EditableLine
label="Déclaration"
value={editData.declaration}
type="date"
onChange={(value) => setEditData(prev => ({ ...prev, declaration: value }))}
/>
<EditableLine
label="Convention collective"
value={editData.ccn}
type="select"
options={[
{ value: "Convention Collective Nationale des Entreprises Artistiques & Culturelles", label: "Convention Collective Nationale des Entreprises Artistiques & Culturelles" },
{ value: "Convention Collective Nationale du Spectacle Vivant Privé", label: "Convention Collective Nationale du Spectacle Vivant Privé" },
{ value: "Convention Collective Nationale des Prestataires de Services du Secteur Tertiaire", label: "Convention Collective Nationale des Prestataires de Services du Secteur Tertiaire" },
{ value: "Convention Collective Nationale de la Production Audiovisuelle", label: "Convention Collective Nationale de la Production Audiovisuelle" },
{ value: "Convention Collective Nationale de l'Édition", label: "Convention Collective Nationale de l'Édition" },
{ value: "Non concerné", label: "Non concerné" },
]}
onChange={(value) => setEditData(prev => ({ ...prev, ccn: value }))}
/>
<EditableLine
label="CCN raccourci"
value={editData.ccn_raccourci}
type="select"
options={[
{ value: "CCNEAC", label: "CCNEAC" },
{ value: "CCNSVP", label: "CCNSVP" },
{ value: "CCNPSST", label: "CCNPSST" },
{ value: "CCNPA", label: "CCNPA" },
{ value: "CCNE", label: "CCNE" },
{ value: "NC", label: "NC" },
]}
onChange={(value) => setEditData(prev => ({ ...prev, ccn_raccourci: value }))}
/>
<EditableLine
label="Code APE"
value={editData.ape}
onChange={(value) => setEditData(prev => ({ ...prev, ape: value }))}
/>
<EditableLine
label="TVA intracommunautaire"
value={editData.tva}
onChange={(value) => setEditData(prev => ({ ...prev, tva: value }))}
/>
<EditableLine
label="N° RNA"
value={editData.rna}
onChange={(value) => setEditData(prev => ({ ...prev, rna: value }))}
/>
<EditableLine
label="Adresse"
value={editData.adresse}
onChange={(value) => setEditData(prev => ({ ...prev, adresse: value }))}
/>
<EditableLine
label="Code postal"
value={editData.cp}
onChange={(value) => setEditData(prev => ({ ...prev, cp: value }))}
/>
<EditableLine
label="Ville"
value={editData.ville}
onChange={(value) => setEditData(prev => ({ ...prev, ville: value }))}
/>
<EditableLine
label="Adresse siège"
value={editData.adresse_siege}
onChange={(value) => setEditData(prev => ({ ...prev, adresse_siege: value }))}
/>
<EditableLine
label="Président(e)"
value={editData.president}
onChange={(value) => setEditData(prev => ({ ...prev, president: value }))}
/>
<EditableLine
label="Trésorier(ère)"
value={editData.tresorier}
onChange={(value) => setEditData(prev => ({ ...prev, tresorier: value }))}
/>
<EditableLine
label="Licence spectacle"
value={editData.licence_spectacles}
onChange={(value) => setEditData(prev => ({ ...prev, licence_spectacles: value }))}
/>
<EditableLine
label="Structure à spectacles ?"
value={editData.structure_a_spectacles ? "Oui" : "Non"}
type="select"
options={[
{ value: "true", label: "Oui" },
{ value: "false", label: "Non" },
]}
onChange={(value) => setEditData(prev => ({ ...prev, structure_a_spectacles: value === "true" }))}
/>
<ImageUpload
label="Logo"
value={editData.logo_base64}
onChange={(base64) => setEditData(prev => ({ ...prev, logo_base64: base64 }))}
/>
</div>
) : (
<div className="space-y-2">
<Line label="Nom" value={organization.name} />
<Line label="Code employeur" value={structureInfos.code_employeur} />
<Line label="ID" value={organization.id} />
<Line label="SIRET" value={structureInfos.siret} />
<Line label="SIREN" value={structureInfos.siren} />
<Line label="Forme juridique" value={structureInfos.forme_juridique} />
<Line label="Déclaration" value={structureInfos.declaration} />
<Line label="Convention collective" value={structureInfos.convention_collective} />
<Line label="CCN raccourci" value={structureInfos.ccn_raccourci} />
<Line label="Code APE" value={structureInfos.code_ape} />
<Line label="TVA intracommunautaire" value={structureInfos.tva} />
<Line label="N° RNA" value={structureInfos.rna} />
<Line label="Adresse" value={structureInfos.adresse} />
<Line label="Code postal" value={structureInfos.cp} />
<Line label="Ville" value={structureInfos.ville} />
<Line label="Adresse siège" value={structureInfos.adresse_siege} />
<Line label="Président(e)" value={structureInfos.presidente} />
<Line label="Trésorier(ère)" value={structureInfos.tresoriere} />
<Line label="Licence spectacle" value={structureInfos.licence_spectacles} />
<Line label="Structure à spectacles ?" value={structureInfos.structure_a_spectacles ? "Oui" : "Non"} />
<LogoLine label="Logo" value={structureInfos.logo_base64} />
<Line label="Créé le" value={formatDate(organization.created_at)} />
<Line label="Mis à jour le" value={formatDate(organization.updated_at)} />
</div>
)}
</div>
</section>
</div>
<div className="space-y-4">
{/* Abonnement */}
<section className="rounded-2xl border bg-white">
<div className="px-4 py-3 border-b">
<h2 className="font-medium">Abonnement</h2>
</div>
<div className="p-4 text-sm">
{isEditing ? (
<div className="space-y-2">
{/* Section Contrat */}
<div className="pb-2">
<div className="text-xs font-semibold text-slate-600 uppercase tracking-wide mb-2">Contrat</div>
<EditableLine
label="Entrée en relation"
value={editData.entree_en_relation}
type="date"
onChange={(value) => setEditData(prev => ({ ...prev, entree_en_relation: value }))}
/>
<EditableLine
label="Statut"
value={editData.statut}
type="select"
options={[
{ value: "Actif", label: "Actif" },
{ value: "Ancien client", label: "Ancien client" },
]}
onChange={(value) => setEditData(prev => ({ ...prev, statut: value }))}
/>
<EditableLine
label="Ouverture de compte"
value={editData.ouverture_compte}
type="select"
options={[
{ value: "Simple", label: "Simple" },
{ value: "Complexe", label: "Complexe" },
]}
onChange={(value) => setEditData(prev => ({ ...prev, ouverture_compte: value }))}
/>
<EditableLine
label="Offre spéciale"
value={editData.offre_speciale}
onChange={(value) => setEditData(prev => ({ ...prev, offre_speciale: value }))}
/>
<EditableLine
label="Note"
value={editData.notes}
onChange={(value) => setEditData(prev => ({ ...prev, notes: value }))}
/>
</div>
{/* Section Apporteur d'Affaires */}
<div className="pt-2 border-t">
<div className="text-xs font-semibold text-slate-600 uppercase tracking-wide mb-2">Apporteur d'affaires</div>
<EditableLine
label="Client apporté ?"
value={editData.is_referred ? "true" : "false"}
type="select"
options={[
{ value: "false", label: "Non" },
{ value: "true", label: "Oui" },
]}
onChange={(value) => setEditData(prev => ({
...prev,
is_referred: value === "true",
referrer_code: value === "false" ? undefined : prev.referrer_code,
commission_rate: value === "false" ? undefined : prev.commission_rate
}))}
/>
{editData.is_referred && (
<>
<EditableLine
label="Apporteur"
value={editData.referrer_code}
type="select"
options={referrers.map(r => ({ value: r.code, label: r.name }))}
onChange={(value) => setEditData(prev => ({ ...prev, referrer_code: value }))}
/>
<EditableLine
label="Taux de commission"
value={editData.commission_rate}
type="number"
onChange={(value) => setEditData(prev => ({ ...prev, commission_rate: value }))}
/>
</>
)}
</div>
{/* Section Facturation */}
<div className="pt-2 border-t">
<div className="text-xs font-semibold text-slate-600 uppercase tracking-wide mb-2">Facturation</div>
<EditableLine
label="IBAN"
value={editData.iban}
onChange={(value) => setEditData(prev => ({ ...prev, iban: value }))}
/>
<EditableLine
label="BIC"
value={editData.bic}
onChange={(value) => setEditData(prev => ({ ...prev, bic: value }))}
/>
<EditableLine
label="ID mandat SEPA"
value={editData.id_mandat_sepa}
onChange={(value) => setEditData(prev => ({ ...prev, id_mandat_sepa: value }))}
/>
</div>
{/* Section Gestion paie */}
<div className="pt-2 border-t">
<div className="text-xs font-semibold text-slate-600 uppercase tracking-wide mb-2">Gestion paie</div>
<EditableLine
label="Virements salaires"
value={editData.virements_salaires}
type="select"
options={[
{ value: "Odentas", label: "Odentas" },
{ value: "Client", label: "Client (structure)" },
]}
onChange={(value) => setEditData(prev => ({ ...prev, virements_salaires: value }))}
/>
<EditableLine
label="AEM"
value={editData.agrement_aem}
onChange={(value) => setEditData(prev => ({ ...prev, agrement_aem: value }))}
/>
</div>
</div>
) : (
<div className="space-y-2">
{/* Section Contrat */}
<div className="pb-2">
<div className="text-xs font-semibold text-slate-600 uppercase tracking-wide mb-2">Contrat</div>
<Line label="Entrée en relation" value={structureInfos.entree_en_relation} />
<Line label="Statut" value={structureInfos.statut} />
<Line label="Ouverture de compte" value={structureInfos.ouverture_compte} />
<Line label="Offre spéciale" value={structureInfos.offre_speciale} />
<Line label="Note" value={structureInfos.notes} />
</div>
{/* Section Apporteur d'Affaires */}
<div className="pt-2 border-t">
<div className="text-xs font-semibold text-slate-600 uppercase tracking-wide mb-2">Apporteur d'affaires</div>
<Line
label="Client apporté ?"
value={clientData.details.is_referred ? "Oui" : "Non"}
/>
{clientData.details.is_referred && (
<>
<Line
label="Apporteur"
value={referrers.find(r => r.code === clientData.details.referrer_code)?.name || clientData.details.referrer_code}
/>
<Line
label="Taux de commission"
value={clientData.details.commission_rate ? `${(clientData.details.commission_rate * 100).toFixed(2)}%` : "—"}
/>
</>
)}
</div>
{/* Section Facturation */}
<div className="pt-2 border-t">
<div className="text-xs font-semibold text-slate-600 uppercase tracking-wide mb-2">Facturation</div>
{/* Carte Statut Mandat SEPA */}
<div className="mb-3">
{clientData.details.id_mandat_sepa ? (
<div className="flex items-center gap-3 px-4 py-3 bg-gradient-to-r from-emerald-50 to-emerald-100 border border-emerald-200 rounded-xl">
<div className="flex-shrink-0 w-10 h-10 bg-emerald-500 rounded-full flex items-center justify-center">
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
</div>
<div className="flex-1">
<div className="font-semibold text-emerald-900">Mandat SEPA actif</div>
<div className="text-sm text-emerald-700">Les prélèvements automatiques sont activés</div>
</div>
</div>
) : (
<div className="flex items-center gap-3 px-4 py-3 bg-gradient-to-r from-amber-50 to-amber-100 border border-amber-200 rounded-xl">
<div className="flex-shrink-0 w-10 h-10 bg-amber-500 rounded-full flex items-center justify-center">
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<div className="flex-1">
<div className="font-semibold text-amber-900">Aucun mandat SEPA</div>
<div className="text-sm text-amber-700">Paiement par virement uniquement</div>
</div>
<button
onClick={() => setShowSepaMandateModal(true)}
className="flex-shrink-0 inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors"
>
<Send className="w-4 h-4" />
Demande de mandat
</button>
</div>
)}
</div>
<Line label="IBAN" value={clientData.details.iban} />
<Line label="BIC" value={clientData.details.bic} />
<Line label="ID mandat SEPA" value={clientData.details.id_mandat_sepa} />
</div>
{/* Section Gestion paie */}
<div className="pt-2 border-t">
<div className="text-xs font-semibold text-slate-600 uppercase tracking-wide mb-2">Gestion paie</div>
<Line
label="Virements salaires"
value={structureInfos.virements_salaires || "—"}
/>
<Line
label="AEM"
value={structureInfos.agrement_aem || "—"}
/>
</div>
</div>
)}
</div>
</section>
{/* Informations de contact */}
<section className="rounded-2xl border bg-white">
<div className="px-4 py-3 border-b">
<h2 className="font-medium">Informations de contact</h2>
</div>
<div className="p-4 text-sm">
{isEditing ? (
<div className="space-y-2">
<EditableLine
label="Prénom contact"
value={editData.prenom_contact}
onChange={(value) => setEditData(prev => ({ ...prev, prenom_contact: value }))}
/>
<EditableLine
label="Nom contact"
value={editData.nom_contact}
onChange={(value) => setEditData(prev => ({ ...prev, nom_contact: value }))}
/>
<EditableLine
label="Email"
value={editData.email_notifs}
type="email"
onChange={(value) => setEditData(prev => ({ ...prev, email_notifs: value }))}
/>
<EditableLine
label="Email CC"
value={editData.email_notifs_cc}
type="email"
onChange={(value) => setEditData(prev => ({ ...prev, email_notifs_cc: value }))}
/>
<EditableLine
label="Email signature"
value={editData.email_signature}
type="email"
onChange={(value) => setEditData(prev => ({ ...prev, email_signature: value }))}
/>
{/* Responsable de traitement (RGPD) */}
<EditableLine
label="Nom responsable de traitement"
value={editData.nom_responsable_traitement}
onChange={(value) => setEditData(prev => ({ ...prev, nom_responsable_traitement: value }))}
/>
<EditableLine
label="Qualité responsable de traitement"
value={editData.qualite_responsable_traitement}
onChange={(value) => setEditData(prev => ({ ...prev, qualite_responsable_traitement: value }))}
/>
<EditableLine
label="Email responsable de traitement"
value={editData.email_responsable_traitement}
type="email"
onChange={(value) => setEditData(prev => ({ ...prev, email_responsable_traitement: value }))}
/>
<EditableLine
label="Téléphone"
value={editData.tel_contact}
onChange={(value) => setEditData(prev => ({ ...prev, tel_contact: value }))}
/>
<EditableLine
label="Prénom signataire"
value={editData.prenom_signataire}
onChange={(value) => setEditData(prev => ({ ...prev, prenom_signataire: value }))}
/>
<EditableLine
label="Nom signataire"
value={editData.nom_signataire}
onChange={(value) => setEditData(prev => ({ ...prev, nom_signataire: value }))}
/>
<EditableLine
label="Qualité signataire"
value={editData.qualite_signataire}
onChange={(value) => setEditData(prev => ({ ...prev, qualite_signataire: value }))}
/>
<EditableLine
label="Signataire agissant par délégation ?"
value={editData.delegation_signature ? "Oui" : "Non"}
type="select"
options={[
{ value: "true", label: "Oui" },
{ value: "false", label: "Non" },
]}
onChange={(value) => setEditData(prev => ({ ...prev, delegation_signature: value === "true" }))}
/>
</div>
) : (
<div className="space-y-2">
<Line label="Contact principal" value={structureInfos.contact_principal} />
<Line label="Email" value={structureInfos.email} />
<Line label="Email CC" value={structureInfos.email_cc} />
<Line label="Email signature" value={structureInfos.email_signature} />
{/* Responsable de traitement (RGPD) */}
<Line label="Nom responsable de traitement" value={structureInfos.nom_responsable_traitement} />
<Line label="Qualité responsable de traitement" value={structureInfos.qualite_responsable_traitement} />
<Line label="Email responsable de traitement" value={structureInfos.email_responsable_traitement} />
<Line label="Téléphone" value={structureInfos.telephone} />
<Line label="Signataire des contrats" value={structureInfos.signataire_contrats} />
<Line label="Qualité signataire" value={clientData.details.qualite_signataire} />
<Line label="Signataire agissant par délégation ?" value={structureInfos.signataire_delegation ? "Oui" : "Non"} />
</div>
)}
</div>
</section>
{/* Caisses & organismes */}
<section className="rounded-2xl border bg-white">
<div className="px-4 py-3 border-b">
<h2 className="font-medium">Caisses & organismes</h2>
</div>
<div className="p-4 text-sm">
{isEditing ? (
<div className="space-y-2">
<EditableLine
label="URSSAF"
value={editData.urssaf}
onChange={(value) => setEditData(prev => ({ ...prev, urssaf: value }))}
/>
<EditableLine
label="AUDIENS"
value={editData.audiens}
onChange={(value) => setEditData(prev => ({ ...prev, audiens: value }))}
/>
<EditableLine
label="Congés Spectacles"
value={editData.conges_spectacles_id}
onChange={(value) => setEditData(prev => ({ ...prev, conges_spectacles_id: value }))}
/>
<EditableLine
label="Pôle Emploi Spectacle"
value={editData.pole_emploi_id}
onChange={(value) => setEditData(prev => ({ ...prev, pole_emploi_id: value }))}
/>
<EditableLine
label="Recouvrement PE Spectacle"
value={editData.recouvrement_pe_id}
onChange={(value) => setEditData(prev => ({ ...prev, recouvrement_pe_id: value }))}
/>
<EditableLine
label="AFDAS"
value={editData.afdas_id}
onChange={(value) => setEditData(prev => ({ ...prev, afdas_id: value }))}
/>
<EditableLine
label="FNAS"
value={editData.fnas_id}
onChange={(value) => setEditData(prev => ({ ...prev, fnas_id: value }))}
/>
<EditableLine
label="FCAP"
value={editData.fcap_id}
onChange={(value) => setEditData(prev => ({ ...prev, fcap_id: value }))}
/>
</div>
) : (
<div className="space-y-2">
<Line label="URSSAF" value={structureInfos.urssaf} />
<Line label="AUDIENS" value={structureInfos.audiens} />
<Line label="Congés Spectacles" value={structureInfos.conges_spectacles} />
<Line label="Pôle Emploi Spectacle" value={structureInfos.pole_emploi_spectacle} />
<Line label="Recouvrement PE Spectacle" value={structureInfos.recouvrement_pe_spectacle} />
<Line label="AFDAS" value={structureInfos.afdas} />
<Line label="FNAS" value={structureInfos.fnas} />
<Line label="FCAP" value={structureInfos.fcap} />
</div>
)}
</div>
</section>
</div>
</div>
{/* Modale de confirmation pour la demande de mandat SEPA */}
{showSepaMandateModal && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-2xl shadow-2xl max-w-md w-full">
<div className="px-6 py-4 border-b">
<h3 className="text-lg font-semibold text-slate-900 flex items-center gap-2">
<Send className="w-5 h-5 text-blue-600" />
Demande de mandat SEPA
</h3>
</div>
<div className="p-6 space-y-4">
<p className="text-slate-700">
Êtes-vous sûr de vouloir envoyer une demande de signature de mandat SEPA au client ?
</p>
<div className="bg-slate-50 rounded-lg p-4 space-y-2">
<div className="font-medium text-slate-900">
Client : {organization.name}
</div>
{structureInfos.email && (
<div className="text-sm text-slate-600">
Email : {structureInfos.email}
</div>
)}
{structureInfos.email_cc && (
<div className="text-sm text-slate-600">
Email CC : {structureInfos.email_cc}
</div>
)}
</div>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div className="text-blue-800 text-sm font-medium mb-2">
Cette action va :
</div>
<ul className="text-blue-700 text-sm space-y-1 list-disc list-inside">
<li>Envoyer un email au client</li>
<li>Inclure un lien vers GoCardless pour la signature</li>
</ul>
</div>
</div>
<div className="px-6 py-4 border-t flex gap-3 justify-end">
<button
onClick={() => setShowSepaMandateModal(false)}
disabled={sendSepaMandateMutation.isPending}
className="px-4 py-2 text-sm border rounded-lg hover:bg-slate-50 transition-colors"
>
Annuler
</button>
<button
onClick={() => sendSepaMandateMutation.mutate()}
disabled={sendSepaMandateMutation.isPending}
className="px-4 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2 disabled:opacity-50"
>
{sendSepaMandateMutation.isPending ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
Envoi en cours...
</>
) : (
<>
<Send className="w-4 h-4" />
Envoyer la demande
</>
)}
</button>
</div>
</div>
</div>
)}
</main>
);
}