view client/src/components/staging/StagingDetail.vue @ 2356:d66f60163c2f

staging: fix optical alignment for non expandable entries
author Thomas Junk <thomas.junk@intevation.de>
date Wed, 20 Feb 2019 13:35:33 +0100
parents 0d7b51930028
children
line wrap: on
line source

<template>
  <div :class="detail">
    <div class="d-flex flex-row">
      <div class="mt-auto d-flex flex-row mb-auto small name text-left">
        <a
          v-if="isSoundingResult(data.kind.toUpperCase())"
          class="text-left"
          @click="zoomTo()"
          href="#"
          >{{ data.summary.bottleneck }}</a
        >
        <span v-if="isBottleneck(data.kind.toUpperCase())" class="text-left"
          ><translate>Bottlenecks</translate> ({{
            data.summary.bottlenecks.length
          }})</span
        >
        <a
          v-if="isApprovedGaugeMeasurement(data.kind.toUpperCase())"
          class="text-left"
          ><translate>Approved Gauge Measurements</translate> ({{
            data.summary.length
          }})</a
        >
        <span
          class="text-left"
          v-if="isFairwayDimension(data.kind.toUpperCase())"
          >{{ data.summary["source-organization"] }} (LOS:
          {{ data.summary.los }})</span
        >
        <a
          href="#"
          class="text-left"
          @click="zoomToStretch(data.summary.stretch)"
          v-if="isStretch(data.kind.toUpperCase())"
          >{{ data.summary.stretch }}</a
        >
      </div>
      <div class="mt-auto mb-auto small text-left type">
        {{ data.kind.toUpperCase() }}
      </div>
      <div v-if="data.summary" class="mt-auto mb-auto small text-left date">
        {{ formatSurveyDate(data.summary.date) }}
      </div>
      <div v-else class="mt-auto mb-auto small text-left date">-</div>
      <div class="mt-auto mb-auto small text-left imported">
        {{ formatSurveyDate(data.enqueued.split("T")[0]) }}
      </div>
      <div class="mt-auto mb-auto small text-left username">
        {{ data.user }}
      </div>
      <div class="controls d-flex flex-row justify-content-end">
        <div>
          <button
            :class="{
              'ml-3': true,
              'mr-3': true,
              btn: true,
              'btn-sm': true,
              'btn-outline-success': needsApproval(data) || isRejected(data),
              'btn-success': isApproved(data)
            }"
            @click="toggleApproval(data.id, $options.STATES.APPROVED)"
          >
            <font-awesome-icon icon="check"></font-awesome-icon>
          </button>
        </div>
        <div>
          <button
            :class="{
              'mr-3': true,
              btn: true,
              'btn-sm': true,
              'btn-outline-danger': needsApproval(data) || isApproved(data),
              'btn-danger': isRejected(data)
            }"
            @click="toggleApproval(data.id, $options.STATES.REJECTED)"
          >
            <font-awesome-icon icon="times" class="pointer"></font-awesome-icon>
          </button>
        </div>
        <div
          v-if="
            !isBottleneck(data.kind.toUpperCase()) ||
              isApprovedGaugeMeasurement(data.kind.toUpperCase())
          "
          class="expander"
        ></div>
        <div v-if="isBottleneck(data.kind.toUpperCase())">
          <div class="mt-auto mb-auto text-info text-left">
            <font-awesome-icon
              class="pointer"
              @click="showDetails()"
              v-if="show"
              icon="angle-up"
              fixed-width
            ></font-awesome-icon>
            <font-awesome-icon
              class="pointer"
              @click="showDetails()"
              v-if="loading"
              icon="spinner"
              fixed-width
            ></font-awesome-icon>
            <font-awesome-icon
              @click="showDetails()"
              class="pointer"
              v-if="!show && !loading"
              icon="angle-down"
              fixed-width
            ></font-awesome-icon>
          </div>
        </div>
        <div v-if="isApprovedGaugeMeasurement(data.kind.toUpperCase())">
          <div
            @click="showAGMDetails = !showAGMDetails"
            class="mt-auto mb-auto text-info text-left"
          >
            <font-awesome-icon
              class="pointer"
              v-if="showAGMDetails"
              icon="angle-up"
              fixed-width
            ></font-awesome-icon>
            <font-awesome-icon
              class="pointer"
              v-if="!showAGMDetails"
              icon="angle-down"
              fixed-width
            ></font-awesome-icon>
          </div>
        </div>
        <div v-else class="empty"></div>
      </div>
    </div>
    <div v-if="show && bottlenecks.length > 0" class="bottlenecksdetails">
      <div
        v-for="(bottleneck, index) in bottlenecks"
        :key="index"
        class="d-flex flex-row"
      >
        <div class="d-flex flex-column">
          <div class="d-flex flex-row">
            <a @click="moveToBottleneck(index)" class="small" href="#">{{
              bottleneck.properties.objnam
            }}</a>
            <div
              @click="showBottleneckDetails(index)"
              class="small mt-auto mb-auto text-info text-left"
            >
              <font-awesome-icon
                class="pointer"
                v-if="showBottleneckDetail === index"
                icon="angle-up"
                fixed-width
              ></font-awesome-icon>
              <font-awesome-icon
                class="pointer"
                v-if="!(showBottleneckDetail === index)"
                icon="angle-down"
                fixed-width
              ></font-awesome-icon>
            </div>
          </div>

          <div class="d-flex flex-row" v-if="showBottleneckDetail === index">
            <table>
              <tr
                v-for="(info, index) in Object.keys(bottleneck.properties)"
                :key="index"
                class="mr-1 small text-muted"
              >
                <td class="condensed text-left">{{ info }}</td>
                <td class="condensed pl-3 text-left">
                  {{ bottleneck.properties[info] }}
                </td>
              </tr>
            </table>
          </div>
        </div>
      </div>
    </div>
    <div v-if="showAGMDetails">
      <div class="pl-3 d-flex flex-row">
        <span class="condensed agmcode text-left"
          ><small><translate>ISRS Code</translate></small></span
        >
        <span class="condensed agmdetail text-left"
          ><small><translate>Date of measurement</translate></small></span
        >
      </div>
      <div class="diffs">
        <div v-for="(result, index) in data.summary" :key="index">
          <div class="pl-3 d-flex flex-row">
            <span
              v-if="result.versions.length == 1"
              class="condensed agmcode text-left"
              ><small
                >{{ result["fk-gauge-id"] }}
                <translate>( New )</translate></small
              ></span
            >
            <span
              v-if="result.versions.length == 2"
              class="condensed agmcode text-left"
              ><small>{{ result["fk-gauge-id"] }}</small></span
            >
            <span class="condensed agmdetail text-left"
              ><small>{{ formatDateTime(result["measure-date"]) }}</small></span
            >
            <div
              @click="toggleDiff(index)"
              class="small ml-auto mt-auto mb-auto text-info text-left"
            >
              <font-awesome-icon
                class="pointer"
                v-if="showDiff == index"
                icon="angle-up"
                fixed-width
              ></font-awesome-icon>
              <font-awesome-icon
                class="pointer"
                v-if="showDiff != index"
                icon="angle-down"
                fixed-width
              ></font-awesome-icon>
            </div>
          </div>
          <div v-if="showDiff == index" class="pl-3 d-flex flex-row">
            <div>
              <div class="d-flex flex-row condensed pl-3 text-left">
                <div class="header border-bottom agmdetailskeys">
                  <small><translate>Value</translate></small>
                </div>
                <div
                  v-if="result.versions.length == 2"
                  class="header border-bottom agmdetailsvalues"
                >
                  <small><translate>Old</translate></small>
                </div>
                <div class="header border-bottom agmdetailsvalues">
                  <small><translate>New</translate></small>
                </div>
              </div>
              <div
                class="d-flex flex-row condensed pl-3 text-left"
                v-for="(entry, index) in Object.keys(result.versions[0])"
                :key="index"
              >
                <div
                  v-if="
                    result.versions.length == 1 ||
                      result.versions[0][entry] != result.versions[1][entry]
                  "
                  class="agmdetailskeys"
                >
                  <small>{{ entry }}</small>
                </div>
                <div
                  v-if="
                    result.versions.length == 1 ||
                      result.versions[0][entry] != result.versions[1][entry]
                  "
                  class="agmdetailsvalues"
                >
                  <small>{{ result.versions[0][entry] }}</small>
                </div>
                <div
                  v-if="
                    result.versions.length == 2 &&
                      result.versions[0][entry] != result.versions[1][entry]
                  "
                  class="agmdetailsvalues"
                >
                  <small>{{ result.versions[1][entry] }}</small>
                </div>
              </div>
            </div>
          </div>
        </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):
 * Thomas Junk <thomas.junk@intevation.de>
 */

import { formatSurveyDate, formatDateTime } from "@/lib/date.js";
import { STATES } from "@/store/imports.js";
import { HTTP } from "@/lib/http";
import { WFS } from "ol/format.js";
import { or as orFilter, equalTo as equalToFilter } from "ol/format/filter.js";
import { displayError } from "@/lib/errors.js";
import { mapState } from "vuex";
import { LAYERS } from "@/store/map.js";

const NO_DIFF = -1;
const NO_BOTTLENECK = -1;

export default {
  name: "stagingdetail",
  props: ["data"],
  data() {
    return {
      showDiff: NO_DIFF,
      showAGMDetails: false,
      showBottleneckDetail: NO_BOTTLENECK,
      show: false,
      loading: false,
      bottlenecks: []
    };
  },
  mounted() {
    this.bottlenecks = [];
    const { id } = this.$route.params;
    this.$store.commit("imports/setImportToReview", id);
    if (this.open) this.showDetails();
  },
  computed: {
    ...mapState("imports", ["importToReview"]),
    open() {
      return this.importToReview == this.data.id;
    },
    detail() {
      return [
        "pb-2",
        "pt-2",
        "d-flex",
        "flex-column",
        "w-100",
        {
          highlight: this.open && this.needsApproval(this.data)
        }
      ];
    }
  },
  watch: {
    showAGMDetails() {
      if (!this.showAGMDetails) this.showDiff = NO_DIFF;
    },
    open() {
      this.show = this.open;
    },
    $route() {
      const { id } = this.$route.params;
      this.$store.commit("imports/setImportToReview", id);
      if (this.open) this.showDetails();
    }
  },
  methods: {
    showBottleneckDetails(index) {
      if (index == this.showBottleneckDetail) {
        this.showBottleneckDetail = NO_BOTTLENECK;
        return;
      }
      this.showBottleneckDetail = index;
    },
    toggleDiff(number) {
      if (this.showDiff !== number || this.showDiff == -1) {
        this.showDiff = number;
      } else {
        this.showDiff = -1;
      }
    },
    zoomToStretch(name) {
      this.$store.commit("map/setLayerVisible", LAYERS.STRETCHES);
      this.$store
        .dispatch("imports/loadStretch", name)
        .then(response => {
          if (response.data.features.length < 1)
            throw new Error("no feaures found for: " + name);
          this.moveToExtent(response.data.features[0]);
        })
        .catch(error => {
          console.log(error);
          const { status, data } = error.response;
          displayError({
            title: this.$gettext("Backend Error"),
            message: `${status}: ${data.message || data}`
          });
        });
    },
    showDetails() {
      if (!this.isBottleneck(this.data.kind.toUpperCase())) return;
      if (this.show) {
        this.show = false;
        return;
      }
      if (this.bottlenecks.length > 0) {
        this.show = true;
        return;
      }
      this.loading = true;
      const generateFilter = () => {
        const { bottlenecks } = this.data.summary;
        if (bottlenecks.length === 1)
          return equalToFilter("bottleneck_id", bottlenecks[0]);
        const orExpressions = bottlenecks.map(x => {
          return equalToFilter("bottleneck_id", x);
        });
        return orFilter(...orExpressions);
      };
      const filterExpression = generateFilter();
      const bottleneckFeatureCollectionRequest = new WFS().writeGetFeature({
        srsName: "EPSG:4326",
        featureNS: "gemma",
        featurePrefix: "gemma",
        featureTypes: ["bottlenecks_geoserver"],
        outputFormat: "application/json",
        filter: filterExpression
      });
      HTTP.post(
        "/internal/wfs",
        new XMLSerializer().serializeToString(
          bottleneckFeatureCollectionRequest
        ),
        {
          headers: {
            "X-Gemma-Auth": localStorage.getItem("token"),
            "Content-type": "text/xml; charset=UTF-8"
          }
        }
      )
        .then(response => {
          this.bottlenecks = response.data.features;
          this.show = true;
          this.loading = false;
        })
        .catch(error => {
          const { status, data } = error.response;
          displayError({
            title: this.$gettext("Backend Error"),
            message: `${status}: ${data.message || data}`
          });
        });
    },
    isFairwayDimension(kind) {
      return kind === "FD";
    },
    isApprovedGaugeMeasurement(kind) {
      return kind === "AGM";
    },
    isBottleneck(kind) {
      return kind === "BN" || kind === "UBN";
    },
    isStretch(kind) {
      return kind === "ST";
    },
    isSoundingResult(kind) {
      return kind === "SR";
    },
    formatSurveyDate(date) {
      return formatSurveyDate(date);
    },
    formatDateTime(date) {
      return formatDateTime(date);
    },
    needsApproval(item) {
      return item.status === STATES.NEEDSAPPROVAL;
    },
    isRejected(item) {
      return item.status === STATES.REJECTED;
    },
    isApproved(item) {
      return item.status === STATES.APPROVED;
    },
    moveToBottleneck(index) {
      this.$store.commit("map/setLayerVisible", LAYERS.BOTTLENECKS);
      this.moveToExtent(this.bottlenecks[index]);
    },
    moveToExtent(feature) {
      this.$store.commit("map/moveToExtent", {
        feature: feature,
        zoom: 17,
        preventZoomOut: true
      });
    },
    moveMap(coordinates) {
      this.$store.commit("map/moveMap", {
        coordinates: coordinates,
        zoom: 17,
        preventZoomOut: true
      });
    },
    zoomTo() {
      const { lat, lon, bottleneck, date } = this.data.summary;
      const coordinates = [lat, lon];
      this.moveMap(coordinates);
      this.$store
        .dispatch("bottlenecks/setSelectedBottleneck", bottleneck)
        .then(() => {
          this.$store.commit("bottlenecks/setSelectedSurveyByDate", date);
        });
    },
    toggleApproval(id, newStatus) {
      this.$store.commit("imports/toggleApproval", {
        id: id,
        newStatus: newStatus
      });
    }
  },
  STATES: STATES
};
</script>

<style lang="scss" scoped>
.expander {
  margin-right: 1.25rem;
}

.bottlenecksdetails {
  overflow-y: auto;
  max-height: 250px;
}
.diffs {
  overflow-y: auto;
  max-height: 250px;
}

.highlight {
  background-color: #f9f9f9;
}

.condensed {
  font-stretch: condensed;
}

.empty {
  margin-right: 20px;
}

.name {
  width: 180px;
}

.date {
  width: 90px;
}

.type {
  width: 40px;
}

.imported {
  width: 90px;
}

.username {
  width: 150px;
}

.controls {
  width: 60px;
}

.agmcode {
  width: 200px;
}

.agmdate {
  width: 100px;
}

.agmdetailskeys {
  width: 130px;
}

.agmdetailsvalues {
  width: 200px;
}
</style>