1
0

Merge branch 'main' into vue3

# Conflicts:
#	src/components/tasks/mixins/createTask.js
This commit is contained in:
kolaente
2021-10-17 13:37:20 +02:00
9 changed files with 211 additions and 138 deletions

View File

@ -0,0 +1,40 @@
import {filterLabelsByQuery} from './labels'
describe('filter labels', () => {
const state = {
labels: [
{id: 1, title: 'label1'},
{id: 2, title: 'label2'},
{id: 3, title: 'label3'},
{id: 4, title: 'label4'},
{id: 5, title: 'label5'},
{id: 6, title: 'label6'},
{id: 7, title: 'label7'},
{id: 8, title: 'label8'},
{id: 9, title: 'label9'},
],
}
it('should return an empty array for an empty query', () => {
const labels = filterLabelsByQuery(state, [], '')
expect(labels).toHaveLength(0)
})
it('should return labels for a query', () => {
const labels = filterLabelsByQuery(state, [], 'label2')
expect(labels).toHaveLength(1)
expect(labels[0].title).toBe('label2')
})
it('should not return found but hidden labels', () => {
interface label {
id: number,
title: string,
}
const labelsToHide: label[] = [{id: 1, title: 'label1'}]
const labels = filterLabelsByQuery(state, labelsToHide, 'label1')
expect(labels).toHaveLength(0)
})
})

29
src/helpers/labels.ts Normal file
View File

@ -0,0 +1,29 @@
interface label {
id: number,
title: string,
}
interface labelState {
labels: label[],
}
/**
* Checks if a list of labels is available in the store and filters them then query
* @param {Object} state
* @param {Array} labelsToHide
* @param {String} query
* @returns {Array}
*/
export function filterLabelsByQuery(state: labelState, labelsToHide: label[], query: string) {
if (query === '') {
return []
}
const labelQuery = query.toLowerCase()
const labelIds = labelsToHide.map(({id}) => id)
return Object
.values(state.labels)
.filter(({id, title}) => {
return !labelIds.includes(id) && title.toLowerCase().includes(labelQuery)
})
}

View File

@ -1,33 +1,17 @@
import LabelService from '@/services/label'
import {setLoading} from '@/store/helper'
import {filterLabelsByQuery} from '@/helpers/labels'
/**
* Returns the labels by id if found
* @param {Object} state
* @param {Array} ids
* @param {Object} state
* @param {Array} ids
* @returns {Array}
*/
function getLabelsByIds(state, ids) {
return Object.values(state.labels).filter(({id}) => ids.includes(id))
}
/**
* Checks if a list of labels is available in the store and filters them then query
* @param {Object} state
* @param {Array} labels
* @param {String} query
* @returns {Array}
*/
function filterLabelsByQuery(state, labels, query) {
const labelIds = labels.map(({id}) => id)
const foundLabels = getLabelsByIds(state, labelIds)
const labelQuery = query.toLowerCase()
return foundLabels.filter(({title}) => {
return !title.toLowerCase().includes(labelQuery)
})
}
export default {
namespaced: true,
state: () => ({
@ -56,7 +40,7 @@ export default {
return (ids) => getLabelsByIds(state, ids)
},
filterLabelsByQuery(state) {
return (...arr) => filterLabelsByQuery(state, ...arr)
return (labelsToHide, query) => filterLabelsByQuery(state, labelsToHide, query)
},
},
actions: {

View File

@ -72,6 +72,7 @@ $filter-container-height: '1rem - #{$switch-view-height}';
margin: .5rem;
border-radius: $radius;
background: $task-background;
padding-bottom: .125rem;
&.loader-container.is-loading:after {
width: 1.5rem;
@ -122,6 +123,7 @@ $filter-container-height: '1rem - #{$switch-view-height}';
.tag, .assignees, .icon, .priority-label, .checklist-summary {
margin-top: 0;
margin-right: .25rem;
margin-bottom: .25rem;
}
.checklist-summary {

View File

@ -15,8 +15,31 @@
}
}
&.has-task-edit-open {
flex-direction: column;
@media screen and (min-width: $tablet) {
flex-direction: row;
.tasks {
width: 66%;
}
}
}
.taskedit {
width: 50%;
width: 33%;
margin-right: 1rem;
margin-left: .5rem;
@media screen and (max-width: $tablet) {
width: 100%;
border-radius: 0;
margin: 0;
border-left: 0;
border-right: 0;
border-bottom: 0;
}
}
}
@ -25,16 +48,6 @@
padding: 0;
text-align: left;
@media screen and (min-width: $tablet) {
&.short {
max-width: 53vw;
}
}
@media screen and (max-width: $tablet) {
max-width: 100%;
}
&.noborder {
margin: 1rem -0.5rem;
}
@ -195,7 +208,7 @@
border-bottom-color: $grey-300;
}
}
.checklist-summary {
padding-left: .5rem;
font-size: .9rem;
@ -235,9 +248,6 @@
}
.taskedit {
min-height: calc(100% - 1rem);
margin-top: 1rem;
.priority-select {
.select, select {
width: 100%;

View File

@ -74,9 +74,8 @@
</a>
</nothing>
<div class="tasks-container">
<div class="tasks-container" :class="{ 'has-task-edit-open': isTaskEdit }">
<div
:class="{ short: isTaskEdit }"
class="tasks mt-0"
v-if="tasks && tasks.length > 0"
>

View File

@ -33,12 +33,15 @@
</h3>
<div v-if="!showAll" class="mb-4">
<x-button type="secondary" @click="showTodaysTasks()" class="mr-2">{{ $t('task.show.today') }}</x-button>
<x-button type="secondary" @click="setDatesToNextWeek()" class="mr-2">{{ $t('task.show.nextWeek') }}</x-button>
<x-button type="secondary" @click="setDatesToNextWeek()" class="mr-2">{{
$t('task.show.nextWeek')
}}
</x-button>
<x-button type="secondary" @click="setDatesToNextMonth()">{{ $t('task.show.nextMonth') }}</x-button>
</div>
<template v-if="!loading && (!tasks || tasks.length === 0) && showNothingToDo">
<h3 class="nothing">{{ $t('task.show.noTasks') }}</h3>
<img alt="" :src="llamaCoolUrl" />
<img alt="" :src="llamaCoolUrl"/>
</template>
<div :class="{ 'is-loading': loading}" class="spinner"></div>
@ -163,7 +166,10 @@ export default {
if (this.showAll) {
this.setTitle(this.$t('task.show.titleCurrent'))
} else {
this.setTitle(this.$t('task.show.titleDates', { from: this.cStartDate.toLocaleDateString(), to: this.cEndDate.toLocaleDateString()}))
this.setTitle(this.$t('task.show.titleDates', {
from: this.cStartDate.toLocaleDateString(),
to: this.cEndDate.toLocaleDateString(),
}))
}
const params = {
@ -200,14 +206,17 @@ export default {
this.$store.dispatch('tasks/loadTasks', params)
.then(r => {
// Sort all tasks to put those with a due date before the ones without a due date, the
// soonest before the later ones.
// We can't use the api sorting here because that sorts tasks with a due date after
// ones without a due date.
r.sort((a, b) => {
return a.dueDate === null && b.dueDate === null ? -1 : 1
})
const tasks = r.filter(t => t.dueDate !== null).concat(r.filter(t => t.dueDate === null))
// Sorting tasks with a due date so that the soonest or overdue are displayed at the top of the list.
const tasksWithDueDates = r
.filter(t => t.dueDate !== null)
.sort((a, b) => a.dueDate > b.dueDate ? 1 : -1)
const tasksWithoutDueDates = r.filter(t => t.dueDate === null)
const tasks = [
...tasksWithDueDates,
...tasksWithoutDueDates,
]
this.tasks = tasks
})