feat: transactions table
This commit is contained in:
parent
1ead9fe359
commit
d6166ce752
29 changed files with 1204 additions and 726 deletions
118
packages/admin-ui/src/components/TableFilters.jsx
Normal file
118
packages/admin-ui/src/components/TableFilters.jsx
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import {
|
||||
Autocomplete,
|
||||
TextField,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
} from '@mui/material'
|
||||
import { AsyncAutocomplete } from './inputs/base/AsyncAutocomplete.jsx'
|
||||
|
||||
export const SelectFilter = ({ column, options = [] }) => {
|
||||
const columnFilterValue = column.getFilterValue()
|
||||
|
||||
return (
|
||||
<FormControl variant="standard" size="small" fullWidth>
|
||||
<Select
|
||||
value={columnFilterValue || ''}
|
||||
onChange={event => {
|
||||
column.setFilterValue(event.target.value || undefined)
|
||||
}}
|
||||
displayEmpty
|
||||
variant="standard">
|
||||
<MenuItem value="">All</MenuItem>
|
||||
{options.map(option => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
|
||||
export const AutocompleteFilter = ({
|
||||
column,
|
||||
options = [],
|
||||
placeholder = 'Filter...',
|
||||
renderOption,
|
||||
getOptionLabel = option => option.label || '',
|
||||
}) => {
|
||||
const columnFilterValue = column.getFilterValue()
|
||||
const selectedOption =
|
||||
options.find(option => option.value === columnFilterValue) || null
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
options={options}
|
||||
value={selectedOption}
|
||||
onChange={(event, newValue) => {
|
||||
column.setFilterValue(newValue?.value || '')
|
||||
}}
|
||||
getOptionLabel={getOptionLabel}
|
||||
isOptionEqualToValue={(option, value) => option?.value === value?.value}
|
||||
renderOption={renderOption}
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
{...params}
|
||||
variant="standard"
|
||||
placeholder={placeholder}
|
||||
size="small"
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
size="small"
|
||||
fullWidth
|
||||
slotProps={{
|
||||
listbox: {
|
||||
style: { maxHeight: 200 },
|
||||
},
|
||||
popper: {
|
||||
style: { width: 'auto' },
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const TextFilter = ({ column, placeholder = 'Filter...' }) => {
|
||||
const columnFilterValue = column.getFilterValue()
|
||||
|
||||
return (
|
||||
<TextField
|
||||
value={columnFilterValue ?? ''}
|
||||
onChange={event => {
|
||||
column.setFilterValue(event.target.value || undefined)
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
variant="standard"
|
||||
size="small"
|
||||
fullWidth
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const AsyncAutocompleteFilter = ({ column, ...props }) => {
|
||||
const [selectedOption, setSelectedOption] = useState(null)
|
||||
const columnFilterValue = column.getFilterValue()
|
||||
const getOptionId = props.getOptionId || (option => option.id)
|
||||
|
||||
useEffect(() => {
|
||||
if (!columnFilterValue) {
|
||||
setSelectedOption(null)
|
||||
}
|
||||
}, [columnFilterValue])
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
column.setFilterValue(newValue ? getOptionId(newValue) : '')
|
||||
setSelectedOption(newValue)
|
||||
}
|
||||
|
||||
return (
|
||||
<AsyncAutocomplete
|
||||
{...props}
|
||||
value={selectedOption}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ const HelpTooltip = memo(({ children, width }) => {
|
|||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="border-0 bg-transparent outline-0 cursor-pointer mt-1"
|
||||
className="flex justify-center align-center border-0 bg-transparent outline-0 cursor-pointer px-1"
|
||||
onMouseEnter={handler.openHelpPopper}>
|
||||
<HelpIcon />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
import React, { useState, useRef } from 'react'
|
||||
import { Autocomplete, TextField } from '@mui/material'
|
||||
|
||||
export const AsyncAutocomplete = ({
|
||||
value,
|
||||
onChange,
|
||||
onSearch,
|
||||
getOptionLabel,
|
||||
getOptionId = option => option.id,
|
||||
placeholder = 'Search...',
|
||||
noOptionsText = 'Type to start searching...',
|
||||
minSearchLength = 2,
|
||||
debounceMs = 300,
|
||||
variant = 'standard',
|
||||
size = 'small',
|
||||
fullWidth = true,
|
||||
...textFieldProps
|
||||
}) => {
|
||||
const [options, setOptions] = useState([])
|
||||
const timeoutRef = useRef(null)
|
||||
|
||||
// Simple debounce using timeout
|
||||
const debouncedSearch = searchTerm => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
}
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
onSearch(searchTerm).then(results => {
|
||||
setOptions(results)
|
||||
})
|
||||
}, debounceMs)
|
||||
}
|
||||
|
||||
const handleInputChange = (event, newInputValue, reason) => {
|
||||
// Only search when user is typing, not when selecting an option
|
||||
if (
|
||||
reason === 'input' &&
|
||||
newInputValue &&
|
||||
newInputValue.length > minSearchLength
|
||||
) {
|
||||
debouncedSearch(newInputValue)
|
||||
}
|
||||
}
|
||||
|
||||
const handleBlur = () => {
|
||||
setOptions([])
|
||||
}
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onInputChange={handleInputChange}
|
||||
getOptionLabel={getOptionLabel}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
getOptionId(option) === getOptionId(value)
|
||||
}
|
||||
noOptionsText={noOptionsText}
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
{...params}
|
||||
variant={variant}
|
||||
placeholder={placeholder}
|
||||
size={size}
|
||||
fullWidth={fullWidth}
|
||||
onBlur={handleBlur}
|
||||
{...textFieldProps}
|
||||
/>
|
||||
)}
|
||||
size={size}
|
||||
fullWidth={fullWidth}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue