@7365admin1/layer-common 1.10.1 → 1.10.2

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.
@@ -0,0 +1,322 @@
1
+ <template>
2
+ <v-row no-gutters>
3
+ <TableMain :headers="headers" :items="paginatePlaceholderItem" v-model:search="searchInput"
4
+ :loading="getAnnouncementPending" :page="page" :pages="pages" :pageRange="pageRange"
5
+ @refresh="getAnnouncementsRefresh" show-header @update:page="handleUpdatePage" @row-click="handleRowClick"
6
+ @create="handleCreateEvent" :can-create="canCreateBulletinBoard" create-label="Add Announcement">
7
+ <template #extension>
8
+ <v-row no-gutters class="w-100 d-flex flex-column">
9
+ <v-tabs v-model="status" color="primary" :height="40" @update:model-value="toRoute" class="w-100">
10
+ <v-tab v-for="tab in tabOptions" :value="tab.status" :key="tab.status" class="text-capitalize">
11
+ {{ tab.name }}
12
+ </v-tab>
13
+ </v-tabs>
14
+ </v-row>
15
+ </template>
16
+
17
+ <template v-slot:item.createdAt="{ item }">
18
+ <span class="d-flex align-center ga-2">
19
+ <v-icon icon="mdi-calendar-start" color="green" size="20" />
20
+ <span class="text-capitalize">{{ toLocalDate(item.createdAt) || "-" }}</span>
21
+ </span>
22
+
23
+ </template>
24
+
25
+ <template v-slot:item.noExpiration="{ item }">
26
+ <BulletinExpirationChip :value="item?.noExpiration" />
27
+ </template>
28
+
29
+ <template v-slot:item.duration="{ item }">
30
+ <span class="d-flex align-center ga-2">
31
+ <v-icon icon="mdi-calendar-start" color="green" size="20" />
32
+ <span class="text-capitalize">{{ toLocalDate(item.startDate) || "-" }}</span>
33
+ </span>
34
+ <span class="d-flex align-center ga-2">
35
+ <v-icon icon="mdi-calendar-end" color="red" size="20" />
36
+ <span class="text-capitalize">{{ toLocalDate(item.endDate) || "_" }}</span>
37
+ </span>
38
+ </template>
39
+ </TableMain>
40
+
41
+ <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" style="z-index: 3000;" />
42
+
43
+ <v-dialog v-model="dialog.showForm" width="600" persistent>
44
+ <BulletinBoardForm :mode="mode" :site-id="siteId" :active-id="selectedAnnouncementId"
45
+ @close="dialog.showForm = false" @done="handleDone" />
46
+ </v-dialog>
47
+
48
+ <v-dialog v-model="dialog.moreActions" v-if="activeAnnouncementObj && canViewBulletinBoardDetails" width="600"
49
+ persistent>
50
+ <BulletinBoardView :active-announcement="activeAnnouncementObj" :can-delete="canDeleteBulletinBoard"
51
+ :can-update="canUpdateBulletinBoard" @edit="handleEditAnnouncement" @close="dialog.moreActions = false"
52
+ @delete="dialog.deletePrompt = true" />
53
+ </v-dialog>
54
+
55
+ <v-dialog v-model="dialog.deletePrompt" width="450" persistent>
56
+ <CardDeleteConfirmation prompt-title="Are you sure want to delete this announcement?"
57
+ :loading="loading.deletingAnnouncement" @close="dialog.deletePrompt = false"
58
+ @delete="handleProceedDeleteAnnouncement" />
59
+ </v-dialog>
60
+ </v-row>
61
+ </template>
62
+ <script setup lang="ts">
63
+ definePageMeta({
64
+ memberOnly: true,
65
+ })
66
+
67
+ const props = defineProps({
68
+ siteId: {
69
+ type: String,
70
+ required: true
71
+ },
72
+ canCreateBulletinBoard: {
73
+ type: Boolean,
74
+ default: true
75
+ },
76
+ canUpdateBulletinBoard: {
77
+ type: Boolean,
78
+ default: true
79
+ },
80
+ canViewBulletinBoardDetails: {
81
+ type: Boolean,
82
+ default: true
83
+ }, canDeleteBulletinBoard: {
84
+ type: Boolean,
85
+ default: true
86
+ }
87
+ })
88
+
89
+
90
+ const { authenticate } = useLocalAuth();
91
+ authenticate();
92
+
93
+ const route = useRoute();
94
+ const orgId = route.params.org as string;
95
+ const siteId = props.siteId;
96
+ const routeName = (useRoute().name as string) ?? "";
97
+ const { getAll, deleteBulletinById } = useBulletin()
98
+ const { debounce } = useUtils()
99
+
100
+
101
+
102
+ const headers = [
103
+ { title: "Title", value: "title" },
104
+ { title: "Date Created", value: "createdAt", align: "start" },
105
+ { title: "Start Date/End Date", value: "duration", align: "start" },
106
+ { title: "No Expiration", value: "noExpiration", align: "start" },
107
+ { title: "", value: "actions" },
108
+ ];
109
+ const items = ref<TAnnouncement[]>([]);
110
+ const page = ref(1);
111
+ const pages = ref(0);
112
+ const pageRange = ref("-- - -- of --");
113
+ const selectedAnnouncementId = ref<string>("")
114
+ const mode = ref<'add' | 'edit'>('add')
115
+ const status = ref<TAnnouncementStatus>()
116
+ const searchInput = ref('')
117
+
118
+ const dialog = reactive({
119
+ showForm: false,
120
+ moreActions: false,
121
+ viewDetails: false,
122
+ deletePrompt: false,
123
+ })
124
+
125
+ const loading = reactive({
126
+ deletingAnnouncement: false
127
+ })
128
+
129
+ const messageSnackbar = ref(false)
130
+ const message = ref('')
131
+ const messageColor = ref<'success' | 'error' | 'info'>()
132
+
133
+ const activeAnnouncementObj = computed(() => {
134
+ return items.value.find(x => x._id === selectedAnnouncementId.value)
135
+ })
136
+
137
+ const paginatePlaceholderItem = computed(() => {
138
+ const pageSize = 10
139
+ const total = items.value.length
140
+ const currentPage = page.value
141
+
142
+ const start = (currentPage - 1) * pageSize
143
+ const end = start + pageSize
144
+
145
+ const from = total === 0 ? 0 : start + 1
146
+ const to = Math.min(end, total)
147
+ pageRange.value = `${from}-${to} of ${total}`
148
+
149
+ pages.value = Math.ceil(total / pageSize)
150
+
151
+ return items.value.slice(start, end)
152
+ })
153
+
154
+
155
+ const {
156
+ data: getAnnouncementReq,
157
+ refresh: getAnnouncementsRefresh,
158
+ pending: getAnnouncementPending,
159
+ } = await useLazyAsyncData(
160
+ `get-all-announcements-${page.value}`,
161
+ () => getAll({ page: page.value, site: siteId, status: status.value }),
162
+ {
163
+ watch: [page, () => route.query],
164
+ }
165
+ );
166
+
167
+ watch(getAnnouncementReq, (newVal) => {
168
+ items.value = newVal?.items || [];
169
+ pages.value = newVal?.pages || 0;
170
+ pageRange.value = newVal?.pageRange || "-- - -- of --";
171
+ });
172
+
173
+
174
+ function eventStatusFormat(status: any) {
175
+ switch (status) {
176
+ case 'In Progress':
177
+ return {
178
+ color: 'orange',
179
+ text: 'In Progress'
180
+ };
181
+ case 'Active':
182
+ return {
183
+ color: 'green',
184
+ text: status
185
+ }
186
+ case 'Scheduled':
187
+ return {
188
+ color: 'blue',
189
+ text: status
190
+ }
191
+ default:
192
+ return {
193
+ color: 'grey',
194
+ text: status
195
+ }
196
+ }
197
+ }
198
+
199
+ const tabOptions = [
200
+ { name: "Active", status: "active" },
201
+ { name: "Expired", status: "expired" },
202
+ ];
203
+
204
+ function toRoute(status: any) {
205
+ const obj = tabOptions.find((x) => x.status === status);
206
+ if (!obj) return;
207
+ page.value = 1
208
+ navigateTo({
209
+ name: routeName,
210
+ params: {
211
+ org: orgId,
212
+ },
213
+ query: {
214
+ status: obj.status,
215
+ },
216
+ });
217
+ }
218
+
219
+ function toLocalDate(utcString: string) {
220
+ if (!utcString) return ""
221
+ return new Date(utcString).toLocaleString("en-US", {
222
+ year: "numeric",
223
+ month: "2-digit",
224
+ day: "2-digit",
225
+ })
226
+ }
227
+
228
+ function handleRowClick(data: any) {
229
+ selectedAnnouncementId.value = data?.item?._id;
230
+ dialog.moreActions = true;
231
+ }
232
+
233
+ function handleUpdatePage(newPageNum: number) {
234
+ page.value = newPageNum;
235
+ }
236
+
237
+ function handleClose() {
238
+ dialog.showForm = false;
239
+
240
+ }
241
+
242
+ function handleFormDone() {
243
+ getAnnouncementsRefresh();
244
+ dialog.showForm = false;
245
+ }
246
+
247
+ function handleFormCreateMore() {
248
+ getAnnouncementsRefresh();
249
+ }
250
+
251
+ function handleCreateEvent() {
252
+ selectedAnnouncementId.value = ""
253
+ mode.value = 'add'
254
+ dialog.showForm = true;
255
+ }
256
+
257
+ function handleDeleteAnnouncement() {
258
+ dialog.moreActions = false;
259
+ dialog.showForm = false;
260
+ dialog.deletePrompt = true;
261
+ }
262
+
263
+ function handleEditAnnouncement() {
264
+ dialog.moreActions = false
265
+ mode.value = "edit"
266
+ dialog.showForm = true;
267
+ }
268
+
269
+ function showMessage(text: string, type: "error" | 'success' | 'info') {
270
+ messageSnackbar.value = true;
271
+ message.value = text;
272
+ messageColor.value = type
273
+ }
274
+
275
+
276
+
277
+
278
+
279
+ async function handleProceedDeleteAnnouncement() {
280
+ try {
281
+ loading.deletingAnnouncement = true;
282
+ const bulletinId = selectedAnnouncementId.value;
283
+ const res = await deleteBulletinById(bulletinId as string);
284
+ if (res) {
285
+ showMessage("Announcement successfully deleted!", "info");
286
+ await getAnnouncementsRefresh();
287
+ dialog.deletePrompt = false;
288
+ }
289
+ } catch (error: any) {
290
+ const errorMessage = error?.response?._data?.message;
291
+ console.log("[ERROR]", error);
292
+ showMessage(
293
+ errorMessage || "Something went wrong. Please try again later.",
294
+ "error"
295
+ );
296
+ } finally {
297
+ loading.deletingAnnouncement = false;
298
+ }
299
+ }
300
+
301
+
302
+
303
+
304
+ async function handleDone() {
305
+ await getAnnouncementsRefresh()
306
+ dialog.showForm = false
307
+ }
308
+
309
+
310
+ const debounceSearch = debounce(getAnnouncementsRefresh, 500)
311
+
312
+ watch([searchInput], ([search]) => {
313
+ debounceSearch()
314
+ })
315
+
316
+
317
+ onMounted(() => {
318
+ const statusQuery = (useRoute()?.query?.status)
319
+ status.value = (statusQuery === "active" || statusQuery === "expired") ? statusQuery : "active"
320
+ })
321
+
322
+ </script>
@@ -27,8 +27,12 @@
27
27
  import { ref, defineProps, defineEmits } from "vue";
28
28
  import Vue3Signature from "vue3-signature";
29
29
 
30
- defineProps({
30
+ const props = defineProps({
31
31
  modelValue: String,
32
+ hideToast: {
33
+ type: Boolean,
34
+ default: false,
35
+ }
32
36
  });
33
37
 
34
38
  const emit = defineEmits(["update:modelValue"]);
@@ -53,21 +57,29 @@ function showMessage(msg: string, color: string) {
53
57
 
54
58
  function onSave(data: string) {
55
59
  emit("update:modelValue", data);
56
- showMessage("Signature saved successfully.", "success");
60
+ if(!props.hideToast) {
61
+ showMessage("Signature saved successfully.", "success");
62
+ }
57
63
  }
58
64
 
59
65
  function save() {
60
66
  const signatureData = signatureRef.value?.save("image/jpeg");
61
67
  if (signatureData) {
62
68
  emit("update:modelValue", signatureData);
63
- showMessage("Signature saved successfully.", "success");
69
+ if(!props.hideToast) {
70
+ showMessage("Signature saved successfully.", "success");
71
+ }
64
72
  } else {
65
- showMessage("No signature to save.", "error");
73
+ if(!props.hideToast) {
74
+ showMessage("No signature to save.", "error");
75
+ }
66
76
  }
67
77
  }
68
78
 
69
79
  function clear() {
70
80
  signatureRef.value?.clear();
71
- showMessage("Signature cleared.", "success");
81
+ if(!props.hideToast) {
82
+ showMessage("Signature cleared.", "success");
83
+ }
72
84
  }
73
85
  </script>
@@ -272,7 +272,7 @@ const submitting = ref(false);
272
272
 
273
273
  const headers = [
274
274
  { title: "Name", value: "name" },
275
- { title: "Qty", value: "qty" },
275
+ { title: "Available Stock Qty", value: "qty" },
276
276
  { title: "Status", value: "status" },
277
277
  ];
278
278
 
@@ -55,6 +55,22 @@ export default function useAccessManagement() {
55
55
  );
56
56
  }
57
57
 
58
+ function getAllAccessCardsCounts(params: {
59
+ site: string;
60
+ userType: string;
61
+ }) {
62
+ return useNuxtApp().$api<Record<string, any>>(
63
+ `/api/access-management/all-access-cards-counts`,
64
+ {
65
+ method: "GET",
66
+ query: {
67
+ site: params.site,
68
+ userType: params.userType,
69
+ },
70
+ }
71
+ );
72
+ }
73
+
58
74
  function getUserTypeAccessCards(params: {
59
75
  page?: number;
60
76
  limit?: number;
@@ -79,6 +95,59 @@ export default function useAccessManagement() {
79
95
  );
80
96
  }
81
97
 
98
+ function bulkPhysicalAccessCard(params: { site: string; file: File }) {
99
+ const formData = new FormData();
100
+ formData.append("file", params.file);
101
+ return useNuxtApp().$api<Record<string, any>>(
102
+ `/api/access-management/bulk-upload`,
103
+ {
104
+ method: "POST",
105
+ query: { site: params.site },
106
+ body: formData,
107
+ }
108
+ );
109
+ }
110
+
111
+ function getAvailableAccessCards(params: {
112
+ site: string;
113
+ userType: string;
114
+ type: string;
115
+ accessLevel: string;
116
+ liftAccessLevel: string;
117
+ }) {
118
+ return useNuxtApp().$api<Record<string, any>>(
119
+ `/api/access-management/access-and-lift-cards`,
120
+ {
121
+ method: "GET",
122
+ query: {
123
+ site: params.site,
124
+ userType: params.userType,
125
+ type: params.type,
126
+ accessLevel: params.accessLevel,
127
+ liftAccessLevel: params.liftAccessLevel,
128
+ },
129
+ }
130
+ );
131
+ }
132
+
133
+ function assignAccessCard(payload: {
134
+ units: string[];
135
+ quantity: number;
136
+ type: string;
137
+ site: string;
138
+ userType: string;
139
+ accessLevel: string;
140
+ liftAccessLevel: string;
141
+ }) {
142
+ return useNuxtApp().$api<Record<string, any>>(
143
+ `/api/access-management/assign-access-card`,
144
+ {
145
+ method: "POST",
146
+ body: payload,
147
+ }
148
+ );
149
+ }
150
+
82
151
  return {
83
152
  getDoorAccessLevels,
84
153
  getLiftAccessLevels,
@@ -86,5 +155,9 @@ export default function useAccessManagement() {
86
155
  addPhysicalCard,
87
156
  addNonPhysicalCard,
88
157
  getUserTypeAccessCards,
158
+ getAllAccessCardsCounts,
159
+ bulkPhysicalAccessCard,
160
+ assignAccessCard,
161
+ getAvailableAccessCards,
89
162
  };
90
163
  }
@@ -0,0 +1,82 @@
1
+ export default function(){
2
+
3
+ const recipientList: { title: string, value: TAnnouncementRecipients }[] = [
4
+ { title: "Admin", value: "admin" },
5
+ { title: "Management Agency", value: "organization" },
6
+ { title: "Site Personnel", value: "site" },
7
+ { title: "Service Provider", value: "service-provider" },
8
+ { title: "Service Provider Member", value: "service-provider-member" },
9
+ { title: "Resident", value: "resident" }
10
+ ]
11
+
12
+ async function add(payload: Partial<TCreateAnnouncementPayload>) {
13
+ return await useNuxtApp().$api<Record<string, any>>("/api/bulletin-boards", {
14
+ method: "POST",
15
+ body: payload,
16
+ });
17
+ }
18
+
19
+ async function update(bulletinId: string, payload: Partial<TCreateAnnouncementPayload>) {
20
+ return await useNuxtApp().$api<Record<string, any>>(`/api/bulletin-boards/id/${bulletinId}`, {
21
+ method: "PUT",
22
+ body: payload,
23
+ });
24
+ }
25
+
26
+ type TGetAllParams = {
27
+ page?: number;
28
+ limit?: number;
29
+ sort?: 'asc' | 'desc';
30
+ site?: string;
31
+ status?: TAnnouncementStatus;
32
+ search?: string
33
+ }
34
+
35
+ async function getAll({
36
+ page = 1,
37
+ limit = 10,
38
+ sort = "asc",
39
+ site,
40
+ status,
41
+ search
42
+ } : TGetAllParams) {
43
+ return await useNuxtApp().$api<Record<string, any>>(
44
+ "/api/bulletin-boards",
45
+ {
46
+ method: "GET",
47
+ query: {
48
+ page,
49
+ limit,
50
+ sort,
51
+ site,
52
+ status,
53
+ search
54
+
55
+ },
56
+ }
57
+ );
58
+ }
59
+
60
+ async function getBulletinById(bulletinId: string) {
61
+ return await useNuxtApp().$api<Record<string, any>>(`/api/bulletin-boards/id/${bulletinId}`, {
62
+ method: "GET",
63
+ });
64
+ }
65
+
66
+ async function deleteBulletinById(bulletinId: string) {
67
+ return await useNuxtApp().$api<Record<string, any>>(`/api/bulletin-boards/${bulletinId}`, {
68
+ method: "PUT",
69
+ });
70
+ }
71
+
72
+
73
+ return {
74
+ recipientList,
75
+ add,
76
+ update,
77
+ getAll,
78
+ getBulletinById,
79
+ deleteBulletinById
80
+
81
+ }
82
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "1.10.1",
5
+ "version": "1.10.2",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {