temp
This commit is contained in:
parent
16433457ad
commit
65974dbf28
4 changed files with 958 additions and 0 deletions
|
@ -31,3 +31,4 @@ export * from './virtual-grid';
|
||||||
export * from './virtual-table';
|
export * from './virtual-table';
|
||||||
export * from './motion';
|
export * from './motion';
|
||||||
export * from './context-menu';
|
export * from './context-menu';
|
||||||
|
export * from './query-builder';
|
||||||
|
|
241
src/renderer/components/query-builder/index.tsx
Normal file
241
src/renderer/components/query-builder/index.tsx
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
import { Group, Stack } from '@mantine/core';
|
||||||
|
import { Select } from '/@/renderer/components/select';
|
||||||
|
import { FilterGroupType } from '/@/renderer/types';
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import { RiAddLine, RiMore2Line } from 'react-icons/ri';
|
||||||
|
import { Button } from '/@/renderer/components/button';
|
||||||
|
import { DropdownMenu } from '/@/renderer/components/dropdown-menu';
|
||||||
|
import { QueryBuilderOption } from '/@/renderer/components/query-builder/query-builder-option';
|
||||||
|
|
||||||
|
export type AdvancedFilterGroup = {
|
||||||
|
children: AdvancedFilterGroup[];
|
||||||
|
rules: AdvancedFilterRule[];
|
||||||
|
type: FilterGroupType;
|
||||||
|
uniqueId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AdvancedFilterRule = {
|
||||||
|
field?: string | null;
|
||||||
|
operator?: string | null;
|
||||||
|
uniqueId: string;
|
||||||
|
value?: string | number | Date | undefined | null | any;
|
||||||
|
};
|
||||||
|
|
||||||
|
const FILTER_GROUP_OPTIONS_DATA = [
|
||||||
|
{
|
||||||
|
label: 'Match all',
|
||||||
|
value: 'all',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Match any',
|
||||||
|
value: 'any',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// const queryJson = [
|
||||||
|
// {
|
||||||
|
// any: [{ is: { loved: true } }, { gt: { rating: 3 } }],
|
||||||
|
// },
|
||||||
|
// { inTheRange: { year: [1981, 1990] } },
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// const parseQuery = (query: Record<string, any>[]) => {
|
||||||
|
// // for (const ruleset in query) {
|
||||||
|
// // // console.log('key', key);
|
||||||
|
// // // console.log('query[key]', query[key]);
|
||||||
|
// // // console.log('Object.keys(query[key])', Object.keys(query[key]));
|
||||||
|
// // // console.log('Object.values(query[key])', Object.values(query[key]));
|
||||||
|
// // // console.log('Object.entries(query[key])', Object.entries(query[key]));
|
||||||
|
|
||||||
|
// // const keys = Object.keys(query[ruleset]);
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// const res = flatMapDeep(query, flatten);
|
||||||
|
// console.log('res', res);
|
||||||
|
|
||||||
|
// return res;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const OperatorSelect = ({ value, onChange }: any) => {
|
||||||
|
// const handleChange = (e: any) => {
|
||||||
|
// onChange(e);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <Select
|
||||||
|
// data={operators}
|
||||||
|
// label="Operator"
|
||||||
|
// value={value}
|
||||||
|
// onChange={handleChange}
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
type AddArgs = {
|
||||||
|
groupIndex: number[];
|
||||||
|
groupValue: string;
|
||||||
|
level: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DeleteArgs = {
|
||||||
|
groupIndex: number[];
|
||||||
|
groupValue: string;
|
||||||
|
level: number;
|
||||||
|
uniqueId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface QueryBuilderProps {
|
||||||
|
data: Record<string, any>;
|
||||||
|
filters: { label: string; value: string }[];
|
||||||
|
groupIndex: number[];
|
||||||
|
level: number;
|
||||||
|
onAddRule: (args: AddArgs) => void;
|
||||||
|
onAddRuleGroup: (args: AddArgs) => void;
|
||||||
|
onChangeField: (args: any) => void;
|
||||||
|
onChangeOperator: (args: any) => void;
|
||||||
|
onChangeType: (args: any) => void;
|
||||||
|
onChangeValue: (args: any) => void;
|
||||||
|
onDeleteRule: (args: DeleteArgs) => void;
|
||||||
|
onDeleteRuleGroup: (args: DeleteArgs) => void;
|
||||||
|
uniqueId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const QueryBuilder = ({
|
||||||
|
data,
|
||||||
|
level,
|
||||||
|
onAddRule,
|
||||||
|
onDeleteRuleGroup,
|
||||||
|
onDeleteRule,
|
||||||
|
onAddRuleGroup,
|
||||||
|
onChangeType,
|
||||||
|
onChangeField,
|
||||||
|
onChangeOperator,
|
||||||
|
onChangeValue,
|
||||||
|
groupIndex,
|
||||||
|
uniqueId,
|
||||||
|
filters,
|
||||||
|
}: QueryBuilderProps) => {
|
||||||
|
const groupValue = Object.keys(data)[0];
|
||||||
|
const rules = data[groupValue].filter((rule: any) => !rule.any && !rule.all);
|
||||||
|
const group = data[groupValue].filter((rule: any) => rule.any || rule.all);
|
||||||
|
|
||||||
|
const handleAddRule = () => {
|
||||||
|
onAddRule({ groupIndex, groupValue, level });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddRuleGroup = () => {
|
||||||
|
onAddRuleGroup({ groupIndex, groupValue, level });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteRuleGroup = () => {
|
||||||
|
onDeleteRuleGroup({ groupIndex, groupValue, level, uniqueId });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeType = (value: string | null) => {
|
||||||
|
onChangeType({ groupIndex, level, value });
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('rules :>> ', rules);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack ml={`${level * 10}px`}>
|
||||||
|
<Group>
|
||||||
|
<Select
|
||||||
|
data={FILTER_GROUP_OPTIONS_DATA}
|
||||||
|
maxWidth={175}
|
||||||
|
size="xs"
|
||||||
|
value={groupValue}
|
||||||
|
width="20%"
|
||||||
|
onChange={handleChangeType}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
px={5}
|
||||||
|
size="xs"
|
||||||
|
tooltip={{ label: 'Add rule' }}
|
||||||
|
variant="default"
|
||||||
|
onClick={handleAddRule}
|
||||||
|
>
|
||||||
|
<RiAddLine size={20} />
|
||||||
|
</Button>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenu.Target>
|
||||||
|
<Button
|
||||||
|
p={0}
|
||||||
|
size="xs"
|
||||||
|
variant="subtle"
|
||||||
|
>
|
||||||
|
<RiMore2Line size={20} />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenu.Target>
|
||||||
|
<DropdownMenu.Dropdown>
|
||||||
|
<DropdownMenu.Item onClick={handleAddRuleGroup}>Add rule group</DropdownMenu.Item>
|
||||||
|
{level > 0 && (
|
||||||
|
<DropdownMenu.Item onClick={handleDeleteRuleGroup}>
|
||||||
|
Remove rule group
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
)}
|
||||||
|
</DropdownMenu.Dropdown>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Group>
|
||||||
|
<AnimatePresence
|
||||||
|
key="advanced-filter-option"
|
||||||
|
initial={false}
|
||||||
|
>
|
||||||
|
{rules.map((rule: AdvancedFilterRule, i: number) => (
|
||||||
|
<motion.div
|
||||||
|
key={`rule-${level}-${Object.keys(rule)[0]}`}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
exit={{ opacity: 0, x: -25 }}
|
||||||
|
initial={{ opacity: 0, x: -25 }}
|
||||||
|
transition={{ duration: 0.2, ease: 'easeInOut' }}
|
||||||
|
>
|
||||||
|
<QueryBuilderOption
|
||||||
|
data={rule}
|
||||||
|
filters={filters}
|
||||||
|
groupIndex={groupIndex || []}
|
||||||
|
groupValue={groupValue}
|
||||||
|
level={level}
|
||||||
|
noRemove={data?.rules?.length === 1}
|
||||||
|
onChangeField={onChangeField}
|
||||||
|
onChangeOperator={onChangeOperator}
|
||||||
|
onChangeValue={onChangeValue}
|
||||||
|
onDeleteRule={onDeleteRule}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</AnimatePresence>
|
||||||
|
{group && (
|
||||||
|
<AnimatePresence
|
||||||
|
key="advanced-filter-group"
|
||||||
|
initial={false}
|
||||||
|
>
|
||||||
|
{group.map((group: AdvancedFilterGroup, index: number) => (
|
||||||
|
<motion.div
|
||||||
|
key={`group-${level}-${index}`}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
exit={{ opacity: 0, x: -25 }}
|
||||||
|
initial={{ opacity: 0, x: -25 }}
|
||||||
|
transition={{ duration: 0.2, ease: 'easeInOut' }}
|
||||||
|
>
|
||||||
|
<QueryBuilder
|
||||||
|
data={group}
|
||||||
|
filters={filters}
|
||||||
|
groupIndex={[...(groupIndex || []), index]}
|
||||||
|
level={level + 1}
|
||||||
|
uniqueId={group.uniqueId}
|
||||||
|
onAddRule={onAddRule}
|
||||||
|
onAddRuleGroup={onAddRuleGroup}
|
||||||
|
onChangeField={onChangeField}
|
||||||
|
onChangeOperator={onChangeOperator}
|
||||||
|
onChangeType={onChangeType}
|
||||||
|
onChangeValue={onChangeValue}
|
||||||
|
onDeleteRule={onDeleteRule}
|
||||||
|
onDeleteRuleGroup={onDeleteRuleGroup}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</AnimatePresence>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
380
src/renderer/components/query-builder/query-builder-option.tsx
Normal file
380
src/renderer/components/query-builder/query-builder-option.tsx
Normal file
|
@ -0,0 +1,380 @@
|
||||||
|
import { Group } from '@mantine/core';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { RiSubtractLine } from 'react-icons/ri';
|
||||||
|
import { Button } from '/@/renderer/components/button';
|
||||||
|
import { TextInput } from '/@/renderer/components/input';
|
||||||
|
import { Select } from '/@/renderer/components/select';
|
||||||
|
import { AdvancedFilterRule } from '/@/renderer/types';
|
||||||
|
|
||||||
|
const operators = [
|
||||||
|
{ label: 'is', value: 'is' },
|
||||||
|
{ label: 'is not', value: 'isNot' },
|
||||||
|
{ label: 'is greater than', value: 'gt' },
|
||||||
|
{ label: 'is less than', value: 'lt' },
|
||||||
|
{ label: 'contains', value: 'contains' },
|
||||||
|
{ label: 'does not contain', value: 'notContains' },
|
||||||
|
{ label: 'starts with', value: 'startsWith' },
|
||||||
|
{ label: 'ends with', value: 'endsWith' },
|
||||||
|
{ label: 'is in the range', value: 'inTheRange' },
|
||||||
|
{ label: 'before', value: 'before' },
|
||||||
|
{ label: 'after', value: 'after' },
|
||||||
|
{ label: 'is in the last', value: 'inTheLast' },
|
||||||
|
{ label: 'is not in the last', value: 'notInTheLast' },
|
||||||
|
];
|
||||||
|
|
||||||
|
type DeleteArgs = {
|
||||||
|
groupIndex: number[];
|
||||||
|
groupValue: string;
|
||||||
|
level: number;
|
||||||
|
uniqueId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface QueryOptionProps {
|
||||||
|
data: AdvancedFilterRule;
|
||||||
|
filters: { label: string; value: string }[];
|
||||||
|
groupIndex: number[];
|
||||||
|
groupValue: string;
|
||||||
|
level: number;
|
||||||
|
noRemove: boolean;
|
||||||
|
onChangeField: (args: any) => void;
|
||||||
|
onChangeOperator: (args: any) => void;
|
||||||
|
onChangeValue: (args: any) => void;
|
||||||
|
onDeleteRule: (args: DeleteArgs) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const QueryBuilderOption = ({
|
||||||
|
data,
|
||||||
|
filters,
|
||||||
|
level,
|
||||||
|
onDeleteRule,
|
||||||
|
groupIndex,
|
||||||
|
groupValue,
|
||||||
|
noRemove,
|
||||||
|
onChangeField,
|
||||||
|
onChangeOperator,
|
||||||
|
onChangeValue,
|
||||||
|
}: QueryOptionProps) => {
|
||||||
|
const { field, operator, uniqueId, value } = data;
|
||||||
|
|
||||||
|
const handleDeleteRule = () => {
|
||||||
|
onDeleteRule({ groupIndex, groupValue, level, uniqueId });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeField = (e: any) => {
|
||||||
|
onChangeField({ groupIndex, level, uniqueId, value: e });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeOperator = (e: any) => {
|
||||||
|
onChangeOperator({ groupIndex, level, uniqueId, value: e });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeValue = (e: any) => {
|
||||||
|
const isDirectValue =
|
||||||
|
typeof e === 'string' ||
|
||||||
|
typeof e === 'number' ||
|
||||||
|
typeof e === 'undefined' ||
|
||||||
|
typeof e === null;
|
||||||
|
|
||||||
|
if (isDirectValue) {
|
||||||
|
return onChangeValue({
|
||||||
|
groupIndex,
|
||||||
|
level,
|
||||||
|
uniqueId,
|
||||||
|
value: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDate = e instanceof Date;
|
||||||
|
|
||||||
|
if (isDate) {
|
||||||
|
return onChangeValue({
|
||||||
|
groupIndex,
|
||||||
|
level,
|
||||||
|
uniqueId,
|
||||||
|
value: dayjs(e).format('YYYY-MM-DD'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return onChangeValue({
|
||||||
|
groupIndex,
|
||||||
|
level,
|
||||||
|
uniqueId,
|
||||||
|
value: e.currentTarget.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// const filterOperatorMap = {
|
||||||
|
// date: (
|
||||||
|
// <Select
|
||||||
|
// searchable
|
||||||
|
// data={DATE_FILTER_OPTIONS_DATA}
|
||||||
|
// maxWidth={175}
|
||||||
|
// size="xs"
|
||||||
|
// value={operator}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeOperator}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// id: (
|
||||||
|
// <Select
|
||||||
|
// searchable
|
||||||
|
// data={ID_FILTER_OPTIONS_DATA}
|
||||||
|
// maxWidth={175}
|
||||||
|
// size="xs"
|
||||||
|
// value={operator}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeOperator}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// number: (
|
||||||
|
// <Select
|
||||||
|
// searchable
|
||||||
|
// data={NUMBER_FILTER_OPTIONS_DATA}
|
||||||
|
// maxWidth={175}
|
||||||
|
// size="xs"
|
||||||
|
// value={operator}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeOperator}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// string: (
|
||||||
|
// <Select
|
||||||
|
// searchable
|
||||||
|
// data={STRING_FILTER_OPTIONS_DATA}
|
||||||
|
// maxWidth={175}
|
||||||
|
// size="xs"
|
||||||
|
// value={operator}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeOperator}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const filterInputValueMap = {
|
||||||
|
// 'albumArtists.genres.id': (
|
||||||
|
// <Select
|
||||||
|
// searchable
|
||||||
|
// data={genresData?.albumArtist || []}
|
||||||
|
// maxWidth={175}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'albumArtists.name': (
|
||||||
|
// <TextInput
|
||||||
|
// maxWidth={175}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'albumArtists.ratings.value': (
|
||||||
|
// <NumberInput
|
||||||
|
// max={5}
|
||||||
|
// maxWidth={175}
|
||||||
|
// min={0}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'albums.dateAdded': (
|
||||||
|
// <DatePicker
|
||||||
|
// initialLevel="year"
|
||||||
|
// maxDate={dayjs(new Date()).year(3000).toDate()}
|
||||||
|
// maxWidth={175}
|
||||||
|
// minDate={dayjs(new Date()).year(1950).toDate()}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'albums.genres.id': (
|
||||||
|
// <Select
|
||||||
|
// searchable
|
||||||
|
// data={genresData?.album || []}
|
||||||
|
// maxWidth={175}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'albums.name': (
|
||||||
|
// <TextInput
|
||||||
|
// maxWidth={175}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'albums.playCount': (
|
||||||
|
// <NumberInput
|
||||||
|
// maxWidth={175}
|
||||||
|
// min={0}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={(e) => handleChangeValue(e)}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'albums.ratings.value': (
|
||||||
|
// <NumberInput
|
||||||
|
// max={5}
|
||||||
|
// maxWidth={175}
|
||||||
|
// min={0}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'albums.releaseDate': (
|
||||||
|
// <DatePicker
|
||||||
|
// initialLevel="year"
|
||||||
|
// maxDate={dayjs(new Date()).year(3000).toDate()}
|
||||||
|
// maxWidth={175}
|
||||||
|
// minDate={dayjs(new Date()).year(1950).toDate()}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'albums.releaseYear': (
|
||||||
|
// <NumberInput
|
||||||
|
// maxWidth={175}
|
||||||
|
// min={0}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'artists.genres.id': (
|
||||||
|
// <Select
|
||||||
|
// searchable
|
||||||
|
// data={genresData?.artist || []}
|
||||||
|
// maxWidth={175}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'artists.name': (
|
||||||
|
// <TextInput
|
||||||
|
// maxWidth={175}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'artists.ratings.value': (
|
||||||
|
// <NumberInput
|
||||||
|
// max={5}
|
||||||
|
// maxWidth={175}
|
||||||
|
// min={0}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'songs.genres.id': (
|
||||||
|
// <Select
|
||||||
|
// searchable
|
||||||
|
// data={genresData?.song || []}
|
||||||
|
// maxWidth={175}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'songs.name': (
|
||||||
|
// <TextInput
|
||||||
|
// maxWidth={175}
|
||||||
|
// size="xs"
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'songs.playCount': (
|
||||||
|
// <NumberInput
|
||||||
|
// maxWidth={175}
|
||||||
|
// min={0}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// 'songs.ratings.value': (
|
||||||
|
// <NumberInput
|
||||||
|
// max={5}
|
||||||
|
// maxWidth={175}
|
||||||
|
// min={0}
|
||||||
|
// size="xs"
|
||||||
|
// value={value}
|
||||||
|
// width="20%"
|
||||||
|
// onChange={handleChangeValue}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// };
|
||||||
|
|
||||||
|
const ml = (level + 1) * 10 - level * 5;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group ml={ml}>
|
||||||
|
<Select
|
||||||
|
searchable
|
||||||
|
data={filters}
|
||||||
|
maxWidth={175}
|
||||||
|
size="xs"
|
||||||
|
value={field}
|
||||||
|
width="20%"
|
||||||
|
onChange={handleChangeField}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
searchable
|
||||||
|
data={operators}
|
||||||
|
// disabled={!field}
|
||||||
|
maxWidth={175}
|
||||||
|
size="xs"
|
||||||
|
value={field}
|
||||||
|
width="20%"
|
||||||
|
onChange={handleChangeField}
|
||||||
|
/>
|
||||||
|
{field ? (
|
||||||
|
<></>
|
||||||
|
) : (
|
||||||
|
<TextInput
|
||||||
|
disabled
|
||||||
|
maxWidth={175}
|
||||||
|
size="xs"
|
||||||
|
width="20%"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* // filterOperatorMap[ // OPTIONS_MAP[field as keyof typeof OPTIONS_MAP].type as keyof typeof
|
||||||
|
filterOperatorMap // ] */}
|
||||||
|
<Button
|
||||||
|
disabled={noRemove}
|
||||||
|
px={5}
|
||||||
|
size="xs"
|
||||||
|
tooltip={{ label: 'Remove rule' }}
|
||||||
|
variant="default"
|
||||||
|
onClick={handleDeleteRule}
|
||||||
|
>
|
||||||
|
<RiSubtractLine size={20} />
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,336 @@
|
||||||
|
import { useState, useImperativeHandle, forwardRef } from 'react';
|
||||||
|
import { uniqueId } from 'lodash';
|
||||||
|
import clone from 'lodash/clone';
|
||||||
|
import setWith from 'lodash/setWith';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import sortBy from 'lodash/sortBy';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import { NDSongQueryFields } from '/@/renderer/api/navidrome.types';
|
||||||
|
import { AdvancedFilterGroup, AdvancedFilterRule, QueryBuilder } from '/@/renderer/components';
|
||||||
|
import { FilterGroupType } from '/@/renderer/types';
|
||||||
|
|
||||||
|
type AddArgs = {
|
||||||
|
groupIndex: number[];
|
||||||
|
groupValue: string;
|
||||||
|
level: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DeleteArgs = {
|
||||||
|
groupIndex: number[];
|
||||||
|
groupValue: string;
|
||||||
|
level: number;
|
||||||
|
uniqueId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortQuery = (query: any) => {
|
||||||
|
let b;
|
||||||
|
|
||||||
|
if (query.all) {
|
||||||
|
b = sortBy(query.all, (item) => {
|
||||||
|
const key = Object.keys(item)[0];
|
||||||
|
return key === 'all' || key === 'any' ? 0 : 1;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
b = sortBy(query.any, (item) => {
|
||||||
|
const key = Object.keys(item)[0];
|
||||||
|
return key === 'all' || key === 'any' ? 0 : 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { all: b };
|
||||||
|
};
|
||||||
|
|
||||||
|
const addUniqueId = (query: any) => {
|
||||||
|
const queryCopy = clone(query);
|
||||||
|
const addId = (item: any) => {
|
||||||
|
const key = Object.keys(item)[0];
|
||||||
|
if (key === 'all' || key === 'any') {
|
||||||
|
item[key].forEach(addId);
|
||||||
|
} else {
|
||||||
|
item[key].uniqueId = nanoid();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
addId(queryCopy);
|
||||||
|
return queryCopy;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PlaylistQueryBuilder = forwardRef(({ query, onChange }: any, ref) => {
|
||||||
|
const [filters, setFilters] = useState<any>(
|
||||||
|
sortQuery(addUniqueId(query)) || {
|
||||||
|
all: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('filters :>> ', JSON.stringify(filters));
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
reset() {
|
||||||
|
setFilters({
|
||||||
|
all: [],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const setFilterHandler = (newFilters: AdvancedFilterGroup) => {
|
||||||
|
setFilters(newFilters);
|
||||||
|
onChange(newFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddRuleGroup = (args: AddArgs) => {
|
||||||
|
const { level, groupIndex, groupValue } = args;
|
||||||
|
const filtersCopy = clone(filters);
|
||||||
|
|
||||||
|
const getPath = (level: number) => {
|
||||||
|
const rootKey = Object.keys(filters)[0];
|
||||||
|
if (level === 0) return rootKey;
|
||||||
|
|
||||||
|
const str = [rootKey];
|
||||||
|
for (const index of groupIndex) {
|
||||||
|
str.push(`[${index}].${groupValue}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${str.join('.')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const path = getPath(level);
|
||||||
|
console.log('path', filtersCopy, path);
|
||||||
|
const updatedFilters = setWith(
|
||||||
|
filtersCopy,
|
||||||
|
path,
|
||||||
|
sortQuery([...get(filtersCopy, path), { any: [{ contains: { title: '' } }] }]),
|
||||||
|
clone,
|
||||||
|
);
|
||||||
|
|
||||||
|
setFilterHandler(updatedFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteRuleGroup = (args: DeleteArgs) => {
|
||||||
|
const { uniqueId, level, groupIndex, groupValue } = args;
|
||||||
|
const filtersCopy = clone(filters);
|
||||||
|
|
||||||
|
const getPath = (level: number) => {
|
||||||
|
const rootKey = Object.keys(filters)[0];
|
||||||
|
if (level === 0) return rootKey;
|
||||||
|
|
||||||
|
const str = [];
|
||||||
|
for (let i = 0; i < groupIndex.length; i += 1) {
|
||||||
|
if (groupIndex.length === 1) {
|
||||||
|
str.push(rootKey);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i === 0) {
|
||||||
|
str.push(`${rootKey}[${groupIndex[i]}]`);
|
||||||
|
} else if (i !== groupIndex.length - 1) {
|
||||||
|
str.push(`${groupValue}[${groupIndex[i]}]`);
|
||||||
|
} else {
|
||||||
|
str.push(`${groupValue}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${str.join('.')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const path = getPath(level);
|
||||||
|
|
||||||
|
const dataAtPath = get(filtersCopy, path);
|
||||||
|
const lv = groupIndex[level - 1];
|
||||||
|
const removed = [...dataAtPath.slice(0, lv), ...dataAtPath.slice(lv + 1)];
|
||||||
|
const updatedFilters = setWith(filtersCopy, path, sortQuery(removed), clone);
|
||||||
|
|
||||||
|
setFilterHandler(updatedFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRulePath = (level: number, groupIndex: number[], groupValue: string) => {
|
||||||
|
if (level === 0) return Object.keys(filters)[0];
|
||||||
|
|
||||||
|
const str = [];
|
||||||
|
for (const index of groupIndex) {
|
||||||
|
str.push(`${Object.keys(filters)[0]}[${index}].${groupValue}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${str.join('.')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// const getRulePath = (
|
||||||
|
// level: number,
|
||||||
|
// groupIndex: number[],
|
||||||
|
// groupValue: string,
|
||||||
|
// uniqueId?: string,
|
||||||
|
// ) => {
|
||||||
|
// const rootKey = Object.keys(filters)[0];
|
||||||
|
// if (level === 0) return rootKey;
|
||||||
|
|
||||||
|
// const str = [];
|
||||||
|
// for (const index of groupIndex) {
|
||||||
|
// if (uniqueId) {
|
||||||
|
// str.push(`${rootKey}[${index}].${groupValue}.${uniqueId}`);
|
||||||
|
// } else {
|
||||||
|
// str.push(`${rootKey}[${index}].${groupValue}`);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return `${str.join('.')}`;
|
||||||
|
// };
|
||||||
|
|
||||||
|
const handleAddRule = (args: AddArgs) => {
|
||||||
|
const { level, groupValue, groupIndex } = args;
|
||||||
|
const filtersCopy = clone(filters);
|
||||||
|
|
||||||
|
const path = getRulePath(level, groupIndex, groupValue);
|
||||||
|
|
||||||
|
const updatedFilters = setWith(
|
||||||
|
filtersCopy,
|
||||||
|
path,
|
||||||
|
[...get(filtersCopy, path), { contains: { title: '', uniqueId: nanoid() } }],
|
||||||
|
clone,
|
||||||
|
);
|
||||||
|
|
||||||
|
setFilterHandler(updatedFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteRule = (args: DeleteArgs) => {
|
||||||
|
const { uniqueId, level, groupIndex, groupValue } = args;
|
||||||
|
const filtersCopy = clone(filters);
|
||||||
|
|
||||||
|
const path = getRulePath(level, groupIndex, groupValue);
|
||||||
|
|
||||||
|
const dataAtPath = get(filtersCopy, path);
|
||||||
|
const lv = groupIndex[level - 1];
|
||||||
|
const removed = [...dataAtPath.slice(0, lv), ...dataAtPath.slice(lv + 1)];
|
||||||
|
|
||||||
|
console.log('removed :>> ', removed);
|
||||||
|
|
||||||
|
const updatedFilters = setWith(filtersCopy, path, removed, clone);
|
||||||
|
|
||||||
|
setFilterHandler(updatedFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeField = (args: any) => {
|
||||||
|
const { uniqueId, level, groupIndex, value } = args;
|
||||||
|
const filtersCopy = clone(filters);
|
||||||
|
|
||||||
|
const path = getRulePath(level, groupIndex);
|
||||||
|
|
||||||
|
console.log('path', path);
|
||||||
|
|
||||||
|
const updatedFilters = setWith(
|
||||||
|
filtersCopy,
|
||||||
|
path,
|
||||||
|
get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
|
||||||
|
if (rule.uniqueId !== uniqueId) return rule;
|
||||||
|
// const defaultOperator = FILTER_OPTIONS_DATA.find(
|
||||||
|
// (option) => option.value === value,
|
||||||
|
// )?.default;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rule,
|
||||||
|
field: value,
|
||||||
|
// operator: defaultOperator || '',
|
||||||
|
operator: '',
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
clone,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('updatedFilters', updatedFilters);
|
||||||
|
|
||||||
|
// setFilterHandler(updatedFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeType = (args: any) => {
|
||||||
|
const { level, groupIndex, value } = args;
|
||||||
|
|
||||||
|
const filtersCopy = clone(filters);
|
||||||
|
|
||||||
|
if (level === 0) {
|
||||||
|
return setFilterHandler({ ...filtersCopy, type: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTypePath = () => {
|
||||||
|
const str = [];
|
||||||
|
for (let i = 0; i < groupIndex.length; i += 1) {
|
||||||
|
str.push(`group[${groupIndex[i]}]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${str.join('.')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const path = getTypePath();
|
||||||
|
const updatedFilters = setWith(
|
||||||
|
filtersCopy,
|
||||||
|
path,
|
||||||
|
{
|
||||||
|
...get(filtersCopy, path),
|
||||||
|
type: value,
|
||||||
|
},
|
||||||
|
clone,
|
||||||
|
);
|
||||||
|
|
||||||
|
return setFilterHandler(updatedFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeOperator = (args: any) => {
|
||||||
|
const { uniqueId, level, groupIndex, value } = args;
|
||||||
|
const filtersCopy = clone(filters);
|
||||||
|
|
||||||
|
const path = getRulePath(level, groupIndex);
|
||||||
|
const updatedFilters = setWith(
|
||||||
|
filtersCopy,
|
||||||
|
path,
|
||||||
|
get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
|
||||||
|
if (rule.uniqueId !== uniqueId) return rule;
|
||||||
|
return {
|
||||||
|
...rule,
|
||||||
|
operator: value,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
clone,
|
||||||
|
);
|
||||||
|
|
||||||
|
setFilterHandler(updatedFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeValue = (args: any) => {
|
||||||
|
const { uniqueId, level, groupIndex, value } = args;
|
||||||
|
const filtersCopy = clone(filters);
|
||||||
|
|
||||||
|
const path = getRulePath(level, groupIndex);
|
||||||
|
const updatedFilters = setWith(
|
||||||
|
filtersCopy,
|
||||||
|
path,
|
||||||
|
get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
|
||||||
|
if (rule.uniqueId !== uniqueId) return rule;
|
||||||
|
return {
|
||||||
|
...rule,
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
clone,
|
||||||
|
);
|
||||||
|
|
||||||
|
setFilterHandler(updatedFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<QueryBuilder
|
||||||
|
data={filters}
|
||||||
|
filters={NDSongQueryFields}
|
||||||
|
groupIndex={[]}
|
||||||
|
level={0}
|
||||||
|
uniqueId={filters.uniqueId}
|
||||||
|
onAddRule={handleAddRule}
|
||||||
|
onAddRuleGroup={handleAddRuleGroup}
|
||||||
|
onChangeField={handleChangeField}
|
||||||
|
onChangeOperator={handleChangeOperator}
|
||||||
|
onChangeType={handleChangeType}
|
||||||
|
onChangeValue={handleChangeValue}
|
||||||
|
onDeleteRule={handleDeleteRule}
|
||||||
|
onDeleteRuleGroup={handleDeleteRuleGroup}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
Reference in a new issue