espace-paie-odentas/app/(app)/informations/page.tsx
2025-10-12 17:05:46 +02:00

256 lines
9.9 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, useMemo, useState } from "react";
import Link from "next/link";
import { useQuery } from "@tanstack/react-query";
import { api } from "@/lib/fetcher";
import { usePageTitle } from "@/hooks/usePageTitle";
type StructureInfos = {
raison_sociale?: string;
siret?: string;
forme_juridique?: string;
declaration?: string;
convention_collective?: string;
code_ape?: string;
rna?: string;
adresse_siege?: string;
presidente?: string;
tresoriere?: string;
contact_principal?: string;
email?: string;
telephone?: string;
signataire_contrats?: string;
signataire_delegation?: string; // "Oui"/"Non"
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 Spectacle = {
nom: string;
numero_objet?: string; // "N° objet"
declaration?: string | null; // ISO date/string from Airtable
};
type ClientInfo = {
id: string;
name: string;
api_name?: string;
} | null;
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 fmtDateFR(d?: string | null) {
if (!d) return "—";
const t = new Date(d);
if (isNaN(t.getTime())) return d; // fallback to raw if unparsable
return t.toLocaleDateString("fr-FR");
}
export default function InformationsPage() {
usePageTitle("Informations de la structure");
const [page, setPage] = useState(1);
const limit = 10;
// Récupération dynamique des infos client via /api/me
const { data: clientInfo } = useQuery({
queryKey: ["client-info"],
queryFn: async () => {
try {
const res = await fetch("/api/me", {
cache: "no-store",
headers: { Accept: "application/json" },
credentials: "include"
});
if (!res.ok) return null;
const me = await res.json();
return {
id: me.active_org_id || null,
name: me.active_org_name || "Organisation",
api_name: me.active_org_api_name
} as ClientInfo;
} catch {
return null;
}
},
staleTime: 30_000, // Cache 30s
});
// 1) Infos structure
const { data: infosResp, isLoading: loadingInfos, isError: errInfos } = useQuery({
queryKey: ["structure-infos", clientInfo?.id], // Inclure l'ID client dans la queryKey
queryFn: () => api<{ infos: StructureInfos }>("/informations", {}, clientInfo), // Passer clientInfo au helper api()
enabled: !!clientInfo, // Ne pas exécuter si pas d'infos client
});
const structure = infosResp?.infos;
// 2) Productions (Supabase via API interne)
const { data: prods, isLoading: loadingProds, isError: errProds } = useQuery({
queryKey: ["spectacles", page, limit, clientInfo?.id], // Inclure l'ID client dans la queryKey
queryFn: () => api<{ items: Spectacle[]; total: number; hasMore: boolean }>(`/informations/productions?page=${page}&limit=${limit}`, {}, clientInfo), // Passer clientInfo au helper api()
enabled: !!clientInfo, // Ne pas exécuter si pas d'infos client
});
const total = prods?.total ?? 0;
const hasMore = prods?.hasMore ?? false;
return (
<main className="p-4 md:p-6 space-y-4">
<div className="flex items-center justify-between">
<h1 className="text-lg font-semibold">Vos informations</h1>
</div>
{/* Bandeau compte spécifique (optionnel) */}
{/* <div className="rounded-2xl border bg-amber-50 text-amber-900 border-amber-200 p-4 text-sm">
Compte spécifique — vous pouvez mettre ici un message dinfo si besoin.
</div> */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 items-start">
<div className="space-y-4">
{/* Colonne gauche : Votre structure */}
<section className="rounded-2xl border bg-white">
<div className="px-4 py-3 border-b">
<h2 className="font-medium">Votre structure</h2>
</div>
<div className="p-4 text-sm">
{loadingInfos && <div className="text-slate-500">Chargement</div>}
{errInfos && <div className="text-red-600">Erreur de chargement.</div>}
{!!structure && (
<div className="space-y-2">
<Line label="Raison sociale" value={structure.raison_sociale} />
<Line label="SIRET" value={structure.siret} />
<Line label="Forme juridique" value={structure.forme_juridique} />
<Line label="Déclaration" value={structure.declaration} />
<Line label="Convention collective" value={structure.convention_collective} />
<Line label="Code APE" value={structure.code_ape} />
<Line label="N° RNA" value={structure.rna} />
<Line label="Adresse siège" value={structure.adresse_siege} />
<Line label="Président" value={structure.presidente} />
<Line label="Trésorier" value={structure.tresoriere} />
</div>
)}
</div>
</section>
{/* Vos productions */}
<section className="rounded-2xl border bg-white">
<div className="px-4 py-3 border-b">
<h2 className="font-medium">Vos productions</h2>
</div>
<div className="p-2">
{loadingProds && <div className="p-3 text-slate-500">Chargement</div>}
{errProds && <div className="p-3 text-red-600">Erreur de chargement.</div>}
{!!prods?.items?.length ? (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="text-left border-b">
<th className="px-3 py-2">Production</th>
<th className="px-3 py-2">Déclaration</th>
<th className="px-3 py-2">N° dobjet</th>
</tr>
</thead>
<tbody>
{prods.items.map((s, i) => (
<tr key={`${s.nom}-${s.numero_objet ?? i}`} className="border-b last:border-b-0">
<td className="px-3 py-2">{s.nom}</td>
<td className="px-3 py-2">{fmtDateFR(s.declaration)}</td>
<td className="px-3 py-2">{s.numero_objet ?? "—"}</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<div className="p-3 text-slate-500">Aucune production.</div>
)}
</div>
<div className="flex items-center justify-end gap-2 p-3 border-t">
<button
className="px-3 py-1.5 rounded-lg border bg-white hover:bg-slate-50"
onClick={() => setPage((p) => Math.max(1, p - 1))}
disabled={page === 1}
>
Précédent
</button>
<div className="text-sm text-slate-500">Page {page}</div>
<button
className="px-3 py-1.5 rounded-lg border bg-white hover:bg-slate-50 disabled:opacity-50"
onClick={() => setPage((p) => (hasMore ? p + 1 : p))}
disabled={!hasMore}
>
Suivant
</button>
</div>
</section>
</div>
<div className="space-y-4">
{/* Colonne droite : Contact + Caisses */}
<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">
{!!structure ? (
<div className="space-y-2">
<Line label="Contact principal" value={structure.contact_principal} />
<Line label="Adresse email" value={structure.email} />
<Line label="Tél contact" value={structure.telephone} />
<Line label="Signataire des contrats" value={structure.signataire_contrats} />
<Line label="Signataire agissant par délégation ?" value={structure.signataire_delegation} />
</div>
) : (
<div className="text-slate-500"></div>
)}
</div>
</section>
<section className="rounded-2xl border bg-white">
<div className="px-4 py-3 border-b flex items-center justify-between">
<h2 className="font-medium">Caisses & organismes</h2>
</div>
<div className="p-4 text-sm">
{!!structure ? (
<div className="space-y-2">
<Line label="Licence spectacles" value={structure.licence_spectacles} />
<Line label="URSSAF" value={structure.urssaf} />
<Line label="AUDIENS" value={structure.audiens} />
<Line label="Congés Spectacles" value={structure.conges_spectacles} />
<Line label="Pôle Emploi Spectacle" value={structure.pole_emploi_spectacle} />
<Line label="Recouvrement PE Spectacle" value={structure.recouvrement_pe_spectacle} />
<Line label="AFDAS" value={structure.afdas} />
<Line label="FNAS" value={structure.fnas} />
<Line label="FCAP" value={structure.fcap} />
</div>
) : (
<div className="text-slate-500"></div>
)}
</div>
</section>
</div>
</div>
</main>
);
}