diff --git a/app/(app)/contrats/page.tsx b/app/(app)/contrats/page.tsx index a011c0f..671ba18 100644 --- a/app/(app)/contrats/page.tsx +++ b/app/(app)/contrats/page.tsx @@ -5,6 +5,7 @@ import { useQuery } from "@tanstack/react-query"; import { api } from "@/lib/fetcher"; import { ChevronLeft, ChevronRight, Loader2, Search, Plus, Pencil, Copy, Table, HelpCircle } from "lucide-react"; import { useDemoMode } from "@/hooks/useDemoMode"; +import { useStaffOrgSelection } from "@/hooks/useStaffOrgSelection"; // --- Types export type Contrat = { @@ -279,8 +280,28 @@ export default function PageContrats(){ }, }); + // Zustand store pour la sélection d'organisation (uniquement pour le staff) + const { selectedOrgId, setSelectedOrg: setGlobalSelectedOrg } = useStaffOrgSelection(); + + // Helper: vérifier si une string est un UUID valide + const isValidUUID = (str: string | null): boolean => { + if (!str) return false; + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + return uuidRegex.test(str); + }; + const [orgs, setOrgs] = useState>([]); - const [selectedOrg, setSelectedOrg] = useState(null); + // N'utiliser selectedOrgId que si c'est un UUID valide (pas un nom de structure) + const [selectedOrg, setSelectedOrg] = useState( + isValidUUID(selectedOrgId) ? selectedOrgId : null + ); + + // Synchroniser le filtre local avec le store global quand selectedOrgId change + useEffect(() => { + if (meData?.is_staff && isValidUUID(selectedOrgId)) { + setSelectedOrg(selectedOrgId); + } + }, [selectedOrgId, meData?.is_staff]); const { data, isLoading, isError, error, isFetching } = useContrats({ regime, @@ -333,7 +354,18 @@ export default function PageContrats(){ {meData?.is_staff && ( setSelectedOrgId(e.target.value)} + onChange={(e) => { + const newOrgId = e.target.value; + setSelectedOrgId(newOrgId); + + // Synchronisation bidirectionnelle : local → global + if (newOrgId) { + const selectedOrg = organizations.find(org => org.id === newOrgId); + if (selectedOrg) { + setGlobalSelectedOrg(newOrgId, selectedOrg.name); + } + } else { + setGlobalSelectedOrg(null, null); + } + }} > {organizations.map((org) => ( diff --git a/app/(app)/facturation/page.tsx b/app/(app)/facturation/page.tsx index bf48a43..0446ade 100644 --- a/app/(app)/facturation/page.tsx +++ b/app/(app)/facturation/page.tsx @@ -1,11 +1,12 @@ "use client"; -import { useMemo, useState } from "react"; +import { useMemo, useState, useEffect } from "react"; import Link from "next/link"; import { useQuery } from "@tanstack/react-query"; import { api } from "@/lib/fetcher"; import { Loader2, CheckCircle2, XCircle, FileDown, Edit, ChevronLeft, ChevronRight } from "lucide-react"; import { usePageTitle } from "@/hooks/usePageTitle"; +import { useStaffOrgSelection } from "@/hooks/useStaffOrgSelection"; // ---------------- Types ---------------- type SepaInfo = { @@ -151,7 +152,7 @@ function Pagination({ page, totalPages, total, limit, onPageChange, onLimitChang } // -------------- Data hook -------------- -function useBilling(page: number, limit: number) { +function useBilling(page: number, limit: number, selectedOrg?: { id: string; name: string; api_name?: string } | null) { // Récupération dynamique des infos client via /api/me const { data: clientInfo } = useQuery({ queryKey: ["client-info"], @@ -177,11 +178,16 @@ function useBilling(page: number, limit: number) { staleTime: 30_000, // Cache 30s }); + // Si selectedOrg est fourni (staff), créer un clientInfo override avec toutes les infos + const effectiveClientInfo = selectedOrg + ? { id: selectedOrg.id, name: selectedOrg.name, api_name: selectedOrg.api_name } as ClientInfo + : clientInfo; + return useQuery({ - queryKey: ["billing", page, limit, clientInfo?.id], // Inclure l'ID client dans la queryKey - queryFn: () => api(`/facturation?page=${page}&limit=${limit}`, {}, clientInfo), // Passer clientInfo au helper api() + queryKey: ["billing", page, limit, effectiveClientInfo?.id], // Inclure l'ID client dans la queryKey + queryFn: () => api(`/facturation?page=${page}&limit=${limit}`, {}, effectiveClientInfo), // Passer clientInfo au helper api() staleTime: 15_000, - enabled: !!clientInfo, // Ne pas exécuter si pas d'infos client + enabled: !!effectiveClientInfo, // Ne pas exécuter si pas d'infos client }); } @@ -189,9 +195,69 @@ function useBilling(page: number, limit: number) { export default function FacturationPage() { usePageTitle("Facturation"); + // Helper pour valider les UUIDs + const isValidUUID = (str: string | null): boolean => { + if (!str) return false; + return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str); + }; + + // Zustand store pour la sélection d'organisation (staff) + const { + selectedOrgId: globalSelectedOrgId, + setSelectedOrg: setGlobalSelectedOrg + } = useStaffOrgSelection(); + const [page, setPage] = useState(1); const [limit, setLimit] = useState(10); - const { data, isLoading, isError, error, isFetching } = useBilling(page, limit); + + // État local initialisé avec la valeur globale si elle est un UUID valide + const [selectedOrgId, setSelectedOrgId] = useState( + isValidUUID(globalSelectedOrgId) ? globalSelectedOrgId : "" + ); + + // Détection staff + const { data: meData } = useQuery({ + queryKey: ["me-info"], + queryFn: async () => { + try { + const res = await fetch('/api/me', { cache: 'no-store', credentials: 'include' }); + if (!res.ok) return null; + return await res.json(); + } catch { return null; } + }, + }); + + // Chargement des organisations (staff uniquement) + const { data: organizations } = useQuery({ + queryKey: ["organizations"], + queryFn: async () => { + try { + const res = await fetch('/api/organizations', { cache: 'no-store', credentials: 'include' }); + if (!res.ok) return []; + const data = await res.json(); + return (data.items || []).map((org: any) => ({ + id: org.id, + name: org.name, + api_name: org.structure_api || org.key + })); + } catch { return []; } + }, + enabled: !!meData?.is_staff, + }); + + // Synchronisation bidirectionnelle : global → local + useEffect(() => { + if (meData?.is_staff && isValidUUID(globalSelectedOrgId)) { + setSelectedOrgId(globalSelectedOrgId); + } + }, [globalSelectedOrgId, meData?.is_staff]); + + // Trouver l'organisation complète sélectionnée (pour passer api_name à useBilling) + const selectedOrgData = selectedOrgId + ? organizations?.find((org: any) => org.id === selectedOrgId) + : null; + + const { data, isLoading, isError, error, isFetching } = useBilling(page, limit, selectedOrgData || null); const items = data?.factures.items ?? []; const hasMore = data?.factures.hasMore ?? false; @@ -200,6 +266,47 @@ export default function FacturationPage() { return (
+ {/* Sélecteur d'organisation (visible uniquement par le staff) */} + {meData?.is_staff && ( +
+
+ + + {organizations && organizations.length > 0 && ( + ({organizations.length}) + )} +
+
+ )} + {/* SEPA / IBAN */}
{isLoading && ( diff --git a/app/(app)/informations/page.tsx b/app/(app)/informations/page.tsx index a715239..ed0d819 100644 --- a/app/(app)/informations/page.tsx +++ b/app/(app)/informations/page.tsx @@ -5,6 +5,7 @@ import Link from "next/link"; import { useQuery } from "@tanstack/react-query"; import { api } from "@/lib/fetcher"; import { usePageTitle } from "@/hooks/usePageTitle"; +import { useStaffOrgSelection } from "@/hooks/useStaffOrgSelection"; type StructureInfos = { raison_sociale?: string; @@ -66,8 +67,25 @@ function fmtDateFR(d?: string | null) { export default function InformationsPage() { usePageTitle("Informations de la structure"); + // Helper pour valider les UUIDs + const isValidUUID = (str: string | null): boolean => { + if (!str) return false; + return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str); + }; + + // Zustand store pour la sélection d'organisation (staff) + const { + selectedOrgId: globalSelectedOrgId, + setSelectedOrg: setGlobalSelectedOrg + } = useStaffOrgSelection(); + const [page, setPage] = useState(1); const limit = 10; + + // État local initialisé avec la valeur globale si elle est un UUID valide + const [selectedOrgId, setSelectedOrgId] = useState( + isValidUUID(globalSelectedOrgId) ? globalSelectedOrgId : "" + ); // Récupération dynamique des infos client via /api/me const { data: clientInfo } = useQuery({ @@ -94,19 +112,66 @@ export default function InformationsPage() { staleTime: 30_000, // Cache 30s }); + // Détection staff + const { data: meData } = useQuery({ + queryKey: ["me-info"], + queryFn: async () => { + try { + const res = await fetch('/api/me', { cache: 'no-store', credentials: 'include' }); + if (!res.ok) return null; + return await res.json(); + } catch { return null; } + }, + }); + + // Chargement des organisations (staff uniquement) + const { data: organizations } = useQuery({ + queryKey: ["organizations"], + queryFn: async () => { + try { + const res = await fetch('/api/organizations', { cache: 'no-store', credentials: 'include' }); + if (!res.ok) return []; + const data = await res.json(); + return (data.items || []).map((org: any) => ({ + id: org.id, + name: org.name, + api_name: org.structure_api || org.key + })); + } catch { return []; } + }, + enabled: !!meData?.is_staff, + }); + + // Synchronisation bidirectionnelle : global → local + useEffect(() => { + if (meData?.is_staff && isValidUUID(globalSelectedOrgId)) { + setSelectedOrgId(globalSelectedOrgId); + } + }, [globalSelectedOrgId, meData?.is_staff]); + + // Trouver l'organisation complète sélectionnée (pour passer api_name aux queries) + const selectedOrgData = selectedOrgId + ? organizations?.find((org: any) => org.id === selectedOrgId) + : null; + + // effectiveClientInfo : override avec selectedOrgData si staff sélectionne une org + const effectiveClientInfo = selectedOrgData + ? { id: selectedOrgData.id, name: selectedOrgData.name, api_name: selectedOrgData.api_name } as ClientInfo + : clientInfo; + // 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 + queryKey: ["structure-infos", effectiveClientInfo?.id], // Inclure l'ID client dans la queryKey + queryFn: () => api<{ infos: StructureInfos }>("/informations", {}, effectiveClientInfo), // Passer effectiveClientInfo au helper api() + enabled: !!effectiveClientInfo, // 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 + queryKey: ["spectacles", page, limit, effectiveClientInfo?.id], // Inclure l'ID client dans la queryKey + queryFn: () => api<{ items: Spectacle[]; total: number; hasMore: boolean }>(`/informations/productions?page=${page}&limit=${limit}`, {}, effectiveClientInfo), // Passer effectiveClientInfo au helper api() + enabled: !!effectiveClientInfo, // Ne pas exécuter si pas d'infos client }); const total = prods?.total ?? 0; @@ -118,9 +183,50 @@ export default function InformationsPage() {

Vos informations

+ {/* Sélecteur d'organisation (visible uniquement par le staff) */} + {meData?.is_staff && ( +
+
+ + + {organizations && organizations.length > 0 && ( + ({organizations.length}) + )} +
+
+ )} + {/* Bandeau compte spécifique (optionnel) */} {/*
- Compte spécifique — vous pouvez mettre ici un message d’info si besoin. + Compte spécifique — vous pouvez mettre ici un message d'info si besoin.
*/}
diff --git a/app/(app)/salaries/page.tsx b/app/(app)/salaries/page.tsx index a0ad8f1..7b0a904 100644 --- a/app/(app)/salaries/page.tsx +++ b/app/(app)/salaries/page.tsx @@ -9,6 +9,7 @@ import { api } from "@/lib/fetcher"; import { Loader2, ChevronLeft, ChevronRight, Plus } from "lucide-react"; import { usePageTitle } from "@/hooks/usePageTitle"; import { useDemoMode } from "@/hooks/useDemoMode"; +import { useStaffOrgSelection } from "@/hooks/useStaffOrgSelection"; /* ===== Types ===== */ type SalarieRow = { @@ -304,6 +305,18 @@ export default function SalariesPage() { // 🎭 Détection du mode démo const { isDemoMode } = useDemoMode(); + // Helper pour valider les UUIDs + const isValidUUID = (str: string | null): boolean => { + if (!str) return false; + return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str); + }; + + // Zustand store pour la sélection d'organisation (staff) + const { + selectedOrgId: globalSelectedOrgId, + setSelectedOrg: setGlobalSelectedOrg + } = useStaffOrgSelection(); + const router = useRouter(); const searchParams = useSearchParams(); @@ -336,7 +349,10 @@ export default function SalariesPage() { }); const [orgs, setOrgs] = useState>([]); - const [selectedOrg, setSelectedOrg] = useState(null); + // État local initialisé avec la valeur globale si elle est un UUID valide + const [selectedOrg, setSelectedOrg] = useState( + isValidUUID(globalSelectedOrgId) ? globalSelectedOrgId : null + ); const { data, isLoading, isFetching } = useSalaries(page, limit, query, selectedOrg, isDemoMode); const rows: SalarieRow[] = data?.items ?? []; @@ -359,6 +375,13 @@ export default function SalariesPage() { return () => { mounted = false; }; }, []); + // Synchronisation bidirectionnelle : global → local + useEffect(() => { + if (meData?.is_staff && isValidUUID(globalSelectedOrgId)) { + setSelectedOrg(globalSelectedOrgId); + } + }, [globalSelectedOrgId, meData?.is_staff]); + // Modal "Nouveau contrat" déclenché depuis la liste const [newContratOpen, setNewContratOpen] = useState(false); const [selectedMatricule, setSelectedMatricule] = useState(null); @@ -398,7 +421,21 @@ export default function SalariesPage() { {meData?.is_staff && ( setSelectedOrgId(e.target.value)} + onChange={(e) => { + const newOrgId = e.target.value; + setSelectedOrgId(newOrgId); + + // Synchronisation bidirectionnelle : local → global + if (newOrgId) { + const selectedOrg = organizations?.find((org: any) => org.id === newOrgId); + if (selectedOrg) { + setGlobalSelectedOrg(newOrgId, selectedOrg.name); + } + } else { + setGlobalSelectedOrg(null, null); + } + }} disabled={!organizations || organizations.length === 0} >
{/* Filtres rapides toujours visibles */} - { + const value = e.target.value || null; + setStructureFilter(value); + // Note: structureFilter est un nom de structure (string), pas un UUID + // On ne synchronise pas avec le store global ici car il attend des UUIDs + }} + className="rounded border px-2 py-1 text-sm" + > {structures.map((s) => ())} diff --git a/components/staff/SalaryTransfersGrid.tsx b/components/staff/SalaryTransfersGrid.tsx index 326ffd5..e9c2681 100644 --- a/components/staff/SalaryTransfersGrid.tsx +++ b/components/staff/SalaryTransfersGrid.tsx @@ -8,6 +8,7 @@ import { toast } from "sonner"; import NotifyClientModal from "./salary-transfers/NotifyClientModal"; import NotifyPaymentSentModal from "./salary-transfers/NotifyPaymentSentModal"; import PayslipsSelectionModal from "./salary-transfers/PayslipsSelectionModal"; +import { useStaffOrgSelection } from "@/hooks/useStaffOrgSelection"; // Utility function to format dates as DD/MM/YYYY function formatDate(dateString: string | null | undefined): string { @@ -79,14 +80,22 @@ export default function SalaryTransfersGrid({ const [showRaw, setShowRaw] = useState(false); const [loading, setLoading] = useState(false); + // Zustand store pour la sélection d'organisation + const { selectedOrgId, setSelectedOrg } = useStaffOrgSelection(); + // Debug log pour vérifier que les organisations sont bien passées useEffect(() => { console.log("[SalaryTransfersGrid] Organizations received:", organizations?.length, organizations); }, [organizations]); + + // Synchroniser le filtre local avec le store global quand selectedOrgId change + useEffect(() => { + setOrgFilter(selectedOrgId); + }, [selectedOrgId]); // filters / sorting / pagination const [q, setQ] = useState(""); - const [orgFilter, setOrgFilter] = useState(null); + const [orgFilter, setOrgFilter] = useState(selectedOrgId); // Initialiser avec la sélection globale const [modeFilter, setModeFilter] = useState(null); const [notificationSentFilter, setNotificationSentFilter] = useState(null); const [notificationOkFilter, setNotificationOkFilter] = useState(null); @@ -115,6 +124,7 @@ export default function SalaryTransfersGrid({ notes: "", selection_mode: "period" as "period" | "manual", payslip_ids: [] as string[], + custom_amounts: {} as Record, }); const [creating, setCreating] = useState(false); @@ -357,6 +367,7 @@ export default function SalaryTransfersGrid({ notes: "", selection_mode: "period", payslip_ids: [], + custom_amounts: {}, }); setShowCreateModal(false); @@ -1011,7 +1022,17 @@ export default function SalaryTransfersGrid({ updateCustomAmount(payslip.id, e.target.value)} + onClick={(e) => e.stopPropagation()} + className="w-28 px-2 py-1 text-sm text-right border rounded focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" + /> + ) : ( + + )} + {payslip.processed ? ( diff --git a/hooks/useStaffOrgSelection.ts b/hooks/useStaffOrgSelection.ts new file mode 100644 index 0000000..8411eb7 --- /dev/null +++ b/hooks/useStaffOrgSelection.ts @@ -0,0 +1,101 @@ +'use client' + +import { create } from 'zustand' +import { persist } from 'zustand/middleware' + +/** + * Interface pour l'état de sélection d'organisation du staff + */ +interface StaffOrgSelectionState { + /** ID de l'organisation sélectionnée (null = toutes les organisations) */ + selectedOrgId: string | null + + /** Nom de l'organisation sélectionnée (pour affichage dans le badge) */ + selectedOrgName: string | null + + /** Définir l'organisation sélectionnée */ + setSelectedOrg: (id: string | null, name: string | null) => void + + /** Réinitialiser la sélection (retour à "Toutes les organisations") */ + clearSelection: () => void +} + +/** + * Hook Zustand pour gérer la sélection d'organisation du staff + * + * Fonctionnalités : + * - État global accessible partout dans l'application + * - Persistence automatique dans localStorage + * - Synchronisation entre tous les composants + * - Validation UUID pour éviter les noms de structure + * + * Usage : + * ```tsx + * const { selectedOrgId, selectedOrgName, setSelectedOrg, clearSelection } = useStaffOrgSelection() + * + * // Sélectionner une orga + * setSelectedOrg("uuid-123", "Théâtre du Soleil") + * + * // Réinitialiser (toutes les orgas) + * clearSelection() + * + * // Filtrer des données + * const filtered = selectedOrgId + * ? data.filter(item => item.org_id === selectedOrgId) + * : data + * ``` + */ + +// Helper: vérifier si une string est un UUID valide +const isValidUUID = (str: string | null | undefined): boolean => { + if (!str) return false; + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + return uuidRegex.test(str); +}; + +export const useStaffOrgSelection = create()( + persist( + (set, get) => ({ + selectedOrgId: null, + selectedOrgName: null, + + setSelectedOrg: (id, name) => { + // Valider que l'ID est bien un UUID (pas un nom de structure) + if (id && !isValidUUID(id)) { + console.warn('[useStaffOrgSelection] Invalid UUID provided:', id, '- Clearing selection'); + set({ selectedOrgId: null, selectedOrgName: null }); + return; + } + set({ selectedOrgId: id, selectedOrgName: name }); + }, + + clearSelection: () => set({ + selectedOrgId: null, + selectedOrgName: null + }), + }), + { + name: 'staff-org-selection', // Clé dans localStorage + // Ne persister que les données essentielles + partialize: (state) => ({ + selectedOrgId: state.selectedOrgId, + selectedOrgName: state.selectedOrgName, + }), + // Migration : nettoyer les valeurs invalides + migrate: (persistedState: any, version: number) => { + if (persistedState && typeof persistedState === 'object') { + const { selectedOrgId } = persistedState; + // Si l'ID n'est pas un UUID valide, le nettoyer + if (selectedOrgId && !isValidUUID(selectedOrgId)) { + console.warn('[useStaffOrgSelection] Cleaning invalid UUID from localStorage:', selectedOrgId); + return { + selectedOrgId: null, + selectedOrgName: null, + }; + } + } + return persistedState; + }, + } + ) +) diff --git a/migrations/add_custom_amount_to_salary_transfer_payslips.sql b/migrations/add_custom_amount_to_salary_transfer_payslips.sql new file mode 100644 index 0000000..2bffff8 --- /dev/null +++ b/migrations/add_custom_amount_to_salary_transfer_payslips.sql @@ -0,0 +1,27 @@ +-- Migration: Ajout du champ custom_amount à salary_transfer_payslips +-- Date: 2025-12-01 +-- Description: Permet de spécifier un montant personnalisé pour chaque paie dans un virement + +-- 1. Ajouter le champ custom_amount +ALTER TABLE salary_transfer_payslips +ADD COLUMN IF NOT EXISTS custom_amount DECIMAL(10, 2); + +-- 2. Commentaire pour documenter le champ +COMMENT ON COLUMN salary_transfer_payslips.custom_amount IS +'Montant personnalisé pour cette paie dans le virement. Si NULL, le montant de la paie (net_after_withholding ou net_amount) sera utilisé.'; + +-- 3. Vérification +DO $$ +BEGIN + -- Vérifier que la colonne custom_amount existe + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'salary_transfer_payslips' + AND column_name = 'custom_amount' + ) THEN + RAISE EXCEPTION 'Colonne custom_amount non créée'; + END IF; + + RAISE NOTICE 'Migration réussie: custom_amount ajouté à salary_transfer_payslips'; +END; +$$; diff --git a/migrations/fix_structure_field_from_production.sql b/migrations/fix_structure_field_from_production.sql new file mode 100644 index 0000000..92733fd --- /dev/null +++ b/migrations/fix_structure_field_from_production.sql @@ -0,0 +1,33 @@ +-- Migration pour corriger les contrats où structure = production_name +-- Ces contrats ont été créés/modifiés avec un bug qui mettait la production dans structure + +-- Mettre à jour le contrat "La Nuit avant Noël" spécifique +UPDATE cddu_contracts +SET structure = organizations.name +FROM organizations +WHERE cddu_contracts.org_id = organizations.id + AND cddu_contracts.id = '9240863d-be8a-449a-918f-68a77eff3bed' + AND cddu_contracts.structure = 'La Nuit avant Noël'; + +-- Corriger tous les autres contrats potentiellement affectés +-- (où structure = production_name et structure != organization.name) +UPDATE cddu_contracts +SET structure = organizations.name +FROM organizations +WHERE cddu_contracts.org_id = organizations.id + AND cddu_contracts.structure = cddu_contracts.production_name + AND cddu_contracts.structure != organizations.name; + +-- Vérification: afficher les contrats mis à jour +SELECT + id, + contract_number, + structure, + production_name, + org_id, + employee_name, + start_date +FROM cddu_contracts +WHERE structure != (SELECT name FROM organizations WHERE id = cddu_contracts.org_id) +ORDER BY created_at DESC +LIMIT 20; diff --git a/package-lock.json b/package-lock.json index 388e69b..8514f6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,7 +55,8 @@ "sharp": "^0.34.4", "sonner": "^2.0.7", "tailwind-merge": "^2.6.0", - "use-debounce": "^10.0.6" + "use-debounce": "^10.0.6", + "zustand": "^5.0.8" }, "devDependencies": { "@types/canvas-confetti": "^1.9.0", @@ -12483,6 +12484,35 @@ "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", "license": "MIT" + }, + "node_modules/zustand": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", + "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index b7dea89..575e170 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,8 @@ "sharp": "^0.34.4", "sonner": "^2.0.7", "tailwind-merge": "^2.6.0", - "use-debounce": "^10.0.6" + "use-debounce": "^10.0.6", + "zustand": "^5.0.8" }, "devDependencies": { "@types/canvas-confetti": "^1.9.0",