@7365admin1/layer-common 3.0.31-staging.12 → 3.0.31-staging.14

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.
@@ -984,60 +984,44 @@
984
984
  <div
985
985
  v-for="item in formattedActivePatrolItems"
986
986
  :key="item.id || item._id"
987
- class="d-flex align-center justify-space-between"
988
- style="
989
- padding: 16px 20px;
990
- border: 1px solid #e2e8f0;
991
- border-radius: 12px;
992
- margin-bottom: 12px;
993
- background-color: #ffffff;
994
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
995
- "
987
+ class="active-patrol-row"
996
988
  >
997
- <div style="flex: 1; min-width: 140px">
998
- <strong
999
- style="
1000
- display: block;
1001
- font-size: 15px;
1002
- font-weight: 700;
1003
- color: #0b2f47;
1004
- margin-bottom: 4px;
1005
- "
1006
- >{{ item.title || item.name }}</strong
1007
- >
1008
- <span style="font-size: 13px; color: #94a3b8">{{
1009
- item.subtitle || item.location || ""
1010
- }}</span>
989
+ <div class="active-patrol-main">
990
+ <strong>{{ item.title || item.name }}</strong>
991
+ <span>{{ item.subtitle || item.location || "" }}</span>
1011
992
  </div>
1012
993
 
1013
- <div
1014
- style="flex: 1; display: flex; align-items: center; gap: 12px"
1015
- >
1016
- <AvatarMain
1017
- :name="item.person || item.assignedTo"
1018
- :size="36"
1019
- />
1020
- <span style="font-size: 14px; color: #64748b">{{
1021
- item.person || item.assignedTo
1022
- }}</span>
994
+ <div class="active-patrol-assignees">
995
+ <div class="active-patrol-assignee">
996
+ <AvatarMain :name="item.primaryAssignee" :size="24" />
997
+ <span>{{ item.primaryAssignee }}</span>
998
+ </div>
999
+ <span
1000
+ v-if="item.extraAssigneeCount > 0"
1001
+ class="active-patrol-more-pill active-patrol-tooltip"
1002
+ :data-tooltip="item.extraAssignees.join(', ')"
1003
+ >
1004
+ +{{ item.extraAssigneeCount }}
1005
+ </span>
1023
1006
  </div>
1024
1007
 
1025
- <div style="flex: 1; display: flex; justify-content: center">
1008
+ <div class="active-patrol-statuses">
1026
1009
  <div
1027
- :style="getPatrolStatusStyle(item.status)"
1028
- style="
1029
- padding: 6px 16px;
1030
- border-radius: 20px;
1031
- font-size: 13px;
1032
- font-weight: 500;
1033
- text-transform: capitalize;
1034
- "
1010
+ class="active-patrol-status-pill"
1011
+ :style="getPatrolStatusStyle(item.primaryStatus)"
1035
1012
  >
1036
- {{ item.status }}
1013
+ {{ item.primaryStatus }}
1037
1014
  </div>
1015
+ <span
1016
+ v-if="item.extraStatusCount > 0"
1017
+ class="active-patrol-more-pill active-patrol-tooltip"
1018
+ :data-tooltip="item.extraStatuses.join(', ')"
1019
+ >
1020
+ +{{ item.extraStatusCount }}
1021
+ </span>
1038
1022
  </div>
1039
1023
 
1040
- <div style="flex: 0 0 auto; text-align: right">
1024
+ <div class="active-patrol-action">
1041
1025
  <v-btn
1042
1026
  variant="outlined"
1043
1027
  class="text-none font-weight-medium"
@@ -2767,18 +2751,23 @@ const activePatrolItems = computed<any[]>(
2767
2751
 
2768
2752
  const formattedActivePatrolItems = computed<any[]>(() => {
2769
2753
  return activePatrolItems.value.map((item) => {
2770
- let status = item.status;
2771
- if (Array.isArray(status)) {
2772
- status = status.join(", ");
2773
- }
2774
- let person = item.person || item.assignedTo || "Unassigned";
2775
- if (Array.isArray(person)) {
2776
- person = person.join(", ");
2777
- }
2754
+ const statuses = normalizeListValue(item.status || "Pending");
2755
+ const assignees = normalizeListValue(
2756
+ item.person || item.assignedTo || "Unassigned"
2757
+ );
2758
+
2778
2759
  return {
2779
2760
  ...item,
2780
- status,
2781
- person,
2761
+ status: statuses.join(", "),
2762
+ statuses,
2763
+ primaryStatus: statuses[0] || "Pending",
2764
+ extraStatuses: statuses.slice(1),
2765
+ extraStatusCount: Math.max(0, statuses.length - 1),
2766
+ person: assignees.join(", "),
2767
+ assignees,
2768
+ primaryAssignee: assignees[0] || "Unassigned",
2769
+ extraAssignees: assignees.slice(1),
2770
+ extraAssigneeCount: Math.max(0, assignees.length - 1),
2782
2771
  };
2783
2772
  });
2784
2773
  });
@@ -3948,6 +3937,20 @@ function normalizeStatus(value: any) {
3948
3937
  return text.replace(/[_-]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
3949
3938
  }
3950
3939
 
3940
+ function normalizeListValue(value: any) {
3941
+ if (Array.isArray(value)) {
3942
+ return value
3943
+ .flatMap((entry) => normalizeListValue(entry))
3944
+ .map((entry) => String(entry).trim())
3945
+ .filter(Boolean);
3946
+ }
3947
+
3948
+ return String(value || "")
3949
+ .split(",")
3950
+ .map((entry) => entry.trim())
3951
+ .filter(Boolean);
3952
+ }
3953
+
3951
3954
  function getStatusTone(status: any) {
3952
3955
  const s = String(status || "").toLowerCase();
3953
3956
  if (
@@ -4415,6 +4418,151 @@ function formatDashboardTime(value?: string) {
4415
4418
  border: 2px solid #f9c97c;
4416
4419
  }
4417
4420
 
4421
+ .active-patrol-row {
4422
+ align-items: center;
4423
+ background-color: #ffffff;
4424
+ border: 1px solid #e2e8f0;
4425
+ border-radius: 8px;
4426
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
4427
+ display: grid;
4428
+ gap: 10px;
4429
+ grid-template-columns: 150px minmax(0, 1fr) max-content max-content;
4430
+ margin-bottom: 12px;
4431
+ min-height: 64px;
4432
+ overflow: visible;
4433
+ padding: 12px 14px;
4434
+ }
4435
+
4436
+ .active-patrol-main {
4437
+ min-width: 0;
4438
+ }
4439
+
4440
+ .active-patrol-main strong {
4441
+ color: #0b2f47;
4442
+ display: block;
4443
+ font-size: 13px;
4444
+ font-weight: 700;
4445
+ line-height: 1.25;
4446
+ margin-bottom: 3px;
4447
+ overflow-wrap: anywhere;
4448
+ }
4449
+
4450
+ .active-patrol-main span {
4451
+ color: #94a3b8;
4452
+ display: block;
4453
+ font-size: 11px;
4454
+ line-height: 1.25;
4455
+ overflow-wrap: anywhere;
4456
+ }
4457
+
4458
+ .active-patrol-assignees {
4459
+ align-items: center;
4460
+ display: flex;
4461
+ gap: 6px;
4462
+ min-width: 0;
4463
+ }
4464
+
4465
+ .active-patrol-assignee {
4466
+ align-items: center;
4467
+ background: #f8fafc;
4468
+ border: 1px solid #e7edf3;
4469
+ border-radius: 999px;
4470
+ display: inline-flex;
4471
+ flex: 1 1 auto;
4472
+ gap: 6px;
4473
+ max-width: 100%;
4474
+ min-width: 0;
4475
+ padding: 2px 8px 2px 3px;
4476
+ }
4477
+
4478
+ .active-patrol-assignee span {
4479
+ color: #475569;
4480
+ font-size: 11px;
4481
+ font-weight: 600;
4482
+ line-height: 1.2;
4483
+ min-width: 0;
4484
+ overflow: hidden;
4485
+ text-overflow: ellipsis;
4486
+ white-space: nowrap;
4487
+ }
4488
+
4489
+ .active-patrol-statuses {
4490
+ align-items: center;
4491
+ display: flex;
4492
+ gap: 6px;
4493
+ justify-content: flex-start;
4494
+ min-width: 0;
4495
+ }
4496
+
4497
+ .active-patrol-status-pill {
4498
+ border-radius: 20px;
4499
+ font-size: 11px;
4500
+ font-weight: 600;
4501
+ line-height: 1.2;
4502
+ padding: 5px 10px;
4503
+ text-transform: capitalize;
4504
+ white-space: nowrap;
4505
+ }
4506
+
4507
+ .active-patrol-more-pill {
4508
+ align-items: center;
4509
+ background: #eef3f8;
4510
+ border: 1px solid #dbe4ed;
4511
+ border-radius: 999px;
4512
+ color: #607386;
4513
+ display: inline-flex;
4514
+ flex: 0 0 auto;
4515
+ font-size: 11px;
4516
+ font-weight: 800;
4517
+ height: 24px;
4518
+ justify-content: center;
4519
+ line-height: 1;
4520
+ min-width: 30px;
4521
+ padding: 0 8px;
4522
+ position: relative;
4523
+ white-space: nowrap;
4524
+ }
4525
+
4526
+ .active-patrol-action {
4527
+ justify-self: end;
4528
+ text-align: right;
4529
+ }
4530
+
4531
+ .active-patrol-tooltip::after {
4532
+ background: #0f2638;
4533
+ border-radius: 6px;
4534
+ bottom: calc(100% + 8px);
4535
+ box-shadow: 0 8px 24px rgba(15, 38, 56, 0.18);
4536
+ color: #ffffff;
4537
+ content: attr(data-tooltip);
4538
+ font-size: 11px;
4539
+ font-weight: 600;
4540
+ left: 50%;
4541
+ line-height: 1.35;
4542
+ max-width: 260px;
4543
+ opacity: 0;
4544
+ padding: 7px 9px;
4545
+ pointer-events: none;
4546
+ position: absolute;
4547
+ text-align: left;
4548
+ transform: translate(-50%, 4px);
4549
+ transition: opacity 0.14s ease, transform 0.14s ease;
4550
+ white-space: normal;
4551
+ width: max-content;
4552
+ z-index: 5;
4553
+ }
4554
+
4555
+ .active-patrol-tooltip:hover::after {
4556
+ opacity: 1;
4557
+ transform: translate(-50%, 0);
4558
+ }
4559
+
4560
+ @media (max-width: 960px) {
4561
+ .active-patrol-row {
4562
+ grid-template-columns: 120px minmax(0, 1fr) max-content max-content;
4563
+ }
4564
+ }
4565
+
4418
4566
  .figma-icon-badge {
4419
4567
  align-items: center;
4420
4568
  border-radius: 50%;
@@ -12,7 +12,7 @@
12
12
  <div class="w-100 d-flex justify-space-between ga-2">
13
13
  <span class="text-subtitle-1 w-100 font-weight-bold mb-3">{{
14
14
  formTitle
15
- }}</span>
15
+ }}</span>
16
16
  <span v-if="withStep2 || withStep3" class="text-subtitle-2 font-weight-bold" style="text-wrap: nowrap">Step
17
17
  <span class="text-primary-button">{{ contractorStep }}</span>/{{ withStep3 ? 3 : withStep2 ? 2 : 1 }}</span>
18
18
  </div>
@@ -30,7 +30,7 @@
30
30
  <v-list-item-title @click.stop="handleAddNewContractorType" class="d-flex align-center ga-1">
31
31
  <span><v-icon icon="mdi-plus" /></span> Add "<strong>{{
32
32
  contractorTypeInput
33
- }}</strong>" as custom contractor type.
33
+ }}</strong>" as custom contractor type.
34
34
  </v-list-item-title>
35
35
  </v-list-item>
36
36
  </template>
@@ -99,16 +99,38 @@
99
99
  <span class="text-subtitle-1">{{ item.name || 'Unknown' }}</span>
100
100
  <span class="text-subtitle-1 text-no-wrap">NRIC: {{ item.nric || 'N/A' }}</span>
101
101
  </div>
102
- <div v-if="getPersonContactOptions(item).length < 2 && getPersonPlateOptions(item).length < 2"
103
- class="d-flex align-center justify-space-between ga-4 w-100">
104
- <span class="text-subtitle-1">{{ getPersonContactOptions(item)[0] || 'N/A' }}</span>
105
- <span class="text-subtitle-1 text-no-wrap">{{ getPersonPlateOptions(item)[0] || 'N/A' }}</span>
106
- </div>
107
102
  </v-list-item>
108
103
  </v-list>
109
104
  </v-menu>
110
105
  </v-col>
111
106
 
107
+
108
+ <v-col v-if="shouldShowField('plateNumber')" cols="12">
109
+ <v-row>
110
+ <v-col cols="12">
111
+ <InputLabel class="text-capitalize" title="Vehicle Number" :required="isVehicleRequired" />
112
+ <!-- <v-text-field v-model.trim.uppercase="visitor.plateNumber" density="comfortable" /> -->
113
+ <v-menu v-model="vehicleSearchMenu" location="bottom" offset-y :close-on-content-click="false">
114
+ <template #activator="{ props }">
115
+ <InputVehicleNumber v-bind="props" v-model="visitor.plateNumber" density="comfortable"
116
+ @update:model-value="handleUpdateVehicleNumber" />
117
+ </template>
118
+
119
+ <v-list v-if="vehicleSearchResults.length" class="pa-1">
120
+ <v-list-item v-for="(item, index) in vehicleSearchResults" :key="item._id || index"
121
+ @click="selectAutofillSearchResult(item, 'vehicleNumber')"
122
+ class="cursor-pointer nric-search-item">
123
+ <div class="d-flex align-center justify-space-between ga-4 w-100">
124
+ <span class="text-subtitle-1">{{ item.name || 'Unknown' }}</span>
125
+ <span class="text-subtitle-1 text-no-wrap">NRIC: {{ item.nric || 'N/A' }}</span>
126
+ </div>
127
+ </v-list-item>
128
+ </v-list>
129
+ </v-menu>
130
+ </v-col>
131
+ </v-row>
132
+ </v-col>
133
+
112
134
  <v-col v-if="shouldShowField('deliveryType')" cols="12">
113
135
  <v-row>
114
136
  <v-col cols="12">
@@ -137,7 +159,7 @@
137
159
  class="d-flex align-center ga-1">
138
160
  <span><v-icon icon="mdi-plus" /></span>Add "<strong>{{
139
161
  companyNameInput
140
- }}</strong>" as new company.
162
+ }}</strong>" as new company.
141
163
  </v-list-item-title>
142
164
  <v-list-item-title v-else-if="!companyNameInput && companyNames.length === 0">
143
165
  Start typing to search for companies.
@@ -167,7 +189,7 @@
167
189
  class="d-flex align-center ga-1">
168
190
  <span><v-icon icon="mdi-plus" /></span>Add "<strong>{{
169
191
  companyNameInput
170
- }}</strong>" as new company.
192
+ }}</strong>" as new company.
171
193
  </v-list-item-title>
172
194
  <v-list-item-title v-else-if="!companyNameInput && companyNames.length === 0">
173
195
  Start typing to search for companies.
@@ -181,36 +203,7 @@
181
203
  </v-col>
182
204
  </template>
183
205
 
184
- <v-col v-if="shouldShowField('plateNumber')" cols="12">
185
- <v-row>
186
- <v-col cols="12">
187
- <InputLabel class="text-capitalize" title="Vehicle Number" :required="isVehicleRequired" />
188
- <!-- <v-text-field v-model.trim.uppercase="visitor.plateNumber" density="comfortable" /> -->
189
- <v-menu v-model="vehicleSearchMenu" location="bottom" offset-y :close-on-content-click="false">
190
- <template #activator="{ props }">
191
- <InputVehicleNumber v-bind="props" v-model="visitor.plateNumber" density="comfortable"
192
- @update:model-value="handleUpdateVehicleNumber" />
193
- </template>
194
206
 
195
- <v-list v-if="vehicleSearchResults.length" class="pa-1">
196
- <v-list-item v-for="(item, index) in vehicleSearchResults" :key="item._id || index"
197
- @click="selectAutofillSearchResult(item, 'vehicleNumber')"
198
- class="cursor-pointer nric-search-item">
199
- <div class="d-flex align-center justify-space-between ga-4 w-100">
200
- <span class="text-subtitle-1">{{ item.name || 'Unknown' }}</span>
201
- <span class="text-subtitle-1 text-no-wrap">NRIC: {{ item.nric || 'N/A' }}</span>
202
- </div>
203
- <div v-if="getPersonContactOptions(item).length < 2 && getPersonPlateOptions(item).length < 2"
204
- class="d-flex align-center justify-space-between ga-4 w-100">
205
- <span class="text-subtitle-1">{{ getPersonContactOptions(item)[0] || 'N/A' }}</span>
206
- <span class="text-subtitle-1 text-no-wrap">{{ getPersonPlateOptions(item)[0] || 'N/A' }}</span>
207
- </div>
208
- </v-list-item>
209
- </v-list>
210
- </v-menu>
211
- </v-col>
212
- </v-row>
213
- </v-col>
214
207
 
215
208
  <v-col v-if="shouldShowField('block')" cols="12">
216
209
  <InputLabel class="text-capitalize" title="Block" required />
@@ -431,7 +424,7 @@
431
424
  </v-data-table>
432
425
 
433
426
  <div class="d-flex justify-end mt-8">
434
- <v-btn v-if="!skipNonCheckedOutNric" color="red" variant="flat" size="large" class="text-none px-10"
427
+ <v-btn v-if="!skipNonCheckedOutNric" color="red" variant="flat" size="large" class="text-none px-10"
435
428
  :loading="loading.searchingNonCheckedOut" @click="handleSkipNricNonCheckedOut">
436
429
  Skip NRIC: {{ visitor.nric || "N/A" }}
437
430
  </v-btn>
@@ -1012,7 +1005,7 @@ function selectAutofillSearchResult(item: Partial<TPeople>, source: Exclude<Auto
1012
1005
  const contactOptions = getPersonContactOptions(item);
1013
1006
  const plateOptions = getPersonPlateOptions(item);
1014
1007
 
1015
- if (contactOptions.length > 1 || plateOptions.length > 1) {
1008
+ if (source === "nric" && (contactOptions.length > 1 || plateOptions.length > 1)) {
1016
1009
  nricSearchMenu.value = false;
1017
1010
  phoneSearchMenu.value = false;
1018
1011
  vehicleSearchMenu.value = false;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "3.0.31-staging.12",
5
+ "version": "3.0.31-staging.14",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {