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),
})