Suggest on creation + distance in kms

This commit is contained in:
Sylvain Duchesne
2026-01-26 18:32:16 +01:00
parent b7f86b139f
commit c9bc957d2a
14 changed files with 1236 additions and 657 deletions
@@ -31,6 +31,18 @@ Fonctionnalité: US-13 Relayer/Modifier/Supprimer un événement
Étant donné que je suis sur la page "relayer un événement"
Alors je peux annuler et revenir à l'écran précédent
Scénario: Détecter un événement similaire déjà relayé
Étant donné que l'écran "create-event" est affiché
Alors le formulaire permet de détecter les doublons
Scénario: Importer un événement depuis une source externe
Étant donné que l'écran "create-event" est affiché
Alors le formulaire permet d'importer depuis Mobilizon ou Transiscope
Scénario: Pas d'alerte doublon lors d'un import externe
Étant donné que l'écran "create-event" est affiché
Alors l'import externe ne déclenche pas d'alerte doublon
Scénario: Modifier un événement
* Scénario non implémenté
+26
View File
@@ -60,3 +60,29 @@ Then('le champ {string} est présent', async function (this: FestipodWorld, fiel
// Steps removed: Form display/validation steps (le champ affiche, erreur de validation, formulaire affiche N champs)
// require browser automation. Scenarios needing these use "* Scénario non implémenté" placeholder.
Then('le formulaire permet de détecter les doublons', async function (this: FestipodWorld) {
expect(this.currentScreenId).to.equal('create-event');
const source = this.getRenderedText();
// CreateEventScreen.tsx has: showDuplicateWarning logic and "Événement similaire détecté" warning
expect(/showDuplicateWarning/.test(source), 'Form should have duplicate detection logic').to.be.true;
expect(/Événement similaire détecté/.test(source), 'Form should have duplicate warning message').to.be.true;
});
Then('le formulaire permet d\'importer depuis Mobilizon ou Transiscope', async function (this: FestipodWorld) {
expect(this.currentScreenId).to.equal('create-event');
const source = this.getRenderedText();
// CreateEventScreen.tsx has: importableEvents with Mobilizon and Transiscope sources
expect(/importableEvents/.test(source), 'Form should have importable events data').to.be.true;
expect(/Mobilizon/.test(source), 'Form should support Mobilizon import').to.be.true;
expect(/Transiscope/.test(source), 'Form should support Transiscope import').to.be.true;
expect(/Importer depuis une source externe/.test(source), 'Form should have import section').to.be.true;
});
Then('l\'import externe ne déclenche pas d\'alerte doublon', async function (this: FestipodWorld) {
expect(this.currentScreenId).to.equal('create-event');
const source = this.getRenderedText();
// CreateEventScreen.tsx has: importedFrom state and !importedFrom in showDuplicateWarning condition
expect(/importedFrom/.test(source), 'Form should track import source').to.be.true;
expect(/&& !importedFrom/.test(source), 'Duplicate warning should be disabled for imports').to.be.true;
});
+11
View File
@@ -180,6 +180,17 @@ Then('je peux voir les événements auxquels l\'utilisateur a participé', async
expect(/Événements passés/.test(source), 'User profile should have "Événements passés" section').to.be.true;
});
Then('les événements affichent leur localisation et distance', async function (this: FestipodWorld) {
expect(this.currentScreenId).to.equal('user-profile');
const source = this.getRenderedText();
// UserProfileScreen.tsx: events have location and distance in data
expect(/location: '[^']+'/.test(source), 'Events should have location data').to.be.true;
expect(/distance: \d+/.test(source), 'Events should have distance data').to.be.true;
// Verify location is rendered in template
expect(/\{event\.location\}/.test(source), 'Events should render location').to.be.true;
expect(/\{event\.distance\}/.test(source), 'Events should render distance').to.be.true;
});
Then('je peux configurer mes notifications', async function (this: FestipodWorld) {
expect(this.currentScreenId).to.equal('settings');
const source = this.getRenderedText();
@@ -17,6 +17,10 @@ Fonctionnalité: US-10 Visualiser la fiche/le profil d'un participant
Étant donné que je suis sur la page "profil utilisateur"
Alors je peux voir les événements auxquels l'utilisateur a participé
Scénario: Voir la localisation des événements
Étant donné que je suis sur la page "profil utilisateur"
Alors les événements affichent leur localisation et distance
Scénario: Voir le formulaire de contact
Étant donné que je suis sur la page "profil utilisateur"
Alors je peux contacter l'utilisateur
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+58 -2
View File
@@ -179,6 +179,48 @@ export const parsedFeatures: ParsedFeature[] = [
}
]
},
{
"name": "Détecter un événement similaire déjà relayé",
"tags": [],
"steps": [
{
"keyword": "Étant donné que ",
"text": "l'écran \"create-event\" est affiché"
},
{
"keyword": "Alors",
"text": "le formulaire permet de détecter les doublons"
}
]
},
{
"name": "Importer un événement depuis une source externe",
"tags": [],
"steps": [
{
"keyword": "Étant donné que ",
"text": "l'écran \"create-event\" est affiché"
},
{
"keyword": "Alors",
"text": "le formulaire permet d'importer depuis Mobilizon ou Transiscope"
}
]
},
{
"name": "Pas d'alerte doublon lors d'un import externe",
"tags": [],
"steps": [
{
"keyword": "Étant donné que ",
"text": "l'écran \"create-event\" est affiché"
},
{
"keyword": "Alors",
"text": "l'import externe ne déclenche pas d'alerte doublon"
}
]
},
{
"name": "Modifier un événement",
"tags": [],
@@ -196,7 +238,7 @@ export const parsedFeatures: ParsedFeature[] = [
}
],
"filePath": "features/event/us-13-creer-evenement.feature",
"rawContent": "# language: fr\n@EVENT @priority-1\nFonctionnalité: US-13 Relayer/Modifier/Supprimer un événement\n En tant qu'utilisateur\n Je peux relayer/modifier/supprimer un événement\n En choisissant les dates, horaires, lieu et thématique\n Afin de relayer/présenter le contenu de cet événement et le catégoriser\n\n Contexte:\n Étant donné que je suis connecté en tant qu'utilisateur\n\n Scénario: Accéder au formulaire de relai d'événement\n Étant donné que je suis sur la page \"accueil\"\n Quand je navigue vers \"relayer un événement\"\n Alors je vois l'écran \"create-event\"\n\n Scénario: Vérifier les champs obligatoires du formulaire\n Étant donné que l'écran \"create-event\" est affiché\n Alors le formulaire contient les champs obligatoires suivants:\n | Nom de l'événement |\n | Date de début |\n | Heure de début |\n | Lieu |\n | Thématique |\n\n Scénario: Vérifier la présence du bouton de relai\n Étant donné que je suis sur la page \"relayer un événement\"\n Alors l'écran contient une section \"Relayer l'événement\"\n\n Scénario: Pouvoir annuler le relai d'événement\n Étant donné que je suis sur la page \"relayer un événement\"\n Alors je peux annuler et revenir à l'écran précédent\n\n Scénario: Modifier un événement\n * Scénario non implémenté\n\n Scénario: Supprimer un événement\n * Scénario non implémenté\n\n Scénario: Retirer une organisation (personne ou structure)\n * Scénario non implémenté\n",
"rawContent": "# language: fr\n@EVENT @priority-1\nFonctionnalité: US-13 Relayer/Modifier/Supprimer un événement\n En tant qu'utilisateur\n Je peux relayer/modifier/supprimer un événement\n En choisissant les dates, horaires, lieu et thématique\n Afin de relayer/présenter le contenu de cet événement et le catégoriser\n\n Contexte:\n Étant donné que je suis connecté en tant qu'utilisateur\n\n Scénario: Accéder au formulaire de relai d'événement\n Étant donné que je suis sur la page \"accueil\"\n Quand je navigue vers \"relayer un événement\"\n Alors je vois l'écran \"create-event\"\n\n Scénario: Vérifier les champs obligatoires du formulaire\n Étant donné que l'écran \"create-event\" est affiché\n Alors le formulaire contient les champs obligatoires suivants:\n | Nom de l'événement |\n | Date de début |\n | Heure de début |\n | Lieu |\n | Thématique |\n\n Scénario: Vérifier la présence du bouton de relai\n Étant donné que je suis sur la page \"relayer un événement\"\n Alors l'écran contient une section \"Relayer l'événement\"\n\n Scénario: Pouvoir annuler le relai d'événement\n Étant donné que je suis sur la page \"relayer un événement\"\n Alors je peux annuler et revenir à l'écran précédent\n\n Scénario: Détecter un événement similaire déjà relayé\n Étant donné que l'écran \"create-event\" est affiché\n Alors le formulaire permet de détecter les doublons\n\n Scénario: Importer un événement depuis une source externe\n Étant donné que l'écran \"create-event\" est affiché\n Alors le formulaire permet d'importer depuis Mobilizon ou Transiscope\n\n Scénario: Pas d'alerte doublon lors d'un import externe\n Étant donné que l'écran \"create-event\" est affiché\n Alors l'import externe ne déclenche pas d'alerte doublon\n\n Scénario: Modifier un événement\n * Scénario non implémenté\n\n Scénario: Supprimer un événement\n * Scénario non implémenté\n\n Scénario: Retirer une organisation (personne ou structure)\n * Scénario non implémenté\n",
"screenIds": [
"create-event",
"home"
@@ -500,6 +542,20 @@ export const parsedFeatures: ParsedFeature[] = [
}
]
},
{
"name": "Voir la localisation des événements",
"tags": [],
"steps": [
{
"keyword": "Étant donné que ",
"text": "je suis sur la page \"profil utilisateur\""
},
{
"keyword": "Alors",
"text": "les événements affichent leur localisation et distance"
}
]
},
{
"name": "Voir le formulaire de contact",
"tags": [],
@@ -535,7 +591,7 @@ export const parsedFeatures: ParsedFeature[] = [
}
],
"filePath": "features/user/us-10-profil-participant.feature",
"rawContent": "# language: fr\n@USER @priority-1\nFonctionnalité: US-10 Visualiser la fiche/le profil d'un participant\n En tant qu'utilisateur\n Je peux sélectionner un individu dans la liste des inscrits à un événement/atelier\n Afin de voir les événements auxquels la personne a participé et voir un formulaire de contact\n\n Contexte:\n Étant donné que je suis connecté en tant qu'utilisateur\n\n Scénario: Accéder au profil d'un participant\n Étant donné que je suis sur la page \"détail événement\"\n Quand je clique sur un participant\n Alors je vois l'écran \"user-profile\"\n\n Scénario: Voir les événements du participant\n Étant donné que je suis sur la page \"profil utilisateur\"\n Alors je peux voir les événements auxquels l'utilisateur a participé\n\n Scénario: Voir le formulaire de contact\n Étant donné que je suis sur la page \"profil utilisateur\"\n Alors je peux contacter l'utilisateur\n\n Scénario: Vérifier les informations du profil\n * Scénario non implémenté\n\n Scénario: Voir les détails du profil utilisateur\n Étant donné que je suis sur la page \"profil utilisateur\"\n Alors l'écran affiche les informations du profil\n",
"rawContent": "# language: fr\n@USER @priority-1\nFonctionnalité: US-10 Visualiser la fiche/le profil d'un participant\n En tant qu'utilisateur\n Je peux sélectionner un individu dans la liste des inscrits à un événement/atelier\n Afin de voir les événements auxquels la personne a participé et voir un formulaire de contact\n\n Contexte:\n Étant donné que je suis connecté en tant qu'utilisateur\n\n Scénario: Accéder au profil d'un participant\n Étant donné que je suis sur la page \"détail événement\"\n Quand je clique sur un participant\n Alors je vois l'écran \"user-profile\"\n\n Scénario: Voir les événements du participant\n Étant donné que je suis sur la page \"profil utilisateur\"\n Alors je peux voir les événements auxquels l'utilisateur a participé\n\n Scénario: Voir la localisation des événements\n Étant donné que je suis sur la page \"profil utilisateur\"\n Alors les événements affichent leur localisation et distance\n\n Scénario: Voir le formulaire de contact\n Étant donné que je suis sur la page \"profil utilisateur\"\n Alors je peux contacter l'utilisateur\n\n Scénario: Vérifier les informations du profil\n * Scénario non implémenté\n\n Scénario: Voir les détails du profil utilisateur\n Étant donné que je suis sur la page \"profil utilisateur\"\n Alors l'écran affiche les informations du profil\n",
"screenIds": [
"event-detail",
"user-profile"
+29 -1
View File
@@ -206,6 +206,27 @@ export const stepDefinitions: StepDefinitionInfo[] = [
"sourceCode": "Then('le champ {string} est présent', async function (this: FestipodWorld, fieldName: string) {\n const source = this.getRenderedText();\n // Check that field label exists in screen source\n const escapedName = fieldName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`>${escapedName}[^<]*<`);\n expect(pattern.test(source), `Field \"${fieldName}\" should be present in screen`).to.be.true;\n});",
"lineNumber": 53
},
{
"pattern": "le formulaire permet de détecter les doublons",
"keyword": "Then",
"file": "form.steps.ts",
"sourceCode": "Then('le formulaire permet de détecter les doublons', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('create-event');\n const source = this.getRenderedText();\n // CreateEventScreen.tsx has: showDuplicateWarning logic and \"Événement similaire détecté\" warning\n expect(/showDuplicateWarning/.test(source), 'Form should have duplicate detection logic').to.be.true;\n expect(/Événement similaire détecté/.test(source), 'Form should have duplicate warning message').to.be.true;\n});",
"lineNumber": 64
},
{
"pattern": "le formulaire permet d'importer depuis Mobilizon ou Transiscope",
"keyword": "Then",
"file": "form.steps.ts",
"sourceCode": "Then('le formulaire permet d\\'importer depuis Mobilizon ou Transiscope', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('create-event');\n const source = this.getRenderedText();\n // CreateEventScreen.tsx has: importableEvents with Mobilizon and Transiscope sources\n expect(/importableEvents/.test(source), 'Form should have importable events data').to.be.true;\n expect(/Mobilizon/.test(source), 'Form should support Mobilizon import').to.be.true;\n expect(/Transiscope/.test(source), 'Form should support Transiscope import').to.be.true;\n expect(/Importer depuis une source externe/.test(source), 'Form should have import section').to.be.true;\n});",
"lineNumber": 72
},
{
"pattern": "l'import externe ne déclenche pas d'alerte doublon",
"keyword": "Then",
"file": "form.steps.ts",
"sourceCode": "Then('l\\'import externe ne déclenche pas d\\'alerte doublon', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('create-event');\n const source = this.getRenderedText();\n // CreateEventScreen.tsx has: importedFrom state and !importedFrom in showDuplicateWarning condition\n expect(/importedFrom/.test(source), 'Form should track import source').to.be.true;\n expect(/&& !importedFrom/.test(source), 'Duplicate warning should be disabled for imports').to.be.true;\n});",
"lineNumber": 82
},
{
"pattern": "je peux voir la liste des participants",
"keyword": "Then",
@@ -325,12 +346,19 @@ export const stepDefinitions: StepDefinitionInfo[] = [
"sourceCode": "Then('je peux voir les événements auxquels l\\'utilisateur a participé', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('user-profile');\n const source = this.getRenderedText();\n // UserProfileScreen.tsx: \"Événements à venir\" and \"Événements passés\" sections\n expect(/Événements à venir/.test(source), 'User profile should have \"Événements à venir\" section').to.be.true;\n expect(/Événements passés/.test(source), 'User profile should have \"Événements passés\" section').to.be.true;\n});",
"lineNumber": 175
},
{
"pattern": "les événements affichent leur localisation et distance",
"keyword": "Then",
"file": "screen.steps.ts",
"sourceCode": "Then('les événements affichent leur localisation et distance', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('user-profile');\n const source = this.getRenderedText();\n // UserProfileScreen.tsx: events have location and distance in data\n expect(/location: '[^']+'/.test(source), 'Events should have location data').to.be.true;\n expect(/distance: \\d+/.test(source), 'Events should have distance data').to.be.true;\n // Verify location is rendered in template\n expect(/\\{event\\.location\\}/.test(source), 'Events should render location').to.be.true;\n expect(/\\{event\\.distance\\}/.test(source), 'Events should render distance').to.be.true;\n});",
"lineNumber": 183
},
{
"pattern": "je peux configurer mes notifications",
"keyword": "Then",
"file": "screen.steps.ts",
"sourceCode": "Then('je peux configurer mes notifications', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('settings');\n const source = this.getRenderedText();\n // SettingsScreen.tsx line 25: <Text>Notifications</Text> with Toggle\n expect(/>Notifications</.test(source), 'Settings should have \"Notifications\" text').to.be.true;\n expect(/<Toggle[^>]*checked=\\{notifications\\}/.test(source), 'Settings should have Toggle for notifications').to.be.true;\n});",
"lineNumber": 183
"lineNumber": 194
}
];
+46 -30
View File
@@ -15,11 +15,11 @@ interface RawFeatureTestStatus {
const rawResults: RawFeatureTestStatus[] = [
{
"featureId": "us-13",
"totalScenarios": 7,
"passed": 4,
"totalScenarios": 10,
"passed": 7,
"failed": 0,
"skipped": 3,
"lastRun": "2026-01-26T17:08:25.769Z",
"lastRun": "2026-01-26T17:31:35.878Z",
"scenarios": [
{
"name": "Accéder au formulaire de relai d'événement",
@@ -37,6 +37,18 @@ const rawResults: RawFeatureTestStatus[] = [
"name": "Pouvoir annuler le relai d'événement",
"status": "passed"
},
{
"name": "Détecter un événement similaire déjà relayé",
"status": "passed"
},
{
"name": "Importer un événement depuis une source externe",
"status": "passed"
},
{
"name": "Pas d'alerte doublon lors d'un import externe",
"status": "passed"
},
{
"name": "Modifier un événement",
"status": "skipped"
@@ -57,7 +69,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 3,
"failed": 0,
"skipped": 0,
"lastRun": "2026-01-26T17:08:25.769Z",
"lastRun": "2026-01-26T17:31:35.878Z",
"scenarios": [
{
"name": "Accéder aux détails d'un événement terminé",
@@ -79,7 +91,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 5,
"lastRun": "2026-01-26T17:08:25.769Z",
"lastRun": "2026-01-26T17:31:35.878Z",
"scenarios": [
{
"name": "Voir les commentaires existants",
@@ -109,7 +121,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 2,
"failed": 0,
"skipped": 4,
"lastRun": "2026-01-26T17:08:25.769Z",
"lastRun": "2026-01-26T17:31:35.878Z",
"scenarios": [
{
"name": "Consulter un événement avant inscription",
@@ -143,7 +155,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 8,
"lastRun": "2026-01-26T17:08:25.769Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Consulter un macro-événement",
@@ -185,7 +197,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 4,
"failed": 0,
"skipped": 0,
"lastRun": "2026-01-26T17:08:25.769Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder aux points de rencontre",
@@ -211,7 +223,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 5,
"lastRun": "2026-01-26T17:08:25.769Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Partager un événement auquel je participe",
@@ -241,7 +253,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 1,
"failed": 0,
"skipped": 3,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Configurer les notifications de nouveaux participants",
@@ -267,7 +279,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 3,
"failed": 0,
"skipped": 2,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Voir les événements à venir sur l'accueil",
@@ -293,11 +305,11 @@ const rawResults: RawFeatureTestStatus[] = [
},
{
"featureId": "us-10",
"totalScenarios": 5,
"passed": 4,
"totalScenarios": 6,
"passed": 5,
"failed": 0,
"skipped": 1,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder au profil d'un participant",
@@ -307,6 +319,10 @@ const rawResults: RawFeatureTestStatus[] = [
"name": "Voir les événements du participant",
"status": "passed"
},
{
"name": "Voir la localisation des événements",
"status": "passed"
},
{
"name": "Voir le formulaire de contact",
"status": "passed"
@@ -327,7 +343,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 3,
"failed": 0,
"skipped": 4,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder à la liste des événements depuis le profil",
@@ -365,7 +381,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 3,
"failed": 0,
"skipped": 2,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder à la liste des inscrits d'un événement",
@@ -395,7 +411,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 5,
"failed": 0,
"skipped": 1,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder à mon profil",
@@ -429,7 +445,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 2,
"failed": 0,
"skipped": 3,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder aux paramètres de profil",
@@ -459,7 +475,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 5,
"failed": 0,
"skipped": 0,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder au partage de profil",
@@ -489,7 +505,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 5,
"failed": 0,
"skipped": 0,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder au partage depuis le profil",
@@ -519,7 +535,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 2,
"failed": 0,
"skipped": 1,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder aux paramètres de notification",
@@ -541,7 +557,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 1,
"failed": 0,
"skipped": 2,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder aux paramètres de notification",
@@ -563,7 +579,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 3,
"failed": 0,
"skipped": 1,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder au formulaire de relai d'événement",
@@ -589,7 +605,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 4,
"failed": 0,
"skipped": 1,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder au profil pour voir la photo",
@@ -619,7 +635,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 5,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder aux détails d'un événement terminé",
@@ -649,7 +665,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 3,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder au bilan consolidé",
@@ -671,7 +687,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 7,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder à la création d'atelier",
@@ -709,7 +725,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 5,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder à la zone de notes personnelles",
@@ -739,7 +755,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 5,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Voir les commentaires existants d'un atelier",
@@ -769,7 +785,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 4,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Voir les ateliers d'un événement",
+108 -4
View File
@@ -2,14 +2,38 @@ import React, { useState } from 'react';
import { Header, Text, Input, Button, Placeholder } from '../components/sketchy';
import type { ScreenProps } from './index';
// Demo data for suggestions
const existingEvents = [
{ name: 'Résidence Reconnexion', relayedBy: 'Thomas Martin' },
];
const importableEvents = [
{
name: 'Festival des Utopies Concrètes',
source: 'Mobilizon',
date: '2026-03-15',
location: 'Paris, Parc de la Villette',
description: 'Festival annuel présentant des alternatives concrètes pour un monde durable.',
},
{
name: 'Rencontres de l\'Écologie',
source: 'Transiscope',
date: '2026-04-20',
location: 'Lyon, Halle Tony Garnier',
description: 'Deux jours de conférences et ateliers sur la transition écologique.',
},
];
export function CreateEventScreen({ navigate }: ScreenProps) {
const [name, setName] = useState('');
const [startDate, setStartDate] = useState('');
const [location, setLocation] = useState('');
const [description, setDescription] = useState('');
const [showSuggestions, setShowSuggestions] = useState(false);
const [importedFrom, setImportedFrom] = useState<string | null>(null);
// Show warning only when key fields are filled
const showDuplicateWarning = name.length > 3 && startDate && location.length > 3;
// Show warning only when key fields are filled AND not imported from external source
const showDuplicateWarning = name.length > 3 && startDate && location.length > 3 && !importedFrom;
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
@@ -53,13 +77,93 @@ export function CreateEventScreen({ navigate }: ScreenProps) {
)}
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<div>
<div style={{ position: 'relative' }}>
<Text style={{ marginBottom: 6, fontSize: 14 }}>Nom de l'événement *</Text>
<Input
placeholder="Donnez un nom à votre événement"
value={name}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setName(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
setShowSuggestions(e.target.value.length > 0);
setImportedFrom(null); // Reset import flag when user types manually
}}
onFocus={() => name.length > 0 && setShowSuggestions(true)}
onBlur={() => setTimeout(() => setShowSuggestions(false), 200)}
/>
{/* Suggestions dropdown */}
{showSuggestions && (
<div style={{
position: 'absolute',
top: '100%',
left: 0,
right: 0,
background: 'white',
border: '2px solid var(--sketch-black)',
borderRadius: 8,
marginTop: 4,
zIndex: 10,
maxHeight: 250,
overflow: 'auto',
}}>
{/* Existing events - not selectable */}
{existingEvents.length > 0 && (
<>
<div style={{ padding: '8px 12px', background: 'var(--sketch-light-gray)', fontSize: 12, fontWeight: 'bold' }}>
Déjà relayé sur Festipod
</div>
{existingEvents.map((event, i) => (
<div
key={`existing-${i}`}
style={{
padding: '10px 12px',
borderBottom: '1px solid var(--sketch-light-gray)',
opacity: 0.6,
cursor: 'not-allowed',
}}
>
<Text style={{ margin: 0, fontSize: 14 }}>{event.name}</Text>
<Text style={{ margin: '2px 0 0 0', fontSize: 12, color: 'var(--sketch-gray)' }}>
Relayé par {event.relayedBy}
</Text>
</div>
))}
</>
)}
{/* Importable events */}
{importableEvents.length > 0 && (
<>
<div style={{ padding: '8px 12px', background: 'var(--sketch-light-gray)', fontSize: 12, fontWeight: 'bold' }}>
Importer depuis une source externe
</div>
{importableEvents.map((event, i) => (
<div
key={`import-${i}`}
style={{
padding: '10px 12px',
borderBottom: '1px solid var(--sketch-light-gray)',
cursor: 'pointer',
}}
onClick={() => {
setName(event.name);
setStartDate(event.date);
setLocation(event.location);
setDescription(event.description);
setImportedFrom(event.source);
setShowSuggestions(false);
}}
>
<Text style={{ margin: 0, fontSize: 14 }}>{event.name}</Text>
<Text style={{ margin: '2px 0 0 0', fontSize: 12, color: 'var(--sketch-gray)' }}>
via {event.source} · {event.location}
</Text>
</div>
))}
</>
)}
</div>
)}
</div>
<div style={{ display: 'flex', gap: 12 }}>
+1
View File
@@ -39,6 +39,7 @@ export function EventDetailScreen({ navigate }: ScreenProps) {
</Text>
<Text style={{ margin: 0, fontSize: 15 }}>
📍 <span className="user-content">Le Revel, Rogues (30)</span>
<span style={{ color: 'var(--sketch-gray)' }}> · 142 km</span>
</Text>
</div>
+8 -1
View File
@@ -2,10 +2,11 @@ import React from 'react';
import { Header, Input, Card, Text, Badge, NavBar } from '../components/sketchy';
import type { ScreenProps } from './index';
function EventCard({ title, date, location, attendees, onClick }: {
function EventCard({ title, date, location, distance, attendees, onClick }: {
title: string;
date: string;
location: string;
distance: number;
attendees: number;
onClick: () => void;
}) {
@@ -17,6 +18,7 @@ function EventCard({ title, date, location, attendees, onClick }: {
</Text>
<Text style={{ margin: '0 0 8px 0', fontSize: 14 }}>
📍 <span className="user-content">{location}</span>
<span style={{ color: 'var(--sketch-gray)' }}> · {distance} km</span>
</Text>
<Badge>{attendees} inscrits</Badge>
</Card>
@@ -68,6 +70,7 @@ export function EventsScreen({ navigate }: ScreenProps) {
title="Résidence Reconnexion"
date="Lun. 16 - Ven. 20 fév."
location="Le Revel, Rogues (30)"
distance={142}
attendees={24}
onClick={() => navigate('event-detail')}
/>
@@ -75,6 +78,7 @@ export function EventsScreen({ navigate }: ScreenProps) {
title="Atelier low-tech"
date="Sam. 8 fév. · 14h00"
location="La Maison du Vélo, Lyon"
distance={3}
attendees={12}
onClick={() => navigate('event-detail')}
/>
@@ -82,6 +86,7 @@ export function EventsScreen({ navigate }: ScreenProps) {
title="Forum Ouvert Transition"
date="Sam. 22 fév. · 9h00"
location="Tiers-lieu L'Hermitage"
distance={89}
attendees={45}
onClick={() => navigate('event-detail')}
/>
@@ -89,6 +94,7 @@ export function EventsScreen({ navigate }: ScreenProps) {
title="Formation CNV"
date="Sam. 1 mars · 9h30"
location="MJC Montplaisir, Lyon"
distance={5}
attendees={16}
onClick={() => navigate('event-detail')}
/>
@@ -96,6 +102,7 @@ export function EventsScreen({ navigate }: ScreenProps) {
title="Rencontre des Colibris"
date="Mer. 12 fév. · 19h00"
location="La Maison de l'Environnement"
distance={7}
attendees={30}
onClick={() => navigate('event-detail')}
/>
+8 -2
View File
@@ -2,14 +2,17 @@ import React from 'react';
import { Button, Title, Text, Card, NavBar, Badge } from '../components/sketchy';
import type { ScreenProps } from './index';
function EventCard({ title, date, location, attendees, onClick }: { title: string; date: string; location: string; attendees: number; onClick: () => void }) {
function EventCard({ title, date, location, distance, attendees, onClick }: { title: string; date: string; location: string; distance: number; attendees: number; onClick: () => void }) {
return (
<Card onClick={onClick} style={{ marginBottom: 12 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div>
<Text className="user-content" style={{ margin: 0, fontWeight: 'bold' }}>{title}</Text>
<Text className="user-content" style={{ margin: '4px 0 0 0', fontSize: 14 }}>{date}</Text>
<Text style={{ margin: '2px 0 0 0', fontSize: 14 }}>📍 <span className="user-content">{location}</span></Text>
<Text style={{ margin: '2px 0 0 0', fontSize: 14 }}>
📍 <span className="user-content">{location}</span>
<span style={{ color: 'var(--sketch-gray)' }}> · {distance} km</span>
</Text>
</div>
<Badge>{attendees} inscrits</Badge>
</div>
@@ -57,6 +60,7 @@ export function HomeScreen({ navigate }: ScreenProps) {
title="Résidence Reconnexion"
date="Lun. 16 - Ven. 20 fév."
location="Le Revel, Rogues (30)"
distance={142}
attendees={24}
onClick={() => navigate('event-detail')}
/>
@@ -64,6 +68,7 @@ export function HomeScreen({ navigate }: ScreenProps) {
title="Atelier low-tech"
date="Sam. 8 fév. · 14h00"
location="La Maison du Vélo, Lyon"
distance={3}
attendees={12}
onClick={() => navigate('event-detail')}
/>
@@ -71,6 +76,7 @@ export function HomeScreen({ navigate }: ScreenProps) {
title="Forum Ouvert Transition"
date="Sam. 22 fév. · 9h00"
location="Tiers-lieu L'Hermitage"
distance={89}
attendees={45}
onClick={() => navigate('event-detail')}
/>
+14 -6
View File
@@ -4,15 +4,15 @@ import type { ScreenProps } from './index';
export function UserProfileScreen({ navigate }: ScreenProps) {
const upcomingEvents = [
{ title: 'Résidence Reconnexion', date: '16-20 fév.', common: true },
{ title: 'Atelier permaculture', date: '28 fév.', common: false },
{ title: 'Résidence Reconnexion', date: '16-20 fév.', location: 'Le Revel, Rogues (30)', distance: 142, common: true },
{ title: 'Atelier permaculture', date: '28 fév.', location: 'Ferme des Music, Vénissieux', distance: 12, common: false },
];
const pastEvents = [
{ title: 'Forum Ouvert Transition', date: '22 jan.', common: true },
{ title: 'Rencontre des Colibris', date: '12 jan.', common: true },
{ title: 'Formation CNV', date: '8 jan.', common: false },
{ title: 'Café des possibles', date: '15 déc.', common: false },
{ title: 'Forum Ouvert Transition', date: '22 jan.', location: 'Tiers-lieu L\'Hermitage', distance: 89, common: true },
{ title: 'Rencontre des Colibris', date: '12 jan.', location: 'La Maison de l\'Environnement', distance: 7, common: true },
{ title: 'Formation CNV', date: '8 jan.', location: 'MJC Montplaisir, Lyon', distance: 5, common: false },
{ title: 'Café des possibles', date: '15 déc.', location: 'Café de la Mairie, Lyon 3', distance: 2, common: false },
];
return (
@@ -65,6 +65,10 @@ export function UserProfileScreen({ navigate }: ScreenProps) {
<Text className="user-content" style={{ margin: '4px 0 0 0', fontSize: 14 }}>
{event.date}
</Text>
<Text style={{ margin: '2px 0 0 0', fontSize: 14 }}>
📍 <span className="user-content">{event.location}</span>
<span style={{ color: 'var(--sketch-gray)' }}> · {event.distance} km</span>
</Text>
</div>
{event.common && <Badge>moi aussi</Badge>}
</div>
@@ -86,6 +90,10 @@ export function UserProfileScreen({ navigate }: ScreenProps) {
<Text className="user-content" style={{ margin: '4px 0 0 0', fontSize: 14 }}>
{event.date}
</Text>
<Text style={{ margin: '2px 0 0 0', fontSize: 14 }}>
📍 <span className="user-content">{event.location}</span>
<span style={{ color: 'var(--sketch-gray)' }}> · {event.distance} km</span>
</Text>
</div>
{event.common && <Badge>moi aussi</Badge>}
</div>