view client/src/components/Bottlenecks.vue @ 2455:54c9fe587fe6

Subdivide SQL function to prepare for improved error handling The context of an error (e.g. the function in which it occured) can be inferred by the database client. Not doing all in one statement will render the context more meaningful.
author Tom Gottfried <tom@intevation.de>
date Fri, 01 Mar 2019 18:38:02 +0100
parents 64ff5984351e
children 9ae2a2f758bb
line wrap: on
line source

<template>
  <div>
    <UIBoxHeader
      icon="ship"
      title="Bottlenecks"
      :closeCallback="$parent.close"
    />
    <div class="row p-2 text-left small">
      <div class="col-5">
        <a href="#" @click="sortBy('name')" class="sort-link">
          <translate>Name</translate>
        </a>
        <font-awesome-icon
          :icon="sortIcon"
          class="ml-1"
          v-if="sortColumn === 'name'"
        ></font-awesome-icon>
      </div>
      <div class="col-2">
        <a href="#" @click="sortBy('latestMeasurement')" class="sort-link">
          <translate>Latest</translate> <br />
          <translate>Measurement</translate>
        </a>
        <font-awesome-icon
          :icon="sortIcon"
          class="ml-1"
          v-if="sortColumn === 'latestMeasurement'"
        ></font-awesome-icon>
      </div>
      <div class="col-3">
        <a href="#" @click="sortBy('chainage')" class="sort-link">
          <translate>Chainage</translate>
        </a>
        <font-awesome-icon
          :icon="sortIcon"
          class="ml-1"
          v-if="sortColumn === 'chainage'"
        ></font-awesome-icon>
      </div>
      <div class="col-2"></div>
    </div>
    <div
      class="bottleneck-list small text-left"
      :style="'max-height: ' + (showSplitscreen ? 18 : 35) + 'rem'"
      v-if="filteredAndSortedBottlenecks().length"
    >
      <div
        v-for="bottleneck in filteredAndSortedBottlenecks()"
        :key="bottleneck.properties.name"
        class="border-top row bottleneck-row mx-0"
      >
        <div class="col-5 py-2 text-left">
          <a href="#" @click="selectBottleneck(bottleneck)">{{
            bottleneck.properties.name
          }}</a>
        </div>
        <div class="col-2 py-2">
          {{ formatSurveyDate(bottleneck.properties.current) }}
        </div>
        <div class="col-3 py-2">
          {{
            displayCurrentChainage(
              bottleneck.properties.from,
              bottleneck.properties.to
            )
          }}
        </div>
        <div class="col-2 pr-0 text-right d-flex flex-column">
          <a
            class="text-info mt-auto mb-auto mr-2"
            @click="loadSurveys(bottleneck.properties.name)"
            v-if="bottleneck.properties.current"
          >
            <font-awesome-icon
              class="pointer"
              icon="spinner"
              fixed-width
              spin
              v-if="loading === bottleneck.properties.name"
            ></font-awesome-icon>
            <font-awesome-icon
              class="pointer"
              icon="angle-down"
              fixed-width
              v-if="
                loading !== bottleneck.properties.name &&
                  openBottleneck !== bottleneck.properties.name
              "
            ></font-awesome-icon>
            <font-awesome-icon
              class="pointer"
              icon="angle-up"
              fixed-width
              v-if="
                loading !== bottleneck.properties.name &&
                  openBottleneck === bottleneck.properties.name
              "
            ></font-awesome-icon>
          </a>
        </div>
        <div
          :class="[
            'col-12 p-0',
            'surveys',
            { open: openBottleneck === bottleneck.properties.name }
          ]"
        >
          <a
            href="#"
            class="d-block px-3 py-2"
            v-for="(survey, index) in openBottleneckSurveys"
            :key="index"
            @click="selectSurvey(survey, bottleneck)"
            >{{ formatSurveyDate(survey.date_info) }}</a
          >
        </div>
      </div>
    </div>
    <div v-else class="small text-center py-3 border-top">
      <translate>No results.</translate>
    </div>
  </div>
</template>

<script>
/* This is Free Software under GNU Affero General Public License v >= 3.0
 * without warranty, see README.md and license for details.
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later
 * License-Filename: LICENSES/AGPL-3.0.txt
 *
 * Copyright (C) 2018 by via donau
 *   – Österreichische Wasserstraßen-Gesellschaft mbH
 * Software engineering by Intevation GmbH
 *
 * Author(s):
 * Markus Kottländer <markus.kottlaender@intevation.de>
 */
import { mapState } from "vuex";
import { HTTP } from "@/lib/http";
import { displayError } from "@/lib/errors.js";
import { formatSurveyDate } from "@/lib/date.js";

export default {
  name: "bottlenecks",
  data() {
    return {
      sortColumn: "name",
      sortDirection: "ASC",
      openBottleneck: null,
      openBottleneckSurveys: null,
      loading: null
    };
  },
  computed: {
    ...mapState("application", [
      "searchQuery",
      "showSearchbarLastState",
      "showSplitscreen"
    ]),
    ...mapState("bottlenecks", ["bottlenecksList"]),
    sortIcon() {
      return this.sortDirection === "ASC"
        ? "sort-amount-down"
        : "sort-amount-up";
    }
  },
  methods: {
    formatSurveyDate(date) {
      return formatSurveyDate(date);
    },
    filteredAndSortedBottlenecks() {
      return this.bottlenecksList
        .filter(bn => {
          return bn.properties.name
            .toLowerCase()
            .includes(this.searchQuery.toLowerCase());
        })
        .sort((bnA, bnB) => {
          switch (this.sortColumn) {
            case "name":
              if (
                bnA.properties.name.toLowerCase() <
                bnB.properties.name.toLowerCase()
              )
                return this.sortDirection === "ASC" ? -1 : 1;
              if (
                bnA.properties.name.toLowerCase() >
                bnB.properties.name.toLowerCase()
              )
                return this.sortDirection === "ASC" ? 1 : -1;
              return 0;

            case "latestMeasurement": {
              if (
                (bnA.properties.current || "") < (bnB.properties.current || "")
              )
                return this.sortDirection === "ASC" ? -1 : 1;
              if (
                (bnA.properties.current || "") > (bnB.properties.current || "")
              )
                return this.sortDirection === "ASC" ? 1 : -1;
              return 0;
            }

            case "chainage":
              if (bnA.properties.from < bnB.properties.from)
                return this.sortDirection === "ASC" ? -1 : 1;
              if (bnA.properties.from > bnB.properties.from)
                return this.sortDirection === "ASC" ? 1 : -1;
              return 0;

            default:
              return 0;
          }
        });
    },
    selectSurvey(survey, bottleneck) {
      this.$store
        .dispatch(
          "bottlenecks/setSelectedBottleneck",
          bottleneck.properties.name
        )
        .then(() => {
          this.$store.commit("bottlenecks/selectedSurvey", survey);
        })
        .then(() => {
          this.$store.commit("map/moveToExtent", {
            feature: bottleneck,
            zoom: 17,
            preventZoomOut: true
          });
        });
    },
    selectBottleneck(bottleneck) {
      this.$store
        .dispatch(
          "bottlenecks/setSelectedBottleneck",
          bottleneck.properties.name
        )
        .then(() => {
          this.$store.commit("bottlenecks/setFirstSurveySelected");
        })
        .then(() => {
          this.$store.commit("map/moveToExtent", {
            feature: bottleneck,
            zoom: 17,
            preventZoomOut: true
          });
        });
    },
    sortBy(column) {
      this.sortColumn = column;
      this.sortDirection = this.sortDirection === "ASC" ? "DESC" : "ASC";
    },
    loadSurveys(name) {
      this.openBottleneckSurveys = null;
      if (name === this.openBottleneck) {
        this.openBottleneck = null;
      } else {
        this.openBottleneck = name;
        this.loading = name;

        HTTP.get("/surveys/" + name, {
          headers: {
            "X-Gemma-Auth": localStorage.getItem("token"),
            "Content-type": "text/xml; charset=UTF-8"
          }
        })
          .then(response => {
            this.openBottleneckSurveys = response.data.surveys.sort((a, b) => {
              return a.date_info < b.date_info ? 1 : -1;
            });
          })
          .catch(error => {
            const { status, data } = error.response;
            displayError({
              title: this.$gettext("Backend Error"),
              message: `${status}: ${data.message || data}`
            });
          })
          .finally(() => (this.loading = null));
      }
    },
    displayCurrentChainage(from, to) {
      return from / 10 + " - " + to / 10;
    }
  },
  mounted() {
    this.$store.dispatch("bottlenecks/loadBottlenecksList");
  }
};
</script>

<style lang="scss" scoped>
.bottleneck-list {
  overflow-y: auto;
}

.bottleneck-list .bottleneck-row a {
  text-decoration: none;
}

.bottleneck-list .bottleneck-row:hover {
  background: #fbfbfb;
}

.surveys {
  max-height: 0;
  min-height: 0;
  overflow: hidden;
}

.surveys a:hover {
  background: #f3f3f3;
}

.surveys.open {
  max-height: 250px;
  overflow: auto;
}

.sort-link {
  color: #444;
  font-weight: bold;
}
</style>