Skip to content

Commit

Permalink
fix: Bring back transactions filter
Browse files Browse the repository at this point in the history
  • Loading branch information
filipdjokic committed Sep 17, 2024
1 parent 2c1d4d5 commit d15eccc
Show file tree
Hide file tree
Showing 32 changed files with 2,160 additions and 347 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ChangeEventHandler, KeyboardEventHandler, useState } from 'react';

export const useTxTypeSearch = (callback: (value: string, clear?: () => void) => void) => {
const [value, setValue] = useState('');
const handleOnChange: ChangeEventHandler<HTMLTextAreaElement> = (e) => {
const newValue = e?.target?.value ?? '';
setValue(newValue);
callback(newValue, clear);
};

const handleOnSubmit = () => {
callback(value, clear);
};

const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = (e) => {
const shift = e?.shiftKey;
const isEnter = e?.keyCode === 13 || e?.key === 'Enter';
if (isEnter && !shift) {
e.preventDefault();
callback(value, clear);
}
};

const clear = () => {
setValue('');
};

return {
handleOnChange,
handleOnSubmit,
value,
handleKeyDown,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import InputAdornment from '@mui/material/InputAdornment';
import InputBase from '@mui/material/InputBase';
import { FC } from 'react';
import { useTxTypeSearch } from '@/components/transaction_type_filter/components/transaction_type_search/hooks';
import useStyles from '@/components/transaction_type_filter/components/transaction_type_search/styles';
import IconSearch from 'shared-utils/assets/icon-search.svg';

type TxTypeSearchProps = {
className?: string;
placeholder: string;
callback: (value: string) => void;
};

const TxTypeSearch: FC<TxTypeSearchProps> = ({ className, placeholder, callback }) => {
const { classes, cx } = useStyles();
const { handleOnSubmit, handleOnChange, handleKeyDown, value } = useTxTypeSearch(callback);
return (
<form className={cx(classes.root, className)} onSubmit={handleOnSubmit}>
<InputBase
placeholder={placeholder}
onChange={handleOnChange}
onKeyDown={handleKeyDown}
value={value}
inputProps={{
'aria-label': placeholder,
}}
startAdornment={
<InputAdornment position="start">
<IconSearch className={classes.iconSearch} />
</InputAdornment>
}
/>
</form>
);
};

export default TxTypeSearch;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { makeStyles } from 'tss-react/mui';

const useStyles = makeStyles()((theme) => ({
root: {
'& .MuiInputBase-root': {
width: '300px',
height: '32px',
background:
theme.palette.mode === 'dark' ? theme.palette.divider : theme.palette.background.default,
padding: theme.spacing(0.4, 1.2),
borderRadius: '8px',
},
'& .MuiInputBase-input': {
textOverflow: 'ellipsis',
'&::placeholder': {
color: theme.palette.custom.fonts.fontFour,
},
},
},
iconSearch: {
fill: 'none',
},
}));

export default useStyles;
279 changes: 279 additions & 0 deletions apps/web-cheqd/src/components/transaction_type_filter/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
// useTransactionTypeFilter hook
import { useEffect, useMemo, useState, ChangeEvent, useCallback } from 'react';
import { useMessageTypesQuery, useMsgTypesByAddressQuery } from '@/graphql/types/general_types';
import { SetterOrUpdater, useRecoilState } from 'recoil';
import { writeFilter, writeOpenDialog, writeSelectedMsgTypes } from '@/recoil/transactions_filter';
import { useRouter } from 'next/router';

// Define types for message type and message types
export type MessageType = {
__typename: string;
type: string;
module: string;
label: string;
};

export type MessageTypes = {
message_type: MessageType;
};

export const useTransactionTypeFilter = () => {
const router = useRouter();

// Fetch message types data based on address or all message types
const {
data: messageTypesData,
error: messageTypesError,
loading: messageTypesLoading,
refetch: messageTypesRefetch,
} = useMessageTypesQuery();
const {
data: msgTypesByAddressData,
error: msgTypesByAddressError,
loading: msgTypesByAddressLoading,
refetch: msgTypesByAddressRefetch,
} = useMsgTypesByAddressQuery({
variables: {
addresses: `{${router.query.address}}` as string,
},
});

// Determine page context
const isAccountsPage = useMemo(() => window.location.pathname.includes('/accounts'), []);
const isValidatorDetailsPage = useMemo(
() => window.location.pathname.includes('/validators/'),
[]
);

// Determine data, error, loading, and refetch function based on page context
const data = isAccountsPage || isValidatorDetailsPage ? msgTypesByAddressData : messageTypesData;
const error =
isAccountsPage || isValidatorDetailsPage ? msgTypesByAddressError : messageTypesError;
const loading =
isAccountsPage || isValidatorDetailsPage ? msgTypesByAddressLoading : messageTypesLoading;
const refetch =
isAccountsPage || isValidatorDetailsPage ? msgTypesByAddressRefetch : messageTypesRefetch;

// State for filtered message types and selected filters
const [filteredTypes, setFilteredTypes] = useState<{ module: string; msgTypes: MessageType[] }[]>(
[]
);
const [selectedFilters, setSelectedFilters] = useState<string[]>([]);
const [selectAllChecked, setSelectAllChecked] = useState<boolean>(false);

// Recoil state for managing filters and dialog state
const [, setFilter] = useRecoilState(writeFilter) as [string, SetterOrUpdater<string>];
const [, setSelectedMsgs] = useRecoilState(writeSelectedMsgTypes) as [
string[],
SetterOrUpdater<string[]>,
];
const [__, setOpenDialog] = useRecoilState(writeOpenDialog) as [
boolean,
SetterOrUpdater<boolean>,
];

// Fetch data again if there's an error
useEffect(() => {
if (error) refetch();
}, [error, refetch]);

// Open and cancel dialog functions
const handleOpen = () => {
setOpenDialog(true);
};

const handleCancel = () => {
setOpenDialog(false);
};

// Merge all messages by label for transactions page
const mergeAllMsgsByLabelForTxsPage = (messages: MessageType[] | undefined): MessageType[] => {
// Initialize label map
const labelMap: { [key: string]: string } = {};

// Iterate over messages to merge by label
messages?.forEach((message) => {
if (!labelMap[message.label]) {
labelMap[message.label] = message.type;
} else {
labelMap[message.label] += `,${message.type}`;
}
});

// Initialize reduced messages array
const reducedMessages: MessageType[] = [];

// Iterate over label map to create reduced messages
Object.entries(labelMap).forEach(([label, type]) => {
reducedMessages.push({
__typename: 'message_type',
type,
module: messages?.find((msg) => msg.label === label)?.module || '',
label,
});
});

return reducedMessages;
};

// Merge messages by label for address pages
const mergeAddressMsgsByLabel = (messages: MessageTypes[] | undefined): MessageType[] => {
// Initialize label map
const labelMap: { [key: string]: string } = {};

// Iterate over messages to merge by label
messages?.forEach((message) => {
if (!labelMap[message?.message_type?.label]) {
labelMap[message?.message_type?.label] = message?.message_type?.type;
} else {
labelMap[message?.message_type?.label] += `,${message?.message_type?.type}`;
}
});

// Initialize reduced messages array
const reducedMessages: MessageType[] = [];

// Iterate over label map to create reduced messages
Object.entries(labelMap).forEach(([label, type]) => {
reducedMessages.push({
__typename: 'message_type',
type,
module:
messages?.find((msg) => msg?.message_type?.label === label)?.message_type?.module || '',
label,
});
});

return reducedMessages;
};

// Format message types based on page context
const formatTypes = useCallback(
(
messages: MessageTypes[] | MessageType[] | null | undefined
): { module: string; msgTypes: MessageType[] }[] => {
if (!messages) {
return [];
}
const msgs = [...messages];

// Merge labels based on page context
const updatedMessages =
isAccountsPage || isValidatorDetailsPage
? mergeAddressMsgsByLabel(msgs as MessageTypes[])
: mergeAllMsgsByLabelForTxsPage(msgs as MessageType[]);

// Initialize module messages map
const moduleMessagesMap: { [key: string]: MessageType[] } = {};

// Iterate over updated messages to group by module
updatedMessages.forEach((msgType) => {
if (!moduleMessagesMap[msgType.module]) {
moduleMessagesMap[msgType.module] = [];
}
if (!moduleMessagesMap[msgType.module].some((msg) => msg.label === msgType.label)) {
moduleMessagesMap[msgType.module].push(msgType);
}
});

return Object.entries(moduleMessagesMap).map(([module, msgTypes]) => ({
module,
msgTypes,
}));
},
[isAccountsPage, isValidatorDetailsPage]
);

// Handle filtering transactions based on selected filters
const handleFilterTxs = () => {
const str = selectedFilters.join(',');
const query = `{${str}}`;
setFilter(query);
setSelectedFilters(selectedFilters);
setSelectAllChecked(false);
handleCancel();
};

// Handle selection of transaction types
const handleTxTypeSelection = (event: ChangeEvent<HTMLInputElement>) => {
const { checked, value } = event.target;
if (checked) {
setSelectedFilters((prevFilters) => [...prevFilters, value]);
setSelectedMsgs((prevFilters) => [...prevFilters, value]);
} else {
setSelectedFilters((prevFilters) => prevFilters.filter((item) => item !== value));
setSelectedMsgs((prevFilters) => prevFilters.filter((item) => item !== value));
setSelectAllChecked(false);
}
};

// Handle selection of all transaction types
const handleSelectAllTxTypes = (event: ChangeEvent<HTMLInputElement>) => {
const { checked } = event.target;
setSelectAllChecked(checked);
if (checked) {
const allTypes = filteredTypes.flatMap((msgData) => msgData.msgTypes.map((msg) => msg.type));
setSelectedFilters(allTypes);
setSelectedMsgs(allTypes);
} else {
setSelectedFilters([]);
setSelectedMsgs([]);
}
};

// Memoized computation of message type list
const msgTypeList = useMemo(() => {
const typesList = formatTypes(
isAccountsPage || isValidatorDetailsPage
? (data?.msgTypes as MessageTypes[])
: (data?.msgTypes as MessageType[])
);
typesList.sort((a, b) => a.module.localeCompare(b.module));
setFilteredTypes(typesList);
return typesList;
}, [data?.msgTypes, formatTypes, isAccountsPage, isValidatorDetailsPage]);

// Function to search/filter transaction types
const txTypeSearchFilter = useCallback(
(value: string) => {
const parsedValue = value.replace(/\s+/g, '').toLowerCase();
if (parsedValue === '' || parsedValue === null) {
const typesList = formatTypes(
isAccountsPage || isValidatorDetailsPage
? (data?.msgTypes as MessageTypes[])
: (data?.msgTypes as MessageType[])
);
typesList.sort((a, b) => a.module.localeCompare(b.module));
setFilteredTypes(typesList);
} else {
const typesList = formatTypes(
isAccountsPage || isValidatorDetailsPage
? (data?.msgTypes as MessageTypes[])
: (data?.msgTypes as MessageType[])
);
typesList.sort((a, b) => a.module.localeCompare(b.module));
const types = typesList.filter(
(v: { module: string; msgTypes: { type: string; label: string }[] }) =>
v.msgTypes.some((ms) => ms.type.toLowerCase().indexOf(parsedValue) !== -1)
);
setFilteredTypes(types);
}
},
[data?.msgTypes, formatTypes, isAccountsPage, isValidatorDetailsPage]
);

return {
data,
loading,
msgTypeList,
filteredTypes,
selectedFilters,
selectAllChecked,
txTypeSearchFilter,
handleCancel,
handleOpen,
handleFilterTxs,
handleTxTypeSelection,
handleSelectAllTxTypes,
};
};
Loading

0 comments on commit d15eccc

Please sign in to comment.