diff --git a/packages/app-builder/src/components/ContinuousScreening/ConfigurationPanel.tsx b/packages/app-builder/src/components/ContinuousScreening/ConfigurationPanel.tsx index dc2f3e6db..d57848cdd 100644 --- a/packages/app-builder/src/components/ContinuousScreening/ConfigurationPanel.tsx +++ b/packages/app-builder/src/components/ContinuousScreening/ConfigurationPanel.tsx @@ -14,6 +14,7 @@ import { PartialCreateContinuousScreeningConfig, } from './context/CreationStepper'; import { FormPagination } from './context/FormPagination'; +import { ListAndTopicDatasetConfigurationBridge } from './context/ListAndTopicDatasetConfigurationBridge'; import { Stepper } from './form/Stepper'; import { DatasetSelection } from './form/steps/DatasetSelection'; import { GeneralInfo } from './form/steps/GeneralInfo'; @@ -53,16 +54,18 @@ export const ConfigurationPanel = ({ return ( - -
- {match(configurationStepper.value.__internals.currentStep) - .with(0, () => ) - .with(1, () => ) - .with(2, () => ) - .with(3, () => ) - .otherwise(() => null)} -
- + + +
+ {match(configurationStepper.value.__internals.currentStep) + .with(0, () => ) + .with(1, () => ) + .with(2, () => ) + .with(3, () => ) + .otherwise(() => null)} +
+ +
); diff --git a/packages/app-builder/src/components/ContinuousScreening/ConfigurationsPage.tsx b/packages/app-builder/src/components/ContinuousScreening/ConfigurationsPage.tsx index d6b9ecbb9..f3eed7f9b 100644 --- a/packages/app-builder/src/components/ContinuousScreening/ConfigurationsPage.tsx +++ b/packages/app-builder/src/components/ContinuousScreening/ConfigurationsPage.tsx @@ -8,21 +8,24 @@ import QueryString from 'qs'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { match } from 'ts-pattern'; -import { Button } from 'ui-design-system'; +import { Button, Tag } from 'ui-design-system'; import { Icon } from 'ui-icons'; +import { CopyToClipboardButton } from '../CopyToClipboardButton'; import GridTable from '../GridTable'; import { Page } from '../Page'; import { PanelRoot } from '../Panel/Panel'; import { Spinner } from '../Spinner'; import { ConfigurationPanel } from './ConfigurationPanel'; -import { CopyToClipboardChip } from './CopyToClipboardChip'; import { CreationModal } from './CreationModal'; import { PartialCreateContinuousScreeningConfig } from './context/CreationStepper'; import { EditionValidationPanel } from './EditionValidationPanel'; -import { Capsule } from './shared/Capsule'; const CellCapsule = ({ children }: { children: React.ReactNode }) => { - return {children}; + return ( + + {children} + + ); }; export const ConfigurationsPage = ({ canEdit }: { canEdit: boolean }) => { @@ -125,7 +128,9 @@ export const ConfigurationsPage = ({ canEdit }: { canEdit: boolean }) => { > {item.name} - + + {item.stableId} + {item.datasets[0] ? {item.datasets[0]} : null} diff --git a/packages/app-builder/src/components/ContinuousScreening/CopyToClipboardChip.tsx b/packages/app-builder/src/components/ContinuousScreening/CopyToClipboardChip.tsx index 5f2ee7001..3095a0a05 100644 --- a/packages/app-builder/src/components/ContinuousScreening/CopyToClipboardChip.tsx +++ b/packages/app-builder/src/components/ContinuousScreening/CopyToClipboardChip.tsx @@ -1,7 +1,9 @@ import { useGetCopyToClipboard } from '@app-builder/utils/use-get-copy-to-clipboard'; import { cn } from 'ui-design-system'; import { Icon } from 'ui-icons'; - +/** + * @deprecated use CopyToClipboardButton instead (with size = chip and rounded = true) + */ export const CopyToClipboardChip = ({ value, className, diff --git a/packages/app-builder/src/components/ContinuousScreening/CreationPage.tsx b/packages/app-builder/src/components/ContinuousScreening/CreationPage.tsx index eb9800cd5..de47210e6 100644 --- a/packages/app-builder/src/components/ContinuousScreening/CreationPage.tsx +++ b/packages/app-builder/src/components/ContinuousScreening/CreationPage.tsx @@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'; import { BreadCrumbs } from '../Breadcrumbs'; import { Page } from '../Page'; import { ContinuousScreeningConfigurationStepper } from './context/CreationStepper'; +import { ListAndTopicDatasetConfigurationBridge } from './context/ListAndTopicDatasetConfigurationBridge'; import { CreationContent } from './form/Content'; import { Stepper } from './form/Stepper'; @@ -42,17 +43,19 @@ export const CreationPage = ({ name, description }: { name: string; description: return ( - - - - t(`continuousScreening:creation.stepper.${stepName}`)} /> - - - - - - - + + + + + t(`continuousScreening:creation.stepper.${stepName}`)} /> + + + + + + + + ); }; diff --git a/packages/app-builder/src/components/ContinuousScreening/context/CreationStepper.tsx b/packages/app-builder/src/components/ContinuousScreening/context/CreationStepper.tsx index 59ca8665d..d8bdd3869 100644 --- a/packages/app-builder/src/components/ContinuousScreening/context/CreationStepper.tsx +++ b/packages/app-builder/src/components/ContinuousScreening/context/CreationStepper.tsx @@ -73,8 +73,8 @@ export const ContinuousScreeningConfigurationStepper = buildStepper({ steps: [ { name: 'generalInfo', schema: generalInfoStepSchema }, { name: 'objectMapping', schema: objectMappingStepSchema }, - { name: 'scoringConfiguration', schema: scoringConfigurationStepSchema }, { name: 'datasetSelection', schema: datasetSelectionStepSchema }, + { name: 'scoringConfiguration', schema: scoringConfigurationStepSchema }, ] as const, validator: createContinuousScreeningConfigSchema, }); diff --git a/packages/app-builder/src/components/ContinuousScreening/context/ListAndTopicDatasetConfigurationBridge.tsx b/packages/app-builder/src/components/ContinuousScreening/context/ListAndTopicDatasetConfigurationBridge.tsx new file mode 100644 index 000000000..01f8b3036 --- /dev/null +++ b/packages/app-builder/src/components/ContinuousScreening/context/ListAndTopicDatasetConfigurationBridge.tsx @@ -0,0 +1,25 @@ +import { ListAndTopicDatasetConfiguration } from '@app-builder/components/ListAndTopicConfiguration'; +import { type ReactNode, useEffect } from 'react'; +import { ContinuousScreeningConfigurationStepper } from './CreationStepper'; + +/* + this component is used to bridge the ListAndTopicDatasetConfiguration context with the ContinuousScreeningConfigurationStepper context + to ensure that the datasets map is updated when the wizard mode changes +*/ +export function ListAndTopicDatasetConfigurationBridge({ children }: { children: ReactNode }) { + const wizard = ContinuousScreeningConfigurationStepper.useSharp(); + const wizardMode = ContinuousScreeningConfigurationStepper.select((s) => s.__internals.mode); + const datasetsMap = wizard.value.data.datasets; + const listSharp = ListAndTopicDatasetConfiguration.createSharp({ + datasets: datasetsMap, + mode: wizardMode, + }); + + useEffect(() => { + listSharp.actions.setMode(wizardMode); + }, [listSharp, wizardMode]); + + return ( + {children} + ); +} diff --git a/packages/app-builder/src/components/ContinuousScreening/form/Content.tsx b/packages/app-builder/src/components/ContinuousScreening/form/Content.tsx index 9e1f6af1a..443d9a91b 100644 --- a/packages/app-builder/src/components/ContinuousScreening/form/Content.tsx +++ b/packages/app-builder/src/components/ContinuousScreening/form/Content.tsx @@ -19,8 +19,8 @@ export const CreationContent = () => {
{match(creationStepper.computed.currentStep.value) .with(1, () => ) - .with(2, () => ) - .with(3, () => ) + .with(2, () => ) + .with(3, () => ) .otherwise(() => null)}
@@ -42,8 +42,8 @@ const CreationContentRecap = () => { })} > {currentStep >= 1 ? : null} - {currentStep >= 2 ? : null} - {currentStep >= 3 ? : null} + {currentStep >= 2 ? : null} + {currentStep >= 3 ? : null} ); }; diff --git a/packages/app-builder/src/components/ContinuousScreening/form/recaps/DatasetSelectionRecap.tsx b/packages/app-builder/src/components/ContinuousScreening/form/recaps/DatasetSelectionRecap.tsx index f691d5a49..8301044d7 100644 --- a/packages/app-builder/src/components/ContinuousScreening/form/recaps/DatasetSelectionRecap.tsx +++ b/packages/app-builder/src/components/ContinuousScreening/form/recaps/DatasetSelectionRecap.tsx @@ -1,59 +1,38 @@ -import { useScreeningDatasetsQuery } from '@app-builder/queries/screening/datasets'; -import { OpenSanctionsCatalogSection } from 'marble-api'; +import { getSectionLeafNames } from '@app-builder/components/ListAndTopicConfiguration'; +import { SCREENING_CATEGORY_I18N_KEY_MAP, type ScreeningCategory } from '@app-builder/models/screening'; +import { type ListConfigFilters, useListConfigQuery } from '@app-builder/queries/screening/lists-config'; import { useTranslation } from 'react-i18next'; -import { match } from 'ts-pattern'; import { ContinuousScreeningConfigurationStepper } from '../../context/CreationStepper'; import { RecapCapsule, RecapRow } from '../../shared/RecapRow'; -const calculateSelectedCountByTags = (sections: OpenSanctionsCatalogSection[], selectedDatasets: string[]) => { - const flatDatasets = sections.flatMap((section) => section.datasets); - const selectedCountByTags: Record = {}; - - selectedDatasets.forEach((datasetName) => { - const dataset = flatDatasets.find((d) => d.name === datasetName); - if (dataset) { - const datasetTag = dataset.tag ? dataset.tag : 'unknown'; - selectedCountByTags[datasetTag] = (selectedCountByTags[datasetTag] ?? 0) + 1; - } - }); - - return selectedCountByTags; -}; +type SectionData = NonNullable; export const DatasetSelectionRecap = () => { const { t } = useTranslation(['continuousScreening', 'scenarios']); - const datasetsQuery = useScreeningDatasetsQuery(); - const selectedDatasets = ContinuousScreeningConfigurationStepper.select((state) => - Object.keys(state.data.datasets).filter((k) => !!state.data.datasets[k]), - ); + const listConfigQuery = useListConfigQuery('continuous_monitoring'); + const datasets = ContinuousScreeningConfigurationStepper.select((state) => state.data.datasets); + + const enabledSections = Object.entries(listConfigQuery.data ?? {}).filter( + ([key, section]) => !!datasets[key] && section != null, + ) as [ScreeningCategory, SectionData][]; return ( - {t('continuousScreening:creation.datasetSelection.recap.title', { count: selectedDatasets.length })} - {match(datasetsQuery) - .with({ isPending: true }, () =>
Loading...
) - .with({ isError: true }, () =>
Error
) - .with({ isSuccess: true }, ({ data }) => { - const selectedCountByTags = calculateSelectedCountByTags(data.datasets.sections, selectedDatasets); - const entries = Object.entries(selectedCountByTags); - - return ( -
- {entries.map(([tag, count]) => ( - - {count}{' '} - {match(tag) - .with('peps', () => t(`scenarios:sanction.lists.peps`)) - .with('third-parties', () => t(`scenarios:sanction.lists.third_parties`)) - .with('sanctions', () => t(`scenarios:sanction.lists.sanctions`)) - .with('adverse-media', () => t(`scenarios:sanction.lists.adverse_media`)) - .otherwise(() => t(`scenarios:sanction.lists.other`))} - - ))} -
- ); - }) - .exhaustive()} + {t('continuousScreening:creation.datasetSelection.recap.title', { count: enabledSections.length })} + {enabledSections.map(([key, section]) => { + const leafCount = getSectionLeafNames(section).filter((n) => !!datasets[n]).length; + const sectionLabel = t(`scenarios:sanction.lists.${SCREENING_CATEGORY_I18N_KEY_MAP[key]}`); + return ( + + + {sectionLabel} + + {t('continuousScreening:creation.datasetSelection.recap.section_items', { count: leafCount })} + + + + ); + })}
); }; diff --git a/packages/app-builder/src/components/ContinuousScreening/form/steps/DatasetSelection.tsx b/packages/app-builder/src/components/ContinuousScreening/form/steps/DatasetSelection.tsx index ec4d9eb93..a83ba2d49 100644 --- a/packages/app-builder/src/components/ContinuousScreening/form/steps/DatasetSelection.tsx +++ b/packages/app-builder/src/components/ContinuousScreening/form/steps/DatasetSelection.tsx @@ -1,22 +1,14 @@ import { Callout } from '@app-builder/components/Callout'; -import { DatasetTag } from '@app-builder/components/Screenings/DatasetTag'; -import { Spinner } from '@app-builder/components/Spinner'; -import { ScreeningCategory } from '@app-builder/models/screening'; -import { useScreeningDatasetsQuery } from '@app-builder/queries/screening/datasets'; -import { useCallbackRef } from '@marble/shared'; -import * as Collapsible from '@radix-ui/react-collapsible'; -import { OpenSanctionsCatalogDataset, OpenSanctionsCatalogSection } from 'marble-api'; -import { MouseEvent, useState } from 'react'; +import { + DatasetSelectionContent, + ListAndTopicDatasetConfiguration, +} from '@app-builder/components/ListAndTopicConfiguration'; +import { type AvailableFeatures } from '@app-builder/models/screening'; import { useTranslation } from 'react-i18next'; -import { match } from 'ts-pattern'; -import { Button, Checkbox } from 'ui-design-system'; -import { Icon } from 'ui-icons'; -import { ContinuousScreeningConfigurationStepper } from '../../context/CreationStepper'; -export const DatasetSelection = () => { +export const DatasetSelection = ({ useCase }: { useCase: AvailableFeatures }) => { const { t } = useTranslation(['common', 'continuousScreening']); - const datasetsQuery = useScreeningDatasetsQuery(); - const mode = ContinuousScreeningConfigurationStepper.select((state) => state.__internals.mode); + const mode = ListAndTopicDatasetConfiguration.select((state) => state.mode); const tKey = mode === 'view' ? 'view' : 'creation'; return ( @@ -25,124 +17,8 @@ export const DatasetSelection = () => { {t(`continuousScreening:${tKey}.datasetSelection.callout`)}
-
- {t('continuousScreening:creation.datasetSelection.list.title')} - -
-
- {match(datasetsQuery) - .with({ isPending: true }, () => ( -
- -
- )) - .with({ isError: true }, () => ( -
-
{t('common:generic_fetch_data_error')}
- -
- )) - .with({ isSuccess: true }, ({ data }) => { - return ( -
- {data.datasets.sections.map((datasetSection) => ( - - ))} -
- ); - }) - .exhaustive()} -
+
); }; - -const SelectedListsCount = () => { - const { t } = useTranslation(['continuousScreening']); - const selectedDatasetsCount = ContinuousScreeningConfigurationStepper.select( - (state) => Object.values(state.data.datasets).filter(Boolean).length, - ); - return {t('continuousScreening:creation.datasetSelection.list.count', { count: selectedDatasetsCount })}; -}; - -const DatasetSection = ({ section }: { section: OpenSanctionsCatalogSection }) => { - const [isExpanded, setIsExpanded] = useState(false); - - return ( - - - setIsExpanded(!isExpanded)}> -
- - {section.title} -
- -
-
- - {section.datasets.map((dataset) => ( - - ))} - -
- ); -}; - -const DatasetItem = ({ dataset }: { dataset: OpenSanctionsCatalogDataset }) => { - const creationStepper = ContinuousScreeningConfigurationStepper.useSharp(); - const isSelected = ContinuousScreeningConfigurationStepper.select((state) => state.data.datasets[dataset.name]); - const handleChange = useCallbackRef(() => { - const stepperData = creationStepper.value.data; - stepperData.datasets[dataset.name] = !stepperData.datasets[dataset.name]; - }); - const mode = ContinuousScreeningConfigurationStepper.select((state) => state.__internals.mode); - - return ( -
-
- - {dataset.title} -
- {dataset.tag ? : null} -
- ); -}; - -const SelectAllCheckbox = ({ section }: { section: OpenSanctionsCatalogSection }) => { - const { t } = useTranslation(['continuousScreening']); - const creationStepper = ContinuousScreeningConfigurationStepper.useSharp(); - const mode = ContinuousScreeningConfigurationStepper.select((state) => state.__internals.mode); - const selectedState = ContinuousScreeningConfigurationStepper.select((state) => { - const selectedCount = section.datasets.filter((dataset) => state.data.datasets[dataset.name]).length; - return selectedCount === section.datasets.length ? true : selectedCount === 0 ? false : 'indeterminate'; - }); - const handleClick = useCallbackRef((e: MouseEvent) => { - e.stopPropagation(); - const nextState = selectedState === true ? false : true; - const datasetsNames = section.datasets.map((d) => d.name); - - datasetsNames.forEach((datasetName) => { - creationStepper.value.data.datasets[datasetName] = nextState; - }); - }); - - return ( -
- - - {t( - `continuousScreening:creation.datasetSelection.list.section.${selectedState === true ? 'unselect_all' : 'select_all'}`, - )} - -
- ); -}; diff --git a/packages/app-builder/src/components/ContinuousScreening/form/steps/GeneralInfo.tsx b/packages/app-builder/src/components/ContinuousScreening/form/steps/GeneralInfo.tsx index c00d086e5..7538873a2 100644 --- a/packages/app-builder/src/components/ContinuousScreening/form/steps/GeneralInfo.tsx +++ b/packages/app-builder/src/components/ContinuousScreening/form/steps/GeneralInfo.tsx @@ -1,6 +1,6 @@ +import { CopyToClipboardButton } from '@app-builder/components/CopyToClipboardButton'; import { useTranslation } from 'react-i18next'; import { Input, TextArea } from 'ui-design-system'; -import { CopyToClipboardChip } from '../../CopyToClipboardChip'; import { ContinuousScreeningConfigurationStepper } from '../../context/CreationStepper'; export const GeneralInfo = ({ stableId }: { stableId: string }) => { @@ -14,7 +14,9 @@ export const GeneralInfo = ({ stableId }: { stableId: string }) => {
(name.value = e.target.value)} />
- + + {stableId} +