remote-access protocol collection; dashboard site timezone + badge alignment

This commit is contained in:
2026-08-10 08:36:39 -04:00
parent e68f522132
commit d9d08f53ac
4 changed files with 228 additions and 20 deletions

View File

@@ -237,7 +237,8 @@
<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { notificationsApi, businessUnitsApi, dashboardDefaultsApi } from '@/api'
import { notificationsApi, businessUnitsApi, dashboardDefaultsApi, settingsApi } from '@/api'
import { formatInZone, zonedInputFromUtc, DEFAULT_TZ } from '@/utils/datetime'
import { getFacilityName, getSiteLogo, getServicenowUrls } from '@/utils/siteSettings'
import { withBase } from '@/utils/basePath'
@@ -386,17 +387,30 @@ const upcomingNotifications = computed(() =>
)
// Clock
// Site timezone drives every time on the board. Defaults until the setting
// loads, so a slow settings call shows plausible time rather than blank.
const siteTimezone = ref(DEFAULT_TZ)
const currentTime = ref('')
function updateClock() {
const now = new Date()
currentTime.value = now.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
// The wall clock too: a board showing a different time to the cards beneath
// it is worse than either being wrong on its own.
currentTime.value = formatInZone(new Date(), siteTimezone.value, {
year: undefined, month: undefined, day: undefined,
hour: '2-digit', minute: '2-digit', second: '2-digit'
})
}
onMounted(async () => {
// Before the first clock tick, so the board never renders viewer-zone time.
try {
const response = await settingsApi.get('site_timezone')
const value = response?.data?.data?.value
if (value) siteTimezone.value = value
} catch (e) {
// Keep the default zone; the board must still come up.
}
updateClock()
setInterval(updateClock, 1000)
@@ -528,15 +542,24 @@ function stateKind(item) {
// timestamp alone makes a reader do the arithmetic.
function startsWhen(dateStr) {
if (!dateStr) return 'SOON'
const start = new Date(dateStr)
const time = start.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })
const startday = new Date(start.getFullYear(), start.getMonth(), start.getDate())
const now = new Date()
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
const days = Math.round((startday - today) / 86400000)
const time = formatInZone(dateStr, siteTimezone.value, {
year: undefined, month: undefined, day: undefined,
hour: 'numeric', minute: '2-digit'
})
// TODAY/TOMORROW has to be decided on the SITE's calendar day. Doing the
// arithmetic on browser-local days makes the wording itself wrong for a
// viewer in another zone - a card can read TOMORROW to a remote admin and
// TODAY on the board, from the same data.
const startday = zonedInputFromUtc(dateStr, siteTimezone.value).slice(0, 10)
const todayday = zonedInputFromUtc(new Date(), siteTimezone.value).slice(0, 10)
const days = Math.round(
(new Date(`${startday}T00:00:00`) - new Date(`${todayday}T00:00:00`)) / 86400000)
if (days <= 0) return `TODAY ${time}`
if (days === 1) return `TOMORROW ${time}`
const when = start.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' })
const when = formatInZone(dateStr, siteTimezone.value, {
weekday: 'short', month: 'short', day: 'numeric',
hour: undefined, minute: undefined
})
return `${when} ${time}`
}
@@ -604,17 +627,20 @@ function textOn(backgroundcolor) {
return (0.2126 * r + 0.7152 * g + 0.0722 * b) > 150 ? '#231b00' : '#fff'
}
// SITE zone, not the viewer's. A board hangs on a wall in the plant: it must
// read plant time whatever the machine driving it is set to, and a kiosk with a
// wrong system timezone would otherwise silently show wrong times to the floor.
function formatTime(dateStr) {
if (!dateStr) return ''
return new Date(dateStr).toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit'
return formatInZone(dateStr, siteTimezone.value, {
year: undefined, month: undefined, day: undefined,
hour: '2-digit', minute: '2-digit'
})
}
function formatDateTime(dateStr) {
if (!dateStr) return ''
return new Date(dateStr).toLocaleString('en-US', {
return formatInZone(dateStr, siteTimezone.value, {
weekday: 'short',
month: 'short',
day: 'numeric',
@@ -935,7 +961,9 @@ function handlePhotoError(e) {
font-weight: 700;
letter-spacing: 1px;
text-transform: uppercase;
margin-bottom: 8px;
/* No margin-bottom. .chip-row centres each item's MARGIN box, so a bottom
margin here lifted this chip ~4px and left the state chip beside it
looking low. The row already provides the spacing underneath. */
}
/* Type colour as a bar down the left edge, the way the legacy board marked a