- Création de la page /staff/contrats/saisie-temps-reel avec tableau éditable - Ajout des colonnes jours_representations et jours_repetitions dans l'API - Construction intelligente du TT Contractuel (concaténation des sources) - Ajout de la colonne temps_reel_traite pour marquer les contrats traités - Interface avec filtres (année, mois, organisation, recherche) - Tri par date/salarié - Édition inline avec auto-save via API - Checkbox pour marquer comme traité (masque automatiquement la ligne) - Toggle pour afficher/masquer les contrats traités - Migration SQL pour la colonne temps_reel_traite - Ajout du menu 'Temps de travail réel' dans la sidebar - Logs de débogage pour le suivi des sauvegardes
166 lines
5.2 KiB
TypeScript
166 lines
5.2 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { createSbServer } from "@/lib/supabaseServer";
|
|
|
|
export async function GET(req: Request) {
|
|
const supabase = createSbServer();
|
|
|
|
// Vérifier l'authentification
|
|
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
if (authError || !user) {
|
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
|
}
|
|
|
|
// Vérifier que l'utilisateur est staff
|
|
const { data: staffUser } = await supabase
|
|
.from("staff_users")
|
|
.select("is_staff")
|
|
.eq("user_id", user.id)
|
|
.maybeSingle();
|
|
|
|
if (!staffUser?.is_staff) {
|
|
return NextResponse.json({ error: "Accès réservé au staff" }, { status: 403 });
|
|
}
|
|
|
|
// Récupérer les paramètres de filtrage depuis l'URL
|
|
const { searchParams } = new URL(req.url);
|
|
const yearFilter = searchParams.get("year");
|
|
const monthFilter = searchParams.get("month");
|
|
const orgFilter = searchParams.get("org");
|
|
|
|
try {
|
|
let query = supabase
|
|
.from("cddu_contracts")
|
|
.select(`
|
|
id,
|
|
production_name,
|
|
employee_name,
|
|
profession,
|
|
start_date,
|
|
end_date,
|
|
org_id,
|
|
jours_travail,
|
|
jours_travail_non_artiste,
|
|
jours_representations,
|
|
jours_repetitions,
|
|
cachets_representations,
|
|
services_repetitions,
|
|
nombre_d_heures,
|
|
precisions_salaire,
|
|
jours_travail_reel,
|
|
nb_representations_reel,
|
|
nb_services_repetitions_reel,
|
|
nb_heures_repetitions_reel,
|
|
nb_heures_annexes_reel,
|
|
nb_cachets_aem_reel,
|
|
nb_heures_aem_reel,
|
|
temps_reel_traite,
|
|
organizations(name)
|
|
`);
|
|
|
|
// Filtre par année
|
|
if (yearFilter && yearFilter !== "all") {
|
|
const yearStart = `${yearFilter}-01-01`;
|
|
const yearEnd = `${yearFilter}-12-31`;
|
|
query = query.gte("start_date", yearStart).lte("start_date", yearEnd);
|
|
}
|
|
|
|
// Filtre par mois
|
|
if (monthFilter && monthFilter !== "all" && yearFilter && yearFilter !== "all") {
|
|
const monthPadded = monthFilter.padStart(2, "0");
|
|
const monthStart = `${yearFilter}-${monthPadded}-01`;
|
|
const nextMonth = parseInt(monthFilter) === 12 ? 1 : parseInt(monthFilter) + 1;
|
|
const nextYear = parseInt(monthFilter) === 12 ? parseInt(yearFilter) + 1 : parseInt(yearFilter);
|
|
const monthEnd = `${nextYear}-${String(nextMonth).padStart(2, "0")}-01`;
|
|
query = query.gte("start_date", monthStart).lt("start_date", monthEnd);
|
|
}
|
|
|
|
// Filtre par organisation
|
|
if (orgFilter && orgFilter !== "all") {
|
|
query = query.eq("org_id", orgFilter);
|
|
}
|
|
|
|
const { data, error } = await query.order("start_date", { ascending: false });
|
|
|
|
if (error) {
|
|
console.error("Erreur récupération contrats:", error);
|
|
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
}
|
|
|
|
// Ajouter le nom de l'organisation
|
|
const contracts = (data || []).map((contract: any) => ({
|
|
...contract,
|
|
organization_name: contract.organizations?.name || "—",
|
|
}));
|
|
|
|
return NextResponse.json(contracts);
|
|
} catch (error: any) {
|
|
console.error("Erreur serveur:", error);
|
|
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
export async function PATCH(req: Request) {
|
|
const supabase = createSbServer();
|
|
|
|
// Vérifier l'authentification
|
|
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
if (authError || !user) {
|
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
|
}
|
|
|
|
// Vérifier que l'utilisateur est staff
|
|
const { data: staffUser } = await supabase
|
|
.from("staff_users")
|
|
.select("is_staff")
|
|
.eq("user_id", user.id)
|
|
.maybeSingle();
|
|
|
|
if (!staffUser?.is_staff) {
|
|
return NextResponse.json({ error: "Accès réservé au staff" }, { status: 403 });
|
|
}
|
|
|
|
try {
|
|
const body = await req.json();
|
|
const { contractId, field, value } = body;
|
|
|
|
console.log("[PATCH temps-reel] Mise à jour:", { contractId, field, value });
|
|
|
|
if (!contractId || !field) {
|
|
return NextResponse.json({ error: "contractId et field requis" }, { status: 400 });
|
|
}
|
|
|
|
// Liste des champs autorisés pour la mise à jour
|
|
const allowedFields = [
|
|
"jours_travail_reel",
|
|
"nb_representations_reel",
|
|
"nb_services_repetitions_reel",
|
|
"nb_heures_repetitions_reel",
|
|
"nb_heures_annexes_reel",
|
|
"nb_cachets_aem_reel",
|
|
"nb_heures_aem_reel",
|
|
"temps_reel_traite",
|
|
];
|
|
|
|
if (!allowedFields.includes(field)) {
|
|
return NextResponse.json({ error: "Champ non autorisé" }, { status: 400 });
|
|
}
|
|
|
|
const { data, error } = await supabase
|
|
.from("cddu_contracts")
|
|
.update({ [field]: value })
|
|
.eq("id", contractId)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) {
|
|
console.error("[PATCH temps-reel] Erreur:", error);
|
|
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
}
|
|
|
|
console.log("[PATCH temps-reel] Succès:", data);
|
|
return NextResponse.json({ success: true, data });
|
|
} catch (error: any) {
|
|
console.error("[PATCH temps-reel] Erreur serveur:", error);
|
|
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
}
|
|
}
|