feat: filters script setup
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
Dominik Pschenitschni 2022-10-02 20:00:36 +02:00
parent 4c0ce26f2d
commit 15f532d37a
Signed by: dpschen
GPG Key ID: B257AC0149F43A77
11 changed files with 595 additions and 489 deletions

View File

@ -20,7 +20,7 @@
:overflow="true" :overflow="true"
variant="hint-modal" variant="hint-modal"
> >
<filters <Filters
:has-title="true" :has-title="true"
v-model="value" v-model="value"
ref="filters" ref="filters"
@ -30,14 +30,16 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {computed, ref, watch} from 'vue' import {computed, ref, watch, type PropType} from 'vue'
import Filters from '@/components/list/partials/filters.vue' import Filters from '@/components/list/partials/filters.vue'
import {getDefaultParams} from '@/composables/taskList' import {getDefaultParams} from '@/composables/taskList'
import type {IParams} from '@/types/IParams'
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Object as PropType<IParams>,
required: true, required: true,
}, },
}) })
@ -63,15 +65,15 @@ watch(
const hasFilters = computed(() => { const hasFilters = computed(() => {
// this.value also contains the page parameter which we don't want to include in filters // this.value also contains the page parameter which we don't want to include in filters
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const {filter_by, filter_value, filter_comparator, filter_concat, s} = value.value const {filterBy, filterValue, filterComparator, filterConcat, s} = value.value
const def = {...getDefaultParams()} const def = {...getDefaultParams()}
const params = {filter_by, filter_value, filter_comparator, filter_concat, s} const params = {filterBy, filterValue, filterComparator, filterConcat, s}
const defaultParams = { const defaultParams = {
filter_by: def.filter_by, filterBy: def.filterBy,
filter_value: def.filter_value, filterValue: def.filterValue,
filter_comparator: def.filter_comparator, filterComparator: def.filterComparator,
filter_concat: def.filter_concat, filterConcat: def.filterConcat,
s: s ? def.s : undefined, s: s ? def.s : undefined,
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +1,63 @@
import {ref, shallowReactive, watch, computed} from 'vue' import {ref, shallowReactive, watch, computed, type Ref} from 'vue'
import {useRoute} from 'vue-router' import {useRoute} from 'vue-router'
import TaskCollectionService from '@/services/taskCollection' import TaskCollectionService from '@/services/taskCollection'
import type { ITask } from '@/modelTypes/ITask'
import type {ITask} from '@/modelTypes/ITask'
import type {IList} from '@/modelTypes/IList'
import type {IParams, OrderBy, SortBy} from '@/types/IParams'
// FIXME: merge with DEFAULT_PARAMS in filters.vue // FIXME: merge with DEFAULT_PARAMS in filters.vue
export const getDefaultParams = () => ({ export const getDefaultParams = () => ({
sort_by: ['position', 'id'], sortBy: ['position', 'id'],
order_by: ['asc', 'desc'], orderBy: ['asc', 'desc'],
filter_by: ['done'], filterBy: ['done'],
filter_value: ['false'], filterValue: ['false'],
filter_comparator: ['equals'], filterComparator: ['equals'],
filter_concat: 'and', filterConcat: 'and',
}) } as IParams)
const SORT_BY_DEFAULT = { type FilterSortOrderMap = Partial<{[key in SortBy]: OrderBy }>
const SORT_BY_DEFAULT: FilterSortOrderMap = {
id: 'desc', id: 'desc',
} } as const
/** /**
* This mixin provides a base set of methods and properties to get tasks on a list. * This mixin provides a base set of methods and properties to get tasks on a list.
*/ */
export function useTaskList(listId, sortByDefault = SORT_BY_DEFAULT) { export function useTaskList(listId: Ref<IList['id']>, sortByDefault = SORT_BY_DEFAULT) {
const params = ref({...getDefaultParams()}) const params = ref({...getDefaultParams()})
const search = ref('') const search = ref('')
const page = ref(1) const page = ref(1)
const sortBy = ref({ ...sortByDefault }) const sortBy = ref<FilterSortOrderMap>({ ...sortByDefault })
// This makes sure an id sort order is always sorted last. // This makes sure an id sort order is always sorted last.
// When tasks would be sorted first by id and then by whatever else was specified, the id sort takes // When tasks would be sorted first by id and then by whatever else was specified, the id sort takes
// precedence over everything else, making any other sort columns pretty useless. // precedence over everything else, making any other sort columns pretty useless.
function formatSortOrder(params) { function formatSortOrder(params: IParams) {
let hasIdFilter = false const sortEntries = Object.entries(sortBy.value)
const sortKeys = Object.keys(sortBy.value) const sortKeys: SortBy[] = []
for (const s of sortKeys) { const orderByValue: OrderBy[] = []
if (s === 'id') { let idFilterValue
sortKeys.splice(s, 1) sortEntries.forEach(([key, value], index) => {
hasIdFilter = true if (key === 'id') {
break idFilterValue = value
sortEntries.splice(index, 1)
return true
} }
} sortKeys.push(key as SortBy)
if (hasIdFilter) { orderByValue.push(value)
})
if (idFilterValue) {
sortKeys.push('id') sortKeys.push('id')
} }
params.sort_by = sortKeys
params.order_by = sortKeys.map(s => sortBy.value[s]) params.sortBy = sortKeys
params.orderBy = orderByValue
return params return params
} }

View File

@ -1,6 +1,11 @@
import {camelCase} from 'camel-case' import {camelCase} from 'camel-case'
import {snakeCase} from 'snake-case' import {snakeCase} from 'snake-case'
export {
camelCase,
snakeCase,
}
/** /**
* Transforms field names to camel case. * Transforms field names to camel case.
* @param object * @param object

View File

@ -1,4 +1,4 @@
export function parseDateOrString(rawValue: string | undefined, fallback: any): string | Date { export function parseDateOrString(rawValue: string | undefined, fallback: null) {
if (typeof rawValue === 'undefined') { if (typeof rawValue === 'undefined') {
return fallback return fallback
} }

View File

@ -6,7 +6,7 @@ export function findById<T extends {id: string | number}>(array : T[], id : stri
return array.find(({id: currentId}) => currentId === id) return array.find(({id: currentId}) => currentId === id)
} }
export function includesById(array: [], id: string | number) { export function includesById(array: any[], id: string | number) {
return array.some(({id: currentId}) => currentId === id) return array.some(({id: currentId}) => currentId === id)
} }

View File

@ -1,12 +1,12 @@
import type {IAbstract} from './IAbstract' import type {IAbstract} from './IAbstract'
import type {IUser} from './IUser' import type {IUser} from './IUser'
import type {IFilter} from '@/types/IFilter' import type {IParams} from '@/types/IParams'
export interface ISavedFilter extends IAbstract { export interface ISavedFilter extends IAbstract {
id: number id: number
title: string title: string
description: string description: string
filters: IFilter filters: IParams
owner: IUser owner: IUser
created: Date created: Date

View File

@ -186,7 +186,7 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
* This one here is the default one, usually the service definitions for a model will override this. * This one here is the default one, usually the service definitions for a model will override this.
*/ */
modelFactory(data : Partial<Model>) { modelFactory(data : Partial<Model>) {
return new AbstractModel(data) return data as Model
} }
/** /**

View File

@ -1,9 +1,32 @@
import type { Priority } from '@/constants/priorities'
export interface IFilter { export interface IFilter {
sortBy: ('done' | 'id')[] // where
orderBy: ('asc' | 'desc')[] listId: string
filterBy: 'done'[]
filterValue: 'false'[] // what
filterComparator: 'equals'[] assignees: string
filterConcat: 'and' done: boolean,
filterIncludeNulls: boolean dueDate: string
} endDate: string
labels: string
namespace: string
percentDone: number
priority: Priority
reminders: string
startDate: string
// filter meta
requireAllFilters: boolean
// toggle what
usePercentDone: boolean
usePriority: boolean
}
export interface IFilterToggles {
usePercentDone: boolean
usePriority: boolean
}
export type IFilterKey = keyof IFilter

38
src/types/IParams.ts Normal file
View File

@ -0,0 +1,38 @@
import type { ITask } from '@/modelTypes/ITask'
export type SortBy =
| 'id'
| 'title'
| 'description'
| 'done'
| 'doneAt'
| 'dueDate'
| 'createdById'
| 'listId'
| 'repeatAfter'
| 'priority'
| 'start_date'
| 'end_date'
| 'hex_color'
| 'percent_done'
| 'uid'
| 'created'
| 'updated'
| 'position'
export type OrderBy = 'asc' | 'desc'
export type FilterComparator = 'equals' | 'greater' | 'greaterEquals' | 'less' | 'lessEquals' | 'like' |'in'
export type FilterBy = keyof ITask | 'namespace'
export type FilterValue = 'false' | string | number | boolean | Date | {dateFrom: string | Date, dateTo: string | Date}
export interface IParams {
sortBy: SortBy[]
orderBy: OrderBy[]
filterBy: FilterBy[]
filterValue: FilterValue[]
filterComparator: FilterComparator[]
filterConcat: 'and' | 'or'
filterIncludeNulls: boolean
s: '' // search
}

View File

@ -226,7 +226,7 @@ const {
const isAlphabeticalSorting = computed(() => { const isAlphabeticalSorting = computed(() => {
return params.value.sort_by.find(sortBy => sortBy === ALPHABETICAL_SORT) !== undefined return params.value.sortBy.find(sortBy => sortBy === ALPHABETICAL_SORT) !== undefined
}) })
const firstNewPosition = computed(() => { const firstNewPosition = computed(() => {