Application and warranty uploads, toner forecast, fiscal week

This commit is contained in:
2026-08-12 12:07:14 -04:00
parent d32513727b
commit 4319f6bc8e
3 changed files with 83 additions and 2 deletions

View File

@@ -12,6 +12,34 @@ ADR-007 and ADR-002.
### Added
- Applications accept an uploaded image and an uploaded installer, rather than
only a path typed into a text box. The path field kept working only while
someone maintained a share by hand, and a link to a file nobody could still
find was indistinguishable from a link to one that worked. The installer
download is gated on `applications.view` because it is licensed vendor
software; the image is public, since tiles render before login.
- Warranty records accept an uploaded proof document - the invoice or
certificate that actually settles a claim. The documentation path stays as it
was: it points at a vendor portal or a share, which an upload cannot replace.
Downloads require `warranty.view`, since a proof carries pricing and a service
tag, and it comes back under the vendor's own filename so it is recognisable
in a support ticket.
- Toner forecast report: days until each cartridge runs out, soonest first,
from Zabbix level history. Ordering by days left rather than by level is the
point - a cartridge at 60% draining fast needs ordering before one sitting at
8% that has not moved in a month. It also counts how many cartridges each
printer has been through in the window, which is the number a purchasing
conversation asks for. Printers with no honest estimate are listed
separately with the reason (too little history, gauge has not moved, replaced
recently) instead of being sorted in as zero or as safe, and an unreachable
Zabbix says so rather than rendering an empty table that reads as "nothing is
due".
- Fiscal week under the site name in the sidebar and on the shop-floor board,
as the classic ASP site showed it. People quote a week number across both
sites, so the arithmetic is a port of the old one (ISO 8601: week 1 contains
4 January, the week's Thursday decides the year) rather than a fresh
interpretation.
- Backup history distinguishes CAPTURED from LAST VERIFIED. Captured is when a
configuration was written; verified is when a PC last posted it and the hash
still matched. They differ by design, because an unchanged config writes no

View File

@@ -4,6 +4,11 @@
<div class="sidebar-header">
<img :src="siteLogo" alt="Site logo" class="sidebar-logo" />
<h1>{{ facilityName }}</h1>
<!-- Under the site name, where the classic site put it. -->
<div class="fiscal-week">
<span class="fiscal-week-label">Fiscal Week</span>
<span class="fiscal-week-number">{{ currentFiscalWeek }}</span>
</div>
</div>
<div class="sidebar-search">
@@ -98,7 +103,7 @@
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import ToastHost from '../components/ToastHost.vue'
import {
@@ -109,6 +114,7 @@ import {
import { useAuthStore } from '../stores/auth'
import { currentTheme, toggleTheme } from '../stores/theme'
import { dashboardApi, notificationsApi } from '../api'
import { fiscalWeek } from '@/utils/fiscalWeek'
import { getFacilityName, getSiteLogo, getServicenowUrls } from '../utils/siteSettings'
import { withBase } from '../utils/basePath'
@@ -137,6 +143,11 @@ const searchQuery = ref('')
const navItems = ref([])
const activeNotifications = ref([])
const facilityName = ref('ShopDB')
// A tab left open overnight would otherwise show last week's number until
// somebody reloaded. Recomputed on a timer rather than only at mount.
const currentFiscalWeek = ref(fiscalWeek())
let fiscalWeekTimer = null
const siteLogo = ref(withBase('/ge-aerospace-logo.svg'))
const servicenowConfig = ref({ enabled: true, searchUrl: '' })
@@ -222,6 +233,10 @@ function buildNavItems(items) {
}
onMounted(async () => {
// Half-hourly: cheap, and it crosses midnight into the new week without a
// reload on a screen nobody touches.
fiscalWeekTimer = setInterval(() => { currentFiscalWeek.value = fiscalWeek() }, 30 * 60 * 1000)
getFacilityName().then(name => { facilityName.value = name })
getSiteLogo().then(logo => { siteLogo.value = logo })
getServicenowUrls().then(config => { servicenowConfig.value = config })
@@ -274,9 +289,32 @@ function onAvatarError(event) {
event.target.src = fallbackAvatar
event.target.classList.add('ge-avatar-fallback')
}
onUnmounted(() => {
if (fiscalWeekTimer) clearInterval(fiscalWeekTimer)
})
</script>
<style scoped>
/* Fiscal week, under the site name, where the classic site put it. The number
carries the weight; the label is small because it never changes. */
.fiscal-week {
margin-top: 0.35rem;
text-align: center;
line-height: 1.15;
}
.fiscal-week-label {
display: block;
font-size: 0.62rem;
letter-spacing: 0.08em;
text-transform: uppercase;
opacity: 0.65;
}
.fiscal-week-number {
display: block;
font-size: 1.2rem;
font-weight: 700;
}
/* Footer user block: identity row on top, compact icon actions below, all
kept inside the fixed-width sidebar (no full-width buttons overflowing). */
.user-menu { display: flex; flex-direction: column; gap: 0.6rem; }
@@ -305,4 +343,4 @@ function onAvatarError(event) {
text-decoration: none;
}
.icon-btn:hover { background: rgba(255,255,255,0.16); color: #fff; }
</style>
</style>

View File

@@ -9,6 +9,9 @@
<div class="header-center">
<div class="location-title">{{ facilityName }}</div>
<h1>Shopfloor Dashboard</h1>
<!-- Same number the sidebar and the classic site show, since the board
is what people read it off from across the floor. -->
<div class="fiscal-week">Fiscal Week {{ currentFiscalWeek }}</div>
</div>
<div class="header-right">
@@ -241,9 +244,12 @@ import { notificationsApi, businessUnitsApi, dashboardDefaultsApi, settingsApi }
import { formatInZone, zonedInputFromUtc, DEFAULT_TZ } from '@/utils/datetime'
import { getFacilityName, getSiteLogo, getServicenowUrls } from '@/utils/siteSettings'
import { withBase } from '@/utils/basePath'
import { fiscalWeek } from '@/utils/fiscalWeek'
const loading = ref(true)
const facilityName = ref('ShopDB')
// A wall display runs for weeks; the clock tick refreshes this too.
const currentFiscalWeek = ref(fiscalWeek())
const siteLogo = ref(withBase('/ge-aerospace-logo.svg'))
// Employee photo placeholder: the shipped GE monogram (square avatar). Kept
// separate from siteLogo so a blank/broken site_logo setting never leaves an
@@ -716,6 +722,15 @@ function handlePhotoError(e) {
text-align: center;
}
.fiscal-week {
font-size: 18px;
font-weight: 600;
letter-spacing: 2px;
color: #b8c4d8;
margin-top: 4px;
text-transform: uppercase;
}
.location-title {
font-size: 18px;
font-weight: 600;