@7365admin1/layer-common 3.2.7 → 3.2.8-staging.201
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/assets/css/primitives.css +1113 -0
- package/assets/css/screens.css +1097 -0
- package/assets/css/shell.css +258 -0
- package/assets/css/tokens.css +236 -0
- package/components/AcceptDialog.vue +17 -18
- package/components/AccessCard/AvailableStats.vue +24 -16
- package/components/AccessCardAddForm.vue +42 -42
- package/components/AccessCardAssignToUnitForm.vue +13 -21
- package/components/AccessCardDeleteDialog.vue +41 -39
- package/components/AccessCardDetailsDialog.vue +125 -79
- package/components/AccessCardPreviewDialog.vue +58 -84
- package/components/AccessCardQrTagging.vue +60 -63
- package/components/AccessCardReplaceForm.vue +27 -23
- package/components/AccessManagement.vue +86 -85
- package/components/AddEqupmentForm.vue +18 -22
- package/components/AddPassKeyToVisitor.vue +95 -73
- package/components/AppButton.vue +62 -0
- package/components/AppCard.vue +20 -0
- package/components/AppDateField.vue +49 -0
- package/components/AppField.vue +76 -0
- package/components/AppKpiTile.vue +65 -0
- package/components/AppSegmented.vue +38 -0
- package/components/AppSelect.vue +247 -0
- package/components/AreaChecklistHistoryLogs.vue +51 -44
- package/components/AreaChecklistHistoryMain.vue +14 -7
- package/components/AreaFormDialog.vue +71 -59
- package/components/AreaMain.vue +73 -107
- package/components/AttachmentsViewer.vue +84 -22
- package/components/AttendanceCheckInOutDialog.vue +85 -88
- package/components/AttendanceDetailsDialog.vue +57 -63
- package/components/AttendanceMain.vue +4 -3
- package/components/AttendanceMapSearchDialog.vue +42 -44
- package/components/AttendanceSettingsDialog.vue +79 -63
- package/components/Avatar/Main.vue +54 -2
- package/components/BillingMain.vue +26 -30
- package/components/BtnUploadFile.vue +53 -11
- package/components/BuildingForm.vue +34 -27
- package/components/BuildingManagement/buildings.vue +39 -39
- package/components/BuildingManagement/units.vue +26 -42
- package/components/BuildingUnitFormAdd.vue +20 -23
- package/components/BuildingUnitFormEdit.vue +8 -4
- package/components/BulletinBoardForm.vue +19 -24
- package/components/BulletinBoardManagement.vue +49 -57
- package/components/BulletinBoardView.vue +9 -6
- package/components/BulletinExpirationChip.vue +13 -4
- package/components/Button/Close.vue +2 -2
- package/components/CameraForm.vue +21 -22
- package/components/CameraMain.vue +50 -49
- package/components/Card/DeleteConfirmation.vue +24 -22
- package/components/Card/MemberInfoSummary.vue +28 -3
- package/components/Card/Toggle.vue +15 -3
- package/components/CardToolbar.vue +37 -0
- package/components/Carousel.vue +56 -36
- package/components/Chat/Bubbles.vue +43 -14
- package/components/Chat/Information.vue +49 -32
- package/components/Chat/ListCard.vue +38 -9
- package/components/Chat/Message.vue +41 -15
- package/components/Chat/Navigation.vue +27 -15
- package/components/Chat/SkeletonLoader.vue +9 -2
- package/components/ChecklistItemRow.vue +25 -1
- package/components/CleaningScheduleMain.vue +44 -49
- package/components/ClientDetailForm.vue +81 -56
- package/components/ClientMain.vue +92 -82
- package/components/ConfirmDialog.vue +14 -8
- package/components/Container/Standard.vue +9 -7
- package/components/CreateSubsciptionPlan.vue +148 -89
- package/components/DashboardEmptyState.vue +48 -3
- package/components/DashboardMain.vue +924 -676
- package/components/DashboardPanel.vue +84 -21
- package/components/DeliveryCompany.vue +34 -20
- package/components/Dialog/DeleteConfirmation.vue +20 -21
- package/components/Dialog/ReplaceAutofillPrompt.vue +19 -26
- package/components/Dialog/ReusablePrompt.vue +30 -29
- package/components/Dialog/UpdateMoreAction.vue +62 -59
- package/components/DocumentForm.vue +14 -21
- package/components/DocumentManagement.vue +162 -108
- package/components/DocumentViewer.vue +29 -12
- package/components/DrawImage.vue +29 -21
- package/components/EntryPass/QrTemplatePreview.vue +27 -16
- package/components/EntryPassInformation.vue +40 -10
- package/components/EntryPassMain.vue +49 -27
- package/components/EquipmentItemMain.vue +84 -97
- package/components/EquipmentManagementMain.vue +81 -103
- package/components/Facility/BookingSetup.vue +43 -52
- package/components/Feedback/Form.vue +26 -12
- package/components/FeedbackMain.vue +35 -49
- package/components/FileInput.vue +75 -58
- package/components/FileInputWithList.vue +59 -12
- package/components/FormDialog.vue +60 -21
- package/components/HidAccessLogDashboard.vue +512 -490
- package/components/HidCameraCaptureDialog.vue +216 -0
- package/components/HidEnabledGate.vue +67 -9
- package/components/HidIntercomManagement.vue +1322 -653
- package/components/HidQrCodeConfiguration.vue +1030 -258
- package/components/HidReaderForm.vue +142 -59
- package/components/HidReaderManagement.vue +211 -323
- package/components/HidServiceSettingsPanel.vue +44 -13
- package/components/HidUserEnrollment.vue +1185 -412
- package/components/HygieneUpdateMoreAction.vue +44 -44
- package/components/IncidentReport/Authorities.vue +30 -18
- package/components/IncidentReport/IncidentInformation.vue +10 -10
- package/components/IncidentReport/IncidentInformationDownload.vue +3 -4
- package/components/IncidentReport/affectedEntities.vue +41 -20
- package/components/Input/Date.vue +3 -0
- package/components/Input/DatePicker.vue +1 -2
- package/components/Input/DateTimePicker.vue +1 -1
- package/components/Input/File.vue +55 -10
- package/components/Input/FileV2.vue +39 -6
- package/components/Input/InputPhoneNumberV2.vue +2 -1
- package/components/Input/ListGroupSelection.vue +56 -8
- package/components/Input/NRICNumber.vue +1 -0
- package/components/Input/NewDate.vue +1 -0
- package/components/Input/Number.vue +1 -0
- package/components/Input/Password.vue +1 -0
- package/components/Input/PhoneNumber.vue +21 -1
- package/components/Input/VehicleNumber.vue +1 -0
- package/components/InputLabel.vue +2 -2
- package/components/InvitationClientForm.vue +21 -19
- package/components/InvitationForm.vue +15 -21
- package/components/InvitationMain.vue +46 -61
- package/components/Layout/Header.vue +263 -36
- package/components/Layout/NavigationDrawer.vue +300 -30
- package/components/ListItem.vue +33 -3
- package/components/LocalPagination.vue +27 -3
- package/components/ManageChecklistMain.vue +136 -86
- package/components/MemberInformation.vue +32 -16
- package/components/MemberMain.vue +113 -93
- package/components/MyAttendanceMain.vue +3 -5
- package/components/NavigationItem.vue +83 -3
- package/components/Nfc/NFCPatrolReportMain.vue +47 -33
- package/components/Nfc/NFCPatrolRouteForm.vue +18 -20
- package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
- package/components/Nfc/NFCPatrolSettings.vue +14 -4
- package/components/Nfc/NFCTagForm.vue +22 -8
- package/components/Nfc/NFCTagMain.vue +41 -43
- package/components/Nfc/PatrolReport/DailyReportTab.vue +8 -1
- package/components/Nfc/PatrolReport/MonthlyReportTab.vue +8 -1
- package/components/Nfc/PatrolReport/MonthlyReportTable.vue +5 -3
- package/components/Nfc/PatrolReport/PatrolActivityTable.vue +26 -6
- package/components/Nfc/PatrolReport/PatrolReportTab.vue +8 -0
- package/components/Nfc/PatrolReport/PatrolRouteSummary.vue +41 -9
- package/components/Nfc/PatrolReport/RemarksDialog.vue +39 -9
- package/components/Nfc/PatrolReport/ReportEmptyState.vue +29 -9
- package/components/Nfc/PatrolReport/ReportFilters.vue +19 -10
- package/components/Nfc/PatrolReport/ReportLocationHeader.vue +32 -6
- package/components/Nfc/PatrolReport/SummaryReportTable.vue +28 -6
- package/components/NotificationSettings.vue +373 -0
- package/components/NumberSettingField.vue +9 -2
- package/components/OnlineFormConfigurationForm.vue +725 -237
- package/components/OnlineFormFill.vue +213 -106
- package/components/OnlineFormRenderPreview.vue +25 -30
- package/components/OnlineFormsConfiguration.vue +113 -109
- package/components/OrgViewDialog.vue +34 -16
- package/components/OvernightParkingAvailability.vue +49 -26
- package/components/OvernightParkingManagement.vue +78 -18
- package/components/PageHeader.vue +43 -0
- package/components/PassInformation.vue +109 -53
- package/components/PasswordConfirmation.vue +44 -26
- package/components/PeopleForm.vue +41 -20
- package/components/PhotoUpload.vue +110 -48
- package/components/PlaceholderComponent.vue +35 -19
- package/components/PlanActionsMenu.vue +12 -2
- package/components/PlanStatusBadge.vue +7 -8
- package/components/PlateNumberDisplay.vue +9 -6
- package/components/QrTemplate/PrintDialog.vue +66 -43
- package/components/RolePermissionFormCreate.vue +104 -79
- package/components/RolePermissionFormPreviewUpdate.vue +111 -109
- package/components/RolePermissionMain.vue +183 -81
- package/components/ScanVisitorQRCode.vue +99 -35
- package/components/ScheduleAreaMain.vue +34 -59
- package/components/ScheduleTaskForm.vue +61 -42
- package/components/ScheduleTaskMain.vue +56 -69
- package/components/SearchVehicleNumberUser.vue +39 -37
- package/components/ServiceProviderFormCreate.vue +29 -20
- package/components/ServiceProviderInvitationPrompt.vue +155 -0
- package/components/ServiceProviderMain.vue +601 -432
- package/components/Signature.vue +40 -34
- package/components/SignaturePad.vue +5 -29
- package/components/SiteSettings.vue +41 -11
- package/components/SiteSettingsWorkOrder.vue +61 -30
- package/components/SlideCardGroup.vue +35 -11
- package/components/Snackbar.vue +36 -3
- package/components/SpecificAttr.vue +18 -8
- package/components/StatusChip.vue +156 -0
- package/components/StockCard.vue +17 -12
- package/components/SubscriptionPlanTable.vue +207 -140
- package/components/SwitchContext.vue +201 -9
- package/components/TableHygiene.vue +36 -46
- package/components/TableList.vue +10 -10
- package/components/TableListSecondary.vue +105 -54
- package/components/TableMain.vue +251 -78
- package/components/TableV3.vue +61 -70
- package/components/TiptapEditor.vue +33 -40
- package/components/TiptapEditorQr.vue +5 -2
- package/components/Tooltip/Info.vue +69 -24
- package/components/UnitMain.vue +40 -60
- package/components/UnitPersonCard.vue +42 -13
- package/components/VehicleAddSelection.vue +48 -32
- package/components/VehicleForm.vue +112 -129
- package/components/VehicleManagement.vue +26 -12
- package/components/VehicleUpdateMoreAction.vue +59 -46
- package/components/VisitorDataFromScannedQRCodeForm.vue +90 -65
- package/components/VisitorForm.vue +774 -210
- package/components/VisitorFormSelection.vue +40 -37
- package/components/VisitorManagement.vue +239 -82
- package/components/VisitorPassKeyQRScanner.vue +65 -18
- package/components/VisitorSearchResultSummary.vue +60 -16
- package/components/VisitorSocketPopUp.vue +22 -14
- package/components/VisitorsReportPreview.vue +44 -29
- package/components/WorkOrder/Create.vue +26 -17
- package/components/WorkOrder/ListView.vue +38 -7
- package/components/WorkOrder/Main.vue +62 -59
- package/composables/useFeedback.ts +3 -3
- package/composables/useHidAmico.ts +269 -0
- package/composables/useHidNavigation.ts +2 -9
- package/composables/useMember.ts +3 -3
- package/composables/useNFCPatrolDailyReport.ts +13 -0
- package/composables/useNFCPatrolMonthlyReport.ts +22 -56
- package/composables/useNFCPatrolReport.ts +13 -0
- package/composables/useNotificationPreference.ts +87 -0
- package/composables/useOnlineFormPages.ts +2 -0
- package/composables/useRole.ts +22 -0
- package/composables/useServiceProvider.ts +3 -3
- package/composables/useServiceProviderInvitation.ts +145 -0
- package/composables/useSipWebPhone.ts +336 -0
- package/composables/useSubscriptionPlan.ts +62 -0
- package/composables/useThemePreference.ts +63 -0
- package/composables/useVisitor.ts +6 -1
- package/composables/useWebUsb.ts +127 -37
- package/composables/useWorkOrder.ts +17 -3
- package/error.vue +30 -6
- package/nuxt.config.ts +36 -0
- package/package.json +25 -4
- package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -2
- package/pages/[org]/[site]/access-mgmt/{identity-mapping → hid-cards}/index.vue +2 -2
- package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
- package/pages/notification-settings.vue +19 -0
- package/pages/payment-method-linked.vue +34 -10
- package/pages/require-customer.vue +48 -10
- package/pages/require-organization-membership.vue +32 -8
- package/pages/unauthorized.vue +29 -4
- package/plugins/secure-member.client.ts +21 -56
- package/plugins/vuetify.ts +69 -2
- package/types/nfc-patrol-report.ts +3 -2
- package/types/site.d.ts +71 -0
- package/types/subscription.ts +18 -11
- package/utils/breadcrumb.test.ts +108 -0
- package/utils/breadcrumb.ts +82 -0
- package/utils/dashboard.test.ts +118 -0
- package/utils/dashboard.ts +53 -0
- package/utils/data.test.ts +95 -0
- package/utils/data.ts +40 -2
- package/utils/hid-discover.test.ts +102 -0
- package/utils/list-page.test.ts +60 -0
- package/utils/list-page.ts +38 -0
- package/utils/materialSymbols.ts +363 -0
- package/utils/nav-sections.test.ts +190 -0
- package/utils/nav-sections.ts +151 -0
- package/utils/nfc-patrol-report.test.ts +130 -0
- package/utils/nfc-patrol-report.ts +101 -0
- package/utils/role.test.ts +69 -0
- package/utils/role.ts +10 -0
- package/utils/route-name.test.ts +85 -0
- package/utils/route-name.ts +24 -0
- package/utils/status.test.ts +110 -0
- package/utils/status.ts +135 -0
- package/utils/table-card.test.ts +84 -0
- package/utils/theme-aa-ledger.ts +148 -0
- package/utils/theme.test.ts +485 -7
- package/utils/theme.ts +295 -110
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -11
- package/.editorconfig +0 -12
- package/.github/workflows/main.yml +0 -17
- package/.github/workflows/publish-staging.yml +0 -47
- package/.github/workflows/publish.yml +0 -39
- package/PUBLISHING.md +0 -269
- package/components/HidIdentityMapping.vue +0 -975
- package/components/ScheduleTaskAreaFormDialog.vue +0 -144
- package/components/ScheduleTaskAreaUpdateMoreAction.vue +0 -109
- package/components/ScheduleTastTicketMain.vue +0 -182
- package/components/TableWithButton.vue +0 -94
- package/test/visitor-socket.test.mjs +0 -36
|
@@ -1,87 +1,86 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-container fluid class="pa-6">
|
|
3
|
-
<!--
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
</
|
|
17
|
-
</
|
|
3
|
+
<!--
|
|
4
|
+
THE DESIGN'S TITLE ROW. Drawn by the shared `PageHeader` at 22px/800 so
|
|
5
|
+
this screen's heading matches every other screen's.
|
|
6
|
+
-->
|
|
7
|
+
<PageHeader title="Subscription Plans">
|
|
8
|
+
<template #actions>
|
|
9
|
+
<v-btn
|
|
10
|
+
class="text-none screen-btn-primary"
|
|
11
|
+
variant="flat"
|
|
12
|
+
@click="openCreateModal"
|
|
13
|
+
>
|
|
14
|
+
Create Plan
|
|
15
|
+
</v-btn>
|
|
16
|
+
</template>
|
|
17
|
+
</PageHeader>
|
|
18
18
|
|
|
19
19
|
<!-- Search & Filters Bar -->
|
|
20
20
|
<v-row class="mb-2" density="compact">
|
|
21
21
|
<!-- Search Input -->
|
|
22
22
|
<v-col cols="12" sm="4" md="3">
|
|
23
|
-
<v-
|
|
24
|
-
v-model="search"
|
|
25
|
-
placeholder="Search Plan"
|
|
26
|
-
prepend-inner-icon="mdi-magnify"
|
|
27
|
-
variant="outlined"
|
|
28
|
-
density="compact"
|
|
29
|
-
hide-details
|
|
30
|
-
clearable
|
|
31
|
-
/>
|
|
23
|
+
<AppField v-model="search" search placeholder="Search Plan" />
|
|
32
24
|
</v-col>
|
|
33
25
|
|
|
34
26
|
<!-- Filter by Category -->
|
|
35
27
|
<v-col cols="12" sm="4" md="3">
|
|
36
|
-
<
|
|
28
|
+
<AppSelect
|
|
37
29
|
v-model="categoryFilter"
|
|
38
30
|
:items="categoryOptions"
|
|
39
31
|
label="Filter by category"
|
|
40
|
-
variant="outlined"
|
|
41
|
-
density="compact"
|
|
42
|
-
hide-details
|
|
43
32
|
/>
|
|
44
33
|
</v-col>
|
|
45
34
|
|
|
46
35
|
<!-- Filter by Billing Cycle -->
|
|
47
36
|
<v-col cols="12" sm="4" md="3">
|
|
48
|
-
<
|
|
37
|
+
<AppSelect
|
|
49
38
|
v-model="billingFilter"
|
|
50
39
|
:items="billingOptions"
|
|
51
40
|
label="Filter by billing cycle"
|
|
52
|
-
variant="outlined"
|
|
53
|
-
density="compact"
|
|
54
|
-
hide-details
|
|
55
41
|
/>
|
|
56
42
|
</v-col>
|
|
57
43
|
</v-row>
|
|
58
44
|
|
|
59
45
|
<!-- Main Table Card -->
|
|
60
|
-
<
|
|
61
|
-
<!--
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
46
|
+
<AppCard class="table-card mt-4">
|
|
47
|
+
<!--
|
|
48
|
+
The tabs and the refresh/pagination are the card's own toolbar row,
|
|
49
|
+
which is where the design draws tabs - it follows the theme because
|
|
50
|
+
it is tokenised, unlike the old hard-coded `bg-grey-lighten-4` strip.
|
|
51
|
+
-->
|
|
52
|
+
<CardToolbar :count="pageRange">
|
|
53
|
+
<template #tabs>
|
|
54
|
+
<v-tabs v-model="activeTab" color="primary" density="compact">
|
|
55
|
+
<v-tab value="active" class="text-none font-weight-bold">Active</v-tab>
|
|
56
|
+
<v-tab value="deactive" class="text-none font-weight-bold">Deactivated</v-tab>
|
|
57
|
+
</v-tabs>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<template #right>
|
|
69
61
|
<v-btn
|
|
70
62
|
icon="mdi-refresh"
|
|
71
63
|
variant="text"
|
|
72
64
|
density="comfortable"
|
|
73
|
-
|
|
65
|
+
aria-label="Refresh"
|
|
74
66
|
@click="refresh"
|
|
75
67
|
/>
|
|
76
|
-
|
|
77
|
-
|
|
68
|
+
<local-pagination
|
|
69
|
+
v-model="page"
|
|
70
|
+
:length="pages"
|
|
71
|
+
@update:value="getSubscriptionPlans"
|
|
72
|
+
/>
|
|
73
|
+
</template>
|
|
74
|
+
</CardToolbar>
|
|
78
75
|
|
|
79
76
|
<!-- Table View -->
|
|
80
77
|
<v-data-table
|
|
81
78
|
:headers="headers"
|
|
82
|
-
:items="
|
|
83
|
-
:
|
|
84
|
-
|
|
79
|
+
:items="items"
|
|
80
|
+
:loading="loading"
|
|
81
|
+
item-value="id"
|
|
82
|
+
items-per-page="20"
|
|
83
|
+
hide-default-footer
|
|
85
84
|
class="elevation-0"
|
|
86
85
|
>
|
|
87
86
|
<!-- Plan Name -->
|
|
@@ -91,7 +90,7 @@
|
|
|
91
90
|
|
|
92
91
|
<!-- Plan Type -->
|
|
93
92
|
<template #item.planType="{ item }">
|
|
94
|
-
{{ item.planType ===
|
|
93
|
+
{{ item.planType === "free_bundle" ? "Free" : "Paid" }}
|
|
95
94
|
</template>
|
|
96
95
|
|
|
97
96
|
<!-- Billing Cycle -->
|
|
@@ -107,22 +106,22 @@
|
|
|
107
106
|
</template>
|
|
108
107
|
|
|
109
108
|
<!-- Applications Badge -->
|
|
109
|
+
<!-- A count, not a status word, so it takes the design's neutral tag
|
|
110
|
+
shape (no leading dot) rather than a colour it has not earned. -->
|
|
110
111
|
<template #item.applicationIds="{ item }">
|
|
111
|
-
<
|
|
112
|
-
{{ item.applicationIds.length }} Application{{
|
|
113
|
-
|
|
112
|
+
<StatusChip tone="neutral" :dot="false">
|
|
113
|
+
{{ item.applicationIds.length }} Application{{
|
|
114
|
+
item.applicationIds.length > 1 ? "s" : ""
|
|
115
|
+
}}
|
|
116
|
+
</StatusChip>
|
|
114
117
|
</template>
|
|
115
118
|
|
|
116
119
|
<!-- Status Badge -->
|
|
120
|
+
<!-- The shared word -> tone map: "Active" is `ok` (was Vuetify
|
|
121
|
+
`success`) and "Deactivated" is unmapped, so it comes out
|
|
122
|
+
neutral - which is the grey it already was. -->
|
|
117
123
|
<template #item.status="{ item }">
|
|
118
|
-
<
|
|
119
|
-
size="small"
|
|
120
|
-
:color="item.status === 'active' ? 'success' : 'grey'"
|
|
121
|
-
variant="flat"
|
|
122
|
-
class="text-caption font-weight-bold"
|
|
123
|
-
>
|
|
124
|
-
{{ item.status === 'active' ? 'Active' : 'Deactive' }}
|
|
125
|
-
</v-chip>
|
|
124
|
+
<StatusChip :status="item.status === 'active' ? 'Active' : 'Deactivated'" />
|
|
126
125
|
</template>
|
|
127
126
|
|
|
128
127
|
<!-- Actions -->
|
|
@@ -153,9 +152,15 @@
|
|
|
153
152
|
|
|
154
153
|
<!-- Toggle Status (Deactivate / Activate) -->
|
|
155
154
|
<v-list-item
|
|
156
|
-
:prepend-icon="
|
|
155
|
+
:prepend-icon="
|
|
156
|
+
item.status === 'active'
|
|
157
|
+
? 'mdi-trash-can-outline'
|
|
158
|
+
: 'mdi-check-circle-outline'
|
|
159
|
+
"
|
|
157
160
|
:title="item.status === 'active' ? 'Deactivate' : 'Activate'"
|
|
158
|
-
:class="
|
|
161
|
+
:class="
|
|
162
|
+
item.status === 'active' ? 'text-error' : 'text-success'
|
|
163
|
+
"
|
|
159
164
|
value="toggle-status"
|
|
160
165
|
class="text-body-2"
|
|
161
166
|
@click="togglePlanStatus(item)"
|
|
@@ -166,13 +171,15 @@
|
|
|
166
171
|
</template>
|
|
167
172
|
|
|
168
173
|
<!-- Empty State -->
|
|
174
|
+
<!-- Same sentence, in the shared empty block the design draws it in. -->
|
|
169
175
|
<template #no-data>
|
|
170
|
-
<div class="
|
|
171
|
-
|
|
176
|
+
<div class="table-card__empty">
|
|
177
|
+
<v-icon icon="mdi-magnify-close" size="32" />
|
|
178
|
+
<span>No plans found.</span>
|
|
172
179
|
</div>
|
|
173
180
|
</template>
|
|
174
181
|
</v-data-table>
|
|
175
|
-
</
|
|
182
|
+
</AppCard>
|
|
176
183
|
|
|
177
184
|
<!-- Create / Edit Dialog -->
|
|
178
185
|
<v-dialog v-model="showModal" max-width="600px" persistent>
|
|
@@ -184,12 +191,15 @@
|
|
|
184
191
|
/>
|
|
185
192
|
</v-card>
|
|
186
193
|
</v-dialog>
|
|
194
|
+
|
|
195
|
+
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
187
196
|
</v-container>
|
|
188
197
|
</template>
|
|
189
198
|
|
|
190
199
|
<script setup lang="ts">
|
|
191
|
-
import { ref,
|
|
200
|
+
import { ref, computed, watch, watchEffect } from "vue";
|
|
192
201
|
import CreateSubscriptionPlan from "./CreateSubsciptionPlan.vue";
|
|
202
|
+
import useSubscriptionPlan from "../composables/useSubscriptionPlan";
|
|
193
203
|
|
|
194
204
|
import type {
|
|
195
205
|
SubscriptionPlan,
|
|
@@ -205,7 +215,12 @@ const headers = [
|
|
|
205
215
|
{ title: "Price", key: "price", align: "start" as const },
|
|
206
216
|
{ title: "Applications", key: "applicationIds", align: "center" as const },
|
|
207
217
|
{ title: "Status", key: "status", align: "center" as const },
|
|
208
|
-
{
|
|
218
|
+
{
|
|
219
|
+
title: "Actions",
|
|
220
|
+
key: "actions",
|
|
221
|
+
align: "center" as const,
|
|
222
|
+
sortable: false,
|
|
223
|
+
},
|
|
209
224
|
];
|
|
210
225
|
|
|
211
226
|
// Select Options
|
|
@@ -222,30 +237,12 @@ const billingOptions = [
|
|
|
222
237
|
{ title: "Yearly", value: "annually" },
|
|
223
238
|
];
|
|
224
239
|
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
planType: "paid_bundle",
|
|
232
|
-
billingCycle: "monthly",
|
|
233
|
-
price: 299,
|
|
234
|
-
applicationIds: ["crm"],
|
|
235
|
-
status: "active",
|
|
236
|
-
},
|
|
237
|
-
{
|
|
238
|
-
id: "2",
|
|
239
|
-
name: "Premium",
|
|
240
|
-
description: "",
|
|
241
|
-
maxSeats: 25,
|
|
242
|
-
planType: "paid_bundle",
|
|
243
|
-
billingCycle: "annually",
|
|
244
|
-
price: 299,
|
|
245
|
-
applicationIds: ["crm", "hrm"],
|
|
246
|
-
status: "active",
|
|
247
|
-
},
|
|
248
|
-
]);
|
|
240
|
+
const {
|
|
241
|
+
getAll: _getAll,
|
|
242
|
+
add: _add,
|
|
243
|
+
updateById: _updateById,
|
|
244
|
+
updateStatusById: _updateStatusById,
|
|
245
|
+
} = useSubscriptionPlan();
|
|
249
246
|
|
|
250
247
|
const search = ref("");
|
|
251
248
|
const categoryFilter = ref("all");
|
|
@@ -253,38 +250,79 @@ const billingFilter = ref<"all" | BillingCycle>("all");
|
|
|
253
250
|
const activeTab = ref<"active" | "deactive">("active");
|
|
254
251
|
|
|
255
252
|
const page = ref(1);
|
|
256
|
-
const
|
|
253
|
+
const pages = ref(0);
|
|
254
|
+
const pageRange = ref("-- - -- of --");
|
|
255
|
+
|
|
256
|
+
const message = ref("");
|
|
257
|
+
const messageSnackbar = ref(false);
|
|
258
|
+
const messageColor = ref("");
|
|
257
259
|
|
|
258
260
|
const showModal = ref(false);
|
|
259
261
|
const editingPlan = ref<SubscriptionPlan | null>(null);
|
|
262
|
+
const items = ref<SubscriptionPlan[]>([]);
|
|
263
|
+
|
|
264
|
+
function normalizePlan(raw: Record<string, any>): SubscriptionPlan {
|
|
265
|
+
return {
|
|
266
|
+
id: String(raw._id ?? raw.id),
|
|
267
|
+
name: raw.name,
|
|
268
|
+
description: raw.description ?? "",
|
|
269
|
+
maxSeats: raw.maxSeats,
|
|
270
|
+
planType: raw.planType,
|
|
271
|
+
billingCycle: raw.billingCycle ?? null,
|
|
272
|
+
price: raw.price ?? null,
|
|
273
|
+
applicationIds: (raw.applications ?? []).map(
|
|
274
|
+
(app: Record<string, any>) => app.applicationId
|
|
275
|
+
),
|
|
276
|
+
status: raw.status === "active" ? "active" : "deactive",
|
|
277
|
+
};
|
|
278
|
+
}
|
|
260
279
|
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
280
|
+
const {
|
|
281
|
+
data: getSubscriptionPlansReq,
|
|
282
|
+
refresh: getSubscriptionPlans,
|
|
283
|
+
status: getAllReqStatus,
|
|
284
|
+
} = useLazyAsyncData("get-all-subscription-plans", () =>
|
|
285
|
+
_getAll({
|
|
286
|
+
page: page.value,
|
|
287
|
+
search: search.value,
|
|
288
|
+
status: activeTab.value,
|
|
289
|
+
planType: categoryFilter.value === "all" ? "" : categoryFilter.value,
|
|
290
|
+
billingCycle: billingFilter.value === "all" ? "" : billingFilter.value,
|
|
291
|
+
})
|
|
292
|
+
);
|
|
264
293
|
|
|
265
|
-
|
|
266
|
-
search.value &&
|
|
267
|
-
!plan.name.toLowerCase().includes(search.value.toLowerCase())
|
|
268
|
-
) {
|
|
269
|
-
return false;
|
|
270
|
-
}
|
|
294
|
+
const loading = computed(() => getAllReqStatus.value === "pending");
|
|
271
295
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
296
|
+
watchEffect(() => {
|
|
297
|
+
if (getSubscriptionPlansReq.value) {
|
|
298
|
+
items.value = (getSubscriptionPlansReq.value.items ?? []).map(
|
|
299
|
+
normalizePlan
|
|
300
|
+
);
|
|
301
|
+
pages.value = getSubscriptionPlansReq.value.pages;
|
|
302
|
+
pageRange.value = getSubscriptionPlansReq.value.pageRange;
|
|
303
|
+
}
|
|
304
|
+
});
|
|
279
305
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
);
|
|
306
|
+
let searchDebounce: ReturnType<typeof setTimeout> | null = null;
|
|
307
|
+
watch(search, () => {
|
|
308
|
+
if (searchDebounce) clearTimeout(searchDebounce);
|
|
309
|
+
searchDebounce = setTimeout(() => {
|
|
310
|
+
page.value = 1;
|
|
311
|
+
getSubscriptionPlans();
|
|
312
|
+
}, 400);
|
|
313
|
+
});
|
|
283
314
|
|
|
284
|
-
watch([
|
|
315
|
+
watch([categoryFilter, billingFilter, activeTab], () => {
|
|
285
316
|
page.value = 1;
|
|
317
|
+
getSubscriptionPlans();
|
|
286
318
|
});
|
|
287
319
|
|
|
320
|
+
function showMessage(msg: string, color: string) {
|
|
321
|
+
message.value = msg;
|
|
322
|
+
messageColor.value = color;
|
|
323
|
+
messageSnackbar.value = true;
|
|
324
|
+
}
|
|
325
|
+
|
|
288
326
|
function formatBillingCycle(cycle?: string) {
|
|
289
327
|
if (!cycle) return "—";
|
|
290
328
|
if (cycle === "annually" || cycle === "yearly") return "Yearly";
|
|
@@ -303,6 +341,7 @@ function refresh() {
|
|
|
303
341
|
search.value = "";
|
|
304
342
|
categoryFilter.value = "all";
|
|
305
343
|
billingFilter.value = "all";
|
|
344
|
+
getSubscriptionPlans();
|
|
306
345
|
}
|
|
307
346
|
|
|
308
347
|
function openCreateModal() {
|
|
@@ -320,43 +359,71 @@ function closeModal() {
|
|
|
320
359
|
editingPlan.value = null;
|
|
321
360
|
}
|
|
322
361
|
|
|
323
|
-
function
|
|
324
|
-
|
|
325
|
-
payload.
|
|
326
|
-
|
|
327
|
-
: payload.
|
|
362
|
+
function buildApplications(payload: PlanFormState) {
|
|
363
|
+
if (payload.planType === "custom_apps") {
|
|
364
|
+
return payload.customApps.map((applicationId) => ({
|
|
365
|
+
applicationId,
|
|
366
|
+
price: payload.customAppPrices[applicationId] ?? 0,
|
|
367
|
+
}));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return payload.bundleApps.map((applicationId) => ({ applicationId }));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async function handleSubmit(payload: PlanFormState) {
|
|
374
|
+
const applications = buildApplications(payload);
|
|
328
375
|
|
|
329
376
|
const price =
|
|
330
377
|
payload.planType === "custom_apps"
|
|
331
|
-
?
|
|
378
|
+
? payload.customApps.reduce(
|
|
379
|
+
(total, value) => total + (payload.customAppPrices[value] ?? 0),
|
|
380
|
+
0
|
|
381
|
+
)
|
|
332
382
|
: payload.planType === "paid_bundle"
|
|
333
383
|
? payload.bundlePrice
|
|
334
384
|
: null;
|
|
335
385
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
id
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
386
|
+
const body = {
|
|
387
|
+
name: payload.name,
|
|
388
|
+
description: payload.description,
|
|
389
|
+
maxSeats: payload.maxSeats,
|
|
390
|
+
planType: payload.planType,
|
|
391
|
+
billingCycle: payload.billingCycle,
|
|
392
|
+
price,
|
|
393
|
+
applications,
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
try {
|
|
397
|
+
if (editingPlan.value) {
|
|
398
|
+
await _updateById(editingPlan.value.id, body);
|
|
399
|
+
showMessage("Subscription plan updated successfully!", "success");
|
|
400
|
+
} else {
|
|
401
|
+
await _add(body);
|
|
402
|
+
showMessage("Subscription plan created successfully!", "success");
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
await getSubscriptionPlans();
|
|
406
|
+
closeModal();
|
|
407
|
+
} catch (error: any) {
|
|
408
|
+
showMessage(
|
|
409
|
+
error?.response?._data?.message || "Failed to save subscription plan.",
|
|
410
|
+
"error"
|
|
411
|
+
);
|
|
358
412
|
}
|
|
413
|
+
}
|
|
359
414
|
|
|
360
|
-
|
|
415
|
+
async function togglePlanStatus(plan: SubscriptionPlan) {
|
|
416
|
+
const nextStatus = plan.status === "active" ? "deactive" : "active";
|
|
417
|
+
|
|
418
|
+
try {
|
|
419
|
+
await _updateStatusById(plan.id, nextStatus);
|
|
420
|
+
await getSubscriptionPlans();
|
|
421
|
+
showMessage("Subscription plan status updated successfully!", "success");
|
|
422
|
+
} catch (error: any) {
|
|
423
|
+
showMessage(
|
|
424
|
+
error?.response?._data?.message || "Failed to update plan status.",
|
|
425
|
+
"error"
|
|
426
|
+
);
|
|
427
|
+
}
|
|
361
428
|
}
|
|
362
429
|
</script>
|