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 @@
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+ toogleContainer(e, indexRow)"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+ {{ getValue() }}
+
+
+
+
+ |
+
+
+
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 @@
+
+
+
+
+ {{ getValue() }}
+
+
+
+
+
+
+
+
+ |
+
+
+
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 @@
+
+
+ {{ initials }}
+
+
+
+
+
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 @@
+
+ hover && showTooltip($event)"
+ @mouseleave="($event) => hover && hideTooltip($event)">
+
+
+
+ {{ content }}
+
+
+
+
+
+
+
+
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 @@
+
+
+ {{ processName }}
+
+
+
+
+
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 @@
+
+
+
+ {{ item.label }}
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ `${totalModel} items` }}
+
+
+
+
+
+
+
+ {{ `${selectedOption.value} ${$t("per page")}` }}
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+ onChangeFilter(column, e, index)" />
+
+
+
+
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 @@
+
+
+
+
+
+
+
+ {{ participant.name }}
+
+
+
+
+
+
+ {{ participants[0].name }}
+
+
+
+
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 @@
+
+
+
+ {{ label }}
+
+
+
+
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 @@
+
+
+
+
+ {{ getValue() }}
+
+
+
+ {{ getValue() }}
+
+
+
+
+
+
+
+
+
+
+ -
+
+ {{ getValueOption(option, index) }}
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+ onChangeFilterOperator(e)" />
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+ {{ $t("Cancel") }}
+
+
+
+ {{ $t("Clear") }}
+
+
+
+ {{ $t("Apply") }}
+
+
+
+
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 @@
+
+
+
+
+
+ {{ $t("Sort Ascending") }}
+
+
+
+
+
+ {{ $t("Sort Descending") }}
+
+
+
+
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')
+
+
+
+
+
+
+
+
+
+
+
+ @if ($canCancel == true && $request->status === 'ACTIVE')
+ -
+
+
+ @endif
+
+
+ @{{ __(statusLabel) }}
+
+
+ -
+
@{{ __(labelDate) }}:
+
+ @{{ moment(statusDate).format() }}
+
+
+ @if ($request->user_id)
+ -
+
{{ __('Requested By') }}:
+
+ {{ __('Web Entry') }}
+
+ @endif
+
+ -
+
{{ __('Process') }}
+ {{ $request->name }}
+
+
+ {{ __('Open Process Launchpad') }}
+
+
+
+
+ @if ($request->participants->count())
+ -
+
{{ __('Participants') }}:
+
+
+ @endif
+
+ @if ($canManuallyComplete == true)
+ -
+
{{ __('Manually Complete Request') }}
+
+
+ @endif
+ @if ($canRetry === true)
+ -
+
{{ __('Retry Request') }}
+
+
+ @endif
+ @if ($eligibleRollbackTask)
+ @can('rollback', $errorTask)
+ -
+
{{ __('Rollback Request') }}
+
+ {{ __('Rollback to task') }}: {{ $eligibleRollbackTask->element_name }} ({{ $eligibleRollbackTask->element_id }})
+
+ @endcan
+ @endif
+ @if ($request->parentRequest)
+ -
+
{{ __('Parent Request') }}
+
+ {{ $request->parentRequest->name }}
+
+ @endif
+ @if (count($request->childRequests))
+ -
+
{{ __('Child Requests') }}
+ @foreach ($request->childRequests as $childRequest)
+
+ @endforeach
+
+ @endif
+
+
+
+
+
+
+
+
+
+
+
+
+@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')