From c593815847d0b35da34e04e9c275468923fae365 Mon Sep 17 00:00:00 2001 From: Gauthier Roebroeck Date: Fri, 20 Mar 2026 16:31:00 +0800 Subject: [PATCH] support any/none for release year filter --- .../filter/by/ReleaseYear.stories.ts | 12 +++++++ .../src/components/filter/by/ReleaseYear.vue | 25 ++++++++++++-- next-ui/src/functions/filter.ts | 33 ++++++++++++++----- next-ui/src/pages/libraries/[id]/series.vue | 2 +- next-ui/src/types/filter.test.ts | 14 ++++++++ next-ui/src/types/filter.ts | 7 +++- 6 files changed, 79 insertions(+), 14 deletions(-) diff --git a/next-ui/src/components/filter/by/ReleaseYear.stories.ts b/next-ui/src/components/filter/by/ReleaseYear.stories.ts index f94257db..1345a6f0 100644 --- a/next-ui/src/components/filter/by/ReleaseYear.stories.ts +++ b/next-ui/src/components/filter/by/ReleaseYear.stories.ts @@ -39,6 +39,18 @@ export const InitialValue: Story = { }, } +export const InitialValueAny: Story = { + args: { + modelValue: { is: 'any' }, + }, +} + +export const InitialValueNone: Story = { + args: { + modelValue: { is: 'none' }, + }, +} + export const InitialRange: Story = { args: { modelValue: { min: '2018', max: '2020' }, diff --git a/next-ui/src/components/filter/by/ReleaseYear.vue b/next-ui/src/components/filter/by/ReleaseYear.vue index 7b43b732..5df70652 100644 --- a/next-ui/src/components/filter/by/ReleaseYear.vue +++ b/next-ui/src/components/filter/by/ReleaseYear.vue @@ -3,7 +3,8 @@ v-model="model.is" label="Year" clearable - :items="items?.content" + :items="selectItems" + @click:clear="model.is = undefined" />
import * as v from 'valibot' -import { filterKeys, SchemaSeriesReleaseYears } from '@/types/filter' +import { filterKeys, filterMessages, SchemaSeriesReleaseYears } from '@/types/filter' import { useQuery } from '@pinia/colada' import { releaseYearsQuery } from '@/colada/referential' import { PageRequest } from '@/types/PageRequest' +import { useIntl } from 'vue-intl' + +const intl = useIntl() type ReleaseYears = v.InferOutput @@ -58,6 +63,20 @@ const { data: items } = useQuery(() => ({ }), })) const disabled = computed(() => (items.value?.totalElements || 0) === 0) +const isSingle = computed(() => model.value.is !== undefined) + +const selectItems = computed(() => [ + { + title: intl.formatMessage(filterMessages.any!), + value: 'any', + }, + { + title: intl.formatMessage(filterMessages.none!), + value: 'none', + }, + ...(items.value?.content?.map((it) => ({ title: it, value: it })) || []), +]) + const min = computed(() => items.value?.content?.map((it) => Number(it))?.at(-1)) const max = computed(() => items.value?.content?.map((it) => Number(it))?.at(0)) diff --git a/next-ui/src/functions/filter.ts b/next-ui/src/functions/filter.ts index 14aba4cf..82788bca 100644 --- a/next-ui/src/functions/filter.ts +++ b/next-ui/src/functions/filter.ts @@ -3,6 +3,7 @@ import { SchemaFilterAuthors, type SchemaFilterSeriesStatus, SchemaFilterStrings, + SchemaSeriesReleaseYears, } from '@/types/filter' import type { InferOutput } from 'valibot' import * as v from 'valibot' @@ -103,23 +104,37 @@ export function schemaFilterReleaseYearToConditions( filter: InferOutput, ) { const conds = [] - if (!!filter.is || !!filter.min) { - const year = Number(filter.is || filter.min) + if (filter.is === 'any') { conds.push({ releaseDate: { - operator: 'after', - dateTime: `${(year - 1).toString().padStart(4, '0')}-12-31T12:00:00Z`, + operator: 'isNotNull', }, }) - } - if (!!filter.is || !!filter.max) { - const year = Number(filter.is || filter.max) + } else if (filter.is === 'none') { conds.push({ releaseDate: { - operator: 'before', - dateTime: `${(year + 1).toString().padStart(4, '0')}-01-01T12:00:00Z`, + operator: 'isNull', }, }) + } else { + if (!!filter.is || !!filter.min) { + const year = Number(filter.is || filter.min) + conds.push({ + releaseDate: { + operator: 'after', + dateTime: `${(year - 1).toString().padStart(4, '0')}-12-31T12:00:00Z`, + }, + }) + } + if (!!filter.is || !!filter.max) { + const year = Number(filter.is || filter.max) + conds.push({ + releaseDate: { + operator: 'before', + dateTime: `${(year + 1).toString().padStart(4, '0')}-01-01T12:00:00Z`, + }, + }) + } } return { allOf: conds, diff --git a/next-ui/src/pages/libraries/[id]/series.vue b/next-ui/src/pages/libraries/[id]/series.vue index 39f82bd6..a277d9eb 100644 --- a/next-ui/src/pages/libraries/[id]/series.vue +++ b/next-ui/src/pages/libraries/[id]/series.vue @@ -117,7 +117,7 @@ diff --git a/next-ui/src/types/filter.test.ts b/next-ui/src/types/filter.test.ts index 176c71be..c4e3223f 100644 --- a/next-ui/src/types/filter.test.ts +++ b/next-ui/src/types/filter.test.ts @@ -82,6 +82,20 @@ describe('schema series release years', () => { expect(result).toStrictEqual(input) }) + test('correct value is: any', () => { + const input = { is: 'any' } + const result = v.parse(SchemaSeriesReleaseYears, input) + + expect(result).toStrictEqual(input) + }) + + test('correct value is: none', () => { + const input = { is: 'none' } + const result = v.parse(SchemaSeriesReleaseYears, input) + + expect(result).toStrictEqual(input) + }) + test('other value throws error', () => { const input = { is: '20254' } diff --git a/next-ui/src/types/filter.ts b/next-ui/src/types/filter.ts index 8825c35d..3f9e5170 100644 --- a/next-ui/src/types/filter.ts +++ b/next-ui/src/types/filter.ts @@ -7,6 +7,11 @@ export const filterMessages: Record = { defaultMessage: 'Any', id: 'bDNv5+', }), + none: defineMessage({ + description: 'Filter values: none', + defaultMessage: 'None', + id: '87q6WX', + }), } export const filterKeys = { @@ -99,7 +104,7 @@ export const SchemaFilterSeriesStatus = createSchemaFilterArray(SchemaSeriesStat * Schema for Series Release Years */ export const SchemaSeriesReleaseYears = v.strictObject({ - is: v.optional(SchemaYear), + is: v.optional(v.union([v.picklist(['any', 'none']), SchemaYear])), min: v.optional(SchemaYear), max: v.optional(SchemaYear), })