refactor(champs): refactor champs components to use typescript
This commit is contained in:
parent
76735819b4
commit
02f977fd8d
15 changed files with 119 additions and 111 deletions
|
@ -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);
|
38
app/javascript/components/Chartkick.tsx
Normal file
38
app/javascript/components/Chartkick.tsx
Normal 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);
|
|
@ -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;
|
|
@ -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 d’Armor', '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;
|
|
@ -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;
|
|
@ -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;
|
11
app/javascript/components/ComboMultipleDropdownList.tsx
Normal file
11
app/javascript/components/ComboMultipleDropdownList.tsx
Normal 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)} />;
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
1
app/javascript/types.d.ts
vendored
1
app/javascript/types.d.ts
vendored
|
@ -21,3 +21,4 @@ declare module '@tmcw/togeojson/dist/togeojson.es.js' {
|
|||
}
|
||||
|
||||
declare module 'react-coordinate-input';
|
||||
declare module 'chartkick';
|
||||
|
|
|
@ -86,12 +86,6 @@ module.exports = function (api) {
|
|||
{
|
||||
async: false
|
||||
}
|
||||
],
|
||||
isProductionEnv && [
|
||||
'babel-plugin-transform-react-remove-prop-types',
|
||||
{
|
||||
removeImport: true
|
||||
}
|
||||
]
|
||||
].filter(Boolean)
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue