@7365admin1/layer-common 1.11.40 → 1.11.42
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 +13 -0
- package/components/AccessCardPreviewDialog.vue +164 -0
- package/components/AttachmentsViewer.vue +44 -15
- package/components/BulletinBoardForm.vue +11 -2
- package/components/BulletinBoardManagement.vue +2 -1
- package/components/ClientMain.vue +36 -22
- package/components/Input/DateTimePicker.vue +14 -1
- package/components/InvitationClientForm.vue +114 -60
- package/components/InvitationForm.vue +15 -4
- package/components/Layout/NavigationDrawer.vue +11 -15
- package/components/MemberMain.vue +9 -12
- package/components/TableMain.vue +1 -1
- package/components/VehicleForm.vue +4 -2
- package/components/VisitorManagement.vue +215 -628
- package/composables/useFile.ts +1 -1
- package/composables/useLocalAuth.ts +12 -0
- package/composables/useMember.ts +41 -14
- package/composables/useOrg.ts +49 -1
- package/composables/useUser.ts +46 -14
- package/package.json +1 -1
- package/types/nuxt-app.d.ts +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 1.11.42
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e11278e: updates and fixes
|
|
8
|
+
|
|
9
|
+
## 1.11.41
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- bc096ed: Fix navigation not get children item
|
|
14
|
+
- 5031c18: Update Vehicle Management
|
|
15
|
+
|
|
3
16
|
## 1.11.40
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
|
@@ -5,6 +5,96 @@
|
|
|
5
5
|
:site-id="siteId"
|
|
6
6
|
/>
|
|
7
7
|
|
|
8
|
+
<!-- Assign to Person Dialog -->
|
|
9
|
+
<v-dialog :model-value="showAssignForm" width="420" persistent>
|
|
10
|
+
<v-card width="100%">
|
|
11
|
+
<v-toolbar density="compact" color="surface">
|
|
12
|
+
<v-row no-gutters class="fill-height px-4" align="center">
|
|
13
|
+
<span class="font-weight-bold">Assign Card to Person</span>
|
|
14
|
+
</v-row>
|
|
15
|
+
</v-toolbar>
|
|
16
|
+
|
|
17
|
+
<v-card-text class="pa-4">
|
|
18
|
+
<div class="text-caption text-grey mb-4">
|
|
19
|
+
Card: <strong class="text-body-2 text-black">{{ selectedCardInUnit?.cardNo }}</strong>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<!-- Person Type Filter -->
|
|
23
|
+
<div class="text-caption font-weight-bold mb-1 text-uppercase text-grey-darken-1">Person Type</div>
|
|
24
|
+
<v-btn-toggle
|
|
25
|
+
v-model="assignPersonType"
|
|
26
|
+
color="primary"
|
|
27
|
+
density="compact"
|
|
28
|
+
variant="outlined"
|
|
29
|
+
rounded="pill"
|
|
30
|
+
class="mb-4"
|
|
31
|
+
>
|
|
32
|
+
<v-btn value="" size="small" class="text-none px-3">All</v-btn>
|
|
33
|
+
<v-btn value="owner" size="small" class="text-none px-3">Unit Owner</v-btn>
|
|
34
|
+
<v-btn value="resident" size="small" class="text-none px-3">Resident</v-btn>
|
|
35
|
+
<v-btn value="tenant" size="small" class="text-none px-3">Tenant</v-btn>
|
|
36
|
+
</v-btn-toggle>
|
|
37
|
+
|
|
38
|
+
<!-- Person Selector -->
|
|
39
|
+
<div class="text-caption font-weight-bold mb-1 text-uppercase text-grey-darken-1">
|
|
40
|
+
Person <span class="text-error">*</span>
|
|
41
|
+
</div>
|
|
42
|
+
<v-autocomplete
|
|
43
|
+
v-model="assignPerson"
|
|
44
|
+
:items="filteredPeopleItems"
|
|
45
|
+
density="compact"
|
|
46
|
+
item-title="name"
|
|
47
|
+
item-value="_id"
|
|
48
|
+
hide-details="auto"
|
|
49
|
+
placeholder="Select person..."
|
|
50
|
+
persistent-placeholder
|
|
51
|
+
:loading="peopleLoading"
|
|
52
|
+
return-object
|
|
53
|
+
no-data-text="No residents or tenants found for this unit"
|
|
54
|
+
>
|
|
55
|
+
<template #item="{ props: itemProps, item }">
|
|
56
|
+
<v-list-item v-bind="itemProps" :subtitle="personTypeLabel(item.raw)" />
|
|
57
|
+
</template>
|
|
58
|
+
<template #selection="{ item }">
|
|
59
|
+
<span>{{ item.raw.name }}</span>
|
|
60
|
+
<v-chip size="x-small" class="ml-2" color="grey" variant="tonal">
|
|
61
|
+
{{ personTypeLabel(item.raw) }}
|
|
62
|
+
</v-chip>
|
|
63
|
+
</template>
|
|
64
|
+
</v-autocomplete>
|
|
65
|
+
</v-card-text>
|
|
66
|
+
|
|
67
|
+
<v-toolbar density="compact">
|
|
68
|
+
<v-row no-gutters>
|
|
69
|
+
<v-col cols="6" class="pa-0">
|
|
70
|
+
<v-btn
|
|
71
|
+
block
|
|
72
|
+
variant="text"
|
|
73
|
+
class="text-none"
|
|
74
|
+
height="48"
|
|
75
|
+
@click="cancelAssign"
|
|
76
|
+
>
|
|
77
|
+
Cancel
|
|
78
|
+
</v-btn>
|
|
79
|
+
</v-col>
|
|
80
|
+
<v-col cols="6" class="pa-0">
|
|
81
|
+
<v-btn
|
|
82
|
+
block
|
|
83
|
+
variant="flat"
|
|
84
|
+
color="black"
|
|
85
|
+
class="text-none"
|
|
86
|
+
height="48"
|
|
87
|
+
:disabled="!assignPerson"
|
|
88
|
+
@click="confirmAssign"
|
|
89
|
+
>
|
|
90
|
+
Assign
|
|
91
|
+
</v-btn>
|
|
92
|
+
</v-col>
|
|
93
|
+
</v-row>
|
|
94
|
+
</v-toolbar>
|
|
95
|
+
</v-card>
|
|
96
|
+
</v-dialog>
|
|
97
|
+
|
|
8
98
|
<v-dialog :model-value="modelValue" width="450" persistent>
|
|
9
99
|
<v-card width="100%">
|
|
10
100
|
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0">
|
|
@@ -211,6 +301,14 @@
|
|
|
211
301
|
</v-btn>
|
|
212
302
|
</template>
|
|
213
303
|
<v-list class="pa-0">
|
|
304
|
+
<v-list-item
|
|
305
|
+
v-if="canAssignToPerson && isSelectedCardAvailable"
|
|
306
|
+
@click="openAssignForm"
|
|
307
|
+
>
|
|
308
|
+
<v-list-item-title class="text-subtitle-2">
|
|
309
|
+
Assign to Person
|
|
310
|
+
</v-list-item-title>
|
|
311
|
+
</v-list-item>
|
|
214
312
|
<v-list-item
|
|
215
313
|
:disabled="!isSelectedCardAssignedPhysical"
|
|
216
314
|
@click="emit('replace')"
|
|
@@ -270,6 +368,10 @@ const props = defineProps({
|
|
|
270
368
|
type: Boolean,
|
|
271
369
|
default: true,
|
|
272
370
|
},
|
|
371
|
+
canAssignToPerson: {
|
|
372
|
+
type: Boolean,
|
|
373
|
+
default: true,
|
|
374
|
+
},
|
|
273
375
|
isSelectedCardAssignedPhysical: {
|
|
274
376
|
type: Boolean,
|
|
275
377
|
default: false,
|
|
@@ -289,9 +391,26 @@ const emit = defineEmits<{
|
|
|
289
391
|
"update:selectedCardInUnit": [value: Record<string, any> | null];
|
|
290
392
|
replace: [];
|
|
291
393
|
delete: [];
|
|
394
|
+
"assign-to-person": [{ card: Record<string, any> | null; person: Record<string, any> | null }];
|
|
292
395
|
}>();
|
|
293
396
|
|
|
397
|
+
const { getPeopleByUnit: _getPeopleByUnit } = usePeople();
|
|
398
|
+
|
|
294
399
|
const historyDialog = ref(false);
|
|
400
|
+
const showAssignForm = ref(false);
|
|
401
|
+
const assignPersonType = ref<string>("");
|
|
402
|
+
const assignPerson = ref<Record<string, any> | null>(null);
|
|
403
|
+
const peopleItems = ref<Record<string, any>[]>([]);
|
|
404
|
+
const peopleLoading = ref(false);
|
|
405
|
+
|
|
406
|
+
const isSelectedCardAvailable = computed(() => {
|
|
407
|
+
if (!props.selectedCardInUnit?._id) return false;
|
|
408
|
+
const id = props.selectedCardInUnit._id;
|
|
409
|
+
return [
|
|
410
|
+
...(props.unit?.available?.physical ?? []),
|
|
411
|
+
...(props.unit?.available?.non_physical ?? []),
|
|
412
|
+
].some((c) => c._id === id);
|
|
413
|
+
});
|
|
295
414
|
|
|
296
415
|
const isSelectedCardDeletable = computed(() => {
|
|
297
416
|
if (!props.selectedCardInUnit?._id) return false;
|
|
@@ -304,6 +423,51 @@ const isSelectedCardDeletable = computed(() => {
|
|
|
304
423
|
].some((c) => c._id === id);
|
|
305
424
|
});
|
|
306
425
|
|
|
426
|
+
const filteredPeopleItems = computed(() => {
|
|
427
|
+
if (!assignPersonType.value) return peopleItems.value;
|
|
428
|
+
if (assignPersonType.value === "owner") return peopleItems.value.filter((p) => p.isOwner);
|
|
429
|
+
return peopleItems.value.filter((p) => p.type === assignPersonType.value && !p.isOwner);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const personTypeLabel = (person: Record<string, any>) => {
|
|
433
|
+
if (person.isOwner) return "Unit Owner";
|
|
434
|
+
if (person.type === "resident") return "Resident";
|
|
435
|
+
if (person.type === "tenant") return "Tenant";
|
|
436
|
+
return person.type ?? "";
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
async function openAssignForm() {
|
|
440
|
+
showAssignForm.value = true;
|
|
441
|
+
assignPerson.value = null;
|
|
442
|
+
assignPersonType.value = "";
|
|
443
|
+
peopleItems.value = [];
|
|
444
|
+
|
|
445
|
+
if (!props.unit?._id) return;
|
|
446
|
+
peopleLoading.value = true;
|
|
447
|
+
try {
|
|
448
|
+
const res = await _getPeopleByUnit(props.unit._id);
|
|
449
|
+
peopleItems.value = res?.data ?? [];
|
|
450
|
+
} catch {
|
|
451
|
+
peopleItems.value = [];
|
|
452
|
+
} finally {
|
|
453
|
+
peopleLoading.value = false;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function cancelAssign() {
|
|
458
|
+
showAssignForm.value = false;
|
|
459
|
+
assignPerson.value = null;
|
|
460
|
+
assignPersonType.value = "";
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function confirmAssign() {
|
|
464
|
+
emit("assign-to-person", {
|
|
465
|
+
card: props.selectedCardInUnit,
|
|
466
|
+
person: assignPerson.value,
|
|
467
|
+
});
|
|
468
|
+
cancelAssign();
|
|
469
|
+
}
|
|
470
|
+
|
|
307
471
|
function toggleCard(card: Record<string, any>) {
|
|
308
472
|
emit(
|
|
309
473
|
"update:selectedCardInUnit",
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
<!-- Loading Skeleton -->
|
|
8
8
|
<template v-if="loading && files?.length > 0">
|
|
9
|
-
<v-card v-for="(file, index) in files" :key="index" height="50px" flat border="sm black"
|
|
9
|
+
<v-card v-for="(file, index) in files" :key="index" height="50px" flat border="sm black"
|
|
10
|
+
class="mb-2">
|
|
10
11
|
<v-skeleton-loader type="list-item" class="mb-2" />
|
|
11
12
|
</v-card>
|
|
12
13
|
</template>
|
|
@@ -56,7 +57,7 @@
|
|
|
56
57
|
</template>
|
|
57
58
|
<v-list-item-title class="text-body-2">{{ item.file.name }}</v-list-item-title>
|
|
58
59
|
<v-list-item-subtitle class="text-caption">{{ item.mimeType
|
|
59
|
-
|
|
60
|
+
}}</v-list-item-subtitle>
|
|
60
61
|
<template #append>
|
|
61
62
|
<v-icon size="16" color="grey-lighten-1">mdi-eye-outline</v-icon>
|
|
62
63
|
</template>
|
|
@@ -81,7 +82,7 @@
|
|
|
81
82
|
</template>
|
|
82
83
|
<v-list-item-title class="text-body-2">{{ item.file.name }}</v-list-item-title>
|
|
83
84
|
<v-list-item-subtitle class="text-caption">{{ item.mimeType
|
|
84
|
-
|
|
85
|
+
}}</v-list-item-subtitle>
|
|
85
86
|
<template #append>
|
|
86
87
|
<v-icon size="16" color="grey-lighten-1">mdi-open-in-new</v-icon>
|
|
87
88
|
</template>
|
|
@@ -200,26 +201,54 @@ watchEffect(async () => {
|
|
|
200
201
|
for (const item of (props.files || [])) {
|
|
201
202
|
// Handle building files format: { id, name }
|
|
202
203
|
if (item.id && item.name && !item.file) {
|
|
204
|
+
let file: File = new File([], item.name);
|
|
203
205
|
let mimeType = 'application/octet-stream';
|
|
206
|
+
|
|
204
207
|
try {
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
208
|
+
// Try to get the actual file with proper MIME type detection from blob
|
|
209
|
+
const fileUrl = getFileUrl(item.id);
|
|
210
|
+
file = await urlToFile(fileUrl, item.name);
|
|
211
|
+
mimeType = file.type || 'application/octet-stream';
|
|
212
|
+
|
|
213
|
+
// If blob detection returned generic type, try API metadata or filename
|
|
214
|
+
if (mimeType === 'application/octet-stream') {
|
|
215
|
+
try {
|
|
216
|
+
const fileMeta = await getFileById(item.id) as any;
|
|
217
|
+
const apiMimeType = fileMeta?.data?.mimeType || fileMeta?.mimeType;
|
|
218
|
+
if (apiMimeType && apiMimeType !== 'application/octet-stream') {
|
|
219
|
+
mimeType = apiMimeType;
|
|
220
|
+
} else {
|
|
221
|
+
const ext = item.name?.split('.').pop()?.toLowerCase();
|
|
222
|
+
mimeType = inferMimeType(ext);
|
|
223
|
+
}
|
|
224
|
+
} catch {
|
|
225
|
+
// Fallback to filename-based detection
|
|
226
|
+
const ext = item.name?.split('.').pop()?.toLowerCase();
|
|
227
|
+
mimeType = inferMimeType(ext);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
// Fallback: try API metadata
|
|
232
|
+
try {
|
|
233
|
+
const fileMeta = await getFileById(item.id) as any;
|
|
234
|
+
const apiMimeType = fileMeta?.data?.mimeType || fileMeta?.mimeType;
|
|
235
|
+
if (apiMimeType && apiMimeType !== 'application/octet-stream') {
|
|
236
|
+
mimeType = apiMimeType;
|
|
237
|
+
} else {
|
|
238
|
+
// Fallback to filename-based detection
|
|
239
|
+
const ext = item.name?.split('.').pop()?.toLowerCase();
|
|
240
|
+
mimeType = inferMimeType(ext);
|
|
241
|
+
}
|
|
242
|
+
} catch {
|
|
243
|
+
// Final fallback: infer from filename
|
|
212
244
|
const ext = item.name?.split('.').pop()?.toLowerCase();
|
|
213
245
|
mimeType = inferMimeType(ext);
|
|
214
246
|
}
|
|
215
|
-
} catch {
|
|
216
|
-
// Fallback: infer MIME type from file name
|
|
217
|
-
const ext = item.name?.split('.').pop()?.toLowerCase();
|
|
218
|
-
mimeType = inferMimeType(ext);
|
|
219
247
|
}
|
|
248
|
+
|
|
220
249
|
processedFiles.push({
|
|
221
250
|
_id: item.id,
|
|
222
|
-
file
|
|
251
|
+
file,
|
|
223
252
|
preview: item.preview ?? false,
|
|
224
253
|
mimeType
|
|
225
254
|
});
|
|
@@ -52,12 +52,12 @@
|
|
|
52
52
|
|
|
53
53
|
<v-col cols="12">
|
|
54
54
|
<InputLabel class="text-capitalize" title="Start Date" required />
|
|
55
|
-
<InputDateTimePicker v-model:utc="bulletinForm.startDate" :rules="[validStartDateRule]"
|
|
55
|
+
<InputDateTimePicker v-model:utc="bulletinForm.startDate" :rules="[validStartDateRule]"/>
|
|
56
56
|
</v-col>
|
|
57
57
|
|
|
58
58
|
<v-col cols="12" v-if="!bulletinForm.noExpiration">
|
|
59
59
|
<InputLabel class="text-capitalize" title="End Date" required />
|
|
60
|
-
<InputDateTimePicker v-model:utc="bulletinForm.endDate" :rules="[validExpiryDateRule]" />
|
|
60
|
+
<InputDateTimePicker v-model:utc="bulletinForm.endDate" :rules="[validExpiryDateRule]" :min="today" />
|
|
61
61
|
</v-col>
|
|
62
62
|
|
|
63
63
|
<v-col cols="12">
|
|
@@ -103,6 +103,15 @@
|
|
|
103
103
|
</template>
|
|
104
104
|
|
|
105
105
|
<script setup lang="ts">
|
|
106
|
+
const today = computed(() => {
|
|
107
|
+
const d = new Date()
|
|
108
|
+
const yyyy = d.getFullYear()
|
|
109
|
+
const mm = String(d.getMonth() + 1).padStart(2, '0')
|
|
110
|
+
const dd = String(d.getDate()).padStart(2, '0')
|
|
111
|
+
console.log('${yyyy}-${mm}-${dd}',`${yyyy}-${mm}-${dd}`)
|
|
112
|
+
return `${yyyy}-${mm}-${dd}`
|
|
113
|
+
})
|
|
114
|
+
|
|
106
115
|
const prop = defineProps({
|
|
107
116
|
siteId: {
|
|
108
117
|
type: String,
|
|
@@ -214,6 +214,7 @@ function eventStatusFormat(status: any) {
|
|
|
214
214
|
|
|
215
215
|
const tabOptions = [
|
|
216
216
|
{ name: "Active", status: "active" },
|
|
217
|
+
{ name: "Upcoming", status: "upcoming" },
|
|
217
218
|
{ name: "Expired", status: "expired" },
|
|
218
219
|
];
|
|
219
220
|
|
|
@@ -332,7 +333,7 @@ watch([searchInput], ([search]) => {
|
|
|
332
333
|
|
|
333
334
|
onMounted(() => {
|
|
334
335
|
const statusQuery = (useRoute()?.query?.status)
|
|
335
|
-
status.value = (statusQuery === "active" || statusQuery === "expired") ? statusQuery : "active"
|
|
336
|
+
status.value = (statusQuery === "active" || statusQuery === "upcoming" || statusQuery === "expired") ? statusQuery : "active"
|
|
336
337
|
})
|
|
337
338
|
|
|
338
339
|
</script>
|
|
@@ -34,8 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
<!-- ACTIONS -->
|
|
36
36
|
<template #actions>
|
|
37
|
-
<v-btn
|
|
38
|
-
@click="openInviteDialog">
|
|
37
|
+
<v-btn class="text-none" rounded="pill" variant="tonal" size="large" @click="openInviteDialog">
|
|
39
38
|
Invite Client
|
|
40
39
|
</v-btn>
|
|
41
40
|
</template>
|
|
@@ -71,7 +70,7 @@
|
|
|
71
70
|
</template>
|
|
72
71
|
|
|
73
72
|
<script setup lang="ts">
|
|
74
|
-
import { computed, onMounted, ref } from 'vue'
|
|
73
|
+
import { computed, nextTick, onMounted, ref } from 'vue'
|
|
75
74
|
import useOrg from '../composables/useOrg'
|
|
76
75
|
import useVerification from '../composables/useVerification'
|
|
77
76
|
import useRole from '../composables/useRole'
|
|
@@ -82,7 +81,7 @@ import useSubscription from '../composables/useSubscription'
|
|
|
82
81
|
const tab = ref<'active' | 'suspended' | 'pending'>('active')
|
|
83
82
|
const dialog = ref(false)
|
|
84
83
|
|
|
85
|
-
const {
|
|
84
|
+
const { getAllV2 } = useOrg()
|
|
86
85
|
const { getVerifications, cancelUserInvitation } = useVerification()
|
|
87
86
|
const { getCustomerSites: getCustomerSitesByOrgId } = useCustomerSite()
|
|
88
87
|
const { getByOrgId: getSubscriptionByOrgId } = useSubscription()
|
|
@@ -112,6 +111,7 @@ const APP_LIST = [
|
|
|
112
111
|
/* ================= HEADERS ================= */
|
|
113
112
|
const orgHeaders = [
|
|
114
113
|
{ title: 'Organization Name', key: 'name' },
|
|
114
|
+
{ title: 'Email', key: 'email' },
|
|
115
115
|
{ title: 'Sites', key: 'sites' },
|
|
116
116
|
{ title: 'Plan Type', key: 'planType' },
|
|
117
117
|
{ title: 'Billing Cycle', key: 'billingCycle' },
|
|
@@ -143,9 +143,22 @@ const pageRange = computed(() => {
|
|
|
143
143
|
})
|
|
144
144
|
|
|
145
145
|
/* ================= HELPERS ================= */
|
|
146
|
-
function formatDate(date?: string) {
|
|
147
|
-
if (
|
|
148
|
-
|
|
146
|
+
function formatDate(date?: string | Date) {
|
|
147
|
+
if (date == null || date === '') return '-'
|
|
148
|
+
const d = typeof date === 'object' && date instanceof Date ? date : new Date(date as string)
|
|
149
|
+
if (Number.isNaN(d.getTime())) return '-'
|
|
150
|
+
return d.toLocaleDateString('en-US')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Subscription document không luôn có `paidType`; fallback theo type/description. */
|
|
154
|
+
function planTypeLabel(sub: Record<string, any>) {
|
|
155
|
+
if (!sub) return '-'
|
|
156
|
+
if (sub.paidType) return String(sub.paidType)
|
|
157
|
+
if (sub.type === 'organization') return 'Organization'
|
|
158
|
+
if (sub.type === 'affiliate') return 'Affiliate'
|
|
159
|
+
const desc = String(sub.description || '').trim()
|
|
160
|
+
if (desc) return desc.length > 48 ? `${desc.slice(0, 45)}…` : desc
|
|
161
|
+
return '-'
|
|
149
162
|
}
|
|
150
163
|
|
|
151
164
|
async function resolveRoleName(id: string) {
|
|
@@ -159,10 +172,11 @@ async function resolveRoleName(id: string) {
|
|
|
159
172
|
const limit = ref(10)
|
|
160
173
|
/* ================= FETCH ================= */
|
|
161
174
|
async function fetchActive() {
|
|
162
|
-
const res = await
|
|
175
|
+
const res = await getAllV2({
|
|
163
176
|
page: page.value,
|
|
164
177
|
search: search.value,
|
|
165
178
|
limit: limit.value,
|
|
179
|
+
status: 'active',
|
|
166
180
|
})
|
|
167
181
|
|
|
168
182
|
const orgs = res?.data?.items || res?.items || []
|
|
@@ -189,7 +203,7 @@ async function fetchActive() {
|
|
|
189
203
|
const subscription = subRes?.data || subRes
|
|
190
204
|
|
|
191
205
|
if (subscription) {
|
|
192
|
-
planType = subscription
|
|
206
|
+
planType = planTypeLabel(subscription)
|
|
193
207
|
billingCycle = subscription?.billingCycle || '-'
|
|
194
208
|
subscriptionStart = formatDate(subscription?.createdAt)
|
|
195
209
|
subscriptionEnd = formatDate(subscription?.nextBillingDate)
|
|
@@ -207,6 +221,7 @@ async function fetchActive() {
|
|
|
207
221
|
subscriptionStart,
|
|
208
222
|
subscriptionEnd,
|
|
209
223
|
status: org?.status || 'active',
|
|
224
|
+
actions: '—',
|
|
210
225
|
}
|
|
211
226
|
})
|
|
212
227
|
)
|
|
@@ -225,10 +240,11 @@ async function fetchActive() {
|
|
|
225
240
|
}
|
|
226
241
|
|
|
227
242
|
async function fetchSuspended() {
|
|
228
|
-
const res = await
|
|
243
|
+
const res = await getAllV2({
|
|
229
244
|
page: page.value,
|
|
230
245
|
search: search.value,
|
|
231
|
-
|
|
246
|
+
limit: limit.value,
|
|
247
|
+
status: 'suspended',
|
|
232
248
|
})
|
|
233
249
|
const orgs = res?.data?.items || res?.items || []
|
|
234
250
|
|
|
@@ -252,7 +268,7 @@ async function fetchSuspended() {
|
|
|
252
268
|
const subRes = await getSubscriptionByOrgId(org._id)
|
|
253
269
|
const subscription = subRes?.data || subRes
|
|
254
270
|
if (subscription) {
|
|
255
|
-
planType = subscription
|
|
271
|
+
planType = planTypeLabel(subscription)
|
|
256
272
|
billingCycle = subscription?.billingCycle || '-'
|
|
257
273
|
subscriptionStart = formatDate(subscription?.createdAt)
|
|
258
274
|
subscriptionEnd = formatDate(subscription?.nextBillingDate)
|
|
@@ -262,7 +278,7 @@ async function fetchSuspended() {
|
|
|
262
278
|
}
|
|
263
279
|
|
|
264
280
|
return {
|
|
265
|
-
_id: org._id
|
|
281
|
+
_id: `${org._id}-${page.value}`,
|
|
266
282
|
...org,
|
|
267
283
|
sites: `${sitesCount} Sites`,
|
|
268
284
|
planType,
|
|
@@ -270,17 +286,17 @@ async function fetchSuspended() {
|
|
|
270
286
|
subscriptionStart,
|
|
271
287
|
subscriptionEnd,
|
|
272
288
|
status: org?.status || 'suspended',
|
|
289
|
+
actions: '—',
|
|
273
290
|
}
|
|
274
291
|
})
|
|
275
292
|
)
|
|
276
293
|
|
|
294
|
+
items.value = []
|
|
295
|
+
await nextTick()
|
|
277
296
|
items.value = [...mapped]
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const match = res?.pageRange?.match(/of\s+(\d+)/i)
|
|
282
|
-
|
|
283
|
-
totalItems.value = match ? Number(match[1]) : mapped.length
|
|
297
|
+
pages.value = res?.pages || 1
|
|
298
|
+
const match = res?.pageRange?.match(/of\s+(\d+)/i)
|
|
299
|
+
totalItems.value = match ? Number(match[1]) : mapped.length
|
|
284
300
|
}
|
|
285
301
|
|
|
286
302
|
async function fetchPending() {
|
|
@@ -322,9 +338,7 @@ async function fetchData() {
|
|
|
322
338
|
await fetchActive()
|
|
323
339
|
}
|
|
324
340
|
else if (tab.value === 'suspended') {
|
|
325
|
-
|
|
326
|
-
pages.value = 1
|
|
327
|
-
totalItems.value = 0
|
|
341
|
+
await fetchSuspended()
|
|
328
342
|
}
|
|
329
343
|
else {
|
|
330
344
|
await fetchPending()
|
|
@@ -7,7 +7,7 @@
|
|
|
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="inputType" v-model="dateTime" />
|
|
10
|
+
<input ref="dateInput" :type="inputType" v-model="dateTime" :min="minValue" />
|
|
11
11
|
</div>
|
|
12
12
|
</div>
|
|
13
13
|
</template>
|
|
@@ -27,6 +27,10 @@ const prop = defineProps({
|
|
|
27
27
|
dateOnly: {
|
|
28
28
|
type: Boolean,
|
|
29
29
|
default: false
|
|
30
|
+
},
|
|
31
|
+
min: {
|
|
32
|
+
type: String,
|
|
33
|
+
default: undefined
|
|
30
34
|
}
|
|
31
35
|
})
|
|
32
36
|
|
|
@@ -36,6 +40,15 @@ const dateTimeUTC = defineModel<string | null>('utc', { default: null }) // UTC
|
|
|
36
40
|
|
|
37
41
|
const dateTimeFormattedReadOnly = ref<string | null>(null)
|
|
38
42
|
const inputType = computed(() => (prop.dateOnly ? 'date' : 'datetime-local'))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
const minValue = computed(() => {
|
|
46
|
+
if (!prop.min) return undefined
|
|
47
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(prop.min)) {
|
|
48
|
+
return prop.dateOnly ? prop.min : `${prop.min}T00:00`
|
|
49
|
+
}
|
|
50
|
+
return prop.min
|
|
51
|
+
})
|
|
39
52
|
const inputPlaceholder = computed(() => (
|
|
40
53
|
prop.placeholder || (prop.dateOnly ? 'MM/DD/YYYY' : 'DD/MM/YYYY, HH:MM AM/PM')
|
|
41
54
|
))
|