feat: add button to clear active filters #924

Merged
konrad merged 25 commits from feat/clear-active-filter into main 2021-11-13 19:48:06 +00:00
11 changed files with 227 additions and 174 deletions

View File

@ -219,10 +219,10 @@ describe('Lists', () => {
cy.get('.table-view .filter-container .items .button')
.contains('Columns')
.click()
cy.get('.table-view .filter-container .card .card-content .fancycheckbox .check')
cy.get('.table-view .filter-container .card.columns-filter .card-content .fancycheckbox .check')
.contains('Priority')
.click()
cy.get('.table-view .filter-container .card .card-content .fancycheckbox .check')
cy.get('.table-view .filter-container .card.columns-filter .card-content .fancycheckbox .check')
.contains('Done')
.click()

View File

@ -1,37 +1,49 @@
<template>
<transition name="fade">
<filters
v-if="visibleInternal"
v-model="value"
ref="filters"
/>
</transition>
<x-button
v-if="hasFilters"
type="secondary"
@click="clearFilters"
>
{{ $t('filters.clear') }}
</x-button>
<popup>
<template #trigger="{toggle}">
konrad marked this conversation as resolved Outdated

The watcher in the component won't ever trigger if the component does not exist at all (with v-if). This hides it visually only.

The watcher in the component won't ever trigger if the component does not exist at all (with `v-if`). This hides it visually only.

Not sure which watcher you mean here. The one inside the <Filters>? If so => maybe a deep watcher helps.

Not sure which watcher you mean here. The one inside the `<Filters>`? If so => maybe a `deep` watcher helps.

I just realize that it also feels generally wrong to put this logic here:
Before the filter popup was just managing the filter and passing along the modelValue.
Probably this could have been easier if the FilterPopup would provide a slot for the popup.
Then maybe another one for the trigger … and then we basically can name the component just popup and integrate popper.js inside.
(that's kind of what my plan was for this anyway)

I just realize that it also feels generally wrong to put this logic here: Before the filter popup was just managing the filter and passing along the modelValue. Probably this could have been easier if the FilterPopup would provide a slot for the popup. Then maybe another one for the trigger … and then we basically can name the component just popup and integrate popper.js inside. (that's kind of what my plan was for this anyway)

Not sure which watcher you mean here. The one inside the ? If so => maybe a deep watcher helps.

Exactly.

Probably this could have been easier if the FilterPopup would provide a slot for the popup.

So that it would not pass along the filter state? I'm actually not sure if this could have been easier in that case - I want to have the same "clear filters" button in all places where the filter popup is being used. I think moving this whole thing in a component simplifies that.

> Not sure which watcher you mean here. The one inside the <Filters>? If so => maybe a deep watcher helps. Exactly. > Probably this could have been easier if the FilterPopup would provide a slot for the popup. So that it would not pass along the filter state? I'm actually not sure if this could have been easier in that case - I want to have the same "clear filters" button in all places where the filter popup is being used. I think moving this whole thing in a component simplifies that.

I think I understand what you mean now. I meant something similar to this:

<!-- new Filter.vue -->
<template>
	<!-- put in the new clearFilters button -->
	<XBbutton
		v-if="hasFilters"
		type="secondary"
		@click="clearFilters"
	>
		{{ $t('filters.clear') }}
	</XButton>
    
    
    <!-- // new popup component, manages `open` state internally; no need for the state to be outside, includes logic of `closeWhenClickedOutside` -->
    <popup>
        <template #trigger="{isOpen, toggle}">
        	<!-- this button is currently inside Kanban, gantt-component, ... -->
            <button
                @click="toggle()"
            >open filters</button>
        </template>

        <template>
            <!-- put in here the complete content of the current Filters.vue -->
        </template>
    </popup>
</template>

<!-- [...] -->
I think I understand what you mean now. I meant something similar to this: ```vue <!-- new Filter.vue --> <template> <!-- put in the new clearFilters button --> <XBbutton v-if="hasFilters" type="secondary" @click="clearFilters" > {{ $t('filters.clear') }} </XButton> <!-- // new popup component, manages `open` state internally; no need for the state to be outside, includes logic of `closeWhenClickedOutside` --> <popup> <template #trigger="{isOpen, toggle}"> <!-- this button is currently inside Kanban, gantt-component, ... --> <button @click="toggle()" >open filters</button> </template> <template> <!-- put in here the complete content of the current Filters.vue --> </template> </popup> </template> <!-- [...] --> ```

So basically moving the button and the open state to the filter popup component?

So basically moving the button and the open state to the filter popup component?

Both buttons, clear and toggle, that is

Both buttons, clear and toggle, that is

I put something together in 86efb07f09. That seems to work. Is that what you had in mind?

I put something together in 86efb07f098392f50ebf607c070f321d34697205. That seems to work. Is that what you had in mind?

yes / no:

  • the popup is becoming it's own component that just contains general popup logic, aka not filter specific.
  • it provides slots for the button and the content. I made this the default slot but maybe #content would have been better.
  • With the slots it provides methods to open / close to the popup and show it's current state — isOpen can be used to control active classes at the button
  • it handles the isOpen state internally.
  • the popup could also be used for: showActiveColumnsFilter in Table.vue

I did though move the reset button together with the other stuff from the current filters.vue.


So if you would have written:

So basically moving the button and the open state to the filter popup component?

Then the answer would be yes

yes / no: - the popup is becoming it's own component that just contains _general_ popup logic, aka not filter specific. - it provides slots for the button and the content. I made this the default slot but maybe `#content` would have been better. - With the slots it provides methods to open / close to the popup and show it's current state — `isOpen` can be used to control active classes at the button - it handles the isOpen state internally. - the popup could also be used for: `showActiveColumnsFilter` in `Table.vue` ---- I did though move the reset button together with the other stuff from the current filters.vue. --- So if you would have written: > So basically moving the button and the open state to the filter ~~popup~~ component? Then the answer would be yes ✅
<x-button
@click.prevent.stop="toggle()"
type="secondary"
icon="filter"
>
{{ $t('filters.title') }}
</x-button>
</template>
<template #content="{isOpen}">
<filters
v-model="value"
ref="filters"
class="filter-popup"
:class="{'is-open': isOpen}"
/>
</template>
</popup>
</template>
<script>
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import Filters from '../../../components/list/partials/filters'
import Filters from '@/components/list/partials/filters'
import {getDefaultParams} from '@/components/tasks/mixins/taskList'
konrad marked this conversation as resolved Outdated

use @

use `@`

Done. I think we should put this in the linter at some point, I keep forgetting it... and webstorm always seems to use relative paths by default.

Done. I think we should put this in the linter at some point, I keep forgetting it... and webstorm always seems to use relative paths by default.
import Popup from '@/components/misc/popup'
export default {
name: 'filter-popup',
components: {
Popup,
Filters,
},
props: {
modelValue: {
required: true,
},
visible: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue'],
data() {
return {
visibleInternal: false,
}
},
computed: {
value: {
get() {
@ -41,34 +53,46 @@ export default {
this.$emit('update:modelValue', value)
},
},
},
mounted() {
document.addEventListener('click', this.hidePopup)
},
beforeUnmount() {
document.removeEventListener('click', this.hidePopup)
hasFilters() {
// this.value also contains the page parameter which we don't want to include in filters
// eslint-disable-next-line no-unused-vars
const {filter_by, filter_value, filter_comparator, filter_concat, s} = this.value
konrad marked this conversation as resolved Outdated

This feels very hacky...

This feels very hacky...

Here specifically:
You can remove a parameter by destructuring it and then just use the rest:

const {page, ...value} = this.value

// just continue to use `value`
JSON.stringify(filterParams) !== JSON.stringify(def)

👆 this line is fine – in case you included that in what you meant feels hacky – as long as it works

All together my feeling is that hasFilters should be a computed that is defined where the filters state is saved.

Here specifically: You can remove a parameter by destructuring it and then just use the rest: ```js const {page, ...value} = this.value // just continue to use `value` ``` ```js JSON.stringify(filterParams) !== JSON.stringify(def) ``` 👆 this line is fine – in case you included that in what you meant feels hacky – as long as it works All together my feeling is that `hasFilters` should be a computed that is defined where the filters state is saved.

You can remove a parameter by destructuring it and then just use the rest:

You mean in that case value would contain everything from this.value without page?

> You can remove a parameter by destructuring it and then just use the rest: You mean in that case `value` would contain everything from `this.value` without `page`?

Changed.

Changed.
const def = {...getDefaultParams()}
const params = {filter_by, filter_value, filter_comparator, filter_concat, s}
const defaultParams = {
filter_by: def.filter_by,
filter_value: def.filter_value,
filter_comparator: def.filter_comparator,
filter_concat: def.filter_concat,
s: s ? def.s : undefined,
}
return JSON.stringify(params) !== JSON.stringify(defaultParams)
},
},
watch: {
modelValue: {
handler(value) {
this.params = value
this.value = value
},
immediate: true,
},
visible() {
this.visibleInternal = !this.visibleInternal
},
},
methods: {
hidePopup(e) {
if (!this.visibleInternal) {
return
}
closeWhenClickedOutside(e, this.$refs.filters.$el, () => {
this.visibleInternal = false
})
clearFilters() {
this.value = {...getDefaultParams()}
},
},
}
</script>
<style scoped lang="scss">
.filter-popup {
margin: 0;
konrad marked this conversation as resolved Outdated

Why the !important?

Why the `!important`?

There was a margin set for .filter-container .card (in list.scss). I've removed that one and the !important statements.

There was a margin set for `.filter-container .card` (in `list.scss`). I've removed that one and the `!important` statements.
&.is-open {
margin: 2rem 0 1rem;
}
}
</style>

View File

@ -458,15 +458,7 @@ export default {
return
}
let foundDone = false
this.params.filter_by.forEach((f, i) => {
if (f === 'done') {
foundDone = i
}
})
if (foundDone === false) {
this.filters.done = true
}
this.filters.done = this.params.filter_by.some((f) => f === 'done') === false
},
async prepareRelatedObjectFilter(kind, filterName = null, servicePrefix = null) {
if (filterName === null) {

View File

@ -0,0 +1,54 @@
<template>
<slot name="trigger" :isOpen="open" :toggle="toggle"></slot>
<div class="popup" :class="{'is-open': open}" ref="popup">
<slot name="content" :isOpen="open"/>
</div>
</template>
<script setup>
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {onBeforeUnmount, onMounted, ref} from 'vue'
const open = ref(false)
const popup = ref(null)
const toggle = () => {
open.value = !open.value
}
function hidePopup(e) {
konrad marked this conversation as resolved Outdated

Picky: use function. Makes it more clear what a "method" is (now simply function) next to a ref.

Picky: use `function`. Makes it more clear what a "method" is (now simply function) next to a ref.

Done.

Done.
if (!open.value) {
return
}
// we actually want to use popup.$el, not its value.
// eslint-disable-next-line vue/no-ref-as-operand
closeWhenClickedOutside(e, popup.value, () => {
open.value = false
})
}
onMounted(() => {
document.addEventListener('click', hidePopup)
})
onBeforeUnmount(() => {
document.removeEventListener('click', hidePopup)
})
</script>
<style scoped lang="scss">
.popup {
transition: opacity $transition;
opacity: 0;
height: 0;
overflow: hidden;
position: absolute;
top: 1rem;
&.is-open {
opacity: 1;
height: auto;
}
}
</style>

View File

@ -9,12 +9,12 @@
>
{{ $t('filters.title') }}
</x-button>
<filter-popup
:visible="showTaskFilter"
v-model="params"
@update:modelValue="loadTasks()"
/>
</div>
<filter-popup
:visible="showTaskFilter"
v-model="params"
@update:modelValue="loadTasks()"
/>
</div>
<div class="dates">
<template v-for="(y, yk) in days" :key="yk + 'year'">
@ -347,7 +347,7 @@ export default {
return
}
let newTask = { ...taskDragged }
let newTask = {...taskDragged}
const didntHaveDates = newTask.startDate === null ? true : false

View File

@ -1,14 +1,14 @@
import TaskCollectionService from '@/services/taskCollection'
// FIXME: merge with DEFAULT_PARAMS in filters.vue
const DEFAULT_PARAMS = {
export const getDefaultParams = () => ({
sort_by: ['position', 'id'],
order_by: ['asc', 'desc'],
filter_by: ['done'],
filter_value: ['false'],
filter_comparator: ['equals'],
filter_concat: 'and',
}
})
/**
konrad marked this conversation as resolved Outdated

I only got this working once I created this factory thing. Every time I'd use the constant directly, the params variable in the components and the value of this const were always the same. I suspect this is due to some JS-reference passing stuff.

I only got this working once I created this factory thing. Every time I'd use the constant directly, the params variable in the components and the value of this const were always the same. I suspect this is due to some JS-reference passing stuff.

Makes sense. I didn't use a getter function since it seemed to me more "JSONy".
How about:

const DEFAULT_PARAMS = {
	sort_by: ['position', 'id'],
	order_by: ['asc', 'desc'],
	filter_by: ['done'],
	filter_value: ['false'],
	filter_comparator: ['equals'],
	filter_concat: 'and',
}

export const getDefaultParams = () => DEFAULT_PARAMS
Makes sense. I didn't use a getter function since it seemed to me more "JSONy". How about: ```js const DEFAULT_PARAMS = { sort_by: ['position', 'id'], order_by: ['asc', 'desc'], filter_by: ['done'], filter_value: ['false'], filter_comparator: ['equals'], filter_concat: 'and', } export const getDefaultParams = () => DEFAULT_PARAMS ```

I tried that, it did not work unfortunately. My theory is that the function only returns a reference to the const which is then changed. And because everything holds a copy to that same const reference, it is changed everywhere simultaniously.

I tried that, it did not work unfortunately. My theory is that the function only returns a reference to the const which is then changed. And because everything holds a copy to that same const reference, it is changed everywhere simultaniously.

Maybe:

export const getDefaultParams = () => { ...DEFAULT_PARAMS }
Maybe: ```js export const getDefaultParams = () => { ...DEFAULT_PARAMS } ```

That seems to work.

That seems to work.

Actually, it does not seem to work. Reverted...

Actually, it does not seem to work. Reverted...
* This mixin provides a base set of methods and properties to get tasks on a list.
@ -26,7 +26,7 @@ export default {
searchTerm: '',
showTaskFilter: false,
params: DEFAULT_PARAMS,
params: {...getDefaultParams()},
}
},
watch: {
@ -94,7 +94,7 @@ export default {
this.initTasks(page, search)
},
loadTasksOnSavedFilter() {
if(typeof this.$route.params.listId !== 'undefined' && parseInt(this.$route.params.listId) < 0) {
if (typeof this.$route.params.listId !== 'undefined' && parseInt(this.$route.params.listId) < 0) {
this.loadTasks(1, '', null, true)
}
},

View File

@ -344,6 +344,7 @@
},
"filters": {
"title": "Filters",
"clear": "Clear Filters",
"attributes": {
"title": "Title",
"titlePlaceholder": "The saved filter title goes here…",

View File

@ -34,7 +34,6 @@ $filter-container-top-link-share-list: -47px;
.card {
text-align: left;
margin-top: calc(1rem - 1px);
}
.fancycheckbox {
@ -47,10 +46,6 @@ $filter-container-top-link-share-list: -47px;
justify-content: space-between;
margin-right: .5rem;
.button, .input {
height: $switch-view-height;
}
.field {
transition: width $transition;
width: 100%;

View File

@ -2,18 +2,11 @@
<div class="kanban-view">
<div class="filter-container" v-if="isSavedFilter">
<div class="items">
<x-button
@click.prevent.stop="toggleFilterPopup"
icon="filter"
type="secondary"
>
{{ $t('filters.title') }}
</x-button>
<filter-popup
v-model="params"
@update:modelValue="loadBuckets"
/>
</div>
<filter-popup
:visible="showFilters"
v-model="params"
/>
</div>
<div
:class="{ 'is-loading': loading && !oneTaskUpdating}"
@ -143,7 +136,7 @@
:component-data="taskDraggableTaskComponentData"
>
<template #item="{element: task}">
<kanban-card :task="task" />
<kanban-card :task="task"/>
</template>
</draggable>
</div>
@ -213,7 +206,7 @@
<!-- This router view is used to show the task popup while keeping the kanban board itself -->
<router-view v-slot="{ Component }">
<transition name="modal">
<component :is="Component" />
<component :is="Component"/>
</transition>
</router-view>
@ -224,10 +217,10 @@
v-if="showBucketDeleteModal"
>
<template #header><span>{{ $t('list.kanban.deleteHeaderBucket') }}</span></template>
<template #text>
<p>{{ $t('list.kanban.deleteBucketText1') }}<br/>
{{ $t('list.kanban.deleteBucketText2') }}</p>
{{ $t('list.kanban.deleteBucketText2') }}</p>
</template>
</modal>
</transition>
@ -300,7 +293,6 @@ export default {
filter_comparator: [],
filter_concat: 'and',
},
showFilters: false,
}
},
created() {
@ -328,10 +320,10 @@ export default {
return {
type: 'transition',
tag: 'div',
name: !this.dragBucket ? 'move-bucket': null,
name: !this.dragBucket ? 'move-bucket' : null,
class: [
'kanban-bucket-container',
{ 'dragging-disabled': !this.canWrite },
{'dragging-disabled': !this.canWrite},
],
}
},
@ -339,10 +331,10 @@ export default {
return {
type: 'transition',
tag: 'div',
name: !this.drag ? 'move-card': null,
name: !this.drag ? 'move-card' : null,
class: [
'dropper',
{ 'dragging-disabled': !this.canWrite },
{'dragging-disabled': !this.canWrite},
],
}
},
@ -357,19 +349,15 @@ export default {
list: state => state.currentList,
}),
},
methods: {
toggleFilterPopup() {
this.showFilters = !this.showFilters
},
methods: {
loadBuckets() {
// Prevent trying to load buckets if the task popup view is active
if (this.$route.name !== 'list.kanban') {
return
}
const { listId, params } = this.loadBucketParameter
const {listId, params} = this.loadBucketParameter
this.collapsedBuckets = getCollapsedBucketState(listId)
@ -424,7 +412,7 @@ export default {
const newTask = cloneDeep(task) // cloning the task to avoid vuex store mutations
newTask.bucketId = newBucket.id,
newTask.kanbanPosition = calculateItemPosition(taskBefore !== null ? taskBefore.kanbanPosition : null, taskAfter !== null ? taskAfter.kanbanPosition : null)
newTask.kanbanPosition = calculateItemPosition(taskBefore !== null ? taskBefore.kanbanPosition : null, taskAfter !== null ? taskAfter.kanbanPosition : null)
try {
await this.$store.dispatch('tasks/update', newTask)

View File

@ -41,19 +41,11 @@
v-if="!showTaskSearch"
/>
</div>
<x-button
@click.prevent.stop="showTaskFilter = !showTaskFilter"
type="secondary"
icon="filter"
>
{{ $t('filters.title') }}
</x-button>
<filter-popup
v-model="params"
@update:modelValue="loadTasks()"
/>
</div>
<filter-popup
:visible="showTaskFilter"
v-model="params"
@update:modelValue="loadTasks()"
/>
</div>
<card :padding="false" :has-content="false" class="has-overflow">
@ -126,7 +118,7 @@
/>
</div>
<Pagination
<Pagination
:total-pages="taskCollectionService.totalPages"
:current-page="currentPage"
/>
@ -135,7 +127,7 @@
<!-- This router view is used to show the task popup while keeping the kanban board itself -->
<router-view v-slot="{ Component }">
<transition name="modal">
<component :is="Component" />
<component :is="Component"/>
</transition>
</router-view>
</div>
@ -155,6 +147,7 @@ import FilterPopup from '@/components/list/partials/filter-popup.vue'
import {HAS_TASKS} from '@/store/mutation-types'
import Nothing from '@/components/misc/nothing.vue'
import Pagination from '@/components/misc/pagination.vue'
import Popup from '@/components/misc/popup'
import draggable from 'vuedraggable'
import {calculateItemPosition} from '../../../helpers/calculateItemPosition'
@ -198,6 +191,7 @@ export default {
taskList,
],
components: {
Popup,
Nothing,
FilterPopup,
SingleTaskInList,
@ -294,11 +288,11 @@ export default {
async saveTaskPosition(e) {
this.drag = false
const task = this.tasks[e.newIndex]
const taskBefore = this.tasks[e.newIndex - 1] ?? null
const taskAfter = this.tasks[e.newIndex + 1] ?? null
const taskAfter = this.tasks[e.newIndex + 1] ?? null
const newTask = {
...task,
position: calculateItemPosition(taskBefore !== null ? taskBefore.position : null, taskAfter !== null ? taskAfter.position : null),

View File

@ -2,67 +2,63 @@
<div :class="{'is-loading': taskCollectionService.loading}" class="table-view loader-container">
<div class="filter-container">
<div class="items">
<x-button
@click.prevent.stop="() => {showActiveColumnsFilter = !showActiveColumnsFilter; showTaskFilter = false}"
icon="th"
type="secondary"
>
{{ $t('list.table.columns') }}
</x-button>
<x-button
@click.prevent.stop="() => {showTaskFilter = !showTaskFilter; showActiveColumnsFilter = false}"
icon="filter"
type="secondary"
>
{{ $t('filters.title') }}
</x-button>
<popup>
<template #trigger="{toggle}">
<x-button
@click.prevent.stop="toggle()"
icon="th"
type="secondary"
>
{{ $t('list.table.columns') }}
</x-button>
</template>
<template #content="{isOpen}">
<card class="columns-filter" :class="{'is-open': isOpen}">
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.id">#</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.done">
{{ $t('task.attributes.done') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.title">
{{ $t('task.attributes.title') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.priority">
{{ $t('task.attributes.priority') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.labels">
{{ $t('task.attributes.labels') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.assignees">
{{ $t('task.attributes.assignees') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.dueDate">
{{ $t('task.attributes.dueDate') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.startDate">
{{ $t('task.attributes.startDate') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.endDate">
{{ $t('task.attributes.endDate') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.percentDone">
{{ $t('task.attributes.percentDone') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.created">
{{ $t('task.attributes.created') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.updated">
{{ $t('task.attributes.updated') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.createdBy">
{{ $t('task.attributes.createdBy') }}
</fancycheckbox>
</card>
</template>
</popup>
<filter-popup
v-model="params"
@update:modelValue="loadTasks()"
/>
</div>
<transition name="fade">
<card v-if="showActiveColumnsFilter">
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.id">#</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.done">
{{ $t('task.attributes.done') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.title">
{{ $t('task.attributes.title') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.priority">
{{ $t('task.attributes.priority') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.labels">
{{ $t('task.attributes.labels') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.assignees">
{{ $t('task.attributes.assignees') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.dueDate">
{{ $t('task.attributes.dueDate') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.startDate">
{{ $t('task.attributes.startDate') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.endDate">
{{ $t('task.attributes.endDate') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.percentDone">
{{ $t('task.attributes.percentDone') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.created">
{{ $t('task.attributes.created') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.updated">
{{ $t('task.attributes.updated') }}
</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.createdBy">
{{ $t('task.attributes.createdBy') }}
</fancycheckbox>
</card>
</transition>
<filter-popup
:visible="showTaskFilter"
v-model="params"
@update:modelValue="loadTasks()"
/>
</div>
<card :padding="false" :has-content="false">
@ -189,21 +185,23 @@
</template>
<script>
import taskList from '../../../components/tasks/mixins/taskList'
import taskList from '@/components/tasks/mixins/taskList'
import Done from '@/components/misc/Done.vue'
import User from '../../../components/misc/user'
import PriorityLabel from '../../../components/tasks/partials/priorityLabel'
import Labels from '../../../components/tasks/partials/labels'
import DateTableCell from '../../../components/tasks/partials/date-table-cell'
import Fancycheckbox from '../../../components/input/fancycheckbox'
import Sort from '../../../components/tasks/partials/sort'
import User from '@/components/misc/user'
import PriorityLabel from '@/components/tasks/partials/priorityLabel'
import Labels from '@/components/tasks/partials/labels'
import DateTableCell from '@/components/tasks/partials/date-table-cell'
import Fancycheckbox from '@/components/input/fancycheckbox'
import Sort from '@/components/tasks/partials/sort'
import {saveListView} from '@/helpers/saveListView'
import FilterPopup from '@/components/list/partials/filter-popup.vue'
import Pagination from '@/components/misc/pagination.vue'
import Popup from '@/components/misc/popup'
konrad marked this conversation as resolved Outdated

use @

use `@`
export default {
name: 'Table',
components: {
Popup,
Done,
FilterPopup,
Sort,
@ -219,7 +217,6 @@ export default {
],
data() {
return {
showActiveColumnsFilter: false,
activeColumns: {
id: true,
done: true,
@ -323,4 +320,12 @@ export default {
}
}
}
.columns-filter {
margin: 0;
konrad marked this conversation as resolved Outdated

why !important?

why `!important`?
&.is-open {
konrad marked this conversation as resolved
Review

we shouldn't style elements from outside.
Why do we need to position this from outside via margin?

we shouldn't style elements from outside. Why do we need to position this from outside via margin?
Review

Because by default, there's no margin. And that would let it flow in the other buttons instead.

How would you solve it? Where would you apply the margin instead?

Because by default, there's no margin. And that would let it flow in the other buttons instead. How would you solve it? Where would you apply the margin instead?
Review

Okay I just tried to fix what I meant but realized there are much more changes needed for that. I think that would be out of the scope of this branch and better fitted when we add a popper.js based popup =)

Okay I just tried to fix what I meant but realized there are much more changes needed for that. I think that would be out of the scope of this branch and better fitted when we add a popper.js based popup =)
Review

Sounds like a topic for another day :)

Sounds like a topic for another day :)
Review

I think that was the last =)

I think that was the last =)
margin: 2rem 0 1rem;
}
}
</style>