diff --git a/ProcessMaker/Http/Controllers/Api/UserConfigurationController.php b/ProcessMaker/Http/Controllers/Api/UserConfigurationController.php index 64ea095fd1..d325c1636b 100644 --- a/ProcessMaker/Http/Controllers/Api/UserConfigurationController.php +++ b/ProcessMaker/Http/Controllers/Api/UserConfigurationController.php @@ -25,6 +25,9 @@ class UserConfigurationController extends Controller 'tasks' => [ 'isMenuCollapse' => true, ], + 'tasks_inbox' => [ + 'isMenuCollapse' => false, + ], ]; public function index() @@ -54,6 +57,7 @@ public function store(Request $request) 'ui_configuration.cases' => 'required|array', 'ui_configuration.requests' => 'required|array', 'ui_configuration.tasks' => 'required|array', + 'ui_configuration.tasks_inbox' => 'required|array', ]); $uiConfiguration = json_encode($request->input('ui_configuration')); diff --git a/ProcessMaker/Http/Controllers/HomeController.php b/ProcessMaker/Http/Controllers/HomeController.php index df81ab10f9..67a7b264d7 100644 --- a/ProcessMaker/Http/Controllers/HomeController.php +++ b/ProcessMaker/Http/Controllers/HomeController.php @@ -14,14 +14,22 @@ public function index(Request $request) if (Auth::check()) { // Redirect to home dynamic only if the package was enable if (hasPackage('package-dynamic-ui')) { - $user = \Auth::user(); - $homePage = \ProcessMaker\Package\PackageDynamicUI\Models\DynamicUI::getHomePage($user); + $user = Auth::user(); - return redirect($homePage); + // Check if there is at least one custom dashboard per user + $customDashboardExists = \ProcessMaker\Package\PackageDynamicUI\Models\DynamicUI::where('type', 'DASHBOARD') + ->where('assignable_id', $user->id) + ->count() > 0; + + if ($customDashboardExists) { + $homePage = \ProcessMaker\Package\PackageDynamicUI\Models\DynamicUI::getHomePage($user); + + return redirect($homePage); + } } // Redirect to the default view - return redirect('/requests'); + return redirect('/tasks'); } } diff --git a/ProcessMaker/Http/Controllers/TaskController.php b/ProcessMaker/Http/Controllers/TaskController.php index fa4c945454..63c2fb474b 100755 --- a/ProcessMaker/Http/Controllers/TaskController.php +++ b/ProcessMaker/Http/Controllers/TaskController.php @@ -58,7 +58,9 @@ public function index() $taskDraftsEnabled = TaskDraft::draftsEnabled(); - return view('tasks.index', compact('title', 'userFilter', 'defaultColumns', 'taskDraftsEnabled')); + $userConfiguration = (new UserConfigurationController())->index()['ui_configuration'] ?? []; + + return view('tasks.index', compact('title', 'userFilter', 'defaultColumns', 'taskDraftsEnabled', 'userConfiguration')); } public function edit(ProcessRequestToken $task, string $preview = '') diff --git a/resources/js/processes-catalogue/components/Process.vue b/resources/js/processes-catalogue/components/Process.vue index 3c72323ca5..1096491bcc 100644 --- a/resources/js/processes-catalogue/components/Process.vue +++ b/resources/js/processes-catalogue/components/Process.vue @@ -25,11 +25,13 @@ @@ -44,7 +46,7 @@ import ProcessDescription from "./optionsMenu/ProcessDescription.vue"; import ProcessCounter from "./optionsMenu/ProcessCounter.vue"; export default { - props: ["process", "processId"], + props: ["process", "processId", "ellipsisPermission"], components: { ProcessInfo, ProcessScreen, MiniPieChart, Bookmark, ProcessDescription, ProcessCounter }, diff --git a/resources/js/processes-catalogue/components/ProcessCollapseInfo.vue b/resources/js/processes-catalogue/components/ProcessCollapseInfo.vue index 6dd2ef0f41..5439cf254a 100644 --- a/resources/js/processes-catalogue/components/ProcessCollapseInfo.vue +++ b/resources/js/processes-catalogue/components/ProcessCollapseInfo.vue @@ -3,6 +3,7 @@
+ :permission="$root.permission || ellipsisPermission" />
@@ -62,6 +62,10 @@ export default { enableCollapse: { type: Boolean, default: true + }, + ellipsisPermission: { + type: Array, + default: () => [] } }, data() { diff --git a/resources/js/processes-catalogue/components/ProcessInfo.vue b/resources/js/processes-catalogue/components/ProcessInfo.vue index b16fc7b969..77669f2ab6 100644 --- a/resources/js/processes-catalogue/components/ProcessInfo.vue +++ b/resources/js/processes-catalogue/components/ProcessInfo.vue @@ -4,6 +4,7 @@ v-show="hideLaunchpad" :process="process" :current-user-id="currentUserId" + :ellipsis-permission="ellipsisPermission" @goBackCategory="$emit('goBackCategory')" /> { this.firstImage = pos + 1; }); - }, computed: { }, diff --git a/resources/js/processes-catalogue/components/ProcessScreen.vue b/resources/js/processes-catalogue/components/ProcessScreen.vue index 1b6870cbfe..1431c6f40f 100644 --- a/resources/js/processes-catalogue/components/ProcessScreen.vue +++ b/resources/js/processes-catalogue/components/ProcessScreen.vue @@ -2,6 +2,7 @@
permissionsNeeded.includes(permission)); + const permissions = (this.$root && this.$root.permission) ? this.$root.permission : this.ellipsisPermission || []; + this.showEllipsis = permissions.some((permission) => permissionsNeeded.includes(permission)); }, /** * Return a process cards from process info diff --git a/resources/js/tasks/components/DashboardViewer.vue b/resources/js/tasks/components/DashboardViewer.vue new file mode 100644 index 0000000000..aa5fd80457 --- /dev/null +++ b/resources/js/tasks/components/DashboardViewer.vue @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/resources/js/tasks/components/ParticipantHomeScreen.vue b/resources/js/tasks/components/ParticipantHomeScreen.vue new file mode 100644 index 0000000000..032629f0cb --- /dev/null +++ b/resources/js/tasks/components/ParticipantHomeScreen.vue @@ -0,0 +1,680 @@ + + + + + diff --git a/resources/js/tasks/components/ProcessBrowser.vue b/resources/js/tasks/components/ProcessBrowser.vue new file mode 100644 index 0000000000..1f5d293f4b --- /dev/null +++ b/resources/js/tasks/components/ProcessBrowser.vue @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/resources/js/tasks/components/ProcessesDashboardsMenu.vue b/resources/js/tasks/components/ProcessesDashboardsMenu.vue new file mode 100644 index 0000000000..ca433ef68e --- /dev/null +++ b/resources/js/tasks/components/ProcessesDashboardsMenu.vue @@ -0,0 +1,194 @@ + + + + + diff --git a/resources/js/tasks/index.js b/resources/js/tasks/index.js index 41ad99016b..ecea920a86 100644 --- a/resources/js/tasks/index.js +++ b/resources/js/tasks/index.js @@ -2,12 +2,18 @@ import Vue from "vue"; import TasksList from "./components/TasksList"; import TasksListCounter from "./components/TasksListCounter.vue"; import setDefaultAdvancedFilterStatus from "../common/setDefaultAdvancedFilterStatus"; +import ParticipantHomeScreen from './components/ParticipantHomeScreen.vue'; Vue.component("TasksList", TasksList); +Vue.component('participant-home-screen', ParticipantHomeScreen); new Vue({ el: "#tasks", data: { + showOldTaskScreen: false, + userConfiguration: window.ProcessMaker.userConfiguration, + urlConfiguration: "users/configuration", + showMenu: true, columns: window.Processmaker.defaultColumns || null, filter: "", pmql: "", @@ -62,7 +68,9 @@ new Vue({ if (!window.location.search.includes("filter_user_recommendation")) { this.$nextTick(() => { - this.$refs.taskList.fetch(); + if (this.$refs.taskList) { + this.$refs.taskList.fetch(); + } }); } }, diff --git a/resources/js/tasks/mixins/TasksMixin.js b/resources/js/tasks/mixins/TasksMixin.js new file mode 100644 index 0000000000..ad96cc0a3e --- /dev/null +++ b/resources/js/tasks/mixins/TasksMixin.js @@ -0,0 +1,191 @@ +export default { + data() { + return { + showOldTaskScreen: false, + urlConfiguration: "users/configuration", + showMenu: false, + columns: window.Processmaker.defaultColumns || null, + filter: "", + pmql: "", + urlPmql: "", + filtersPmql: "", + fullPmql: "", + status: [], + inOverdueMessage: "", + additions: [], + priorityField: "is_priority", + draftField: "draft", + isDataLoading: false, + inbox: true, + priority: false, + draft: false, + tab: "inbox", + inboxCount: null, + draftCount: null, + priorityCount: null, + priorityFilter: [ + { + subject: { + type: "Field", + value: "is_priority", + }, + operator: "=", + value: true, + _column_field: "is_priority", + _column_label: "Priority", + _hide_badge: true, + }, + ], + draftFilter: [ + { + subject: { + type: "Relationship", + value: "draft.id", + }, + operator: ">", + value: 0, + _column_field: "draft", + _column_label: "Draft", + _hide_badge: true, + }, + ], + }; + }, + computed: { + effectiveSavedsearchDefaultsEditRoute() { + return ( + this.savedsearchDefaultsEditRoute || + window.ProcessMaker.savedsearchDefaultsEditRoute + ); + }, + }, + methods: { + defineUserConfiguration() { + this.localUserConfiguration = JSON.parse( + window.ProcessMaker.userConfiguration || "{}" + ); + if (this.localUserConfiguration.tasks_inbox) { + this.showMenu = this.localUserConfiguration.tasks_inbox.isMenuCollapse; + } else { + this.showMenu = false; + this.localUserConfiguration.tasks_inbox = { + isMenuCollapse: false, + }; + } + }, + hideMenu() { + this.showMenu = !this.showMenu; + this.updateUserConfiguration(); + }, + updateUserConfiguration() { + this.localUserConfiguration.tasks_inbox.isMenuCollapse = this.showMenu; + ProcessMaker.apiClient + .put(this.urlConfiguration, { + ui_configuration: this.localUserConfiguration, + }) + .catch((error) => { + console.error("Error", error); + }); + }, + switchTab(tab) { + this.tab = tab; + const taskListComponent = this.$refs.taskList; + taskListComponent.advancedFilter[this.priorityField] = []; + taskListComponent.advancedFilter[this.draftField] = []; + switch (tab) { + case "priority": + taskListComponent.advancedFilter["is_priority"] = this.priorityFilter; + break; + case "draft": + taskListComponent.advancedFilter["draft"] = this.draftFilter; + break; + } + taskListComponent.markStyleWhenColumnSetAFilter(); + taskListComponent.storeFilterConfiguration(); + taskListComponent.fetch(true); + }, + dataLoading(value) { + this.isDataLoading = value; + }, + onFetchTask() { + this.inbox = true; + this.priority = this.draft = false; + let filters = window.ProcessMaker.advanced_filter?.filters; + if (!Array.isArray(filters)) { + filters = []; + } + filters.forEach((item) => { + if (item._column_field === "is_priority") { + this.priority = true; + this.inbox = this.draft = false; + } + if (item._column_field === "draft") { + this.draft = true; + this.inbox = this.priority = false; + } + }); + }, + handleTabCount(value) { + if (this.tab === "inbox") { + this.inboxCount = value; + } + if (this.tab === "draft") { + this.draftCount = value; + } + if (this.tab === "priority") { + this.priorityCount = value; + } + }, + onFiltersPmqlChange(value) { + this.filtersPmql = value[0]; + this.fullPmql = this.getFullPmql(); + }, + onNLQConversion(query) { + this.onChange(query); + this.onSearch(); + }, + onChange(query) { + this.pmql = query; + this.fullPmql = this.getFullPmql(); + }, + onSearch() { + if (this.$refs.taskList) { + this.$refs.taskList.fetch(true); + } + }, + onInboxRules() { + window.location.href = "/tasks/rules"; + }, + setInOverdueMessage(inOverdue) { + let inOverdueMessage = ""; + if (inOverdue) { + const taskText = + inOverdue > 1 + ? this.$t("Tasks").toLowerCase() + : this.$t("Task").toLowerCase(); + inOverdueMessage = this.$t( + "You have {{ inOverDue }} overdue {{ taskText }} pending", + { inOverDue: inOverdue, taskText } + ); + } + this.inOverdueMessage = inOverdueMessage; + }, + getFullPmql() { + let fullPmqlString = ""; + + if (this.filtersPmql && this.filtersPmql !== "") { + fullPmqlString = this.filtersPmql; + } + + if (fullPmqlString !== "" && this.pmql && this.pmql !== "") { + fullPmqlString = `${fullPmqlString} AND ${this.pmql}`; + } + + if (fullPmqlString === "" && this.pmql && this.pmql !== "") { + fullPmqlString = this.pmql; + } + + return fullPmqlString; + }, + }, +}; diff --git a/resources/js/tasks/router.js b/resources/js/tasks/router.js new file mode 100644 index 0000000000..ab96b9b45b --- /dev/null +++ b/resources/js/tasks/router.js @@ -0,0 +1,32 @@ +import Vue from "vue"; +import VueRouter from "vue-router"; +import ProcessBrowser from "./components/ProcessBrowser.vue"; +import DashboardViewer from "./components/DashboardViewer.vue"; + +Vue.use(VueRouter); + +const router = new VueRouter({ + mode: "history", + base: "/tasks", + routes: [ + { + path: "/process/:processId", + name: "process-browser", + component: ProcessBrowser, + props: (route) => ({ + processId: parseInt(route.params.processId) || null, + process: null, + }), + }, + { + path: "/dashboard/:dashboardId", + name: "dashboard", + component: DashboardViewer, + props: (route) => ({ + dashboardId: route.params.dashboardId || null, + }), + }, + ], +}); + +export default router; diff --git a/resources/views/tasks/index.blade.php b/resources/views/tasks/index.blade.php index cc3082c845..d2319e5ab7 100644 --- a/resources/views/tasks/index.blade.php +++ b/resources/views/tasks/index.blade.php @@ -1,7 +1,7 @@ @extends('layouts.layout') @section('title') - {{__($title)}} + {{ __($title) }} @endsection @section('sidebar') @@ -9,175 +9,162 @@ @endsection @section('breadcrumbs') - @include('shared.breadcrumbs', ['routes' => [ - __('Tasks') => route('tasks.index'), - __($title) => null, - ]]) + @include('shared.breadcrumbs', [ + 'routes' => [ + __('Tasks') => route('tasks.index'), + __($title) => null, + ], + ]) @endsection + @section('content') -
-
-
- - @{{ inOverdueMessage }} - -
-
+
+
+
+
+
+ + @{{ inOverdueMessage }} + +
+
-
-
- - -
-
-
-
-
-
- -
- -
+
+ +
-
@endsection @section('js') - + + @endsection @section('css') @@ -222,14 +209,17 @@ class="ml-md-2" min-height: 25px; border-radius: 50%; } + .task-nav { border-bottom: 0 !important; } + .task-nav-link.active { color: #1572C2 !important; font-weight: 700; font-size: 15px; } + .task-nav-link { color: #556271; font-weight: 400; @@ -237,25 +227,231 @@ class="ml-md-2" border-top-left-radius: 5px !important; border-top-right-radius: 5px !important; } + .task-list-body { border-radius: 5px; } + .task-inbox-rules { - width: max-content; + width: max-content; } + .task-inbox-rules-content { - display: flex; - justify-content: space-between; - padding: 15px; + display: flex; + justify-content: space-between; + padding: 15px; } + .task-inbox-rules-content-text { - width: 310px; - padding-left: 10px; + width: 310px; + padding-left: 10px; } + @endsection diff --git a/tests/Feature/Api/UserConfigurationTest.php b/tests/Feature/Api/UserConfigurationTest.php index 0a2b4a0c8d..95fdce0acc 100644 --- a/tests/Feature/Api/UserConfigurationTest.php +++ b/tests/Feature/Api/UserConfigurationTest.php @@ -55,6 +55,9 @@ public function testStoreUserConfigurationAndGetNewValues() 'tasks' => [ 'isMenuCollapse' => false, ], + 'tasks_inbox' => [ + 'isMenuCollapse' => false, + ], ]; $response = $this->apiCall('PUT', self::API_TEST_URL, ['ui_configuration' => $values]); @@ -74,6 +77,7 @@ public function testStoreUserConfigurationAndGetNewValues() $this->assertEquals($uiConfig->cases->isMenuCollapse, $values['cases']['isMenuCollapse']); $this->assertEquals($uiConfig->requests->isMenuCollapse, $values['requests']['isMenuCollapse']); $this->assertEquals($uiConfig->tasks->isMenuCollapse, $values['tasks']['isMenuCollapse']); + $this->assertEquals($uiConfig->tasks_inbox->isMenuCollapse, $values['tasks_inbox']['isMenuCollapse']); } /** @@ -86,7 +90,7 @@ public function testStoreUserConfigurationWithInvalidValues() // Validate the header status code $response->assertStatus(422); - $this->assertEquals('The Ui configuration field is required. (and 4 more errors)', $response->json()['message']); + $this->assertEquals('The Ui configuration field is required. (and 5 more errors)', $response->json()['message']); // An incomplete ui_configuration $values = [ @@ -99,6 +103,9 @@ public function testStoreUserConfigurationWithInvalidValues() 'tasks' => [ 'isMenuCollapse' => false, ], + 'tasks_inbox' => [ + 'isMenuCollapse' => false, + ], ]; $response = $this->apiCall('PUT', self::API_TEST_URL, ['ui_configuration' => $values]); // Validate the header status code diff --git a/webpack.mix.js b/webpack.mix.js index ef6c05502a..96d065d9c7 100644 --- a/webpack.mix.js +++ b/webpack.mix.js @@ -131,6 +131,7 @@ mix .js("resources/js/tasks/index.js", "public/js/tasks/index.js") .js("resources/js/tasks/mobile.js", "public/js/tasks/mobile.js") .js("resources/js/tasks/show.js", "public/js/tasks/show.js") + .js("resources/js/tasks/router.js", "public/js/tasks/router.js") .js("resources/js/notifications/index.js", "public/js/notifications/index.js") .js('resources/js/inbox-rules/index.js', 'public/js/inbox-rules')