support any/none for release year filter

This commit is contained in:
Gauthier Roebroeck 2026-03-20 16:31:00 +08:00
parent 5a205942df
commit c593815847
6 changed files with 79 additions and 14 deletions

View file

@ -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' },

View file

@ -3,7 +3,8 @@
v-model="model.is"
label="Year"
clearable
:items="items?.content"
:items="selectItems"
@click:clear="model.is = undefined"
/>
<div class="d-flex justify-space-between align-center pb-2">
<span
@ -13,6 +14,7 @@
>
<v-chip
v-if="!!model.min && !!model.max"
:disabled="isSingle"
closable
color="primary"
rounded
@ -26,7 +28,7 @@
<v-range-slider
v-model="modelRange"
strict
:disabled="disabled"
:disabled="disabled || isSingle"
:step="1"
:min="min"
:max="max"
@ -36,10 +38,13 @@
<script setup lang="ts">
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<typeof SchemaSeriesReleaseYears>
@ -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))

View file

@ -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<typeof SchemaSeriesReleaseYears>,
) {
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,

View file

@ -117,7 +117,7 @@
<FilterExpansionPanel
title="Release year"
:count="(!!filterReleaseYear.is ? 1 : 0) + (!!filterReleaseYear.min ? 1 : 0)"
:count="!!filterReleaseYear.is ? 1 : !!filterReleaseYear.min ? 1 : 0"
@clear="clearFilterYear()"
>
<FilterByReleaseYear v-model="filterReleaseYear" />

View file

@ -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' }

View file

@ -7,6 +7,11 @@ export const filterMessages: Record<string, MessageDescriptor> = {
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),
})