@7365admin1/layer-common 1.10.9 → 1.10.10

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 1.10.10
4
+
5
+ ### Patch Changes
6
+
7
+ - 276911d: Update Bulletin Board changes
8
+
3
9
  ## 1.10.9
4
10
 
5
11
  ### Patch Changes
@@ -2,7 +2,7 @@
2
2
  <v-row no-gutters>
3
3
  <TableMain :headers="headers" :items="paginatePlaceholderItem" v-model:search="searchInput"
4
4
  :loading="getAnnouncementPending" :page="page" :pages="pages" :pageRange="pageRange"
5
- @refresh="getAnnouncementsRefresh" show-header @update:page="handleUpdatePage" @row-click="handleRowClick"
5
+ @refresh="getAnnouncementsRefresh" :show-header="APP_CONSTANTS.RESIDENT === props.recipients" @update:page="handleUpdatePage" @row-click="handleRowClick"
6
6
  @create="handleCreateEvent" :can-create="canCreateBulletinBoard" create-label="Add Announcement">
7
7
  <template #extension>
8
8
  <v-row no-gutters class="w-100 d-flex flex-column">
@@ -60,6 +60,8 @@
60
60
  </v-row>
61
61
  </template>
62
62
  <script setup lang="ts">
63
+ import { APP_CONSTANTS } from '../constants/app';
64
+
63
65
  definePageMeta({
64
66
  memberOnly: true,
65
67
  })
@@ -104,13 +106,21 @@ const { debounce } = useUtils()
104
106
 
105
107
 
106
108
 
107
- const headers = [
108
- { title: "Title", value: "title" },
109
- { title: "Date Created", value: "createdAt", align: "start" },
110
- { title: "Start Date/End Date", value: "duration", align: "start" },
111
- { title: "No Expiration", value: "noExpiration", align: "start" },
112
- { title: "", value: "actions" },
113
- ];
109
+ const headers = computed(() => {
110
+ const arr = [
111
+ { title: "Title", value: "title", align: "start" },
112
+ ]
113
+
114
+ if (props.recipients === APP_CONSTANTS.RESIDENT) {
115
+ arr.push({ title: "Date Created", value: "createdAt", align: "start" });
116
+ arr.push({ title: "Start Date/End Date", value: "duration", align: "start" });
117
+ arr.push({ title: "No Expiration", value: "noExpiration", align: "start" });
118
+ arr.push({ title: "", value: "actions", align: "center" });
119
+ }
120
+
121
+ return arr;
122
+
123
+ });
114
124
  const items = ref<TAnnouncement[]>([]);
115
125
  const page = ref(1);
116
126
  const pages = ref(0);
@@ -0,0 +1,240 @@
1
+ <template>
2
+ <v-row no-gutters class="pa-3">
3
+ <v-col cols="12">
4
+ <v-card width="100%" variant="outlined" border="thin" rounded="lg" max-width="700" max-height="500"
5
+ style="overflow-y: auto;">
6
+ <v-toolbar density="compact" color="grey-lighten-4 pl-2 pr-4">
7
+ <v-row v-if="!hideTitle" no-gutters class="px-3 font-weight-bold">
8
+ Delivery Companies
9
+ </v-row>
10
+
11
+ <template #append>
12
+ <v-btn v-if="!props.readOnly" variant="flat" color="primary" class="text-none"
13
+ @click="handleAdd">
14
+ Add
15
+ </v-btn>
16
+ </template>
17
+ </v-toolbar>
18
+
19
+ <v-card-text class="pa-0" style="min-height: 100px;">
20
+ <template v-if="companies.length > 0">
21
+ <draggable :list="companies" item-key="index" class="drag-area"
22
+ :disabled="editingIndex !== null">
23
+ <v-list-item v-for="(item, index) in companies" :key="index" class="mt-1">
24
+ <v-row class="d-flex justify-space-between align-center" no-gutters>
25
+ <span class="d-flex align-center">
26
+ <v-btn icon="mdi-drag" class="drag-handle text-grey mr-2" flat
27
+ density="compact" />
28
+
29
+ <div v-if="editingIndex === index" class="w-full">
30
+ <v-text-field v-model="editedValue" class="text-subtitle-2"
31
+ prepend-icon="mdi-truck" style="min-width: 300px;" density="compact"
32
+ hide-details autofocus />
33
+ </div>
34
+
35
+ <div v-else>
36
+ <span>
37
+ <v-icon icon="mdi-truck" class="text-h6 mr-2" />
38
+ </span>
39
+ {{ item }}
40
+ </div>
41
+ </span>
42
+
43
+ <span class="d-flex align-center ga-2">
44
+ <template v-if="editingIndex === index">
45
+ <v-btn icon="mdi-check" color="green" density="compact"
46
+ @click="saveEdit(index)" />
47
+ <v-btn icon="mdi-close" color="grey" density="compact"
48
+ @click="cancelEdit" />
49
+ </template>
50
+
51
+ <template v-else>
52
+ <v-btn v-if="!props.readOnly" flat icon="mdi-pencil" class="text-grey"
53
+ density="compact" @click="startEdit(index)" />
54
+ <v-btn v-if="!props.readOnly" flat icon="mdi-trash-outline" class="text-red"
55
+ density="compact" @click="handleRemove(index)" />
56
+ </template>
57
+ </span>
58
+ </v-row>
59
+ </v-list-item>
60
+ </draggable>
61
+ </template>
62
+
63
+ <div v-else class="text-center py-5">
64
+ No delivery companies added yet.
65
+ </div>
66
+ </v-card-text>
67
+ </v-card>
68
+ </v-col>
69
+
70
+ <v-dialog v-model="dialog.add" persistent width="450">
71
+ <v-card width="100%">
72
+ <v-toolbar>
73
+ <v-row no-gutters class="fill-height px-6" align="center">
74
+ <span class="font-weight-bold text-h5 text-capitalize">
75
+ Add Delivery Company
76
+ </span>
77
+ </v-row>
78
+ </v-toolbar>
79
+
80
+ <v-card-text>
81
+ <v-text-field v-model="editedValue" placeholder="Company Name" autofocus />
82
+ </v-card-text>
83
+
84
+ <v-toolbar>
85
+ <v-row no-gutters justify="end" align="center" class="px-5">
86
+ <v-btn variant="text" text="Cancel" @click="dialog.add = false" />
87
+ <v-btn variant="flat" color="primary" class="text-none ml-2"
88
+ @click="saveEdit(companies.length)">
89
+ Add
90
+ </v-btn>
91
+ </v-row>
92
+ </v-toolbar>
93
+ </v-card>
94
+ </v-dialog>
95
+
96
+ <Snackbar v-model="toast.show" :text="toast.message" :color="toast.color" />
97
+
98
+ <v-dialog v-model="dialog.delete" persistent width="450">
99
+ <CardDeleteConfirmation
100
+ :prompt-title="`Are you sure you want to delete this ${companies?.[deleteIndex!]}?`"
101
+ @close="dialog.delete = false"
102
+ @delete="removeCompany(deleteIndex!)"
103
+ />
104
+ </v-dialog>
105
+ </v-row>
106
+ </template>
107
+
108
+ <script setup lang="ts">
109
+ import useSiteSettings from '../composables/useSiteSettings'
110
+
111
+ const props = defineProps<{
112
+ site: string
113
+ readOnly?: boolean
114
+ hideTitle?: boolean
115
+ }>()
116
+
117
+ const emit = defineEmits<{
118
+ (e: 'update:companiesValue', value: string[]): void
119
+ (e: 'refresh-site'): void
120
+ }>()
121
+
122
+ const { updateSitebyId } = useSiteSettings()
123
+
124
+ const initialCompanies = defineModel<string[]>('initial', {
125
+ type: Array,
126
+ default: []
127
+ })
128
+
129
+ const dialog = reactive({
130
+ add: false,
131
+ delete: false,
132
+ })
133
+
134
+ const toast = reactive({
135
+ show: false,
136
+ message: '',
137
+ color: 'success',
138
+ })
139
+
140
+ const companies = ref<string[]>([])
141
+ const editingIndex = ref<number | null>(null)
142
+ const editedValue = ref('')
143
+ const isHydrating = ref(true)
144
+ const deleteIndex = ref<number | null>(null)
145
+
146
+
147
+
148
+ watch(
149
+ companies,
150
+ async (newVal) => {
151
+ await persistCompanies()
152
+ },
153
+ { deep: true, immediate: false }
154
+ )
155
+
156
+ const startEdit = (index: number) => {
157
+ editingIndex.value = index
158
+ editedValue.value = companies.value[index] || ''
159
+ }
160
+
161
+ const saveEdit = async (index: number) => {
162
+ const value = editedValue.value.trim()
163
+ if (!value) return
164
+
165
+ const updated = [...companies.value]
166
+
167
+ if (index >= updated.length) {
168
+ updated.push(value)
169
+ } else {
170
+ updated[index] = value
171
+ }
172
+
173
+ companies.value = updated
174
+ editingIndex.value = null
175
+ editedValue.value = ''
176
+ dialog.add = false
177
+ }
178
+
179
+ const cancelEdit = () => {
180
+ editingIndex.value = null
181
+ editedValue.value = ''
182
+ }
183
+
184
+ const handleAdd = () => {
185
+ dialog.add = true
186
+ editedValue.value = ''
187
+ editingIndex.value = null
188
+ }
189
+
190
+ const removeCompany = async (index: number) => {
191
+ const updated = [...companies.value]
192
+ updated.splice(index, 1)
193
+ companies.value = updated
194
+ dialog.delete = false
195
+ }
196
+
197
+ const handleRemove = async (index: number) => {
198
+ deleteIndex.value = index;
199
+ dialog.delete = true;
200
+ }
201
+
202
+ const persistCompanies = async () => {
203
+ try {
204
+ const isSame =
205
+ companies.value.length === initialCompanies.value.length &&
206
+ companies.value.every((c, i) => c === initialCompanies.value[i]);
207
+ if (isSame) return
208
+
209
+
210
+ await updateSitebyId(props.site, {
211
+ deliveryCompanyList: companies.value,
212
+ })
213
+
214
+ toast.message = 'Delivery companies updated successfully!'
215
+ toast.color = 'success'
216
+ toast.show = true
217
+ emit('refresh-site')
218
+ } catch (error) {
219
+ console.error('Failed to update delivery companies:', error)
220
+ toast.message = 'Failed to update delivery companies. Please try again.'
221
+ toast.color = 'error'
222
+ toast.show = true
223
+ }
224
+ }
225
+
226
+ watch(initialCompanies, (val) => {
227
+ companies.value = Array.isArray(val) ? [...val] : []
228
+ isHydrating.value = false
229
+ }, { immediate: true })
230
+ </script>
231
+
232
+ <style scoped>
233
+ .drag-area {
234
+ cursor: grab;
235
+ }
236
+
237
+ .drag-handle {
238
+ cursor: grab;
239
+ }
240
+ </style>
@@ -189,6 +189,8 @@
189
189
  </v-card>
190
190
  </v-container>
191
191
  </v-dialog>
192
+
193
+ <Snackbar v-model="snackbar" :text="snackbarText" :color="snackbarColor" />
192
194
  </div>
193
195
  </template>
194
196
 
@@ -226,7 +228,7 @@ const props = defineProps({
226
228
 
227
229
  const emit = defineEmits(["update:modelValue", "update:quantity", "update:cards"]);
228
230
 
229
- const { getAvailableContractorCards } = useAccessManagement();
231
+ const { getAvailableContractorCards, generateQrVms } = useAccessManagement();
230
232
 
231
233
  const nfcEnabled = computed(() => props.settings?.data?.settings?.nfcPass ?? false);
232
234
  const printer = computed(() => props.settings?.data?.settings?.printer ?? { vendorId: null, productId: null });
@@ -250,6 +252,17 @@ const selectedCards = computed({
250
252
  },
251
253
  });
252
254
 
255
+ // ─── Snackbar ─────────────────────────────────────────────────────
256
+ const snackbar = ref(false);
257
+ const snackbarText = ref("");
258
+ const snackbarColor = ref("error");
259
+
260
+ function showSnackbar(text: string, color = "error") {
261
+ snackbarText.value = text;
262
+ snackbarColor.value = color;
263
+ snackbar.value = true;
264
+ }
265
+
253
266
  // ─── Card fetching ────────────────────────────────────────────────
254
267
  const cardItems = ref<any[]>([]);
255
268
  const cardsLoading = ref(false);
@@ -266,6 +279,27 @@ async function fetchCards(type: "NFC" | "QRCODE" = "NFC") {
266
279
  });
267
280
  cardItems.value = res?.data?.[0]?.items ?? [];
268
281
  availableCount.value = (res?.data?.[2] as any)?.count ?? cardItems.value.length;
282
+
283
+ if (type === "QRCODE" && props.settings?.data?._id && availableCount.value !== null && availableCount.value < 50) {
284
+ await generateQrVms({
285
+ site: props.settings.data.site,
286
+ unitId: props.unitId,
287
+ quantity: 50,
288
+ });
289
+ // Re-fetch to get the updated count after generation
290
+ const refreshed = await getAvailableContractorCards({
291
+ type,
292
+ siteId: props.siteId,
293
+ unitId: props.unitId,
294
+ });
295
+ cardItems.value = refreshed?.data?.[0]?.items ?? [];
296
+ availableCount.value = (refreshed?.data?.[2] as any)?.count ?? cardItems.value.length;
297
+ }
298
+ } catch (err: any) {
299
+ const msg = err?.data?.message ?? err?.message ?? String(err);
300
+ showSnackbar(`Entry pass service error: ${msg}`);
301
+ availableCount.value = null;
302
+ cardItems.value = [];
269
303
  } finally {
270
304
  cardsLoading.value = false;
271
305
  }
@@ -109,29 +109,14 @@
109
109
  >
110
110
  <template #item.createdBy="{ item, index }">
111
111
  <v-avatar
112
- v-if="
113
- item?.createdBy &&
114
- Boolean(item?.createdBy) &&
115
- typeof item?.createdBy === 'object' &&
116
- Object.keys(item?.createdBy).length > 0
117
- "
112
+ v-if="item?.createdBy?.name"
118
113
  size="small"
119
114
  :color="materialColors[index % materialColors.length]"
120
115
  class="text-subtitle-2 mr-1 mr-md-4"
121
116
  >
122
- {{
123
- getInitial(
124
- `${item?.createdBy?.givenName ?? ""} ${
125
- item?.createdBy?.surname ?? ""
126
- }`
127
- ) || ""
128
- }}
117
+ {{ getInitial(item?.createdBy?.name) ?? "" }}
129
118
  </v-avatar>
130
- {{
131
- `${item?.createdBy?.givenName ?? ""} ${
132
- item?.createdBy?.surname ?? ""
133
- }`
134
- }}
119
+ {{ item?.createdBy?.name ?? "N/A" }}
135
120
  </template>
136
121
  <template #item.subject="{ item }">
137
122
  {{ item.subject || "N/A" }}
@@ -685,7 +670,7 @@ const {
685
670
  } = useUtils();
686
671
 
687
672
  const headers: Array<Record<string, any>> = [
688
- { title: "Name", value: "createdByName", align: "start" },
673
+ { title: "Name", value: "createdBy", align: "start" },
689
674
  { title: "Subject", value: "subject", align: "start" },
690
675
  { title: "Date", value: "createdAt", align: "start" },
691
676
  { title: "App", value: "app", align: "start" },