@7365admin1/layer-common 1.8.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 (198) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +11 -0
  3. package/.editorconfig +12 -0
  4. package/.github/workflows/main.yml +17 -0
  5. package/.github/workflows/publish.yml +39 -0
  6. package/.nuxtrc +1 -0
  7. package/.playground/app.vue +41 -0
  8. package/.playground/eslint.config.mjs +6 -0
  9. package/.playground/nuxt.config.ts +22 -0
  10. package/.playground/pages/feedback.vue +30 -0
  11. package/CHANGELOG.md +263 -0
  12. package/README.md +73 -0
  13. package/app.vue +3 -0
  14. package/components/AccessCardAddForm.vue +363 -0
  15. package/components/AccessManagement.vue +420 -0
  16. package/components/Avatar/Main.vue +68 -0
  17. package/components/BillingMain.vue +66 -0
  18. package/components/BtnUploadFile.vue +139 -0
  19. package/components/BuildingForm.vue +303 -0
  20. package/components/BuildingManagement/buildings.vue +335 -0
  21. package/components/BuildingManagement/units.vue +350 -0
  22. package/components/BuildingUnitFormAdd.vue +441 -0
  23. package/components/BuildingUnitFormEdit.vue +429 -0
  24. package/components/CameraForm.vue +264 -0
  25. package/components/CameraMain.vue +352 -0
  26. package/components/Card/DeleteConfirmation.vue +51 -0
  27. package/components/Card/MemberInfoSummary.vue +44 -0
  28. package/components/Card/Toggle.vue +25 -0
  29. package/components/Chat/Bubbles.vue +53 -0
  30. package/components/Chat/Information.vue +416 -0
  31. package/components/Chat/ListCard.vue +62 -0
  32. package/components/Chat/Message.vue +158 -0
  33. package/components/Chat/Navigation.vue +150 -0
  34. package/components/ConfirmDialog.vue +66 -0
  35. package/components/Container/Standard.vue +33 -0
  36. package/components/DashboardPlaceholder.vue +1524 -0
  37. package/components/Dialog/DeleteConfirmation.vue +51 -0
  38. package/components/Dialog/ReplaceAutofillPrompt.vue +49 -0
  39. package/components/Dialog/UpdateMoreAction.vue +103 -0
  40. package/components/DocumentForm.vue +187 -0
  41. package/components/DocumentManagement.vue +376 -0
  42. package/components/Editor.vue +95 -0
  43. package/components/EntryPassMain.vue +518 -0
  44. package/components/Feedback/Form.vue +173 -0
  45. package/components/FeedbackDetail.vue +599 -0
  46. package/components/FeedbackMain.vue +588 -0
  47. package/components/FormDialog.vue +65 -0
  48. package/components/ImageCarousel.vue +138 -0
  49. package/components/Input/Date.vue +177 -0
  50. package/components/Input/DateTimePicker.vue +131 -0
  51. package/components/Input/File.vue +236 -0
  52. package/components/Input/FileV2.vue +234 -0
  53. package/components/Input/InputPhoneNumberV2.vue +164 -0
  54. package/components/Input/ListGroupSelection.vue +96 -0
  55. package/components/Input/NRICNumber.vue +53 -0
  56. package/components/Input/NewDate.vue +123 -0
  57. package/components/Input/Number.vue +124 -0
  58. package/components/Input/Password.vue +22 -0
  59. package/components/Input/PhoneNumber.vue +188 -0
  60. package/components/Input/VehicleNumber.vue +49 -0
  61. package/components/InputLabel.vue +22 -0
  62. package/components/InvitationForm.vue +359 -0
  63. package/components/InvitationMain.vue +310 -0
  64. package/components/Layout/Header.vue +129 -0
  65. package/components/Layout/NavigationDrawer.vue +44 -0
  66. package/components/ListItem.vue +35 -0
  67. package/components/ListView.vue +87 -0
  68. package/components/LocalPagination.vue +31 -0
  69. package/components/MemberMain.vue +459 -0
  70. package/components/NFC/NFCPatrolReportMain.vue +591 -0
  71. package/components/NFC/NFCPatrolRouteForm.vue +596 -0
  72. package/components/NFC/NFCPatrolRouteMain.vue +539 -0
  73. package/components/NFC/NFCTagForm.vue +236 -0
  74. package/components/NFC/NFCTagMain.vue +337 -0
  75. package/components/NFC/PatrolSettings.vue +130 -0
  76. package/components/NavigationItem.vue +83 -0
  77. package/components/NumberSettingField.vue +107 -0
  78. package/components/OnlineFormConfigurationForm.vue +290 -0
  79. package/components/OnlineFormsConfiguration.vue +429 -0
  80. package/components/PeopleForm.vue +452 -0
  81. package/components/PlaceholderComponent.vue +34 -0
  82. package/components/RolePermissionFormCreate.vue +161 -0
  83. package/components/RolePermissionFormPreviewUpdate.vue +183 -0
  84. package/components/RolePermissionMain.vue +361 -0
  85. package/components/SearchVehicleNumberUser.vue +91 -0
  86. package/components/ServiceProviderFormCreate.vue +154 -0
  87. package/components/ServiceProviderMain.vue +547 -0
  88. package/components/SignaturePad.vue +73 -0
  89. package/components/Snackbar.vue +23 -0
  90. package/components/SpecificAttr.vue +53 -0
  91. package/components/SupplyManagement.vue +292 -0
  92. package/components/SwitchContext.vue +108 -0
  93. package/components/TableList.vue +150 -0
  94. package/components/TableListSecondary.vue +164 -0
  95. package/components/TableMain.vue +142 -0
  96. package/components/TableWithButton.vue +94 -0
  97. package/components/VehicleUpdateMoreAction.vue +84 -0
  98. package/components/VideoPlayer.vue +125 -0
  99. package/components/VisitorForm.vue +659 -0
  100. package/components/VisitorFormSelection.vue +53 -0
  101. package/components/VisitorManagement.vue +490 -0
  102. package/components/WorkOrder/Create.vue +284 -0
  103. package/components/WorkOrder/Detail.vue +71 -0
  104. package/components/WorkOrder/ListView.vue +96 -0
  105. package/components/WorkOrder/Main.vue +489 -0
  106. package/components/Workorder.vue +1 -0
  107. package/composables/useAddress.ts +107 -0
  108. package/composables/useBuilding.ts +250 -0
  109. package/composables/useBuildingUnit.ts +116 -0
  110. package/composables/useCard.ts +46 -0
  111. package/composables/useCommonPermission.ts +207 -0
  112. package/composables/useCustomer.ts +113 -0
  113. package/composables/useCustomerSite.ts +56 -0
  114. package/composables/useDashboard.ts +31 -0
  115. package/composables/useDashboardData.ts +425 -0
  116. package/composables/useDocument.ts +57 -0
  117. package/composables/useFacility.ts +246 -0
  118. package/composables/useFeedback.ts +119 -0
  119. package/composables/useFile.ts +55 -0
  120. package/composables/useInvoice.ts +18 -0
  121. package/composables/useLocal.ts +131 -0
  122. package/composables/useLocalAuth.ts +137 -0
  123. package/composables/useLocalSetup.ts +13 -0
  124. package/composables/useMember.ts +111 -0
  125. package/composables/useNFCPatrolRoute.ts +77 -0
  126. package/composables/useNFCPatrolSettings.ts +19 -0
  127. package/composables/useNFCPatrolTag.ts +53 -0
  128. package/composables/useOnlineForm.ts +67 -0
  129. package/composables/useOrg.ts +129 -0
  130. package/composables/usePDFDownload.ts +25 -0
  131. package/composables/usePaymentMethod.ts +101 -0
  132. package/composables/usePeople.ts +81 -0
  133. package/composables/usePermission.ts +54 -0
  134. package/composables/usePhoneCountries.ts +561 -0
  135. package/composables/usePrice.ts +15 -0
  136. package/composables/usePromoCode.ts +36 -0
  137. package/composables/useRecapPermission.ts +26 -0
  138. package/composables/useRole.ts +104 -0
  139. package/composables/useSecurityUtils.ts +18 -0
  140. package/composables/useServiceProvider.ts +224 -0
  141. package/composables/useSite.ts +109 -0
  142. package/composables/useSiteEntryPassSettings.ts +46 -0
  143. package/composables/useSiteSettings.ts +123 -0
  144. package/composables/useSubscription.ts +150 -0
  145. package/composables/useUser.ts +132 -0
  146. package/composables/useUtils.ts +445 -0
  147. package/composables/useVerification.ts +34 -0
  148. package/composables/useVisitor.ts +120 -0
  149. package/composables/useWorkOrder.ts +85 -0
  150. package/error.vue +41 -0
  151. package/layouts/plain.vue +7 -0
  152. package/middleware/01.auth.ts +20 -0
  153. package/middleware/02.org.ts +21 -0
  154. package/middleware/03.customer.ts +13 -0
  155. package/middleware/member.ts +4 -0
  156. package/nuxt.config.ts +54 -0
  157. package/package.json +39 -0
  158. package/pages/index.vue +3 -0
  159. package/pages/payment-method-linked.vue +31 -0
  160. package/pages/require-customer.vue +56 -0
  161. package/pages/require-organization-membership.vue +47 -0
  162. package/pages/unauthorized.vue +29 -0
  163. package/plugins/API.ts +21 -0
  164. package/plugins/iconify.client.ts +5 -0
  165. package/plugins/secure-member.client.ts +86 -0
  166. package/plugins/vuetify.ts +62 -0
  167. package/public/bg-camera.jpg +0 -0
  168. package/public/bg-city.jpg +0 -0
  169. package/public/bg-condo.jpg +0 -0
  170. package/public/images/icons/delete-icon.png +0 -0
  171. package/public/sprite.svg +1 -0
  172. package/tsconfig.json +3 -0
  173. package/types/address.d.ts +13 -0
  174. package/types/building.d.ts +27 -0
  175. package/types/camera.d.ts +31 -0
  176. package/types/card.d.ts +22 -0
  177. package/types/customer.d.ts +27 -0
  178. package/types/document.d.ts +6 -0
  179. package/types/feedback.d.ts +68 -0
  180. package/types/local.d.ts +74 -0
  181. package/types/member.d.ts +21 -0
  182. package/types/online-form.d.ts +15 -0
  183. package/types/org.d.ts +13 -0
  184. package/types/people.d.ts +24 -0
  185. package/types/permission.d.ts +25 -0
  186. package/types/phone-number.d.ts +10 -0
  187. package/types/price.d.ts +17 -0
  188. package/types/promo-code.d.ts +19 -0
  189. package/types/role.d.ts +11 -0
  190. package/types/select.d.ts +4 -0
  191. package/types/service-provider.d.ts +15 -0
  192. package/types/site.d.ts +20 -0
  193. package/types/subscription.d.ts +23 -0
  194. package/types/user.d.ts +19 -0
  195. package/types/verification.d.ts +20 -0
  196. package/types/visitor.d.ts +42 -0
  197. package/types/work-order.d.ts +42 -0
  198. package/utils/phoneMasks.ts +1703 -0
@@ -0,0 +1,420 @@
1
+ <template>
2
+ <v-row no-gutters>
3
+ <v-col cols="12" class="mb-2">
4
+ <v-row no-gutters align="center" justify="space-between">
5
+ <v-btn
6
+ class="text-none"
7
+ rounded="pill"
8
+ variant="tonal"
9
+ size="large"
10
+ @click="setCard()"
11
+ v-if="canCreate && canCreateAccessCard"
12
+ >
13
+ Add Access Card
14
+ </v-btn>
15
+
16
+ <v-text-field
17
+ v-model="searchText"
18
+ placeholder="Search Card, Unit..."
19
+ variant="outlined"
20
+ density="comfortable"
21
+ clearable
22
+ hide-details
23
+ class="ml-2"
24
+ style="max-width: 250px"
25
+ />
26
+ </v-row>
27
+ </v-col>
28
+ <v-col cols="12">
29
+ <v-card
30
+ width="100%"
31
+ variant="outlined"
32
+ border="thin"
33
+ rounded="lg"
34
+ :loading="loading"
35
+ >
36
+ <v-toolbar density="compact" color="grey-lighten-4">
37
+ <template #prepend>
38
+ <v-btn fab icon density="comfortable" @click="getCards">
39
+ <v-icon>mdi-refresh</v-icon>
40
+ </v-btn>
41
+ </template>
42
+
43
+ <template #append>
44
+ <v-row no-gutters justify="end" align="center">
45
+ <span class="mr-2 text-caption text-fontgray">
46
+ {{ pageRange }}
47
+ </span>
48
+ <local-pagination
49
+ v-model="page"
50
+ :length="pages"
51
+ @update:value="getCards"
52
+ />
53
+ </v-row>
54
+ </template>
55
+ </v-toolbar>
56
+ <v-data-table
57
+ :headers="headers"
58
+ :items="items"
59
+ item-value="_id"
60
+ items-per-page="10"
61
+ fixed-header
62
+ hide-default-footer
63
+ @click:row="tableRowClickHandler"
64
+ style="max-height: calc(100vh - (200px))"
65
+ />
66
+ </v-card>
67
+ </v-col>
68
+
69
+ <!-- Create Dialog -->
70
+ <v-dialog v-model="createDialog" width="650" persistent>
71
+ <AccessCardAddForm
72
+ @cancel="createDialog = false"
73
+ @success="successCreate()"
74
+ />
75
+ </v-dialog>
76
+
77
+ <!-- Edit Dialog -->
78
+ <v-dialog v-model="editDialog" width="650" persistent>
79
+ <AccessCardAddForm
80
+ mode="edit"
81
+ @cancel="editDialog = false"
82
+ @success="successUpdate()"
83
+ :card="selectedCard"
84
+ />
85
+ </v-dialog>
86
+
87
+ <!-- Preview Dialog -->
88
+ <v-dialog v-model="previewDialog" width="450" persistent>
89
+ <v-card width="100%">
90
+ <v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0">
91
+ <v-row no-gutters class="mb-4">
92
+ <v-col cols="12">
93
+ <strong>Card:</strong> {{ selectedCard?.cardNumber ?? "N/A" }}
94
+ </v-col>
95
+ <v-col cols="12">
96
+ <strong>Access Type:</strong>
97
+ {{ selectedCard?.accessCardType ?? "N/A" }}
98
+ </v-col>
99
+ <v-col cols="12">
100
+ <strong>Unit:</strong>
101
+ {{ selectedCard?.unit || "N/A" }}
102
+ </v-col>
103
+ <v-col cols="12">
104
+ <strong>Assign:</strong> {{ selectedCard?.assign || "N/A" }}
105
+ </v-col>
106
+ <v-col cols="12">
107
+ <strong>Status:</strong> {{ selectedCard?.status ?? "N/A" }}
108
+ </v-col>
109
+ </v-row></v-card-text
110
+ >
111
+ <v-toolbar class="pa-0" density="compact">
112
+ <v-row no-gutters>
113
+ <v-col cols="6" class="pa-0">
114
+ <v-btn
115
+ block
116
+ variant="text"
117
+ class="text-none"
118
+ size="large"
119
+ @click="previewDialog = false"
120
+ height="48"
121
+ >
122
+ Close
123
+ </v-btn>
124
+ </v-col>
125
+ <v-col cols="6" class="pa-0" v-if="canUpdate">
126
+ <v-menu>
127
+ <template #activator="{ props }">
128
+ <v-btn
129
+ block
130
+ variant="flat"
131
+ color="black"
132
+ class="text-none"
133
+ height="48"
134
+ v-bind="props"
135
+ tile
136
+ >
137
+ More actions
138
+ </v-btn>
139
+ </template>
140
+ <v-list class="pa-0">
141
+ <v-list-item
142
+ @click="openEditDialog()"
143
+ v-if="canUpdateAccessCard"
144
+ >
145
+ <v-list-item-title class="text-subtitle-2">
146
+ Edit Card
147
+ </v-list-item-title>
148
+ </v-list-item>
149
+ <v-list-item @click="openReplaceDialog()" v-if="canReplaceAccessCard">
150
+ <v-list-item-title class="text-subtitle-2">
151
+ Replace Card
152
+ </v-list-item-title>
153
+ </v-list-item>
154
+ <v-list-item
155
+ @click="openDeleteDialog()"
156
+ class="text-red"
157
+ v-if="canDeleteAccessCard"
158
+ >
159
+ <v-list-item-title class="text-subtitle-2">
160
+ Delete Card
161
+ </v-list-item-title>
162
+ </v-list-item>
163
+ </v-list>
164
+ </v-menu>
165
+ </v-col>
166
+ </v-row>
167
+ </v-toolbar></v-card
168
+ >
169
+ </v-dialog>
170
+
171
+ <!-- Delete Dialog -->
172
+ <v-dialog
173
+ v-model="confirmDialog"
174
+ :loading="deleteLoading"
175
+ width="450"
176
+ persistent
177
+ >
178
+ <v-card width="100%">
179
+ <v-toolbar density="compact" class="pl-4">
180
+ <span class="font-weight-medium text-h5">Delete Card</span>
181
+ </v-toolbar>
182
+ <v-card-text>
183
+ <p class="text-subtitle-2 text-center">
184
+ Are you sure you want to delete this card? This action cannot be
185
+ undone.
186
+ </p>
187
+
188
+ <v-row v-if="message" no-gutters justify="center" class="mt-4">
189
+ <span class="text-caption text-error text-center">
190
+ {{ message }}
191
+ </span>
192
+ </v-row></v-card-text
193
+ >
194
+ <v-toolbar density="compact">
195
+ <v-row no-gutters>
196
+ <v-col cols="6">
197
+ <v-btn
198
+ tile
199
+ block
200
+ size="48"
201
+ variant="text"
202
+ class="text-none"
203
+ @click="confirmDialog = false"
204
+ :disabled="deleteLoading"
205
+ >
206
+ Close
207
+ </v-btn>
208
+ </v-col>
209
+ <v-col cols="6">
210
+ <v-btn
211
+ tile
212
+ block
213
+ size="48"
214
+ color="black"
215
+ variant="flat"
216
+ class="text-none"
217
+ @click="handleDeleteCard"
218
+ :loading="deleteLoading"
219
+ >
220
+ Delete Card
221
+ </v-btn>
222
+ </v-col>
223
+ </v-row></v-toolbar
224
+ >
225
+ </v-card>
226
+ </v-dialog>
227
+
228
+ <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
229
+ </v-row>
230
+ </template>
231
+ <script setup lang="ts">
232
+ definePageMeta({
233
+ middleware: ["01-auth", "02-org"],
234
+ memberOnly: true,
235
+ });
236
+ const props = defineProps({
237
+ headers: {
238
+ type: Array as PropType<Array<Record<string, any>>>,
239
+ default: () => [
240
+ {
241
+ title: "Card",
242
+ value: "cardNumber",
243
+ },
244
+ {
245
+ title: "Unit",
246
+ value: "unit",
247
+ },
248
+ {
249
+ title: "Assign",
250
+ value: "assign",
251
+ },
252
+ { title: "Action", value: "action-table" },
253
+ ],
254
+ },
255
+ canCreate: {
256
+ type: Boolean,
257
+ default: true,
258
+ },
259
+ canUpdate: {
260
+ type: Boolean,
261
+ default: true,
262
+ },
263
+ canDelete: {
264
+ type: Boolean,
265
+ default: true,
266
+ },
267
+ canCreateAccessCard: {
268
+ type: Boolean,
269
+ default: true,
270
+ },
271
+ canUpdateAccessCard: {
272
+ type: Boolean,
273
+ default: true,
274
+ },
275
+ canDeleteAccessCard: {
276
+ type: Boolean,
277
+ default: true,
278
+ },
279
+ canReplaceAccessCard: {
280
+ type: Boolean,
281
+ default: true,
282
+ },
283
+ });
284
+
285
+ // const { headerSearch } = useLocal();
286
+ const page = ref(1);
287
+ const pages = ref(0);
288
+ const pageRange = ref("-- - -- of --");
289
+
290
+ const message = ref("");
291
+ const messageSnackbar = ref(false);
292
+ const messageColor = ref("");
293
+
294
+ const items = ref<Array<Record<string, any>>>([]);
295
+ const createDialog = ref(false);
296
+ const editDialog = ref(false);
297
+ const previewDialog = ref(false);
298
+ const deleteLoading = ref(false);
299
+ const confirmDialog = ref(false);
300
+ const searchText = ref("");
301
+ const replaceDialog = ref(false);
302
+
303
+ const selectedCard = ref<TCard>({
304
+ _id: "",
305
+ name: "",
306
+ accessCardType: "",
307
+ // visitorType: "",
308
+ type: "",
309
+ cardNumber: "",
310
+ startDate: "",
311
+ endDate: "",
312
+ door: "",
313
+ accessGroup: [],
314
+ cardType: "",
315
+ pinNo: "",
316
+ useAsLiftCard: false,
317
+ liftAccessLevel: "",
318
+ isActivate: true,
319
+ isAntiPassBack: false,
320
+ status: "",
321
+ org: "",
322
+ site: "",
323
+ unit: "",
324
+ assign: "",
325
+ });
326
+ const selectedCardId = ref<string | null>(null);
327
+
328
+ const { getAll: _getAllCards, deleteById: _deleteCard } = useCard();
329
+
330
+ const {
331
+ data: getCardReq,
332
+ refresh: getCards,
333
+ status: getAllReqStatus,
334
+ } = useLazyAsyncData(
335
+ "get-all-cards",
336
+ () =>
337
+ _getAllCards({
338
+ page: page.value,
339
+ search: searchText.value,
340
+ // search: headerSearch.value,
341
+ }),
342
+ {
343
+ watch: [page, searchText],
344
+ }
345
+ );
346
+
347
+ const loading = computed(() => getAllReqStatus.value === "pending");
348
+
349
+ watchEffect(() => {
350
+ if (getCardReq.value) {
351
+ items.value = getCardReq.value.items;
352
+ pages.value = getCardReq.value.pages;
353
+ pageRange.value = getCardReq.value.pageRange;
354
+ }
355
+ });
356
+
357
+ function setCard({ mode = "create", dialog = true, data = {} as TCard } = {}) {
358
+ if (mode === "create") {
359
+ createDialog.value = dialog;
360
+ } else if (mode === "edit") {
361
+ editDialog.value = dialog;
362
+ selectedCard.value = data;
363
+ } else if (mode === "preview") {
364
+ previewDialog.value = dialog;
365
+ selectedCard.value = data;
366
+ }
367
+ }
368
+
369
+ function successCreate() {
370
+ createDialog.value = false;
371
+ getCards();
372
+ showMessage("Card created successfully!", "success");
373
+ }
374
+
375
+ function successUpdate() {
376
+ editDialog.value = false;
377
+ previewDialog.value = false;
378
+ getCards();
379
+ showMessage("Card updated successfully!", "success");
380
+ }
381
+
382
+ function showMessage(msg: string, color: string) {
383
+ message.value = msg;
384
+ messageColor.value = color;
385
+ messageSnackbar.value = true;
386
+ }
387
+
388
+ function tableRowClickHandler(_: any, data: any) {
389
+ selectedCard.value = data.item as TCard;
390
+ previewDialog.value = true;
391
+ }
392
+
393
+ function openEditDialog() {
394
+ editDialog.value = true;
395
+ }
396
+
397
+ function openDeleteDialog() {
398
+ confirmDialog.value = true;
399
+ message.value = "";
400
+ }
401
+
402
+ function openReplaceDialog() {
403
+ replaceDialog.value = true;
404
+ }
405
+
406
+ async function handleDeleteCard() {
407
+ deleteLoading.value = true;
408
+ try {
409
+ await _deleteCard(selectedCard.value._id ?? "");
410
+ await getCards();
411
+ selectedCardId.value = null;
412
+ confirmDialog.value = false;
413
+ previewDialog.value = false;
414
+ } catch (error: any) {
415
+ message.value = error?.response?._data?.message || "Failed to delete card";
416
+ } finally {
417
+ deleteLoading.value = false;
418
+ }
419
+ }
420
+ </script>
@@ -0,0 +1,68 @@
1
+ <template>
2
+ <v-avatar :color="getColor(name || id)" :size="size">
3
+ <v-img v-if="imageSrc" alt="John" :src="imageUrl" />
4
+ <span v-else :style="{
5
+ fontSize: `${fontSize}px`,
6
+ lineHeight: `${size}px`
7
+ }">{{ initials }}</span>
8
+ </v-avatar>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ const props = defineProps({
13
+ name: {
14
+ type: String,
15
+ default: ""
16
+ },
17
+ color: {
18
+ type: String,
19
+ default: 'red'
20
+ },
21
+ size: {
22
+ type: Number,
23
+ default: 40
24
+ },
25
+ id: {
26
+ type: String, //fallback for constant default color without name
27
+ default: ""
28
+ },
29
+ imageSrc:{
30
+ type: String,
31
+ default: ""
32
+ }
33
+
34
+ })
35
+
36
+
37
+ const { getNameInitials } = useUtils();
38
+ const { getFileUrl } = useFile();
39
+
40
+ const initials = computed(() => {
41
+ return getNameInitials(props.name)
42
+ })
43
+
44
+ const fontSize = computed(() => Math.round(props.size * 0.5))
45
+
46
+ function getColor(str?: string): string {
47
+ if(!str) return "grey"
48
+
49
+ let hash = 0
50
+ for (let i = 0; i < str.length; i++) {
51
+ hash = (hash << 5) - hash + str.charCodeAt(i)
52
+ hash |= 0 // Convert to 32-bit integer
53
+ }
54
+
55
+ const hue = Math.abs(hash) % 360
56
+ const saturation = 60 + (Math.abs((hash >> 3) % 20)) // 60–80%
57
+ const lightness = 45 + (Math.abs((hash >> 5) % 15)) // 45–60%
58
+
59
+ return `hsl(${hue}, ${saturation}%, ${lightness}%)`
60
+ }
61
+
62
+ const imageUrl = computed(() => {
63
+ return getFileUrl(props?.imageSrc || '')
64
+ })
65
+
66
+ </script>
67
+
68
+ <style scoped></style>
@@ -0,0 +1,66 @@
1
+ <template>
2
+ <v-row no-gutters>
3
+ <v-col cols="12">
4
+ <v-card
5
+ width="100%"
6
+ variant="outlined"
7
+ border="thin"
8
+ rounded="lg"
9
+ :loading="false"
10
+ >
11
+ <v-toolbar density="compact" color="grey-lighten-4">
12
+ <template #prepend>
13
+ <v-btn fab icon density="comfortable">
14
+ <v-icon>mdi-refresh</v-icon>
15
+ </v-btn>
16
+ </template>
17
+ <template #append>
18
+ <v-row no-gutters justify="end" align="center">
19
+ <span class="mr-2 text-caption text-fontgray">
20
+ {{ pageRange }}
21
+ </span>
22
+ <local-pagination v-model="page" :length="pages" />
23
+ </v-row>
24
+ </template>
25
+ </v-toolbar>
26
+
27
+ <v-data-table
28
+ :headers="headers"
29
+ :items="items"
30
+ item-value="_id"
31
+ items-per-page="20"
32
+ fixed-header
33
+ hide-default-footer
34
+ hide-default-header
35
+ style="max-height: calc(100vh - (180px))"
36
+ >
37
+ <template #item.permissions="{ value }">
38
+ <span class="text-caption font-weight-bold text-capitalize">
39
+ permissions
40
+ </span>
41
+ <v-chip>{{ value.length }}</v-chip>
42
+ </template>
43
+
44
+ <template #item.nature="{ item }">
45
+ <span class="text-capitalize">
46
+ {{ item.nature }}
47
+ </span>
48
+ </template>
49
+ </v-data-table>
50
+ </v-card>
51
+ </v-col>
52
+ </v-row>
53
+ </template>
54
+
55
+ <script setup lang="ts">
56
+ const page = ref(1);
57
+ const pages = ref(0);
58
+ const pageRange = ref("-- - -- of --");
59
+
60
+ const items = ref<Array<Record<string, any>>>([]);
61
+
62
+ const headers = [
63
+ { title: "Name", value: "name" },
64
+ { title: "Nature", value: "nature" },
65
+ ];
66
+ </script>
@@ -0,0 +1,139 @@
1
+ <template>
2
+ <v-input
3
+ :label="label"
4
+ :error="!!errorMessage"
5
+ :error-messages="errorMessage"
6
+ :hide-details="readOnly ?? !!value"
7
+ class="btn-input-file"
8
+ >
9
+ <template #default>
10
+ <v-row no-gutters>
11
+ <v-col v-if="value || readOnly" cols="12">
12
+ <v-row no-gutters>
13
+ <v-card
14
+ variant="outlined"
15
+ border="thin"
16
+ class="px-4 text-caption font-weight-medium"
17
+ :rounded="readOnly ? '' : '0 s'"
18
+ height="36"
19
+ @click="emit('click:view')"
20
+ >
21
+ <v-row no-gutters class="fill-height" align="center">
22
+ {{ value ? "View attachment" : "No file attached" }}
23
+ </v-row>
24
+ </v-card>
25
+ <v-card
26
+ v-if="!readOnly"
27
+ variant="outlined"
28
+ border="s-0 thin"
29
+ rounded="0 e"
30
+ class="px-4 d-inline"
31
+ height="36"
32
+ @click="emit('click:close')"
33
+ >
34
+ <v-row no-gutters class="fill-height" align="center">
35
+ <v-icon size="small"> mdi-close </v-icon>
36
+ </v-row>
37
+ </v-card>
38
+ </v-row>
39
+ </v-col>
40
+
41
+ <v-col v-else cols="12">
42
+ <v-btn
43
+ variant="outlined"
44
+ border="thin"
45
+ class="text-none"
46
+ :prepend-icon="value ? '' : 'mdi-tray-arrow-up'"
47
+ :disabled="loading"
48
+ :loading="loading"
49
+ @click="selectAttachment"
50
+ >
51
+ Add file
52
+ </v-btn>
53
+ </v-col>
54
+ </v-row>
55
+
56
+ <v-file-input
57
+ v-show="false"
58
+ v-model="attachment"
59
+ multiple
60
+ accept="image/*"
61
+ :class="`attachment-input-${inputClass}`"
62
+ :name="name + 'custom-input-file'"
63
+ @change="uploadAttachment"
64
+ />
65
+ </template>
66
+ </v-input>
67
+ </template>
68
+
69
+ <script setup lang="ts">
70
+ const value = defineModel<string>({ required: true, default: "" });
71
+ const attachment = ref<File | null>(null);
72
+ const loading = ref(false);
73
+
74
+ const emit = defineEmits(["click:view", "click:close", "upload"]);
75
+
76
+ const { addFile } = useFile();
77
+ const inputClass = Date.now().toString();
78
+
79
+ function selectAttachment() {
80
+ const input = document.querySelector(
81
+ `.attachment-input-${inputClass} input`
82
+ ) as HTMLInputElement;
83
+ input.click();
84
+ }
85
+
86
+ async function uploadAttachment(e: Event) {
87
+ loading.value = true;
88
+ const files = (e.target as HTMLInputElement).files;
89
+
90
+ if (files && files.length > 0) {
91
+ const file = files[0];
92
+
93
+ try {
94
+ const res = await addFile(file);
95
+ value.value = res.id as string;
96
+ await emit("upload", res);
97
+ } catch (error) {
98
+ console.log(error);
99
+ }
100
+ } else {
101
+ console.log("No file selected");
102
+ }
103
+ loading.value = false;
104
+ }
105
+
106
+ // Other props (optional)
107
+ const { label, rules, name, readOnly } = defineProps({
108
+ label: {
109
+ type: String,
110
+ default: "Rating",
111
+ },
112
+ // Array of validation functions: (number) => true | string
113
+ rules: {
114
+ type: Array<Function>,
115
+ default: () => [],
116
+ },
117
+ name: {
118
+ type: String,
119
+ required: true,
120
+ default: "button-upload-file",
121
+ },
122
+ readOnly: {
123
+ type: Boolean,
124
+ default: false,
125
+ },
126
+ });
127
+
128
+ /**
129
+ * Compute the first failed validation rule, if any.
130
+ * Each rule returns true (if valid) or a string (if invalid).
131
+ */
132
+ const errorMessage = computed(() => {
133
+ for (const rule of rules) {
134
+ const result = rule(attachment.value || value.value);
135
+ return typeof result === "string" ? result : null;
136
+ }
137
+ return null;
138
+ });
139
+ </script>