@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,112 +1,93 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
variant="outlined"
|
|
63
|
-
border="thin"
|
|
64
|
-
rounded="lg"
|
|
65
|
-
:loading="loading"
|
|
66
|
-
>
|
|
67
|
-
<div class="sp-table-header">
|
|
68
|
-
<v-toolbar
|
|
69
|
-
density="compact"
|
|
70
|
-
color="grey-lighten-4"
|
|
71
|
-
flat
|
|
72
|
-
class="sp-toolbar-top px-2"
|
|
73
|
-
>
|
|
74
|
-
<v-btn
|
|
75
|
-
icon
|
|
76
|
-
variant="text"
|
|
77
|
-
density="comfortable"
|
|
78
|
-
@click="loadList()"
|
|
79
|
-
>
|
|
80
|
-
<v-icon>mdi-refresh</v-icon>
|
|
81
|
-
</v-btn>
|
|
82
|
-
|
|
83
|
-
<v-spacer />
|
|
84
|
-
|
|
85
|
-
<template v-if="canViewServiceProviders">
|
|
86
|
-
<span class="mr-2 text-caption text-medium-emphasis">{{
|
|
87
|
-
pageRange
|
|
88
|
-
}}</span>
|
|
89
|
-
<local-pagination
|
|
90
|
-
v-model="page"
|
|
91
|
-
:length="pages"
|
|
92
|
-
@update:value="loadList()"
|
|
93
|
-
/>
|
|
94
|
-
</template>
|
|
95
|
-
</v-toolbar>
|
|
96
|
-
|
|
97
|
-
<v-tabs
|
|
98
|
-
v-model="listTab"
|
|
99
|
-
color="primary"
|
|
100
|
-
density="comfortable"
|
|
101
|
-
class="sp-tabs"
|
|
102
|
-
align-tabs="start"
|
|
2
|
+
<div>
|
|
3
|
+
<!--
|
|
4
|
+
THE DESIGN'S PAGE SCAFFOLD (same shape as Members).
|
|
5
|
+
|
|
6
|
+
The screen had no title at all, its primary actions sat top-LEFT, the
|
|
7
|
+
search box floated outside the card, and the refresh + pager sat on one
|
|
8
|
+
row with the tabs stacked on a second row below. The design draws a 22px
|
|
9
|
+
title with the actions on the right of that line, then ONE row inside the
|
|
10
|
+
card: tabs left, search / refresh / count / pager right.
|
|
11
|
+
|
|
12
|
+
Placement only. The same buttons open the same dialogs under the same
|
|
13
|
+
permission conditions, the same three tabs load the same three lists, the
|
|
14
|
+
same search feeds the same debounced `loadList()`, and the table, its
|
|
15
|
+
columns and its row menu are untouched.
|
|
16
|
+
-->
|
|
17
|
+
<PageHeader title="Service Providers">
|
|
18
|
+
<template #actions>
|
|
19
|
+
<!--
|
|
20
|
+
`showBillingButton` defaults to true, so this button rendered in every
|
|
21
|
+
app that mounts this screen - but only property-management has a
|
|
22
|
+
service-provider-mgmt/billing page, so everywhere else the click threw
|
|
23
|
+
on an unknown route name. Render it only where the page exists.
|
|
24
|
+
-->
|
|
25
|
+
<AppButton
|
|
26
|
+
v-if="showBillingButton && billingRouteName"
|
|
27
|
+
variant="ghost"
|
|
28
|
+
@click="useRouter().push({ name: billingRouteName })"
|
|
29
|
+
>
|
|
30
|
+
Billing
|
|
31
|
+
</AppButton>
|
|
32
|
+
<AppButton
|
|
33
|
+
v-if="showAddButton && canAddServiceProvider"
|
|
34
|
+
variant="ghost"
|
|
35
|
+
@click="(createDialog = true), setServiceProvider({ mode: 'add' })"
|
|
36
|
+
>
|
|
37
|
+
Add
|
|
38
|
+
</AppButton>
|
|
39
|
+
<AppButton
|
|
40
|
+
v-if="canInviteServiceProvider"
|
|
41
|
+
@click="
|
|
42
|
+
(createInviteDialog = true), setServiceProvider({ mode: 'invite' })
|
|
43
|
+
"
|
|
44
|
+
>
|
|
45
|
+
Invite
|
|
46
|
+
</AppButton>
|
|
47
|
+
</template>
|
|
48
|
+
</PageHeader>
|
|
49
|
+
|
|
50
|
+
<AppCard class="table-card">
|
|
51
|
+
<CardToolbar :count="canViewServiceProviders ? pageRange : ''">
|
|
52
|
+
<template #tabs>
|
|
53
|
+
<button
|
|
54
|
+
v-for="tab in listTabs"
|
|
55
|
+
:key="tab.value"
|
|
56
|
+
type="button"
|
|
57
|
+
role="tab"
|
|
58
|
+
class="app-tab"
|
|
59
|
+
:class="{ 'app-tab--active': listTab === tab.value }"
|
|
60
|
+
:aria-selected="listTab === tab.value"
|
|
61
|
+
@click="listTab = tab.value"
|
|
103
62
|
>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
63
|
+
{{ tab.label }}
|
|
64
|
+
</button>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<template #right>
|
|
68
|
+
<AppField
|
|
69
|
+
v-model="searchText"
|
|
70
|
+
search
|
|
71
|
+
compact
|
|
72
|
+
placeholder="Search"
|
|
73
|
+
klass="app-toolbar__search"
|
|
74
|
+
/>
|
|
75
|
+
<AppButton
|
|
76
|
+
variant="icon"
|
|
77
|
+
icon="mdi-refresh"
|
|
78
|
+
aria-label="Refresh"
|
|
79
|
+
@click="loadList()"
|
|
80
|
+
/>
|
|
81
|
+
<local-pagination
|
|
82
|
+
v-if="canViewServiceProviders"
|
|
83
|
+
v-model="page"
|
|
84
|
+
:length="pages"
|
|
85
|
+
@update:value="loadList()"
|
|
86
|
+
/>
|
|
87
|
+
</template>
|
|
88
|
+
</CardToolbar>
|
|
89
|
+
|
|
90
|
+
<div class="app-table-scroll">
|
|
110
91
|
<v-data-table
|
|
111
92
|
v-if="canViewServiceProviders"
|
|
112
93
|
:headers="headers"
|
|
@@ -115,39 +96,89 @@
|
|
|
115
96
|
:items-per-page="pageSize"
|
|
116
97
|
fixed-header
|
|
117
98
|
hide-default-footer
|
|
99
|
+
:loading="loading"
|
|
118
100
|
class="sp-table"
|
|
119
101
|
style="max-height: calc(100vh - 280px)"
|
|
120
102
|
>
|
|
121
103
|
<template #item.companyName="{ item }">
|
|
122
|
-
<span class="
|
|
104
|
+
<span class="d-block text-truncate sp-col-company">{{
|
|
123
105
|
item.companyName
|
|
124
106
|
}}</span>
|
|
125
107
|
</template>
|
|
126
108
|
<template #item.email="{ item }">
|
|
127
|
-
<span class="
|
|
109
|
+
<span class="d-block text-truncate sp-col-email">{{
|
|
128
110
|
item.email
|
|
129
111
|
}}</span>
|
|
130
112
|
</template>
|
|
131
113
|
<template #item.type="{ item }">
|
|
132
|
-
<span class="
|
|
114
|
+
<span class="d-block text-truncate sp-col-type">{{
|
|
133
115
|
item.type
|
|
134
116
|
}}</span>
|
|
135
117
|
</template>
|
|
136
118
|
<template #item.members="{ item }">
|
|
137
|
-
<span
|
|
119
|
+
<span>{{ item.members }}</span>
|
|
138
120
|
</template>
|
|
139
121
|
<template #item.status="{ item }">
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
122
|
+
<!--
|
|
123
|
+
Invitation rows use a FILLED chip, not a tonal one. A tonal chip
|
|
124
|
+
paints the status colour as TEXT, and measured on white the
|
|
125
|
+
warning and info tokens come out at 2.4:1 and 3.1:1 — under the
|
|
126
|
+
4.5:1 a 12px label needs. Filled puts the colour in the
|
|
127
|
+
background and lets Vuetify pick a readable foreground for it.
|
|
128
|
+
-->
|
|
129
|
+
<StatusChip
|
|
144
130
|
class="text-none"
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
131
|
+
:status="item.statusRaw || item.status"
|
|
132
|
+
:label="item.status"
|
|
133
|
+
/>
|
|
134
|
+
<!-- Was `text-caption text-medium-emphasis` - Roboto 12px at
|
|
135
|
+
Vuetify's 60% opacity. The design's sub-line is `--muted`. -->
|
|
136
|
+
<div v-if="item.rejectionReason" class="mt-1 sp-reject-reason">
|
|
137
|
+
Seven365 said: {{ item.rejectionReason }}
|
|
138
|
+
</div>
|
|
148
139
|
</template>
|
|
149
140
|
<template #item.actions="{ item }">
|
|
150
|
-
|
|
141
|
+
<!--
|
|
142
|
+
Invitation rows. The menu used to be drawn only for
|
|
143
|
+
`source === 'provider'`, so a pending invitation showed no
|
|
144
|
+
actions at all and the cancel already wired up behind it was
|
|
145
|
+
unreachable.
|
|
146
|
+
-->
|
|
147
|
+
<v-menu v-if="item.source === 'invite'" location="bottom end">
|
|
148
|
+
<template #activator="{ props: menuProps }">
|
|
149
|
+
<v-btn
|
|
150
|
+
v-bind="menuProps"
|
|
151
|
+
icon
|
|
152
|
+
variant="text"
|
|
153
|
+
density="comfortable"
|
|
154
|
+
aria-label="Invitation actions"
|
|
155
|
+
>
|
|
156
|
+
<v-icon>mdi-dots-vertical</v-icon>
|
|
157
|
+
</v-btn>
|
|
158
|
+
</template>
|
|
159
|
+
<v-list density="compact" min-width="220">
|
|
160
|
+
<v-list-item
|
|
161
|
+
title="Cancel invitation"
|
|
162
|
+
:disabled="!canCancelInvite(item)"
|
|
163
|
+
:subtitle="canCancelInvite(item) ? undefined : 'Only while it is still waiting.'"
|
|
164
|
+
@click="openInviteAction(item, 'cancel')"
|
|
165
|
+
/>
|
|
166
|
+
<v-list-item
|
|
167
|
+
title="Resend invitation"
|
|
168
|
+
:disabled="!canResendInvite(item)"
|
|
169
|
+
:subtitle="resendHint(item)"
|
|
170
|
+
@click="openInviteAction(item, 'resend')"
|
|
171
|
+
/>
|
|
172
|
+
<!-- `base-color="error"` is Vuetify's own red (#B00020 light,
|
|
173
|
+
#CF6679 dark), not the design's `--err`. -->
|
|
174
|
+
<v-list-item
|
|
175
|
+
title="Delete invitation"
|
|
176
|
+
class="sp-menu-danger"
|
|
177
|
+
@click="openInviteAction(item, 'delete')"
|
|
178
|
+
/>
|
|
179
|
+
</v-list>
|
|
180
|
+
</v-menu>
|
|
181
|
+
<v-menu v-else-if="item.source === 'provider'" location="bottom end">
|
|
151
182
|
<template #activator="{ props: menuProps }">
|
|
152
183
|
<v-btn
|
|
153
184
|
v-bind="menuProps"
|
|
@@ -167,82 +198,109 @@
|
|
|
167
198
|
/>
|
|
168
199
|
<v-list-item
|
|
169
200
|
:title="rowActionLabel(item)"
|
|
170
|
-
:
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
"
|
|
201
|
+
:class="{
|
|
202
|
+
'sp-menu-danger': !(
|
|
203
|
+
item.source === 'provider' && listTab === 'inactive'
|
|
204
|
+
),
|
|
205
|
+
}"
|
|
175
206
|
@click="onDeleteRow(item)"
|
|
176
207
|
/>
|
|
177
208
|
</v-list>
|
|
178
209
|
</v-menu>
|
|
179
210
|
</template>
|
|
211
|
+
<!--
|
|
212
|
+
"There are none" and "we could not fetch them" are not the same
|
|
213
|
+
sentence, and this table drew the first for both: a failed request
|
|
214
|
+
left `items` at [] and the default no-data row read "No data
|
|
215
|
+
available". That is what hid the Pending tab being unreachable in
|
|
216
|
+
six apps for months. `loadFailed` splits them.
|
|
217
|
+
-->
|
|
218
|
+
<template #no-data>
|
|
219
|
+
<DashboardEmptyState :error="loadFailed" />
|
|
220
|
+
</template>
|
|
180
221
|
</v-data-table>
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div v-if="!canViewServiceProviders" class="pa-4 sp-no-permission">
|
|
225
|
+
You do not have permission to view service providers.
|
|
226
|
+
</div>
|
|
227
|
+
</AppCard>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<!--
|
|
231
|
+
THE MEMBERS DIALOG ON THE DESIGN'S MODAL.
|
|
232
|
+
|
|
233
|
+
Was a `rounded="lg"` v-card whose title was a hand-drawn 20px/700 bar and
|
|
234
|
+
whose table painted its own 64px rows in `#667085` - a literal grey that is
|
|
235
|
+
the same grey on the dark theme, where the card behind it is nearly black.
|
|
236
|
+
It is `.screen-modal` now: the design's radius, shadow and card surface, the
|
|
237
|
+
shared `v-card-title` size/weight, the body as the thing that scrolls, and
|
|
238
|
+
the shared ghost footer instead of a block text button pretending to be one.
|
|
239
|
+
|
|
240
|
+
Same members, same two columns, same loading and empty copy, same close.
|
|
241
|
+
-->
|
|
188
242
|
<v-dialog v-model="viewDialog" max-width="514">
|
|
189
|
-
<v-card
|
|
190
|
-
<v-card-title
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
<tr>
|
|
199
|
-
<th>Name</th>
|
|
200
|
-
<th class="text-right">Role</th>
|
|
201
|
-
</tr>
|
|
202
|
-
</thead>
|
|
203
|
-
<tbody>
|
|
204
|
-
<tr v-if="loadingMembers">
|
|
205
|
-
<td colspan="2" class="text-center text-medium-emphasis">
|
|
206
|
-
Loading...
|
|
207
|
-
</td>
|
|
208
|
-
</tr>
|
|
209
|
-
<tr v-else-if="!serviceProviderMembers.length">
|
|
210
|
-
<td colspan="2" class="text-center text-medium-emphasis">
|
|
211
|
-
No members found.
|
|
212
|
-
</td>
|
|
213
|
-
</tr>
|
|
214
|
-
<template v-else>
|
|
215
|
-
<tr v-for="member in serviceProviderMembers" :key="member._id">
|
|
216
|
-
<td>{{ member.name }}</td>
|
|
217
|
-
<td class="text-right">{{ member.role }}</td>
|
|
243
|
+
<v-card class="screen-modal">
|
|
244
|
+
<v-card-title>Members ({{ memberDialogCount }})</v-card-title>
|
|
245
|
+
|
|
246
|
+
<div class="screen-modal__body sp-members-scroll">
|
|
247
|
+
<v-table class="sp-members-table">
|
|
248
|
+
<thead>
|
|
249
|
+
<tr>
|
|
250
|
+
<th>Name</th>
|
|
251
|
+
<th class="text-right">Role</th>
|
|
218
252
|
</tr>
|
|
219
|
-
</
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
253
|
+
</thead>
|
|
254
|
+
<tbody>
|
|
255
|
+
<tr v-if="loadingMembers">
|
|
256
|
+
<td colspan="2" class="text-center sp-members-note">
|
|
257
|
+
Loading...
|
|
258
|
+
</td>
|
|
259
|
+
</tr>
|
|
260
|
+
<tr v-else-if="!serviceProviderMembers.length">
|
|
261
|
+
<td colspan="2" class="text-center sp-members-note">
|
|
262
|
+
No members found.
|
|
263
|
+
</td>
|
|
264
|
+
</tr>
|
|
265
|
+
<template v-else>
|
|
266
|
+
<tr v-for="member in serviceProviderMembers" :key="member._id">
|
|
267
|
+
<td>{{ member.name }}</td>
|
|
268
|
+
<td class="text-right">{{ member.role }}</td>
|
|
269
|
+
</tr>
|
|
270
|
+
</template>
|
|
271
|
+
</tbody>
|
|
272
|
+
</v-table>
|
|
273
|
+
</div>
|
|
224
274
|
|
|
225
|
-
<
|
|
275
|
+
<div class="screen-modal__footer">
|
|
226
276
|
<v-btn
|
|
227
|
-
|
|
228
|
-
variant="
|
|
229
|
-
height="56"
|
|
230
|
-
class="text-none font-weight-bold"
|
|
277
|
+
class="screen-btn-ghost"
|
|
278
|
+
variant="outlined"
|
|
231
279
|
@click="viewDialog = false"
|
|
232
280
|
>
|
|
233
281
|
Close
|
|
234
282
|
</v-btn>
|
|
235
|
-
</
|
|
283
|
+
</div>
|
|
236
284
|
</v-card>
|
|
237
285
|
</v-dialog>
|
|
238
286
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
<v-card-title class="sp-status-title">
|
|
242
|
-
{{ statusDialogTitle }}
|
|
243
|
-
</v-card-title>
|
|
287
|
+
<!--
|
|
288
|
+
THE TWO CONFIRMATIONS ON THE DESIGN'S MODAL.
|
|
244
289
|
|
|
245
|
-
|
|
290
|
+
Both were a 728px card with an 82px `#f5f5f5` title bar in 28px/700, a
|
|
291
|
+
44px/46px body in 24px type, and two 82px-tall half-width buttons on
|
|
292
|
+
hardcoded `#ffffff` / `#00263a` - none of which is a token, so the whole
|
|
293
|
+
thing drew light-theme colours on the dark theme. They are the shared
|
|
294
|
+
confirmation shape now: `.screen-modal`, the design's title, the body at
|
|
295
|
+
`--fs-cell`, and the ghost-then-primary footer stated once in `screens.css`.
|
|
296
|
+
|
|
297
|
+
Same question, same copy, same labels, same handlers, same busy states.
|
|
298
|
+
-->
|
|
299
|
+
<v-dialog v-model="statusDialog" max-width="520" persistent>
|
|
300
|
+
<v-card class="screen-modal">
|
|
301
|
+
<v-card-title>{{ statusDialogTitle }}</v-card-title>
|
|
302
|
+
|
|
303
|
+
<v-card-text class="screen-modal__body">
|
|
246
304
|
<div class="sp-status-question">
|
|
247
305
|
{{ statusDialogQuestion }}
|
|
248
306
|
</div>
|
|
@@ -251,47 +309,87 @@
|
|
|
251
309
|
</div>
|
|
252
310
|
</v-card-text>
|
|
253
311
|
|
|
254
|
-
<
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
block
|
|
273
|
-
variant="flat"
|
|
274
|
-
height="82"
|
|
275
|
-
class="text-none sp-status-confirm"
|
|
276
|
-
:loading="statusUpdating"
|
|
277
|
-
@click="confirmStatusChange"
|
|
278
|
-
>
|
|
279
|
-
{{ statusConfirmLabel }}
|
|
280
|
-
</v-btn>
|
|
281
|
-
</v-col>
|
|
282
|
-
</v-row>
|
|
283
|
-
</v-card-actions>
|
|
312
|
+
<div class="screen-modal__footer">
|
|
313
|
+
<v-btn
|
|
314
|
+
class="screen-btn-ghost"
|
|
315
|
+
variant="outlined"
|
|
316
|
+
:disabled="statusUpdating"
|
|
317
|
+
@click="closeStatusDialog"
|
|
318
|
+
>
|
|
319
|
+
Cancel
|
|
320
|
+
</v-btn>
|
|
321
|
+
<v-btn
|
|
322
|
+
class="screen-btn-primary"
|
|
323
|
+
variant="flat"
|
|
324
|
+
:loading="statusUpdating"
|
|
325
|
+
@click="confirmStatusChange"
|
|
326
|
+
>
|
|
327
|
+
{{ statusConfirmLabel }}
|
|
328
|
+
</v-btn>
|
|
329
|
+
</div>
|
|
284
330
|
</v-card>
|
|
285
331
|
</v-dialog>
|
|
286
332
|
|
|
333
|
+
<!-- Cancel / Resend / Delete an invitation. Same shape as the provider
|
|
334
|
+
dialog above so the two read as one product. -->
|
|
335
|
+
<v-dialog v-model="inviteDialog" max-width="520" persistent>
|
|
336
|
+
<v-card class="screen-modal">
|
|
337
|
+
<v-card-title>{{ inviteDialogTitle }}</v-card-title>
|
|
338
|
+
|
|
339
|
+
<v-card-text class="screen-modal__body">
|
|
340
|
+
<div class="sp-status-question">
|
|
341
|
+
{{ inviteDialogQuestion }}
|
|
342
|
+
</div>
|
|
343
|
+
<div class="sp-status-copy">
|
|
344
|
+
{{ inviteDialogCopy }}
|
|
345
|
+
</div>
|
|
346
|
+
</v-card-text>
|
|
347
|
+
|
|
348
|
+
<div class="screen-modal__footer">
|
|
349
|
+
<v-btn
|
|
350
|
+
class="screen-btn-ghost"
|
|
351
|
+
variant="outlined"
|
|
352
|
+
:disabled="inviteWorking"
|
|
353
|
+
@click="closeInviteDialog"
|
|
354
|
+
>
|
|
355
|
+
Keep it
|
|
356
|
+
</v-btn>
|
|
357
|
+
<v-btn
|
|
358
|
+
class="screen-btn-primary"
|
|
359
|
+
variant="flat"
|
|
360
|
+
:loading="inviteWorking"
|
|
361
|
+
@click="confirmInviteAction"
|
|
362
|
+
>
|
|
363
|
+
{{ inviteConfirmLabel }}
|
|
364
|
+
</v-btn>
|
|
365
|
+
</div>
|
|
366
|
+
</v-card>
|
|
367
|
+
</v-dialog>
|
|
368
|
+
|
|
369
|
+
<!--
|
|
370
|
+
ADD SERVICE PROVIDER, on the design's modal.
|
|
371
|
+
|
|
372
|
+
The heading was a bare `v-toolbar` - Vuetify paints that from its own
|
|
373
|
+
`surface`, a grey slab across the top of the card and visibly lighter than
|
|
374
|
+
the card on the dark theme - carrying a `text-h5` (24px/500 Roboto). The
|
|
375
|
+
footer was a SECOND toolbar holding a text Cancel and a `color="black"`
|
|
376
|
+
Submit: a literal black fill in both themes. Both are the shared shapes now,
|
|
377
|
+
and the four untokenised fields take `.filter-field`.
|
|
378
|
+
-->
|
|
287
379
|
<v-dialog v-model="createDialog" width="500" persistent>
|
|
288
|
-
<v-card
|
|
289
|
-
<v-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
380
|
+
<v-card class="screen-modal">
|
|
381
|
+
<v-card-title class="d-flex align-center">
|
|
382
|
+
Add Service Provider
|
|
383
|
+
<v-spacer />
|
|
384
|
+
<v-btn
|
|
385
|
+
variant="text"
|
|
386
|
+
icon="mdi-close"
|
|
387
|
+
class="screen-modal__close"
|
|
388
|
+
aria-label="Close"
|
|
389
|
+
@click="setServiceProvider({ mode: 'add', dialog: false })"
|
|
390
|
+
/>
|
|
391
|
+
</v-card-title>
|
|
392
|
+
<v-card-text class="screen-modal__body">
|
|
295
393
|
<v-form
|
|
296
394
|
v-model="validServiceProvider"
|
|
297
395
|
:disabled="disableServiceProvider"
|
|
@@ -305,6 +403,7 @@
|
|
|
305
403
|
v-model="serviceProvider.name"
|
|
306
404
|
density="comfortable"
|
|
307
405
|
:rules="[requiredRule]"
|
|
406
|
+
class="filter-field"
|
|
308
407
|
></v-text-field>
|
|
309
408
|
</v-col>
|
|
310
409
|
</v-row>
|
|
@@ -321,6 +420,7 @@
|
|
|
321
420
|
:items="natureOfBusinessV3"
|
|
322
421
|
density="comfortable"
|
|
323
422
|
:rules="[requiredRule]"
|
|
423
|
+
class="filter-field"
|
|
324
424
|
></v-select>
|
|
325
425
|
</v-col>
|
|
326
426
|
</v-row>
|
|
@@ -334,6 +434,7 @@
|
|
|
334
434
|
v-model="serviceProvider.email"
|
|
335
435
|
density="comfortable"
|
|
336
436
|
:rules="[emailRule, requiredRule]"
|
|
437
|
+
class="filter-field"
|
|
337
438
|
></v-text-field>
|
|
338
439
|
</v-col>
|
|
339
440
|
</v-row>
|
|
@@ -348,6 +449,7 @@
|
|
|
348
449
|
density="comfortable"
|
|
349
450
|
no-resize
|
|
350
451
|
rows="2"
|
|
452
|
+
class="filter-field"
|
|
351
453
|
></v-textarea>
|
|
352
454
|
</v-col>
|
|
353
455
|
</v-row>
|
|
@@ -370,9 +472,10 @@
|
|
|
370
472
|
<v-col cols="12" class="my-2">
|
|
371
473
|
<v-row no-gutters>
|
|
372
474
|
<v-col cols="12" class="text-center">
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
475
|
+
<!-- `text-error` is Vuetify's error colour, not the design's
|
|
476
|
+
`--err`; the two disagree, and only one of them moves
|
|
477
|
+
with the theme. -->
|
|
478
|
+
<span class="sp-form-error">
|
|
376
479
|
{{ messageServiceProvider }}
|
|
377
480
|
</span>
|
|
378
481
|
</v-col>
|
|
@@ -381,50 +484,52 @@
|
|
|
381
484
|
</v-row>
|
|
382
485
|
</v-form>
|
|
383
486
|
</v-card-text>
|
|
384
|
-
<
|
|
385
|
-
<v-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
color="black"
|
|
403
|
-
class="text-none"
|
|
404
|
-
size="small"
|
|
405
|
-
:disabled="!validServiceProvider"
|
|
406
|
-
:loading="disableServiceProvider"
|
|
407
|
-
@click="submitServiceProviderAdd"
|
|
408
|
-
>
|
|
409
|
-
Submit
|
|
410
|
-
</v-btn>
|
|
411
|
-
</v-col>
|
|
412
|
-
</v-row>
|
|
413
|
-
</v-toolbar>
|
|
487
|
+
<div class="screen-modal__footer">
|
|
488
|
+
<v-btn
|
|
489
|
+
class="screen-btn-ghost"
|
|
490
|
+
variant="outlined"
|
|
491
|
+
@click="setServiceProvider({ mode: 'add', dialog: false })"
|
|
492
|
+
>
|
|
493
|
+
Cancel
|
|
494
|
+
</v-btn>
|
|
495
|
+
<v-btn
|
|
496
|
+
class="screen-btn-primary"
|
|
497
|
+
variant="flat"
|
|
498
|
+
:disabled="!validServiceProvider"
|
|
499
|
+
:loading="disableServiceProvider"
|
|
500
|
+
@click="submitServiceProviderAdd"
|
|
501
|
+
>
|
|
502
|
+
Submit
|
|
503
|
+
</v-btn>
|
|
504
|
+
</div>
|
|
414
505
|
</v-card>
|
|
415
506
|
</v-dialog>
|
|
416
507
|
|
|
508
|
+
<!--
|
|
509
|
+
INVITE SERVICE PROVIDER, on the design's modal.
|
|
510
|
+
|
|
511
|
+
Was a `rounded="lg"` card with a 76px toolbar heading in `text-h5`, a body
|
|
512
|
+
padded 48/44/34 whose fields were forced to 64px tall and 20px type, and a
|
|
513
|
+
footer of two 76px square-cornered buttons on hardcoded `#ffffff` /
|
|
514
|
+
`#0b253b` at 20px/700. None of those figures is in the handoff and none of
|
|
515
|
+
those colours is a token. It is the shared modal now, with the same fields
|
|
516
|
+
and the same two actions in the same order.
|
|
517
|
+
-->
|
|
417
518
|
<v-dialog v-model="createInviteDialog" width="580" persistent>
|
|
418
|
-
<v-card
|
|
419
|
-
<v-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
519
|
+
<v-card class="screen-modal">
|
|
520
|
+
<v-card-title class="d-flex align-center">
|
|
521
|
+
Invite Service Provider
|
|
522
|
+
<v-spacer />
|
|
523
|
+
<v-btn
|
|
524
|
+
variant="text"
|
|
525
|
+
icon="mdi-close"
|
|
526
|
+
class="screen-modal__close"
|
|
527
|
+
aria-label="Close"
|
|
528
|
+
@click="setServiceProvider({ mode: 'invite', dialog: false })"
|
|
529
|
+
/>
|
|
530
|
+
</v-card-title>
|
|
531
|
+
|
|
532
|
+
<v-card-text class="screen-modal__body">
|
|
428
533
|
<v-form
|
|
429
534
|
v-model="validServiceProvider"
|
|
430
535
|
:disabled="disableServiceProvider"
|
|
@@ -440,6 +545,7 @@
|
|
|
440
545
|
variant="outlined"
|
|
441
546
|
density="comfortable"
|
|
442
547
|
:rules="[requiredRule, emailRule]"
|
|
548
|
+
class="filter-field"
|
|
443
549
|
></v-text-field>
|
|
444
550
|
</v-col>
|
|
445
551
|
</v-row>
|
|
@@ -465,6 +571,7 @@
|
|
|
465
571
|
readonly
|
|
466
572
|
variant="outlined"
|
|
467
573
|
density="comfortable"
|
|
574
|
+
class="filter-field"
|
|
468
575
|
/>
|
|
469
576
|
</v-col>
|
|
470
577
|
</v-row>
|
|
@@ -488,6 +595,7 @@
|
|
|
488
595
|
variant="outlined"
|
|
489
596
|
density="comfortable"
|
|
490
597
|
:rules="[requiredRule]"
|
|
598
|
+
class="filter-field"
|
|
491
599
|
></v-select>
|
|
492
600
|
</v-col>
|
|
493
601
|
</v-row>
|
|
@@ -505,6 +613,7 @@
|
|
|
505
613
|
variant="outlined"
|
|
506
614
|
density="comfortable"
|
|
507
615
|
:rules="[requiredRule]"
|
|
616
|
+
class="filter-field"
|
|
508
617
|
@update:model-value="setInviteSiteName"
|
|
509
618
|
></v-select>
|
|
510
619
|
</v-col>
|
|
@@ -516,9 +625,10 @@
|
|
|
516
625
|
<v-col cols="12" class="my-2">
|
|
517
626
|
<v-row no-gutters>
|
|
518
627
|
<v-col cols="12" class="text-center">
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
628
|
+
<!-- `text-error` is Vuetify's error colour, not the design's
|
|
629
|
+
`--err`; the two disagree, and only one of them moves
|
|
630
|
+
with the theme. -->
|
|
631
|
+
<span class="sp-form-error">
|
|
522
632
|
{{ messageServiceProvider }}
|
|
523
633
|
</span>
|
|
524
634
|
</v-col>
|
|
@@ -528,35 +638,24 @@
|
|
|
528
638
|
</v-form>
|
|
529
639
|
</v-card-text>
|
|
530
640
|
|
|
531
|
-
<
|
|
532
|
-
<v-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
class="text-none sp-invite-submit-btn"
|
|
550
|
-
height="76"
|
|
551
|
-
:disabled="!canSubmitInvite"
|
|
552
|
-
:loading="disableServiceProvider"
|
|
553
|
-
@click="submitServiceProviderInvite()"
|
|
554
|
-
>
|
|
555
|
-
Submit
|
|
556
|
-
</v-btn>
|
|
557
|
-
</v-col>
|
|
558
|
-
</v-row>
|
|
559
|
-
</v-toolbar>
|
|
641
|
+
<div class="screen-modal__footer">
|
|
642
|
+
<v-btn
|
|
643
|
+
class="screen-btn-ghost"
|
|
644
|
+
variant="outlined"
|
|
645
|
+
@click="setServiceProvider({ mode: 'invite', dialog: false })"
|
|
646
|
+
>
|
|
647
|
+
Back
|
|
648
|
+
</v-btn>
|
|
649
|
+
<v-btn
|
|
650
|
+
class="screen-btn-primary"
|
|
651
|
+
variant="flat"
|
|
652
|
+
:disabled="!canSubmitInvite"
|
|
653
|
+
:loading="disableServiceProvider"
|
|
654
|
+
@click="submitServiceProviderInvite()"
|
|
655
|
+
>
|
|
656
|
+
Submit
|
|
657
|
+
</v-btn>
|
|
658
|
+
</div>
|
|
560
659
|
</v-card>
|
|
561
660
|
</v-dialog>
|
|
562
661
|
|
|
@@ -569,6 +668,7 @@ import useUser from "../composables/useUser";
|
|
|
569
668
|
import useVerification from "../composables/useVerification";
|
|
570
669
|
// import useRole from "../composables/useRole";
|
|
571
670
|
import { errorConverter } from "../utils/data";
|
|
671
|
+
import { pickRouteName } from "../utils/route-name";
|
|
572
672
|
|
|
573
673
|
type ListTab = "active" | "pending" | "inactive";
|
|
574
674
|
|
|
@@ -582,6 +682,11 @@ type TableRow = {
|
|
|
582
682
|
members: string;
|
|
583
683
|
status: string;
|
|
584
684
|
source: "provider" | "invite";
|
|
685
|
+
/** the stored status, for deciding which actions a row may offer */
|
|
686
|
+
statusRaw?: string;
|
|
687
|
+
/** shown under a "Not approved" row, in Seven365's own words */
|
|
688
|
+
rejectionReason?: string;
|
|
689
|
+
siteName?: string;
|
|
585
690
|
};
|
|
586
691
|
|
|
587
692
|
type ServiceProviderMemberRow = {
|
|
@@ -642,6 +747,14 @@ const props = defineProps({
|
|
|
642
747
|
},
|
|
643
748
|
});
|
|
644
749
|
|
|
750
|
+
// Only property-management contributes a service-provider-mgmt/billing page;
|
|
751
|
+
// null in every other consumer, which hides the button instead of throwing.
|
|
752
|
+
const billingRouteName = pickRouteName(
|
|
753
|
+
useRouter(),
|
|
754
|
+
"org-site-service-provider-mgmt-billing",
|
|
755
|
+
"service-provider-mgmt-billing"
|
|
756
|
+
);
|
|
757
|
+
|
|
645
758
|
const headers = [
|
|
646
759
|
{
|
|
647
760
|
title: "Company name",
|
|
@@ -692,6 +805,12 @@ const { requiredRule, emailRule, debounce } = useUtils();
|
|
|
692
805
|
const { getUserByEmail } = useUser();
|
|
693
806
|
|
|
694
807
|
const listTab = ref<ListTab>("active");
|
|
808
|
+
/** The same three tabs, in the same order, with the same labels. */
|
|
809
|
+
const listTabs: Array<{ value: ListTab; label: string }> = [
|
|
810
|
+
{ value: "active", label: "Service Providers" },
|
|
811
|
+
{ value: "pending", label: "Pending" },
|
|
812
|
+
{ value: "inactive", label: "In-Active" },
|
|
813
|
+
];
|
|
695
814
|
const page = ref(1);
|
|
696
815
|
const pages = ref(0);
|
|
697
816
|
const pageRange = ref("-- - -- of --");
|
|
@@ -703,6 +822,8 @@ const messageColor = ref("");
|
|
|
703
822
|
|
|
704
823
|
const items = ref<TableRow[]>([]);
|
|
705
824
|
const loading = ref(false);
|
|
825
|
+
/** The last load failed, as opposed to genuinely returning nothing. */
|
|
826
|
+
const loadFailed = ref(false);
|
|
706
827
|
|
|
707
828
|
const validServiceProvider = ref(false);
|
|
708
829
|
const disableServiceProvider = ref(false);
|
|
@@ -739,6 +860,13 @@ const { getAll: getAllMembers, getAllByUserId } = useMember();
|
|
|
739
860
|
const { getVerifications, cancelUserInvitation, getVerificationByEmail } =
|
|
740
861
|
useVerification();
|
|
741
862
|
|
|
863
|
+
const {
|
|
864
|
+
getInvitations,
|
|
865
|
+
cancelInvitation,
|
|
866
|
+
resendInvitation,
|
|
867
|
+
deleteInvitation,
|
|
868
|
+
} = useServiceProviderInvitation();
|
|
869
|
+
|
|
742
870
|
const existingAccount = ref<any>(null);
|
|
743
871
|
const checkingExistingAccount = ref(false);
|
|
744
872
|
// const { getRoles } = useRole();
|
|
@@ -877,6 +1005,109 @@ function canViewMembers(row: TableRow) {
|
|
|
877
1005
|
return row.source === "provider";
|
|
878
1006
|
}
|
|
879
1007
|
|
|
1008
|
+
// --- invitation actions (Cancel / Resend / Delete) ---------------------------
|
|
1009
|
+
//
|
|
1010
|
+
// Each one confirms first, and each one leaves an audit line on the server —
|
|
1011
|
+
// who did it, when, and what. Delete is a SOFT delete: the row leaves this
|
|
1012
|
+
// list, Seven365 keeps the record and its history.
|
|
1013
|
+
|
|
1014
|
+
type InviteAction = "cancel" | "resend" | "delete";
|
|
1015
|
+
|
|
1016
|
+
const inviteDialog = ref(false);
|
|
1017
|
+
const inviteWorking = ref(false);
|
|
1018
|
+
const inviteActionRow = ref<TableRow | null>(null);
|
|
1019
|
+
const inviteAction = ref<InviteAction>("cancel");
|
|
1020
|
+
|
|
1021
|
+
function canCancelInvite(row: TableRow) {
|
|
1022
|
+
return ["awaiting-approval", "pending"].includes(row.statusRaw ?? "");
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function canResendInvite(row: TableRow) {
|
|
1026
|
+
return (row.statusRaw ?? "") === "pending";
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function resendHint(row: TableRow) {
|
|
1030
|
+
if (canResendInvite(row)) return undefined;
|
|
1031
|
+
return (row.statusRaw ?? "") === "awaiting-approval"
|
|
1032
|
+
? "You can resend once Seven365 has approved it."
|
|
1033
|
+
: "Only an approved invitation can be sent again.";
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
const inviteDialogTitle = computed(() => {
|
|
1037
|
+
if (inviteAction.value === "resend") return "Resend Invitation";
|
|
1038
|
+
if (inviteAction.value === "delete") return "Delete Invitation";
|
|
1039
|
+
return "Cancel Invitation";
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
const inviteDialogQuestion = computed(() => {
|
|
1043
|
+
const row = inviteActionRow.value;
|
|
1044
|
+
const who = row?.companyName && row.companyName !== "-" ? row.companyName : row?.email;
|
|
1045
|
+
const site = row?.siteName ? ` for ${row.siteName}` : "";
|
|
1046
|
+
|
|
1047
|
+
if (inviteAction.value === "resend") {
|
|
1048
|
+
return `Send this invitation again to ${row?.email}?`;
|
|
1049
|
+
}
|
|
1050
|
+
if (inviteAction.value === "delete") {
|
|
1051
|
+
return "Delete this invitation permanently?";
|
|
1052
|
+
}
|
|
1053
|
+
return `Cancel this invitation to ${who}${site}?`;
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
const inviteDialogCopy = computed(() => {
|
|
1057
|
+
if (inviteAction.value === "resend") {
|
|
1058
|
+
return "They will get a fresh message and another 3 days to accept. This does not create a second invitation.";
|
|
1059
|
+
}
|
|
1060
|
+
if (inviteAction.value === "delete") {
|
|
1061
|
+
return "It will disappear from this list. Seven365 keeps the record of who sent it and what happened to it.";
|
|
1062
|
+
}
|
|
1063
|
+
return "They will not be able to accept it. You can send a new one later.";
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
const inviteConfirmLabel = computed(() => {
|
|
1067
|
+
if (inviteAction.value === "resend") return "Resend";
|
|
1068
|
+
if (inviteAction.value === "delete") return "Delete";
|
|
1069
|
+
return "Cancel invitation";
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
function openInviteAction(row: TableRow, action: InviteAction) {
|
|
1073
|
+
if (action === "cancel" && !canCancelInvite(row)) return;
|
|
1074
|
+
if (action === "resend" && !canResendInvite(row)) return;
|
|
1075
|
+
inviteActionRow.value = row;
|
|
1076
|
+
inviteAction.value = action;
|
|
1077
|
+
inviteDialog.value = true;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function closeInviteDialog() {
|
|
1081
|
+
if (inviteWorking.value) return;
|
|
1082
|
+
inviteDialog.value = false;
|
|
1083
|
+
inviteActionRow.value = null;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
async function confirmInviteAction() {
|
|
1087
|
+
const row = inviteActionRow.value;
|
|
1088
|
+
if (!row) return;
|
|
1089
|
+
|
|
1090
|
+
inviteWorking.value = true;
|
|
1091
|
+
try {
|
|
1092
|
+
const run =
|
|
1093
|
+
inviteAction.value === "resend"
|
|
1094
|
+
? resendInvitation
|
|
1095
|
+
: inviteAction.value === "delete"
|
|
1096
|
+
? deleteInvitation
|
|
1097
|
+
: cancelInvitation;
|
|
1098
|
+
|
|
1099
|
+
const result = await run(row._id);
|
|
1100
|
+
showMessage(result?.message ?? "Done.", "success");
|
|
1101
|
+
inviteDialog.value = false;
|
|
1102
|
+
inviteActionRow.value = null;
|
|
1103
|
+
await loadList();
|
|
1104
|
+
} catch (error: any) {
|
|
1105
|
+
showMessage(errorConverter(error), "error");
|
|
1106
|
+
} finally {
|
|
1107
|
+
inviteWorking.value = false;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
880
1111
|
const statusDialog = ref(false);
|
|
881
1112
|
const statusUpdating = ref(false);
|
|
882
1113
|
const statusActionRow = ref<TableRow | null>(null);
|
|
@@ -908,7 +1139,11 @@ function rowActionLabel(row: TableRow) {
|
|
|
908
1139
|
|
|
909
1140
|
function typeDisplay(type: string, nature?: string) {
|
|
910
1141
|
if (nature && String(nature).trim()) return String(nature);
|
|
911
|
-
|
|
1142
|
+
// useLocal returns a plain array; reading `.value` off it was always
|
|
1143
|
+
// undefined, so every row fell through to the raw stored slug
|
|
1144
|
+
// ("security_agency") instead of "Security".
|
|
1145
|
+
const source = natureOfBusinessV3 as any;
|
|
1146
|
+
const list = (Array.isArray(source) ? source : source?.value) as Array<{
|
|
912
1147
|
title?: string;
|
|
913
1148
|
value?: string;
|
|
914
1149
|
}>;
|
|
@@ -924,13 +1159,9 @@ function statusDisplay(raw: string, source: TableRow["source"]) {
|
|
|
924
1159
|
return raw.charAt(0).toUpperCase() + raw.slice(1).toLowerCase();
|
|
925
1160
|
}
|
|
926
1161
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
if (s === "pending") return "warning";
|
|
931
|
-
if (s === "in-active" || s === "inactive") return "default";
|
|
932
|
-
return "primary";
|
|
933
|
-
}
|
|
1162
|
+
/* The status chips read their tone from the shared mapping in
|
|
1163
|
+
`utils/status.ts` now - which carries every raw invitation status this map
|
|
1164
|
+
carried, with the same tone. */
|
|
934
1165
|
|
|
935
1166
|
// async function loadInviteRoles() {
|
|
936
1167
|
// loadingInviteRoles.value = true;
|
|
@@ -1006,7 +1237,12 @@ function mapInviteRows(raw: Record<string, any>[]): TableRow[] {
|
|
|
1006
1237
|
? typeDisplay(row.metadata.app)
|
|
1007
1238
|
: inviteTypeLabel(row.type ?? ""),
|
|
1008
1239
|
members: "-",
|
|
1009
|
-
|
|
1240
|
+
// Every invitation used to read "Pending" whatever had happened to it. It
|
|
1241
|
+
// now says which of the seven states it is actually in, in words.
|
|
1242
|
+
status: row.statusLabel || serviceProviderInviteLabel(row.status),
|
|
1243
|
+
statusRaw: String(row.status ?? ""),
|
|
1244
|
+
rejectionReason: row.rejectionReason ?? "",
|
|
1245
|
+
siteName: row.metadata?.siteName ?? "",
|
|
1010
1246
|
source: "invite",
|
|
1011
1247
|
}));
|
|
1012
1248
|
}
|
|
@@ -1020,18 +1256,31 @@ async function loadList() {
|
|
|
1020
1256
|
// }
|
|
1021
1257
|
|
|
1022
1258
|
loading.value = true;
|
|
1259
|
+
loadFailed.value = false;
|
|
1260
|
+
// A request that never reaches the API can still resolve: a path with no
|
|
1261
|
+
// proxy rule is answered by the SPA fallback with 200 text/html, and ofetch
|
|
1262
|
+
// hands that back as a STRING. `.items` on a string is undefined, so
|
|
1263
|
+
// `?? []` used to turn that into a clean empty table. Anything that is not
|
|
1264
|
+
// an array is a failure, not a zero - say so.
|
|
1265
|
+
const rowsOf = (data: any) => {
|
|
1266
|
+
if (!Array.isArray(data?.items)) {
|
|
1267
|
+
throw new Error("The server did not return a list. Please try again.");
|
|
1268
|
+
}
|
|
1269
|
+
return data.items;
|
|
1270
|
+
};
|
|
1023
1271
|
try {
|
|
1024
1272
|
if (listTab.value === "pending") {
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1273
|
+
// Everything that is not a finished engagement, so a rejection or a
|
|
1274
|
+
// cancellation is visible here instead of the row simply vanishing.
|
|
1275
|
+
const data = await getInvitations({
|
|
1276
|
+
org: props.orgId,
|
|
1277
|
+
status:
|
|
1278
|
+
"awaiting-approval,pending,rejected,declined,cancelled,expired",
|
|
1028
1279
|
page: page.value,
|
|
1029
1280
|
search: searchText.value?.trim() || "",
|
|
1030
|
-
siteId: props.siteId,
|
|
1031
|
-
orgId: props.orgId,
|
|
1032
1281
|
limit: pageSize,
|
|
1033
1282
|
});
|
|
1034
|
-
items.value = mapInviteRows(data
|
|
1283
|
+
items.value = mapInviteRows(rowsOf(data));
|
|
1035
1284
|
pages.value = data.pages ?? 0;
|
|
1036
1285
|
pageRange.value = data.pageRange ?? "-- - -- of --";
|
|
1037
1286
|
return;
|
|
@@ -1045,10 +1294,11 @@ async function loadList() {
|
|
|
1045
1294
|
limit: pageSize,
|
|
1046
1295
|
status: statusParam,
|
|
1047
1296
|
});
|
|
1048
|
-
items.value = mapProviderRows(data
|
|
1297
|
+
items.value = mapProviderRows(rowsOf(data));
|
|
1049
1298
|
pages.value = data.pages ?? 0;
|
|
1050
1299
|
pageRange.value = data.pageRange ?? "-- - -- of --";
|
|
1051
1300
|
} catch (error: any) {
|
|
1301
|
+
loadFailed.value = true;
|
|
1052
1302
|
showMessage(errorConverter(error), "error");
|
|
1053
1303
|
items.value = [];
|
|
1054
1304
|
pages.value = 0;
|
|
@@ -1274,28 +1524,8 @@ async function submitServiceProviderInvite() {
|
|
|
1274
1524
|
</script>
|
|
1275
1525
|
|
|
1276
1526
|
<style scoped>
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
align-items: center;
|
|
1280
|
-
flex-wrap: nowrap;
|
|
1281
|
-
gap: 4px;
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
.sp-table-header {
|
|
1285
|
-
background: #f5f5f5;
|
|
1286
|
-
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
.sp-tabs {
|
|
1290
|
-
width: 100%;
|
|
1291
|
-
min-height: 40px;
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
.sp-tabs :deep(.v-tab) {
|
|
1295
|
-
min-width: 120px;
|
|
1296
|
-
justify-content: flex-start;
|
|
1297
|
-
text-transform: none;
|
|
1298
|
-
}
|
|
1527
|
+
/* The tab strip's own theming went with the strip: the tabs are the card's
|
|
1528
|
+
first row now and take `.app-tab` from the shared primitives. */
|
|
1299
1529
|
|
|
1300
1530
|
.sp-col-company {
|
|
1301
1531
|
max-width: 220px;
|
|
@@ -1309,142 +1539,81 @@ async function submitServiceProviderInvite() {
|
|
|
1309
1539
|
max-width: 180px;
|
|
1310
1540
|
}
|
|
1311
1541
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1542
|
+
/* The header/cell type is `.table-card`'s, stated once in `screens.css`; what
|
|
1543
|
+
was here was a `rgba(0,0,0,0.7)` header that never reached the glass anyway
|
|
1544
|
+
(the shared rule carries `!important`) and would have been a black-on-black
|
|
1545
|
+
header on the dark theme if it had. Only the alignment is kept. */
|
|
1318
1546
|
.sp-table :deep(tbody td) {
|
|
1319
1547
|
vertical-align: middle;
|
|
1320
1548
|
}
|
|
1321
1549
|
|
|
1322
|
-
|
|
1323
|
-
|
|
1550
|
+
/* The design's sub-line: `--muted` at 12px, the same figure batch I's
|
|
1551
|
+
`AttachmentsViewer` settled on. There is no `--fs-sub` token. */
|
|
1552
|
+
.sp-reject-reason,
|
|
1553
|
+
.sp-no-permission,
|
|
1554
|
+
.sp-members-note {
|
|
1555
|
+
font-family: var(--font-sans);
|
|
1556
|
+
font-size: 12px;
|
|
1557
|
+
color: var(--muted);
|
|
1324
1558
|
}
|
|
1325
1559
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1560
|
+
/* A row menu is teleported out of this component's subtree by `v-menu`, so the
|
|
1561
|
+
scoped attribute lands on the item and the title needs `:deep`. */
|
|
1562
|
+
.sp-menu-danger :deep(.v-list-item-title) {
|
|
1563
|
+
color: var(--err);
|
|
1330
1564
|
}
|
|
1331
1565
|
|
|
1332
|
-
.sp-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
min-height: 64px;
|
|
1338
|
-
font-size: 1.25rem;
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
.sp-invite-footer {
|
|
1342
|
-
border-top: 1px solid rgba(0, 0, 0, 0.12);
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
.sp-invite-footer-btn {
|
|
1346
|
-
border-radius: 0;
|
|
1347
|
-
background: #ffffff !important;
|
|
1348
|
-
color: #111827 !important;
|
|
1349
|
-
font-size: 1.25rem;
|
|
1350
|
-
font-weight: 700;
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
.sp-invite-submit-btn {
|
|
1354
|
-
border-radius: 0;
|
|
1355
|
-
background: #0b253b !important;
|
|
1356
|
-
color: #ffffff !important;
|
|
1357
|
-
font-size: 1.25rem;
|
|
1358
|
-
font-weight: 700;
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
.sp-members-card {
|
|
1362
|
-
overflow: hidden;
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
.sp-members-title {
|
|
1366
|
-
min-height: 56px;
|
|
1367
|
-
display: flex;
|
|
1368
|
-
align-items: center;
|
|
1369
|
-
padding: 0 16px;
|
|
1370
|
-
font-size: 1.25rem;
|
|
1371
|
-
font-weight: 700;
|
|
1566
|
+
.sp-form-error {
|
|
1567
|
+
font-family: var(--font-sans);
|
|
1568
|
+
font-size: 12px;
|
|
1569
|
+
font-weight: 500;
|
|
1570
|
+
color: var(--err);
|
|
1372
1571
|
}
|
|
1373
1572
|
|
|
1374
|
-
|
|
1573
|
+
/* The modal body scrolls, not the card - the shared rule; this only bounds it
|
|
1574
|
+
so a long member list does not push the footer off the screen. */
|
|
1575
|
+
.sp-members-scroll {
|
|
1375
1576
|
max-height: 444px;
|
|
1376
|
-
overflow-y: auto;
|
|
1377
1577
|
}
|
|
1378
1578
|
|
|
1379
1579
|
.sp-members-table :deep(th) {
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
font-
|
|
1383
|
-
|
|
1580
|
+
color: var(--muted);
|
|
1581
|
+
font-size: var(--fs-table-head);
|
|
1582
|
+
font-weight: var(--fw-table-head);
|
|
1583
|
+
letter-spacing: var(--ls-table-head);
|
|
1584
|
+
text-transform: uppercase;
|
|
1585
|
+
background: var(--thead);
|
|
1384
1586
|
}
|
|
1385
1587
|
|
|
1386
1588
|
.sp-members-table :deep(td) {
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
font-size: 1rem;
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
.sp-status-card {
|
|
1393
|
-
overflow: hidden;
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
.sp-status-title {
|
|
1397
|
-
min-height: 82px;
|
|
1398
|
-
display: flex;
|
|
1399
|
-
align-items: center;
|
|
1400
|
-
padding: 0 24px;
|
|
1401
|
-
background: #f5f5f5;
|
|
1402
|
-
color: #101828;
|
|
1403
|
-
font-size: 1.75rem;
|
|
1404
|
-
font-weight: 700;
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
.sp-status-body {
|
|
1408
|
-
padding: 44px 46px 42px;
|
|
1589
|
+
color: var(--text);
|
|
1590
|
+
font-size: var(--fs-cell);
|
|
1409
1591
|
}
|
|
1410
1592
|
|
|
1411
1593
|
.sp-status-question {
|
|
1412
|
-
color:
|
|
1413
|
-
font-size:
|
|
1414
|
-
font-weight:
|
|
1594
|
+
color: var(--text);
|
|
1595
|
+
font-size: var(--fs-card-title);
|
|
1596
|
+
font-weight: var(--fw-card-title);
|
|
1415
1597
|
line-height: 1.35;
|
|
1416
1598
|
}
|
|
1417
1599
|
|
|
1418
1600
|
.sp-status-copy {
|
|
1419
|
-
margin-top:
|
|
1420
|
-
color:
|
|
1421
|
-
font-size:
|
|
1422
|
-
line-height: 1.
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
.sp-status-cancel {
|
|
1426
|
-
color: #101828;
|
|
1427
|
-
background: #ffffff;
|
|
1428
|
-
font-size: 1.5rem;
|
|
1429
|
-
font-weight: 700;
|
|
1601
|
+
margin-top: 12px;
|
|
1602
|
+
color: var(--muted);
|
|
1603
|
+
font-size: var(--fs-cell);
|
|
1604
|
+
line-height: 1.5;
|
|
1430
1605
|
}
|
|
1431
1606
|
|
|
1432
|
-
.sp-status-confirm {
|
|
1433
|
-
color: #ffffff;
|
|
1434
|
-
background: #00263a;
|
|
1435
|
-
font-size: 1.5rem;
|
|
1436
|
-
font-weight: 700;
|
|
1437
|
-
}
|
|
1438
1607
|
.sp-existing-title {
|
|
1439
|
-
font-size:
|
|
1440
|
-
font-weight:
|
|
1441
|
-
color:
|
|
1608
|
+
font-size: var(--fs-card-title);
|
|
1609
|
+
font-weight: var(--fw-card-title);
|
|
1610
|
+
color: var(--text);
|
|
1442
1611
|
margin-bottom: 16px;
|
|
1443
1612
|
}
|
|
1444
1613
|
|
|
1445
1614
|
.sp-existing-copy {
|
|
1446
|
-
font-size:
|
|
1447
|
-
color:
|
|
1615
|
+
font-size: var(--fs-cell);
|
|
1616
|
+
color: var(--muted);
|
|
1448
1617
|
line-height: 1.5;
|
|
1449
1618
|
margin-bottom: 24px;
|
|
1450
1619
|
}
|