refactor(champs): refactor champs components to use typescript

This commit is contained in:
Paul Chavard 2022-04-15 13:05:46 +02:00
parent 76735819b4
commit 02f977fd8d
15 changed files with 119 additions and 111 deletions

View file

@ -1,35 +0,0 @@
import Chartkick from 'chartkick';
import Highcharts from 'highcharts';
import { toggle, delegate } from '@utils';
export default function () {
return null;
}
function toggleChart(event) {
const nextSelectorItem = event.target,
chartClass = event.target.dataset.toggleChart,
nextChart = document.querySelector(chartClass),
nextChartId = nextChart.children[0].id,
currentSelectorItem = nextSelectorItem.parentElement.querySelector(
'.segmented-control-item-active'
),
currentChart = nextSelectorItem.parentElement.parentElement.querySelector(
'.chart:not(.hidden)'
);
// Change the current selector and the next selector states
currentSelectorItem.classList.toggle('segmented-control-item-active');
nextSelectorItem.classList.toggle('segmented-control-item-active');
// Hide the currently shown chart and show the new one
toggle(currentChart);
toggle(nextChart);
// Reflow needed, see https://github.com/highcharts/highcharts/issues/1979
Chartkick.charts[nextChartId].getChartObject().reflow();
}
delegate('click', '[data-toggle-chart]', toggleChart);
Chartkick.use(Highcharts);

View file

@ -0,0 +1,38 @@
import Chartkick from 'chartkick';
import Highcharts from 'highcharts';
import { toggle, delegate } from '@utils';
export default function () {
return null;
}
function toggleChart(event: MouseEvent) {
const nextSelectorItem = event.target as HTMLButtonElement,
chartClass = nextSelectorItem.dataset.toggleChart,
nextChart = chartClass
? document.querySelector<HTMLDivElement>(chartClass)
: undefined,
nextChartId = nextChart?.children[0]?.id,
currentSelectorItem = nextSelectorItem.parentElement?.querySelector(
'.segmented-control-item-active'
),
currentChart =
nextSelectorItem.parentElement?.parentElement?.querySelector<HTMLDivElement>(
'.chart:not(.hidden)'
);
// Change the current selector and the next selector states
currentSelectorItem?.classList.toggle('segmented-control-item-active');
nextSelectorItem.classList.toggle('segmented-control-item-active');
// Hide the currently shown chart and show the new one
currentChart && toggle(currentChart);
nextChart && toggle(nextChart);
// Reflow needed, see https://github.com/highcharts/highcharts/issues/1979
nextChartId && Chartkick.charts[nextChartId]?.getChartObject()?.reflow();
}
delegate('click', '[data-toggle-chart]', toggleChart);
Chartkick.use(Highcharts);

View file

@ -1,16 +1,32 @@
import React from 'react';
import { QueryClientProvider } from 'react-query';
import ComboSearch from './ComboSearch';
import ComboSearch, { ComboSearchProps } from './ComboSearch';
import { queryClient } from './shared/queryClient';
function ComboAnnuaireEducationSearch(props) {
type AnnuaireEducationResult = {
fields: {
identifiant_de_l_etablissement: string;
nom_etablissement: string;
nom_commune: string;
};
};
function transformResults(_: unknown, result: unknown) {
const results = result as { records: AnnuaireEducationResult[] };
return results.records as AnnuaireEducationResult[];
}
export default function ComboAnnuaireEducationSearch(
props: ComboSearchProps<AnnuaireEducationResult>
) {
return (
<QueryClientProvider client={queryClient}>
<ComboSearch
{...props}
scope="annuaire-education"
minimumInputLength={3}
transformResults={(_, { records }) => records}
transformResults={transformResults}
transformResult={({
fields: {
identifiant_de_l_etablissement: id,
@ -18,10 +34,7 @@ function ComboAnnuaireEducationSearch(props) {
nom_commune
}
}) => [id, `${nom_etablissement}, ${nom_commune} (${id})`]}
{...props}
/>
</QueryClientProvider>
);
}
export default ComboAnnuaireEducationSearch;

View file

@ -1,19 +1,21 @@
import React from 'react';
import { QueryClientProvider } from 'react-query';
import { matchSorter } from 'match-sorter';
import PropTypes from 'prop-types';
import ComboSearch from './ComboSearch';
import ComboSearch, { ComboSearchProps } from './ComboSearch';
import { queryClient } from './shared/queryClient';
import { ComboDepartementsSearch } from './ComboDepartementsSearch';
import { useHiddenField, groupId } from './shared/hooks';
type CommuneResult = { code: string; nom: string; codesPostaux: string[] };
// Avoid hiding similar matches for precise queries (like "Sainte Marie")
function searchResultsLimit(term) {
function searchResultsLimit(term: string) {
return term.length > 5 ? 10 : 5;
}
function expandResultsWithMultiplePostalCodes(term, results) {
function expandResultsWithMultiplePostalCodes(term: string, result: unknown) {
const results = result as CommuneResult[];
// A single result may have several associated postal codes.
// To make the search results more precise, we want to generate
// an actual result for each postal code.
@ -44,13 +46,16 @@ const placeholderDepartements = [
['77 Seine-et-Marne', 'Melun'],
['22 Côtes dArmor', 'Saint-Brieuc'],
['47 Lot-et-Garonne', 'Agen']
];
] as const;
const [placeholderDepartement, placeholderCommune] =
placeholderDepartements[
Math.floor(Math.random() * (placeholderDepartements.length - 1))
];
function ComboCommunesSearch({ id, ...props }) {
export default function ComboCommunesSearch({
id,
...props
}: ComboSearchProps<CommuneResult> & { id: string }) {
const group = groupId(id);
const [departementValue, setDepartementValue] = useHiddenField(
group,
@ -74,14 +79,14 @@ function ComboCommunesSearch({ id, ...props }) {
</div>
<ComboDepartementsSearch
{...props}
id={!codeDepartement ? id : null}
id={!codeDepartement ? id : undefined}
describedby={departementDescribedBy}
placeholder={placeholderDepartement}
addForeignDepartement={false}
value={departementValue}
onChange={(_, result) => {
setDepartementValue(result?.nom);
setCodeDepartement(result?.code);
setDepartementValue(result?.nom ?? '');
setCodeDepartement(result?.code ?? '');
}}
/>
</div>
@ -112,9 +117,3 @@ function ComboCommunesSearch({ id, ...props }) {
</QueryClientProvider>
);
}
ComboCommunesSearch.propTypes = {
id: PropTypes.string
};
export default ComboCommunesSearch;

View file

@ -1,14 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import { QueryClientProvider } from 'react-query';
import { matchSorter } from 'match-sorter';
import ComboSearch from './ComboSearch';
import ComboSearch, { ComboSearchProps } from './ComboSearch';
import { queryClient } from './shared/queryClient';
type DepartementResult = { code: string; nom: string };
const extraTerms = [{ code: '99', nom: 'Etranger' }];
function expandResultsWithForeignDepartement(term, results) {
function expandResultsWithForeignDepartement(term: string, result: unknown) {
const results = result as DepartementResult[];
return [
...results,
...matchSorter(extraTerms, term, {
@ -17,10 +19,17 @@ function expandResultsWithForeignDepartement(term, results) {
];
}
type ComboDepartementsSearchProps = Omit<
ComboSearchProps<DepartementResult> & {
addForeignDepartement: boolean;
},
'transformResult' | 'transformResults'
>;
export function ComboDepartementsSearch({
addForeignDepartement = true,
...props
}) {
}: ComboDepartementsSearchProps) {
return (
<ComboSearch
{...props}
@ -34,17 +43,12 @@ export function ComboDepartementsSearch({
);
}
function ComboDepartementsSearchDefault(params) {
export default function ComboDepartementsSearchDefault(
params: ComboDepartementsSearchProps
) {
return (
<QueryClientProvider client={queryClient}>
<ComboDepartementsSearch {...params} />
</QueryClientProvider>
);
}
ComboDepartementsSearch.propTypes = {
...ComboSearch.propTypes,
addForeignDepartement: PropTypes.bool
};
export default ComboDepartementsSearchDefault;

View file

@ -1,15 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { groupId } from './shared/hooks';
import ComboMultiple from './ComboMultiple';
function ComboMultipleDropdownList({ id, ...props }) {
return <ComboMultiple group={groupId(id)} id={id} {...props} />;
}
ComboMultipleDropdownList.propTypes = {
id: PropTypes.string
};
export default ComboMultipleDropdownList;

View file

@ -0,0 +1,11 @@
import React from 'react';
import { groupId } from './shared/hooks';
import ComboMultiple, { ComboMultipleProps } from './ComboMultiple';
export default function ComboMultipleDropdownList({
id,
...props
}: ComboMultipleProps & { id: string }) {
return <ComboMultiple id={id} {...props} group={groupId(id)} />;
}

View file

@ -1,20 +1,20 @@
import React from 'react';
import { QueryClientProvider } from 'react-query';
import ComboSearch from './ComboSearch';
import ComboSearch, { ComboSearchProps } from './ComboSearch';
import { queryClient } from './shared/queryClient';
function ComboPaysSearch(props) {
export default function ComboPaysSearch(
props: ComboSearchProps<{ code: string; value: string; label: string }>
) {
return (
<QueryClientProvider client={queryClient}>
<ComboSearch
{...props}
scope="pays"
minimumInputLength={0}
transformResult={({ code, value, label }) => [code, value, label]}
{...props}
/>
</QueryClientProvider>
);
}
export default ComboPaysSearch;

View file

@ -1,20 +1,20 @@
import React from 'react';
import { QueryClientProvider } from 'react-query';
import ComboSearch from './ComboSearch';
import ComboSearch, { ComboSearchProps } from './ComboSearch';
import { queryClient } from './shared/queryClient';
function ComboRegionsSearch(props) {
export default function ComboRegionsSearch(
props: ComboSearchProps<{ code: string; nom: string }>
) {
return (
<QueryClientProvider client={queryClient}>
<ComboSearch
{...props}
scope="regions"
minimumInputLength={0}
transformResult={({ code, nom }) => [code, nom]}
{...props}
/>
</QueryClientProvider>
);
}
export default ComboRegionsSearch;

View file

@ -20,7 +20,7 @@ import { useDeferredSubmit, useHiddenField, groupId } from './shared/hooks';
type TransformResults<Result> = (term: string, results: unknown) => Result[];
type TransformResult<Result> = (
result: Result
) => [key: string, value: string, label: string];
) => [key: string, value: string, label?: string];
export type ComboSearchProps<Result> = {
onChange?: (value: string | null, result?: Result) => void;
@ -28,7 +28,7 @@ export type ComboSearchProps<Result> = {
scope: string;
scopeExtra?: string;
minimumInputLength: number;
transformResults: TransformResults<Result>;
transformResults?: TransformResults<Result>;
transformResult: TransformResult<Result>;
allowInputValues?: boolean;
id?: string;

View file

@ -42,15 +42,20 @@ export function removeClass(el: HTMLElement, cssClass: string) {
el && el.classList.remove(cssClass);
}
export function delegate(
export function delegate<E extends Event = Event>(
eventNames: string,
selector: string,
callback: () => void
callback: (event: E) => void
) {
eventNames
.split(' ')
.forEach((eventName) =>
Rails.delegate(document, selector, eventName, callback)
Rails.delegate(
document,
selector,
eventName,
callback as (event: Event) => void
)
);
}

View file

@ -21,3 +21,4 @@ declare module '@tmcw/togeojson/dist/togeojson.es.js' {
}
declare module 'react-coordinate-input';
declare module 'chartkick';

View file

@ -86,12 +86,6 @@ module.exports = function (api) {
{
async: false
}
],
isProductionEnv && [
'babel-plugin-transform-react-remove-prop-types',
{
removeImport: true
}
]
].filter(Boolean)
};

View file

@ -18,7 +18,6 @@
"@sentry/browser": "6.12.0",
"@tmcw/togeojson": "^4.3.0",
"babel-plugin-macros": "^2.8.0",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"chartkick": "^4.1.1",
"core-js": "^3.6.5",
"debounce": "^1.2.1",
@ -30,7 +29,6 @@
"is-hotkey": "^0.2.0",
"maplibre-gl": "^1.15.2",
"match-sorter": "^6.2.0",
"prop-types": "^15.7.2",
"react": "^18.0.0",
"react-coordinate-input": "^1.0.0",
"react-dom": "^18.0.0",

View file

@ -3417,11 +3417,6 @@ babel-plugin-polyfill-regenerator@^0.3.0:
dependencies:
"@babel/helper-define-polyfill-provider" "^0.3.0"
babel-plugin-transform-react-remove-prop-types@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a"
integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==
backoff@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/backoff/-/backoff-2.5.0.tgz#f616eda9d3e4b66b8ca7fca79f695722c5f8e26f"