@7365admin1/layer-common 3.0.19 → 3.0.21

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.
@@ -19,7 +19,7 @@
19
19
 
20
20
  <v-toolbar class="pa-0" density="compact">
21
21
  <v-row no-gutters>
22
- <v-col cols="6" class="pa-0">
22
+ <v-col :cols="showMoreActions ? 6 : 12" class="pa-0">
23
23
  <v-btn
24
24
  block
25
25
  variant="text"
@@ -32,7 +32,7 @@
32
32
  </v-btn>
33
33
  </v-col>
34
34
 
35
- <v-col cols="6" class="pa-0">
35
+ <v-col v-if="showMoreActions" cols="6" class="pa-0">
36
36
  <v-menu>
37
37
  <template #activator="{ props }">
38
38
  <v-btn
@@ -204,6 +204,11 @@ const prop = defineProps({
204
204
  type: String,
205
205
  default: "Actions",
206
206
  },
207
+
208
+ showMoreActions: {
209
+ type: Boolean,
210
+ default: true,
211
+ },
207
212
  });
208
213
 
209
214
  const emit = defineEmits([
@@ -1,186 +1,201 @@
1
1
  <template>
2
- <div class="d-flex flex-column">
3
- <v-text-field v-bind="$attrs" ref="dateTimePickerRef" :model-value="dateTimeFormattedReadOnly" autocomplete="off"
4
- :placeholder="inputPlaceholder" :rules="rules" style="z-index: 10" @click="openDatePicker">
5
- <template #append-inner>
6
- <v-icon icon="mdi-calendar" @click.stop="openDatePicker" />
7
- </template>
8
- </v-text-field>
9
- <div class="w-100 d-flex align-end ga-3 hidden-input">
10
- <input ref="dateInput" :type="inputType" v-model="dateTime" :min="minValue" />
11
- </div>
2
+ <div class="d-flex flex-column">
3
+ <v-text-field
4
+ v-bind="$attrs"
5
+ ref="dateTimePickerRef"
6
+ :model-value="dateTimeFormattedReadOnly"
7
+ autocomplete="off"
8
+ :placeholder="inputPlaceholder"
9
+ :rules="rules"
10
+ style="z-index: 10"
11
+ @click="openDatePicker"
12
+ >
13
+ <template #append-inner>
14
+ <v-icon icon="mdi-calendar" @click.stop="openDatePicker" />
15
+ </template>
16
+ </v-text-field>
17
+ <div class="w-100 d-flex align-end ga-3 hidden-input">
18
+ <input
19
+ ref="dateInput"
20
+ :type="inputType"
21
+ v-model="dateTime"
22
+ :min="minValue"
23
+ />
12
24
  </div>
25
+ </div>
13
26
  </template>
14
27
 
15
28
  <script setup lang="ts">
16
- import { ref, computed, watch } from 'vue'
29
+ import { ref, computed, watch } from "vue";
17
30
 
18
31
  const prop = defineProps({
19
- rules: {
20
- type: Array as PropType<Array<any>>,
21
- default: () => []
22
- },
23
- placeholder: {
24
- type: String,
25
- default: 'DD/MM/YYYY, HH:MM AM/PM'
26
- },
27
- dateOnly: {
28
- type: Boolean,
29
- default: false
30
- },
31
- min: {
32
- type: String,
33
- default: undefined
34
- }
35
- })
36
-
37
- const { formatDateISO8601 } = useUtils()
38
- const dateTime = defineModel<string | null>({ default: null }) //2025-10-10T13:09 format
39
- const dateTimeUTC = defineModel<string | null>('utc', { default: null }) // UTC format
40
-
41
- const dateTimeFormattedReadOnly = ref<string | null>(null)
42
- const inputType = computed(() => (prop.dateOnly ? 'date' : 'datetime-local'))
43
-
32
+ rules: {
33
+ type: Array as PropType<Array<any>>,
34
+ default: () => [],
35
+ },
36
+ placeholder: {
37
+ type: String,
38
+ default: "DD/MM/YYYY, HH:MM AM/PM",
39
+ },
40
+ dateOnly: {
41
+ type: Boolean,
42
+ default: false,
43
+ },
44
+ min: {
45
+ type: String,
46
+ default: undefined,
47
+ },
48
+ });
49
+
50
+ const { formatDateISO8601 } = useUtils();
51
+ const dateTime = defineModel<string | null>({ default: null }); //2025-10-10T13:09 format
52
+ const dateTimeUTC = defineModel<string | null>("utc", { default: null }); // UTC format
53
+
54
+ const dateTimeFormattedReadOnly = ref<string | null>(null);
55
+ const inputType = computed(() => (prop.dateOnly ? "date" : "datetime-local"));
44
56
 
45
57
  const minValue = computed(() => {
46
- if (!prop.min) return undefined
47
- if (/^\d{4}-\d{2}-\d{2}$/.test(prop.min)) {
48
- return prop.dateOnly ? prop.min : `${prop.min}T00:00`
49
- }
50
- return prop.min
51
- })
52
- const inputPlaceholder = computed(() => (
53
- prop.placeholder || (prop.dateOnly ? 'MM/DD/YYYY' : 'DD/MM/YYYY, HH:MM AM/PM')
54
- ))
55
-
56
-
57
-
58
-
59
- const dateInput = ref<HTMLInputElement | null>(null)
60
- const dateTimePickerRef = ref<HTMLInputElement | null>(null)
61
-
62
- const isInitialLoad = ref(true)
58
+ if (!prop.min) return undefined;
59
+ if (/^\d{4}-\d{2}-\d{2}$/.test(prop.min)) {
60
+ return prop.dateOnly ? prop.min : `${prop.min}T00:00`;
61
+ }
62
+ return prop.min;
63
+ });
64
+ const inputPlaceholder = computed(
65
+ () =>
66
+ prop.placeholder ||
67
+ (prop.dateOnly ? "MM/DD/YYYY" : "DD/MM/YYYY, HH:MM AM/PM")
68
+ );
69
+
70
+ const dateInput = ref<HTMLInputElement | null>(null);
71
+ const dateTimePickerRef = ref<HTMLInputElement | null>(null);
72
+
73
+ const isInitialLoad = ref(true);
63
74
 
64
75
  function openDatePicker() {
65
- setTimeout(() => {
66
- dateInput.value?.showPicker?.()
67
- }, 0)
76
+ setTimeout(() => {
77
+ dateInput.value?.showPicker?.();
78
+ }, 0);
68
79
  }
69
80
 
70
81
  function validate() {
71
- (dateTimePickerRef.value as any)?.validate()
82
+ (dateTimePickerRef.value as any)?.validate();
72
83
  }
73
84
 
74
85
  function convertToReadableFormat(dateStr: string): string {
86
+ if (!dateStr) return "";
75
87
 
76
- if (!dateStr) return "";
88
+ const date = new Date(dateStr);
89
+ if (Number.isNaN(date.getTime())) return "";
77
90
 
78
- const date = new Date(dateStr)
79
- if (Number.isNaN(date.getTime())) return "";
91
+ const month = String(date.getMonth() + 1).padStart(2, "0");
92
+ const day = String(date.getDate()).padStart(2, "0");
93
+ const year = date.getFullYear();
80
94
 
81
- const month = String(date.getMonth() + 1).padStart(2, '0')
82
- const day = String(date.getDate()).padStart(2, '0')
83
- const year = date.getFullYear()
95
+ if (prop.dateOnly) {
96
+ return `${day}/${month}/${year}`;
97
+ }
84
98
 
85
- if (prop.dateOnly) {
86
- return `${month}/${day}/${year}`
87
- }
99
+ let hours = date.getHours();
100
+ const minutes = String(date.getMinutes()).padStart(2, "0");
88
101
 
89
- let hours = date.getHours()
90
- const minutes = String(date.getMinutes()).padStart(2, '0')
102
+ const ampm = hours >= 12 ? "PM" : "AM";
103
+ hours = hours % 12;
104
+ hours = hours ? hours : 12;
91
105
 
92
- const ampm = hours >= 12 ? 'PM' : 'AM'
93
- hours = hours % 12
94
- hours = hours ? hours : 12
106
+ const formattedTime = `${String(hours).padStart(2, "0")}:${minutes} ${ampm}`;
95
107
 
96
- const formattedTime = `${String(hours).padStart(2, '0')}:${minutes} ${ampm}`
97
-
98
- return `${day}/${month}/${year}, ${formattedTime}`
108
+ return `${day}/${month}/${year}, ${formattedTime}`;
99
109
  }
100
110
 
101
111
  function toDateOnlyInputValue(dateStr: string): string {
102
- if (!dateStr) return ''
103
- if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return dateStr
104
- const date = new Date(dateStr)
105
- if (Number.isNaN(date.getTime())) return ''
106
- const year = date.getFullYear()
107
- const month = String(date.getMonth() + 1).padStart(2, '0')
108
- const day = String(date.getDate()).padStart(2, '0')
109
- return `${year}-${month}-${day}`
112
+ if (!dateStr) return "";
113
+ if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return dateStr;
114
+ const date = new Date(dateStr);
115
+ if (Number.isNaN(date.getTime())) return "";
116
+ const year = date.getFullYear();
117
+ const month = String(date.getMonth() + 1).padStart(2, "0");
118
+ const day = String(date.getDate()).padStart(2, "0");
119
+ return `${year}-${month}-${day}`;
110
120
  }
111
121
 
112
- function handleInitialDate(){
113
- const dateDefault = dateTime.value
114
- const dateUTC = dateTimeUTC.value
115
- if(dateDefault){
116
- dateTimeFormattedReadOnly.value = convertToReadableFormat(dateDefault)
117
- if (prop.dateOnly) {
118
- dateTimeUTC.value = toDateOnlyInputValue(dateDefault)
119
- } else {
120
- const localDate = new Date(dateDefault)
121
- dateTimeUTC.value = localDate.toISOString()
122
- }
123
- } else if (dateUTC){
124
- dateTimeFormattedReadOnly.value = convertToReadableFormat(dateUTC)
125
- if (prop.dateOnly) {
126
- dateTime.value = toDateOnlyInputValue(dateUTC)
127
- } else {
128
- const localDate = new Date(dateUTC)
129
- dateTime.value = formatDateISO8601(localDate)
130
- }
122
+ function handleInitialDate() {
123
+ const dateDefault = dateTime.value;
124
+ const dateUTC = dateTimeUTC.value;
125
+ if (dateDefault) {
126
+ dateTimeFormattedReadOnly.value = convertToReadableFormat(dateDefault);
127
+ if (prop.dateOnly) {
128
+ dateTimeUTC.value = toDateOnlyInputValue(dateDefault);
129
+ } else {
130
+ const localDate = new Date(dateDefault);
131
+ dateTimeUTC.value = localDate.toISOString();
132
+ }
133
+ } else if (dateUTC) {
134
+ dateTimeFormattedReadOnly.value = convertToReadableFormat(dateUTC);
135
+ if (prop.dateOnly) {
136
+ dateTime.value = toDateOnlyInputValue(dateUTC);
131
137
  } else {
132
- dateTimeFormattedReadOnly.value = null
138
+ const localDate = new Date(dateUTC);
139
+ dateTime.value = formatDateISO8601(localDate);
133
140
  }
141
+ } else {
142
+ dateTimeFormattedReadOnly.value = null;
143
+ }
134
144
  }
135
145
 
136
-
137
- watch(dateTime, (dateVal) => {
138
- if (isInitialLoad.value) return // ignore the first run
146
+ watch(
147
+ dateTime,
148
+ (dateVal) => {
149
+ if (isInitialLoad.value) return; // ignore the first run
139
150
  if (!dateVal) {
140
- dateTimeFormattedReadOnly.value = null;
141
- dateTimeUTC.value = null
142
- return
151
+ dateTimeFormattedReadOnly.value = null;
152
+ dateTimeUTC.value = null;
153
+ return;
143
154
  }
144
155
 
145
- dateTimeFormattedReadOnly.value = convertToReadableFormat(dateVal)
156
+ dateTimeFormattedReadOnly.value = convertToReadableFormat(dateVal);
146
157
  if (prop.dateOnly) {
147
- dateTimeUTC.value = toDateOnlyInputValue(dateVal)
158
+ dateTimeUTC.value = toDateOnlyInputValue(dateVal);
148
159
  } else {
149
- const localDate = new Date(dateVal)
150
- dateTimeUTC.value = localDate.toISOString()
160
+ const localDate = new Date(dateVal);
161
+ dateTimeUTC.value = localDate.toISOString();
151
162
  }
152
-
153
- }, { immediate: false })
154
-
155
- watch(dateTimeUTC, () => {
156
- handleInitialDate()
157
- }, { immediate: true})
158
-
163
+ },
164
+ { immediate: false }
165
+ );
166
+
167
+ watch(
168
+ dateTimeUTC,
169
+ () => {
170
+ handleInitialDate();
171
+ },
172
+ { immediate: true }
173
+ );
159
174
 
160
175
  onMounted(async () => {
161
- await nextTick()
162
- isInitialLoad.value = false
163
- // Wait until Vuetify renders its internal input
164
- const nativeInput = (dateTimePickerRef.value as any)?.$el?.querySelector('input')
165
- if (nativeInput) {
166
- nativeInput.addEventListener('click', (e: MouseEvent) => {
167
- e.stopPropagation()
168
- openDatePicker()
169
- })
170
- }
171
-
172
- })
173
-
176
+ await nextTick();
177
+ isInitialLoad.value = false;
178
+ // Wait until Vuetify renders its internal input
179
+ const nativeInput = (dateTimePickerRef.value as any)?.$el?.querySelector(
180
+ "input"
181
+ );
182
+ if (nativeInput) {
183
+ nativeInput.addEventListener("click", (e: MouseEvent) => {
184
+ e.stopPropagation();
185
+ openDatePicker();
186
+ });
187
+ }
188
+ });
174
189
 
175
190
  defineExpose({
176
- validate
177
- })
191
+ validate,
192
+ });
178
193
  </script>
179
194
 
180
195
  <style scoped>
181
196
  .hidden-input {
182
- opacity: 0;
183
- height: 0;
184
- width: 1px;
197
+ opacity: 0;
198
+ height: 0;
199
+ width: 1px;
185
200
  }
186
201
  </style>
@@ -73,6 +73,9 @@
73
73
  <template #item.nature="{ item }">
74
74
  {{ replaceMatch(item.nature, "_", " ") }}
75
75
  </template>
76
+ <template #item.dateInvited="{ item }">
77
+ {{ formatDateInvited(item.dateInvited) }}
78
+ </template>
76
79
  <template #item.action-table="{ item }">
77
80
  <v-menu
78
81
  v-if="
@@ -289,19 +292,24 @@ const props = defineProps({
289
292
  value: "name",
290
293
  },
291
294
  {
292
- title: "Role",
295
+ title: "Email",
296
+
297
+ value: "email",
298
+ },
299
+ {
300
+ title: "Roles and Permission",
293
301
 
294
302
  value: "roleName",
295
303
  },
296
304
  {
297
- title: "Organization",
305
+ title: "Site Invited To",
298
306
 
299
- value: "orgName",
307
+ value: "siteName",
300
308
  },
301
309
  {
302
- title: "Status",
310
+ title: "Date",
303
311
 
304
- value: "status",
312
+ value: "dateInvited",
305
313
  },
306
314
  {
307
315
  title: "Action",
@@ -347,7 +355,7 @@ const normalizedRouteParams = computed(() => {
347
355
 
348
356
  const tabOptions = [
349
357
  { name: "Active", status: "active" },
350
- { name: "Suspended", status: "suspended" },
358
+ { name: "Inactive", status: "suspended" },
351
359
  ];
352
360
 
353
361
  function toRoute(status: any) {
@@ -477,6 +485,29 @@ function showMessage(msg: string, color: string) {
477
485
  messageSnackbar.value = true;
478
486
  }
479
487
 
488
+ function formatDateInvited(value?: string | null) {
489
+ if (!value) return "";
490
+
491
+ const date = new Date(value);
492
+ if (Number.isNaN(date.getTime())) return "";
493
+
494
+ const parts = new Intl.DateTimeFormat("en-US", {
495
+ month: "long",
496
+ day: "numeric",
497
+ year: "numeric",
498
+ hour: "2-digit",
499
+ minute: "2-digit",
500
+ hour12: true,
501
+ }).formatToParts(date);
502
+
503
+ const getPart = (type: Intl.DateTimeFormatPartTypes) =>
504
+ parts.find((part) => part.type === type)?.value ?? "";
505
+
506
+ return `${getPart("month")} ${getPart("day")}, ${getPart("year")} ${getPart(
507
+ "hour"
508
+ )}:${getPart("minute")}${getPart("dayPeriod").toUpperCase()}`;
509
+ }
510
+
480
511
  async function handleUpdateMemberStatus() {
481
512
  if (!selectedMemberId.value) return;
482
513
  updateLoading.value = true;
@@ -732,6 +732,7 @@ const {
732
732
  add: addServiceProvider,
733
733
  createServiceProviderInvite,
734
734
  updateStatus: updateServiceProviderStatus,
735
+ invite
735
736
  } = useServiceProvider();
736
737
  const { getAll: getAllMembers } = useMember();
737
738
 
@@ -759,9 +760,10 @@ const createInviteDialog = ref(false);
759
760
  // const serviceProviderInviteRoles = ref<Array<Record<string, any>>>([]);
760
761
 
761
762
  const { getByUserType } = useMember();
763
+ const { getOrgs } = useOrg();
762
764
 
763
765
  async function checkExistingAccount(email: string) {
764
- // existingAccount.value = null;
766
+ existingAccount.value = null;
765
767
 
766
768
  if (!email.trim() || emailRule(email) !== true) return;
767
769
 
@@ -772,19 +774,23 @@ async function checkExistingAccount(email: string) {
772
774
 
773
775
  if (!user?._id) return;
774
776
 
775
- const member = await getByUserType(user._id, "organization");
776
- console.log("member raw:", member);
777
- console.log("onboardingCompleted:", member?.onboardingCompleted);
777
+ const orgs = await getOrgs({
778
+ user: user._id,
779
+ });
780
+
781
+ const matchedOrg = orgs.items?.find(
782
+ (org: any) => org.type === serviceProviderInvite.value.app
783
+ );
784
+
785
+ if (matchedOrg) {
786
+ existingAccount.value = {
787
+ _id: matchedOrg.value,
788
+ text: matchedOrg.text,
789
+ ...matchedOrg,
790
+ };
791
+ }
778
792
 
779
- if (member?.onboardingCompleted) {
780
- existingAccount.value = {
781
- _id: member.org,
782
- text: member.orgName,
783
- ...member,
784
- };
785
- }
786
793
  } catch (error) {
787
- console.error(error);
788
794
  existingAccount.value = null;
789
795
  } finally {
790
796
  checkingExistingAccount.value = false;
@@ -799,6 +805,14 @@ watch(
799
805
  await checkExistingAccount(value);
800
806
  }
801
807
  );
808
+ watch(
809
+ () => serviceProviderInvite.value.app,
810
+ () => {
811
+ if (serviceProviderInvite.value.email) {
812
+ checkExistingAccount(serviceProviderInvite.value.email);
813
+ }
814
+ }
815
+ );
802
816
 
803
817
  const serviceProviderInviteApps = computed(() =>
804
818
  natureOfBusinessV3.map((item) => ({
@@ -1183,33 +1197,42 @@ async function submitServiceProviderAdd() {
1183
1197
  disableServiceProvider.value = false;
1184
1198
  }
1185
1199
  }
1186
-
1187
1200
  async function submitServiceProviderInvite() {
1188
1201
  disableServiceProvider.value = true;
1189
1202
  messageServiceProvider.value = "";
1203
+
1190
1204
  try {
1191
- const payload = {
1205
+ await invite({
1192
1206
  email: serviceProviderInvite.value.email.trim(),
1193
- name: "",
1194
- app: serviceProviderInvite.value.app,
1195
1207
  orgId: props.orgId,
1196
1208
  siteId: serviceProviderInvite.value.siteId,
1197
1209
  siteName:
1198
- serviceProviderInvite.value.siteName || site.value?.name || props.siteName,
1199
- };
1210
+ serviceProviderInvite.value.siteName ||
1211
+ site.value?.name ||
1212
+ props.siteName,
1213
+
1214
+ inviteType: existingAccount.value
1215
+ ? "create-org"
1216
+ : "organization-invite",
1217
+ });
1200
1218
 
1201
- await createServiceProviderInvite(payload);
1202
1219
  showMessage("Invitation sent.", "success");
1220
+
1203
1221
  if (createMoreServiceProvider.value) {
1204
1222
  serviceProviderInvite.value.email = "";
1205
1223
  } else {
1206
- await setServiceProvider({ mode: "invite", dialog: false });
1224
+ await setServiceProvider({
1225
+ mode: "invite",
1226
+ dialog: false,
1227
+ });
1207
1228
  }
1229
+
1208
1230
  await loadList();
1209
1231
  } catch (error: any) {
1210
1232
  messageServiceProvider.value =
1211
1233
  error?.response?._data?.message ??
1212
1234
  "An error occurred while inviting the service provider.";
1235
+
1213
1236
  showMessage(errorConverter(error), "error");
1214
1237
  } finally {
1215
1238
  disableServiceProvider.value = false;
@@ -132,6 +132,25 @@ export default function useHidAmico() {
132
132
  });
133
133
  }
134
134
 
135
+ function getIntercomStatus(readerId: string) {
136
+ return useNuxtApp().$api<Record<string, any>>(`${basePath}/readers/${readerId}/intercom/status`, {
137
+ method: "GET",
138
+ });
139
+ }
140
+
141
+ function makeIntercomCall(readerId: string, target: string) {
142
+ return useNuxtApp().$api<Record<string, any>>(`${basePath}/readers/${readerId}/intercom/call`, {
143
+ method: "POST",
144
+ body: { target },
145
+ });
146
+ }
147
+
148
+ function finalizeIntercomCall(readerId: string) {
149
+ return useNuxtApp().$api<Record<string, any>>(`${basePath}/readers/${readerId}/intercom/hangup`, {
150
+ method: "POST",
151
+ });
152
+ }
153
+
135
154
  return {
136
155
  getReaders,
137
156
  createReader,
@@ -148,5 +167,8 @@ export default function useHidAmico() {
148
167
  createIdentity,
149
168
  updateIdentity,
150
169
  deleteIdentity,
170
+ getIntercomStatus,
171
+ makeIntercomCall,
172
+ finalizeIntercomCall,
151
173
  };
152
174
  }
@@ -34,6 +34,13 @@ export default function useHidNavigation() {
34
34
  params: { org, site },
35
35
  },
36
36
  },
37
+ {
38
+ title: "Intercom",
39
+ route: {
40
+ name: "org-site-access-mgmt-intercom",
41
+ params: { org, site },
42
+ },
43
+ },
37
44
  {
38
45
  title: "Identity Mapping",
39
46
  route: {
@@ -174,12 +174,13 @@ export default function useServiceProvider() {
174
174
  orgId = "",
175
175
  siteId = "",
176
176
  siteName = "",
177
+ inviteType=""
177
178
  } = {}) {
178
179
  return useNuxtApp().$api<Record<string, any>>(
179
180
  "/api/service-providers/invite",
180
181
  {
181
182
  method: "POST",
182
- body: { email, orgId, siteId, siteName },
183
+ body: { email, orgId, siteId, siteName, inviteType },
183
184
  }
184
185
  );
185
186
  }
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.19",
5
+ "version": "3.0.21",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {
@@ -0,0 +1,24 @@
1
+ <template>
2
+ <v-container fluid>
3
+ <HidEnabledGate
4
+ :site="siteId"
5
+ :org="orgId"
6
+ message="Enable HID as a service for this site before configuring HID intercom."
7
+ >
8
+ <HidIntercomManagement :site="siteId" />
9
+ </HidEnabledGate>
10
+ </v-container>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ definePageMeta({
15
+ layout: "default",
16
+ middleware: ["01-auth", "02-org"],
17
+ memberOnly: true,
18
+ });
19
+
20
+ const route = useRoute();
21
+
22
+ const orgId = computed(() => String(route.params.org || ""));
23
+ const siteId = computed(() => String(route.params.site || ""));
24
+ </script>