2026-08-18 22:31:56 +03:00
|
|
|
|
import moment from "moment";
|
|
|
|
|
|
import { createResource, createSignal, Show } from "solid-js";
|
|
|
|
|
|
import { SelectCity } from "../../components/SelectCity";
|
|
|
|
|
|
import { SelectTimeRange } from "../../components/SelectTimeRange";
|
|
|
|
|
|
import { MapView } from "../../components/map/MapView";
|
|
|
|
|
|
import { MapResolution } from "../../components/map/mapConsts";
|
|
|
|
|
|
import { FETCH_DELAY_MS, apiHost, ResultKeyVal } from "../../consts";
|
|
|
|
|
|
import { isQueryResult, QueryResult } from "../cities/helpers";
|
|
|
|
|
|
import { LoadingSpinner } from "../../components/spinner/LoadingSpinner";
|
|
|
|
|
|
import "./mapGraphics.css";
|
|
|
|
|
|
|
2026-08-18 22:45:14 +03:00
|
|
|
|
export function Faktiska() {
|
2026-08-18 22:31:56 +03:00
|
|
|
|
const now = new Date(new Date().setMinutes(30));
|
|
|
|
|
|
const [cities, setCities] = createSignal<Set<string>>(new Set());
|
|
|
|
|
|
const [start, setStart] = createSignal(moment(now).subtract(1, "day").toDate());
|
|
|
|
|
|
const [end, setEnd] = createSignal(now);
|
|
|
|
|
|
const [field, setField] = createSignal("tempMax");
|
|
|
|
|
|
const [key, setKey] = createSignal("max");
|
|
|
|
|
|
const [resolution, setResolution] = createSignal<"1920x1080" | "3840x1440">("1920x1080");
|
|
|
|
|
|
const [wind, setWind] = createSignal(false);
|
|
|
|
|
|
const [trigger, setTrigger] = createSignal(0);
|
|
|
|
|
|
|
|
|
|
|
|
const fetchData = async () => {
|
|
|
|
|
|
if (!cities().size) return undefined;
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, FETCH_DELAY_MS));
|
|
|
|
|
|
const cityPath = [...cities()].join(",");
|
|
|
|
|
|
const range = `${moment(start()).format("YYYYMMDD_HHmm")}-${moment(end()).format("YYYYMMDD_HHmm")}`;
|
|
|
|
|
|
const response = await fetch(`${apiHost}/api/query/city/${cityPath}/${range}/hour/${field()}/${key()}`);
|
|
|
|
|
|
if (!response.ok) throw new Error(`Query failed (${response.status})`);
|
|
|
|
|
|
return response.json();
|
|
|
|
|
|
};
|
|
|
|
|
|
const [result] = createResource(trigger, fetchData);
|
|
|
|
|
|
const mapType = (): MapResolution => `map_${resolution()}${wind() ? "_wind" : ""}` as MapResolution;
|
|
|
|
|
|
const cityData = (): ResultKeyVal[] => Object.entries((result() as QueryResult).result).map(([city, value]) => [city, typeof value === "number" ? value : -99]);
|
|
|
|
|
|
|
|
|
|
|
|
return <div class="mapGraphicsPage pageWorkspace">
|
2026-08-18 22:45:14 +03:00
|
|
|
|
<div class="pageHeading"><div><span class="eyebrow">Daily newsroom production</span><h1>Faktiskā</h1><p>Create the actual-weather map with temperatures and weather symbols.</p></div><div class="selectionCount">{cities().size} cities selected</div></div>
|
2026-08-18 22:31:56 +03:00
|
|
|
|
<div class="graphicsSetup">
|
2026-08-19 10:38:25 +03:00
|
|
|
|
<aside class="cityPanel workspaceRail"><h2>1. Choose cities</h2><SelectCity getCities={cities} setCities={setCities}/></aside>
|
2026-08-18 22:31:56 +03:00
|
|
|
|
<section class="graphicsMain">
|
2026-08-19 10:38:25 +03:00
|
|
|
|
<div class="productionToolbar">
|
|
|
|
|
|
<div class="productionOptions"><h2>2. Choose output</h2>
|
|
|
|
|
|
<div class="choiceGroup"><span>Output</span><div><button classList={{active: resolution() === "1920x1080"}} onClick={() => setResolution("1920x1080")}>1920 × 1080</button><button classList={{active: resolution() === "3840x1440"}} onClick={() => setResolution("3840x1440")}>3840 × 1440</button><label class="windChoice"><input type="checkbox" checked={wind()} onChange={e => setWind(e.target.checked)}/> Wind layout</label></div></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="dataOptions"><h2>3. Retrieve temperatures</h2><div class="dataOptionsGrid"><SelectTimeRange getStart={start} setStart={setStart} getEnd={end} setEnd={setEnd}/><label><span>Temperature field</span><select value={field()} onChange={e => setField(e.target.value)}><option value="tempMax">Maximum temperature</option><option value="tempMin">Minimum temperature</option><option value="tempAvg">Average temperature</option></select></label><label><span>Calculation</span><select value={key()} onChange={e => setKey(e.target.value)}><option value="max">Maximum</option><option value="min">Minimum</option><option value="avg">Average</option></select></label><button class="primary loadMapButton" disabled={!cities().size} onClick={() => setTrigger(Date.now())}>Load map data</button></div></div>
|
2026-08-18 22:31:56 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
<Show when={result.loading}><LoadingSpinner text="Loading map data"/></Show>
|
|
|
|
|
|
<Show when={result.error}><div class="emptyState"><strong>Could not load map data</strong><span>{result.error.message}</span></div></Show>
|
2026-08-18 22:45:14 +03:00
|
|
|
|
<Show when={result() && isQueryResult(result())}><section class="mapProduction"><h2>4. Add conditions and export</h2><MapView type={mapType()} data={cityData} mode="faktiska"/></section></Show>
|
2026-08-18 22:31:56 +03:00
|
|
|
|
<Show when={!result() && !result.loading && !result.error}><div class="emptyState"><strong>Choose cities and load data</strong><span>The production controls and map preview will appear here.</span></div></Show>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>;
|
|
|
|
|
|
}
|