src/lib/analytics.ts (NEW) — 3 queries server-only :
- getMonthlyRevenueSeries({organizationId?, monthsBack=12}) → 12
buckets « YYYY-MM » avec carbetRevenue + rentalRevenue + total.
Scope optionnel par org via memberships (Booking) et
provider.organizationId (RentalBooking).
- getCarbetsOccupancy({organizationId?, monthsBack=3}) → liste triée
par occupancyPct avec bookedNights/totalNights pour chaque carbet
PUBLISHED (filtré par memberships si org).
- getAdminGlobalKpis() → users (total + breakdown par rôle),
carbetsPublished, bookings/rentals 30j, revenue 30j, top 5 carbets
+ top 5 providers par CA 30j.
src/components/analytics/MonthlyRevenueChart.tsx (NEW) — bar chart
SVG simple (pas de lib externe), stack carbet + rental, grid Y, tooltips
via <title>, légende couleurs. Responsive overflow-x-auto.
/espace-ce/analytics/page.tsx (NEW) :
- 3 KPIs (CA 12 mois total / Carbet / Matériel)
- MonthlyRevenueChart scopé par org
- Liste taux d'occupation carbets 3 derniers mois (barres horizontales)
- Lien ajouté depuis le dashboard /espace-ce
/admin/analytics/page.tsx (NEW) :
- 4 KPIs (utilisateurs, carbets publiés, bookings 30j, CA 30j)
- Breakdown users par rôle (barres horizontales + pourcentages)
- Carte « Activité 30j » avec bookings carbet + locations matériel
- MonthlyRevenueChart global
- Top 5 carbets (CA 30j) + Top 5 prestataires rental (CA 30j)
- Sidebar admin gagne entrée « Analytics » sous « Vue d'ensemble »
Pas de nouvelle dépendance npm — graphiques en SVG natif.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Email automatique pour les invites CE_MEMBER :
- sendCeInviteEmail(to, orgName, inviteUrl, inviterName?) : template
best-effort (dry-run sans Resend), bouton CTA + lien direct en plain
text. Mentionne TTL 14j + warning si pas le destinataire attendu.
- createInviteAction branche l'envoi automatique quand un email est
renseigné dans le formulaire. Audit log gagne emailedAutomatically.
- InviteForm UI : affiche « lien généré · email envoyé » quand un
email était fourni. Texte d'aide mis à jour.
- Sans email → comportement inchangé : lien à copier manuellement.
Admin /admin/carbets/[id] gagne section memberships :
- src/lib/admin/carbets.ts : getCarbetForEdit inclut organizations +
listOrganizationsForLink helper (toutes orgs triées approved desc).
- 2 actions admin : linkCarbetToOrganizationAction (idempotent) +
unlinkCarbetFromOrganizationAction. Audit scope=admin.carbets,
events carbet.org.link / carbet.org.unlink.
- CarbetMemberships client component : liste les orgs liées (badge
pending si org non approuvée) + select des orgs disponibles + boutons
Lier/Délier. Désactive le select quand toutes les orgs sont déjà
liées.
Le link admin permet de :
- Lier rétroactivement un carbet existant à un CE (cas où l'orga
intègre un carbet d'un hôte individuel).
- Délier un carbet quand un CE part ou que le carbet repasse en
gestion individuelle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
src/lib/ce-access.ts (NEW) :
- requireCeManagerSession (redirect connexion ou / si rôle insuffisant)
- getCurrentCeOrganization (CE_MANAGER → son org via organizationId,
ADMIN → org ciblée par paramètre ou null)
- canManageCarbetForCe (owner direct OU membre d'une org liée)
- requireApprovedOrg (redirect /espace-ce?pending=1 si non validée)
Emails best-effort :
- sendNewCeRequest → admin (contact@karbe) avec lien filtré
/admin/organizations?status=pending
- sendCeApproved → CE_MANAGERs actifs de l'org après validation
- Branchement dans approveOrganizationAction : envoie le mail à tous
les CE_MANAGERs actifs de l'org en best-effort.
Signup CE public :
- SignupForm 4e tuile « Comité d'Entreprise » avec champ orgName.
Layout grid 4 colonnes sur lg, 2 sur sm.
- /api/signup étendu :
- zod accepte CE_MANAGER + orgName
- transaction $tx atomique : Organization (approved=false, slug
auto-unique via slugify + suffix) + User (role=CE_MANAGER,
organizationId lié)
- sendNewCeRequest best-effort
- réponse étendue avec organizationId
- Pattern slug : retry avec suffix -2, -3… jusqu'à libre
Dashboard /espace-ce :
- layout.tsx : requirePluginOr404("ce-management") +
requireCeManagerSession
- page.tsx : 4 KPIs (carbets co-gérés, items rental, bookings 30j,
revenu 30j), bannière « En attente de validation » si pending,
2 ActionCards (Mes carbets, Matériel rental) marquées « Bientôt »
jusqu'aux sprints I et J
- ce-dashboard.ts : getCeOrgKpis (agrège bookings carbets via
membership + rentalBookings via provider.organizationId) +
listCeCarbets pour Sprint I
SiteHeader : lien « Espace CE » conditionné par role + plugin
(mirror du lien Espace prestataire).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Schema:
- Organization gagne le workflow d'approbation (approved + approvedAt +
approvedBy + contactEmail). Backfill : toutes les orgs existantes
(CMCK) → approved=true via migration.
- Nouveau OrganizationCarbetMembership (manyToMany Org↔Carbet) pour la
co-gestion CE : un Carbet a un ownerId (créateur initial) + 0..n
memberships ; chaque CE_MANAGER d'une org liée peut gérer le carbet
en plus de l'owner. Pour un hôte individuel = pas de membership.
- RentalProvider.organizationId (nullable, SetNull on delete) : un CE
peut posséder son provider ; les CE_MANAGERs membres de l'org y ont
accès en plus du manager nominal.
Plugin ce-management ajouté au registry (catégorie business, off par
défaut). Quand off : signup CE caché + dashboard /espace-ce 404.
Admin organizations :
- Tab statut (Toutes / À valider [count] / Validées) avec compteur des
organisations pending dans l'en-tête.
- Badge statut sur la liste et la page détail.
- Bouton « Valider l'organisation » sur le détail (action
approveOrganizationAction → flip approved=true + approvedAt + audit
log organization.approve). Idempotent : un re-appel sur une org déjà
validée ne re-loggue pas.
- Détail montre les compteurs carbetMemberships + rentalProviders.
Migration appliquée à la DB prod (CMCK backfill validé).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Le client component CarbetForm importait des options depuis lib/admin/carbets
qui contient "server-only" → erreur build turbopack. Sortie des options dans
src/lib/admin/carbet-options.ts sans server-only.