@7365admin1/layer-common 1.11.48 → 1.11.50
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/CHANGELOG.md +12 -0
- package/components/CameraForm.vue +21 -7
- package/components/CameraMain.vue +30 -3
- package/components/EntryPassMain.vue +49 -0
- package/components/Input/DatePicker.vue +11 -2
- package/components/SiteSettings.vue +190 -69
- package/components/VehicleForm.vue +3 -3
- package/components/VisitorForm.vue +6 -6
- package/composables/useMember.ts +17 -1
- package/composables/useOrg.ts +24 -2
- package/package.json +1 -1
- package/types/vehicle.d.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -177,16 +177,16 @@ const hostRule = computed(() => {
|
|
|
177
177
|
return (v: string | any) => {
|
|
178
178
|
if (!v) return true;
|
|
179
179
|
|
|
180
|
-
const isAnpr = prop.type ===
|
|
181
|
-
|
|
180
|
+
const isAnpr = prop.type === "anpr";
|
|
181
|
+
|
|
182
182
|
const anprRegex = /^http:\/\/[a-z0-9.-]+\.[a-z]{2,}(:\d+)?$/i;
|
|
183
183
|
const defaultRegex = /^https:\/\/[a-z0-9.-]+\.[a-z]{2,}\/[a-z0-9._-]+$/i;
|
|
184
184
|
|
|
185
185
|
if (isAnpr) {
|
|
186
|
-
return anprRegex.test(v) ||
|
|
186
|
+
return anprRegex.test(v) || "Format: http://domain:port";
|
|
187
187
|
}
|
|
188
|
-
|
|
189
|
-
return defaultRegex.test(v) ||
|
|
188
|
+
|
|
189
|
+
return defaultRegex.test(v) || "Format: https://domain/path";
|
|
190
190
|
};
|
|
191
191
|
});
|
|
192
192
|
|
|
@@ -211,8 +211,18 @@ if (prop.mode === "edit") {
|
|
|
211
211
|
console.log("prop.camera", prop?.camera);
|
|
212
212
|
camera.value.site = prop.site;
|
|
213
213
|
camera.value.type = prop.type;
|
|
214
|
-
camera.value.category =
|
|
215
|
-
|
|
214
|
+
camera.value.category =
|
|
215
|
+
prop.camera?.category && prop.title == "Edit Camera"
|
|
216
|
+
? prop.camera?.category
|
|
217
|
+
: prop.type == "anpr"
|
|
218
|
+
? "resident"
|
|
219
|
+
: "standard";
|
|
220
|
+
camera.value.direction =
|
|
221
|
+
prop.camera?.direction && prop.title == "Edit Camera"
|
|
222
|
+
? prop.camera?.direction
|
|
223
|
+
: prop.type == "anpr"
|
|
224
|
+
? "both"
|
|
225
|
+
: "none";
|
|
216
226
|
|
|
217
227
|
const validForm = ref(false);
|
|
218
228
|
const formRef = ref<HTMLFormElement | null>(null);
|
|
@@ -289,6 +299,10 @@ async function submit() {
|
|
|
289
299
|
|
|
290
300
|
emit("success", `${prop.title} added successfully!`);
|
|
291
301
|
} catch (error: any) {
|
|
302
|
+
console.log(
|
|
303
|
+
"error",
|
|
304
|
+
error.response?._data?.message || "Failed to add camera"
|
|
305
|
+
);
|
|
292
306
|
emit("error", error.response?._data?.message || "Failed to add camera");
|
|
293
307
|
} finally {
|
|
294
308
|
disable.value = false;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
<template #append>
|
|
13
13
|
<v-btn
|
|
14
|
-
|
|
14
|
+
v-if="!prop.readOnly"
|
|
15
15
|
variant="flat"
|
|
16
16
|
color="primary"
|
|
17
17
|
class="text-none"
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
:type="prop.type"
|
|
62
62
|
@cancel="dialogAdd = false"
|
|
63
63
|
@success="handleSuccess"
|
|
64
|
+
@error="handleError"
|
|
64
65
|
/>
|
|
65
66
|
</v-dialog>
|
|
66
67
|
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
:type="prop.type"
|
|
73
74
|
@cancel="dialogEdit = false"
|
|
74
75
|
@success="handleSuccess"
|
|
76
|
+
@error="handleError"
|
|
75
77
|
mode="edit"
|
|
76
78
|
:camera="camera"
|
|
77
79
|
/>
|
|
@@ -200,6 +202,13 @@
|
|
|
200
202
|
</v-card>
|
|
201
203
|
</v-dialog>
|
|
202
204
|
|
|
205
|
+
<Snackbar
|
|
206
|
+
v-model="messageSnackbar"
|
|
207
|
+
:text="message"
|
|
208
|
+
:color="messageColor"
|
|
209
|
+
style="z-index: 3000"
|
|
210
|
+
/>
|
|
211
|
+
|
|
203
212
|
<v-dialog v-model="dialogDelete" persistent width="540">
|
|
204
213
|
<v-card width="100%">
|
|
205
214
|
<v-card-text
|
|
@@ -319,6 +328,16 @@ const dialogEdit = ref(false);
|
|
|
319
328
|
const dialogPreview = ref(false);
|
|
320
329
|
const dialogDelete = ref(false);
|
|
321
330
|
|
|
331
|
+
const messageSnackbar = ref(false);
|
|
332
|
+
const message = ref("");
|
|
333
|
+
const messageColor = ref<"success" | "error" | "info">();
|
|
334
|
+
|
|
335
|
+
function showMessage(text: string, type: "success" | "error" | "info") {
|
|
336
|
+
messageSnackbar.value = true;
|
|
337
|
+
message.value = text;
|
|
338
|
+
messageColor.value = type;
|
|
339
|
+
}
|
|
340
|
+
|
|
322
341
|
function openDialogEdit() {
|
|
323
342
|
dialogEdit.value = true;
|
|
324
343
|
|
|
@@ -331,6 +350,11 @@ function openDialogDelete() {
|
|
|
331
350
|
if (dialogPreview.value) dialogPreview.value = false;
|
|
332
351
|
}
|
|
333
352
|
|
|
353
|
+
function handleError(msg: string) {
|
|
354
|
+
const text = prop.type === "ip" ? "CCTV camera already exist" : msg;
|
|
355
|
+
showMessage(text, "error");
|
|
356
|
+
}
|
|
357
|
+
|
|
334
358
|
function handleSuccess() {
|
|
335
359
|
if (dialogAdd.value) dialogAdd.value = false;
|
|
336
360
|
|
|
@@ -366,8 +390,11 @@ async function submitDelete() {
|
|
|
366
390
|
await deleteSiteCameraById(camera.value._id ?? "");
|
|
367
391
|
await getCameraRefresh();
|
|
368
392
|
dialogDelete.value = false;
|
|
369
|
-
} catch (error) {
|
|
370
|
-
|
|
393
|
+
} catch (error: any) {
|
|
394
|
+
showMessage(
|
|
395
|
+
error?.response?._data?.message || "Failed to delete camera.",
|
|
396
|
+
"error"
|
|
397
|
+
);
|
|
371
398
|
}
|
|
372
399
|
}
|
|
373
400
|
</script>
|
|
@@ -217,6 +217,46 @@
|
|
|
217
217
|
|
|
218
218
|
<v-divider thickness="1"></v-divider>
|
|
219
219
|
|
|
220
|
+
<div class="d-flex w-100 mt-7 pb-10">
|
|
221
|
+
<p class="font-weight-medium flex-grow-1">
|
|
222
|
+
QR Code Validity
|
|
223
|
+
<span class="d-block text-caption text-grey-darken-1 mt-1">
|
|
224
|
+
Set how long a generated QR code remains valid before it expires.
|
|
225
|
+
</span>
|
|
226
|
+
<v-col cols="12" class="pr-0 mt-3">
|
|
227
|
+
<v-row no-gutters>
|
|
228
|
+
<v-col cols="12" md="4" class="mb-5">
|
|
229
|
+
<v-text-field
|
|
230
|
+
label="Validity (minutes)"
|
|
231
|
+
placeholder="e.g. 30"
|
|
232
|
+
hide-details
|
|
233
|
+
density="comfortable"
|
|
234
|
+
clearable
|
|
235
|
+
type="number"
|
|
236
|
+
min="1"
|
|
237
|
+
v-model.number="currentEntryPassSettings.qrCodeValidityMinutes"
|
|
238
|
+
></v-text-field>
|
|
239
|
+
</v-col>
|
|
240
|
+
</v-row>
|
|
241
|
+
<v-row>
|
|
242
|
+
<v-col cols="12" md="3">
|
|
243
|
+
<v-btn
|
|
244
|
+
color="primary"
|
|
245
|
+
variant="flat"
|
|
246
|
+
prepend-icon="mdi-timer-outline"
|
|
247
|
+
block
|
|
248
|
+
@click="saveQrCodeValidity()"
|
|
249
|
+
>
|
|
250
|
+
Save
|
|
251
|
+
</v-btn>
|
|
252
|
+
</v-col>
|
|
253
|
+
</v-row>
|
|
254
|
+
</v-col>
|
|
255
|
+
</p>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<v-divider thickness="1"></v-divider>
|
|
259
|
+
|
|
220
260
|
<div class="d-flex w-100 mt-7 pb-10">
|
|
221
261
|
<p class="font-weight-medium flex-grow-1">
|
|
222
262
|
Resident-app Disclaimer Message
|
|
@@ -326,6 +366,7 @@ const currentEntryPassSettings = ref<Record<string, any>>({
|
|
|
326
366
|
qrCodeTemplateHeader: "",
|
|
327
367
|
qrCodeTemplateHeaderSubText: "",
|
|
328
368
|
qrCodeTemplateDate: "",
|
|
369
|
+
qrCodeValidityMinutes: null,
|
|
329
370
|
disclaimerMessage: "",
|
|
330
371
|
url: "",
|
|
331
372
|
template: { id: "", name: "" },
|
|
@@ -489,6 +530,7 @@ watchEffect(() => {
|
|
|
489
530
|
qrCodeTemplateHeader: data.settings?.qrTemplate?.header ?? "",
|
|
490
531
|
qrCodeTemplateHeaderSubText: data.settings?.qrTemplate?.subText ?? "",
|
|
491
532
|
qrCodeTemplateDate: data.settings?.qrTemplate?.date ?? "",
|
|
533
|
+
qrCodeValidityMinutes: data.settings?.validityMinutes ?? null,
|
|
492
534
|
url: data.settings?.url ?? "",
|
|
493
535
|
template: data.settings?.template ?? { id: "", name: "" },
|
|
494
536
|
createdAt: data.createdAt ?? "",
|
|
@@ -518,6 +560,7 @@ function buildPayload() {
|
|
|
518
560
|
subText: currentEntryPassSettings.value.qrCodeTemplateHeaderSubText,
|
|
519
561
|
date: currentEntryPassSettings.value.qrCodeTemplateDate || null,
|
|
520
562
|
},
|
|
563
|
+
validityMinutes: currentEntryPassSettings.value.qrCodeValidityMinutes || null,
|
|
521
564
|
url: currentEntryPassSettings.value.url,
|
|
522
565
|
template: currentEntryPassSettings.value.template?.id
|
|
523
566
|
? currentEntryPassSettings.value.template
|
|
@@ -550,6 +593,12 @@ async function saveQrCodeTemplate() {
|
|
|
550
593
|
getSiteEntryPassSettings();
|
|
551
594
|
}
|
|
552
595
|
|
|
596
|
+
async function saveQrCodeValidity() {
|
|
597
|
+
await _save(buildPayload());
|
|
598
|
+
showMessage("Entry Pass Settings updated successfully!", "success");
|
|
599
|
+
getSiteEntryPassSettings();
|
|
600
|
+
}
|
|
601
|
+
|
|
553
602
|
async function saveResidentAppDisclaimerMsg() {
|
|
554
603
|
await _save(buildPayload());
|
|
555
604
|
showMessage("Entry Pass Settings updated successfully!", "success");
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="d-flex flex-column">
|
|
3
3
|
<v-text-field v-bind="$attrs" ref="datePickerRef" :model-value="dateFormattedReadOnly" autocomplete="off"
|
|
4
|
-
:placeholder="placeholder" :rules="rules" style="z-index: 10" @click="openDatePicker">
|
|
4
|
+
:placeholder="placeholder" :rules="rules" style="z-index: 10" @click="openDatePicker" @click:clear.stop="clearDate">
|
|
5
5
|
<template #append-inner>
|
|
6
6
|
<v-icon icon="mdi-calendar" @click.stop="openDatePicker" />
|
|
7
7
|
</template>
|
|
8
8
|
</v-text-field>
|
|
9
9
|
<div class="w-100 d-flex align-end ga-3 hidden-input">
|
|
10
|
-
<input ref="dateInput" type="date" v-model="date" />
|
|
10
|
+
<input ref="dateInput" type="date" v-model="date" :min="min" />
|
|
11
11
|
</div>
|
|
12
12
|
</div>
|
|
13
13
|
</template>
|
|
@@ -22,6 +22,10 @@ const prop = defineProps({
|
|
|
22
22
|
placeholder: {
|
|
23
23
|
type: String,
|
|
24
24
|
default: 'DD/MM/YYYY'
|
|
25
|
+
},
|
|
26
|
+
min: {
|
|
27
|
+
type: String,
|
|
28
|
+
default: undefined
|
|
25
29
|
}
|
|
26
30
|
})
|
|
27
31
|
|
|
@@ -34,6 +38,11 @@ const datePickerRef = ref<HTMLInputElement | null>(null)
|
|
|
34
38
|
|
|
35
39
|
const isInitialLoad = ref(true)
|
|
36
40
|
|
|
41
|
+
function clearDate() {
|
|
42
|
+
date.value = null
|
|
43
|
+
dateFormattedReadOnly.value = null
|
|
44
|
+
}
|
|
45
|
+
|
|
37
46
|
function openDatePicker() {
|
|
38
47
|
setTimeout(() => {
|
|
39
48
|
dateInput.value?.showPicker?.()
|
|
@@ -14,36 +14,73 @@
|
|
|
14
14
|
<v-row>
|
|
15
15
|
<!-- Cover Photo + Preview -->
|
|
16
16
|
<v-col cols="12" lg="5" class="pt-0">
|
|
17
|
-
<InputLabel
|
|
18
|
-
|
|
17
|
+
<InputLabel
|
|
18
|
+
title="Cover Photo (displayed in resident app)"
|
|
19
|
+
class="d-block mb-2"
|
|
20
|
+
/>
|
|
21
|
+
<InputFileV2
|
|
22
|
+
v-model="coverPhoto"
|
|
23
|
+
accept="image/*"
|
|
24
|
+
title="Upload cover photo"
|
|
25
|
+
:height="120"
|
|
26
|
+
/>
|
|
19
27
|
</v-col>
|
|
20
28
|
<v-col cols="12" lg="7" class="pl-lg-4 pt-4 pt-lg-0">
|
|
21
29
|
<InputLabel title="Preview" class="d-block mb-2" />
|
|
22
|
-
<v-img
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
<v-img
|
|
31
|
+
v-if="coverPhotoUrl"
|
|
32
|
+
:src="coverPhotoUrl"
|
|
33
|
+
aspect-ratio="16/9"
|
|
34
|
+
cover
|
|
35
|
+
rounded="lg"
|
|
36
|
+
class="w-100 border"
|
|
37
|
+
/>
|
|
38
|
+
<div
|
|
39
|
+
v-else
|
|
25
40
|
class="w-100 d-flex align-center justify-center rounded-lg bg-grey-lighten-4 border border-dashed"
|
|
26
|
-
style="aspect-ratio: 16/9"
|
|
27
|
-
|
|
41
|
+
style="aspect-ratio: 16/9"
|
|
42
|
+
>
|
|
43
|
+
<span class="text-caption text-medium-emphasis"
|
|
44
|
+
>No cover photo uploaded</span
|
|
45
|
+
>
|
|
28
46
|
</div>
|
|
29
47
|
</v-col>
|
|
30
48
|
|
|
31
49
|
<!-- Description + Site Documents -->
|
|
32
50
|
<v-col cols="12" lg="6" class="mt-2">
|
|
33
51
|
<InputLabel title="Description" class="d-block mb-1" />
|
|
34
|
-
<v-textarea
|
|
35
|
-
|
|
52
|
+
<v-textarea
|
|
53
|
+
v-model="description"
|
|
54
|
+
variant="outlined"
|
|
55
|
+
density="compact"
|
|
56
|
+
rows="4"
|
|
57
|
+
hide-details
|
|
58
|
+
no-resize
|
|
59
|
+
style="height: 119px"
|
|
60
|
+
/>
|
|
36
61
|
</v-col>
|
|
37
62
|
<v-col cols="12" lg="6" class="mt-2 pl-lg-2">
|
|
38
63
|
<InputLabel title="Site Documents" class="d-block mb-1" />
|
|
39
|
-
<InputFileV2
|
|
40
|
-
|
|
64
|
+
<InputFileV2
|
|
65
|
+
v-model="siteDocuments"
|
|
66
|
+
:multiple="true"
|
|
67
|
+
accept="*/*"
|
|
68
|
+
title="Upload documents"
|
|
69
|
+
:height="104"
|
|
70
|
+
/>
|
|
41
71
|
</v-col>
|
|
42
72
|
|
|
43
73
|
<!-- Submit -->
|
|
44
74
|
<v-col cols="12" class="pt-0 d-flex justify-end">
|
|
45
|
-
<v-btn
|
|
46
|
-
|
|
75
|
+
<v-btn
|
|
76
|
+
color="primary-button"
|
|
77
|
+
class="text-none"
|
|
78
|
+
size="large"
|
|
79
|
+
variant="flat"
|
|
80
|
+
text="Save"
|
|
81
|
+
:loading="savingSiteInfo"
|
|
82
|
+
@click="saveSiteInfo"
|
|
83
|
+
/>
|
|
47
84
|
</v-col>
|
|
48
85
|
|
|
49
86
|
<!-- Divider -->
|
|
@@ -54,8 +91,14 @@
|
|
|
54
91
|
<v-col cols="12">
|
|
55
92
|
<v-row no-gutters>
|
|
56
93
|
<v-col cols="12" lg="4" class="mt-2">
|
|
57
|
-
<NumberSettingField
|
|
58
|
-
|
|
94
|
+
<NumberSettingField
|
|
95
|
+
v-model="blocks"
|
|
96
|
+
title="No. of blocks"
|
|
97
|
+
type="blocks"
|
|
98
|
+
:site-id="siteId"
|
|
99
|
+
:disabled="existingBlockNumber === blocks"
|
|
100
|
+
@success="refreshSiteData"
|
|
101
|
+
/>
|
|
59
102
|
</v-col>
|
|
60
103
|
</v-row>
|
|
61
104
|
</v-col>
|
|
@@ -63,9 +106,14 @@
|
|
|
63
106
|
<v-col cols="12">
|
|
64
107
|
<v-row no-gutters>
|
|
65
108
|
<v-col cols="12" lg="4" class="mt-2">
|
|
66
|
-
<NumberSettingField
|
|
67
|
-
|
|
68
|
-
|
|
109
|
+
<NumberSettingField
|
|
110
|
+
v-model="guardPosts"
|
|
111
|
+
title="No. of guard posts"
|
|
112
|
+
type="guard_posts"
|
|
113
|
+
:site-id="siteId"
|
|
114
|
+
:disabled="existingGuardPostNumber === guardPosts"
|
|
115
|
+
@success="refreshSiteData"
|
|
116
|
+
/>
|
|
69
117
|
</v-col>
|
|
70
118
|
</v-row>
|
|
71
119
|
</v-col>
|
|
@@ -94,7 +142,7 @@
|
|
|
94
142
|
hide-details
|
|
95
143
|
></v-switch>
|
|
96
144
|
</v-col>
|
|
97
|
-
|
|
145
|
+
|
|
98
146
|
<v-col cols="6">
|
|
99
147
|
<v-switch
|
|
100
148
|
v-model="ANPRSwitches.openBarrierPickUpDropOff"
|
|
@@ -133,8 +181,11 @@
|
|
|
133
181
|
|
|
134
182
|
<v-divider class="my-4"></v-divider>
|
|
135
183
|
|
|
136
|
-
<CameraMain
|
|
137
|
-
:
|
|
184
|
+
<CameraMain
|
|
185
|
+
:site="siteId"
|
|
186
|
+
:read-only="!canManageSiteSettings"
|
|
187
|
+
:guard-posts="siteData?.metadata?.guardPosts"
|
|
188
|
+
/>
|
|
138
189
|
</v-expansion-panel-text>
|
|
139
190
|
</v-expansion-panel>
|
|
140
191
|
|
|
@@ -146,7 +197,11 @@
|
|
|
146
197
|
</v-expansion-panel-title>
|
|
147
198
|
|
|
148
199
|
<v-expansion-panel-text>
|
|
149
|
-
<CameraMain
|
|
200
|
+
<CameraMain
|
|
201
|
+
:site="siteId"
|
|
202
|
+
type="ip"
|
|
203
|
+
:read-only="!canManageSiteSettings"
|
|
204
|
+
/>
|
|
150
205
|
</v-expansion-panel-text>
|
|
151
206
|
</v-expansion-panel>
|
|
152
207
|
|
|
@@ -157,15 +212,39 @@
|
|
|
157
212
|
</v-expansion-panel-title>
|
|
158
213
|
|
|
159
214
|
<v-expansion-panel-text>
|
|
160
|
-
<v-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
:
|
|
215
|
+
<v-autocomplete
|
|
216
|
+
v-if="!props.service"
|
|
217
|
+
v-model="selectedServiceProvider"
|
|
218
|
+
:items="serviceProviders"
|
|
219
|
+
:loading="isFetchingServiceProviders"
|
|
220
|
+
:disabled="!canManageSiteSettings"
|
|
221
|
+
label="Select Service Provider"
|
|
222
|
+
@click="fetchServiceProvidersBySiteService"
|
|
223
|
+
class="mt-4"
|
|
224
|
+
/>
|
|
225
|
+
|
|
226
|
+
<v-text-field
|
|
227
|
+
v-model="prefix"
|
|
228
|
+
label="Prefix"
|
|
229
|
+
:disabled="isLoading || !canManageSiteSettings"
|
|
230
|
+
@input="handleInput"
|
|
231
|
+
/>
|
|
232
|
+
|
|
233
|
+
<v-select
|
|
234
|
+
v-model="noOfDigits"
|
|
235
|
+
:items="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
|
|
236
|
+
label="No. of Digits"
|
|
237
|
+
:disabled="isLoading || !canManageSiteSettings"
|
|
238
|
+
/>
|
|
165
239
|
|
|
166
240
|
<v-text-field v-model="orderNumberPreview" label="Preview" readonly />
|
|
167
241
|
|
|
168
|
-
<v-btn
|
|
242
|
+
<v-btn
|
|
243
|
+
v-if="canManageSiteSettings"
|
|
244
|
+
class="mt-2"
|
|
245
|
+
:loading="isLoading"
|
|
246
|
+
@click="save"
|
|
247
|
+
>
|
|
169
248
|
Save
|
|
170
249
|
</v-btn>
|
|
171
250
|
</v-expansion-panel-text>
|
|
@@ -177,8 +256,13 @@
|
|
|
177
256
|
Delivery Companies
|
|
178
257
|
</v-expansion-panel-title>
|
|
179
258
|
<v-expansion-panel-text class="">
|
|
180
|
-
<DeliveryCompany
|
|
181
|
-
|
|
259
|
+
<DeliveryCompany
|
|
260
|
+
:site="siteId"
|
|
261
|
+
v-model:initial="deliveryCompanies"
|
|
262
|
+
@refresh-site="refreshSiteData"
|
|
263
|
+
@update:companiesValue="handleUpdateCompanies"
|
|
264
|
+
:read-only="!canManageSiteSettings"
|
|
265
|
+
/>
|
|
182
266
|
</v-expansion-panel-text>
|
|
183
267
|
</v-expansion-panel>
|
|
184
268
|
|
|
@@ -192,7 +276,11 @@
|
|
|
192
276
|
</v-expansion-panel-text>
|
|
193
277
|
</v-expansion-panel>
|
|
194
278
|
|
|
195
|
-
<slot
|
|
279
|
+
<slot
|
|
280
|
+
name="panels"
|
|
281
|
+
:can-manage="canManageSiteSettings"
|
|
282
|
+
:site-id="siteId"
|
|
283
|
+
/>
|
|
196
284
|
</v-expansion-panels>
|
|
197
285
|
|
|
198
286
|
<Snackbar v-model="toast.show" :text="toast.message" :color="toast.color" />
|
|
@@ -200,11 +288,11 @@
|
|
|
200
288
|
</template>
|
|
201
289
|
|
|
202
290
|
<script setup lang="ts">
|
|
203
|
-
import useFile from
|
|
204
|
-
import useSiteSettings from
|
|
205
|
-
import useUtils from
|
|
206
|
-
import useWorkOrder from
|
|
207
|
-
import useANPRSettings from
|
|
291
|
+
import useFile from "../composables/useFile";
|
|
292
|
+
import useSiteSettings from "../composables/useSiteSettings";
|
|
293
|
+
import useUtils from "../composables/useUtils";
|
|
294
|
+
import useWorkOrder from "../composables/useWorkOrder";
|
|
295
|
+
import useANPRSettings from "../composables/useANPRSettings";
|
|
208
296
|
|
|
209
297
|
const props = defineProps({
|
|
210
298
|
siteId: { type: String, required: true },
|
|
@@ -215,17 +303,17 @@ const props = defineProps({
|
|
|
215
303
|
showWorkOrderSettings: { type: Boolean, default: false },
|
|
216
304
|
showDeliveryCompanies: { type: Boolean, default: false },
|
|
217
305
|
showResidentVisitors: { type: Boolean, default: false },
|
|
306
|
+
service: { type: String, default: false },
|
|
218
307
|
});
|
|
219
308
|
|
|
220
|
-
|
|
221
309
|
const openPanels = ref([]); // default open first
|
|
222
310
|
|
|
223
|
-
const { getSiteById, updateSitebyId, updateSiteInformation } =
|
|
311
|
+
const { getSiteById, updateSitebyId, updateSiteInformation } =
|
|
312
|
+
useSiteSettings();
|
|
224
313
|
const { requiredRule } = useUtils();
|
|
225
314
|
const { getFileUrl } = useFile();
|
|
226
315
|
const { getWorkOrderSettings, createWorkOrderSettings } = useWorkOrder();
|
|
227
316
|
|
|
228
|
-
|
|
229
317
|
const blocks = ref(0);
|
|
230
318
|
const guardPosts = ref(0);
|
|
231
319
|
const gracePeriod = ref(0);
|
|
@@ -251,9 +339,8 @@ const siteDocuments = ref<string[]>([]);
|
|
|
251
339
|
const siteDocumentsNames = ref<Record<string, string>>({});
|
|
252
340
|
const savingSiteInfo = ref<boolean>(false);
|
|
253
341
|
|
|
254
|
-
|
|
255
342
|
const coverPhotoUrl = computed(() =>
|
|
256
|
-
coverPhoto.value.length > 0 ? getFileUrl(coverPhoto.value[0]) : ""
|
|
343
|
+
coverPhoto.value.length > 0 ? getFileUrl(coverPhoto.value[0]) : ""
|
|
257
344
|
);
|
|
258
345
|
|
|
259
346
|
const toast = reactive({
|
|
@@ -268,7 +355,6 @@ function showSnackbar(text: string, color = "success") {
|
|
|
268
355
|
toast.show = true;
|
|
269
356
|
}
|
|
270
357
|
|
|
271
|
-
|
|
272
358
|
const { data: siteData, refresh: refreshSiteData } = await useLazyAsyncData(
|
|
273
359
|
`site-${props.siteId}`,
|
|
274
360
|
() => getSiteById(props.siteId)
|
|
@@ -285,7 +371,7 @@ watch(
|
|
|
285
371
|
guardPosts.value = siteDataValue.metadata?.guardPosts || 0;
|
|
286
372
|
existingGuardPostNumber.value = guardPosts.value;
|
|
287
373
|
deliveryCompanies.value = Array.isArray(
|
|
288
|
-
siteDataValue?.deliveryCompanyList
|
|
374
|
+
siteDataValue?.deliveryCompanyList
|
|
289
375
|
)
|
|
290
376
|
? [...siteDataValue.deliveryCompanyList]
|
|
291
377
|
: [];
|
|
@@ -305,7 +391,6 @@ watch(
|
|
|
305
391
|
{ immediate: true }
|
|
306
392
|
);
|
|
307
393
|
|
|
308
|
-
|
|
309
394
|
async function handleSaveGracePeriod() {
|
|
310
395
|
gracePeriodLoading.value = true;
|
|
311
396
|
try {
|
|
@@ -372,6 +457,7 @@ const { data: workOrderSetting } = await useLazyAsyncData(
|
|
|
372
457
|
|
|
373
458
|
watchEffect(() => {
|
|
374
459
|
if (workOrderSetting.value) {
|
|
460
|
+
selectedServiceProvider.value = workOrderSetting.value?.service;
|
|
375
461
|
prefix.value = workOrderSetting.value?.prefix;
|
|
376
462
|
noOfDigits.value = workOrderSetting.value?.noOfDigits;
|
|
377
463
|
}
|
|
@@ -389,12 +475,52 @@ const orderNumberPreview = computed(() => {
|
|
|
389
475
|
return `${prefix.value}${"0".repeat(noOfDigits.value - 1)}1`;
|
|
390
476
|
});
|
|
391
477
|
|
|
478
|
+
const route = useRoute();
|
|
479
|
+
|
|
480
|
+
const message = ref("");
|
|
481
|
+
const messageColor = ref("");
|
|
482
|
+
const messageSnackbar = ref(false);
|
|
483
|
+
|
|
484
|
+
function showMessage(msg: string, color: string) {
|
|
485
|
+
message.value = msg;
|
|
486
|
+
messageColor.value = color;
|
|
487
|
+
messageSnackbar.value = true;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const { getAll: getAllServiceProvider } = useServiceProvider();
|
|
491
|
+
|
|
492
|
+
const serviceProviders = ref<TServiceProvider[]>([]);
|
|
493
|
+
const isFetchingServiceProviders = ref(false);
|
|
494
|
+
let selectedServiceProvider = ref<string>("");
|
|
495
|
+
let providersArray = ref<string[]>([]);
|
|
496
|
+
|
|
497
|
+
async function fetchServiceProvidersBySiteService() {
|
|
498
|
+
isFetchingServiceProviders.value = true;
|
|
499
|
+
|
|
500
|
+
try {
|
|
501
|
+
const response = await getAllServiceProvider({
|
|
502
|
+
siteId: route.params.site as string,
|
|
503
|
+
limit: 1000,
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
providersArray.value = [];
|
|
507
|
+
serviceProviders.value = response.items.map((i: any) => {
|
|
508
|
+
providersArray.value.push(i);
|
|
509
|
+
return i.name;
|
|
510
|
+
});
|
|
511
|
+
} catch (error: any) {
|
|
512
|
+
return showMessage(errorConverter(error), "error");
|
|
513
|
+
} finally {
|
|
514
|
+
isFetchingServiceProviders.value = false;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
392
518
|
async function save() {
|
|
393
519
|
try {
|
|
394
520
|
isLoading.value = true;
|
|
395
521
|
await createWorkOrderSettings({
|
|
396
522
|
site: props.siteId,
|
|
397
|
-
service:
|
|
523
|
+
service: props.service || selectedServiceProvider.value,
|
|
398
524
|
prefix: prefix.value,
|
|
399
525
|
noOfDigits: noOfDigits.value,
|
|
400
526
|
});
|
|
@@ -412,36 +538,32 @@ function show(message: string, color: string) {
|
|
|
412
538
|
toast.color = color;
|
|
413
539
|
}
|
|
414
540
|
|
|
415
|
-
|
|
416
541
|
// ANPR Settings
|
|
417
|
-
const {getANPRSettingBySite, createUpdateANPRSettings} = useANPRSettings();
|
|
418
|
-
const isSaving = ref(false)
|
|
542
|
+
const { getANPRSettingBySite, createUpdateANPRSettings } = useANPRSettings();
|
|
543
|
+
const isSaving = ref(false);
|
|
419
544
|
|
|
420
545
|
// Reactive object to hold the state of the 3 switches
|
|
421
546
|
const ANPRSwitches = ref({
|
|
422
547
|
enableUnregistered: false,
|
|
423
548
|
openBarrierPickUpDropOff: false,
|
|
424
|
-
vehicleSnapshot: false
|
|
425
|
-
})
|
|
549
|
+
vehicleSnapshot: false,
|
|
550
|
+
});
|
|
426
551
|
|
|
427
|
-
await useLazyAsyncData(
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if(typeof data?.ANPRSwitches == "object"){
|
|
436
|
-
ANPRSwitches.value = data?.ANPRSwitches
|
|
437
|
-
}
|
|
552
|
+
await useLazyAsyncData(`anpr-settings-${props.siteId}`, async () => {
|
|
553
|
+
const data = await getANPRSettingBySite({
|
|
554
|
+
site: props.siteId,
|
|
555
|
+
userType: "site",
|
|
556
|
+
});
|
|
557
|
+
console.log("data", data);
|
|
558
|
+
if (typeof data?.ANPRSwitches == "object") {
|
|
559
|
+
ANPRSwitches.value = data?.ANPRSwitches;
|
|
438
560
|
}
|
|
439
|
-
);
|
|
561
|
+
});
|
|
440
562
|
|
|
441
563
|
const saveANPRSettings = async () => {
|
|
442
564
|
try {
|
|
443
|
-
isSaving.value = true
|
|
444
|
-
|
|
565
|
+
isSaving.value = true;
|
|
566
|
+
|
|
445
567
|
// Replace this with your actual API endpoint / store action
|
|
446
568
|
// await $fetch(`/api/sites/${siteId.value}/camera-settings`, {
|
|
447
569
|
// method: 'POST',
|
|
@@ -453,14 +575,13 @@ const saveANPRSettings = async () => {
|
|
|
453
575
|
updatedBy: props.createdBy,
|
|
454
576
|
userType: "site",
|
|
455
577
|
};
|
|
456
|
-
|
|
578
|
+
console.log("saveANPRSettings data:", data);
|
|
457
579
|
await createUpdateANPRSettings(data);
|
|
458
580
|
show("Saved successfully", "success");
|
|
459
581
|
} catch (error) {
|
|
460
|
-
console.error(
|
|
582
|
+
console.error("Failed to save settings:", error);
|
|
461
583
|
} finally {
|
|
462
|
-
isSaving.value = false
|
|
584
|
+
isSaving.value = false;
|
|
463
585
|
}
|
|
464
|
-
}
|
|
465
|
-
|
|
586
|
+
};
|
|
466
587
|
</script>
|
|
@@ -34,19 +34,19 @@
|
|
|
34
34
|
|
|
35
35
|
<v-col v-if="shouldShowField('block')" cols="12">
|
|
36
36
|
<InputLabel class="text-capitalize" title="Block" required />
|
|
37
|
-
<v-
|
|
37
|
+
<v-autocomplete v-model="vehicle.block" :items="blocksArray" item-value="value" item-title="title"
|
|
38
38
|
@update:model-value="handleChangeBlock" density="comfortable" :rules="[requiredRule]" />
|
|
39
39
|
</v-col>
|
|
40
40
|
|
|
41
41
|
<v-col v-if="shouldShowField('level')" cols="12">
|
|
42
42
|
<InputLabel class="text-capitalize" title="Level" required />
|
|
43
|
-
<v-
|
|
43
|
+
<v-autocomplete v-model="vehicle.level" :items="levelsArray" density="comfortable" :disabled="!vehicle.block"
|
|
44
44
|
@update:model-value="handleChangeLevel" :rules="[requiredRule]" />
|
|
45
45
|
</v-col>
|
|
46
46
|
|
|
47
47
|
<v-col v-if="shouldShowField('unit')" cols="12">
|
|
48
48
|
<InputLabel class="text-capitalize" title="Unit" required />
|
|
49
|
-
<v-
|
|
49
|
+
<v-autocomplete v-model="vehicle.unit" :items="unitsArray" density="comfortable" :disabled="!vehicle.level"
|
|
50
50
|
:rules="[requiredRule]" />
|
|
51
51
|
</v-col>
|
|
52
52
|
|
|
@@ -152,21 +152,21 @@
|
|
|
152
152
|
|
|
153
153
|
<v-col v-if="shouldShowField('block')" cols="12">
|
|
154
154
|
<InputLabel class="text-capitalize" title="Block" required />
|
|
155
|
-
<v-
|
|
155
|
+
<v-autocomplete v-model="visitor.block" :items="blocksArray" item-value="value" item-title="title"
|
|
156
156
|
:loading="blockStatus === 'pending'" @update:model-value="handleChangeBlock" density="comfortable"
|
|
157
157
|
:rules="[requiredRule]" />
|
|
158
158
|
</v-col>
|
|
159
159
|
|
|
160
160
|
<v-col v-if="shouldShowField('level')" cols="12">
|
|
161
161
|
<InputLabel class="text-capitalize" title="Level" required />
|
|
162
|
-
<v-
|
|
162
|
+
<v-autocomplete v-model="visitor.level" :items="levelsArray" density="comfortable" :disabled="!visitor.block"
|
|
163
163
|
:loading="levelsStatus === 'pending'" @update:model-value="handleChangeLevel" :rules="[requiredRule]" />
|
|
164
164
|
</v-col>
|
|
165
165
|
|
|
166
166
|
|
|
167
167
|
<v-col v-if="shouldShowField('unit')" cols="12">
|
|
168
168
|
<InputLabel class="text-capitalize" title="Unit" required />
|
|
169
|
-
<v-
|
|
169
|
+
<v-autocomplete v-model="visitor.unit" :items="unitsArray" density="comfortable" item-title="title"
|
|
170
170
|
item-value="value" :disabled="!visitor.level" :loading="unitsStatus === 'pending'" :rules="[requiredRule]"
|
|
171
171
|
@update:model-value="handleUpdateUnit" />
|
|
172
172
|
</v-col>
|
|
@@ -546,9 +546,9 @@ watch(fetchPersonByContactReq, (obj: Partial<TPerson>) => {
|
|
|
546
546
|
if (!visitor.company) {
|
|
547
547
|
visitor.company = companyNames.value?.[0]
|
|
548
548
|
}
|
|
549
|
-
visitor.block = visitor.block ?? obj.block ?? ""
|
|
550
|
-
visitor.level = visitor.level ?? obj.level ?? ""
|
|
551
|
-
visitor.unit = visitor.unit ?? obj.unit ?? ""
|
|
549
|
+
// visitor.block = visitor.block ?? obj.block ?? ""
|
|
550
|
+
// visitor.level = visitor.level ?? obj.level ?? ""
|
|
551
|
+
// visitor.unit = visitor.unit ?? obj.unit ?? ""
|
|
552
552
|
visitor.nric = visitor.nric ?? obj.nric ?? ""
|
|
553
553
|
|
|
554
554
|
setTimeout(() => {
|
package/composables/useMember.ts
CHANGED
|
@@ -114,6 +114,7 @@ export default function useMember() {
|
|
|
114
114
|
body: payload,
|
|
115
115
|
})
|
|
116
116
|
}
|
|
117
|
+
|
|
117
118
|
function updateMemberSite(
|
|
118
119
|
id: string,
|
|
119
120
|
siteId: string,
|
|
@@ -132,6 +133,20 @@ export default function useMember() {
|
|
|
132
133
|
)
|
|
133
134
|
}
|
|
134
135
|
|
|
136
|
+
function getByUserTypeOrg(
|
|
137
|
+
user: string,
|
|
138
|
+
type: string,
|
|
139
|
+
org: string
|
|
140
|
+
) {
|
|
141
|
+
return useNuxtApp().$api<TMember>(
|
|
142
|
+
`/api/members/user/${user}/app/${type}/org`,
|
|
143
|
+
{
|
|
144
|
+
method: "GET",
|
|
145
|
+
query: { org },
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
135
150
|
return {
|
|
136
151
|
members,
|
|
137
152
|
member,
|
|
@@ -149,6 +164,7 @@ export default function useMember() {
|
|
|
149
164
|
updateMemberRole,
|
|
150
165
|
createMemberDirect,
|
|
151
166
|
updateMemberSite,
|
|
152
|
-
getAllByUserId
|
|
167
|
+
getAllByUserId,
|
|
168
|
+
getByUserTypeOrg
|
|
153
169
|
};
|
|
154
170
|
}
|
package/composables/useOrg.ts
CHANGED
|
@@ -80,6 +80,12 @@ export default function useOrg() {
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
function getOrgById(id = "") {
|
|
84
|
+
return useNuxtApp().$api<TOrg>(`/api/organizations/org/id/${id}`, {
|
|
85
|
+
method: "GET",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
83
89
|
const { currentUser } = useLocalAuth();
|
|
84
90
|
|
|
85
91
|
const currentOrg = useState("currentOrg", () => "");
|
|
@@ -121,7 +127,7 @@ export default function useOrg() {
|
|
|
121
127
|
value: Partial<Pick<TOrg, "name" | "type" | "email" | "nature" | "contact">>
|
|
122
128
|
) {
|
|
123
129
|
return useNuxtApp()
|
|
124
|
-
.$api<Record<string, any>>("/api/organizations", {
|
|
130
|
+
.$api<Record<string, any>>("/api/organizations/onboarding", {
|
|
125
131
|
method: "POST",
|
|
126
132
|
body: value,
|
|
127
133
|
})
|
|
@@ -131,6 +137,20 @@ export default function useOrg() {
|
|
|
131
137
|
) as TOrg;
|
|
132
138
|
});
|
|
133
139
|
}
|
|
140
|
+
|
|
141
|
+
function completeOnboarding(id: string) {
|
|
142
|
+
return useNuxtApp()
|
|
143
|
+
.$api<Record<string, any>>(
|
|
144
|
+
`/api/organizations/onboarding/complete/${id}`,
|
|
145
|
+
{
|
|
146
|
+
method: "POST",
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
.then((response: Record<string, any>) => {
|
|
150
|
+
return (response?.data ?? response?.organization ?? response) as TOrg;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
134
154
|
function updateOrg(
|
|
135
155
|
id: string,
|
|
136
156
|
value: Partial<Pick<TOrg, "name" | "type" | "email" | "nature" | "contact">>
|
|
@@ -224,12 +244,14 @@ export default function useOrg() {
|
|
|
224
244
|
getByName,
|
|
225
245
|
currentOrg,
|
|
226
246
|
getById,
|
|
247
|
+
getOrgById,
|
|
227
248
|
getAll,
|
|
228
249
|
getAllV2,
|
|
229
250
|
add,
|
|
230
251
|
addOnboardingOrg,
|
|
231
252
|
updateOrg,
|
|
232
253
|
getOrgsByEmail,
|
|
233
|
-
getOrganizationsWithSubscription
|
|
254
|
+
getOrganizationsWithSubscription,
|
|
255
|
+
completeOnboarding
|
|
234
256
|
};
|
|
235
257
|
}
|
package/package.json
CHANGED
package/types/vehicle.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ declare type TVehicle = {
|
|
|
9
9
|
block: number | "";
|
|
10
10
|
level: string;
|
|
11
11
|
unit: string;
|
|
12
|
-
|
|
12
|
+
unitName?: string; // For display purposes, the API will return the unit name if available
|
|
13
13
|
nric?: string | null;
|
|
14
14
|
remarks?: string;
|
|
15
15
|
seasonPassType?: string;
|