From f8401b4c059f83f2afbbcfb21a6810556d2aad17 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Tue, 9 Dec 2025 12:09:43 +0100 Subject: [PATCH 01/19] feat(#55): add D3.js library to project dependencies --- front/package.json | 1 + package-lock.json | 441 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 442 insertions(+) diff --git a/front/package.json b/front/package.json index e521b3f..d7ccf80 100644 --- a/front/package.json +++ b/front/package.json @@ -9,6 +9,7 @@ "dependencies": { "@fontsource/nunito-sans": "^5.2.7", "@tailwindcss/postcss": "^4.1.17", + "d3": "^7.9.0", "next": "^15.4.1", "postcss": "^8.5.6", "react": "^19.1.0", diff --git a/package-lock.json b/package-lock.json index cc9078a..1397bce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "dependencies": { "@fontsource/nunito-sans": "^5.2.7", "@tailwindcss/postcss": "^4.1.17", + "d3": "^7.9.0", "next": "^15.4.1", "postcss": "^8.5.6", "react": "^19.1.0", @@ -1416,6 +1417,15 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -1474,6 +1484,407 @@ "dev": true, "license": "MIT" }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/daisyui": { "version": "5.5.5", "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.5.5.tgz", @@ -1496,6 +1907,15 @@ "node": ">=6" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "license": "MIT", @@ -1998,6 +2418,15 @@ "node": ">=0.10.0" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-arrayish": { "version": "0.3.2", "license": "MIT", @@ -3162,6 +3591,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, "node_modules/rollup": { "version": "4.46.4", "dev": true, @@ -3200,6 +3635,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/safer-buffer": { "version": "2.1.2", "license": "MIT" From 1e8f57ac060961068ce4f250b18a3ed4d3b5c46d Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Tue, 9 Dec 2025 12:10:01 +0100 Subject: [PATCH 02/19] feat(#55): add custom CSS variable for total water graphic color --- front/src/app/globals.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/front/src/app/globals.css b/front/src/app/globals.css index 7025098..f0275c1 100644 --- a/front/src/app/globals.css +++ b/front/src/app/globals.css @@ -24,6 +24,9 @@ /* Title color */ --color-title: #051c1f; + /* Graphic total water */ + --color-total-water: #26d6ed; + /* Accesible visited link color */ --color-visited-link: #257782; From 4d49977d0bdf0c2ad748ebc668f79048835b64f0 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Tue, 9 Dec 2025 12:10:38 +0100 Subject: [PATCH 03/19] feat(#55): add reservoir data model and mock data --- front/src/app/model/reservoir-data.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 front/src/app/model/reservoir-data.ts diff --git a/front/src/app/model/reservoir-data.ts b/front/src/app/model/reservoir-data.ts new file mode 100644 index 0000000..6ace67b --- /dev/null +++ b/front/src/app/model/reservoir-data.ts @@ -0,0 +1,11 @@ +export interface ReservoirData { + currentVolume: number; + totalCapacity: number; + measurementDate: string; +} + +export const mockData: ReservoirData = { + currentVolume: 1500, + totalCapacity: 50000, + measurementDate: "25/12/2025", +}; From 45d9080dfa2d1af4a92787732c6f0bada502f9a9 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Tue, 9 Dec 2025 12:11:05 +0100 Subject: [PATCH 04/19] feat(#55): add gauge chart model with dimensions and arc configuration --- .../gauge-chart/components/model.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/model.ts diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/model.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/model.ts new file mode 100644 index 0000000..fb87310 --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/model.ts @@ -0,0 +1,25 @@ +interface GaugeDimensions { + width: number; + height: number; +} + +interface ArcConfig { + innerRadius: number; + outerRadius: number; + startAngle: number; + endAngle: number; + cornerRadius: number; +} + +export const gaugeDimensions: GaugeDimensions = { + width: 220, + height: 184, +}; + +export const arcConfig: ArcConfig = { + innerRadius: 90, + outerRadius: 110, + startAngle: -Math.PI * 0.75, + endAngle: Math.PI * 0.75, + cornerRadius: 12, +}; From e609d99dba7af135ed8c00eca8cb5de9401c4748 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Tue, 9 Dec 2025 12:15:14 +0100 Subject: [PATCH 05/19] feat(#55): implement gauge arcs component with D3.js for dynamic arc rendering --- .../components/gauge-arcs.business.ts | 38 +++++++++++++ .../components/gauge-arcs.component.tsx | 53 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts create mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts new file mode 100644 index 0000000..ddf2e5d --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts @@ -0,0 +1,38 @@ +import * as d3 from "d3"; +import { arcConfig } from "./model"; + +type ArcGroup = d3.Selection; + +interface DrawArcParams { + arcGroup: ArcGroup; + endAngle: number; + fillColor: string; +} + +const createArcGenerator = (endAngle: number) => { + return d3 + .arc() + .innerRadius(arcConfig.innerRadius) + .outerRadius(arcConfig.outerRadius) + .startAngle(arcConfig.startAngle) + .endAngle(endAngle) + .cornerRadius(arcConfig.cornerRadius); +}; + +// TODO: add unit tests for calculateFilledAngle + +export const calculateFilledAngle = (percentage: number): number => { + // Total sweep of the arc (from start to end) + const totalAngle = arcConfig.endAngle - arcConfig.startAngle; + // Calculate where the filled arc should end based on percentage + return arcConfig.startAngle + percentage * totalAngle; +}; + +export const drawArc = ({ arcGroup, endAngle, fillColor }: DrawArcParams) => { + const arcGenerator = createArcGenerator(endAngle); + + arcGroup + .append("path") + .attr("d", arcGenerator as any) + .style("fill", fillColor); +}; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx new file mode 100644 index 0000000..e9d4cb8 --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx @@ -0,0 +1,53 @@ +"use client"; + +import * as d3 from "d3"; +import { useEffect, useRef } from "react"; +import { calculateFilledAngle, drawArc } from "./gauge-arcs.business"; +import { arcConfig, gaugeDimensions } from "./model"; + +interface GaugeArcsProps { + percentage: number; +} + +export const GaugeArcs = ({ percentage }: GaugeArcsProps) => { + const svgRef = useRef(null); + + useEffect(() => { + if (!svgRef.current) return; + + const svg = d3.select(svgRef.current); + svg.selectAll("*").remove(); + + // Center position + const centerX = gaugeDimensions.width / 2; + const centerY = arcConfig.outerRadius; + + // Create centered group + const arcGroup = svg + .append("g") + .attr("transform", `translate(${centerX}, ${centerY})`); + + // 1. Background arc (--color-total-water, full) + drawArc({ + arcGroup, + endAngle: arcConfig.endAngle, + fillColor: "var(--color-total-water)", + }); + + // 2. Filled arc (primary color, based on percentage prop) + const filledEndAngle = calculateFilledAngle(percentage); + drawArc({ + arcGroup, + endAngle: filledEndAngle, + fillColor: "var(--color-primary)", + }); + }, [percentage]); + + return ( + + ); +}; From bfe17560cbc64541937192cd502f2f4acabbb1ed Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Tue, 9 Dec 2025 12:15:45 +0100 Subject: [PATCH 06/19] feat(#55): add GaugeChart component with gauge information and arcs for visual representation --- .../gauge-information.component.tsx | 20 ++++++++++++ .../gauge-chart/components/index.ts | 2 ++ .../gauge-chart/gauge-chart.component.tsx | 31 +++++++++++++++++++ .../reservoir-gauge/gauge-chart/index.ts | 1 + 4 files changed, 54 insertions(+) create mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-information.component.tsx create mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/index.ts create mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/gauge-chart.component.tsx create mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/index.ts diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-information.component.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-information.component.tsx new file mode 100644 index 0000000..7479665 --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-information.component.tsx @@ -0,0 +1,20 @@ +interface Props { + percentage: number; + measurementDate: string; +} + +export const GaugeInformation = ({ percentage, measurementDate }: Props) => { + const displayPercentage = `${Math.round(percentage * 100)}`; + + return ( +
+ + {displayPercentage} + % + + + {measurementDate} + +
+ ); +}; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/index.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/index.ts new file mode 100644 index 0000000..e73c0af --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/index.ts @@ -0,0 +1,2 @@ +export * from "./gauge-arcs.component"; +export * from "./gauge-information.component"; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/gauge-chart.component.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/gauge-chart.component.tsx new file mode 100644 index 0000000..b28c872 --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/gauge-chart.component.tsx @@ -0,0 +1,31 @@ +import { gaugeDimensions } from "./components/model"; +import { GaugeInformation } from "./components"; +import { GaugeArcs } from "./components/gauge-arcs.component"; + +interface Props { + percentage: number; + measurementDate: string; +} + +export const GaugeChart = ({ percentage, measurementDate }: Props) => { + return ( +
+ {/* The SVG arc */} + + + {/* Center text */} +
+ +
+
+ ); +}; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/index.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/index.ts new file mode 100644 index 0000000..1b2c7de --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/index.ts @@ -0,0 +1 @@ +export * from "./gauge-chart.component"; From e9a3e6262da649962a7639d264d35899b95501de Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Tue, 9 Dec 2025 12:16:51 +0100 Subject: [PATCH 07/19] feat(#55): integrate ReservoirGauge component into EmbalseDetallePage, to display reservoir data --- front/src/app/embalse/[embalse]/page.tsx | 7 ++++- .../gauge-legend.component.tsx | 26 +++++++++++++++++ .../[embalse]/reservoir-gauge/index.ts | 1 + .../reservoir-gauge/reservoir-gauge.tsx | 28 +++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/gauge-legend.component.tsx create mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/index.ts create mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx diff --git a/front/src/app/embalse/[embalse]/page.tsx b/front/src/app/embalse/[embalse]/page.tsx index a50d99c..6131595 100644 --- a/front/src/app/embalse/[embalse]/page.tsx +++ b/front/src/app/embalse/[embalse]/page.tsx @@ -1,4 +1,6 @@ import Link from "next/link"; +import { mockData } from "../../model/reservoir-data"; +import { ReservoirGauge } from "./reservoir-gauge"; interface Props { params: Promise<{ embalse: string }>; @@ -6,9 +8,12 @@ interface Props { export default async function EmbalseDetallePage({ params }: Props) { const { embalse } = await params; + + const reservoirData = mockData; + return (
-

Detalle del embalse: {embalse}

+
); } diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-legend.component.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-legend.component.tsx new file mode 100644 index 0000000..6cc484b --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-legend.component.tsx @@ -0,0 +1,26 @@ +interface Props { + currentVolume: number; + totalCapacity: number; +} + +export const GaugeLegend = ({ currentVolume, totalCapacity }: Props) => { + return ( +
+ {/* Embalsada (filled water) - uses primary color */} +
+ + + Embalsada: {currentVolume}m³ + +
+ + {/* Total capacity - uses total-water color */} +
+ + + Total: {totalCapacity}m³ + +
+
+ ); +}; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/index.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/index.ts new file mode 100644 index 0000000..315d231 --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/index.ts @@ -0,0 +1 @@ +export * from "./reservoir-gauge"; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx new file mode 100644 index 0000000..0b9cb77 --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx @@ -0,0 +1,28 @@ +import { ReservoirData } from "../../../model/reservoir-data"; +import { GaugeChart } from "./gauge-chart"; +import { GaugeLegend } from "./gauge-legend.component"; + +interface Props extends ReservoirData { + name: string; +} + +export const ReservoirGauge = ({ + name, + currentVolume, + totalCapacity, + measurementDate, +}: Props) => { + // const percentage = currentVolume / totalCapacity; + // TODO: replace hardcoded % for real reservoir filled water percentage + + return ( +
+

Embalse de {name}

+ + +
+ ); +}; From 795c1732e82258d20ad20cf2c9263cc5830ae37b Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Tue, 9 Dec 2025 12:46:42 +0100 Subject: [PATCH 08/19] fix(#55): normalize percentage input in calculateFilledAngle function to ensure valid arc rendering --- .../gauge-chart/components/gauge-arcs.business.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts index ddf2e5d..07a779d 100644 --- a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts @@ -22,10 +22,12 @@ const createArcGenerator = (endAngle: number) => { // TODO: add unit tests for calculateFilledAngle export const calculateFilledAngle = (percentage: number): number => { + // Ensure percentage is within valid range [0, 1] + const normalized = Math.max(0, Math.min(1, percentage)); // Total sweep of the arc (from start to end) const totalAngle = arcConfig.endAngle - arcConfig.startAngle; // Calculate where the filled arc should end based on percentage - return arcConfig.startAngle + percentage * totalAngle; + return arcConfig.startAngle + normalized * totalAngle; }; export const drawArc = ({ arcGroup, endAngle, fillColor }: DrawArcParams) => { From 63d207360cb98d937facc8f233913c1286152d64 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Tue, 9 Dec 2025 12:46:56 +0100 Subject: [PATCH 09/19] style(#55): update ReservoirGauge component styling for improved layout and text alignment --- .../app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx index 0b9cb77..568f6d7 100644 --- a/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx @@ -16,8 +16,8 @@ export const ReservoirGauge = ({ // TODO: replace hardcoded % for real reservoir filled water percentage return ( -
-

Embalse de {name}

+
+

Embalse de {name}

Date: Fri, 12 Dec 2025 10:15:27 +0100 Subject: [PATCH 10/19] refactor(#55): replace spread operator with explicit props in ReservoirGauge --- front/src/app/embalse/[embalse]/page.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/front/src/app/embalse/[embalse]/page.tsx b/front/src/app/embalse/[embalse]/page.tsx index 6131595..b3262d1 100644 --- a/front/src/app/embalse/[embalse]/page.tsx +++ b/front/src/app/embalse/[embalse]/page.tsx @@ -1,4 +1,3 @@ -import Link from "next/link"; import { mockData } from "../../model/reservoir-data"; import { ReservoirGauge } from "./reservoir-gauge"; @@ -13,7 +12,12 @@ export default async function EmbalseDetallePage({ params }: Props) { return (
- +
); } From f16bea142ab21a36af0034251b5b1e01b5db8d28 Mon Sep 17 00:00:00 2001 From: Gorka Reguero Date: Tue, 13 Jan 2026 11:46:25 +0100 Subject: [PATCH 11/19] animacion gauge-Arc --- .../components/gauge-arcs.business.ts | 38 +++++++++++++++++-- .../components/gauge-arcs.component.tsx | 4 +- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts index 07a779d..5bdb6bd 100644 --- a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts @@ -30,11 +30,43 @@ export const calculateFilledAngle = (percentage: number): number => { return arcConfig.startAngle + normalized * totalAngle; }; -export const drawArc = ({ arcGroup, endAngle, fillColor }: DrawArcParams) => { +export const drawArc = ({ arcGroup, endAngle, fillColor }: DrawArcParams, animate: boolean = false) => { const arcGenerator = createArcGenerator(endAngle); + if (animate) { + arcGroup + .append("path") + .attr("d", arcGenerator as any) + .style("fill", fillColor) + .attr("opacity", 0) + .transition() + .duration(1500) + .ease(d3.easeCubicInOut) + .attr("opacity", 1); + } else { + arcGroup + .append("path") + .attr("d", arcGenerator as any) + .style("fill", fillColor); + } +}; + +export const drawAnimatedArc = ({ arcGroup, endAngle, fillColor }: DrawArcParams) => { + const arcGeneratorStart = createArcGenerator(arcConfig.startAngle); + const arcGeneratorEnd = createArcGenerator(endAngle); + arcGroup .append("path") - .attr("d", arcGenerator as any) - .style("fill", fillColor); + .attr("d", arcGeneratorStart as any) + .style("fill", fillColor) + .transition() + .duration(2000) + .ease(d3.easeCubicInOut) + .attrTween("d", function() { + const interpolate = d3.interpolate(arcConfig.startAngle, endAngle); + return function(t) { + const arcGenerator = createArcGenerator(interpolate(t)); + return arcGenerator(this) || ""; + }; + }); }; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx index e9d4cb8..ab66153 100644 --- a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx @@ -2,7 +2,7 @@ import * as d3 from "d3"; import { useEffect, useRef } from "react"; -import { calculateFilledAngle, drawArc } from "./gauge-arcs.business"; +import { calculateFilledAngle, drawAnimatedArc, drawArc } from "./gauge-arcs.business"; import { arcConfig, gaugeDimensions } from "./model"; interface GaugeArcsProps { @@ -36,7 +36,7 @@ export const GaugeArcs = ({ percentage }: GaugeArcsProps) => { // 2. Filled arc (primary color, based on percentage prop) const filledEndAngle = calculateFilledAngle(percentage); - drawArc({ + drawAnimatedArc({ arcGroup, endAngle: filledEndAngle, fillColor: "var(--color-primary)", From c9c577d5dfdcb6cb8835b5984785479f67646ead Mon Sep 17 00:00:00 2001 From: Gorka Reguero Date: Tue, 20 Jan 2026 13:53:05 +0100 Subject: [PATCH 12/19] eliminacion funcion sin uso --- .../gauge-chart/components/gauge-arcs.business.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts index 5bdb6bd..059ee8b 100644 --- a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts @@ -51,9 +51,10 @@ export const drawArc = ({ arcGroup, endAngle, fillColor }: DrawArcParams, animat } }; + export const drawAnimatedArc = ({ arcGroup, endAngle, fillColor }: DrawArcParams) => { const arcGeneratorStart = createArcGenerator(arcConfig.startAngle); - const arcGeneratorEnd = createArcGenerator(endAngle); + arcGroup .append("path") From becf0cb54d84e8fda94f4b3698ea31d98db1aa4e Mon Sep 17 00:00:00 2001 From: Gorka Reguero Date: Tue, 20 Jan 2026 16:58:49 +0100 Subject: [PATCH 13/19] test + cards datos del embalse --- front/src/app/embalse/[embalse]/page.tsx | 4 ++ .../components/gauge-arcs.business.test.ts | 61 +++++++++++++++++++ .../components/gauge-arcs.business.ts | 2 +- .../components/gauge-arcs.component.tsx | 2 +- .../gauge-details.component.tsx | 32 ++++++++++ .../reservoir-gauge/gauge-info.component.tsx | 13 ++++ .../reservoir-gauge/reservoir-gauge.tsx | 27 +++++--- front/src/app/model/reservoir-data.ts | 33 ++++++++++ 8 files changed, 165 insertions(+), 9 deletions(-) create mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.test.ts create mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/gauge-details.component.tsx create mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/gauge-info.component.tsx diff --git a/front/src/app/embalse/[embalse]/page.tsx b/front/src/app/embalse/[embalse]/page.tsx index b3262d1..18fe049 100644 --- a/front/src/app/embalse/[embalse]/page.tsx +++ b/front/src/app/embalse/[embalse]/page.tsx @@ -9,6 +9,8 @@ export default async function EmbalseDetallePage({ params }: Props) { const { embalse } = await params; const reservoirData = mockData; + const datosEmbv = mockData.datosEmbalse; + const reservoirInfo = mockData.reservoirInfo; return (
@@ -17,6 +19,8 @@ export default async function EmbalseDetallePage({ params }: Props) { currentVolume={reservoirData.currentVolume} totalCapacity={reservoirData.totalCapacity} measurementDate={reservoirData.measurementDate} + datosEmbalse={datosEmbv} + reservoirInfo={reservoirInfo} />
); diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.test.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.test.ts new file mode 100644 index 0000000..713a152 --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.test.ts @@ -0,0 +1,61 @@ +import { describe, it, expect } from "vitest"; +import { calculateFilledAngle } from "./gauge-arcs.business"; +import { arcConfig } from "./model"; + +describe("calculateFilledAngle", () => { + it("should return start angle when percentage is 0", () => { + const result = calculateFilledAngle(0); + expect(result).toBe(arcConfig.startAngle); + }); + + it("should return end angle when percentage is 1", () => { + const result = calculateFilledAngle(1); + expect(result).toBe(arcConfig.endAngle); + }); + + it("should return middle angle when percentage is 0.5", () => { + const result = calculateFilledAngle(0.5); + const expectedMiddle = arcConfig.startAngle + (arcConfig.endAngle - arcConfig.startAngle) * 0.5; + expect(result).toBe(expectedMiddle); + }); + + it("should handle percentages between 0 and 1 correctly", () => { + const testCases = [ + { percentage: 0.25, expected: arcConfig.startAngle + (arcConfig.endAngle - arcConfig.startAngle) * 0.25 }, + { percentage: 0.75, expected: arcConfig.startAngle + (arcConfig.endAngle - arcConfig.startAngle) * 0.75 }, + { percentage: 0.1, expected: arcConfig.startAngle + (arcConfig.endAngle - arcConfig.startAngle) * 0.1 }, + ]; + + testCases.forEach(({ percentage, expected }) => { + const result = calculateFilledAngle(percentage); + expect(result).toBeCloseTo(expected, 10); + }); + }); + + it("should clamp negative percentages to 0", () => { + const result = calculateFilledAngle(-0.5); + expect(result).toBe(arcConfig.startAngle); + }); + + it("should clamp percentages greater than 1 to 1", () => { + const result = calculateFilledAngle(1.5); + expect(result).toBe(arcConfig.endAngle); + }); + + it("should handle edge case percentages", () => { + expect(calculateFilledAngle(-Infinity)).toBe(arcConfig.startAngle); + expect(calculateFilledAngle(Infinity)).toBe(arcConfig.endAngle); + expect(isNaN(calculateFilledAngle(NaN))).toBe(true); + }); + + it("should handle very small positive percentages", () => { + const result = calculateFilledAngle(0.001); + const expected = arcConfig.startAngle + (arcConfig.endAngle - arcConfig.startAngle) * 0.001; + expect(result).toBeCloseTo(expected, 10); + }); + + it("should handle very large negative percentages", () => { + const result = calculateFilledAngle(-1000); + expect(result).toBe(arcConfig.startAngle); + }); +}); \ No newline at end of file diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts index 059ee8b..f96eb17 100644 --- a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts @@ -19,7 +19,7 @@ const createArcGenerator = (endAngle: number) => { .cornerRadius(arcConfig.cornerRadius); }; -// TODO: add unit tests for calculateFilledAngle + export const calculateFilledAngle = (percentage: number): number => { // Ensure percentage is within valid range [0, 1] diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx index ab66153..3bb9e62 100644 --- a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx @@ -31,7 +31,7 @@ export const GaugeArcs = ({ percentage }: GaugeArcsProps) => { drawArc({ arcGroup, endAngle: arcConfig.endAngle, - fillColor: "var(--color-total-water)", + fillColor: "var(--color-total-water)", }); // 2. Filled arc (primary color, based on percentage prop) diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-details.component.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-details.component.tsx new file mode 100644 index 0000000..6546844 --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-details.component.tsx @@ -0,0 +1,32 @@ +'use client'; +import { use, useState } from 'react'; + +export const GaugeDetailsSkeleton = ({ datosEmbalse }) => { + + const [isOpen, setIsOpen] = useState(false); + return ( + +
+ +

Datos del embalse + +

+ {isOpen && ( +
    +
  • Cuenca: {datosEmbalse.Cuenca}
  • +
  • Provincia: {datosEmbalse.Provincia}
  • +
  • Municipio: {datosEmbalse.Municipio}
  • +
  • Río: {datosEmbalse.Rio}
  • +
  • Embalses Aguas Abajo: {datosEmbalse.EmbalsesAguasAbajo}
  • +
  • Tipo de Presa: {datosEmbalse.TipoDePresa}
  • +
  • Año de Construcción: {datosEmbalse.AnioConstruccion}
  • +
  • Superficie: {datosEmbalse.Superficie}
  • +
  • Localización: {datosEmbalse.Localizacion}
  • +
+ )} + +
+ ); +} diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-info.component.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-info.component.tsx new file mode 100644 index 0000000..e7296be --- /dev/null +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-info.component.tsx @@ -0,0 +1,13 @@ +export const ReservoirInfo = ({ reservoirInfo }) => { + return ( +
+

Descubre el embalse

+

{reservoirInfo?.Description}

+ Mapa de embalses +
+ ); +} diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx index 568f6d7..9e50213 100644 --- a/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx +++ b/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx @@ -1,6 +1,8 @@ import { ReservoirData } from "../../../model/reservoir-data"; import { GaugeChart } from "./gauge-chart"; +import { GaugeDetailsSkeleton } from "./gauge-details.component"; import { GaugeLegend } from "./gauge-legend.component"; +import { ReservoirInfo } from "./gauge-info.component"; interface Props extends ReservoirData { name: string; @@ -11,18 +13,29 @@ export const ReservoirGauge = ({ currentVolume, totalCapacity, measurementDate, + datosEmbalse, + reservoirInfo }: Props) => { // const percentage = currentVolume / totalCapacity; // TODO: replace hardcoded % for real reservoir filled water percentage return ( -
-

Embalse de {name}

- - +
+
+

Embalse de {name}

+ + +
+
+ +
+
+ +
+
); }; diff --git a/front/src/app/model/reservoir-data.ts b/front/src/app/model/reservoir-data.ts index 6ace67b..7e5297f 100644 --- a/front/src/app/model/reservoir-data.ts +++ b/front/src/app/model/reservoir-data.ts @@ -1,11 +1,44 @@ +export interface DatosEmbalse { + Cuenca: string; + Provincia: string; + Municipio: string; + Rio: string; + EmbalsesAguasAbajo: number; + TipoDePresa: string; + AnioConstruccion: number; + Superficie: number; + Localizacion: string; +} +export interface ReservoirInfo { +Description: string; +} + export interface ReservoirData { currentVolume: number; totalCapacity: number; measurementDate: string; + datosEmbalse: DatosEmbalse; + reservoirInfo : ReservoirInfo; } + export const mockData: ReservoirData = { currentVolume: 1500, totalCapacity: 50000, measurementDate: "25/12/2025", + datosEmbalse:{ + Cuenca: "Cuenca Ejemplo", + Provincia: "Provincia Ejemplo", + Municipio: "Municipio Ejemplo", + Rio: "Río Ejemplo", + EmbalsesAguasAbajo: 3, + TipoDePresa: "Tipo Ejemplo", + AnioConstruccion: 1990, + Superficie: 250, + Localizacion: "Localización Ejemplo" + }, + reservoirInfo: { + Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + } + }; From fce3677a43efd8fd6754d80dbacd54f81e51a84f Mon Sep 17 00:00:00 2001 From: Braulio Date: Wed, 28 Jan 2026 08:57:27 +0100 Subject: [PATCH 14/19] refactor to pod structure --- front/src/app/embalse/[embalse]/page.tsx | 30 +++++++++----- .../gauge-chart/components/index.ts | 2 - .../reservoir-gauge/gauge-chart/index.ts | 1 - .../gauge-details.component.tsx | 32 --------------- .../reservoir-gauge/gauge-info.component.tsx | 13 ------ .../[embalse]/reservoir-gauge/index.ts | 1 - .../reservoir-gauge/reservoir-gauge.tsx | 41 ------------------- front/src/app/layout.tsx | 2 +- .../{app => }/layouts/footer.component.tsx | 0 .../{app => }/layouts/header.component.tsx | 0 front/src/{app => }/layouts/index.ts | 0 front/src/{app => }/model/reservoir-data.ts | 0 .../components/reservoir-card-detail.tsx | 32 +++++++++++++++ .../components/reservoir-card-gauge.tsx | 32 +++++++++++++++ .../reservoir-card-info.component.tsx | 13 ++++++ .../components/gauge-arcs.business.test.ts | 0 .../components/gauge-arcs.business.ts | 0 .../components/gauge-arcs.component.tsx | 0 .../components}/gauge-chart.component.tsx | 6 +-- .../gauge-information.component.tsx | 0 .../components}/gauge-legend.component.tsx | 0 .../gauge-chart/components/index.tsx | 25 +++++++++++ .../gauge-chart/components/model.ts | 0 .../reservoir-gauge/gauge-chart/index.ts | 1 + .../components/reservoir-gauge/index.ts | 1 + front/src/pods/embalse/embalse.pod.tsx | 0 26 files changed, 128 insertions(+), 104 deletions(-) delete mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/index.ts delete mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/index.ts delete mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/gauge-details.component.tsx delete mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/gauge-info.component.tsx delete mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/index.ts delete mode 100644 front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx rename front/src/{app => }/layouts/footer.component.tsx (100%) rename front/src/{app => }/layouts/header.component.tsx (100%) rename front/src/{app => }/layouts/index.ts (100%) rename front/src/{app => }/model/reservoir-data.ts (100%) create mode 100644 front/src/pods/embalse/components/reservoir-card-detail.tsx create mode 100644 front/src/pods/embalse/components/reservoir-card-gauge.tsx create mode 100644 front/src/pods/embalse/components/reservoir-card-info.component.tsx rename front/src/{app/embalse/[embalse] => pods/embalse/components}/reservoir-gauge/gauge-chart/components/gauge-arcs.business.test.ts (100%) rename front/src/{app/embalse/[embalse] => pods/embalse/components}/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts (100%) rename front/src/{app/embalse/[embalse] => pods/embalse/components}/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx (100%) rename front/src/{app/embalse/[embalse]/reservoir-gauge/gauge-chart => pods/embalse/components/reservoir-gauge/gauge-chart/components}/gauge-chart.component.tsx (78%) rename front/src/{app/embalse/[embalse] => pods/embalse/components}/reservoir-gauge/gauge-chart/components/gauge-information.component.tsx (100%) rename front/src/{app/embalse/[embalse]/reservoir-gauge => pods/embalse/components/reservoir-gauge/gauge-chart/components}/gauge-legend.component.tsx (100%) create mode 100644 front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/index.tsx rename front/src/{app/embalse/[embalse] => pods/embalse/components}/reservoir-gauge/gauge-chart/components/model.ts (100%) create mode 100644 front/src/pods/embalse/components/reservoir-gauge/gauge-chart/index.ts create mode 100644 front/src/pods/embalse/components/reservoir-gauge/index.ts create mode 100644 front/src/pods/embalse/embalse.pod.tsx diff --git a/front/src/app/embalse/[embalse]/page.tsx b/front/src/app/embalse/[embalse]/page.tsx index 18fe049..cfb4e15 100644 --- a/front/src/app/embalse/[embalse]/page.tsx +++ b/front/src/app/embalse/[embalse]/page.tsx @@ -1,5 +1,7 @@ -import { mockData } from "../../model/reservoir-data"; -import { ReservoirGauge } from "./reservoir-gauge"; +import { mockData } from "../../../model/reservoir-data"; +import { ReservoirCardGauge } from "../../../pods/embalse/components/reservoir-gauge"; +import { ReservoirCardDetail } from "../../../pods/embalse/components/reservoir-card-detail"; +import { ReservoirCardInfo } from "../../../pods/embalse/components/reservoir-card-info.component"; interface Props { params: Promise<{ embalse: string }>; @@ -14,14 +16,22 @@ export default async function EmbalseDetallePage({ params }: Props) { return (
- +
+ +
+ +
+
+ +
+
); } diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/index.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/index.ts deleted file mode 100644 index e73c0af..0000000 --- a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./gauge-arcs.component"; -export * from "./gauge-information.component"; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/index.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/index.ts deleted file mode 100644 index 1b2c7de..0000000 --- a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./gauge-chart.component"; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-details.component.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-details.component.tsx deleted file mode 100644 index 6546844..0000000 --- a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-details.component.tsx +++ /dev/null @@ -1,32 +0,0 @@ -'use client'; -import { use, useState } from 'react'; - -export const GaugeDetailsSkeleton = ({ datosEmbalse }) => { - - const [isOpen, setIsOpen] = useState(false); - return ( - -
- -

Datos del embalse - -

- {isOpen && ( -
    -
  • Cuenca: {datosEmbalse.Cuenca}
  • -
  • Provincia: {datosEmbalse.Provincia}
  • -
  • Municipio: {datosEmbalse.Municipio}
  • -
  • Río: {datosEmbalse.Rio}
  • -
  • Embalses Aguas Abajo: {datosEmbalse.EmbalsesAguasAbajo}
  • -
  • Tipo de Presa: {datosEmbalse.TipoDePresa}
  • -
  • Año de Construcción: {datosEmbalse.AnioConstruccion}
  • -
  • Superficie: {datosEmbalse.Superficie}
  • -
  • Localización: {datosEmbalse.Localizacion}
  • -
- )} - -
- ); -} diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-info.component.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-info.component.tsx deleted file mode 100644 index e7296be..0000000 --- a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-info.component.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export const ReservoirInfo = ({ reservoirInfo }) => { - return ( -
-

Descubre el embalse

-

{reservoirInfo?.Description}

- Mapa de embalses -
- ); -} diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/index.ts b/front/src/app/embalse/[embalse]/reservoir-gauge/index.ts deleted file mode 100644 index 315d231..0000000 --- a/front/src/app/embalse/[embalse]/reservoir-gauge/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./reservoir-gauge"; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx b/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx deleted file mode 100644 index 9e50213..0000000 --- a/front/src/app/embalse/[embalse]/reservoir-gauge/reservoir-gauge.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { ReservoirData } from "../../../model/reservoir-data"; -import { GaugeChart } from "./gauge-chart"; -import { GaugeDetailsSkeleton } from "./gauge-details.component"; -import { GaugeLegend } from "./gauge-legend.component"; -import { ReservoirInfo } from "./gauge-info.component"; - -interface Props extends ReservoirData { - name: string; -} - -export const ReservoirGauge = ({ - name, - currentVolume, - totalCapacity, - measurementDate, - datosEmbalse, - reservoirInfo -}: Props) => { - // const percentage = currentVolume / totalCapacity; - // TODO: replace hardcoded % for real reservoir filled water percentage - - return ( -
-
-

Embalse de {name}

- - -
-
- -
-
- -
- -
- ); -}; diff --git a/front/src/app/layout.tsx b/front/src/app/layout.tsx index c4137ee..7b72e35 100644 --- a/front/src/app/layout.tsx +++ b/front/src/app/layout.tsx @@ -1,6 +1,6 @@ import React from "react"; import "./globals.css"; -import { FooterComponent, HeaderComponent } from "./layouts"; +import { FooterComponent, HeaderComponent } from "../layouts"; interface Props { children: React.ReactNode; diff --git a/front/src/app/layouts/footer.component.tsx b/front/src/layouts/footer.component.tsx similarity index 100% rename from front/src/app/layouts/footer.component.tsx rename to front/src/layouts/footer.component.tsx diff --git a/front/src/app/layouts/header.component.tsx b/front/src/layouts/header.component.tsx similarity index 100% rename from front/src/app/layouts/header.component.tsx rename to front/src/layouts/header.component.tsx diff --git a/front/src/app/layouts/index.ts b/front/src/layouts/index.ts similarity index 100% rename from front/src/app/layouts/index.ts rename to front/src/layouts/index.ts diff --git a/front/src/app/model/reservoir-data.ts b/front/src/model/reservoir-data.ts similarity index 100% rename from front/src/app/model/reservoir-data.ts rename to front/src/model/reservoir-data.ts diff --git a/front/src/pods/embalse/components/reservoir-card-detail.tsx b/front/src/pods/embalse/components/reservoir-card-detail.tsx new file mode 100644 index 0000000..f76f472 --- /dev/null +++ b/front/src/pods/embalse/components/reservoir-card-detail.tsx @@ -0,0 +1,32 @@ +"use client"; +import { use, useState } from "react"; + +export const ReservoirCardDetail = ({ datosEmbalse }) => { + const [isOpen, setIsOpen] = useState(false); + return ( +
+

+ Datos del embalse + +

+ {isOpen && ( +
    +
  • Cuenca: {datosEmbalse.Cuenca}
  • +
  • Provincia: {datosEmbalse.Provincia}
  • +
  • Municipio: {datosEmbalse.Municipio}
  • +
  • Río: {datosEmbalse.Rio}
  • +
  • Embalses Aguas Abajo: {datosEmbalse.EmbalsesAguasAbajo}
  • +
  • Tipo de Presa: {datosEmbalse.TipoDePresa}
  • +
  • Año de Construcción: {datosEmbalse.AnioConstruccion}
  • +
  • Superficie: {datosEmbalse.Superficie}
  • +
  • Localización: {datosEmbalse.Localizacion}
  • +
+ )} +
+ ); +}; diff --git a/front/src/pods/embalse/components/reservoir-card-gauge.tsx b/front/src/pods/embalse/components/reservoir-card-gauge.tsx new file mode 100644 index 0000000..65e30bd --- /dev/null +++ b/front/src/pods/embalse/components/reservoir-card-gauge.tsx @@ -0,0 +1,32 @@ +import { ReservoirData } from "../../../../model/reservoir-data"; +import { GaugeChart } from "./gauge-chart"; +import { ReservoirCardDetail } from "../reservoir-card-detail"; +import { GaugeLegend } from "./gauge-chart/components/gauge-legend.component"; +import { ReservoirCardInfo } from "./reservoir-card-info.component"; + +interface Props extends ReservoirData { + name: string; +} + +export const ReservoirCardGauge = ({ + name, + currentVolume, + totalCapacity, + measurementDate, + datosEmbalse, + reservoirInfo, +}: Props) => { + // const percentage = currentVolume / totalCapacity; + // TODO: replace hardcoded % for real reservoir filled water percentage + + return ( +
+

Embalse de {name}

+ + +
+ ); +}; diff --git a/front/src/pods/embalse/components/reservoir-card-info.component.tsx b/front/src/pods/embalse/components/reservoir-card-info.component.tsx new file mode 100644 index 0000000..4b93190 --- /dev/null +++ b/front/src/pods/embalse/components/reservoir-card-info.component.tsx @@ -0,0 +1,13 @@ +export const ReservoirCardInfo = ({ reservoirInfo }) => { + return ( +
+

Descubre el embalse

+

{reservoirInfo?.Description}

+ Mapa de embalses +
+ ); +}; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.test.ts b/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/gauge-arcs.business.test.ts similarity index 100% rename from front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.test.ts rename to front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/gauge-arcs.business.test.ts diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts b/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts similarity index 100% rename from front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts rename to front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/gauge-arcs.business.ts diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx b/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx similarity index 100% rename from front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx rename to front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/gauge-arcs.component.tsx diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/gauge-chart.component.tsx b/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/gauge-chart.component.tsx similarity index 78% rename from front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/gauge-chart.component.tsx rename to front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/gauge-chart.component.tsx index b28c872..a759ac3 100644 --- a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/gauge-chart.component.tsx +++ b/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/gauge-chart.component.tsx @@ -1,6 +1,6 @@ -import { gaugeDimensions } from "./components/model"; -import { GaugeInformation } from "./components"; -import { GaugeArcs } from "./components/gauge-arcs.component"; +import { gaugeDimensions } from "./model"; +import { GaugeInformation } from "./gauge-information.component"; +import { GaugeArcs } from "./gauge-arcs.component"; interface Props { percentage: number; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-information.component.tsx b/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/gauge-information.component.tsx similarity index 100% rename from front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/gauge-information.component.tsx rename to front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/gauge-information.component.tsx diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-legend.component.tsx b/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/gauge-legend.component.tsx similarity index 100% rename from front/src/app/embalse/[embalse]/reservoir-gauge/gauge-legend.component.tsx rename to front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/gauge-legend.component.tsx diff --git a/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/index.tsx b/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/index.tsx new file mode 100644 index 0000000..3ee07fc --- /dev/null +++ b/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/index.tsx @@ -0,0 +1,25 @@ +import { GaugeChart } from "./gauge-chart.component"; +import { GaugeLegend } from "./gauge-legend.component"; + +interface Props { + name: string; + percentage: number; + measurementDate: string; + currentVolume: number; + totalCapacity: number; +} + +export const ReservoirGauge = (props: Props) => { + const { name, percentage, measurementDate, currentVolume, totalCapacity } = + props; + return ( +
+

Embalse de {name}

+ + +
+ ); +}; diff --git a/front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/model.ts b/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/model.ts similarity index 100% rename from front/src/app/embalse/[embalse]/reservoir-gauge/gauge-chart/components/model.ts rename to front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/model.ts diff --git a/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/index.ts b/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/index.ts new file mode 100644 index 0000000..5304589 --- /dev/null +++ b/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/index.ts @@ -0,0 +1 @@ +export * from "./components/gauge-chart.component"; diff --git a/front/src/pods/embalse/components/reservoir-gauge/index.ts b/front/src/pods/embalse/components/reservoir-gauge/index.ts new file mode 100644 index 0000000..760c75d --- /dev/null +++ b/front/src/pods/embalse/components/reservoir-gauge/index.ts @@ -0,0 +1 @@ +export * from "./gauge-chart"; diff --git a/front/src/pods/embalse/embalse.pod.tsx b/front/src/pods/embalse/embalse.pod.tsx new file mode 100644 index 0000000..e69de29 From aaf1ac30b6105fb3f8c94ee853b99094adc67263 Mon Sep 17 00:00:00 2001 From: Braulio Date: Wed, 28 Jan 2026 09:01:04 +0100 Subject: [PATCH 15/19] update --- front/src/app/embalse/[embalse]/page.tsx | 6 +++--- .../pods/embalse/components/reservoir-card-gauge.tsx | 10 +++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/front/src/app/embalse/[embalse]/page.tsx b/front/src/app/embalse/[embalse]/page.tsx index cfb4e15..1ceea12 100644 --- a/front/src/app/embalse/[embalse]/page.tsx +++ b/front/src/app/embalse/[embalse]/page.tsx @@ -1,7 +1,7 @@ +import { ReservoirCardGauge } from "@/pods/embalse/components/reservoir-card-gauge"; import { mockData } from "../../../model/reservoir-data"; -import { ReservoirCardGauge } from "../../../pods/embalse/components/reservoir-gauge"; -import { ReservoirCardDetail } from "../../../pods/embalse/components/reservoir-card-detail"; -import { ReservoirCardInfo } from "../../../pods/embalse/components/reservoir-card-info.component"; +import { ReservoirCardInfo } from "@/pods/embalse/components/reservoir-card-info.component"; +import { ReservoirCardDetail } from "@/pods/embalse/components/reservoir-card-detail"; interface Props { params: Promise<{ embalse: string }>; diff --git a/front/src/pods/embalse/components/reservoir-card-gauge.tsx b/front/src/pods/embalse/components/reservoir-card-gauge.tsx index 65e30bd..b23d385 100644 --- a/front/src/pods/embalse/components/reservoir-card-gauge.tsx +++ b/front/src/pods/embalse/components/reservoir-card-gauge.tsx @@ -1,8 +1,6 @@ -import { ReservoirData } from "../../../../model/reservoir-data"; -import { GaugeChart } from "./gauge-chart"; -import { ReservoirCardDetail } from "../reservoir-card-detail"; -import { GaugeLegend } from "./gauge-chart/components/gauge-legend.component"; -import { ReservoirCardInfo } from "./reservoir-card-info.component"; +import { ReservoirData } from "@/model/reservoir-data"; +import { GaugeChart } from "./reservoir-gauge"; +import { GaugeLegend } from "./reservoir-gauge/gauge-chart/components/gauge-legend.component"; interface Props extends ReservoirData { name: string; @@ -13,8 +11,6 @@ export const ReservoirCardGauge = ({ currentVolume, totalCapacity, measurementDate, - datosEmbalse, - reservoirInfo, }: Props) => { // const percentage = currentVolume / totalCapacity; // TODO: replace hardcoded % for real reservoir filled water percentage From 3aeca9ec0b8443104cc6d21e1ac4d6ded9a3b04a Mon Sep 17 00:00:00 2001 From: manudous Date: Wed, 28 Jan 2026 09:11:49 +0100 Subject: [PATCH 16/19] implement EmbalsePod component and refactor EmbalseDetallePage to use it --- front/src/app/embalse/[embalse]/page.tsx | 30 ++-------------- front/src/pods/embalse/components/index.ts | 4 +++ front/src/pods/embalse/embalse.pod.tsx | 40 ++++++++++++++++++++++ front/src/pods/embalse/index.ts | 1 + 4 files changed, 47 insertions(+), 28 deletions(-) create mode 100644 front/src/pods/embalse/components/index.ts create mode 100644 front/src/pods/embalse/index.ts diff --git a/front/src/app/embalse/[embalse]/page.tsx b/front/src/app/embalse/[embalse]/page.tsx index 1ceea12..86a6d43 100644 --- a/front/src/app/embalse/[embalse]/page.tsx +++ b/front/src/app/embalse/[embalse]/page.tsx @@ -1,7 +1,4 @@ -import { ReservoirCardGauge } from "@/pods/embalse/components/reservoir-card-gauge"; -import { mockData } from "../../../model/reservoir-data"; -import { ReservoirCardInfo } from "@/pods/embalse/components/reservoir-card-info.component"; -import { ReservoirCardDetail } from "@/pods/embalse/components/reservoir-card-detail"; +import { EmbalsePod } from "@/pods/embalse"; interface Props { params: Promise<{ embalse: string }>; @@ -10,28 +7,5 @@ interface Props { export default async function EmbalseDetallePage({ params }: Props) { const { embalse } = await params; - const reservoirData = mockData; - const datosEmbv = mockData.datosEmbalse; - const reservoirInfo = mockData.reservoirInfo; - - return ( -
-
- -
- -
-
- -
-
-
- ); + return ; } diff --git a/front/src/pods/embalse/components/index.ts b/front/src/pods/embalse/components/index.ts new file mode 100644 index 0000000..0843170 --- /dev/null +++ b/front/src/pods/embalse/components/index.ts @@ -0,0 +1,4 @@ +export * from "./reservoir-card-detail"; +export * from "./reservoir-card-gauge"; +export * from "./reservoir-card-info.component"; +export * from "./reservoir-gauge"; diff --git a/front/src/pods/embalse/embalse.pod.tsx b/front/src/pods/embalse/embalse.pod.tsx index e69de29..7f03a75 100644 --- a/front/src/pods/embalse/embalse.pod.tsx +++ b/front/src/pods/embalse/embalse.pod.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { + ReservoirCardDetail, + ReservoirCardGauge, + ReservoirCardInfo, +} from "./components"; +import { mockData } from "@/model/reservoir-data"; + +interface Props { + embalse: string; +} + +export const EmbalsePod: React.FC = (props) => { + const { embalse } = props; + + const reservoirData = mockData; + const datosEmbv = mockData.datosEmbalse; + const reservoirInfo = mockData.reservoirInfo; + + return ( +
+
+ +
+ +
+
+ +
+
+
+ ); +}; diff --git a/front/src/pods/embalse/index.ts b/front/src/pods/embalse/index.ts new file mode 100644 index 0000000..0f10c84 --- /dev/null +++ b/front/src/pods/embalse/index.ts @@ -0,0 +1 @@ +export * from "./embalse.pod"; From b917d2be86b4f391c1b029460f7904e4b24da28f Mon Sep 17 00:00:00 2001 From: manudous Date: Wed, 28 Jan 2026 09:39:36 +0100 Subject: [PATCH 17/19] refactor: restructure reservoir data handling and update components to use new data model --- front/src/model/reservoir-data.ts | 44 ------------------ .../components/reservoir-card-detail.tsx | 46 ++++++++----------- .../components/reservoir-card-gauge.tsx | 14 +++--- .../reservoir-card-info.component.tsx | 9 +++- front/src/pods/embalse/embalse-mock-data.ts | 22 +++++++++ front/src/pods/embalse/embalse.pod.tsx | 19 ++------ front/src/pods/embalse/embalse.vm.ts | 23 ++++++++++ 7 files changed, 83 insertions(+), 94 deletions(-) delete mode 100644 front/src/model/reservoir-data.ts create mode 100644 front/src/pods/embalse/embalse-mock-data.ts create mode 100644 front/src/pods/embalse/embalse.vm.ts diff --git a/front/src/model/reservoir-data.ts b/front/src/model/reservoir-data.ts deleted file mode 100644 index 7e5297f..0000000 --- a/front/src/model/reservoir-data.ts +++ /dev/null @@ -1,44 +0,0 @@ -export interface DatosEmbalse { - Cuenca: string; - Provincia: string; - Municipio: string; - Rio: string; - EmbalsesAguasAbajo: number; - TipoDePresa: string; - AnioConstruccion: number; - Superficie: number; - Localizacion: string; -} -export interface ReservoirInfo { -Description: string; -} - -export interface ReservoirData { - currentVolume: number; - totalCapacity: number; - measurementDate: string; - datosEmbalse: DatosEmbalse; - reservoirInfo : ReservoirInfo; -} - - -export const mockData: ReservoirData = { - currentVolume: 1500, - totalCapacity: 50000, - measurementDate: "25/12/2025", - datosEmbalse:{ - Cuenca: "Cuenca Ejemplo", - Provincia: "Provincia Ejemplo", - Municipio: "Municipio Ejemplo", - Rio: "Río Ejemplo", - EmbalsesAguasAbajo: 3, - TipoDePresa: "Tipo Ejemplo", - AnioConstruccion: 1990, - Superficie: 250, - Localizacion: "Localización Ejemplo" - }, - reservoirInfo: { - Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." - } - -}; diff --git a/front/src/pods/embalse/components/reservoir-card-detail.tsx b/front/src/pods/embalse/components/reservoir-card-detail.tsx index f76f472..d2a3661 100644 --- a/front/src/pods/embalse/components/reservoir-card-detail.tsx +++ b/front/src/pods/embalse/components/reservoir-card-detail.tsx @@ -1,32 +1,26 @@ -"use client"; -import { use, useState } from "react"; +import React from "react"; +import { DatosEmbalse } from "../embalse.vm"; -export const ReservoirCardDetail = ({ datosEmbalse }) => { - const [isOpen, setIsOpen] = useState(false); +interface Props { + datosEmbalse: DatosEmbalse; +} + +export const ReservoirCardDetail: React.FC = (props) => { + const { datosEmbalse } = props; return (
-

- Datos del embalse - -

- {isOpen && ( -
    -
  • Cuenca: {datosEmbalse.Cuenca}
  • -
  • Provincia: {datosEmbalse.Provincia}
  • -
  • Municipio: {datosEmbalse.Municipio}
  • -
  • Río: {datosEmbalse.Rio}
  • -
  • Embalses Aguas Abajo: {datosEmbalse.EmbalsesAguasAbajo}
  • -
  • Tipo de Presa: {datosEmbalse.TipoDePresa}
  • -
  • Año de Construcción: {datosEmbalse.AnioConstruccion}
  • -
  • Superficie: {datosEmbalse.Superficie}
  • -
  • Localización: {datosEmbalse.Localizacion}
  • -
- )} +

Datos del embalse

+
    +
  • Cuenca: {datosEmbalse.cuenca}
  • +
  • Provincia: {datosEmbalse.provincia}
  • +
  • Municipio: {datosEmbalse.municipio}
  • +
  • Río: {datosEmbalse.rio}
  • +
  • Embalses Aguas Abajo: {datosEmbalse.embalsesAguasAbajo}
  • +
  • Tipo de Presa: {datosEmbalse.tipoDePresa}
  • +
  • Año de Construcción: {datosEmbalse.anioConstruccion}
  • +
  • Superficie: {datosEmbalse.superficie}
  • +
  • Localización: {datosEmbalse.localizacion}
  • +
); }; diff --git a/front/src/pods/embalse/components/reservoir-card-gauge.tsx b/front/src/pods/embalse/components/reservoir-card-gauge.tsx index b23d385..f3d4a35 100644 --- a/front/src/pods/embalse/components/reservoir-card-gauge.tsx +++ b/front/src/pods/embalse/components/reservoir-card-gauge.tsx @@ -1,17 +1,15 @@ -import { ReservoirData } from "@/model/reservoir-data"; +import { ReservoirData } from "../embalse.vm"; import { GaugeChart } from "./reservoir-gauge"; import { GaugeLegend } from "./reservoir-gauge/gauge-chart/components/gauge-legend.component"; -interface Props extends ReservoirData { +interface Props { name: string; + reservoirData: ReservoirData; } -export const ReservoirCardGauge = ({ - name, - currentVolume, - totalCapacity, - measurementDate, -}: Props) => { +export const ReservoirCardGauge: React.FC = (props) => { + const { name, reservoirData } = props; + const { currentVolume, totalCapacity, measurementDate } = reservoirData; // const percentage = currentVolume / totalCapacity; // TODO: replace hardcoded % for real reservoir filled water percentage diff --git a/front/src/pods/embalse/components/reservoir-card-info.component.tsx b/front/src/pods/embalse/components/reservoir-card-info.component.tsx index 4b93190..06f01c6 100644 --- a/front/src/pods/embalse/components/reservoir-card-info.component.tsx +++ b/front/src/pods/embalse/components/reservoir-card-info.component.tsx @@ -1,4 +1,11 @@ -export const ReservoirCardInfo = ({ reservoirInfo }) => { +import { ReservoirInfo } from "../embalse.vm"; + +interface Props { + reservoirInfo: ReservoirInfo; +} + +export const ReservoirCardInfo: React.FC = (props) => { + const { reservoirInfo } = props; return (

Descubre el embalse

diff --git a/front/src/pods/embalse/embalse-mock-data.ts b/front/src/pods/embalse/embalse-mock-data.ts new file mode 100644 index 0000000..b9a3892 --- /dev/null +++ b/front/src/pods/embalse/embalse-mock-data.ts @@ -0,0 +1,22 @@ +import { ReservoirData } from "./embalse.vm"; + +export const MOCK_DATA: ReservoirData = { + currentVolume: 1500, + totalCapacity: 50000, + measurementDate: "25/12/2025", + datosEmbalse: { + cuenca: "Cuenca Ejemplo", + provincia: "Provincia Ejemplo", + municipio: "Municipio Ejemplo", + rio: "Río Ejemplo", + embalsesAguasAbajo: 3, + tipoDePresa: "Tipo Ejemplo", + anioConstruccion: 1990, + superficie: 250, + localizacion: "Localización Ejemplo", + }, + reservoirInfo: { + Description: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + }, +}; diff --git a/front/src/pods/embalse/embalse.pod.tsx b/front/src/pods/embalse/embalse.pod.tsx index 7f03a75..d327947 100644 --- a/front/src/pods/embalse/embalse.pod.tsx +++ b/front/src/pods/embalse/embalse.pod.tsx @@ -4,7 +4,7 @@ import { ReservoirCardGauge, ReservoirCardInfo, } from "./components"; -import { mockData } from "@/model/reservoir-data"; +import { MOCK_DATA } from "./embalse-mock-data"; interface Props { embalse: string; @@ -13,26 +13,15 @@ interface Props { export const EmbalsePod: React.FC = (props) => { const { embalse } = props; - const reservoirData = mockData; - const datosEmbv = mockData.datosEmbalse; - const reservoirInfo = mockData.reservoirInfo; - return (
- +
- +
- +
diff --git a/front/src/pods/embalse/embalse.vm.ts b/front/src/pods/embalse/embalse.vm.ts new file mode 100644 index 0000000..2f778f8 --- /dev/null +++ b/front/src/pods/embalse/embalse.vm.ts @@ -0,0 +1,23 @@ +export interface DatosEmbalse { + cuenca: string; + provincia: string; + municipio: string; + rio: string; + embalsesAguasAbajo: number; + tipoDePresa: string; + anioConstruccion: number; + superficie: number; + localizacion: string; +} + +export interface ReservoirInfo { + Description: string; +} + +export interface ReservoirData { + currentVolume: number; + totalCapacity: number; + measurementDate: string; + datosEmbalse: DatosEmbalse; + reservoirInfo: ReservoirInfo; +} From ccc6d29f16948ada7ad6c27f275d9e1554a65904 Mon Sep 17 00:00:00 2001 From: manudous Date: Wed, 28 Jan 2026 09:42:38 +0100 Subject: [PATCH 18/19] feat: add Embalse component and refactor EmbalsePod to utilize it --- front/src/pods/embalse/embalse.component.tsx | 28 ++++++++++++++++++++ front/src/pods/embalse/embalse.pod.tsx | 21 ++------------- 2 files changed, 30 insertions(+), 19 deletions(-) create mode 100644 front/src/pods/embalse/embalse.component.tsx diff --git a/front/src/pods/embalse/embalse.component.tsx b/front/src/pods/embalse/embalse.component.tsx new file mode 100644 index 0000000..8b52f41 --- /dev/null +++ b/front/src/pods/embalse/embalse.component.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import { + ReservoirCardDetail, + ReservoirCardGauge, + ReservoirCardInfo, +} from "./components"; +import { MOCK_DATA } from "./embalse-mock-data"; + +interface Props { + embalse: string; +} + +export const Embalse: React.FC = (props) => { + const { embalse } = props; + return ( +
+
+ +
+ +
+
+ +
+
+
+ ); +}; diff --git a/front/src/pods/embalse/embalse.pod.tsx b/front/src/pods/embalse/embalse.pod.tsx index d327947..e1c978d 100644 --- a/front/src/pods/embalse/embalse.pod.tsx +++ b/front/src/pods/embalse/embalse.pod.tsx @@ -1,10 +1,5 @@ import React from "react"; -import { - ReservoirCardDetail, - ReservoirCardGauge, - ReservoirCardInfo, -} from "./components"; -import { MOCK_DATA } from "./embalse-mock-data"; +import { Embalse } from "./embalse.component"; interface Props { embalse: string; @@ -13,17 +8,5 @@ interface Props { export const EmbalsePod: React.FC = (props) => { const { embalse } = props; - return ( -
-
- -
- -
-
- -
-
-
- ); + return ; }; From 81cee3d9f0c066d33023b4e749fc879a2d53ac9d Mon Sep 17 00:00:00 2001 From: manudous Date: Wed, 28 Jan 2026 11:14:01 +0100 Subject: [PATCH 19/19] refactor: remove unused percentage prop from ReservoirGauge component --- .../reservoir-gauge/gauge-chart/components/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/index.tsx b/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/index.tsx index 3ee07fc..83fb7a7 100644 --- a/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/index.tsx +++ b/front/src/pods/embalse/components/reservoir-gauge/gauge-chart/components/index.tsx @@ -10,8 +10,7 @@ interface Props { } export const ReservoirGauge = (props: Props) => { - const { name, percentage, measurementDate, currentVolume, totalCapacity } = - props; + const { name, measurementDate, currentVolume, totalCapacity } = props; return (

Embalse de {name}