espace-paie-odentas/app/(app)/staff/payslips/page.tsx
odentas 897af4b23a feat: Ajout fonctionnalités virements, facturation, signatures et emails
- Ajout sous-header total net à payer sur page virements-salaires
- Migration transfer_done_at pour tracking précis des virements
- Nouvelle page saisie tableau pour création factures en masse
- APIs bulk pour mise à jour dates signature et jours technicien
- API demande mandat SEPA avec email template
- Webhook DocuSeal pour signature contrats (mode TEST)
- Composants modaux détails et vérification PDF fiches de paie
- Upload/suppression/remplacement PDFs dans PayslipsGrid
- Amélioration affichage colonnes et filtres grilles contrats/paies
- Template email mandat SEPA avec sous-texte CTA
- APIs bulk facturation (création, update statut/date paiement)
- API clients sans facture pour période donnée
- Corrections calculs dates et montants avec auto-remplissage
2025-11-02 23:26:19 +01:00

99 lines
4 KiB
TypeScript

// app/(app)/staff/payslips/page.tsx
import { cookies } from "next/headers";
import NextDynamic from "next/dynamic";
import { createSbServer } from "@/lib/supabaseServer";
export const dynamic = "force-dynamic";
const PayslipsGrid = NextDynamic<any>(() => import("../../../../components/staff/PayslipsGrid"), { ssr: false });
export default async function StaffPayslipsPage() {
const sb = createSbServer();
const { data: { user } } = await sb.auth.getUser();
if (!user) {
return (
<main className="p-6">
<h1 className="text-lg font-semibold">Accès refusé</h1>
<p className="text-sm text-slate-600">Vous devez être connecté.</p>
</main>
);
}
const { data: me } = await sb.from("staff_users").select("is_staff").eq("user_id", user.id).maybeSingle();
const isStaff = !!me?.is_staff;
if (!isStaff) {
return (
<main className="p-6">
<h1 className="text-lg font-semibold">Accès refusé</h1>
<p className="text-sm text-slate-600">Cette page est réservée au Staff.</p>
</main>
);
}
// Initial fetch: server-side list of latest payslips (limited)
// Utiliser une jointure pour récupérer les informations du contrat et du salarié
const { data: payslips, error } = await sb
.from("payslips")
.select(
`id, contract_id, period_start, period_end, period_month, pay_number, pay_date,
gross_amount, net_amount, net_after_withholding, employer_cost,
processed, aem_status, transfer_done, organization_id, created_at,
cddu_contracts!contract_id(
id, contract_number, employee_name, employee_id, structure, type_de_contrat, org_id,
salaries!employee_id(salarie, nom, prenom),
organizations!org_id(organization_details(code_employeur))
)`
)
.order("period_start", { ascending: false })
.limit(200);
// Server-side debug logging
try {
console.log("[staff/payslips] supabase fetch payslips result count:", Array.isArray(payslips) ? payslips.length : typeof payslips);
if (error) console.log("[staff/payslips] supabase error:", error);
if (payslips && payslips.length > 0) console.log("[staff/payslips] sample:", payslips.slice(0, 3));
} catch (e) {
// ignore logging errors
}
if (error) {
return (
<main className="p-6">
<h1 className="text-lg font-semibold">Erreur</h1>
<p className="text-sm text-rose-600">{error.message}</p>
<div className="mt-4 p-3 bg-slate-50 rounded text-sm text-slate-700">
<div><strong>Debug</strong></div>
<div>Supabase returned an error when reading <code>payslips</code>. See server logs for details.</div>
</div>
</main>
);
}
// active org (optional) for staff we allow global view
const c = cookies();
const activeOrgId = c.get("active_org_id")?.value || null;
return (
<main className="p-6">
<div className="flex items-center justify-between mb-4">
<h1 className="text-lg font-semibold">Fiches de paie (Staff)</h1>
</div>
<div className="rounded-2xl border bg-white p-4">
{(!payslips || payslips.length === 0) && (
<div className="mb-4 p-3 rounded bg-yellow-50 text-sm text-slate-800 border">
<div><strong>Debug:</strong> Aucune fiche de paie trouvée côté serveur (initialData vide).</div>
<div className="mt-2 text-xs text-slate-600">Si tu utilises Realtime / RLS, vérifie que la table <code>payslips</code> est publiée pour Realtime et que les policies autorisent la lecture pour ton utilisateur staff.</div>
<details className="mt-2 text-xs">
<summary className="cursor-pointer text-xs underline">Voir le payload serveur (preview)</summary>
<pre className="mt-2 max-h-48 overflow-auto text-xs bg-slate-50 p-2 rounded border">{JSON.stringify(payslips ?? [], null, 2)}</pre>
</details>
</div>
)}
{/* Client-side interactive grid */}
<PayslipsGrid initialData={payslips ?? []} activeOrgId={activeOrgId} />
</div>
</main>
);
}