@7365admin1/layer-common 3.2.2-staging.96 → 3.2.2-staging.98

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.
@@ -343,6 +343,31 @@
343
343
  /* The card. */
344
344
  /* ------------------------------------------------------------------ */
345
345
 
346
+ /**
347
+ * THE WIDE-TABLE BLOWOUT.
348
+ *
349
+ * The card sits in a `v-col`, i.e. a flex item, and a flex item's automatic
350
+ * minimum size is its content's minimum - so a table with 8 columns set a
351
+ * ~686px floor the column could not shrink below, and the PAGE scrolled
352
+ * sideways at 360 and 768 instead of the table doing it. `min-width: 0` lets
353
+ * it shrink; the table then scrolls INSIDE the card, which is how the design
354
+ * draws it - the row is deliberately not restacked into key/value blocks,
355
+ * because a column carries meaning by its position. Same decision as
356
+ * `.app-table-scroll` in `primitives.css`.
357
+ *
358
+ * It is on BOTH of this component's own flex boxes - its root `v-row` (which
359
+ * is itself an item of the CALLER's row) and the column holding the card -
360
+ * because the content's minimum walks all the way up the chain, and stopping
361
+ * it at the inner column alone left the page still scrolling sideways.
362
+ */
363
+ .table-card__shrink {
364
+ min-width: 0;
365
+ }
366
+
367
+ .table-card .v-table__wrapper {
368
+ overflow-x: auto;
369
+ }
370
+
346
371
  .table-card {
347
372
  font-family: var(--font-sans);
348
373
  background: var(--card);
@@ -1,87 +1,68 @@
1
1
  <template>
2
2
  <v-row no-gutters>
3
- <v-col cols="12" class="mb-2">
4
- <v-row no-gutters align="center" justify="space-between">
5
- <v-row no-gutters class="ga-2">
6
- <v-btn
7
- class="text-none"
8
- rounded="pill"
9
- variant="tonal"
10
- size="large"
11
- @click="setCard()"
12
- v-if="canCreate && canCreateAccessCard"
13
- >
14
- Add Access Card
15
- </v-btn>
16
- <v-btn
17
- class="text-none"
18
- rounded="pill"
19
- variant="tonal"
20
- size="large"
21
- @click="assignDialog = true"
3
+ <!--
4
+ The design's title row. Was two `tonal` pills and a floating-label search
5
+ floating above the card with no page name at all; the search now sits on
6
+ the card's own toolbar row, where the design puts it. Same models, same
7
+ handlers, same permission gates.
8
+ -->
9
+ <v-col cols="12">
10
+ <!-- Nav calls this screen "Cards" (under Access Mgmt) - the title matches
11
+ the navigation, it is not new copy. -->
12
+ <PageHeader title="Cards">
13
+ <template #actions>
14
+ <AppButton
22
15
  v-if="canUpdateAccessCard && canAssignAccessCard"
16
+ variant="ghost"
17
+ @click="assignDialog = true"
23
18
  >
24
19
  Assign to Unit
25
- </v-btn>
26
- </v-row>
27
-
28
- <v-text-field
29
- v-model="searchText"
30
- placeholder="Search Card, Unit..."
31
- variant="outlined"
32
- density="comfortable"
33
- clearable
34
- hide-details
35
- class="ml-2"
36
- style="max-width: 250px"
37
- />
38
- </v-row>
20
+ </AppButton>
21
+ <AppButton
22
+ v-if="canCreate && canCreateAccessCard"
23
+ icon="mdi-plus"
24
+ @click="setCard()"
25
+ >
26
+ Add Access Card
27
+ </AppButton>
28
+ </template>
29
+ </PageHeader>
39
30
  </v-col>
40
31
  <!-- Available Card Stats -->
41
32
  <v-col cols="12" class="mb-2" v-if="canViewAllCards">
42
33
  <AccessCardAvailableStats ref="statsRef" :site-id="siteId" />
43
34
  </v-col>
44
35
 
45
- <v-col cols="12" v-if="canViewAllCards">
46
- <v-card
47
- width="100%"
48
- variant="outlined"
49
- border="thin"
50
- rounded="lg"
51
- :loading="loading"
52
- >
53
- <v-toolbar density="compact" color="grey-lighten-4">
54
- <template #prepend>
55
- <v-btn fab icon density="comfortable" @click="getCards">
56
- <v-icon>mdi-refresh</v-icon>
57
- </v-btn>
58
- </template>
59
-
60
- <template #append>
61
- <v-row no-gutters justify="end" align="center">
62
- <span class="mr-2 text-caption text-fontgray">
63
- {{ pageRange }}
64
- </span>
65
- <local-pagination
66
- v-model="page"
67
- :length="pages"
68
- @update:value="getCards"
69
- />
70
- </v-row>
71
- </template>
72
- </v-toolbar>
73
- <v-data-table
74
- :headers="headers"
75
- :items="items"
76
- item-value="_id"
77
- items-per-page="10"
78
- fixed-header
79
- hide-default-footer
80
- @click:row="tableRowClickHandler"
81
- style="max-height: calc(100vh - (200px))"
36
+ <!--
37
+ Was `v-card` + a `grey-lighten-4` `v-toolbar` (a white slab that stayed
38
+ white in dark mode) + a bare `v-data-table`. `TableMain` is the same three
39
+ things drawn as the design: same headers, items, page size, refresh,
40
+ page range, pager and row click.
41
+ -->
42
+ <TableMain
43
+ v-if="canViewAllCards"
44
+ :headers="headers"
45
+ :items="items"
46
+ :loading="loading"
47
+ :page="page"
48
+ :pages="pages"
49
+ :pageRange="pageRange"
50
+ :items-per-page="10"
51
+ show-header
52
+ @refresh="getCards"
53
+ @update:page="page = $event; getCards()"
54
+ @row-click="(data: any) => tableRowClickHandler(null, data)"
55
+ >
56
+ <template #prepend-additional>
57
+ <AppField
58
+ v-model="searchText"
59
+ search
60
+ compact
61
+ placeholder="Search Card, Unit..."
62
+ klass="app-toolbar__search"
82
63
  />
83
- </v-card>
84
- </v-col>
64
+ </template>
65
+ </TableMain>
85
66
 
86
67
  <!-- Create Dialog -->
87
68
  <v-dialog v-model="createDialog" width="650" persistent>
@@ -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 }">
@@ -1,85 +1,71 @@
1
1
  <template>
2
2
  <v-row no-gutters>
3
3
  <v-col cols="12">
4
- <v-row no-gutters class="mb-4" align="center">
5
- <v-col cols="12" md="6">
6
- <v-btn
4
+ <!-- Title row; nav calls this screen "Routes". The Add pill moves onto it
5
+ and the search onto the card's toolbar, where the design draws them. -->
6
+ <PageHeader title="Routes">
7
+ <template #actions>
8
+ <AppButton
7
9
  v-if="props.canCreateNfcPatrol"
8
- rounded="pill"
9
- variant="outlined"
10
-
11
- size="large"
12
- class="text-none px-8"
10
+ icon="mdi-plus"
13
11
  @click="dialogAdd = true"
14
12
  >
15
13
  Add Route
16
- </v-btn>
17
- </v-col>
18
- <v-col cols="12" md="6" class="d-flex justify-end">
19
- <v-text-field
14
+ </AppButton>
15
+ </template>
16
+ </PageHeader>
17
+
18
+ <!--
19
+ Was `v-card` + `v-data-table` + a `grey-lighten-4` `v-toolbar` BELOW the
20
+ table holding the page range and pager (a white slab that stayed white in
21
+ dark mode). `TableMain` draws the same card with that row on top, which
22
+ is where the design puts it. Same headers, items, page size, row click
23
+ and item slots; the refresh button is wired to the refresh this screen
24
+ already calls after add/edit.
25
+ -->
26
+ <TableMain
27
+ :headers="headers"
28
+ :items="items"
29
+ :page="page"
30
+ :pages="pages"
31
+ :pageRange="pageRange"
32
+ show-header
33
+ @refresh="getRoutesRefresh()"
34
+ @update:page="page = $event"
35
+ @row-click="(data: any) => handleRowClick(null, data)"
36
+ >
37
+ <template #prepend-additional>
38
+ <AppField
20
39
  v-model="searchInput"
21
- density="comfortable"
40
+ search
41
+ compact
22
42
  placeholder="Search"
23
- clearable
24
- append-inner-icon="mdi-magnify"
25
- hide-details
26
- variant="outlined"
27
- rounded="lg"
28
- style="max-width: 400px"
29
- bg-color="white"
43
+ klass="app-toolbar__search"
30
44
  />
31
- </v-col>
32
- </v-row>
33
-
34
- <v-card width="100%" variant="outlined" border="thin" rounded="lg">
35
-
36
- <v-card-text class="pa-0">
37
- <v-data-table
38
- :headers="headers"
39
- :items="items"
40
- :items-per-page="20"
41
- fixed-header
42
- hide-default-footer
43
- @click:row="handleRowClick"
44
- style="max-height: calc(100vh - 300px)"
45
- class="nfc-route-table"
46
- hover
47
- >
48
- <template #item.numberOfCheckpoints="{ value }">
49
- <div class="text-center">{{ value || "--" }}</div>
50
- </template>
51
-
52
- <template #item.tolerance="{ item }">
53
- <div class="text-center">
54
- <div>{{ item.tolerance ? `${item.tolerance} mins` : "--" }}</div>
55
- <div class="text-grey">{{ item.totalTravelTime ? `${item.totalTravelTime} mins` : "--" }}</div>
56
- </div>
57
- </template>
58
-
59
- <template #item.schedules="{ value }">
60
- <span v-if="Array.isArray(value) && value.length > 0">
61
- {{ value.map(s => `${s.start}-${s.end}`).join(", ") }}
62
- </span>
63
- <span v-else>--</span>
64
- </template>
65
-
66
- <template #item.repeatDays="{ value }">
67
- <span>{{ value || "--" }} </span>
68
- </template>
69
- </v-data-table>
70
- </v-card-text>
71
-
72
- <v-toolbar density="compact" color="grey-lighten-4">
73
- <template #append>
74
- <v-row no-gutters justify="end" align="center">
75
- <span class="mr-2 text-caption text-fontgray">
76
- {{ pageRange }}
77
- </span>
78
- <local-pagination v-model="page" :length="pages" />
79
- </v-row>
80
- </template>
81
- </v-toolbar>
82
- </v-card>
45
+ </template>
46
+
47
+ <template #item.numberOfCheckpoints="{ value }">
48
+ <div class="text-center">{{ value || "--" }}</div>
49
+ </template>
50
+
51
+ <template #item.tolerance="{ item }">
52
+ <div class="text-center">
53
+ <div>{{ item.tolerance ? `${item.tolerance} mins` : "--" }}</div>
54
+ <div class="text-grey">{{ item.totalTravelTime ? `${item.totalTravelTime} mins` : "--" }}</div>
55
+ </div>
56
+ </template>
57
+
58
+ <template #item.schedules="{ value }">
59
+ <span v-if="Array.isArray(value) && value.length > 0">
60
+ {{ value.map((s: any) => `${s.start}-${s.end}`).join(", ") }}
61
+ </span>
62
+ <span v-else>--</span>
63
+ </template>
64
+
65
+ <template #item.repeatDays="{ value }">
66
+ <span>{{ value || "--" }} </span>
67
+ </template>
68
+ </TableMain>
83
69
  </v-col>
84
70
 
85
71
  <!-- Add Dialog -->
@@ -558,27 +544,9 @@ async function submitDelete() {
558
544
  }
559
545
  }
560
546
  </script>
561
-
562
- <style scoped>
563
- .nfc-route-table :deep(.v-data-table__th) {
564
- background-color: rgb(250, 250, 250);
565
- font-weight: 500;
566
- white-space: pre-line;
567
- padding: 16px 12px !important;
568
- border-bottom: 1px solid rgba(0, 0, 0, 0.12);
569
- }
570
-
571
- .nfc-route-table :deep(.v-data-table__td) {
572
- padding: 16px 12px !important;
573
- border-bottom: 1px solid rgba(0, 0, 0, 0.05);
574
- }
575
-
576
- .nfc-route-table :deep(tbody tr:hover) {
577
- background-color: rgba(0, 0, 0, 0.02) !important;
578
- cursor: pointer;
579
- }
580
-
581
- .nfc-route-table :deep(.v-data-table__tr) {
582
- border-bottom: 1px solid rgba(0, 0, 0, 0.05);
583
- }
584
- </style>
547
+ <!--
548
+ The screen's own table CSS is gone with the `v-data-table` it styled: it
549
+ hardcoded an `rgb(250,250,250)` header fill and black-alpha borders, which
550
+ stay light in dark mode. `TableMain` draws the design's header row and
551
+ borders from the theme tokens instead.
552
+ -->
@@ -12,7 +12,10 @@
12
12
  -->
13
13
  <template>
14
14
  <div class="app-page-head">
15
- <div>
15
+ <!-- A caller with actions but no page name (the shared table card, when the
16
+ screen has not given it a title) gets just the action group, at the
17
+ left, rather than an empty heading holding a line of blank space. -->
18
+ <div v-if="title || $slots.title || subtitle || $slots.subtitle">
16
19
  <h1 class="app-page-head__title">
17
20
  <slot name="title">{{ title }}</slot>
18
21
  </h1>
@@ -14,7 +14,7 @@
14
14
  passed its own `no-data` slot.
15
15
  -->
16
16
  <template>
17
- <v-row no-gutters>
17
+ <v-row no-gutters class="table-card__shrink">
18
18
  <!--
19
19
  THE DESIGN'S PAGE TITLE (Phase 9).
20
20
 
@@ -29,57 +29,69 @@
29
29
  Placement only. No caller changes, no slot moves, no behaviour: a screen
30
30
  that passes no title renders exactly what it rendered before.
31
31
  -->
32
- <v-col cols="12" v-if="title">
33
- <PageHeader :title="title" />
34
- </v-col>
32
+ <!--
33
+ The actions used to be drawn on a SECOND row underneath that heading,
34
+ which is not where the design puts them - the design's title row is the
35
+ page name on the left and the screen's actions on the right of the SAME
36
+ line, which is why every screen converted so far had to bypass this
37
+ component and draw its own `PageHeader` above it. They are on the title
38
+ row now, in `PageHeader`'s own `actions` slot.
35
39
 
36
- <!-- Top Actions -->
37
- <v-col cols="12" class="mb-2" v-if="canCreate || $slots.actions">
38
- <v-row no-gutters>
39
- <slot name="actions">
40
- <v-col cols="12">
41
- <v-row no-gutters class="ga-2 align-center table-card__actions">
42
- <!--
43
- The design's primary button: the accent fill that may carry a
44
- white label (`--accent-strong`; white on `--accent` is 3.23:1),
45
- 40px, 10px radius, leading icon. Same click, same label.
46
- -->
47
- <v-btn
48
- v-if="canCreate"
49
- class="text-none table-card__primary"
50
- variant="flat"
51
- prepend-icon="mdi-plus"
52
- @click="emits('create')"
53
- >
54
- {{ createLabel }}
55
- </v-btn>
56
- <v-text-field
57
- v-if="canSearch"
58
- v-model="searchInput"
59
- density="compact"
60
- placeholder="Search"
61
- clearable
62
- max-width="300"
63
- append-inner-icon="mdi-magnify"
64
- hide-details
65
- class="table-card__search"
66
- />
67
- <!-- Ghost/secondary, as the design specifies for Scan QR Code. -->
68
- <v-btn
69
- v-if="canScanVisitorQRCode"
70
- text="Scan QR Code"
71
- class="text-none ml-2 table-card__ghost"
72
- variant="outlined"
73
- @click="emits('scan')"
74
- />
75
- </v-row>
76
- </v-col>
77
- </slot>
78
- </v-row>
40
+ Same buttons, same slot name, same content, same events - one row instead
41
+ of two. A screen with no title still gets its actions at the left, where
42
+ they have always been, because the title block is not drawn at all.
43
+ -->
44
+ <v-col cols="12" v-if="title || canCreate || $slots.actions">
45
+ <PageHeader :title="title">
46
+ <template v-if="canCreate || $slots.actions" #actions>
47
+ <slot name="actions">
48
+ <!--
49
+ The design's primary button: the accent fill that may carry a
50
+ white label (`--accent-strong`; white on `--accent` is 3.23:1),
51
+ 40px, 10px radius, leading icon. Same click, same label.
52
+ -->
53
+ <v-btn
54
+ v-if="canCreate"
55
+ class="text-none table-card__primary"
56
+ variant="flat"
57
+ prepend-icon="mdi-plus"
58
+ @click="emits('create')"
59
+ >
60
+ {{ createLabel }}
61
+ </v-btn>
62
+ <v-text-field
63
+ v-if="canSearch"
64
+ v-model="searchInput"
65
+ density="compact"
66
+ placeholder="Search"
67
+ clearable
68
+ max-width="300"
69
+ append-inner-icon="mdi-magnify"
70
+ hide-details
71
+ class="table-card__search"
72
+ />
73
+ <!-- Ghost/secondary, as the design specifies for Scan QR Code. -->
74
+ <v-btn
75
+ v-if="canScanVisitorQRCode"
76
+ text="Scan QR Code"
77
+ class="text-none table-card__ghost"
78
+ variant="outlined"
79
+ @click="emits('scan')"
80
+ />
81
+ </slot>
82
+ </template>
83
+ </PageHeader>
79
84
  </v-col>
80
85
 
81
86
  <!-- Table Card -->
82
- <v-col cols="12">
87
+ <!--
88
+ `table-card__shrink` is the fix for the wide-table blowout: this column is a
89
+ flex item, and a flex item's automatic minimum size is its CONTENT's, so
90
+ a table with enough columns set a floor the column would not shrink below
91
+ and the whole page scrolled sideways at 360 and 768. The table scrolls
92
+ inside the card instead - see `.table-card__shrink` in `screens.css`.
93
+ -->
94
+ <v-col cols="12" class="table-card__shrink">
83
95
  <!--
84
96
  THE DESIGN'S CARD AND ITS IN-CARD TOP ROW (Phase 9).
85
97
 
@@ -105,6 +117,17 @@
105
117
 
106
118
  <!-- Toolbar -->
107
119
  <CardToolbar :count="pageRange">
120
+ <!--
121
+ The design draws a screen's tabs ON the toolbar row, not on a
122
+ stacked strip above the table. `CardToolbar` has always had the
123
+ slot; `TableMain` simply never forwarded it, so callers put their
124
+ `v-tabs` in `extension` and got a second row. Purely additive: a
125
+ caller that passes no `tabs` renders exactly what it rendered.
126
+ -->
127
+ <template v-if="$slots.tabs" #tabs>
128
+ <slot name="tabs" />
129
+ </template>
130
+
108
131
  <template #left>
109
132
  <v-btn fab icon density="comfortable" variant="text" @click="emits('refresh')">
110
133
  <v-icon>mdi-refresh</v-icon>
@@ -1,17 +1,28 @@
1
1
  <template>
2
2
  <v-row no-gutters>
3
+ <!--
4
+ The design's title row: page name left, primary action right. Was a
5
+ `tonal` pill drawn by `TableMain` on its own row under the (absent)
6
+ heading. Same handler, same permission gate.
7
+ -->
8
+ <v-col cols="12">
9
+ <PageHeader title="Vehicle Mgmt">
10
+ <template #actions>
11
+ <AppButton v-if="canCreateVehicle" icon="mdi-plus" @click="handleAddVehicleClick">
12
+ Add Vehicle
13
+ </AppButton>
14
+ </template>
15
+ </PageHeader>
16
+ </v-col>
17
+
3
18
  <TableMain :headers="headers" :items="items" :loading="loading || getVehiclesPending" :page="page" :pages="pages"
4
- :pageRange="pageRange" :extension-height="70" :canCreate="canCreateVehicle" @refresh="getVehiclesRefresh"
5
- createLabel="Add Vehicle" show-header @row-click="handleRowClick" @create="handleAddVehicleClick"
19
+ :pageRange="pageRange" @refresh="getVehiclesRefresh"
20
+ show-header @row-click="handleRowClick"
6
21
  @update:page="handleUpdatePage">
7
22
 
8
- <template #extension>
9
- <v-row no-gutters class="px-5 py-1 d-flex align-center justify-end ga-3">
10
- <v-text-field v-model="searchInput" density="compact" placeholder="Search" clearable max-width="300"
11
- append-inner-icon="mdi-magnify" hide-details />
12
- <!-- <v-select v-model="vehicleTypeFilter" density="compact" item-title="label" item-value="value"
13
- placeholder="Filter by Type" clearable max-width="200" hide-details :items="typeOptions" /> -->
14
- </v-row>
23
+ <!-- Search joins the toolbar row rather than stacking a second one. -->
24
+ <template #prepend-additional>
25
+ <AppField v-model="searchInput" search compact placeholder="Search" klass="app-toolbar__search" />
15
26
  </template>
16
27
 
17
28
  <template #item.block="{ value }">
@@ -1,50 +1,28 @@
1
1
  <template>
2
2
  <v-row no-gutters>
3
- <TableMain
4
- :headers="headers"
5
- :items="items"
6
- :loading="getVisitorPending"
7
- :page="page"
8
- :pages="pages"
9
- :extension-height="110"
10
- :offset="300"
11
- :pageRange="pageRange"
12
- @refresh="getVisitorRefresh"
13
- @update:page="handleUpdatePage"
14
- show-header
15
- @row-click="handleRowClick"
16
- >
17
- <template #actions>
18
- <v-row no-gutters>
19
- <v-btn
20
- v-if="canAddVisitor"
21
- class="text-none"
22
- rounded="pill"
23
- variant="tonal"
24
- size="large"
25
- @click="handleAddNew"
26
- text="Add Visitor"
27
- />
28
- <v-btn
3
+ <!--
4
+ THE DESIGN'S PAGE TITLE ROW (Phase 9).
5
+
6
+ Drawn here rather than through `TableMain`'s `title` prop because the
7
+ design puts the screen's actions on the title line, and `TableMain`
8
+ renders its `actions` slot as a separate row under the heading. Same
9
+ three buttons, same handlers, same permission gates - moved, not changed.
10
+ -->
11
+ <v-col cols="12">
12
+ <PageHeader title="Visitor Mgmt">
13
+ <template #actions>
14
+ <AppButton
29
15
  v-if="canScanVisitorQRCode"
30
- text="Scan QR Code"
31
- class="text-none ml-2"
32
- rounded="pill"
33
- variant="tonal"
34
- size="large"
16
+ variant="ghost"
35
17
  @click="dialog.scanVisitorQRCode = true"
36
- />
18
+ >
19
+ Scan QR Code
20
+ </AppButton>
37
21
  <v-menu>
38
22
  <template #activator="{ props }">
39
- <v-btn
40
- v-bind="props"
41
- text="Download Report"
42
- class="text-none ml-2"
43
- prepend-icon="mdi-download"
44
- rounded="pill"
45
- variant="tonal"
46
- size="large"
47
- />
23
+ <AppButton v-bind="props" variant="ghost" icon="mdi-download">
24
+ Download Report
25
+ </AppButton>
48
26
  </template>
49
27
  <v-list>
50
28
  <v-list-item
@@ -59,8 +37,27 @@
59
37
  />
60
38
  </v-list>
61
39
  </v-menu>
62
- </v-row>
63
- </template>
40
+ <AppButton v-if="canAddVisitor" icon="mdi-plus" @click="handleAddNew">
41
+ Add Visitor
42
+ </AppButton>
43
+ </template>
44
+ </PageHeader>
45
+ </v-col>
46
+
47
+ <TableMain
48
+ :headers="headers"
49
+ :items="items"
50
+ :loading="getVisitorPending"
51
+ :page="page"
52
+ :pages="pages"
53
+ :extension-height="110"
54
+ :offset="300"
55
+ :pageRange="pageRange"
56
+ @refresh="getVisitorRefresh"
57
+ @update:page="handleUpdatePage"
58
+ show-header
59
+ @row-click="handleRowClick"
60
+ >
64
61
  <template #extension>
65
62
  <v-row no-gutters class="w-100 d-flex flex-column">
66
63
  <v-tabs
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "3.2.2-staging.96",
5
+ "version": "3.2.2-staging.98",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {