Redesign Brīdinājumi cards as icon chips with a detail popup
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { createEffect, createMemo, createResource, createSignal, For, Show } from 'solid-js';
|
||||
import { createEffect, createMemo, createResource, createSignal, For, onCleanup, Show } from 'solid-js';
|
||||
import { Portal } from 'solid-js/web';
|
||||
import { AlertTriangle, CloudFog, CloudLightning, CloudRain, CloudSnow, Flame, ThermometerSnowflake, Waves, Wind, X } from 'lucide-solid';
|
||||
import { apiHost } from '../../consts';
|
||||
import { download } from '../../helpers/download';
|
||||
import { LoadingSpinner } from '../../components/spinner/LoadingSpinner';
|
||||
@@ -6,6 +8,24 @@ import { drawWarningMap, type WeatherWarning, warningSeverity } from './warningD
|
||||
import { warningTemplates, type WarningOutput } from './warningTemplates';
|
||||
import './warnings.css';
|
||||
|
||||
function phenomenonIcon(phenomenon: string) {
|
||||
const normalized = (phenomenon || '').toLocaleLowerCase('lv-LV');
|
||||
if (normalized.includes('vēj')) return Wind;
|
||||
if (normalized.includes('pērkon') || normalized.includes('negais')) return CloudLightning;
|
||||
if (normalized.includes('lietus') || normalized.includes('lietav')) return CloudRain;
|
||||
if (normalized.includes('snieg')) return CloudSnow;
|
||||
if (normalized.includes('migl')) return CloudFog;
|
||||
if (normalized.includes('saln') || normalized.includes('sals')) return ThermometerSnowflake;
|
||||
if (normalized.includes('karstum')) return Flame;
|
||||
if (normalized.includes('plūd') || normalized.includes('ūdens')) return Waves;
|
||||
return AlertTriangle;
|
||||
}
|
||||
|
||||
function PhenomenonIcon(props: { phenomenon: string; size?: number }) {
|
||||
const Icon = phenomenonIcon(props.phenomenon);
|
||||
return <Icon size={props.size ?? 18} strokeWidth={1.8}/>;
|
||||
}
|
||||
|
||||
type WarningResponse = { source: string; fetchedAt: string; warnings: WeatherWarning[] };
|
||||
|
||||
const fetchWarnings = async (): Promise<WarningResponse> => {
|
||||
@@ -34,6 +54,16 @@ export function Warnings() {
|
||||
}));
|
||||
const [selectedId, setSelectedId] = createSignal<string>();
|
||||
const selectedWarning = createMemo(() => filteredWarnings().find(item => item.id === selectedId()));
|
||||
const [detailOpen, setDetailOpen] = createSignal(false);
|
||||
const openWarning = (id: string) => { setSelectedId(id); setDetailOpen(true); };
|
||||
const closeDetail = () => setDetailOpen(false);
|
||||
|
||||
createEffect(() => {
|
||||
if (!detailOpen()) return;
|
||||
const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') closeDetail(); };
|
||||
document.addEventListener('keydown', onKeyDown);
|
||||
onCleanup(() => document.removeEventListener('keydown', onKeyDown));
|
||||
});
|
||||
|
||||
const loadArtwork = () => {
|
||||
const current = template();
|
||||
@@ -97,20 +127,31 @@ export function Warnings() {
|
||||
<Show when={data.error}><div class="inlineNotice error"><strong>Brīdinājumu datus neizdevās ielādēt.</strong><button class="secondary" onClick={() => refetch()}>Mēģināt vēlreiz</button></div></Show>
|
||||
<Show when={!data.loading && !data.error && filteredWarnings().length === 0}><div class="inlineNotice">Izvēlētajam periodam nav aktīvu brīdinājumu. Karti joprojām var priekšskatīt, bet tajā nebūs iekrāsotu teritoriju.</div></Show>
|
||||
|
||||
<Show when={filteredWarnings().length > 0}><div class="warningSummary"><For each={filteredWarnings()}>{warning => {
|
||||
const active = () => warning.id === selectedId();
|
||||
return <button type="button" class={`warningCard ${warningSeverity(warning.intensity)}`} classList={{ active: active() }} aria-pressed={active()} onClick={() => setSelectedId(warning.id)}>
|
||||
<Show when={filteredWarnings().length > 0}><div class="warningSummary"><For each={filteredWarnings()}>{warning =>
|
||||
<button type="button" class={`warningCard ${warningSeverity(warning.intensity)}`} classList={{ active: warning.id === selectedId() }} aria-pressed={warning.id === selectedId()} onClick={() => openWarning(warning.id)}>
|
||||
<PhenomenonIcon phenomenon={warning.phenomenon}/>
|
||||
<strong>{warning.phenomenon || 'Brīdinājums'}</strong>
|
||||
<Show when={active()}><div class="warningCardDetails">
|
||||
<span>{warning.intensity} · {warning.regions}</span>
|
||||
<small>{formatPeriod(warning.validFrom, warning.validUntil)}</small>
|
||||
<Show when={warning.description}><p>{warning.description}</p></Show>
|
||||
<Show when={warning.risks}><p class="warningRisks">{warning.risks}</p></Show>
|
||||
</div></Show>
|
||||
</button>;
|
||||
}}</For></div></Show>
|
||||
</button>
|
||||
}</For></div></Show>
|
||||
|
||||
<section class="warningsPreview"><div class="mapToolbar"><div><strong>3. Kartes priekšskatījums</strong><span>{template().width} × {template().height} px eksports</span></div><button class="primary" disabled={data.loading || Boolean(data.error)} onClick={downloadPng}>Lejupielādēt PNG</button></div><div class="canvasFrame"><canvas ref={canvas} width={template().width} height={template().height}/></div></section>
|
||||
|
||||
<Show when={detailOpen() && selectedWarning()}>{warning =>
|
||||
<Portal>
|
||||
<div class="warningBackdrop" onClick={closeDetail}>
|
||||
<div class="warningModal" onClick={event => event.stopPropagation()}>
|
||||
<button type="button" class="warningModalClose" aria-label="Aizvērt" onClick={closeDetail}><X size={20}/></button>
|
||||
<div class={`warningModalHeader ${warningSeverity(warning().intensity)}`}>
|
||||
<PhenomenonIcon phenomenon={warning().phenomenon} size={30}/>
|
||||
<div><strong>{warning().phenomenon || 'Brīdinājums'}</strong><span>{warning().intensity} · {warning().regions}</span></div>
|
||||
</div>
|
||||
<p class="warningModalPeriod">{formatPeriod(warning().validFrom, warning().validUntil)}</p>
|
||||
<Show when={warning().description}><p>{warning().description}</p></Show>
|
||||
<Show when={warning().risks}><p class="warningRisks">{warning().risks}</p></Show>
|
||||
</div>
|
||||
</div>
|
||||
</Portal>
|
||||
}</Show>
|
||||
</section>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1,30 @@
|
||||
.warningsPage{max-width:1900px}.warningsHeading{margin-bottom:18px}.warningsSetup{display:grid;grid-template-columns:260px minmax(0,1fr);gap:28px;padding:18px 0 24px;border-top:1px solid var(--border);border-bottom:1px solid var(--border)}.warningsSetup h2{margin:0 0 14px;font-size:20px}.warningFilterGrid{display:grid;grid-template-columns:220px 260px minmax(320px,1fr);gap:12px}.warningFilterGrid label{display:grid;gap:5px}.warningFilterGrid label span{font-size:12px;color:var(--text-muted)}.warningTitle input{width:100%}.warningSummary{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));align-items:start;gap:10px;margin:18px 0}.warningCard{display:grid;gap:3px;padding:12px 14px;border:1px solid var(--border);border-left-width:7px;border-radius:10px;background:rgba(255,255,255,.6);width:100%;appearance:none;font:inherit;text-align:left;cursor:pointer}.warningCard.yellow{border-left-color:#ffd600}.warningCard.orange{border-left-color:#f27e20}.warningCard.red{border-left-color:#cc1c20}.warningCard span,.warningCard small{color:var(--text-muted)}.warningCard.active{background:rgba(255,255,255,.95);border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-pale)}.warningCardDetails{display:grid;gap:4px;margin-top:6px}.warningCardDetails p{margin:0;font-size:13px;line-height:1.4;color:var(--text-muted)}.warningRisks{font-style:italic}.warningsPreview{padding-top:24px}.warningsPreview canvas{display:block;width:100%;height:auto}.inlineNotice button{margin-left:14px}@media(max-width:1100px){.warningsSetup{grid-template-columns:1fr}.warningFilterGrid{grid-template-columns:repeat(2,minmax(220px,1fr))}.warningTitle{grid-column:1/-1}}@media(max-width:650px){.warningFilterGrid,.resolutionChoices{grid-template-columns:1fr}.warningTitle{grid-column:auto}}
|
||||
.warningsPage{max-width:1900px}.warningsHeading{margin-bottom:18px}.warningsSetup{display:grid;grid-template-columns:260px minmax(0,1fr);gap:28px;padding:18px 0 24px;border-top:1px solid var(--border);border-bottom:1px solid var(--border)}.warningsSetup h2{margin:0 0 14px;font-size:20px}.warningFilterGrid{display:grid;grid-template-columns:220px 260px minmax(320px,1fr);gap:12px}.warningFilterGrid label{display:grid;gap:5px}.warningFilterGrid label span{font-size:12px;color:var(--text-muted)}.warningTitle input{width:100%}.warningsPreview{padding-top:24px}.warningsPreview canvas{display:block;width:100%;height:auto}.inlineNotice button{margin-left:14px}
|
||||
|
||||
.warningSummary{display:flex;flex-wrap:wrap;gap:10px;margin:18px 0}
|
||||
.warningCard{display:flex;align-items:center;gap:9px;flex:0 1 190px;padding:12px 16px;border:1px solid var(--border);border-left-width:7px;border-radius:12px;background:rgba(255,255,255,.6);appearance:none;font:inherit;color:var(--text);text-align:left;cursor:pointer;transition:transform 150ms,box-shadow 150ms,background 150ms}
|
||||
.warningCard:hover{transform:translateY(-2px);box-shadow:var(--shadow-sm)}
|
||||
.warningCard svg{flex:0 0 auto;color:var(--text-muted)}
|
||||
.warningCard strong{font-size:14px}
|
||||
.warningCard.yellow{border-left-color:#ffd600}
|
||||
.warningCard.orange{border-left-color:#f27e20}
|
||||
.warningCard.red{border-left-color:#cc1c20}
|
||||
.warningCard.active{background:rgba(255,255,255,.95);border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-pale)}
|
||||
|
||||
.warningBackdrop{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(10,20,35,.55);backdrop-filter:blur(6px);animation:warningFadeIn 180ms ease}
|
||||
.warningModal{position:relative;width:100%;max-width:640px;max-height:85vh;overflow-y:auto;padding:28px;border-radius:20px;background:var(--surface);box-shadow:var(--shadow-md);animation:warningPopIn 200ms ease}
|
||||
.warningModalClose{position:absolute;top:16px;right:16px;display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:50%;background:var(--surface-strong);color:var(--text-muted);cursor:pointer;transition:background 150ms,color 150ms}
|
||||
.warningModalClose:hover{background:var(--accent-pale);color:var(--accent-strong)}
|
||||
.warningModalHeader{display:flex;align-items:center;gap:14px;padding:0 32px 14px 0;margin-bottom:14px;border-bottom:1px solid var(--border)}
|
||||
.warningModalHeader strong{display:block;font-size:20px}
|
||||
.warningModalHeader span{display:block;margin-top:2px;color:var(--text-muted);font-size:14px}
|
||||
.warningModalHeader.yellow svg{color:#caa000}
|
||||
.warningModalHeader.orange svg{color:#c1580f}
|
||||
.warningModalHeader.red svg{color:#b91c1c}
|
||||
.warningModalPeriod{margin:0 0 14px;color:var(--text-muted);font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:.03em}
|
||||
.warningModal p{margin:0 0 14px;line-height:1.55;white-space:pre-line}
|
||||
.warningRisks{font-style:italic;color:var(--text-muted)}
|
||||
@keyframes warningFadeIn{from{opacity:0}to{opacity:1}}
|
||||
@keyframes warningPopIn{from{opacity:0;transform:translateY(10px) scale(.98)}to{opacity:1;transform:none}}
|
||||
|
||||
@media(max-width:1100px){.warningsSetup{grid-template-columns:1fr}.warningFilterGrid{grid-template-columns:repeat(2,minmax(220px,1fr))}.warningTitle{grid-column:1/-1}}
|
||||
@media(max-width:650px){.warningFilterGrid,.resolutionChoices{grid-template-columns:1fr}.warningTitle{grid-column:auto}}
|
||||
|
||||
Reference in New Issue
Block a user