Skip to content

Commit

Permalink
Merge branch 'dev' into bal-3116
Browse files Browse the repository at this point in the history
  • Loading branch information
tomer-shvadron authored Dec 24, 2024
2 parents 458cabc + 19fcf9f commit bdbeff3
Show file tree
Hide file tree
Showing 37 changed files with 754 additions and 243 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ReactNode, useCallback, useState } from 'react';
import { CheckIcon, PlusCircledIcon } from '@radix-ui/react-icons';
import { ReactNode, useCallback } from 'react';
import {
Badge,
Button,
Expand All @@ -14,7 +15,7 @@ import {
PopoverContent,
PopoverTrigger,
} from '@ballerine/ui';
import { CheckIcon, PlusCircledIcon } from '@radix-ui/react-icons';

import { Separator } from '@/common/components/atoms/Separator/Separator';

interface IMultiSelectProps<
Expand All @@ -29,6 +30,19 @@ interface IMultiSelectProps<
onSelect: (value: Array<TOption['value']>) => void;
onClearSelect: () => void;
options: TOption[];
props?: {
content?: {
className?: string;
};
trigger?: {
leftIcon?: JSX.Element;
rightIcon?: JSX.Element;
className?: string;
title?: {
className?: string;
};
};
};
}

export const MultiSelect = <
Expand All @@ -39,32 +53,36 @@ export const MultiSelect = <
},
>({
title,
selectedValues,
selectedValues: selected,
onSelect,
onClearSelect,
options,
props,
}: IMultiSelectProps<TOption>) => {
const [selected, setSelected] = useState(selectedValues);

const onSelectChange = useCallback(
(value: TOption['value']) => {
const isSelected = selected.some(selectedValue => selectedValue === value);
const nextSelected = isSelected
? selected.filter(selectedValue => selectedValue !== value)
: [...selected, value];

setSelected(nextSelected);
onSelect(nextSelected);
},
[onSelect, selected],
);

const TriggerLeftIcon = props?.trigger?.leftIcon ?? <PlusCircledIcon className="mr-2 h-4 w-4" />;

return (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" size="sm" className="h-8 border">
<PlusCircledIcon className="mr-2 h-4 w-4" />
{title}
<Button
variant="outline"
size="sm"
className={ctw(`h-8 border`, props?.trigger?.className)}
>
{TriggerLeftIcon}
<span className={ctw(props?.trigger?.title?.className)}>{title}</span>
{selected?.length > 0 && (
<>
<Separator orientation="vertical" className="mx-2 h-4" />
Expand All @@ -81,8 +99,8 @@ export const MultiSelect = <
.filter(option => selected.some(value => value === option.value))
.map(option => (
<Badge
variant="secondary"
key={option.value}
variant="secondary"
className="rounded-sm px-1 font-normal"
>
{option.label}
Expand All @@ -92,10 +110,11 @@ export const MultiSelect = <
</div>
</>
)}
{props?.trigger?.rightIcon}
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0" align="start">
<Command>
<PopoverContent className={ctw(`w-[200px] p-0`, props?.content?.className)} align="start">
<Command filter={(value, search) => (value.includes(search) ? 1 : 0)}>
<CommandInput placeholder={title} />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
Expand All @@ -104,7 +123,11 @@ export const MultiSelect = <
const isSelected = selected.some(value => value === option.value);

return (
<CommandItem key={option.value} onSelect={() => onSelectChange(option.value)}>
<CommandItem
key={option.value}
onSelect={() => onSelectChange(option.value)}
className={`cursor-pointer`}
>
<div
className={ctw(
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
Expand All @@ -126,11 +149,8 @@ export const MultiSelect = <
<CommandSeparator />
<CommandGroup>
<CommandItem
onSelect={() => {
onClearSelect();
setSelected([]);
}}
className="justify-center text-center"
onSelect={onClearSelect}
className="cursor-pointer justify-center text-center"
>
Clear filters
</CommandItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,35 @@ import { Calendar } from '../../organisms/Calendar/Calendar';
type TDateRangePickerProps = {
onChange: NonNullable<ComponentProps<typeof Calendar>['onSelect']>;
value: NonNullable<ComponentProps<typeof Calendar>['selected']>;
placeholder?: string;
className?: ComponentProps<'div'>['className'];
};

export const DateRangePicker = ({ onChange, value, className }: TDateRangePickerProps) => {
export const DateRangePicker = ({
onChange,
value,
placeholder,
className,
}: TDateRangePickerProps) => {
return (
<div className={ctw('grid gap-2', className)}>
<Popover>
<PopoverTrigger asChild>
<Button
id="date"
variant={'outline'}
className={ctw('w-[300px] justify-start text-left font-normal', {
className={ctw('h-8 w-[250px] justify-start text-left font-normal', {
'text-muted-foreground': !value,
})}
>
<CalendarIcon className="size-4 mr-2" />
<CalendarIcon className="mr-2 d-4" />
{value?.from && value?.to && (
<>
{formatDate(value.from, 'LLL dd, y')} - {formatDate(value.to, 'LLL dd, y')}
</>
)}
{value?.from && !value?.to && formatDate(value.from, 'LLL dd, y')}
{!value?.from && !value?.to && <span>Pick a date</span>}
{!value?.from && !value?.to && <span>{placeholder ?? 'Pick a date'}</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ import { FunctionComponent } from 'react';

export const Search: FunctionComponent<{
value: string;
placeholder?: string;
onChange: (search: string) => void;
}> = ({ value, onChange }) => {
}> = ({ value, placeholder, onChange }) => {
return (
<div className="relative flex flex-col gap-1">
<div className="input-group flex h-[32px] items-center rounded-[44px] border border-[#E5E7EB] shadow-[0_4px_4px_0_rgba(174,174,174,0.0625)]">
<div className="input-group flex h-[32px] w-[250px] items-center rounded-[44px] border border-[#E5E7EB] shadow-[0_4px_4px_0_rgba(174,174,174,0.0625)]">
<div className={`btn btn-square btn-ghost pointer-events-none -ms-2`}>
<LucideSearch size={13} />
</div>
<input
type={'search'}
className="input input-xs -ml-3 h-[18px] w-full !border-0 pl-0 text-xs !outline-none !ring-0 placeholder:text-base-content"
placeholder={`Search`}
placeholder={placeholder ?? `Search`}
value={value}
onChange={e => onChange(e.target.value)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,5 @@ export const Calendar = ({
/>
);
};

Calendar.displayName = 'Calendar';
15 changes: 4 additions & 11 deletions apps/backoffice-v2/src/common/hooks/useSearch/useSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,8 @@ import { useDebounce } from '../useDebounce/useDebounce';
import { useSerializedSearchParams } from '@/common/hooks/useSerializedSearchParams/useSerializedSearchParams';
import { useIsMounted } from '@/common/hooks/useIsMounted/useIsMounted';

export const useSearch = (
{
initialSearch = '',
}: {
initialSearch?: string;
} = {
initialSearch: '',
},
) => {
const [{ search = initialSearch }, setSearchParams] = useSerializedSearchParams();
export const useSearch = () => {
const [{ search }, setSearchParams] = useSerializedSearchParams();
const [_search, setSearch] = useState(search);
const debouncedSearch = useDebounce(_search, 240);
const onSearchChange = useCallback((search: string) => {
Expand All @@ -32,7 +24,8 @@ export const useSearch = (
}, [debouncedSearch]);

return {
search: _search,
search: _search as string,
debouncedSearch: debouncedSearch as string,
onSearch: onSearchChange,
};
};
32 changes: 15 additions & 17 deletions apps/backoffice-v2/src/domains/business-reports/fetchers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import qs from 'qs';
import { z } from 'zod';
import { apiClient } from '@/common/api-client/api-client';
import { t } from 'i18next';
import { toast } from 'sonner';
import { UnknownRecord } from 'type-fest';

import { Method } from '@/common/enums';
import { apiClient } from '@/common/api-client/api-client';
import { TReportStatusValue, TRiskLevel } from '@/pages/MerchantMonitoring/schemas';
import { handleZodError } from '@/common/utils/handle-zod-error/handle-zod-error';
import qs from 'qs';
import { toast } from 'sonner';
import { t } from 'i18next';
import {
MERCHANT_REPORT_STATUSES,
MERCHANT_REPORT_STATUSES_MAP,
Expand All @@ -13,7 +16,6 @@ import {
MerchantReportType,
MerchantReportVersion,
} from '@/domains/business-reports/constants';
import { UnknownRecord } from 'type-fest';

export const BusinessReportSchema = z
.object({
Expand Down Expand Up @@ -84,24 +86,20 @@ export const fetchLatestBusinessReport = async ({
return handleZodError(error, data);
};

export const fetchBusinessReports = async ({
reportType,
...params
}: {
reportType: MerchantReportType;
export const fetchBusinessReports = async (params: {
reportType?: MerchantReportType;
riskLevels: TRiskLevel[];
statuses: TReportStatusValue[];
findings: string[];
from?: string;
to?: string;
page: {
number: number;
size: number;
};
orderBy: string;
}) => {
const queryParams = qs.stringify(
{
...params,
type: reportType,
},
{ encode: false },
);
const queryParams = qs.stringify(params, { encode: false });

const [data, error] = await apiClient({
endpoint: `../external/business-reports/?${queryParams}`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useIsAuthenticated } from '@/domains/auth/context/AuthProvider/hooks/useIsAuthenticated/useIsAuthenticated';
import { useQuery } from '@tanstack/react-query';
import { businessReportsQueryKey } from '@/domains/business-reports/query-keys';
import { isString } from '@/common/utils/is-string/is-string';

import { MerchantReportType } from '@/domains/business-reports/constants';
import { businessReportsQueryKey } from '@/domains/business-reports/query-keys';
import { TReportStatusValue, TRiskLevel } from '@/pages/MerchantMonitoring/schemas';
import { useIsAuthenticated } from '@/domains/auth/context/AuthProvider/hooks/useIsAuthenticated/useIsAuthenticated';

export const useBusinessReportsQuery = ({
reportType,
Expand All @@ -11,26 +12,41 @@ export const useBusinessReportsQuery = ({
pageSize,
sortBy,
sortDir,
riskLevels,
statuses,
findings,
from,
to,
}: {
reportType: MerchantReportType;
reportType?: MerchantReportType;
search: string;
page: number;
pageSize: number;
sortBy: string;
sortDir: string;
riskLevels: TRiskLevel[];
statuses: TReportStatusValue[];
findings: string[];
from?: string;
to?: string;
}) => {
const isAuthenticated = useIsAuthenticated();

return useQuery({
...businessReportsQueryKey.list({ reportType, search, page, pageSize, sortBy, sortDir }),
enabled:
isAuthenticated &&
isString(reportType) &&
!!reportType &&
!!sortBy &&
!!sortDir &&
!!page &&
!!pageSize,
...businessReportsQueryKey.list({
reportType,
search,
page,
pageSize,
sortBy,
sortDir,
riskLevels,
statuses,
findings,
from,
to,
}),
enabled: isAuthenticated && !!sortBy && !!sortDir && !!page && !!pageSize,
staleTime: 100_000,
refetchInterval: 1_000_000,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
fetchLatestBusinessReport,
} from '@/domains/business-reports/fetchers';
import { MerchantReportType } from '@/domains/business-reports/constants';
import { TReportStatusValue, TRiskLevel } from '@/pages/MerchantMonitoring/schemas';

export const businessReportsQueryKey = createQueryKeys('business-reports', {
list: ({
Expand All @@ -15,12 +16,17 @@ export const businessReportsQueryKey = createQueryKeys('business-reports', {
sortDir,
...params
}: {
reportType: MerchantReportType;
reportType?: MerchantReportType;
search: string;
page: number;
pageSize: number;
sortBy: string;
sortDir: string;
riskLevels: TRiskLevel[];
statuses: TReportStatusValue[];
findings: string[];
from?: string;
to?: string;
}) => ({
queryKey: [{ page, pageSize, sortBy, sortDir, ...params }],
queryFn: () => {
Expand Down
Loading

0 comments on commit bdbeff3

Please sign in to comment.