Skip to content

Commit e94ffc1

Browse files
authored
feat: categorized equivalents in comparator (#899)
1 parent 406cc7b commit e94ffc1

14 files changed

Lines changed: 551 additions & 49 deletions

src/components/comparateur/overscreens/Checkbox.module.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
}
3030
}
3131

32+
.simpleEquivalent {
33+
border-radius: 0;
34+
}
35+
3236
.equivalentLabel {
3337
display: flex;
3438
width: 100%;

src/components/comparateur/overscreens/Checkbox.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import classNames from 'classnames'
12
import { useTranslations } from 'next-intl'
23
import { ForwardedRef, forwardRef, useEffect, useState } from 'react'
34
import useParamContext from 'src/providers/ParamProvider'
@@ -13,10 +14,12 @@ const Checkbox = (
1314
equivalent,
1415
equivalents,
1516
setEquivalents,
17+
simple,
1618
}: {
1719
equivalent: ComputedEquivalent
1820
equivalents: string[]
1921
setEquivalents: (value: string[]) => void
22+
simple?: boolean
2023
},
2124
ref: ForwardedRef<HTMLInputElement>
2225
) => {
@@ -44,7 +47,9 @@ const Checkbox = (
4447
ref={ref}
4548
key={equivalent.slug}
4649
id={equivalent.slug}
47-
className={interacted && equivalents.length > 7 ? styles.warningEquivalent : styles.equivalent}
50+
className={classNames(interacted && equivalents.length > 7 ? styles.warningEquivalent : styles.equivalent, {
51+
[styles.simpleEquivalent]: simple,
52+
})}
4853
checked={equivalents.includes(equivalent.slug)}
4954
setChecked={(checked) => {
5055
if (equivalents.length > 7) {
@@ -66,7 +71,7 @@ const Checkbox = (
6671
</div>
6772
)}
6873
</div>
69-
<EquivalentIcon height={2} equivalent={equivalent} />
74+
{!simple && <EquivalentIcon height={2} equivalent={equivalent} />}
7075
</>
7176
}
7277
/>

src/components/comparateur/overscreens/ComparisonEquivalents.module.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@
1717
background-color: var(--primary-10);
1818
}
1919
}
20+
21+
.simpleButton {
22+
border-radius: 0;
23+
}
Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import classNames from 'node_modules/classnames'
12
import useParamContext from 'src/providers/ParamProvider'
23
import { Equivalent } from 'types/equivalent'
34
import { getName } from 'utils/Equivalent/equivalent'
@@ -9,38 +10,46 @@ const ComparisonEquivalents = ({
910
onClose,
1011
equivalents,
1112
index,
13+
simple,
1214
}: {
1315
onClose: () => void
1416
equivalents: Equivalent[]
1517
index: 0 | 1
18+
simple?: boolean
1619
}) => {
1720
const {
1821
language,
1922
transport: { comparison, setComparison, selected },
2023
} = useParamContext()
2124

22-
return equivalents.map((equivalent) => (
23-
<li key={equivalent.slug}>
24-
<button
25-
className={styles.button}
26-
onClick={() => {
27-
if (index === 0) {
28-
setComparison([equivalent.slug, comparison[1]])
29-
} else {
30-
setComparison([comparison[0], equivalent.slug])
31-
}
32-
track(
33-
`Transport ${selected === 'distance' ? 'distance' : 'itinéraire'}`,
34-
'Nouvelle comparaison',
35-
equivalent.slug
36-
)
37-
onClose()
38-
}}>
39-
<span>{getName(language, equivalent, false, 1, false, true)}</span>
40-
<EquivalentIcon height={2} equivalent={equivalent} />
41-
</button>
42-
</li>
43-
))
25+
return (
26+
<ul>
27+
{equivalents.map((equivalent) => (
28+
<li key={equivalent.slug}>
29+
<button
30+
className={classNames(styles.button, {
31+
[styles.simpleButton]: simple,
32+
})}
33+
onClick={() => {
34+
if (index === 0) {
35+
setComparison([equivalent.slug, comparison[1]])
36+
} else {
37+
setComparison([comparison[0], equivalent.slug])
38+
}
39+
track(
40+
`Transport ${selected === 'distance' ? 'distance' : 'itinéraire'}`,
41+
'Nouvelle comparaison',
42+
equivalent.slug
43+
)
44+
onClose()
45+
}}>
46+
<span>{getName(language, equivalent, false, 1, false, true)}</span>
47+
{!simple && <EquivalentIcon height={2} equivalent={equivalent} />}
48+
</button>
49+
</li>
50+
))}
51+
</ul>
52+
)
4453
}
4554

4655
export default ComparisonEquivalents

src/components/comparateur/overscreens/ComparisonOverscreen.tsx

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,60 @@
11
'use client'
22
import { useTranslations } from 'next-intl'
3-
import { useState } from 'react'
3+
import { useMemo, useState } from 'react'
44
import useParamContext from 'src/providers/ParamProvider'
55
import { useSearchEquivalent } from 'src/providers/useSearchEquivalent'
6+
import { ComputedEquivalent } from 'types/equivalent'
67
import { computedEquivalents } from 'data/categories/computedEquivalents'
78
import { deplacements } from 'data/categories/deplacement'
89
import { getEquivalentWithCarpool } from 'utils/carpool'
910
import Button from 'components/base/buttons/Button'
1011
import HiddenLabel from 'components/form/HiddenLabel'
1112
import Input from 'components/form/Input'
1213
import ComparisonEquivalents from './ComparisonEquivalents'
14+
import SubCategoryEquivalent from './SubCategoryEquivalent'
15+
import { subCategories } from './equivalentCategories'
1316
import styles from './EquivalentsOverscreen.module.css'
1417

18+
const allEquivalents = computedEquivalents('transport', deplacements).flatMap(getEquivalentWithCarpool)
19+
1520
const ComparisonOverscreen = ({ index }: { index: 0 | 1 }) => {
1621
const { setOverscreen } = useParamContext()
1722
const [search, setSearch] = useState('')
18-
const results = useSearchEquivalent(
19-
search,
20-
false,
21-
4,
22-
false,
23-
computedEquivalents('transport', deplacements).flatMap(getEquivalentWithCarpool)
24-
)
23+
const results = useSearchEquivalent(search, false, 4, false, allEquivalents)
2524

2625
const tSearch = useTranslations('comparateur.overscreen')
2726
const t = useTranslations('overscreen.transport')
2827
const tModal = useTranslations('modal')
2928
const onClose = () => {
3029
setOverscreen('transport', '')
3130
}
31+
32+
const { categoriesEquivalents, categorizedSlugs } = useMemo(() => {
33+
const result = {} as Record<string, Record<string, ComputedEquivalent[]>>
34+
Object.entries(subCategories).forEach(([category, categoryEquivalents]) => {
35+
const categories = {} as Record<string, ComputedEquivalent[]>
36+
Object.entries(categoryEquivalents).forEach(([subCategory, subCategoryEquivalents]) => {
37+
const filteredEquivalents = subCategoryEquivalents
38+
.map((subCategoryEquivalent) => results.find((equivalent) => equivalent.slug === subCategoryEquivalent))
39+
.filter((equivalent) => equivalent !== undefined)
40+
if (filteredEquivalents.length > 0) {
41+
categories[subCategory] = filteredEquivalents
42+
}
43+
})
44+
if (Object.keys(categories).length > 0) {
45+
result[category] = categories
46+
}
47+
})
48+
return {
49+
categoriesEquivalents: result,
50+
categorizedSlugs: Object.values(result).flatMap((subCategories) =>
51+
Object.values(subCategories).flatMap((subCategoryEquivalents) =>
52+
subCategoryEquivalents.map((equivalent) => equivalent.slug)
53+
)
54+
),
55+
}
56+
}, [results])
57+
3258
return (
3359
<>
3460
<div className={styles.header}>
@@ -48,7 +74,7 @@ const ComparisonOverscreen = ({ index }: { index: 0 | 1 }) => {
4874
{tModal('close')}
4975
</Button>
5076
</div>
51-
<ul className={styles.content}>
77+
<div className={styles.content}>
5278
{search ? (
5379
results.length > 0 ? (
5480
<ComparisonEquivalents onClose={onClose} equivalents={results} index={index} />
@@ -68,9 +94,26 @@ const ComparisonOverscreen = ({ index }: { index: 0 | 1 }) => {
6894
</div>
6995
)
7096
) : (
71-
<ComparisonEquivalents onClose={onClose} equivalents={results} index={index} />
97+
<>
98+
{Object.entries(categoriesEquivalents)
99+
.filter(([category]) => category.startsWith('voiture'))
100+
.map(([category, subCategories]) => (
101+
<SubCategoryEquivalent
102+
category={allEquivalents.find((equivalent) => equivalent.slug === category) as ComputedEquivalent}
103+
categoriesEquivalents={subCategories}
104+
onClose={onClose}
105+
index={index}
106+
key={category}
107+
/>
108+
))}
109+
<ComparisonEquivalents
110+
onClose={onClose}
111+
equivalents={results.filter((equivalent) => !categorizedSlugs.includes(equivalent.slug))}
112+
index={index}
113+
/>
114+
</>
72115
)}
73-
</ul>
116+
</div>
74117
<div className={styles.footerCenter}>
75118
<Button
76119
onClick={() => {
Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { RefObject } from 'react'
1+
import { RefObject, useMemo } from 'react'
22
import { ComputedEquivalent } from 'types/equivalent'
33
import Checkbox from './Checkbox'
4+
import SubCategoryEquivalent from './SubCategoryEquivalent'
5+
import { subCategories } from './equivalentCategories'
46

57
const Equivalents = ({
68
equivalentsToDisplay,
@@ -16,16 +18,63 @@ const Equivalents = ({
1618
list?: boolean
1719
}) => {
1820
const Container = list ? 'li' : 'div'
19-
return equivalentsToDisplay.map((equivalent, index) => (
20-
<Container key={equivalent.slug}>
21-
<Checkbox
22-
equivalents={equivalents}
23-
equivalent={equivalent}
24-
setEquivalents={setEquivalents}
25-
ref={index === 0 ? firstRef : undefined}
26-
/>
27-
</Container>
28-
))
21+
const { categoriesEquivalents, categorizedSlugs } = useMemo(() => {
22+
if (list) {
23+
return { categoriesEquivalents: {}, categorizedSlugs: [] }
24+
}
25+
const result = {} as Record<string, Record<string, ComputedEquivalent[]>>
26+
Object.entries(subCategories).forEach(([category, categoryEquivalents]) => {
27+
const categories = {} as Record<string, ComputedEquivalent[]>
28+
Object.entries(categoryEquivalents).forEach(([subCategory, subCategoryEquivalents]) => {
29+
const filteredEquivalents = subCategoryEquivalents
30+
.map((subCategoryEquivalent) =>
31+
equivalentsToDisplay.find((equivalent) => equivalent.slug === subCategoryEquivalent)
32+
)
33+
.filter((equivalent) => equivalent !== undefined)
34+
if (filteredEquivalents.length > 0) {
35+
categories[subCategory] = filteredEquivalents
36+
}
37+
})
38+
if (Object.keys(categories).length > 0) {
39+
result[category] = categories
40+
}
41+
})
42+
return {
43+
categoriesEquivalents: result,
44+
categorizedSlugs: Object.values(result).flatMap((subCategories) =>
45+
Object.values(subCategories).flatMap((subCategoryEquivalents) =>
46+
subCategoryEquivalents.map((equivalent) => equivalent.slug)
47+
)
48+
),
49+
}
50+
}, [equivalentsToDisplay, list])
51+
52+
return (
53+
<>
54+
{Object.entries(categoriesEquivalents).map(([category, subCategories]) => (
55+
<SubCategoryEquivalent
56+
category={equivalentsToDisplay.find((equivalent) => equivalent.slug === category) as ComputedEquivalent}
57+
categoriesEquivalents={subCategories}
58+
equivalents={equivalents}
59+
setEquivalents={setEquivalents}
60+
checkbox
61+
key={category}
62+
/>
63+
))}
64+
{equivalentsToDisplay
65+
.filter((equivalent) => !categorizedSlugs.includes(equivalent.slug))
66+
.map((equivalent, index) => (
67+
<Container key={equivalent.slug}>
68+
<Checkbox
69+
equivalents={equivalents}
70+
equivalent={equivalent}
71+
setEquivalents={setEquivalents}
72+
ref={index === 0 ? firstRef : undefined}
73+
/>
74+
</Container>
75+
))}
76+
</>
77+
)
2978
}
3079

3180
export default Equivalents
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
.header {
2+
cursor: pointer;
3+
display: flex;
4+
padding: 0.75rem;
5+
align-items: center;
6+
gap: 0.75rem;
7+
width: 100%;
8+
background-color: transparent;
9+
border-radius: 0.5rem;
10+
font-weight: 500;
11+
color: var(--neutral-50);
12+
13+
&:hover {
14+
color: var(--neutral-80);
15+
background-color: var(--primary-10);
16+
}
17+
}
18+
19+
.coloredNumber {
20+
color: var(--primary-60);
21+
font-weight: 700;
22+
}
23+
24+
.title {
25+
display: flex;
26+
flex: 1;
27+
justify-content: space-between;
28+
}
29+
30+
.button {
31+
padding: 0.5rem;
32+
border-radius: 50%;
33+
border: solid 2px var(--primary-30);
34+
display: flex;
35+
justify-content: center;
36+
align-items: center;
37+
color: var(--primary-50);
38+
39+
svg {
40+
width: 1.5rem;
41+
height: 1.5rem;
42+
}
43+
}
44+
45+
.subCategories {
46+
display: flex;
47+
flex-direction: column;
48+
gap: 1rem;
49+
margin-top: 0.5rem;
50+
padding: 0.5rem 0 1.5rem 0.75rem;
51+
}
52+
53+
.subCategory {
54+
color: var(--neutral-50);
55+
font-size: 0.875rem;
56+
font-weight: 500;
57+
line-height: 1.5rem;
58+
text-transform: uppercase;
59+
}
60+
61+
.checkbox {
62+
&:not(:last-child) {
63+
border-bottom: 2px solid var(--neutral-20);
64+
}
65+
}

0 commit comments

Comments
 (0)