Skip to content

Commit b7df200

Browse files
committed
feat: add nb_selected to datasets & memo to some components
1 parent a21352c commit b7df200

File tree

10 files changed

+77
-73
lines changed

10 files changed

+77
-73
lines changed

packages/app-builder/src/components/Scenario/Sanction/FieldNode.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,17 @@ import { type AstNode, NewUndefinedAstNode } from '@app-builder/models';
33
import { MatchOperand } from './MatchOperand';
44

55
export const FieldNode = ({
6-
name,
76
value,
87
placeholder,
98
onChange,
109
onBlur,
1110
}: {
1211
value?: AstNode;
13-
name?: string;
1412
onChange?: (value: AstNode) => void;
1513
onBlur?: () => void;
1614
placeholder?: string;
1715
}) => (
18-
<>
19-
<input name={name} className="sr-only" tabIndex={-1} onBlur={onBlur} />
16+
<div onBlur={onBlur}>
2017
<MatchOperand
2118
node={value ?? NewUndefinedAstNode()}
2219
placeholder={placeholder}
@@ -28,5 +25,5 @@ export const FieldNode = ({
2825
onChange?.(node);
2926
}}
3027
/>
31-
</>
28+
</div>
3229
);

packages/app-builder/src/components/Scenario/Sanction/FieldNodeConcat.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import { Icon } from 'ui-icons';
77

88
import { MatchOperand } from './MatchOperand';
99

10-
export function FieldMatches({
11-
name,
10+
export function FieldNodeConcat({
1211
value,
1312
limit,
1413
onBlur,
@@ -18,7 +17,6 @@ export function FieldMatches({
1817
value?: AstNode;
1918
limit?: number;
2019
placeholder?: string;
21-
name?: string;
2220
onChange?: (nodes: AstNode) => void;
2321
onBlur?: () => void;
2422
}) {
@@ -41,14 +39,15 @@ export function FieldMatches({
4139
onChange?.({
4240
name: 'StringConcat',
4341
children: values(nodes),
44-
namedChildren: {},
42+
namedChildren: {
43+
withSeparator: { constant: true, namedChildren: {}, children: [] },
44+
},
4545
});
4646
}
4747
}, [nodes, matches.length, onChange]);
4848

4949
return (
50-
<div className="flex flex-wrap gap-2">
51-
<input name={name} className="sr-only" tabIndex={-1} onBlur={onBlur} />
50+
<div onBlur={onBlur} className="flex flex-wrap gap-2">
5251
{(matches.length === 0 ? defaultMatches : matches).map(
5352
([id, match], i) => (
5453
<div key={id} className="flex gap-2">

packages/app-builder/src/components/Scenario/Sanction/FieldSanction.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,21 @@ import {
88
useMemo,
99
useState,
1010
} from 'react';
11+
import { memo } from 'react';
1112
import { useTranslation } from 'react-i18next';
1213
import { Checkbox, CollapsibleV2 } from 'ui-design-system';
1314
import { Icon } from 'ui-icons';
1415

15-
const FieldCategory = ({
16+
const FieldCategory = memo(function FieldCategory({
1617
section,
1718
selectedIds,
1819
updateSelectedIds,
1920
}: {
2021
section: OpenSanctionsCatalogSection;
2122
selectedIds: string[];
2223
updateSelectedIds: Dispatch<SetStateAction<string[]>>;
23-
}) => {
24-
const { t } = useTranslation(['common']);
24+
}) {
25+
const { t } = useTranslation(['common', 'scenarios']);
2526
const [open, setOpen] = useState(false);
2627

2728
const defaultListIds = useMemo(
@@ -34,6 +35,11 @@ const FieldCategory = ({
3435
[selectedIds, defaultListIds],
3536
);
3637

38+
const nbSelected = useMemo(
39+
() => selectedIds.filter((id) => defaultListIds.includes(id)).length,
40+
[selectedIds, defaultListIds],
41+
);
42+
3743
return (
3844
<CollapsibleV2.Provider defaultOpen={open}>
3945
<div key={section.name} className="w-full overflow-hidden rounded-lg">
@@ -49,6 +55,13 @@ const FieldCategory = ({
4955
})}
5056
/>
5157
<span className="text-s font-semibold">{section.title}</span>
58+
{!isAllSelected && nbSelected ? (
59+
<span className="text-s text-purple-65 font-semibold">
60+
{t('scenarios:sanction.lists.nb_selected', {
61+
count: nbSelected,
62+
})}
63+
</span>
64+
) : null}
5265
</CollapsibleV2.Title>
5366
<div className="flex items-center gap-4">
5467
<span className="text-grey-50 text-xs">
@@ -89,18 +102,16 @@ const FieldCategory = ({
89102
</div>
90103
</CollapsibleV2.Provider>
91104
);
92-
};
105+
});
93106

94107
export const FieldSanction = ({
95-
name,
96108
onChange,
97109
onBlur,
98110
sections,
99111
defaultValue,
100112
}: {
101113
defaultValue?: string[];
102114
sections: OpenSanctionsCatalogSection[];
103-
name?: string;
104115
onChange?: (value: string[]) => void;
105116
onBlur?: () => void;
106117
}) => {
@@ -113,8 +124,7 @@ export const FieldSanction = ({
113124
}, [selectedIds, onChange]);
114125

115126
return (
116-
<div className="flex flex-col gap-4">
117-
<input name={name} className="sr-only" tabIndex={-1} onBlur={onBlur} />
127+
<div onBlur={onBlur} className="flex flex-col gap-4">
118128
{sections.map((section) => (
119129
<FieldCategory
120130
key={section.name}

packages/app-builder/src/components/Scenario/Sanction/FieldTrigger.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,13 @@ import { useStore } from 'zustand';
1010
import { AstBuilder, type AstBuilderProps } from '../AstBuilder';
1111

1212
export const FieldTrigger = ({
13-
name,
1413
trigger,
1514
scenarioId,
1615
iterationId,
1716
options,
1817
onChange,
1918
onBlur,
2019
}: {
21-
name?: string;
2220
trigger?: AstNode;
2321
onChange?: (node: AstNode | undefined) => void;
2422
onBlur?: () => void;
@@ -44,9 +42,8 @@ export const FieldTrigger = ({
4442
}, [astNode, onChange]);
4543

4644
return (
47-
<>
48-
<input name={name} className="sr-only" tabIndex={-1} onBlur={onBlur} />
45+
<div onBlur={onBlur}>
4946
<AstBuilder astEditorStore={astEditorStore} options={options} />
50-
</>
47+
</div>
5148
);
5249
};

packages/app-builder/src/components/Scenario/Sanction/MatchOperand.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@ import {
33
useGetAstNodeOperandProps,
44
useOperandOptions,
55
} from '@app-builder/services/editor/options';
6+
import { memo } from 'react';
67

78
import { Operand } from '../AstBuilder/AstBuilderNode/Operand';
89

9-
export const MatchOperand = ({
10+
export const MatchOperand = memo(function MatchOperand({
1011
node,
1112
onSave,
1213
placeholder,
1314
}: {
1415
node: AstNode;
1516
onSave?: (astNode: AstNode) => void;
1617
placeholder?: string;
17-
}) => {
18+
}) {
1819
const getOperandAstNodeOperandProps = useGetAstNodeOperandProps();
1920
const options = useOperandOptions();
2021

@@ -27,4 +28,4 @@ export const MatchOperand = ({
2728
onSave={onSave}
2829
/>
2930
);
30-
};
31+
});

packages/app-builder/src/locales/ar/scenarios.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,5 +393,7 @@
393393
"sanction.nudge": "تحسين قواعدك من خلال فحص العقوبات بناءً على واجهة برمجة تطبيقات OpenSacntion",
394394
"sanction_counterparty_id": "معرف الطرف المقابل",
395395
"sanction_counterparty_name": "اسم الطرف المقابل",
396-
"sanction_transaction_label": "تسمية المعاملة"
396+
"sanction_transaction_label": "تسمية المعاملة",
397+
"sanction.lists.nb_selected_one": "({{count}} المحدد)",
398+
"sanction.lists.nb_selected_other": "({{count}} المحدد)"
397399
}

packages/app-builder/src/locales/en/scenarios.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@
182182
"sanction.match_settings.callout": "Choose information that should be checked.",
183183
"sanction.lists.title": "Sanction lists",
184184
"sanction.lists.callout": "Select lists that are relevant to your business",
185+
"sanction.lists.nb_selected_one": "({{count}} selected)",
186+
"sanction.lists.nb_selected_other": "({{count}} selected)",
185187
"sanction_transaction_label": "Transaction Label",
186188
"sanction_counterparty_id": "Counterparty ID",
187189
"sanction_counterparty_name": "Counterparty name",

packages/app-builder/src/locales/fr/scenarios.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,5 +393,7 @@
393393
"sanction.nudge": "Améliorez vos règles avec un chèque de sanction basé sur l'OpenSacntion API",
394394
"sanction_counterparty_id": "Identifiant de contrepartie",
395395
"sanction_counterparty_name": "Nom de contrepartie",
396-
"sanction_transaction_label": "Étiquette de transaction"
396+
"sanction_transaction_label": "Étiquette de transaction",
397+
"sanction.lists.nb_selected_one": "({{count}} sélectionnée)",
398+
"sanction.lists.nb_selected_other": "({{count}} sélectionnées)"
397399
}

packages/app-builder/src/routes/_builder+/scenarios+/$scenarioId+/i+/$iterationId+/sanction.tsx

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { FormLabel } from '@app-builder/components/Form/Tanstack/FormLabel';
1111
import { setToastMessage } from '@app-builder/components/MarbleToaster';
1212
import { type AstBuilderProps } from '@app-builder/components/Scenario/AstBuilder';
1313
import { FieldNode } from '@app-builder/components/Scenario/Sanction/FieldNode';
14-
import { FieldMatches } from '@app-builder/components/Scenario/Sanction/FieldNodeConcat';
14+
import { FieldNodeConcat } from '@app-builder/components/Scenario/Sanction/FieldNodeConcat';
1515
import { FieldOutcomes } from '@app-builder/components/Scenario/Sanction/FieldOutcomes';
1616
import { FieldRuleGroup } from '@app-builder/components/Scenario/Sanction/FieldRuleGroup';
1717
import { FieldSanction } from '@app-builder/components/Scenario/Sanction/FieldSanction';
@@ -27,14 +27,14 @@ import { OptionsProvider } from '@app-builder/services/editor/options';
2727
import { serverServices } from '@app-builder/services/init.server';
2828
import { getRoute } from '@app-builder/utils/routes';
2929
import { fromParams, fromUUID, useParam } from '@app-builder/utils/short-uuid';
30-
import { parseWithZod } from '@conform-to/zod';
3130
import {
3231
type ActionFunctionArgs,
3332
json,
3433
type LoaderFunctionArgs,
3534
} from '@remix-run/node';
3635
import { useFetcher, useLoaderData } from '@remix-run/react';
3736
import { useForm } from '@tanstack/react-form';
37+
import { decode as formDataToObject } from 'decode-formdata';
3838
import { type Namespace } from 'i18next';
3939
import { serialize as objectToFormData } from 'object-to-formdata';
4040
import { useMemo } from 'react';
@@ -126,7 +126,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
126126
const editSanctionFormSchema = z.object({
127127
name: z.string().nonempty(),
128128
description: z.string().optional(),
129-
scoreModifier: z.number(),
129+
scoreModifier: z.coerce.number(),
130130
ruleGroup: z.string().nonempty(),
131131
forceOutcome: z.union([
132132
z.literal('review'),
@@ -150,31 +150,32 @@ export async function action({ request, params }: ActionFunctionArgs) {
150150
toastSessionService: { getSession, commitSession },
151151
} = serverServices;
152152

153-
const session = await getSession(request);
154-
const formData = await request.formData();
155-
const iterationId = fromParams(params, 'iterationId');
156-
157-
const submission = parseWithZod(formData, { schema: editSanctionFormSchema });
153+
const [session, formData, { scenarioIterationSanctionRepository }] =
154+
await Promise.all([
155+
getSession(request),
156+
request.formData(),
157+
authService.isAuthenticated(request, {
158+
failureRedirect: getRoute('/sign-in'),
159+
}),
160+
]);
158161

159-
if (submission.status !== 'success') {
160-
return json(submission.reply());
161-
}
162-
163-
const { scenarioIterationSanctionRepository } =
164-
await authService.isAuthenticated(request, {
165-
failureRedirect: getRoute('/sign-in'),
166-
});
162+
const iterationId = fromParams(params, 'iterationId');
163+
const formDataDecoded = formDataToObject(formData, {
164+
arrays: ['datasets'],
165+
});
167166

168167
try {
168+
const data = editSanctionFormSchema.parse(formDataDecoded);
169+
169170
await scenarioIterationSanctionRepository.upsertSanctionCheckConfig({
170171
iterationId,
171172
changes: {
172-
...submission.value,
173-
counterPartyId: submission.value.counterPartyId as AstNode | undefined,
174-
triggerRule: submission.value.triggerRule as AstNode | undefined,
173+
...data,
174+
counterPartyId: data.counterPartyId as AstNode | undefined,
175+
triggerRule: data.triggerRule as AstNode | undefined,
175176
query: {
176-
name: submission.value.query.name as AstNode,
177-
label: submission.value.query.label as AstNode | undefined,
177+
name: data.query.name as AstNode,
178+
label: data.query.label as AstNode | undefined,
178179
},
179180
},
180181
});
@@ -455,7 +456,6 @@ export default function SanctionDetail() {
455456
options={options}
456457
onBlur={field.handleBlur}
457458
onChange={field.handleChange}
458-
name={field.name}
459459
trigger={field.state.value}
460460
/>
461461
)}
@@ -482,7 +482,6 @@ export default function SanctionDetail() {
482482
{t('scenarios:sanction_counterparty_id')}
483483
</FormLabel>
484484
<FieldNode
485-
name={field.name}
486485
value={field.state.value}
487486
onChange={field.handleChange}
488487
onBlur={field.handleBlur}
@@ -500,8 +499,7 @@ export default function SanctionDetail() {
500499
<FormLabel name={field.name}>
501500
{t('scenarios:sanction_counterparty_name')}
502501
</FormLabel>
503-
<FieldMatches
504-
name={field.name}
502+
<FieldNodeConcat
505503
value={field.state.value}
506504
onChange={field.handleChange}
507505
onBlur={field.handleBlur}
@@ -521,7 +519,6 @@ export default function SanctionDetail() {
521519
{t('scenarios:sanction_transaction_label')}
522520
</FormLabel>
523521
<FieldNode
524-
name={field.name}
525522
value={field.state.value}
526523
onChange={field.handleChange}
527524
onBlur={field.handleBlur}
@@ -552,7 +549,6 @@ export default function SanctionDetail() {
552549
{(field) => (
553550
<div className="flex flex-col gap-2">
554551
<FieldSanction
555-
name={field.name}
556552
defaultValue={field.state.value}
557553
onChange={field.handleChange}
558554
onBlur={field.handleBlur}

0 commit comments

Comments
 (0)