HistoryFilterMenu.tsx
1 import { XCircleIcon } from '@heroicons/react/solid'; 2 import { Trans } from '@lingui/macro'; 3 import { Check as CheckIcon, Sort as SortIcon } from '@mui/icons-material'; 4 import { 5 Box, 6 Button, 7 Divider, 8 Menu, 9 MenuItem, 10 SvgIcon, 11 Typography, 12 useMediaQuery, 13 useTheme, 14 } from '@mui/material'; 15 import React, { useEffect, useState } from 'react'; 16 import { DarkTooltip } from 'src/components/infoTooltips/DarkTooltip'; 17 import { useRootStore } from 'src/store/root'; 18 import { TRANSACTION_HISTORY } from 'src/utils/mixPanelEvents'; 19 20 import { FilterOptions } from './types'; 21 22 interface HistoryFilterMenuProps { 23 onFilterChange: (filter: FilterOptions[]) => void; 24 currentFilter: FilterOptions[]; 25 } 26 27 interface FilterLabelProps { 28 filter: FilterOptions; 29 } 30 31 const FilterLabel: React.FC<FilterLabelProps> = ({ filter }) => { 32 switch (filter) { 33 case FilterOptions.SUPPLY: 34 return <Trans>Supply</Trans>; 35 case FilterOptions.BORROW: 36 return <Trans>Borrow</Trans>; 37 case FilterOptions.WITHDRAW: 38 return <Trans>Withdraw</Trans>; 39 case FilterOptions.REPAY: 40 return <Trans>Repay</Trans>; 41 case FilterOptions.RATECHANGE: 42 return <Trans>Rate change</Trans>; 43 case FilterOptions.COLLATERALCHANGE: 44 return <Trans>Collateral change</Trans>; 45 case FilterOptions.LIQUIDATION: 46 return <Trans>Liquidation</Trans>; 47 } 48 }; 49 50 export const HistoryFilterMenu: React.FC<HistoryFilterMenuProps> = ({ 51 onFilterChange, 52 currentFilter, 53 }) => { 54 const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); 55 const [localFilter, setLocalFilter] = useState<FilterOptions[]>(currentFilter); 56 const trackEvent = useRootStore((store) => store.trackEvent); 57 58 useEffect(() => { 59 onFilterChange(localFilter); 60 }, [localFilter, onFilterChange]); 61 62 const theme = useTheme(); 63 const downToMD = useMediaQuery(theme.breakpoints.down('md')); 64 65 const allSelected = currentFilter.length === 0; 66 67 const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { 68 setAnchorEl(event.currentTarget); 69 }; 70 71 const handleClose = () => { 72 setAnchorEl(null); 73 onFilterChange(currentFilter); 74 }; 75 76 const handleFilterClick = (filter: FilterOptions | undefined) => { 77 let newFilter: FilterOptions[] = []; 78 if (filter !== undefined) { 79 if (currentFilter.includes(filter)) { 80 newFilter = currentFilter.filter((item) => item !== filter); 81 } else { 82 trackEvent(TRANSACTION_HISTORY.FILTER, { value: filter }); 83 newFilter = [...currentFilter, filter]; 84 // Checks if all filter options are selected, enum length is divided by 2 based on how Typescript creates object from enum 85 if (newFilter.length === Object.keys(FilterOptions).length / 2) { 86 newFilter = []; 87 } 88 } 89 } 90 91 setLocalFilter(newFilter); 92 }; 93 94 const FilterButtonLabel = () => { 95 if (allSelected) { 96 return <Trans>All transactions</Trans>; 97 } else { 98 const displayLimit = 2; 99 const hiddenCount = currentFilter.length - displayLimit; 100 const displayedFilters = currentFilter.slice(0, displayLimit).map((filter) => ( 101 <React.Fragment key={filter}> 102 <FilterLabel filter={filter} /> 103 {filter !== currentFilter[currentFilter.length - 1] && ','} 104 {filter !== currentFilter[displayLimit - 1] && ' '} 105 </React.Fragment> 106 )); 107 108 return ( 109 <Box sx={{ display: 'flex' }}> 110 <Typography variant="description" color={theme.palette.primary.main} sx={{ mr: 1 }}> 111 TXs: 112 </Typography> 113 {displayedFilters} 114 {hiddenCount > 0 && <React.Fragment>...(+{hiddenCount})</React.Fragment>} 115 </Box> 116 ); 117 } 118 }; 119 120 const handleClearFilter = (event: React.MouseEvent) => { 121 trackEvent(TRANSACTION_HISTORY.FILTER, { value: 'cleared' }); 122 event.stopPropagation(); 123 setLocalFilter([]); 124 }; 125 126 return ( 127 <Box> 128 <Button 129 sx={{ 130 minWidth: 148, 131 maxWidth: downToMD ? '100%' : 360, 132 display: 'flex', 133 justifyContent: 'space-between', 134 alignItems: 'center', 135 height: 36, 136 border: '1px solid', 137 borderColor: 'divider', 138 borderRadius: '4px', 139 mr: downToMD ? 0 : 2, 140 ml: downToMD ? 4 : 0, 141 pl: 2, 142 pr: 1, 143 }} 144 onClick={handleClick} 145 > 146 <Box display="flex" alignItems="center" overflow="hidden"> 147 <SvgIcon height={9} width={9} color="primary"> 148 <SortIcon /> 149 </SvgIcon> 150 <Typography 151 variant="subheader1" 152 color="text.primary" 153 sx={{ 154 ml: 1, 155 textOverflow: 'ellipsis', 156 whiteSpace: 'nowrap', 157 overflow: 'hidden', 158 mr: 1, 159 }} 160 > 161 <FilterButtonLabel /> 162 </Typography> 163 </Box> 164 {!allSelected && ( 165 <DarkTooltip 166 title={ 167 <Typography variant="caption" color="common.white"> 168 <Trans>Reset</Trans> 169 </Typography> 170 } 171 > 172 <Box 173 sx={{ 174 cursor: 'pointer', 175 color: 'primary', 176 height: 'auto', 177 width: 'auto', 178 display: 'flex', 179 alignItems: 'center', 180 }} 181 onClick={handleClearFilter} 182 > 183 <XCircleIcon color="#A5A8B6" width={18} height={18} /> 184 </Box> 185 </DarkTooltip> 186 )} 187 </Button> 188 <Menu 189 anchorEl={anchorEl} 190 open={Boolean(anchorEl)} 191 onClose={handleClose} 192 PaperProps={{ 193 sx: { 194 width: 280, 195 maxHeight: 300, 196 mt: 1, 197 boxShadow: '0px 0px 2px rgba(0, 0, 0, 0.2), 0px 2px 10px rgba(0, 0, 0, 0.1)', 198 borderRadius: '4px', 199 }, 200 }} 201 > 202 <MenuItem 203 onClick={() => handleFilterClick(undefined)} 204 sx={{ 205 background: allSelected ? theme.palette.background.surface : undefined, 206 display: 'flex', 207 justifyContent: 'space-between', 208 }} 209 > 210 <Typography variant="subheader1" color="text.primary"> 211 <Trans>All transactions</Trans> 212 </Typography> 213 {allSelected && ( 214 <SvgIcon sx={{ fontSize: '16px' }}> 215 <CheckIcon /> 216 </SvgIcon> 217 )} 218 </MenuItem> 219 <Divider sx={{ mt: 1 }} /> 220 <Box 221 sx={{ 222 overflowY: 'scroll', 223 maxHeight: 200, 224 scrollbarWidth: 'none', 225 msOverflowStyle: 'none', 226 '::-webkit-scrollbar': { 227 display: 'none', 228 }, 229 }} 230 > 231 {Object.keys(FilterOptions) 232 .filter((key) => isNaN(Number(key))) 233 .map((optionKey) => { 234 const option = FilterOptions[optionKey as keyof typeof FilterOptions]; 235 return ( 236 <MenuItem 237 key={optionKey} 238 onClick={() => handleFilterClick(option)} 239 sx={{ 240 background: currentFilter.includes(option) 241 ? theme.palette.background.surface 242 : undefined, 243 display: 'flex', 244 justifyContent: 'space-between', 245 }} 246 > 247 <Typography variant="subheader1" color="text.primary"> 248 <FilterLabel filter={option} /> 249 </Typography> 250 {currentFilter.includes(option) && ( 251 <SvgIcon sx={{ fontSize: '16px' }}> 252 <CheckIcon /> 253 </SvgIcon> 254 )} 255 </MenuItem> 256 ); 257 })} 258 </Box> 259 </Menu> 260 </Box> 261 ); 262 };