Kiosk: dashboard-matching state-colour borders, AC top-right + TRV row layout

- kiosk-block border-left now uses the same st-vacant/booked/heating_up/
  occupied/cooling_down colour scheme as the Dashboard's ZoneCard
- Layout tweak (user, 2026-08-26): a room only ever has one AC unit, so
  it's pinned top-right; lower half is a row of up to 3 TRVs with setpoint
  stacked below the icon rather than beside it (more legible at kiosk touch
  size than the dashboard's compact rad-row)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-08-26 10:57:31 +00:00
parent b6352cf5f7
commit 65d53d1d41
2 changed files with 44 additions and 28 deletions

View file

@ -32,38 +32,31 @@ interface Props {
}
// One tile per ROOM/ZONE — same idea as the Dashboard's ZoneCard, not one tile
// per device. A room with both a TRV and an AC unit shows both in one tile, via
// the exact same rad-row/ac-block markup+CSS ZoneCard.tsx uses, so the kiosk
// visually matches the staff dashboard (per user correction, 2026-08-26, to an
// earlier per-device-tile version of this file).
// per device (per user correction, 2026-08-26). Border colour is the room's
// live state, same st-* classes/vars ZoneCard uses, so a tile reads the same
// way on the kiosk as on the staff dashboard. Layout differs from ZoneCard
// deliberately (user, 2026-08-26): a room only ever has one AC unit, so it's
// pinned top-right, leaving the lower half for a row of up to 3 TRVs (setpoint
// stacked below the icon rather than beside it — more legible at a glance/touch
// size than the dashboard's compact side-by-side rad-row).
export default function KioskZoneBlock({ zone, liveDevices, selectionMode, selected, onTap }: Props) {
const trvs = liveDevices
.filter(d => d.device_type === 'shelly_trv')
.sort((a, b) => locationRank(a.location) - locationRank(b.location))
.slice(0, 3)
const ac = liveDevices.find(d => KIOSK_AC_DEVICE_TYPES.includes(d.device_type))
const acOn = ac?.power_state === 'on'
const ModeIcon = MODE_ICONS[ac?.current_mode || ''] || Fan
const fanLevel = ac ? (MHI_FAN_SPEEDS as readonly string[]).indexOf(ac.fan_speed || '') + 1 : 0
return (
<div className={`kiosk-block${selected ? ' selected' : ''}`} onClick={onTap} role="button">
<div className={`kiosk-block st-${zone.room_state}${selected ? ' selected' : ''}`} onClick={onTap} role="button">
{selectionMode && (
<div className="kiosk-block-check">{selected && <Check size={14} strokeWidth={2.5} />}</div>
)}
<div className="kiosk-block-zone">{zone.name}</div>
<div className="kiosk-block-body">
{trvs.length > 0 && (
<div className="rad-row">
{trvs.slice(0, 3).map(d => (
<div key={d.id} className={`rad-icon ${d.heating_active ? 'heating' : 'idle'}`}>
<Heater size={16} strokeWidth={1.75} />
<span>{fmtTemp(d.current_target_temp)}</span>
</div>
))}
</div>
)}
<div className="kiosk-block-top">
<span className="kiosk-block-zone">{zone.name}</span>
{ac && (
<div className={`ac-block${acOn ? '' : ' off'}`}>
{ac.remote_locked && <Lock size={12} strokeWidth={1.75} className="ac-lock" />}
@ -76,9 +69,20 @@ export default function KioskZoneBlock({ zone, liveDevices, selectionMode, selec
<div className="ac-temp">{fmtTemp(ac.current_target_temp)} / {fmtTemp(ac.current_room_temp)}</div>
</div>
)}
{trvs.length === 0 && !ac && <span className="muted" style={{ fontSize: 11.5 }}>No live devices</span>}
</div>
{trvs.length > 0 ? (
<div className="kiosk-trv-row">
{trvs.map(d => (
<div key={d.id} className={`kiosk-trv${d.heating_active ? ' heating' : ''}`}>
<Heater size={18} strokeWidth={1.75} />
<span>{fmtTemp(d.current_target_temp)}</span>
</div>
))}
</div>
) : !ac ? (
<span className="muted" style={{ fontSize: 11.5 }}>No live devices</span>
) : null}
</div>
)
}