@7365admin1/layer-common 3.2.1-staging.75 → 3.2.2-staging.100

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 (87) hide show
  1. package/.changeset/notification-preferences.md +17 -0
  2. package/.changeset/pm-sidebar-grouping-and-list-scaffold.md +27 -0
  3. package/CHANGELOG.md +6 -0
  4. package/assets/css/primitives.css +680 -0
  5. package/assets/css/screens.css +541 -0
  6. package/assets/css/shell.css +258 -0
  7. package/assets/css/tokens.css +220 -0
  8. package/components/AccessCardQrTagging.vue +1 -1
  9. package/components/AccessManagement.vue +53 -72
  10. package/components/AppButton.vue +52 -0
  11. package/components/AppCard.vue +20 -0
  12. package/components/AppDateField.vue +35 -0
  13. package/components/AppField.vue +46 -0
  14. package/components/AppKpiTile.vue +65 -0
  15. package/components/AppSegmented.vue +38 -0
  16. package/components/AppSelect.vue +203 -0
  17. package/components/AreaChecklistHistoryMain.vue +6 -2
  18. package/components/AreaMain.vue +9 -17
  19. package/components/AttendanceMain.vue +4 -3
  20. package/components/Avatar/Main.vue +54 -2
  21. package/components/BillingMain.vue +26 -30
  22. package/components/BuildingManagement/buildings.vue +27 -38
  23. package/components/BuildingManagement/units.vue +26 -42
  24. package/components/BulletinBoardManagement.vue +23 -10
  25. package/components/CardToolbar.vue +37 -0
  26. package/components/CleaningScheduleMain.vue +29 -46
  27. package/components/ClientMain.vue +90 -78
  28. package/components/DashboardEmptyState.vue +11 -1
  29. package/components/DashboardMain.vue +285 -207
  30. package/components/DocumentManagement.vue +129 -93
  31. package/components/EntryPassMain.vue +49 -27
  32. package/components/EquipmentItemMain.vue +4 -6
  33. package/components/EquipmentManagementMain.vue +9 -7
  34. package/components/FeedbackMain.vue +35 -49
  35. package/components/FormDialog.vue +60 -21
  36. package/components/HidIntercomManagement.vue +40 -24
  37. package/components/HidQrCodeConfiguration.vue +24 -2
  38. package/components/HidUserEnrollment.vue +68 -31
  39. package/components/InvitationMain.vue +46 -61
  40. package/components/Layout/Header.vue +289 -37
  41. package/components/Layout/NavigationDrawer.vue +299 -30
  42. package/components/ManageChecklistMain.vue +17 -17
  43. package/components/MemberMain.vue +79 -60
  44. package/components/MyAttendanceMain.vue +3 -5
  45. package/components/Nfc/NFCPatrolRouteMain.vue +64 -96
  46. package/components/NotificationSettings.vue +361 -0
  47. package/components/OnlineFormsConfiguration.vue +1 -1
  48. package/components/PageHeader.vue +43 -0
  49. package/components/PassInformation.vue +104 -45
  50. package/components/RolePermissionMain.vue +184 -91
  51. package/components/ScanVisitorQRCode.vue +35 -7
  52. package/components/ScheduleAreaMain.vue +15 -49
  53. package/components/ScheduleTaskMain.vue +23 -26
  54. package/components/ScheduleTastTicketMain.vue +18 -43
  55. package/components/ServiceProviderMain.vue +113 -168
  56. package/components/SiteSettings.vue +21 -8
  57. package/components/SpecificAttr.vue +10 -2
  58. package/components/StatusChip.vue +93 -0
  59. package/components/SwitchContext.vue +33 -1
  60. package/components/TableHygiene.vue +29 -46
  61. package/components/TableList.vue +1 -1
  62. package/components/TableListSecondary.vue +1 -1
  63. package/components/TableMain.vue +161 -77
  64. package/components/TableV3.vue +61 -66
  65. package/components/UnitMain.vue +9 -17
  66. package/components/VehicleManagement.vue +20 -9
  67. package/components/VisitorForm.vue +100 -7
  68. package/components/VisitorManagement.vue +219 -68
  69. package/components/WorkOrder/Main.vue +42 -51
  70. package/composables/useHidAmico.ts +26 -2
  71. package/composables/useNotificationPreference.ts +87 -0
  72. package/composables/useOrg.ts +1 -1
  73. package/composables/useRole.ts +22 -0
  74. package/composables/useSipWebPhone.ts +28 -3
  75. package/composables/useThemePreference.ts +63 -0
  76. package/composables/useVisitor.ts +6 -1
  77. package/nuxt.config.ts +31 -0
  78. package/package.json +3 -2
  79. package/pages/notification-settings.vue +19 -0
  80. package/plugins/vuetify.ts +32 -1
  81. package/types/org.d.ts +2 -0
  82. package/utils/nav-sections.test.ts +190 -0
  83. package/utils/nav-sections.ts +151 -0
  84. package/utils/status.test.ts +77 -0
  85. package/utils/status.ts +90 -0
  86. package/utils/theme.test.ts +325 -7
  87. package/utils/theme.ts +268 -137
@@ -1,46 +1,35 @@
1
1
  <template>
2
2
  <v-row no-gutters>
3
- <v-col cols="12" class="mb-2">
4
- <v-row no-gutters>
5
- <v-btn class="text-none" rounded="pill" variant="tonal" @click="setBuilding()" size="large"
6
- v-if="canCreate && canCreateBuilding">
7
- Add Block
8
- </v-btn>
9
- </v-row>
10
- </v-col>
3
+ <!-- Title row; nav calls this screen "Blocks". The Add pill moves onto it. -->
11
4
  <v-col cols="12">
12
- <v-card width="100%" variant="outlined" border="thin" rounded="lg" :loading="loading">
13
- <v-toolbar density="compact" color="grey-lighten-4">
14
- <template #prepend>
15
- <v-btn fab icon density="comfortable" @click="getBuildings()">
16
- <v-icon>mdi-refresh</v-icon>
17
- </v-btn>
18
- <v-text-field v-model="searchInput" autocomplete="off" placeholder="Search" density="compact" hide-details
19
- dense clearable class="mx-2" style="width: 200px;" />
20
- </template>
21
-
22
- <template #append>
23
- <v-row no-gutters justify="end" align="center">
24
- <span class="mr-2 text-caption text-fontgray">
25
- {{ pageRange }}
26
- </span>
27
- <local-pagination v-model="page" :length="pages" @update:value="getBuildings()" />
28
- </v-row>
29
- </template>
30
- </v-toolbar>
31
-
32
- <v-data-table :headers="headers" :items="items" item-value="_id" items-per-page="20" fixed-header
33
- hide-default-footer @click:row="tableRowClickHandler" style="max-height: calc(100vh - (200px))">
34
- <template #item.createdAt="{ value }">
35
- {{ new Date(value).toLocaleDateString() }}
36
- </template>
37
- <template #item.levels="{ item }">
38
- {{ item.levels.length }}
39
- </template>
40
- </v-data-table>
41
- </v-card>
5
+ <PageHeader title="Blocks">
6
+ <template #actions>
7
+ <AppButton v-if="canCreate && canCreateBuilding" icon="mdi-plus" @click="setBuilding()">
8
+ Add Block
9
+ </AppButton>
10
+ </template>
11
+ </PageHeader>
42
12
  </v-col>
43
13
 
14
+ <!--
15
+ Was `v-card` + a `grey-lighten-4` `v-toolbar` + a bare `v-data-table`.
16
+ `TableMain` draws the same card, the same refresh/search/page-range/pager
17
+ row and the same table - with the design's uppercase header row.
18
+ -->
19
+ <TableMain :headers="headers" :items="items" :loading="loading" :page="page" :pages="pages"
20
+ :pageRange="pageRange" show-header @refresh="getBuildings()"
21
+ @update:page="page = $event; getBuildings()" @row-click="(data: any) => tableRowClickHandler(null, data)">
22
+ <template #prepend-additional>
23
+ <AppField v-model="searchInput" search compact placeholder="Search" klass="app-toolbar__search" />
24
+ </template>
25
+ <template #item.createdAt="{ value }">
26
+ {{ new Date(value).toLocaleDateString() }}
27
+ </template>
28
+ <template #item.levels="{ item }">
29
+ {{ item.levels.length }}
30
+ </template>
31
+ </TableMain>
32
+
44
33
  <!-- Create Dialog -->
45
34
  <v-dialog v-model="createDialog" width="450" persistent>
46
35
  <BuildingForm :site="site" mode="add" :blocks="items" @cancel="createDialog = false" @success="successCreate()"
@@ -1,50 +1,34 @@
1
1
  <template>
2
2
  <v-row no-gutters>
3
- <v-col cols="12" class="mb-2">
4
- <v-row no-gutters>
5
- <v-btn class="text-none" rounded="pill" variant="tonal" @click="setBuilding()" size="large"
6
- v-if="canCreate && canCreateUnit">
7
- Add Unit
8
- </v-btn>
9
- <v-btn class="text-none ml-2" rounded="pill" variant="tonal" @click="openBulkUploadFilePicker()" size="large"
10
- :loading="bulkUploadLoading" v-if="canCreate && canCreateUnit">
11
- <v-icon start>mdi-upload</v-icon>
12
- Bulk Upload
13
- </v-btn>
14
- <input ref="bulkUploadFileInput" type="file" accept=".xlsx,.xls,.csv" class="d-none"
15
- @change="handleBulkUploadFileChange" />
16
- </v-row>
17
- </v-col>
3
+ <!-- Title row; nav calls this screen "Units". Both pills move onto it. -->
18
4
  <v-col cols="12">
19
- <v-card width="100%" variant="outlined" border="thin" rounded="lg" :loading="loading">
20
- <v-toolbar density="compact" color="grey-lighten-4">
21
- <template #prepend>
22
- <v-btn fab icon density="comfortable" @click="getBuildingUnit()">
23
- <v-icon>mdi-refresh</v-icon>
24
- </v-btn>
25
- <v-text-field v-model="searchInput" placeholder="Search" density="compact" hide-details dense clearable
26
- class="mx-2" style="width: 200px;" />
27
- </template>
28
-
29
- <template #append>
30
- <v-row no-gutters justify="end" align="center">
31
- <span class="mr-2 text-caption text-fontgray">
32
- {{ pageRange }}
33
- </span>
34
- <local-pagination v-model="page" :length="pages" @update:value="getBuildingUnit()" />
35
- </v-row>
36
- </template>
37
- </v-toolbar>
38
-
39
- <v-data-table :headers="props.headers" :items="items" item-value="_id" items-per-page="20" fixed-header
40
- hide-default-footer @click:row="tableRowClickHandler" style="max-height: calc(100vh - (200px))">
41
- <template #item.block="{ value, item }">
42
- {{ value ? `Block ${value}` : "" }}
43
- </template>
44
- </v-data-table>
45
- </v-card>
5
+ <PageHeader title="Units">
6
+ <template #actions>
7
+ <AppButton v-if="canCreate && canCreateUnit" variant="ghost" icon="mdi-upload"
8
+ :disabled="bulkUploadLoading" @click="openBulkUploadFilePicker()">
9
+ Bulk Upload
10
+ </AppButton>
11
+ <AppButton v-if="canCreate && canCreateUnit" icon="mdi-plus" @click="setBuilding()">
12
+ Add Unit
13
+ </AppButton>
14
+ </template>
15
+ </PageHeader>
16
+ <input ref="bulkUploadFileInput" type="file" accept=".xlsx,.xls,.csv" class="d-none"
17
+ @change="handleBulkUploadFileChange" />
46
18
  </v-col>
47
19
 
20
+ <!-- Was `v-card` + a `grey-lighten-4` `v-toolbar` + a bare `v-data-table`. -->
21
+ <TableMain :headers="props.headers" :items="items" :loading="loading" :page="page" :pages="pages"
22
+ :pageRange="pageRange" show-header @refresh="getBuildingUnit()"
23
+ @update:page="page = $event; getBuildingUnit()" @row-click="(data: any) => tableRowClickHandler(null, data)">
24
+ <template #prepend-additional>
25
+ <AppField v-model="searchInput" search compact placeholder="Search" klass="app-toolbar__search" />
26
+ </template>
27
+ <template #item.block="{ value, item }">
28
+ {{ value ? `Block ${value}` : "" }}
29
+ </template>
30
+ </TableMain>
31
+
48
32
  <!-- Create Dialog -->
49
33
  <v-dialog v-model="createDialog" :width="smAndUp ? '50svw' : '100%'" persistent>
50
34
  <BuildingUnitFormAdd :site="site" @cancel="createDialog = false" @success="successCreate()"
@@ -1,17 +1,30 @@
1
1
  <template>
2
2
  <v-row no-gutters>
3
+ <!-- Title left, primary action right - the design's opening row. -->
4
+ <v-col cols="12">
5
+ <PageHeader title="Bulletin Board">
6
+ <template #actions>
7
+ <AppButton v-if="canCreateBulletinBoard" icon="mdi-plus" @click="handleCreateEvent">
8
+ Add Announcement
9
+ </AppButton>
10
+ </template>
11
+ </PageHeader>
12
+ </v-col>
13
+
3
14
  <TableMain :headers="headers" :items="paginatePlaceholderItem" v-model:search="searchInput"
4
15
  :loading="getAnnouncementPending" :page="page" :pages="pages" :pageRange="pageRange"
5
- @refresh="getAnnouncementsRefresh" :show-header="APP_CONSTANTS.RESIDENT === props.recipients" @update:page="handleUpdatePage" @row-click="handleRowClick"
6
- @create="handleCreateEvent" :can-create="canCreateBulletinBoard" create-label="Add Announcement">
7
- <template #extension>
8
- <v-row no-gutters class="w-100 d-flex flex-column">
9
- <v-tabs v-model="status" color="primary" :height="40" @update:model-value="toRoute" class="w-100">
10
- <v-tab v-for="tab in tabOptions" :value="tab.status" :key="tab.status" class="text-capitalize">
11
- {{ tab.name }}
12
- </v-tab>
13
- </v-tabs>
14
- </v-row>
16
+ @refresh="getAnnouncementsRefresh" :show-header="APP_CONSTANTS.RESIDENT === props.recipients" @update:page="handleUpdatePage" @row-click="handleRowClick">
17
+ <!--
18
+ The tabs move ONTO the toolbar row (the design draws them there)
19
+ instead of a stacked strip above the table. Same options, same
20
+ order, same `toRoute` - which already sets the status itself.
21
+ -->
22
+ <template #tabs>
23
+ <button v-for="tab in tabOptions" :key="tab.status" type="button" role="tab" class="app-tab"
24
+ :class="{ 'app-tab--active': status === tab.status }" :aria-selected="status === tab.status"
25
+ @click="status = tab.status as any; toRoute(tab.status)">
26
+ {{ tab.name }}
27
+ </button>
15
28
  </template>
16
29
 
17
30
  <template v-slot:item.createdAt="{ item }">
@@ -0,0 +1,37 @@
1
+ <!--
2
+ THE IN-CARD TOP ROW.
3
+
4
+ The design does NOT put a screen's tabs above its card - they are the card's
5
+ own first row, with the pagination on the opposite end of that same line, and
6
+ the tab underline meets the card's rule. Today those tabs sit in a
7
+ `v-toolbar`'s extension slot, which stacks them BELOW a separate bar and
8
+ costs a whole row of height.
9
+
10
+ Two shapes, both from the handoff: with tabs (`0 20px`, the tabs supply the
11
+ vertical padding) and without (`10px 14px`, a refresh button on the left).
12
+ This component only places things - the tabs, the count and the pager are
13
+ passed in and keep their own behaviour.
14
+ -->
15
+ <template>
16
+ <div class="app-toolbar" :class="{ 'app-toolbar--plain': !$slots.tabs }">
17
+ <div v-if="$slots.tabs" class="app-tabs" role="tablist">
18
+ <slot name="tabs" />
19
+ </div>
20
+
21
+ <slot name="left" />
22
+
23
+ <div class="app-toolbar__spacer" />
24
+
25
+ <div class="app-toolbar__right">
26
+ <span v-if="count" class="app-toolbar__count">{{ count }}</span>
27
+ <slot name="right" />
28
+ </div>
29
+ </div>
30
+ </template>
31
+
32
+ <script setup lang="ts">
33
+ defineProps({
34
+ /** The design's `1-1 of 1` range, printed as the screen already computes it. */
35
+ count: { type: String, default: "" },
36
+ });
37
+ </script>
@@ -17,11 +17,7 @@
17
17
  >
18
18
  <template #extension>
19
19
  <v-row no-gutters class="w-100 d-flex flex-column">
20
- <v-card
21
- class="w-100 px-3 d-flex align-center ga-5 py-2"
22
- flat
23
- :height="60"
24
- >
20
+ <div class="filter-bar">
25
21
  <InputDateTimePicker
26
22
  v-model:utc="startDate"
27
23
  density="compact"
@@ -47,7 +43,7 @@
47
43
  item-value="value"
48
44
  label="Status"
49
45
  />
50
- </v-card>
46
+ </div>
51
47
  </v-row>
52
48
  </template>
53
49
 
@@ -60,25 +56,23 @@
60
56
  : ""
61
57
  }}
62
58
  </template>
59
+ <!--
60
+ A countdown, not a status word, so it passes the tone the screen has
61
+ already worked out from the time left rather than a word to map.
62
+ -->
63
63
  <template #item.closeIn="{ item }">
64
- <v-chip
64
+ <StatusChip
65
65
  class="text-capitalize"
66
- variant="flat"
67
- :color="getCloseInColor(item._id)"
68
- pill
69
- >
70
- {{ remainingTime[item._id] || "00h 00m" }}
71
- </v-chip>
66
+ :tone="getCloseInTone(item._id)"
67
+ :label="remainingTime[item._id] || '00h 00m'"
68
+ />
72
69
  </template>
73
70
  <template #item.status="{ value }">
74
- <v-chip
71
+ <StatusChip
75
72
  class="text-capitalize"
76
- :color="getStatusColor(value)"
77
- variant="flat"
78
- pill
79
- >
80
- {{ value || "No Status" }}
81
- </v-chip>
73
+ :status="value"
74
+ :label="value || 'No Status'"
75
+ />
82
76
  </template>
83
77
  <template #item.completedAt="{ value }">
84
78
  {{
@@ -159,33 +153,22 @@ const downloadingId = ref<string | null>(null);
159
153
  const { getCleaningSchedules, downloadChecklistPdf } = useCleaningSchedules();
160
154
  const { formatDate } = useUtils();
161
155
 
162
- const getStatusColor = (status: unknown): string => {
163
- if (!status) return "grey";
164
-
165
- const normalized = String(status).toLowerCase();
166
-
167
- switch (normalized) {
168
- case "open":
169
- return "grey";
170
- case "ongoing":
171
- return "primary";
172
- case "completed":
173
- case "accepted":
174
- return "success";
175
- case "closed":
176
- case "rejected":
177
- return "error";
178
- default:
179
- return "secondary";
180
- }
181
- };
182
-
183
- const getCloseInColor = (id: string): string => {
156
+ /**
157
+ * The status word now picks its own colour, from the one shared mapping in
158
+ * `utils/status.ts` - which carries every word this screen used to switch on,
159
+ * with the tone it used to give it. The screen no longer keeps a private
160
+ * palette that only it agrees with.
161
+ *
162
+ * The countdown is not a status word, so the thresholds stay here; only the
163
+ * NAMES change, from Vuetify colours to the design's tones. Same three bands,
164
+ * same boundaries.
165
+ */
166
+ const getCloseInTone = (id: string): "info" | "warn" | "err" => {
184
167
  const secs = remainingSeconds.value[id];
185
- if (secs === undefined) return "primary";
186
- if (secs <= 0) return "error";
187
- if (secs < 3 * 3600) return "warning";
188
- return "primary";
168
+ if (secs === undefined) return "info";
169
+ if (secs <= 0) return "err";
170
+ if (secs < 3 * 3600) return "warn";
171
+ return "info";
189
172
  };
190
173
 
191
174
  const canDownloadSchedule = computed(() => props.canDownloadSchedule);