@7365admin1/layer-common 3.2.6 → 3.2.8-staging.200

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 (284) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/assets/css/primitives.css +1113 -0
  3. package/assets/css/screens.css +1097 -0
  4. package/assets/css/shell.css +258 -0
  5. package/assets/css/tokens.css +236 -0
  6. package/components/AcceptDialog.vue +17 -18
  7. package/components/AccessCard/AvailableStats.vue +24 -16
  8. package/components/AccessCardAddForm.vue +42 -42
  9. package/components/AccessCardAssignToUnitForm.vue +13 -21
  10. package/components/AccessCardDeleteDialog.vue +41 -39
  11. package/components/AccessCardDetailsDialog.vue +125 -79
  12. package/components/AccessCardPreviewDialog.vue +58 -84
  13. package/components/AccessCardQrTagging.vue +60 -63
  14. package/components/AccessCardReplaceForm.vue +27 -23
  15. package/components/AccessManagement.vue +86 -85
  16. package/components/AddEqupmentForm.vue +18 -22
  17. package/components/AddPassKeyToVisitor.vue +95 -73
  18. package/components/AppButton.vue +62 -0
  19. package/components/AppCard.vue +20 -0
  20. package/components/AppDateField.vue +49 -0
  21. package/components/AppField.vue +76 -0
  22. package/components/AppKpiTile.vue +65 -0
  23. package/components/AppSegmented.vue +38 -0
  24. package/components/AppSelect.vue +247 -0
  25. package/components/AreaChecklistHistoryLogs.vue +51 -44
  26. package/components/AreaChecklistHistoryMain.vue +14 -7
  27. package/components/AreaFormDialog.vue +71 -59
  28. package/components/AreaMain.vue +73 -107
  29. package/components/AttachmentsViewer.vue +84 -22
  30. package/components/AttendanceCheckInOutDialog.vue +85 -88
  31. package/components/AttendanceDetailsDialog.vue +57 -63
  32. package/components/AttendanceMain.vue +4 -3
  33. package/components/AttendanceMapSearchDialog.vue +42 -44
  34. package/components/AttendanceSettingsDialog.vue +79 -63
  35. package/components/Avatar/Main.vue +54 -2
  36. package/components/BillingMain.vue +26 -30
  37. package/components/BtnUploadFile.vue +53 -11
  38. package/components/BuildingForm.vue +34 -27
  39. package/components/BuildingManagement/buildings.vue +39 -39
  40. package/components/BuildingManagement/units.vue +26 -42
  41. package/components/BuildingUnitFormAdd.vue +20 -23
  42. package/components/BuildingUnitFormEdit.vue +8 -4
  43. package/components/BulletinBoardForm.vue +19 -24
  44. package/components/BulletinBoardManagement.vue +49 -57
  45. package/components/BulletinBoardView.vue +9 -6
  46. package/components/BulletinExpirationChip.vue +13 -4
  47. package/components/Button/Close.vue +2 -2
  48. package/components/CameraForm.vue +21 -22
  49. package/components/CameraMain.vue +50 -49
  50. package/components/Card/DeleteConfirmation.vue +24 -22
  51. package/components/Card/MemberInfoSummary.vue +28 -3
  52. package/components/Card/Toggle.vue +15 -3
  53. package/components/CardToolbar.vue +37 -0
  54. package/components/Carousel.vue +56 -36
  55. package/components/Chat/Bubbles.vue +43 -14
  56. package/components/Chat/Information.vue +49 -32
  57. package/components/Chat/ListCard.vue +38 -9
  58. package/components/Chat/Message.vue +41 -15
  59. package/components/Chat/Navigation.vue +27 -15
  60. package/components/Chat/SkeletonLoader.vue +9 -2
  61. package/components/ChecklistItemRow.vue +25 -1
  62. package/components/CleaningScheduleMain.vue +44 -49
  63. package/components/ClientDetailForm.vue +81 -56
  64. package/components/ClientMain.vue +92 -82
  65. package/components/ConfirmDialog.vue +14 -8
  66. package/components/Container/Standard.vue +9 -7
  67. package/components/CreateSubsciptionPlan.vue +148 -89
  68. package/components/DashboardEmptyState.vue +48 -3
  69. package/components/DashboardMain.vue +924 -676
  70. package/components/DashboardPanel.vue +84 -21
  71. package/components/DeliveryCompany.vue +34 -20
  72. package/components/Dialog/DeleteConfirmation.vue +20 -21
  73. package/components/Dialog/ReplaceAutofillPrompt.vue +19 -26
  74. package/components/Dialog/ReusablePrompt.vue +30 -29
  75. package/components/Dialog/UpdateMoreAction.vue +62 -59
  76. package/components/DocumentForm.vue +14 -21
  77. package/components/DocumentManagement.vue +162 -108
  78. package/components/DocumentViewer.vue +29 -12
  79. package/components/DrawImage.vue +29 -21
  80. package/components/EntryPass/QrTemplatePreview.vue +27 -16
  81. package/components/EntryPassInformation.vue +40 -10
  82. package/components/EntryPassMain.vue +49 -27
  83. package/components/EquipmentItemMain.vue +84 -97
  84. package/components/EquipmentManagementMain.vue +81 -103
  85. package/components/Facility/BookingSetup.vue +43 -52
  86. package/components/Feedback/Form.vue +26 -12
  87. package/components/FeedbackMain.vue +35 -49
  88. package/components/FileInput.vue +75 -58
  89. package/components/FileInputWithList.vue +59 -12
  90. package/components/FormDialog.vue +60 -21
  91. package/components/HidAccessLogDashboard.vue +512 -490
  92. package/components/HidCameraCaptureDialog.vue +216 -0
  93. package/components/HidEnabledGate.vue +67 -9
  94. package/components/HidIntercomManagement.vue +1322 -653
  95. package/components/HidQrCodeConfiguration.vue +1030 -258
  96. package/components/HidReaderForm.vue +142 -59
  97. package/components/HidReaderManagement.vue +211 -323
  98. package/components/HidServiceSettingsPanel.vue +44 -13
  99. package/components/HidUserEnrollment.vue +1185 -412
  100. package/components/HygieneUpdateMoreAction.vue +44 -44
  101. package/components/IncidentReport/Authorities.vue +30 -18
  102. package/components/IncidentReport/IncidentInformation.vue +10 -10
  103. package/components/IncidentReport/IncidentInformationDownload.vue +3 -4
  104. package/components/IncidentReport/affectedEntities.vue +41 -20
  105. package/components/Input/Date.vue +3 -0
  106. package/components/Input/DatePicker.vue +1 -2
  107. package/components/Input/DateTimePicker.vue +1 -1
  108. package/components/Input/File.vue +55 -10
  109. package/components/Input/FileV2.vue +39 -6
  110. package/components/Input/InputPhoneNumberV2.vue +2 -1
  111. package/components/Input/ListGroupSelection.vue +56 -8
  112. package/components/Input/NRICNumber.vue +1 -0
  113. package/components/Input/NewDate.vue +1 -0
  114. package/components/Input/Number.vue +1 -0
  115. package/components/Input/Password.vue +1 -0
  116. package/components/Input/PhoneNumber.vue +21 -1
  117. package/components/Input/VehicleNumber.vue +1 -0
  118. package/components/InputLabel.vue +2 -2
  119. package/components/InvitationClientForm.vue +21 -19
  120. package/components/InvitationForm.vue +15 -21
  121. package/components/InvitationMain.vue +46 -61
  122. package/components/Layout/Header.vue +263 -36
  123. package/components/Layout/NavigationDrawer.vue +300 -30
  124. package/components/ListItem.vue +33 -3
  125. package/components/LocalPagination.vue +27 -3
  126. package/components/ManageChecklistMain.vue +136 -86
  127. package/components/MemberInformation.vue +32 -16
  128. package/components/MemberMain.vue +113 -93
  129. package/components/MyAttendanceMain.vue +3 -5
  130. package/components/NavigationItem.vue +83 -3
  131. package/components/Nfc/NFCPatrolReportMain.vue +47 -33
  132. package/components/Nfc/NFCPatrolRouteForm.vue +18 -20
  133. package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
  134. package/components/Nfc/NFCPatrolSettings.vue +14 -4
  135. package/components/Nfc/NFCTagForm.vue +22 -8
  136. package/components/Nfc/NFCTagMain.vue +41 -43
  137. package/components/Nfc/PatrolReport/DailyReportTab.vue +8 -1
  138. package/components/Nfc/PatrolReport/MonthlyReportTab.vue +8 -1
  139. package/components/Nfc/PatrolReport/MonthlyReportTable.vue +5 -3
  140. package/components/Nfc/PatrolReport/PatrolActivityTable.vue +26 -6
  141. package/components/Nfc/PatrolReport/PatrolReportTab.vue +8 -0
  142. package/components/Nfc/PatrolReport/PatrolRouteSummary.vue +41 -9
  143. package/components/Nfc/PatrolReport/RemarksDialog.vue +39 -9
  144. package/components/Nfc/PatrolReport/ReportEmptyState.vue +29 -9
  145. package/components/Nfc/PatrolReport/ReportFilters.vue +19 -10
  146. package/components/Nfc/PatrolReport/ReportLocationHeader.vue +32 -6
  147. package/components/Nfc/PatrolReport/SummaryReportTable.vue +28 -6
  148. package/components/NotificationSettings.vue +373 -0
  149. package/components/NumberSettingField.vue +9 -2
  150. package/components/OnlineFormConfigurationForm.vue +725 -237
  151. package/components/OnlineFormFill.vue +213 -106
  152. package/components/OnlineFormRenderPreview.vue +25 -30
  153. package/components/OnlineFormsConfiguration.vue +113 -109
  154. package/components/OrgViewDialog.vue +34 -16
  155. package/components/OvernightParkingAvailability.vue +49 -26
  156. package/components/OvernightParkingManagement.vue +78 -18
  157. package/components/PageHeader.vue +43 -0
  158. package/components/PassInformation.vue +109 -53
  159. package/components/PasswordConfirmation.vue +44 -26
  160. package/components/PeopleForm.vue +41 -20
  161. package/components/PhotoUpload.vue +110 -48
  162. package/components/PlaceholderComponent.vue +35 -19
  163. package/components/PlanActionsMenu.vue +12 -2
  164. package/components/PlanStatusBadge.vue +7 -8
  165. package/components/PlateNumberDisplay.vue +9 -6
  166. package/components/QrTemplate/PrintDialog.vue +66 -43
  167. package/components/RolePermissionFormCreate.vue +104 -79
  168. package/components/RolePermissionFormPreviewUpdate.vue +111 -109
  169. package/components/RolePermissionMain.vue +183 -81
  170. package/components/ScanVisitorQRCode.vue +99 -35
  171. package/components/ScheduleAreaMain.vue +34 -59
  172. package/components/ScheduleTaskForm.vue +61 -42
  173. package/components/ScheduleTaskMain.vue +56 -69
  174. package/components/SearchVehicleNumberUser.vue +39 -37
  175. package/components/ServiceProviderFormCreate.vue +29 -20
  176. package/components/ServiceProviderInvitationPrompt.vue +155 -0
  177. package/components/ServiceProviderMain.vue +601 -432
  178. package/components/Signature.vue +40 -34
  179. package/components/SignaturePad.vue +5 -29
  180. package/components/SiteSettings.vue +41 -11
  181. package/components/SiteSettingsWorkOrder.vue +61 -30
  182. package/components/SlideCardGroup.vue +35 -11
  183. package/components/Snackbar.vue +36 -3
  184. package/components/SpecificAttr.vue +18 -8
  185. package/components/StatusChip.vue +156 -0
  186. package/components/StockCard.vue +17 -12
  187. package/components/SubscriptionPlanTable.vue +207 -140
  188. package/components/SwitchContext.vue +201 -9
  189. package/components/TableHygiene.vue +36 -46
  190. package/components/TableList.vue +10 -10
  191. package/components/TableListSecondary.vue +105 -54
  192. package/components/TableMain.vue +241 -79
  193. package/components/TableV3.vue +61 -70
  194. package/components/TiptapEditor.vue +33 -40
  195. package/components/TiptapEditorQr.vue +5 -2
  196. package/components/Tooltip/Info.vue +69 -24
  197. package/components/UnitMain.vue +40 -60
  198. package/components/UnitPersonCard.vue +42 -13
  199. package/components/VehicleAddSelection.vue +48 -32
  200. package/components/VehicleForm.vue +112 -129
  201. package/components/VehicleManagement.vue +26 -12
  202. package/components/VehicleUpdateMoreAction.vue +59 -46
  203. package/components/VisitorDataFromScannedQRCodeForm.vue +90 -65
  204. package/components/VisitorForm.vue +774 -210
  205. package/components/VisitorFormSelection.vue +40 -37
  206. package/components/VisitorManagement.vue +239 -82
  207. package/components/VisitorPassKeyQRScanner.vue +65 -18
  208. package/components/VisitorSearchResultSummary.vue +60 -16
  209. package/components/VisitorSocketPopUp.vue +22 -14
  210. package/components/VisitorsReportPreview.vue +44 -29
  211. package/components/WorkOrder/Create.vue +26 -17
  212. package/components/WorkOrder/ListView.vue +38 -7
  213. package/components/WorkOrder/Main.vue +62 -59
  214. package/composables/useFeedback.ts +3 -3
  215. package/composables/useHidAmico.ts +269 -0
  216. package/composables/useHidNavigation.ts +2 -9
  217. package/composables/useMember.ts +3 -3
  218. package/composables/useNFCPatrolDailyReport.ts +13 -0
  219. package/composables/useNFCPatrolMonthlyReport.ts +22 -56
  220. package/composables/useNFCPatrolReport.ts +13 -0
  221. package/composables/useNotificationPreference.ts +87 -0
  222. package/composables/useOnlineFormPages.ts +2 -0
  223. package/composables/useRole.ts +22 -0
  224. package/composables/useServiceProvider.ts +3 -3
  225. package/composables/useServiceProviderInvitation.ts +145 -0
  226. package/composables/useSipWebPhone.ts +336 -0
  227. package/composables/useSubscriptionPlan.ts +62 -0
  228. package/composables/useThemePreference.ts +63 -0
  229. package/composables/useVisitor.ts +6 -1
  230. package/composables/useWebUsb.ts +127 -37
  231. package/composables/useWorkOrder.ts +17 -3
  232. package/error.vue +30 -6
  233. package/nuxt.config.ts +36 -0
  234. package/package.json +25 -4
  235. package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -2
  236. package/pages/[org]/[site]/access-mgmt/{identity-mapping → hid-cards}/index.vue +2 -2
  237. package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
  238. package/pages/notification-settings.vue +19 -0
  239. package/pages/payment-method-linked.vue +34 -10
  240. package/pages/require-customer.vue +48 -10
  241. package/pages/require-organization-membership.vue +32 -8
  242. package/pages/unauthorized.vue +29 -4
  243. package/plugins/secure-member.client.ts +21 -56
  244. package/plugins/vuetify.ts +69 -2
  245. package/types/nfc-patrol-report.ts +3 -2
  246. package/types/site.d.ts +71 -0
  247. package/types/subscription.ts +18 -11
  248. package/utils/breadcrumb.test.ts +108 -0
  249. package/utils/breadcrumb.ts +82 -0
  250. package/utils/dashboard.test.ts +118 -0
  251. package/utils/dashboard.ts +53 -0
  252. package/utils/data.test.ts +95 -0
  253. package/utils/data.ts +40 -2
  254. package/utils/hid-discover.test.ts +102 -0
  255. package/utils/list-page.test.ts +60 -0
  256. package/utils/list-page.ts +38 -0
  257. package/utils/materialSymbols.ts +363 -0
  258. package/utils/nav-sections.test.ts +190 -0
  259. package/utils/nav-sections.ts +151 -0
  260. package/utils/nfc-patrol-report.test.ts +130 -0
  261. package/utils/nfc-patrol-report.ts +101 -0
  262. package/utils/role.test.ts +69 -0
  263. package/utils/role.ts +10 -0
  264. package/utils/route-name.test.ts +85 -0
  265. package/utils/route-name.ts +24 -0
  266. package/utils/status.test.ts +110 -0
  267. package/utils/status.ts +135 -0
  268. package/utils/table-card.test.ts +84 -0
  269. package/utils/theme-aa-ledger.ts +148 -0
  270. package/utils/theme.test.ts +485 -7
  271. package/utils/theme.ts +295 -110
  272. package/.changeset/README.md +0 -8
  273. package/.changeset/config.json +0 -11
  274. package/.editorconfig +0 -12
  275. package/.github/workflows/main.yml +0 -17
  276. package/.github/workflows/publish-staging.yml +0 -47
  277. package/.github/workflows/publish.yml +0 -39
  278. package/PUBLISHING.md +0 -269
  279. package/components/HidIdentityMapping.vue +0 -975
  280. package/components/ScheduleTaskAreaFormDialog.vue +0 -144
  281. package/components/ScheduleTaskAreaUpdateMoreAction.vue +0 -109
  282. package/components/ScheduleTastTicketMain.vue +0 -182
  283. package/components/TableWithButton.vue +0 -94
  284. package/test/visitor-socket.test.mjs +0 -36
@@ -1,87 +1,86 @@
1
1
  <template>
2
2
  <v-container fluid class="pa-6">
3
- <!-- Header: Title & Create Button -->
4
- <div class="d-flex align-center justify-space-between mb-6">
5
- <h1 class="text-h5 font-weight-bold text-primary">
6
- Subscription Plans
7
- </h1>
8
-
9
- <v-btn
10
- color="secondary"
11
- rounded="pill"
12
- elevation="0"
13
- @click="openCreateModal"
14
- >
15
- Create Plan
16
- </v-btn>
17
- </div>
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-text-field
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
- <v-select
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
- <v-select
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
- <v-card variant="outlined" class="mt-4">
61
- <!-- Status Tabs & Actions Bar -->
62
- <div class="d-flex align-center justify-space-between bg-grey-lighten-4 border-b">
63
- <v-tabs v-model="activeTab" color="primary" density="compact">
64
- <v-tab value="active" class="text-none font-weight-bold">Active</v-tab>
65
- <v-tab value="deactive" class="text-none font-weight-bold">Deactive</v-tab>
66
- </v-tabs>
67
-
68
- <div class="pa-2">
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
- title="Refresh"
65
+ aria-label="Refresh"
74
66
  @click="refresh"
75
67
  />
76
- </div>
77
- </div>
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="filteredPlans"
83
- :page="page"
84
- :items-per-page="pageSize"
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 === 'free_bundle' ? 'Free' : 'Paid' }}
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
- <v-chip size="small" variant="tonal" color="grey-darken-2">
112
- {{ item.applicationIds.length }} Application{{ item.applicationIds.length > 1 ? 's' : '' }}
113
- </v-chip>
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
- <v-chip
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="item.status === 'active' ? 'mdi-trash-can-outline' : 'mdi-check-circle-outline'"
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="item.status === 'active' ? 'text-error' : 'text-success'"
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="py-8 text-center text-grey-darken-1">
171
- No plans found.
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
- </v-card>
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, reactive, computed, watch } from "vue";
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
- { title: "Actions", key: "actions", align: "center" as const, sortable: false },
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 plans = reactive<SubscriptionPlan[]>([
226
- {
227
- id: "1",
228
- name: "Standard",
229
- description: "",
230
- maxSeats: 10,
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 pageSize = 20;
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 filteredPlans = computed(() =>
262
- plans.filter((plan) => {
263
- if (plan.status !== activeTab.value) return false;
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
- if (
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
- if (categoryFilter.value !== "all" && plan.planType !== categoryFilter.value) {
273
- return false;
274
- }
275
-
276
- if (billingFilter.value !== "all" && plan.billingCycle !== billingFilter.value) {
277
- return false;
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
- return true;
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([search, categoryFilter, billingFilter, activeTab], () => {
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 handleSubmit(payload: PlanFormState) {
324
- const applicationIds =
325
- payload.planType === "custom_apps"
326
- ? payload.customApps
327
- : payload.bundleApps;
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
- ? null
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
- if (editingPlan.value) {
337
- Object.assign(editingPlan.value, {
338
- name: payload.name,
339
- description: payload.description,
340
- maxSeats: payload.maxSeats,
341
- planType: payload.planType,
342
- billingCycle: payload.billingCycle,
343
- price,
344
- applicationIds,
345
- });
346
- } else {
347
- plans.push({
348
- id: crypto.randomUUID(),
349
- name: payload.name,
350
- description: payload.description,
351
- maxSeats: payload.maxSeats,
352
- planType: payload.planType,
353
- billingCycle: payload.billingCycle,
354
- price,
355
- applicationIds,
356
- status: "active",
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
- closeModal();
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>