From 130883a8785066dd31bae2aaef1896137892e272 Mon Sep 17 00:00:00 2001 From: JohannaPeanut <76495099+JohannaPeanut@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:33:27 +0100 Subject: [PATCH 01/40] Survey: duplicate survey --- src/pages/beteiligung/[surveySlug]/index.tsx | 10 +- src/participation-frm7/components/Debug.tsx | 7 + src/participation-frm7/components/Done.tsx | 21 ++ src/participation-frm7/components/Email.tsx | 44 ++++ src/participation-frm7/components/More.tsx | 32 +++ .../components/core/Text.tsx | 40 ++++ .../core/buttons/ParticipationButton.tsx | 28 +++ .../buttons/ParticipationButtonWrapper.tsx | 13 ++ .../core/links/ParticipationLink.tsx | 53 +++++ .../components/core/links/styles.ts | 72 ++++++ .../components/feedback/Feedback.tsx | 126 ++++++++++ .../components/feedback/FeedbackFirstPage.tsx | 58 +++++ .../feedback/FeedbackSecondPage.tsx | 70 ++++++ .../form/ParticipationLabeledCheckbox.tsx | 69 ++++++ .../ParticipationLabeledCheckboxGroup.tsx | 21 ++ .../form/ParticipationLabeledRadiobutton.tsx | 70 ++++++ .../ParticipationLabeledRadiobuttonGroup.tsx | 20 ++ .../form/ParticipationLabeledTextField.tsx | 81 +++++++ .../ParticipationLabeledTextareaField.tsx | 71 ++++++ .../components/form/SurveyForm.tsx | 58 +++++ .../components/frm7-inactive.tsx | 27 +++ src/participation-frm7/components/frm7.tsx | 175 ++++++++++++++ .../layout/ContainerParticipation.tsx | 9 + .../components/layout/FooterParticipation.tsx | 15 ++ .../components/layout/HeaderParticipation.tsx | 32 +++ .../components/layout/LayoutParticipation.tsx | 41 ++++ .../components/layout/ProgressBar.tsx | 22 ++ .../layout/ScreenHeaderParticipation.tsx | 24 ++ .../components/maps/MapBanner.tsx | 28 +++ .../maps/ParticipationBackgroundSwitcher.tsx | 84 +++++++ .../components/maps/ParticipationMap.tsx | 178 ++++++++++++++ .../maps/ParticipationStaticMap.tsx | 73 ++++++ .../components/maps/Pin.tsx | 17 ++ .../components/maps/StaticPin.tsx | 23 ++ .../components/survey/Page.tsx | 44 ++++ .../survey/ParticipationSpinnerLayover.tsx | 33 +++ .../components/survey/Question.tsx | 83 +++++++ .../components/survey/Survey.tsx | 109 +++++++++ .../components/survey/SurveyButton.tsx | 41 ++++ src/participation-frm7/context/contexts.ts | 24 ++ src/participation-frm7/data/README.md | 23 ++ src/participation-frm7/data/email.json | 53 +++++ src/participation-frm7/data/feedback.json | 220 ++++++++++++++++++ src/participation-frm7/data/more.json | 33 +++ src/participation-frm7/data/schema.json | 121 ++++++++++ src/participation-frm7/data/survey.json | 163 +++++++++++++ src/participation-frm7/data/types.ts | 50 ++++ .../utils/scrollToTopWithDelay.ts | 1 + src/participation/data/survey.json | 4 +- src/surveys/queries/getPublicSurveyBySlug.ts | 2 +- 50 files changed, 2712 insertions(+), 4 deletions(-) create mode 100644 src/participation-frm7/components/Debug.tsx create mode 100644 src/participation-frm7/components/Done.tsx create mode 100644 src/participation-frm7/components/Email.tsx create mode 100644 src/participation-frm7/components/More.tsx create mode 100644 src/participation-frm7/components/core/Text.tsx create mode 100644 src/participation-frm7/components/core/buttons/ParticipationButton.tsx create mode 100644 src/participation-frm7/components/core/buttons/ParticipationButtonWrapper.tsx create mode 100644 src/participation-frm7/components/core/links/ParticipationLink.tsx create mode 100644 src/participation-frm7/components/core/links/styles.ts create mode 100644 src/participation-frm7/components/feedback/Feedback.tsx create mode 100644 src/participation-frm7/components/feedback/FeedbackFirstPage.tsx create mode 100644 src/participation-frm7/components/feedback/FeedbackSecondPage.tsx create mode 100644 src/participation-frm7/components/form/ParticipationLabeledCheckbox.tsx create mode 100644 src/participation-frm7/components/form/ParticipationLabeledCheckboxGroup.tsx create mode 100644 src/participation-frm7/components/form/ParticipationLabeledRadiobutton.tsx create mode 100644 src/participation-frm7/components/form/ParticipationLabeledRadiobuttonGroup.tsx create mode 100644 src/participation-frm7/components/form/ParticipationLabeledTextField.tsx create mode 100644 src/participation-frm7/components/form/ParticipationLabeledTextareaField.tsx create mode 100644 src/participation-frm7/components/form/SurveyForm.tsx create mode 100644 src/participation-frm7/components/frm7-inactive.tsx create mode 100644 src/participation-frm7/components/frm7.tsx create mode 100644 src/participation-frm7/components/layout/ContainerParticipation.tsx create mode 100644 src/participation-frm7/components/layout/FooterParticipation.tsx create mode 100644 src/participation-frm7/components/layout/HeaderParticipation.tsx create mode 100644 src/participation-frm7/components/layout/LayoutParticipation.tsx create mode 100644 src/participation-frm7/components/layout/ProgressBar.tsx create mode 100644 src/participation-frm7/components/layout/ScreenHeaderParticipation.tsx create mode 100644 src/participation-frm7/components/maps/MapBanner.tsx create mode 100644 src/participation-frm7/components/maps/ParticipationBackgroundSwitcher.tsx create mode 100644 src/participation-frm7/components/maps/ParticipationMap.tsx create mode 100644 src/participation-frm7/components/maps/ParticipationStaticMap.tsx create mode 100644 src/participation-frm7/components/maps/Pin.tsx create mode 100644 src/participation-frm7/components/maps/StaticPin.tsx create mode 100644 src/participation-frm7/components/survey/Page.tsx create mode 100644 src/participation-frm7/components/survey/ParticipationSpinnerLayover.tsx create mode 100644 src/participation-frm7/components/survey/Question.tsx create mode 100644 src/participation-frm7/components/survey/Survey.tsx create mode 100644 src/participation-frm7/components/survey/SurveyButton.tsx create mode 100644 src/participation-frm7/context/contexts.ts create mode 100644 src/participation-frm7/data/README.md create mode 100644 src/participation-frm7/data/email.json create mode 100644 src/participation-frm7/data/feedback.json create mode 100644 src/participation-frm7/data/more.json create mode 100644 src/participation-frm7/data/schema.json create mode 100644 src/participation-frm7/data/survey.json create mode 100644 src/participation-frm7/data/types.ts create mode 100644 src/participation-frm7/utils/scrollToTopWithDelay.ts diff --git a/src/pages/beteiligung/[surveySlug]/index.tsx b/src/pages/beteiligung/[surveySlug]/index.tsx index f6f8fbcb1..a560d99a5 100644 --- a/src/pages/beteiligung/[surveySlug]/index.tsx +++ b/src/pages/beteiligung/[surveySlug]/index.tsx @@ -5,11 +5,19 @@ import { Spinner } from "src/core/components/Spinner" import getPublicSurveyBySlug from "src/surveys/queries/getPublicSurveyBySlug" import ParticipationMainPage from "src/participation/components/rs8" import ParticipationInactivePage from "src/participation/components/rs8-inactive" +import ParticipationFrm7MainPage from "src/participation-frm7/components/frm7" +import ParticipationFrm7InactivePage from "src/participation-frm7/components/frm7-inactive" export const Survey = () => { const surveySlug = useParam("surveySlug", "string") const [survey] = useQuery(getPublicSurveyBySlug, { slug: surveySlug! }) - return survey.active ? : + // only returns something if there is a 'Survey' in the DB with the slug (url params) and the slug is either rs8 or frm7 + if (!survey) return null + if (surveySlug === "rs8") + return survey.active ? : + if (surveySlug === "frm7") + return survey.active ? : + return null } const PublicSurveyPage = () => { diff --git a/src/participation-frm7/components/Debug.tsx b/src/participation-frm7/components/Debug.tsx new file mode 100644 index 000000000..55ba631fa --- /dev/null +++ b/src/participation-frm7/components/Debug.tsx @@ -0,0 +1,7 @@ +type Props = { + children: React.ReactNode +} & React.ButtonHTMLAttributes + +export const Debug: React.FC = ({ children, ...props }) => { + return
{children}
+} diff --git a/src/participation-frm7/components/Done.tsx b/src/participation-frm7/components/Done.tsx new file mode 100644 index 000000000..a3e2eecd4 --- /dev/null +++ b/src/participation-frm7/components/Done.tsx @@ -0,0 +1,21 @@ +export { FORM_ERROR } from "src/core/components/forms" +import { ParticipationLink } from "./core/links/ParticipationLink" +import { ScreenHeaderParticipation } from "./layout/ScreenHeaderParticipation" + +export const Done = () => { + return ( +
+ + + Zurück Startseite + +
+ ) +} diff --git a/src/participation-frm7/components/Email.tsx b/src/participation-frm7/components/Email.tsx new file mode 100644 index 000000000..7e5844686 --- /dev/null +++ b/src/participation-frm7/components/Email.tsx @@ -0,0 +1,44 @@ +import { useCallback, useEffect, useState } from "react" +import { iframeResizer } from "iframe-resizer" + +export { FORM_ERROR } from "src/core/components/forms" +import { ParticipationH2, ParticipationP } from "./core/Text" +import { ParticipationLink } from "./core/links/ParticipationLink" +import { ScreenHeaderParticipation } from "./layout/ScreenHeaderParticipation" + +type Props = { + onSubmit: any + email: any // TODO +} + +export const Email: React.FC = ({ onSubmit, email }) => { + const page = email.pages[0] + + useEffect(() => { + iframeResizer({}, "#mailjet-widget") + }, []) + + return ( +
+ + {page.questions[0].label.de} + {page.questions[0].props.text.de} + {/* todo */} +
+ `, + }} + /> + +
+ {/* todo */} + + Zurück zur Startseite + +
+
+ ) +} diff --git a/src/participation-frm7/components/More.tsx b/src/participation-frm7/components/More.tsx new file mode 100644 index 000000000..ae29a5d11 --- /dev/null +++ b/src/participation-frm7/components/More.tsx @@ -0,0 +1,32 @@ +import { ParticipationH2 } from "src/participation-frm7/components/core/Text" +import { ParticipationButton } from "src/participation-frm7/components/core/buttons/ParticipationButton" +import { ScreenHeaderParticipation } from "src/participation-frm7/components/layout/ScreenHeaderParticipation" +import { ParticipationButtonWrapper } from "src/participation-frm7/components/core/buttons/ParticipationButtonWrapper" + +export { FORM_ERROR } from "src/core/components/forms" + +type Props = { + onClickMore: any + onClickFinish: any + more: any // TODO +} + +export const More: React.FC = ({ more, onClickMore, onClickFinish }) => { + const { title, description, questions, buttons } = more.pages[0] + const question = questions[0] + + return ( + <> + + {question.label.de} + + + {buttons[0].label.de} + + + {buttons[1].label.de} + + + + ) +} diff --git a/src/participation-frm7/components/core/Text.tsx b/src/participation-frm7/components/core/Text.tsx new file mode 100644 index 000000000..5ce3eae78 --- /dev/null +++ b/src/participation-frm7/components/core/Text.tsx @@ -0,0 +1,40 @@ +import clsx from "clsx" +import { ReactNode } from "react" + +type Props = { + children: ReactNode + className?: string +} + +export const ParticipationH1: React.FC = ({ className, children }) => { + return ( +

+ {children} +

+ ) +} + +export const ParticipationH2: React.FC = ({ className, children }) => { + return ( +

+ {children} +

+ ) +} + +export const ParticipationH3: React.FC = ({ className, children }) => { + return ( +

+ {children} +

+ ) +} + +export const ParticipationP: React.FC = ({ className, children }) => { + return

{children}

+} diff --git a/src/participation-frm7/components/core/buttons/ParticipationButton.tsx b/src/participation-frm7/components/core/buttons/ParticipationButton.tsx new file mode 100644 index 000000000..6f740369e --- /dev/null +++ b/src/participation-frm7/components/core/buttons/ParticipationButton.tsx @@ -0,0 +1,28 @@ +import clsx from "clsx" +import { ReactNode } from "react" +import { participationBlueButtonStyles, participationWhiteButtonStyles } from "../links/styles" +export { FORM_ERROR } from "src/core/components/forms" + +type Props = { + color?: string + disabled?: boolean + children: string | ReactNode +} & React.ButtonHTMLAttributes + +export const ParticipationButton: React.FC = ({ + disabled, + color = "pink", + children, + ...props +}) => { + const buttonStyles = clsx( + "px-12", + color === "white" ? participationWhiteButtonStyles : participationBlueButtonStyles, + ) + + return ( + + ) +} diff --git a/src/participation-frm7/components/core/buttons/ParticipationButtonWrapper.tsx b/src/participation-frm7/components/core/buttons/ParticipationButtonWrapper.tsx new file mode 100644 index 000000000..c496acd54 --- /dev/null +++ b/src/participation-frm7/components/core/buttons/ParticipationButtonWrapper.tsx @@ -0,0 +1,13 @@ +import { ReactNode } from "react" + +type Props = { + children: ReactNode +} + +export const ParticipationButtonWrapper: React.FC = ({ children }) => { + return ( +
+ {children} +
+ ) +} diff --git a/src/participation-frm7/components/core/links/ParticipationLink.tsx b/src/participation-frm7/components/core/links/ParticipationLink.tsx new file mode 100644 index 000000000..5dd96710b --- /dev/null +++ b/src/participation-frm7/components/core/links/ParticipationLink.tsx @@ -0,0 +1,53 @@ +import { RouteUrlObject } from "blitz" +import clsx from "clsx" +import NextLink from "next/link" +import { forwardRef } from "react" +import { selectParticipationLinkStyle } from "./styles" + +// the link component is duplicated to avoid dependencies between TS and 'Beteiligung' + +export type ParticipationLinkProps = { + href: RouteUrlObject | string + className?: string + classNameOverwrites?: string + /** @default `false` */ + blank?: boolean + /** @desc Style Link as Button */ + button?: true | "white" | "blue" + children: React.ReactNode +} & Omit, "href"> + +export const ParticipationLink: React.FC = forwardRef< + HTMLAnchorElement, + ParticipationLinkProps +>(({ href, className, classNameOverwrites, children, blank = false, button, ...props }, ref) => { + const classNames = clsx(classNameOverwrites ?? selectParticipationLinkStyle(button, className)) + + // external link + if (typeof href === "string") { + return ( + + {children} + + ) + } + + return ( + + {children} + + ) +}) diff --git a/src/participation-frm7/components/core/links/styles.ts b/src/participation-frm7/components/core/links/styles.ts new file mode 100644 index 000000000..560a037a7 --- /dev/null +++ b/src/participation-frm7/components/core/links/styles.ts @@ -0,0 +1,72 @@ +import clsx from "clsx" +import { ParticipationLinkProps } from "./ParticipationLink" + +// BUTTON: +const buttonBase = + "w-full font-medium sm:w-auto shadow-sm text-sm pb-3.5 pt-4 px-6 rounded-lg inline-flex items-center justify-center no-underline" + +// LINK +export const partcipationLinkStyles = + "underline text-blue-500 decoreation-blue-500 hover:text-blue-800 hover:decoration-blue-800 active:ring-1 ring-blue-500 rounded" + +// HOVER and ACTIVE +// for button elements +const hoverStyleForButtonElement = "enabled:hover:bg-blue-800 enabled:hover:text-white" +const activeStyleWhiteButtonElement = + "enabled:active:ring-2 enabled:active:ring-blue-800 enabled:active:bg-white enabled:active:text-black" +const activeStyleblueButtonElement = + "enabled:active:ring-2 enabled:active:ring-blue-800 enabled:active:bg-blue-500 enabled:hover:bg-blue-800 enabled:hover:text-white" +// for link elements +const hoverStyleForLinkElement = "hover:bg-blue-800 hover:text-white" +const activeStyleblueLinkElement = + "active:ring-2 active:ring-blue-800 active:bg-blue-500 hover:bg-blue-800 hover:text-white" +const activeStyleWhiteLinkElement = + "active:ring-2 active:ring-blue-800 active:bg-white active:text-black" + +// WHITE BUTTON +// for link elements +const whiteButtonStylesForLinkElement = clsx( + buttonBase, + "bg-white ring-1 ring-gray-400", + hoverStyleForLinkElement, + activeStyleWhiteLinkElement, +) +// for button elements +export const participationWhiteButtonStyles = clsx( + buttonBase, + "enabled:bg-white enabled:ring-1 enabled:ring-gray-400", + "disabled:text-gray-400 disabled:bg-white disabled:ring-1 disabled:ring-gray-200", + hoverStyleForButtonElement, + activeStyleWhiteButtonElement, +) + +// blue BUTTON +// for link elements +const blueButtonStylesForLinkElement = clsx( + buttonBase, + "text-white bg-blue-500", + activeStyleblueLinkElement, +) +// for button elements +export const participationBlueButtonStyles = clsx( + buttonBase, + "enabled:text-white enabled:bg-blue-500", + "disabled:bg-blue-100 disabled:text-white", + activeStyleblueButtonElement, +) + +export const selectParticipationLinkStyle = ( + button: ParticipationLinkProps["button"], + className?: string, +) => { + switch (button) { + case true: + return clsx(blueButtonStylesForLinkElement, className) + case "white": + return clsx(whiteButtonStylesForLinkElement, className) + case "blue": + return clsx(blueButtonStylesForLinkElement, className) + default: + return clsx(partcipationLinkStyles, className) + } +} diff --git a/src/participation-frm7/components/feedback/Feedback.tsx b/src/participation-frm7/components/feedback/Feedback.tsx new file mode 100644 index 000000000..8f55878e3 --- /dev/null +++ b/src/participation-frm7/components/feedback/Feedback.tsx @@ -0,0 +1,126 @@ +import { useCallback, useContext, useState } from "react" +import { stageProgressDefinition } from "src/participation-frm7/components/frm7" +import { PinContext, ProgressContext } from "src/participation-frm7/context/contexts" +import SurveyForm from "src/participation-frm7/components/form/SurveyForm" +import { FeedbackFirstPage } from "src/participation-frm7/components/feedback/FeedbackFirstPage" +import { FeedbackSecondPage } from "src/participation-frm7/components/feedback/FeedbackSecondPage" +import { scrollToTopWithDelay } from "src/participation-frm7/utils/scrollToTopWithDelay" + +export { FORM_ERROR } from "src/core/components/forms" + +type Props = { + onSubmit: any + feedback: any // TODO +} + +export const Feedback: React.FC = ({ onSubmit, feedback }) => { + const { setProgress } = useContext(ProgressContext) + const [pinPosition, setPinPosition] = useState(null) + const [values, setValues] = useState({}) + const [isPageOneCompleted, setIsPageOneCompleted] = useState(false) + const [isPageTwoCompleted, setIsPageTwoCompleted] = useState(false) + const [isMapDirty, setIsMapDirty] = useState(false) + const [feedbackPageProgress, setFeedbackPageProgress] = useState(0) + const [feedbackCategory, setFeedbackCategory] = useState(6) // default: 6 / "Sonstiges" + + const [isMap, setIsMap] = useState(false) + + const { pages } = feedback + + const projectGeometry = feedback.pages[0].questions[2].props.projectGeometry + const layerStyles = feedback.pages[0].questions[2].props.layerStyles + + const pinId = pages[0].questions.find( + (question: Record) => question.component === "map", + ).id + + const categories = pages[0].questions[0].props.responses + + const handleNextPage = () => { + const newFeedbackPageProgress = Math.min(pages.length, feedbackPageProgress + 1) + setFeedbackPageProgress(newFeedbackPageProgress) + setProgress(stageProgressDefinition["FEEDBACK"] + newFeedbackPageProgress) + scrollToTopWithDelay() + } + + const handleBackPage = () => { + const newFeedbackPageProgress = Math.max(0, feedbackPageProgress - 1) + setFeedbackPageProgress(newFeedbackPageProgress) + setProgress(stageProgressDefinition["FEEDBACK"] + newFeedbackPageProgress) + scrollToTopWithDelay() + } + + const transformValues = (values: Record) => { + const responses: Record = {} + Object.entries(values).forEach(([k, v]) => { + const [questionType, questionId, responseId] = k.split("-") + switch (questionType) { + case "single": + responses[questionId!] = v === null ? null : Number(v) + break + case "multi": + if (!(questionId! in responses)) responses[questionId!] = [] + // @ts-ignore + if (v) responses[questionId!].push(Number(responseId)) + break + case "text": + responses[questionId!] = v === "" ? null : String(v) + break + } + }) + return responses + } + + const handleSubmit = (values: Record, submitterId?: string) => { + values = transformValues(values) + delete values["22"] // delete map ja/nein response + onSubmit({ ...values, [pinId]: isMap ? pinPosition : null }, submitterId) + } + + // when Form changes, check if Radio "Ja" is selected - set state to true + const handleChange = useCallback( + (values: Record) => { + setValues(values) + values = transformValues(values) + const isMapOption = values["22"] === 1 // "1" -> yes, "2" -> no - see feedback.json + setIsMap(isMapOption) + const isQuestionsCompletedPageOne = values["21"] && values["22"] + setIsPageOneCompleted( + !isMapOption // if user did not choose map + ? isQuestionsCompletedPageOne // page is completed in case both questions are answered + : isQuestionsCompletedPageOne && isMapDirty, // page is completed in case both questions are answered and user has touched the map marker + ) + const isMinimumOneQuestionPageTwo = values["34"] || values["35"] + setIsPageTwoCompleted(isMinimumOneQuestionPageTwo) + if (!isMapOption) setPinPosition(null) // set pinPosition to null if not yes + setFeedbackCategory(values["21"] || categories.length) // sets state to response id of chosen category (question 21) // fallback: '"Sonstiges" + }, + [categories.length, isMapDirty], + ) + + return ( + // @ts-ignore + + + {feedbackPageProgress === 0 && ( + + )} + {feedbackPageProgress === 1 && ( + + )} + + + ) +} diff --git a/src/participation-frm7/components/feedback/FeedbackFirstPage.tsx b/src/participation-frm7/components/feedback/FeedbackFirstPage.tsx new file mode 100644 index 000000000..7348985d0 --- /dev/null +++ b/src/participation-frm7/components/feedback/FeedbackFirstPage.tsx @@ -0,0 +1,58 @@ +import { MapProvider } from "react-map-gl/maplibre" +import { ParticipationButton } from "src/participation-frm7/components/core/buttons/ParticipationButton" +import { ScreenHeaderParticipation } from "src/participation-frm7/components/layout/ScreenHeaderParticipation" +import { ParticipationMap } from "src/participation-frm7/components/maps/ParticipationMap" +import { Question } from "src/participation-frm7/components/survey/Question" +import { ParticipationButtonWrapper } from "src/participation-frm7/components/core/buttons/ParticipationButtonWrapper" + +export { FORM_ERROR } from "src/core/components/forms" + +type Props = { + page: any // TODO + isMap: boolean + onButtonClick: any // TODO + isCompleted: boolean + mapIsDirtyProps: any +} + +export const FeedbackFirstPage: React.FC = ({ + isCompleted, + page, + isMap, + onButtonClick, + mapIsDirtyProps, +}) => { + const { title, description, questions, buttons } = page + + const mapProps = questions.find( + (question: Record) => question.component === "map", + ).props + + return ( + <> + + + + + {isMap && ( + + + + )} + {/* TODO Disabled */} + + + {buttons[0].label.de} + + + + ) +} diff --git a/src/participation-frm7/components/feedback/FeedbackSecondPage.tsx b/src/participation-frm7/components/feedback/FeedbackSecondPage.tsx new file mode 100644 index 000000000..1aa072e6c --- /dev/null +++ b/src/participation-frm7/components/feedback/FeedbackSecondPage.tsx @@ -0,0 +1,70 @@ +import { useContext } from "react" +import { PinContext } from "src/participation-frm7/context/contexts" +import { ParticipationButton } from "src/participation-frm7/components/core/buttons/ParticipationButton" +import { ParticipationButtonWrapper } from "src/participation-frm7/components/core/buttons/ParticipationButtonWrapper" +import { ScreenHeaderParticipation } from "src/participation-frm7/components/layout/ScreenHeaderParticipation" +import { + ParticipationH2, + ParticipationH3, + ParticipationP, +} from "src/participation-frm7/components/core/Text" +import { ParticipationStaticMap } from "src/participation-frm7/components/maps/ParticipationStaticMap" +import { Question } from "src/participation-frm7/components/survey/Question" +import { MultiLineString } from "@turf/helpers" + +export { FORM_ERROR } from "src/core/components/forms" + +type Props = { + page: any // TODO + onButtonClick: any // TODO + staticMapProps: { + projectGeometry: MultiLineString + layerStyles: Record + } + feedbackCategory: string + isCompleted: boolean +} + +export const FeedbackSecondPage: React.FC = ({ + page, + isCompleted, + onButtonClick, + staticMapProps, + feedbackCategory, +}) => { + const { pinPosition } = useContext(PinContext) + const { title, description, questions, buttons } = page + + const textAreaQuestions = questions.filter((q: Record) => q.component === "text") + + return ( + <> + + {questions[0].label.de} + {feedbackCategory} + + {pinPosition && ( + <> + {questions[1].label.de} + + + )} +
+ + +
+ + + + {buttons[0].label.de} + + + {buttons[1].label.de} + + + + Zurück + + + ) +} diff --git a/src/participation-frm7/components/form/ParticipationLabeledCheckbox.tsx b/src/participation-frm7/components/form/ParticipationLabeledCheckbox.tsx new file mode 100644 index 000000000..6786ad2a9 --- /dev/null +++ b/src/participation-frm7/components/form/ParticipationLabeledCheckbox.tsx @@ -0,0 +1,69 @@ +import { ErrorMessage } from "@hookform/error-message" +import clsx from "clsx" +import { ComponentPropsWithoutRef, forwardRef, PropsWithoutRef, ReactNode } from "react" +import { useFormContext } from "react-hook-form" + +export interface TParticipationLabeledCheckbox + extends PropsWithoutRef { + /** Field name. */ + name: string + /** Field label. */ + label: string | ReactNode + /** Help text below field label. */ + help?: string + outerProps?: PropsWithoutRef + labelProps?: ComponentPropsWithoutRef<"label"> +} + +export const ParticipationLabeledCheckbox = forwardRef< + HTMLInputElement, + TParticipationLabeledCheckbox +>(({ name, label, help, outerProps, labelProps, ...props }, ref) => { + const { + register, + formState: { isSubmitting, errors }, + } = useFormContext() + + const hasError = Boolean(errors[name]) + + return ( +
+
+ +
+ +
+ ) +}) diff --git a/src/participation-frm7/components/form/ParticipationLabeledCheckboxGroup.tsx b/src/participation-frm7/components/form/ParticipationLabeledCheckboxGroup.tsx new file mode 100644 index 000000000..5d43d519f --- /dev/null +++ b/src/participation-frm7/components/form/ParticipationLabeledCheckboxGroup.tsx @@ -0,0 +1,21 @@ +import clsx from "clsx" +import React from "react" +import { + ParticipationLabeledCheckbox, + TParticipationLabeledCheckbox, +} from "src/participation-frm7/components/form/ParticipationLabeledCheckbox" + +type Props = { + items: TParticipationLabeledCheckbox[] + className?: string +} + +export const ParticipationLabeledCheckboxGroup: React.FC = ({ items, className }) => { + return ( +
+ {items.map((item, index) => { + return + })} +
+ ) +} diff --git a/src/participation-frm7/components/form/ParticipationLabeledRadiobutton.tsx b/src/participation-frm7/components/form/ParticipationLabeledRadiobutton.tsx new file mode 100644 index 000000000..b1252ce46 --- /dev/null +++ b/src/participation-frm7/components/form/ParticipationLabeledRadiobutton.tsx @@ -0,0 +1,70 @@ +import { ErrorMessage } from "@hookform/error-message" +import clsx from "clsx" +import { ComponentPropsWithoutRef, forwardRef, PropsWithoutRef } from "react" +import { useFormContext } from "react-hook-form" + +export interface ParticipationLabeledRadiobuttonProps + extends PropsWithoutRef { + /** Radiobutton scope. */ + scope: string + /** Field name. */ + name: string + /** Field label. */ + label: string + /** Field value. */ + value: string + /** Help text below field label. */ + help?: string + outerProps?: PropsWithoutRef + labelProps?: ComponentPropsWithoutRef<"label"> +} + +export const ParticipationLabeledRadiobutton = forwardRef< + HTMLInputElement, + ParticipationLabeledRadiobuttonProps +>(({ scope, name, label, value, help, outerProps, labelProps, ...props }, ref) => { + const { + register, + formState: { isSubmitting, errors }, + } = useFormContext() + + const hasError = Boolean(errors[name]) + + return ( +
+
+ +
+ +
+ ) +}) diff --git a/src/participation-frm7/components/form/ParticipationLabeledRadiobuttonGroup.tsx b/src/participation-frm7/components/form/ParticipationLabeledRadiobuttonGroup.tsx new file mode 100644 index 000000000..5c892dba2 --- /dev/null +++ b/src/participation-frm7/components/form/ParticipationLabeledRadiobuttonGroup.tsx @@ -0,0 +1,20 @@ +import React from "react" +import { + ParticipationLabeledRadiobutton, + ParticipationLabeledRadiobuttonProps, +} from "src/participation-frm7/components/form/ParticipationLabeledRadiobutton" + +type Props = { + items: ParticipationLabeledRadiobuttonProps[] + className?: string +} + +export const ParticipationLabeledRadiobuttonGroup: React.FC = ({ items, className }) => { + return ( +
+ {items.map((item) => { + return + })} +
+ ) +} diff --git a/src/participation-frm7/components/form/ParticipationLabeledTextField.tsx b/src/participation-frm7/components/form/ParticipationLabeledTextField.tsx new file mode 100644 index 000000000..dddc6bce9 --- /dev/null +++ b/src/participation-frm7/components/form/ParticipationLabeledTextField.tsx @@ -0,0 +1,81 @@ +import { ErrorMessage } from "@hookform/error-message" +import clsx from "clsx" +import { ComponentPropsWithoutRef, forwardRef, PropsWithoutRef } from "react" +import { useFormContext } from "react-hook-form" + +export interface LabeledTextFieldProps extends PropsWithoutRef { + /** Field name. */ + name: string + /** Field label. */ + label: string + help?: string + /** Field type. Doesn't include radio buttons and checkboxes */ + type?: + | "text" + | "password" + | "email" + | "number" + // | "datetime-local" // This is broken in Firefox, so we cannot use it + | "tel" + | "url" + | "date" + | "time" + outerProps?: PropsWithoutRef + labelProps?: ComponentPropsWithoutRef<"label"> + optional?: boolean +} + +export const ParticipationLabeledTextField = forwardRef( + ({ name, label, help, outerProps, labelProps, optional, ...props }, ref) => { + const { + register, + formState: { isSubmitting, errors }, + // getValues, + // setValue, + } = useFormContext() + + const hasError = Boolean(errors[name]) + + // Field Type `datetime-local` requires a format of "YYYY-MM-DDTHH:MM:SS" to pre fill the value. + // const value = getValues()[name] + // if (value && props.type === "datetime-local") { + // setValue(name, new Date(value as string).toISOString().split(".")[0]) + // } + + return ( +
+ + + {Boolean(help) &&

{help}

} + + ( +
+ {message} +
+ )} + errors={errors} + name={name} + /> +
+ ) + }, +) diff --git a/src/participation-frm7/components/form/ParticipationLabeledTextareaField.tsx b/src/participation-frm7/components/form/ParticipationLabeledTextareaField.tsx new file mode 100644 index 000000000..a07fc3848 --- /dev/null +++ b/src/participation-frm7/components/form/ParticipationLabeledTextareaField.tsx @@ -0,0 +1,71 @@ +import { ErrorMessage } from "@hookform/error-message" +import clsx from "clsx" +import { ComponentPropsWithoutRef, forwardRef, PropsWithoutRef } from "react" +import { useFormContext } from "react-hook-form" + +export interface ParticipationLabeledTextareaProps + extends PropsWithoutRef { + /** Field name. */ + name: string + /** Field label. */ + label: string + help?: string + outerProps?: PropsWithoutRef + labelProps?: ComponentPropsWithoutRef<"label"> + optional?: boolean +} + +export const ParticipationLabeledTextareaField = forwardRef< + HTMLTextAreaElement, + ParticipationLabeledTextareaProps +>( + ( + { name, label, help, outerProps, labelProps, optional, className: textareaClasName, ...props }, + ref, + ) => { + const { + register, + formState: { isSubmitting, errors }, + } = useFormContext() + + const hasError = Boolean(errors[name]) + + return ( +
+ +