Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intégrer “Aucun” dans les mosaïques de réponses [NGC-1239] #747

Draft
wants to merge 7 commits into
base: preprod
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 63 additions & 82 deletions src/components/form/Question.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,25 @@ import {
questionChooseAnswer,
questionTypeAnswer,
} from '@/constants/tracking/question'
import Button from '@/design-system/inputs/Button'
import { useRule } from '@/publicodes-state'
import { trackEvent } from '@/utils/matomo/trackEvent'
import { DottedName } from '@incubateur-ademe/nosgestesclimat'
import { useEffect, useRef, useState } from 'react'
import { useEffect, useRef } from 'react'
import { twMerge } from 'tailwind-merge'
import Trans from '../translation/Trans'
import Category from './question/Category'
import Warning from './question/Warning'

type Props = {
question: DottedName
tempValue?: number | undefined
setTempValue?: (value: number | undefined) => void
showInputsLabel?: React.ReactNode | string
className?: string
}

export default function Question({
question,
tempValue,
setTempValue,
showInputsLabel,
className,
}: Props) {
const {
Expand All @@ -52,6 +48,7 @@ export default function Question({
isMissing,
choices,
assistance,
suggestions,
questionsOfMosaicFromParent,
activeNotifications,
plancher,
Expand All @@ -73,8 +70,6 @@ export default function Question({
}
}, [type, numericValue, setTempValue, question])

const [isOpen, setIsOpen] = useState(showInputsLabel ? false : true)

return (
<>
<div className={twMerge('mb-6 flex flex-col items-start', className)}>
Expand All @@ -83,91 +78,77 @@ export default function Question({

<Suggestions
question={question}
suggestions={suggestions}
setValue={(value) => {
if (type === 'number') {
if (setTempValue) setTempValue(value as number)
}
setValue(value, { foldedStep: question })
}}
/>
{showInputsLabel ? (
<Button
color="link"
size="xs"
onClick={() => setIsOpen((prevIsOpen) => !prevIsOpen)}
className="mb-2">
{isOpen ? <Trans>Fermer</Trans> : showInputsLabel}
</Button>
) : null}
{isOpen && (
<>
{type === 'number' && (
<NumberInput
unit={unit}
value={setTempValue ? tempValue : numericValue}
setValue={(value) => {
if (setTempValue) {
setTempValue(value)
}
setValue(value, { foldedStep: question })
trackEvent(questionTypeAnswer({ question, answer: value }))
}}
isMissing={isMissing}
min={0}
data-cypress-id={question}
id={DEFAULT_FOCUS_ELEMENT_ID}
aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID}
/>
)}

{type === 'boolean' && (
<BooleanInput
value={value}
setValue={(value) => {
{
setValue(value, { foldedStep: question })
trackEvent(
questionChooseAnswer({ question, answer: value })
)
}
}}
isMissing={isMissing}
data-cypress-id={question}
label={label || ''}
id={DEFAULT_FOCUS_ELEMENT_ID}
aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID}
/>
)}
{type === 'number' && (
<NumberInput
unit={unit}
value={setTempValue ? tempValue : numericValue}
setValue={(value) => {
if (setTempValue) {
setTempValue(value)
}
setValue(value, { foldedStep: question })
trackEvent(questionTypeAnswer({ question, answer: value }))
}}
isMissing={isMissing}
min={0}
data-cypress-id={question}
id={DEFAULT_FOCUS_ELEMENT_ID}
aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID}
/>
)}

{type === 'choices' && (
<ChoicesInput
question={question}
choices={choices}
value={String(value)}
setValue={(value) => {
{
setValue(value, { foldedStep: question })
trackEvent(
questionChooseAnswer({ question, answer: value })
)
}
}}
isMissing={isMissing}
data-cypress-id={question}
label={label || ''}
id={DEFAULT_FOCUS_ELEMENT_ID}
aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID}
/>
)}
{type === 'boolean' && (
<BooleanInput
value={value}
setValue={(value) => {
{
setValue(value, { foldedStep: question })
trackEvent(questionChooseAnswer({ question, answer: value }))
}
}}
isMissing={isMissing}
data-cypress-id={question}
label={label || ''}
id={DEFAULT_FOCUS_ELEMENT_ID}
aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID}
/>
)}

{type === 'choices' && (
<ChoicesInput
question={question}
choices={choices}
value={String(value)}
setValue={(value) => {
{
setValue(value, { foldedStep: question })
trackEvent(questionChooseAnswer({ question, answer: value }))
}
}}
isMissing={isMissing}
data-cypress-id={question}
label={label || ''}
id={DEFAULT_FOCUS_ELEMENT_ID}
aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID}
/>
)}

{type === 'mosaic' && (
<Mosaic
question={question}
questionsOfMosaic={questionsOfMosaicFromParent}
aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID}
/>
)}
</>
{type === 'mosaic' && (
<Mosaic
question={question}
questionsOfMosaic={questionsOfMosaicFromParent}
suggestions={suggestions}
aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID}
/>
)}
</div>

Expand Down
17 changes: 17 additions & 0 deletions src/components/form/question/Mosaic.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import { aucunLabels } from '@/constants/aucunLabels'
import { FormattedSuggestion } from '@/publicodes-state/types'
import { DottedName } from '@incubateur-ademe/nosgestesclimat'
import MosaicAucunOption from './mosaic/MosaicAucunOption'
import MosaicQuestion from './mosaic/MosaicQuestion'

type Props = {
question: DottedName
questionsOfMosaic: DottedName[]
suggestions: FormattedSuggestion[]
}

export default function Mosaic({
question,
questionsOfMosaic,
suggestions,
...props
}: Props) {
const aucunOption = suggestions.find((suggestion) =>
aucunLabels.has(suggestion.label)
)

return (
<fieldset className="grid gap-2 md:grid-cols-2 md:gap-4">
{questionsOfMosaic
Expand All @@ -24,6 +33,14 @@ export default function Mosaic({
/>
))
: 'Cette mosaique n a pas d enfants.'}
{aucunOption ? (
<MosaicAucunOption
question={question}
aucunOption={aucunOption}
questionsOfMosaic={questionsOfMosaic}
{...props}
/>
) : null}
</fieldset>
)
}
23 changes: 17 additions & 6 deletions src/components/form/question/Suggestions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { aucunLabels } from '@/constants/aucunLabels'
import { questionClickSuggestion } from '@/constants/tracking/question'
import { baseClassNames, sizeClassNames } from '@/design-system/inputs/Button'
import Emoji from '@/design-system/utils/Emoji'
Expand All @@ -10,27 +11,37 @@ import {
getHoverBorderCategoryColor,
getTextCategoryColor,
} from '@/helpers/getCategoryColorClass'
import { useForm, useRule } from '@/publicodes-state'
import { useForm } from '@/publicodes-state'
import { FormattedSuggestion } from '@/publicodes-state/types'
import { capitalizeString } from '@/utils/capitalizeString'
import { trackEvent } from '@/utils/matomo/trackEvent'
import { DottedName, NodeValue } from '@incubateur-ademe/nosgestesclimat'
import { twMerge } from 'tailwind-merge'

type Props = {
question: DottedName
suggestions: FormattedSuggestion[]
setValue: (value: NodeValue | Record<string, NodeValue>) => void
}

export default function Suggestions({ question, setValue }: Props) {
const { suggestions } = useRule(question)

export default function Suggestions({
question,
suggestions,
setValue,
}: Props) {
const { currentCategory } = useForm()

if (!suggestions?.length) return
const filteredSuggestions = suggestions.filter(
(suggestion) => !aucunLabels.includes(suggestion.label)
)

if (filteredSuggestions.length === 0) {
return null
}

return (
<div className="mb-6 flex flex-wrap justify-start gap-x-2 gap-y-2.5 text-sm">
{suggestions.map((suggestion) => (
{filteredSuggestions.map((suggestion) => (
<button
key={suggestion.label}
data-cypress-id="suggestion"
Expand Down
87 changes: 87 additions & 0 deletions src/components/form/question/mosaic/MosaicAucunOption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { questionClickSuggestion } from '@/constants/tracking/question'
import Emoji from '@/design-system/utils/Emoji'
import { useEngine, useRule } from '@/publicodes-state'
import { FormattedSuggestion } from '@/publicodes-state/types'
import { capitalizeString } from '@/utils/capitalizeString'
import { trackEvent } from '@/utils/matomo/trackEvent'
import { DottedName } from '@incubateur-ademe/nosgestesclimat'
import { motion } from 'framer-motion'
import { useEffect, useState } from 'react'
import { twMerge } from 'tailwind-merge'

type Props = {
question: DottedName
aucunOption: FormattedSuggestion
questionsOfMosaic: DottedName[]
}

const buttonClassNames = {
checked: 'border-primary-700 text-primary-700 border-2 cursor-pointer ',
unchecked: 'border-primary-200 hover:bg-primary-50 border-2 cursor-pointer ',
}
const checkClassNames = {
checked: 'border-primary-700',
unchecked: 'border-primary-200',
}

const labelClassNames = {
checked: 'text-primary-700',
unchecked: 'text-primary-700',
}

export default function MosaicAucunOption({
question,
aucunOption,
questionsOfMosaic,
}: Props) {
const { setValue, isMissing } = useRule(question)

const { getValue } = useEngine()

const isMosaicChildrenSelected = questionsOfMosaic
.map((question) => getValue(question))
.some((value) => value)

const [isSelected, setIsSelected] = useState(false)
const status = isSelected ? 'checked' : 'unchecked'

useEffect(() => {
if (isMissing) {
return
}
setIsSelected(!isMosaicChildrenSelected)
}, [isMosaicChildrenSelected, isMissing])

return (
<button
className={twMerge(
`relative flex h-full items-center gap-2 rounded-xl border bg-white px-4 py-2 text-left transition-colors`,
buttonClassNames[status]
)}
onClick={() => {
trackEvent(
questionClickSuggestion({ question, answer: aucunOption.label })
)
setValue(aucunOption.value, { foldedStep: question })
}}>
<span
className={`${checkClassNames[status]} flex h-5 w-5 items-center justify-center rounded-sm border-2 leading-4`}>
{status === 'checked' ? (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.2 }}
className={`font-mono text-2xl ${labelClassNames[status]}`}>
</motion.div>
) : (
''
)}
</span>
<span
className={`inline-block align-middle text-sm md:text-lg ${labelClassNames[status]}`}>
<Emoji>{capitalizeString(aucunOption.label)}</Emoji>
</span>
</button>
)
}
10 changes: 10 additions & 0 deletions src/constants/aucunLabels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const aucunLabels = new Set([
'aucun',
'pas de chauffage',
'aucun animal',
'aucun sport',
'aucun 🙅‍♂️',
'pas de chauffage 🙅‍♂️',
'aucun animal 🙅‍♂️',
'aucun sport 🙅‍♂️',
])
Loading
Loading