@processmaker/screen-builder 2.87.1 → 2.88.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,48 +1,134 @@
1
1
  <template>
2
2
  <div v-if="showTable">
3
- <vuetable
4
- ref="vuetable"
5
- :data-manager="dataManager"
6
- :sort-order="sortOrder"
7
- :api-mode="false"
8
- :fields="fields"
3
+ <filter-table
4
+ :headers="tableHeaders"
9
5
  :data="tableData"
10
- :css="css"
11
- data-path="data"
12
- pagination-path="meta"
6
+ :unread="unreadColumnName"
7
+ :loading="shouldShowLoader"
8
+ @table-row-mouseover="handleRowMouseover"
9
+ @table-row-mouseleave="handleRowMouseleave"
13
10
  >
14
- <template slot="name" slot-scope="props">
15
- <b-link
16
- v-uni-id="props.rowData.id.toString()"
17
- :href="onAction('edit', props.rowData, props.rowIndex)"
11
+ <template
12
+ v-for="(column, index) in tableHeaders"
13
+ v-slot:[column.field]
14
+ >
15
+ <div
16
+ :key="index"
17
+ style="display: inline-block"
18
18
  >
19
- {{ props.rowData.element_name }}
20
- </b-link>
19
+ <img
20
+ v-if="column.field === 'is_priority'"
21
+ src="../../assets/priority-header.svg"
22
+ alt="priority-header"
23
+ width="20"
24
+ height="20"
25
+ />
26
+ <span v-else>{{ $t(column.label) }}</span>
27
+ </div>
21
28
  </template>
22
- <template slot="requestName" slot-scope="props">
23
- <b-link
24
- :href="onAction('showRequestSummary', props.rowData, props.rowIndex)"
29
+ <template
30
+ v-for="(row, rowIndex) in tableData.data"
31
+ v-slot:[`row-${rowIndex}`]
32
+ >
33
+ <td v-for="(header, colIndex) in tableHeaders"
34
+ :key="`${rowIndex}-${colIndex}`"
25
35
  >
26
- #{{ props.rowData.process_request.id }}
27
- {{ props.rowData.process_request.name }}
28
- </b-link>
36
+ <template v-if="containsHTML(getNestedPropertyValue(row, header))">
37
+ <div
38
+ :id="`element-${rowIndex}-${colIndex}`"
39
+ :class="{ 'pm-table-truncate': header.truncate }"
40
+ :style="{ maxWidth: header.width + 'px' }"
41
+ >
42
+ <span v-html="sanitize(getNestedPropertyValue(row, header))"></span>
43
+ </div>
44
+ <b-tooltip
45
+ v-if="header.truncate"
46
+ :target="`element-${rowIndex}-${colIndex}`"
47
+ custom-class="pm-table-tooltip"
48
+ @show="checkIfTooltipIsNeeded"
49
+ >
50
+ {{ sanitizeTooltip(getNestedPropertyValue(row, header)) }}
51
+ </b-tooltip>
52
+ </template>
53
+ <template v-else>
54
+ <template v-if="isComponent(row[header.field])">
55
+ <component
56
+ :is="row[header.field].component"
57
+ v-bind="row[header.field].props"
58
+ >
59
+ </component>
60
+ </template>
61
+ <template v-else>
62
+ <template v-if="header.field === 'due_at'">
63
+ <span
64
+ :class="[
65
+ 'badge',
66
+ 'badge-' + row['color_badge'],
67
+ 'due-' + row['color_badge']
68
+ ]"
69
+ >
70
+ {{ formatRemainingTime(row.due_at) }}
71
+ </span>
72
+ <span>{{ getNestedPropertyValue(row, header) }}</span>
73
+ </template>
74
+ <template v-else-if="header.field === 'is_priority'">
75
+ <span>
76
+ <img
77
+ :src="getImgPriority(row[header.field])"
78
+ :alt="row[header.field] ? 'priority' : 'no-priority'"
79
+ width="20"
80
+ height="20"
81
+ @click.prevent="togglePriority(row.id, !row[header.field])"
82
+ />
83
+ </span>
84
+ </template>
85
+ <template v-else>
86
+ <div
87
+ :id="`element-${rowIndex}-${colIndex}`"
88
+ :class="{ 'pm-table-truncate': header.truncate }"
89
+ :style="{ maxWidth: header.width + 'px' }"
90
+ >
91
+ {{ getNestedPropertyValue(row, header) }}
92
+ <b-tooltip
93
+ v-if="header.truncate"
94
+ :target="`element-${rowIndex}-${colIndex}`"
95
+ custom-class="pm-table-tooltip"
96
+ @show="checkIfTooltipIsNeeded"
97
+ >
98
+ {{ getNestedPropertyValue(row, header) }}
99
+ </b-tooltip>
100
+ </div>
101
+ </template>
102
+ </template>
103
+ </template>
104
+ </td>
29
105
  </template>
30
- <template slot="dueDate" slot-scope="props">
31
- <span :class="classDueDate(props.rowData.due_at)">
32
- {{ formatDate(props.rowData.due_at) }}
33
- </span>
34
- </template>
35
- <template slot="completedDate" slot-scope="props">
36
- <span class="text-dark">
37
- {{ formatDate(props.rowData.completed_at) }}
38
- </span>
39
- </template>
40
- <template slot="preview" slot-scope="props">
41
- <span>
42
- <i class="fa fa-eye" @click="previewTasks(props.rowData)" />
43
- </span>
106
+ </filter-table>
107
+ <component
108
+ :is="taskTooltip"
109
+ v-show="isTooltipVisible"
110
+ :position="rowPosition"
111
+ >
112
+ <template v-slot:task-tooltip-body>
113
+ <div @mouseover="clearHideTimer" @mouseleave="hideTooltip">
114
+ <slot
115
+ name="tooltip"
116
+ :tooltip-row-data="tooltipRowData"
117
+ :preview-tasks="previewTasks"
118
+ >
119
+ <span>
120
+ <b-button
121
+ class="icon-button"
122
+ variant="light"
123
+ @click="previewTasks(tooltipRowData)"
124
+ >
125
+ <i class="fas fa-eye" />
126
+ </b-button>
127
+ </span>
128
+ </slot>
129
+ </div>
44
130
  </template>
45
- </vuetable>
131
+ </component>
46
132
  <component :is="tasksPreview" ref="preview-sidebar" />
47
133
  </div>
48
134
  <div v-else>
@@ -71,6 +157,8 @@ export default {
71
157
  order_direction: "DESC",
72
158
  status: "",
73
159
  showTable: true,
160
+ tableHeaders: [],
161
+ pmqlSearch: "",
74
162
  sortOrder: [
75
163
  {
76
164
  field: "ID",
@@ -78,8 +166,16 @@ export default {
78
166
  direction: "DESC"
79
167
  }
80
168
  ],
169
+ advancedFilter: "",
81
170
  tasksPreview:
82
- (window.SharedComponents && window.SharedComponents.TasksHome) || {}
171
+ (window.SharedComponents && window.SharedComponents.TasksHome) || {},
172
+ taskTooltip:
173
+ (window.SharedComponents && window.SharedComponents.TaskTooltip) || {},
174
+ rowPosition: {},
175
+ ellipsisShow: false,
176
+ isTooltipVisible: false,
177
+ disableRuleTooltip: false,
178
+ hiderTimer: null
83
179
  };
84
180
  },
85
181
  computed: {
@@ -88,8 +184,8 @@ export default {
88
184
  }
89
185
  },
90
186
  mounted() {
91
- this.setFields();
92
- this.pmql = `(user_id = ${ProcessMaker.user.id}) AND (status = "In Progress")`;
187
+ this.setupColumns();
188
+ this.pmql = `(user_id = ${ProcessMaker.user.id})`;
93
189
  this.fetch();
94
190
  this.$root.$on("dropdownSelectionTask", this.fetchData);
95
191
  this.$root.$on("searchTask", this.fetchSearch);
@@ -111,6 +207,10 @@ export default {
111
207
  pmql = this.pmql;
112
208
  }
113
209
 
210
+ if (this.pmqlSearch) {
211
+ pmql = pmql + "AND" + this.pmqlSearch;
212
+ }
213
+
114
214
  if (this.filterDropdowns !== undefined) {
115
215
  filterDropdowns = this.filterDropdowns;
116
216
  }
@@ -145,12 +245,20 @@ export default {
145
245
  ProcessMaker.apiClient
146
246
  .get(
147
247
  `tasks?page=${this.page}&include=process,processRequest,processRequest.user,user,data` +
148
- `&pmql=${encodeURIComponent(pmql)}&per_page=${
149
- this.perPage
150
- }${filterParams}${this.getSortParam()}&non_system=true&${filterDropdowns}`
248
+ `&pmql=${encodeURIComponent(pmql)}
249
+ &per_page=${this.perPage}
250
+ ${filterParams}
251
+ ${this.getSortParam()}
252
+ ${this.advancedFilter}
253
+ &non_system=true&${filterDropdowns}`
151
254
  )
152
255
  .then((response) => {
153
256
  this.showTable = response.data.data.length !== 0;
257
+ for (const record of response.data.data) {
258
+ record["case_title"] = this.formatCaseTitle(record.process_request, record);
259
+ record["color_badge"] = this.formatColorBadge(record["due_at"]);
260
+ record["element_name"] = this.formatActiveTask(record);
261
+ }
154
262
  this.tableData = response.data;
155
263
  this.countResponse = this.tableData.meta.total;
156
264
  this.countOverdue = `${this.tableData.meta.in_overdue}`;
@@ -174,19 +282,81 @@ export default {
174
282
  });
175
283
  });
176
284
  },
285
+ formatActiveTask(row) {
286
+ return `
287
+ <a href="${this.openTask(row)}"
288
+ data-cy="active-task-data"
289
+ class="text-nowrap">
290
+ ${row.element_name}
291
+ </a>`;
292
+ },
293
+ formatColorBadge(date) {
294
+ const days = this.remainingTime(date);
295
+ return days >= 0 ? "primary" : "danger";
296
+ },
297
+ formatCaseTitle(processRequest, record) {
298
+ const draftBadge = this.verifyDraft(record);
299
+ return `
300
+ ${draftBadge}
301
+ <a href="${this.openRequest(processRequest)}" class="text-nowrap">
302
+ ${this.getCaseTitle(processRequest, record)}
303
+ </a>`;
304
+ },
305
+ getCaseTitle(process, record) {
306
+ return (
307
+ process.case_title_formatted ||
308
+ process.case_title ||
309
+ record.case_title ||
310
+ ""
311
+ );
312
+ },
313
+ verifyDraft(record) {
314
+ let draftBadge = "";
315
+ if (record.draft && record.status !== "CLOSED") {
316
+ draftBadge = `
317
+ <span class ="badge badge-warning status-warnig">
318
+ ${this.$t("Draft")}
319
+ </span>
320
+ `;
321
+ }
322
+ return draftBadge;
323
+ },
324
+ openTask(data) {
325
+ return `/tasks/${data.id}/edit`;
326
+ },
327
+ openRequest(data) {
328
+ return `/requests/${data.id}`;
329
+ },
177
330
  getColumns() {
178
331
  const columns = [
179
332
  {
180
333
  label: "Task",
181
- field: "task",
334
+ field: "element_name",
182
335
  sortable: true,
183
- default: true
336
+ default: true,
337
+ width: 153,
338
+ fixed_width: 153,
339
+ resizable: false
340
+ },
341
+ {
342
+ label: "Priority",
343
+ field: "is_priority",
344
+ sortable: false,
345
+ default: true,
346
+ width: 48,
347
+ fixed_width: 48,
348
+ resizable: false
184
349
  },
185
350
  {
186
- label: "Request",
187
- field: "request",
351
+ label: "Case title",
352
+ field: "case_title",
353
+ name: "__slot:case_number",
188
354
  sortable: true,
189
- default: true
355
+ default: true,
356
+ width: 314,
357
+ truncate: true,
358
+ fixed_width: 314,
359
+ resizable: false
190
360
  }
191
361
  ];
192
362
 
@@ -195,19 +365,26 @@ export default {
195
365
  label: "Completed",
196
366
  field: "completed_at",
197
367
  sortable: true,
198
- default: true
368
+ default: true,
369
+ width: 220,
370
+ fixed_width: 220,
371
+ resizable: false
199
372
  });
200
373
  } else {
201
374
  columns.push({
202
375
  label: "Due",
203
376
  field: "due_at",
204
377
  sortable: true,
205
- default: true
378
+ default: true,
379
+ width: 220,
380
+ fixed_width: 220,
381
+ resizable: false
206
382
  });
207
383
  }
208
384
  return columns;
209
385
  },
210
- setFields() {
386
+ setupColumns() {
387
+ this.tableHeaders = this.getColumns();
211
388
  const columns = this.getColumns();
212
389
 
213
390
  columns.forEach((column) => {
@@ -221,6 +398,10 @@ export default {
221
398
  field.field = "element_name";
222
399
  field.sortField = "element_name";
223
400
  break;
401
+ case "is_priority":
402
+ field.name = "__slot:is_priority";
403
+ field.field = "is_priority";
404
+ break;
224
405
  case "request":
225
406
  field.name = "__slot:requestName";
226
407
  field.sortField = "process_requests.id,process_requests.name";
@@ -254,10 +435,6 @@ export default {
254
435
  name: "__slot:preview",
255
436
  title: ""
256
437
  });
257
-
258
- this.$nextTick(() => {
259
- this.$refs.vuetable.normalizeFields();
260
- });
261
438
  },
262
439
  formatDate(value, format) {
263
440
  format = format || "";
@@ -298,9 +475,15 @@ export default {
298
475
  : "text-dark";
299
476
  },
300
477
  fetchData(selectedOption) {
301
- if (selectedOption === "In Progress" || selectedOption === "all") {
302
- this.filterDropdowns = "";
303
- this.pmql = `(user_id = ${ProcessMaker.user.id}) AND (status = "In Progress")`;
478
+ this.filterDropdowns = "";
479
+ this.pmql = `(user_id = ${ProcessMaker.user.id})`
480
+ this.advancedFilter = "";
481
+ if (selectedOption === "Self-service") {
482
+ this.pmql = "";
483
+ this.advancedFilter = `&advanced_filter=[${encodeURIComponent('{"subject":{"type":"Status","value":"status"},"operator":"=","value":"Self Service"}')}]`;
484
+ }
485
+ if (selectedOption === "In Progress") {
486
+ this.pmql = this.pmql + `AND (status = "In Progress")`;
304
487
  }
305
488
  if (selectedOption === "Overdue") {
306
489
  this.filterDropdowns = "overdue=true";
@@ -308,10 +491,126 @@ export default {
308
491
  this.fetch();
309
492
  },
310
493
  fetchSearch(searchData) {
311
- this.pmql = "";
312
- this.pmql = searchData;
494
+ this.pmqlSearch = "";
495
+ this.pmqlSearch = searchData;
313
496
  this.fetch();
497
+ },
498
+ formatRemainingTime(date) {
499
+ const millisecondsPerDay = 1000 * 60 * 60 * 24;
500
+ const remaining = this.remainingTime(date);
501
+ const daysRemaining = Math.ceil(remaining / millisecondsPerDay);
502
+ if (daysRemaining <= 1 && daysRemaining >= -1) {
503
+ const hoursRemaining = Math.ceil(remaining / (1000 * 60 * 60));
504
+ return `${hoursRemaining}H`;
505
+ }
506
+
507
+ return `${daysRemaining}D`;
508
+ },
509
+ remainingTime(date) {
510
+ date = moment(date);
511
+ if (!date.isValid()) {
512
+ return 0;
513
+ }
514
+ return date.diff(this.now);
515
+ },
516
+ sanitizeTooltip(html) {
517
+ let cleanHtml = html.replace(/<script(.*?)>[\s\S]*?<\/script>/gi, "");
518
+ cleanHtml = cleanHtml.replace(/<style(.*?)>[\s\S]*?<\/style>/gi, "");
519
+ cleanHtml = cleanHtml.replace(
520
+ /<(?!img|input|meta|time|button|select|textarea|datalist|progress|meter)[^>]*>/gi,
521
+ ""
522
+ );
523
+ cleanHtml = cleanHtml.replace(/\s+/g, " ");
524
+
525
+ return cleanHtml;
526
+ },
527
+ getImgPriority(data) {
528
+ return data ? "/img/priority.svg" : "/img/no-priority.svg";
529
+ },
530
+ togglePriority(taskId, isPriority) {
531
+ ProcessMaker.apiClient
532
+ .put(`tasks/${taskId}/setPriority`, { is_priority: isPriority })
533
+ .then(() => {
534
+ this.fetch();
535
+ });
536
+ },
537
+ handleRowMouseover(row) {
538
+ debugger;
539
+ if (this.ellipsisShow) {
540
+ this.isTooltipVisible = !this.disableRuleTooltip;
541
+ this.clearHideTimer();
542
+ return;
543
+ }
544
+ this.clearHideTimer();
545
+
546
+ const tableContainer = document.getElementById("table-container");
547
+ const rectTableContainer = tableContainer.getBoundingClientRect();
548
+ const topAdjust = rectTableContainer.top;
549
+
550
+ let elementHeight = 28;
551
+
552
+ this.isTooltipVisible = !this.disableRuleTooltip;
553
+ this.tooltipRowData = row;
554
+
555
+ const rowElement = document.getElementById(`row-${row.id}`);
556
+ let yPosition = 0;
557
+
558
+ const rect = rowElement.getBoundingClientRect();
559
+ yPosition = rect.top + window.scrollY;
560
+
561
+ const selectedFiltersBar = document.querySelector(
562
+ ".selected-filters-bar"
563
+ );
564
+ const selectedFiltersBarHeight = selectedFiltersBar
565
+ ? selectedFiltersBar.offsetHeight
566
+ : 0;
567
+
568
+ elementHeight -= selectedFiltersBarHeight;
569
+
570
+ const rightBorderX = rect.right;
571
+
572
+ const bottomBorderY = yPosition - topAdjust - elementHeight + 100;
573
+
574
+ this.rowPosition = {
575
+ x: rightBorderX,
576
+ y: bottomBorderY
577
+ };
578
+ },
579
+ handleRowMouseleave() {
580
+ this.startHideTimer();
581
+ },
582
+ startHideTimer() {
583
+ this.hideTimer = setTimeout(() => {
584
+ this.hideTooltip();
585
+ }, 500);
586
+ },
587
+ clearHideTimer() {
588
+ clearTimeout(this.hideTimer);
589
+ },
590
+ hideTooltip() {
591
+ if (this.ellipsisShow) {
592
+ return;
593
+ }
594
+ this.isTooltipVisible = false;
314
595
  }
315
596
  }
316
597
  };
317
598
  </script>
599
+
600
+ <style scoped>
601
+ .due-danger {
602
+ background-color: rgba(237, 72, 88, 0.2);
603
+ color: rgba(237, 72, 88, 1);
604
+ font-weight: 600;
605
+ border-radius: 5px;
606
+ }
607
+ .due-primary {
608
+ background: rgba(205, 221, 238, 1);
609
+ color: rgba(86, 104, 119, 1);
610
+ font-weight: 600;
611
+ border-radius: 5px;
612
+ }
613
+ .pm-table-container {
614
+ height: 300px;
615
+ }
616
+ </style>
@@ -23,7 +23,6 @@
23
23
  <vue-form-renderer
24
24
  ref="renderer"
25
25
  v-model="requestData"
26
- :class="{ loading: loadingTask }"
27
26
  :config="screen.config"
28
27
  :computed="screen.computed"
29
28
  :custom-css="screen.custom_css"
@@ -121,7 +120,6 @@ export default {
121
120
  refreshScreen: 0,
122
121
  redirecting: null,
123
122
  loadingButton: false,
124
- loadingTask: false,
125
123
  };
126
124
  },
127
125
  watch: {
@@ -321,9 +319,6 @@ export default {
321
319
  })
322
320
  .catch(() => {
323
321
  this.hasErrors = true;
324
- })
325
- .finally(() => {
326
- this.loadingTask = false;
327
322
  });
328
323
  });
329
324
  },
@@ -623,9 +618,3 @@ export default {
623
618
  },
624
619
  };
625
620
  </script>
626
-
627
- <style scoped>
628
- .loading {
629
- pointer-events: none;
630
- }
631
- </style>
@@ -951,10 +951,10 @@ export default [
951
951
  label: "List Table",
952
952
  options: [
953
953
  { value: "My Tasks", content: "My Tasks" },
954
- { value: "My Requests", content: "My Requests" },
954
+ { value: "My Requests", content: "My Cases" },
955
955
  {
956
956
  value: "Start New Request",
957
- content: "Start New Request"
957
+ content: "Start New Case"
958
958
  }
959
959
  ]
960
960
  }
@@ -1,3 +1,5 @@
1
+ import { get } from "lodash";
2
+
1
3
  export default {
2
4
  data() {
3
5
  return {
@@ -28,6 +30,90 @@ export default {
28
30
  }
29
31
  this.orderDirection = sortOrder[0].direction;
30
32
  this.fetch();
33
+ },
34
+ getNestedPropertyValue(obj, header) {
35
+ return this.format(get(obj, header.field), header);
36
+ },
37
+ format(value, header) {
38
+ let config = "";
39
+ if (header.format === "datetime") {
40
+ config = ProcessMaker.user.datetime_format;
41
+ value = this.convertUTCToLocal(value, config);
42
+ }
43
+ if (header.format === "date") {
44
+ config = ProcessMaker.user.datetime_format.replace(
45
+ /[\sHh:msaAzZ]/g,
46
+ ""
47
+ );
48
+ value = this.convertUTCToLocal(value, config);
49
+ }
50
+ return value;
51
+ },
52
+ convertUTCToLocal(value, config) {
53
+ if (value) {
54
+ if (moment(value).isValid()) {
55
+ return window.moment(value).format(config);
56
+ }
57
+ return value;
58
+ }
59
+ return "-";
60
+ },
61
+ containsHTML(text) {
62
+ const doc = new DOMParser().parseFromString(text, "text/html");
63
+ return Array.from(doc.body.childNodes).some(
64
+ (node) => node.nodeType === Node.ELEMENT_NODE
65
+ );
66
+ },
67
+ isComponent(content) {
68
+ if (content && typeof content === "object") {
69
+ return content.component && typeof content.props === "object";
70
+ }
71
+ return false;
72
+ },
73
+ sanitize(html) {
74
+ return this.removeScripts(html);
75
+ },
76
+ removeScripts(input) {
77
+ const doc = new DOMParser().parseFromString(input, "text/html");
78
+
79
+ const scripts = doc.querySelectorAll("script");
80
+ scripts.forEach((script) => {
81
+ script.remove();
82
+ });
83
+
84
+ const styles = doc.querySelectorAll("style");
85
+ styles.forEach((style) => {
86
+ style.remove();
87
+ });
88
+
89
+ return doc.body.innerHTML;
90
+ },
91
+ changePage(page) {
92
+ this.page = page;
93
+ this.fetch();
94
+ },
95
+ changePerPage(value) {
96
+ this.perPage = value;
97
+ this.fetch();
98
+ },
99
+ formatAvatar(user) {
100
+ return {
101
+ component: "AvatarImage",
102
+ props: {
103
+ size: "25",
104
+ "input-data": user,
105
+ "hide-name": false,
106
+ "name-clickable": true
107
+ }
108
+ };
109
+ },
110
+ formatCategory(categories) {
111
+ return categories.map((item) => item.name).join(", ");
112
+ },
113
+ checkIfTooltipIsNeeded(e, v) {
114
+ if (e.target.offsetWidth >= e.target.scrollWidth) {
115
+ e.preventDefault();
116
+ }
31
117
  }
32
118
  }
33
119
  };