Compare commits
18 Commits
55a425c4d7
...
7cc42e55e2
Author | SHA1 | Date | |
---|---|---|---|
|
7cc42e55e2 | ||
|
bd6493eb53 | ||
|
f542c55448 | ||
95276ceebe | |||
1558921f42 | |||
bf5088e546 | |||
6f366d4907 | |||
d7554d9e70 | |||
8a72fe26f8 | |||
13cab62d14 | |||
81de986d8d | |||
915f677c2a | |||
8a6e3d5bd7 | |||
81fe8391e4 | |||
89e37b88d9 | |||
cc6801c5b1 | |||
767b058915 | |||
2c0d3f2885 |
|
@ -21,7 +21,7 @@ describe('Project View Table', () => {
|
|||
TaskFactory.create(1)
|
||||
cy.visit('/projects/1/3')
|
||||
|
||||
cy.get('.project-table .filter-container .items .button')
|
||||
cy.get('.project-table .filter-container .button')
|
||||
.contains('Columns')
|
||||
.click()
|
||||
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox')
|
||||
|
|
|
@ -16,9 +16,11 @@ import {useColorScheme} from '@/composables/useColorScheme'
|
|||
const {
|
||||
entityKind,
|
||||
entityId,
|
||||
disabled = false,
|
||||
} = defineProps<{
|
||||
entityKind: ReactionKind,
|
||||
entityId: number,
|
||||
disabled?: boolean,
|
||||
}>()
|
||||
|
||||
const authStore = useAuthStore()
|
||||
|
@ -143,11 +145,13 @@ async function toggleReaction(value: string) {
|
|||
v-tooltip="getReactionTooltip(users, value)"
|
||||
class="reaction-button"
|
||||
:class="{'current-user-has-reacted': hasCurrentUserReactedWithEmoji(value)}"
|
||||
:disabled
|
||||
@click="toggleReaction(value)"
|
||||
>
|
||||
{{ value }} {{ users.length }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="!disabled"
|
||||
ref="emojiPickerButtonRef"
|
||||
v-tooltip="$t('reaction.add')"
|
||||
class="reaction-button"
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
}"
|
||||
>
|
||||
<template v-if="icon">
|
||||
<icon
|
||||
<icon
|
||||
v-if="showIconOnly"
|
||||
:icon="icon"
|
||||
:style="{'color': iconColor !== '' ? iconColor : undefined}"
|
||||
|
@ -22,7 +22,7 @@
|
|||
v-else
|
||||
class="icon is-small"
|
||||
>
|
||||
<icon
|
||||
<icon
|
||||
:icon="icon"
|
||||
:style="{'color': iconColor !== '' ? iconColor : undefined}"
|
||||
/>
|
||||
|
@ -34,20 +34,20 @@
|
|||
|
||||
<script lang="ts">
|
||||
const BUTTON_TYPES_MAP = {
|
||||
primary: 'is-primary',
|
||||
secondary: 'is-outlined',
|
||||
tertiary: 'is-text is-inverted underline-none',
|
||||
primary: 'is-primary',
|
||||
secondary: 'is-outlined',
|
||||
tertiary: 'is-text is-inverted underline-none',
|
||||
} as const
|
||||
|
||||
export type ButtonTypes = keyof typeof BUTTON_TYPES_MAP
|
||||
|
||||
export default { name: 'XButton' }
|
||||
export default {name: 'XButton'}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, useSlots} from 'vue'
|
||||
import BaseButton, {type BaseButtonProps} from '@/components/base/BaseButton.vue'
|
||||
import type { IconProp } from '@fortawesome/fontawesome-svg-core'
|
||||
import type {IconProp} from '@fortawesome/fontawesome-svg-core'
|
||||
|
||||
// extending the props of the BaseButton
|
||||
export interface ButtonProps extends /* @vue-ignore */ BaseButtonProps {
|
||||
|
@ -76,37 +76,38 @@ const showIconOnly = computed(() => icon !== '' && typeof slots.default === 'und
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.button {
|
||||
transition: all $transition;
|
||||
border: 0;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
height: auto;
|
||||
min-height: $button-height;
|
||||
box-shadow: var(--shadow-sm);
|
||||
display: inline-flex;
|
||||
white-space: var(--button-white-space);
|
||||
transition: all $transition;
|
||||
border: 0;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
height: auto;
|
||||
min-height: $button-height;
|
||||
box-shadow: var(--shadow-sm);
|
||||
display: inline-flex;
|
||||
white-space: var(--button-white-space);
|
||||
line-height: 1;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
&.fullheight {
|
||||
padding-right: 7px;
|
||||
height: 100%;
|
||||
}
|
||||
&.fullheight {
|
||||
padding-right: 7px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.is-active,
|
||||
&.is-focused,
|
||||
&:active,
|
||||
&:focus,
|
||||
&:focus:not(:active) {
|
||||
box-shadow: var(--shadow-xs) !important;
|
||||
}
|
||||
&.is-active,
|
||||
&.is-focused,
|
||||
&:active,
|
||||
&:focus,
|
||||
&:focus:not(:active) {
|
||||
box-shadow: var(--shadow-xs) !important;
|
||||
}
|
||||
|
||||
&.is-primary.is-outlined:hover {
|
||||
color: var(--white);
|
||||
}
|
||||
&.is-primary.is-outlined:hover {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
|
||||
.is-small {
|
||||
|
@ -114,6 +115,6 @@ const showIconOnly = computed(() => icon !== '' && typeof slots.default === 'und
|
|||
}
|
||||
|
||||
.underline-none {
|
||||
text-decoration: none !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
</style>
|
|
@ -7,8 +7,14 @@
|
|||
{{ getProjectTitle(currentProject) }}
|
||||
</h1>
|
||||
|
||||
<div class="switch-view-container d-print-none">
|
||||
<div class="switch-view">
|
||||
<div
|
||||
class="switch-view-container d-print-none"
|
||||
:class="{'is-justify-content-flex-end': views.length === 1}"
|
||||
>
|
||||
<div
|
||||
v-if="views.length > 1"
|
||||
class="switch-view"
|
||||
>
|
||||
<BaseButton
|
||||
v-for="v in views"
|
||||
:key="v.id"
|
||||
|
@ -149,8 +155,14 @@ function getViewTitle(view: IProjectView) {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.switch-view-container {
|
||||
min-height: $switch-view-height;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
@ -162,8 +174,6 @@ function getViewTitle(view: IProjectView) {
|
|||
border-radius: $radius;
|
||||
font-size: .75rem;
|
||||
box-shadow: var(--shadow-sm);
|
||||
height: $switch-view-height;
|
||||
margin: 0 auto 1rem;
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
|
|
|
@ -379,6 +379,7 @@ const blurDebounced = useDebounceFn(() => emit('blur'), 500)
|
|||
}
|
||||
|
||||
.filter-input-highlight {
|
||||
background: var(--white);
|
||||
height: 2.5em;
|
||||
line-height: 1.5;
|
||||
padding: .5em .75em;
|
||||
|
|
|
@ -1,26 +1,10 @@
|
|||
<!-- Vikunja is a to-do list application to facilitate your life. -->
|
||||
<!-- Copyright 2018-present Vikunja and contributors. All rights reserved. -->
|
||||
<!-- -->
|
||||
<!-- This program is free software: you can redistribute it and/or modify -->
|
||||
<!-- it under the terms of the GNU Affero General Public Licensee as published by -->
|
||||
<!-- the Free Software Foundation, either version 3 of the License, or -->
|
||||
<!-- (at your option) any later version. -->
|
||||
<!-- -->
|
||||
<!-- This program is distributed in the hope that it will be useful, -->
|
||||
<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of -->
|
||||
<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -->
|
||||
<!-- GNU Affero General Public Licensee for more details. -->
|
||||
<!-- -->
|
||||
<!-- You should have received a copy of the GNU Affero General Public Licensee -->
|
||||
<!-- along with this program. If not, see <https://www.gnu.org/licenses/>. -->
|
||||
|
||||
<template>
|
||||
<ProjectWrapper
|
||||
class="project-gantt"
|
||||
:project-id="filters.projectId"
|
||||
:view
|
||||
:view-id
|
||||
>
|
||||
<template #header>
|
||||
<template #default>
|
||||
<card :has-content="false">
|
||||
<div class="gantt-options">
|
||||
<div class="field">
|
||||
|
@ -61,9 +45,7 @@
|
|||
</Fancycheckbox>
|
||||
</div>
|
||||
</card>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<div class="gantt-chart-container">
|
||||
<card
|
||||
:has-content="false"
|
||||
|
@ -95,7 +77,7 @@ import {useI18n} from 'vue-i18n'
|
|||
import type {RouteLocationNormalized} from 'vue-router'
|
||||
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
import { getFlatpickrLanguage } from '@/helpers/flatpickrLanguage'
|
||||
import {getFlatpickrLanguage} from '@/helpers/flatpickrLanguage'
|
||||
|
||||
import Foo from '@/components/misc/flatpickr/Flatpickr.vue'
|
||||
import ProjectWrapper from '@/components/project/ProjectWrapper.vue'
|
||||
|
|
|
@ -6,69 +6,68 @@
|
|||
>
|
||||
<template #header>
|
||||
<div class="filter-container">
|
||||
<div class="items">
|
||||
<Popup>
|
||||
<template #trigger="{toggle}">
|
||||
<x-button
|
||||
icon="th"
|
||||
variant="secondary"
|
||||
@click.prevent.stop="toggle()"
|
||||
>
|
||||
{{ $t('project.table.columns') }}
|
||||
</x-button>
|
||||
</template>
|
||||
<template #content="{isOpen}">
|
||||
<card
|
||||
class="columns-filter"
|
||||
:class="{'is-open': isOpen}"
|
||||
>
|
||||
<Fancycheckbox v-model="activeColumns.index">
|
||||
#
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.done">
|
||||
{{ $t('task.attributes.done') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.title">
|
||||
{{ $t('task.attributes.title') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.priority">
|
||||
{{ $t('task.attributes.priority') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.labels">
|
||||
{{ $t('task.attributes.labels') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.assignees">
|
||||
{{ $t('task.attributes.assignees') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.dueDate">
|
||||
{{ $t('task.attributes.dueDate') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.startDate">
|
||||
{{ $t('task.attributes.startDate') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.endDate">
|
||||
{{ $t('task.attributes.endDate') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.percentDone">
|
||||
{{ $t('task.attributes.percentDone') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.doneAt">
|
||||
{{ $t('task.attributes.doneAt') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.created">
|
||||
{{ $t('task.attributes.created') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.updated">
|
||||
{{ $t('task.attributes.updated') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.createdBy">
|
||||
{{ $t('task.attributes.createdBy') }}
|
||||
</Fancycheckbox>
|
||||
</card>
|
||||
</template>
|
||||
</Popup>
|
||||
<FilterPopup v-model="params" />
|
||||
</div>
|
||||
<Popup>
|
||||
<template #trigger="{toggle}">
|
||||
<x-button
|
||||
icon="th"
|
||||
variant="secondary"
|
||||
class="mr-2"
|
||||
@click.prevent.stop="toggle()"
|
||||
>
|
||||
{{ $t('project.table.columns') }}
|
||||
</x-button>
|
||||
</template>
|
||||
<template #content="{isOpen}">
|
||||
<card
|
||||
class="columns-filter"
|
||||
:class="{'is-open': isOpen}"
|
||||
>
|
||||
<Fancycheckbox v-model="activeColumns.index">
|
||||
#
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.done">
|
||||
{{ $t('task.attributes.done') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.title">
|
||||
{{ $t('task.attributes.title') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.priority">
|
||||
{{ $t('task.attributes.priority') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.labels">
|
||||
{{ $t('task.attributes.labels') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.assignees">
|
||||
{{ $t('task.attributes.assignees') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.dueDate">
|
||||
{{ $t('task.attributes.dueDate') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.startDate">
|
||||
{{ $t('task.attributes.startDate') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.endDate">
|
||||
{{ $t('task.attributes.endDate') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.percentDone">
|
||||
{{ $t('task.attributes.percentDone') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.doneAt">
|
||||
{{ $t('task.attributes.doneAt') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.created">
|
||||
{{ $t('task.attributes.created') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.updated">
|
||||
{{ $t('task.attributes.updated') }}
|
||||
</Fancycheckbox>
|
||||
<Fancycheckbox v-model="activeColumns.createdBy">
|
||||
{{ $t('task.attributes.createdBy') }}
|
||||
</Fancycheckbox>
|
||||
</card>
|
||||
</template>
|
||||
</Popup>
|
||||
<FilterPopup v-model="params" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -397,4 +396,8 @@ const taskDetailRoutes = computed(() => Object.fromEntries(
|
|||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.filter-container :deep(.popup) {
|
||||
top: 7rem;
|
||||
}
|
||||
</style>
|
|
@ -2,12 +2,65 @@
|
|||
import type {IProjectView} from '@/modelTypes/IProjectView'
|
||||
import XButton from '@/components/input/button.vue'
|
||||
import FilterInput from '@/components/project/partials/FilterInput.vue'
|
||||
import {ref} from 'vue'
|
||||
import {ref, watch} from 'vue'
|
||||
import {transformFilterStringForApi, transformFilterStringFromApi} from '@/helpers/filters'
|
||||
import {useLabelStore} from '@/stores/labels'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
|
||||
const {
|
||||
modelValue,
|
||||
} = defineProps<{
|
||||
modelValue: IProjectView,
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const view = ref<IProjectView>()
|
||||
|
||||
const labelStore = useLabelStore()
|
||||
const projectStore = useProjectStore()
|
||||
|
||||
watch(
|
||||
() => modelValue,
|
||||
newValue => {
|
||||
const transformed = {
|
||||
...newValue,
|
||||
filter: transformFilterStringFromApi(
|
||||
newValue.filter,
|
||||
labelId => labelStore.getLabelById(labelId)?.title,
|
||||
projectId => projectStore.projects[projectId]?.title || null,
|
||||
),
|
||||
}
|
||||
|
||||
if (JSON.stringify(view.value) !== JSON.stringify(transformed)) {
|
||||
view.value = transformed
|
||||
}
|
||||
},
|
||||
{immediate: true, deep: true},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => view.value,
|
||||
newView => {
|
||||
emit('update:modelValue', {
|
||||
...newView,
|
||||
filter: transformFilterStringForApi(
|
||||
newView.filter,
|
||||
labelTitle => labelStore.filterLabelsByQuery([], labelTitle)[0]?.id || null,
|
||||
projectTitle => {
|
||||
const found = projectStore.findProjectByExactname(projectTitle)
|
||||
return found?.id || null
|
||||
},
|
||||
),
|
||||
})
|
||||
},
|
||||
{deep: true},
|
||||
)
|
||||
|
||||
const model = defineModel<IProjectView>()
|
||||
const titleValid = ref(true)
|
||||
|
||||
function validateTitle() {
|
||||
titleValid.value = model.value.title !== ''
|
||||
titleValid.value = view.value?.title !== ''
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -23,14 +76,14 @@ function validateTitle() {
|
|||
<div class="control">
|
||||
<input
|
||||
id="title"
|
||||
v-model="model.title"
|
||||
v-model="view.title"
|
||||
v-focus
|
||||
class="input"
|
||||
:placeholder="$t('project.share.links.namePlaceholder')"
|
||||
@blur="validateTitle"
|
||||
>
|
||||
</div>
|
||||
<p
|
||||
<p
|
||||
v-if="!titleValid"
|
||||
class="help is-danger"
|
||||
>
|
||||
|
@ -49,7 +102,7 @@ function validateTitle() {
|
|||
<div class="select">
|
||||
<select
|
||||
id="kind"
|
||||
v-model="model.viewKind"
|
||||
v-model="view.viewKind"
|
||||
>
|
||||
<option value="list">
|
||||
{{ $t('project.list.title') }}
|
||||
|
@ -69,12 +122,12 @@ function validateTitle() {
|
|||
</div>
|
||||
|
||||
<FilterInput
|
||||
v-model="model.filter"
|
||||
v-model="view.filter"
|
||||
:input-label="$t('project.views.filter')"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="model.viewKind === 'kanban'"
|
||||
v-if="view.viewKind === 'kanban'"
|
||||
class="field"
|
||||
>
|
||||
<label
|
||||
|
@ -87,7 +140,7 @@ function validateTitle() {
|
|||
<div class="select">
|
||||
<select
|
||||
id="configMode"
|
||||
v-model="model.bucketConfigurationMode"
|
||||
v-model="view.bucketConfigurationMode"
|
||||
>
|
||||
<option value="manual">
|
||||
{{ $t('project.views.bucketConfigManual') }}
|
||||
|
@ -101,7 +154,7 @@ function validateTitle() {
|
|||
</div>
|
||||
|
||||
<div
|
||||
v-if="model.viewKind === 'kanban' && model.bucketConfigurationMode === 'filter'"
|
||||
v-if="view.viewKind === 'kanban' && view.bucketConfigurationMode === 'filter'"
|
||||
class="field"
|
||||
>
|
||||
<label class="label">
|
||||
|
@ -109,13 +162,13 @@ function validateTitle() {
|
|||
</label>
|
||||
<div class="control">
|
||||
<div
|
||||
v-for="(b, index) in model.bucketConfiguration"
|
||||
v-for="(b, index) in view.bucketConfiguration"
|
||||
:key="'bucket_'+index"
|
||||
class="filter-bucket"
|
||||
>
|
||||
<button
|
||||
class="is-danger"
|
||||
@click.prevent="() => model.bucketConfiguration.splice(index, 1)"
|
||||
@click.prevent="() => view.bucketConfiguration.splice(index, 1)"
|
||||
>
|
||||
<icon icon="trash-alt" />
|
||||
</button>
|
||||
|
@ -130,7 +183,7 @@ function validateTitle() {
|
|||
<div class="control">
|
||||
<input
|
||||
:id="'bucket_'+index+'_title'"
|
||||
v-model="model.bucketConfiguration[index].title"
|
||||
v-model="view.bucketConfiguration[index].title"
|
||||
class="input"
|
||||
:placeholder="$t('project.share.links.namePlaceholder')"
|
||||
>
|
||||
|
@ -138,7 +191,7 @@ function validateTitle() {
|
|||
</div>
|
||||
|
||||
<FilterInput
|
||||
v-model="model.bucketConfiguration[index].filter"
|
||||
v-model="view.bucketConfiguration[index].filter"
|
||||
:input-label="$t('project.views.filter')"
|
||||
/>
|
||||
</div>
|
||||
|
@ -147,7 +200,7 @@ function validateTitle() {
|
|||
<XButton
|
||||
variant="secondary"
|
||||
icon="plus"
|
||||
@click="() => model.bucketConfiguration.push({title: '', filter: ''})"
|
||||
@click="() => view.bucketConfiguration.push({title: '', filter: ''})"
|
||||
>
|
||||
{{ $t('project.kanban.addBucket') }}
|
||||
</XButton>
|
||||
|
|
|
@ -16,7 +16,22 @@
|
|||
:search-results="found"
|
||||
:label="searchLabel"
|
||||
@search="find"
|
||||
/>
|
||||
>
|
||||
<template #searchResult="{option: result}">
|
||||
<User
|
||||
v-if="shareType === 'user'"
|
||||
:avatar-size="24"
|
||||
:show-username="true"
|
||||
:user="result"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="search-result"
|
||||
>
|
||||
{{ result.name }}
|
||||
</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
</p>
|
||||
<p class="control">
|
||||
<x-button @click="add()">
|
||||
|
@ -173,6 +188,7 @@ import Nothing from '@/components/misc/nothing.vue'
|
|||
import {success} from '@/message'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {useConfigStore} from '@/stores/config'
|
||||
import User from '@/components/misc/user.vue'
|
||||
|
||||
// FIXME: I think this whole thing can now only manage user/team sharing for projects? Maybe remove a little generalization?
|
||||
|
||||
|
|
|
@ -21,12 +21,12 @@
|
|||
@dragendBar="updateGanttTask"
|
||||
@dblclickBar="openTask"
|
||||
>
|
||||
<template #timeunit="{value, date}">
|
||||
<template #timeunit="{date}">
|
||||
<div
|
||||
class="timeunit-wrapper"
|
||||
:class="{'today': dateIsToday(date)}"
|
||||
>
|
||||
<span>{{ value }}</span>
|
||||
<span>{{ date.getDate() }}</span>
|
||||
<span class="weekday">
|
||||
{{ weekDayFromDate(date) }}
|
||||
</span>
|
||||
|
|
|
@ -102,6 +102,7 @@
|
|||
class="mt-2"
|
||||
entity-kind="comments"
|
||||
:entity-id="c.id"
|
||||
:disabled="!canWrite"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -27,13 +27,13 @@ export const AUTOCOMPLETE_FIELDS = [
|
|||
]
|
||||
|
||||
export const AVAILABLE_FILTER_FIELDS = [
|
||||
'done',
|
||||
'priority',
|
||||
'percentDone',
|
||||
...DATE_FIELDS,
|
||||
...ASSIGNEE_FIELDS,
|
||||
...LABEL_FIELDS,
|
||||
...PROJECT_FIELDS,
|
||||
'done',
|
||||
'priority',
|
||||
'percentDone',
|
||||
]
|
||||
|
||||
export const FILTER_OPERATORS = [
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
@import "tooltip";
|
||||
@import "labels";
|
||||
@import "project";
|
||||
@import "task";
|
||||
@import "tasks";
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
// FIXME: should be a component <FilterContainer>
|
||||
// used in
|
||||
// - Kanban.vue
|
||||
// - Project.vue
|
||||
// - Table.vue
|
||||
|
||||
$filter-container-top-default: -59px;
|
||||
$filter-container-top-link-share-gantt: -133px;
|
||||
$filter-container-top-link-share-list: -47px;
|
||||
|
||||
.filter-container {
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
min-width: 400px;
|
||||
max-width: 180px;
|
||||
position: absolute;
|
||||
right: 1.5rem;
|
||||
margin-top: $filter-container-top-default;
|
||||
z-index: 4;
|
||||
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.button:not(:last-of-type) {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
height: $switch-view-height;
|
||||
}
|
||||
|
||||
.card {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
position: static;
|
||||
margin: 0 0 1rem 0 !important;
|
||||
max-width: 100%;
|
||||
min-width: auto;
|
||||
|
||||
.items {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.search {
|
||||
width: 100%;
|
||||
|
||||
.control:first-child {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.link-share-container .gantt-chart-container .filter-container,
|
||||
.gantt-chart-container .filter-container {
|
||||
right: 0;
|
||||
margin-top: calc(#{$filter-container-top-link-share-gantt - 2} - 7rem);
|
||||
}
|
||||
|
||||
.link-share-container .gantt-chart-container .filter-container {
|
||||
margin-top: calc(#{$filter-container-top-link-share-gantt} - 5rem);
|
||||
}
|
||||
|
||||
.link-share-container .list-view .filter-container {
|
||||
margin-top: $filter-container-top-link-share-list - 10px;
|
||||
}
|
||||
|
||||
.link-share-container.project\.table-view,
|
||||
.link-share-container.project\.list-view {
|
||||
.filter-container {
|
||||
right: 9rem;
|
||||
margin-top: $filter-container-top-default;
|
||||
}
|
||||
}
|
|
@ -89,7 +89,7 @@ async function saveView() {
|
|||
v-model="newView"
|
||||
class="mb-4"
|
||||
/>
|
||||
<div class="is-flex is-justify-content-end">
|
||||
<div class="is-flex is-justify-content-end mb-4">
|
||||
<XButton
|
||||
:loading="projectViewService.loading"
|
||||
@click="createView"
|
||||
|
|
|
@ -328,6 +328,7 @@
|
|||
entity-kind="tasks"
|
||||
:entity-id="task.id"
|
||||
class="details"
|
||||
:disabled="!canWrite"
|
||||
/>
|
||||
|
||||
<!-- Attachments -->
|
||||
|
|
|
@ -384,27 +384,27 @@ func (b *Bucket) Delete(s *xorm.Session, a web.Auth) (err error) {
|
|||
}
|
||||
|
||||
// Get the default bucket
|
||||
p, err := GetProjectViewByIDAndProject(s, b.ProjectViewID, b.ProjectID)
|
||||
pv, err := GetProjectViewByIDAndProject(s, b.ProjectViewID, b.ProjectID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var updateProject bool
|
||||
if b.ID == p.DefaultBucketID {
|
||||
p.DefaultBucketID = 0
|
||||
updateProject = true
|
||||
var updateProjectView bool
|
||||
if b.ID == pv.DefaultBucketID {
|
||||
pv.DefaultBucketID = 0
|
||||
updateProjectView = true
|
||||
}
|
||||
if b.ID == p.DoneBucketID {
|
||||
p.DoneBucketID = 0
|
||||
updateProject = true
|
||||
if b.ID == pv.DoneBucketID {
|
||||
pv.DoneBucketID = 0
|
||||
updateProjectView = true
|
||||
}
|
||||
if updateProject {
|
||||
err = p.Update(s, a)
|
||||
if updateProjectView {
|
||||
err = pv.Update(s, a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
defaultBucketID, err := getDefaultBucketID(s, p)
|
||||
defaultBucketID, err := getDefaultBucketID(s, pv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -216,10 +216,10 @@ func TestBucket_Delete(t *testing.T) {
|
|||
err := b.Delete(s, u)
|
||||
require.NoError(t, err)
|
||||
|
||||
db.AssertMissing(t, "project_views", map[string]interface{}{
|
||||
db.AssertExists(t, "project_views", map[string]interface{}{
|
||||
"id": b.ProjectViewID,
|
||||
"done_bucket_id": 0,
|
||||
})
|
||||
}, false)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -991,7 +991,12 @@ func (p *Project) Create(s *xorm.Session, a web.Auth) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
return p.ReadOne(s, a)
|
||||
fullProject, err := GetProjectSimpleByID(s, p.ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return fullProject.ReadOne(s, a)
|
||||
}
|
||||
|
||||
func (p *Project) isDefaultProject(s *xorm.Session) (is bool, err error) {
|
||||
|
|
|
@ -331,11 +331,19 @@ func (p *ProjectView) Update(s *xorm.Session, _ web.Auth) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
_, err = s.ID(p.ID).Update(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = s.
|
||||
ID(p.ID).
|
||||
Cols(
|
||||
"title",
|
||||
"view_kind",
|
||||
"filter",
|
||||
"position",
|
||||
"bucket_configuration_mode",
|
||||
"bucket_configuration",
|
||||
"default_bucket_id",
|
||||
"done_bucket_id",
|
||||
).
|
||||
Update(p)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user