diff --git a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php index 29f6df3b0e..a57f89cd23 100644 --- a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php +++ b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php @@ -839,4 +839,43 @@ public function endEventDestination(ProcessRequest $request) return response()->json(['message' => __('End event found'), 'data' => $data]); } + + /** + * This endpoint returns requests by case number + * + * @param Request $request + * + * @return ApiCollection + */ + public function getRequestsByCase(Request $request, User $user = null) + { + if (!$user) { + $user = Auth::user(); + } + + // Validate the inputs, including optional ones + $request->validate([ + 'case_number' => 'required|integer', + 'order_by' => 'nullable|string|in:id,name,status,user_id,initiated_at,participants', + 'order_direction' => 'nullable|string|in:asc,desc', + 'page' => 'nullable|integer|min:1', + 'per_page' => 'nullable|integer', + ]); + + $query = ProcessRequest::forUser($user); + + // Filter by case_number + $query->filterByCaseNumber($request); + + // Apply ordering only if a valid order_by field is provided + $query->applyOrdering($request); + $response = $query->applyPagination($request); + + // Get activeTasks and participants + $response = $response->map(function ($processRequest) use ($request) { + return new ProcessRequestResource($processRequest); + }); + + return new ApiCollection($response); + } } diff --git a/ProcessMaker/Http/Controllers/Api/TaskController.php b/ProcessMaker/Http/Controllers/Api/TaskController.php index 2b25834bd5..93c167271a 100644 --- a/ProcessMaker/Http/Controllers/Api/TaskController.php +++ b/ProcessMaker/Http/Controllers/Api/TaskController.php @@ -53,6 +53,15 @@ class TaskController extends Controller 'Completed' => 'CLOSED', ]; + protected $defaultCase = [ + 'id', // Task # + 'element_name', // Task Name + 'user_id', // Participant + 'process_id', // Process + 'due_at', // Due At + 'process_request_id', // Request Id # + ]; + /** * Display a listing of the resource. * @@ -149,6 +158,55 @@ public function index(Request $request, $getTotal = false, User $user = null) return new TaskCollection($response); } + /** + * Get the task list related to the case + * @param Request $request + * @param User $user used by Saved Search package to return accurate counts + * @return array + */ + public function indexCase(Request $request, User $user = null) + { + if (!$user) { + $user = Auth::user(); + } + + // Validate the inputs, including optional ones + $request->validate([ + 'case_number' => 'required|integer', + 'status' => 'nullable|string|in:ACTIVE,CLOSED', + 'order_by' => 'nullable|string|in:id,element_name,due_at,user.lastname,process.name', + 'order_direction' => 'nullable|string|in:asc,desc', + 'page' => 'nullable|integer|min:1', + 'per_page' => 'nullable|integer', + ]); + + // Get only the columns defined + $query = ProcessRequestToken::select($this->defaultCase); + // Filter by case_number + $query->filterByCaseNumber($request); + // Filter by status + $query->filterByStatus($request); + // Return the process information + $query->getProcess(); + // Return the user information + $query->getUser(); + // Filter only the task related to the user + $this->applyForCurrentUser($query, $user); + // Exclude non visible task + $this->excludeNonVisibleTasks($query, $request); + // Apply ordering only if a valid order_by field is provided + $query->applyOrdering($request); + + try { + $response = $query->applyPagination($request); + $response->inOverdue = 0; + } catch (QueryException $e) { + return $this->handleQueryException($e); + } + + return new TaskCollection($response); + } + /** * Display the specified resource. * @TODO remove this method,view and route this is not a used file diff --git a/ProcessMaker/Http/Controllers/CasesController.php b/ProcessMaker/Http/Controllers/CasesController.php new file mode 100644 index 0000000000..e8a94dcbab --- /dev/null +++ b/ProcessMaker/Http/Controllers/CasesController.php @@ -0,0 +1,199 @@ +input('skipInterstitial') && $request->status === 'ACTIVE') { + $startEvent = $request->tokens()->orderBy('id')->first(); + if ($startEvent) { + $definition = $startEvent->getDefinition(); + $allowInterstitial = false; + if (isset($definition['allowInterstitial'])) { + $allowInterstitial = filter_var( + $definition['allowInterstitial'], + FILTER_VALIDATE_BOOLEAN, + FILTER_NULL_ON_FAILURE + ); + } + if ($allowInterstitial && $request->user_id == Auth::id() && request()->has('fromTriggerStartEvent')) { + $active = $request->tokens() + ->where('user_id', Auth::id()) + ->where('element_type', 'task') + ->where('status', 'ACTIVE') + ->orderBy('id')->first(); + + // If the interstitial is enabled on the start event, then use it as the task + if ($active) { + $task = $allowInterstitial ? $startEvent : $active; + } else { + $task = $startEvent; + } + + return redirect(route('tasks.edit', [ + 'task' => $task->getKey(), + ])); + } + } + } + + $userHasCommentsForRequest = Comment::where('commentable_type', ProcessRequest::class) + ->where('commentable_id', $request->id) + ->where('body', 'like', '%{{' . \Auth::user()->id . '}}%') + ->count() > 0; + + $requestMedia = $request->media()->get()->pluck('id'); + + $userHasCommentsForMedia = Comment::where('commentable_type', \ProcessMaker\Models\Media::class) + ->whereIn('commentable_id', $requestMedia) + ->where('body', 'like', '%{{' . \Auth::user()->id . '}}%') + ->count() > 0; + + if (!$userHasCommentsForMedia && !$userHasCommentsForRequest) { + $this->authorize('view', $request); + } + + $request->participants; + $request->user; + $request->summary = $request->summary(); + + if ($request->status === 'CANCELED' && $request->process->cancel_screen_id) { + $request->summary_screen = $request->process->cancelScreen; + } else { + $request->summary_screen = $request->getSummaryScreen(); + } + $request->request_detail_screen = Screen::find($request->process->request_detail_screen_id); + + $canCancel = Auth::user()->can('cancel', $request->processVersion); + $canViewComments = (Auth::user()->hasPermissionsFor('comments')->count() > 0) || class_exists(PackageServiceProvider::class); + $canManuallyComplete = Auth::user()->is_administrator && $request->status === 'ERROR'; + $canRetry = false; + + if ($canManuallyComplete) { + $retry = RetryProcessRequest::for($request); + + $canRetry = $retry->hasRetriableTasks() && + !$retry->hasNonRetriableTasks() && + !$retry->isChildRequest(); + } + + $files = \ProcessMaker\Models\Media::getFilesRequest($request); + + $canPrintScreens = $this->canUserPrintScreen($request); + + $manager = app(ScreenBuilderManager::class); + event(new ScreenBuilderStarting($manager, ($request->summary_screen) ? $request->summary_screen->type : 'FORM')); + + $addons = []; + $dataActionsAddons = []; + + $isProcessManager = $request->process?->manager_id === Auth::user()->id; + + $eligibleRollbackTask = null; + $errorTask = RollbackProcessRequest::getErrorTask($request); + if ($errorTask) { + $eligibleRollbackTask = RollbackProcessRequest::eligibleRollbackTask($errorTask); + } + $this->summaryScreenTranslation($request); + return view('cases.edit', compact( + 'request', + 'files', + 'canCancel', + 'canViewComments', + 'canManuallyComplete', + 'canRetry', + 'manager', + 'canPrintScreens', + 'addons', + 'isProcessManager', + 'eligibleRollbackTask', + 'errorTask', + )); + } + + /** + * the user may or may not print forms + * + * @param ProcessRequest $request + * @return bool + */ + private function canUserPrintScreen(ProcessRequest $request) + { + //validate user is administrator + if (Auth::user()->is_administrator) { + return true; + } + + //validate user is participant or requester + if (in_array(Auth::user()->id, $request->participants()->get()->pluck('id')->toArray())) { + return true; + } + + // Any user with permissions Edit Request Data, Edit Task Data and view All Requests + if (Auth::user()->can('view-all_requests') && Auth::user()->can('edit-request_data') && Auth::user()->can('edit-task_data')) { + return true; + } + + return false; + } + + /** + * Translates the summary screen strings + * @param ProcessRequest $request + * @return void + */ + public function summaryScreenTranslation(ProcessRequest $request): void + { + if ($request->summary_screen) { + $processTranslation = new ProcessTranslation($request->process); + $translatedConf = $processTranslation->applyTranslations($request->summary_screen); + $request->summary_screen['config'] = $translatedConf; + } + } +} diff --git a/ProcessMaker/Http/Controllers/RequestController.php b/ProcessMaker/Http/Controllers/RequestController.php index b4924debc7..d36004d1e0 100644 --- a/ProcessMaker/Http/Controllers/RequestController.php +++ b/ProcessMaker/Http/Controllers/RequestController.php @@ -45,7 +45,7 @@ public function index($type = null) $this->authorize('view-all_requests'); } - $title = 'My Cases'; + $title = 'My Request'; $types = ['all'=>'All Requests', 'in_progress'=>'Requests In Progress', 'completed'=>'Completed Requests']; diff --git a/ProcessMaker/Http/Middleware/GenerateMenus.php b/ProcessMaker/Http/Middleware/GenerateMenus.php index 2105da72fb..d8db58df4d 100644 --- a/ProcessMaker/Http/Middleware/GenerateMenus.php +++ b/ProcessMaker/Http/Middleware/GenerateMenus.php @@ -36,10 +36,10 @@ public function handle(Request $request, Closure $next) ['route' => 'process.browser.index', 'id' => 'process-browser'] )->active('process-browser/*'); }); - $menu->group(['prefix' => 'requests'], function ($request_items) { + $menu->group(['prefix' => 'cases'], function ($request_items) { $request_items->add( __('Cases'), - ['route' => 'cases.index', 'id' => 'requests'] + ['route' => 'cases-main.index', 'id' => 'cases'] )->active('cases/*'); }); //@TODO change the index to the correct blade @@ -140,25 +140,53 @@ public function handle(Request $request, Closure $next) $submenu = $menu->add(__('Processes')); }); Menu::make('sidebar_request', function ($menu) { - $submenu = $menu->add(__('Cases')); - $submenu->add(__('My Cases'), [ - 'route' => ['cases_by_type', ''], + $submenu = $menu->add(__('Requests')); + $submenu->add(__('My Requests'), [ + 'route' => ['requests_by_type', ''], 'icon' => 'fa-id-badge', ]); $submenu->add(__('In Progress'), [ - 'route' => ['cases_by_type', 'in_progress'], + 'route' => ['requests_by_type', 'in_progress'], 'icon' => 'fa-clipboard-list', ]); $submenu->add(__('Completed'), [ - 'route' => ['cases_by_type', 'completed'], + 'route' => ['requests_by_type', 'completed'], 'icon' => 'fa-clipboard-check', ]); if (\Auth::check() && \Auth::user()->can('view-all_requests')) { + $submenu->add(__('All Requests'), [ + 'route' => ['requests_by_type', 'all'], + 'icon' => 'fa-clipboard', + ]); + } + }); + + Menu::make('sidebar_cases', function ($menu) { + $submenu = $menu->add(__('Cases')); + $submenu->add(__('My Cases'), [ + 'route' => ['cases-main.index', ''], + 'icon' => 'fa-user', + ]); + $submenu->add(__('In Progress'), [ + 'route' => ['cases-main.index', 'in_progress'], + 'icon' => 'fa-list', + ]); + $submenu->add(__('Completed'), [ + 'route' => ['cases-main.index', 'completed'], + 'icon' => 'fa-check-circle', + ]); + if (\Auth::check() && \Auth::user()->can('view-all_cases')) { $submenu->add(__('All Cases'), [ - 'route' => ['cases_by_type', 'all'], + 'route' => ['cases-main.index', 'all'], 'icon' => 'fa-clipboard', ]); } + if (\Auth::check() && \Auth::user()->can('view-my_requests')) { + $submenu->add(__('My Requests'), [ + 'route' => ['requests_by_type', ''], + 'icon' => 'fa-play', + ]); + } }); Menu::make('sidebar_processes', function ($menu) { diff --git a/ProcessMaker/Models/ProcessRequest.php b/ProcessMaker/Models/ProcessRequest.php index 989521a8b9..6ddb886dcc 100644 --- a/ProcessMaker/Models/ProcessRequest.php +++ b/ProcessMaker/Models/ProcessRequest.php @@ -165,8 +165,6 @@ class ProcessRequest extends ProcessMakerModel implements ExecutionInstanceInter 'participants', ]; - const DEFAULT_CASE_TITLE = 'Case #{{_request.case_number}}'; - /** * Determine whether the item should be indexed. * @@ -1002,7 +1000,7 @@ public function getCaseTitleFromProcess(): string $caseTitle = $this->process()->select('case_title')->first()->case_title; } - return $caseTitle ?: self::DEFAULT_CASE_TITLE; + return $caseTitle ?: $this->name; } /** @@ -1091,4 +1089,36 @@ public function getElementDestination(): ?array return $endEvents->first()->elementDestination; } + + /** + * Scope apply order + */ + public function scopeApplyOrdering($query, $request) + { + $orderBy = $request->input('order_by', 'name'); + $orderDirection = $request->input('order_direction', 'asc'); + + return $query->orderBy($orderBy, $orderDirection); + } + + /** + * Scope apply pagination + */ + public function scopeApplyPagination($query, $request) + { + $page = $request->input('page', 1); + $perPage = $request->input('per_page', 10); + + return $query->paginate($perPage); + } + + /** + * Scope to filter by case_number + */ + public function scopeFilterByCaseNumber($query, $request) + { + $caseNumber = $request->input('case_number'); + + return $query->where('case_number', $caseNumber); + } } diff --git a/ProcessMaker/Models/ProcessRequestToken.php b/ProcessMaker/Models/ProcessRequestToken.php index 1b88fe7ca6..0a0b8c32c3 100644 --- a/ProcessMaker/Models/ProcessRequestToken.php +++ b/ProcessMaker/Models/ProcessRequestToken.php @@ -288,6 +288,70 @@ public function assignableUsers() return new TokenAssignableUsers($query, $this); } + /** + * Scope to filter by case_number through the processRequest relationship + */ + public function scopeFilterByCaseNumber($query, $request) + { + $caseNumber = $request->input('case_number'); + + return $query->whereHas('processRequest', function ($query) use ($caseNumber) { + $query->where('case_number', $caseNumber); + }); + } + + /** + * Scope to filter by status + */ + public function scopeFilterByStatus($query, $request) + { + $status = $request->input('status', 'ACTIVE'); + + return $query->where('status', $status); + } + + /** + * Scope get process information + */ + public function scopeGetProcess($query) + { + return $query->with(['process' => function ($query) { + $query->select('id', 'name'); + }]); + } + + /** + * Scope get user information + */ + public function scopeGetUser($query) + { + return $query->with(['user' => function ($query) { + $query->select('id', 'firstname', 'lastname', 'username', 'avatar'); + }]); + } + + /** + * Scope apply order + */ + public function scopeApplyOrdering($query, $request) + { + $orderBy = $request->input('order_by', 'due_at'); + $orderDirection = $request->input('order_direction', 'asc'); + + return $query->orderBy($orderBy, $orderDirection); + } + + /** + * Scope apply pagination + */ + public function scopeApplyPagination($query, $request) + { + $page = $request->input('page', 1); + $perPage = $request->input('per_page', 10); + + return $query->paginate($perPage); + } + /** * Returns either the owner element or its properties * diff --git a/ProcessMaker/Observers/UserObserver.php b/ProcessMaker/Observers/UserObserver.php index 36c14ca046..d8e45ef942 100644 --- a/ProcessMaker/Observers/UserObserver.php +++ b/ProcessMaker/Observers/UserObserver.php @@ -37,6 +37,7 @@ public function created(User $user): void { $perList = [ 'view-process-catalog', + 'view-my_requests', ]; $permissionIds = Permission::whereIn('name', $perList)->pluck('id')->toArray(); $user->permissions()->attach($permissionIds); diff --git a/database/seeders/PermissionSeeder.php b/database/seeders/PermissionSeeder.php index f0e3c0b198..8acb45e4da 100644 --- a/database/seeders/PermissionSeeder.php +++ b/database/seeders/PermissionSeeder.php @@ -3,7 +3,9 @@ namespace Database\Seeders; use Illuminate\Database\Seeder; +use ProcessMaker\Models\Group; use ProcessMaker\Models\Permission; +use ProcessMaker\Models\User; class PermissionSeeder extends Seeder { @@ -83,8 +85,10 @@ class PermissionSeeder extends Seeder 'Username and Password' => [ 'edit-user-and-password', ], - 'Requests' => [ + 'Cases and Requests' => [ + 'view-all_cases', 'view-all_requests', + 'view-my_requests', 'edit-request_data', 'edit-task_data', ], @@ -114,16 +118,24 @@ class PermissionSeeder extends Seeder ], ]; + private $defaultPermissions = [ + 'view-my_requests', + ]; + public function run($seedUser = null) { foreach ($this->permissionGroups as $groupName => $permissions) { foreach ($permissions as $permissionString) { - Permission::updateOrCreate([ + $permission = Permission::updateOrCreate([ 'name' => $permissionString, ], [ 'title' => ucwords(preg_replace('/(\-|_)/', ' ', $permissionString)), 'group' => $groupName, ]); + + if (in_array($permissionString, $this->defaultPermissions)) { + $this->assignDefaultPermission($permission); + } } } @@ -134,4 +146,26 @@ public function run($seedUser = null) $seedUser->save(); } } + + /** + * Assign default permission to users and groups. + */ + private function assignDefaultPermission(Permission $permission): void + { + $userIds = User::nonSystem()->pluck('id'); + $groupIds = Group::pluck('id'); + + // Define the chunk size for the permission assignment + $chunkSize = 500; + + // Attach user IDs in chunks + $userIds->chunk($chunkSize)->each(function ($chunk) use ($permission) { + $permission->users()->attach($chunk); + }); + + // Attach group IDs in chunks + $groupIds->chunk($chunkSize)->each(function ($chunk) use ($permission) { + $permission->groups()->attach($chunk); + }); + } } diff --git a/package.json b/package.json index 9e92914283..faa7025532 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@babel/eslint-parser": "^7.15.8", "@babel/preset-env": "^7.23.9", "accounting": "^0.4.1", + "autoprefixer": "^10.4.20", "babel-plugin-istanbul": "^6.1.1", "chartjs-plugin-colorschemes": "^0.4.0", "cross-env": "^7.0.3", @@ -38,9 +39,11 @@ "laravel-mix": "^6.0.49", "moment": "^2.30.1", "moment-timezone": "^0.5.45", + "postcss": "^8.4.45", "resolve-url-loader": "^3.1.2", "sass": "^1.77.4", "sass-loader": "^12.6.0", + "tailwindcss": "^3.4.10", "vue-loader": "^15.10.0" }, "dependencies": { @@ -60,7 +63,6 @@ "@processmaker/vue-form-elements": "0.59.0", "@processmaker/vue-multiselect": "2.3.0", "@tinymce/tinymce-vue": "2.0.0", - "autoprefixer": "10.4.5", "axios": "^0.27.2", "bootstrap": "^4.5.3", "bootstrap-vue": "^2.18.1", diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000000..33ad091d26 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/resources/js/components/shared/FilterTable.vue b/resources/js/components/shared/FilterTable.vue index aafdb15225..9e49f82b07 100644 --- a/resources/js/components/shared/FilterTable.vue +++ b/resources/js/components/shared/FilterTable.vue @@ -382,6 +382,13 @@ export default { right: 7px; background-color: #F2F8FE } +.pm-table-ellipsis-column{ + text-transform: capitalize; +} +.pm-table-column-header-text { + overflow: hidden; + text-overflow: ellipsis; +} .pm-table-ellipsis-column .pm-table-filter-button { opacity: 0; visibility: hidden; diff --git a/resources/js/requests/components/RequestsListing.vue b/resources/js/requests/components/RequestsListing.vue index ef273f8e53..dabd3b3b8a 100644 --- a/resources/js/requests/components/RequestsListing.vue +++ b/resources/js/requests/components/RequestsListing.vue @@ -275,6 +275,13 @@ export default { return this.$props.columns; } return [ + { + label: "Request ID", + field: "id", + sortable: true, + default: true, + width: 115, + }, { label: "Case #", field: "case_number", @@ -408,7 +415,7 @@ export default { return ` - ${value.case_title_formatted || value.case_title || ""} + ${value.case_title_formatted || value.case_title || value.name} `; }, formatParticipants(participants) { @@ -622,15 +629,6 @@ export default { } }; - diff --git a/resources/js/tasks/components/TasksList.vue b/resources/js/tasks/components/TasksList.vue index 5db813ee4d..04181f5e63 100644 --- a/resources/js/tasks/components/TasksList.vue +++ b/resources/js/tasks/components/TasksList.vue @@ -917,7 +917,7 @@ export default { }; - diff --git a/resources/jscomposition/base/form/InputLeading.vue b/resources/jscomposition/base/form/InputLeading.vue new file mode 100644 index 0000000000..d65427d1b6 --- /dev/null +++ b/resources/jscomposition/base/form/InputLeading.vue @@ -0,0 +1,33 @@ + + \ No newline at end of file diff --git a/resources/jscomposition/base/form/index.js b/resources/jscomposition/base/form/index.js new file mode 100644 index 0000000000..8eec1e2f5b --- /dev/null +++ b/resources/jscomposition/base/form/index.js @@ -0,0 +1,9 @@ +import Dropdown from "./Dropdown.vue"; +import InputLeading from "./InputLeading.vue"; + +export default {} + +export { + Dropdown, + InputLeading +} \ No newline at end of file diff --git a/resources/jscomposition/base/index.js b/resources/jscomposition/base/index.js new file mode 100644 index 0000000000..ddbe2a67b6 --- /dev/null +++ b/resources/jscomposition/base/index.js @@ -0,0 +1,7 @@ +export * from "./buttons/index" +export * from "./cards/index" +export * from "./form/index" +export * from "./table/index" +export * from "./ui/index" + +export default {} diff --git a/resources/jscomposition/base/table/BaseTable.vue b/resources/jscomposition/base/table/BaseTable.vue new file mode 100644 index 0000000000..6e0b02a8a4 --- /dev/null +++ b/resources/jscomposition/base/table/BaseTable.vue @@ -0,0 +1,120 @@ + + + diff --git a/resources/jscomposition/base/table/ContainerRow.vue b/resources/jscomposition/base/table/ContainerRow.vue new file mode 100644 index 0000000000..0e960b83b0 --- /dev/null +++ b/resources/jscomposition/base/table/ContainerRow.vue @@ -0,0 +1,23 @@ + + + diff --git a/resources/jscomposition/base/table/TCell.vue b/resources/jscomposition/base/table/TCell.vue new file mode 100644 index 0000000000..81aba43a3c --- /dev/null +++ b/resources/jscomposition/base/table/TCell.vue @@ -0,0 +1,70 @@ + + + diff --git a/resources/jscomposition/base/table/THeader.vue b/resources/jscomposition/base/table/THeader.vue new file mode 100644 index 0000000000..be3c1e0588 --- /dev/null +++ b/resources/jscomposition/base/table/THeader.vue @@ -0,0 +1,58 @@ + + + diff --git a/resources/jscomposition/base/table/TRow.vue b/resources/jscomposition/base/table/TRow.vue new file mode 100644 index 0000000000..7506895657 --- /dev/null +++ b/resources/jscomposition/base/table/TRow.vue @@ -0,0 +1,25 @@ + + + diff --git a/resources/jscomposition/base/table/composables/columnComposable.js b/resources/jscomposition/base/table/composables/columnComposable.js new file mode 100644 index 0000000000..dde1e46c5c --- /dev/null +++ b/resources/jscomposition/base/table/composables/columnComposable.js @@ -0,0 +1,52 @@ +import { ref, onUnmounted } from "vue"; + +export default {}; +/** + * This composable only works with columns in AppTable + * @param {*} column is a ref variable, come from App Table + * @param {*} tableName + * @returns + */ +export const columnResizeComposable = (column) => { + const startX = ref(0); + const startWidth = ref(0); + const isResizing = ref(false); + + //Resize the column value + const doResize = (event) => { + if (isResizing.value) { + const diff = event.pageX - startX.value; + const min = 30; + const currentWidth = Math.max(min, startWidth.value + diff); + + column.width = currentWidth; + } + }; + + const stopResize = () => { + if (isResizing.value) { + document.removeEventListener("mousemove", doResize); + document.removeEventListener("mouseup", stopResize); + isResizing.value = false; + } + }; + + // Init the events in mousemove and finish in mouseup event + const startResize = (event, index) => { + isResizing.value = true; + startX.value = event.pageX; + startWidth.value = column.width || 200; + + document.addEventListener("mousemove", doResize); + document.addEventListener("mouseup", stopResize); + }; + + onUnmounted(() => { + document.removeEventListener("mousemove", doResize); + document.removeEventListener("mouseup", stopResize); + }); + + return { + startResize, + }; +}; diff --git a/resources/jscomposition/base/table/index.js b/resources/jscomposition/base/table/index.js new file mode 100644 index 0000000000..c290a40e63 --- /dev/null +++ b/resources/jscomposition/base/table/index.js @@ -0,0 +1,9 @@ +import BaseTable from "./BaseTable.vue"; +import Pagination from "../../system/table/Pagination.vue"; +import TCell from "./TCell.vue"; +import THeader from "./THeader.vue"; +import TRow from "./TRow.vue"; + +export default {}; + +export { BaseTable, Pagination, TCell, THeader, TRow }; diff --git a/resources/jscomposition/base/ui/AppAvatar.vue b/resources/jscomposition/base/ui/AppAvatar.vue new file mode 100644 index 0000000000..185a9a4eb8 --- /dev/null +++ b/resources/jscomposition/base/ui/AppAvatar.vue @@ -0,0 +1,35 @@ + + diff --git a/resources/jscomposition/base/ui/AppPopover.vue b/resources/jscomposition/base/ui/AppPopover.vue new file mode 100644 index 0000000000..df33d1e9df --- /dev/null +++ b/resources/jscomposition/base/ui/AppPopover.vue @@ -0,0 +1,206 @@ + + + + + diff --git a/resources/jscomposition/base/ui/Badge.vue b/resources/jscomposition/base/ui/Badge.vue new file mode 100644 index 0000000000..f1f6c173b4 --- /dev/null +++ b/resources/jscomposition/base/ui/Badge.vue @@ -0,0 +1,24 @@ + + + diff --git a/resources/jscomposition/base/ui/CollapsableContainer.vue b/resources/jscomposition/base/ui/CollapsableContainer.vue new file mode 100644 index 0000000000..b3fc74dcae --- /dev/null +++ b/resources/jscomposition/base/ui/CollapsableContainer.vue @@ -0,0 +1,49 @@ + + + diff --git a/resources/jscomposition/base/ui/index.js b/resources/jscomposition/base/ui/index.js new file mode 100644 index 0000000000..3ecdf6bdb9 --- /dev/null +++ b/resources/jscomposition/base/ui/index.js @@ -0,0 +1,10 @@ +import Badge from "./Badge.vue"; +import AppAvatar from "./AppAvatar.vue"; +import AppPopover from "./AppPopover.vue"; +import CollapsableContainer from "./CollapsableContainer.vue"; + +export default {}; + +export { + Badge, AppAvatar, AppPopover, CollapsableContainer, +}; diff --git a/resources/jscomposition/cases/casesDetail/api/index.js b/resources/jscomposition/cases/casesDetail/api/index.js new file mode 100644 index 0000000000..1f982e1a0c --- /dev/null +++ b/resources/jscomposition/cases/casesDetail/api/index.js @@ -0,0 +1,58 @@ +import { api } from "../variables"; + +const getData = async () => { + const objectsList = []; + + for (let i = 0; i <= 31; i += 1) { + const obj = { + id: `${i}`, + case_number: 100, + element_name: `Case Title ${i}`, + process: { + name: `Process ${i}`, + }, + user: { + fullname: `Avatar ${i}`, + }, + current_task: `Task ${i}`, + status: "IN_PROGRESS", + started: `21/21/${i}`, + due_at: `21/21/${i}`, + completed_date: `21/21/${i}`, + screen_id: 4, + }; + + objectsList.push(obj); + } + + return objectsList; +}; + +export const getDataRequests = async ({ params, pagination }) => { + const response = await api.get("requests-by-case", { + params: { + ...params, + ...pagination, + }, + }); + + return response.data.data; +}; + +export const getDataTask = async ({ params, pagination }) => { + const response = await api.get("tasks-by-case/", { + params: { + ...params, + ...pagination, + }, + }); + + return response.data.data; +}; +const getScreenData = (id) => { + const response = ProcessMaker.apiClient.get(`screens/${id}`); + + return response; +}; + +export { getData, getScreenData }; diff --git a/resources/jscomposition/cases/casesDetail/components/CaseDetail.vue b/resources/jscomposition/cases/casesDetail/components/CaseDetail.vue new file mode 100644 index 0000000000..d244d7550e --- /dev/null +++ b/resources/jscomposition/cases/casesDetail/components/CaseDetail.vue @@ -0,0 +1,50 @@ + + + diff --git a/resources/jscomposition/cases/casesDetail/components/CompletedForms.vue b/resources/jscomposition/cases/casesDetail/components/CompletedForms.vue new file mode 100644 index 0000000000..df5fae7851 --- /dev/null +++ b/resources/jscomposition/cases/casesDetail/components/CompletedForms.vue @@ -0,0 +1,58 @@ + + + diff --git a/resources/jscomposition/cases/casesDetail/components/DisplayForm.vue b/resources/jscomposition/cases/casesDetail/components/DisplayForm.vue new file mode 100644 index 0000000000..3ffb06bc86 --- /dev/null +++ b/resources/jscomposition/cases/casesDetail/components/DisplayForm.vue @@ -0,0 +1,44 @@ + + + diff --git a/resources/jscomposition/cases/casesDetail/components/EllipsisMenu.vue b/resources/jscomposition/cases/casesDetail/components/EllipsisMenu.vue new file mode 100644 index 0000000000..4376c3bf79 --- /dev/null +++ b/resources/jscomposition/cases/casesDetail/components/EllipsisMenu.vue @@ -0,0 +1,54 @@ + + + diff --git a/resources/jscomposition/cases/casesDetail/components/RequestTable.vue b/resources/jscomposition/cases/casesDetail/components/RequestTable.vue new file mode 100644 index 0000000000..c3b38c5c19 --- /dev/null +++ b/resources/jscomposition/cases/casesDetail/components/RequestTable.vue @@ -0,0 +1,67 @@ + + + diff --git a/resources/jscomposition/cases/casesDetail/components/TabHistory.vue b/resources/jscomposition/cases/casesDetail/components/TabHistory.vue new file mode 100644 index 0000000000..6a8adf283a --- /dev/null +++ b/resources/jscomposition/cases/casesDetail/components/TabHistory.vue @@ -0,0 +1,35 @@ + + + diff --git a/resources/jscomposition/cases/casesDetail/components/Tabs.vue b/resources/jscomposition/cases/casesDetail/components/Tabs.vue new file mode 100644 index 0000000000..5c9cc509c6 --- /dev/null +++ b/resources/jscomposition/cases/casesDetail/components/Tabs.vue @@ -0,0 +1,64 @@ + + + diff --git a/resources/jscomposition/cases/casesDetail/components/TaskTable.vue b/resources/jscomposition/cases/casesDetail/components/TaskTable.vue new file mode 100644 index 0000000000..60276e8a0a --- /dev/null +++ b/resources/jscomposition/cases/casesDetail/components/TaskTable.vue @@ -0,0 +1,72 @@ + + + diff --git a/resources/jscomposition/cases/casesDetail/config/columns.js b/resources/jscomposition/cases/casesDetail/config/columns.js new file mode 100644 index 0000000000..7203deca15 --- /dev/null +++ b/resources/jscomposition/cases/casesDetail/config/columns.js @@ -0,0 +1,186 @@ +import { + LinkCell, + StatusCell, + TruncatedOptionsCell, + CollapseFormCell, +} from "../../../system/index"; + +export default {}; + +// Column for Task +const taskNumberColumn = () => ({ + field: "case_number", + header: "Tasks #", + resizable: true, + width: 200, + filter: true, + formatter:(row, column, columns)=>{ + return row.element_name; + }, + cellRenderer: () => ({ + component: LinkCell, + params: { + click: (row, column, columns) => { + window.document.location = `/tasks/${row.id}/edit`; + }, + }, + }), +}); + +const taskNameColumn = () => ({ + field: "element_name", + header: "Task Name", + resizable: true, + width: 200, + filter: true, + cellRenderer: () => ({ + component: LinkCell, + params: { + click: (row, column, columns) => { + window.document.location = `/tasks/${row.id}/edit`; + }, + }, + }), +}); + +const processNameColumn = () => ({ + field: "process.name", + header: "Process", + resizable: true, + width: 200, +}); + +const assignedColumn = () => ({ + field: "user.fullname", + header: "Assigned", + resizable: true, + width: 200, + filter: true, +}); + +const dueDateColumn = () => ({ + field: "due_at", + header: "Due Date", + resizable: true, + width: 200, + filter: true, +}); + +// Columns for Requests +const requestIdColumn = () => ({ + field: "id", + header: "Request ID", + resizable: true, + filter: { type: "sortable" }, + width: 80, + cellRenderer: () => ({ + component: LinkCell, + params: { + click: (row, column, columns) => { + window.document.location = `/requests/${row.id}`; + }, + }, + }), +}); + +const processRequestColumn = () => ({ + field: "name", + header: "Process Name", + resizable: true, + width: 200, + filter: { type: "sortable" }, + cellRenderer: () => ({ + component: LinkCell, + params: { + click: (row, column, columns) => { + window.document.location = `/requests/${row.id}`; + }, + }, + }), +}); + +const taskColumn = () => ({ + field: "active_tasks", + header: "Task", + resizable: true, + width: 140, + formatter:(row, column, columns)=>{ + return row.active_tasks.length? row.active_tasks[0].element_name : ""; + }, + cellRenderer: () => ({ + component: TruncatedOptionsCell, + params: { + click: (option, row, column, columns) => { + window.document.location = `/tasks/${option.id}/edit`; + }, + formatterOptions:(option, row, column, columns)=>{ + return option.element_name; + } + }, + }), +}); + +const statusColumn = () => ({ + field: "status", + header: "Status", + filter: { type: "sortable" }, + resizable: true, + width: 140, + cellRenderer: () => ({ + component: StatusCell, + }), +}); + +const startedColumn = () => ({ + field: "initiated_at", + header: "Started", + filter: { type: "sortable" }, + resizable: true, + width: 200, +}); + +const completedDateColumn = () => ({ + field: "completed_date", + header: "Completed Date", + resizable: true, + width: 200, +}); + +const actionColumn = () => ({ + field: "", + header: "", + resizable: false, + width: 50, + cellRenderer: () => CollapseFormCell, + params: {}, +}); + +export const getColumns = (type) => { + const columns = { + tasks: [ + taskNumberColumn(), + taskNameColumn(), + processNameColumn(), + assignedColumn(), + dueDateColumn(), + ], + requests: [ + requestIdColumn(), + processRequestColumn(), + taskColumn(), + statusColumn(), + startedColumn(), + ], + completed_forms: [ + actionColumn(), + taskNumberColumn(), + taskNameColumn(), + processNameColumn(), + assignedColumn(), + completedDateColumn(), + dueDateColumn(), + ], + }; + + return columns[type]; +}; diff --git a/resources/jscomposition/cases/casesDetail/edit.js b/resources/jscomposition/cases/casesDetail/edit.js new file mode 100644 index 0000000000..50e0eae0fc --- /dev/null +++ b/resources/jscomposition/cases/casesDetail/edit.js @@ -0,0 +1,380 @@ +import Vue from "vue"; +import CaseDetail from "./components/CaseDetail.vue"; +import Tabs from "./components/Tabs.vue"; +import Timeline from "../../../js/components/Timeline.vue"; +import { CollapsableContainer } from "../../base"; + +Vue.component("Timeline", Timeline); + +new Vue({ + el: "#case-detail", + components: { CaseDetail, Tabs, CollapsableContainer }, + data() { + return { + activeTab: "pending", + showCancelRequest: false, + fieldsToUpdate: [], + jsonData: "", + selectedData: "", + monacoLargeOptions: { + automaticLayout: true, + }, + showJSONEditor: false, + data, + requestId, + request, + files, + refreshTasks: 0, + canCancel, + canViewPrint, + status: "ACTIVE", + userRequested: [], + errorLogs, + disabled: false, + retryDisabled: false, + packages: [], + processId, + canViewComments, + isObjectLoading: false, + showTree: false, + showTabs: true, + showInfo: true, + tabDefault: "details", + tabs: [ + { + name: "Details", href: "#details", current: "details", show: true, content: null, + }, + { + name: "Comments", href: "#comments", current: "comments", show: true, content: null, + }, + ], + headerModel: false, + }; + }, + computed: { + activeErrors() { + return this.request.status === "ERROR"; + }, + activePending() { + return this.request.status === "ACTIVE"; + }, + /** + * Get the list of participants in the request. + * + */ + participants() { + return this.request.participants; + }, + /** + * Request Summary - that is blank place holder if there are in progress tasks, + * if the request is completed it will show key value pairs. + * + */ + showSummary() { + return this.request.status === "ACTIVE" || this.request.status === "COMPLETED" || this.request.status === "CANCELED"; + }, + /** + * Show tasks if request status is not completed or pending + * + */ + showTasks() { + return this.request.status !== "COMPLETED" && this.request.status !== "PENDING"; + }, + /** + * If the screen summary is configured. + */ + showScreenSummary() { + return this.request.summary_screen !== null; + }, + /** + * Get the summary of the Request. + * + */ + summary() { + return this.request.summary; + }, + /** + * Get Screen summary + * */ + screenSummary() { + return this.request.summary_screen; + }, + /** + * prepare data screen + */ + dataSummary() { + const options = {}; + this.request.summary.forEach((option) => { + if (option.type === "datetime") { + options[option.key] = moment(option.value) + .tz(window.ProcessMaker.user.timezone) + .format("MM/DD/YYYY HH:mm"); + } else if (option.type === "date") { + options[option.key] = moment(option.value) + .tz(window.ProcessMaker.user.timezone) + .format("MM/DD/YYYY"); + } else { + options[option.key] = option.value; + } + }); + return options; + }, + /** + * If the screen request detail is configured. + */ + showScreenRequestDetail() { + return !!this.request.request_detail_screen; + }, + /** + * Get Screen request detail + */ + screenRequestDetail() { + return this.request.request_detail_screen ? this.request.request_detail_screen.config : null; + }, + classStatusCard() { + const header = { + ACTIVE: "active-style", + COMPLETED: "active-style", + CANCELED: "canceled-style ", + ERROR: "canceled-style", + }; + return `card-header text-status ${header[this.request.status.toUpperCase()]}`; + }, + labelDate() { + const label = { + ACTIVE: "In Progress Since", + COMPLETED: "Completed On", + CANCELED: "Canceled ", + ERROR: "Failed On", + }; + return label[this.request.status.toUpperCase()]; + }, + statusDate() { + const status = { + ACTIVE: this.request.created_at, + COMPLETED: this.request.completed_at, + CANCELED: this.request.updated_at, + ERROR: this.request.updated_at, + }; + + return status[this.request.status.toUpperCase()]; + }, + statusLabel() { + const status = { + ACTIVE: this.$t("In Progress"), + COMPLETED: this.$t("Completed"), + CANCELED: this.$t("Canceled"), + ERROR: this.$t("Error"), + }; + + return status[this.request.status.toUpperCase()]; + }, + requestBy() { + return [this.request.user]; + }, + panCommentInVueOptionsComponents() { + return "pan-comment" in Vue.options.components; + }, + }, + mounted() { + this.packages = window.ProcessMaker.requestShowPackages; + this.listenRequestUpdates(); + this.cleanScreenButtons(); + this.editJsonData(); + }, + methods: { + switchTab(tab) { + this.activeTab = tab; + if (tab === "overview") { + this.isObjectLoading = true; + } + ProcessMaker.EventBus.$emit("tab-switched", tab); + }, + switchTabInfo(tab) { + this.showInfo = !this.showInfo; + if (window.Intercom) { + window.Intercom("update", { hide_default_launcher: tab === "comments" }); + } + }, + onLoadedObject() { + this.isObjectLoading = false; + }, + requestStatusClass(status) { + const bubbleColor = { + active: "text-success", + inactive: "text-danger", + error: "text-danger", + draft: "text-warning", + archived: "text-info", + completed: "text-primary", + }; + return `fas fa-circle ${bubbleColor[status.toLowerCase()]} small`; + }, + // Data editor + updateRequestData() { + const data = JSON.parse(this.jsonData); + ProcessMaker.apiClient.put(`requests/${this.requestId}`, { + data, + }).then(() => { + this.fieldsToUpdate.splice(0); + ProcessMaker.alert(this.$t("The request data was saved."), "success"); + }); + }, + saveJsonData() { + try { + const value = JSON.parse(this.jsonData); + this.updateRequestData(); + } catch (e) { + // Invalid data + } + }, + editJsonData() { + this.jsonData = JSON.stringify(this.data, null, 4); + }, + /** + * Refresh the Request details. + * + */ + refreshRequest() { + this.$refs.pending.fetch(); + this.$refs.completed.fetch(); + ProcessMaker.apiClient.get(`requests/${this.requestId}`, { + params: { + include: "participants,user,summary,summaryScreen", + }, + }).then((response) => { + for (const attribute in response.data) { + this.updateModel(this.request, attribute, response.data[attribute]); + } + this.refreshTasks++; + }); + }, + /** + * Update a model property. + * + */ + updateModel(obj, prop, value, defaultValue) { + const descriptor = Object.getOwnPropertyDescriptor(obj, prop); + value = value !== undefined ? value : (descriptor ? obj[prop] : defaultValue); + if (descriptor && !(descriptor.get instanceof Function)) { + delete obj[prop]; + Vue.set(obj, prop, value); + } else if (descriptor && obj[prop] !== value) { + Vue.set(obj, prop, value); + } + }, + /** + * Listen for Request updates. + * + */ + listenRequestUpdates() { + const userId = document.head.querySelector("meta[name=\"user-id\"]").content; + Echo.private(`ProcessMaker.Models.User.${userId}`).notification((token) => { + if (token.request_id === this.requestId) { + this.refreshRequest(); + } + }); + }, + /** + * disable buttons in screen + */ + cleanScreenButtons() { + if (this.showScreenSummary) { + this.$refs.screen.config[0].items.forEach((item) => { + item.config.disabled = true; + if (item.component === "FormButton") { + item.config.event = ""; + item.config.variant = `${item.config.variant} disabled`; + } + }); + } + }, + okCancel() { + // single click + if (this.disabled) { + return; + } + this.disabled = true; + ProcessMaker.apiClient.put(`requests/${this.requestId}`, { + status: "CANCELED", + }).then(() => { + ProcessMaker.alert(this.$t("The request was canceled."), "success"); + window.location.reload(); + }).catch(() => { + this.disabled = false; + }); + }, + onCancel() { + ProcessMaker.confirmModal( + this.$t("Caution!"), + this.$t("Are you sure you want cancel this request?"), + "", + () => { + this.okCancel(); + }, + ); + }, + completeRequest() { + ProcessMaker.confirmModal( + this.$t("Caution!"), + this.$t("Are you sure you want to complete this request?"), + "", + () => { + ProcessMaker.apiClient.put(`requests/${this.requestId}`, { + status: "COMPLETED", + }).then(() => { + ProcessMaker.alert(this.$t("Request Completed"), "success"); + window.location.reload(); + }); + }, + ); + }, + retryRequest() { + const apiRequest = () => { + this.retryDisabled = true; + let success = true; + + ProcessMaker.apiClient.put(`requests/${this.requestId}/retry`).then((response) => { + if (response.status !== 200) { + return; + } + + const { message } = response.data; + success = response.data.success || false; + + if (success) { + if (Array.isArray(message)) { + for (const line of message) { + ProcessMaker.alert(this.$t(line), "success"); + } + } + } else { + ProcessMaker.alert(this.$t("Request could not be retried"), "danger"); + } + }).finally(() => setTimeout(() => window.location.reload(), success ? 3000 : 1000)); + }; + + ProcessMaker.confirmModal( + this.$t("Confirm"), + this.$t("Are you sure you want to retry this request?"), + "default", + apiRequest, + ); + }, + rollback(errorTaskId, rollbackToName) { + ProcessMaker.confirmModal( + this.$t("Confirm"), + this.$t( + "Are you sure you want to rollback to the task @{{name}}? Warning! This request will continue as the current published process version.", + { name: rollbackToName }, + ), + "default", + () => { + ProcessMaker.apiClient.post(`tasks/${errorTaskId}/rollback`).then((response) => { + window.location.reload(); + }); + }, + ); + }, + }, +}); diff --git a/resources/jscomposition/cases/casesDetail/variables/index.js b/resources/jscomposition/cases/casesDetail/variables/index.js new file mode 100644 index 0000000000..70f1e26039 --- /dev/null +++ b/resources/jscomposition/cases/casesDetail/variables/index.js @@ -0,0 +1,11 @@ +export default {}; + +export const getRequestId = () => requestId; + +export const getRequestStatus = () => request.status; + +export const getComentableType = () => comentable_type; + +export const getProcessName = () => request.process.name; + +export const api = window.ProcessMaker?.apiClient; diff --git a/resources/jscomposition/cases/casesMain/App.vue b/resources/jscomposition/cases/casesMain/App.vue new file mode 100644 index 0000000000..6f6b358b21 --- /dev/null +++ b/resources/jscomposition/cases/casesMain/App.vue @@ -0,0 +1,18 @@ + + diff --git a/resources/jscomposition/cases/casesMain/CasesDataSection.vue b/resources/jscomposition/cases/casesMain/CasesDataSection.vue new file mode 100644 index 0000000000..d4040f7f21 --- /dev/null +++ b/resources/jscomposition/cases/casesMain/CasesDataSection.vue @@ -0,0 +1,90 @@ + + diff --git a/resources/jscomposition/cases/casesMain/CasesMain.vue b/resources/jscomposition/cases/casesMain/CasesMain.vue new file mode 100644 index 0000000000..8fd2746a4d --- /dev/null +++ b/resources/jscomposition/cases/casesMain/CasesMain.vue @@ -0,0 +1,81 @@ + + diff --git a/resources/jscomposition/cases/casesMain/api/index.js b/resources/jscomposition/cases/casesMain/api/index.js new file mode 100644 index 0000000000..566f0adfb3 --- /dev/null +++ b/resources/jscomposition/cases/casesMain/api/index.js @@ -0,0 +1,555 @@ +export default {}; + +// Method to get counters - change with processmaker API +export const getCounters = async () => { + const url = "http://localhost:3000/appcounters"; + return { + myCases: 36, + inProgress: 20, + completed: 125, + allCases: 145, + allRequests: 777, + }; +}; + +// Method to get data case list - change with processmaker API +export const getData = async () => { + const objects_list = []; + + for (let i = 0; i <= 31; i++) { + const obj = { + caseNumber: `${i}`, + caseTitle: `Case Title ${i}`, + process: `Process ${i}`, + task: `Task ${i}`, + participants: `Avatar ${i}`, + status: `badge ${i}`, + started: `21/21/${i}`, + completed: `22/22/${i}`, + }; + + objects_list.push(obj); + } + + return objects_list; +}; + +export const allCasesData = () => ({ + data: [ + { + case_number: 0, + user_id: 3, + case_title: "aut enim natus", + case_title_formatted: "ratione repellat rerum", + case_status: "COMPLETED", + processes: [ + { id: 5, name: "eligendi ut" }, + { id: 29869, name: "dolorem qui" }, + { id: 3, name: "accusantium consectetur" }, + ], + requests: [ + { + id: 92570, + name: "delectus voluptatem", + parent_request: 24, + }, + { id: 8846, name: "est accusamus culpa", parent_request: 2 }, + ], + request_tokens: null, + tasks: [ + { id: "123", name: "libero tenetur quos quibusdam" }, + { id: "node_2329", name: "modi voluptas quo" }, + { id: "node_4561", name: "asperiores tenetur" }, + ], + participants: [{ id: 25, name: "Dr. Madie Predovic PhD" }], + initiated_at: "1997-08-01T19:59:17.000000Z", + completed_at: "2015-11-10T21:25:11.000000Z", + }, + { + case_number: 1, + user_id: 3, + case_title: "et tempora omnis", + case_title_formatted: "odio reprehenderit eum", + case_status: "IN_PROGRESS", + processes: [ + { id: 39969254, name: "aut itaque" }, + { id: 316545, name: "voluptatem optio" }, + { id: 540006393, name: "et dolor" }, + ], + requests: [ + { id: 689, name: "voluptas aut", parent_request: 185512574 }, + { + id: 3262060, + name: "adipisci est qui", + parent_request: 93397820, + }, + ], + request_tokens: null, + tasks: [ + { id: "node_8166", name: "asperiores qui qui molestias" }, + { id: "node_4102", name: "non repudiandae aut" }, + { id: "node_4351", name: "ducimus facilis" }, + ], + participants: [ + { id: 122025, name: "Jaylin Heaney" }, + { id: 6, name: "Jessika Heller" }, + { id: 748506844, name: "Jillian Gibson" }, + ], + initiated_at: "1980-12-28T08:22:58.000000Z", + completed_at: "2012-07-05T12:27:04.000000Z", + }, + { + case_number: 2, + user_id: 3, + case_title: "qui cum amet", + case_title_formatted: "ad ab modi", + case_status: "COMPLETED", + processes: [ + { id: 6624, name: "velit voluptatibus" }, + { id: 34, name: "consequatur quis" }, + { id: 527764, name: "sint dolores" }, + ], + requests: [ + { id: 57, name: "impedit ducimus", parent_request: 476 }, + { id: 4, name: "qui dolorum non", parent_request: 688 }, + ], + request_tokens: null, + tasks: [ + { id: "node_1050", name: "maiores vel iste a" }, + { id: "node_7764", name: "vel nesciunt ratione" }, + { id: "node_4394", name: "molestiae qui" }, + ], + participants: [ + { id: 8, name: "Kristofer Crist" }, + { id: 163202910, name: "Leonard Bergnaum DDS" }, + { id: 444, name: "Mr. Arvid Schiller MD" }, + ], + initiated_at: "1983-05-19T16:24:24.000000Z", + completed_at: "2019-09-07T09:29:56.000000Z", + }, + { + case_number: 3, + user_id: 3, + case_title: "voluptatem quidem quia", + case_title_formatted: "qui officiis sapiente", + case_status: "COMPLETED", + processes: [ + { id: 92, name: "repellendus voluptatibus" }, + { id: 880611150, name: "architecto est" }, + { id: 9284, name: "eligendi veniam" }, + ], + requests: [ + { id: 3, name: "et aliquid", parent_request: 34377361 }, + { + id: 400403, + name: "consequatur vel magni", + parent_request: 81981614, + }, + ], + request_tokens: null, + tasks: [ + { id: "node_1259", name: "et temporibus totam quia" }, + { id: "node_6096", name: "perferendis animi sapiente" }, + { id: "node_3455", name: "ut occaecati" }, + ], + participants: [ + { id: 448166, name: "Eldon Cartwright DVM" }, + { id: 62433751, name: "Miss Ofelia Grimes" }, + { id: 96612, name: "Dr. Wilburn Treutel" }, + ], + initiated_at: "2006-05-21T07:20:51.000000Z", + completed_at: "1982-03-29T07:36:25.000000Z", + }, + { + case_number: 4, + user_id: 1, + case_title: "sit ut sit", + case_title_formatted: "voluptatem deleniti commodi", + case_status: "IN_PROGRESS", + processes: [ + { id: 767, name: "cupiditate in" }, + { id: 38716, name: "recusandae sequi" }, + { id: 5101818, name: "ut atque" }, + ], + requests: [ + { id: 845, name: "est voluptates", parent_request: 991 }, + { + id: 6922, + name: "repellat deserunt vitae", + parent_request: 7, + }, + ], + request_tokens: null, + tasks: [ + { id: "node_9443", name: "et dignissimos quibusdam esse" }, + { id: "node_3803", name: "fuga voluptatem ratione" }, + { id: "node_6273", name: "non omnis" }, + ], + participants: [ + { id: 2, name: "Allene Purdy" }, + { id: 13471022, name: "Rhiannon Beer DDS" }, + { id: 493335, name: "Emma Lemke PhD" }, + ], + initiated_at: "1989-01-02T20:52:49.000000Z", + completed_at: "1990-07-04T19:11:55.000000Z", + }, + { + case_number: 5, + user_id: 1, + case_title: "est est ad", + case_title_formatted: "dicta vel molestiae", + case_status: "IN_PROGRESS", + processes: [ + { id: 69224450, name: "vero ea" }, + { id: 2477, name: "excepturi voluptatem" }, + { id: 11857, name: "aut occaecati" }, + ], + requests: [ + { + id: 95, + name: "molestias voluptatem", + parent_request: 6682658, + }, + { id: 9706673, name: "et quasi ipsum", parent_request: 2 }, + ], + request_tokens: null, + tasks: [ + { id: "node_1023", name: "quia a molestiae labore" }, + { id: "node_8478", name: "assumenda omnis quis" }, + { id: "node_1863", name: "repellendus saepe" }, + ], + participants: [ + { id: 5762571, name: "Prof. Chad Ledner" }, + { id: 9550, name: "Leta Wunsch Jr." }, + { id: 6589672, name: "Mr. Raul Turcotte" }, + ], + initiated_at: "2018-07-11T00:44:04.000000Z", + completed_at: "1977-04-21T20:33:50.000000Z", + }, + { + case_number: 6, + user_id: 1, + case_title: "ullam error doloribus", + case_title_formatted: "officiis molestiae ut", + case_status: "IN_PROGRESS", + processes: [ + { id: 5181637, name: "delectus dolores" }, + { id: 58250809, name: "fuga beatae" }, + { id: 93813951, name: "atque neque" }, + ], + requests: [ + { + id: 6694520, + name: "nihil aperiam", + parent_request: 525456, + }, + { + id: 9067565, + name: "beatae voluptatem dolorem", + parent_request: 0, + }, + ], + request_tokens: null, + tasks: [ + { id: "node_8227", name: "totam rerum aut ipsum" }, + { id: "node_5132", name: "culpa nisi deleniti" }, + { id: "node_7065", name: "exercitationem minus" }, + ], + participants: [ + { id: 37343205, name: "Abbey Fay" }, + { id: 9459, name: "Dr. Ricardo Bernier" }, + { id: 790987, name: "Baylee Brekke" }, + ], + initiated_at: "2022-12-11T20:48:55.000000Z", + completed_at: "2021-11-07T18:15:58.000000Z", + }, + { + case_number: 7, + user_id: 3, + case_title: "eaque amet repellendus", + case_title_formatted: "et debitis sit", + case_status: "IN_PROGRESS", + processes: [ + { id: 328969, name: "facere sit" }, + { id: 30, name: "aut omnis" }, + { id: 261429681, name: "rerum est" }, + ], + requests: [ + { id: 5157906, name: "voluptate ratione", parent_request: 0 }, + { + id: 60923, + name: "voluptatem eius ipsa", + parent_request: 27, + }, + ], + request_tokens: null, + tasks: [ + { id: "node_8511", name: "cumque exercitationem quia sit" }, + { id: "node_4341", name: "et qui aliquid" }, + { id: "node_8437", name: "rerum exercitationem" }, + ], + participants: [ + { id: 7556, name: "Bonnie Altenwerth" }, + { id: 442, name: "Prof. Nathanael Vandervort" }, + { id: 23533498, name: "Kirk Pfeffer" }, + ], + initiated_at: "1974-10-13T02:35:47.000000Z", + completed_at: "1976-05-06T00:54:14.000000Z", + }, + { + case_number: 8, + user_id: 3, + case_title: "reiciendis optio dicta", + case_title_formatted: "voluptas omnis culpa", + case_status: "IN_PROGRESS", + processes: [ + { id: 44327628, name: "quod illum" }, + { id: 5080232, name: "in omnis" }, + { id: 54, name: "veritatis qui" }, + ], + requests: [ + { + id: 4444, + name: "corrupti adipisci", + parent_request: 2310454, + }, + { + id: 562211360, + name: "voluptatibus id omnis", + parent_request: 4, + }, + ], + request_tokens: null, + tasks: [ + { id: "node_8141", name: "fugiat tenetur nihil pariatur" }, + { id: "node_1343", name: "omnis labore illo" }, + { id: "node_0356", name: "aut aspernatur" }, + ], + participants: [ + { id: 2, name: "Luella Gislason" }, + { id: 47785406, name: "Miss Janae Turner" }, + { id: 96768614, name: "Amya Larson" }, + ], + initiated_at: "1978-01-13T20:53:00.000000Z", + completed_at: "2003-05-09T01:51:49.000000Z", + }, + { + case_number: 9, + user_id: 1, + case_title: "sint eius corporis", + case_title_formatted: "eaque ea quas", + case_status: "COMPLETED", + processes: [ + { id: 219515988, name: "consequatur maiores" }, + { id: 174836, name: "soluta sed" }, + { id: 9, name: "hic architecto" }, + ], + requests: [ + { id: 3084, name: "tempore incidunt", parent_request: 30470 }, + { + id: 2, + name: "neque aut suscipit", + parent_request: 81171529, + }, + ], + request_tokens: null, + tasks: [ + { id: "node_7893", name: "quos ipsam odit quia" }, + { id: "node_0942", name: "et voluptatem perferendis" }, + { id: "node_3020", name: "voluptas recusandae" }, + ], + participants: [ + { id: 8, name: "Kira Buckridge" }, + { id: 34, name: "Briana Rath" }, + { id: 3526, name: "Mr. Franco Veum" }, + ], + initiated_at: "1988-02-11T06:46:46.000000Z", + completed_at: "1997-11-09T18:06:35.000000Z", + }, + { + case_number: 11, + user_id: 3, + case_title: "facere accusantium sequi", + case_title_formatted: "inventore et sequi", + case_status: "IN_PROGRESS", + processes: [ + { id: 94422, name: "sapiente culpa" }, + { id: 4, name: "vero dolorum" }, + { id: 294713669, name: "sit dolor" }, + ], + requests: [ + { + id: 898, + name: "voluptatum perferendis", + parent_request: 116178, + }, + { + id: 96313178, + name: "dolor quis ad", + parent_request: 480366, + }, + ], + request_tokens: null, + tasks: [ + { id: "node_3665", name: "eligendi explicabo suscipit sed" }, + { id: "node_7456", name: "eaque pariatur consectetur" }, + { id: "node_8114", name: "dignissimos occaecati" }, + ], + participants: [ + { id: 52560, name: "Turner Schuppe" }, + { id: 16577119, name: "Chasity Reinger" }, + { id: 116, name: "Victor Padberg" }, + ], + initiated_at: "1995-05-26T12:28:06.000000Z", + completed_at: "1995-12-13T11:27:23.000000Z", + }, + { + case_number: 12, + user_id: 1, + case_title: "quasi perspiciatis ut", + case_title_formatted: "perferendis non ut", + case_status: "IN_PROGRESS", + processes: [ + { id: 284983263, name: "dolorem soluta" }, + { id: 95252498, name: "quos aut" }, + { id: 1115, name: "sed saepe" }, + ], + requests: [ + { id: 17469, name: "tenetur temporibus", parent_request: 3 }, + { + id: 94439, + name: "odio accusantium sed", + parent_request: 189566, + }, + ], + request_tokens: null, + tasks: [ + { id: "node_6891", name: "unde ratione ab quia" }, + { id: "node_2660", name: "tenetur odio sed" }, + { id: "node_5814", name: "ut unde" }, + ], + participants: [ + { id: 44, name: "Kathlyn Johns IV" }, + { id: 447100, name: "Mr. Jamie Yundt" }, + { id: 6992588, name: "Cade McCullough" }, + ], + initiated_at: "1994-05-27T02:03:26.000000Z", + completed_at: "2021-01-29T20:34:17.000000Z", + }, + { + case_number: 13, + user_id: 1, + case_title: "recusandae quas provident", + case_title_formatted: "placeat veniam fugiat", + case_status: "IN_PROGRESS", + processes: [ + { id: 16, name: "ad ratione" }, + { id: 605, name: "molestiae libero" }, + { id: 57, name: "quia aspernatur" }, + ], + requests: [ + { id: 4536, name: "sit quia", parent_request: 78 }, + { id: 7982002, name: "ea ut itaque", parent_request: 7 }, + ], + request_tokens: null, + tasks: [ + { id: "node_1228", name: "error vero exercitationem in" }, + { id: "node_1423", name: "et quia voluptas" }, + { id: "node_8448", name: "consequatur ipsa" }, + ], + participants: [ + { id: 629, name: "Mrs. Vivianne Kling Sr." }, + { id: 170285, name: "Anthony Reichert" }, + { id: 845, name: "Miss Cayla Hyatt DVM" }, + ], + initiated_at: "1994-01-07T14:19:05.000000Z", + completed_at: "1976-07-19T06:02:22.000000Z", + }, + { + case_number: 14, + user_id: 1, + case_title: "dolore expedita possimus", + case_title_formatted: "quia consequuntur blanditiis", + case_status: "COMPLETED", + processes: [ + { id: 96, name: "deleniti nam" }, + { id: 79, name: "totam aut" }, + { id: 142841245, name: "commodi quod" }, + ], + requests: [ + { id: 346, name: "eum consequatur", parent_request: 78984 }, + { + id: 353990, + name: "aut dolorem deleniti", + parent_request: 6272, + }, + ], + request_tokens: null, + tasks: [ + { id: "node_0731", name: "quia excepturi ea aspernatur" }, + { id: "node_4862", name: "iure fugit sed" }, + { id: "node_6267", name: "repellendus fugiat" }, + ], + participants: [ + { id: 76277, name: "Madonna Purdy" }, + { id: 28625426, name: "Mr. Timmothy Ankunding MD" }, + { id: 377993949, name: "Johann Stoltenberg" }, + ], + initiated_at: "1972-11-14T13:12:41.000000Z", + completed_at: "1989-03-29T06:06:42.000000Z", + }, + { + case_number: 15, + user_id: 3, + case_title: "adipisci quisquam nulla", + case_title_formatted: "velit facere nihil", + case_status: "IN_PROGRESS", + processes: [ + { id: 770828, name: "recusandae saepe" }, + { id: 5361354, name: "quia voluptatem" }, + { id: 47187, name: "earum molestiae" }, + ], + requests: [ + { id: 4160, name: "voluptas qui", parent_request: 50115 }, + { + id: 3757, + name: "sunt delectus perspiciatis", + parent_request: 942749, + }, + ], + request_tokens: null, + tasks: [ + { id: "node_7354", name: "commodi sint aliquid et" }, + { id: "node_0561", name: "quod similique eum" }, + { id: "node_1082", name: "soluta ut" }, + ], + participants: [ + { id: 53865127, name: "Marcellus Bailey" }, + { id: 4, name: "Adrianna Leannon" }, + { id: 86, name: "Dr. Luis Miller Jr." }, + ], + initiated_at: "1980-10-10T09:54:40.000000Z", + completed_at: "1990-08-24T08:24:20.000000Z", + }, + ], + meta: { + total: 1000, perPage: 15, currentPage: 1, lastPage: 67, + }, +}); + +export const getAllData = async ({ type, page, perPage }) => { + const response = []; + const allData = allCasesData(); + + for (let index = 0; index < perPage; index += 1) { + const idxLooper = index % (allData.data.length - 1); + + const item = allData.data[idxLooper]; + item.case_number = index + 100 * page; + item.case_title = `${type} ${page} ${item.case_title}`; + item.case_title_formatted = `${type} ${page} ${item.case_title_formatted}`; + response.push(item); + } + + return response; +}; diff --git a/resources/jscomposition/cases/casesMain/components/AppCounters.vue b/resources/jscomposition/cases/casesMain/components/AppCounters.vue new file mode 100644 index 0000000000..203bede000 --- /dev/null +++ b/resources/jscomposition/cases/casesMain/components/AppCounters.vue @@ -0,0 +1,63 @@ + + + diff --git a/resources/jscomposition/cases/casesMain/components/AvatarContainer.vue b/resources/jscomposition/cases/casesMain/components/AvatarContainer.vue new file mode 100644 index 0000000000..03a1b14a30 --- /dev/null +++ b/resources/jscomposition/cases/casesMain/components/AvatarContainer.vue @@ -0,0 +1,30 @@ + + diff --git a/resources/jscomposition/cases/casesMain/components/BadgesSection.vue b/resources/jscomposition/cases/casesMain/components/BadgesSection.vue new file mode 100644 index 0000000000..86065b6759 --- /dev/null +++ b/resources/jscomposition/cases/casesMain/components/BadgesSection.vue @@ -0,0 +1,36 @@ + + diff --git a/resources/jscomposition/cases/casesMain/components/CaseFilter.vue b/resources/jscomposition/cases/casesMain/components/CaseFilter.vue new file mode 100644 index 0000000000..3d9e721653 --- /dev/null +++ b/resources/jscomposition/cases/casesMain/components/CaseFilter.vue @@ -0,0 +1,22 @@ + + diff --git a/resources/jscomposition/cases/casesMain/config/badges.js b/resources/jscomposition/cases/casesMain/config/badges.js new file mode 100644 index 0000000000..d2832c87ba --- /dev/null +++ b/resources/jscomposition/cases/casesMain/config/badges.js @@ -0,0 +1,16 @@ +export default {}; + +export const badges = [ + { + label: "Status : In progress", + }, + { + label: "Status : To do", + }, + { + label: "Status : New Badge", + }, + { + label: "Status : Badge Example", + }, +]; diff --git a/resources/jscomposition/cases/casesMain/config/columns.js b/resources/jscomposition/cases/casesMain/config/columns.js new file mode 100644 index 0000000000..90f90028dc --- /dev/null +++ b/resources/jscomposition/cases/casesMain/config/columns.js @@ -0,0 +1,233 @@ +import moment from "moment"; + +import { + CaseTitleCell, + TruncatedOptionsCell, + ParticipantsCell, + StatusCell, + LinkCell, +} from "../../../system/index"; + +const formatDate = (value, format) => { + let config = "DD/MM/YYYY hh:mm"; + if ( + typeof ProcessMaker !== "undefined" + && ProcessMaker.user + && ProcessMaker.user.datetime_format + ) { + if (format === "datetime") { + config = ProcessMaker.user.datetime_format; + } + if (format === "date") { + config = ProcessMaker.user.datetime_format.replace( + /[\sHh:msaAzZ]/g, + "", + ); + } + } + if (value) { + if (moment(value).isValid()) { + return moment(value).format(config); + } + return value; + } + return "n/a"; +}; + +export default {}; +/** + * Example Column + * field: String + * header: String + * headerFormatter: callback + * resizable: Boolean + * visible: Callback + * formatter: Callback - Build the value in the cell + * width: Number + * cellRenderer: Object Vue to custom the cell + * filter: This attribute is optional + */ + +// My cases: [Case#, Case Title, Process, Task, Participants, Status, Started, Completed] +// In Progress : [ Case#, Case Title, Process, Task, Participants, Status, Started] +// Completed : [Case#, Case Title, Process, Task, Participants, Status, Started, Completed] +// AllCases : [Case#, Case Title, Process, Task, Participants, Status, Started, Completed] +// AllRequest : [Case#, Case Title, Process, Task, Participants, Status, Started, Completed] + +export const caseNumberColumn = () => ({ + field: "case_number", + header: "Case #", + resizable: true, + width: 100, + filter: { + dataType: "string", + operators: ["=", ">", ">=", "in", "between"], + }, + cellRenderer: () => ({ + component: LinkCell, + params: { + click: (row, column, columns) => { + window.document.location = `/cases/${row.case_number}`; + }, + }, + }), +}); + +export const caseTitleColumn = () => ({ + field: "case_title", + header: "Case Title", + resizable: true, + width: 200, + cellRenderer: () => ({ + component: CaseTitleCell, + params: { + click: (row, column, columns) => { + window.document.location = `/cases/${row.case_number}`; + }, + }, + }), + filter: { + dataType: "string", + operators: ["=", ">", ">=", "in", "between"], + }, +}); + +export const processColumn = () => ({ + field: "processes", + header: "Process", + resizable: true, + width: 200, + cellRenderer: () => ({ + component: TruncatedOptionsCell, + params: { + click: (option, row, column, columns) => { + }, + }, + }), + filter: { + dataType: "string", + operators: ["="], + }, +}); + +export const taskColumn = () => ({ + field: "tasks", + header: "Task", + resizable: true, + width: 200, + cellRenderer: () => ({ + component: TruncatedOptionsCell, + params: { + click: (option, row, column, columns) => { + window.document.location = `/tasks/${option.id}/edit`; + }, + }, + }), + filter: { + dataType: "string", + operators: ["="], + }, +}); + +export const participantsColumn = () => ({ + field: "participants", + header: "Participants", + resizable: true, + width: 200, + cellRenderer: () => ({ + component: ParticipantsCell, + params: { + click: (option, row, column, columns) => { + window.document.location = `/profile/${option.id}`; + }, + }, + }), + filter: { + dataType: "string", + operators: ["="], + }, +}); + +export const statusColumn = () => ({ + field: "case_status", + header: "Status", + resizable: true, + width: 200, + cellRenderer: () => ({ + component: StatusCell, + }), + filter: { + dataType: "string", + operators: ["="], + }, +}); + +export const startedColumn = () => ({ + field: "initiated_at", + header: "Started", + resizable: true, + width: 200, + formatter: (row, column, columns) => formatDate(row.initiated_at, "datetime"), + filter: { + dataType: "datetime", + operators: ["between", ">", ">=", "<", "<="], + }, +}); + +export const completedColumn = () => ({ + field: "completed_at", + header: "Completed", + resizable: true, + width: 200, + formatter: (row, column, columns) => formatDate(row.completed_at, "datetime"), + filter: { + dataType: "datetime", + operators: ["between", ">", ">=", "<", "<="], + }, +}); + +export const getColumns = (type) => { + const columnsDefinition = { + default: [ + caseNumberColumn(), + caseTitleColumn(), + processColumn(), + taskColumn(), + participantsColumn(), + statusColumn(), + startedColumn(), + completedColumn(), + ], + in_progress: [ + caseNumberColumn(), + caseTitleColumn(), + processColumn(), + taskColumn(), + participantsColumn(), + statusColumn(), + startedColumn(), + ], + completed: [ + caseNumberColumn(), + caseTitleColumn(), + processColumn(), + taskColumn(), + participantsColumn(), + statusColumn(), + startedColumn(), + completedColumn(), + ], + all: [ + caseNumberColumn(), + caseTitleColumn(), + processColumn(), + taskColumn(), + participantsColumn(), + statusColumn(), + startedColumn(), + completedColumn(), + ], + }; + + return columnsDefinition[type] || columnsDefinition.default; +}; diff --git a/resources/jscomposition/cases/casesMain/config/index.js b/resources/jscomposition/cases/casesMain/config/index.js new file mode 100644 index 0000000000..963420fb99 --- /dev/null +++ b/resources/jscomposition/cases/casesMain/config/index.js @@ -0,0 +1,2 @@ +export * from "./columns"; +export * from "./badges"; diff --git a/resources/jscomposition/cases/casesMain/main.js b/resources/jscomposition/cases/casesMain/main.js new file mode 100644 index 0000000000..97c89abc83 --- /dev/null +++ b/resources/jscomposition/cases/casesMain/main.js @@ -0,0 +1,19 @@ +import App from "./App.vue"; +import { routes } from "./routes"; + +Vue.use(VueRouter); + +const router = new VueRouter({ + mode: "history", + base: "/", + routes, +}); + +new Vue({ + el: "#cases-main", + router, + components: { + App, + }, + render: (h) => h(App), +}); diff --git a/resources/jscomposition/cases/casesMain/routes.js b/resources/jscomposition/cases/casesMain/routes.js new file mode 100644 index 0000000000..cea03cba52 --- /dev/null +++ b/resources/jscomposition/cases/casesMain/routes.js @@ -0,0 +1,27 @@ +import CasesMain from "./CasesMain.vue"; +import CasesDataSection from "./CasesDataSection.vue"; + +export default {}; + +export const routes = [ + { + name: "cases", + path: "/cases", + component: CasesMain, + props(route) { + return {}; + }, + children: [ + { + name: "cases-request", + path: ":id?", + component: CasesDataSection, + props(route) { + return { + listId: route.params?.id || "", + }; + }, + }, + ], + }, +]; diff --git a/resources/jscomposition/cases/casesMain/utils/counters.js b/resources/jscomposition/cases/casesMain/utils/counters.js new file mode 100644 index 0000000000..4de961bad1 --- /dev/null +++ b/resources/jscomposition/cases/casesMain/utils/counters.js @@ -0,0 +1,53 @@ +import { t } from "i18next"; + +export default {}; + +export const formatCounters = (data) => { + const counters = [ + { + header: t("My cases"), + body: data.myCases.toString(), + color: "amber", + icon: "far fa-user", + url: "/cases", + }, + { + header: t("In progress"), + body: data.inProgress.toString(), + color: "green", + icon: "fas fa-list", + url: "/cases/in_progress", + }, + { + header: t("Completed"), + body: data.completed.toString(), + color: "blue", + icon: "far fa-check-circle", + url: "/cases/completed", + }, + ]; + + if (data.allCases) { + counters.push({ + header: t("All cases"), + body: data.allCases.toString(), + color: "purple", + icon: "far fa-clipboard", + url: "/cases/all", + }); + } + + if (data.allRequests) { + counters.push({ + header: t("My requests"), + body: data.allRequests.toString(), + color: "gray", + icon: "fas fa-play", + url: () => { + window.location.href = "/requests"; + }, + }); + } + + return counters; +}; diff --git a/resources/jscomposition/cases/casesMain/utils/index.js b/resources/jscomposition/cases/casesMain/utils/index.js new file mode 100644 index 0000000000..d6a4a113d4 --- /dev/null +++ b/resources/jscomposition/cases/casesMain/utils/index.js @@ -0,0 +1 @@ +export * from "./counters"; diff --git a/resources/jscomposition/config/configBreadcrum.js b/resources/jscomposition/config/configBreadcrum.js new file mode 100644 index 0000000000..7cce51641b --- /dev/null +++ b/resources/jscomposition/config/configBreadcrum.js @@ -0,0 +1,9 @@ +export const configHomeBreadcrum = () => ({ + name: "", + icon: "fas fa-home", + href: "/", + current: false, + first: true, +}); + +export default {}; diff --git a/resources/jscomposition/config/index.js b/resources/jscomposition/config/index.js new file mode 100644 index 0000000000..e173e903ac --- /dev/null +++ b/resources/jscomposition/config/index.js @@ -0,0 +1 @@ +export * from "./configBreadcrum"; diff --git a/resources/jscomposition/system/Breadcrums.vue b/resources/jscomposition/system/Breadcrums.vue new file mode 100644 index 0000000000..9ee3d8277e --- /dev/null +++ b/resources/jscomposition/system/Breadcrums.vue @@ -0,0 +1,46 @@ + + diff --git a/resources/jscomposition/system/index.js b/resources/jscomposition/system/index.js new file mode 100644 index 0000000000..462c7f79f3 --- /dev/null +++ b/resources/jscomposition/system/index.js @@ -0,0 +1,7 @@ +import Breadcrums from "./Breadcrums.vue"; + +export * from "./table/index" + +export default {}; + +export { Breadcrums }; diff --git a/resources/jscomposition/system/table/FilterableTable.vue b/resources/jscomposition/system/table/FilterableTable.vue new file mode 100644 index 0000000000..f58b5ab632 --- /dev/null +++ b/resources/jscomposition/system/table/FilterableTable.vue @@ -0,0 +1,30 @@ + + diff --git a/resources/jscomposition/system/table/Pagination.vue b/resources/jscomposition/system/table/Pagination.vue new file mode 100644 index 0000000000..9f00435935 --- /dev/null +++ b/resources/jscomposition/system/table/Pagination.vue @@ -0,0 +1,241 @@ + + + diff --git a/resources/jscomposition/system/table/SortTable.vue b/resources/jscomposition/system/table/SortTable.vue new file mode 100644 index 0000000000..3c1db18512 --- /dev/null +++ b/resources/jscomposition/system/table/SortTable.vue @@ -0,0 +1,46 @@ + + diff --git a/resources/jscomposition/system/table/cell/CaseTitleCell.vue b/resources/jscomposition/system/table/cell/CaseTitleCell.vue new file mode 100644 index 0000000000..1212cec9c4 --- /dev/null +++ b/resources/jscomposition/system/table/cell/CaseTitleCell.vue @@ -0,0 +1,59 @@ + + diff --git a/resources/jscomposition/system/table/cell/CollapseFormCell.vue b/resources/jscomposition/system/table/cell/CollapseFormCell.vue new file mode 100644 index 0000000000..ab2d971895 --- /dev/null +++ b/resources/jscomposition/system/table/cell/CollapseFormCell.vue @@ -0,0 +1,49 @@ + + + diff --git a/resources/jscomposition/system/table/cell/LinkCell.vue b/resources/jscomposition/system/table/cell/LinkCell.vue new file mode 100644 index 0000000000..5456b17a64 --- /dev/null +++ b/resources/jscomposition/system/table/cell/LinkCell.vue @@ -0,0 +1,45 @@ + + diff --git a/resources/jscomposition/system/table/cell/ParticipantsCell.vue b/resources/jscomposition/system/table/cell/ParticipantsCell.vue new file mode 100644 index 0000000000..4907726bd9 --- /dev/null +++ b/resources/jscomposition/system/table/cell/ParticipantsCell.vue @@ -0,0 +1,89 @@ + + diff --git a/resources/jscomposition/system/table/cell/StatusCell.vue b/resources/jscomposition/system/table/cell/StatusCell.vue new file mode 100644 index 0000000000..9556db9a3a --- /dev/null +++ b/resources/jscomposition/system/table/cell/StatusCell.vue @@ -0,0 +1,74 @@ + + diff --git a/resources/jscomposition/system/table/cell/TruncatedOptionsCell.vue b/resources/jscomposition/system/table/cell/TruncatedOptionsCell.vue new file mode 100644 index 0000000000..e6c369cc5d --- /dev/null +++ b/resources/jscomposition/system/table/cell/TruncatedOptionsCell.vue @@ -0,0 +1,119 @@ + + diff --git a/resources/jscomposition/system/table/cell/index.js b/resources/jscomposition/system/table/cell/index.js new file mode 100644 index 0000000000..218f02f1da --- /dev/null +++ b/resources/jscomposition/system/table/cell/index.js @@ -0,0 +1,15 @@ +import ParticipantsCell from "./ParticipantsCell.vue"; +import TruncatedOptionsCell from "./TruncatedOptionsCell.vue"; +import StatusCell from "./StatusCell.vue"; +import CaseTitleCell from "./CaseTitleCell.vue"; +import LinkCell from "./LinkCell.vue"; +import CollapseFormCell from "./CollapseFormCell.vue"; + +export { + ParticipantsCell, + TruncatedOptionsCell, + StatusCell, + CaseTitleCell, + LinkCell, + CollapseFormCell, +}; diff --git a/resources/jscomposition/system/table/filter/defaultFilter/FilterColumn.vue b/resources/jscomposition/system/table/filter/defaultFilter/FilterColumn.vue new file mode 100644 index 0000000000..43e625d64f --- /dev/null +++ b/resources/jscomposition/system/table/filter/defaultFilter/FilterColumn.vue @@ -0,0 +1,85 @@ + + diff --git a/resources/jscomposition/system/table/filter/defaultFilter/FooterButtons.vue b/resources/jscomposition/system/table/filter/defaultFilter/FooterButtons.vue new file mode 100644 index 0000000000..e6d69b3d2f --- /dev/null +++ b/resources/jscomposition/system/table/filter/defaultFilter/FooterButtons.vue @@ -0,0 +1,29 @@ + + diff --git a/resources/jscomposition/system/table/filter/defaultFilter/SortingButtons.vue b/resources/jscomposition/system/table/filter/defaultFilter/SortingButtons.vue new file mode 100644 index 0000000000..bfc4d66f76 --- /dev/null +++ b/resources/jscomposition/system/table/filter/defaultFilter/SortingButtons.vue @@ -0,0 +1,32 @@ + + diff --git a/resources/jscomposition/system/table/filter/defaultFilter/index.js b/resources/jscomposition/system/table/filter/defaultFilter/index.js new file mode 100644 index 0000000000..a3208c7eed --- /dev/null +++ b/resources/jscomposition/system/table/filter/defaultFilter/index.js @@ -0,0 +1,11 @@ +import FilterColumn from "./FilterColumn.vue"; +import FooterButtons from "./FooterButtons.vue"; +import SortingButtons from "./SortingButtons.vue"; + +export * from "./operator/index"; + +export { + FilterColumn, + FooterButtons, + SortingButtons, +}; diff --git a/resources/jscomposition/system/table/filter/defaultFilter/operator/BetweenOperator.vue b/resources/jscomposition/system/table/filter/defaultFilter/operator/BetweenOperator.vue new file mode 100644 index 0000000000..19182999e8 --- /dev/null +++ b/resources/jscomposition/system/table/filter/defaultFilter/operator/BetweenOperator.vue @@ -0,0 +1,48 @@ + + diff --git a/resources/jscomposition/system/table/filter/defaultFilter/operator/DateBetweenOperator.vue b/resources/jscomposition/system/table/filter/defaultFilter/operator/DateBetweenOperator.vue new file mode 100644 index 0000000000..f89cfb1a6f --- /dev/null +++ b/resources/jscomposition/system/table/filter/defaultFilter/operator/DateBetweenOperator.vue @@ -0,0 +1,48 @@ + + diff --git a/resources/jscomposition/system/table/filter/defaultFilter/operator/DateOperator.vue b/resources/jscomposition/system/table/filter/defaultFilter/operator/DateOperator.vue new file mode 100644 index 0000000000..ffa69b243e --- /dev/null +++ b/resources/jscomposition/system/table/filter/defaultFilter/operator/DateOperator.vue @@ -0,0 +1,18 @@ + + diff --git a/resources/jscomposition/system/table/filter/defaultFilter/operator/FilterOperator.vue b/resources/jscomposition/system/table/filter/defaultFilter/operator/FilterOperator.vue new file mode 100644 index 0000000000..d51977d4f4 --- /dev/null +++ b/resources/jscomposition/system/table/filter/defaultFilter/operator/FilterOperator.vue @@ -0,0 +1,125 @@ + + diff --git a/resources/jscomposition/system/table/filter/defaultFilter/operator/InOperator.vue b/resources/jscomposition/system/table/filter/defaultFilter/operator/InOperator.vue new file mode 100644 index 0000000000..b12b073084 --- /dev/null +++ b/resources/jscomposition/system/table/filter/defaultFilter/operator/InOperator.vue @@ -0,0 +1,72 @@ + + diff --git a/resources/jscomposition/system/table/filter/defaultFilter/operator/InputOperator.vue b/resources/jscomposition/system/table/filter/defaultFilter/operator/InputOperator.vue new file mode 100644 index 0000000000..82ea3fc6b0 --- /dev/null +++ b/resources/jscomposition/system/table/filter/defaultFilter/operator/InputOperator.vue @@ -0,0 +1,17 @@ + + diff --git a/resources/jscomposition/system/table/filter/defaultFilter/operator/index.js b/resources/jscomposition/system/table/filter/defaultFilter/operator/index.js new file mode 100644 index 0000000000..b1c78c6c8e --- /dev/null +++ b/resources/jscomposition/system/table/filter/defaultFilter/operator/index.js @@ -0,0 +1,15 @@ +import BetweenOperator from "./BetweenOperator.vue"; +import FilterOperator from "./FilterOperator.vue"; +import InOperator from "./InOperator.vue"; +import InputOperator from "./InputOperator.vue"; +import DateOperator from "./DateOperator.vue"; +import DateBetweenOperator from "./DateBetweenOperator.vue"; + +export { + BetweenOperator, + FilterOperator, + InOperator, + InputOperator, + DateOperator, + DateBetweenOperator, +}; diff --git a/resources/jscomposition/system/table/filter/defaultFilter/operator/operatorConfig.js b/resources/jscomposition/system/table/filter/defaultFilter/operator/operatorConfig.js new file mode 100644 index 0000000000..366769d6c4 --- /dev/null +++ b/resources/jscomposition/system/table/filter/defaultFilter/operator/operatorConfig.js @@ -0,0 +1,39 @@ +import InputOperator from "./InputOperator.vue"; +import BetweenOperator from "./BetweenOperator.vue"; +import InOperator from "./InOperator.vue"; +import DateOperator from "./DateOperator.vue"; +import DateBetweenOperator from "./DateBetweenOperator.vue"; + +const operatorType = [ + { + operator: ["=", ">", "<", ">=", "<=", "contains", "regex"], + type: ["number", "string"], + component: () => InputOperator, + }, + { + operator: ["between"], + type: ["number", "string"], + component: () => BetweenOperator, + }, + { + operator: ["in"], + type: ["number", "string"], + component: () => InOperator, + }, + { + operator: ["<", "<=", ">", ">="], + type: ["datetime"], + component: () => DateOperator, + }, + { + operator: ["between"], + type: ["datetime"], + component: () => DateBetweenOperator, + }, +]; + +export const getOperatorType = (operator = "=", type = "string") => { + const response = operatorType.find((e) => e.operator.includes(operator) && e.type.includes(type)); + + return response; +}; diff --git a/resources/jscomposition/system/table/filter/index.js b/resources/jscomposition/system/table/filter/index.js new file mode 100644 index 0000000000..72464fa69c --- /dev/null +++ b/resources/jscomposition/system/table/filter/index.js @@ -0,0 +1,2 @@ +export * from "./defaultFilter/index"; +export * from "./sortableFilter/index"; diff --git a/resources/jscomposition/system/table/filter/sortableFilter/SortableFilter.vue b/resources/jscomposition/system/table/filter/sortableFilter/SortableFilter.vue new file mode 100644 index 0000000000..94a930dd8c --- /dev/null +++ b/resources/jscomposition/system/table/filter/sortableFilter/SortableFilter.vue @@ -0,0 +1,41 @@ + + + diff --git a/resources/jscomposition/system/table/filter/sortableFilter/index.js b/resources/jscomposition/system/table/filter/sortableFilter/index.js new file mode 100644 index 0000000000..6d8f856462 --- /dev/null +++ b/resources/jscomposition/system/table/filter/sortableFilter/index.js @@ -0,0 +1,5 @@ +import SortableFilter from "./SortableFilter.vue"; + +export { + SortableFilter, +}; diff --git a/resources/jscomposition/system/table/index.js b/resources/jscomposition/system/table/index.js new file mode 100644 index 0000000000..f6470d7d1e --- /dev/null +++ b/resources/jscomposition/system/table/index.js @@ -0,0 +1,12 @@ +import FilterableTable from "./FilterableTable.vue"; +import SortTable from "./SortTable.vue"; +import Pagination from "./Pagination.vue"; + +export * from "./cell/index"; +export * from "./filter/index"; + +export { + FilterableTable, + SortTable, + Pagination, +}; diff --git a/resources/sass/tailwind.css b/resources/sass/tailwind.css new file mode 100644 index 0000000000..e66e36037c --- /dev/null +++ b/resources/sass/tailwind.css @@ -0,0 +1,9 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + a{ + color: #0872C2; + } + } \ No newline at end of file diff --git a/resources/views/cases/casesMain.blade.php b/resources/views/cases/casesMain.blade.php new file mode 100644 index 0000000000..c14ac7fb97 --- /dev/null +++ b/resources/views/cases/casesMain.blade.php @@ -0,0 +1,19 @@ +@extends('layouts.layout',['content_margin' => '', 'overflow-auto' => '']) + +@section('title') +@endsection + +@section('sidebar') +@include('layouts.sidebar', ['sidebar'=> Menu::get('sidebar_cases')]) +@endsection + +@section('content') +
+@endsection + +@section('js') + +@endsection + +@section('css') +@endsection diff --git a/resources/views/cases/edit.blade.php b/resources/views/cases/edit.blade.php new file mode 100644 index 0000000000..810de3a7c4 --- /dev/null +++ b/resources/views/cases/edit.blade.php @@ -0,0 +1,185 @@ +@extends('layouts.layout',['content_margin' => '', 'overflow-auto' => '']) + +@section('title') + {{ __('Case Detail') }} +@endsection + +@section('sidebar') + @include('layouts.sidebar', ['sidebar' => Menu::get('sidebar_cases')]) +@endsection + +@section('breadcrumbs') + @include('shared.breadcrumbs', ['routes' => [ + __('Cases') => route('cases.index'), + ]]) +@endsection + +@section('content') +
+ + + + + + + +
+@endsection + +@section('js') + + +@endsection diff --git a/resources/views/layouts/layout.blade.php b/resources/views/layouts/layout.blade.php index 2fd4356d4c..301ddc5189 100644 --- a/resources/views/layouts/layout.blade.php +++ b/resources/views/layouts/layout.blade.php @@ -39,6 +39,7 @@ + @yield('css')