Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit f645eb0

Browse files
tomer-shvadronliorzblrn
andauthoredMay 13, 2024··
DataTable and updated AML block (#2369)
* feat: dataTable and updated AML block * feat: moved datatable from molecules to organisms * feat: add new rule * feat: add new rule 2 * feat: pr comments * Revert "feat: add new rule 2" This reverts commit b74a303. * Revert "feat: add new rule" This reverts commit 0920f0f. --------- Co-authored-by: Lior Zamir <[email protected]>
1 parent 0388faa commit f645eb0

File tree

26 files changed

+691
-828
lines changed

26 files changed

+691
-828
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
import React, {
2+
ComponentProps,
3+
FunctionComponent,
4+
useCallback,
5+
useEffect,
6+
useMemo,
7+
useState,
8+
} from 'react';
9+
import {
10+
Cell,
11+
ColumnDef,
12+
ExpandedState,
13+
flexRender,
14+
getCoreRowModel,
15+
getExpandedRowModel,
16+
getSortedRowModel,
17+
OnChangeFn,
18+
RowData,
19+
RowSelectionState,
20+
SortingState,
21+
TableOptions,
22+
useReactTable,
23+
} from '@tanstack/react-table';
24+
import { ScrollArea } from '@/common/components/molecules/ScrollArea/ScrollArea';
25+
import { DefaultCell } from '@/lib/blocks/components/TableCell/DefaultCell';
26+
import {
27+
Table,
28+
TableBody,
29+
TableCaption,
30+
TableCell,
31+
TableHead,
32+
TableHeader,
33+
TableRow,
34+
} from '@/common/components/atoms/Table';
35+
import { ctw } from '@/common/utils/ctw/ctw';
36+
import { CollapsibleContent as ShadCNCollapsibleContent } from '@/common/components/molecules/Collapsible/Collapsible.Content';
37+
import { Collapsible } from '@/common/components/molecules/Collapsible/Collapsible';
38+
import { useSort } from '@/common/hooks/useSort/useSort';
39+
import { isInstanceOfFunction } from '@/common/utils/is-instance-of-function/is-instance-of-function';
40+
import { checkIsBooleanishRecord } from '@/lib/zod/utils/checkers';
41+
import { useSelect } from '@/common/hooks/useSelect/useSelect';
42+
import { ChevronDown } from 'lucide-react';
43+
import { FunctionComponentWithChildren } from '@/common/types';
44+
45+
export interface IDataTableProps<TData, TValue = any> {
46+
data: TData[];
47+
sortByField?: string;
48+
columns: Array<ColumnDef<TData, TValue>>;
49+
caption?: ComponentProps<typeof TableCaption>['children'];
50+
51+
CellContentWrapper?: FunctionComponentWithChildren<{ cell: Cell<TData, TValue> }>;
52+
CollapsibleContent?: FunctionComponent<{ row: TData }>;
53+
54+
// Component props
55+
props?: {
56+
scroll?: Partial<ComponentProps<typeof ScrollArea>>;
57+
table?: ComponentProps<typeof Table>;
58+
header?: ComponentProps<typeof TableHeader>;
59+
head?: ComponentProps<typeof TableHead>;
60+
row?: ComponentProps<typeof TableRow>;
61+
body?: ComponentProps<typeof TableBody>;
62+
cell?: ComponentProps<typeof TableCell>;
63+
caption?: ComponentProps<typeof TableCaption>;
64+
};
65+
66+
// react-table options
67+
options?: Omit<TableOptions<TData>, 'getCoreRowModel' | 'data' | 'columns'>;
68+
}
69+
70+
export const DataTable = <TData extends RowData, TValue = any>({
71+
data,
72+
props,
73+
caption,
74+
columns,
75+
CellContentWrapper,
76+
options = {},
77+
CollapsibleContent,
78+
}: IDataTableProps<TData, TValue>) => {
79+
const [expanded, setExpanded] = useState<ExpandedState>({});
80+
81+
const { enableSorting = false } = options;
82+
83+
const { onSort, sortBy, sortDir } = useSort();
84+
const [sorting, setSorting] = useState<SortingState>([
85+
{
86+
id: sortBy || options?.initialState?.sorting?.[0]?.id || 'id',
87+
desc: sortDir === 'desc' || options?.initialState?.sorting?.[0]?.desc || false,
88+
},
89+
]);
90+
91+
const onSortingChange: OnChangeFn<SortingState> = useCallback(
92+
sortingUpdaterOrValue => {
93+
setSorting(prevSortingState => {
94+
if (!isInstanceOfFunction(sortingUpdaterOrValue)) {
95+
onSort({
96+
sortBy: sortingUpdaterOrValue[0]?.id || sortBy,
97+
sortDir: sortingUpdaterOrValue[0]?.desc ? 'desc' : 'asc',
98+
});
99+
100+
return sortingUpdaterOrValue;
101+
}
102+
103+
const newSortingState = sortingUpdaterOrValue(prevSortingState);
104+
105+
onSort({
106+
sortBy: newSortingState[0]?.id || sortBy,
107+
sortDir: newSortingState[0]?.desc ? 'desc' : 'asc',
108+
});
109+
110+
return newSortingState;
111+
});
112+
},
113+
[onSort, sortBy],
114+
);
115+
116+
const { selected: ids, onSelect } = useSelect();
117+
const [rowSelection, setRowSelection] = useState<RowSelectionState>(
118+
checkIsBooleanishRecord(ids) ? ids : {},
119+
);
120+
121+
const onRowSelectionChange: OnChangeFn<RowSelectionState> = useCallback(
122+
selectionUpdaterOrValue => {
123+
setRowSelection(prevSelectionState => {
124+
if (!isInstanceOfFunction(selectionUpdaterOrValue)) {
125+
onSelect(selectionUpdaterOrValue);
126+
127+
return selectionUpdaterOrValue;
128+
}
129+
130+
const newSelectionState = selectionUpdaterOrValue(prevSelectionState);
131+
132+
onSelect(newSelectionState);
133+
134+
return newSelectionState;
135+
});
136+
},
137+
[onSelect],
138+
);
139+
140+
useEffect(() => {
141+
if (Object.keys(ids ?? {}).length > 0) return;
142+
143+
setRowSelection({});
144+
}, [ids]);
145+
146+
const state = useMemo(
147+
() => ({
148+
rowSelection,
149+
...(enableSorting && {
150+
sorting,
151+
}),
152+
...(CollapsibleContent && {
153+
expanded,
154+
}),
155+
}),
156+
[CollapsibleContent, enableSorting, expanded, rowSelection, sorting],
157+
);
158+
159+
const table = useReactTable<TData>({
160+
state,
161+
...options,
162+
data: data ?? [],
163+
columns: columns ?? [],
164+
onRowSelectionChange,
165+
enableRowSelection: true,
166+
getRowId: row => row.id,
167+
getCoreRowModel: getCoreRowModel(),
168+
defaultColumn: {
169+
cell: DefaultCell,
170+
},
171+
...(enableSorting && {
172+
enableSorting,
173+
onSortingChange,
174+
manualSorting: true,
175+
sortDescFirst: true,
176+
enableSortingRemoval: false,
177+
getSortedRowModel: getSortedRowModel(),
178+
}),
179+
...(CollapsibleContent
180+
? {
181+
onExpandedChange: setExpanded,
182+
getExpandedRowModel: getExpandedRowModel(),
183+
}
184+
: {}),
185+
});
186+
187+
return (
188+
<div className="d-full relative overflow-auto rounded-md border bg-white shadow">
189+
<ScrollArea orientation="both" {...props?.scroll}>
190+
<Table {...props?.table}>
191+
{caption && (
192+
<TableCaption
193+
{...props?.caption}
194+
className={ctw('text-foreground', props?.caption?.className)}
195+
>
196+
{caption}
197+
</TableCaption>
198+
)}
199+
<TableHeader className="border-0" {...props?.header}>
200+
{table.getHeaderGroups()?.map(({ id, headers }) => (
201+
<TableRow
202+
key={id}
203+
{...props?.row}
204+
className={ctw('border-b-none', props?.row?.className)}
205+
>
206+
{headers?.map((header, index) => {
207+
return (
208+
<TableHead
209+
key={header.id}
210+
{...props?.head}
211+
className={ctw(
212+
'sticky top-0 z-10 h-[34px] bg-white p-1 text-[14px] font-bold text-[#787981]',
213+
{
214+
'!pl-3.5': index === 0,
215+
},
216+
props?.head?.className,
217+
)}
218+
>
219+
{header.column.id === 'select' && (
220+
<span className={'pe-4'}>
221+
{flexRender(header.column.columnDef.header, header.getContext())}
222+
</span>
223+
)}
224+
{header.column.getCanSort() && header.column.id !== 'select' && (
225+
<button
226+
className="flex h-9 flex-row items-center gap-x-2 px-3 text-left text-[#A3A3A3]"
227+
onClick={() => header.column.toggleSorting()}
228+
>
229+
<span>
230+
{flexRender(header.column.columnDef.header, header.getContext())}
231+
</span>
232+
<ChevronDown
233+
className={ctw('d-4', {
234+
'rotate-180': header.column.getIsSorted() === 'asc',
235+
})}
236+
/>
237+
</button>
238+
)}
239+
{!header.column.getCanSort() &&
240+
!header.isPlaceholder &&
241+
flexRender(header.column.columnDef.header, header.getContext())}
242+
</TableHead>
243+
);
244+
})}
245+
</TableRow>
246+
))}
247+
</TableHeader>
248+
<TableBody {...props?.body}>
249+
{!!table.getRowModel().rows?.length &&
250+
table.getRowModel().rows?.map(row => (
251+
<React.Fragment key={row.id}>
252+
<TableRow
253+
key={row.id}
254+
{...props?.row}
255+
className={ctw(
256+
'h-[76px] border-b-0 even:bg-[#F4F6FD]/50 hover:bg-[#F4F6FD]/90',
257+
props?.row?.className,
258+
)}
259+
>
260+
{row.getVisibleCells()?.map(cell => (
261+
<TableCell
262+
key={cell.id}
263+
{...props?.cell}
264+
className={ctw('!py-px !pl-3.5', props?.cell?.className)}
265+
>
266+
{CellContentWrapper ? (
267+
<CellContentWrapper cell={cell}>
268+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
269+
</CellContentWrapper>
270+
) : (
271+
flexRender(cell.column.columnDef.cell, cell.getContext())
272+
)}
273+
</TableCell>
274+
))}
275+
</TableRow>
276+
{CollapsibleContent && (
277+
<Collapsible open={row.getIsExpanded()} asChild>
278+
<ShadCNCollapsibleContent asChild>
279+
<TableRow className={`max-h-[228px] border-y-[1px]`}>
280+
<TableCell colSpan={10} className={`p-8`}>
281+
<CollapsibleContent row={row.original} />
282+
</TableCell>
283+
</TableRow>
284+
</ShadCNCollapsibleContent>
285+
</Collapsible>
286+
)}
287+
</React.Fragment>
288+
))}
289+
{!table.getRowModel().rows?.length && (
290+
<TableRow
291+
{...props?.row}
292+
className={ctw('hover:bg-unset h-6 border-none', props?.row?.className)}
293+
>
294+
<TableCell
295+
colSpan={columns?.length}
296+
{...props?.cell}
297+
className={ctw('!py-px !pl-3.5', props?.cell?.className)}
298+
>
299+
No results.
300+
</TableCell>
301+
</TableRow>
302+
)}
303+
</TableBody>
304+
</Table>
305+
</ScrollArea>
306+
</div>
307+
);
308+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { THit } from '@/lib/blocks/components/AmlBlock/utils/aml-adapter';
2+
import { TextWithNAFallback } from '@/common/components/atoms/TextWithNAFallback/TextWithNAFallback';
3+
import React, { useMemo } from 'react';
4+
import { buttonVariants } from '@/common/components/atoms/Button/Button';
5+
import dayjs from 'dayjs';
6+
7+
interface IAmlMatchProps {
8+
match: {
9+
pep: THit['pep'];
10+
warnings: THit['warnings'];
11+
sanctions: THit['sanctions'];
12+
adverseMedia: THit['adverseMedia'];
13+
fitnessProbity: THit['fitnessProbity'];
14+
};
15+
}
16+
17+
export const AmlMatch = ({ match }: IAmlMatchProps) => {
18+
const orderedTypes = useMemo(
19+
() => [
20+
{ key: 'pep', accessor: 'person', header: 'PEP' },
21+
{ key: 'warnings', accessor: 'warning', header: 'Warnings' },
22+
{ key: 'sanctions', accessor: 'sanction', header: 'Sanctions' },
23+
{ key: 'adverseMedia', accessor: 'entry', header: 'Adverse Media' },
24+
{ key: 'fitnessProbity', accessor: 'entry', header: 'Fitness Probity' },
25+
],
26+
[],
27+
);
28+
29+
return (
30+
<div className={`flex flex-col gap-y-2`}>
31+
<div className={`flex gap-x-6 px-4 font-semibold`}>
32+
<span className={`w-[100px] text-left`}>Type</span>
33+
<span className={`w-full text-left`}>Source Name</span>
34+
<span className={`w-[120px] text-left`}>Source URL</span>
35+
<span className={`w-[150px] text-left`}>Date</span>
36+
</div>
37+
{orderedTypes.map(type =>
38+
match[type.key].map(item => (
39+
<div key={type.key} className={`flex gap-x-6 px-4`}>
40+
<span className={`w-[100px] text-left`}>{type.header}</span>
41+
<TextWithNAFallback className={`w-full text-left`}>
42+
{item[type.accessor]}
43+
</TextWithNAFallback>
44+
<TextWithNAFallback className={`w-[120px] text-left`}>
45+
{item.source && (
46+
<a
47+
className={buttonVariants({
48+
variant: 'link',
49+
className: 'h-[unset] cursor-pointer !p-0 !text-blue-500',
50+
})}
51+
target={'_blank'}
52+
rel={'noopener noreferrer'}
53+
href={item.source}
54+
>
55+
Link
56+
</a>
57+
)}
58+
</TextWithNAFallback>
59+
<TextWithNAFallback className={`w-[150px] text-left`}>
60+
{item.date && dayjs(item.date).format('MMM DD, YYYY')}
61+
</TextWithNAFallback>
62+
</div>
63+
)),
64+
)}
65+
</div>
66+
);
67+
};

‎apps/backoffice-v2/src/lib/blocks/components/AmlBlock/hooks/useAmlBlock/useAmlBlock.tsx

+91-243
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import { createBlocksTyped } from '@/lib/blocks/create-blocks-typed/create-blocks-typed';
2-
import { ComponentProps, useMemo } from 'react';
1+
import { toTitleCase } from 'string-ts';
2+
import { ChevronDown } from 'lucide-react';
3+
import React, { ComponentProps, useMemo } from 'react';
4+
import { createColumnHelper } from '@tanstack/react-table';
5+
36
import { Badge } from '@ballerine/ui';
4-
import { WarningFilledSvg } from '@/common/components/atoms/icons';
5-
import { buttonVariants } from '@/common/components/atoms/Button/Button';
6-
import { amlAdapter } from '@/lib/blocks/components/AmlBlock/utils/aml-adapter';
7+
import { ctw } from '@/common/utils/ctw/ctw';
78
import { TWorkflowById } from '@/domains/workflows/fetchers';
9+
import { AmlMatch } from '@/lib/blocks/components/AmlBlock/AmlMatch';
10+
import { amlAdapter } from '@/lib/blocks/components/AmlBlock/utils/aml-adapter';
11+
import { Button } from '@/common/components/atoms/Button/Button';
12+
import { createBlocksTyped } from '@/lib/blocks/create-blocks-typed/create-blocks-typed';
13+
import { TextWithNAFallback } from '@/common/components/atoms/TextWithNAFallback/TextWithNAFallback';
814

915
export const useAmlBlock = (data: Array<TWorkflowById['context']['aml']>) => {
1016
const amlBlock = useMemo(() => {
@@ -17,6 +23,8 @@ export const useAmlBlock = (data: Array<TWorkflowById['context']['aml']>) => {
1723

1824
const { totalMatches, fullReport, dateOfCheck, matches } = amlAdapter(aml);
1925

26+
const columnHelper = createColumnHelper<ReturnType<typeof amlAdapter>['matches'][number]>();
27+
2028
return [
2129
...createBlocksTyped()
2230
.addBlock()
@@ -78,264 +86,104 @@ export const useAmlBlock = (data: Array<TWorkflowById['context']['aml']>) => {
7886
},
7987
})
8088
.addCell({
81-
type: 'table',
89+
type: 'dataTable',
8290
value: {
83-
props: {
84-
table: {
85-
className: 'my-8',
86-
},
87-
},
91+
data: matches,
92+
props: { scroll: { className: 'h-[50vh]' }, cell: { className: '!p-0' } },
93+
CollapsibleContent: ({ row: match }) => <AmlMatch match={match} />,
8894
columns: [
95+
columnHelper.display({
96+
id: 'collapsible',
97+
cell: ({ row }) => (
98+
<Button
99+
onClick={() => row.toggleExpanded()}
100+
disabled={row.getCanExpand()}
101+
variant="ghost"
102+
size="icon"
103+
className={`p-[7px]`}
104+
>
105+
<ChevronDown
106+
className={ctw('d-4', {
107+
'rotate-180': row.getIsExpanded(),
108+
})}
109+
/>
110+
</Button>
111+
),
112+
}),
113+
columnHelper.display({
114+
id: 'index',
115+
cell: info => {
116+
const index = info.cell.row.index + 1;
117+
118+
return (
119+
<TextWithNAFallback className={`p-1 font-semibold`}>
120+
{index}
121+
</TextWithNAFallback>
122+
);
123+
},
124+
header: '#',
125+
}),
89126
{
90127
accessorKey: 'matchedName',
91128
header: 'Matched Name',
92129
},
93-
{
94-
accessorKey: 'dateOfBirth',
95-
header: 'Date Of Birth',
96-
},
97130
{
98131
accessorKey: 'countries',
99132
header: 'Countries',
100133
},
101-
{
102-
accessorKey: 'aka',
103-
header: 'AKA',
104-
},
105-
],
106-
data: matches,
107-
},
108-
})
109-
.build()
110-
.flat(1),
111-
...(matches?.flatMap(({ warnings, sanctions, pep, adverseMedia }, index) =>
112-
createBlocksTyped()
113-
.addBlock()
114-
.addCell({
115-
type: 'container',
116-
value: createBlocksTyped()
117-
.addBlock()
118-
.addCell({
119-
type: 'subheading',
120-
value: `Match ${index + 1}`,
121-
props: {
122-
className: 'text-lg block my-6',
123-
},
124-
})
125-
.addCell({
126-
type: 'table',
127-
value: {
128-
props: {
129-
table: {
130-
className: 'my-8 w-full',
131-
},
132-
},
133-
columns: [
134-
{
135-
accessorKey: 'warning',
136-
header: 'Warning',
137-
cell: props => {
138-
const value = props.getValue();
139-
140-
return (
141-
<div className={'flex space-x-2'}>
142-
<WarningFilledSvg className={'mt-px'} width={'20'} height={'20'} />
143-
<span>{value}</span>
144-
</div>
145-
);
146-
},
147-
},
148-
{
149-
accessorKey: 'date',
150-
header: 'Date',
151-
},
152-
{
153-
accessorKey: 'source',
154-
header: 'Source URL',
155-
cell: props => {
156-
const value = props.getValue();
134+
columnHelper.accessor('matchTypes', {
135+
header: 'Match Type',
136+
cell: info => {
137+
const matchTypes = info.getValue();
157138

158-
return (
159-
<a
160-
className={buttonVariants({
161-
variant: 'link',
162-
className: 'h-[unset] cursor-pointer !p-0 !text-blue-500',
163-
})}
164-
target={'_blank'}
165-
rel={'noopener noreferrer'}
166-
href={value}
167-
>
168-
Link
169-
</a>
170-
);
171-
},
172-
},
173-
],
174-
data: warnings,
139+
return <TextWithNAFallback>{toTitleCase(matchTypes)}</TextWithNAFallback>;
175140
},
176-
})
177-
.addCell({
178-
type: 'table',
179-
props: {
180-
table: {
181-
className: 'my-8 w-full',
182-
},
183-
},
184-
value: {
185-
columns: [
186-
{
187-
accessorKey: 'sanction',
188-
header: 'Sanction',
189-
cell: props => {
190-
const value = props.getValue();
191-
192-
return (
193-
<div className={'flex space-x-2'}>
194-
<WarningFilledSvg className={'mt-px'} width={'20'} height={'20'} />
195-
<span>{value}</span>
196-
</div>
197-
);
198-
},
199-
},
200-
{
201-
accessorKey: 'date',
202-
header: 'Date',
203-
},
204-
{
205-
accessorKey: 'source',
206-
header: 'Source URL',
207-
cell: props => {
208-
const value = props.getValue();
141+
}),
142+
columnHelper.accessor('pep', {
143+
cell: info => {
144+
const pepLength = info.getValue().length;
209145

210-
return (
211-
<a
212-
className={buttonVariants({
213-
variant: 'link',
214-
className: 'h-[unset] cursor-pointer !p-0 !text-blue-500',
215-
})}
216-
target={'_blank'}
217-
rel={'noopener noreferrer'}
218-
href={value}
219-
>
220-
Link
221-
</a>
222-
);
223-
},
224-
},
225-
],
226-
data: sanctions,
146+
return <TextWithNAFallback>{pepLength}</TextWithNAFallback>;
227147
},
228-
})
229-
.addCell({
230-
type: 'table',
231-
value: {
232-
props: {
233-
table: {
234-
className: 'my-8 w-full',
235-
},
236-
},
237-
columns: [
238-
{
239-
accessorKey: 'person',
240-
header: 'PEP (Politically Exposed Person)',
241-
cell: props => {
242-
const value = props.getValue();
148+
header: 'PEP',
149+
}),
150+
columnHelper.accessor('warnings', {
151+
cell: info => {
152+
const warningsLength = info.getValue().length;
243153

244-
return (
245-
<div className={'flex space-x-2'}>
246-
<WarningFilledSvg className={'mt-px'} width={'20'} height={'20'} />
247-
<span>{value}</span>
248-
</div>
249-
);
250-
},
251-
},
252-
{
253-
accessorKey: 'date',
254-
header: 'Date',
255-
},
256-
{
257-
accessorKey: 'source',
258-
header: 'Source URL',
259-
cell: props => {
260-
const value = props.getValue();
154+
return <TextWithNAFallback>{warningsLength}</TextWithNAFallback>;
155+
},
156+
header: 'Warnings',
157+
}),
158+
columnHelper.accessor('sanctions', {
159+
cell: info => {
160+
const sanctionsLength = info.getValue().length;
261161

262-
return (
263-
<a
264-
className={buttonVariants({
265-
variant: 'link',
266-
className: 'h-[unset] cursor-pointer !p-0 !text-blue-500',
267-
})}
268-
target={'_blank'}
269-
rel={'noopener noreferrer'}
270-
href={value}
271-
>
272-
Link
273-
</a>
274-
);
275-
},
276-
},
277-
],
278-
data: pep,
162+
return <TextWithNAFallback>{sanctionsLength}</TextWithNAFallback>;
279163
},
280-
})
281-
.addCell({
282-
type: 'table',
283-
value: {
284-
props: {
285-
table: {
286-
className: 'my-8',
287-
},
288-
},
289-
columns: [
290-
{
291-
accessorKey: 'entry',
292-
header: 'Adverse Media',
293-
cell: props => {
294-
const value = props.getValue();
164+
header: 'Sanctions',
165+
}),
166+
columnHelper.accessor('adverseMedia', {
167+
cell: info => {
168+
const adverseMediaLength = info.getValue().length;
295169

296-
return (
297-
<div className={'flex space-x-2'}>
298-
<WarningFilledSvg className={'mt-px'} width={'20'} height={'20'} />
299-
<span>{value}</span>
300-
</div>
301-
);
302-
},
303-
},
304-
{
305-
accessorKey: 'date',
306-
header: 'Date',
307-
},
308-
{
309-
accessorKey: 'source',
310-
header: 'Source URL',
311-
cell: props => {
312-
const value = props.getValue();
170+
return <TextWithNAFallback>{adverseMediaLength}</TextWithNAFallback>;
171+
},
172+
header: 'Adverse Media',
173+
}),
174+
columnHelper.accessor('fitnessProbity', {
175+
cell: info => {
176+
const fitnessProbityLength = info.getValue().length;
313177

314-
return (
315-
<a
316-
className={buttonVariants({
317-
variant: 'link',
318-
className: 'h-[unset] cursor-pointer !p-0 !text-blue-500',
319-
})}
320-
target={'_blank'}
321-
rel={'noopener noreferrer'}
322-
href={value}
323-
>
324-
Link
325-
</a>
326-
);
327-
},
328-
},
329-
],
330-
data: adverseMedia,
178+
return <TextWithNAFallback>{fitnessProbityLength}</TextWithNAFallback>;
331179
},
332-
})
333-
.build()
334-
.flat(1),
335-
})
336-
.build()
337-
.flat(1),
338-
) ?? []),
180+
header: 'Fitness Probity',
181+
}),
182+
],
183+
},
184+
})
185+
.build()
186+
.flat(1),
339187
];
340188
});
341189
}, [data]);

‎apps/backoffice-v2/src/lib/blocks/components/AmlBlock/utils/aml-adapter.ts

+16-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const SourceInfoSchema = z
99
.optional()
1010
.nullable();
1111

12-
const HitSchema = z.object({
12+
export const HitSchema = z.object({
1313
matchedName: z.string().optional().nullable(),
1414
dateOfBirth: z.string().optional().nullable(),
1515
countries: z.array(z.string()).optional().nullable(),
@@ -19,14 +19,17 @@ const HitSchema = z.object({
1919
sanctions: z.array(SourceInfoSchema).optional().nullable(),
2020
pep: z.array(SourceInfoSchema).optional().nullable(),
2121
adverseMedia: z.array(SourceInfoSchema).optional().nullable(),
22+
fitnessProbity: z.array(SourceInfoSchema).optional().nullable(),
2223
});
2324

25+
export type THit = z.infer<typeof HitSchema>;
26+
2427
export const AmlSchema = z.object({
2528
hits: z.array(HitSchema).optional().nullable(),
2629
createdAt: z.string().optional().nullable(),
2730
});
2831

29-
export type TAml = z.output<typeof AmlSchema>;
32+
export type TAml = z.infer<typeof AmlSchema>;
3033

3134
export const amlAdapter = (aml: TAml) => {
3235
const { hits, createdAt } = aml;
@@ -47,36 +50,43 @@ export const amlAdapter = (aml: TAml) => {
4750
warnings,
4851
pep,
4952
adverseMedia,
53+
fitnessProbity,
5054
}) => ({
5155
matchedName,
5256
dateOfBirth,
5357
countries: countries?.join(', ') ?? '',
5458
matchTypes: matchTypes?.join(', ') ?? '',
5559
aka: aka?.join(', ') ?? '',
5660
sanctions:
57-
sanctions?.map(sanction => ({
61+
sanctions?.filter(Boolean).map(sanction => ({
5862
sanction: sanction?.sourceName,
5963
date: sanction?.date,
6064
source: sanction?.sourceUrl,
6165
})) ?? [],
6266
warnings:
63-
warnings?.map(warning => ({
67+
warnings?.filter(Boolean).map(warning => ({
6468
warning: warning?.sourceName,
6569
date: warning?.date,
6670
source: warning?.sourceUrl,
6771
})) ?? [],
6872
pep:
69-
pep?.map(pepItem => ({
73+
pep?.filter(Boolean).map(pepItem => ({
7074
person: pepItem?.sourceName,
7175
date: pepItem?.date,
7276
source: pepItem?.sourceUrl,
7377
})) ?? [],
7478
adverseMedia:
75-
adverseMedia?.map(adverseMediaItem => ({
79+
adverseMedia?.filter(Boolean).map(adverseMediaItem => ({
7680
entry: adverseMediaItem?.sourceName,
7781
date: adverseMediaItem?.date,
7882
source: adverseMediaItem?.sourceUrl,
7983
})) ?? [],
84+
fitnessProbity:
85+
fitnessProbity?.filter(Boolean).map(fitnessProbityItem => ({
86+
entry: fitnessProbityItem?.sourceName,
87+
date: fitnessProbityItem?.date,
88+
source: fitnessProbityItem?.sourceUrl,
89+
})) ?? [],
8090
}),
8191
) ?? [],
8292
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { FunctionComponent } from 'react';
2+
import { ExtractCellProps } from '@ballerine/blocks';
3+
import { DataTable } from '@/common/components/organisms/DataTable/DataTable';
4+
5+
export const DataTableCell: FunctionComponent<ExtractCellProps<'dataTable'>> = ({ value }) => (
6+
<DataTable {...value} />
7+
);

‎apps/backoffice-v2/src/lib/blocks/components/KycBlock/hooks/useKycBlock/useKycBlock.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ export const useKycBlock = ({
346346
.addBlock()
347347
.addCell(headerCell)
348348
.addCell({
349-
type: 'nodeCell',
349+
type: 'node',
350350
value: <Separator className={`my-2`} />,
351351
})
352352
.addCell({
@@ -453,7 +453,7 @@ export const useKycBlock = ({
453453
.flat(1),
454454
})
455455
.addCell({
456-
type: 'nodeCell',
456+
type: 'node',
457457
value: <Separator className={`my-2`} />,
458458
})
459459
.addCell({
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import { ExtractCellProps } from '@ballerine/blocks';
22
import { FunctionComponent } from 'react';
33

4-
export const NodeCell: FunctionComponent<ExtractCellProps<'nodeCell'>> = ({ value }) => (
5-
<>{value}</>
6-
);
4+
export const NodeCell: FunctionComponent<ExtractCellProps<'node'>> = ({ value }) => <>{value}</>;
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { flexRender, getCoreRowModel, RowData, useReactTable } from '@tanstack/react-table';
2-
import React, { ElementRef, ForwardedRef, forwardRef } from 'react';
2+
import React from 'react';
33
import {
44
Table,
55
TableBody,
@@ -10,98 +10,96 @@ import {
1010
TableRow,
1111
} from '../../../../common/components/atoms/Table';
1212
import { ITableCellProps } from './interfaces';
13-
import { ctw } from '../../../../common/utils/ctw/ctw';
13+
import { ctw } from '@/common/utils/ctw/ctw';
1414
import { DefaultCell } from './DefaultCell';
1515

16-
export const TableCell = forwardRef(
17-
<TData extends RowData, TValue = unknown>(
18-
{ value }: ITableCellProps<TData, TValue>,
19-
ref: ForwardedRef<ElementRef<typeof Table>>,
20-
) => {
21-
const table = useReactTable<TData>({
22-
...value?.options,
23-
data: value.data ?? [],
24-
columns: value.columns ?? [],
25-
getCoreRowModel: getCoreRowModel(),
26-
defaultColumn: {
27-
cell: DefaultCell,
28-
},
29-
});
16+
export const TableCell = <TData extends RowData, TValue = any>({
17+
value,
18+
}: ITableCellProps<TData, TValue>) => {
19+
const table = useReactTable<TData>({
20+
...value?.options,
21+
data: value.data ?? [],
22+
columns: value.columns ?? [],
23+
getCoreRowModel: getCoreRowModel(),
24+
defaultColumn: {
25+
cell: DefaultCell,
26+
},
27+
});
3028

31-
return (
32-
<Table ref={ref} {...value?.props?.table}>
33-
{value?.caption && (
34-
<TableCaption
35-
{...value?.props?.caption}
36-
className={ctw(value?.props?.caption?.className, 'text-foreground')}
29+
return (
30+
<Table {...value?.props?.table}>
31+
{value?.caption && (
32+
<TableCaption
33+
{...value?.props?.caption}
34+
className={ctw(value?.props?.caption?.className, 'text-foreground')}
35+
>
36+
{value.caption}
37+
</TableCaption>
38+
)}
39+
<TableHeader {...value?.props?.header}>
40+
{table.getHeaderGroups()?.map(headerGroup => (
41+
<TableRow
42+
key={headerGroup.id}
43+
{...value?.props?.row}
44+
className={ctw(value?.props?.row?.className, 'hover:bg-unset border-none')}
3745
>
38-
{value.caption}
39-
</TableCaption>
40-
)}
41-
<TableHeader {...value?.props?.header}>
42-
{table.getHeaderGroups()?.map(headerGroup => (
46+
{headerGroup.headers?.map((header, index) => {
47+
return (
48+
<TableHead
49+
key={header.id}
50+
{...value?.props?.head}
51+
className={ctw(
52+
value?.props?.head?.className,
53+
'!h-[unset] !pl-3 pb-2 pt-0 text-sm font-medium leading-none text-foreground',
54+
{
55+
'!pl-3.5': index === 0,
56+
},
57+
)}
58+
>
59+
{!header.isPlaceholder &&
60+
flexRender(header.column.columnDef.header, header.getContext())}
61+
</TableHead>
62+
);
63+
})}
64+
</TableRow>
65+
))}
66+
</TableHeader>
67+
<TableBody {...value?.props?.body}>
68+
{!!table.getRowModel().rows?.length &&
69+
table.getRowModel().rows?.map(row => (
4370
<TableRow
44-
key={headerGroup.id}
71+
key={row.id}
4572
{...value?.props?.row}
46-
className={ctw(value?.props?.row?.className, 'hover:bg-unset border-none')}
73+
className={ctw(value?.props?.row?.className, 'hover:bg-unset h-6 border-none')}
4774
>
48-
{headerGroup.headers?.map((header, index) => {
49-
return (
50-
<TableHead
51-
key={header.id}
52-
{...value?.props?.head}
53-
className={ctw(
54-
value?.props?.head?.className,
55-
'!h-[unset] !pl-3 pb-2 pt-0 text-sm font-medium leading-none text-foreground',
56-
{
57-
'!pl-3.5': index === 0,
58-
},
59-
)}
60-
>
61-
{!header.isPlaceholder &&
62-
flexRender(header.column.columnDef.header, header.getContext())}
63-
</TableHead>
64-
);
65-
})}
75+
{row.getVisibleCells()?.map(cell => (
76+
<TableCellComponent
77+
key={cell.id}
78+
{...value?.props?.cell}
79+
className={ctw(value?.props?.cell?.className, '!py-px !pl-3.5')}
80+
>
81+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
82+
</TableCellComponent>
83+
))}
6684
</TableRow>
6785
))}
68-
</TableHeader>
69-
<TableBody {...value?.props?.body}>
70-
{!!table.getRowModel().rows?.length &&
71-
table.getRowModel().rows?.map(row => (
72-
<TableRow
73-
key={row.id}
74-
{...value?.props?.row}
75-
className={ctw(value?.props?.row?.className, 'hover:bg-unset h-6 border-none')}
76-
>
77-
{row.getVisibleCells()?.map(cell => (
78-
<TableCellComponent
79-
key={cell.id}
80-
{...value?.props?.cell}
81-
className={ctw(value?.props?.cell?.className, '!py-px !pl-3.5')}
82-
>
83-
{flexRender(cell.column.columnDef.cell, cell.getContext())}
84-
</TableCellComponent>
85-
))}
86-
</TableRow>
87-
))}
88-
{!table.getRowModel().rows?.length && (
89-
<TableRow
90-
{...value?.props?.row}
91-
className={ctw(value?.props?.row?.className, 'hover:bg-unset h-6 border-none')}
86+
{!table.getRowModel().rows?.length && (
87+
<TableRow
88+
{...value?.props?.row}
89+
className={ctw(value?.props?.row?.className, 'hover:bg-unset h-6 border-none')}
90+
>
91+
<TableCellComponent
92+
colSpan={value?.columns?.length}
93+
{...value?.props?.cell}
94+
className={ctw(value?.props?.cell?.className, '!py-px !pl-3.5')}
9295
>
93-
<TableCellComponent
94-
colSpan={value?.columns?.length}
95-
{...value?.props?.cell}
96-
className={ctw(value?.props?.cell?.className, '!py-px !pl-3.5')}
97-
>
98-
No results.
99-
</TableCellComponent>
100-
</TableRow>
101-
)}
102-
</TableBody>
103-
</Table>
104-
);
105-
},
106-
);
96+
No results.
97+
</TableCellComponent>
98+
</TableRow>
99+
)}
100+
</TableBody>
101+
</Table>
102+
);
103+
};
104+
107105
TableCell.displayName = 'TableCell';

‎apps/backoffice-v2/src/lib/blocks/components/TableCell/interfaces.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import {
77
TableHead,
88
TableHeader,
99
TableRow,
10-
} from '../../../../common/components/atoms/Table';
10+
} from '@/common/components/atoms/Table';
1111
import { ColumnDef, RowData, TableOptions } from '@tanstack/react-table';
1212

13-
export interface ITableCellProps<TData extends RowData, TValue = unknown> {
13+
export interface ITableCellProps<TData extends RowData, TValue = any> {
1414
// Props to be used explicitly by the cell inside 'value'. Props outside 'value' to be used by the blocks API.
1515
value: {
1616
caption?: ComponentProps<typeof TableCaption>['children'];
1717
columns: Array<ColumnDef<TData, TValue>>;
18-
data: Array<TData>;
18+
data: TData[];
1919

2020
// Component props
2121
props?: {

‎apps/backoffice-v2/src/lib/blocks/create-blocks-typed/create-blocks-typed.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { Subheading } from '@/lib/blocks/components/Subheading/Subheading';
1919
import { TableCell } from '@/lib/blocks/components/TableCell/TableCell';
2020
import { TCell } from '@/lib/blocks/create-blocks-typed/types';
2121
import { CellsMap, createBlocks } from '@ballerine/blocks';
22+
import { DataTableCell } from '@/lib/blocks/components/DataTableCell/DataTableCell';
2223

2324
export const createBlocksTyped = () => createBlocks<TCell>();
2425

@@ -45,9 +46,10 @@ export const cells: CellsMap = {
4546
map: MapCell,
4647
caseCallToActionLegacy: CaseCallToActionLegacy,
4748
table: TableCell,
49+
dataTable: DataTableCell,
4850
paragraph: Paragraph,
4951
dialog: DialogCell,
5052
block: BlockCell,
51-
nodeCell: NodeCell,
53+
node: NodeCell,
5254
pdfViewer: PDFViewerCell,
5355
};

‎apps/backoffice-v2/src/lib/blocks/create-blocks-typed/types.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { CommonWorkflowStates } from '@ballerine/common';
2121
import { AnyChildren, AnyObject } from '@ballerine/ui';
2222
import { ColumnDef, TableOptions } from '@tanstack/react-table';
2323
import { ComponentProps, ReactNode } from 'react';
24+
import { DataTable } from '@/common/components/organisms/DataTable/DataTable';
2425

2526
export type TBlockCell = {
2627
type: 'block';
@@ -203,6 +204,11 @@ export type TTableCell = {
203204
};
204205
};
205206

207+
export type TDataTableCell = {
208+
type: 'dataTable';
209+
value: ComponentProps<typeof DataTable>;
210+
};
211+
206212
export type TParagraphCell = {
207213
type: 'paragraph';
208214
value: ReactNode | ReactNode[];
@@ -215,7 +221,7 @@ export type TDialogCell = {
215221
};
216222

217223
export type TNodeCell = {
218-
type: 'nodeCell';
224+
type: 'node';
219225
value: AnyChildren;
220226
};
221227

@@ -236,6 +242,7 @@ export type TCell =
236242
| TMapCell
237243
| TCaseCallToActionLegacyCell
238244
| TTableCell
245+
| TDataTableCell
239246
| TParagraphCell
240247
| TDialogCell
241248
| TNodeCell

‎apps/backoffice-v2/src/lib/blocks/hooks/useProcessTrackerBlock/useProcessTrackerBlock.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const useProcessTrackerBlock = ({
1212
createBlocksTyped()
1313
.addBlock()
1414
.addCell({
15-
type: 'nodeCell',
15+
type: 'node',
1616
value: <ProcessTracker workflow={workflow} plugins={plugins} processes={processes} />,
1717
})
1818
.build(),

‎apps/backoffice-v2/src/lib/blocks/variants/DefaultBlocks/hooks/useCaseBlocksLogic/utils/create-assosiacted-company-document-blocks.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const createAssociatedCompanyDocumentBlocks = (
1717

1818
childWorkflows.forEach(childWorkflow => {
1919
blocks.addCell({
20-
type: 'nodeCell',
20+
type: 'node',
2121
value: (
2222
<ChildDocumentBlocks
2323
parentWorkflowId={workflow.id}

‎apps/backoffice-v2/src/lib/blocks/variants/DefaultBlocks/hooks/useCaseBlocksLogic/utils/create-kyc-blocks.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const createKycBlocks = (workflow: TWorkflowById) => {
1313

1414
childWorkflows.forEach(childWorkflow => {
1515
blocks.addCell({
16-
type: 'nodeCell',
16+
type: 'node',
1717
value: (
1818
<KycBlock
1919
parentWorkflowId={workflow.id}
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,32 @@
1-
import {
2-
Table,
3-
TableCell,
4-
TableHead,
5-
TableHeader,
6-
TableRow,
7-
} from '@/common/components/atoms/Table';
8-
import { ScrollArea } from '@/common/components/molecules/ScrollArea/ScrollArea';
9-
import { TableBody } from '@ballerine/ui';
10-
import { flexRender } from '@tanstack/react-table';
11-
import { ChevronDown } from 'lucide-react';
12-
import { ctw } from '@/common/utils/ctw/ctw';
131
import React, { FunctionComponent } from 'react';
142
import { IProfilesTableProps } from '@/pages/Profiles/Individuals/components/ProfilesTable/interfaces';
15-
import { useProfilesTableLogic } from '@/pages/Profiles/Individuals/components/ProfilesTable/hooks/useProfiesTableLogic/useProfiesTableLogic';
3+
import { DataTable, IDataTableProps } from '@/common/components/organisms/DataTable/DataTable';
4+
import { columns } from './columns';
165

176
export const ProfilesTable: FunctionComponent<IProfilesTableProps> = ({ data }) => {
18-
const { table, locale, search } = useProfilesTableLogic({ data });
7+
// const locale = useLocale();
8+
// const { search } = useLocation();
199

20-
return (
21-
<div className="d-full relative overflow-auto rounded-md border bg-white shadow">
22-
<ScrollArea orientation="both" className="h-full">
23-
<Table>
24-
<TableHeader className="border-0">
25-
{table.getHeaderGroups().map(({ id, headers }) => {
26-
return (
27-
<TableRow key={id} className={`border-b-none`}>
28-
{headers.map(header => (
29-
<TableHead
30-
key={header.id}
31-
className={`sticky top-0 z-10 h-[34px] bg-white p-1 text-[14px] font-bold text-[#787981]`}
32-
>
33-
{(header.column.id === 'select' || !header.column.getCanSort()) && (
34-
<span
35-
className={ctw({
36-
'pe-4': header.column.id === 'select',
37-
'flex h-9 flex-row items-center px-3 text-left text-[#A3A3A3]':
38-
header.column.id !== 'select',
39-
})}
40-
>
41-
{flexRender(header.column.columnDef.header, header.getContext())}
42-
</span>
43-
)}
44-
{header.column.id !== 'select' && header.column.getCanSort() && (
45-
<button
46-
className="flex h-9 flex-row items-center gap-x-2 px-3 text-left text-[#A3A3A3]"
47-
onClick={() => header.column.toggleSorting()}
48-
>
49-
<span>
50-
{flexRender(header.column.columnDef.header, header.getContext())}
51-
</span>
52-
<ChevronDown
53-
className={ctw('d-4', {
54-
'rotate-180': header.column.getIsSorted() === 'asc',
55-
})}
56-
/>
57-
</button>
58-
)}
59-
</TableHead>
60-
))}
61-
</TableRow>
62-
);
63-
})}
64-
</TableHeader>
65-
<TableBody>
66-
{table.getRowModel().rows.map(row => {
67-
return (
68-
<TableRow
69-
key={row.id}
70-
className="h-[76px] border-b-0 even:bg-[#F4F6FD]/50 hover:bg-[#F4F6FD]/90"
71-
>
72-
{row.getVisibleCells().map(cell => {
73-
const itemId = cell.id.replace(`_${cell.column.id}`, '');
10+
const Cell: IDataTableProps<typeof data>['CellContentWrapper'] = ({ cell, children }) => {
11+
// const itemId = cell.id.replace(`_${cell.column.id}`, '');
12+
13+
return (
14+
<span
15+
// to={`/${locale}/profiles/individuals/${itemId}${search}`}
16+
className={`d-full flex p-4`}
17+
>
18+
{children}
19+
</span>
20+
);
21+
};
7422

75-
return (
76-
<TableCell key={cell.id} className={`p-0`}>
77-
<span
78-
// to={`/${locale}/profiles/individuals/${itemId}${search}`}
79-
className={`d-full flex p-4`}
80-
>
81-
{flexRender(cell.column.columnDef.cell, cell.getContext())}
82-
</span>
83-
</TableCell>
84-
);
85-
})}
86-
</TableRow>
87-
);
88-
})}
89-
</TableBody>
90-
</Table>
91-
</ScrollArea>
92-
</div>
23+
return (
24+
<DataTable
25+
data={data}
26+
columns={columns}
27+
CellContentWrapper={Cell}
28+
sortByField={`createdAt`}
29+
props={{ scroll: { className: 'h-full' }, cell: { className: '!p-0' } }}
30+
/>
9331
);
9432
};

‎apps/backoffice-v2/src/pages/Profiles/Individuals/components/ProfilesTable/columns.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import dayjs from 'dayjs';
44
import React from 'react';
55
import { titleCase } from 'string-ts';
66
import { TObjectValues } from '@/common/types';
7-
import { TIndividualsProfiles } from '@/domains/profiles/fetchers';
87
import { TooltipProvider } from '@/common/components/atoms/Tooltip/Tooltip.Provider';
98
import { Tooltip } from '@/common/components/atoms/Tooltip/Tooltip';
109
import { TooltipTrigger } from '@/common/components/atoms/Tooltip/Tooltip.Trigger';
1110
import { TooltipContent } from '@/common/components/atoms/Tooltip/Tooltip.Content';
1211
import { CopyToClipboard } from '@/common/components/atoms/CopyToClipboard/CopyToClipboard';
1312
import { CheckCircle } from '@/common/components/atoms/CheckCircle/CheckCircle';
1413
import { XCircle } from '@/common/components/atoms/XCircle/XCircle';
14+
import { TIndividualProfile } from '@/domains/profiles/fetchers';
1515

1616
export const Role = {
1717
UBO: 'UBO',
@@ -51,7 +51,7 @@ export const Sanctions = [
5151
Sanction.NOT_MONITORED,
5252
] as const satisfies ReadonlyArray<TObjectValues<typeof Sanction>>;
5353

54-
const columnHelper = createColumnHelper<TIndividualsProfiles>();
54+
const columnHelper = createColumnHelper<TIndividualProfile>();
5555

5656
export const columns = [
5757
columnHelper.accessor('name', {
@@ -94,7 +94,7 @@ export const columns = [
9494
<TextWithNAFallback className={`w-[11.8ch] truncate`}>
9595
{correlationId}
9696
</TextWithNAFallback>
97-
<CopyToClipboard textToCopy={correlationId} disabled={!correlationId} />
97+
<CopyToClipboard textToCopy={correlationId ?? ''} disabled={!correlationId} />
9898
</div>
9999
</TooltipTrigger>
100100
{correlationId && <TooltipContent>{correlationId}</TooltipContent>}

‎apps/backoffice-v2/src/pages/Profiles/Individuals/components/ProfilesTable/hooks/useProfiesTableLogic/useProfiesTableLogic.tsx

-72
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,22 @@
1-
import {
2-
Table,
3-
TableCell,
4-
TableHead,
5-
TableHeader,
6-
TableRow,
7-
} from '@/common/components/atoms/Table';
8-
import { ScrollArea } from '@/common/components/molecules/ScrollArea/ScrollArea';
9-
import { TableBody } from '@ballerine/ui';
10-
import { flexRender } from '@tanstack/react-table';
11-
import { ChevronDown } from 'lucide-react';
12-
import { ctw } from '@/common/utils/ctw/ctw';
131
import React, { FunctionComponent } from 'react';
142
import { IAlertsTableProps } from '@/pages/TransactionMonitoringAlerts/components/AlertsTable/interfaces';
15-
import { useAlertsTableLogic } from '@/pages/TransactionMonitoringAlerts/components/AlertsTable/hooks/useAlertsTableLogic/useAlertsTableLogic';
16-
import { Link } from 'react-router-dom';
3+
import { DataTable } from '@/common/components/organisms/DataTable/DataTable';
4+
import { columns } from './columns';
5+
import { useAlertsTableLogic } from '@/pages/TransactionMonitoringAlerts/components/AlertsTable/hooks/useAlertsTableLogic';
176

187
export const AlertsTable: FunctionComponent<IAlertsTableProps> = ({ data }) => {
19-
const { table, locale, onRowClick, search } = useAlertsTableLogic({ data });
8+
const { Cell } = useAlertsTableLogic({ data });
209

2110
return (
22-
<div className="d-full relative overflow-auto rounded-md border bg-white shadow">
23-
<ScrollArea orientation="both" className="h-full">
24-
<Table>
25-
<TableHeader className="border-0">
26-
{table.getHeaderGroups().map(({ id, headers }) => {
27-
return (
28-
<TableRow key={id} className={`border-b-none`}>
29-
{headers.map(header => (
30-
<TableHead
31-
key={header.id}
32-
className={`sticky top-0 z-10 h-[34px] bg-white p-1 text-[14px] font-bold text-[#787981]`}
33-
>
34-
{header.column.id === 'select' && (
35-
<span className={'pe-4'}>
36-
{flexRender(header.column.columnDef.header, header.getContext())}
37-
</span>
38-
)}
39-
{header.column.id !== 'select' && (
40-
<button
41-
className="flex h-9 flex-row items-center gap-x-2 px-3 text-left text-[#A3A3A3]"
42-
onClick={() => header.column.toggleSorting()}
43-
>
44-
<span>
45-
{flexRender(header.column.columnDef.header, header.getContext())}
46-
</span>
47-
<ChevronDown
48-
className={ctw('d-4', {
49-
'rotate-180': header.column.getIsSorted() === 'asc',
50-
})}
51-
/>
52-
</button>
53-
)}
54-
</TableHead>
55-
))}
56-
</TableRow>
57-
);
58-
})}
59-
</TableHeader>
60-
<TableBody>
61-
{table.getRowModel().rows.map(row => {
62-
return (
63-
<TableRow
64-
key={row.id}
65-
className="h-[76px] border-b-0 even:bg-[#F4F6FD]/50 hover:bg-[#F4F6FD]/90"
66-
>
67-
{row.getVisibleCells().map(cell => {
68-
const itemId = cell.id.replace(`_${cell.column.id}`, '');
69-
const item = data.find(item => item.id === itemId);
70-
71-
return (
72-
<TableCell key={cell.id} className={`p-0`}>
73-
{cell.column.id === 'select' &&
74-
flexRender(cell.column.columnDef.cell, cell.getContext())}
75-
{cell.column.id !== 'select' && (
76-
<Link
77-
to={`/${locale}/transaction-monitoring/alerts/${itemId}${search}&businessId=${
78-
item?.merchant?.id ?? ''
79-
}&counterpartyId=${item?.counterpartyId ?? ''}`}
80-
onClick={onRowClick}
81-
className={`d-full flex p-4`}
82-
>
83-
{flexRender(cell.column.columnDef.cell, cell.getContext())}
84-
</Link>
85-
)}
86-
</TableCell>
87-
);
88-
})}
89-
</TableRow>
90-
);
91-
})}
92-
</TableBody>
93-
</Table>
94-
</ScrollArea>
95-
</div>
11+
<DataTable
12+
data={data}
13+
columns={columns}
14+
CellContentWrapper={Cell}
15+
options={{
16+
enableSorting: true,
17+
initialState: { sorting: [{ id: 'createdAt', desc: true }] },
18+
}}
19+
props={{ scroll: { className: 'h-full' }, cell: { className: '!p-0' } }}
20+
/>
9621
);
9722
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { useLocale } from '@/common/hooks/useLocale/useLocale';
2+
import { Link, useLocation } from 'react-router-dom';
3+
import React, { useCallback } from 'react';
4+
import { IDataTableProps } from '@/common/components/organisms/DataTable/DataTable';
5+
import { TAlertsList } from '@/domains/alerts/fetchers';
6+
7+
interface IUseAlertsTableLogic {
8+
data: TAlertsList;
9+
}
10+
11+
export const useAlertsTableLogic = ({ data }: IUseAlertsTableLogic) => {
12+
const locale = useLocale();
13+
const { pathname, search } = useLocation();
14+
15+
const onClick = useCallback(() => {
16+
sessionStorage.setItem(
17+
'transaction-monitoring:transactions-drawer:previous-path',
18+
`${pathname}${search}`,
19+
);
20+
}, [pathname, search]);
21+
22+
const Cell: IDataTableProps<typeof data>['CellContentWrapper'] = ({ cell, children }) => {
23+
const itemId = cell.id.replace(`_${cell.column.id}`, '');
24+
const item = data.find(item => item.id === itemId);
25+
26+
return (
27+
<Link
28+
to={`/${locale}/transaction-monitoring/alerts/${itemId}${search}&businessId=${
29+
item?.merchant?.id ?? ''
30+
}&counterpartyId=${item?.counterpartyId ?? ''}`}
31+
onClick={onClick}
32+
className={`d-full flex p-4`}
33+
>
34+
{children}
35+
</Link>
36+
);
37+
};
38+
39+
return { Cell };
40+
};

‎apps/backoffice-v2/src/pages/TransactionMonitoringAlerts/components/AlertsTable/hooks/useAlertsTableLogic/useAlertsTableLogic.tsx

-109
This file was deleted.

‎apps/backoffice-v2/src/pages/TransactionMonitoringAlertsAnalysis/components/AlertAnalysisSheet/AlertAnalysisSheet.tsx

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { SheetContent } from '@/common/components/atoms/Sheet';
22
import { Sheet } from '@/common/components/atoms/Sheet/Sheet';
3-
import { AlertAnalysisTable } from 'src/pages/TransactionMonitoringAlertsAnalysis/components/AlertAnalysisTable';
4-
import { FunctionComponent, ReactNode } from 'react';
3+
import React, { FunctionComponent, ReactNode } from 'react';
54
import { TTransactionsList } from '@/domains/transactions/fetchers';
5+
import { ExpandedTransactionDetails } from '@/pages/TransactionMonitoringAlertsAnalysis/components/AlertAnalysisSheet/ExpandedTransactionDetails';
6+
import { columns } from '@/pages/TransactionMonitoringAlertsAnalysis/components/AlertAnalysisSheet/columns';
7+
import { DataTable } from '@/common/components/organisms/DataTable/DataTable';
68

79
export interface IAlertAnalysisProps {
810
onOpenStateChange: () => void;
@@ -29,7 +31,14 @@ export const AlertAnalysisSheet: FunctionComponent<IAlertAnalysisProps> = ({
2931
</div>
3032
</div>
3133
<div>
32-
<AlertAnalysisTable transactions={transactions ?? []} />
34+
<DataTable
35+
columns={columns}
36+
data={transactions}
37+
props={{ scroll: { className: 'h-[47vh]' } }}
38+
CollapsibleContent={({ row: transaction }) => (
39+
<ExpandedTransactionDetails transaction={transaction} />
40+
)}
41+
/>
3342
</div>
3443
</div>
3544
</SheetContent>

‎apps/backoffice-v2/src/pages/TransactionMonitoringAlertsAnalysis/components/AlertAnalysisTable/columns.tsx ‎apps/backoffice-v2/src/pages/TransactionMonitoringAlertsAnalysis/components/AlertAnalysisSheet/columns.tsx

+5-11
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,7 @@ export const columns = [
8989
const counterpartyOriginatorName = info.getValue();
9090

9191
return (
92-
<TextWithNAFallback className="text-sm font-semibold">
93-
{counterpartyOriginatorName}
94-
</TextWithNAFallback>
92+
<TextWithNAFallback className="text-sm">{counterpartyOriginatorName}</TextWithNAFallback>
9593
);
9694
},
9795
header: 'Originator Name',
@@ -101,7 +99,7 @@ export const columns = [
10199
const counterpartyOriginatorCorrelationId = info.getValue();
102100

103101
return (
104-
<TextWithNAFallback className="text-sm font-semibold">
102+
<TextWithNAFallback className="text-sm">
105103
{counterpartyOriginatorCorrelationId}
106104
</TextWithNAFallback>
107105
);
@@ -113,9 +111,7 @@ export const columns = [
113111
const counterpartyBeneficiaryName = info.getValue();
114112

115113
return (
116-
<TextWithNAFallback className="text-sm font-semibold">
117-
{counterpartyBeneficiaryName}
118-
</TextWithNAFallback>
114+
<TextWithNAFallback className="text-sm">{counterpartyBeneficiaryName}</TextWithNAFallback>
119115
);
120116
},
121117
header: 'Beneficiary Name',
@@ -125,7 +121,7 @@ export const columns = [
125121
const counterpartyBeneficiaryCorrelationId = info.getValue();
126122

127123
return (
128-
<TextWithNAFallback className="text-sm font-semibold">
124+
<TextWithNAFallback className="text-sm">
129125
{counterpartyBeneficiaryCorrelationId}
130126
</TextWithNAFallback>
131127
);
@@ -137,9 +133,7 @@ export const columns = [
137133
const paymentMethod = info.getValue() ?? '';
138134

139135
return (
140-
<TextWithNAFallback className="text-sm font-semibold">
141-
{titleCase(paymentMethod)}
142-
</TextWithNAFallback>
136+
<TextWithNAFallback className="text-sm">{titleCase(paymentMethod)}</TextWithNAFallback>
143137
);
144138
},
145139
header: 'Payment Method',

‎apps/backoffice-v2/src/pages/TransactionMonitoringAlertsAnalysis/components/AlertAnalysisTable/AlertAnalysisTable.tsx

-106
This file was deleted.

‎apps/backoffice-v2/src/pages/TransactionMonitoringAlertsAnalysis/components/AlertAnalysisTable/index.ts

-1
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.