Email report on every report page; kiosk long-name fix

This commit is contained in:
2026-08-27 11:01:28 -04:00
parent 4dfbf5f8ec
commit 5f069b1a5f
7 changed files with 233 additions and 12 deletions

View File

@@ -4,7 +4,7 @@
<h1>Toner Report</h1>
<div class="header-actions">
<button v-if="!loading && !error" class="btn btn-secondary" @click="exportCSV">Export CSV</button>
<EmailReportButton v-if="!loading && !error" subject="Toner / Supply Report"
<EmailReportButton v-if="!loading && !error && canemail" subject="Toner / Supply Report"
:columns="emailColumns" :rows="emailRows" />
<router-link to="/reports" class="btn btn-secondary">Back to Reports</router-link>
</div>
@@ -133,9 +133,16 @@
import { ref, computed, onMounted } from 'vue'
import { printersApi } from '@/api'
import EmailReportButton from '@/components/EmailReportButton.vue'
import { useAuthStore } from '@/stores/auth'
import LocationMapTooltip from '@/components/LocationMapTooltip.vue'
import { getPrinterHostnameTemplate } from '@/utils/siteSettings'
// Emailing a report is the reports.export permission the endpoint enforces.
// Offering the button to somebody who will only get a 403 is worse than not
// offering it.
const authStore = useAuthStore()
const canemail = computed(() => authStore.hasPermission('reports.export'))
const emailColumns = [
{ key: 'printer', label: 'Printer' },
{ key: 'hostname', label: 'Hostname' },

View File

@@ -50,7 +50,7 @@
@click="act(drive, 'checkin')">
<span class="drive-name">
{{ drive.device_id }}
<small v-if="drive.device_desc">{{ drive.device_desc }}</small>
<small v-if="drive.device_desc" class="drive-desc">{{ drive.device_desc }}</small>
</span>
<span class="drive-action return">Return</span>
</button>
@@ -83,7 +83,7 @@
@click="act(drive, 'checkout')">
<span class="drive-name">
{{ drive.device_id }}
<small v-if="drive.device_desc">{{ drive.device_desc }}</small>
<small v-if="drive.device_desc" class="drive-desc">{{ drive.device_desc }}</small>
<small v-if="drive.locker_location">Locker {{ drive.locker_location }}</small>
</span>
<span class="drive-action take">Take</span>
@@ -112,7 +112,7 @@
@click="act(drive, 'checkin', true)">
<span class="drive-name">
{{ drive.device_id }}
<small>{{ drive.holdername || 'somebody else' }}</small>
<small class="drive-desc">{{ drive.holdername || 'somebody else' }}</small>
</span>
<span class="drive-action return">Return</span>
</button>
@@ -436,8 +436,29 @@ function reset () {
cursor: pointer;
}
.drive-row:disabled { opacity: 0.5; }
.drive-name { display: flex; flex-direction: column; gap: 0.15rem; }
/* The name has to YIELD. A flex item defaults to min-width:auto, so a long
label kept its intrinsic width and shoved Take or Return out through the side
of the card - a drive nobody could take. `anywhere` breaks the labels that
carry no spaces at all, which a serial-style name often does not. */
.drive-name {
display: flex;
flex-direction: column;
gap: 0.15rem;
flex: 1 1 auto;
min-width: 0;
overflow-wrap: anywhere;
}
.drive-name small { color: var(--text-light); font-size: 0.85rem; }
/* Two lines of description is enough to tell two drives apart; more just makes
one card tall and every card in its row tall with it. The locker line below
is never clipped - it is how somebody finds the thing. */
.drive-desc {
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.drive-action {
flex: 0 0 auto;
padding: 0.5rem 1.1rem;

View File

@@ -4,7 +4,7 @@
<h1>Warranty Report</h1>
<div class="header-actions">
<button v-if="!loading" class="btn btn-secondary" @click="exportCSV">Export CSV</button>
<EmailReportButton v-if="!loading" subject="Warranty Report"
<EmailReportButton v-if="!loading && canemail" subject="Warranty Report"
:columns="emailColumns" :rows="emailRows" />
<router-link to="/reports" class="btn btn-secondary">Back to Reports</router-link>
</div>
@@ -65,8 +65,15 @@
import { ref, computed, onMounted } from 'vue'
import { warrantyApi } from '@/api'
import EmailReportButton from '@/components/EmailReportButton.vue'
import { useAuthStore } from '@/stores/auth'
import MachineMapChip from '../components/MachineMapChip.vue'
// Emailing a report is the reports.export permission the endpoint enforces.
// Offering the button to somebody who will only get a 403 is worse than not
// offering it.
const authStore = useAuthStore()
const canemail = computed(() => authStore.hasPermission('reports.export'))
const loading = ref(true)
const counts = ref({})
const buckets = ref({})