diff --git a/src/renderer/components/query-builder/query-builder-option.tsx b/src/renderer/components/query-builder/query-builder-option.tsx index 017a18a4c..5865b2192 100644 --- a/src/renderer/components/query-builder/query-builder-option.tsx +++ b/src/renderer/components/query-builder/query-builder-option.tsx @@ -139,7 +139,7 @@ const QueryValueInput = ({ case 'dateRange': if (operator === 'inTheRangeDate') { return ( - + { for (const rule of filter.rules) { if (rule.field && rule.operator) { const [field] = rule.field.split('.'); - let operator = rule.operator; + const operator = mapDatePickerOperatorToApi(rule.operator); let value = rule.value; const booleanFields = NDSongQueryFields.filter( @@ -144,14 +135,6 @@ export const convertQueryGroupToNDQuery = (filter: QueryBuilderGroup) => { value = value === 'true'; } - if (operator === 'beforeDate') { - operator = 'before'; - } else if (operator === 'afterDate') { - operator = 'after'; - } else if (operator === 'inTheRangeDate') { - operator = 'inTheRange'; - } - switch (field) { default: rootQuery[rootQueryType].push({ @@ -200,19 +183,8 @@ export const convertNDQueryToQueryGroup = (query: Record) => { value = value.toString(); } - const dateFields = NDSongQueryFields.filter( - (queryField) => queryField.type === 'date' || queryField.type === 'dateRange', - ).map((field) => field.value); - - if (dateFields.includes(field)) { - if (operator === 'before') { - operator = 'beforeDate'; - } else if (operator === 'after') { - operator = 'afterDate'; - } else if (operator === 'inTheRange') { - operator = 'inTheRangeDate'; - } - } + // Use date-picker operator in UI when value is date-like (e.g. YYYY-MM-DD); otherwise keep API operator + operator = mapApiOperatorToDatePicker(operator, value); rootGroup.rules.push({ field, @@ -225,3 +197,31 @@ export const convertNDQueryToQueryGroup = (query: Record) => { return rootGroup; }; + +const DATE_STRING_REGEX = /^\d{4}-\d{2}-\d{2}$/; + +function isDateLikeValue(value: unknown): boolean { + if (value instanceof Date) return true; + if (typeof value === 'string') return DATE_STRING_REGEX.test(value.trim()); + return false; +} + +function isDateRangeValue(value: unknown): value is [null | string, null | string] { + if (!Array.isArray(value) || value.length !== 2) return false; + const [a, b] = value; + return (a == null || isDateLikeValue(a)) && (b == null || isDateLikeValue(b)); +} + +function mapApiOperatorToDatePicker(operator: string, value: unknown): string { + if (operator === 'before' && isDateLikeValue(value)) return 'beforeDate'; + if (operator === 'after' && isDateLikeValue(value)) return 'afterDate'; + if (operator === 'inTheRange' && isDateRangeValue(value)) return 'inTheRangeDate'; + return operator; +} + +function mapDatePickerOperatorToApi(operator: string): string { + if (operator === 'beforeDate') return 'before'; + if (operator === 'afterDate') return 'after'; + if (operator === 'inTheRangeDate') return 'inTheRange'; + return operator; +} diff --git a/src/shared/api/navidrome/navidrome-types.ts b/src/shared/api/navidrome/navidrome-types.ts index c2a935877..f9846672c 100644 --- a/src/shared/api/navidrome/navidrome-types.ts +++ b/src/shared/api/navidrome/navidrome-types.ts @@ -144,6 +144,7 @@ export const NDSongQueryFields = [ { label: 'MusicBrainz Work Id', type: 'string', value: 'musicbrainz_workid' }, { label: 'Name', type: 'string', value: 'title' }, { label: 'Original Date', type: 'date', value: 'originaldate' }, + { label: 'Original Year', type: 'date', value: 'originalyear' }, { label: 'Performer', type: 'string', value: 'performer' }, { label: 'Play Count', type: 'number', value: 'playcount' }, { label: 'Playlist', type: 'playlist', value: 'id' },