diff --git a/appinfo/routes.php b/appinfo/routes.php
index 4ab64f84a..6330381cf 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -66,6 +66,7 @@ return [
['name' => 'card#assignUser', 'url' => '/cards/{cardId}/assign', 'verb' => 'POST'],
['name' => 'card#unassignUser', 'url' => '/cards/{cardId}/unassign', 'verb' => 'PUT'],
+ // attachments
['name' => 'attachment#getAll', 'url' => '/cards/{cardId}/attachments', 'verb' => 'GET'],
['name' => 'attachment#create', 'url' => '/cards/{cardId}/attachment', 'verb' => 'POST'],
['name' => 'attachment#display', 'url' => '/cards/{cardId}/attachment/{attachmentId}', 'verb' => 'GET'],
@@ -110,6 +111,8 @@ return [
['name' => 'card_api#reorder', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/reorder', 'verb' => 'PUT'],
['name' => 'card_api#delete', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}', 'verb' => 'DELETE'],
+ ['name' => 'card_api#findAllWithDue', 'url' => '/api/v1.0/dashboard/due', 'verb' => 'GET'],
+
['name' => 'label_api#get', 'url' => '/api/v1.0/boards/{boardId}/labels/{labelId}', 'verb' => 'GET'],
['name' => 'label_api#create', 'url' => '/api/v1.0/boards/{boardId}/labels', 'verb' => 'POST'],
['name' => 'label_api#update', 'url' => '/api/v1.0/boards/{boardId}/labels/{labelId}', 'verb' => 'PUT'],
@@ -131,5 +134,9 @@ return [
['name' => 'comments_api#create', 'url' => '/api/v1.0/cards/{cardId}/comments', 'verb' => 'POST'],
['name' => 'comments_api#update', 'url' => '/api/v1.0/cards/{cardId}/comments/{commentId}', 'verb' => 'PUT'],
['name' => 'comments_api#delete', 'url' => '/api/v1.0/cards/{cardId}/comments/{commentId}', 'verb' => 'DELETE'],
+
+ // dashboard
+ ['name' => 'overview_api#findAllWithDue', 'url' => '/api/v1.0/overview/due', 'verb' => 'GET'],
+ ['name' => 'overview_api#findAssignedCards', 'url' => '/api/v1.0/overview/assigned', 'verb' => 'GET'],
]
];
diff --git a/lib/Controller/OverviewApiController.php b/lib/Controller/OverviewApiController.php
new file mode 100644
index 000000000..7635dc6c6
--- /dev/null
+++ b/lib/Controller/OverviewApiController.php
@@ -0,0 +1,61 @@
+
+ *
+ * @author Jakob Röhrl
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License 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 License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Deck\Controller;
+
+use OCA\Deck\Service\OverviewService;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
+use OCP\IRequest;
+
+class OverviewApiController extends OCSController {
+
+ /** @var OverviewService */
+ private $dashboardService;
+
+ /** @var string */
+ private $userId;
+
+ public function __construct($appName, IRequest $request, OverviewService $dashboardService, $userId) {
+ parent::__construct($appName, $request);
+ $this->dashboardService = $dashboardService;
+ $this->userId = $userId;
+ }
+
+ /**
+ * @NoAdminRequired
+ */
+ public function findAllWithDue(): DataResponse {
+ return new DataResponse($this->dashboardService->findAllWithDue($this->userId));
+ }
+
+ /**
+ * @NoAdminRequired
+ */
+ public function findAssignedCards(): DataResponse {
+ return new DataResponse($this->dashboardService->findAssignedCards($this->userId));
+ }
+}
diff --git a/lib/Db/CardMapper.php b/lib/Db/CardMapper.php
index ad2c97724..bbbcdb560 100644
--- a/lib/Db/CardMapper.php
+++ b/lib/Db/CardMapper.php
@@ -141,6 +141,23 @@ class CardMapper extends DeckMapper implements IPermissionMapper {
return $this->findEntities($sql, [$stackId], $limit, $offset);
}
+ public function findAllWithDue($boardId) {
+ $sql = 'SELECT c.* FROM `*PREFIX*deck_cards` c
+ INNER JOIN `*PREFIX*deck_stacks` s ON s.id = c.stack_id
+ INNER JOIN `*PREFIX*deck_boards` b ON b.id = s.board_id
+ WHERE `s`.`board_id` = ? AND duedate IS NOT NULL AND NOT c.archived AND c.deleted_at = 0 AND s.deleted_at = 0 AND NOT b.archived AND b.deleted_at = 0';
+ return $this->findEntities($sql, [$boardId]);
+ }
+
+ public function findAssignedCards($boardId, $username) {
+ $sql = 'SELECT c.* FROM `*PREFIX*deck_cards` c
+ INNER JOIN `*PREFIX*deck_stacks` s ON s.id = c.stack_id
+ INNER JOIN `*PREFIX*deck_boards` b ON b.id = s.board_id
+ INNER JOIN `*PREFIX*deck_assigned_users` u ON c.id = card_id
+ WHERE `s`.`board_id` = ? AND participant = ? AND NOT c.archived AND c.deleted_at = 0 AND s.deleted_at = 0 AND NOT b.archived AND b.deleted_at = 0';
+ return $this->findEntities($sql, [$boardId, $username]);
+ }
+
public function findOverdue() {
$sql = 'SELECT id,title,duedate,notified from `*PREFIX*deck_cards` WHERE duedate < NOW() AND NOT archived AND deleted_at = 0';
return $this->findEntities($sql);
diff --git a/lib/Service/CardService.php b/lib/Service/CardService.php
index 50ea6fa49..9d4867705 100644
--- a/lib/Service/CardService.php
+++ b/lib/Service/CardService.php
@@ -564,4 +564,28 @@ class CardService {
'\OCA\Deck\Card::onUpdate', new FTSEvent(null, ['id' => $cardId, 'card' => $card])
);
}
+
+ /**
+ *
+ * @return array
+ * @throws \OCA\Deck\NoPermissionException
+ * @throws BadRequestException
+ */
+ public function findAllWithDue($userId) {
+ $cards = $this->cardMapper->findAllWithDue($userId);
+
+ return $cards;
+ }
+
+ /**
+ *
+ * @return array
+ * @throws \OCA\Deck\NoPermissionException
+ * @throws BadRequestException
+ */
+ public function findAssignedCards($userId) {
+ $cards = $this->cardMapper->findAssignedCards($userId);
+
+ return $cards;
+ }
}
diff --git a/lib/Service/OverviewService.php b/lib/Service/OverviewService.php
new file mode 100644
index 000000000..fc0168c82
--- /dev/null
+++ b/lib/Service/OverviewService.php
@@ -0,0 +1,143 @@
+
+ *
+ * @author Julius Härtl
+ * @author Maxence Lange
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License 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 License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Deck\Service;
+
+use OCA\Deck\Db\AssignedUsersMapper;
+use OCA\Deck\Db\Card;
+use OCA\Deck\Db\CardMapper;
+use OCP\Comments\ICommentsManager;
+use OCP\IGroupManager;
+use OCA\Deck\Db\Board;
+use OCA\Deck\Db\BoardMapper;
+use OCA\Deck\Db\LabelMapper;
+use OCP\IUserManager;
+
+class OverviewService {
+
+ /** @var BoardMapper */
+ private $boardMapper;
+ /** @var LabelMapper */
+ private $labelMapper;
+ /** @var CardMapper */
+ private $cardMapper;
+ /** @var AssignedUsersMapper */
+ private $assignedUsersMapper;
+ /** @var IUserManager */
+ private $userManager;
+ /** @var IGroupManager */
+ private $groupManager;
+ /** @var ICommentsManager */
+ private $commentsManager;
+ /** @var AttachmentService */
+ private $attachmentService;
+
+ public function __construct(
+ BoardMapper $boardMapper,
+ LabelMapper $labelMapper,
+ CardMapper $cardMapper,
+ AssignedUsersMapper $assignedUsersMapper,
+ IUserManager $userManager,
+ IGroupManager $groupManager,
+ ICommentsManager $commentsManager,
+ AttachmentService $attachmentService
+ ) {
+ $this->boardMapper = $boardMapper;
+ $this->labelMapper = $labelMapper;
+ $this->cardMapper = $cardMapper;
+ $this->assignedUsersMapper = $assignedUsersMapper;
+ $this->userManager = $userManager;
+ $this->groupManager = $groupManager;
+ $this->commentsManager = $commentsManager;
+ $this->attachmentService = $attachmentService;
+ }
+
+ public function enrich(Card $card, string $userId): void {
+ $cardId = $card->getId();
+
+ $this->cardMapper->mapOwner($card);
+ $card->setAssignedUsers($this->assignedUsersMapper->find($cardId));
+ $card->setLabels($this->labelMapper->findAssignedLabelsForCard($cardId));
+ $card->setAttachmentCount($this->attachmentService->count($cardId));
+
+ $user = $this->userManager->get($userId);
+ if ($user !== null) {
+ $lastRead = $this->commentsManager->getReadMark('deckCard', (string)$card->getId(), $user);
+ $count = $this->commentsManager->getNumberOfCommentsForObject('deckCard', (string)$card->getId(), $lastRead);
+ $card->setCommentsUnread($count);
+ }
+ }
+
+ public function findAllWithDue(string $userId): array {
+ $userBoards = $this->findAllBoardsFromUser($userId);
+ $allDueCards = [];
+ foreach ($userBoards as $userBoard) {
+ $service = $this;
+ $allDueCards[] = array_map(static function ($card) use ($service, $userBoard, $userId) {
+ $service->enrich($card, $userId);
+ $cardData = $card->jsonSerialize();
+ $cardData['boardId'] = $userBoard->getId();
+ return $cardData;
+ }, $this->cardMapper->findAllWithDue($userBoard->getId()));
+ }
+ return $allDueCards;
+ }
+
+ public function findAssignedCards(string $userId): array {
+ $userBoards = $this->findAllBoardsFromUser($userId);
+ $allAssignedCards = [];
+ foreach ($userBoards as $userBoard) {
+ $service = $this;
+ $allAssignedCards[] = array_map(static function ($card) use ($service, $userBoard, $userId) {
+ $service->enrich($card, $userId);
+ $cardData = $card->jsonSerialize();
+ $cardData['boardId'] = $userBoard->getId();
+ return $cardData;
+ }, $this->cardMapper->findAssignedCards($userBoard->getId(), $userId));
+ }
+ return $allAssignedCards;
+ }
+
+ // FIXME: This is duplicate code with the board service
+
+ private function findAllBoardsFromUser(string $userId): array {
+ $userInfo = $this->getBoardPrerequisites($userId);
+ $userBoards = $this->boardMapper->findAllByUser($userInfo['user'], null, null);
+ $groupBoards = $this->boardMapper->findAllByGroups($userInfo['user'], $userInfo['groups'],null, null);
+ $circleBoards = $this->boardMapper->findAllByCircles($userInfo['user'], null, null);
+ return array_merge($userBoards, $groupBoards, $circleBoards);
+ }
+
+ private function getBoardPrerequisites($userId): array {
+ $user = $this->userManager->get($userId);
+ $groups = $user !== null ? $this->groupManager->getUserGroupIds($user) : [];
+ return [
+ 'user' => $userId,
+ 'groups' => $groups
+ ];
+ }
+}
diff --git a/src/components/Controls.vue b/src/components/Controls.vue
index 6a4fbb309..3017f9a96 100644
--- a/src/components/Controls.vue
+++ b/src/components/Controls.vue
@@ -30,6 +30,9 @@
({{ t('deck', 'Archived cards') }})
+
@import '../../css/animations.scss';
-
- $board-spacing: 15px;
- $stack-spacing: 10px;
- $stack-width: 300px;
+ @import '../../css/variables.scss';
form {
text-align: center;
diff --git a/src/components/board/Stack.vue b/src/components/board/Stack.vue
index 8f2b35186..27fe84eec 100644
--- a/src/components/board/Stack.vue
+++ b/src/components/board/Stack.vue
@@ -253,8 +253,7 @@ export default {
diff --git a/src/css/variables.scss b/src/css/variables.scss
new file mode 100644
index 000000000..ff339e192
--- /dev/null
+++ b/src/css/variables.scss
@@ -0,0 +1,5 @@
+$card-spacing: 10px;
+$card-padding: 10px;
+$stack-spacing: 10px;
+$stack-width: 260px;
+$board-spacing: 15px;
diff --git a/src/router.js b/src/router.js
index 3d42dd0aa..f5fce9f51 100644
--- a/src/router.js
+++ b/src/router.js
@@ -29,6 +29,7 @@ import Board from './components/board/Board'
import Sidebar from './components/Sidebar'
import BoardSidebar from './components/board/BoardSidebar'
import CardSidebar from './components/card/CardSidebar'
+import Overview from './components/overview/Overview'
Vue.use(Router)
@@ -41,6 +42,20 @@ export default new Router({
name: 'main',
component: Boards,
},
+ {
+ path: '/overview/:filter',
+ name: 'overview',
+ components: {
+ default: Overview,
+ },
+ props: {
+ default: (route) => {
+ return {
+ filter: route.params.filter,
+ }
+ },
+ },
+ },
{
path: '/board',
name: 'boards',
diff --git a/src/services/OverviewApi.js b/src/services/OverviewApi.js
new file mode 100644
index 000000000..8fac07ff9
--- /dev/null
+++ b/src/services/OverviewApi.js
@@ -0,0 +1,56 @@
+/*
+ * @copyright Copyright (c) 2020 Jakob Röhrl
+ *
+ * @author Jakob Röhrl
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License 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 License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+import axios from '@nextcloud/axios'
+import { generateOcsUrl } from '@nextcloud/router'
+
+export class OverviewApi {
+
+ url(url) {
+ return generateOcsUrl(`apps/deck/api/v1.0`) + url
+ }
+
+ findAllWithDue(data) {
+ return axios.get(this.url(`overview/due`), {
+ headers: { 'OCS-APIRequest': 'true' },
+ })
+ .then(
+ (response) => Promise.resolve(response.data.ocs.data),
+ (err) => Promise.reject(err)
+ )
+ .catch((err) => Promise.reject(err)
+ )
+ }
+
+ findMyAssignedCards(data) {
+ return axios.get(this.url(`overview/assigned`), {
+ headers: { 'OCS-APIRequest': 'true' },
+ })
+ .then(
+ (response) => Promise.resolve(response.data.ocs.data),
+ (err) => Promise.reject(err)
+ )
+ .catch((err) => Promise.reject(err)
+ )
+ }
+
+}
diff --git a/src/store/card.js b/src/store/card.js
index d3061cb18..4f4f4c987 100644
--- a/src/store/card.js
+++ b/src/store/card.js
@@ -96,9 +96,6 @@ export default {
},
},
mutations: {
- clearCards(state) {
- state.cards = []
- },
addCard(state, card) {
card.labels = card.labels || []
card.assignedUsers = card.assignedUsers || []
diff --git a/src/store/main.js b/src/store/main.js
index 6189fd934..d66149cf5 100644
--- a/src/store/main.js
+++ b/src/store/main.js
@@ -26,12 +26,13 @@ import Vue from 'vue'
import Vuex from 'vuex'
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
-import { BoardApi } from './../services/BoardApi'
+import { BoardApi } from '../services/BoardApi'
import stack from './stack'
import card from './card'
import comment from './comment'
import trashbin from './trashbin'
import attachment from './attachment'
+import overview from './overview'
import debounce from 'lodash/debounce'
Vue.use(Vuex)
@@ -51,6 +52,7 @@ export default new Vuex.Store({
comment,
trashbin,
attachment,
+ overview,
},
strict: debug,
state: {
diff --git a/src/store/overview.js b/src/store/overview.js
new file mode 100644
index 000000000..438ee8e59
--- /dev/null
+++ b/src/store/overview.js
@@ -0,0 +1,71 @@
+/*
+ * @copyright Copyright (c) 2020 Jakob Röhrl
+ *
+ * @author Jakob Röhrl
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License 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 License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+import Vue from 'vue'
+import Vuex from 'vuex'
+import { OverviewApi } from '../services/OverviewApi'
+Vue.use(Vuex)
+
+const apiClient = new OverviewApi()
+export default {
+ state: {
+ withDue: [],
+ assignedCards: [],
+ },
+ getters: {
+ withDueDashboard: state => {
+ return state.withDue
+ },
+ assignedCardsDashboard: state => {
+ return state.assignedCards
+ },
+ },
+ mutations: {
+ setWithDueDashboard(state, withDue) {
+ state.withDue = withDue
+ },
+ setAssignedCards(state, assignedCards) {
+ state.assignedCards = assignedCards
+ },
+ },
+ actions: {
+ async loadDueDashboard({ commit }) {
+ commit('setCurrentBoard', null)
+ const cardsWithDueDate = await apiClient.findAllWithDue()
+ const withDueFlat = cardsWithDueDate.flat()
+ for (const i in withDueFlat) {
+ commit('addCard', withDueFlat[i])
+ }
+ commit('setWithDueDashboard', withDueFlat)
+ },
+
+ async loadAssignDashboard({ commit }) {
+ commit('setCurrentBoard', null)
+ const assignedCards = await apiClient.findMyAssignedCards()
+ const assignedCardsFlat = assignedCards.flat()
+ for (const i in assignedCardsFlat) {
+ commit('addCard', assignedCardsFlat[i])
+ }
+ commit('setAssignedCards', assignedCardsFlat)
+ },
+ },
+}
diff --git a/src/store/stack.js b/src/store/stack.js
index afaa1a496..3f428e71f 100644
--- a/src/store/stack.js
+++ b/src/store/stack.js
@@ -76,7 +76,6 @@ export default {
})
},
async loadStacks({ commit }, boardId) {
- commit('clearCards')
let call = 'loadStacks'
if (this.state.showArchived === true) {
call = 'loadArchivedStacks'