Update table/profiles to match the at a glance box
Add edit columns toggle for mobile
This commit is contained in:
fraxle
2026-06-10 13:58:19 +01:00
parent 10452115d7
commit 1857cd9f3b
6 changed files with 108 additions and 36 deletions
+12 -2
View File
@@ -131,6 +131,7 @@ export function UTCIForecast() {
// location. The input then overlays the location line to save space.
const [searchOpen, setSearchOpen] = useState(false);
const searchWrapRef = useRef(null);
const [colTogglesOpen, setColTogglesOpen] = useState(false);
const searchInputRef = useRef(null);
// Focus the input the moment the search opens.
@@ -290,6 +291,7 @@ export function UTCIForecast() {
activeProfile,
outdoorsVariant,
skinType,
visibleCols,
);
// Human-readable date for the "Day at a glance" heading, e.g. "Mon 2nd June 2026".
@@ -629,6 +631,11 @@ export function UTCIForecast() {
<div class="table-main">
<div class="col-toggles">
<span class="col-toggles-label">Columns:</span>
<button class=${'col-toggles-edit-btn' + (colTogglesOpen ? ' col-toggles-edit-btn--open' : '')} onClick=${() => setColTogglesOpen(v => !v)}>
<span class="col-toggles-edit-btn-label">${colTogglesOpen ? 'Done' : 'Edit columns'}</span>
<svg class="col-toggles-edit-btn-icon" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M2 4l4 4 4-4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" fill="none"/></svg>
</button>
<div class=${'col-toggles-body' + (colTogglesOpen ? ' col-toggles-body--open' : '')}>
${(isPro ? (activeProfile === 'custom' || activeProfile === 'showall' || activeCols['utciP']) : activeCols['utciP']) && html`
<span class=${'col-toggle-group' + (visibleCols.utciP ? ' col-toggle-group--expanded' : '')}>
<${CustomSelect}
@@ -791,6 +798,7 @@ export function UTCIForecast() {
Units
</label>
</span>
</div>
</div>
<div class=${`utci-table-wrap${tableCanScrollLeft ? ' scroll-fade-left' : ''}${tableCanScrollRight ? ' scroll-fade-right' : ''}${showUnits ? '' : ' no-units'}`} ref=${tableWrapRef}>
@@ -1013,9 +1021,11 @@ export function UTCIForecast() {
${visibleCols.precip && html`
<td class=${groupStart('precip')} style=${{ background: r.snow > 0 ? scaleBg(r.snow, 0, 4, '90,140,210') : scaleBg(r.precip, 0, 8, '70,145,200') }}>
<span style=${{ display: 'inline-flex', alignItems: 'center', gap: '5px', verticalAlign: 'middle' }}>
<${PrecipIcon} precip=${r.precip} snow=${r.snow} size=${28} />
<${PrecipIcon} precip=${r.precipProb > 0 ? r.precip : 0} snow=${r.precipProb > 0 ? r.snow : 0} size=${28} />
<span style=${{ color: r.snow > 0 ? '#2a5fa8' : r.precip > 0 ? '#2a6a90' : '#7a5c30' }}>
${r.snow > 0 ? fmt(r.snow) + u('cm') : r.precip > 0 ? fmt(r.precip) + u('mm') : '—'}
${r.precipProb > 0
? (r.snow > 0 ? fmt(r.snow) + u('cm') : r.precip > 0 ? fmt(r.precip) + u('mm') : '—')
: '—'}
</span>
</span>
</td>`}
+37 -30
View File
@@ -363,8 +363,9 @@ export function computeWhyFeelsLike(row, env) {
// variant - active sub-variant key e.g. "running", "beach" (or null)
// skinType - Fitzpatrick skin type key for UV burn time
// ------------------------------------------------------------------------
export function computeGlanceSummary(todayRows, profile, variant, skinType) {
export function computeGlanceSummary(todayRows, profile, variant, skinType, cols = null) {
if (!todayRows || todayRows.length === 0) return [];
const show = (key) => !cols || !!cols[key];
const hhmm = (iso) => iso ? iso.slice(11, 16) : null;
const dayRows = todayRows.filter(r => r.elev > 0);
@@ -437,20 +438,20 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType) {
: 'No suitable window',
alert: !fieldWindow,
},
{
...(show('soilT') ? [{
icon: '🌱',
label: 'Soil warms to 10°C by',
value: soilWarmRow ? hhmm(soilWarmRow.iso) : 'Not today',
alert: !soilWarmRow,
},
rainItem,
{
}] : []),
...(show('precipProb') ? [rainItem] : []),
...(show('soilM') ? [{
icon: '💧',
label: 'Soil moisture',
value: moistureLabel(soilM) ?? '-',
alert: soilM != null && (soilM < 0.10 || soilM > 0.50),
},
...(maxPollen >= 10 ? [{
}] : []),
...(show('pollen') && maxPollen >= 10 ? [{
icon: '🌿',
label: 'Grass pollen',
value: pollenLabel(maxPollen),
@@ -465,19 +466,18 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType) {
const dangerFrom = todayRows.find(r => r.vehicleT != null && r.vehicleT >= 35);
return [
{
...(show('vehicleT') ? [{
icon: '🌡',
label: 'Peak cabin temp',
value: peakCabin ? `${Math.round(peakCabin.vehicleT)}° at ${hhmm(peakCabin.iso)}` : '-',
alert: !!(peakCabin && peakCabin.vehicleT >= 35),
},
{
}, {
icon: '🧒',
label: 'Children/pets in car',
value: dangerFrom ? `Unsafe from ${hhmm(dangerFrom.iso)}` : 'Safe all day',
alert: !!dangerFrom,
},
rainItem,
}] : []),
...(show('precipProb') ? [rainItem] : []),
{
icon: '🌤',
label: 'Best travel comfort',
@@ -502,33 +502,33 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType) {
const maxPollen = Math.max(0, ...todayRows.map(r => r.grassPollen ?? 0));
return [
{
...(show('indoorT') ? [{
icon: '🌡',
label: 'Peak indoor (unmanaged)',
value: peakIndoor ? `${Math.round(peakIndoor.indoorT)}° at ${hhmm(peakIndoor.iso)}` : '-',
alert: !!(peakIndoor && peakIndoor.indoorT >= 28),
},
{
}] : []),
...(show('managedT') ? [{
icon: '🌡',
label: 'Peak indoor (managed)',
value: peakManaged ? `${Math.round(peakManaged.managedT)}°` : '-',
alert: !!(peakManaged && peakManaged.managedT >= 28),
},
{
}] : []),
...(show('indoorT') ? [{
icon: '🪟',
label: 'Open windows',
value: ventWindow
? `${hhmm(ventWindow.start.iso)} ${hhmm(ventWindow.end.iso)}`
: 'Keep closed',
alert: false,
},
{
}] : []),
...(show('aqi') ? [{
icon: '💨',
label: 'Air quality',
value: peakAqi > 0 ? aqiLabel(peakAqi) : '-',
alert: peakAqi >= 60,
},
...(maxPollen >= 10 ? [{
}] : []),
...(show('pollen') && maxPollen >= 10 ? [{
icon: '🌼',
label: 'Pollen',
value: pollenLabel(maxPollen),
@@ -558,20 +558,20 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType) {
: 'No cool window today',
alert: !coolWindow,
},
rainItem,
...(burnMins ? [{
...(show('precipProb') ? [rainItem] : []),
...(show('burn') && burnMins ? [{
icon: '☀',
label: 'UV burn time (peak)',
value: burnMins,
alert: !!(peakUvRow && peakUvRow.uv >= 6),
}] : []),
{
...(show('aqi') ? [{
icon: '💨',
label: 'Air quality',
value: peakAqi > 0 ? aqiLabel(peakAqi) : '-',
alert: peakAqi >= 60,
},
...(maxPollen >= 10 ? [{
}] : []),
...(show('pollen') && maxPollen >= 10 ? [{
icon: '🌿',
label: 'Pollen',
value: pollenLabel(maxPollen),
@@ -587,6 +587,7 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType) {
const peakFelt = peakRow('utciAdj');
const peakUvRow = peakRow('uv');
const peakAqi = Math.max(0, ...todayRows.map(r => r.aqi ?? 0));
const maxPollen = Math.max(0, ...todayRows.map(r => r.grassPollen ?? 0));
const burnMins = peakUvRow && peakUvRow.uv > 0
? burnLabel(sunburnMinutes(peakUvRow.uv, skinType))
: null;
@@ -606,18 +607,24 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType) {
value: peakFelt ? `${Math.round(peakFelt.utciAdj)}° at ${hhmm(peakFelt.iso)}` : '-',
alert: !!(peakFelt && (peakFelt.utciAdj >= 32 || peakFelt.utciAdj < 0)),
},
...(burnMins ? [{
...((show('burn') || show('uvA')) && burnMins ? [{
icon: '☀',
label: 'UV burn time (peak)',
value: burnMins,
alert: !!(peakUvRow && peakUvRow.uv >= 6),
}] : []),
rainItem,
{
...(show('precipProb') ? [rainItem] : []),
...(show('aqi') ? [{
icon: '💨',
label: 'Air quality',
value: peakAqi > 0 ? aqiLabel(peakAqi) : '-',
alert: peakAqi >= 60,
},
}] : []),
...(show('pollen') && maxPollen >= 10 ? [{
icon: '🌿',
label: 'Pollen',
value: pollenLabel(maxPollen),
alert: maxPollen >= 50,
}] : []),
];
}
+1 -1
View File
@@ -29,7 +29,7 @@ export const FILTER_PROFILES = {
label: 'Basic',
icon: '🌡️',
scene: "url('assets/images/profiles/basic.png') center / cover no-repeat, linear-gradient(160deg,#b8d8e8,#daeef8)",
cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: false, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false },
cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: false, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: false, pollen: true, rh: true },
},
home: {
label: 'Home',