From 523ce993c57e268c390bedc60aa387f63839a47c Mon Sep 17 00:00:00 2001 From: Victor Bianchi Date: Mon, 30 Sep 2024 16:28:13 -0300 Subject: [PATCH] feat: implement file sorting Signed-off-by: Victor Bianchi --- lib/Controller/FileController.php | 10 +++- lib/Db/SignRequestMapper.php | 15 +++++- lib/Service/FileService.php | 8 ++- src/Components/File/File.vue | 8 +-- src/store/files.js | 53 +++++++++++--------- src/store/filesSorting.js | 2 +- src/views/FilesList/FilesList.vue | 2 +- src/views/FilesList/FilesListTableHeader.vue | 6 +-- src/views/FilesList/VirtualList.vue | 2 +- 9 files changed, 68 insertions(+), 38 deletions(-) diff --git a/lib/Controller/FileController.php b/lib/Controller/FileController.php index bf3d416bd..e9b275737 100644 --- a/lib/Controller/FileController.php +++ b/lib/Controller/FileController.php @@ -197,6 +197,8 @@ public function validate(?string $type = null, $identifier = null): DataResponse * @param int|null $length Total of elements to return * @param int|null $start Start date of signature request (UNIX timestamp) * @param int|null $end End date of signature request (UNIX timestamp) + * @param string|null $sortBy Name of the column to sort by + * @param string|null $sortDirection Ascending or descending order * @return DataResponse * * 200: OK @@ -212,6 +214,8 @@ public function list( ?array $status = null, ?int $start = null, ?int $end = null, + ?string $sortBy = null, + ?string $sortDirection = null, ): DataResponse { $filter = array_filter([ 'signer_uuid' => $signer_uuid, @@ -220,9 +224,13 @@ public function list( 'start' => $start, 'end' => $end, ], static function ($var) { return $var !== null; }); + $sort = [ + 'sortBy' => $sortBy, + 'sortDirection' => $sortDirection, + ]; $return = $this->fileService ->setMe($this->userSession->getUser()) - ->listAssociatedFilesOfSignFlow($page, $length, $filter); + ->listAssociatedFilesOfSignFlow($page, $length, $filter, $sort); return new DataResponse($return, Http::STATUS_OK); } diff --git a/lib/Db/SignRequestMapper.php b/lib/Db/SignRequestMapper.php index 56aaf2a31..61a6c5c32 100644 --- a/lib/Db/SignRequestMapper.php +++ b/lib/Db/SignRequestMapper.php @@ -329,11 +329,12 @@ public function getFilesAssociatedFilesWithMeFormatted( array $filter, ?int $page = null, ?int $length = null, + ?array $sort = [], ): array { $filter['email'] = $user->getEMailAddress(); $filter['length'] = $length; $filter['page'] = $page; - $pagination = $this->getFilesAssociatedFilesWithMeStmt($user->getUID(), $filter); + $pagination = $this->getFilesAssociatedFilesWithMeStmt($user->getUID(), $filter, $sort); $pagination->setMaxPerPage($length); $pagination->setCurrentPage($page); $currentPageResults = $pagination->getCurrentPageResults(); @@ -493,7 +494,11 @@ private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, ?arra return $qb; } - private function getFilesAssociatedFilesWithMeStmt(string $userId, ?array $filter = []): Pagination { + private function getFilesAssociatedFilesWithMeStmt( + string $userId, + ?array $filter = [], + ?array $sort = [], + ): Pagination { $qb = $this->getFilesAssociatedFilesWithMeQueryBuilder($userId, $filter); $qb->select( 'f.id', @@ -504,6 +509,12 @@ private function getFilesAssociatedFilesWithMeStmt(string $userId, ?array $filte 'f.status', 'f.metadata', ); + if (!empty($sort) && in_array($sort['sortBy'], ['name', 'status', 'created_at'])) { + $qb->orderBy( + $qb->func()->lower('f.' . $sort['sortBy']), + $sort['sortDirection'] == 'asc' ? 'asc' : 'desc' + ); + } $qb->selectAlias('f.created_at', 'request_date'); $countQueryBuilderModifier = function (IQueryBuilder $qb): int { diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index 1d9632e77..da777230e 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -432,7 +432,12 @@ public function setFileByPath(string $path): self { * * @psalm-return array{data: array, pagination: array} */ - public function listAssociatedFilesOfSignFlow($page = null, $length = null, array $filter = []): array { + public function listAssociatedFilesOfSignFlow( + $page = null, + $length = null, + array $filter = [], + array $sort = [], + ): array { $page = $page ?? 1; $length = $length ?? (int)$this->appConfig->getAppValue('length_of_page', '100'); @@ -441,6 +446,7 @@ public function listAssociatedFilesOfSignFlow($page = null, $length = null, arra $filter, $page, $length, + $sort, ); $signers = $this->signRequestMapper->getByMultipleFileId(array_column($return['data'], 'id')); diff --git a/src/Components/File/File.vue b/src/Components/File/File.vue index 4be34805b..f5f8088a4 100644 --- a/src/Components/File/File.vue +++ b/src/Components/File/File.vue @@ -11,10 +11,10 @@ @load="backgroundFailed = false">
-
- {{ filesStore.files[currentNodeId].statusText !== 'none' ? filesStore.files[currentNodeId].statusText : '' }} +
+ {{ filesStore.files.get(currentNodeId).statusText !== 'none' ? filesStore.files.get(currentNodeId).statusText : '' }}
-

{{ filesStore.files[currentNodeId].name }}

+

{{ filesStore.files.get(currentNodeId).name }}

@@ -60,7 +60,7 @@ export default { return null } let previewUrl = '' - if (this.filesStore.files[this.currentNodeId]?.uuid?.length > 0) { + if (this.filesStore.files.get(this.currentNodeId)?.uuid?.length > 0) { previewUrl = generateOcsUrl('/apps/libresign/api/v1/file/thumbnail/{nodeId}', { nodeId: this.currentNodeId, }) diff --git a/src/store/files.js b/src/store/files.js index 1c57e5e75..28d0a486a 100644 --- a/src/store/files.js +++ b/src/store/files.js @@ -20,7 +20,7 @@ export const useFilesStore = function(...args) { const store = defineStore('files', { state: () => { return { - files: {}, + files: new Map(), selectedNodeId: 0, identifyingSigner: false, loading: false, @@ -30,7 +30,7 @@ export const useFilesStore = function(...args) { actions: { addFile(file) { - set(this.files, file.nodeId, file) + this.files.set(file.nodeId, file) this.hydrateFile(file.nodeId) }, selectFile(nodeId) { @@ -44,13 +44,13 @@ export const useFilesStore = function(...args) { sidebarStore.activeRequestSignatureTab() }, getFile() { - return this.files[this.selectedNodeId] ?? {} + return this.files.get(this.selectedNodeId) ?? {} }, async flushSelectedFile() { const files = await this.getAllFiles({ nodeId: this.selectedNodeId, }) - this.addFile(files[this.selectedNodeId]) + this.addFile(files.get(this.selectedNodeId)) }, enableIdentifySigner() { this.identifyingSigner = true @@ -65,7 +65,7 @@ export const useFilesStore = function(...args) { if (!Object.hasOwn(this.getFile(), 'signers')) { return false } - return this.files[this.selectedNodeId].signers.length > 0 + return this.files.get(this.selectedNodeId).signers.length > 0 }, isPartialSigned() { if (this.selectedNodeId === 0) { @@ -74,7 +74,7 @@ export const useFilesStore = function(...args) { if (!Object.hasOwn(this.getFile(), 'signers')) { return false } - return this.files[this.selectedNodeId].signers + return this.files.get(this.selectedNodeId).signers .filter(signer => signer.signed?.length > 0).length > 0 }, isFullSigned() { @@ -84,15 +84,15 @@ export const useFilesStore = function(...args) { if (!Object.hasOwn(this.getFile(), 'signers')) { return false } - return this.files[this.selectedNodeId].signers.length > 0 - && this.files[this.selectedNodeId].signers - .filter(signer => signer.signed?.length > 0).length === this.files[this.selectedNodeId].signers.length + return this.files.get(this.selectedNodeId).signers.length > 0 + && this.files.get(this.selectedNodeId).signers + .filter(signer => signer.signed?.length > 0).length === this.files.get(this.selectedNodeId).signers.length }, getSubtitle() { if (this.selectedNodeId === 0) { return '' } - const file = this.files[this.selectedNodeId] + const file = this.files.get(this.selectedNodeId) if ((file?.requested_by?.userId ?? '').length === 0 || file?.request_date.length === 0) { return '' } @@ -102,18 +102,18 @@ export const useFilesStore = function(...args) { }) }, async hydrateFile(nodeId) { - if (Object.hasOwn(this.files[nodeId], 'uuid')) { + if (Object.hasOwn(this.files.get(nodeId), 'uuid')) { return } await axios.get(generateOcsUrl('/apps/libresign/api/v1/file/validate/file_id/{fileId}', { fileId: nodeId, })) .then((response) => { - set(this.files, nodeId, response.data.ocs.data) - this.addUniqueIdentifierToAllSigners(this.files[nodeId].signers) + this.files.set(nodeId, response.data.ocs.data) + this.addUniqueIdentifierToAllSigners(this.files.get(nodeId).signers) }) .catch(() => { - set(this.files[nodeId], 'signers', []) + set(this.files.get(nodeId), 'signers', []) }) }, addUniqueIdentifierToAllSigners(signers) { @@ -134,17 +134,17 @@ export const useFilesStore = function(...args) { signerUpdate(signer) { this.addIdentifierToSigner(signer) // Remove if already exists - for (let i = this.files[this.selectedNodeId].signers.length - 1; i >= 0; i--) { - if (this.files[this.selectedNodeId].signers[i].identify === signer.identify) { - this.files[this.selectedNodeId].signers.splice(i, 1) + for (let i = this.files.get(this.selectedNodeId).signers.length - 1; i >= 0; i--) { + if (this.files.get(this.selectedNodeId).signers[i].identify === signer.identify) { + this.files.get(this.selectedNodeId).signers.splice(i, 1) break } - if (this.files[this.selectedNodeId].signers[i].signRequestId === signer.identify) { - this.files[this.selectedNodeId].signers.splice(i, 1) + if (this.files.get(this.selectedNodeId).signers[i].signRequestId === signer.identify) { + this.files.get(this.selectedNodeId).signers.splice(i, 1) break } } - this.files[this.selectedNodeId].signers.push(signer) + this.files.get(this.selectedNodeId).signers.push(signer) }, async deleteSigner(signer) { if (!isNaN(signer.signRequestId)) { @@ -155,9 +155,9 @@ export const useFilesStore = function(...args) { })) } set( - this.files[this.selectedNodeId], + this.files.get(this.selectedNodeId), 'signers', - this.files[this.selectedNodeId].signers.filter((i) => i.identify !== signer.identify), + this.files.get(this.selectedNodeId).signers.filter((i) => i.identify !== signer.identify), ) }, async getAllFiles(filter) { @@ -172,9 +172,14 @@ export const useFilesStore = function(...args) { filter.end = Math.floor(end / 1000) } const { sortingMode, sortingDirection } = useFilesSortingStore() - // TODO: pass sortingMode and sortingDirection to API call + if (sortingMode) { + filter.sortBy = sortingMode + } + if (sortingDirection) { + filter.sortDirection = sortingDirection + } const response = await axios.get(generateOcsUrl('/apps/libresign/api/v1/file/list'), { params: filter }) - this.files = {} + this.files = new Map() response.data.ocs.data.data.forEach(file => { this.addFile(file) }) diff --git a/src/store/filesSorting.js b/src/store/filesSorting.js index 70f8904a3..7e89b6617 100644 --- a/src/store/filesSorting.js +++ b/src/store/filesSorting.js @@ -11,7 +11,7 @@ const DEFAULT_SORTING_DIRECTION = 'asc' export const useFilesSortingStore = defineStore('filesSorting', { state: () => ({ - sortingMode: 'basename', + sortingMode: 'name', sortingDirection: DEFAULT_SORTING_DIRECTION, }), diff --git a/src/views/FilesList/FilesList.vue b/src/views/FilesList/FilesList.vue index 5be7eaab2..1cee2bffe 100644 --- a/src/views/FilesList/FilesList.vue +++ b/src/views/FilesList/FilesList.vue @@ -150,7 +150,7 @@ export default { return this.dirContentsFiltered }, isEmptyDir() { - return this.filesStore.files.length === 0 + return this.filesStore.files.size === 0 }, isRefreshing() { return !this.isEmptyDir diff --git a/src/views/FilesList/FilesListTableHeader.vue b/src/views/FilesList/FilesListTableHeader.vue index 32d8ea8a3..19211678b 100644 --- a/src/views/FilesList/FilesListTableHeader.vue +++ b/src/views/FilesList/FilesListTableHeader.vue @@ -13,7 +13,7 @@ - + @@ -59,8 +59,8 @@ export default { sort: true, }, { - title: t('libresign', 'Modified'), - id: 'modified', + title: t('libresign', 'Created at'), + id: 'created_at', sort: true, }, ], diff --git a/src/views/FilesList/VirtualList.vue b/src/views/FilesList/VirtualList.vue index e7a9a6c01..8c3404c72 100644 --- a/src/views/FilesList/VirtualList.vue +++ b/src/views/FilesList/VirtualList.vue @@ -14,7 +14,7 @@ :class="userConfigStore.grid_view ? 'files-list__tbody--grid' : 'files-list__tbody--list'" data-cy-files-list-tbody>