diff --git a/docs/API.md b/docs/API.md index c3d40d265d..ed67000632 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1128,6 +1128,7 @@ Deck stores user and app configuration values globally and per board. The GET en | --- | --- | | calendar | Determines if the calendar/tasks integration through the CalDAV backend is enabled for the user (boolean) | | cardDetailsInModal | Determines if the bigger view is used (boolean) | +| hideNoDueOnOverview | Determines if the No Due Date column should be displayed | | cardIdBadge | Determines if the ID badges are displayed on cards (boolean) | | groupLimit | Determines if creating new boards is limited to certain groups of the instance. The resulting output is an array of group objects with the id and the displayname (Admin only)| @@ -1173,6 +1174,7 @@ Deck stores user and app configuration values globally and per board. The GET en | notify-due | `off`, `assigned` or `all` | | calendar | Boolean | | cardDetailsInModal | Boolean | +| hideNoDueOnOverview | Boolean | | cardIdBadge | Boolean | #### Example request diff --git a/lib/Controller/OverviewApiController.php b/lib/Controller/OverviewApiController.php index 9374425317..4777297df9 100644 --- a/lib/Controller/OverviewApiController.php +++ b/lib/Controller/OverviewApiController.php @@ -27,6 +27,7 @@ public function __construct( #[NoAdminRequired] public function upcomingCards(): DataResponse { - return new DataResponse($this->dashboardService->findUpcomingCards($this->userId)); + $hideNoDue = ($this->request->getParam('hideNoDueOnOverview') == "true"); + return new DataResponse($this->dashboardService->findUpcomingCards($this->userId, $hideNoDue)); } } diff --git a/lib/Dashboard/DeckWidgetUpcoming.php b/lib/Dashboard/DeckWidgetUpcoming.php index 4e46b435cd..76bd65eea7 100644 --- a/lib/Dashboard/DeckWidgetUpcoming.php +++ b/lib/Dashboard/DeckWidgetUpcoming.php @@ -96,7 +96,7 @@ public function load(): void { * @inheritDoc */ public function getItems(string $userId, ?string $since = null, int $limit = 7): array { - $upcomingCards = $this->dashboardService->findUpcomingCards($userId); + $upcomingCards = $this->dashboardService->findUpcomingCards($userId, false); $nowTimestamp = (new Datetime())->getTimestamp(); $sinceTimestamp = $since !== null ? (new Datetime($since))->getTimestamp() : null; $upcomingCards = array_filter($upcomingCards, static function (array $card) use ($nowTimestamp, $sinceTimestamp) { diff --git a/lib/Db/CardMapper.php b/lib/Db/CardMapper.php index 47f3e58af3..db998ed047 100644 --- a/lib/Db/CardMapper.php +++ b/lib/Db/CardMapper.php @@ -328,9 +328,10 @@ public function findAllWithDue(array $boardIds): array { /** * @param int[] $boardIds + * @param bool $filterNoDue * @return Card[] */ - public function findToMeOrNotAssignedCards(array $boardIds, string $username): array { + public function findToMeOrNotAssignedCards(array $boardIds, string $username, bool $filterNoDue): array { $qb = $this->db->getQueryBuilder(); $qb->select('c.*') ->from('deck_cards', 'c') @@ -349,6 +350,9 @@ public function findToMeOrNotAssignedCards(array $boardIds, string $username): a ->andWhere($qb->expr()->eq('s.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))) ->andWhere($qb->expr()->eq('b.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL))) ->andWhere($qb->expr()->eq('b.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))); + if($filterNoDue) { + $qb = $qb->andWhere($qb->expr()->isNotNull('duedate')); + } return $this->findEntities($qb); } diff --git a/lib/Service/ConfigService.php b/lib/Service/ConfigService.php index b4afb8fc45..e870a290b0 100644 --- a/lib/Service/ConfigService.php +++ b/lib/Service/ConfigService.php @@ -52,7 +52,8 @@ public function getAll(): array { $data = [ 'calendar' => $this->isCalendarEnabled(), 'cardDetailsInModal' => $this->isCardDetailsInModal(), - 'cardIdBadge' => $this->isCardIdBadgeEnabled() + 'cardIdBadge' => $this->isCardIdBadgeEnabled(), + 'hideNoDueOnOverview' => $this->isHideNoDueOnOverviewEnabled() ]; if ($this->groupManager->isAdmin($userId)) { $data['groupLimit'] = $this->get('groupLimit'); @@ -85,6 +86,11 @@ public function get(string $key) { return false; } return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', true); + case 'hideNoDueOnOverview': + if ($this->getUserId() === null) { + return false; + } + return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'hideNoDueOnOverview', true); case 'cardIdBadge': if ($this->getUserId() === null) { return false; @@ -123,6 +129,20 @@ public function isCardDetailsInModal(?int $boardId = null): bool { return (bool)$this->config->getUserValue($userId, Application::APP_ID, 'board:' . $boardId . ':cardDetailsInModal', $defaultState); } + public function isHideNoDueOnOverviewEnabled(?int $boardId = null): bool { + $userId = $this->getUserId(); + if ($userId === null) { + return false; + } + + $defaultState = (bool)$this->config->getUserValue($userId, Application::APP_ID, 'hideNoDueOnOverview', false); + if ($boardId === null) { + return $defaultState; + } + + return (bool)$this->config->getUserValue($userId, Application::APP_ID, 'board:' . $boardId . ':hideNoDueOnOverview', $defaultState); + } + public function isCardIdBadgeEnabled(): bool { $userId = $this->getUserId(); if ($userId === null) { @@ -177,6 +197,10 @@ public function set($key, $value) { $this->config->setUserValue($userId, Application::APP_ID, 'cardDetailsInModal', (string)$value); $result = $value; break; + case 'hideNoDueOnOverview': + $this->config->setUserValue($userId, Application::APP_ID, 'hideNoDueOnOverview', (string)$value); + $result = $value; + break; case 'cardIdBadge': $this->config->setUserValue($userId, Application::APP_ID, 'cardIdBadge', (string)$value); $result = $value; diff --git a/lib/Service/OverviewService.php b/lib/Service/OverviewService.php index bacd0b43ae..d5a92cee98 100644 --- a/lib/Service/OverviewService.php +++ b/lib/Service/OverviewService.php @@ -23,7 +23,7 @@ public function __construct( ) { } - public function findUpcomingCards(string $userId): array { + public function findUpcomingCards(string $userId, bool $filterNoDue): array { $userBoards = $this->boardMapper->findAllForUser($userId); $boardOwnerIds = array_filter(array_map(function (Board $board) { @@ -35,9 +35,9 @@ public function findUpcomingCards(string $userId): array { $foundCards = array_merge( // private board: get all my assigned or unassigned cards - $this->cardMapper->findToMeOrNotAssignedCards($boardOwnerIds, $userId), + $this->cardMapper->findToMeOrNotAssignedCards($boardOwnerIds, $userId, $filterNoDue), // shared board: get all my assigned or unassigned cards - $this->cardMapper->findToMeOrNotAssignedCards($boardSharedIds, $userId) + $this->cardMapper->findToMeOrNotAssignedCards($boardSharedIds, $userId, $filterNoDue) ); $this->cardService->enrichCards($foundCards); diff --git a/src/App.vue b/src/App.vue index 881f75ced9..aba4793edd 100644 --- a/src/App.vue +++ b/src/App.vue @@ -98,6 +98,14 @@ export default { this.$store.dispatch('setConfig', { cardDetailsInModal: newValue }) }, }, + hideNoDueOnOverview: { + get() { + return this.$store.getters.config('hideNoDueOnOverview') + }, + set(newValue) { + this.$store.dispatch('setConfig', { hideNoDueOnOverview: newValue }) + }, + }, }, created() { const initialState = loadState('deck', 'initialBoards', null) diff --git a/src/components/DeckAppSettings.vue b/src/components/DeckAppSettings.vue index 3935dbff5d..f336e16ef5 100644 --- a/src/components/DeckAppSettings.vue +++ b/src/components/DeckAppSettings.vue @@ -12,6 +12,8 @@ + @@ -119,6 +121,14 @@ export default { this.$store.dispatch('setConfig', { cardDetailsInModal: newValue }) }, }, + hideNoDueOnOverview: { + get() { + return this.$store.getters.config('hideNoDueOnOverview') + }, + set(newValue) { + this.$store.dispatch('setConfig', { hideNoDueOnOverview: newValue }) + }, + }, cardIdBadge: { get() { return this.$store.getters.config('cardIdBadge') diff --git a/src/components/overview/Overview.vue b/src/components/overview/Overview.vue index 74d10b7bf7..da568f771a 100644 --- a/src/components/overview/Overview.vue +++ b/src/components/overview/Overview.vue @@ -47,6 +47,7 @@ import CardItem from '../cards/CardItem.vue' import GlobalSearchResults from '../search/GlobalSearchResults.vue' import { useOverviewStore } from '../../stores/overview.js' import { mapActions, mapState } from 'pinia' +import { mapGetters } from 'vuex' const FILTER_UPCOMING = 'upcoming' @@ -75,11 +76,6 @@ const COLUMN_PROPS_LIST = [ title: 'Later', filter: 'later', }, - { - title: 'No due', - filter: 'nodue', - sort: false, - }, ] export default { @@ -96,9 +92,17 @@ export default { }, }, data() { + const dynamicList = [...COLUMN_PROPS_LIST]; + if(!this.$store.getters.config('hideNoDueOnOverview')) { + dynamicList.push({ + title: 'No due', + filter: 'nodue', + sort: false, + }); + } return { loading: true, - columnPropsList: COLUMN_PROPS_LIST, + columnPropsList: dynamicList, } }, computed: { @@ -113,6 +117,14 @@ export default { return '' } }, + hideNoDueOnOverview: { + get() { + return this.$store.getters.config('hideNoDueOnOverview') + }, + set(newValue) { + this.$store.dispatch('setConfig', { hideNoDueOnOverview: newValue }) + }, + }, ...mapState(useOverviewStore, ['assignedCards']), }, watch: { @@ -129,7 +141,7 @@ export default { this.loading = true try { if (this.filter === FILTER_UPCOMING) { - await this.loadUpcoming() + await this.loadUpcoming(this.$store.getters.config('hideNoDueOnOverview')) } } catch (e) { console.error(e) diff --git a/src/services/OverviewApi.js b/src/services/OverviewApi.js index 91aeecfce6..e6e6169a9f 100644 --- a/src/services/OverviewApi.js +++ b/src/services/OverviewApi.js @@ -12,9 +12,12 @@ export class OverviewApi { return generateOcsUrl(`apps/deck/api/v1.0/${url}`) } - get(filter) { + get(filter, hideNoDueOnOverview) { return axios.get(this.url(`overview/${filter}`), { headers: { 'OCS-APIRequest': 'true' }, + params: { + hideNoDueOnOverview: hideNoDueOnOverview, + } }) .then( (response) => Promise.resolve(response.data.ocs.data), diff --git a/src/stores/dashboard.js b/src/stores/dashboard.js index 9e417a29ab..143dd43399 100644 --- a/src/stores/dashboard.js +++ b/src/stores/dashboard.js @@ -13,8 +13,8 @@ export const useDashboardStore = defineStore('dashboard', { assignedCards: [], }), actions: { - async loadUpcoming() { - const upcommingCards = await apiClient.get('upcoming') + async loadUpcoming(hideNoDueOnOverview) { + const upcommingCards = await apiClient.get('upcoming', hideNoDueOnOverview) this.assignedCards = upcommingCards }, }, diff --git a/src/stores/overview.js b/src/stores/overview.js index 843c05a04c..2206b875d6 100644 --- a/src/stores/overview.js +++ b/src/stores/overview.js @@ -14,13 +14,13 @@ export const useOverviewStore = defineStore('overview', { loading: false, }), actions: { - async loadUpcoming() { + async loadUpcoming(hideNoDueOnOverview) { if (this.loading) { return this.loading } const promise = (async () => { this.$vuex.commit('setCurrentBoard', null) - const assignedCards = await overviewApi.get('upcoming') + const assignedCards = await overviewApi.get('upcoming', hideNoDueOnOverview) const assignedCardsFlat = Object.values(assignedCards).flat() for (const i in assignedCardsFlat) { this.$vuex.commit('addCard', assignedCardsFlat[i])