@7365admin1/layer-common 1.10.9 → 1.11.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/components/AccessCardAddForm.vue +1 -1
  3. package/components/AccessCardAssignToUnitForm.vue +10 -13
  4. package/components/AccessCardQrTagging.vue +2 -2
  5. package/components/BulletinBoardManagement.vue +18 -8
  6. package/components/Chat/SkeletonLoader.vue +71 -0
  7. package/components/DashboardMain.vue +176 -0
  8. package/components/DeliveryCompany.vue +240 -0
  9. package/components/EntryPassInformation.vue +38 -8
  10. package/components/FeedbackMain.vue +4 -19
  11. package/components/FileInputWithList.vue +304 -0
  12. package/components/IncidentReport/Authorities.vue +189 -151
  13. package/components/IncidentReport/IncidentInformation.vue +28 -12
  14. package/components/IncidentReport/IncidentInformationDownload.vue +225 -0
  15. package/components/IncidentReport/affectedEntities.vue +13 -57
  16. package/components/Signature.vue +133 -0
  17. package/components/SiteSettings.vue +285 -0
  18. package/components/SlideCardGroup.vue +194 -0
  19. package/components/Tooltip/Info.vue +33 -0
  20. package/components/VisitorForm.vue +65 -3
  21. package/components/VisitorManagement.vue +23 -6
  22. package/composables/useAccessManagement.ts +44 -6
  23. package/composables/useBulletin.ts +8 -3
  24. package/composables/useBulletinBoardPermission.ts +48 -0
  25. package/composables/useCleaningPermission.ts +2 -0
  26. package/composables/useComment.ts +147 -0
  27. package/composables/useCommonPermission.ts +29 -1
  28. package/composables/useFeedback.ts +79 -29
  29. package/composables/useFile.ts +6 -0
  30. package/composables/usePDFDownload.ts +1 -1
  31. package/composables/useSiteSettings.ts +1 -1
  32. package/composables/useVisitor.ts +6 -5
  33. package/composables/useWorkOrder.ts +61 -26
  34. package/constants/app.ts +12 -0
  35. package/nuxt.config.ts +2 -0
  36. package/package.json +3 -1
  37. package/plugins/vue-draggable-next.client.ts +5 -0
  38. package/public/default-image.svg +4 -0
  39. package/public/placeholder-image.svg +6 -0
  40. package/types/comment.d.ts +38 -0
  41. package/types/dashboard.d.ts +12 -0
  42. package/types/feedback.d.ts +56 -20
  43. package/types/site.d.ts +2 -1
  44. package/types/work-order.d.ts +54 -18
  45. package/utils/data.ts +31 -0
@@ -1,58 +1,5 @@
1
1
  <template>
2
2
  <v-row no-gutters class="px-5 pt-4">
3
- <!-- Any unit affected -->
4
- <v-col cols="12" class="border-b pb-5 mb-5 mt-1">
5
- <p class="mb-2" style="font-size: 17px; font-weight: 600">
6
- Any unit affected?
7
- </p>
8
- <p
9
- v-if="affectedEntities?.anyUnitAffectedValue == 'no'"
10
- class="mt-2 text-h6 text-capitalize"
11
- >
12
- {{ affectedEntities?.anyUnitAffectedValue }}
13
- </p>
14
- <!-- Block Level Unit Section -->
15
- <v-row v-else no-gutters class="d-flex align-center mt-5">
16
- <v-row
17
- v-if="affectedEntities?.affectedUnit.hasOwnProperty('block')"
18
- no-gutters
19
- >
20
- <v-col cols="12" sm="4">
21
- <InputLabel class="text-capitalize" title="Block" />
22
- <p class="my-1 text-h6">
23
- {{ affectedEntities?.affectedUnit?.block }}
24
- </p>
25
- </v-col>
26
- <v-col cols="12" sm="4">
27
- <InputLabel class="text-capitalize" title="Level" />
28
- <p class="my-1 text-h6">
29
- {{ affectedEntities?.affectedUnit?.level }}
30
- </p>
31
- </v-col>
32
- <v-col cols="12" sm="4">
33
- <InputLabel class="text-capitalize" title="Unit" />
34
- <p class="my-1 text-h6">
35
- {{ affectedEntities?.affectedUnit?.unit }}
36
- </p>
37
- </v-col>
38
- </v-row>
39
-
40
- <v-col v-else cols="12" class="px-1 mb-0">
41
- <InputLabel class="text-capitalize" title="Location" />
42
- <p class="my-1 text-h6 text-capitalize">
43
- {{ affectedEntities?.affectedUnit?.other }}
44
- </p>
45
- </v-col>
46
-
47
- <v-col cols="12" class="px-1 mb-0 mt-5">
48
- <InputLabel class="text-capitalize" title="Remarks" />
49
- <p class="my-1 text-h6 text-capitalize">
50
- {{ affectedEntities?.affectedUnit?.remarks }}
51
- </p>
52
- </v-col>
53
- </v-row>
54
- </v-col>
55
-
56
3
  <!-- Anyone affected/injured -->
57
4
  <v-col cols="12" class="border-b pb-5 mb-5 mt-1">
58
5
  <p class="mb-2" style="font-size: 17px; font-weight: 600">
@@ -80,6 +27,7 @@
80
27
  <div class="d-flex align-center">
81
28
  NRIC
82
29
  <v-icon
30
+ v-if="isShowEyeIcon"
83
31
  class="cursor-pointer ml-1"
84
32
  size="18"
85
33
  color="blue"
@@ -143,6 +91,10 @@ const props = defineProps({
143
91
  type: Object as PropType<Record<string, any> | null>,
144
92
  required: true,
145
93
  },
94
+ isShowEyeIcon: {
95
+ type: Boolean,
96
+ required: true,
97
+ },
146
98
  });
147
99
 
148
100
  // emits
@@ -161,6 +113,14 @@ const injuredTableHeader = [
161
113
  title: "Contact Number",
162
114
  value: "contact",
163
115
  },
116
+ {
117
+ title: "Block / Level / Unit Location",
118
+ value: "incidentLocation",
119
+ },
120
+ {
121
+ title: "Remarks",
122
+ value: "remarks",
123
+ },
164
124
  ];
165
125
 
166
126
  const damagePropertyTableHeader = [
@@ -180,10 +140,6 @@ const damagePropertyTableHeader = [
180
140
  title: "Contact Number",
181
141
  value: "contact",
182
142
  },
183
- {
184
- title: "Action",
185
- value: "action",
186
- },
187
143
  ];
188
144
 
189
145
  const isShowNRIC = ref(false);
@@ -0,0 +1,133 @@
1
+ <template>
2
+ <v-dialog max-width="700" v-model="isDialogVisible" persistent>
3
+ <v-card>
4
+ <v-toolbar>
5
+ <v-toolbar-title>Signature </v-toolbar-title>
6
+ <v-spacer />
7
+ <v-btn icon="mdi-close" @click="hideModal"></v-btn>
8
+ </v-toolbar>
9
+ <v-card-text>
10
+ <v-row no-gutters>
11
+ <v-col cols="12">
12
+ <div class="text-subtitle-1 text-medium-emphasis ml-2">
13
+ Your signature here
14
+ </div>
15
+
16
+ <NuxtSignaturePad
17
+ ref="signature"
18
+ :options="state.option"
19
+ :width="'100%'"
20
+ :height="'400px'"
21
+ :disabled="state.disabled"
22
+ class="border"
23
+ />
24
+ </v-col>
25
+
26
+ <v-col cols="12" class="text-center">
27
+ <v-row class="d-flex">
28
+ <v-col class="w-50 px-3">
29
+ <v-btn
30
+ text="clear"
31
+ color="warning"
32
+ type="submit"
33
+ class="my-4 w-100 rounded-lg"
34
+ height="40px"
35
+ @click="clear()"
36
+ />
37
+ </v-col>
38
+
39
+ <v-col class="w-50 px-3">
40
+ <v-btn
41
+ text="submit"
42
+ color="#1867C0"
43
+ type="submit"
44
+ class="my-4 w-100 rounded-lg"
45
+ height="40px"
46
+ :loading="loading"
47
+ @click="submit"
48
+ :disabled="loading"
49
+ />
50
+ </v-col>
51
+ </v-row>
52
+ </v-col>
53
+ </v-row>
54
+ </v-card-text>
55
+ </v-card>
56
+ </v-dialog>
57
+ </template>
58
+
59
+ <script setup lang="ts">
60
+ const loading = ref(false);
61
+ // const { isValid } = useAudit();
62
+ // const { uiRequiredInput, uiSetSnackbar } = useUtils();
63
+
64
+ const message = ref("");
65
+ const messageColor = ref("");
66
+ const messageSnackbar = ref(false);
67
+
68
+ function showMessage(msg: string, color: string) {
69
+ message.value = msg;
70
+ messageColor.value = color;
71
+ messageSnackbar.value = true;
72
+ }
73
+
74
+ const signature = ref(null);
75
+ const state = ref({
76
+ count: 0,
77
+ option: {
78
+ penColor: "rgb(0, 0, 0)",
79
+ backgroundColor: "rgb(255,255,255)",
80
+ },
81
+ disabled: false,
82
+ });
83
+
84
+ const emit = defineEmits<{
85
+ (event: "onSubmit", payload: string): void;
86
+ (event: "onCloseDialog"): void;
87
+ }>();
88
+ let props = defineProps({
89
+ isShowDialog: {
90
+ type: Boolean,
91
+ default: false,
92
+ },
93
+ });
94
+
95
+ const isDialogVisible = computed(() => props.isShowDialog);
96
+
97
+ const hideModal = () => {
98
+ emit("onCloseDialog");
99
+ };
100
+ const file = ref<File | null>(null);
101
+ const { addFile } = useFile();
102
+ const submit = async () => {
103
+ try {
104
+ loading.value = true;
105
+ const base64 = signature.value.saveSignature();
106
+ const blob = await (await fetch(base64)).blob();
107
+
108
+ file.value = new File([blob], "signature.jpg", { type: "image/jpeg" });
109
+
110
+ const uploadItem = {
111
+ data: file.value,
112
+ name: file.value.name,
113
+ url: URL.createObjectURL(file.value),
114
+ progress: 0,
115
+ type: file.value.type,
116
+ };
117
+
118
+ const response = await addFile(uploadItem.data);
119
+
120
+ if (response && response.length > 0) {
121
+ emit("onSubmit", response[0]._id);
122
+ }
123
+ } catch (error) {
124
+ showMessage("Error uploading signature. Please try again.", "error");
125
+ } finally {
126
+ loading.value = false;
127
+ }
128
+ };
129
+
130
+ const clear = () => {
131
+ signature.value.clearCanvas();
132
+ };
133
+ </script>
@@ -0,0 +1,285 @@
1
+ <template>
2
+ <v-row no-gutters>
3
+ <v-expansion-panels multiple v-model="openPanels" >
4
+
5
+ <!-- SITE SETTINGS -->
6
+ <v-expansion-panel color="primary">
7
+ <v-expansion-panel-title>
8
+ <v-icon class="mr-2">mdi-cog</v-icon>
9
+ Site Settings
10
+ </v-expansion-panel-title>
11
+
12
+ <v-expansion-panel-text>
13
+ <v-row no-gutters class="d-flex justify-center">
14
+ <v-col cols="12">
15
+ <v-row no-gutters>
16
+ <v-col cols="12">
17
+ <span class="text-h4 font-weight-bold"> Site Settings </span>
18
+ </v-col>
19
+
20
+ <v-col cols="12">
21
+ <v-row no-gutters>
22
+ <v-col cols="12" lg="4" class="mt-2">
23
+ <NumberSettingField v-model="blocks" title="No. of blocks" type="blocks"
24
+ :read-only="!canManageSiteSettings"
25
+ :existing-block-number="existingBlockNumber" :site-id="siteId"
26
+ :disabled="existingBlockNumber === blocks" @success="refreshSiteData" />
27
+ </v-col>
28
+ </v-row>
29
+ </v-col>
30
+
31
+ <v-col cols="12">
32
+ <v-row no-gutters>
33
+ <v-col cols="12" lg="4" class="mt-2">
34
+ <NumberSettingField v-model="guardPosts" title="No. of guard posts"
35
+ type="guard_posts" :read-only="!canManageSiteSettings"
36
+ :existing-guard-posts-number="existingGuardPostNumber" :site-id="siteId"
37
+ :disabled="existingGuardPostNumber === guardPosts"
38
+ @success="refreshSiteData" />
39
+ </v-col>
40
+ </v-row>
41
+ </v-col>
42
+
43
+ <v-col cols="12">
44
+ <v-row no-gutters>
45
+ <v-col cols="12" lg="4" class="mt-2">
46
+ <v-row no-gutters>
47
+ <v-form v-model="gracePeriodValid">
48
+ <v-row>
49
+ <v-col cols="6">
50
+ <InputLabel class="text-capitalize font-weight-bold"
51
+ title="Grace Period" required />
52
+ <v-text-field v-model="gracePeriod" type="number"
53
+ density="comfortable" :readonly="!canManageSiteSettings"
54
+ :rules="[requiredRule]" />
55
+ </v-col>
56
+
57
+ <v-col cols="6">
58
+ <v-btn v-if="canManageSiteSettings" color="primary"
59
+ class="text-none mt-6" size="large" variant="flat"
60
+ :disabled="!gracePeriodValid ||
61
+ existingGracePeriodNumber === gracePeriod
62
+ " :loading="gracePeriodLoading" text="Save"
63
+ @click="handleSaveGracePeriod" />
64
+ </v-col>
65
+ </v-row>
66
+ </v-form>
67
+ </v-row>
68
+ </v-col>
69
+ </v-row>
70
+ </v-col>
71
+
72
+ </v-row>
73
+ </v-col>
74
+ </v-row>
75
+ </v-expansion-panel-text>
76
+ </v-expansion-panel>
77
+
78
+ <!-- ANPR CAMERA -->
79
+ <v-expansion-panel color="primary">
80
+ <v-expansion-panel-title>
81
+ <v-icon class="mr-2">mdi-camera</v-icon>
82
+ ANPR Camera
83
+ </v-expansion-panel-title>
84
+
85
+ <v-expansion-panel-text>
86
+ <CameraMain :site="siteId" :read-only="!canManageSiteSettings"
87
+ :guard-posts="siteData?.metadata?.guardPosts" />
88
+ </v-expansion-panel-text>
89
+ </v-expansion-panel>
90
+
91
+ <!-- CCTV CAMERA -->
92
+ <v-expansion-panel color="primary">
93
+ <v-expansion-panel-title>
94
+ <v-icon class="mr-2">mdi-cctv</v-icon>
95
+ CCTV Camera
96
+ </v-expansion-panel-title>
97
+
98
+ <v-expansion-panel-text>
99
+ <CameraMain :site="siteId" type="ip" :read-only="!canManageSiteSettings" />
100
+ </v-expansion-panel-text>
101
+ </v-expansion-panel>
102
+
103
+ <v-expansion-panel color="primary">
104
+ <v-expansion-panel-title>
105
+ <v-icon class="mr-2">mdi-format-list-numbered</v-icon>
106
+ Work Order Settings
107
+ </v-expansion-panel-title>
108
+
109
+ <v-expansion-panel-text>
110
+ <v-text-field v-model="prefix" label="Prefix" :disabled="isLoading || !canManageSiteSettings"
111
+ @input="handleInput" />
112
+
113
+ <v-select v-model="noOfDigits" :items="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" label="No. of Digits"
114
+ :disabled="isLoading || !canManageSiteSettings" />
115
+
116
+ <v-text-field v-model="orderNumberPreview" label="Preview" readonly />
117
+
118
+ <v-btn v-if="canManageSiteSettings" class="mt-2" :loading="isLoading" @click="save">
119
+ Save
120
+ </v-btn>
121
+ </v-expansion-panel-text>
122
+ </v-expansion-panel>
123
+
124
+
125
+ <v-expansion-panel color="primary">
126
+ <v-expansion-panel-title>
127
+ <v-icon class="mr-2">mdi-truck</v-icon>
128
+ Delivery Companies
129
+ </v-expansion-panel-title>
130
+ <v-expansion-panel-text class="">
131
+ <DeliveryCompany :site="siteId" v-model:initial="deliveryCompanies" @refresh-site="refreshSiteData" @update:companiesValue="handleUpdateCompanies"
132
+ :read-only="!canManageSiteSettings" />
133
+ </v-expansion-panel-text>
134
+
135
+ </v-expansion-panel>
136
+
137
+ </v-expansion-panels>
138
+
139
+ <Snackbar v-model="toast.show" :text="toast.message" :color="toast.color" />
140
+ </v-row>
141
+ </template>
142
+
143
+ <script setup lang="ts">
144
+ const props = defineProps({
145
+ siteId: { type: String, required: true },
146
+ canManageSiteSettings: { type: Boolean, default: false }
147
+ })
148
+
149
+ const openPanels = ref([0]) // default open first
150
+
151
+ const { getSiteById, updateSitebyId } = useSiteSettings()
152
+ const { requiredRule } = useUtils()
153
+ const { getFileUrl } = useFile()
154
+ const { getWorkOrderSettings, createWorkOrderSettings } = useWorkOrder()
155
+
156
+ const blocks = ref(0)
157
+ const guardPosts = ref(0)
158
+ const gracePeriod = ref(0)
159
+
160
+ const existingBlockNumber = ref(0)
161
+ const existingGuardPostNumber = ref(0)
162
+ const existingGracePeriodNumber = ref(0)
163
+
164
+ const gracePeriodValid = ref(false)
165
+ const gracePeriodLoading = ref(false)
166
+ const deliveryCompanies = ref<string[]>([])
167
+
168
+ const siteLogo = ref<any[]>([])
169
+ const uploadedSiteLogo = ref("")
170
+
171
+ const prefix = ref("")
172
+ const noOfDigits = ref(1)
173
+ const isLoading = ref(false)
174
+
175
+ const toast = reactive({
176
+ show: false,
177
+ message: "",
178
+ color: ""
179
+ })
180
+
181
+ const { data: siteData, refresh: refreshSiteData } = await useLazyAsyncData(
182
+ `site-${props.siteId}`,
183
+ () => getSiteById(props.siteId)
184
+ )
185
+
186
+ watch(siteData, (val: TSite) => {
187
+ if (!val) return
188
+
189
+ blocks.value = val.metadata?.block || 0
190
+ guardPosts.value = val.metadata?.guardPosts || 0
191
+ gracePeriod.value = val.metadata?.gracePeriod || 0
192
+
193
+ existingBlockNumber.value = blocks.value
194
+ existingGuardPostNumber.value = guardPosts.value
195
+ existingGracePeriodNumber.value = gracePeriod.value
196
+ deliveryCompanies.value = Array.isArray(val.deliveryCompanyList) ? [...val.deliveryCompanyList] : []
197
+
198
+ uploadedSiteLogo.value = val.metadata?.incidentLogo || ""
199
+ }, { immediate: true })
200
+
201
+ async function handleSaveGracePeriod() {
202
+ gracePeriodLoading.value = true
203
+ try {
204
+ await updateSitebyId(props.siteId, {
205
+ field: "metadata.gracePeriod",
206
+ value: gracePeriod.value
207
+ })
208
+ show("Grace period updated", "success")
209
+ refreshSiteData()
210
+ } catch (e: any) {
211
+ show(e?.data?.message || "Error updating grace period", "error")
212
+ } finally {
213
+ gracePeriodLoading.value = false
214
+ }
215
+ }
216
+
217
+ async function handleLogo(action: string) {
218
+ try {
219
+ await updateSitebyId(props.siteId, {
220
+ field: "metadata.incidentLogo",
221
+ value: action === "add" ? siteLogo.value[0] : 0
222
+ })
223
+ show("Logo updated", "success")
224
+ refreshSiteData()
225
+ } catch {
226
+ show("Error updating logo", "error")
227
+ } finally {
228
+ siteLogo.value = []
229
+ }
230
+ }
231
+
232
+ const onUploadedLogoPreview = () => {
233
+ if (!uploadedSiteLogo.value) return ""
234
+ return getFileUrl(uploadedSiteLogo.value)
235
+ }
236
+
237
+ const { data: workOrderSetting } = await useLazyAsyncData(
238
+ `work-${props.siteId}`,
239
+ () => getWorkOrderSettings({ site: props.siteId, service: "Security" })
240
+ )
241
+
242
+ watchEffect(() => {
243
+ if (workOrderSetting.value) {
244
+ prefix.value = workOrderSetting.value.prefix
245
+ noOfDigits.value = workOrderSetting.value.noOfDigits
246
+ }
247
+ })
248
+
249
+ function handleInput(e: any) {
250
+ prefix.value = e.target.value
251
+ .replace(/[^A-Z]/g, "")
252
+ .toUpperCase()
253
+ }
254
+
255
+ function handleUpdateCompanies(value: string[]) {
256
+ deliveryCompanies.value = value
257
+ }
258
+
259
+ const orderNumberPreview = computed(() => {
260
+ return `${prefix.value}${"0".repeat(noOfDigits.value - 1)}1`
261
+ })
262
+
263
+ async function save() {
264
+ try {
265
+ isLoading.value = true
266
+ await createWorkOrderSettings({
267
+ site: props.siteId,
268
+ service: "Security",
269
+ prefix: prefix.value,
270
+ noOfDigits: noOfDigits.value
271
+ })
272
+ show("Saved successfully", "success")
273
+ } catch {
274
+ show("Error saving", "error")
275
+ } finally {
276
+ isLoading.value = false
277
+ }
278
+ }
279
+
280
+ function show(message: string, color: string) {
281
+ toast.show = true
282
+ toast.message = message
283
+ toast.color = color
284
+ }
285
+ </script>