Skip to content

Commit

Permalink
feature/business_report_metrics (#2658)
Browse files Browse the repository at this point in the history
* added status metric reports

* added mcc stats

* feat: added aggregations and handling of pie charts

* feat: finalized metrics of reports

* fixed comments

* feat: fixed comments of Omri

---------

Co-authored-by: Omri Levy <61207713+Omri-Levy@users.noreply.github.com>
  • Loading branch information
Blokh and Omri-Levy authored Sep 4, 2024
1 parent 1c35980 commit 64b30cc
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 155 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,28 @@ export const ReportsByRiskLevelSchema = z.object({
});

export const HomeMetricsOutputSchema = z.object({
mccCounts: z.array(
z.object({
mcc: z.number(),
count: z.number(),
percentage: z.number(),
mccDescription: z.string(),
}),
),
reportStatuses: z.array(
z.object({
status: z.string(),
count: z.number(),
}),
),
riskIndicators: z.array(
z.object({
name: z.string(),
count: z.number(),
}),
),
reports: z.object({
reportsRisks: z.object({
all: ReportsByRiskLevelSchema,
inProgress: ReportsByRiskLevelSchema,
approved: ReportsByRiskLevelSchema,
}),
});
Expand Down
7 changes: 6 additions & 1 deletion apps/backoffice-v2/src/pages/Statistics/Statistics.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ export const Statistics: FunctionComponent = () => {
<h1 className={'pb-5 text-2xl font-bold'}>Statistics</h1>
<div className={'flex flex-col space-y-8'}>
<UserStatistics fullName={'John Doe'} />
<PortfolioRiskStatistics riskIndicators={data.riskIndicators} reports={data.reports} />
<PortfolioRiskStatistics
riskIndicators={data.riskIndicators}
reportStatuses={data.reportStatuses}
reportsRisks={data.reportsRisks}
mccCounts={data.mccCounts}
/>
<WorkflowStatistics />
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { CardContent } from '@/common/components/atoms/Card/Card.Content';
import { Cell, Pie, PieChart } from 'recharts';
import { ctw } from '@/common/utils/ctw/ctw';
import { Button } from '@/common/components/atoms/Button/Button';
import { TrendingDown, TrendingUp } from 'lucide-react';
import { TrendingUp } from 'lucide-react';
import {
Table,
TableBody,
Expand All @@ -18,11 +18,14 @@ import { titleCase } from 'string-ts';
import { usePortfolioRiskStatisticsLogic } from '@/pages/Statistics/components/PortfolioRiskStatistics/hooks/usePortfolioRiskStatisticsLogic/usePortfolioRiskStatisticsLogic';
import { z } from 'zod';
import { HomeMetricsOutputSchema } from '@/domains/metrics/hooks/queries/useHomeMetricsQuery/useHomeMetricsQuery';
import {
reportStatusToBackgroundColor,
reportStatusToFillColor,
} from '@/pages/Statistics/components/PortfolioRiskStatistics/constants';

export const PortfolioRiskStatistics: FunctionComponent<{
riskIndicators: z.infer<typeof HomeMetricsOutputSchema>['riskIndicators'];
reports: z.infer<typeof HomeMetricsOutputSchema>['reports'];
}> = ({ riskIndicators, reports }) => {
export const PortfolioRiskStatistics: FunctionComponent<
z.infer<typeof HomeMetricsOutputSchema>
> = ({ riskIndicators, reportsRisks, reportStatuses, mccCounts }) => {
const {
riskLevelToFillColor,
parent,
Expand All @@ -35,84 +38,15 @@ export const PortfolioRiskStatistics: FunctionComponent<{
filteredRiskIndicators,
} = usePortfolioRiskStatisticsLogic({
riskIndicators,
reports,
reportsRisks,
reportStatuses,
mccCounts,
});

return (
<div>
<h5 className={'mb-4 font-bold'}>Portfolio Risk Statistics</h5>
<div className={'grid grid-cols-3 gap-6'}>
<div className={'min-h-[27.5rem] rounded-xl bg-[#F6F6F6] p-2'}>
<Card className={'flex h-full flex-col px-3'}>
<CardHeader className={'pb-1'}>Portfolio Risk</CardHeader>
<CardContent>
<p className={'mb-8 text-slate-400'}>
Risk levels of approved merchants from completed onboarding flows.
</p>
<div className={'flex flex-col items-center space-y-4 pt-3'}>
<PieChart width={184} height={184}>
<text
x={92}
y={82}
textAnchor="middle"
dominantBaseline="middle"
className={'text-lg font-bold'}
>
{Object.values(reports.approved).reduce((acc, curr) => acc + curr, 0)}
</text>
<text x={92} y={102} textAnchor="middle" dominantBaseline="middle">
Merchants
</text>
<Pie
data={Object.entries(reports.approved).map(([riskLevel, value]) => ({
name: `${titleCase(riskLevel)} Risk`,
value,
}))}
cx={87}
cy={87}
innerRadius={78}
outerRadius={92}
fill="#8884d8"
paddingAngle={5}
dataKey="value"
cornerRadius={9999}
>
{Object.keys(riskLevelToFillColor).map(riskLevel => (
<Cell
key={riskLevel}
className={ctw(
riskLevelToFillColor[riskLevel as keyof typeof riskLevelToFillColor],
'outline-none',
)}
/>
))}
</Pie>
</PieChart>
<ul className={'flex w-full max-w-sm flex-col space-y-2'}>
{Object.entries(reports.approved).map(([riskLevel, value]) => (
<li
key={riskLevel}
className={'flex items-center space-x-4 border-b py-1 text-xs'}
>
<span
className={ctw(
'flex h-2 w-2 rounded-full',
riskLevelToBackgroundColor[
riskLevel as keyof typeof riskLevelToBackgroundColor
],
)}
/>
<div className={'flex w-full justify-between'}>
<span className={'text-slate-500'}>{titleCase(riskLevel)} Risk</span>
<span>{value}</span>
</div>
</li>
))}
</ul>
</div>
</CardContent>
</Card>
</div>
<div className={'grid grid-cols-2 gap-3'}>
{filters?.map(filter => {
const totalRisk = Object.values(filter.riskLevels).reduce((acc, curr) => acc + curr, 0);
Expand All @@ -123,7 +57,7 @@ export const PortfolioRiskStatistics: FunctionComponent<{
className={'col-span-full min-h-[13.125rem] rounded-xl bg-[#F6F6F6] p-2'}
>
<Card className={'flex h-full flex-col px-3'}>
<CardHeader className={'pb-1'}>{filter.name} Risk</CardHeader>
<CardHeader className={'pb-1'}>{filter.name}</CardHeader>
<CardContent>
<p className={'mb-8 text-slate-400'}>{filter.description}</p>
<div className={'flex items-center space-x-5 pt-3'}>
Expand Down Expand Up @@ -205,16 +139,93 @@ export const PortfolioRiskStatistics: FunctionComponent<{
</div>
);
})}
<div
key={'Report Statuses'}
className={'col-span-full min-h-[13.125rem] rounded-xl bg-[#F6F6F6] p-2'}
>
<Card className={'flex h-full flex-col px-3'}>
<CardHeader className={'pb-1'}>Report Status</CardHeader>
<CardContent>
<p className={'mb-8 text-slate-400'}>Merchant monitoring statuses</p>
<div className={'flex items-center space-x-5 pt-3'}>
<PieChart width={104} height={104}>
<text
x={52}
y={44}
textAnchor="middle"
dominantBaseline="middle"
className={ctw('font-bold')}
>
{reportStatuses.reduce((acc, curr) => acc + curr.count, 0)}
</text>
<text
x={52}
y={60}
textAnchor="middle"
dominantBaseline="middle"
className={'text-xs'}
>
{'Reports'}
</text>
<Pie
data={reportStatuses.map(({ status, count }) => ({
name: titleCase(status),
count,
}))}
cx={47}
cy={47}
innerRadius={43}
outerRadius={52}
fill="#8884d8"
paddingAngle={5}
dataKey="count"
cornerRadius={9999}
>
{reportStatuses.map(({ status }) => (
<Cell
key={status}
className={ctw(
reportStatusToFillColor[status as keyof typeof reportStatusToFillColor],
'outline-none',
)}
/>
))}
</Pie>
</PieChart>
<ul className={'w-full max-w-sm'}>
{reportStatuses.map(({ status, count }) => {
return (
<li key={status} className={'flex items-center space-x-4 text-xs'}>
<span
className={ctw(
'flex h-2 w-2 rounded-full',
reportStatusToBackgroundColor[
status as keyof typeof reportStatusToBackgroundColor
],
)}
/>
<div className={'flex w-full justify-between'}>
<span className={'text-slate-500'}>{titleCase(status)}</span>
{count}
</div>
</li>
);
})}
</ul>
</div>
</CardContent>
</Card>
</div>
</div>
<div className={'min-h-[10.125rem] rounded-xl bg-[#F6F6F6] p-2'}>
<Card className={'flex h-full flex-col px-3'}>
<CardHeader className={'pb-1'}>Risk Indicators</CardHeader>
<CardHeader className={'pb-1'}>MCCs Detected</CardHeader>
<CardContent>
<div className={'mb-7 flex items-end space-x-2'}>
<span className={'text-3xl font-semibold'}>
{Intl.NumberFormat().format(totalRiskIndicators)}
{Intl.NumberFormat().format(mccCounts.reduce((acc, curr) => acc + curr.count, 0))}
</span>
<span className={'text-sm leading-7 text-slate-500'}>Total indicators</span>
<span className={'text-sm leading-7 text-slate-500'}>Total MCCs Detected</span>
</div>
<div className={'mb-6'}>
<Button
Expand All @@ -229,22 +240,84 @@ export const PortfolioRiskStatistics: FunctionComponent<{
onClick={onSortRiskIndicators('desc')}
>
<TrendingUp />
Highest First
Most Common
</Button>
</div>
<Table>
<TableHeader className={'[&_tr]:border-b-0'}>
<TableRow className={'hover:bg-[unset]'}>
<TableHead className={'h-0 ps-0 font-bold text-foreground'}>MCCs</TableHead>
<TableHead className={'h-0 ps-0 font-bold text-foreground'}>Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody ref={parent}>
{mccCounts.slice(0, 8).map(({ mccDescription, mcc, count }, index) => (
<TableRow key={mccDescription} className={'border-b-0 hover:bg-[unset]'}>
<TableCell
className={ctw('pb-0 ps-0', {
'pt-2': index !== 0,
})}
>
<div className={'h-full'}>
<div
className={`rounded bg-blue-200 p-1 transition-all`}
style={{
width: `${widths[index]}%`,
}}
>
{`${mcc} - ${mccDescription}`}
</div>
</div>
</TableCell>
<TableCell className={'pb-0 ps-0'}>
{Intl.NumberFormat().format(count)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</div>
<div className={'min-h-[10.125rem] rounded-xl bg-[#F6F6F6] p-2'}>
<Card className={'flex h-full flex-col px-3'}>
<CardHeader className={'pb-1'}>Risk Indicators</CardHeader>
<CardContent>
<div className={'mb-7 flex items-end space-x-2'}>
<span className={'text-3xl font-semibold'}>
{Intl.NumberFormat().format(totalRiskIndicators)}
</span>
<span className={'text-sm leading-7 text-slate-500'}>Total indicators</span>
</div>
<div className={'mb-6'}>
<Button
variant={'ghost'}
className={ctw(
'gap-x-2 rounded-none border-b border-b-slate-400 text-slate-400',
{
'border-b-[rgb(0,122,255)] text-[rgb(0,122,255)] hover:text-[rgb(0,122,255)]':
riskIndicatorsSorting === 'asc',
riskIndicatorsSorting === 'desc',
},
)}
onClick={onSortRiskIndicators('asc')}
onClick={onSortRiskIndicators('desc')}
>
<TrendingDown />
Lowest First
<TrendingUp />
Highest First
</Button>
{/*<Button*/}
{/* variant={'ghost'}*/}
{/* className={ctw(*/}
{/* 'gap-x-2 rounded-none border-b border-b-slate-400 text-slate-400',*/}
{/* {*/}
{/* 'border-b-[rgb(0,122,255)] text-[rgb(0,122,255)] hover:text-[rgb(0,122,255)]':*/}
{/* riskIndicatorsSorting === 'asc',*/}
{/* },*/}
{/* )}*/}
{/* onClick={onSortRiskIndicators('asc')}*/}
{/*>*/}
{/* <TrendingDown />*/}
{/* Lowest First*/}
{/*</Button>*/}
</div>
<Table>
<TableHeader className={'[&_tr]:border-b-0'}>
Expand Down Expand Up @@ -272,7 +345,6 @@ export const PortfolioRiskStatistics: FunctionComponent<{
>
{titleCase(name ?? '')}
</div>
{/*<span className={'relative z-50 ms-4'}>{titleCase(name ?? '')}</span>*/}
</div>
</TableCell>
<TableCell className={'pb-0 ps-0'}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,17 @@ export const riskLevelToBackgroundColor = {
high: 'bg-destructive',
critical: 'bg-foreground',
} as const;

export const reportStatusToFillColor = {
completed: 'fill-success',
in_progress: 'fill-blue-400',
in_queue: 'fill-warning',
failed: 'fill-destructive',
} as const;

export const reportStatusToBackgroundColor = {
completed: 'bg-success',
in_progress: 'bg-blue-400',
in_queue: 'bg-warning',
failed: 'bg-destructive',
} as const;
Loading

0 comments on commit 64b30cc

Please sign in to comment.