diff --git a/front/public/locales/en/stdcm-help-section.json b/front/public/locales/en/stdcm-help-section.json new file mode 100644 index 00000000000..6249aeb6d77 --- /dev/null +++ b/front/public/locales/en/stdcm-help-section.json @@ -0,0 +1,321 @@ +{ + "help": "Help", + "backToIndex": "help index", + "asu": [ + { + "title": "Application name", + "value": "OSRD" + }, + { + "title": "Assistance", + "value": "Support applicatif ASU" + }, + { + "title": "Phone", + "value": "70 70 70 (ou 09 72 72 12 70)" + }, + { + "title": "Application Code (AVAYA)", + "value": "176" + } + ], + "asuLink": "ASU support link", + "sections": { + "application": { + "title": "The application", + "content": [ + { + "type": "text", + "value": "Last Minute Request (LMR) is a tool that allows freight train operators to quickly simulate the feasibility of last-minute path creation requests." + }, + { + "type": "text", + "value": "LMR allows you to:" + }, + { + "type": "list", + "items": [ + { + "value": "Carry out fully independent last-minute capacity search simulations" + }, + { + "value": "Download an LMR simulation sheet" + } + ] + } + ], + "subSections": [ + { + "title": "Improvements", + "content": [ + { + "type": "text", + "value": "We have a great roadmap for making Last Minute Request more relevant and useful to our users. Your feedback is very important for us to learn from your use, and to prioritise these efforts correctly." + }, + { + "type": "text", + "value": "Please don't hesitate to share your ideas and comments." + }, + { + "type": "text", + "value": "Contact us" + } + ] + }, + { + "title": "Design", + "content": [ + { + "type": "text", + "value": "Last Minute Request is part of a wider programme dedicated to facilitating rail operations planning: Open Source Railway Designer. The simulation engine, OSRD, is fed by a representation of the rail network, the various types of rolling stocks, train schedules, and works schedules. SNCF Réseau and other European infrastructure managers are participating in this effort, which is being led by a team of engineers, developers and managers who are passionate about trains." + } + ] + } + ] + }, + "convoy": { + "title": "The consist", + "content": [ + { + "type": "text", + "value": "Defining the consist allows you to obtain a simulation that is as close as possible to reality. The more closely the information you enter matches the actual composition of your consist, the closer the speed of the simulated train will be to operational." + }, + { + "type": "text", + "value": "The tool does not yet allow you to change the composition en route. If you need to do this, enter the most restrictive consist in terms of weight, length and maximum speed." + } + ], + "subSections": [ + { + "title": "Traction unit", + "content": [ + { + "type": "text", + "value": "Enter the first few letters to find the locomotive(s) for your consist in the catalogue." + } + ] + }, + { + "title": "Towed rolling stock", + "content": [ + { + "type": "text", + "value": "In the same way as for the traction unit, enter the first few letters to find the equipment that will make up your consist." + } + ] + }, + { + "title": "Weight, length, max. speed", + "content": [ + { + "type": "text", + "value": "Indicate the total weight of your consist (in tons), the total length (in meters) and the maximum speed (in km/h)." + } + ] + }, + { + "title": "Composition code", + "content": [ + { + "type": "text", + "value": "Select your consist's composition code from the list." + } + ] + } + ] + }, + "path": { + "title": "The route", + "content": [ + { + "type": "text", + "value": "Indicate the origin, destination and any stopping points on your route (excluding turn-offs) to obtain a simulation." + }, + { + "type": "text", + "value": "In the case of linked paths, you can also enter an previous or posterior path." + }, + { + "type": "text", + "value": "The points you enter appear on the map as you enter them." + }, + { + "type": "text", + "value": "If a warning message tells you that no path has been found for these staking points, check all the CIs and CHs on your route, as well as the compatibility of your consist with this route." + } + ], + "subSections": [ + { + "title": "Previous path", + "content": [ + { + "type": "text", + "value": "As an option, the previous path allows you to indicate a path linked upstream of your simulation. Simply search for its number and date of circulation, then click on \"Find\"." + }, + { + "type": "text", + "value": "If several paths match your search, select the one you want from the list." + }, + { + "type": "text", + "value": "Once you have selected your train path, the origin information is pre-filled with a 30-minute margin on the departure time. All fields can still be modified." + } + ] + }, + { + "title": "Origin", + "content": [ + { + "type": "text", + "value": "Enter the CI code using a text search on the name or trigram of your desired point of origin. A CH will appear in the CH selector. Select the correct CH code from the list." + }, + { + "type": "text", + "value": "Enter the start date and time using the mouse or keyboard, then specify the tolerance before and after your start time (in minutes)." + }, + { + "type": "text", + "value": "If you want to impose a timetable on the destination rather than the origin, replace ‘precise timetable’ with ‘respect destination time’, and enter the times in the destination block." + } + ] + }, + { + "title": "Intermediate OP", + "content": [ + { + "type": "text", + "value": "Intermediate OPs are optional and allow you to impose a via or stop at a specific point. Simply enter the CI and CH codes as for the origin, then select the type of staking from the three options: via, driver changeover, service stop." + }, + { + "type": "text", + "value": "In the case of driver changeover or service stop, enter the minimum stop time required, in minutes. To find more solutions, the tool can extend these stops in the simulation." + }, + { + "type": "text", + "value": "N.B.: Turnarounds are not yet taken into account in the app." + } + ] + }, + { + "title": "Destination", + "content": [ + { + "type": "text", + "value": "As for the origin, enter the CI and CH codes." + }, + { + "type": "text", + "value": "If you wish to impose a timetable on the destination rather than the origin, replace ‘as soon as possible’ with ‘precise timetable’. The Date, Time and Tolerance fields then appear and can be filled in as for the origin." + }, + { + "type": "text", + "value": "N.B.: You must enter either an origin time or a destination timetable, not both." + } + ] + }, + { + "title": "Posterior path", + "content": [ + { + "type": "text", + "value": "Optional, it works in the same way as the previous path but acts on the destination rather than the origin." + } + ] + } + ] + }, + "result": { + "title": "The result of the calculation", + "content": [ + { + "type": "text", + "value": "Depending on the capacity available for your train at the times requested, the tool will suggest a simulated path, or inform you of the reasons why this is not possible." + } + ], + "subSections": [ + { + "title": "Path simulation", + "content": [ + { + "type": "text", + "value": "If a path has been found, a table summarising the calculated timetables is displayed. Initially, only the requested milestones are shown, but you can extend this list by clicking on ‘Show all milestones’. The route is also shown on a map." + }, + { + "type": "text", + "value": "If you are happy with the simulated path, click on ‘Keep this simulation’. A new button will appear allowing you to download a simulation form." + }, + { + "type": "text", + "value": "If the simulated path does not suit you, go back to the form and adjust the parameters of your request to run a new simulation. This will then appear next to the previous one once the calculation has been completed." + } + ] + }, + { + "title": "No capacity", + "content": [ + { + "type": "text", + "value": "If no train path has been found, you will be informed of the various conflicts encountered with other train paths or work. Here again, you can go back to the form to adjust the parameters of your request and run a simulation again." + } + ] + } + ] + }, + "glossary": { + "title": "Glossary", + "content": [ + { + "type": "definitions", + "definitions": [ + { + "term": "CI", + "definition": "Permanent Code" + }, + { + "term": "CH", + "definition": "Worksite Code" + }, + { + "term": "LMR", + "definition": "Last Minute Request" + }, + { + "term": "OP", + "definition": "Operational Point" + } + ] + } + ] + }, + "faq": { + "title": "Frequently asked questions", + "content": [ + { + "type": "faq", + "questions": [ + { + "question": "What is a simulation sheet?", + "answer": "A simulation sheet is a document presenting the simulation information as well as the timetable of the proposal made by LMR." + }, + { + "question": "LMR is not offering me a train path, why?", + "answer": "LMR does not create capacity. The tool only proposes a path according to your needs and the residual capacity available at the time of your simulation." + }, + { + "question": "When I run my simulation, is the capacity blocked?", + "answer": "An LMR simulation does not block capacity. The simulation only allows the user to know what capacity is available." + }, + { + "question": "Is it possible to make requests for turnarounds in the app?", + "answer": "LMR does not allow requests for turnarounds. These developments will nevertheless be taken into account for future versions of the app." + }, + { + "question": "Does the tool take side tracks into account?", + "answer": "LMR does not take side tracks into account yet. These developments will nevertheless be taken into account for future versions of the tool." + } + ] + } + ] + } + } +} diff --git a/front/public/locales/fr/stdcm-help-section.json b/front/public/locales/fr/stdcm-help-section.json new file mode 100644 index 00000000000..c406f1bb2cb --- /dev/null +++ b/front/public/locales/fr/stdcm-help-section.json @@ -0,0 +1,354 @@ +{ + "help": "Aide", + "backToIndex": "index de l'aide", + "asu": [ + { + "title": "Nom de l'application", + "value": "OSRD" + }, + { + "title": "Assistance", + "value": "Support applicatif ASU" + }, + { + "title": "Téléphone", + "value": "70 70 70 (ou 09 72 72 12 70)" + }, + { + "title": "Code Application (AVAYA)", + "value": "176" + } + ], + "asuLink": "Lien vers le support ASU", + "sections": { + "application": { + "title": "L'application", + "content": [ + { + "type": "text", + "value": "Last Minute Request (LMR) est un outil qui permet aux exploitants ferroviaires fret clients de SNCF Réseau de simuler rapidement la faisabilité des demandes de création de sillon de dernière minute." + }, + { + "type": "text", + "value": "LMR permet de :" + }, + { + "type": "list", + "items": [ + { + "value": "Réaliser des simulations de recherche de capacité de dernière minute en totale autonomie" + }, + { + "value": "Joindre aux demandes de création de sillon de dernière minute dans GESICO, une fiche de simulation LMR permettant de simplifier les gestes métier des horairistes" + } + ] + } + ], + "subSections": [ + { + "title": "Périmètre", + "content": [ + { + "type": "text", + "value": "Mis en service au tout début de l’année 2025, Last Minute Request fait ses premiers pas dans un périmètre limité pour pouvoir l’évaluer et le corriger avec le moins d’impact sur votre opérationnel." + }, + { + "type": "text", + "value": "Vos requêtes sont pour le moment possibles uniquement entre Perrigny et Miramas, entre J-7 et J-1 17 heures, et ne prennent pas en compte les ATEs, matières dangereuses, et gabarits GB." + } + ] + }, + { + "title": "Améliorations", + "content": [ + { + "type": "text", + "value": "Nous avons une belle feuille de route pour rendre Last Minute Request plus pertinent et utile pour vous. Vos retours sont très importants pour que nous apprenions de votre utilisation, et pour que nous puissions prioriser correctement ces efforts. N’hésitez surtout pas à communiquer vos idées et commentaires." + }, + { + "type": "text", + "value": "Contactez-nous" + } + ] + }, + { + "title": "Conception", + "content": [ + { + "type": "text", + "value": "Last Minute Request fait partie d’un programme plus large dédié à faciliter la planification de l’opérationnel ferroviaire : Open Source Railway Designer. Le moteur de simulation, OSRD, est nourri par une représentation du réseau ferré, des différents matériels roulants, et des calendriers de circulations et travaux. SNCF Réseau et d’autres gestionnaires d’infrastructures européens participent à cet effort porté par une équipe d’ingénieurs, de développeurs et d’encadrants passionnés par le train." + } + ] + } + ] + }, + "convoy": { + "title": "Le convoi", + "content": [ + { + "type": "text", + "value": "Définir le convoi permet d'obtenir une simulation au plus proche de la réalité. Plus les informations que vous indiquez sont conformes à la composition réelle de votre convoi, plus la vitesse du train simulé sera proche de l'opérationnel." + }, + { + "type": "text", + "value": "L'outil ne permet pas encore de changer de composition en cours de route. Si vous avez ce besoin, indiquez le convoi le plus restrictif en termes de tonnage, longueur, et vitesse maximale." + } + ], + "subSections": [ + { + "title": "Engin de traction", + "content": [ + { + "type": "text", + "value": "Indiquez les premières lettres pour trouver la ou les locomotives de votre convoi dans le catalogue." + } + ] + }, + { + "title": "Matériel remorqué", + "content": [ + { + "type": "text", + "value": "De la même façon que pour l'engin de traction, indiquez les premières lettres pour retrouver le matériel qui composera votre convoi." + } + ] + }, + { + "title": "Tonnage, longueur, vitesse max.", + "content": [ + { + "type": "text", + "value": "Indiquez le tonnage total de votre convoi (en tonnes), la longueur totale (en mètres), et la vitesse maximale (en km/h)." + } + ] + }, + { + "title": "Code de composition", + "content": [ + { + "type": "text", + "value": "Sélectionnez le code de composition de votre convoi parmi la liste." + } + ] + } + ] + }, + "path": { + "title": "Le parcours", + "content": [ + { + "type": "text", + "value": "Indiquez l'origine, la destination, et les éventuels points de passage et arrêts de votre parcours (hors rebroussements) pour pouvoir obtenir une simulation. Ceux-ci sont limités à l'axe Perrigny-Miramas dans un premier temps." + }, + { + "type": "text", + "value": "En cas de sillons liés, vous pouvez également renseigner un sillon antérieur ou postérieur." + }, + { + "type": "text", + "value": "Les points que vous renseignez apparaissent sur la carte au fur et à mesure de la saisie." + }, + { + "type": "text", + "value": "Si un message d'avertissement vous indique qu'aucun chemin n'a été trouvé pour ces points de jalonnement, vérifiez tous les CI et CH de votre parcours, ainsi que la compatibilité de votre convoi avec cet axe." + } + ], + "subSections": [ + { + "title": "Sillon antérieur", + "content": [ + { + "type": "text", + "value": "Optionnel, le sillon antérieur vous permet d'indiquer un sillon lié en amont de votre simulation. Recherchez simplement son numéro et sa date de circulation, puis cliquez sur \"Trouver\"." + }, + { + "type": "text", + "value": "Si plusieurs sillons correspondent à votre recherche, indiquez celui que vous souhaitez parmi la liste proposée." + }, + { + "type": "text", + "value": "Une fois votre sillon sélectionné, les informations de l'origine sont pré-remplies avec une marge de 30 minutes sur l'horaire de départ. Tous les champs restent néanmoins modifiables." + } + ] + }, + { + "title": "Origine", + "content": [ + { + "type": "text", + "value": "Renseignez le code CI avec une recherche textuelle sur le nom ou le trigramme de votre point d'origine souhaité. Un code chantier apparaît alors dans le sélecteur de CH. Sélectionnez le bon code CH parmi la liste proposée." + }, + { + "type": "text", + "value": "Renseignez la date et l’heure de départ via la souris ou le clavier, puis indiquez la tolérance​ avant et après votre heure de départ (en minutes)." + }, + { + "type": "text", + "value": "Si vous souhaitez imposer un horaire à la destination plutôt qu'à l'origine, remplacez \"horaire précis\" par \"respecter l'horaire de destination\", et indiquez les horaires dans le bloc de la destination." + } + ] + }, + { + "title": "PR intermédiaire", + "content": [ + { + "type": "text", + "value": "Optionnels, les PR intermédiaires vous permettent d'imposer un passage ou un arrêt à un endroit précis. Renseignez simplement les codes CI et CH comme pour l'origine, puis le type de jalonnement parmi les trois options : passage, relève conducteur, arrêt de service." + }, + { + "type": "text", + "value": "Dans le cas de relève conducteur ou arrêt de service, indiquez le temps d'arrêt minimum souhaité, en minutes. Pour trouver davantage de solutions, l'outil peut rallonger ces arrêts dans la simulation." + }, + { + "type": "text", + "value": "N.B. : Les rebroussements ne sont pas encore pris en compte dans l'outil." + } + ] + }, + { + "title": "Destination", + "content": [ + { + "type": "text", + "value": "Comme pour l'origine, renseignez les codes CI et CH." + }, + { + "type": "text", + "value": "Si vous souhaitez imposer un horaire à la destination plutôt qu'à l'origine, remplacez \"dès que possible\" par \"horaire précis\". Les champs Date, Heure, et Tolérance apparaissent alors et peuvent être remplis comme pour l'origine." + }, + { + "type": "text", + "value": "N.B. : Il faut renseigner soit un horaire d’origine soit un horaire de destination, pas les deux." + } + ] + }, + { + "title": "Sillon postérieur", + "content": [ + { + "type": "text", + "value": "Optionnel, il fonctionne de la même façon que le sillon antérieur mais agit sur la destination plutôt que sur l'origine." + } + ] + } + ] + }, + "result": { + "title": "Le résultat du calcul", + "content": [ + { + "type": "text", + "value": "En fonction de la capacité disponible pour votre convoi aux horaires demandés, l'outil vous proposera une simulation de sillon, ou vous informera des raisons qui la rendent impossible." + } + ], + "subSections": [ + { + "title": "La simulation de sillon", + "content": [ + { + "type": "text", + "value": "Dans le cas où un sillon a été trouvé, un tableau récapitulatif des horaires calculés vous est proposé. Initialement, seuls les jalons demandés sont présents mais vous pouvez étendre cette liste en cliquant sur \"Afficher tous les jalons\". Vous disposez également d'une représentation du parcours sur une carte." + }, + { + "type": "text", + "value": "Si le sillon simulé vous convient, cliquez sur \"Retenir cette simulation\". Un nouveau bouton apparaît alors vous permettant de télécharger une fiche de simulation, à insérer en tant que pièce jointe dans la demande GESICO." + }, + { + "type": "text", + "value": "Si le sillon simulé ne vous convient pas, remontez dans le formulaire et ajustez les paramètres de votre demande pour lancer une nouvelle simulation. Celle-ci apparaîtra alors à côté de la précédente une fois le calcul terminé." + } + ] + }, + { + "title": "Pas de capacité", + "content": [ + { + "type": "text", + "value": "Dans le cas où aucun sillon n'a pu être trouvé, vous êtes informés des différents conflits rencontrés avec d'autres sillons ou travaux. Vous pouvez là aussi remonter dans le formulaire pour ajuster les paramètres de votre demande et relancer une simulation." + } + ] + } + ] + }, + "glossary": { + "title": "Glossaire", + "content": [ + { + "type": "definitions", + "definitions": [ + { + "term": "CI", + "definition": "Code Immuable" + }, + { + "term": "CH", + "definition": "Code Chantier" + }, + { + "term": "LMR", + "definition": "Last Minute Request" + }, + { + "term": "PR", + "definition": "Point Remarquable" + }, + { + "term": "SDM", + "definition": "Sillon de Dernière Minute" + } + ] + } + ] + }, + "faq": { + "title": "Foire aux questions", + "content": [ + { + "type": "faq", + "questions": [ + { + "question": "Qu’est-ce qu’une fiche de simulation ?", + "answer": "Une fiche de simulation est un document présentant les informations de la simulation ainsi que le jalonnement horairisé de la proposition faite par LMR. La fiche peut être téléchargée par l’utilisateur et ensuite introduite dans une demande GESICO pour une prise en compte et vérification par l’horairiste." + }, + { + "question": "J’ai joint une fiche de simulation et mon sillon est refusé, pourquoi ?", + "answer": "Une fois votre fiche de simulation jointe à la demande dans GESICO, l’horairiste procède à une étape de validation prenant en compte des paramètres tels que la capacité en gare, l’horodatage et autres règles d’exploitation. Si après vérification le sillon ne peut plus être attribué, il sera refusé." + }, + { + "question": "LMR ne me propose pas de sillon, pourquoi ?", + "answer": "LMR ne créé pas de la capacité. L’outil ne vous propose un sillon qu’en fonction de votre besoin et de la capacité résiduelle disponible au moment de votre simulation." + }, + { + "question": "Puis-je passer par LMR pour passer une commande de création de sillon ?", + "answer": "LMR est un outil de simulation. Il ne permet en aucun cas de faire des demandes de SDM mais uniquement des requêtes de simulation. Les demandes de SDM se font dans GESICO avec la possibilité de joindre une fiche de simulation issue de LMR." + }, + { + "question": "Est-il possible de simuler une demande de sillon intégrant un ATE ?", + "answer": "Non, pour le moment les simulations se font uniquement pour des sillons simples, sans prescription de sécurité." + }, + { + "question": "Quand j’ai fait ma simulation, est-ce que la capacité est bloquée ?", + "answer": "Une simulation LMR ne bloque pas la capacité. La simulation permet uniquement à l’utilisateur de savoir quelle est la capacité disponible et ensuite de faire une demande « éclairée » dans l’outil GESICO." + }, + { + "question": "Est-ce possible de faire une simulation sur une Origine Destination en dehors de Perrigny-Miramas ?", + "answer": "Le périmètre du démonstrateur est limité uniquement à l’OD Perrigny-Miramas. Toutefois, des développements d’extension de l’OD sont prévus ultérieurement." + }, + { + "question": "Est-ce possible de faire une simulation à J-10 ?", + "answer": "Le périmètre du démonstrateur est limité entre J-7 et J-1 17h, c’est-à-dire uniquement pour des sillons de dernière minute." + }, + { + "question": "Existe-t-il une possibilité de faire des demandes de rebroussement dans l’outil ?", + "answer": "Dans le cadre du démonstrateur, l’outil LMR ne permet pas de faire des demandes de rebroussement. Ces développements sont néanmoins pris en compte pour de prochaines versions de l’outil." + }, + { + "question": "L’outil prend-t-il en compte les voies d’évitement ?", + "answer": "Dans le cadre du démonstrateur, LMR ne prend pas en compte les voies d’évitement. Ces développements sont néanmoins pris en compte pour de prochaines versions de l’outil." + } + ] + } + ] + } + } +} diff --git a/front/scripts/i18n-checker.ts b/front/scripts/i18n-checker.ts index 5ddd74911b4..9b5ec564a29 100644 --- a/front/scripts/i18n-checker.ts +++ b/front/scripts/i18n-checker.ts @@ -21,6 +21,8 @@ const IGNORE_MISSING: RegExp[] = [ /translation:destinationTime/, /translation:leaveAt/, /stdcm:simulation.results/, + /stdcm-help-section:asu/, + /stdcm-help-section:sections/, /translation:remainingTrackConflicts/, /translation:remainingWorkConflicts/, /translation:trackConflict/, diff --git a/front/src/applications/stdcm/components/StdcmHeader.tsx b/front/src/applications/stdcm/components/StdcmHeader.tsx index 6216172e933..c107f1d609a 100644 --- a/front/src/applications/stdcm/components/StdcmHeader.tsx +++ b/front/src/applications/stdcm/components/StdcmHeader.tsx @@ -8,10 +8,17 @@ import { getIsSuperUser } from 'reducers/user/userSelectors'; type StdcmHeaderProps = { isDebugMode: boolean; onDebugModeToggle: React.Dispatch>; + toggleHelpModule: () => void; + showHelpModule: boolean; }; -const StdcmHeader = ({ isDebugMode, onDebugModeToggle }: StdcmHeaderProps) => { - const { t } = useTranslation('stdcm'); +const StdcmHeader = ({ + isDebugMode, + onDebugModeToggle, + toggleHelpModule, + showHelpModule, +}: StdcmHeaderProps) => { + const { t } = useTranslation(['stdcm', 'translation']); const isSuperUser = useSelector(getIsSuperUser); return ( @@ -19,20 +26,28 @@ const StdcmHeader = ({ isDebugMode, onDebugModeToggle }: StdcmHeaderProps) => { ST DCM
- {t('notificationTitle')} + {t('stdcm:notificationTitle')}
{isSuperUser && ( -
+
+
)}
diff --git a/front/src/applications/stdcm/components/StdcmHelpModule/HelpSection.tsx b/front/src/applications/stdcm/components/StdcmHelpModule/HelpSection.tsx new file mode 100644 index 00000000000..dc2dbf39fd5 --- /dev/null +++ b/front/src/applications/stdcm/components/StdcmHelpModule/HelpSection.tsx @@ -0,0 +1,53 @@ +import { ArrowLeft } from '@osrd-project/ui-icons'; +import cx from 'classnames'; +import { useTranslation } from 'react-i18next'; + +import SectionContentManager from './SectionContentManager'; +import type { Section } from './types'; + +type HelpSectionProps = { + section: string; + isActive: boolean; + closeHelpSection: () => void; +}; + +const HelpSection = ({ section, isActive, closeHelpSection }: HelpSectionProps) => { + const { t } = useTranslation('stdcm-help-section'); + const currentSection = t(`sections.${section}`, { returnObjects: true }) as Section; + + return ( +
+
+ +
+
+

+ {currentSection.title} +

+ {Array.isArray(currentSection.content) && + currentSection.content.map((content, index) => ( + + ))} + {currentSection.subSections?.map((subSection) => ( +
+

{subSection.title}

+ {subSection.content?.map((content, idx) => ( + + ))} +
+ ))} +
+
+ ); +}; + +export default HelpSection; diff --git a/front/src/applications/stdcm/components/StdcmHelpModule/SectionContentManager.tsx b/front/src/applications/stdcm/components/StdcmHelpModule/SectionContentManager.tsx new file mode 100644 index 00000000000..389512be8e5 --- /dev/null +++ b/front/src/applications/stdcm/components/StdcmHelpModule/SectionContentManager.tsx @@ -0,0 +1,57 @@ +import type { Content } from './types'; + +type SectionContentManagerProps = { + content: Content; +}; + +const SectionContentManager = ({ content }: SectionContentManagerProps) => ( +
+ {(() => { + switch (content.type) { + case 'list': + return ( + + ); + + case 'text': + // eslint-disable-next-line react/no-danger + return

; + case 'faq': + return ( +

    + {content.questions && + content.questions.map((question, index) => ( +
  1. +

    + {index + 1}. {question.question} +

    +

    {question.answer}

    +
  2. + ))} +
+ ); + case 'definitions': + return ( + + + {content.definitions.map((item) => ( + + + + + ))} + +
{item.term}{item.definition}
+ ); + default: + return null; + } + })()} +
+); + +export default SectionContentManager; diff --git a/front/src/applications/stdcm/components/StdcmHelpModule/StdcmHelpModule.tsx b/front/src/applications/stdcm/components/StdcmHelpModule/StdcmHelpModule.tsx new file mode 100644 index 00000000000..9d8e318fa98 --- /dev/null +++ b/front/src/applications/stdcm/components/StdcmHelpModule/StdcmHelpModule.tsx @@ -0,0 +1,78 @@ +import { useState } from 'react'; + +import { ChevronRight, X } from '@osrd-project/ui-icons'; +import cx from 'classnames'; +import { useTranslation } from 'react-i18next'; + +import HelpSection from './HelpSection'; + +type StdcmHelpSectionProps = { + toggleHelpModule: () => void; + showHelpModule: boolean; +}; + +const StdcmHelpModule = ({ toggleHelpModule, showHelpModule }: StdcmHelpSectionProps) => { + const { t } = useTranslation('stdcm-help-section'); + + const [activeSection, setActiveSection] = useState(null); + + const closeHelpModule = () => { + setActiveSection(null); + toggleHelpModule(); + }; + const closeHelpSection = () => setActiveSection(null); + const support = t('asu', { returnObjects: true }) as { title: string; value: string }[]; + const sections = Object.keys(t('sections', { returnObjects: true })); + return ( +
+
+ +
+
+

{t('help')}

+
+ {sections.map((section, index) => ( +
+ + {index !== sections.length - 1 &&
} +
+ ))} +
+
+
+ {Array.isArray(support) && + support.map((item, index) => ( +
+
+
{item.title}
+
{item.value}
+
+ {index !== support.length - 1 &&
} +
+ ))} +
+ + {t('asuLink')} + +
+
+ {sections.map((section) => ( + + ))} +
+ ); +}; + +export default StdcmHelpModule; diff --git a/front/src/applications/stdcm/components/StdcmHelpModule/types.ts b/front/src/applications/stdcm/components/StdcmHelpModule/types.ts new file mode 100644 index 00000000000..8c008d2d4df --- /dev/null +++ b/front/src/applications/stdcm/components/StdcmHelpModule/types.ts @@ -0,0 +1,38 @@ +export type Section = { + id: string; + title: string; + content?: Content[]; + subSections?: Omit[]; +}; + +export type Content = ListContent | TextContent | FAQContent | GlossaryContent; + +type ListContent = { + type: 'list'; + items: { value: string }[]; +}; + +type TextContent = { + type: 'text'; + value: string; +}; + +type FAQItem = { + question: string; + answer: string; +}; + +type FAQContent = { + type: 'faq'; + questions: FAQItem[]; +}; + +type GlossaryContent = { + type: 'definitions'; + definitions: GlossaryItem[]; +}; + +type GlossaryItem = { + term: string; + definition: string; +}; diff --git a/front/src/applications/stdcm/views/StdcmView.tsx b/front/src/applications/stdcm/views/StdcmView.tsx index a0097351b17..824a02d5612 100644 --- a/front/src/applications/stdcm/views/StdcmView.tsx +++ b/front/src/applications/stdcm/views/StdcmView.tsx @@ -12,6 +12,7 @@ import { replaceElementAtIndex } from 'utils/array'; import StdcmEmptyConfigError from '../components/StdcmEmptyConfigError'; import StdcmConfig from '../components/StdcmForm/StdcmConfig'; import StdcmHeader from '../components/StdcmHeader'; +import StdcmHelpModule from '../components/StdcmHelpModule/StdcmHelpModule'; import StdcmResults from '../components/StdcmResults'; import StdcmStatusBanner from '../components/StdcmStatusBanner'; import useStdcmEnvironment, { NO_CONFIG_FOUND_MSG } from '../hooks/useStdcmEnv'; @@ -27,6 +28,7 @@ const StdcmView = () => { const [retainedSimulationIndex, setRetainedSimulationIndex] = useState(-1); const [showBtnToLaunchSimulation, setShowBtnToLaunchSimulation] = useState(false); const [isDebugMode, setIsDebugMode] = useState(false); + const [showHelpModule, setShowHelpModule] = useState(false); const { launchStdcmRequest, @@ -65,6 +67,8 @@ const StdcmView = () => { dispatch(resetStdcmConfig()); }; + const toggleHelpModule = () => setShowHelpModule((show) => !show); + // reset config data with the selected simulation data useEffect(() => { if (selectedSimulation) { @@ -171,7 +175,12 @@ const StdcmView = () => { return (
setShowStatusBanner(false)}> - + {!isNil(error) ? ( @@ -206,6 +215,7 @@ const StdcmView = () => { )}
)} + )} {loading && } diff --git a/front/src/styles/scss/applications/_stdcm.scss b/front/src/styles/scss/applications/_stdcm.scss index 818e0319960..5754dda8889 100644 --- a/front/src/styles/scss/applications/_stdcm.scss +++ b/front/src/styles/scss/applications/_stdcm.scss @@ -1,6 +1,7 @@ @import 'stdcm/card'; @import 'stdcm/configError'; @import 'stdcm/header'; +@import 'stdcm/helpModule'; @import 'stdcm/home'; @import 'stdcm/linkedPath'; @import 'stdcm/loader'; diff --git a/front/src/styles/scss/applications/stdcm/_header.scss b/front/src/styles/scss/applications/stdcm/_header.scss index 5b7627bea1f..43762482a07 100644 --- a/front/src/styles/scss/applications/stdcm/_header.scss +++ b/front/src/styles/scss/applications/stdcm/_header.scss @@ -6,8 +6,9 @@ display: flex; align-items: center; justify-content: space-between; + padding-right: 50px; - .stdcm-header__title { + &__title { max-height: 48px; color: rgba(0, 0, 0, 1); font-size: 2rem; @@ -18,7 +19,7 @@ line-height: 40px; } - .stdcm-header__notification { + &__notification { max-height: 48px; border-radius: 2px; border: 4px solid rgba(112, 193, 229, 1); @@ -42,36 +43,42 @@ } } - .stdcm-header_debug { - margin-right: 48px; + &__debug { button:focus { outline: none; } button { height: 40px; - width: 40px; border-radius: 6px; + color: var(--primary50); + border: 1px solid var(--primary30); + &:hover { + color: var(--primary80); + border: 1px solid var(--primary80); + background-color: var(--white100); + } + &:focus { + outline: none; + } &:disabled { pointer-events: none; opacity: 0.5; } - &.debug-on { - box-shadow: 0 0 0 2px rgba(255, 255, 255) inset; + &.selected { + box-shadow: 0 0 0 2px var(--white100) inset; border: 1px solid rgba(92, 89, 85); background-color: rgba(255, 242, 179); &:hover { - border: 3px solid rgba(255, 255, 255); - border: 1px solid rgba(0, 0, 0); - background-color: rgba(0, 0, 0, 0.05); + border: 3px solid var(--white100); + border: 1px solid var(--black100); + background-color: var(--black5); } } - &.debug-off { - color: rgba(114, 168, 247, 1); - border: 1px solid rgba(114, 168, 247); + &.debug { + width: 40px; + color: var(--primary30); &:hover { - color: rgba(31, 15, 150); - border: 1px solid rgba(31, 15, 150); - background-color: rgba(255, 255, 255); + color: var(--primary80); } } } diff --git a/front/src/styles/scss/applications/stdcm/_helpModule.scss b/front/src/styles/scss/applications/stdcm/_helpModule.scss new file mode 100644 index 00000000000..5d1048eb7b9 --- /dev/null +++ b/front/src/styles/scss/applications/stdcm/_helpModule.scss @@ -0,0 +1,141 @@ +.stdcm__help-module { + transition: all 0.3s; + position: fixed; + top: 0; + right: -100%; + height: 100vh; + width: 520px; + background-color: var(--white100); + box-shadow: + 0 6px 21px -5px rgba(24, 68, 239, 0.26), + 0 16px 30px -5px rgba(0, 0, 0, 0.16), + 0 3px 5px -2px rgba(0, 0, 0, 0.1); + padding: 72px 48px 72px 56px; + display: flex; + flex-direction: column; + &.active { + right: 0; + } + &__close { + position: absolute; + top: 24px; + right: 24px; + color: var(--grey30); + } + &__title { + color: rgba(0, 0, 0, 1); + font-size: 2.5rem; + font-weight: 600; + letter-spacing: -0.8px; + line-height: 48px; + } + &__chapters { + padding-top: 58px; + button { + color: var(--primary60); + font-weight: 600; + width: 100%; + padding-top: 14px; + padding-bottom: 14px; + .icon { + color: var(--grey30); + } + } + hr { + margin-top: 4px; + margin-bottom: 4px; + background-color: var(--grey20); + } + } + .stdcm__help-section { + background-color: var(--white100); + transition: all 0.3s; + position: fixed; + top: 72px; + padding-left: 24px; + right: -100%; + height: calc(100vh - 72px); + overflow-y: hidden; + width: inherit; + &.active { + right: 0; + } + &__header { + margin-bottom: 75px; + } + &__back-button { + color: var(--grey50); + font-size: 1.125rem; + font-weight: 600; + width: 100%; + .icon { + color: var(--primary60); + } + } + &__title { + font-size: 2rem; + font-weight: 600; + margin-bottom: 46px; + color: var(--black100); + } + &__subtitle { + font-size: 1.5rem; + font-weight: 600; + height: 32px; + margin-top: 41px; + margin-bottom: 7px; + } + &__content { + padding-left: 32px; + padding-right: 48px; + padding-bottom: 72px; + overflow-y: auto; + height: calc(100% - 40px); + .section__content { + ul { + list-style-type: disc; + list-style-position: outside; + padding-left: 15px; + } + code { + padding-left: 2px; + padding-right: 2px; + background-color: var(--grey10); + color: var(--grey60); + } + } + } + } + footer { + margin-top: auto; + + .support-info { + width: inherit; + display: flex; + justify-content: space-between; + &__title { + font-size: 1rem; + font-weight: 600; + line-height: 24px; + color: var(--black100); + } + &__content { + font-size: 1rem; + font-weight: 400; + line-height: 24px; + color: var(--black100); + } + } + hr { + background-color: var(--grey20); + margin: 2px 0px; + } + .support-asu__link { + margin-top: 31px; + text-align: right; + color: var(--primary60); + font-size: 1.125rem; + font-weight: 600; + } + } +} diff --git a/front/src/styles/scss/applications/stdcm/_home.scss b/front/src/styles/scss/applications/stdcm/_home.scss index 02bdd9c99c0..ba869b3be5f 100644 --- a/front/src/styles/scss/applications/stdcm/_home.scss +++ b/front/src/styles/scss/applications/stdcm/_home.scss @@ -1,6 +1,7 @@ .stdcm { font-family: 'IBM Plex Sans'; min-width: 1360px; + position: relative; /* We set the cursor to default to avoid the pointer cursor that appears due to role="button" on the div.