@processmaker/screen-builder 3.0.2 → 3.0.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@processmaker/screen-builder",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "scripts": {
5
5
  "dev": "VITE_COVERAGE=true vite",
6
6
  "build": "vite build",
@@ -57,7 +57,7 @@
57
57
  "@fortawesome/fontawesome-free": "^5.6.1",
58
58
  "@originjs/vite-plugin-commonjs": "^1.0.3",
59
59
  "@panter/vue-i18next": "^0.15.2",
60
- "@processmaker/vue-form-elements": "0.61.0",
60
+ "@processmaker/vue-form-elements": "0.61.1",
61
61
  "@processmaker/vue-multiselect": "2.3.0",
62
62
  "@storybook/addon-essentials": "^7.6.13",
63
63
  "@storybook/addon-interactions": "^7.6.13",
@@ -116,7 +116,7 @@
116
116
  },
117
117
  "peerDependencies": {
118
118
  "@panter/vue-i18next": "^0.15.0",
119
- "@processmaker/vue-form-elements": "0.61.0",
119
+ "@processmaker/vue-form-elements": "0.61.1",
120
120
  "i18next": "^15.0.8",
121
121
  "vue": "^2.6.12",
122
122
  "vuex": "^3.1.1"
@@ -24,9 +24,11 @@
24
24
  <hr class="card-divider" />
25
25
  <b-card-body class="p-1">
26
26
  <div class="template-details">
27
- <span class="template-name d-block pt-1">{{ template.name }}</span>
27
+ <span class="template-name d-block pt-1">{{
28
+ truncateText(template.name, 45)
29
+ }}</span>
28
30
  <span class="template-description d-block">{{
29
- truncateText(template.description, 100)
31
+ truncateText(template.description, 60)
30
32
  }}</span>
31
33
  </div>
32
34
  <b-collapse v-model="isApplyOptionsActive">
@@ -27,6 +27,15 @@
27
27
  class="mr-2 ml-1"
28
28
  />
29
29
  {{ element.config.name || $t("Variable Name") }}
30
+ <b-badge
31
+ v-if="isInClipboard(items[index])"
32
+ data-cy="copied-badge"
33
+ class="m-2 custom-badge"
34
+ pill
35
+ >
36
+ <i class="far fa-check-circle"></i>
37
+ <span class="pl-2">{{ $t('Copied')}}</span>
38
+ </b-badge>
30
39
  <div class="ml-auto">
31
40
  <clipboard-button
32
41
  :index="index"
@@ -91,6 +100,15 @@
91
100
  class="mr-2 ml-1"
92
101
  />
93
102
  {{ element.config.name || $t("Variable Name") }}
103
+ <b-badge
104
+ v-if="isInClipboard(items[index])"
105
+ data-cy="copied-badge"
106
+ class="m-2 custom-badge"
107
+ pill
108
+ >
109
+ <i class="far fa-check-circle"></i>
110
+ <span class="pl-2">{{ $t('Copied')}}</span>
111
+ </b-badge>
94
112
  <div class="ml-auto">
95
113
  <clipboard-button
96
114
  :index="index"
@@ -334,4 +352,12 @@ export default {
334
352
  box-shadow: 0 0 0 13px rgba(0, 0, 0, 0);
335
353
  }
336
354
  }
355
+ .custom-badge {
356
+ background-color: #D1F4D7 !important;
357
+ color: #06723A !important;
358
+ padding: 0.5rem 0.75rem;
359
+ border-radius: 8px;
360
+ font-weight: 500;
361
+ font-size: 14px;
362
+ }
337
363
  </style>
@@ -42,6 +42,15 @@
42
42
  class="mr-2 ml-1"
43
43
  />
44
44
  {{ element.config.name || $t("Variable Name") }}
45
+ <b-badge
46
+ v-if="isInClipboard(element)"
47
+ data-cy="copied-badge"
48
+ class="m-2 custom-badge"
49
+ pill
50
+ >
51
+ <i class="far fa-check-circle"></i>
52
+ <span class="pl-2">{{ $t('Copied')}}</span>
53
+ </b-badge>
45
54
  <div class="ml-auto">
46
55
  <clipboard-button
47
56
  :index="index"
@@ -108,6 +117,15 @@
108
117
  class="mr-2 ml-1"
109
118
  />
110
119
  {{ element.config.name || $t("Variable Name") }}
120
+ <b-badge
121
+ v-if="isInClipboard(element)"
122
+ data-cy="copied-badge"
123
+ class="m-2 custom-badge"
124
+ pill
125
+ >
126
+ <i class="far fa-check-circle"></i>
127
+ <span class="pl-2">{{ $t('Copied')}}</span>
128
+ </b-badge>
111
129
  <div class="ml-auto">
112
130
  <clipboard-button
113
131
  :index="index"
@@ -375,4 +393,12 @@ export default {
375
393
  box-shadow: 0 0 0 13px rgba(0, 0, 0, 0);
376
394
  }
377
395
  }
396
+ .custom-badge {
397
+ background-color: #D1F4D7 !important;
398
+ color: #06723A !important;
399
+ padding: 0.5rem 0.75rem;
400
+ border-radius: 8px;
401
+ font-weight: 500;
402
+ font-size: 14px;
403
+ }
378
404
  </style>
@@ -194,6 +194,9 @@
194
194
  },
195
195
  deep: true
196
196
  },
197
+ dataSelectionOptions() {
198
+ this.singleField = null;
199
+ }
197
200
  },
198
201
  };
199
202
  </script>
@@ -36,6 +36,9 @@
36
36
  ],
37
37
  };
38
38
  },
39
+ mounted () {
40
+ this.callBuilder(this.designerOptions);
41
+ },
39
42
  computed: {
40
43
  options() {
41
44
  return Object.fromEntries(
@@ -56,10 +59,15 @@
56
59
  options: {
57
60
  handler() {
58
61
  this.$emit("input", this.options);
59
- this.$root.$emit("style-mode", this.options.designerOptions);
62
+ this.callBuilder(this.options.designerOptions);
60
63
  },
61
64
  deep: true
62
65
  },
63
66
  },
67
+ methods: {
68
+ callBuilder(option) {
69
+ this.$root.$emit("style-mode", option);
70
+ }
71
+ }
64
72
  };
65
73
  </script>
@@ -13,7 +13,7 @@
13
13
  <div v-if="settings.encrypted">
14
14
  <select-user-group
15
15
  :key="componentKey"
16
- :label="$t('Users/Groups to View')"
16
+ :label="$t('Users/Groups to View Encrypted Fields')"
17
17
  v-model="settings.assignments"
18
18
  :multiple="true"
19
19
  @input="emitChanges"
@@ -158,6 +158,10 @@ export default {
158
158
  this.removeDefaultClasses();
159
159
  },
160
160
  mounted() {
161
+ if (this.value) {
162
+ this.fetchFiles();
163
+ }
164
+
161
165
  this.$root.$on('set-upload-data-name',
162
166
  (recordList, index, id) => this.listenRecordList(recordList, index, id));
163
167
 
@@ -597,6 +601,10 @@ export default {
597
601
  }
598
602
 
599
603
  if (displayMessage.length > 0) {
604
+ const data = JSON.parse(displayMessage);
605
+ if (data.message) {
606
+ displayMessage = data.message;
607
+ }
600
608
  window.ProcessMaker.alert(`${this.$t('File Upload Error:')} ${displayMessage}`, 'danger');
601
609
  }
602
610
 
@@ -699,7 +707,29 @@ export default {
699
707
  },
700
708
  cfSkipFileUpload() {
701
709
  this.$emit('cf-skip-file-upload');
702
- }
710
+ },
711
+ async fetchFiles() {
712
+ const fileIds = Array.isArray(this.value) ? this.value : [this.value];
713
+
714
+ const fetchPromises = fileIds.map(async (file) => {
715
+ const id = file?.file ?? file;
716
+ const endpoint = `files/${id}`;
717
+ try {
718
+ const response = await ProcessMaker.apiClient.get(endpoint);
719
+ if (response?.data) {
720
+ const fileExists = this.files.some(existingFile => existingFile.id === response.data.id);
721
+ // Check if the file already exists in the list before adding it.
722
+ if (!fileExists) {
723
+ this.files.push(response.data);
724
+ }
725
+ }
726
+ } catch (error) {
727
+ console.error(`Failed to fetch file ${id}`, error);
728
+ }
729
+ });
730
+
731
+ return await Promise.all(fetchPromises);
732
+ },
703
733
  },
704
734
  };
705
735
  </script>
@@ -16,6 +16,7 @@
16
16
  <script>
17
17
  import VueFormRenderer from "../vue-form-renderer.vue";
18
18
  import CollectionRecordsList from "../inspector/collection-records-list.vue";
19
+ import _ from 'lodash';
19
20
 
20
21
  const globalObject = typeof window === "undefined" ? global : window;
21
22
 
@@ -179,12 +180,12 @@ export default {
179
180
  this.selDisplayMode === "View" ? viewScreen : editScreen;
180
181
 
181
182
  this.loadScreen(this.screenCollectionId);
182
-
183
+
183
184
  //This section validates if Collection has draft data
184
185
  if(this.taskDraft?.draft?.data == null || this.taskDraft.draft.data === '') {
185
186
  this.localData = respData;
186
187
  }else{
187
- this.localData = this.taskDraft.draft.data;
188
+ this.localData = _.merge({}, respData, this.taskDraft.draft.data);
188
189
  }
189
190
 
190
191
  })
@@ -212,9 +213,9 @@ export default {
212
213
  },
213
214
  record(record) {
214
215
  this.hasMustache = false;
215
- if (record && !isNaN(record) && record > 0 && this.collection) {
216
+ if (record && !isNaN(record) && record > 0 && this.collection.collectionId) {
216
217
  this.selRecordId = record;
217
- this.loadRecordCollection(this.selCollectionId, record, this.collectionmode);
218
+ this.loadRecordCollection(this.collection.collectionId, record, this.selDisplayMode);
218
219
  } else {
219
220
  if (this.isMustache(record)) {
220
221
  this.callbackRecord();
@@ -206,9 +206,9 @@ export default {
206
206
  },
207
207
  record(record) {
208
208
  this.hasMustache = false;
209
- if (record && !isNaN(record) && record > 0 && this.collection) {
209
+ if (record && !isNaN(record) && record > 0 && this.collection.collectionId) {
210
210
  this.selRecordId = record;
211
- this.loadRecordCollection(this.selCollectionId, record, this.collectionmode);
211
+ this.loadRecordCollection(this.collection.collectionId, record, this.collectionmode);
212
212
  } else {
213
213
  if (this.isMustache(record)) {
214
214
  this.callbackRecord();
@@ -41,7 +41,7 @@
41
41
  <input
42
42
  v-else
43
43
  v-model="localValue"
44
- v-bind="componentConfig"
44
+ v-bind="componentConfigEncField"
45
45
  v-uni-id="name"
46
46
  :name="name"
47
47
  class="form-control"
@@ -231,6 +231,7 @@ export default {
231
231
  labelBtn: '',
232
232
  errorEncryptedField: '',
233
233
  concealExecuted: false,
234
+ componentConfigEncField: null,
234
235
  };
235
236
  },
236
237
  computed: {
@@ -289,17 +290,18 @@ export default {
289
290
  * "encrypted" attribute is enabled
290
291
  */
291
292
  if (this.encryptedConfig?.encrypted) {
293
+ this.componentConfigEncField = JSON.parse(JSON.stringify(this.componentConfig));
292
294
  if (uuidValidate(this.localValue)) {
293
295
  this.inputType = "password";
294
296
  this.iconBtn = "fas fa-eye";
295
297
  this.labelBtn = this.$t("Reveal");
296
298
  this.concealExecuted = true;
297
- this.componentConfig.readonly = true;
299
+ this.componentConfigEncField.readonly = true;
298
300
  } else {
299
301
  this.inputType = "text";
300
302
  this.iconBtn = "fas fa-eye-slash";
301
303
  this.labelBtn = this.$t("Conceal");
302
- this.componentConfig.readonly = false;
304
+ this.componentConfigEncField.readonly = this.componentConfig.readonly;
303
305
  }
304
306
  } else {
305
307
  this.inputType = this.dataType;
@@ -381,7 +383,7 @@ export default {
381
383
  this.iconBtn = "fas fa-eye";
382
384
  this.labelBtn = this.$t("Reveal");
383
385
  this.concealExecuted = true;
384
- this.componentConfig.readonly = true;
386
+ this.componentConfigEncField.readonly = true;
385
387
  this.errorEncryptedField = "";
386
388
 
387
389
  // Assign uuid from encrypted data
@@ -392,7 +394,7 @@ export default {
392
394
  this.inputType = this.dataType;
393
395
  this.iconBtn = "fas fa-eye-slash";
394
396
  this.labelBtn = this.$t("Conceal");
395
- this.componentConfig.readonly = false;
397
+ this.componentConfigEncField.readonly = this.componentConfig.readonly;
396
398
 
397
399
  // Assign value decrypted
398
400
  this.localValue = decryptedValue;
@@ -107,6 +107,7 @@ export default {
107
107
  alwaysAllowEditing: { type: Boolean, default: false },
108
108
  disableInterstitial: { type: Boolean, default: false },
109
109
  waitLoadingListeners: { type: Boolean, default: false },
110
+ isWebEntry: { type: Boolean, default: false },
110
111
  },
111
112
  data() {
112
113
  return {
@@ -677,55 +678,97 @@ export default {
677
678
  return null;
678
679
  }
679
680
  },
681
+
680
682
  /**
681
- * Handles the process completion and redirects the user based on the task assignment.
682
- * @param {object} data - The data object containing endEventDestination.
683
- * @param {string} userId - The ID of the current user.
684
- * @param {string} requestId - The ID of the current request.
683
+ * Handles redirection upon process completion, considering destination type and user task validation.
684
+ * @async
685
+ * @param {Object} data - Contains information about the end event destination.
686
+ * @param {number} userId - ID of the current user.
687
+ * @param {number} requestId - ID of the request to complete.
688
+ * @returns {Promise<void>}
685
689
  */
686
690
  async processCompletedRedirect(data, userId, requestId) {
691
+ // Emit completion event if accessed through web entry.
692
+ if (this.isWebEntry) {
693
+ this.$emit("completed", requestId);
694
+ return;
695
+ }
696
+
687
697
  try {
688
- // Verify if is not anotherProcess type
689
- if (data.endEventDestination.type !== "anotherProcess") {
690
- if (data?.endEventDestination.value) {
691
- window.location.href = data?.endEventDestination.value;
692
- } else {
693
- window.location.href = `/requests/${this.requestId}`;
694
- }
698
+ const destinationUrl = this.resolveDestinationUrl(data);
699
+ if (destinationUrl) {
700
+ window.location.href = destinationUrl;
695
701
  return;
696
702
  }
697
- // Parse endEventDestination from the provided data
698
- const endEventDestination = this.parseJsonSafely(
699
- data.endEventDestination.value
700
- );
701
- // Get the next request using retry logic
702
- const nextRequest = await this.retryApiCall(() =>
703
- this.getNextRequest(
704
- endEventDestination.processId,
705
- endEventDestination.startEvent
706
- )
707
- );
708
-
709
- const params = {
710
- processRequestId: nextRequest.data.id,
711
- status: "ACTIVE",
712
- page: 1,
713
- perPage: 1
714
- };
715
- // Get the tasks for the next request using retry logic
716
- const response = await this.retryApiCall(() => this.getTasks(params));
717
- // Handle the first task from the response
718
- const firstTask = response.data.data[0];
719
- if (firstTask && firstTask.user_id === userId) {
720
- this.redirectToTask(firstTask.id);
721
- } else {
722
- this.redirectToRequest(requestId);
723
- }
703
+
704
+ // Proceed to handle redirection to the next request if applicable.
705
+ await this.handleNextRequestRedirection(data, userId, requestId);
724
706
  } catch (error) {
725
707
  console.error("Error processing completed redirect:", error);
726
708
  this.$emit("completed", requestId);
727
709
  }
728
710
  },
711
+
712
+ /**
713
+ * Resolves the URL to redirect to if the end event is not another process.
714
+ * @param {Object} data - Contains the end event destination data.
715
+ * @returns {string|null} - The URL for redirection, or null if proceeding to another process.
716
+ */
717
+ resolveDestinationUrl(data) {
718
+ if (data.endEventDestination.type !== "anotherProcess") {
719
+ return data.endEventDestination.value || `/requests/${this.requestId}`;
720
+ }
721
+ return null;
722
+ },
723
+
724
+ /**
725
+ * Handles redirection logic to the next request's task or fallback to the request itself.
726
+ * @async
727
+ * @param {Object} data - Contains the end event destination.
728
+ * @param {number} userId - ID of the current user.
729
+ * @param {number} requestId - ID of the request to complete.
730
+ * @returns {Promise<void>}
731
+ */
732
+ async handleNextRequestRedirection(data, userId, requestId) {
733
+ const nextRequest = await this.fetchNextRequest(data.endEventDestination);
734
+ const firstTask = await this.fetchFirstTask(nextRequest);
735
+
736
+ if (firstTask?.user_id === userId) {
737
+ this.redirectToTask(firstTask.id);
738
+ } else {
739
+ this.redirectToRequest(requestId);
740
+ }
741
+ },
742
+
743
+ /**
744
+ * Fetch the next request using retry logic.
745
+ * @async
746
+ * @param {Object} endEventDestination - The parsed end event destination object.
747
+ * @returns {Promise<Object>} - The next request data.
748
+ */
749
+ async fetchNextRequest(endEventDestination) {
750
+ const destinationData = this.parseJsonSafely(endEventDestination.value);
751
+ return await this.retryApiCall(() =>
752
+ this.getNextRequest(destinationData.processId, destinationData.startEvent)
753
+ );
754
+ },
755
+
756
+ /**
757
+ * Fetch the first task from the next request using retry logic.
758
+ * @async
759
+ * @param {Object} nextRequest - The next request object.
760
+ * @returns {Promise<Object|null>} - The first task data, or null if no tasks found.
761
+ */
762
+ async fetchFirstTask(nextRequest) {
763
+ const params = {
764
+ processRequestId: nextRequest.data.id,
765
+ status: "ACTIVE",
766
+ page: 1,
767
+ perPage: 1
768
+ };
769
+ const response = await this.retryApiCall(() => this.getTasks(params));
770
+ return response.data.data[0] || null;
771
+ },
729
772
  getAllowedRequestId() {
730
773
  const permissions = this.task.user_request_permission || [];
731
774
  const permission = permissions.find(item => item.process_request_id === this.parentRequest)
@@ -127,6 +127,7 @@
127
127
  v-if="isClipboardPage(tabPage)"
128
128
  variant="link"
129
129
  @click="clearClipboard"
130
+ class="no-text-transform"
130
131
  >
131
132
  {{ $t('Clear All') }}
132
133
  </b-button>
@@ -201,6 +202,15 @@
201
202
  class="mr-2 ml-1"
202
203
  />
203
204
  {{ element.config.name || element.label || $t("Field Name") }}
205
+ <b-badge
206
+ v-if="!isClipboardPage(tabPage) && isInClipboard(extendedPages[tabPage].items[index])"
207
+ data-cy="copied-badge"
208
+ class="m-2 custom-badge"
209
+ pill
210
+ >
211
+ <i class="far fa-check-circle"></i>
212
+ <span class="pl-2">{{ $t('Copied')}}</span>
213
+ </b-badge>
204
214
  <div class="ml-auto">
205
215
  <clipboard-button
206
216
  v-if="!isClipboardPage(tabPage)"
@@ -245,7 +255,9 @@
245
255
  v-model="element.items"
246
256
  :validation-errors="validationErrors"
247
257
  class="card-body"
248
- :class="styleMode === 'Modern' ? elementCssClassModern(element) : elementCssClass(element)"
258
+ :class="styleMode === 'Modern' && element.component === 'FormRecordList'
259
+ ? elementCssClassModern(element)
260
+ : elementCssClass(element)"
249
261
  :selected="selected"
250
262
  :config="element.config"
251
263
  :ai-element="element"
@@ -266,6 +278,15 @@
266
278
  class="mr-2 ml-1"
267
279
  />
268
280
  {{ element.config.name || $t("Variable Name") }}
281
+ <b-badge
282
+ v-if="!isClipboardPage(tabPage) && isInClipboard(extendedPages[tabPage].items[index])"
283
+ data-cy="copied-badge"
284
+ class="m-2 custom-badge"
285
+ pill
286
+ >
287
+ <i class="far fa-check-circle"></i>
288
+ <span class="pl-2">{{ $t('Copied')}}</span>
289
+ </b-badge>
269
290
  <div class="ml-auto">
270
291
  <clipboard-button
271
292
  v-if="!isClipboardPage(tabPage)"
@@ -299,7 +320,9 @@
299
320
  :tabindex="element.config.interactive ? 0 : -1"
300
321
  class="card-body m-0 pb-4 pt-4"
301
322
  :class="[
302
- styleMode === 'Modern' ? elementCssClassModern(element) : elementCssClass(element),
323
+ styleMode === 'Modern' && element.component === 'FormRecordList'
324
+ ? elementCssClassModern(element)
325
+ : elementCssClass(element),
303
326
  { 'prevent-interaction': !element.config.interactive }
304
327
  ]"
305
328
  @input="
@@ -1745,4 +1768,15 @@ $side-bar-font-size: 0.875rem;
1745
1768
  cursor: not-allowed; /* Cambia el cursor cuando se pasa por encima */
1746
1769
  pointer-events: all; /* Permite que el pseudo-elemento reciba eventos del ratón */
1747
1770
  }
1771
+ .custom-badge {
1772
+ background-color: #D1F4D7 !important;
1773
+ color: #06723A !important;
1774
+ padding: 0.5rem 0.75rem;
1775
+ border-radius: 8px;
1776
+ font-weight: 500;
1777
+ font-size: 14px;
1778
+ }
1779
+ .no-text-transform {
1780
+ text-transform: none;
1781
+ }
1748
1782
  </style>
package/src/main.js CHANGED
@@ -325,6 +325,14 @@ window.Echo = {
325
325
  }, 1000);
326
326
  });
327
327
  },
328
+
329
+ eventMockNext(event, response) {
330
+ this.listeners.forEach((listener) => {
331
+ setTimeout(() => {
332
+ listener.callback(response);
333
+ }, 1000);
334
+ });
335
+ },
328
336
  private() {
329
337
  return {
330
338
  notification(callback) {
@@ -115,6 +115,9 @@ export default {
115
115
  // replace uuids in clipboard content
116
116
  clipboardContent.forEach(this.updateUuids);
117
117
  page.items.splice(index, 1, ...clipboardContent);
118
+ if (clipboardContent.length) {
119
+ window.ProcessMaker.alert(this.$t("Clipboard Pasted Succesfully"), "success");
120
+ }
118
121
  }
119
122
  if (item.items) {
120
123
  replaceInPage(item);
@@ -144,6 +147,7 @@ export default {
144
147
  }
145
148
  );
146
149
  if (confirm) {
150
+ this.clipboardPage.items = [];
147
151
  this.$store.dispatch("clipboardModule/clearClipboard");
148
152
  this.$root.$emit('update-clipboard');
149
153
  }