// Contexte inwink : expose l'objet courant (event / community / tenant) déduit // de l'URL ou choisi par l'utilisateur, et les loaders de listes pour les pickers. import { createContext, useMemo, useState, type PropsWithChildren } from 'react'; import { useInwinkCustomerApi } from '../hooks/useInwinkCustomerApi'; import { deriveScope, loadPersistedContext, persistContext, readInwinkContextFromUrl, type InwinkContextParams, type InwinkScope, } from '../lib/inwinkContextParams'; /** Option affichable dans un picker (événement, communauté ou tenant). */ export interface InwinkContextOption { id: string; label: string; } export interface IInwinkContextQuery { search?: string; id?: string; } interface InwinkContextValue { scope: InwinkScope; eventId?: string; communityId?: string; tenantId?: string; /** Sélectionne un événement (réinitialise la communauté). */ setEvent: (id: string) => void; /** Sélectionne une communauté (réinitialise l'événement). */ setCommunity: (id: string) => void; /** Sélectionne un tenant / audience (réinitialise event + community). */ setTenant: (id: string) => void; /** * Liste les événements de l'utilisateur (API Customer `me/events/query`), * triés par date décroissante. `query` filtre côté serveur par nom ou id. */ loadEvents: (query?: IInwinkContextQuery) => Promise; /** * Liste les communautés de l'utilisateur (API Customer `me/communities/query`). * `query` filtre côté serveur par nom ou id. */ loadCommunities: (query?: IInwinkContextQuery) => Promise; /** * Liste les tenants / audiences de l'utilisateur (API Customer `me/tenants/query`). * `query` filtre côté serveur par nom ou id. */ loadTenants: (query?: IInwinkContextQuery) => Promise; } export const InwinkContext = createContext(null); // Pagination des requêtes `*/query`. Les listes sont paginées côté serveur : // la recherche (`query`) sert à retrouver un objet au-delà de la 1re page. const PAGE = { index: 0, size: 200 }; // Construit le corps d'une requête de liste (`me//query`) : // - `query` → filtre serveur par id (égalité) OU libellé (`$contains`) ; // - `labelField` → champ texte de l'entité (`title` pour les events, `name` sinon) ; // - `orders` → tri serveur (ex. date décroissante pour les events). // Ajuste `selects` / `filters` selon les entités après discovery MCP (cf. AGENTS.md). function buildQueryBody(opts: { query?: IInwinkContextQuery; labelField: string; orders?: unknown[]; }): Record { const body: Record = { page: PAGE }; if (opts.orders) body.orders = opts.orders; if (opts.query?.id) { body.filters ??= {}; body.filters.id = opts.query.id; } else if (opts.query?.search) { body.filters ??= {}; const q = opts.query?.search?.trim(); if (q) { body.filters.$and ??= []; //detect if q is a guid const guidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; if (guidRegex.test(q)) { body.filters.$and.push({ id: q }); } else { body.filters.$and.push({ [opts.labelField]: { $contains: q } }); } } } return body; } function asRecord(value: unknown): Record { return value && typeof value === 'object' ? (value as Record) : {}; } // Normalise une réponse `*/query` en options { id, label }, quel que soit le wrapping. function normalizeOptions(raw: unknown): InwinkContextOption[] { const root = asRecord(raw); const list: unknown[] = Array.isArray(raw) ? raw : Array.isArray(root.data) ? (root.data as unknown[]) : Array.isArray(root.items) ? (root.items as unknown[]) : []; const options: InwinkContextOption[] = []; for (const entry of list) { const item = asRecord(entry); const id = item.id ?? item.eventId ?? item.communityId ?? item.tenantId; if (id == null) continue; const label = item.title ?? item.name ?? item.displayName ?? id; options.push({ id: String(id), label: String(label) }); } return options; } /** Init : l'URL fait foi ; sinon on retombe sur le contexte persisté en session. */ function initialParams(): InwinkContextParams { const fromUrl = readInwinkContextFromUrl(); if (fromUrl.eventId || fromUrl.communityId || fromUrl.tenantId) { persistContext(fromUrl); return fromUrl; } return loadPersistedContext(); } export function InwinkContextProvider({ children }: PropsWithChildren) { const customerApi = useInwinkCustomerApi(); const [params, setParams] = useState(initialParams); const value = useMemo(() => { const apply = (next: InwinkContextParams) => { setParams(next); persistContext(next); }; return { scope: deriveScope(params), eventId: params.eventId, communityId: params.communityId, tenantId: params.tenantId, setEvent: (id) => apply({ ...params, eventId: id, communityId: undefined }), setCommunity: (id) => apply({ ...params, communityId: id, eventId: undefined }), setTenant: (id) => apply({ tenantId: id }), loadEvents: async (query?: IInwinkContextQuery) => normalizeOptions( await customerApi.post( '/00000000-0000-0000-0000-000000000000/my/events/query', buildQueryBody({ query, labelField: 'title', orders: [{ desc: true, value: { startDate: {} } }] }), ), ), loadCommunities: async (query?: IInwinkContextQuery) => normalizeOptions( await customerApi.post( '/00000000-0000-0000-0000-000000000000/my/communities/query', buildQueryBody({ query, labelField: 'name' }), ), ), loadTenants: async (query?: IInwinkContextQuery) => normalizeOptions( await customerApi.post( '/00000000-0000-0000-0000-000000000000/my/tenants/query', buildQueryBody({ query, labelField: 'name' }), ), ), }; }, [params, customerApi]); return {children}; }