view client/src/bottlenecks/Bottlenecks.vue @ 1146:74e180ad3d6b

fairway profile UI improvements splitscreen button position at top of profile container bottleneck name and survey date as headline in profile container moved logout button to sidebar menu to avoid unnecessary overlapping
author Markus Kottlaender <markus@intevation.de>
date Tue, 13 Nov 2018 11:12:12 +0100
parents 5f98d0c9d738
children da75faa8043f
line wrap: on
line source

<template>
    <div :class="bottlenecksStyle" :style="'left: ' + (showSidebar ? '17rem' : '64px')">
        <div @click="$store.commit('application/showBottlenecks', !showBottlenecks);" class="ui-element close-bottlenecks">
            <i class="fa fa-close"></i>
        </div>

        <h4>Bottlenecks</h4>
        <hr class="mb-0">
        <input type="text" v-model="search" placeholder="Search Bottleneck..." class="border-0 w-100 p-2" />
        <div class="row p-2 border-top text-left small">
            <div class="col-5">
                <a href="#" @click="sortBy('name')" class="sort-link">Name</a>
                <i :class="sortClass" v-if="sortColumn === 'name'"></i>
            </div>
            <div class="col-2">
                <a href="#" @click="sortBy('latestMeasurement')" class="sort-link">Latest Measurement</a>
                <i :class="sortClass" v-if="sortColumn === 'latestMeasurement'"></i>
            </div>
            <div class="col-3">
                <a href="#" @click="sortBy('chainage')" class="sort-link">Chainage</a>
                <i :class="sortClass" v-if="sortColumn === 'chainage'"></i>
            </div>
            <div class="col-2"></div>
        </div>
        <div class="bottleneck-list small text-left">
            <div v-for="bottleneck in filteredAndSortedBottlenecks()" :key="bottleneck.properties.name" class="border-top row mx-0 py-2">
                <div class="col-5 text-left">
                    <a href="#" class="d-block" @click="moveToBottleneck(bottleneck)">
                        {{ bottleneck.properties.name }}
                    </a>
                </div>
                <div class="col-2">
                    {{ displayCurrentSurvey(bottleneck.properties.current) }}
                </div>
                <div class="col-3">
                    {{ displayCurrentChainage(bottleneck.properties.from, bottleneck.properties.from) }}
                </div>
                <div class="col-2 text-right">
                    <button type="button" class="btn btn-sm btn-outline-secondary" @click="toggleBottleneck(bottleneck.properties.name)">
                        <i class="fa fa-angle-down"></i>
                    </button>
                </div>
                <div :class="['col-12', 'surveys', {open: openBottleneck === bottleneck.properties.name}]">
                   <a href="#" class="d-block p-2" v-for="(survey, index) in openBottleneckSurveys" :key="index" @click="selectSurvey(survey, bottleneck)">
                      {{ survey.date_info }}
                    </a>
                </div>
            </div>
        </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 { fromLonLat } from "ol/proj";
import { HTTP } from "../application/lib/http";
import { displayError } from "../application/lib/errors.js";

export default {
  name: "bottlenecks",
  data() {
    return {
      search: "",
      sortColumn: "name",
      sortDirection: "ASC",
      openBottleneck: null,
      openBottleneckSurveys: null
    };
  },
  computed: {
    ...mapState("application", ["showBottlenecks", "showSidebar"]),
    ...mapState("bottlenecks", ["bottlenecks"]),
    ...mapState("map", ["openLayersMap"]),
    bottlenecksStyle() {
      return {
        "ui-element": true,
        bottlenecks: true,
        overlay: true,
        bottleneckscollapsed: !this.showBottlenecks,
        bottlenecksextended: this.showBottlenecks,
        shadow: true
      };
    },
    sortClass() {
      return {
        fa: true,
        "fa-sort-amount-asc": this.sortDirection === "ASC",
        "fa-sort-amount-desc": this.sortDirection === "DESC",
        "ml-1": true
      };
    }
  },
  methods: {
    filteredAndSortedBottlenecks() {
      return this.bottlenecks
        .filter(bn => {
          return bn.properties.name
            .toLowerCase()
            .includes(this.search.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
      );
      this.$store.commit("bottlenecks/setSelectedSurvey", survey);
      this.moveToBottleneck(bottleneck);
    },
    moveToBottleneck(bottleneck) {
      // TODO: make this central, duplicates code from application/Topbar.vue
      let view = this.openLayersMap.getView();
      const currentZoom = view.getZoom();
      const newZoom = Math.max(17, currentZoom);
      view.animate(
        {
          zoom: newZoom,
          center: fromLonLat(
            bottleneck.geometry.coordinates,
            view.getProjection()
          )
        },
        700
      );
    },
    sortBy(column) {
      this.sortColumn = column;
      this.sortDirection = this.sortDirection === "ASC" ? "DESC" : "ASC";
    },
    toggleBottleneck(name) {
      this.openBottleneckSurveys = null;
      if (name === this.openBottleneck) {
        this.openBottleneck = null;
      } else {
        this.openBottleneck = 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;
          })
          .catch(error => {
            const { status, data } = error.response;
            displayError({
              title: "Backend Error",
              message: `${status}: ${data.message || data}`
            });
          });
      }
    },
    displayCurrentSurvey(current) {
      return current ? current.substr(0, current.length - 1) : "";
    },
    displayCurrentChainage(from, to) {
      return from / 10 + " - " + to / 10;
    }
  },
  mounted() {
    this.$store.dispatch("bottlenecks/loadBottlenecks");
  }
};
</script>

<style lang="scss" scoped>
.bottlenecks {
  position: absolute;
  z-index: -2;
  top: $offset;
  background-color: #ffffff;
  padding-top: $offset;
  opacity: $slight-transparent;
  border-radius: $border-radius;
  transition: left 0.3s ease;
  overflow: hidden;
  background: #fff;
}

.bottleneckscollapsed {
  width: 0;
  height: 0;
  transition: $transition-fast;
}

.bottlenecksextended {
  width: 600px;
}

.close-bottlenecks {
  position: absolute;
  z-index: 2;
  right: 0;
  top: 7px;
  border-radius: $border-radius;
  height: $icon-width;
  width: $icon-height;
  display: none;
}

.bottlenecksextended .close-bottlenecks {
  display: block;
}

.bottleneck-list {
  overflow-y: auto;
  max-height: 500px;
}

.surveys {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
}

.surveys.open {
  max-height: 999px;
}

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