Introduction
Daylight Festival propose six séances sur trois scènes. Deux séances peuvent porter le même titre : votre planning personnel doit donc mémoriser leur véritable identité. Vous relierez une page React adaptative préparée à une sélection unique, puis calculerez ses cartes, son nombre de séances et sa durée totale à partir du programme actuel. Filtrer le programme ne doit pas faire disparaître un choix précédent.
Vous devez connaître les composants React, JSX, les props, useState, les événements, le rendu conditionnel, map/filter, les clés de listes et les champs contrôlés, ainsi que les tableaux, objets et fonctions JavaScript et les bases HTML/CSS. Ce laboratoire enseigne l'état dérivé, la recherche des enregistrements actuels, les mises à jour immuables de la sélection, la réduction des durées et les messages concernant les ID absents. Il démarre indépendamment, sans fichiers d'une VM précédente. Les 55 minutes sont une estimation de l'auteur, pas une durée mesurée auprès de débutants. Toutes les séances et identités sont synthétiques.
Utilisez Workspace pour le terminal et Festival pour les liens vers les pages réelles. Dans ce laboratoire, modifiez uniquement /home/labex/project/festival-schedule/work/src/selection.js et src/App.jsx. Conservez src/settings.js, src/main.jsx, src/conflicts.js, src/storage.js, le moteur de rendu et les styles préparés de shared, Study, les dépendances et la configuration du service. Les décisions de conflit et le stockage du navigateur ne sont pas demandés ici. L'exemple Study complet possède déjà ces comportements préparés ; vous les construirez dans le laboratoire suivant. Les libellés du site restent en anglais.
Retrouver les séances actuelles à partir des choix
Dans cette étape, utilisez un exemple normal, puis transformez des ID stables en véritables enregistrements du programme actuel.
Dans Festival, choisissez Open Study schedule in a new page. Ce lien réel ouvre une page autonome du navigateur, hors de l'onglet intégré du produit. Effectuez les actions du site dans cette nouvelle page. Avec Tab, atteignez Add to schedule pour First Light, Garden, 10:00–10:30, puis appuyez sur Enter. Poursuivez jusqu'à Paper Signals, Studio, 10:30–11:15, et ajoutez cette séance. Choisissez View my schedule : les deux cartes réelles et 2 sessions · 75 minutes apparaissent. Le lien de navigation de la journée rejoint le titre du planning ; Back to the programme revient au catalogue. Cet exemple possède ses propres choix : il ne constitue ni votre travail ni un résultat d'évaluation. La mise en page, les messages des cartes et la navigation sont déjà préparés ; vous construisez leur logique de sélection.
Fermez Study. Dans la page d'entrée Festival du produit, choisissez Open Work schedule in a new page et laissez la page ouverte. Elle est incomplète. Passez à l'onglet Workspace du produit pour modifier les fichiers. Changer d'onglet du produit est différent d'un lien du site, d'une ancre ou du bouton Précédent du navigateur. La commande préparée de Workspace entre dans le dossier de travail ; la commande visible ci-dessous précise aussi le chemin de départ. cd change le dossier de ce terminal, un chemin absolu commence par /, pwd affiche le chemin actuel et cat affiche un petit fichier sans le modifier.
cd /home/labex/project/festival-schedule/work
pwd
cat src/settings.js
Le catalogue utilise des ID comme D11 et D13. Les deux séances s'appellent First Light, mais leur scène et leur horaire diffèrent. Le settings.initialIDs préparé contient D11 et RETIRED-07 : une petite sélection initiale synthétique fournie pour ce laboratoire, pas le stockage d'une VM précédente. D11 existe encore ; RETIRED-07 n'existe plus. Seul un enregistrement actuel doit devenir une carte. L'ID absent recevra une explication explicite à l'étape 3.
Une petite faute dans un nom de fichier se corrige. La commande suivante écrit volontairement mal selection. Elle doit signaler que le fichier n'existe pas et se terminer avec un statut non nul ; elle ne change rien :
cat src/selction.js
Lisez l'erreur, comparez l'orthographe, puis utilisez le bon chemin :
cat src/selection.js
Le fichier contient des fonctions vides. currentRecords sélectionnera les enregistrements du catalogue actuel par ID, et non par titre ou position dans le tableau. filter crée un nouveau tableau ; includes vérifie si un ID a été choisi. Les ID du catalogue étant uniques, répéter un ID ne peut pas créer une carte en double. Triez le nouveau tableau par début numérique, puis par ID ASCII en cas d'égalité. Le || du comparateur n'utilise la comparaison des ID que lorsque la différence des horaires vaut zéro. condition ? first : second choisit entre deux valeurs. Le catalogue original est conservé.
Enregistrez un fichier complet avec un here-document. Dans cat > path <<'JS', > remplace le fichier, << fournit les lignes suivantes comme entrée et les guillemets autour de JS empêchent le shell de développer le JavaScript. Le JS final est seul sur sa ligne. Ces commandes permettent de modifier les fichiers dans le terminal ; vous pouvez aussi modifier les mêmes fichiers dans WebIDE et enregistrer avec Ctrl+S. Les trois autres fonctions restent incomplètes pour les prochaines étapes.
cat > src/selection.js <<'JS'
export function currentRecords(ids, catalog) {
return catalog.filter(session => ids.includes(session.id)).sort((a, b) =>
a.start - b.start || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)
);
}
export function totalMinutes(records) {
return 0;
}
export function visibleRecords(catalog, stage) {
return catalog;
}
export function missingNotice(ids, catalog) {
return '';
}
JS
Revenez à la page Work autonome et actualisez normalement le navigateur. View my schedule montre maintenant la véritable carte Garden First Light, 10:00–10:30, avec sa description actuelle. Aucune carte ne doit être inventée pour RETIRED-07. Le résumé conserve ses valeurs initiales et Add/Remove ne sont pas encore reliés ; l'étape suivante les complète. Repérez dans le fichier la comparaison avec session.id : changer l'ordre du catalogue ne doit pas changer l'objet désigné par D11.
Si le navigateur affiche une erreur de module ou de syntaxe, revenez à Workspace, exécutez pwd et cat src/selection.js, comparez imports, noms de fonctions, guillemets et accolades au bloc complet et enregistrez la correction au chemin exact. Revenez ensuite à Work et actualisez normalement. Ne réinitialisez pas la VM et ne réinstallez pas les paquets pour corriger une faute de nom ou de JavaScript. Lancez la vérification de l'étape lorsque la carte actuelle est visible.
Calculer le résumé et mettre à jour une sélection unique
Dans cette étape, reliez Add et Remove à une liste unique d'ID et calculez son nombre de séances et sa durée.
Commencez dans Workspace, à /home/labex/project/festival-schedule/work. Un nombre ou une durée stocké séparément peut devenir incohérent lorsqu'une carte est retirée. Gardez selectedIDs comme état de sélection ; retrouvez les enregistrements actuels à chaque rendu. Leur longueur donne le nombre de séances. reduce combine les enregistrements en une valeur : partez de zéro minute et ajoutez chaque end - start. Les horaires préparés sont des minutes entières sur une même date locale ; aucune analyse Date ni conversion de fuseau n'est nécessaire. D11 dure 30 minutes et D12, 45 : ensemble, elles totalisent 75. Les pauses entre séances ne sont pas du temps de participation.
Remplacez selection.js en conservant la recherche par ID et en complétant la réduction. Le filtre et l'explication des ID absents restent des fonctions initiales pour l'étape 3.
cat > src/selection.js <<'JS'
export function currentRecords(ids, catalog) {
return catalog.filter(session => ids.includes(session.id)).sort((a, b) =>
a.start - b.start || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)
);
}
export function totalMinutes(records) {
return records.reduce((minutes, session) => minutes + session.end - session.start, 0);
}
export function visibleRecords(catalog, stage) {
return catalog;
}
export function missingNotice(ids, catalog) {
return '';
}
JS
Reliez maintenant le composant React. useState fournit chaque valeur d'état et sa fonction de mise à jour. Les ID sélectionnés, la scène visible et les messages ont des rôles différents ; changer de scène ne doit pas remplacer la liste d'ID. Les cartes sélectionnées et le résumé sont des valeurs calculées, pas des états supplémentaires.
La fonction de mise à jour ids => [...ids, id] reçoit le tableau actuel et en crée un nouveau avec la décomposition .... Le retrait crée un nouveau tableau avec filter ; il retire l'ID correspondant, pas toutes les séances portant le même titre. Vérifier un ID déjà sélectionné évite les doublons. find retrouve l'enregistrement réel de l'ID cliqué. feedback est un petit objet transmis au moteur de rendu préparé : sessionID place le résultat d'Add près de la carte concernée et text contient son message lisible. Le moteur de rendu maintient le message visible sans déplacer le focus clavier vers une région de statut dynamique.
clock vient du View.jsx préparé et formate les minutes entières pour l'affichage. describe associe titre, scène et horaire pour distinguer les séances homonymes. Toutes les props ci-dessous ont une source concrète dans l'état ou les enregistrements dérivés. Remplacez entièrement App.jsx :
cat > src/App.jsx <<'JS'
import React, {useState} from 'react';
import View, {clock} from '../../shared/View.jsx';
import {catalog, settings} from './settings.js';
import {currentRecords, totalMinutes, visibleRecords, missingNotice} from './selection.js';
const describe = session => session.title + ' (' + session.stage + ', ' + clock(session.start) + '-' + clock(session.end) + ')';
export default function App() {
const [selectedIDs, setSelectedIDs] = useState(settings.initialIDs);
const [stage, setStage] = useState('all');
const [feedback, setFeedback] = useState({sessionID: null, text: ''});
const selected = currentRecords(selectedIDs, catalog);
const visible = visibleRecords(catalog, stage);
const minutes = totalMinutes(selected);
function add(id) {
const session = catalog.find(item => item.id === id);
if (!session || selectedIDs.includes(id)) return;
setSelectedIDs(ids => [...ids, id]);
setFeedback({sessionID: id, text: 'Added ' + describe(session) + '.'});
}
function remove(id) {
setSelectedIDs(ids => ids.filter(existing => existing !== id));
setFeedback({sessionID: null, text: ''});
}
function changeStage(value) {
setStage(value);
setFeedback({sessionID: null, text: ''});
}
return <View settings={settings} visible={visible} selected={selected}
count={selected.length} minutes={minutes} selectedIDs={selectedIDs}
stage={stage} onStage={changeStage} onAdd={add} onRemove={remove}
feedback={feedback} missingNotice={missingNotice(selectedIDs, catalog)} />;
}
JS
Revenez à Work et actualisez. La sélection synthétique initiale donne 1 session · 30 minutes et la carte Garden First Light. Ajoutez Paper Signals : vous obtenez 2 séances et 75 minutes. Ajoutez First Light à Loft, 12:00–12:30 : les deux First Light doivent rester, avec des scènes et horaires distincts, soit 3 séances et 105 minutes. Retirez uniquement Garden First Light. Paper Signals et la First Light plus tardive de Loft restent : 2 séances et 75 minutes. Cela prouve aussi que la durée additionne le temps de participation, pas l'intervalle entre le premier début et la dernière fin.
Le moteur de rendu fourni utilise l'ID de chaque séance comme clé React. Les clés suivent l'identité quand une liste change ; elles ne remplacent pas votre état de sélection. Le bouton Add devient Added et une nouvelle sélection identique ne doit pas créer de copie. Dans cette unité, le contrôle de scène utilise encore une fonction initiale et les chevauchements ne sont volontairement pas empêchés. N'affirmez pas que toute combinaison est déjà un planning sans conflit. Lancez la vérification lorsque les relations entre cartes et résumé fonctionnent.
Filtrer le programme sans perdre les choix
Dans cette étape, terminez le filtrage par scène et expliquez un ancien ID indisponible tout en conservant les séances valides.
Commencez dans Workspace. visibleRecords filtre uniquement le tableau du programme selon stage === 'all' ou une scène correspondante, puis trie son nouveau tableau par début et ID. Cette fonction ne modifie jamais selectedIDs. missingNotice recherche un ID choisi sans enregistrement actuel. some renvoie true si au moins un élément satisfait une condition ; le some interne vérifie l'existence d'un ID actuel correspondant et ! signifie qu'il n'y en a pas. L'avis est un message normal, pas une erreur qui élimine tout le planning. La fonction peut renvoyer une chaîne vide si rien ne manque.
Enregistrez le fichier auxiliaire complet :
cat > src/selection.js <<'JS'
export function currentRecords(ids, catalog) {
return catalog.filter(session => ids.includes(session.id)).sort((a, b) =>
a.start - b.start || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)
);
}
export function totalMinutes(records) {
return records.reduce((minutes, session) => minutes + session.end - session.start, 0);
}
export function visibleRecords(catalog, stage) {
return catalog.filter(session => stage === 'all' || session.stage === stage).sort((a, b) =>
a.start - b.start || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)
);
}
export function missingNotice(ids, catalog) {
const missing = ids.some(id => !catalog.some(session => session.id === id));
return missing ? 'Some earlier choices are no longer listed. Your available sessions are kept.' : '';
}
JS
Revenez à Work et actualisez. Le D11 préparé reste une véritable carte de 30 minutes. Un avis visible explique que certains anciens choix ne sont plus proposés ; il n'invente pas de titre périmé pour RETIRED-07 et ne supprime pas D11. La source reste le settings.initialIDs public de cette unité indépendante, pas localStorage. Ajoutez de nouveau Paper Signals pour obtenir deux choix totalisant 75 minutes.
Réglez Stage sur Garden. Le programme ne contient plus que les séances de Garden, tandis que View my schedule contient encore Garden First Light et Studio Paper Signals, soit 2 séances et 75 minutes. Remettez Stage sur All stages : Paper Signals est toujours Added. Retirez-la depuis My schedule et confirmez que seul cet ID quitte la liste. L'avis sur l'ID synthétique retiré peut rester ; il n'apporte ni carte ni minute. Une actualisation normale ramène cette première unité à ses ID initiaux préparés, car la persistance n'est pas encore implémentée.
Essayez Tab et Enter sur le contrôle de scène, les liens de navigation et les boutons Add/Remove disponibles. Sur écran étroit, View my schedule reste dans la navigation de la journée et le résultat d'Add apparaît près de la carte cliquée. Comparez titre, scène, horaire et description, pas seulement le nombre. Conservez le focus visible et les libellés utiles du moteur de rendu préparé. Lancez la dernière vérification. Vous disposez maintenant d'une sélection cohérente et d'une vue des données actuelles ; le prochain laboratoire indépendant ajoute les conflits temporels et la restauration du navigateur.
Pour reproduire la référence ci-dessous, revenez à la page Work autonome et actualisez normalement. Cela restaure le choix initial indépendant Garden First Light et l'avis d'ID retiré. Réglez Stage sur All stages. Si Paper Signals n'est pas Added, choisissez son bouton Add to schedule. Comparez alors les deux cartes actuelles et le résumé de 75 minutes avec l'avis distinct de choix indisponible. L'ID retiré ne crée ni carte ni minute.

Résumé
Vous avez construit une sélection React fondée sur des ID stables, retrouvé ses enregistrements dans le catalogue actuel et calculé ses cartes, son nombre de séances et ses minutes programmées. Les séances homonymes sont restées distinctes, le filtrage a conservé les choix et un ID synthétique retiré a reçu une explication claire sans perdre les séances valides.
Cette unité conserve volontairement les choix en mémoire et n'empêche pas les chevauchements. Le laboratoire suivant démarre indépendamment avec cette sélection préparée, puis enseigne les décisions de conflit et le stockage du navigateur sur une même origine.
