@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.
Files changed (283) hide show
  1. package/assets/css/primitives.css +1113 -0
  2. package/assets/css/screens.css +1097 -0
  3. package/assets/css/shell.css +258 -0
  4. package/assets/css/tokens.css +236 -0
  5. package/components/AcceptDialog.vue +17 -18
  6. package/components/AccessCard/AvailableStats.vue +24 -16
  7. package/components/AccessCardAddForm.vue +42 -42
  8. package/components/AccessCardAssignToUnitForm.vue +13 -21
  9. package/components/AccessCardDeleteDialog.vue +41 -39
  10. package/components/AccessCardDetailsDialog.vue +125 -79
  11. package/components/AccessCardPreviewDialog.vue +58 -84
  12. package/components/AccessCardQrTagging.vue +60 -63
  13. package/components/AccessCardReplaceForm.vue +27 -23
  14. package/components/AccessManagement.vue +86 -85
  15. package/components/AddEqupmentForm.vue +18 -22
  16. package/components/AddPassKeyToVisitor.vue +95 -73
  17. package/components/AppButton.vue +62 -0
  18. package/components/AppCard.vue +20 -0
  19. package/components/AppDateField.vue +49 -0
  20. package/components/AppField.vue +76 -0
  21. package/components/AppKpiTile.vue +65 -0
  22. package/components/AppSegmented.vue +38 -0
  23. package/components/AppSelect.vue +247 -0
  24. package/components/AreaChecklistHistoryLogs.vue +51 -44
  25. package/components/AreaChecklistHistoryMain.vue +14 -7
  26. package/components/AreaFormDialog.vue +71 -59
  27. package/components/AreaMain.vue +73 -107
  28. package/components/AttachmentsViewer.vue +84 -22
  29. package/components/AttendanceCheckInOutDialog.vue +85 -88
  30. package/components/AttendanceDetailsDialog.vue +57 -63
  31. package/components/AttendanceMain.vue +4 -3
  32. package/components/AttendanceMapSearchDialog.vue +42 -44
  33. package/components/AttendanceSettingsDialog.vue +79 -63
  34. package/components/Avatar/Main.vue +54 -2
  35. package/components/BillingMain.vue +26 -30
  36. package/components/BtnUploadFile.vue +53 -11
  37. package/components/BuildingForm.vue +34 -27
  38. package/components/BuildingManagement/buildings.vue +39 -39
  39. package/components/BuildingManagement/units.vue +26 -42
  40. package/components/BuildingUnitFormAdd.vue +20 -23
  41. package/components/BuildingUnitFormEdit.vue +8 -4
  42. package/components/BulletinBoardForm.vue +19 -24
  43. package/components/BulletinBoardManagement.vue +49 -57
  44. package/components/BulletinBoardView.vue +9 -6
  45. package/components/BulletinExpirationChip.vue +13 -4
  46. package/components/Button/Close.vue +2 -2
  47. package/components/CameraForm.vue +21 -22
  48. package/components/CameraMain.vue +50 -49
  49. package/components/Card/DeleteConfirmation.vue +24 -22
  50. package/components/Card/MemberInfoSummary.vue +28 -3
  51. package/components/Card/Toggle.vue +15 -3
  52. package/components/CardToolbar.vue +37 -0
  53. package/components/Carousel.vue +56 -36
  54. package/components/Chat/Bubbles.vue +43 -14
  55. package/components/Chat/Information.vue +49 -32
  56. package/components/Chat/ListCard.vue +38 -9
  57. package/components/Chat/Message.vue +41 -15
  58. package/components/Chat/Navigation.vue +27 -15
  59. package/components/Chat/SkeletonLoader.vue +9 -2
  60. package/components/ChecklistItemRow.vue +25 -1
  61. package/components/CleaningScheduleMain.vue +44 -49
  62. package/components/ClientDetailForm.vue +81 -56
  63. package/components/ClientMain.vue +92 -82
  64. package/components/ConfirmDialog.vue +14 -8
  65. package/components/Container/Standard.vue +9 -7
  66. package/components/CreateSubsciptionPlan.vue +148 -89
  67. package/components/DashboardEmptyState.vue +48 -3
  68. package/components/DashboardMain.vue +924 -676
  69. package/components/DashboardPanel.vue +84 -21
  70. package/components/DeliveryCompany.vue +34 -20
  71. package/components/Dialog/DeleteConfirmation.vue +20 -21
  72. package/components/Dialog/ReplaceAutofillPrompt.vue +19 -26
  73. package/components/Dialog/ReusablePrompt.vue +30 -29
  74. package/components/Dialog/UpdateMoreAction.vue +62 -59
  75. package/components/DocumentForm.vue +14 -21
  76. package/components/DocumentManagement.vue +162 -108
  77. package/components/DocumentViewer.vue +29 -12
  78. package/components/DrawImage.vue +29 -21
  79. package/components/EntryPass/QrTemplatePreview.vue +27 -16
  80. package/components/EntryPassInformation.vue +40 -10
  81. package/components/EntryPassMain.vue +49 -27
  82. package/components/EquipmentItemMain.vue +84 -97
  83. package/components/EquipmentManagementMain.vue +81 -103
  84. package/components/Facility/BookingSetup.vue +43 -52
  85. package/components/Feedback/Form.vue +26 -12
  86. package/components/FeedbackMain.vue +35 -49
  87. package/components/FileInput.vue +75 -58
  88. package/components/FileInputWithList.vue +59 -12
  89. package/components/FormDialog.vue +60 -21
  90. package/components/HidAccessLogDashboard.vue +512 -490
  91. package/components/HidCameraCaptureDialog.vue +216 -0
  92. package/components/HidEnabledGate.vue +67 -9
  93. package/components/HidIntercomManagement.vue +1322 -653
  94. package/components/HidQrCodeConfiguration.vue +1030 -258
  95. package/components/HidReaderForm.vue +142 -59
  96. package/components/HidReaderManagement.vue +211 -323
  97. package/components/HidServiceSettingsPanel.vue +44 -13
  98. package/components/HidUserEnrollment.vue +1185 -412
  99. package/components/HygieneUpdateMoreAction.vue +44 -44
  100. package/components/IncidentReport/Authorities.vue +30 -18
  101. package/components/IncidentReport/IncidentInformation.vue +10 -10
  102. package/components/IncidentReport/IncidentInformationDownload.vue +3 -4
  103. package/components/IncidentReport/affectedEntities.vue +41 -20
  104. package/components/Input/Date.vue +3 -0
  105. package/components/Input/DatePicker.vue +1 -2
  106. package/components/Input/DateTimePicker.vue +1 -1
  107. package/components/Input/File.vue +55 -10
  108. package/components/Input/FileV2.vue +39 -6
  109. package/components/Input/InputPhoneNumberV2.vue +2 -1
  110. package/components/Input/ListGroupSelection.vue +56 -8
  111. package/components/Input/NRICNumber.vue +1 -0
  112. package/components/Input/NewDate.vue +1 -0
  113. package/components/Input/Number.vue +1 -0
  114. package/components/Input/Password.vue +1 -0
  115. package/components/Input/PhoneNumber.vue +21 -1
  116. package/components/Input/VehicleNumber.vue +1 -0
  117. package/components/InputLabel.vue +2 -2
  118. package/components/InvitationClientForm.vue +21 -19
  119. package/components/InvitationForm.vue +15 -21
  120. package/components/InvitationMain.vue +46 -61
  121. package/components/Layout/Header.vue +263 -36
  122. package/components/Layout/NavigationDrawer.vue +300 -30
  123. package/components/ListItem.vue +33 -3
  124. package/components/LocalPagination.vue +27 -3
  125. package/components/ManageChecklistMain.vue +136 -86
  126. package/components/MemberInformation.vue +32 -16
  127. package/components/MemberMain.vue +113 -93
  128. package/components/MyAttendanceMain.vue +3 -5
  129. package/components/NavigationItem.vue +83 -3
  130. package/components/Nfc/NFCPatrolReportMain.vue +47 -33
  131. package/components/Nfc/NFCPatrolRouteForm.vue +18 -20
  132. package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
  133. package/components/Nfc/NFCPatrolSettings.vue +14 -4
  134. package/components/Nfc/NFCTagForm.vue +22 -8
  135. package/components/Nfc/NFCTagMain.vue +41 -43
  136. package/components/Nfc/PatrolReport/DailyReportTab.vue +8 -1
  137. package/components/Nfc/PatrolReport/MonthlyReportTab.vue +8 -1
  138. package/components/Nfc/PatrolReport/MonthlyReportTable.vue +5 -3
  139. package/components/Nfc/PatrolReport/PatrolActivityTable.vue +26 -6
  140. package/components/Nfc/PatrolReport/PatrolReportTab.vue +8 -0
  141. package/components/Nfc/PatrolReport/PatrolRouteSummary.vue +41 -9
  142. package/components/Nfc/PatrolReport/RemarksDialog.vue +39 -9
  143. package/components/Nfc/PatrolReport/ReportEmptyState.vue +29 -9
  144. package/components/Nfc/PatrolReport/ReportFilters.vue +19 -10
  145. package/components/Nfc/PatrolReport/ReportLocationHeader.vue +32 -6
  146. package/components/Nfc/PatrolReport/SummaryReportTable.vue +28 -6
  147. package/components/NotificationSettings.vue +373 -0
  148. package/components/NumberSettingField.vue +9 -2
  149. package/components/OnlineFormConfigurationForm.vue +725 -237
  150. package/components/OnlineFormFill.vue +213 -106
  151. package/components/OnlineFormRenderPreview.vue +25 -30
  152. package/components/OnlineFormsConfiguration.vue +113 -109
  153. package/components/OrgViewDialog.vue +34 -16
  154. package/components/OvernightParkingAvailability.vue +49 -26
  155. package/components/OvernightParkingManagement.vue +78 -18
  156. package/components/PageHeader.vue +43 -0
  157. package/components/PassInformation.vue +109 -53
  158. package/components/PasswordConfirmation.vue +44 -26
  159. package/components/PeopleForm.vue +41 -20
  160. package/components/PhotoUpload.vue +110 -48
  161. package/components/PlaceholderComponent.vue +35 -19
  162. package/components/PlanActionsMenu.vue +12 -2
  163. package/components/PlanStatusBadge.vue +7 -8
  164. package/components/PlateNumberDisplay.vue +9 -6
  165. package/components/QrTemplate/PrintDialog.vue +66 -43
  166. package/components/RolePermissionFormCreate.vue +104 -79
  167. package/components/RolePermissionFormPreviewUpdate.vue +111 -109
  168. package/components/RolePermissionMain.vue +183 -81
  169. package/components/ScanVisitorQRCode.vue +99 -35
  170. package/components/ScheduleAreaMain.vue +34 -59
  171. package/components/ScheduleTaskForm.vue +61 -42
  172. package/components/ScheduleTaskMain.vue +56 -69
  173. package/components/SearchVehicleNumberUser.vue +39 -37
  174. package/components/ServiceProviderFormCreate.vue +29 -20
  175. package/components/ServiceProviderInvitationPrompt.vue +155 -0
  176. package/components/ServiceProviderMain.vue +601 -432
  177. package/components/Signature.vue +40 -34
  178. package/components/SignaturePad.vue +5 -29
  179. package/components/SiteSettings.vue +41 -11
  180. package/components/SiteSettingsWorkOrder.vue +61 -30
  181. package/components/SlideCardGroup.vue +35 -11
  182. package/components/Snackbar.vue +36 -3
  183. package/components/SpecificAttr.vue +18 -8
  184. package/components/StatusChip.vue +156 -0
  185. package/components/StockCard.vue +17 -12
  186. package/components/SubscriptionPlanTable.vue +207 -140
  187. package/components/SwitchContext.vue +201 -9
  188. package/components/TableHygiene.vue +36 -46
  189. package/components/TableList.vue +10 -10
  190. package/components/TableListSecondary.vue +105 -54
  191. package/components/TableMain.vue +251 -78
  192. package/components/TableV3.vue +61 -70
  193. package/components/TiptapEditor.vue +33 -40
  194. package/components/TiptapEditorQr.vue +5 -2
  195. package/components/Tooltip/Info.vue +69 -24
  196. package/components/UnitMain.vue +40 -60
  197. package/components/UnitPersonCard.vue +42 -13
  198. package/components/VehicleAddSelection.vue +48 -32
  199. package/components/VehicleForm.vue +112 -129
  200. package/components/VehicleManagement.vue +26 -12
  201. package/components/VehicleUpdateMoreAction.vue +59 -46
  202. package/components/VisitorDataFromScannedQRCodeForm.vue +90 -65
  203. package/components/VisitorForm.vue +774 -210
  204. package/components/VisitorFormSelection.vue +40 -37
  205. package/components/VisitorManagement.vue +239 -82
  206. package/components/VisitorPassKeyQRScanner.vue +65 -18
  207. package/components/VisitorSearchResultSummary.vue +60 -16
  208. package/components/VisitorSocketPopUp.vue +22 -14
  209. package/components/VisitorsReportPreview.vue +44 -29
  210. package/components/WorkOrder/Create.vue +26 -17
  211. package/components/WorkOrder/ListView.vue +38 -7
  212. package/components/WorkOrder/Main.vue +62 -59
  213. package/composables/useFeedback.ts +3 -3
  214. package/composables/useHidAmico.ts +269 -0
  215. package/composables/useHidNavigation.ts +2 -9
  216. package/composables/useMember.ts +3 -3
  217. package/composables/useNFCPatrolDailyReport.ts +13 -0
  218. package/composables/useNFCPatrolMonthlyReport.ts +22 -56
  219. package/composables/useNFCPatrolReport.ts +13 -0
  220. package/composables/useNotificationPreference.ts +87 -0
  221. package/composables/useOnlineFormPages.ts +2 -0
  222. package/composables/useRole.ts +22 -0
  223. package/composables/useServiceProvider.ts +3 -3
  224. package/composables/useServiceProviderInvitation.ts +145 -0
  225. package/composables/useSipWebPhone.ts +336 -0
  226. package/composables/useSubscriptionPlan.ts +62 -0
  227. package/composables/useThemePreference.ts +63 -0
  228. package/composables/useVisitor.ts +6 -1
  229. package/composables/useWebUsb.ts +127 -37
  230. package/composables/useWorkOrder.ts +17 -3
  231. package/error.vue +30 -6
  232. package/nuxt.config.ts +36 -0
  233. package/package.json +25 -4
  234. package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -2
  235. package/pages/[org]/[site]/access-mgmt/{identity-mapping → hid-cards}/index.vue +2 -2
  236. package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
  237. package/pages/notification-settings.vue +19 -0
  238. package/pages/payment-method-linked.vue +34 -10
  239. package/pages/require-customer.vue +48 -10
  240. package/pages/require-organization-membership.vue +32 -8
  241. package/pages/unauthorized.vue +29 -4
  242. package/plugins/secure-member.client.ts +21 -56
  243. package/plugins/vuetify.ts +69 -2
  244. package/types/nfc-patrol-report.ts +3 -2
  245. package/types/site.d.ts +71 -0
  246. package/types/subscription.ts +18 -11
  247. package/utils/breadcrumb.test.ts +108 -0
  248. package/utils/breadcrumb.ts +82 -0
  249. package/utils/dashboard.test.ts +118 -0
  250. package/utils/dashboard.ts +53 -0
  251. package/utils/data.test.ts +95 -0
  252. package/utils/data.ts +40 -2
  253. package/utils/hid-discover.test.ts +102 -0
  254. package/utils/list-page.test.ts +60 -0
  255. package/utils/list-page.ts +38 -0
  256. package/utils/materialSymbols.ts +363 -0
  257. package/utils/nav-sections.test.ts +190 -0
  258. package/utils/nav-sections.ts +151 -0
  259. package/utils/nfc-patrol-report.test.ts +130 -0
  260. package/utils/nfc-patrol-report.ts +101 -0
  261. package/utils/role.test.ts +69 -0
  262. package/utils/role.ts +10 -0
  263. package/utils/route-name.test.ts +85 -0
  264. package/utils/route-name.ts +24 -0
  265. package/utils/status.test.ts +110 -0
  266. package/utils/status.ts +135 -0
  267. package/utils/table-card.test.ts +84 -0
  268. package/utils/theme-aa-ledger.ts +148 -0
  269. package/utils/theme.test.ts +485 -7
  270. package/utils/theme.ts +295 -110
  271. package/.changeset/README.md +0 -8
  272. package/.changeset/config.json +0 -11
  273. package/.editorconfig +0 -12
  274. package/.github/workflows/main.yml +0 -17
  275. package/.github/workflows/publish-staging.yml +0 -47
  276. package/.github/workflows/publish.yml +0 -39
  277. package/PUBLISHING.md +0 -269
  278. package/components/HidIdentityMapping.vue +0 -975
  279. package/components/ScheduleTaskAreaFormDialog.vue +0 -144
  280. package/components/ScheduleTaskAreaUpdateMoreAction.vue +0 -109
  281. package/components/ScheduleTastTicketMain.vue +0 -182
  282. package/components/TableWithButton.vue +0 -94
  283. package/test/visitor-socket.test.mjs +0 -36
@@ -1,36 +1,31 @@
1
1
  <template>
2
- <v-card width="100%">
3
- <v-toolbar>
4
- <v-row no-gutters class="fill-height px-6" align="center">
5
- <span class="font-weight-bold text-h5">
6
- {{ props.name }}
7
- </span>
8
- </v-row>
9
- </v-toolbar>
10
- <v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0">
2
+ <v-card width="100%" class="screen-modal role-perm">
3
+ <div class="role-perm__head">
4
+ <v-card-title>{{ props.name }}</v-card-title>
5
+ </div>
6
+ <div class="screen-modal__body role-perm__body">
11
7
  <v-form v-model="validForm" :disabled="disable">
12
8
  <v-row no-gutters>
13
9
  <v-col cols="12" class="mt-4">
14
- <div class="d-flex align-center ga-4 text-caption mb-3">
10
+ <div class="role-perm__legend">
15
11
  <span class="d-flex align-center ga-2">
16
- <v-icon color="amber-darken-2" size="14">mdi-circle</v-icon>
12
+ <v-icon class="role-perm__dot--both" size="14">mdi-circle</v-icon>
17
13
  Web and Mobile
18
14
  </span>
19
15
  <span class="d-flex align-center ga-2">
20
- <v-icon color="blue-darken-2" size="14">mdi-circle</v-icon>
16
+ <v-icon class="role-perm__dot--web" size="14">mdi-circle</v-icon>
21
17
  Web only
22
18
  </span>
23
19
  </div>
24
- <v-btn
20
+ <AppButton
25
21
  v-if="edit"
26
- size="small"
27
- variant="tonal"
28
- class="text-none mb-2"
22
+ variant="ghost"
23
+ class="mb-2"
29
24
  @click="toggleSelectAll(!isAllSelected)"
30
25
  >
31
26
  {{ isAllSelected ? "Unselect All" : "Select All" }}
32
- </v-btn>
33
- <v-card variant="outlined" border="thin" rounded="lg">
27
+ </AppButton>
28
+ <div class="role-perm__list">
34
29
  <template
35
30
  v-for="(actions, resourceKey, resourceIndex) in safePermissions"
36
31
  :key="resourceKey"
@@ -38,28 +33,23 @@
38
33
  <v-divider v-if="resourceIndex > 0" />
39
34
  <v-list-item density="compact" :ripple="false">
40
35
  <template #prepend>
41
- <v-chip
36
+ <StatusChip
42
37
  class="mr-3"
43
- size="small"
44
- label
45
- :color="resourceColor(String(resourceKey))"
46
- >
47
- {{ selectedActionCount(String(resourceKey)) }}
48
- </v-chip>
38
+ :dot="false"
39
+ :tone="isWebOnly(String(resourceKey)) ? 'info' : 'warn'"
40
+ :label="String(selectedActionCount(String(resourceKey)))"
41
+ />
49
42
  </template>
50
- <v-list-item-title
51
- class="text-capitalize text-subtitle-2 font-weight-medium"
52
- >
43
+ <v-list-item-title class="text-capitalize role-perm__row-title">
53
44
  {{ formatResourceLabel(String(resourceKey)) }}
54
45
  </v-list-item-title>
55
46
  <template #append>
56
47
  <v-switch
57
48
  :model-value="isCategoryEnabled(String(resourceKey))"
58
49
  :disabled="!edit"
59
- color="green"
50
+ class="app-switch"
60
51
  density="compact"
61
52
  hide-details
62
- inset
63
53
  @update:model-value="
64
54
  toggleCategory(String(resourceKey), $event)
65
55
  "
@@ -81,7 +71,7 @@
81
71
  )
82
72
  "
83
73
  :disabled="!edit"
84
- color="primary"
74
+ color="accent"
85
75
  density="compact"
86
76
  class="mr-2"
87
77
  @update:model-value="
@@ -93,20 +83,18 @@
93
83
  "
94
84
  />
95
85
  </template>
96
- <v-list-item-title
97
- class="text-subtitle-2 font-weight-medium text-capitalize"
98
- >
86
+ <v-list-item-title class="text-capitalize role-perm__row-title">
99
87
  {{ String(actionKey).replace(/-/g, " ") }}
100
88
  </v-list-item-title>
101
- <v-list-item-subtitle class="text-caption">
89
+ <v-list-item-subtitle class="role-perm__row-sub">
102
90
  {{ action.description }}
103
91
  </v-list-item-subtitle>
104
92
  </v-list-item>
105
93
  </template>
106
94
  </template>
107
95
  </template>
108
- </v-card>
109
- <div v-if="errorMessages" class="text-caption text-error mt-2">
96
+ </div>
97
+ <div v-if="errorMessages" class="role-perm__error mt-2">
110
98
  {{ errorMessages }}
111
99
  </div>
112
100
  </v-col>
@@ -114,11 +102,7 @@
114
102
  <v-col v-if="edit" cols="12" class="my-2">
115
103
  <v-row no-gutters>
116
104
  <v-col cols="12" class="text-center">
117
- <span
118
- class="text-none text-subtitle-2 font-weight-medium text-error"
119
- >
120
- {{ message }}
121
- </span>
105
+ <span class="role-perm__error">{{ message }}</span>
122
106
  </v-col>
123
107
  </v-row>
124
108
  </v-col>
@@ -126,74 +110,23 @@
126
110
  <v-col cols="12" class="my-2">
127
111
  <v-row no-gutters>
128
112
  <v-col cols="12" class="text-center">
129
- <span
130
- class="text-none text-subtitle-2 font-weight-medium text-error"
131
- >
132
- {{ message }}
133
- </span>
113
+ <span class="role-perm__error">{{ message }}</span>
134
114
  </v-col>
135
115
  </v-row>
136
116
  </v-col>
137
117
  </v-row>
138
118
  </v-form>
139
- </v-card-text>
140
-
141
- <v-toolbar>
142
- <v-row class="px-6">
143
- <v-col v-if="!edit" cols="6">
144
- <v-btn
145
- block
146
- variant="text"
147
- class="text-none"
148
- size="large"
149
- @click="emit('cancel')"
150
- >
151
- Close
152
- </v-btn>
153
- </v-col>
154
-
155
- <v-col v-else cols="6">
156
- <v-btn
157
- block
158
- variant="text"
159
- class="text-none"
160
- size="large"
161
- @click="edit = false"
162
- >
163
- Cancel
164
- </v-btn>
165
- </v-col>
166
-
167
- <v-col v-if="!edit" cols="6">
168
- <v-btn
169
- block
170
- variant="flat"
171
- color="black"
172
- class="text-none"
173
- size="large"
174
- :disabled="disable"
175
- @click="edit = true"
176
- >
177
- Edit
178
- </v-btn>
179
- </v-col>
180
-
181
- <v-col v-else cols="6">
182
- <v-btn
183
- block
184
- variant="flat"
185
- color="black"
186
- class="text-none"
187
- size="large"
188
- :disabled="Boolean(errorMessages) || disable"
189
- :loading="disable"
190
- @click="submit()"
191
- >
192
- Submit
193
- </v-btn>
194
- </v-col>
195
- </v-row>
196
- </v-toolbar>
119
+ </div>
120
+
121
+ <div class="role-perm__footer">
122
+ <AppButton v-if="!edit" variant="ghost" @click="emit('cancel')">Close</AppButton>
123
+ <AppButton v-else variant="ghost" @click="edit = false">Cancel</AppButton>
124
+
125
+ <AppButton v-if="!edit" :disabled="disable" @click="edit = true">Edit</AppButton>
126
+ <AppButton v-else :disabled="Boolean(errorMessages) || disable" @click="submit()">
127
+ Submit
128
+ </AppButton>
129
+ </div>
197
130
  </v-card>
198
131
  </template>
199
132
 
@@ -242,10 +175,8 @@ function formatResourceLabel(key: string) {
242
175
  return props.webOnlyResources.includes(key) ? `${label} (Web Only)` : label;
243
176
  }
244
177
 
245
- function resourceColor(resource: string) {
246
- return props.webOnlyResources.includes(resource)
247
- ? "blue-darken-2"
248
- : "amber-darken-2";
178
+ function isWebOnly(resource: string) {
179
+ return props.webOnlyResources.includes(resource);
249
180
  }
250
181
 
251
182
  function selectedActionCount(resource: string) {
@@ -357,3 +288,74 @@ async function submit() {
357
288
  }
358
289
  }
359
290
  </script>
291
+
292
+ <style scoped>
293
+ .role-perm__head {
294
+ display: flex;
295
+ align-items: center;
296
+ justify-content: space-between;
297
+ padding-right: 12px;
298
+ }
299
+
300
+ .role-perm__body {
301
+ padding: 0 20px;
302
+ max-height: calc(100vh - 160px);
303
+ }
304
+
305
+ .role-perm__legend {
306
+ display: flex;
307
+ align-items: center;
308
+ gap: 16px;
309
+ margin-bottom: 12px;
310
+ font-size: var(--fs-breadcrumb);
311
+ color: var(--text2);
312
+ }
313
+
314
+ /* The two legend dots carried `amber-darken-2` and `blue-darken-2` - fixed
315
+ Vuetify hues that do not follow the theme, and the same two hues the count
316
+ chip beside each row used. Both are the design's tones now, and the chip and
317
+ the dot stay in step because they read the same `isWebOnly`. */
318
+ .role-perm__dot--both {
319
+ color: var(--warn);
320
+ }
321
+
322
+ .role-perm__dot--web {
323
+ color: var(--info);
324
+ }
325
+
326
+ /* Was a `variant="outlined"` v-card, i.e. a card inside a card. It is the list
327
+ frame, so it takes the design's card border and radius without laying a
328
+ second surface over the first. */
329
+ .role-perm__list {
330
+ border: 1px solid var(--border);
331
+ border-radius: var(--r-card);
332
+ overflow: hidden;
333
+ }
334
+
335
+ .role-perm__row-title {
336
+ font-size: var(--fs-cell) !important;
337
+ font-weight: var(--fw-cell) !important;
338
+ color: var(--text);
339
+ }
340
+
341
+ .role-perm__row-sub {
342
+ font-size: var(--fs-breadcrumb) !important;
343
+ color: var(--text2);
344
+ }
345
+
346
+ .role-perm__error {
347
+ font-size: var(--fs-cell);
348
+ font-weight: 600;
349
+ color: var(--err);
350
+ }
351
+
352
+ /* The design's dialog footer. Deliberately not the shared
353
+ `.screen-modal__footer` - that class is on the batch-D branch (#1150), not
354
+ on this base, and a second copy in `screens.css` would collide with it. */
355
+ .role-perm__footer {
356
+ display: flex;
357
+ justify-content: flex-end;
358
+ gap: 8px;
359
+ padding: 16px 20px 20px;
360
+ }
361
+ </style>
@@ -1,49 +1,46 @@
1
1
  <template>
2
- <v-row no-gutters>
3
- <v-col cols="12" class="mb-2">
4
- <v-row no-gutters>
5
- <v-btn
6
- class="text-none"
7
- rounded="pill"
8
- variant="tonal"
9
- @click="createDialog = true"
10
- size="large"
11
- v-if="canCreateRole"
12
- >
2
+ <div>
3
+ <!--
4
+ THE DESIGN'S PAGE SCAFFOLD (same shape as Service Providers).
5
+
6
+ `Create role` moves from a bare row above the card onto the title line,
7
+ on the right, where the design puts a screen's primary action. It is the
8
+ same button under the same `canCreateRole` permission opening the same
9
+ dialog. The refresh, the range, the pager and the table are unchanged.
10
+ -->
11
+ <PageHeader title="Roles and Permissions">
12
+ <template #actions>
13
+ <AppButton v-if="canCreateRole" @click="createDialog = true">
13
14
  Create role
14
- </v-btn>
15
- </v-row>
16
- </v-col>
17
- <v-col cols="12">
18
- <v-card
19
- width="100%"
20
- variant="outlined"
21
- border="thin"
22
- rounded="lg"
23
- :loading="loading"
24
- >
25
- <v-toolbar density="compact" color="grey-lighten-4">
26
- <template #prepend>
27
- <v-btn fab icon density="comfortable" @click="getRoles()">
28
- <v-icon>mdi-refresh</v-icon>
29
- </v-btn>
30
- </template>
31
-
32
- <template #append>
33
- <v-row no-gutters justify="end" align="center">
34
- <span class="mr-2 text-caption text-fontgray">
35
- {{ pageRange }}
36
- </span>
37
- <local-pagination
38
- v-model="page"
39
- :length="pages"
40
- @update:value="getRoles()"
41
- />
42
- </v-row>
43
- </template>
44
- </v-toolbar>
45
-
46
- <v-data-table
15
+ </AppButton>
16
+ </template>
17
+ </PageHeader>
18
+
19
+ <AppCard class="table-card">
20
+ <!-- `v-card`'s `:loading` bar, drawn explicitly: a loading screen must
21
+ still say so now that the card is not a `v-card`. -->
22
+ <v-progress-linear v-if="loading" indeterminate height="2" />
23
+
24
+ <CardToolbar :count="pageRange">
25
+ <template #left>
26
+ <AppButton
27
+ variant="icon"
28
+ icon="mdi-refresh"
29
+ aria-label="Refresh"
30
+ @click="getRoles()"
31
+ />
32
+ </template>
33
+
34
+ <template #right>
35
+ <local-pagination
36
+ v-model="page"
37
+ :length="pages"
38
+ @update:value="getRoles()"
39
+ />
40
+ </template>
41
+ </CardToolbar>
42
+
43
+ <v-data-table
47
44
  :headers="props.headers"
48
45
  :items="items"
49
46
  item-value="_id"
@@ -58,7 +55,11 @@
58
55
  <span class="text-caption font-weight-bold text-capitalize">
59
56
  permissions
60
57
  </span>
61
- <v-chip>{{ getPermissionCount(value) }}</v-chip>
58
+ <StatusChip
59
+ :dot="false"
60
+ tone="info"
61
+ :label="String(getPermissionCount(value))"
62
+ />
62
63
  </template>
63
64
 
64
65
  <template #item.action-table="{ item }" v-if="canDeleteRole">
@@ -73,9 +74,8 @@
73
74
  </v-list>
74
75
  </v-menu>
75
76
  </template>
76
- </v-data-table>
77
- </v-card>
78
- </v-col>
77
+ </v-data-table>
78
+ </AppCard>
79
79
 
80
80
  <!-- dialogs -->
81
81
  <v-dialog v-model="createDialog" width="500" persistent>
@@ -84,6 +84,7 @@
84
84
  :permissions="props.permissions"
85
85
  :org="props.orgId"
86
86
  :site-id="props.siteId"
87
+ :site-state="siteFieldShown"
87
88
  :web-only-resources="props.webOnlyResources"
88
89
  @success="success()"
89
90
  :type="props.type"
@@ -105,48 +106,88 @@
105
106
  />
106
107
  </v-dialog>
107
108
 
108
- <ConfirmDialog
109
- v-model="confirmDialog"
110
- :loading="deleteLoading"
111
- @submit="handleDeleteRole"
112
- >
113
- <template #title>
114
- <span class="font-weight-medium text-h5">Delete Role</span>
115
- </template>
116
-
117
- <template #description>
118
- <p class="text-subtitle-2">
119
- Are you sure you want to delete this role? This action cannot be
120
- undone.
121
- </p>
122
- </template>
109
+ <!--
110
+ Was a `v-toolbar` title bar over a toolbar footer of two block buttons,
111
+ the confirm one on `color="black"` - not a token, so it drew a
112
+ light-theme fill on the dark theme. The shared confirmation shape now.
113
+ Same question, same reassignment rows, same disabled rule.
114
+ -->
115
+ <v-dialog v-model="confirmDialog" max-width="520">
116
+ <v-card class="screen-modal">
117
+ <v-card-title>Delete Role</v-card-title>
118
+
119
+ <v-card-text class="screen-modal__body">
120
+ <span v-if="!hasAssignedMembers">
121
+ Are you sure you want to delete this role? This action cannot be
122
+ undone.
123
+ </span>
124
+ <template v-else>
125
+ <p class="mb-4">
126
+ There are currently
127
+ <span class="text-error font-weight-bold">
128
+ {{ deletionPreview.members.length }}
129
+ {{ deletionPreview.members.length === 1 ? "user" : "users" }}
130
+ </span>
131
+ assigned to this role. Reassign each user before deleting it so
132
+ they do not lose access.
133
+ </p>
134
+
135
+ <v-row
136
+ v-for="member in deletionPreview.members"
137
+ :key="member._id"
138
+ no-gutters
139
+ class="mb-3 pa-3 border rounded-lg"
140
+ align="center"
141
+ >
142
+ <v-col cols="12" sm="5" class="mb-2 mb-sm-0">
143
+ {{ member.name || member.user }}
144
+ </v-col>
145
+ <v-col cols="12" sm="7">
146
+ <v-select
147
+ v-model="memberReassignments[member._id]"
148
+ :items="deletionPreview.replacementRoles"
149
+ item-title="name"
150
+ item-value="_id"
151
+ label="Reassign role"
152
+ density="comfortable"
153
+ hide-details
154
+ class="filter-field"
155
+ />
156
+ </v-col>
157
+ </v-row>
158
+ </template>
159
+ </v-card-text>
123
160
 
124
- <template #footer>
125
- <div class="d-flex justify-center ga-3 w-100">
161
+ <div class="screen-modal__footer">
126
162
  <v-btn
127
- variant="text"
128
- @click="confirmDialog = false"
163
+ class="screen-btn-ghost"
164
+ variant="outlined"
129
165
  :disabled="deleteLoading"
166
+ @click="closeDeleteDialog"
130
167
  >
131
- Close
168
+ Cancel
132
169
  </v-btn>
133
170
  <v-btn
134
- color="primary"
171
+ class="screen-btn-primary"
135
172
  variant="flat"
136
- @click="handleDeleteRole"
137
173
  :loading="deleteLoading"
174
+ :disabled="hasAssignedMembers && !canConfirmDeletion"
175
+ @click="handleDeleteRole"
138
176
  >
139
- Delete Role
177
+ {{ hasAssignedMembers ? "Confirm" : "Delete" }}
140
178
  </v-btn>
141
179
  </div>
142
- </template>
143
- </ConfirmDialog>
180
+ </v-card>
181
+ </v-dialog>
144
182
 
145
183
  <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
146
- </v-row>
184
+ </div>
147
185
  </template>
148
186
 
149
187
  <script setup lang="ts">
188
+ // Relative import: this layer's `utils/` is not auto-imported into consumers.
189
+ import { showSiteField } from "../utils/role";
190
+
150
191
  const props = defineProps({
151
192
  orgId: {
152
193
  type: String,
@@ -160,6 +201,18 @@ const props = defineProps({
160
201
  type: String,
161
202
  default: "",
162
203
  },
204
+ /*
205
+ * Whether the Create-role dialog shows its (read-only) Site field.
206
+ * The dialog defaulted this to `true` but this component never declared or
207
+ * forwarded it, and its root is a `<div>`, so a fallthrough `site-state`
208
+ * attribute landed on that div instead of the dialog - no consuming app
209
+ * could switch the field off. Admin roles are never site-scoped, so the
210
+ * field drew permanently disabled and empty there (QA, admin PR #18).
211
+ */
212
+ siteState: {
213
+ type: Boolean,
214
+ default: true,
215
+ },
163
216
  type: {
164
217
  type: String,
165
218
  required: true,
@@ -259,6 +312,10 @@ function tableRowClickHandler(_: any, data: any) {
259
312
 
260
313
  const createDialog = ref(false);
261
314
 
315
+ const siteFieldShown = computed(() =>
316
+ showSiteField(props.siteState, props.type)
317
+ );
318
+
262
319
  function success() {
263
320
  createDialog.value = false;
264
321
  getRoles();
@@ -367,10 +424,49 @@ async function refreshRolesAfterUpdate(savedPermissions: string[]) {
367
424
  const confirmDialog = ref(false);
368
425
  const selectedRoleId = ref<string | null>(null);
369
426
  const deleteLoading = ref(false);
427
+ const deletionPreview = ref({
428
+ members: [] as Array<Record<string, any>>,
429
+ replacementRoles: [] as Array<Record<string, any>>,
430
+ });
431
+ const memberReassignments = ref<Record<string, string>>({});
370
432
 
371
- function openDeleteDialog(id: string) {
433
+ const hasAssignedMembers = computed(
434
+ () => deletionPreview.value.members.length > 0
435
+ );
436
+
437
+ const canConfirmDeletion = computed(() =>
438
+ deletionPreview.value.members.every(
439
+ (member) => Boolean(memberReassignments.value[member._id])
440
+ )
441
+ );
442
+
443
+ const { getDeletionPreview, deleteWithReassignments } = useRole();
444
+
445
+ async function openDeleteDialog(id: string) {
372
446
  selectedRoleId.value = id;
373
- confirmDialog.value = true;
447
+ deletionPreview.value = { members: [], replacementRoles: [] };
448
+ memberReassignments.value = {};
449
+ deleteLoading.value = true;
450
+
451
+ try {
452
+ const res = await getDeletionPreview(id);
453
+ deletionPreview.value = res.data;
454
+ confirmDialog.value = true;
455
+ } catch (error: any) {
456
+ const errorMessage =
457
+ error?.response?._data?.message || "Failed to load role assignments.";
458
+ showMessage(errorMessage, "error");
459
+ selectedRoleId.value = null;
460
+ } finally {
461
+ deleteLoading.value = false;
462
+ }
463
+ }
464
+
465
+ function closeDeleteDialog() {
466
+ confirmDialog.value = false;
467
+ selectedRoleId.value = null;
468
+ deletionPreview.value = { members: [], replacementRoles: [] };
469
+ memberReassignments.value = {};
374
470
  }
375
471
 
376
472
  function showMessage(msg: string, color: string) {
@@ -383,17 +479,23 @@ async function handleDeleteRole() {
383
479
  if (!selectedRoleId.value) return;
384
480
  deleteLoading.value = true;
385
481
  try {
386
- const res = await deleteRole(selectedRoleId.value);
387
-
388
- confirmDialog.value = false;
482
+ const reassignments = deletionPreview.value.members.map((member) => ({
483
+ memberId: member._id,
484
+ roleId: memberReassignments.value[member._id],
485
+ }));
486
+ const res = hasAssignedMembers.value
487
+ ? await deleteWithReassignments(selectedRoleId.value, reassignments)
488
+ : await deleteRole(selectedRoleId.value);
489
+
490
+ closeDeleteDialog();
389
491
  showMessage(res.message, "success");
390
492
  getRoles();
391
493
  } catch (error: any) {
392
- const errorMessage = error?.response?._data?.message;
494
+ const errorMessage =
495
+ error?.response?._data?.message || "Failed to delete role.";
393
496
  showMessage(errorMessage, "error");
394
497
  } finally {
395
498
  deleteLoading.value = false;
396
- selectedRoleId.value = null;
397
499
  }
398
500
  }
399
501
  </script>