diff --git a/src/App.tsx b/src/App.tsx index 78806ca..ae00420 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -150,7 +150,7 @@ const Header = () => { diff --git a/src/i18n/de.json b/src/i18n/de.json index 4c78086..c4c8fa7 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -182,8 +182,68 @@ "content": "Inhalt", "downloads": "Hilfsblatt herunterladen" }, - "new_completion_project_page": { - "title": "Fragebogen" + "new_project_completion_page": { + "title": "Fragebogen", + "result": { + "hints": { + "title": "ToDo", + "defaults": { + "contact_itkom": "Kontaktiere die IT-Kommission, damit das Tool in der IT-Landschaft aktualisiert werden kann.", + "fill_concept": "Fülle das Betriebskonzept aus un reiche es ein, eine Vorlage findest du im Downloadbereich." + } + }, + "answers": { + "title": "Deine Antworten" + } + }, + "questions": { + "next": "weiter", + "landscape": { + "text": "Ist das Tool bereits in der IT-Landaschaft dokumentiert?", + "responses": { + "yes": "Ja", + "no": "Nein" + }, + "landscape_no": "Das Tool ist nicht in der IT-Landschaft dokumentiert. Bitte kontaktiere die IT-Kommission, damit das Tool in der IT-Landschaft aufgenommen werden kann" + }, + "roles": { + "text": "Welche Rollen sind für das Tool definiert?", + "responses": { + "po": "Product Owner", + "technical_contact": "Technischer Kontakt", + "content_management": "Content Management" + } + }, + "artifacts": { + "text": "Welche Artefakte sind für das Tool erstellt worden?", + "responses": { + "documentation": "Dokumentation / Architektur", + "passwords": "Passwörter / Zugriffe", + "code": "Sourcecode", + "api_documentation": "API Dokumentation", + "specifications": "Spezifikationen" + } + }, + "budget": { + "text": "Sind die laufenden Kosten für den Betrieb des Tools geklärt?", + "responses": { + "no_costs": "Es fallen keine Kosten an", + "yes_defined": "Ja, es sind alle Kosten definiert", + "not_defined": "Weiss nicht, ob es Kosten gibt" + }, + "budget_no_costs": "Keine Kosten? Bitte prüf dies nochmals", + "budget_yes_defined": "Super, die Kosten sind definiert. Bitte stelle sicher, dass diese auch im Budget der PBS berücksichtigt werden.", + "budget_not_defined": "Bitte kläre die Kosten ab, damit die PBS sie für den Betrieb des Tools berücksichtigen kann." + }, + "tasks": { + "text": "Welche Aufgaben fallen an, um das Tool zu betreiben und zu unterhalten?", + "responses": { + "none": "Es gibt keine Aufgaben", + "still_open": "Weiss nicht, ob es Aufgaben gibt", + "defined": "Ja, es sind alle Aufgaben definiert" + } + } + } }, "principles_page": { "title": "Prinzipien", @@ -391,4 +451,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 84660e1..4aacfef 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -182,8 +182,68 @@ "content": "Contenu", "downloads": "# Hilfsblatt herunterladen" }, - "new_completion_project_page": { - "title": "# Fragebogen" + "new_project_completion_page": { + "title": "Questionnaire", + "result": { + "hints": { + "title": "À faire", + "defaults": { + "contact_itkom": "Contacte la Commission IT pour que l’outil soit mis à jour dans le paysage IT.", + "fill_concept": "Remplis le concept d’exploitation et soumets-le, un modèle se trouve dans la zone de téléchargement." + } + }, + "answers": { + "title": "Tes réponses" + } + }, + "questions": { + "next": "suivant", + "landscape": { + "text": "L’outil est-il déjà documenté dans le paysage IT ?", + "responses": { + "yes": "Oui", + "no": "Non" + }, + "landscape_no": "L’outil n’est pas documenté dans le paysage IT. Merci de contacter la Commission IT pour qu’il soit intégré." + }, + "roles": { + "text": "Quelles sont les rôles définis pour l’outil ?", + "responses": { + "po": "Product Owner", + "technical_contact": "Contact technique", + "content_management": "Gestion du contenu" + } + }, + "artifacts": { + "text": "Quels artefacts ont été créés pour l’outil ?", + "responses": { + "documentation": "Documentation / Architecture", + "passwords": "Mots de passe / Accès", + "code": "Code source", + "api_documentation": "Documentation API", + "specifications": "Spécifications" + } + }, + "budget": { + "text": "Les coûts d’exploitation de l’outil sont-ils clarifiés ?", + "responses": { + "no_costs": "Aucun coût", + "yes_defined": "Oui, tous les coûts sont définis", + "not_defined": "Je ne sais pas s’il y a des coûts" + }, + "budget_no_costs": "Aucun coût ? Merci de vérifier à nouveau.", + "budget_yes_defined": "Super, les coûts sont définis. Merci de t’assurer qu’ils sont pris en compte dans le budget du MSdS.", + "budget_not_defined": "Merci de clarifier les coûts afin que le MSdS puisse les prendre en compte pour l’exploitation de l’outil." + }, + "tasks": { + "text": "Quelles tâches sont nécessaires pour exploiter et entretenir l’outil ?", + "responses": { + "none": "Aucune tâche", + "still_open": "Je ne sais pas s’il y a des tâches", + "defined": "Oui, toutes les tâches sont définies" + } + } + } }, "principles_page": { "title": "Principes", diff --git a/src/pages/CompletionPage.tsx b/src/pages/CompletionPage.tsx index 2fc9f12..6d9fba9 100644 --- a/src/pages/CompletionPage.tsx +++ b/src/pages/CompletionPage.tsx @@ -28,7 +28,7 @@ export default function CompletionPage() {

{t('completion_page.title')}

{t('completion_page.introduction')}

- {t('completion_page.complete_project')} + {t('completion_page.complete_project')}
diff --git a/src/pages/completion/NewCompletionProjectPage.tsx b/src/pages/completion/NewCompletionProjectPage.tsx index 8535b40..4e85ca1 100644 --- a/src/pages/completion/NewCompletionProjectPage.tsx +++ b/src/pages/completion/NewCompletionProjectPage.tsx @@ -2,15 +2,193 @@ import { useTranslation } from 'react-i18next'; import { Helmet } from 'react-helmet' import { MainContainer } from '../../App' +import { Checkbox, State } from '../digitalisation/NewDigitalisationProjectPage'; +import { useState } from 'react'; +import { Question, Response, Root, Reply } from '../../types'; +import ClippyStage from '../../components/ClippyStage'; +import questionJson from './completion_questions.json'; +import { Button, ButtonContainer } from '../HomePage'; + +const defaultState = (): State => ({ currentSlide: 'projectPhase', clippyVariant: 'focus' }) +const defaultReplies = (): Reply[] => ([]) export default function NewCompletionProjectPage() { + const [state] = useState(defaultState) + const [replies, setReplies] = useState(defaultReplies) + const [currentQuestion, setCurrentQuestion] = useState(0) + const [showResult, setShowResult] = useState(false); const { t } = useTranslation() + const questionsRoot: Root = questionJson + const questions: Question[] = questionsRoot.questions + + function nextQuestion(option: Response) { + let nextQuestion = currentQuestion + 1; + if (option.next_question) { + nextQuestion = option.next_question + } + + if (nextQuestion < questionJson.questions.length) { + setCurrentQuestion(nextQuestion); + } else { + setShowResult(true) + } + return + } + function replyWith(option: Response, question: Question, event?: React.ChangeEvent) { + if (event) { + if (event.target.checked) { + + const existing = replies.find((r: Reply) => r.id === question.key + option.key) + if (existing) { + setReplies(replies.filter(a => + a.id !== question.key + option.key + )) + } else { + setReplies([ + ...replies, + { id: question.key + option.key, question: question, response: option } + ]) + } + } else { + setReplies(replies.filter(a => + a.id !== question.key + option.key + )) + } + return + } + + nextQuestion(option) + + const found = replies.find((r: Reply) => r.question.key === question.key) + if (found) { + found.response = option + const newReplies = replies.map((r: Reply) => r.question.key === question.key ? found : r) + setReplies(newReplies) + } else { + setReplies([ + ...replies, + { id: `${question.key}-${option.key}`, question: question, response: option } + ]) + } + } + + function printOptions(question: Question) { + if (question.multiple_choice && question.multiple_choice === true) { + const checkboxes = question.responses.map(function (option: Response) { + return
+ +
+ }) + return
+ {checkboxes}
+ +
+ } + + return question.responses.map(function (option: Response) { + return + }) + } + + function splitReplies(replies: Reply[]) { + // Single-choice replies (not multiple_choice) + const singleChoiceReplies = replies.filter( + (reply) => !reply.question.multiple_choice + ); + + // Grouped multiple-choice replies + const multiChoiceGroups: { [questionKey: string]: Reply[] } = {}; + replies.forEach((reply) => { + if (reply.question.multiple_choice) { + if (!multiChoiceGroups[reply.question.key]) { + multiChoiceGroups[reply.question.key] = []; + } + multiChoiceGroups[reply.question.key].push(reply); + } + }); + + return { + singleChoiceReplies, + multiChoiceGroups, // object: { [questionKey]: Reply[] } + }; + } + + function calculateResult() { + let hints: string[] = [] + let defaultHints = [ + t("new_project_completion_page.result.hints.defaults.contact_itkom"), + t("new_project_completion_page.result.hints.defaults.fill_concept") + ] + hints.push(...defaultHints) + + replies.map(function (reply: Reply) { + if (reply.response.result_hint_key) { + hints.push(t(`new_project_completion_page.questions.${reply.question.key}.${reply.response.result_hint_key}`)) + } + }) + + const { singleChoiceReplies, multiChoiceGroups } = splitReplies(replies); + + return
+

{t("new_project_completion_page.result.hints.title")}

+
    + {hints.map((hint, index) =>
  • {hint}
  • )} +
+ +

{t("new_project_completion_page.result.answers.title")}

+ {singleChoiceReplies.map(function (reply: Reply) { + return ( +
+

+ {t(`new_project_completion_page.questions.${reply.question.key}.text`)} +
+ {t(`new_project_completion_page.questions.${reply.question.key}.responses.${reply.response.key}`)} +

+
+ ) + })} + {Object.entries(multiChoiceGroups).map(([questionKey, groupReplies]: [string, Reply[]]) => ( +
+

+ {t(`new_project_completion_page.questions.${questionKey}.text`)} +

+
    + {groupReplies.map((reply) => ( +
  • + {t(`new_project_completion_page.questions.${reply.question.key}.responses.${reply.response.key}`)} +
  • + ))} +
+
+ ))} +
+ } + return - {t('new_completion_project_page.title')} + {t('new_project_completion_page.title')} -

{t('new_completion_project_page.title')}

+

{t('new_project_completion_page.title')}

+ + + {showResult ? +
+ {calculateResult()} +
+ : +
+

{t(`new_project_completion_page.questions.${questions[currentQuestion].key}.text`)}

+
{printOptions(questions[currentQuestion])}
+
+ } +
} diff --git a/src/pages/completion/completion_questions.json b/src/pages/completion/completion_questions.json new file mode 100644 index 0000000..e7b273a --- /dev/null +++ b/src/pages/completion/completion_questions.json @@ -0,0 +1,126 @@ +{ + "questions": [ + { + "index": 0, + "key": "landscape", + "responses": [ + { + "key": "no", + "result_hint_key": "landscape_no", + "next_question": null, + "clippy_variant": "angry", + "tools": [], + "kills": [] + }, + { + "key": "yes", + "next_question": 1, + "tools": [], + "kills": [] + } + ] + }, + { + "index": 1, + "key": "roles", + "multiple_choice": true, + "responses": [ + { + "key": "po", + "tools": [], + "kills": [] + + }, + { + "key": "technical_contact", + "tools": [], + "kills": [] + }, + { + "key": "content_management", + "tools": [], + "kills": [] + } + ] + }, + { + "index": 2, + "key": "artifacts", + "multiple_choice": true, + "responses": [ + { + "key": "documentation", + "tools": [], + "kills": [] + }, + { + "key": "passwords", + "tools": [], + "kills": [] + }, + { + "key": "code", + "tools": [], + "kills": [] + }, + { + "key": "specifications", + "tools": [], + "kills": [] + }, + { + "key": "api_documentation", + "tools": [], + "kills": [] + } + ] + }, + { + "index": 3, + "key": "budget", + "responses": [ + { + "key": "no_costs", + "result_hint_key": "budget_no_costs", + "tools": [], + "kills": [] + }, + { + "key": "yes_defined", + "result_hint_key": "budget_yes_defined", + "tools": [], + "kills": [] + }, + { + "key": "not_defined", + "result_hint_key": "budget_not_defined", + "tools": [], + "kills": [] + } + ] + }, + { + "index": 4, + "key": "tasks", + "responses": [ + { + "key": "none", + "result_hint_key": "tasks_none", + "tools": [], + "kills": [] + }, + { + "key": "still_open", + "result_hint_key": "tasks_still_open", + "tools": [], + "kills": [] + }, + { + "key": "defined", + "tools": [], + "kills": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/pages/digitalisation/NewDigitalisationProjectPage.tsx b/src/pages/digitalisation/NewDigitalisationProjectPage.tsx index 639d9c0..967f63e 100644 --- a/src/pages/digitalisation/NewDigitalisationProjectPage.tsx +++ b/src/pages/digitalisation/NewDigitalisationProjectPage.tsx @@ -8,12 +8,12 @@ import { Button, ButtonContainer } from '../HomePage'; import questionJson from './../../questions.json'; import styled from '@emotion/styled'; -type State = { +export type State = { currentSlide: string; clippyVariant: "" | "focus" | "rolleye" | 'angry'; } -const Checkbox = styled.input` +export const Checkbox = styled.input` margin-right: 10px ` @@ -260,7 +260,8 @@ export default function NewDigitalisationProjectPage() {

{t('new_project_digitalisation_page.title')}

- {showScore ?
+ {showScore ? +

{t("new_project_digitalisation_page.evaluation.title")}

{t("new_project_digitalisation_page.evaluation.explanation")}

diff --git a/src/types.d.ts b/src/types.d.ts index dd58b0b..c6870fc 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -30,7 +30,8 @@ export interface Question { export interface Response { key: string + result_hint_key?: string tools: string[] kills: string[] - next_question?: number + next_question?: number | null }