mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
Progress on advanced filters
This commit is contained in:
@@ -24,10 +24,15 @@ const getList = async (
|
|||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
const { serverId } = req.params;
|
const { serverId } = req.params;
|
||||||
const { take, skip, serverUrlId } = req.query;
|
const { take, skip, serverUrlId, advancedFilters } = req.query;
|
||||||
|
|
||||||
|
const decodedAdvancedFilters = advancedFilters && decodeURI(advancedFilters);
|
||||||
|
const jsonAdvancedFilters =
|
||||||
|
decodedAdvancedFilters && JSON.parse(decodedAdvancedFilters);
|
||||||
|
|
||||||
const albums = await service.albums.findMany({
|
const albums = await service.albums.findMany({
|
||||||
...req.query,
|
...req.query,
|
||||||
|
advancedFilters: jsonAdvancedFilters,
|
||||||
serverId,
|
serverId,
|
||||||
skip: Number(skip),
|
skip: Number(skip),
|
||||||
take: Number(take),
|
take: Number(take),
|
||||||
@@ -66,6 +71,7 @@ const getDetailSongList = async (
|
|||||||
|
|
||||||
const albums = await service.albums.findMany({
|
const albums = await service.albums.findMany({
|
||||||
...req.query,
|
...req.query,
|
||||||
|
advancedFilters: undefined,
|
||||||
serverId,
|
serverId,
|
||||||
skip: Number(skip),
|
skip: Number(skip),
|
||||||
take: Number(take),
|
take: Number(take),
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const sort = (sortBy: AlbumSort, orderBy: SortOrder) => {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case AlbumSort.DATE_RELEASED:
|
case AlbumSort.DATE_RELEASED:
|
||||||
order = { releaseDate: orderBy, year: orderBy };
|
order = { releaseDate: orderBy };
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AlbumSort.DATE_RELEASED_YEAR:
|
case AlbumSort.DATE_RELEASED_YEAR:
|
||||||
@@ -80,7 +80,204 @@ const sort = (sortBy: AlbumSort, orderBy: SortOrder) => {
|
|||||||
return order;
|
return order;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum FilterGroupType {
|
||||||
|
AND = 'AND',
|
||||||
|
OR = 'OR',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AdvancedFilterRule = {
|
||||||
|
field: string | null;
|
||||||
|
operator: string | null;
|
||||||
|
uniqueId: string;
|
||||||
|
value: string | number | Date | undefined | null | any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AdvancedFilterGroup = {
|
||||||
|
group: AdvancedFilterGroup[];
|
||||||
|
rules: AdvancedFilterRule[];
|
||||||
|
type: FilterGroupType;
|
||||||
|
uniqueId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const advancedFilterGroup = (
|
||||||
|
groups: AdvancedFilterGroup[],
|
||||||
|
user: AuthUser,
|
||||||
|
data: any[]
|
||||||
|
) => {
|
||||||
|
if (groups.length === 0) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterGroups: any[] = [];
|
||||||
|
|
||||||
|
for (const group of groups) {
|
||||||
|
const rootType = group.type.toUpperCase();
|
||||||
|
const query: any = {
|
||||||
|
[rootType]: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const rule of group.rules) {
|
||||||
|
if (rule.field && rule.operator) {
|
||||||
|
const [table, field, relationField] = rule.field.split('.');
|
||||||
|
|
||||||
|
if (field === 'ratings') {
|
||||||
|
if (table === 'albums') {
|
||||||
|
query[rootType].push({
|
||||||
|
[field]: {
|
||||||
|
some: {
|
||||||
|
[relationField]: {
|
||||||
|
[rule.operator]: rule.value,
|
||||||
|
},
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
query[rootType].push({
|
||||||
|
[table]: {
|
||||||
|
some: {
|
||||||
|
[field]: {
|
||||||
|
some: {
|
||||||
|
[relationField]: {
|
||||||
|
[rule.operator]: rule.value,
|
||||||
|
},
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (table === 'albums') {
|
||||||
|
const obj = {
|
||||||
|
[field]: {
|
||||||
|
[rule.operator]: rule.value,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
query[rootType].push(obj);
|
||||||
|
} else {
|
||||||
|
const obj = {
|
||||||
|
[table]: {
|
||||||
|
some: {
|
||||||
|
[field]: {
|
||||||
|
[rule.operator]: rule.value,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
query[rootType].push(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group.group.length > 0) {
|
||||||
|
const b = advancedFilterGroup(group.group, user, data);
|
||||||
|
b.forEach((c) => query[rootType].push(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
data.push(query);
|
||||||
|
filterGroups.push(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filterGroups;
|
||||||
|
};
|
||||||
|
|
||||||
|
const advancedFilter = (filter: AdvancedFilterGroup, user: AuthUser) => {
|
||||||
|
const rootQueryType = filter.type.toUpperCase();
|
||||||
|
const rootQuery = {
|
||||||
|
[rootQueryType]: [] as any[],
|
||||||
|
};
|
||||||
|
|
||||||
|
const operatorMap = {
|
||||||
|
'!=': 'not',
|
||||||
|
'!~': 'contains',
|
||||||
|
$: 'endsWith',
|
||||||
|
'<': 'lt',
|
||||||
|
'<=': 'lte',
|
||||||
|
'=': 'equals',
|
||||||
|
'>': 'gt',
|
||||||
|
'>=': 'gte',
|
||||||
|
'^': 'startsWith',
|
||||||
|
'~': 'contains',
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const rule of filter.rules) {
|
||||||
|
if (rule.field && rule.operator) {
|
||||||
|
let [table, field, relationField] = rule.field.split('.');
|
||||||
|
const condition = rule.operator === '!~' ? 'none' : 'some';
|
||||||
|
const op = operatorMap[rule.operator as keyof typeof operatorMap];
|
||||||
|
|
||||||
|
switch (table) {
|
||||||
|
case 'albums':
|
||||||
|
if (field === 'ratings') {
|
||||||
|
rootQuery[rootQueryType].push({
|
||||||
|
[field]: {
|
||||||
|
[condition]: {
|
||||||
|
[relationField]: {
|
||||||
|
[op]: rule.value,
|
||||||
|
},
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rootQuery[rootQueryType].push({
|
||||||
|
[field]: {
|
||||||
|
mode: 'insensitive',
|
||||||
|
[op]: rule.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (field === 'ratings') {
|
||||||
|
rootQuery[rootQueryType].push({
|
||||||
|
[table]: {
|
||||||
|
some: {
|
||||||
|
[field]: {
|
||||||
|
some: {
|
||||||
|
[relationField]: {
|
||||||
|
[op]: rule.value,
|
||||||
|
},
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
rootQuery[rootQueryType].push({
|
||||||
|
[table]: {
|
||||||
|
[condition]: {
|
||||||
|
[field]: {
|
||||||
|
mode: 'insensitive',
|
||||||
|
[op]: rule.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const groups = advancedFilterGroup(filter.group, user, []);
|
||||||
|
for (const group of groups) {
|
||||||
|
rootQuery[rootQueryType].push(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootQuery;
|
||||||
|
};
|
||||||
|
|
||||||
export const albumHelpers = {
|
export const albumHelpers = {
|
||||||
|
advancedFilter,
|
||||||
include,
|
include,
|
||||||
sort,
|
sort,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import { Prisma } from '@prisma/client';
|
||||||
import { AuthUser } from '@/middleware';
|
import { AuthUser } from '@/middleware';
|
||||||
import { OffsetPagination, SortOrder } from '@/types/types';
|
import { OffsetPagination, SortOrder } from '@/types/types';
|
||||||
import { ApiError } from '@/utils';
|
import { ApiError } from '@/utils';
|
||||||
import { AlbumSort } from '@helpers/albums.helpers';
|
import { AdvancedFilterGroup, AlbumSort } from '@helpers/albums.helpers';
|
||||||
import { helpers } from '@helpers/index';
|
import { helpers } from '@helpers/index';
|
||||||
import { prisma } from '@lib/prisma';
|
import { prisma } from '@lib/prisma';
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ const findById = async (user: AuthUser, options: { id: string }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type AlbumFindManyOptions = {
|
export type AlbumFindManyOptions = {
|
||||||
|
advancedFilters?: AdvancedFilterGroup;
|
||||||
orderBy: SortOrder;
|
orderBy: SortOrder;
|
||||||
serverFolderId?: string[];
|
serverFolderId?: string[];
|
||||||
serverId: string;
|
serverId: string;
|
||||||
@@ -32,8 +34,16 @@ export type AlbumFindManyOptions = {
|
|||||||
} & OffsetPagination;
|
} & OffsetPagination;
|
||||||
|
|
||||||
const findMany = async (options: AlbumFindManyOptions) => {
|
const findMany = async (options: AlbumFindManyOptions) => {
|
||||||
const { take, serverFolderId, skip, sortBy, orderBy, user, serverId } =
|
const {
|
||||||
options;
|
take,
|
||||||
|
serverFolderId,
|
||||||
|
skip,
|
||||||
|
sortBy,
|
||||||
|
orderBy,
|
||||||
|
user,
|
||||||
|
serverId,
|
||||||
|
advancedFilters,
|
||||||
|
} = options;
|
||||||
|
|
||||||
const serverFolderIds =
|
const serverFolderIds =
|
||||||
serverFolderId ||
|
serverFolderId ||
|
||||||
@@ -42,6 +52,9 @@ const findMany = async (options: AlbumFindManyOptions) => {
|
|||||||
let totalEntries = 0;
|
let totalEntries = 0;
|
||||||
let albums;
|
let albums;
|
||||||
|
|
||||||
|
const advancedFiltersQuery =
|
||||||
|
advancedFilters && helpers.albums.advancedFilter(advancedFilters, user);
|
||||||
|
|
||||||
if (sortBy === AlbumSort.RATING) {
|
if (sortBy === AlbumSort.RATING) {
|
||||||
const [count, result] = await prisma.$transaction([
|
const [count, result] = await prisma.$transaction([
|
||||||
prisma.albumRating.count({
|
prisma.albumRating.count({
|
||||||
@@ -92,14 +105,24 @@ const findMany = async (options: AlbumFindManyOptions) => {
|
|||||||
} else {
|
} else {
|
||||||
[totalEntries, albums] = await prisma.$transaction([
|
[totalEntries, albums] = await prisma.$transaction([
|
||||||
prisma.album.count({
|
prisma.album.count({
|
||||||
where: { OR: helpers.shared.serverFolderFilter(serverFolderIds) },
|
where: {
|
||||||
|
AND: [
|
||||||
|
helpers.shared.serverFolderFilter(serverFolderIds),
|
||||||
|
advancedFiltersQuery as Prisma.AlbumWhereInput,
|
||||||
|
],
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
prisma.album.findMany({
|
prisma.album.findMany({
|
||||||
include: helpers.albums.include(user, { songs: false }),
|
include: helpers.albums.include(user, { songs: false }),
|
||||||
orderBy: [helpers.albums.sort(sortBy, orderBy)],
|
orderBy: [helpers.albums.sort(sortBy, orderBy)],
|
||||||
skip,
|
skip,
|
||||||
take,
|
take,
|
||||||
where: { OR: helpers.shared.serverFolderFilter(serverFolderIds) },
|
where: {
|
||||||
|
AND: [
|
||||||
|
helpers.shared.serverFolderFilter(serverFolderIds),
|
||||||
|
advancedFiltersQuery as Prisma.AlbumWhereInput,
|
||||||
|
],
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const list = {
|
|||||||
...serverFolderIdValidation,
|
...serverFolderIdValidation,
|
||||||
...orderByValidation,
|
...orderByValidation,
|
||||||
...serverUrlIdValidation,
|
...serverUrlIdValidation,
|
||||||
|
advancedFilters: z.optional(z.string()),
|
||||||
sortBy: z.nativeEnum(AlbumSort),
|
sortBy: z.nativeEnum(AlbumSort),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export enum AlbumSort {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type AlbumListParams = PaginationParams & {
|
export type AlbumListParams = PaginationParams & {
|
||||||
|
advancedFilters?: string;
|
||||||
orderBy: SortOrder;
|
orderBy: SortOrder;
|
||||||
serverFolderId?: string[];
|
serverFolderId?: string[];
|
||||||
serverUrlId?: string;
|
serverUrlId?: string;
|
||||||
|
|||||||
@@ -1,29 +1,31 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { Box, Stack, Group } from '@mantine/core';
|
import { Box, Stack, Group } from '@mantine/core';
|
||||||
import _ from 'lodash';
|
import dayjs from 'dayjs';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import set from 'lodash/set';
|
||||||
import { nanoid } from 'nanoid/non-secure';
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
import { RiAddLine, RiMore2Line, RiSubtractLine } from 'react-icons/ri';
|
import { RiAddLine, RiMore2Line, RiSubtractLine } from 'react-icons/ri';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
DatePicker,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
NumberInput,
|
NumberInput,
|
||||||
Select,
|
Select,
|
||||||
TextInput,
|
TextInput,
|
||||||
} from '@/renderer/components';
|
} from '@/renderer/components';
|
||||||
|
|
||||||
enum FilterGroupType {
|
export enum FilterGroupType {
|
||||||
AND = 'and',
|
AND = 'AND',
|
||||||
OR = 'or',
|
OR = 'OR',
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdvancedFilterRule = {
|
export type AdvancedFilterRule = {
|
||||||
field: string;
|
field: string | null;
|
||||||
operator: string;
|
operator: string | null;
|
||||||
uniqueId: string;
|
uniqueId: string;
|
||||||
value: string;
|
value: string | number | Date | undefined | null | any;
|
||||||
};
|
};
|
||||||
|
|
||||||
type AdvancedFilterGroup = {
|
export type AdvancedFilterGroup = {
|
||||||
group: AdvancedFilterGroup[];
|
group: AdvancedFilterGroup[];
|
||||||
rules: AdvancedFilterRule[];
|
rules: AdvancedFilterRule[];
|
||||||
type: FilterGroupType;
|
type: FilterGroupType;
|
||||||
@@ -33,6 +35,8 @@ type AdvancedFilterGroup = {
|
|||||||
const DATE_FILTER_OPTIONS_DATA = [
|
const DATE_FILTER_OPTIONS_DATA = [
|
||||||
{ label: 'is before', value: '<' },
|
{ label: 'is before', value: '<' },
|
||||||
{ label: 'is after', value: '>' },
|
{ label: 'is after', value: '>' },
|
||||||
|
{ label: 'is before or equal to', value: '<=' },
|
||||||
|
{ label: 'is after or equal to', value: '>=' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const STRING_FILTER_OPTIONS_DATA = [
|
const STRING_FILTER_OPTIONS_DATA = [
|
||||||
@@ -49,164 +53,413 @@ const NUMBER_FILTER_OPTIONS_DATA = [
|
|||||||
{ label: 'is not', value: '!=' },
|
{ label: 'is not', value: '!=' },
|
||||||
{ label: 'is greater than', value: '>' },
|
{ label: 'is greater than', value: '>' },
|
||||||
{ label: 'is less than', value: '<' },
|
{ label: 'is less than', value: '<' },
|
||||||
|
{ label: 'is greater than or equal to', value: '>=' },
|
||||||
|
{ label: 'is less than or equal to', value: '<=' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const ID_FILTER_OPTIONS_DATA = [
|
||||||
|
{ label: 'is', value: 'equals' },
|
||||||
|
{ label: 'is not', value: 'not' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const FILTER_GROUP_OPTIONS_DATA = [
|
const FILTER_GROUP_OPTIONS_DATA = [
|
||||||
{
|
{
|
||||||
label: 'Match ALL',
|
label: 'Match all',
|
||||||
value: FilterGroupType.AND,
|
value: FilterGroupType.AND,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Match ANY',
|
label: 'Match any',
|
||||||
value: FilterGroupType.OR,
|
value: FilterGroupType.OR,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const FILTER_OPTIONS_DATA = [
|
const FILTER_OPTIONS_DATA = [
|
||||||
{
|
{
|
||||||
label: 'Artist Title',
|
label: 'Artist Name',
|
||||||
value: 'artist.title',
|
value: 'artists.name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Artist Rating',
|
label: 'Artist Rating',
|
||||||
value: 'artist.rating',
|
value: 'artists.ratings.value',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Artist Genre',
|
label: 'Artist Genre',
|
||||||
value: 'artist.genre',
|
value: 'artists.genre',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Album Title',
|
label: 'Album Artist Name',
|
||||||
value: 'album.title',
|
value: 'albumArtists.name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Album Artist Rating',
|
||||||
|
value: 'albumArtists.ratings.value',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Album Artist Genre',
|
||||||
|
value: 'albumArtists.genre',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Album Name',
|
||||||
|
value: 'albums.name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Album Genre',
|
label: 'Album Genre',
|
||||||
value: 'album.genre',
|
value: 'albums.genre',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Album Rating',
|
label: 'Album Rating',
|
||||||
value: 'album.rating',
|
value: 'albums.ratings.value',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Album Year',
|
label: 'Album Year',
|
||||||
value: 'album.year',
|
value: 'albums.year',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Album Release Date',
|
label: 'Album Release Date',
|
||||||
value: 'album.releaseDate',
|
value: 'albums.releaseDate',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Album Plays',
|
label: 'Album Plays',
|
||||||
value: 'album.playCount',
|
value: 'albums.playCount',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Album Date Added',
|
label: 'Album Date Added',
|
||||||
value: 'album.dateAdded',
|
value: 'albums.dateAdded',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Track Title',
|
label: 'Track Name',
|
||||||
value: 'track.title',
|
value: 'songs.name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Track Plays',
|
label: 'Track Plays',
|
||||||
value: 'track.plays',
|
value: 'songs.playCount',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Track Rating',
|
label: 'Track Rating',
|
||||||
value: 'track.rating',
|
value: 'songs.ratings.value',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const OPTIONS_MAP = {
|
const OPTIONS_MAP = {
|
||||||
'album.dateAdded': {
|
'albumArtists.genre': {
|
||||||
|
type: 'id',
|
||||||
|
},
|
||||||
|
'albumArtists.name': {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
'albumArtists.ratings.value': {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
'albums.dateAdded': {
|
||||||
type: 'date',
|
type: 'date',
|
||||||
},
|
},
|
||||||
'album.favorite': {
|
'albums.favorite': {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
},
|
},
|
||||||
'album.genre': {
|
'albums.genre': {
|
||||||
|
type: 'id',
|
||||||
|
},
|
||||||
|
'albums.name': {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
'album.playCount': {
|
'albums.playCount': {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
'album.rating': {
|
'albums.ratings.value': {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
'album.releaseDate': {
|
'albums.releaseDate': {
|
||||||
type: 'date',
|
type: 'date',
|
||||||
},
|
},
|
||||||
'album.title': {
|
'albums.year': {
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
'album.year': {
|
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
'artist.genre': {
|
'artists.genre': {
|
||||||
|
type: 'id',
|
||||||
|
},
|
||||||
|
'artists.name': {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
'artist.rating': {
|
'artists.ratings.value': {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
'artist.title': {
|
'songs.name': {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
'track.plays': {
|
'songs.playCount': {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
'track.rating': {
|
'songs.ratings.value': {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
'track.title': {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const FilterOption = ({ level, onDeleteRule, uniqueId, groupIndex }: any) => {
|
interface FilterOptionProps {
|
||||||
const [selectedOption, setSelectedOption] = useState<
|
data: AdvancedFilterRule;
|
||||||
string | null | typeof OPTIONS_MAP
|
groupIndex: number[];
|
||||||
>(FILTER_OPTIONS_DATA[0].value);
|
level: number;
|
||||||
|
onChangeField: (args: any) => void;
|
||||||
|
onChangeOperator: (args: any) => void;
|
||||||
|
onChangeValue: (args: any) => void;
|
||||||
|
onDeleteRule: (args: DeleteArgs) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FilterOption = ({
|
||||||
|
data,
|
||||||
|
level,
|
||||||
|
onDeleteRule,
|
||||||
|
groupIndex,
|
||||||
|
onChangeField,
|
||||||
|
onChangeOperator,
|
||||||
|
onChangeValue,
|
||||||
|
}: FilterOptionProps) => {
|
||||||
|
const { field, operator, uniqueId, value } = data;
|
||||||
|
|
||||||
const handleDeleteRule = () => {
|
const handleDeleteRule = () => {
|
||||||
onDeleteRule({ groupIndex, level, uniqueId });
|
onDeleteRule({ groupIndex, level, uniqueId });
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterMap = {
|
const handleChangeField = (e: any) => {
|
||||||
date: <Select data={DATE_FILTER_OPTIONS_DATA} size="xs" width={150} />,
|
onChangeField({ groupIndex, level, uniqueId, value: e });
|
||||||
number: <Select data={NUMBER_FILTER_OPTIONS_DATA} size="xs" width={150} />,
|
|
||||||
string: <Select data={STRING_FILTER_OPTIONS_DATA} size="xs" width={150} />,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterInputMap = {
|
const handleChangeOperator = (e: any) => {
|
||||||
'album.dateAdded': <TextInput size="xs" width={150} />,
|
onChangeOperator({ groupIndex, level, uniqueId, value: e });
|
||||||
'album.genre': <Select searchable data={['hello']} size="xs" width={150} />,
|
};
|
||||||
'album.playCount': <NumberInput size="xs" width={150} />,
|
|
||||||
'album.rating': <NumberInput size="xs" width={150} />,
|
const handleChangeValue = (e: any) => {
|
||||||
'album.releaseDate': <TextInput size="xs" width={150} />,
|
const isDirectValue =
|
||||||
'album.title': <TextInput size="xs" width={150} />,
|
typeof e === 'string' ||
|
||||||
'album.year': <NumberInput size="xs" width={150} />,
|
typeof e === 'number' ||
|
||||||
'artist.genre': <Select searchable data={[]} size="xs" width={150} />,
|
typeof e === 'undefined' ||
|
||||||
'artist.rating': <NumberInput size="xs" width={150} />,
|
typeof e === null;
|
||||||
'artist.title': <TextInput size="xs" width={150} />,
|
|
||||||
'track.plays': <NumberInput size="xs" width={150} />,
|
if (isDirectValue) {
|
||||||
'track.rating': <NumberInput size="xs" width={150} />,
|
return onChangeValue({
|
||||||
'track.title': <TextInput size="xs" width={150} />,
|
groupIndex,
|
||||||
|
level,
|
||||||
|
uniqueId,
|
||||||
|
value: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return onChangeValue({
|
||||||
|
groupIndex,
|
||||||
|
level,
|
||||||
|
uniqueId,
|
||||||
|
value: e.currentTarget.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterOperatorMap = {
|
||||||
|
date: (
|
||||||
|
<Select
|
||||||
|
data={DATE_FILTER_OPTIONS_DATA}
|
||||||
|
size="xs"
|
||||||
|
value={operator}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeOperator}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
id: (
|
||||||
|
<Select
|
||||||
|
data={ID_FILTER_OPTIONS_DATA}
|
||||||
|
size="xs"
|
||||||
|
value={operator}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeOperator}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
number: (
|
||||||
|
<Select
|
||||||
|
data={NUMBER_FILTER_OPTIONS_DATA}
|
||||||
|
size="xs"
|
||||||
|
value={operator}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeOperator}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
string: (
|
||||||
|
<Select
|
||||||
|
data={STRING_FILTER_OPTIONS_DATA}
|
||||||
|
size="xs"
|
||||||
|
value={operator}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeOperator}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterInputValueMap = {
|
||||||
|
'albumArtists.genre': (
|
||||||
|
<Select
|
||||||
|
searchable
|
||||||
|
data={[]}
|
||||||
|
size="xs"
|
||||||
|
value={value}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
'albumArtists.name': (
|
||||||
|
<TextInput
|
||||||
|
size="xs"
|
||||||
|
value={value}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
'albumArtists.ratings.value': (
|
||||||
|
<NumberInput
|
||||||
|
max={5}
|
||||||
|
min={0}
|
||||||
|
size="xs"
|
||||||
|
value={value}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
'albums.dateAdded': (
|
||||||
|
<DatePicker
|
||||||
|
initialLevel="year"
|
||||||
|
maxDate={dayjs(new Date()).year(3000).toDate()}
|
||||||
|
minDate={dayjs(new Date()).year(1950).toDate()}
|
||||||
|
size="xs"
|
||||||
|
value={value}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
'albums.genre': (
|
||||||
|
<Select
|
||||||
|
searchable
|
||||||
|
data={[]}
|
||||||
|
size="xs"
|
||||||
|
value={value}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
'albums.name': (
|
||||||
|
<TextInput
|
||||||
|
size="xs"
|
||||||
|
value={value}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
'albums.playCount': (
|
||||||
|
<NumberInput
|
||||||
|
min={0}
|
||||||
|
size="xs"
|
||||||
|
value={value}
|
||||||
|
width={150}
|
||||||
|
onChange={(e) => handleChangeValue(e)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
'albums.ratings.value': (
|
||||||
|
<NumberInput
|
||||||
|
max={5}
|
||||||
|
min={0}
|
||||||
|
size="xs"
|
||||||
|
value={value}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
'albums.releaseDate': (
|
||||||
|
<DatePicker
|
||||||
|
initialLevel="year"
|
||||||
|
maxDate={dayjs(new Date()).year(3000).toDate()}
|
||||||
|
minDate={dayjs(new Date()).year(1950).toDate()}
|
||||||
|
size="xs"
|
||||||
|
value={value}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
'albums.year': (
|
||||||
|
<NumberInput
|
||||||
|
min={0}
|
||||||
|
size="xs"
|
||||||
|
value={value}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
'artists.genre': (
|
||||||
|
<Select
|
||||||
|
searchable
|
||||||
|
data={[]}
|
||||||
|
size="xs"
|
||||||
|
value={value}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
'artists.name': (
|
||||||
|
<TextInput
|
||||||
|
size="xs"
|
||||||
|
value={value}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
'artists.ratings.value': (
|
||||||
|
<NumberInput
|
||||||
|
max={5}
|
||||||
|
min={0}
|
||||||
|
size="xs"
|
||||||
|
value={value}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
'songs.name': (
|
||||||
|
<TextInput size="xs" width={150} onChange={handleChangeValue} />
|
||||||
|
),
|
||||||
|
'songs.playCount': (
|
||||||
|
<NumberInput
|
||||||
|
min={0}
|
||||||
|
size="xs"
|
||||||
|
value={value}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
'songs.ratings.value': (
|
||||||
|
<NumberInput
|
||||||
|
max={5}
|
||||||
|
min={0}
|
||||||
|
size="xs"
|
||||||
|
value={value}
|
||||||
|
width={150}
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group ml={level === 0 ? '10px' : `${level * 10}px`}>
|
<Group ml={`${(level + 1) * 10}px`}>
|
||||||
<Select
|
<Select
|
||||||
data={FILTER_OPTIONS_DATA}
|
data={FILTER_OPTIONS_DATA}
|
||||||
size="xs"
|
size="xs"
|
||||||
onChange={setSelectedOption}
|
value={field}
|
||||||
|
onChange={handleChangeField}
|
||||||
/>
|
/>
|
||||||
{selectedOption &&
|
{field ? (
|
||||||
filterMap[
|
filterOperatorMap[
|
||||||
OPTIONS_MAP[selectedOption as keyof typeof OPTIONS_MAP]
|
OPTIONS_MAP[field as keyof typeof OPTIONS_MAP]
|
||||||
.type as keyof typeof filterMap
|
.type as keyof typeof filterOperatorMap
|
||||||
]}
|
]
|
||||||
{selectedOption &&
|
) : (
|
||||||
filterInputMap[selectedOption as keyof typeof filterInputMap]}
|
<TextInput disabled size="xs" width={150} />
|
||||||
|
)}
|
||||||
|
{field ? (
|
||||||
|
filterInputValueMap[field as keyof typeof filterInputValueMap]
|
||||||
|
) : (
|
||||||
|
<TextInput disabled size="xs" width={150} />
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
px={5}
|
px={5}
|
||||||
size="xs"
|
size="xs"
|
||||||
@@ -237,6 +490,10 @@ interface FilterGroupProps {
|
|||||||
level: number;
|
level: number;
|
||||||
onAddRule: (args: AddArgs) => void;
|
onAddRule: (args: AddArgs) => void;
|
||||||
onAddRuleGroup: (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;
|
onDeleteRule: (args: DeleteArgs) => void;
|
||||||
onDeleteRuleGroup: (args: DeleteArgs) => void;
|
onDeleteRuleGroup: (args: DeleteArgs) => void;
|
||||||
uniqueId: string;
|
uniqueId: string;
|
||||||
@@ -249,6 +506,10 @@ const FilterGroup = ({
|
|||||||
onDeleteRuleGroup,
|
onDeleteRuleGroup,
|
||||||
onDeleteRule,
|
onDeleteRule,
|
||||||
onAddRuleGroup,
|
onAddRuleGroup,
|
||||||
|
onChangeType,
|
||||||
|
onChangeField,
|
||||||
|
onChangeOperator,
|
||||||
|
onChangeValue,
|
||||||
groupIndex,
|
groupIndex,
|
||||||
uniqueId,
|
uniqueId,
|
||||||
}: FilterGroupProps) => {
|
}: FilterGroupProps) => {
|
||||||
@@ -264,13 +525,18 @@ const FilterGroup = ({
|
|||||||
onDeleteRuleGroup({ groupIndex, level, uniqueId });
|
onDeleteRuleGroup({ groupIndex, level, uniqueId });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleChangeType = (value: string | null) => {
|
||||||
|
onChangeType({ groupIndex, level, value });
|
||||||
|
};
|
||||||
|
|
||||||
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}
|
||||||
defaultValue={FILTER_GROUP_OPTIONS_DATA[0].value}
|
|
||||||
size="xs"
|
size="xs"
|
||||||
|
value={data.type}
|
||||||
|
onChange={handleChangeType}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
px={5}
|
px={5}
|
||||||
@@ -300,16 +566,16 @@ const FilterGroup = ({
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Group>
|
</Group>
|
||||||
{data.rules.map((rule: AdvancedFilterRule) => (
|
{data.rules.map((rule: AdvancedFilterRule) => (
|
||||||
<>
|
<FilterOption
|
||||||
<FilterOption
|
key={rule.uniqueId}
|
||||||
key={rule.uniqueId}
|
data={rule}
|
||||||
groupIndex={groupIndex || []}
|
groupIndex={groupIndex || []}
|
||||||
level={level}
|
level={level}
|
||||||
uniqueId={rule.uniqueId}
|
onChangeField={onChangeField}
|
||||||
onAddRule={handleAddRule}
|
onChangeOperator={onChangeOperator}
|
||||||
onDeleteRule={onDeleteRule}
|
onChangeValue={onChangeValue}
|
||||||
/>
|
onDeleteRule={onDeleteRule}
|
||||||
</>
|
/>
|
||||||
))}
|
))}
|
||||||
{data.group && (
|
{data.group && (
|
||||||
<>
|
<>
|
||||||
@@ -322,6 +588,10 @@ const FilterGroup = ({
|
|||||||
uniqueId={group.uniqueId}
|
uniqueId={group.uniqueId}
|
||||||
onAddRule={onAddRule}
|
onAddRule={onAddRule}
|
||||||
onAddRuleGroup={onAddRuleGroup}
|
onAddRuleGroup={onAddRuleGroup}
|
||||||
|
onChangeField={onChangeField}
|
||||||
|
onChangeOperator={onChangeOperator}
|
||||||
|
onChangeType={onChangeType}
|
||||||
|
onChangeValue={onChangeValue}
|
||||||
onDeleteRule={onDeleteRule}
|
onDeleteRule={onDeleteRule}
|
||||||
onDeleteRuleGroup={onDeleteRuleGroup}
|
onDeleteRuleGroup={onDeleteRuleGroup}
|
||||||
/>
|
/>
|
||||||
@@ -332,14 +602,7 @@ const FilterGroup = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AdvancedFilters = () => {
|
export const AdvancedFilters = ({ filters, setFilters }: any) => {
|
||||||
const [filters, setFilters] = useState<AdvancedFilterGroup>({
|
|
||||||
group: [],
|
|
||||||
rules: [],
|
|
||||||
type: FilterGroupType.AND,
|
|
||||||
uniqueId: nanoid(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleAddRuleGroup = (args: AddArgs) => {
|
const handleAddRuleGroup = (args: AddArgs) => {
|
||||||
const { level, groupIndex } = args;
|
const { level, groupIndex } = args;
|
||||||
const filtersCopy = { ...filters };
|
const filtersCopy = { ...filters };
|
||||||
@@ -356,12 +619,19 @@ export const AdvancedFilters = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const path = getPath(level);
|
const path = getPath(level);
|
||||||
const updatedFilters = _.set(filtersCopy, path, [
|
const updatedFilters = set(filtersCopy, path, [
|
||||||
..._.get(filtersCopy, path),
|
...get(filtersCopy, path),
|
||||||
{
|
{
|
||||||
group: [],
|
group: [],
|
||||||
rules: [],
|
rules: [
|
||||||
type: 'push',
|
{
|
||||||
|
field: undefined,
|
||||||
|
operator: undefined,
|
||||||
|
uniqueId: nanoid(),
|
||||||
|
value: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: FilterGroupType.AND,
|
||||||
uniqueId: nanoid(),
|
uniqueId: nanoid(),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -390,8 +660,8 @@ export const AdvancedFilters = () => {
|
|||||||
|
|
||||||
const path = getPath(level);
|
const path = getPath(level);
|
||||||
|
|
||||||
const updatedFilters = _.set(filtersCopy, path, [
|
const updatedFilters = set(filtersCopy, path, [
|
||||||
..._.get(filtersCopy, path).filter(
|
...get(filtersCopy, path).filter(
|
||||||
(group: AdvancedFilterGroup) => group.uniqueId !== uniqueId
|
(group: AdvancedFilterGroup) => group.uniqueId !== uniqueId
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
@@ -399,25 +669,30 @@ export const AdvancedFilters = () => {
|
|||||||
setFilters(updatedFilters);
|
setFilters(updatedFilters);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getRulePath = (level: number, groupIndex: number[]) => {
|
||||||
|
if (level === 0) return 'rules';
|
||||||
|
|
||||||
|
const str = [];
|
||||||
|
for (const index of groupIndex) {
|
||||||
|
str.push(`group[${index}]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${str.join('.')}.rules`;
|
||||||
|
};
|
||||||
|
|
||||||
const handleAddRule = (args: AddArgs) => {
|
const handleAddRule = (args: AddArgs) => {
|
||||||
const { level, groupIndex } = args;
|
const { level, groupIndex } = args;
|
||||||
const filtersCopy = { ...filters };
|
const filtersCopy = { ...filters };
|
||||||
|
|
||||||
const getPath = (level: number) => {
|
const path = getRulePath(level, groupIndex);
|
||||||
if (level === 0) return 'rules';
|
const updatedFilters = set(filtersCopy, path, [
|
||||||
|
...get(filtersCopy, path),
|
||||||
const str = [];
|
{
|
||||||
for (const index of groupIndex) {
|
field: null,
|
||||||
str.push(`group[${index}]`);
|
operator: null,
|
||||||
}
|
uniqueId: nanoid(),
|
||||||
|
value: null,
|
||||||
return `${str.join('.')}.rules`;
|
},
|
||||||
};
|
|
||||||
|
|
||||||
const path = getPath(level);
|
|
||||||
const updatedFilters = _.set(filtersCopy, path, [
|
|
||||||
..._.get(filtersCopy, path),
|
|
||||||
{ filter: 'newrule', uniqueId: nanoid() },
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setFilters(updatedFilters);
|
setFilters(updatedFilters);
|
||||||
@@ -427,22 +702,11 @@ export const AdvancedFilters = () => {
|
|||||||
const { uniqueId, level, groupIndex } = args;
|
const { uniqueId, level, groupIndex } = args;
|
||||||
const filtersCopy = { ...filters };
|
const filtersCopy = { ...filters };
|
||||||
|
|
||||||
const getPath = (level: number) => {
|
const path = getRulePath(level, groupIndex);
|
||||||
if (level === 0) return 'rules';
|
const updatedFilters = set(
|
||||||
|
|
||||||
const str = [];
|
|
||||||
for (const index of groupIndex) {
|
|
||||||
str.push(`group[${index}]`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${str.join('.')}.rules`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const path = getPath(level);
|
|
||||||
const updatedFilters = _.set(
|
|
||||||
filtersCopy,
|
filtersCopy,
|
||||||
path,
|
path,
|
||||||
_.get(filtersCopy, path).filter(
|
get(filtersCopy, path).filter(
|
||||||
(rule: AdvancedFilterRule) => rule.uniqueId !== uniqueId
|
(rule: AdvancedFilterRule) => rule.uniqueId !== uniqueId
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -450,6 +714,95 @@ export const AdvancedFilters = () => {
|
|||||||
setFilters(updatedFilters);
|
setFilters(updatedFilters);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleChangeField = (args: any) => {
|
||||||
|
const { uniqueId, level, groupIndex, value } = args;
|
||||||
|
const filtersCopy = { ...filters };
|
||||||
|
|
||||||
|
const path = getRulePath(level, groupIndex);
|
||||||
|
const updatedFilters = set(
|
||||||
|
filtersCopy,
|
||||||
|
path,
|
||||||
|
get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
|
||||||
|
if (rule.uniqueId !== uniqueId) return rule;
|
||||||
|
return {
|
||||||
|
...rule,
|
||||||
|
field: value,
|
||||||
|
operator: null,
|
||||||
|
value: null,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setFilters(updatedFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeType = (args: any) => {
|
||||||
|
const { level, groupIndex, value } = args;
|
||||||
|
|
||||||
|
const filtersCopy = { ...filters };
|
||||||
|
|
||||||
|
if (level === 0) {
|
||||||
|
return setFilters({ ...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 = set(filtersCopy, path, {
|
||||||
|
...get(filtersCopy, path),
|
||||||
|
type: value,
|
||||||
|
});
|
||||||
|
|
||||||
|
return setFilters(updatedFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeOperator = (args: any) => {
|
||||||
|
const { uniqueId, level, groupIndex, value } = args;
|
||||||
|
const filtersCopy = { ...filters };
|
||||||
|
|
||||||
|
const path = getRulePath(level, groupIndex);
|
||||||
|
const updatedFilters = set(
|
||||||
|
filtersCopy,
|
||||||
|
path,
|
||||||
|
get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
|
||||||
|
if (rule.uniqueId !== uniqueId) return rule;
|
||||||
|
return {
|
||||||
|
...rule,
|
||||||
|
operator: value,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setFilters(updatedFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeValue = (args: any) => {
|
||||||
|
const { uniqueId, level, groupIndex, value } = args;
|
||||||
|
const filtersCopy = { ...filters };
|
||||||
|
|
||||||
|
const path = getRulePath(level, groupIndex);
|
||||||
|
const updatedFilters = set(
|
||||||
|
filtersCopy,
|
||||||
|
path,
|
||||||
|
get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
|
||||||
|
if (rule.uniqueId !== uniqueId) return rule;
|
||||||
|
return {
|
||||||
|
...rule,
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setFilters(updatedFilters);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box m={10}>
|
<Box m={10}>
|
||||||
<FilterGroup
|
<FilterGroup
|
||||||
@@ -459,6 +812,10 @@ export const AdvancedFilters = () => {
|
|||||||
uniqueId={filters.uniqueId}
|
uniqueId={filters.uniqueId}
|
||||||
onAddRule={handleAddRule}
|
onAddRule={handleAddRule}
|
||||||
onAddRuleGroup={handleAddRuleGroup}
|
onAddRuleGroup={handleAddRuleGroup}
|
||||||
|
onChangeField={handleChangeField}
|
||||||
|
onChangeOperator={handleChangeOperator}
|
||||||
|
onChangeType={handleChangeType}
|
||||||
|
onChangeValue={handleChangeValue}
|
||||||
onDeleteRule={handleDeleteRule}
|
onDeleteRule={handleDeleteRule}
|
||||||
onDeleteRuleGroup={handleDeleteRuleGroup}
|
onDeleteRuleGroup={handleDeleteRuleGroup}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
/* eslint-disable no-plusplus */
|
/* eslint-disable no-plusplus */
|
||||||
import { useState, useCallback, useMemo } from 'react';
|
import { useState, useCallback, useMemo } from 'react';
|
||||||
import { Group, Checkbox } from '@mantine/core';
|
import { Group, Checkbox } from '@mantine/core';
|
||||||
import { useSetState } from '@mantine/hooks';
|
import { useDebouncedValue, useSetState } from '@mantine/hooks';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
import { RiArrowDownSLine } from 'react-icons/ri';
|
import { RiArrowDownSLine } from 'react-icons/ri';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
import { api } from '@/renderer/api';
|
import { api } from '@/renderer/api';
|
||||||
@@ -17,6 +18,11 @@ import {
|
|||||||
VirtualGridContainer,
|
VirtualGridContainer,
|
||||||
VirtualInfiniteGrid,
|
VirtualInfiniteGrid,
|
||||||
} from '@/renderer/components';
|
} from '@/renderer/components';
|
||||||
|
import {
|
||||||
|
AdvancedFilterGroup,
|
||||||
|
AdvancedFilters,
|
||||||
|
FilterGroupType,
|
||||||
|
} from '@/renderer/features/albums/components/advanced-filters';
|
||||||
import { useAlbumList } from '@/renderer/features/albums/queries/use-album-list';
|
import { useAlbumList } from '@/renderer/features/albums/queries/use-album-list';
|
||||||
import { useServerList } from '@/renderer/features/servers';
|
import { useServerList } from '@/renderer/features/servers';
|
||||||
import { AnimatedPage, useServerCredential } from '@/renderer/features/shared';
|
import { AnimatedPage, useServerCredential } from '@/renderer/features/shared';
|
||||||
@@ -60,12 +66,23 @@ export const AlbumListRoute = () => {
|
|||||||
sortBy: AlbumSort.NAME,
|
sortBy: AlbumSort.NAME,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [advancedFilters, setAdvancedFilters] = useState<AdvancedFilterGroup>({
|
||||||
|
group: [],
|
||||||
|
rules: [{ field: null, operator: null, uniqueId: nanoid(), value: null }],
|
||||||
|
type: FilterGroupType.AND,
|
||||||
|
uniqueId: nanoid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const [debouncedFilters] = useDebouncedValue(advancedFilters, 300);
|
||||||
|
const encoded = encodeURI(JSON.stringify(debouncedFilters));
|
||||||
|
|
||||||
const serverFolders = useMemo(() => {
|
const serverFolders = useMemo(() => {
|
||||||
const server = servers?.data.find((server) => server.id === serverId);
|
const server = servers?.data.find((server) => server.id === serverId);
|
||||||
return server?.serverFolders;
|
return server?.serverFolders;
|
||||||
}, [serverId, servers]);
|
}, [serverId, servers]);
|
||||||
|
|
||||||
const { data: albums } = useAlbumList({
|
const { data: albums } = useAlbumList({
|
||||||
|
advancedFilters: encoded,
|
||||||
orderBy: filters.orderBy,
|
orderBy: filters.orderBy,
|
||||||
serverFolderId: filters.serverFolderId,
|
serverFolderId: filters.serverFolderId,
|
||||||
skip: 0,
|
skip: 0,
|
||||||
@@ -76,9 +93,17 @@ export const AlbumListRoute = () => {
|
|||||||
const fetch = useCallback(
|
const fetch = useCallback(
|
||||||
async ({ skip, take }) => {
|
async ({ skip, take }) => {
|
||||||
const albums = await queryClient.fetchQuery(
|
const albums = await queryClient.fetchQuery(
|
||||||
queryKeys.albums.list(serverId, { skip, take, ...filters }),
|
queryKeys.albums.list(serverId, {
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
...filters,
|
||||||
|
advancedFilters: encoded,
|
||||||
|
}),
|
||||||
async () =>
|
async () =>
|
||||||
api.albums.getAlbumList({ serverId }, { skip, take, ...filters })
|
api.albums.getAlbumList(
|
||||||
|
{ serverId },
|
||||||
|
{ skip, take, ...filters, advancedFilters: encoded }
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// * Adds server token
|
// * Adds server token
|
||||||
@@ -95,7 +120,7 @@ export const AlbumListRoute = () => {
|
|||||||
|
|
||||||
return albums;
|
return albums;
|
||||||
},
|
},
|
||||||
[filters, isImageTokenRequired, queryClient, serverId, serverToken]
|
[encoded, filters, isImageTokenRequired, queryClient, serverId, serverToken]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -171,12 +196,18 @@ export const AlbumListRoute = () => {
|
|||||||
</DropdownMenu.Dropdown>
|
</DropdownMenu.Dropdown>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Group>
|
</Group>
|
||||||
<ViewTypeButton
|
<Group position="right">
|
||||||
handler={setViewType}
|
<ViewTypeButton
|
||||||
menuProps={{ position: 'bottom-end' }}
|
handler={setViewType}
|
||||||
type={viewType}
|
menuProps={{ position: 'bottom-end' }}
|
||||||
/>
|
type={viewType}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
<AdvancedFilters
|
||||||
|
filters={advancedFilters}
|
||||||
|
setFilters={setAdvancedFilters}
|
||||||
|
/>
|
||||||
<VirtualGridAutoSizerContainer>
|
<VirtualGridAutoSizerContainer>
|
||||||
<AutoSizer>
|
<AutoSizer>
|
||||||
{({ height, width }) => (
|
{({ height, width }) => (
|
||||||
@@ -201,7 +232,7 @@ export const AlbumListRoute = () => {
|
|||||||
itemGap={20}
|
itemGap={20}
|
||||||
itemSize={200}
|
itemSize={200}
|
||||||
itemType={LibraryItem.ALBUM}
|
itemType={LibraryItem.ALBUM}
|
||||||
minimumBatchSize={100}
|
minimumBatchSize={40}
|
||||||
width={width}
|
width={width}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user