Compare commits

..

24 Commits

Author SHA1 Message Date
David Fencl 5894c51256 chore: update water data sets and migrate docker configuration to directory-based structure
continuous-integration/drone/push Build encountered an error
2026-06-13 22:51:47 +02:00
David Fencl a1a1685ae3 feat: update water monitoring datasets with recent sensor data points 2026-06-13 15:14:13 +02:00
David Fencl 62d69fbb1e chore: update lake datasets, add new monitoring locations, and introduce docker-compose infrastructure 2026-06-13 13:09:26 +02:00
David Fencl c8fe97078d commit 2026-06-09 20:45:08 +02:00
David Fencl c4cad149ea feat: update lake data and optimize weather widget rendering 2026-06-09 20:27:07 +02:00
David Fencl 4939d1c5dc feat: update lake water data and optimize visual components for real-time monitoring 2026-06-08 22:32:10 +02:00
David Fencl 8fe39b7ab0 feat: update water level datasets and improve Tooltip component responsiveness 2026-06-08 21:30:34 +02:00
David Fencl cdb653d660 feat: update lake water data and refine WindChart component functionality 2026-06-08 21:10:29 +02:00
David Fencl 48b44cd642 feat: update historical lake sensor data and improve wind chart component rendering 2026-06-08 20:49:01 +02:00
David Fencl 7a7abdd3e5 feat: update lake water data, implement service worker and manifest, and add favicon 2026-06-08 20:06:23 +02:00
David Fencl f8a7be7fa3 feat: implement sensor glitch detection for water levels and update data cleaning logic 2026-06-08 19:45:37 +02:00
David Fencl 62c861e610 feat: add rivers overview component and sync lake volume data across the dataset 2026-06-08 19:36:54 +02:00
David Fencl ec540e056d feat: implement weather radar component and update water resource data records. before river 2026-06-06 21:04:19 +02:00
David Fencl 231961da19 feat: add disclaimer modal, update lake data, and improve component interactions 2026-06-06 20:35:47 +02:00
David Fencl a67a2247c3 feat: import new reservoir data, add lake management scripts, and update overview UI components
continuous-integration/drone/push Build encountered an error
2026-06-06 20:14:36 +02:00
David Fencl cf05e844d8 feat: update water level metrics and optimize sidebar UI layout 2026-06-06 18:38:18 +02:00
David Fencl 6395df1992 feat: implement multilingual SEO support and enhance map UI with data synchronization updates 2026-06-06 17:24:30 +02:00
David Fencl 66021e001e refactor: remove unused lake JSON files and truncate excessive historical data in index files 2026-06-06 12:41:42 +02:00
David Fencl db1aadcc8d feat: add automatic data polling, conditional search visibility, and extended scraper functionality for monthly lake records 2026-06-06 12:34:20 +02:00
David Fencl dbb22e7972 refactor: centralize lake metrics calculations into a utility module with comprehensive unit tests 2026-06-06 11:45:56 +02:00
David Fencl 6d77c20c84 refactor: remove coverage report and add weather widget and navigation utility files 2026-06-06 11:41:13 +02:00
David Fencl a3b3d40769 feat: add circular progress component and update historical lake data indices 2026-06-06 10:38:43 +02:00
David Fencl 27551f9183 feat: implement Favorites feature with persistent storage and sidebar integration and update lake data. 2026-06-05 23:57:17 +02:00
David Fencl b660f0f6c3 feat: add contact link to settings, update lake labels and sidebar icons, and enhance KPI flow visualization 2026-06-05 23:40:56 +02:00
142 changed files with 820924 additions and 39671 deletions
+3
View File
@@ -22,3 +22,6 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# Documentation / Ideas
docs/
+1 -1
View File
@@ -13,7 +13,7 @@ WORKDIR /var/www/html
# Enable necessary Apache modules
RUN a2enmod rewrite headers
COPY vhost.conf /etc/apache2/sites-available/000-default.conf
COPY Docker/vhost.conf /etc/apache2/sites-available/000-default.conf
# Copy the built application from the build stage
COPY --from=build /app/dist /app/dist
+30
View File
@@ -0,0 +1,30 @@
version: '3.8'
services:
db:
image: timescale/timescaledb:latest-pg16
container_name: hladinator-db
restart: always
environment:
POSTGRES_DB: hladinator
POSTGRES_USER: hladinator_user
POSTGRES_PASSWORD: hladinator_db_password_change_me
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
web:
build:
context: ..
dockerfile: Docker/Dockerfile
container_name: hladinator-web
restart: always
ports:
- "80:80"
depends_on:
- db
volumes:
pgdata:
driver: local
+1 -1
View File
@@ -1,4 +1,4 @@
# File: .docker/apache/vhost.conf
# File: Docker/vhost.conf
LoadModule headers_module /usr/lib/apache2/modules/mod_headers.so
<VirtualHost *:80>
+62 -32
View File
@@ -1,72 +1,102 @@
# 🌊 HLADINATOR
HLADINATOR je interaktivní a vizuálně poutavá webová aplikace pro sledování aktuálního stavu a historie českých přehrad. Aplikace poskytuje přesná data o výšce hladiny, odtoku, přítoku, aktuálním objemu a navíc sbírá historii počasí (teploty a srážek) přímo od zdroje.
HLADINATOR is an interactive and visually engaging web application for monitoring the current status and history of Czech reservoirs. The application provides precise data on water level, outflow, inflow, current volume, and additionally collects weather history (temperature and precipitation) directly from the source.
Zdroj dat: **Povodí Vltavy (pvl.cz)** a další povodí v ČR.
Data source: **Povodí Vltavy (pvl.cz)** and other river basin administrators in the Czech Republic.
---
## 🚀 Jak spustit aplikaci lokálně
## 🚀 How to Run the Application Locally
Aplikace je postavena na moderním stacku (React, Vite, TypeScript, Recharts, Leaflet). Pro její spuštění nepotřebuješ žádný složitý backend, data se čtou z předgenerovaných JSON souborů.
The application is built on a modern stack (React, Vite, TypeScript, Recharts, Leaflet). You don't need any complex backend to run it locally; the data is read directly from pre-generated static JSON files.
1. Nainstaluj závislosti (pokud jsi to ještě neudělal):
1. Install dependencies (if you haven't already):
```bash
npm install
```
2. Spusť lokální vývojový server:
2. Start the local development server:
```bash
npm run dev
```
3. Otevři prohlížeč na adrese `http://localhost:5173`.
3. Open your browser at `http://localhost:5173`.
---
## 🔄 Jak aktualizovat data (Scraping)
## 🔄 How to Update Data (Scraping)
Povodí Vltavy neposkytuje standard API pro historii srážek a teplot, ani nepodporuje přímé dotazy z klientského prohlížeče (kvůli CORS a bezpečnosti). Proto využíváme vlastní **scraper**.
Povodí Vltavy does not provide a standard API for weather history, nor does it support direct requests from client browsers (due to CORS and security restrictions). Therefore, we use our own custom **scraper**.
Pro ruční stažení těch nejnovějších dat z webu povodí spusť v terminálu:
To manually fetch the latest data from the river basin websites, run in your terminal:
```bash
npm run data:update
```
Tento příkaz provede dvě věci:
1. `npm run scrape`: Otevře stránky povodí pro všech 12 přehrad, přečte tabulky s historickými měřeními a najde "Aktuální hodnoty", odkud vytáhne exaktní **přítok, objem, srážky a teplotu**. Tato data inteligentně sloučí s tvojí lokální databází (`public/data/*.json`). Pokud Povodí aktuálně počasí neposkytuje, skript zrecykluje tvou dřívější uloženou hodnotu, aby se graf "nerozbil".
2. `npm run build-index`: Zaktualizuje hlavní indexový soubor `lakes_index.json`, který aplikace využívá pro vykreslení rychlých náhledů (např. v levém menu nebo na mapě).
This command performs two actions:
1. `npm run scrape`: Scrapes the website for all **53 reservoirs and river stations**, parses historical measurement tables, extracts precise **inflow, outflow, volume, precipitation, and temperature**. It then merges this data intelligently with your local database (`public/data/*.json`) and automatically backfills missing values from previous steps to avoid zero-drop anomalies in the charts.
2. `npm run build-index`: Updates the main index file `lakes_index.json`, which the app uses to render fast previews (e.g., in the side menu or on the map).
---
## ⏰ Automatické stahování dat (Cron / Spouštěč)
## ⏰ Automated Data Updates (Cron / Scheduler)
Aby se ti automaticky budovala bohatá historie počasí a srážek i ve chvíli, kdy spíš, doporučuji nastavit automatické spouštění skriptu `npm run data:update`.
To automatically accumulate weather and precipitation history even when your machine is off or you are sleeping, we recommend automating the execution of the `npm run data:update` script.
Zde jsou nejběžnější možnosti, jak si to můžeš nastavit ty sám:
Here are the most common deployment methods:
### Možnost A: Přes Crontab na Macu / Linuxu (Lokálně)
Pokud ti běží počítač (nebo domácí server/Raspberry Pi) nepřetržitě, můžeš využít systémový `cron`.
1. Otevři terminál a napiš: `crontab -e`
2. Na konec souboru vlož následující řádek (uprav cestu ke svému projektu a Node.js):
### Option A: Using Crontab on macOS / Linux (Local)
If you have a computer or home server (like a Raspberry Pi) running continuously:
1. Open the terminal and type: `crontab -e`
2. Add the following line at the end of the file (adjust the paths to your project and Node.js installation):
```bash
# Spustit scraping každých 15 minut
# Run scraping every 15 minutes
*/15 * * * * cd /Users/davis/WebstormProjects/davisfe.cz && /usr/local/bin/npm run data:update >> scraper.log 2>&1
```
3. Ulož a zavři editor. Od této chvíle se systém postará o automatický sběr dat!
3. Save and close the editor. The system scheduler will take care of the rest.
### Možnost B: Pomocí GitHub Actions (Pro Produkci)
Až projekt nahraješ na GitHub, můžeš si vytvořit workflow soubor (např. `.github/workflows/scrape.yml`), který bude skript spouštět na serverech GitHubu zdarma každou hodinu, a výsledné `.json` soubory automaticky commitne a publikuje na web.
### Option B: Using GitHub Actions (For Production Hosting)
Once you push the project to GitHub, you can create a workflow file (e.g., `.github/workflows/scrape.yml`) to run the scraping script every hour on GitHub runners for free, and automatically commit and publish the updated `.json` data files back to the repository.
### Možnost C: Jednoduchý integrovaný spouštěč (Nejlehčí)
Pokud nechceš řešit složitý systémový crontab, napsal jsem pro tebe přímo do Node.js malý spouštěč. Stačí si otevřít další okno terminálu a napsat:
### Option C: Built-in Simple Scheduler (Recommended for Development)
If you do not want to set up system cron, the project has a built-in scheduler. Open another terminal tab/window and run:
```bash
npm run data:watch 10
npm run data:watch
```
Tento příkaz ihned provede první stažení a následně bude aplikaci automaticky aktualizovat **každých 10 minut** (číslo na konci si můžeš libovolně přepsat podle toho, jak často chceš stahovat). Skript poběží, dokud okno terminálu nezavřeš.
This command triggers an immediate update and then automatically schedules updates at 7 minutes past every 10-minute step (e.g., 18:07, 18:17, 18:27...). This delay ensures that the river basin web page has updated its data, preventing duplicate/empty requests.
---
## 📁 Struktura klíčových datových složek
## 🐳 Running in Docker (Production & Own Server)
* `/scripts/lakesConfig.ts` - Tady najdeš definici všech 12 sledovaných přehrad (včetně jejich ID pro Povodí Vltavy, GPS souřadnic, maximálních objemů a stavebních kót). Sem můžeš přidávat nové přehrady.
* `/public/data/` - Zde se ukládají vygenerovaná JSON data. V produkci musí být tyto soubory přístupné jako statické assety.
* `/src/components/` - Obsahuje samotné vizuální karty, Leaflet mapu a detailní `LakeDetail.tsx` (kde se vykresluje hydrologický a meteorologický graf přes Recharts).
If you want to deploy the application on your own server and run a PostgreSQL (TimescaleDB) database alongside it for future data collection, a Docker Compose configuration is prepared inside the `Docker` directory.
### Requirements:
- Installed **Docker** and **Docker Compose**.
### Deployment:
1. Go to the `Docker` directory and build/run the containers in the background:
```bash
cd Docker
docker-compose up -d --build
```
2. Docker Compose will launch two containers:
- **`hladinator-db`**: PostgreSQL (TimescaleDB) database running on port `5432` with a `pgdata` volume for data persistence.
- **`hladinator-web`**: Apache web server serving the built React static application on port `80`.
3. The web application is then accessible on port `80` of your server.
---
## 🛠️ Fixing Anomalies in History (Zero Drops / Teeth in Graphs)
If the scraper hasn't run for a while (e.g., when your computer was turned off) and data was filled in subsequently, anomalies or drops to zero (teeth) might appear in the inflow and volume graphs. To clean up the entire history and interpolate these points from the last known state, run:
```bash
npm run data:fix
```
This script scans all data JSON files, detects anomalies/zeros, and repairs them.
## 📁 Key File and Folder Structure
* `/scripts/lakesConfig.ts` - Contains configuration definitions for all **53 monitored reservoirs and rivers** (including their river basin ID, GPS coordinates, maximum capacity limits, and elevation heights). You can add new stations here.
* `/public/data/` - Static storage location for generated JSON files. In production, these must be exposed as static assets.
* `/src/components/` - Holds user interface components, the Leaflet map, and `LakeDetail.tsx` (renders historical hydrology and weather charts via Recharts with automatic anomaly filtering).
+63
View File
@@ -0,0 +1,63 @@
import axios from 'axios';
import * as cheerio from 'cheerio';
import fs from 'fs';
import https from 'https';
async function compare() {
const URL = 'https://www.pvl.cz/portal/nadrze/cz/pc/Mereni.aspx?oid=1&id=VLL1';
const agent = new https.Agent({ rejectUnauthorized: false });
const response = await axios.get(URL, { httpsAgent: agent });
const $ = cheerio.load(response.data);
let tblFound = null;
$('table').each((i, tbl) => {
if ($(tbl).text().includes('Datum') && $(tbl).text().includes('Odtok')) {
tblFound = $(tbl);
}
});
const pvlRows = [];
if (tblFound) {
tblFound.find('tr').each((i, row) => {
if (i === 0) return;
const cols = $(row).find('td');
if (cols.length >= 3) {
const rawDate = $(cols[0]).text().trim();
const levelStr = $(cols[1]).text().trim().replace(',', '.');
let flowStr = $(cols[2]).text().trim().replace(',', '.');
if (flowStr === '' && cols.length >= 4) {
flowStr = $(cols[3]).text().trim().replace(',', '.');
}
pvlRows.push({
date: rawDate,
level: parseFloat(levelStr),
flow: parseFloat(flowStr)
});
}
});
}
const localData = JSON.parse(fs.readFileSync('public/data/VLL1.json', 'utf-8'));
// Sort local data descending (newest first) to match PVL which is newest first
const sortedLocal = localData.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
console.log('--- POROVNÁNÍ DAT: LIPNO 1 ---');
console.log(String('PVL.CZ').padEnd(40) + ' | ' + 'NAŠE LOKÁLNÍ DATABÁZE');
console.log('-'.repeat(85));
for (let i = 0; i < Math.min(10, pvlRows.length); i++) {
const p = pvlRows[i];
const l = sortedLocal[i];
// Format our local UTC timestamp back to something readable
const d = new Date(l.timestamp);
const localDateStr = `${d.getDate().toString().padStart(2, '0')}.${(d.getMonth()+1).toString().padStart(2, '0')}.${d.getFullYear()} ${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`;
const pvlStr = `[${p.date}] H: ${p.level} m, O: ${p.flow} m3/s`.padEnd(40);
const locStr = `[${localDateStr}] H: ${l.level} m, O: ${l.flow} m3/s, P: ${l.inflow} m3/s`;
console.log(`${pvlStr} | ${locStr}`);
}
}
compare().catch(console.error);
-224
View File
@@ -1,224 +0,0 @@
body, html {
margin:0; padding: 0;
height: 100%;
}
body {
font-family: Helvetica Neue, Helvetica, Arial;
font-size: 14px;
color:#333;
}
.small { font-size: 12px; }
*, *:after, *:before {
-webkit-box-sizing:border-box;
-moz-box-sizing:border-box;
box-sizing:border-box;
}
h1 { font-size: 20px; margin: 0;}
h2 { font-size: 14px; }
pre {
font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
margin: 0;
padding: 0;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
}
a { color:#0074D9; text-decoration:none; }
a:hover { text-decoration:underline; }
.strong { font-weight: bold; }
.space-top1 { padding: 10px 0 0 0; }
.pad2y { padding: 20px 0; }
.pad1y { padding: 10px 0; }
.pad2x { padding: 0 20px; }
.pad2 { padding: 20px; }
.pad1 { padding: 10px; }
.space-left2 { padding-left:55px; }
.space-right2 { padding-right:20px; }
.center { text-align:center; }
.clearfix { display:block; }
.clearfix:after {
content:'';
display:block;
height:0;
clear:both;
visibility:hidden;
}
.fl { float: left; }
@media only screen and (max-width:640px) {
.col3 { width:100%; max-width:100%; }
.hide-mobile { display:none!important; }
}
.quiet {
color: #7f7f7f;
color: rgba(0,0,0,0.5);
}
.quiet a { opacity: 0.7; }
.fraction {
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 10px;
color: #555;
background: #E8E8E8;
padding: 4px 5px;
border-radius: 3px;
vertical-align: middle;
}
div.path a:link, div.path a:visited { color: #333; }
table.coverage {
border-collapse: collapse;
margin: 10px 0 0 0;
padding: 0;
}
table.coverage td {
margin: 0;
padding: 0;
vertical-align: top;
}
table.coverage td.line-count {
text-align: right;
padding: 0 5px 0 20px;
}
table.coverage td.line-coverage {
text-align: right;
padding-right: 10px;
min-width:20px;
}
table.coverage td span.cline-any {
display: inline-block;
padding: 0 5px;
width: 100%;
}
.missing-if-branch {
display: inline-block;
margin-right: 5px;
border-radius: 3px;
position: relative;
padding: 0 4px;
background: #333;
color: yellow;
}
.skip-if-branch {
display: none;
margin-right: 10px;
position: relative;
padding: 0 4px;
background: #ccc;
color: white;
}
.missing-if-branch .typ, .skip-if-branch .typ {
color: inherit !important;
}
.coverage-summary {
border-collapse: collapse;
width: 100%;
}
.coverage-summary tr { border-bottom: 1px solid #bbb; }
.keyline-all { border: 1px solid #ddd; }
.coverage-summary td, .coverage-summary th { padding: 10px; }
.coverage-summary tbody { border: 1px solid #bbb; }
.coverage-summary td { border-right: 1px solid #bbb; }
.coverage-summary td:last-child { border-right: none; }
.coverage-summary th {
text-align: left;
font-weight: normal;
white-space: nowrap;
}
.coverage-summary th.file { border-right: none !important; }
.coverage-summary th.pct { }
.coverage-summary th.pic,
.coverage-summary th.abs,
.coverage-summary td.pct,
.coverage-summary td.abs { text-align: right; }
.coverage-summary td.file { white-space: nowrap; }
.coverage-summary td.pic { min-width: 120px !important; }
.coverage-summary tfoot td { }
.coverage-summary .sorter {
height: 10px;
width: 7px;
display: inline-block;
margin-left: 0.5em;
background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
}
.coverage-summary .sorted .sorter {
background-position: 0 -20px;
}
.coverage-summary .sorted-desc .sorter {
background-position: 0 -10px;
}
.status-line { height: 10px; }
/* yellow */
.cbranch-no { background: yellow !important; color: #111; }
/* dark red */
.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
.low .chart { border:1px solid #C21F39 }
.highlighted,
.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
background: #C21F39 !important;
}
/* medium red */
.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
/* light red */
.low, .cline-no { background:#FCE1E5 }
/* light green */
.high, .cline-yes { background:rgb(230,245,208) }
/* medium green */
.cstat-yes { background:rgb(161,215,106) }
/* dark green */
.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
.high .chart { border:1px solid rgb(77,146,33) }
/* dark yellow (gold) */
.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
.medium .chart { border:1px solid #f9cd0b; }
/* light yellow */
.medium { background: #fff4c2; }
.cstat-skip { background: #ddd; color: #111; }
.fstat-skip { background: #ddd; color: #111 !important; }
.cbranch-skip { background: #ddd !important; color: #111; }
span.cline-neutral { background: #eaeaea; }
.coverage-summary td.empty {
opacity: .5;
padding-top: 4px;
padding-bottom: 4px;
line-height: 1;
color: #888;
}
.cover-fill, .cover-empty {
display:inline-block;
height: 12px;
}
.chart {
line-height: 0;
}
.cover-empty {
background: white;
}
.cover-full {
border-right: none !important;
}
pre.prettyprint {
border: none !important;
padding: 0 !important;
margin: 0 !important;
}
.com { color: #999 !important; }
.ignore-none { color: #999; font-weight: normal; }
.wrapper {
min-height: 100%;
height: auto !important;
height: 100%;
margin: 0 auto -48px;
}
.footer, .push {
height: 48px;
}
-87
View File
@@ -1,87 +0,0 @@
/* eslint-disable */
var jumpToCode = (function init() {
// Classes of code we would like to highlight in the file view
var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
// Elements to highlight in the file listing view
var fileListingElements = ['td.pct.low'];
// We don't want to select elements that are direct descendants of another match
var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
// Selector that finds elements on the page to which we can jump
var selector =
fileListingElements.join(', ') +
', ' +
notSelector +
missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
// The NodeList of matching elements
var missingCoverageElements = document.querySelectorAll(selector);
var currentIndex;
function toggleClass(index) {
missingCoverageElements
.item(currentIndex)
.classList.remove('highlighted');
missingCoverageElements.item(index).classList.add('highlighted');
}
function makeCurrent(index) {
toggleClass(index);
currentIndex = index;
missingCoverageElements.item(index).scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center'
});
}
function goToPrevious() {
var nextIndex = 0;
if (typeof currentIndex !== 'number' || currentIndex === 0) {
nextIndex = missingCoverageElements.length - 1;
} else if (missingCoverageElements.length > 1) {
nextIndex = currentIndex - 1;
}
makeCurrent(nextIndex);
}
function goToNext() {
var nextIndex = 0;
if (
typeof currentIndex === 'number' &&
currentIndex < missingCoverageElements.length - 1
) {
nextIndex = currentIndex + 1;
}
makeCurrent(nextIndex);
}
return function jump(event) {
if (
document.getElementById('fileSearch') === document.activeElement &&
document.activeElement != null
) {
// if we're currently focused on the search input, we don't want to navigate
return;
}
switch (event.which) {
case 78: // n
case 74: // j
goToNext();
break;
case 66: // b
case 75: // k
case 80: // p
goToPrevious();
break;
}
};
})();
window.addEventListener('keydown', jumpToCode);
-133
View File
@@ -1,133 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1780693430332" clover="3.2.0">
<project timestamp="1780693430332" name="All files">
<metrics statements="106" coveredstatements="32" conditionals="98" coveredconditionals="29" methods="18" coveredmethods="5" elements="222" coveredelements="66" complexity="0" loc="106" ncloc="106" packages="3" files="4" classes="4"/>
<package name="scripts">
<metrics statements="93" coveredstatements="24" conditionals="75" coveredconditionals="14" methods="12" coveredmethods="3"/>
<file name="lakesConfig.ts" path="/Users/davis/WebstormProjects/davisfe.cz/scripts/lakesConfig.ts">
<metrics statements="1" coveredstatements="1" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
<line num="12" count="1" type="stmt"/>
</file>
<file name="scrapeLakes.ts" path="/Users/davis/WebstormProjects/davisfe.cz/scripts/scrapeLakes.ts">
<metrics statements="92" coveredstatements="23" conditionals="75" coveredconditionals="14" methods="12" coveredmethods="3"/>
<line num="20" count="7" type="stmt"/>
<line num="21" count="7" type="cond" truecount="4" falsecount="0"/>
<line num="22" count="4" type="stmt"/>
<line num="23" count="4" type="stmt"/>
<line num="24" count="4" type="stmt"/>
<line num="26" count="4" type="cond" truecount="4" falsecount="0"/>
<line num="28" count="2" type="stmt"/>
<line num="29" count="2" type="stmt"/>
<line num="30" count="2" type="stmt"/>
<line num="31" count="2" type="stmt"/>
<line num="32" count="2" type="cond" truecount="1" falsecount="1"/>
<line num="33" count="2" type="cond" truecount="5" falsecount="0"/>
<line num="34" count="1" type="stmt"/>
<line num="36" count="0" type="stmt"/>
<line num="41" count="1" type="stmt"/>
<line num="42" count="1" type="stmt"/>
<line num="44" count="1" type="stmt"/>
<line num="45" count="1" type="stmt"/>
<line num="46" count="1" type="stmt"/>
<line num="53" count="0" type="stmt"/>
<line num="55" count="0" type="stmt"/>
<line num="56" count="0" type="stmt"/>
<line num="57" count="0" type="stmt"/>
<line num="58" count="0" type="stmt"/>
<line num="60" count="0" type="stmt"/>
<line num="61" count="0" type="stmt"/>
<line num="62" count="0" type="cond" truecount="0" falsecount="4"/>
<line num="63" count="0" type="stmt"/>
<line num="64" count="0" type="stmt"/>
<line num="65" count="0" type="stmt"/>
<line num="66" count="0" type="cond" truecount="0" falsecount="4"/>
<line num="67" count="0" type="cond" truecount="0" falsecount="4"/>
<line num="68" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="69" count="0" type="stmt"/>
<line num="70" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="72" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="73" count="0" type="stmt"/>
<line num="74" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="80" count="0" type="stmt"/>
<line num="81" count="0" type="stmt"/>
<line num="82" count="0" type="stmt"/>
<line num="83" count="0" type="cond" truecount="0" falsecount="4"/>
<line num="84" count="0" type="stmt"/>
<line num="88" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="89" count="0" type="stmt"/>
<line num="90" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="91" count="0" type="stmt"/>
<line num="92" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="93" count="0" type="stmt"/>
<line num="94" count="0" type="stmt"/>
<line num="95" count="0" type="stmt"/>
<line num="96" count="0" type="cond" truecount="0" falsecount="4"/>
<line num="97" count="0" type="stmt"/>
<line num="100" count="0" type="stmt"/>
<line num="101" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="102" count="0" type="stmt"/>
<line num="114" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="116" count="0" type="stmt"/>
<line num="117" count="0" type="stmt"/>
<line num="118" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="119" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="122" count="0" type="stmt"/>
<line num="123" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="124" count="0" type="stmt"/>
<line num="125" count="0" type="stmt"/>
<line num="128" count="0" type="stmt"/>
<line num="129" count="0" type="stmt"/>
<line num="130" count="0" type="stmt"/>
<line num="132" count="0" type="stmt"/>
<line num="133" count="0" type="stmt"/>
<line num="137" count="0" type="stmt"/>
<line num="138" count="0" type="stmt"/>
<line num="139" count="0" type="stmt"/>
<line num="140" count="0" type="cond" truecount="0" falsecount="4"/>
<line num="141" count="0" type="stmt"/>
<line num="142" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="143" count="0" type="stmt"/>
<line num="146" count="0" type="cond" truecount="0" falsecount="4"/>
<line num="147" count="0" type="stmt"/>
<line num="148" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="149" count="0" type="stmt"/>
<line num="153" count="0" type="stmt"/>
<line num="154" count="0" type="stmt"/>
<line num="156" count="0" type="stmt"/>
<line num="159" count="0" type="stmt"/>
<line num="164" count="1" type="stmt"/>
<line num="166" count="1" type="stmt"/>
<line num="168" count="1" type="stmt"/>
<line num="169" count="1" type="stmt"/>
<line num="171" count="0" type="stmt"/>
<line num="174" count="0" type="stmt"/>
<line num="177" count="1" type="stmt"/>
</file>
</package>
<package name="src">
<metrics statements="1" coveredstatements="1" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
<file name="translations.ts" path="/Users/davis/WebstormProjects/davisfe.cz/src/translations.ts">
<metrics statements="1" coveredstatements="1" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
<line num="3" count="2" type="stmt"/>
</file>
</package>
<package name="src.components">
<metrics statements="12" coveredstatements="7" conditionals="23" coveredconditionals="15" methods="6" coveredmethods="2"/>
<file name="KpiCards.tsx" path="/Users/davis/WebstormProjects/davisfe.cz/src/components/KpiCards.tsx">
<metrics statements="12" coveredstatements="7" conditionals="23" coveredconditionals="15" methods="6" coveredmethods="2"/>
<line num="21" count="1" type="cond" truecount="1" falsecount="0"/>
<line num="22" count="3" type="stmt"/>
<line num="23" count="3" type="stmt"/>
<line num="24" count="3" type="stmt"/>
<line num="26" count="3" type="stmt"/>
<line num="27" count="3" type="cond" truecount="1" falsecount="1"/>
<line num="28" count="0" type="stmt"/>
<line num="29" count="0" type="stmt"/>
<line num="31" count="0" type="stmt"/>
<line num="35" count="3" type="stmt"/>
<line num="86" count="0" type="stmt"/>
<line num="93" count="0" type="stmt"/>
</file>
</package>
</project>
</coverage>
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 B

-146
View File
@@ -1,146 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for All files</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1>All files</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">28.92% </span>
<span class="quiet">Statements</span>
<span class='fraction'>35/121</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">29.59% </span>
<span class="quiet">Branches</span>
<span class='fraction'>29/98</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">27.77% </span>
<span class="quiet">Functions</span>
<span class='fraction'>5/18</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">30.18% </span>
<span class="quiet">Lines</span>
<span class='fraction'>32/106</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line low'></div>
<div class="pad1">
<table class="coverage-summary">
<thead>
<tr>
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
</tr>
</thead>
<tbody><tr>
<td class="file low" data-value="scripts"><a href="scripts/index.html">scripts</a></td>
<td data-value="25.23" class="pic low">
<div class="chart"><div class="cover-fill" style="width: 25%"></div><div class="cover-empty" style="width: 75%"></div></div>
</td>
<td data-value="25.23" class="pct low">25.23%</td>
<td data-value="107" class="abs low">27/107</td>
<td data-value="18.66" class="pct low">18.66%</td>
<td data-value="75" class="abs low">14/75</td>
<td data-value="25" class="pct low">25%</td>
<td data-value="12" class="abs low">3/12</td>
<td data-value="25.8" class="pct low">25.8%</td>
<td data-value="93" class="abs low">24/93</td>
</tr>
<tr>
<td class="file high" data-value="src"><a href="src/index.html">src</a></td>
<td data-value="100" class="pic high">
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="1" class="abs high">1/1</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="0" class="abs high">0/0</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="0" class="abs high">0/0</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="1" class="abs high">1/1</td>
</tr>
<tr>
<td class="file medium" data-value="src/components"><a href="src/components/index.html">src/components</a></td>
<td data-value="53.84" class="pic medium">
<div class="chart"><div class="cover-fill" style="width: 53%"></div><div class="cover-empty" style="width: 47%"></div></div>
</td>
<td data-value="53.84" class="pct medium">53.84%</td>
<td data-value="13" class="abs medium">7/13</td>
<td data-value="65.21" class="pct medium">65.21%</td>
<td data-value="23" class="abs medium">15/23</td>
<td data-value="33.33" class="pct low">33.33%</td>
<td data-value="6" class="abs low">2/6</td>
<td data-value="58.33" class="pct medium">58.33%</td>
<td data-value="12" class="abs medium">7/12</td>
</tr>
</tbody>
</table>
</div>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-06-05T21:03:50.324Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>
-1
View File
@@ -1 +0,0 @@
.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
File diff suppressed because one or more lines are too long
-131
View File
@@ -1,131 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for scripts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> scripts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">25.23% </span>
<span class="quiet">Statements</span>
<span class='fraction'>27/107</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">18.66% </span>
<span class="quiet">Branches</span>
<span class='fraction'>14/75</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">25% </span>
<span class="quiet">Functions</span>
<span class='fraction'>3/12</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">25.8% </span>
<span class="quiet">Lines</span>
<span class='fraction'>24/93</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line low'></div>
<div class="pad1">
<table class="coverage-summary">
<thead>
<tr>
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
</tr>
</thead>
<tbody><tr>
<td class="file high" data-value="lakesConfig.ts"><a href="lakesConfig.ts.html">lakesConfig.ts</a></td>
<td data-value="100" class="pic high">
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="1" class="abs high">1/1</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="0" class="abs high">0/0</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="0" class="abs high">0/0</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="1" class="abs high">1/1</td>
</tr>
<tr>
<td class="file low" data-value="scrapeLakes.ts"><a href="scrapeLakes.ts.html">scrapeLakes.ts</a></td>
<td data-value="24.52" class="pic low">
<div class="chart"><div class="cover-fill" style="width: 24%"></div><div class="cover-empty" style="width: 76%"></div></div>
</td>
<td data-value="24.52" class="pct low">24.52%</td>
<td data-value="106" class="abs low">26/106</td>
<td data-value="18.66" class="pct low">18.66%</td>
<td data-value="75" class="abs low">14/75</td>
<td data-value="25" class="pct low">25%</td>
<td data-value="12" class="abs low">3/12</td>
<td data-value="25" class="pct low">25%</td>
<td data-value="92" class="abs low">23/92</td>
</tr>
</tbody>
</table>
</div>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-06-05T21:03:50.324Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
-160
View File
@@ -1,160 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for scripts/lakesConfig.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">scripts</a> lakesConfig.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>1/1</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>1/1</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">export interface LakeConfig {
id: string;
text: string;
priority?: boolean;
coords: [number, number];
maxVolume?: number;
minLevel?: number;
maxLevel?: number;
storageLevel?: number;
}
&nbsp;
export const lakesConfig: LakeConfig[] = [
{ id: "VLL1|1", text: "VD Lipno 1 - Vltava", priority: true, coords: [48.6322, 14.2215], maxVolume: 306.0, minLevel: 715.00, maxLevel: 725.60, storageLevel: 724.9 },
{ id: "VLL2|1", text: "VD Lipno II - Vltava", coords: [48.6250, 14.3180], maxVolume: 1.5, minLevel: 510.0, maxLevel: 511.5, storageLevel: 511.5 },
{ id: "VLHN|1", text: "VD Hněvkovice - Vltava", priority: true, coords: [49.1830, 14.4440], maxVolume: 21.1, minLevel: 365.0, maxLevel: 370.5, storageLevel: 370.1 },
{ id: "VLKO|1", text: "VD Kořensko - Vltava", coords: [49.2550, 14.3980], maxVolume: 2.8, minLevel: 352.0, maxLevel: 353.5, storageLevel: 352.6 },
{ id: "VLOR|2", text: "VD Orlík - Vltava", priority: true, coords: [49.6060, 14.1700], maxVolume: 716.5, minLevel: 330.00, maxLevel: 354.00, storageLevel: 349.9 },
{ id: "UHKA|2", text: "VD Kamýk - Vltava", coords: [49.6360, 14.2540], maxVolume: 12.8, minLevel: 283.0, maxLevel: 285.6, storageLevel: 285.6 },
{ id: "VLSL|2", text: "VD Slapy - Vltava", priority: true, coords: [49.8220, 14.4360], maxVolume: 269.3, minLevel: 265.50, maxLevel: 271.10, storageLevel: 270.6 },
{ id: "VLST|2", text: "VD Štěchovice - Vltava", coords: [49.8450, 14.4120], maxVolume: 11.2, minLevel: 217.0, maxLevel: 219.5, storageLevel: 219.4 },
{ id: "VRSN|2", text: "VD Vrané - Vltava", coords: [49.9340, 14.3850], maxVolume: 11.1, minLevel: 198.5, maxLevel: 200.5, storageLevel: 200.5 },
{ id: "SVKR|2", text: "VD Švihov - Želivka", priority: true, coords: [49.7180, 15.1060], maxVolume: 266.6, minLevel: 343.0, maxLevel: 377.0, storageLevel: 377.0 },
{ id: "MARI|1", text: "VD Římov - Malše", priority: true, coords: [48.8470, 14.4870], maxVolume: 33.8, minLevel: 458.0, maxLevel: 471.0, storageLevel: 470.65 },
{ id: "MZHR|3", text: "VD Hracholusky - Mže", priority: true, coords: [49.7890, 13.1550], maxVolume: 56.7, minLevel: 360.0, maxLevel: 373.0, storageLevel: 354.1 }
];
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-06-05T21:03:50.324Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
-616
View File
@@ -1,616 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for scripts/scrapeLakes.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">scripts</a> scrapeLakes.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">24.52% </span>
<span class="quiet">Statements</span>
<span class='fraction'>26/106</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">18.66% </span>
<span class="quiet">Branches</span>
<span class='fraction'>14/75</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">25% </span>
<span class="quiet">Functions</span>
<span class='fraction'>3/12</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">25% </span>
<span class="quiet">Lines</span>
<span class='fraction'>23/92</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line low'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import * as fs from 'fs';
import * as path from 'path';
import * as cheerio from 'cheerio';
import axios from 'axios';
import https from 'https';
import { lakesConfig } from './lakesConfig';
&nbsp;
interface DataRecord {
timestamp: string;
level: number;
flow: number;
inflow?: number;
volume?: number;
temperature?: number | null;
precipitation?: number | null;
}
&nbsp;
// Parse date from DD.MM.YYYY HH:MM to ISO
export function parseDateString(dateStr: string): string | null {
try {
if (!dateStr || !dateStr.includes(' ')) return null;
const [datePart, timePart] = dateStr.trim().split(' ');
const [day, month, year] = datePart.split('.');
const [hours, minutes] = timePart.split(':');
if (!year || !hours) return null;
const y = parseInt(year);
const m = parseInt(month) - 1;
const dDay = parseInt(day);
const d = new Date(y, m, dDay, parseInt(hours), parseInt(minutes));
<span class="missing-if-branch" title="if path not taken" >I</span>if (isNaN(d.getTime())) <span class="cstat-no" title="statement not covered" >return null;</span>
if (d.getFullYear() !== y || d.getMonth() !== m || d.getDate() !== dDay) return null;
return d.toISOString();
} catch (e) {
<span class="cstat-no" title="statement not covered" > return null;</span>
}
}
&nbsp;
async function scrapeLake(lakeId: string, oid: string, internalId: string) {
const URL = `https://www.pvl.cz/portal/nadrze/cz/pc/Mereni.aspx?oid=${oid}&amp;id=${internalId}`;
const DATA_FILE = path.resolve(`public/data/${internalId}.json`);
&nbsp;
try {
const agent = new https.Agent({ rejectUnauthorized: false });
const response = await axios.get(URL, {
httpsAgent: agent,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
});
&nbsp;
const $ = <span class="cstat-no" title="statement not covered" >cheerio.load(response.data);</span>
let currentInflow = <span class="cstat-no" title="statement not covered" >0;</span>
let currentVolume = <span class="cstat-no" title="statement not covered" >0;</span>
let currentTemp: number | null = <span class="cstat-no" title="statement not covered" >null;</span>
let currentPrecip: number | null = <span class="cstat-no" title="statement not covered" >null;</span>
<span class="cstat-no" title="statement not covered" > $('table').each(<span class="fstat-no" title="function not covered" >(i</span>, tbl) =&gt; {</span>
const text = <span class="cstat-no" title="statement not covered" >$(tbl).text();</span>
<span class="cstat-no" title="statement not covered" > if (text.includes('Aktuální hodnoty') &amp;&amp; text.includes('Přítok')) {</span>
<span class="cstat-no" title="statement not covered" > $(tbl).find('tr').each(<span class="fstat-no" title="function not covered" >(j</span>, r) =&gt; {</span>
const label = <span class="cstat-no" title="statement not covered" >$(r).find('td').eq(0).text().trim();</span>
const valStr = <span class="cstat-no" title="statement not covered" >$(r).find('td').eq(1).text().trim().replace(/\s/g, '').replace(',', '.');</span>
<span class="cstat-no" title="statement not covered" > if (label.includes('Přítok')) <span class="cstat-no" title="statement not covered" >currentInflow = parseFloat(valStr) || 0;</span></span>
<span class="cstat-no" title="statement not covered" > if (label.includes('Objem')) <span class="cstat-no" title="statement not covered" >currentVolume = parseFloat(valStr) || 0;</span></span>
<span class="cstat-no" title="statement not covered" > if (label.includes('Teplota')) {</span>
const v = <span class="cstat-no" title="statement not covered" >parseFloat(valStr);</span>
<span class="cstat-no" title="statement not covered" > if (!isNaN(v)) <span class="cstat-no" title="statement not covered" >currentTemp = v;</span></span>
}
<span class="cstat-no" title="statement not covered" > if (label.includes('Srážky')) {</span>
const v = <span class="cstat-no" title="statement not covered" >parseFloat(valStr);</span>
<span class="cstat-no" title="statement not covered" > if (!isNaN(v)) <span class="cstat-no" title="statement not covered" >currentPrecip = v;</span></span>
}
});
}
});
&nbsp;
const records: DataRecord[] = <span class="cstat-no" title="statement not covered" >[];</span>
let dataTable = <span class="cstat-no" title="statement not covered" >null;</span>
<span class="cstat-no" title="statement not covered" > $('table').each(<span class="fstat-no" title="function not covered" >(i</span>, tbl) =&gt; {</span>
<span class="cstat-no" title="statement not covered" > if ($(tbl).text().includes('Datum') &amp;&amp; $(tbl).text().includes('Odtok')) {</span>
<span class="cstat-no" title="statement not covered" > dataTable = $(tbl);</span>
}
});
&nbsp;
<span class="cstat-no" title="statement not covered" > if (dataTable) {</span>
<span class="cstat-no" title="statement not covered" > dataTable.find('tr').each(<span class="fstat-no" title="function not covered" >(i</span>, row) =&gt; {</span>
<span class="cstat-no" title="statement not covered" > if (i === 0) <span class="cstat-no" title="statement not covered" >return; // skip header</span></span>
const cols = <span class="cstat-no" title="statement not covered" >$(row).find('td');</span>
<span class="cstat-no" title="statement not covered" > if (cols.length &gt;= 3) {</span>
const rawDate = <span class="cstat-no" title="statement not covered" >$(cols[0]).text().trim(); </span>
const levelStr = <span class="cstat-no" title="statement not covered" >$(cols[1]).text().trim().replace(',', '.');</span>
let flowStr = <span class="cstat-no" title="statement not covered" >$(cols[2]).text().trim().replace(',', '.');</span>
<span class="cstat-no" title="statement not covered" > if (flowStr === '' &amp;&amp; cols.length &gt;= 4) {</span>
<span class="cstat-no" title="statement not covered" > flowStr = $(cols[3]).text().trim().replace(',', '.');</span>
}
&nbsp;
const parsedDateStr = <span class="cstat-no" title="statement not covered" >parseDateString(rawDate);</span>
<span class="cstat-no" title="statement not covered" > if (parsedDateStr) {</span>
<span class="cstat-no" title="statement not covered" > records.push({</span>
timestamp: parsedDateStr,
level: parseFloat(levelStr) || 0,
flow: parseFloat(flowStr) || 0,
inflow: 0,
volume: 0
});
}
}
});
}
&nbsp;
<span class="cstat-no" title="statement not covered" > if (records.length &gt; 0) {</span>
// Apply current values to the latest record
<span class="cstat-no" title="statement not covered" > records[0].inflow = currentInflow;</span>
<span class="cstat-no" title="statement not covered" > records[0].volume = currentVolume;</span>
<span class="cstat-no" title="statement not covered" > if (currentTemp !== null) <span class="cstat-no" title="statement not covered" >records[0].temperature = currentTemp;</span></span>
<span class="cstat-no" title="statement not covered" > if (currentPrecip !== null) <span class="cstat-no" title="statement not covered" >records[0].precipitation = currentPrecip;</span></span>
}
&nbsp;
let existingData: DataRecord[] = <span class="cstat-no" title="statement not covered" >[];</span>
<span class="cstat-no" title="statement not covered" > if (fs.existsSync(DATA_FILE)) {</span>
const fileContent = <span class="cstat-no" title="statement not covered" >fs.readFileSync(DATA_FILE, 'utf-8');</span>
<span class="cstat-no" title="statement not covered" > existingData = JSON.parse(fileContent);</span>
}
&nbsp;
const dataMap = <span class="cstat-no" title="statement not covered" >new Map&lt;string, DataRecord&gt;();</span>
<span class="cstat-no" title="statement not covered" > existingData.forEach(<span class="fstat-no" title="function not covered" >item =&gt; <span class="cstat-no" title="statement not covered" >d</span>ataMap.set(item.timestamp, item))</span>;</span>
<span class="cstat-no" title="statement not covered" > records.forEach(<span class="fstat-no" title="function not covered" >item =&gt; <span class="cstat-no" title="statement not covered" >d</span>ataMap.set(item.timestamp, item))</span>;</span>
&nbsp;
const mergedData = <span class="cstat-no" title="statement not covered" >Array.from(dataMap.values()).sort(<span class="fstat-no" title="function not covered" >(a</span>, b) =&gt; {</span>
<span class="cstat-no" title="statement not covered" > return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();</span>
});
&nbsp;
// Propagate previous values if missing (user requested)
let lastKnownTemp: number | null = <span class="cstat-no" title="statement not covered" >null;</span>
let lastKnownPrecip: number | null = <span class="cstat-no" title="statement not covered" >null;</span>
<span class="cstat-no" title="statement not covered" > mergedData.forEach(<span class="fstat-no" title="function not covered" >item =&gt; {</span></span>
<span class="cstat-no" title="statement not covered" > if (item.temperature !== undefined &amp;&amp; item.temperature !== null) {</span>
<span class="cstat-no" title="statement not covered" > lastKnownTemp = item.temperature;</span>
<span class="cstat-no" title="statement not covered" > } else if (lastKnownTemp !== null) {</span>
<span class="cstat-no" title="statement not covered" > item.temperature = lastKnownTemp;</span>
}
&nbsp;
<span class="cstat-no" title="statement not covered" > if (item.precipitation !== undefined &amp;&amp; item.precipitation !== null) {</span>
<span class="cstat-no" title="statement not covered" > lastKnownPrecip = item.precipitation;</span>
<span class="cstat-no" title="statement not covered" > } else if (lastKnownPrecip !== null) {</span>
<span class="cstat-no" title="statement not covered" > item.precipitation = lastKnownPrecip;</span>
}
});
&nbsp;
<span class="cstat-no" title="statement not covered" > fs.mkdirSync(path.dirname(DATA_FILE), { recursive: true });</span>
<span class="cstat-no" title="statement not covered" > fs.writeFileSync(DATA_FILE, JSON.stringify(mergedData, null, 2), 'utf-8');</span>
<span class="cstat-no" title="statement not covered" > console.log(`[${internalId}] Scraped ${records.length} records. DB total: ${mergedData.length}`);</span>
&nbsp;
} catch (error: any) {
<span class="cstat-no" title="statement not covered" > console.error(`[${internalId}] Error scraping data:`, error.message);</span>
}
}
&nbsp;
async function runScraper() {
console.log(`Starting bulk scraper for ${lakesConfig.length} lakes...`);
for (const lake of lakesConfig) {
// ID format: VLL1|1 -&gt; internalId=VLL1, oid=1
const [internalId, oid] = lake.id.split('|');
await scrapeLake(lake.id, oid, internalId);
// Add small delay to not hammer the server
<span class="cstat-no" title="statement not covered" > await new Promise(<span class="fstat-no" title="function not covered" >resolve =&gt; <span class="cstat-no" title="statement not covered" >s</span>etTimeout(resolve, 500))</span>;</span>
}
<span class="cstat-no" title="statement not covered" > console.log('Bulk scraping finished.');</span>
}
&nbsp;
runScraper();
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-06-05T21:03:50.324Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 B

-210
View File
@@ -1,210 +0,0 @@
/* eslint-disable */
var addSorting = (function() {
'use strict';
var cols,
currentSort = {
index: 0,
desc: false
};
// returns the summary table element
function getTable() {
return document.querySelector('.coverage-summary');
}
// returns the thead element of the summary table
function getTableHeader() {
return getTable().querySelector('thead tr');
}
// returns the tbody element of the summary table
function getTableBody() {
return getTable().querySelector('tbody');
}
// returns the th element for nth column
function getNthColumn(n) {
return getTableHeader().querySelectorAll('th')[n];
}
function onFilterInput() {
const searchValue = document.getElementById('fileSearch').value;
const rows = document.getElementsByTagName('tbody')[0].children;
// Try to create a RegExp from the searchValue. If it fails (invalid regex),
// it will be treated as a plain text search
let searchRegex;
try {
searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive
} catch (error) {
searchRegex = null;
}
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
let isMatch = false;
if (searchRegex) {
// If a valid regex was created, use it for matching
isMatch = searchRegex.test(row.textContent);
} else {
// Otherwise, fall back to the original plain text search
isMatch = row.textContent
.toLowerCase()
.includes(searchValue.toLowerCase());
}
row.style.display = isMatch ? '' : 'none';
}
}
// loads the search box
function addSearchBox() {
var template = document.getElementById('filterTemplate');
var templateClone = template.content.cloneNode(true);
templateClone.getElementById('fileSearch').oninput = onFilterInput;
template.parentElement.appendChild(templateClone);
}
// loads all columns
function loadColumns() {
var colNodes = getTableHeader().querySelectorAll('th'),
colNode,
cols = [],
col,
i;
for (i = 0; i < colNodes.length; i += 1) {
colNode = colNodes[i];
col = {
key: colNode.getAttribute('data-col'),
sortable: !colNode.getAttribute('data-nosort'),
type: colNode.getAttribute('data-type') || 'string'
};
cols.push(col);
if (col.sortable) {
col.defaultDescSort = col.type === 'number';
colNode.innerHTML =
colNode.innerHTML + '<span class="sorter"></span>';
}
}
return cols;
}
// attaches a data attribute to every tr element with an object
// of data values keyed by column name
function loadRowData(tableRow) {
var tableCols = tableRow.querySelectorAll('td'),
colNode,
col,
data = {},
i,
val;
for (i = 0; i < tableCols.length; i += 1) {
colNode = tableCols[i];
col = cols[i];
val = colNode.getAttribute('data-value');
if (col.type === 'number') {
val = Number(val);
}
data[col.key] = val;
}
return data;
}
// loads all row data
function loadData() {
var rows = getTableBody().querySelectorAll('tr'),
i;
for (i = 0; i < rows.length; i += 1) {
rows[i].data = loadRowData(rows[i]);
}
}
// sorts the table using the data for the ith column
function sortByIndex(index, desc) {
var key = cols[index].key,
sorter = function(a, b) {
a = a.data[key];
b = b.data[key];
return a < b ? -1 : a > b ? 1 : 0;
},
finalSorter = sorter,
tableBody = document.querySelector('.coverage-summary tbody'),
rowNodes = tableBody.querySelectorAll('tr'),
rows = [],
i;
if (desc) {
finalSorter = function(a, b) {
return -1 * sorter(a, b);
};
}
for (i = 0; i < rowNodes.length; i += 1) {
rows.push(rowNodes[i]);
tableBody.removeChild(rowNodes[i]);
}
rows.sort(finalSorter);
for (i = 0; i < rows.length; i += 1) {
tableBody.appendChild(rows[i]);
}
}
// removes sort indicators for current column being sorted
function removeSortIndicators() {
var col = getNthColumn(currentSort.index),
cls = col.className;
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
col.className = cls;
}
// adds sort indicators for current column being sorted
function addSortIndicators() {
getNthColumn(currentSort.index).className += currentSort.desc
? ' sorted-desc'
: ' sorted';
}
// adds event listeners for all sorter widgets
function enableUI() {
var i,
el,
ithSorter = function ithSorter(i) {
var col = cols[i];
return function() {
var desc = col.defaultDescSort;
if (currentSort.index === i) {
desc = !currentSort.desc;
}
sortByIndex(i, desc);
removeSortIndicators();
currentSort.index = i;
currentSort.desc = desc;
addSortIndicators();
};
};
for (i = 0; i < cols.length; i += 1) {
if (cols[i].sortable) {
// add the click event handler on the th so users
// dont have to click on those tiny arrows
el = getNthColumn(i).querySelector('.sorter').parentElement;
if (el.addEventListener) {
el.addEventListener('click', ithSorter(i));
} else {
el.attachEvent('onclick', ithSorter(i));
}
}
}
}
// adds sorting functionality to the UI
return function() {
if (!getTable()) {
return;
}
cols = loadColumns();
loadData();
addSearchBox();
addSortIndicators();
enableUI();
};
})();
window.addEventListener('load', addSorting);
-466
View File
@@ -1,466 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/components/KpiCards.tsx</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../../prettify.css" />
<link rel="stylesheet" href="../../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> KpiCards.tsx</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">53.84% </span>
<span class="quiet">Statements</span>
<span class='fraction'>7/13</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">65.21% </span>
<span class="quiet">Branches</span>
<span class='fraction'>15/23</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">33.33% </span>
<span class="quiet">Functions</span>
<span class='fraction'>2/6</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">58.33% </span>
<span class="quiet">Lines</span>
<span class='fraction'>7/12</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line medium'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { FiArrowUp, FiArrowDown } from 'react-icons/fi';
import { type Language, t } from '../translations';
import { useState, useEffect } from 'react';
&nbsp;
interface KpiData {
level: number;
inflow: number;
outflow: number;
outflow: number;
volume: number;
fullness: number;
storageDiff?: number;
}
&nbsp;
interface Props {
data: KpiData;
language: Language;
lakeName?: string;
}
&nbsp;
const KpiCards = ({ data, language, lakeName = 'Lipno 1' }: Props) =&gt; {
const [showTooltip, setShowTooltip] = useState(false);
const dict = t[language].kpi;
const flowDiff = data.inflow - data.outflow;
&nbsp;
useEffect(() =&gt; {
<span class="missing-if-branch" title="if path not taken" >I</span>if (showTooltip) {
const timer = <span class="cstat-no" title="statement not covered" >setTimeout(<span class="fstat-no" title="function not covered" >() =&gt; {</span></span>
<span class="cstat-no" title="statement not covered" > setShowTooltip(false);</span>
}, 3500);
<span class="cstat-no" title="statement not covered" > return <span class="fstat-no" title="function not covered" >() =&gt; <span class="cstat-no" title="statement not covered" >c</span>learTimeout(timer);</span></span>
}
}, [showTooltip]);
&nbsp;
return (
&lt;div className="kpi-grid-container"&gt;
{/* CARD 1: HLADINA */}
&lt;div className="kpi-card"&gt;
&lt;div style={{ fontSize: '1rem', color: 'var(--text-muted)', marginBottom: '0.5rem' }}&gt;
{dict.level} {lakeName}
&lt;/div&gt;
&lt;div style={{ fontSize: '2.5rem', fontWeight: 'bold', color: 'var(--color-cyan)', lineHeight: 1, marginBottom: '0.5rem' }}&gt;
{data.level.toFixed(2)} &lt;span style={{ fontSize: '1rem', color: 'var(--text-muted)', fontWeight: 'normal' }}&gt;m n. m.&lt;/span&gt;
&lt;/div&gt;
&lt;div style={{ fontSize: '0.85rem', color: 'var(--color-green)' }}&gt;
(+0.02 m / 24h)
&lt;/div&gt;
{/* Decorative Circle for Level */}
&lt;div style={{ position: 'absolute', right: '1.5rem', top: '1.5rem', width: '40px', height: '40px', borderRadius: '50%', border: '4px solid rgba(0, 195, 255, 0.2)', borderTopColor: 'var(--color-cyan)', transform: 'rotate(45deg)' }}&gt;&lt;/div&gt;
&lt;/div&gt;
&nbsp;
{/* CARD 2: PRŮTOK */}
&lt;div className="kpi-card"&gt;
&lt;div style={{ fontSize: '1rem', color: 'var(--text-muted)', marginBottom: '0.5rem' }}&gt;
{dict.flow}
&lt;/div&gt;
&lt;div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', height: '100%' }}&gt;
&lt;div style={{ display: 'flex', flexDirection: 'column', gap: '0.25rem', fontSize: '0.85rem' }}&gt;
&lt;div style={{ fontSize: '0.85rem', color: 'var(--text-muted)', display: 'flex', alignItems: 'center' }}&gt;
&lt;span style={{ display: 'inline-block', width: '8px', height: '8px', borderRadius: '50%', backgroundColor: '#8b5cf6', marginRight: '6px' }}&gt;&lt;/span&gt;
{dict.inflow}: &lt;span style={{ fontWeight: 'bold', color: 'var(--text-main)', marginLeft: '4px' }}&gt;{data.inflow.toFixed(1)} m³/s&lt;/span&gt;
&lt;/div&gt;
&lt;div style={{ fontSize: '0.85rem', color: 'var(--text-muted)', marginTop: '0.25rem', display: 'flex', alignItems: 'center', gap: '0.25rem' }}&gt;
&lt;span style={{ display: 'inline-block', width: '8px', height: '8px', borderRadius: '50%', backgroundColor: 'var(--color-orange)', marginRight: '2px' }}&gt;&lt;/span&gt;
{dict.outflow}: &lt;span style={{ fontWeight: 'bold', color: 'var(--text-main)' }}&gt;{data.outflow.toFixed(1)} m³/s&lt;/span&gt;
{flowDiff &gt; 0 ? &lt;FiArrowUp color="var(--color-green)" /&gt; : <span class="branch-1 cbranch-no" title="branch not covered" >flowDiff &lt; 0 ? &lt;FiArrowDown color="var(--color-red)" /&gt; : null}</span>
&lt;/div&gt;
&lt;/div&gt;
{/* Flow Circle */}
&lt;div style={{ width: '40px', height: '40px', borderRadius: '50%', border: '4px solid rgba(0, 195, 255, 0.2)', borderTopColor: 'var(--color-cyan)', borderRightColor: 'var(--color-cyan)', display: 'flex', alignItems: 'center', justifyContent: 'center', transform: 'rotate(-45deg)' }}&gt;
&lt;span style={{ fontSize: '0.65rem', transform: 'rotate(45deg)', color: 'var(--text-main)', fontWeight: 'bold' }}&gt;
&lt;div style={{ lineHeight: 1 }}&gt;{data.outflow.toFixed(1)}&lt;/div&gt;
&lt;div style={{ fontSize: '0.45rem', opacity: 0.7 }}&gt;m³/s&lt;/div&gt;
&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&nbsp;
{/* CARD 3: NAPLNĚNOST */}
&lt;div className="kpi-card"&gt;
&lt;div style={{ fontSize: '1rem', color: 'var(--text-muted)', marginBottom: '0.5rem', display: 'flex', alignItems: 'center', gap: '0.4rem', position: 'relative' }}&gt;
{dict.fullness}
&lt;span
onClick={<span class="fstat-no" title="function not covered" >() =&gt; <span class="cstat-no" title="statement not covered" >s</span>etShowTooltip(!showTooltip)}</span>
style={{ cursor: 'pointer', fontSize: '0.85rem', opacity: 0.6, padding: '0 4px' }}
&gt;
&lt;/span&gt;
{showTooltip &amp;&amp; (
<span class="branch-1 cbranch-no" title="branch not covered" > &lt;div </span>
onClick={<span class="fstat-no" title="function not covered" >() =&gt; <span class="cstat-no" title="statement not covered" >s</span>etShowTooltip(false)}</span>
style={{
position: 'absolute',
bottom: '100%',
left: '50%',
transform: 'translateX(-50%)',
marginBottom: '8px',
backgroundColor: 'var(--bg-card)',
border: '1px solid var(--border-color)',
padding: '0.75rem',
borderRadius: '8px',
width: '250px',
zIndex: 100,
boxShadow: '0 4px 12px rgba(0,0,0,0.5)',
color: 'var(--text-main)',
fontSize: '0.85rem',
lineHeight: 1.4,
cursor: 'pointer'
}}&gt;
{language === 'cs' ? "Rozdíl mezi aktuální hladinou a hladinou zásobního prostoru (důležité pro jachtaře a rekreaci)." : "Difference between current water level and storage space level (important for sailing and recreation)."}
&lt;/div&gt;
)}
&lt;/div&gt;
&lt;div style={{ fontSize: '2rem', fontWeight: 'bold', lineHeight: 1, marginBottom: '0.5rem', color: data.storageDiff &amp;&amp; data.storageDiff &lt; 0 ? 'var(--color-red)' : 'var(--color-cyan)' }}&gt;
{data.storageDiff !== undefined &amp;&amp; data.storageDiff !== 0 ? (data.storageDiff &gt; 0 ? `+${data.storageDiff.toFixed(2)} m` : `${data.storageDiff.toFixed(2)} m`) : (data.fullness &gt; 0 ? `${data.fullness.toFixed(1)}%` : <span class="branch-1 cbranch-no" title="branch not covered" >'N/A')}</span>
&lt;/div&gt;
&lt;div style={{ fontSize: '0.85rem', color: 'var(--text-muted)' }}&gt;
{dict.volume}: {data.volume.toFixed(1)} mil. m³
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
);
};
&nbsp;
export default KpiCards;
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-06-05T21:03:50.324Z
</div>
<script src="../../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../../sorter.js"></script>
<script src="../../block-navigation.js"></script>
</body>
</html>
-116
View File
@@ -1,116 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/components</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../../prettify.css" />
<link rel="stylesheet" href="../../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../../index.html">All files</a> src/components</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">53.84% </span>
<span class="quiet">Statements</span>
<span class='fraction'>7/13</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">65.21% </span>
<span class="quiet">Branches</span>
<span class='fraction'>15/23</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">33.33% </span>
<span class="quiet">Functions</span>
<span class='fraction'>2/6</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">58.33% </span>
<span class="quiet">Lines</span>
<span class='fraction'>7/12</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line medium'></div>
<div class="pad1">
<table class="coverage-summary">
<thead>
<tr>
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
</tr>
</thead>
<tbody><tr>
<td class="file medium" data-value="KpiCards.tsx"><a href="KpiCards.tsx.html">KpiCards.tsx</a></td>
<td data-value="53.84" class="pic medium">
<div class="chart"><div class="cover-fill" style="width: 53%"></div><div class="cover-empty" style="width: 47%"></div></div>
</td>
<td data-value="53.84" class="pct medium">53.84%</td>
<td data-value="13" class="abs medium">7/13</td>
<td data-value="65.21" class="pct medium">65.21%</td>
<td data-value="23" class="abs medium">15/23</td>
<td data-value="33.33" class="pct low">33.33%</td>
<td data-value="6" class="abs low">2/6</td>
<td data-value="58.33" class="pct medium">58.33%</td>
<td data-value="12" class="abs medium">7/12</td>
</tr>
</tbody>
</table>
</div>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-06-05T21:03:50.324Z
</div>
<script src="../../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../../sorter.js"></script>
<script src="../../block-navigation.js"></script>
</body>
</html>
-116
View File
@@ -1,116 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> src</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>1/1</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>1/1</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<div class="pad1">
<table class="coverage-summary">
<thead>
<tr>
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
</tr>
</thead>
<tbody><tr>
<td class="file high" data-value="translations.ts"><a href="translations.ts.html">translations.ts</a></td>
<td data-value="100" class="pic high">
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="1" class="abs high">1/1</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="0" class="abs high">0/0</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="0" class="abs high">0/0</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="1" class="abs high">1/1</td>
</tr>
</tbody>
</table>
</div>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-06-05T21:03:50.324Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
-385
View File
@@ -1,385 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/translations.ts</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../index.html">All files</a> / <a href="index.html">src</a> translations.ts</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>1/1</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>1/1</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">export type Language = 'en' | 'cs';
&nbsp;
export const t = {
en: {
sidebar: {
favorites: 'Favorites',
lakes: 'Lakes',
map: 'Map',
settings: 'Settings'
},
topbar: {
search: 'Search river or reservoir (e.g. Lipno)...',
updated: 'Last updated:'
},
kpi: {
level: 'WATER LEVEL',
flow: 'FLOW RATE',
inflow: 'Inflow',
outflow: 'Outflow',
fullness: 'STORAGE LEVEL',
volume: 'Volume'
},
chart: {
title: 'Long-term development',
timeframe: 'Timeframe',
timeframeMobile: 'Time',
view: 'View',
raw: 'Raw data',
smoothed: 'Smoothed',
calendar: 'Calendar',
all: 'All',
year: 'Year',
level: 'Water level',
inflow: 'Inflow',
outflow: 'Outflow',
maxLevel: 'Max retention level',
storageLevel: 'Storage space level',
dataSources: 'Data sources:',
createdIn: 'Created with ♥ in the Czech Republic'
},
settings: {
title: 'Settings',
theme: 'Theme',
dark: 'Dark',
light: 'Light',
language: 'Language',
english: 'English',
czech: 'Čeština',
buyCoffee: 'Buy Me a Coffee'
}
},
cs: {
sidebar: {
favorites: 'Oblíbené',
lakes: 'Jezera',
map: 'Mapa',
settings: 'Nastavení'
},
topbar: {
search: 'Hledat tok nebo nádrž (např. Lipno)...',
updated: 'Aktualizováno:'
},
kpi: {
level: 'HLADINA',
flow: 'PRŮTOK',
inflow: 'Přítok',
outflow: 'Odtok',
fullness: 'ZÁSOBNÍ PROSTOR',
volume: 'Objem'
},
chart: {
title: 'Dlouhodobý vývoj',
timeframe: 'Časové období',
timeframeMobile: 'Časové',
view: 'Zobrazení',
raw: 'Syrová data',
smoothed: 'Vyhlazená',
calendar: 'Kalendář',
all: 'Vše',
year: 'Rok',
level: 'Hladina',
inflow: 'Přítok',
outflow: 'Odtok',
maxLevel: 'Max. retenční hladina',
storageLevel: 'Hladina zásobního prostoru',
dataSources: 'Zdroje dat:',
createdIn: 'Vytvořeno s ♥ v České republice'
},
settings: {
title: 'Nastavení',
theme: 'Vzhled',
dark: 'Tmavý',
light: 'Světlý',
language: 'Jazyk',
english: 'English',
czech: 'Čeština',
buyCoffee: 'Kup mi kávu'
}
}
};
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-06-05T21:03:50.324Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
+22 -2
View File
@@ -2,12 +2,32 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/jpeg" href="/favicon.jpg" />
<link rel="icon" type="image/png" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Davis Fencl</title>
<title>Hladinátor - Aktuální stav přehrad a nádrží</title>
<meta name="description" content="Sledujte aktuální vodní stav, průtok a vývoj počasí na českých přehradách a nádržích v reálném čase." />
<meta property="og:title" content="Hladinátor - Aktuální stav přehrad a nádrží" />
<meta property="og:description" content="Sledujte aktuální vodní stav, průtok a vývoj počasí na českých přehradách a nádržích v reálném čase." />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://hladinator.cz" />
<!-- PWA Settings -->
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#1e293b" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="apple-touch-icon" href="/favicon.png" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('Service Worker registered:', reg.scope))
.catch(err => console.error('Service Worker failed:', err));
});
}
</script>
</body>
</html>
+48 -1
View File
@@ -14,6 +14,7 @@
"leaflet": "^1.9.4",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-helmet-async": "^3.0.0",
"react-icons": "^5.5.0",
"react-leaflet": "^5.0.0",
"react-router-dom": "^7.9.6",
@@ -3936,6 +3937,15 @@
"node": ">=12"
}
},
"node_modules/invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -4016,7 +4026,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
@@ -4219,6 +4228,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -4688,6 +4709,26 @@
"react": "^19.2.0"
}
},
"node_modules/react-fast-compare": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
"license": "MIT"
},
"node_modules/react-helmet-async": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-3.0.0.tgz",
"integrity": "sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w==",
"license": "Apache-2.0",
"dependencies": {
"invariant": "^2.2.4",
"react-fast-compare": "^3.2.2",
"shallowequal": "^1.1.0"
},
"peerDependencies": {
"react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-icons": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
@@ -4957,6 +4998,12 @@
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
"license": "MIT"
},
"node_modules/shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
"license": "MIT"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+2
View File
@@ -13,6 +13,7 @@
"build-index": "tsx scripts/buildIndex.ts",
"data:update": "npm run scrape && npm run build-index",
"data:watch": "tsx scripts/watchData.ts",
"data:fix": "tsx scripts/fix_lake_inflows.ts",
"test": "vitest run",
"test:watch": "vitest",
"coverage": "vitest run --coverage"
@@ -24,6 +25,7 @@
"leaflet": "^1.9.4",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-helmet-async": "^3.0.0",
"react-icons": "^5.5.0",
"react-leaflet": "^5.0.0",
"react-router-dom": "^7.9.6",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+12638
View File
File diff suppressed because it is too large Load Diff
+15455
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+15482
View File
File diff suppressed because it is too large Load Diff
+15248
View File
File diff suppressed because it is too large Load Diff
+12287
View File
File diff suppressed because it is too large Load Diff
+15500
View File
File diff suppressed because it is too large Load Diff
+15656
View File
File diff suppressed because it is too large Load Diff
+15167
View File
File diff suppressed because it is too large Load Diff
+12665
View File
File diff suppressed because it is too large Load Diff
+12665
View File
File diff suppressed because it is too large Load Diff
+15437
View File
File diff suppressed because it is too large Load Diff
+15545
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+12982 -4000
View File
File diff suppressed because it is too large Load Diff
+13108 -4054
View File
File diff suppressed because it is too large Load Diff
+15473
View File
File diff suppressed because it is too large Load Diff
+15212
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+13943
View File
File diff suppressed because it is too large Load Diff
+15473
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+15518
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+15509
View File
File diff suppressed because it is too large Load Diff
+15446
View File
File diff suppressed because it is too large Load Diff
+15554
View File
File diff suppressed because it is too large Load Diff
+15257
View File
File diff suppressed because it is too large Load Diff
+15545
View File
File diff suppressed because it is too large Load Diff
+15563
View File
File diff suppressed because it is too large Load Diff
+15527
View File
File diff suppressed because it is too large Load Diff
+15509
View File
File diff suppressed because it is too large Load Diff
+15527
View File
File diff suppressed because it is too large Load Diff
+15545
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+12912 -3993
View File
File diff suppressed because it is too large Load Diff
+15509
View File
File diff suppressed because it is too large Load Diff
+12947 -3992
View File
File diff suppressed because it is too large Load Diff
+13012 -4033
View File
File diff suppressed because it is too large Load Diff
+13002 -4038
View File
File diff suppressed because it is too large Load Diff
+13060 -4006
View File
File diff suppressed because it is too large Load Diff
+13015 -4015
View File
File diff suppressed because it is too large Load Diff
+13031 -4004
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+15617
View File
File diff suppressed because it is too large Load Diff
+235
View File
@@ -0,0 +1,235 @@
[
{
"timestamp": "2026-05-31T05:00:00.000Z",
"level": 0,
"flow": 48.8,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-01T05:00:00.000Z",
"level": 34,
"flow": 80.2,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-02T05:00:00.000Z",
"level": 20,
"flow": 66.46,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-03T05:00:00.000Z",
"level": 18,
"flow": 63.7,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-04T05:00:00.000Z",
"level": 15,
"flow": 60.95,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-05T05:00:00.000Z",
"level": 15,
"flow": 61.4,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-05T20:00:00.000Z",
"level": 12,
"flow": 59.06,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-05T21:00:00.000Z",
"level": 7,
"flow": 54.32,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-05T22:00:00.000Z",
"level": 6,
"flow": 53.76,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-05T23:00:00.000Z",
"level": 11,
"flow": 57.64,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T00:00:00.000Z",
"level": 12,
"flow": 58.7,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T01:00:00.000Z",
"level": 12,
"flow": 58.25,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T02:00:00.000Z",
"level": 15,
"flow": 60.95,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T03:00:00.000Z",
"level": 13,
"flow": 59.87,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T04:00:00.000Z",
"level": 10,
"flow": 56.73,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T05:00:00.000Z",
"level": 15,
"flow": 61.76,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T06:00:00.000Z",
"level": 11,
"flow": 57.64,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T07:00:00.000Z",
"level": 6,
"flow": 53.6,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T08:00:00.000Z",
"level": 10,
"flow": 57.08,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T09:00:00.000Z",
"level": 14,
"flow": 60.68,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T10:00:00.000Z",
"level": 7,
"flow": 54.4,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T11:00:00.000Z",
"level": 3,
"flow": 51.34,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T12:00:00.000Z",
"level": 12,
"flow": 58.25,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T13:00:00.000Z",
"level": 13,
"flow": 59.33,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T14:00:00.000Z",
"level": 5,
"flow": 52.71,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T15:00:00.000Z",
"level": 10,
"flow": 56.64,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T16:00:00.000Z",
"level": 11,
"flow": 57.4,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T17:00:00.000Z",
"level": 9,
"flow": 55.7,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T18:00:00.000Z",
"level": 3,
"flow": 51.18,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T18:10:00.000Z",
"level": 4,
"flow": 51.58,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T18:20:00.000Z",
"level": 4,
"flow": 51.66,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T18:30:00.000Z",
"level": 5,
"flow": 52.44,
"inflow": 0,
"volume": 0
},
{
"timestamp": "2026-06-06T18:40:00.000Z",
"level": 5,
"flow": 53.04,
"inflow": 0,
"volume": 0,
"temperature": 22.1,
"precipitation": 0
}
]
-1
View File
@@ -1 +0,0 @@
[]
+15509
View File
File diff suppressed because it is too large Load Diff
+2016 -256
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 KiB

+17
View File
@@ -0,0 +1,17 @@
{
"short_name": "Hladinátor",
"name": "Hladinátor - Stav přehrad a nádrží",
"icons": [
{
"src": "/favicon.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "any maskable"
}
],
"start_url": "/",
"background_color": "#1e293b",
"theme_color": "#1e293b",
"display": "standalone",
"orientation": "portrait"
}
+4
View File
@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://hladinator.cz/sitemap.xml
+66
View File
@@ -0,0 +1,66 @@
const CACHE_NAME = 'hladinator-v1';
const ASSETS_TO_CACHE = [
'/',
'/index.html',
'/favicon.png',
'/manifest.json'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(ASSETS_TO_CACHE);
})
);
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) => {
return Promise.all(
keys.map((key) => {
if (key !== CACHE_NAME) {
return caches.delete(key);
}
})
);
})
);
self.clients.claim();
});
self.addEventListener('fetch', (event) => {
// Only handle same-origin HTTP/HTTPS requests
if (!event.request.url.startsWith(self.location.origin)) return;
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
if (cachedResponse) {
// Fetch new version in background to update cache (stale-while-revalidate)
fetch(event.request).then((networkResponse) => {
if (networkResponse.status === 200) {
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, networkResponse));
}
}).catch(() => {/* ignore network failures */});
return cachedResponse;
}
return fetch(event.request).then((networkResponse) => {
// Cache static files and JSON data on the fly
if (networkResponse.status === 200 && (
event.request.url.includes('.json') ||
event.request.url.includes('.css') ||
event.request.url.includes('.js')
)) {
const responseToCache = networkResponse.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, responseToCache));
}
return networkResponse;
}).catch(() => {
// Offline fallback
});
})
);
});
+1449
View File
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More