Add initial nd smart playlist ui
This commit is contained in:
parent
65974dbf28
commit
75ef43dffb
6 changed files with 604 additions and 407 deletions
|
@ -339,3 +339,40 @@ export type NDPlaylistSongList = {
|
||||||
startIndex: number;
|
startIndex: number;
|
||||||
totalRecordCount: number;
|
totalRecordCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const NDSongQueryFields = [
|
||||||
|
{ label: 'Title', value: 'title' },
|
||||||
|
{ label: 'Album', value: 'album' },
|
||||||
|
{ label: 'Artist', value: 'artist' },
|
||||||
|
{ label: 'Album artist', value: 'albumartist' },
|
||||||
|
{ label: 'Has cover art', value: 'hascoverart' },
|
||||||
|
{ label: 'Track number', value: 'tracknumber' },
|
||||||
|
{ label: 'Disc number', value: 'discnumber' },
|
||||||
|
{ label: 'Year', value: 'year' },
|
||||||
|
{ label: 'Size', value: 'size' },
|
||||||
|
{ label: 'Is compilation', value: 'compilation' },
|
||||||
|
{ label: 'Date added', value: 'dateadded' },
|
||||||
|
{ label: 'Date modified', value: 'datemodified' },
|
||||||
|
{ label: 'Disc subtitle', value: 'discsubtitle' },
|
||||||
|
{ label: 'Comment', value: 'comment' },
|
||||||
|
{ label: 'Lyrics', value: 'lyrics' },
|
||||||
|
{ label: 'Sort title', value: 'sorttitle' },
|
||||||
|
{ label: 'Sort album', value: 'sortalbum' },
|
||||||
|
{ label: 'Sort artist', value: 'sortartist' },
|
||||||
|
{ label: 'Sort album artist', value: 'sortalbumartist' },
|
||||||
|
{ label: 'Album type', value: 'albumtype' },
|
||||||
|
{ label: 'Album comment', value: 'albumcomment' },
|
||||||
|
{ label: 'Catalog number', value: 'catalognumber' },
|
||||||
|
{ label: 'File path', value: 'filepath' },
|
||||||
|
{ label: 'File type', value: 'filetype' },
|
||||||
|
{ label: 'Duration', value: 'duration' },
|
||||||
|
{ label: 'Bitrate', value: 'bitrate' },
|
||||||
|
{ label: 'BPM', value: 'bpm' },
|
||||||
|
{ label: 'Channels', value: 'channels' },
|
||||||
|
{ label: 'Genre', value: 'genre' },
|
||||||
|
{ label: 'Is favorite', value: 'loved' },
|
||||||
|
{ label: 'Date favorited', value: 'dateloved' },
|
||||||
|
{ label: 'Last played', value: 'lastplayed' },
|
||||||
|
{ label: 'Play count', value: 'playcount' },
|
||||||
|
{ label: 'Rating', value: 'rating' },
|
||||||
|
];
|
||||||
|
|
|
@ -1,25 +1,11 @@
|
||||||
import { Group, Stack } from '@mantine/core';
|
import { Group, Stack } from '@mantine/core';
|
||||||
import { Select } from '/@/renderer/components/select';
|
import { Select } from '/@/renderer/components/select';
|
||||||
import { FilterGroupType } from '/@/renderer/types';
|
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { RiAddLine, RiMore2Line } from 'react-icons/ri';
|
import { RiAddLine, RiMore2Line } from 'react-icons/ri';
|
||||||
import { Button } from '/@/renderer/components/button';
|
import { Button } from '/@/renderer/components/button';
|
||||||
import { DropdownMenu } from '/@/renderer/components/dropdown-menu';
|
import { DropdownMenu } from '/@/renderer/components/dropdown-menu';
|
||||||
import { QueryBuilderOption } from '/@/renderer/components/query-builder/query-builder-option';
|
import { QueryBuilderOption } from '/@/renderer/components/query-builder/query-builder-option';
|
||||||
|
import { QueryBuilderGroup, QueryBuilderRule } from '/@/renderer/types';
|
||||||
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 = [
|
const FILTER_GROUP_OPTIONS_DATA = [
|
||||||
{
|
{
|
||||||
|
@ -32,58 +18,16 @@ const FILTER_GROUP_OPTIONS_DATA = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 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 = {
|
type AddArgs = {
|
||||||
groupIndex: number[];
|
groupIndex: number[];
|
||||||
groupValue: string;
|
|
||||||
level: number;
|
level: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DeleteArgs = {
|
type DeleteArgs = {
|
||||||
groupIndex: number[];
|
groupIndex: number[];
|
||||||
groupValue: string;
|
|
||||||
level: number;
|
level: number;
|
||||||
uniqueId: string;
|
uniqueId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface QueryBuilderProps {
|
interface QueryBuilderProps {
|
||||||
data: Record<string, any>;
|
data: Record<string, any>;
|
||||||
filters: { label: string; value: string }[];
|
filters: { label: string; value: string }[];
|
||||||
|
@ -115,42 +59,36 @@ export const QueryBuilder = ({
|
||||||
uniqueId,
|
uniqueId,
|
||||||
filters,
|
filters,
|
||||||
}: QueryBuilderProps) => {
|
}: 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 = () => {
|
const handleAddRule = () => {
|
||||||
onAddRule({ groupIndex, groupValue, level });
|
onAddRule({ groupIndex, level });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddRuleGroup = () => {
|
const handleAddRuleGroup = () => {
|
||||||
onAddRuleGroup({ groupIndex, groupValue, level });
|
onAddRuleGroup({ groupIndex, level });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteRuleGroup = () => {
|
const handleDeleteRuleGroup = () => {
|
||||||
onDeleteRuleGroup({ groupIndex, groupValue, level, uniqueId });
|
onDeleteRuleGroup({ groupIndex, level, uniqueId });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeType = (value: string | null) => {
|
const handleChangeType = (value: string | null) => {
|
||||||
onChangeType({ groupIndex, level, value });
|
onChangeType({ groupIndex, level, value });
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('rules :>> ', rules);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack ml={`${level * 10}px`}>
|
<Stack ml={`${level * 10}px`}>
|
||||||
<Group>
|
<Group>
|
||||||
<Select
|
<Select
|
||||||
data={FILTER_GROUP_OPTIONS_DATA}
|
data={FILTER_GROUP_OPTIONS_DATA}
|
||||||
maxWidth={175}
|
maxWidth={175}
|
||||||
size="xs"
|
size="sm"
|
||||||
value={groupValue}
|
value={data.type}
|
||||||
width="20%"
|
width="20%"
|
||||||
onChange={handleChangeType}
|
onChange={handleChangeType}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
px={5}
|
px={5}
|
||||||
size="xs"
|
size="sm"
|
||||||
tooltip={{ label: 'Add rule' }}
|
tooltip={{ label: 'Add rule' }}
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={handleAddRule}
|
onClick={handleAddRule}
|
||||||
|
@ -161,7 +99,7 @@ export const QueryBuilder = ({
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
p={0}
|
p={0}
|
||||||
size="xs"
|
size="sm"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
<RiMore2Line size={20} />
|
<RiMore2Line size={20} />
|
||||||
|
@ -181,9 +119,9 @@ export const QueryBuilder = ({
|
||||||
key="advanced-filter-option"
|
key="advanced-filter-option"
|
||||||
initial={false}
|
initial={false}
|
||||||
>
|
>
|
||||||
{rules.map((rule: AdvancedFilterRule, i: number) => (
|
{data?.rules?.map((rule: QueryBuilderRule) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={`rule-${level}-${Object.keys(rule)[0]}`}
|
key={rule.uniqueId}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
exit={{ opacity: 0, x: -25 }}
|
exit={{ opacity: 0, x: -25 }}
|
||||||
initial={{ opacity: 0, x: -25 }}
|
initial={{ opacity: 0, x: -25 }}
|
||||||
|
@ -193,7 +131,7 @@ export const QueryBuilder = ({
|
||||||
data={rule}
|
data={rule}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
groupIndex={groupIndex || []}
|
groupIndex={groupIndex || []}
|
||||||
groupValue={groupValue}
|
// groupValue={groupValue}
|
||||||
level={level}
|
level={level}
|
||||||
noRemove={data?.rules?.length === 1}
|
noRemove={data?.rules?.length === 1}
|
||||||
onChangeField={onChangeField}
|
onChangeField={onChangeField}
|
||||||
|
@ -204,14 +142,14 @@ export const QueryBuilder = ({
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
{group && (
|
{data?.group && (
|
||||||
<AnimatePresence
|
<AnimatePresence
|
||||||
key="advanced-filter-group"
|
key="advanced-filter-group"
|
||||||
initial={false}
|
initial={false}
|
||||||
>
|
>
|
||||||
{group.map((group: AdvancedFilterGroup, index: number) => (
|
{data.group?.map((group: QueryBuilderGroup, index: number) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={`group-${level}-${index}`}
|
key={group.uniqueId}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
exit={{ opacity: 0, x: -25 }}
|
exit={{ opacity: 0, x: -25 }}
|
||||||
initial={{ opacity: 0, x: -25 }}
|
initial={{ opacity: 0, x: -25 }}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { RiSubtractLine } from 'react-icons/ri';
|
||||||
import { Button } from '/@/renderer/components/button';
|
import { Button } from '/@/renderer/components/button';
|
||||||
import { TextInput } from '/@/renderer/components/input';
|
import { TextInput } from '/@/renderer/components/input';
|
||||||
import { Select } from '/@/renderer/components/select';
|
import { Select } from '/@/renderer/components/select';
|
||||||
import { AdvancedFilterRule } from '/@/renderer/types';
|
import { QueryBuilderRule } from '/@/renderer/types';
|
||||||
|
|
||||||
const operators = [
|
const operators = [
|
||||||
{ label: 'is', value: 'is' },
|
{ label: 'is', value: 'is' },
|
||||||
|
@ -24,16 +24,15 @@ const operators = [
|
||||||
|
|
||||||
type DeleteArgs = {
|
type DeleteArgs = {
|
||||||
groupIndex: number[];
|
groupIndex: number[];
|
||||||
groupValue: string;
|
|
||||||
level: number;
|
level: number;
|
||||||
uniqueId: string;
|
uniqueId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface QueryOptionProps {
|
interface QueryOptionProps {
|
||||||
data: AdvancedFilterRule;
|
data: QueryBuilderRule;
|
||||||
filters: { label: string; value: string }[];
|
filters: { label: string; value: string }[];
|
||||||
groupIndex: number[];
|
groupIndex: number[];
|
||||||
groupValue: string;
|
// groupValue: string;
|
||||||
level: number;
|
level: number;
|
||||||
noRemove: boolean;
|
noRemove: boolean;
|
||||||
onChangeField: (args: any) => void;
|
onChangeField: (args: any) => void;
|
||||||
|
@ -48,7 +47,6 @@ export const QueryBuilderOption = ({
|
||||||
level,
|
level,
|
||||||
onDeleteRule,
|
onDeleteRule,
|
||||||
groupIndex,
|
groupIndex,
|
||||||
groupValue,
|
|
||||||
noRemove,
|
noRemove,
|
||||||
onChangeField,
|
onChangeField,
|
||||||
onChangeOperator,
|
onChangeOperator,
|
||||||
|
@ -57,7 +55,7 @@ export const QueryBuilderOption = ({
|
||||||
const { field, operator, uniqueId, value } = data;
|
const { field, operator, uniqueId, value } = data;
|
||||||
|
|
||||||
const handleDeleteRule = () => {
|
const handleDeleteRule = () => {
|
||||||
onDeleteRule({ groupIndex, groupValue, level, uniqueId });
|
onDeleteRule({ groupIndex, level, uniqueId });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeField = (e: any) => {
|
const handleChangeField = (e: any) => {
|
||||||
|
@ -69,6 +67,8 @@ export const QueryBuilderOption = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeValue = (e: any) => {
|
const handleChangeValue = (e: any) => {
|
||||||
|
console.log('e', e);
|
||||||
|
|
||||||
const isDirectValue =
|
const isDirectValue =
|
||||||
typeof e === 'string' ||
|
typeof e === 'string' ||
|
||||||
typeof e === 'number' ||
|
typeof e === 'number' ||
|
||||||
|
@ -330,7 +330,7 @@ export const QueryBuilderOption = ({
|
||||||
// ),
|
// ),
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const ml = (level + 1) * 10 - level * 5;
|
const ml = (level + 1) * 10;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group ml={ml}>
|
<Group ml={ml}>
|
||||||
|
@ -338,7 +338,7 @@ export const QueryBuilderOption = ({
|
||||||
searchable
|
searchable
|
||||||
data={filters}
|
data={filters}
|
||||||
maxWidth={175}
|
maxWidth={175}
|
||||||
size="xs"
|
size="sm"
|
||||||
value={field}
|
value={field}
|
||||||
width="20%"
|
width="20%"
|
||||||
onChange={handleChangeField}
|
onChange={handleChangeField}
|
||||||
|
@ -346,21 +346,29 @@ export const QueryBuilderOption = ({
|
||||||
<Select
|
<Select
|
||||||
searchable
|
searchable
|
||||||
data={operators}
|
data={operators}
|
||||||
// disabled={!field}
|
disabled={!field}
|
||||||
maxWidth={175}
|
maxWidth={175}
|
||||||
size="xs"
|
size="sm"
|
||||||
value={field}
|
value={operator}
|
||||||
width="20%"
|
width="20%"
|
||||||
onChange={handleChangeField}
|
onChange={handleChangeOperator}
|
||||||
/>
|
/>
|
||||||
{field ? (
|
{field ? (
|
||||||
<></>
|
<TextInput
|
||||||
|
defaultValue={value}
|
||||||
|
maxWidth={175}
|
||||||
|
size="sm"
|
||||||
|
width="20%"
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<TextInput
|
<TextInput
|
||||||
disabled
|
disabled
|
||||||
|
defaultValue={value}
|
||||||
maxWidth={175}
|
maxWidth={175}
|
||||||
size="xs"
|
size="sm"
|
||||||
width="20%"
|
width="20%"
|
||||||
|
onChange={handleChangeValue}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* // filterOperatorMap[ // OPTIONS_MAP[field as keyof typeof OPTIONS_MAP].type as keyof typeof
|
{/* // filterOperatorMap[ // OPTIONS_MAP[field as keyof typeof OPTIONS_MAP].type as keyof typeof
|
||||||
|
@ -368,7 +376,7 @@ export const QueryBuilderOption = ({
|
||||||
<Button
|
<Button
|
||||||
disabled={noRemove}
|
disabled={noRemove}
|
||||||
px={5}
|
px={5}
|
||||||
size="xs"
|
size="sm"
|
||||||
tooltip={{ label: 'Remove rule' }}
|
tooltip={{ label: 'Remove rule' }}
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={handleDeleteRule}
|
onClick={handleDeleteRule}
|
||||||
|
|
|
@ -1,69 +1,43 @@
|
||||||
import { useState, useImperativeHandle, forwardRef } from 'react';
|
import { useState, useImperativeHandle, forwardRef } from 'react';
|
||||||
import { uniqueId } from 'lodash';
|
import { Flex, Group, ScrollArea } from '@mantine/core';
|
||||||
import clone from 'lodash/clone';
|
import clone from 'lodash/clone';
|
||||||
import setWith from 'lodash/setWith';
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import sortBy from 'lodash/sortBy';
|
import setWith from 'lodash/setWith';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { NDSongQueryFields } from '/@/renderer/api/navidrome.types';
|
import { NDSongQueryFields } from '/@/renderer/api/navidrome.types';
|
||||||
import { AdvancedFilterGroup, AdvancedFilterRule, QueryBuilder } from '/@/renderer/components';
|
import { Button, DropdownMenu, NumberInput, QueryBuilder } from '/@/renderer/components';
|
||||||
import { FilterGroupType } from '/@/renderer/types';
|
import {
|
||||||
|
convertNDQueryToQueryGroup,
|
||||||
|
convertQueryGroupToNDQuery,
|
||||||
|
} from '/@/renderer/features/playlists/utils';
|
||||||
|
import { QueryBuilderGroup, QueryBuilderRule } from '/@/renderer/types';
|
||||||
|
import { RiMore2Fill } from 'react-icons/ri';
|
||||||
|
|
||||||
type AddArgs = {
|
type AddArgs = {
|
||||||
groupIndex: number[];
|
groupIndex: number[];
|
||||||
groupValue: string;
|
|
||||||
level: number;
|
level: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DeleteArgs = {
|
type DeleteArgs = {
|
||||||
groupIndex: number[];
|
groupIndex: number[];
|
||||||
groupValue: string;
|
|
||||||
level: number;
|
level: number;
|
||||||
uniqueId: string;
|
uniqueId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortQuery = (query: any) => {
|
interface PlaylistQueryBuilderProps {
|
||||||
let b;
|
onSave: (parsedFilter: any) => void;
|
||||||
|
onSaveAs: (parsedFilter: any) => void;
|
||||||
if (query.all) {
|
query: any;
|
||||||
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 };
|
export const PlaylistQueryBuilder = forwardRef(
|
||||||
};
|
({ query, onSave, onSaveAs }: PlaylistQueryBuilderProps, ref) => {
|
||||||
|
|
||||||
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>(
|
const [filters, setFilters] = useState<any>(
|
||||||
sortQuery(addUniqueId(query)) || {
|
convertNDQueryToQueryGroup(query) || {
|
||||||
all: [],
|
all: [],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('filters :>> ', JSON.stringify(filters));
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
reset() {
|
reset() {
|
||||||
setFilters({
|
setFilters({
|
||||||
|
@ -72,33 +46,54 @@ export const PlaylistQueryBuilder = forwardRef(({ query, onChange }: any, ref) =
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const setFilterHandler = (newFilters: AdvancedFilterGroup) => {
|
const setFilterHandler = (newFilters: QueryBuilderGroup) => {
|
||||||
setFilters(newFilters);
|
setFilters(newFilters);
|
||||||
onChange(newFilters);
|
// onSave(newFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
onSave(convertQueryGroupToNDQuery(filters));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveAs = () => {
|
||||||
|
onSaveAs(convertQueryGroupToNDQuery(filters));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddRuleGroup = (args: AddArgs) => {
|
const handleAddRuleGroup = (args: AddArgs) => {
|
||||||
const { level, groupIndex, groupValue } = args;
|
const { level, groupIndex } = args;
|
||||||
const filtersCopy = clone(filters);
|
const filtersCopy = clone(filters);
|
||||||
|
|
||||||
const getPath = (level: number) => {
|
const getPath = (level: number) => {
|
||||||
const rootKey = Object.keys(filters)[0];
|
if (level === 0) return 'group';
|
||||||
if (level === 0) return rootKey;
|
|
||||||
|
|
||||||
const str = [rootKey];
|
const str = [];
|
||||||
for (const index of groupIndex) {
|
for (const index of groupIndex) {
|
||||||
str.push(`[${index}].${groupValue}`);
|
str.push(`group[${index}]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${str.join('.')}`;
|
return `${str.join('.')}.group`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const path = getPath(level);
|
const path = getPath(level);
|
||||||
console.log('path', filtersCopy, path);
|
|
||||||
const updatedFilters = setWith(
|
const updatedFilters = setWith(
|
||||||
filtersCopy,
|
filtersCopy,
|
||||||
path,
|
path,
|
||||||
sortQuery([...get(filtersCopy, path), { any: [{ contains: { title: '' } }] }]),
|
[
|
||||||
|
...get(filtersCopy, path),
|
||||||
|
{
|
||||||
|
group: [],
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
field: '',
|
||||||
|
operator: '',
|
||||||
|
uniqueId: nanoid(),
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'any',
|
||||||
|
uniqueId: nanoid(),
|
||||||
|
},
|
||||||
|
],
|
||||||
clone,
|
clone,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -106,26 +101,18 @@ export const PlaylistQueryBuilder = forwardRef(({ query, onChange }: any, ref) =
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteRuleGroup = (args: DeleteArgs) => {
|
const handleDeleteRuleGroup = (args: DeleteArgs) => {
|
||||||
const { uniqueId, level, groupIndex, groupValue } = args;
|
const { uniqueId, level, groupIndex } = args;
|
||||||
const filtersCopy = clone(filters);
|
const filtersCopy = clone(filters);
|
||||||
|
|
||||||
const getPath = (level: number) => {
|
const getPath = (level: number) => {
|
||||||
const rootKey = Object.keys(filters)[0];
|
if (level === 0) return 'group';
|
||||||
if (level === 0) return rootKey;
|
|
||||||
|
|
||||||
const str = [];
|
const str = [];
|
||||||
for (let i = 0; i < groupIndex.length; i += 1) {
|
for (let i = 0; i < groupIndex.length; i += 1) {
|
||||||
if (groupIndex.length === 1) {
|
if (i !== groupIndex.length - 1) {
|
||||||
str.push(rootKey);
|
str.push(`group[${groupIndex[i]}]`);
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i === 0) {
|
|
||||||
str.push(`${rootKey}[${groupIndex[i]}]`);
|
|
||||||
} else if (i !== groupIndex.length - 1) {
|
|
||||||
str.push(`${groupValue}[${groupIndex[i]}]`);
|
|
||||||
} else {
|
} else {
|
||||||
str.push(`${groupValue}`);
|
str.push(`group`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,56 +121,48 @@ export const PlaylistQueryBuilder = forwardRef(({ query, onChange }: any, ref) =
|
||||||
|
|
||||||
const path = getPath(level);
|
const path = getPath(level);
|
||||||
|
|
||||||
const dataAtPath = get(filtersCopy, path);
|
const updatedFilters = setWith(
|
||||||
const lv = groupIndex[level - 1];
|
filtersCopy,
|
||||||
const removed = [...dataAtPath.slice(0, lv), ...dataAtPath.slice(lv + 1)];
|
path,
|
||||||
const updatedFilters = setWith(filtersCopy, path, sortQuery(removed), clone);
|
[
|
||||||
|
...get(filtersCopy, path).filter(
|
||||||
|
(group: QueryBuilderGroup) => group.uniqueId !== uniqueId,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
clone,
|
||||||
|
);
|
||||||
|
|
||||||
setFilterHandler(updatedFilters);
|
setFilterHandler(updatedFilters);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRulePath = (level: number, groupIndex: number[], groupValue: string) => {
|
const getRulePath = (level: number, groupIndex: number[]) => {
|
||||||
if (level === 0) return Object.keys(filters)[0];
|
if (level === 0) return 'rules';
|
||||||
|
|
||||||
const str = [];
|
const str = [];
|
||||||
for (const index of groupIndex) {
|
for (const index of groupIndex) {
|
||||||
str.push(`${Object.keys(filters)[0]}[${index}].${groupValue}`);
|
str.push(`group[${index}]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${str.join('.')}`;
|
return `${str.join('.')}.rules`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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 handleAddRule = (args: AddArgs) => {
|
||||||
const { level, groupValue, groupIndex } = args;
|
const { level, groupIndex } = args;
|
||||||
const filtersCopy = clone(filters);
|
const filtersCopy = clone(filters);
|
||||||
|
|
||||||
const path = getRulePath(level, groupIndex, groupValue);
|
const path = getRulePath(level, groupIndex);
|
||||||
|
|
||||||
const updatedFilters = setWith(
|
const updatedFilters = setWith(
|
||||||
filtersCopy,
|
filtersCopy,
|
||||||
path,
|
path,
|
||||||
[...get(filtersCopy, path), { contains: { title: '', uniqueId: nanoid() } }],
|
[
|
||||||
|
...get(filtersCopy, path),
|
||||||
|
{
|
||||||
|
field: 'title',
|
||||||
|
operator: 'contains',
|
||||||
|
uniqueId: nanoid(),
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
clone,
|
clone,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -191,18 +170,16 @@ export const PlaylistQueryBuilder = forwardRef(({ query, onChange }: any, ref) =
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteRule = (args: DeleteArgs) => {
|
const handleDeleteRule = (args: DeleteArgs) => {
|
||||||
const { uniqueId, level, groupIndex, groupValue } = args;
|
const { uniqueId, level, groupIndex } = args;
|
||||||
const filtersCopy = clone(filters);
|
const filtersCopy = clone(filters);
|
||||||
|
|
||||||
const path = getRulePath(level, groupIndex, groupValue);
|
const path = getRulePath(level, groupIndex);
|
||||||
|
const updatedFilters = setWith(
|
||||||
const dataAtPath = get(filtersCopy, path);
|
filtersCopy,
|
||||||
const lv = groupIndex[level - 1];
|
path,
|
||||||
const removed = [...dataAtPath.slice(0, lv), ...dataAtPath.slice(lv + 1)];
|
get(filtersCopy, path).filter((rule: QueryBuilderRule) => rule.uniqueId !== uniqueId),
|
||||||
|
clone,
|
||||||
console.log('removed :>> ', removed);
|
);
|
||||||
|
|
||||||
const updatedFilters = setWith(filtersCopy, path, removed, clone);
|
|
||||||
|
|
||||||
setFilterHandler(updatedFilters);
|
setFilterHandler(updatedFilters);
|
||||||
};
|
};
|
||||||
|
@ -212,13 +189,10 @@ export const PlaylistQueryBuilder = forwardRef(({ query, onChange }: any, ref) =
|
||||||
const filtersCopy = clone(filters);
|
const filtersCopy = clone(filters);
|
||||||
|
|
||||||
const path = getRulePath(level, groupIndex);
|
const path = getRulePath(level, groupIndex);
|
||||||
|
|
||||||
console.log('path', path);
|
|
||||||
|
|
||||||
const updatedFilters = setWith(
|
const updatedFilters = setWith(
|
||||||
filtersCopy,
|
filtersCopy,
|
||||||
path,
|
path,
|
||||||
get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
|
get(filtersCopy, path).map((rule: QueryBuilderGroup) => {
|
||||||
if (rule.uniqueId !== uniqueId) return rule;
|
if (rule.uniqueId !== uniqueId) return rule;
|
||||||
// const defaultOperator = FILTER_OPTIONS_DATA.find(
|
// const defaultOperator = FILTER_OPTIONS_DATA.find(
|
||||||
// (option) => option.value === value,
|
// (option) => option.value === value,
|
||||||
|
@ -227,7 +201,6 @@ export const PlaylistQueryBuilder = forwardRef(({ query, onChange }: any, ref) =
|
||||||
return {
|
return {
|
||||||
...rule,
|
...rule,
|
||||||
field: value,
|
field: value,
|
||||||
// operator: defaultOperator || '',
|
|
||||||
operator: '',
|
operator: '',
|
||||||
value: '',
|
value: '',
|
||||||
};
|
};
|
||||||
|
@ -235,9 +208,7 @@ export const PlaylistQueryBuilder = forwardRef(({ query, onChange }: any, ref) =
|
||||||
clone,
|
clone,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('updatedFilters', updatedFilters);
|
setFilterHandler(updatedFilters);
|
||||||
|
|
||||||
// setFilterHandler(updatedFilters);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeType = (args: any) => {
|
const handleChangeType = (args: any) => {
|
||||||
|
@ -280,7 +251,7 @@ export const PlaylistQueryBuilder = forwardRef(({ query, onChange }: any, ref) =
|
||||||
const updatedFilters = setWith(
|
const updatedFilters = setWith(
|
||||||
filtersCopy,
|
filtersCopy,
|
||||||
path,
|
path,
|
||||||
get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
|
get(filtersCopy, path).map((rule: QueryBuilderRule) => {
|
||||||
if (rule.uniqueId !== uniqueId) return rule;
|
if (rule.uniqueId !== uniqueId) return rule;
|
||||||
return {
|
return {
|
||||||
...rule,
|
...rule,
|
||||||
|
@ -298,10 +269,11 @@ export const PlaylistQueryBuilder = forwardRef(({ query, onChange }: any, ref) =
|
||||||
const filtersCopy = clone(filters);
|
const filtersCopy = clone(filters);
|
||||||
|
|
||||||
const path = getRulePath(level, groupIndex);
|
const path = getRulePath(level, groupIndex);
|
||||||
|
console.log('path', path);
|
||||||
const updatedFilters = setWith(
|
const updatedFilters = setWith(
|
||||||
filtersCopy,
|
filtersCopy,
|
||||||
path,
|
path,
|
||||||
get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
|
get(filtersCopy, path).map((rule: QueryBuilderRule) => {
|
||||||
if (rule.uniqueId !== uniqueId) return rule;
|
if (rule.uniqueId !== uniqueId) return rule;
|
||||||
return {
|
return {
|
||||||
...rule,
|
...rule,
|
||||||
|
@ -315,7 +287,12 @@ export const PlaylistQueryBuilder = forwardRef(({ query, onChange }: any, ref) =
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Flex
|
||||||
|
direction="column"
|
||||||
|
h="100%"
|
||||||
|
justify="space-between"
|
||||||
|
>
|
||||||
|
<ScrollArea h="100%">
|
||||||
<QueryBuilder
|
<QueryBuilder
|
||||||
data={filters}
|
data={filters}
|
||||||
filters={NDSongQueryFields}
|
filters={NDSongQueryFields}
|
||||||
|
@ -331,6 +308,39 @@ export const PlaylistQueryBuilder = forwardRef(({ query, onChange }: any, ref) =
|
||||||
onDeleteRule={handleDeleteRule}
|
onDeleteRule={handleDeleteRule}
|
||||||
onDeleteRuleGroup={handleDeleteRuleGroup}
|
onDeleteRuleGroup={handleDeleteRuleGroup}
|
||||||
/>
|
/>
|
||||||
</>
|
</ScrollArea>
|
||||||
|
<Group
|
||||||
|
align="flex-end"
|
||||||
|
p="1rem 1rem 0"
|
||||||
|
position="apart"
|
||||||
|
>
|
||||||
|
<NumberInput
|
||||||
|
label="Limit to"
|
||||||
|
width={75}
|
||||||
|
/>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
variant="filled"
|
||||||
|
onClick={handleSave}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<DropdownMenu position="bottom-end">
|
||||||
|
<DropdownMenu.Target>
|
||||||
|
<Button
|
||||||
|
p="0.5em"
|
||||||
|
variant="default"
|
||||||
|
>
|
||||||
|
<RiMore2Fill size={15} />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenu.Target>
|
||||||
|
<DropdownMenu.Dropdown>
|
||||||
|
<DropdownMenu.Item onClick={handleSaveAs}>Save as</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Dropdown>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
|
@ -1,21 +1,116 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { useParams } from 'react-router';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { VirtualGridContainer } from '/@/renderer/components';
|
import { Stack } from '@mantine/core';
|
||||||
|
import { closeAllModals, openModal } from '@mantine/modals';
|
||||||
|
import { generatePath, useNavigate, useParams } from 'react-router';
|
||||||
import { PlaylistDetailSongListContent } from '../components/playlist-detail-song-list-content';
|
import { PlaylistDetailSongListContent } from '../components/playlist-detail-song-list-content';
|
||||||
import { PlaylistDetailSongListHeader } from '../components/playlist-detail-song-list-header';
|
import { PlaylistDetailSongListHeader } from '../components/playlist-detail-song-list-header';
|
||||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
|
import { PlaylistQueryBuilder } from '/@/renderer/features/playlists/components/playlist-query-builder';
|
||||||
|
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
||||||
|
import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation';
|
||||||
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
|
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
|
||||||
|
import { Paper, toast } from '/@/renderer/components';
|
||||||
|
import { SaveAsPlaylistForm } from '/@/renderer/features/playlists/components/save-as-playlist-form';
|
||||||
|
|
||||||
const PlaylistDetailSongListRoute = () => {
|
const PlaylistDetailSongListRoute = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const tableRef = useRef<AgGridReactType | null>(null);
|
const tableRef = useRef<AgGridReactType | null>(null);
|
||||||
const { playlistId } = useParams() as { playlistId: string };
|
const { playlistId } = useParams() as { playlistId: string };
|
||||||
|
|
||||||
|
const detailQuery = usePlaylistDetail({ id: playlistId });
|
||||||
|
const createPlaylistMutation = useCreatePlaylist();
|
||||||
|
const deletePlaylistMutation = useDeletePlaylist();
|
||||||
|
|
||||||
|
const handleSave = (filter: Record<string, any>) => {
|
||||||
|
const rules = {
|
||||||
|
...filter,
|
||||||
|
order: 'desc',
|
||||||
|
sort: 'year',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!detailQuery?.data) return;
|
||||||
|
|
||||||
|
createPlaylistMutation.mutate(
|
||||||
|
{
|
||||||
|
body: {
|
||||||
|
comment: detailQuery?.data?.description || '',
|
||||||
|
name: detailQuery?.data?.name,
|
||||||
|
ndParams: {
|
||||||
|
owner: detailQuery?.data?.owner || '',
|
||||||
|
ownerId: detailQuery?.data?.ownerId || '',
|
||||||
|
public: detailQuery?.data?.public || false,
|
||||||
|
rules,
|
||||||
|
sync: detailQuery?.data?.sync || false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: (data) => {
|
||||||
|
toast.success({ message: 'Smart playlist saved' });
|
||||||
|
navigate(generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: data?.id || '' }), {
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
deletePlaylistMutation.mutate({ query: { id: playlistId } });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveAs = (filter: Record<string, any>) => {
|
||||||
|
openModal({
|
||||||
|
children: (
|
||||||
|
<SaveAsPlaylistForm
|
||||||
|
body={{
|
||||||
|
comment: detailQuery?.data?.description || '',
|
||||||
|
name: detailQuery?.data?.name,
|
||||||
|
ndParams: {
|
||||||
|
owner: detailQuery?.data?.owner || '',
|
||||||
|
ownerId: detailQuery?.data?.ownerId || '',
|
||||||
|
public: detailQuery?.data?.public || false,
|
||||||
|
rules: {
|
||||||
|
...filter,
|
||||||
|
order: 'desc',
|
||||||
|
sort: 'year',
|
||||||
|
},
|
||||||
|
sync: detailQuery?.data?.sync || false,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onCancel={closeAllModals}
|
||||||
|
onSuccess={(data) =>
|
||||||
|
navigate(generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: data?.id || '' }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
title: 'Save as',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatedPage key={`playlist-detail-songList-${playlistId}`}>
|
<AnimatedPage key={`playlist-detail-songList-${playlistId}`}>
|
||||||
<VirtualGridContainer>
|
<Stack
|
||||||
|
h="100%"
|
||||||
|
spacing={0}
|
||||||
|
>
|
||||||
<PlaylistDetailSongListHeader tableRef={tableRef} />
|
<PlaylistDetailSongListHeader tableRef={tableRef} />
|
||||||
|
<Paper
|
||||||
|
sx={{
|
||||||
|
maxHeight: '35vh',
|
||||||
|
padding: '1rem',
|
||||||
|
}}
|
||||||
|
w="100%"
|
||||||
|
>
|
||||||
|
{!detailQuery?.isLoading && (
|
||||||
|
<PlaylistQueryBuilder
|
||||||
|
query={detailQuery?.data?.rules || { all: [] }}
|
||||||
|
onSave={handleSave}
|
||||||
|
onSaveAs={handleSaveAs}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
<PlaylistDetailSongListContent tableRef={tableRef} />
|
<PlaylistDetailSongListContent tableRef={tableRef} />
|
||||||
</VirtualGridContainer>
|
</Stack>
|
||||||
</AnimatedPage>
|
</AnimatedPage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
109
src/renderer/features/playlists/utils.ts
Normal file
109
src/renderer/features/playlists/utils.ts
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
|
import { QueryBuilderGroup } from '/@/renderer/types';
|
||||||
|
|
||||||
|
export const parseQueryBuilderChildren = (groups: QueryBuilderGroup[], data: any[]) => {
|
||||||
|
if (groups.length === 0) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterGroups: any[] = [];
|
||||||
|
|
||||||
|
for (const group of groups) {
|
||||||
|
const rootType = group.type;
|
||||||
|
const query: any = {
|
||||||
|
[rootType]: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const rule of group.rules) {
|
||||||
|
if (rule.field && rule.operator) {
|
||||||
|
const [table, field] = rule.field.split('.');
|
||||||
|
const operator = rule.operator;
|
||||||
|
const value = field !== 'releaseDate' ? rule.value : new Date(rule.value);
|
||||||
|
|
||||||
|
switch (table) {
|
||||||
|
default:
|
||||||
|
query[rootType].push({
|
||||||
|
[operator]: {
|
||||||
|
[table]: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group.group.length > 0) {
|
||||||
|
const b = parseQueryBuilderChildren(group.group, data);
|
||||||
|
b.forEach((c) => query[rootType].push(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
data.push(query);
|
||||||
|
filterGroups.push(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filterGroups;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert QueryBuilderGroup to default query
|
||||||
|
export const convertQueryGroupToNDQuery = (filter: QueryBuilderGroup) => {
|
||||||
|
const rootQueryType = filter.type;
|
||||||
|
const rootQuery = {
|
||||||
|
[rootQueryType]: [] as any[],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const rule of filter.rules) {
|
||||||
|
if (rule.field && rule.operator) {
|
||||||
|
const [table] = rule.field.split('.');
|
||||||
|
const operator = rule.operator;
|
||||||
|
const value = rule.value;
|
||||||
|
|
||||||
|
switch (table) {
|
||||||
|
default:
|
||||||
|
rootQuery[rootQueryType].push({
|
||||||
|
[operator]: {
|
||||||
|
[table]: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const groups = parseQueryBuilderChildren(filter.group, []);
|
||||||
|
for (const group of groups) {
|
||||||
|
rootQuery[rootQueryType].push(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootQuery;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert default query to QueryBuilderGroup
|
||||||
|
export const convertNDQueryToQueryGroup = (query: Record<string, any>) => {
|
||||||
|
const rootType = Object.keys(query)[0];
|
||||||
|
const rootGroup: QueryBuilderGroup = {
|
||||||
|
group: [],
|
||||||
|
rules: [],
|
||||||
|
type: rootType as 'any' | 'all',
|
||||||
|
uniqueId: nanoid(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const rule of query[rootType]) {
|
||||||
|
if (rule.any || rule.all) {
|
||||||
|
const group = convertNDQueryToQueryGroup(rule);
|
||||||
|
rootGroup.group.push(group);
|
||||||
|
} else {
|
||||||
|
const operator = Object.keys(rule)[0];
|
||||||
|
const field = Object.keys(rule[operator])[0];
|
||||||
|
const value = rule[operator][field];
|
||||||
|
|
||||||
|
rootGroup.rules.push({
|
||||||
|
field,
|
||||||
|
operator,
|
||||||
|
uniqueId: nanoid(),
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootGroup;
|
||||||
|
};
|
Reference in a new issue