Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion front/src/app/embalse-provincia/[provincia]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import Link from "next/link";
import { PROVINCIAS } from "@/core/constants";
import { Card } from "@/common/components/card.component";
import { Metadata } from "next";

interface Props {
params: Promise<{ provincia: string }>;
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { provincia } = await params;

const nombreProvincia = PROVINCIAS.find(
(province) => province.id === provincia,
)?.name;

return {
title: `Embalses de ${nombreProvincia}`,
};
}

export default async function EmbalseProvinciaListadoPage({ params }: Props) {
const { provincia } = await params;

Expand Down Expand Up @@ -34,7 +47,7 @@ export default async function EmbalseProvinciaListadoPage({ params }: Props) {
<img
className="mt-4 w-full rounded-xl sm:w-1/2 lg:w-1/3"
src="/images/embalse-generico.jpg"
alt="Mapa de embalses"
alt={`Mapa de ubicación de embalses de ${nombreProvincia}`}
/>
</Card>
);
Expand Down
5 changes: 5 additions & 0 deletions front/src/app/embalse-provincia/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import Link from "next/link";
import { Metadata } from "next";
import { PROVINCIAS } from "@/core/constants";
import { Card } from "@/common/components/card.component";

export const metadata: Metadata = {
title: "Embalses por provincias",
};

export default function EmbalsesProvinciaPage() {
return (
<Card>
Expand Down
15 changes: 14 additions & 1 deletion front/src/app/embalse/[embalse]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { EmbalsePod } from "@/pods/embalse";
import { getEmbalseBySlug } from "@/pods/embalse/embalse.repository";
import { mapEmbalseToReservoirData } from "@/pods/embalse/embalse.mapper";

export const revalidate = 300; // ISR: regenerar cada 5 minutos

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { embalse } = await params;
const embalseSlug = await getEmbalseBySlug(embalse);

return {
title: embalseSlug.nombre,
};
}

interface Props {
params: Promise<{ embalse: string }>;
}

export default async function EmbalseDetallePage({ params }: Props) {
/**
* Llamamos a getEmbalseBySlug con el slug de la URL.
Expand All @@ -19,4 +32,4 @@ export default async function EmbalseDetallePage({ params }: Props) {
//mapeamos el documento a ReservoirData y lo pasamos al pod
const reservoirData = mapEmbalseToReservoirData(embalseDoc);
return <EmbalsePod reservoirData={reservoirData} />;
}
}
2 changes: 1 addition & 1 deletion front/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface Props {
const RootLayout = (props: Props) => {
const { children } = props;
return (
<html lang="en" data-theme="info-embalse">
<html lang="es" data-theme="info-embalse">
<body
className="bg-base-200 text-base-content flex min-h-screen flex-col"
suppressHydrationWarning
Expand Down
2 changes: 1 addition & 1 deletion front/src/layouts/footer.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const FooterComponent: FC = () => {
return (
<footer className="bg-base-100">
<div className="border-accent flex flex-col items-center gap-2 border-t-2 p-3">
<div className="flex flex-col items-center gap-2 pb-[10px]">
<div className="flex flex-col items-center gap-2 pb-2.5">
<Link
href="/embalse-provincia"
className="link-accessible text-[15px] leading-none font-normal"
Expand Down
5 changes: 4 additions & 1 deletion front/src/layouts/header.component.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import Link from "next/link";
import { FC } from "react";

export const HeaderComponent: FC = () => {
return (
<header className="bg-base-100">
<div className="border-accent flex h-[60px] items-center border-b-3 pl-5">
<img src="/images/logo.svg" alt="InfoEmbalse logo" />
<Link href="/" className="link-accessible text-lg font-bold">
<img src="/images/logo.svg" alt="InfoEmbalse logo" />
</Link>
</div>
</header>
);
Expand Down
43 changes: 43 additions & 0 deletions front/src/pods/embalse-search/components/filtered-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import { useCombobox } from "downshift";
import { EmbalseSearchModel } from "../embalse-search.vm";

interface Props {
isOpen: boolean;
filteredEmbalses: EmbalseSearchModel[];
getMenuProps: ReturnType<typeof useCombobox>["getMenuProps"];
getItemProps: ReturnType<typeof useCombobox>["getItemProps"];
highlightedIndex: number | null;
}

export const FilteredList: React.FC<Props> = (props) => {
const {
isOpen,
filteredEmbalses,
getMenuProps,
getItemProps,
highlightedIndex,
} = props;

return (
<ul
{...getMenuProps()}
className={`menu bg-base-100 absolute z-10 mt-1 max-h-60 w-full overflow-y-auto rounded-lg shadow-lg ${
isOpen && filteredEmbalses.length > 0 ? "" : "hidden"
}`}
>
{isOpen &&
filteredEmbalses.map((item, index) => (
<li
key={item.slug}
{...getItemProps({ item, index })}
className={`cursor-pointer px-4 py-2 ${
highlightedIndex === index ? "bg-primary text-white" : ""
}`}
>
{item.name}
</li>
))}
</ul>
);
};
28 changes: 28 additions & 0 deletions front/src/pods/embalse-search/components/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from "react";
import { useCombobox } from "downshift";
import { SearchIcon } from "./search-icon";

interface Props {
getInputProps: ReturnType<typeof useCombobox>["getInputProps"];
}

export const Input: React.FC<Props> = (props) => {
const { getInputProps } = props;

return (
<label
htmlFor="embalse-search"
className="input input-bordered flex w-full items-center gap-2"
>
<input
{...getInputProps({
placeholder: "La tolba",
className: "grow",
id: "embalse-search",
"aria-label": "Buscar embalse por nombre o provincia",
})}
/>
<SearchIcon />
</label>
);
};
21 changes: 21 additions & 0 deletions front/src/pods/embalse-search/components/no-result.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from "react";

interface Props {
inputValue: string;
}

export const NoResult: React.FC<Props> = (props) => {
const { inputValue } = props;
return (
<div
className="bg-base-100 absolute z-10 mt-1 w-full rounded-lg p-4 text-center shadow-lg"
role="status"
aria-live="polite"
>
<p className="text-sm opacity-70">
No se encontraron embalses que coincidan con &quot;
{inputValue}&quot;
</p>
</div>
);
};
1 change: 1 addition & 0 deletions front/src/pods/embalse-search/components/search-icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from "react";
export const SearchIcon: React.FC = () => {
return (
<svg
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
Expand Down
68 changes: 31 additions & 37 deletions front/src/pods/embalse-search/embalse-search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import { useState } from "react";
import { useCombobox } from "downshift";
import { useRouter } from "next/navigation";
import { SearchIcon } from "./components/search-icon";
import { NoResult } from "./components/no-result";
import { Embalse } from "./api";
import { EmbalseSearchModel } from "./embalse-search.vm";
import { mapEmbalseToSearch } from "./embalse-search.mapper";
import { FilteredList } from "./components/filtered-list";
import { Input } from "./components/input";

interface Props {
embalses: Embalse[];
Expand All @@ -18,6 +20,8 @@ export const EmbalseSearch: React.FC<Props> = (props) => {
const [filteredEmbalses, setFilteredEmbalses] = useState<
EmbalseSearchModel[]
>([]);
const [isNavigating, setIsNavigating] = useState<boolean>(false);
const [inputValue, setInputValue] = useState<string>("");

const getFilteredEmbalses = (inputValue: string): EmbalseSearchModel[] => {
const lower = inputValue.toLowerCase();
Expand All @@ -41,57 +45,47 @@ export const EmbalseSearch: React.FC<Props> = (props) => {
items: filteredEmbalses,
itemToString: (item) => (item ? item.name : ""),
onInputValueChange: ({ inputValue: newValue }) => {
setInputValue(newValue || "");
setFilteredEmbalses(newValue ? getFilteredEmbalses(newValue) : []);
},
onSelectedItemChange: ({ selectedItem }) => {
if (selectedItem) {
//console.log("Embalse seleccionado: ", selectedItem);
setIsNavigating(true);
router.push(`/embalse/${selectedItem.slug}`);
}
},
});

const showNoResults = inputValue.length > 0 && filteredEmbalses.length === 0 && !isNavigating;

return (
<div className="relative flex flex-1 flex-col overflow-hidden p-8">
<div className="absolute inset-0 bg-[url('/images/embalse-generico.jpg')] bg-cover bg-center p-8 opacity-40"></div>
<div
className="absolute inset-0 bg-[url('/images/embalse-generico.jpg')] bg-cover bg-center p-8 opacity-40"
aria-hidden="true"
></div>
<div className="flex grow flex-col items-center justify-center">
<div className="bg-base-100 absolute flex max-w-10/12 flex-col gap-8 rounded-xl p-8 shadow-lg">
<section
className="bg-base-100 absolute flex max-w-10/12 flex-col gap-8 rounded-xl p-8 shadow-lg"
aria-labelledby="search-title"
>
<div className="text-center">
<h2 className="font-bold">Embalses</h2>
<h2 id="search-title" className="font-bold">
Embalses
</h2>
</div>

<div className="flex flex-col gap-4">
<div className="relative">
<label className="input input-bordered flex w-full items-center gap-2">
<input
{...getInputProps({
placeholder: "La tolba",
className: "grow",
})}
/>
<SearchIcon />
</label>
<ul
{...getMenuProps()}
className={`menu bg-base-100 absolute z-10 mt-1 max-h-60 w-full overflow-y-auto rounded-lg shadow-lg ${
isOpen && filteredEmbalses.length > 0 ? "" : "hidden"
}`}
>
{isOpen &&
filteredEmbalses.map((item, index) => (
<li
key={item.slug}
{...getItemProps({ item, index })}
className={`cursor-pointer px-4 py-2 ${
highlightedIndex === index
? "bg-primary text-white"
: ""
}`}
>
{item.name}
</li>
))}
</ul>
<div className="relative" role="search">
<Input getInputProps={getInputProps} />
<FilteredList
isOpen={isOpen}
filteredEmbalses={filteredEmbalses}
getMenuProps={getMenuProps}
getItemProps={getItemProps}
highlightedIndex={highlightedIndex}
/>
{showNoResults && <NoResult inputValue={inputValue} />}
</div>
<div>
<p className="text-sm">
Expand All @@ -100,7 +94,7 @@ export const EmbalseSearch: React.FC<Props> = (props) => {
</p>
</div>
</div>
</div>
</section>
</div>
</div>
);
Expand Down
28 changes: 20 additions & 8 deletions front/src/pods/embalse/components/reservoir-card-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,25 @@ interface Props {
export const ReservoirCardDetail: React.FC<Props> = (props) => {
const { datosEmbalse } = props;
return (
<div className="flex w-full flex-col items-start gap-4">
<h3>Datos del embalse</h3>
<ul>
<li>Cuenca: {datosEmbalse.cuenca}</li>
<li>Provincia: {datosEmbalse.provincia}</li>
<li>Uso: {datosEmbalse.uso}</li>
</ul>
</div>
<section
className="flex w-full flex-col items-start gap-4"
aria-labelledby="data-title"
>
<h3 id="data-title">Datos del embalse</h3>
<dl className="space-y-2">
<div>
<dt className="inline font-semibold">Cuenca:</dt>{" "}
<dd className="inline">{datosEmbalse.cuenca}</dd>
</div>
<div>
<dt className="inline font-semibold">Provincia:</dt>{" "}
<dd className="inline">{datosEmbalse.provincia}</dd>
</div>
<div>
<dt className="inline font-semibold">Uso:</dt>{" "}
<dd className="inline">{datosEmbalse.uso}</dd>
</div>
</dl>
</section>
);
};
19 changes: 11 additions & 8 deletions front/src/pods/embalse/components/reservoir-card-gauge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@ interface Props {
export const ReservoirCardGauge: React.FC<Props> = (props) => {
const { name, reservoirData } = props;
const { currentVolume, totalCapacity, measurementDate } = reservoirData;
const percentage = totalCapacity > 0 ? currentVolume / totalCapacity :
0;
const percentage = totalCapacity > 0 ? currentVolume / totalCapacity : 0;
return (
<div className="card bg-base-100 mx-auto w-full max-w-[400px] items-center gap-6 rounded-2xl p-4 shadow-lg">
<h2 className="text-center">{name}</h2>
<GaugeChart percentage={percentage} measurementDate=
{measurementDate} />
<section
className="card bg-base-100 mx-auto w-full max-w-[400px] items-center gap-6 rounded-2xl p-4 shadow-lg"
aria-labelledby="gauge-title"
>
<h2 id="gauge-title" className="text-center">
{name}
</h2>
<GaugeChart percentage={percentage} measurementDate={measurementDate} />
<GaugeLegend
currentVolume={currentVolume}
totalCapacity={totalCapacity}
/>
</div>
</section>
);
};
};
Loading