diff --git a/classes/submission/Repository.php b/classes/submission/Repository.php index 493d73df3f9..4f025c7287f 100644 --- a/classes/submission/Repository.php +++ b/classes/submission/Repository.php @@ -795,7 +795,7 @@ public function getUrlSubmissionWizard(Context $context, ?int $submissionId = nu /** * Get all views, views count to be retrieved separately due to performance reasons */ - public function getDashboardViews(Context $context, User $user): Collection + public function getDashboardViews(Context $context, User $user, array $selectedRoleIds = []): Collection { $types = DashboardView::getTypes()->flip(); $roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */ @@ -804,6 +804,10 @@ public function getDashboardViews(Context $context, User $user): Collection foreach ($roles as $role) { $roleIds[] = $role->getRoleId(); } + if($selectedRoleIds) { + $roleIds = array_values(array_intersect($roleIds, $selectedRoleIds)); + } + $canAccessUnassignedSubmission = !empty(array_intersect([Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_MANAGER], $roleIds)); $views = $this->mapDashboardViews($types, $context, $user, $canAccessUnassignedSubmission); diff --git a/classes/submission/maps/Schema.php b/classes/submission/maps/Schema.php index fd700b09fab..f51fb62a1ba 100644 --- a/classes/submission/maps/Schema.php +++ b/classes/submission/maps/Schema.php @@ -291,19 +291,25 @@ protected function getPropertyReviewAssignments(Submission $submission): array $request = Application::get()->getRequest(); $currentUser = $request->getUser(); - $context = $request->getContext(); - $due = is_null($reviewAssignment->getDateDue()) ? null : date('Y-m-d', strtotime($reviewAssignment->getDateDue())); - $responseDue = is_null($reviewAssignment->getDateResponseDue()) ? null : date('Y-m-d', strtotime($reviewAssignment->getDateResponseDue())); + $dateDue = is_null($reviewAssignment->getDateDue()) ? null : date('Y-m-d', strtotime($reviewAssignment->getDateDue())); + $dateResponseDue = is_null($reviewAssignment->getDateResponseDue()) ? null : date('Y-m-d', strtotime($reviewAssignment->getDateResponseDue())); + $dateConfirmed = is_null($reviewAssignment->getDateConfirmed()) ? null : date('Y-m-d', strtotime($reviewAssignment->getDateConfirmed())); + $dateCompleted = is_null($reviewAssignment->getDateCompleted()) ? null : date('Y-m-d', strtotime($reviewAssignment->getDateCompleted())); + $dateAssigned = is_null($reviewAssignment->getDateAssigned()) ? null : date('Y-m-d', strtotime($reviewAssignment->getDateAssigned())); $reviews[] = [ 'id' => (int) $reviewAssignment->getId(), 'isCurrentUserAssigned' => $currentUser->getId() == (int) $reviewAssignment->getReviewerId(), 'statusId' => (int) $reviewAssignment->getStatus(), 'status' => __($reviewAssignment->getStatusKey()), - 'due' => $due, - 'responseDue' => $responseDue, + 'dateDue' => $dateDue, + 'dateResponseDue' => $dateResponseDue, + 'dateConfirmed' => $dateConfirmed, + 'dateCompleted' => $dateCompleted, + 'dateAssigned' => $dateAssigned, 'round' => (int) $reviewAssignment->getRound(), 'roundId' => (int) $reviewAssignment->getReviewRoundId(), + 'recommendation' => $reviewAssignment->getRecommendation() ]; } diff --git a/classes/template/PKPTemplateManager.php b/classes/template/PKPTemplateManager.php index 4becaa89e41..50cf4b29341 100644 --- a/classes/template/PKPTemplateManager.php +++ b/classes/template/PKPTemplateManager.php @@ -975,11 +975,36 @@ public function setupBackendPage() if ($request->getContext()) { if (count(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_REVIEWER, Role::ROLE_ID_AUTHOR], $userRoles))) { - $menu['submissions'] = [ - 'name' => __('navigation.submissions'), - 'url' => $router->url($request, null, 'submissions'), - 'isCurrent' => $router->getRequestedPage($request) === 'submissions', - ]; + if(Config::getVar('features', 'enable_new_submission_listing')) { + if (count(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT], $userRoles))) { + $menu['dashboards'] = [ + 'name' => __('navigation.dashboards'), + 'url' => $router->url($request, null, 'dashboard', 'editorial'), + 'isCurrent' => $router->getRequestedPage($request) === 'dashboards', + ]; + } + if(count(array_intersect([ Role::ROLE_ID_REVIEWER], $userRoles))) { + $menu['reviewAssignments'] = [ + 'name' => __('navigation.reviewAssignments'), + 'url' => $router->url($request, null, 'dashboard', 'reviewAssignments'), + 'isCurrent' => $router->getRequestedPage($request) === 'reviewAssignments', + ]; + } + if(count(array_intersect([ Role::ROLE_ID_AUTHOR], $userRoles))) { + $menu['mySubmissions'] = [ + 'name' => __('navigation.mySubmissions'), + 'url' => $router->url($request, null, 'dashboard', 'mySubmissions'), + 'isCurrent' => $router->getRequestedPage($request) === 'mySubmissions', + ]; + } + } else { + $menu['submissions'] = [ + 'name' => __('navigation.submissions'), + 'url' => $router->url($request, null, 'submissions'), + 'isCurrent' => $router->getRequestedPage($request) === 'submissions', + ]; + + } } elseif (count($userRoles) === 1 && in_array(Role::ROLE_ID_READER, $userRoles)) { $menu['submit'] = [ 'name' => __('author.submit'), @@ -1206,7 +1231,7 @@ public function fetchJson($template, $status = true) public function display($template = null, $cache_id = null, $compile_id = null, $parent = null) { // Output global constants and locale keys used in new component library - $output = ''; + $output = 'window.pkp = window.pkp || {};'; if (!empty($this->_constants)) { $output .= 'pkp.const = ' . json_encode($this->_constants) . ';'; } @@ -1217,7 +1242,8 @@ public function display($template = null, $cache_id = null, $compile_id = null, $context = $request->getContext(); $pageContext = [ - 'apiBaseUrl' => $dispatcher->url($request, PKPApplication::ROUTE_API, $context?->getPath() ?: 'index') + 'apiBaseUrl' => $dispatcher->url($request, PKPApplication::ROUTE_API, $context?->getPath() ?: 'index'), + 'pageBaseUrl' => $dispatcher->url($request, PKPApplication::ROUTE_PAGE, $context?->getPath() ?: 'index') . '/' ]; $output .= 'pkp.context = ' . json_encode($pageContext) . ';'; @@ -1246,7 +1272,7 @@ public function display($template = null, $cache_id = null, $compile_id = null, 'pkpAppData', $output, [ - 'priority' => self::STYLE_SEQUENCE_LATE, + 'priority' => self::STYLE_SEQUENCE_NORMAL, 'contexts' => ['backend'], 'inline' => true, ] diff --git a/locale/en/common.po b/locale/en/common.po index 5cf11ec8dab..9abce0630f8 100644 --- a/locale/en/common.po +++ b/locale/en/common.po @@ -1558,6 +1558,15 @@ msgstr "Step {$step}" msgid "navigation.submissions" msgstr "Submissions" +msgid "navigation.dashboards" +msgstr "Dashboards" + +msgid "navigation.reviewAssignments" +msgstr "Review Assignments" + +msgid "navigation.mySubmissions" +msgstr "My Submissions" + msgid "navigation.system" msgstr "System" diff --git a/locale/en/submission.po b/locale/en/submission.po index d2f61c6594d..ff553ed0de2 100644 --- a/locale/en/submission.po +++ b/locale/en/submission.po @@ -2425,3 +2425,78 @@ msgstr "Competing Interests" msgid "author.competingInterests.description" msgstr "Please disclose any competing interests this author may have with the research subject." + +msgid "submission.list.reviewAssignment.statusAwaitingResponse.title" +msgstr "Awaiting Response from the reviewer" + +msgid "submission.list.reviewAssignment.statusAwaitingResponse.description" +msgstr "Review request has been shared with reviewer. Awaiting response in {$days} days on {$date}" + +msgid "submission.list.reviewAssignment.statusDeclined.title" +msgstr "Review Request declined on {$date}" + +msgid "submission.list.reviewAssignment.statusDeclined.description" +msgstr "Reviewer declined the review request on {$date}" + +msgid "submission.list.reviewAssignment.statusResponseOverdue.title" +msgstr "Review Request overdue by {$days} days" + +msgid "submission.list.reviewAssignment.statusResponseOverdue.description" +msgstr "This reviewer has not responded to the review request. A response was due on {$date}" + +msgid "submission.list.reviewAssignment.statusAccepted.title" +msgstr "Ongoing review - request accepted" + +msgid "submission.list.reviewAssignment.statusAccepted.description" +msgstr "This reviewer has accepted the review request. Their review is due in {$days} days on {$date}." + +msgid "submission.list.reviewAssignment.statusReviewOverdue.title" +msgstr "Review overdue by {$days} days" + +msgid "submission.list.reviewAssignment.statusReviewOverdue.description" +msgstr "This reviewer has not completed their review. A response was due on {$date}." + +msgid "submission.list.reviewAssignment.statusReceived.title" +msgstr "Review completed on {$date}" + +msgid "submission.list.reviewAssignment.statusReceived.description" +msgstr "The review was completed on {$date} with the following recommendation: {$recommendation}" + +msgid "submission.list.reviewAssignment.statusComplete.title" +msgstr "Review was confirmed by editor" + +msgid "submission.list.reviewAssignment.statusComplete.description" +msgstr "The review was accepted by the editor on {$date}." + +msgid "submission.list.reviewAssignment.statusCancelled.title" +msgstr "Reviewer cancelled review request" + +msgid "submission.list.reviewAssignment.statusCancelled.description" +msgstr "Reviewer has cancelled the review request on {$date}" + +msgid "submission.list.reviewAssignment.statusRequestResend.title" +msgstr "Awaiting Response from the reviewer" + +msgid "submission.list.reviewAssignment.statusRequestResend.description" +msgstr "Review request has been reshared with reviewer. Awaiting response in {$days} days on {$date}" + +msgid "submission.list.reviewAssignment.action.viewDetails" +msgstr "View details" + +msgid "submission.list.reviewAssignment.action.resendReviewRequest" +msgstr "Resend Review Request" + +msgid "submission.list.reviewAssignment.action.cancelReviewer" +msgstr "Cancel Reviewer" + +msgid "submission.list.reviewAssignment.action.unassignReviewer" +msgstr "Unassign" + +msgid "submission.list.reviewAssignment.action.editDueDate" +msgstr "Edit Due Date" + +msgid "submission.list.reviewAssignment.action.viewRecommendation" +msgstr "View recommendation" + +msgid "submission.list.reviewAssignment.action.viewUnreadRecommendation" +msgstr "View unread recommendation" \ No newline at end of file diff --git a/pages/dashboard/DashboardHandlerNext.php b/pages/dashboard/DashboardHandlerNext.php index 61548a45252..788ca877de6 100644 --- a/pages/dashboard/DashboardHandlerNext.php +++ b/pages/dashboard/DashboardHandlerNext.php @@ -21,21 +21,27 @@ use APP\template\TemplateManager; use PKP\components\forms\dashboard\SubmissionFilters; use PKP\core\JSONMessage; -use PKP\core\PKPApplication; use PKP\core\PKPRequest; -use PKP\db\DAORegistry; use PKP\plugins\Hook; use PKP\security\authorization\PKPSiteAccessPolicy; use PKP\security\Role; use PKP\submission\DashboardView; -use PKP\submission\GenreDAO; -use PKP\submission\PKPSubmission; +use PKP\submission\reviewAssignment\ReviewAssignment; +use PKP\submission\reviewRound\ReviewRound; define('SUBMISSIONS_LIST_ACTIVE', 'active'); define('SUBMISSIONS_LIST_ARCHIVE', 'archive'); define('SUBMISSIONS_LIST_MY_QUEUE', 'myQueue'); define('SUBMISSIONS_LIST_UNASSIGNED', 'unassigned'); +enum DashboardPage: string +{ + case EditorialDashboard = 'editorialDashboard'; + case MyReviewAssignments = 'myReviewAssignments'; + case MySubmissions = 'mySubmissions'; +} + + class DashboardHandlerNext extends Handler { /** @copydoc PKPHandler::_isBackendPage */ @@ -43,16 +49,43 @@ class DashboardHandlerNext extends Handler public int $perPage = 30; + /** Identify in which context is looking at the submissions */ + public DashboardPage $dashboardPage; + + /** + * editorial, review_assignments + */ + public array $selectedRoleIds = []; /** * Constructor */ - public function __construct() + public function __construct(DashboardPage $dashboardPage) { parent::__construct(); + $this->dashboardPage = $dashboardPage; + + if($this->dashboardPage === DashboardPage::EditorialDashboard) { + $this->selectedRoleIds = [Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT]; + } elseif($this->dashboardPage === DashboardPage::MyReviewAssignments) { + $this->selectedRoleIds = [Role::ROLE_ID_REVIEWER]; + } else { + $this->selectedRoleIds = [Role::ROLE_ID_AUTHOR]; + } + + $this->addRoleAssignment( + [Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT], + ['index', 'editorial'] + ); + $this->addRoleAssignment( - [Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_AUTHOR, Role::ROLE_ID_REVIEWER, Role::ROLE_ID_ASSISTANT], - ['index', 'tasks', 'myQueue', 'unassigned', 'active', 'archives'] + Role::ROLE_ID_REVIEWER, + ['reviewAssignments'] + ); + + $this->addRoleAssignment( + Role::ROLE_ID_AUTHOR, + ['mySubmissions'] ); } @@ -84,7 +117,6 @@ public function index($args, $request) $this->setupTemplate($request); $userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES); - $apiUrl = $dispatcher->url($request, PKPApplication::ROUTE_API, $context->getPath(), '_submissions'); $sections = Repo::section() ->getCollector() @@ -103,27 +135,10 @@ public function index($args, $request) $categories ); - $collector = Repo::submission() - ->getCollector() - ->filterByContextIds([(int) $request->getContext()->getId()]) - ->filterByStatus([PKPSubmission::STATUS_QUEUED]); - - if (empty(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN], $userRoles))) { - $collector->assignedTo([(int) $request->getUser()->getId()]); - } - - $userGroups = Repo::userGroup() - ->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); - - /** @var GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($context->getId())->toArray(); $templateMgr->setState([ 'pageInitConfig' => [ - 'apiUrl' => $apiUrl, + 'dashboardPage' => $this->dashboardPage, 'assignParticipantUrl' => $dispatcher->url( $request, Application::ROUTE_COMPONENT, @@ -137,17 +152,7 @@ public function index($args, $request) ] ), 'countPerPage' => $this->perPage, - 'currentViewId' => 'active', 'filtersForm' => $filtersForm->getConfig(), - 'submissions' => Repo::submission() - ->getSchemaMap() - ->mapManyToSubmissionsList( - $collector->limit($this->perPage)->getMany(), - $userGroups, - $genres - ) - ->values(), - 'submissionsCount' => $collector->limit(null)->getCount(), 'views' => $this->getViews(), 'columns' => $this->getColumns(), @@ -162,11 +167,71 @@ public function index($args, $request) $templateMgr->setConstants([ 'STAGE_STATUS_SUBMISSION_UNASSIGNED' => Repo::submission()::STAGE_STATUS_SUBMISSION_UNASSIGNED, + 'REVIEW_ASSIGNMENT_STATUS_AWAITING_RESPONSE' => ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_AWAITING_RESPONSE, + 'REVIEW_ASSIGNMENT_STATUS_RESPONSE_OVERDUE' => ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_RESPONSE_OVERDUE, + 'REVIEW_ASSIGNMENT_STATUS_REVIEW_OVERDUE' => ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_REVIEW_OVERDUE, + 'REVIEW_ASSIGNMENT_STATUS_ACCEPTED' => ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_ACCEPTED, + 'REVIEW_ASSIGNMENT_STATUS_RECEIVED' => ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_RECEIVED, + 'REVIEW_ASSIGNMENT_STATUS_COMPLETE' => ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_COMPLETE, + 'REVIEW_ASSIGNMENT_STATUS_THANKED' => ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_THANKED, + 'REVIEW_ASSIGNMENT_STATUS_CANCELLED' => ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_CANCELLED, + 'REVIEW_ASSIGNMENT_STATUS_REQUEST_RESEND' => ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_REQUEST_RESEND, + 'REVIEW_ROUND_STATUS_PENDING_REVIEWERS' => ReviewRound::REVIEW_ROUND_STATUS_PENDING_REVIEWERS, + 'REVIEW_ROUND_STATUS_REVIEWS_READY' => ReviewRound::REVIEW_ROUND_STATUS_REVIEWS_READY, + 'REVIEW_ROUND_STATUS_REVIEWS_COMPLETED' => ReviewRound::REVIEW_ROUND_STATUS_REVIEWS_COMPLETED, + 'REVIEW_ROUND_STATUS_REVIEWS_OVERDUE' => ReviewRound::REVIEW_ROUND_STATUS_REVIEWS_OVERDUE, + 'REVIEW_ROUND_STATUS_REVISIONS_SUBMITTED' => ReviewRound::REVIEW_ROUND_STATUS_REVISIONS_SUBMITTED, + 'REVIEW_ROUND_STATUS_REVISIONS_REQUESTED' => ReviewRound::REVIEW_ROUND_STATUS_REVISIONS_REQUESTED, + 'SUBMISSION_REVIEW_METHOD_ANONYMOUS' => ReviewAssignment::SUBMISSION_REVIEW_METHOD_ANONYMOUS, + 'SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS' => ReviewAssignment::SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS, + 'SUBMISSION_REVIEW_METHOD_OPEN' => ReviewAssignment::SUBMISSION_REVIEW_METHOD_OPEN, + + 'SUBMISSION_REVIEWER_RECOMMENDATION_ACCEPT' => ReviewAssignment::SUBMISSION_REVIEWER_RECOMMENDATION_ACCEPT, + 'SUBMISSION_REVIEWER_RECOMMENDATION_PENDING_REVISIONS' => ReviewAssignment::SUBMISSION_REVIEWER_RECOMMENDATION_PENDING_REVISIONS, + 'SUBMISSION_REVIEWER_RECOMMENDATION_RESUBMIT_HERE' => ReviewAssignment::SUBMISSION_REVIEWER_RECOMMENDATION_RESUBMIT_HERE, + 'SUBMISSION_REVIEWER_RECOMMENDATION_RESUBMIT_ELSEWHERE' => ReviewAssignment::SUBMISSION_REVIEWER_RECOMMENDATION_RESUBMIT_ELSEWHERE, + 'SUBMISSION_REVIEWER_RECOMMENDATION_DECLINE' => ReviewAssignment::SUBMISSION_REVIEWER_RECOMMENDATION_DECLINE, + 'SUBMISSION_REVIEWER_RECOMMENDATION_SEE_COMMENTS' => ReviewAssignment::SUBMISSION_REVIEWER_RECOMMENDATION_SEE_COMMENTS, + ]); $templateMgr->display('dashboard/editors.tpl'); } + + /** + * Display about index page. + * + * @param PKPRequest $request + * @param array $args + */ + public function editorial($args, $request) + { + return $this->index($args, $request); + } + + /** + * Display Review Assignments page. + * + * @param PKPRequest $request + * @param array $args + */ + public function reviewAssignments($args, $request) + { + return $this->index($args, $request); + } + + /** + * Display My Submissions page. + * + * @param PKPRequest $request + * @param array $args + */ + public function mySubmissions($args, $request) + { + return $this->index($args, $request); + } + /** * View tasks popup * @@ -194,7 +259,7 @@ protected function getViews(): array $context = $request->getContext(); $user = $request->getUser(); $userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES); - $dashboardViews = Repo::submission()->getDashboardViews($context, $user); + $dashboardViews = Repo::submission()->getDashboardViews($context, $user, $this->selectedRoleIds); $viewsData = $dashboardViews->map(fn (DashboardView $dashboardView) => $dashboardView->getData())->values()->toArray(); Hook::call('Dashboard::views', [&$viewsData, $userRoles]); @@ -207,16 +272,27 @@ protected function getViews(): array * * @hook Dashboard::columns [[&$columns, $userRoles]] */ - protected function getColumns(): array + public function getColumns(): array { - $columns = [ - $this->createColumn('id', __('common.id'), 'columnId', true), - $this->createColumn('title', __('navigation.submissions'), 'columnTitle'), - $this->createColumn('stage', __('workflow.stage'), 'columnStage'), - $this->createColumn('days', __('editor.submission.days'), 'columnDays'), - $this->createColumn('activity', __('stats.editorialActivity'), 'columnActivity'), - $this->createColumn('actions', __('admin.jobs.list.actions'), 'columnActions') - ]; + $columns = []; + + if($this->dashboardPage === DashboardPage::MyReviewAssignments) { + $columns = [ + $this->createColumn('id', __('common.id'), 'ColumnReviewAssignmentId', true), + $this->createColumn('title', __('navigation.submissions'), 'ColumnReviewAssignmentTitle'), + $this->createColumn('activity', __('stats.editorialActivity'), 'ColumnReviewAssignmentActivity'), + $this->createColumn('actions', __('admin.jobs.list.actions'), 'ColumnReviewAssignmentActions') + ]; + } else { + $columns = [ + $this->createColumn('id', __('common.id'), 'ColumnSubmissionId', true), + $this->createColumn('title', __('navigation.submissions'), 'ColumnSubmissionTitle'), + $this->createColumn('stage', __('workflow.stage'), 'ColumnSubmissionStage'), + $this->createColumn('days', __('editor.submission.days'), 'ColumnSubmissionDays'), + $this->createColumn('activity', __('stats.editorialActivity'), 'ColumnSubmissionActivity'), + $this->createColumn('actions', __('admin.jobs.list.actions'), 'ColumnSubmissionActions') + ]; + } $userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES); diff --git a/pages/dashboard/index.php b/pages/dashboard/index.php new file mode 100644 index 00000000000..f591107206b --- /dev/null +++ b/pages/dashboard/index.php @@ -0,0 +1,29 @@ +[^'"`]+)['"`]/g; + const regex = /(?:^|\W)tk?\([\s]*['"`](?[^'"`]+)['"`]/g; extraKeys ||= [];