Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bal 3191 #2905

Merged
merged 12 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>) => {
tomer-shvadron marked this conversation as resolved.
Show resolved Hide resolved
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
Loading