ID PostHog + affichage prod et salariés nouveau CDDU + problème DPAE contrats

This commit is contained in:
odentas 2025-10-14 12:12:18 +02:00
parent 2f7c5dad7e
commit 61ae5b0bb4
5 changed files with 142 additions and 35 deletions

View file

@ -18,15 +18,32 @@ PostHog est configuré pour tracker automatiquement les événements utilisateur
- Initialise PostHog au chargement de l'application
- Active le mode debug en développement
- Expose `window.posthog` pour les tests console en dev
- **Important** : Utilise `person_profiles: 'identified_only'` - les utilisateurs ne sont créés dans PostHog que lorsqu'ils sont explicitement identifiés
2. **PostHogPageView** (`components/PostHogPageView.tsx`)
- Track automatiquement chaque changement de page
- Capture l'URL complète avec query params
3. **PostHogIdentifier** (`components/PostHogIdentifier.tsx`)
- Identifie automatiquement les utilisateurs connectés
- **Composant clé pour l'identification des utilisateurs**
- Identifie automatiquement les utilisateurs connectés via l'API `/api/me`
- Associe les propriétés utilisateur (email, nom, company, etc.)
- Réinitialise l'identité lors de la déconnexion
- **Note** : Si vous ne voyez pas les utilisateurs identifiés dans PostHog, vérifiez que ce composant est bien présent dans votre layout
## 🔍 Pourquoi les utilisateurs sont anonymes ?
Si vous voyez tous les utilisateurs anonymisés dans PostHog, c'est probablement parce que :
1. ❌ **Le composant `PostHogIdentifier` n'est pas présent** dans le layout
2. ❌ **L'API `/api/me` ne retourne pas les bonnes données** (vérifiez la structure JSON)
3. ❌ **L'utilisateur n'est pas authentifié** au moment où PostHog charge
4. ❌ **Les variables d'environnement PostHog sont manquantes**
### Solution :
- Vérifiez que `<PostHogIdentifier />` est bien dans `app/layout.tsx`
- Ouvrez la console du navigateur et regardez les logs : vous devriez voir `👤 PostHog: Utilisateur identifié: email@example.com`
- Si vous voyez une erreur, vérifiez que l'API `/api/me` fonctionne correctement
## 📈 Événements trackés automatiquement
@ -39,9 +56,9 @@ PostHog est configuré pour tracker automatiquement les événements utilisateur
Lors de l'identification (connexion) :
- `email` : Email de l'utilisateur
- `first_name` : Prénom
- `last_name` : Nom
- `company_name` : Nom de la société
- `company_id` : ID de la société
- `display_name` : Nom d'affichage complet
- `company_name` : Nom de la société active (active_org_name)
- `company_id` : ID de la société active (active_org_id)
- `is_staff` : Membre du staff ou non
## 🔧 Utilisation avancée
@ -227,6 +244,42 @@ posthog.opt_in_capturing();
4. ✅ Vérifier Network : requêtes `/batch/` en status 200
5. ✅ Vérifier la clé API dans le dashboard PostHog
### ❌ Les utilisateurs sont tous anonymes / non identifiés
**Symptôme** : Dans PostHog, vous voyez des utilisateurs avec des IDs anonymes (ex: `anon_xyz123`) au lieu des vrais utilisateurs.
**Cause** : Le composant `PostHogIdentifier` n'identifie pas correctement les utilisateurs.
**Solutions** :
1. **Vérifier la console du navigateur** :
- Vous devriez voir : `👤 PostHog: Utilisateur identifié: email@example.com ID: user-uuid`
- Si vous voyez une erreur, notez le message
2. **Vérifier que PostHogIdentifier est dans le layout** :
- Ouvrir `app/layout.tsx`
- Chercher `<PostHogIdentifier />`
- Doit être à l'intérieur du `<Providers>` ou `<PostHogProvider>`
3. **Tester l'API /api/me** :
```bash
# Dans la console du navigateur (quand vous êtes connecté)
fetch('/api/me').then(r => r.json()).then(console.log)
```
- Doit retourner : `{ user: { id: "...", email: "..." }, active_org_id: "...", ... }`
- Si `user.id` est undefined, il y a un problème avec l'API
4. **Vérifier la configuration PostHog** :
- Dans `PostHogProvider.tsx`, vous devez avoir : `person_profiles: 'identified_only'`
- Cela signifie que PostHog ne crée des profils que pour les utilisateurs identifiés explicitement
5. **Forcer une réidentification** :
```javascript
// Dans la console du navigateur
window.posthog.reset(); // Efface l'identité actuelle
window.location.reload(); // Recharge la page pour réidentifier
```
### Double initialisation
Si vous voyez "You have already initialized PostHog!", c'est normal en dev (React Strict Mode). En production, cela n'arrive pas.

View file

@ -594,7 +594,9 @@ export default function ContratMultiPage() {
{(() => {
const raw = String(data.dpae || "");
const norm = raw.normalize("NFD").replace(/\p{Diacritic}/gu, "").trim().toLowerCase();
if (norm === "ok" || norm === "envoyee" || norm === "retourok" || norm === "retour_ok") {
// États "OK" / "Effectuée"
if (norm === "ok" || norm === "faite" || norm === "envoyee" || norm === "retourok" || norm === "retour_ok") {
return (
<Field
label="DPAE"
@ -606,7 +608,9 @@ export default function ContratMultiPage() {
/>
);
}
if (norm === "a traiter" || norm === "a_traiter" || norm === "a_traiter") {
// États "À traiter" / "En cours"
if (norm === "a traiter" || norm === "a_traiter" || norm === "a faire" || norm === "a_faire") {
return (
<Field
label="DPAE"
@ -618,6 +622,27 @@ export default function ContratMultiPage() {
/>
);
}
// État "Refusée"
if (norm === "refusee" || norm === "refuse") {
return (
<Field
label="DPAE"
value={(
<span className="inline-flex items-center gap-1 text-[11px] px-2 py-0.5 rounded-full bg-rose-100 text-rose-800">
<Clock className="w-3 h-3" /> Refusée
</span>
)}
/>
);
}
// Si une valeur existe mais n'est pas reconnue, l'afficher telle quelle
if (raw) {
return <Field label="DPAE" value={<span className="text-xs text-slate-600">{raw}</span>} />;
}
// Aucune valeur
return <Field label="DPAE" value={<span className="text-xs text-slate-400"></span>} />;
})()}
</Section>

View file

@ -1344,19 +1344,45 @@ return (
<Section title="Déclarations" icon={Shield}>
<Field
label="DPAE"
value={
data.dpae === "OK" ? (
<span className="inline-flex items-center gap-1 text-[11px] px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-800">
<CheckCircle className="w-3 h-3" /> Effectuée
</span>
) : data.dpae === "À traiter" ? (
<span className="inline-flex items-center gap-1 text-[11px] px-2 py-0.5 rounded-full bg-amber-100 text-amber-800">
<Clock className="w-3 h-3" /> En cours
</span>
) : (
<span className="text-xs text-slate-400"></span>
)
}
value={(() => {
const raw = String(data.dpae || "");
const norm = raw.normalize("NFD").replace(/\p{Diacritic}/gu, "").trim().toLowerCase();
// États "OK" / "Effectuée"
if (norm === "ok" || norm === "faite" || norm === "envoyee" || norm === "retourok" || norm === "retour_ok") {
return (
<span className="inline-flex items-center gap-1 text-[11px] px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-800">
<CheckCircle className="w-3 h-3" /> Effectuée
</span>
);
}
// États "À traiter" / "En cours"
if (norm === "a traiter" || norm === "a_traiter" || norm === "a faire" || norm === "a_faire") {
return (
<span className="inline-flex items-center gap-1 text-[11px] px-2 py-0.5 rounded-full bg-amber-100 text-amber-800">
<Clock className="w-3 h-3" /> En cours
</span>
);
}
// État "Refusée"
if (norm === "refusee" || norm === "refuse") {
return (
<span className="inline-flex items-center gap-1 text-[11px] px-2 py-0.5 rounded-full bg-rose-100 text-rose-800">
<Clock className="w-3 h-3" /> Refusée
</span>
);
}
// Si une valeur existe mais n'est pas reconnue, l'afficher telle quelle
if (raw) {
return <span className="text-xs text-slate-600">{raw}</span>;
}
// Aucune valeur
return <span className="text-xs text-slate-400"></span>;
})()}
/>
</Section>

View file

@ -22,19 +22,22 @@ export default function PostHogIdentifier() {
const me = await res.json();
if (me?.id) {
// L'API /api/me retourne { user: { id, email, ... }, active_org_id, ... }
const userId = me?.user?.id || me?.user_id;
if (userId) {
// Identifier l'utilisateur avec son ID unique
posthog?.identify(me.id, {
email: me.email,
first_name: me.first_name,
last_name: me.last_name,
company_name: me.company_name,
company_id: me.company_id,
posthog?.identify(userId, {
email: me.user?.email || me.email,
first_name: me.user?.first_name || me.first_name,
display_name: me.user?.display_name || me.display_name,
company_name: me.active_org_name,
company_id: me.active_org_id,
is_staff: me.is_staff || false,
// Ajoutez d'autres propriétés utiles pour vos analytics
});
console.log('👤 PostHog: Utilisateur identifié:', me.email);
console.log('👤 PostHog: Utilisateur identifié:', me.user?.email || me.email, 'ID:', userId);
}
} catch (e) {
console.error('❌ PostHog: Erreur lors de l\'identification:', e);

View file

@ -229,7 +229,7 @@ function useSearchSalaries(q: string, orgId?: string | null) {
api<{ items: SalarieOption[] }>(`/salaries?${params.toString()}`, {}, clientInfo).then((r) => ({
items: r.items?.map((s) => ({ matricule: s.matricule, nom: s.nom, email: s.email ?? undefined })) ?? [],
})),
enabled: q.trim().length > 1 && (!!orgId || !!clientInfo),
enabled: (!!orgId || !!clientInfo),
staleTime: 10_000,
});
}
@ -266,7 +266,7 @@ function useSearchSpectacles(q: string, orgId?: string | null) {
numero_objet: (s as any).numero_objet || (s as any)["n° d'objet"] || (s as any).numero || null,
})) ?? [],
})),
enabled: q.trim().length > 1 && !!clientInfo,
enabled: !!clientInfo,
staleTime: 10_000,
});
}
@ -1332,7 +1332,7 @@ useEffect(() => {
onChange={(e) => setSpectacleQuery(e.target.value)}
onKeyDown={(e) => {
const items = spectacleSearch?.items || [];
if (spectacleQuery.length <= 1 || items.length === 0) return;
if (items.length === 0) return;
if (e.key === 'ArrowDown') { e.preventDefault(); setSpectacleActive(i => Math.min(i + 1, items.length - 1)); }
if (e.key === 'ArrowUp') { e.preventDefault(); setSpectacleActive(i => Math.max(i - 1, 0)); }
if (e.key === 'Enter') {
@ -1349,7 +1349,7 @@ useEffect(() => {
(e.currentTarget as HTMLInputElement).blur();
}
}}
placeholder="Rechercher une production (min. 2 lettres)…"
placeholder="Rechercher une production…"
className="w-full pl-9 px-3 py-2 rounded-lg border bg-white text-sm"
/>
</div>
@ -1376,7 +1376,7 @@ useEffect(() => {
Recherche
</div>
)}
{spectacleSearch?.items && spectacleQuery.length > 1 && (
{spectacleSearch?.items && (
<div className="mt-2 max-h-56 overflow-auto rounded-lg border">
{spectacleSearch.items.length === 0 ? (
<div className="p-3 text-sm text-slate-500">Aucun résultat.</div>
@ -1451,7 +1451,7 @@ useEffect(() => {
onChange={(e) => setSalarieQuery(e.target.value)}
onKeyDown={(e) => {
const items = salarieSearch?.items || [];
if (salarieQuery.length <= 1 || items.length === 0) return;
if (items.length === 0) return;
if (e.key === 'ArrowDown') { e.preventDefault(); setSalarieActive(i => Math.min(i + 1, items.length - 1)); }
if (e.key === 'ArrowUp') { e.preventDefault(); setSalarieActive(i => Math.max(i - 1, 0)); }
if (e.key === 'Enter') {
@ -1464,7 +1464,7 @@ useEffect(() => {
(e.currentTarget as HTMLInputElement).blur();
}
}}
placeholder="Rechercher un salarié (min. 2 lettres)…"
placeholder="Rechercher un salarié…"
className="w-full pl-9 px-3 py-2 rounded-lg border bg-white text-sm"
/>
</div>
@ -1483,7 +1483,7 @@ useEffect(() => {
Recherche
</div>
)}
{salarieSearch?.items && salarieQuery.length > 1 && (
{salarieSearch?.items && (
<div className="mt-2 max-h-56 overflow-auto rounded-lg border">
{salarieSearch.items.length === 0 ? (
<div className="p-3 text-sm text-slate-500">Aucun résultat.</div>