From 20a97904517c0adeca9c7c337e16d7ae3eb6d1e6 Mon Sep 17 00:00:00 2001
From: Olivier Maury <Olivier.Maury@inrae.fr>
Date: Wed, 27 Sep 2023 14:28:00 +0200
Subject: [PATCH] =?UTF-8?q?Afficher=20les=20valeurs=20de=20meant=20annuel?=
 =?UTF-8?q?=20pour=20l'ann=C3=A9e=20en=20cours=20au=20chargement?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 bin/start_codeserver.sh                       |   1 +
 config/pmd-suppressions.properties            |   8 +-
 .../www/client/i18n/AppConstants.java         |  18 +-
 .../www/client/i18n/AppMessages.java          |   8 +-
 .../www/client/presenter/LayoutPresenter.java |  43 +++-
 .../www/client/presenter/MapPresenter.java    |  30 ++-
 .../www/client/util/DateUtils.java            |  29 +++
 .../www/client/view/LayoutView.java           | 209 +++++++++++-------
 .../client/i18n/AppConstants_fr.properties    |   3 +-
 .../www/client/i18n/AppMessages_fr.properties |   8 +-
 .../www/server/dao/DailyValueDao.java         |  20 +-
 .../server/dao/DailyValueDaoHibernate.java    |  36 ++-
 .../www/server/dao/IndicatorDaoHibernate.java |   3 +-
 .../www/server/rs/IndicatorResource.java      |  78 +++++--
 .../dao/DailyValueDaoHibernateTest.java       |  31 ++-
 .../www/server/rs/IndicatorResourceTest.java  |  17 +-
 .../agrometinfo/www/shared/dto/ChoiceDTO.java |  31 ++-
 .../www/shared/dto/ErrorResponseDTO.java      |  79 +++++++
 .../www/shared/dto/IndicatorDTO.java          |  24 +-
 ...dicatorCategoryDTO.java => PeriodDTO.java} |  12 +-
 .../www/shared/service/IndicatorService.java  |  41 ++--
 21 files changed, 528 insertions(+), 201 deletions(-)
 create mode 100644 www-client/src/main/java/fr/agrometinfo/www/client/util/DateUtils.java
 create mode 100644 www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ErrorResponseDTO.java
 rename www-shared/src/main/java/fr/agrometinfo/www/shared/dto/{IndicatorCategoryDTO.java => PeriodDTO.java} (60%)

diff --git a/bin/start_codeserver.sh b/bin/start_codeserver.sh
index 30b1c24..f48b6df 100755
--- a/bin/start_codeserver.sh
+++ b/bin/start_codeserver.sh
@@ -1,6 +1,7 @@
 #!/bin/bash
 DIR=$(dirname $0)
 cd $DIR/..
+mvn clean install --projects=.,www-shared
 if [ "$1" == "" ]; then
 	mvn gwt:codeserver -pl *-client -am
 else
diff --git a/config/pmd-suppressions.properties b/config/pmd-suppressions.properties
index 0d0eea0..fd6a1ac 100644
--- a/config/pmd-suppressions.properties
+++ b/config/pmd-suppressions.properties
@@ -1,11 +1,11 @@
 # See https://maven.apache.org/plugins/maven-pmd-plugin/examples/violation-exclusions.html
 # annotations generate not clean code
-fr.agrometinfo.www.shared.dto.IndicatorCategoryDTOBeanJsonDeserializerImpl=UnnecessaryImport
-fr.agrometinfo.www.shared.dto.IndicatorCategoryDTOBeanJsonSerializerImpl=UnnecessaryImport
-fr.agrometinfo.www.shared.dto.IndicatorCategoryDTO_MapperImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.IndicatorDTOBeanJsonDeserializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.IndicatorDTOBeanJsonSerializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.IndicatorDTO_MapperImpl=UnnecessaryImport
+fr.agrometinfo.www.shared.dto.PeriodDTOBeanJsonDeserializerImpl=UnnecessaryImport
+fr.agrometinfo.www.shared.dto.PeriodDTOBeanJsonSerializerImpl=UnnecessaryImport
+fr.agrometinfo.www.shared.dto.PeriodDTO_MapperImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.service.IndicatorServiceFactory=UnnecessaryImport
 org.geojson.FeatureBeanJsonDeserializerImpl=UnnecessaryImport
 org.geojson.FeatureBeanJsonSerializerImpl=UnnecessaryImport
@@ -21,4 +21,4 @@ org.geojson.PolygonBeanJsonDeserializerImpl=UnnecessaryImport
 org.geojson.PolygonBeanJsonSerializerImpl=UnnecessaryImport
 org.geojson.Polygon_MapperImpl=UnnecessaryImport
 # wrong positive
-fr.agrometinfo.www.server.dao.RegionDaoHibernate=UselessOverridingMethod
\ No newline at end of file
+fr.agrometinfo.www.server.dao.RegionDaoHibernate=UselessOverridingMethod
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java b/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java
index a7e4255..e5c58a8 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java
@@ -51,6 +51,12 @@ public interface AppConstants extends com.google.gwt.i18n.client.ConstantsWithLo
     @DefaultStringValue("Choose an indicator")
     String chooseIndicator();
 
+    /**
+     * @return translation
+     */
+    @DefaultStringValue("Choose period")
+    String choosePeriod();
+
     /**
      * @return translation
      */
@@ -154,12 +160,6 @@ public interface AppConstants extends com.google.gwt.i18n.client.ConstantsWithLo
     @DefaultStringValue("HTTP status text:")
     String failureStatusText();
 
-    /**
-     * @return translation
-     */
-    @DefaultStringValue("Indicator categories")
-    String indicatorCategories();
-
     /**
      * @return translation
      */
@@ -178,6 +178,12 @@ public interface AppConstants extends com.google.gwt.i18n.client.ConstantsWithLo
     @DefaultStringValue("Log out")
     String logout();
 
+    /**
+     * @return translation
+     */
+    @DefaultStringValue("Metropolitan France")
+    String metropolitanFrance();
+
     /**
      * @return translation
      */
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppMessages.java b/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppMessages.java
index 90a3783..40f1da0 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppMessages.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppMessages.java
@@ -35,10 +35,10 @@ public interface AppMessages extends Messages {
      * @param nb nb
      * @return translation
      */
-    @DefaultMessage("{0} indicator categories.")
-    @AlternateMessage({"=0", "No indicator categories.", //
-        "=1", "1 indicator category."})
-    String nbOfIndicatorCategories(@PluralCount int nb);
+    @DefaultMessage("{0} periods.")
+    @AlternateMessage({"=0", "No period.", //
+            "=1", "1 period."})
+    String nbOfIndicatorPeriods(@PluralCount int nb);
 
     /**
      * @param nb nb
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java
index beafb19..30f2017 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java
@@ -1,9 +1,9 @@
 package fr.agrometinfo.www.client.presenter;
 
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
-import org.dominokit.domino.ui.notifications.Notification;
 import org.dominokit.rest.shared.request.FailedResponseBean;
 
 import com.google.gwt.core.client.GWT;
@@ -11,8 +11,9 @@ import com.google.gwt.user.client.Cookies;
 
 import fr.agrometinfo.www.client.view.BaseView;
 import fr.agrometinfo.www.client.view.LayoutView;
+import fr.agrometinfo.www.client.view.MapView;
 import fr.agrometinfo.www.shared.dto.ChoiceDTO;
-import fr.agrometinfo.www.shared.dto.IndicatorCategoryDTO;
+import fr.agrometinfo.www.shared.dto.PeriodDTO;
 import fr.agrometinfo.www.shared.service.IndicatorServiceFactory;
 
 /**
@@ -40,9 +41,19 @@ public final class LayoutPresenter implements Presenter {
         void setDevMode(boolean isDevMode);
 
         /**
-         * @param list indicator categories
+         * @param list indicator periods with indicators
          */
-        void setIndicatorCategories(List<IndicatorCategoryDTO> list);
+        void setPeriods(List<PeriodDTO> list);
+
+        /**
+         * @param list regions with indicators
+         */
+        void setRegions(Map<String, String> list);
+
+        /**
+         * @param list years of computed values
+         */
+        void setYears(List<Integer> list);
     }
 
     /**
@@ -50,6 +61,11 @@ public final class LayoutPresenter implements Presenter {
      */
     private LayoutView view;
 
+    /**
+     * Presenter for {@link MapView}.
+     */
+    private final MapPresenter mapPresenter = new MapPresenter();
+
     /**
      * @see https://github.com/gwtproject/gwt/issues/7631#issuecomment-110876116
      * @return if the application is running in (Super) DevMode.
@@ -70,9 +86,7 @@ public final class LayoutPresenter implements Presenter {
      * @param choice the user's choice
      */
     public void onChoiceChange(final ChoiceDTO choice) {
-        Notification.createSuccess(choice.toString())
-        .setPosition(Notification.TOP_LEFT)
-        .show();
+        mapPresenter.loadValues(choice);
     }
 
     /**
@@ -87,11 +101,22 @@ public final class LayoutPresenter implements Presenter {
         GWT.log("LayoutPresenter.start()");
         view = new LayoutView();
         view.setPresenter(this);
+        view.setMapPresenter(mapPresenter);
         view.init();
         view.setDevMode(isDevMode());
 
-        IndicatorServiceFactory.INSTANCE.getIndicatorCategories() //
-        .onSuccess(view::setIndicatorCategories) //
+        IndicatorServiceFactory.INSTANCE.getPeriods() //
+        .onSuccess(view::setPeriods) //
+        .onFailed(view::failureNotification) //
+        .send();
+
+        IndicatorServiceFactory.INSTANCE.getRegions() //
+        .onSuccess(view::setRegions) //
+        .onFailed(view::failureNotification) //
+        .send();
+
+        IndicatorServiceFactory.INSTANCE.getYears() //
+        .onSuccess(view::setYears) //
         .onFailed(view::failureNotification) //
         .send();
     }
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/MapPresenter.java b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/MapPresenter.java
index 95dbb1d..18a3ba9 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/MapPresenter.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/MapPresenter.java
@@ -8,6 +8,7 @@ import org.dominokit.domino.ui.modals.Window;
 import org.dominokit.domino.ui.typography.Paragraph;
 import org.dominokit.domino.ui.utils.DominoElement;
 import org.dominokit.rest.JsRestfulRequestFactory;
+import org.dominokit.rest.shared.RestfulRequest;
 import org.geojson.FeatureCollection;
 import org.pepstock.charba.client.BarChart;
 import org.pepstock.charba.client.colors.HtmlColor;
@@ -22,6 +23,7 @@ import elemental2.dom.Node;
 import fr.agrometinfo.www.client.i18n.AppConstants;
 import fr.agrometinfo.www.client.view.BaseView;
 import fr.agrometinfo.www.client.view.MapView;
+import fr.agrometinfo.www.shared.dto.ChoiceDTO;
 import fr.agrometinfo.www.shared.service.IndicatorService;
 
 /**
@@ -50,6 +52,12 @@ public final class MapPresenter implements Presenter {
      */
     private static final AppConstants CSTS = GWT.create(AppConstants.class);
 
+    /**
+     * URL to get indicator values.
+     */
+    private static final String VALUES_URL = GWT.getModuleBaseURL().replace("/app/", "") + "/rs/"
+            + IndicatorService.PATH + "/" + IndicatorService.PATH_VALUES;
+
     /**
      * Container for the map.
      */
@@ -61,13 +69,23 @@ public final class MapPresenter implements Presenter {
     private View view;
 
     /**
-     * Load cells from the server.
+     * Load indicator values on the map.
+     *
+     * @param choice user choice for the indicator values
      */
-    public void loadCells() {
+    public void loadValues(final ChoiceDTO choice) {
         final JsRestfulRequestFactory factory = new JsRestfulRequestFactory();
-        final String url = GWT.getModuleBaseURL().replace("/app/", "") + "/rs/" + IndicatorService.PATH + "/"
-                + IndicatorService.PATH_CELLS;
-        factory.get(url).onSuccess(response -> {
+        final RestfulRequest request = factory.get(VALUES_URL);
+        request.addQueryParam("indicator", choice.getIndicator());
+        if (choice.getPeriod() != null) {
+            request.addQueryParam("period", choice.getPeriod());
+        }
+        if (choice.getRegion() != null) {
+            request.addQueryParam("region", choice.getRegion());
+        }
+        request.addQueryParam("year", String.valueOf(choice.getYear()));
+        request.addQueryParam("comparison", String.valueOf(choice.getComparison()));
+        request.onSuccess(response -> {
             view.setGeoJson(response.getBodyAsString());
         }).send();
     }
@@ -110,7 +128,7 @@ public final class MapPresenter implements Presenter {
         .setSize(IsModalDialog.ModalSize.LARGE)
         // .setHeaderBackground(Color.PINK)
         .add(Paragraph.create("Paragraphe !" + ids)).appendChild((Node) chartElement.as())
-                .appendFooterChild(Button.create(CSTS.close()).addClickListener(evt -> window.close())).open();
+        .appendFooterChild(Button.create(CSTS.close()).addClickListener(evt -> window.close())).open();
     }
 
     /**
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/util/DateUtils.java b/www-client/src/main/java/fr/agrometinfo/www/client/util/DateUtils.java
new file mode 100644
index 0000000..92ae65b
--- /dev/null
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/util/DateUtils.java
@@ -0,0 +1,29 @@
+package fr.agrometinfo.www.client.util;
+
+import java.util.Date;
+
+/**
+ * Helper class for Date.
+ *
+ * Indeed, as GWT does not handle LocalDate, we still use java.util.date.
+ *
+ * @author Olivier Maury
+ */
+public interface DateUtils {
+    /**
+     * @return the current year.
+     */
+    static int getCurrentYear() {
+        return getYear(new Date());
+    }
+
+    /**
+     * @param date date to handle
+     * @return extracted year from date
+     */
+    @SuppressWarnings("deprecation")
+    private static int getYear(final Date date) {
+        final int offset = 1900;
+        return date.getYear() + offset;
+    }
+}
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java b/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java
index ea46425..f8af4d2 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java
@@ -8,6 +8,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.StringJoiner;
 
 import org.dominokit.domino.ui.button.Button;
@@ -52,8 +53,8 @@ import fr.agrometinfo.www.client.presenter.MapPresenter;
 import fr.agrometinfo.www.client.ui.AgroclimAppsMenu;
 import fr.agrometinfo.www.client.ui.HTMLSelectElementBuilder;
 import fr.agrometinfo.www.shared.dto.ChoiceDTO;
-import fr.agrometinfo.www.shared.dto.IndicatorCategoryDTO;
 import fr.agrometinfo.www.shared.dto.IndicatorDTO;
+import fr.agrometinfo.www.shared.dto.PeriodDTO;
 
 /**
  * View for the general layout.
@@ -61,6 +62,10 @@ import fr.agrometinfo.www.shared.dto.IndicatorDTO;
  * @author Olivier Maury
  */
 public final class LayoutView extends AbstractBaseView<LayoutPresenter> implements LayoutPresenter.View {
+    /**
+     * Code of indicator to show by default.
+     */
+    private static final String DEFAULT_INDICATOR = "meant";
 
     /**
      * I18N constants.
@@ -81,7 +86,7 @@ public final class LayoutView extends AbstractBaseView<LayoutPresenter> implemen
         menu.add(link);
     }
 
-    private static  void addMenuItem(final Menu<String> menu, final String text, final MdiIcon icon, final String url) {
+    private static void addMenuItem(final Menu<String> menu, final String text, final MdiIcon icon, final String url) {
         final Runnable runnable;
         if (url.startsWith("http")) {
             runnable = () -> DomGlobal.window.open(url, "_blank");
@@ -91,11 +96,6 @@ public final class LayoutView extends AbstractBaseView<LayoutPresenter> implemen
         addMenuItem(menu, text, icon, runnable);
     }
 
-    private static HtmlContentBuilder<HTMLElement> faIcon(final String className, final String color) {
-        // <i class="fas fa-cloud-showers-heavy" style="color:#AAAAAA;"></i>
-        return Elements.i().css("fas " + className).style("color:" + color);
-    }
-
     private static String getDetails(final FailedResponseBean failure) {
         final StringJoiner sj = new StringJoiner("<br/>");
         sj.add(CSTS.failureBody());
@@ -108,11 +108,6 @@ public final class LayoutView extends AbstractBaseView<LayoutPresenter> implemen
         return sj.toString();
     }
 
-    /**
-     * Container for the categories.
-     */
-    private final HtmlContentBuilder<HTMLDivElement> categoriesElem = Elements.div().css("indicator-categories");
-
     /**
      * Choice from the user.
      */
@@ -138,6 +133,11 @@ public final class LayoutView extends AbstractBaseView<LayoutPresenter> implemen
      */
     private final HtmlContentBuilder<HTMLSelectElement> indicatorSelect = select();
 
+    /**
+     * Selector for indicator periods.
+     */
+    private final HtmlContentBuilder<HTMLSelectElement> periodSelect = select();
+
     /**
      * Application layout.
      */
@@ -161,6 +161,16 @@ public final class LayoutView extends AbstractBaseView<LayoutPresenter> implemen
      */
     private final Menu<String> userDropMenu = Menu.<String>create();
 
+    /**
+     * Presenter for {@link MapView}.
+     */
+    private MapPresenter mapPresenter;
+
+    /**
+     * Available indicator periods with indicators.
+     */
+    private List<PeriodDTO> periods;
+
     /**
      * @param text     link text
      * @param callback {@link EventCallbackFn<MouseEvent>} to be added to the click
@@ -218,62 +228,42 @@ public final class LayoutView extends AbstractBaseView<LayoutPresenter> implemen
         panel.appendChild(summary);
 
         //
-        GWT.log("initLeftPanel() categories");
-        panel.appendChild(BlockHeader.create(CSTS.indicatorCategories()).element());
-        panel.appendChild(categoriesElem);
+        GWT.log("initLeftPanel() periods");
+        panel.appendChild(BlockHeader.create(CSTS.choosePeriod()).element());
+        panel.appendChild(periodSelect);
+        new HTMLSelectElementBuilder<PeriodDTO>() //
+        .setSelect(periodSelect) //
+        .setPrompt(CSTS.selectPrompt()) //
+        .addValueChangeHandler(this::onPeriodChange) //
+        .build();
 
         //
         GWT.log("initLeftPanel() indicators");
         panel.appendChild(BlockHeader.create(CSTS.chooseIndicator()).element());
         panel.appendChild(indicatorSelect);
+        new HTMLSelectElementBuilder<IndicatorDTO>() //
+        .setSelect(indicatorSelect) //
+        .setPrompt(CSTS.selectPrompt()) //
+        .addValueChangeHandler(this::onIndicatorChange) //
+        .build();
 
         //
         GWT.log("initLeftPanel() regions");
         panel.appendChild(BlockHeader.create(CSTS.chooseRegion()).element());
-        final Map<String, String> regionOptions = new HashMap<>();
-        regionOptions.put("1", "Guadeloupe");
-        regionOptions.put("2", "Martinique");
-        regionOptions.put("3", "Guyane");
-        regionOptions.put("4", "La Réunion");
-        regionOptions.put("6", "Mayotte");
-        regionOptions.put("11", "ÃŽle-de-France");
-        regionOptions.put("24", "Centre-Val de Loire");
-        regionOptions.put("27", "Bourgogne-Franche-Comté");
-        regionOptions.put("28", "Normandie");
-        regionOptions.put("32", "Hauts-de-France");
-        regionOptions.put("44", "Grand Est");
-        regionOptions.put("52", "Pays de la Loire");
-        regionOptions.put("53", "Bretagne");
-        regionOptions.put("75", "Nouvelle-Aquitaine");
-        regionOptions.put("76", "Occitanie");
-        regionOptions.put("84", "Auvergne-Rhône-Alpes");
-        regionOptions.put("93", "Provence-Alpes-Côte d'Azur");
-        regionOptions.put("94", "Corse");
         new HTMLSelectElementBuilder<Entry<String, String>>() //
         .setSelect(regionSelect) //
-        .setPrompt(CSTS.selectPrompt()) //
-        .setTextFunction(Entry<String, String>::getValue) //
-        .setValueFunction(Entry<String, String>::getKey) //
+        .setPrompt(CSTS.metropolitanFrance()) //
         .addValueChangeHandler(this::onRegionChange) //
-        .addOptions(regionOptions.entrySet()) //
         .build();
         panel.appendChild(regionSelect);
 
         //
         GWT.log("initLeftPanel() year");
         panel.appendChild(BlockHeader.create(CSTS.chooseYear()));
-        final Map<String, String> yearOptions = new HashMap<>();
-        yearOptions.put("1998", "1998");
-        yearOptions.put("2001", "2001");
-        yearOptions.put("2003", "2003");
-        yearOptions.put("2023", "2023");
         new HTMLSelectElementBuilder<Entry<String, String>>() //
         .setSelect(yearSelect) //
         .setPrompt(CSTS.selectPrompt()) //
-        .setTextFunction(Entry<String, String>::getValue) //
-        .setValueFunction(Entry<String, String>::getKey) //
-        .addValueChangeHandler(this::onYearChange) //
-        .addOptions(yearOptions.entrySet());
+        .addValueChangeHandler(this::onYearChange);
         panel.appendChild(yearSelect);
 
         //
@@ -307,10 +297,15 @@ public final class LayoutView extends AbstractBaseView<LayoutPresenter> implemen
         //
         setContentPanelHeight();
         Window.addResizeHandler(e -> setContentPanelHeight());
-        final MapPresenter mapPresenter = new MapPresenter();
         mapPresenter.setContainer(layout.getContentPanel());
         mapPresenter.start();
-        mapPresenter.loadCells();
+        // final ChoiceDTO defaultChoice = new ChoiceDTO();
+        // defaultChoice.setComparison(false);
+        // defaultChoice.setIndicator("meant");
+        // defaultChoice.setRegion(null);
+        // defaultChoice.setPeriod("year");
+        // defaultChoice.setYear(DateUtils.getCurrentYear());
+        // mapPresenter.loadValues(defaultChoice);
     }
 
     private void initTopBar() {
@@ -337,8 +332,7 @@ public final class LayoutView extends AbstractBaseView<LayoutPresenter> implemen
                 .hideOn(ScreenMedia.SMALL_AND_DOWN) //
                 .setDropMenu(new AgroclimAppsMenu())) //
         .appendChild(DominoElement.of(li() //
-                .css(Styles.pull_right)
-                .add(a() //
+                .css(Styles.pull_right).add(a() //
                         .add(Icons.ALL.dots_vertical_mdi().clickable())))
                 .showOn(ScreenMedia.SMALL_AND_DOWN) //
                 .hideOn(ScreenMedia.MEDIUM_AND_UP) //
@@ -367,25 +361,53 @@ public final class LayoutView extends AbstractBaseView<LayoutPresenter> implemen
     }
 
     private void onComparisonChange(final Boolean newValue) {
-        notification("Item selected [ " + newValue + " ]");
         choice.setComparison(newValue);
         onChoiceChange();
     }
 
     private void onIndicatorChange(final String newValue) {
-        notification("Item selected [ " + newValue + " ]");
         choice.setIndicator(newValue);
         onChoiceChange();
     }
 
+    private void onPeriodChange(final String newValue) {
+        choice.setPeriod(newValue);
+        choice.setIndicator(null);
+
+        // fill indicators
+        // TODO clear select
+        List<IndicatorDTO> list = null;
+        for (final PeriodDTO p : this.periods) {
+            if (p.getCode().equals(newValue)) {
+                list = p.getIndicators();
+                break;
+            }
+        }
+        if (list == null) {
+            return;
+        }
+        DomGlobal.console.info("Indicators : " + list);
+        new HTMLSelectElementBuilder<IndicatorDTO>() //
+        .setSelect(indicatorSelect) //
+        .setTextFunction(IndicatorDTO::getDescription) //
+        .setValueFunction(IndicatorDTO::getCode) //
+        .addOptions(list) //
+        .build();
+        // select "meant"
+        if (list.stream().map(IndicatorDTO::getCode).anyMatch(DEFAULT_INDICATOR::equals)) {
+            indicatorSelect.element().value = DEFAULT_INDICATOR;
+            onIndicatorChange(DEFAULT_INDICATOR);
+        } else {
+            onChoiceChange();
+        }
+    }
+
     private void onRegionChange(final String newValue) {
-        notification("Item selected [ " + newValue + " ]");
         choice.setRegion(newValue);
         onChoiceChange();
     }
 
     private void onYearChange(final String newValue) {
-        notification("Item selected [ " + newValue + " ]");
         try {
             choice.setYear(Integer.valueOf(newValue));
         } catch (final NumberFormatException e) {
@@ -421,39 +443,62 @@ public final class LayoutView extends AbstractBaseView<LayoutPresenter> implemen
         }
     }
 
+    /**
+     * @param presenter presenter for {@link MapView}.
+     */
+    public void setMapPresenter(final MapPresenter presenter) {
+        this.mapPresenter = presenter;
+    }
+
     @Override
-    public void setIndicatorCategories(final List<IndicatorCategoryDTO> list) {
+    public void setPeriods(final List<PeriodDTO> list) {
+        this.periods = list;
         // summary
         final StringJoiner sj = new StringJoiner(" ");
-        sj.add(MSGS.nbOfIndicatorCategories(list.size()));
+        sj.add(MSGS.nbOfIndicatorPeriods(list.size()));
         sj.add(MSGS.nbOfIndicators(list.stream().mapToInt(c -> c.getIndicators().size()).sum()));
         summary.textContent = sj.toString();
-        // categories
-        for (final IndicatorCategoryDTO category : list) {
-            final Icon icon = Icons.ALL.wb_sunny();
-            icon.setColor("#f3b80e");
-            icon.setTooltip(category.getDescription(), PopupPosition.RIGHT);
-            categoriesElem.add(icon);
-        }
-        categoriesElem.addAll(//
-                faIcon("fa-cloud-showers-heavy", "#AAAAAA"), //
-                faIcon("fa-snowflake", "#3db0c8"), //
-                faIcon("fa-thermometer-quarter", "#87CEFA"), //
-                faIcon("fa-thermometer-three-quarters", "#FF0000"), //
-                faIcon("fa-thermometer-half", "#8B4513") //
-                );
-        // indicators
-        if (!list.isEmpty()) {
-            DomGlobal.console.info("Indicators : " + list);
-            new HTMLSelectElementBuilder<IndicatorDTO>() //
-            .setSelect(indicatorSelect) //
-            .setPrompt(CSTS.selectPrompt()) //
-            .setTextFunction(IndicatorDTO::getUid) //
-            .setValueFunction(IndicatorDTO::getDescription) //
-            .addValueChangeHandler(this::onIndicatorChange) //
-            .addOptions(list.get(0).getIndicators()) //
-            .build();
+
+        if (this.periods.isEmpty()) {
+            return;
         }
+        // display periods
+        DomGlobal.console.info("Periods : " + list);
+        new HTMLSelectElementBuilder<PeriodDTO>() //
+        .setSelect(periodSelect) //
+        .setTextFunction(PeriodDTO::getDescription) //
+        .setValueFunction(PeriodDTO::getCode) //
+        .addOptions(list) //
+        .build();
+        // select "year"
+        periodSelect.element().value = "year";
+        onPeriodChange(periodSelect.element().value);
+    }
 
+    @Override
+    public void setRegions(final Map<String, String> list) {
+        new HTMLSelectElementBuilder<Entry<String, String>>() //
+        .setSelect(regionSelect) //
+        .setTextFunction(Entry<String, String>::getValue) //
+        .setValueFunction(Entry<String, String>::getKey) //
+        .addOptions(list.entrySet()) //
+        .build();
+    }
+
+    @Override
+    public void setYears(final List<Integer> list) {
+        final Map<String, String> yearOptions = new HashMap<>();
+        list.forEach(y -> yearOptions.put(String.valueOf(y), String.valueOf(y)));
+        new HTMLSelectElementBuilder<Entry<String, String>>() //
+        .setSelect(yearSelect) //
+        .setTextFunction(Entry<String, String>::getValue) //
+        .setValueFunction(Entry<String, String>::getKey) //
+        .addOptions(yearOptions.entrySet());
+        final Optional<Integer> defaultYear = list.stream().max(Integer::compare);
+        defaultYear.ifPresent(year -> {
+            yearSelect.element().value = year.toString();
+            choice.setYear(year);
+            onChoiceChange();
+        });
     }
 }
diff --git a/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties b/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties
index f53f276..9fca179 100644
--- a/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties
+++ b/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties
@@ -6,6 +6,7 @@ agroclimApps = Les applications d’AgroClim
 applicationLoading = Chargement de l’application…
 cancel= Annuler
 chooseIndicator= Choisir un indicateur
+choosePeriod= Choisir une période
 chooseRegion= Choisir une région
 chooseYear= Choisir une année
 close = Fermer
@@ -23,10 +24,10 @@ exportData= Exporter les données
 failureBody = Corps :
 failureHeaders = Entêtes HTTP :
 failureStatusText = Texte d’état HTTP :
-indicatorCategories= Catégories d’indicateurs
 login = Se connecter
 loginOrSignIn = ou s’inscrire avec
 logout = Se déconnecter
+metropolitanFrance = France métropolitaine
 no= Non
 normalComparison= Comparaison à la normale
 normalComparisonTooltip= <b>La comparaison à la normale</b> <em>se calcule en soustrayant <b>la moyenne de l’indicateur choisi</b> pour les trente dernières années (1990-2020) de <b>l’année sélectionnée</b>
diff --git a/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppMessages_fr.properties b/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppMessages_fr.properties
index b92a648..758ba39 100644
--- a/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppMessages_fr.properties
+++ b/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppMessages_fr.properties
@@ -1,10 +1,10 @@
 # Ce fichier est encodé en UTF-8.
 account = Compte de {0} ({1})
 failureStatusCode = Code d’état HTTP : {0}
-nbOfIndicatorCategories[\=0] = Aucune catégorie d’indicateurs.
-nbOfIndicatorCategories[\=1] = Une catégorie d’indicateurs.
-nbOfIndicatorCategories[one] = Une catégorie d’indicateurs.
-nbOfIndicatorCategories = {0} catégories d’indicateurs.
+nbOfIndicatorPeriods[\=0] = Aucune catégorie d’indicateurs.
+nbOfIndicatorPeriods[\=1] = Une catégorie d’indicateurs.
+nbOfIndicatorPeriods[one] = Une catégorie d’indicateurs.
+nbOfIndicatorPeriods = {0} catégories d’indicateurs.
 nbOfIndicators[\=0] = Aucun indicateur.
 nbOfIndicators[\=1] = Un indicateur.
 nbOfIndicators[one] = Un indicateur.
diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/dao/DailyValueDao.java b/www-server/src/main/java/fr/agrometinfo/www/server/dao/DailyValueDao.java
index 8ff8ce7..6d297d0 100644
--- a/www-server/src/main/java/fr/agrometinfo/www/server/dao/DailyValueDao.java
+++ b/www-server/src/main/java/fr/agrometinfo/www/server/dao/DailyValueDao.java
@@ -34,11 +34,27 @@ public interface DailyValueDao {
     List<DailyValue> find(Indicator indicator, Region region, LocalDate date);
 
     /**
-     * The last {@link DailyValue#getDate()} for the indicator.
+     * @return the indicators (with period) related to computed values
+     */
+    List<Indicator> findIndicators();
+
+    /**
+     * The last {@link DailyValue#getDate()} for the indicator on a year.
      *
      * @param indicator indicator to search
+     * @param year      year
      * @return last date or null
      */
-    LocalDate findLastDate(Indicator indicator);
+    LocalDate findLastDate(Indicator indicator, Integer year);
+
+    /**
+     * @return the regions related to computed values
+     */
+    List<Region> findRegions();
+
+    /**
+     * @return the years related to computed values
+     */
+    List<Integer> findYears();
 
 }
diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/dao/DailyValueDaoHibernate.java b/www-server/src/main/java/fr/agrometinfo/www/server/dao/DailyValueDaoHibernate.java
index 4c0613d..bbd3b2b 100644
--- a/www-server/src/main/java/fr/agrometinfo/www/server/dao/DailyValueDaoHibernate.java
+++ b/www-server/src/main/java/fr/agrometinfo/www/server/dao/DailyValueDaoHibernate.java
@@ -33,16 +33,42 @@ public class DailyValueDaoHibernate extends DaoHibernate<DailyValue> implements
     public final List<DailyValue> find(final Indicator indicator, final Region region, final LocalDate date) {
         final var jpql = """
                 SELECT t FROM DailyValue AS t
-                WHERE t.indicator=:indicator AND t.date=:date AND t.cell.department.region=:region
-                """;
+                WHERE t.indicator=:indicator AND t.date=:date AND t.cell.department.region=:region""";
         return super.findAllByJPQL(jpql, Map.of("indicator", indicator, "date", date, "region", region),
                 DailyValue.class);
     }
 
     @Override
-    public final LocalDate findLastDate(final Indicator indicator) {
-        final var jpql = "SELECT MAX(t.date) FROM DailyValue AS t WHERE t.indicator=:indicator";
-        return super.findOneByJPQL(jpql, Map.of("indicator", indicator), LocalDate.class);
+    public final List<Indicator> findIndicators() {
+        final var jpql = "SELECT DISTINCT i FROM DailyValue AS t JOIN t.indicator AS i JOIN t.indicator.period";
+        return super.findAllByJPQL(jpql, null, Indicator.class);
+    }
+
+    @Override
+    public final LocalDate findLastDate(final Indicator indicator, final Integer year) {
+        final var jpql = """
+                SELECT MAX(t.date)
+                FROM DailyValue AS t
+                WHERE t.indicator=:indicator AND EXTRACT(YEAR FROM t.date) = :year""";
+        return super.findOneByJPQL(jpql, Map.of("indicator", indicator, "year", year), LocalDate.class);
+    }
+
+    @Override
+    public final List<Region> findRegions() {
+        final var jpql = """
+                SELECT DISTINCT r
+                FROM DailyValue AS t
+                    JOIN t.cell AS c
+                    JOIN c.department AS d
+                    JOIN d.region AS r
+                ORDER BY r.name""";
+        return super.findAllByJPQL(jpql, null, Region.class);
+    }
+
+    @Override
+    public final List<Integer> findYears() {
+        final var jpql = "SELECT DISTINCT EXTRACT(YEAR FROM date) AS year FROM DailyValue AS t ORDER BY year";
+        return super.findAllByJPQL(jpql, null, Integer.class);
     }
 
 }
diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/dao/IndicatorDaoHibernate.java b/www-server/src/main/java/fr/agrometinfo/www/server/dao/IndicatorDaoHibernate.java
index 934149c..edeec7a 100644
--- a/www-server/src/main/java/fr/agrometinfo/www/server/dao/IndicatorDaoHibernate.java
+++ b/www-server/src/main/java/fr/agrometinfo/www/server/dao/IndicatorDaoHibernate.java
@@ -4,6 +4,7 @@ import java.util.Map;
 
 import fr.agrometinfo.www.server.model.Indicator;
 import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.validation.constraints.NotNull;
 
 /**
  * Hibernate implementation of {@link IndicatorDao}.
@@ -28,7 +29,7 @@ public class IndicatorDaoHibernate extends DaoHibernate<Indicator> implements In
      * @return found or null
      */
     @Override
-    public Indicator findByCodeAndPeriod(final String code, final String periodCode) {
+    public Indicator findByCodeAndPeriod(@NotNull final String code, @NotNull final String periodCode) {
         final var jpql = "SELECT t FROM Indicator AS t WHERE t.code=:code AND t.period.code=:periodCode";
         return super.findOneByJPQL(jpql, Map.of("code", code, "periodCode", periodCode), Indicator.class);
     }
diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java b/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java
index 39a2fd4..32b1e9b 100644
--- a/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java
+++ b/www-server/src/main/java/fr/agrometinfo/www/server/rs/IndicatorResource.java
@@ -6,6 +6,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import org.geojson.Feature;
 import org.geojson.FeatureCollection;
@@ -21,8 +22,9 @@ import fr.agrometinfo.www.server.model.DailyValue;
 import fr.agrometinfo.www.server.model.Indicator;
 import fr.agrometinfo.www.server.model.Region;
 import fr.agrometinfo.www.server.util.LocaleUtils;
-import fr.agrometinfo.www.shared.dto.IndicatorCategoryDTO;
+import fr.agrometinfo.www.shared.dto.ErrorResponseDTO;
 import fr.agrometinfo.www.shared.dto.IndicatorDTO;
+import fr.agrometinfo.www.shared.dto.PeriodDTO;
 import fr.agrometinfo.www.shared.service.IndicatorService;
 import jakarta.annotation.PostConstruct;
 import jakarta.enterprise.context.RequestScoped;
@@ -32,8 +34,10 @@ import jakarta.ws.rs.GET;
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.Produces;
 import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.WebApplicationException;
 import jakarta.ws.rs.core.Context;
 import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
 import lombok.extern.log4j.Log4j2;
 
 /**
@@ -135,16 +139,23 @@ public class IndicatorResource implements IndicatorService {
     @Inject
     private DailyValueDao dailyValueDao;
 
-    @GET
-    @Path(IndicatorService.PATH_CELLS)
-    @Produces("application/geo+json")
-    @Override
-    public FeatureCollection getCells() {
-        final FeatureCollection collection = new FeatureCollection();
-        cellDao.findAll().stream() //
-        .map(IndicatorResource::toFeature) //
-        .forEach(collection::add);
-        return collection;
+    /**
+     * Ensure the value of query parameter is not null and not blank.
+     *
+     * @param value          value of query parameter to check
+     * @param queryParamName name of query parameter
+     * @throws WebApplicationException if validation failed.
+     */
+    private void checkRequired(final Object value, final String queryParamName) {
+        if ((value instanceof final String str && str.isBlank()) || value == null) {
+            final var status = Response.Status.BAD_REQUEST;
+            throw new WebApplicationException(
+                    Response.status(status) //
+                    .entity(ErrorResponseDTO.of(status.getStatusCode(), //
+                            status.getReasonPhrase(), //
+                            queryParamName + " parameter is mandatory")) //
+                    .build());
+        }
     }
 
     /**
@@ -154,40 +165,53 @@ public class IndicatorResource implements IndicatorService {
     @Path(IndicatorService.PATH_LIST)
     @Produces(MediaType.APPLICATION_JSON)
     @Override
-    public List<IndicatorCategoryDTO> getIndicatorCategories() {
+    public List<PeriodDTO> getPeriods() {
         // TODO : ajouter un cache (CacheControl, E-Tag et WebFilter)
         LOGGER.traceEntry();
         final var locale = LocaleUtils.getLocale(httpServletRequest);
-        final var indicators = indicatorDao.findAll();
-        final Map<Long, IndicatorCategoryDTO> dtos = new HashMap<>();
+        final var indicators = dailyValueDao.findIndicators();
+        final Map<Long, PeriodDTO> dtos = new HashMap<>();
         for (final Indicator indicator : indicators) {
             final var p = indicator.getPeriod();
             final var key = Long.valueOf(p.getId());
-            if (!dtos.containsKey(key)) {
-                final var cat = new IndicatorCategoryDTO();
-                cat.setDescription(getTranslation(p.getNames(), locale));
-                cat.setUid(p.getCode());
-                cat.setIndicators(new ArrayList<>());
-                dtos.put(key, cat);
-            }
+            dtos.computeIfAbsent(key, k -> {
+                final var period = new PeriodDTO();
+                period.setDescription(getTranslation(p.getNames(), locale));
+                period.setCode(p.getCode());
+                period.setIndicators(new ArrayList<>());
+                return period;
+            });
             final var dto = new IndicatorDTO();
             dto.setDescription(getTranslation(indicator.getDescriptions(), locale));
-            dto.setUid(indicator.getCode());
+            dto.setCode(indicator.getCode());
             dtos.get(key).getIndicators().add(dto);
         }
         return new ArrayList<>(dtos.values());
     }
 
+    @GET
+    @Path(IndicatorService.PATH_REGIONS)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Override
+    public Map<String, String> getRegions() {
+        return dailyValueDao.findRegions().stream()//
+                .collect(Collectors.toMap(r -> String.valueOf(r.getId()), Region::getName));
+    }
+
     @GET
     @Path(IndicatorService.PATH_VALUES)
     @Produces("application/geo+json")
     @Override
     public FeatureCollection getValues(@QueryParam(value = "indicator") final String indicatorUid,
             @QueryParam(value = "period") final String periodCode, @QueryParam(value = "region") final Integer regionId,
+            @QueryParam(value = "year") final Integer year,
             @QueryParam(value = "comparison") final Boolean comparison) {
+        checkRequired(indicatorUid, "indicator");
+        checkRequired(periodCode, "period");
+        checkRequired(year, "year");
         final FeatureCollection collection = new FeatureCollection();
         final Indicator indicator = indicatorDao.findByCodeAndPeriod(indicatorUid, periodCode);
-        final LocalDate date = dailyValueDao.findLastDate(indicator);
+        final LocalDate date = dailyValueDao.findLastDate(indicator, year);
         final Region region;
         if (regionId == null) {
             region = null;
@@ -213,6 +237,14 @@ public class IndicatorResource implements IndicatorService {
         return collection;
     }
 
+    @GET
+    @Path(IndicatorService.PATH_YEARS)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Override
+    public List<Integer> getYears() {
+        return dailyValueDao.findYears();
+    }
+
     @PostConstruct
     public void init() {
         LOGGER.traceEntry();
diff --git a/www-server/src/test/java/fr/agrometinfo/www/server/dao/DailyValueDaoHibernateTest.java b/www-server/src/test/java/fr/agrometinfo/www/server/dao/DailyValueDaoHibernateTest.java
index c66c13f..69f4eb3 100644
--- a/www-server/src/test/java/fr/agrometinfo/www/server/dao/DailyValueDaoHibernateTest.java
+++ b/www-server/src/test/java/fr/agrometinfo/www/server/dao/DailyValueDaoHibernateTest.java
@@ -1,5 +1,6 @@
 package fr.agrometinfo.www.server.dao;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 import org.junit.jupiter.api.Test;
@@ -9,7 +10,7 @@ import fr.agrometinfo.www.server.model.Indicator;
 /**
  * Test DailyValueDao Hibernate implementation.
  */
-public class DailyValueDaoHibernateTest {
+class DailyValueDaoHibernateTest {
     /**
      * DAO to test.
      */
@@ -20,10 +21,34 @@ public class DailyValueDaoHibernateTest {
      */
     private final IndicatorDao indicatorDao = new IndicatorDaoHibernate();
 
+    @Test
+    void findIndicators() {
+        final var actual = dao.findIndicators();
+        assertNotNull(actual);
+        assertEquals(Integer.valueOf(1), actual.size());
+        assertEquals("rainsum", actual.get(0).getCode());
+        assertEquals("year", actual.get(0).getPeriod().getCode());
+    }
+
     @Test
     void findLastDate() {
         final var indicator = indicatorDao.findByCodeAndPeriod("rainsum", "year");
-        final var lastDate = dao.findLastDate(indicator);
-        assertNotNull(lastDate);
+        final var actual = dao.findLastDate(indicator, 2023);
+        assertNotNull(actual);
+    }
+
+    @Test
+    void findRegions() {
+        final var actual = dao.findRegions();
+        assertNotNull(actual);
+        assertEquals(Integer.valueOf(1), actual.size());
+    }
+
+    @Test
+    void findYears() {
+        final var actual = dao.findYears();
+        assertNotNull(actual);
+        assertEquals(Integer.valueOf(1), actual.size());
+        assertEquals(Integer.valueOf(2023), actual.get(0));
     }
 }
diff --git a/www-server/src/test/java/fr/agrometinfo/www/server/rs/IndicatorResourceTest.java b/www-server/src/test/java/fr/agrometinfo/www/server/rs/IndicatorResourceTest.java
index 9396d87..8add6c1 100644
--- a/www-server/src/test/java/fr/agrometinfo/www/server/rs/IndicatorResourceTest.java
+++ b/www-server/src/test/java/fr/agrometinfo/www/server/rs/IndicatorResourceTest.java
@@ -11,7 +11,6 @@ import org.junit.jupiter.api.Test;
 
 import fr.agrometinfo.www.server.dao.CellDao;
 import fr.agrometinfo.www.server.dao.CellDaoHibernate;
-import fr.agrometinfo.www.server.dao.CellDaoHibernateTest;
 import fr.agrometinfo.www.server.dao.DailyValueDao;
 import fr.agrometinfo.www.server.dao.DailyValueDaoHibernate;
 import fr.agrometinfo.www.server.dao.IndicatorDao;
@@ -50,24 +49,16 @@ class IndicatorResourceTest extends JerseyTest {
         });
     }
 
-    @Test
-    void getCells() {
-        final FeatureCollection actual = target(IndicatorResource.PATH + SEP + IndicatorResource.PATH_CELLS).request()
-                .get(FeatureCollection.class);
-        assertNotNull(actual);
-        final Integer expected = Long.valueOf(CellDaoHibernateTest.COUNT).intValue();
-        assertEquals(expected, actual.getFeatures().size());
-    }
-
     /**
      * Expected values from sql/dailyvalues.csv.
      */
     @Test
     void getValues() {
         final FeatureCollection actual = target(IndicatorResource.PATH + SEP + IndicatorResource.PATH_VALUES)//
-                .queryParam("indicator", "rainsum")//
-                .queryParam("period", "year")//
-                .queryParam("compare", "false")//
+                .queryParam("indicator", "rainsum") //
+                .queryParam("period", "year") //
+                .queryParam("compare", "false") //
+                .queryParam("year", "2023") //
                 .request()//
                 .get(FeatureCollection.class);
         assertNotNull(actual);
diff --git a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ChoiceDTO.java b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ChoiceDTO.java
index b041c80..62a66dd 100644
--- a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ChoiceDTO.java
+++ b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ChoiceDTO.java
@@ -10,13 +10,18 @@ public final class ChoiceDTO {
     /**
      * The user wants to compare with normal.
      */
-    private Boolean comparison;
+    private Boolean comparison = false;
 
     /**
      * ID of chosen indicator.
      */
     private String indicator;
 
+    /**
+     * Period code of chosen indicator.
+     */
+    private String period;
+
     /**
      * Chosen region.
      */
@@ -41,6 +46,13 @@ public final class ChoiceDTO {
         return indicator;
     }
 
+    /**
+     * @return Period code of chosen indicator.
+     */
+    public String getPeriod() {
+        return period;
+    }
+
     /**
      * @return Chosen region.
      */
@@ -59,10 +71,9 @@ public final class ChoiceDTO {
      * @return the user chose all options
      */
     public boolean isValid() {
-        return indicator != null && !indicator.trim().isEmpty() //
-                && region != null && !region.trim().isEmpty() //
+        return period != null && !period.trim().isEmpty() //
+                && indicator != null && !indicator.trim().isEmpty() //
                 && year != null;
-
     }
 
     /**
@@ -79,6 +90,13 @@ public final class ChoiceDTO {
         this.indicator = value;
     }
 
+    /**
+     * @param value Period code of chosen indicator.
+     */
+    public void setPeriod(final String value) {
+        this.period = value;
+    }
+
     /**
      * @param value Chosen region.
      */
@@ -95,7 +113,8 @@ public final class ChoiceDTO {
 
     @Override
     public String toString() {
-        return "ChoiceDTO{" + "comparison=" + comparison + ", indicator=" + indicator + ", region=" + region + ", year="
-                + year + '}';
+        return "ChoiceDTO [comparison=" + comparison + ", indicator=" + indicator + ", period=" + period + ", region="
+                + region + ", year=" + year + "]";
     }
+
 }
diff --git a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ErrorResponseDTO.java b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ErrorResponseDTO.java
new file mode 100644
index 0000000..4a7a6a5
--- /dev/null
+++ b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/ErrorResponseDTO.java
@@ -0,0 +1,79 @@
+package fr.agrometinfo.www.shared.dto;
+
+/**
+ * JSON DTO to give error details.
+ *
+ * @author Olivier Maury
+ */
+public class ErrorResponseDTO {
+    /**
+     * Builder.
+     *
+     * @param statusCode   HTTP status code
+     * @param reasonPhrase Related HTTP status message
+     * @param explanation  Explanation
+     * @return instance
+     */
+    public static ErrorResponseDTO of(final int statusCode, final String reasonPhrase, final String explanation) {
+        final ErrorResponseDTO dto = new ErrorResponseDTO();
+        dto.setExplanation(explanation);
+        dto.setReasonPhrase(reasonPhrase);
+        dto.setStatusCode(statusCode);
+        return dto;
+    }
+    /**
+     * HTTP status code.
+     */
+    private int statusCode;
+    /**
+     * Related HTTP status message.
+     */
+    private String reasonPhrase;
+
+    /**
+     * Explanation.
+     */
+    private String explanation;
+
+    /**
+     * @return Explanation
+     */
+    public final String getExplanation() {
+        return explanation;
+    }
+
+    /**
+     * @return Related HTTP status message.
+     */
+    public final String getReasonPhrase() {
+        return reasonPhrase;
+    }
+
+    /**
+     * @return HTTP status code.
+     */
+    public final int getStatusCode() {
+        return statusCode;
+    }
+
+    /**
+     * @param value Explanation
+     */
+    public final void setExplanation(final String value) {
+        this.explanation = value;
+    }
+
+    /**
+     * @param value Related HTTP status message.
+     */
+    public final void setReasonPhrase(final String value) {
+        this.reasonPhrase = value;
+    }
+
+    /**
+     * @param value HTTP status code
+     */
+    public final void setStatusCode(final int value) {
+        this.statusCode = value;
+    }
+}
diff --git a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/IndicatorDTO.java b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/IndicatorDTO.java
index fccf0bf..91105da 100644
--- a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/IndicatorDTO.java
+++ b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/IndicatorDTO.java
@@ -18,7 +18,14 @@ public class IndicatorDTO {
      *
      * Eg.: meant for "Average temperature".
      */
-    private String uid;
+    private String code;
+
+    /**
+     * @return indicator UID
+     */
+    public final String getCode() {
+        return code;
+    }
 
     /**
      * @return Localized description.
@@ -28,10 +35,10 @@ public class IndicatorDTO {
     }
 
     /**
-     * @return the uid
+     * @param value Indicator UID
      */
-    public final String getUid() {
-        return uid;
+    public final void setCode(final String value) {
+        this.code = value;
     }
 
     /**
@@ -41,18 +48,11 @@ public class IndicatorDTO {
         this.description = value;
     }
 
-    /**
-     * @param id the uid to set
-     */
-    public final void setUid(final String id) {
-        this.uid = id;
-    }
-
     /**
      * String representation.
      */
     @Override
     public String toString() {
-        return "IndicatorDTO{" + "uid=" + uid + ", description=" + description + '}';
+        return "IndicatorDTO{" + "code=" + code + ", description=" + description + '}';
     }
 }
diff --git a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/IndicatorCategoryDTO.java b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/PeriodDTO.java
similarity index 60%
rename from www-shared/src/main/java/fr/agrometinfo/www/shared/dto/IndicatorCategoryDTO.java
rename to www-shared/src/main/java/fr/agrometinfo/www/shared/dto/PeriodDTO.java
index 29a028b..3f60f35 100644
--- a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/IndicatorCategoryDTO.java
+++ b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/PeriodDTO.java
@@ -5,26 +5,26 @@ import java.util.List;
 import org.dominokit.jackson.annotation.JSONMapper;
 
 /**
- * Indicator category.
+ * Indicator period.
  *
  * @author Olivier Maury
  */
 @JSONMapper
-public final class IndicatorCategoryDTO extends IndicatorDTO {
+public final class PeriodDTO extends IndicatorDTO {
     /**
-     * The indicators related to this category.
+     * The indicators related to this period.
      */
     private List<IndicatorDTO> indicators;
 
     /**
-     * @return The indicators related to this category.
+     * @return The indicators related to this period.
      */
     public List<IndicatorDTO> getIndicators() {
         return indicators;
     }
 
     /**
-     * @param value The indicators related to this category.
+     * @param value The indicators related to this period.
      */
     public void setIndicators(final List<IndicatorDTO> value) {
         this.indicators = value;
@@ -32,7 +32,7 @@ public final class IndicatorCategoryDTO extends IndicatorDTO {
 
     @Override
     public String toString() {
-        return "IndicatorCategoryDTO{" + "indicators=" + indicators + '}';
+        return "PeriodDTO{" + "indicators=" + indicators + '}';
     }
 
 }
diff --git a/www-shared/src/main/java/fr/agrometinfo/www/shared/service/IndicatorService.java b/www-shared/src/main/java/fr/agrometinfo/www/shared/service/IndicatorService.java
index a6eae67..b3a60ea 100644
--- a/www-shared/src/main/java/fr/agrometinfo/www/shared/service/IndicatorService.java
+++ b/www-shared/src/main/java/fr/agrometinfo/www/shared/service/IndicatorService.java
@@ -1,6 +1,7 @@
 package fr.agrometinfo.www.shared.service;
 
 import java.util.List;
+import java.util.Map;
 
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
@@ -9,7 +10,7 @@ import javax.ws.rs.QueryParam;
 import org.dominokit.rest.shared.request.service.annotations.RequestFactory;
 import org.geojson.FeatureCollection;
 
-import fr.agrometinfo.www.shared.dto.IndicatorCategoryDTO;
+import fr.agrometinfo.www.shared.dto.PeriodDTO;
 
 /**
  * Interface for client and server resource.
@@ -20,40 +21,45 @@ import fr.agrometinfo.www.shared.dto.IndicatorCategoryDTO;
 @Path(IndicatorService.PATH)
 public interface IndicatorService {
     /**
-     * Path for {@link IndicatorService#getIndicatorCategories()}.
+     * Service base path.
      */
-    String PATH_LIST = "list";
+    String PATH = "indicator";
     /**
-     * Path for {@link IndicatorService#getCells()}.
+     * Path for {@link IndicatorService#getPeriods()}.
      */
-    String PATH_CELLS = "cells";
+    String PATH_LIST = "list";
     /**
      * Path for {@link IndicatorService#getValues()}.
      */
     String PATH_VALUES = "values";
     /**
-     * Service base path.
+     * Path for {@link IndicatorService#getRegions()}.
      */
-    String PATH = "indicator";
+    String PATH_REGIONS = "regions";
+    /**
+     * Path for {@link IndicatorService#getYears()}.
+     */
+    String PATH_YEARS = "years";
 
     /**
-     * @return some cells
+     * @return list of years of computed indicators.
      */
     @GET
-    @Path(PATH_CELLS)
-    FeatureCollection getCells();
+    @Path(PATH_LIST)
+    List<PeriodDTO> getPeriods();
 
     /**
-     * @return list of available categories with indicators.
+     * @return list of regions (id : name) of computed indicators.
      */
     @GET
-    @Path(PATH_LIST)
-    List<IndicatorCategoryDTO> getIndicatorCategories();
+    @Path(PATH_REGIONS)
+    Map<String, String> getRegions();
 
     /**
      * @param indicator  indicator coe
      * @param period     period code
      * @param region     region ID
+     * @param year       year
      * @param comparison if returned values are comparison to normal
      * @return indicator values
      */
@@ -61,5 +67,12 @@ public interface IndicatorService {
     @Path(PATH_VALUES)
     FeatureCollection getValues(@QueryParam(value = "indicator") String indicator,
             @QueryParam(value = "period") String period, @QueryParam(value = "region") Integer region,
-            @QueryParam(value = "comparison") Boolean comparison);
+            @QueryParam(value = "year") Integer year, @QueryParam(value = "comparison") Boolean comparison);
+
+    /**
+     * @return list of computed years.
+     */
+    @GET
+    @Path(PATH_YEARS)
+    List<Integer> getYears();
 }
-- 
GitLab