view client/src/components/ImportStretches.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 8fc546b03822
children 9ae2a2f758bb
line wrap: on
line source

<template>
  <div class="d-flex flex-column mb-3">
    <UIBoxHeader
      icon="road"
      title="Define Stretches"
      :closeCallback="$parent.close"
    />
    <div v-if="!edit" class="mb-3 mr-3 ml-3 text-left">
      <table v-if="stretches.length > 0" class="table">
        <thead>
          <tr>
            <th class="header"><translate>Name</translate></th>
            <th class="header"><translate>Datum</translate></th>
            <th class="header"><translate>Source organization</translate></th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          <tr class="small" v-for="(stretch, index) in stretches" :key="index">
            <td class="">
              <a
                class="linkto text-info"
                v-if="isInStaging(stretch.properties.name)"
                @click="gotoStaging(getStagingLink(stretch.properties.name))"
              >
                {{ stretch.properties.name
                }}<font-awesome-icon
                  class="ml-1 text-danger"
                  icon="exclamation-triangle"
                  fixed-width
                ></font-awesome-icon
                ><small class="ml-1">review</small>
              </a>
              <a v-else @click="moveMapToStretch(index)" href="#">{{
                stretch.properties.name
              }}</a>
            </td>
            <td class="">
              {{ formatSurveyDate(stretch.properties["date_info"]) }}
            </td>
            <td>{{ stretch.properties["source_organization"] }}</td>
            <td class="text-right">
              <button
                class="btn btn-sm btn-dark mr-1"
                @click="editStretch(index)"
              >
                <font-awesome-icon icon="pencil-alt" fixed-width />
              </button>
              <button
                class="btn btn-sm btn-dark"
                @click="deleteStretch(stretch)"
              >
                <font-awesome-icon icon="trash" fixed-width />
              </button>
            </td>
          </tr>
        </tbody>
      </table>
      <div class="mt-3" v-if="stretches.length == 0">
        <translate>No results.</translate>
      </div>
    </div>
    <div v-if="edit">
      <div class="ml-3 mr-3">
        <div class="d-flex flex-row justify-content-between">
          <div class="mt-2 w-50 mr-2 text-left">
            <small class="text-muted"> <translate>ID</translate> </small>
            <input
              id="id"
              type="text"
              class="form-control"
              placeholder="AT_Section_12"
              aria-label="id"
              v-model="id"
              :disabled="!idEditable"
            />
            <span class="text-left text-danger">
              <small v-if="idError && !id">
                <translate>Please enter an id</translate>
              </small>
            </span>
          </div>
          <div class="mt-2 w-50 ml-2 text-left">
            <div>
              <small class="text-muted">
                <translate>Countrycode</translate>
              </small>
              <input
                id="countryCode"
                type="text"
                class="form-control"
                placeholder="AT"
                aria-label="id"
                v-model="countryCode"
              />
              <span class="text-left text-danger">
                <small v-if="countryCodeError && !countryCode">
                  <translate>Please enter a countrycode </translate>
                </small>
              </span>
            </div>
            <div class="w-50 ml-2"></div>
          </div>
        </div>
        <div class="d-flex flex-column  justify-content-between">
          <div class="mt-2 text-left">
            <small class="text-muted"> <translate>Start rhm</translate> </small>
            <div class="d-flex flex-row">
              <input
                id="startrhm"
                type="text"
                class="form-control"
                placeholder="e.g. ATXXX000010000019900"
                aria-label="startrhm"
                v-model="startrhm"
              />
              <span class="input-group-text">
                <font-awesome-icon
                  @click="togglePipette('start')"
                  :class="{ 'text-info': pipetteStart }"
                  icon="bullseye"
                ></font-awesome-icon>
              </span>
            </div>
            <span class="text-left text-danger">
              <small v-if="startrhmError && !startrhm">
                <translate>Please enter a start point</translate>
              </small>
            </span>
          </div>
          <div class="mt-2 text-left">
            <small class="text-muted"> <translate>End rhm</translate> </small>
            <div class="d-flex flex-row">
              <input
                id="endrhm"
                type="text"
                class="form-control"
                placeholder="e.g. ATXXX000010000019900"
                aria-label="endrhm"
                v-model="endrhm"
              />
              <span class="input-group-text">
                <font-awesome-icon
                  @click="togglePipette('end')"
                  :class="{ 'text-info': pipetteEnd }"
                  icon="bullseye"
                ></font-awesome-icon>
              </span>
            </div>
            <span class="text-left text-danger">
              <small v-if="endrhmError && !endrhm">
                <translate>Please enter an end point</translate>
              </small>
            </span>
          </div>
          <span class="text-left text-danger">
            <small v-if="!pointsValid">
              <translate>Startpoint is not before endpoint.</translate>
            </small>
          </span>
        </div>
        <div class="d-flex flex-row justify-content-between">
          <div class="mt-2  mr-2 w-50  text-left">
            <small class="text-muted">
              <translate>Object name</translate>
            </small>
            <input
              id="objbn"
              type="text"
              class="form-control"
              placeholder=""
              aria-label="objbn"
              v-model="objbn"
            />
            <span class="text-left text-danger">
              <small v-if="objbnError && !objbn">
                <translate>Please enter an objectname</translate>
              </small>
            </span>
          </div>
          <div class="mt-2  ml-2 w-50  text-left">
            <small class="text-muted">
              <translate>National Object name</translate>
            </small>
            <input
              id="nobjbn"
              type="text"
              class="form-control"
              placeholder=""
              aria-label="nobjbn"
              v-model="nobjbn"
            />
          </div>
        </div>
        <div class="d-flex flex-row justify-content-between">
          <div class="mt-2 mr-2 w-50 text-left">
            <small class="text-muted"> <translate>Date info</translate> </small>
            <input
              id="date_info"
              type="date"
              class="form-control"
              placeholder="date_info"
              aria-label="date_info"
              v-model="date_info"
            />
            <span class="text-left text-danger">
              <small v-if="date_infoError && !date_info">
                <translate>Please enter a date</translate>
              </small>
            </span>
          </div>
          <div class="mt-2 ml-2 w-50 text-left">
            <small class="text-muted"> <translate>Source</translate> </small>
            <input
              id="source"
              type="text"
              class="form-control"
              placeholder="source"
              aria-label="source"
              v-model="source"
            />
            <span class="text-left text-danger">
              <small v-if="sourceError && !source">
                <translate>Please enter a source</translate>
              </small>
            </span>
          </div>
        </div>
      </div>
      <div class="text-right mt-2 mr-3 mb-3">
        <button @click="edit = false" class="btn btn-warning mr-2">Back</button>
        <button
          @click="save"
          type="submit"
          class="shadow-sm btn btn-info submit-button"
        >
          <translate>Submit</translate>
        </button>
      </div>
    </div>
    <div class="text-right mr-3">
      <button v-if="!edit" @click="startEdit()" class="btn btn-info">
        <translate>New stretch</translate>
      </button>
    </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 { mapState, mapGetters } from "vuex";
import { displayError, displayInfo } from "@/lib/errors.js";
import { formatSurveyDate } from "@/lib/date.js";
import { LAYERS } from "@/store/map.js";

export default {
  name: "importstretches",
  data() {
    return {
      edit: false,
      idEditable: true,
      id: "",
      funktion: "",
      startrhm: "",
      endrhm: "",
      objbn: "",
      nobjbn: "",
      countryCode: "",
      date_info: new Date().toISOString().split("T")[0],
      source: "",
      pipetteStart: false,
      pipetteEnd: false,
      idError: false,
      funktionError: false,
      startrhmError: false,
      endrhmError: false,
      objbnError: false,
      nobjbnError: false,
      date_infoError: false,
      sourceError: false,
      countryCodeError: false
    };
  },
  mounted() {
    this.edit = false;
    this.loadStretches().catch(error => {
      const { status, data } = error.response;
      displayError({
        title: this.$gettext("Backend Error"),
        message: `${status}: ${data.message || data}`
      });
    });
    this.loadStagingData().catch(error => {
      const { status, data } = error.response;
      displayError({
        title: this.$gettext("Backend Error"),
        message: `${status}: ${data.message || data}`
      });
    });
  },
  methods: {
    gotoStaging(id) {
      this.$router.push("/review/" + id);
    },
    isInStaging(stretchname) {
      for (let s of this.stretchesInStaging) {
        if (s.name == stretchname) return true;
      }
      return false;
    },
    getStagingLink(stretchname) {
      for (let s of this.stretchesInStaging) {
        if (s.name == stretchname) return s.id;
      }
    },
    loadStagingData() {
      return new Promise((resolve, reject) => {
        this.$store
          .dispatch("imports/getStaging")
          .then(response => {
            resolve(response);
          })
          .catch(error => {
            reject(error);
          });
      });
    },
    editStretch(index) {
      const properties = this.stretches[index].properties;
      this.date_info = properties.date_info.split("T")[0];
      this.id = properties.name;
      this.nobjbn = properties.nobjnam;
      this.objbn = properties.objnam;
      this.countryCode = properties.countries;
      this.source = properties["source_organization"];
      this.edit = true;
      this.startrhm = properties.lower;
      this.endrhm = properties.upper;
      this.idEditable = false;
    },
    deleteStretch(stretch) {
      this.$store.commit("application/popup", {
        icon: "trash",
        title: this.$gettext("Delete Stretch"),
        content:
          this.$gettext("Do you really want to delete this stretch:") +
          `<br>
        <b>${stretch.properties.name}, ${
            stretch.properties.source_organization
          } (${stretch.properties.countries})</b>`,
        confirm: {
          label: this.$gettext("Delete"),
          icon: "trash",
          callback: () => {
            displayInfo({
              title: this.$gettext("Not implemented"),
              message: this.$gettext("Deleting " + stretch.id)
            });
          }
        },
        cancel: {
          label: this.$gettext("Cancel"),
          icon: "times"
        }
      });
    },
    moveMapToStretch(index) {
      this.$store.commit("map/setLayerVisible", LAYERS.STRETCHES);
      this.$store.commit("map/moveToExtent", {
        feature: this.stretches[index],
        zoom: 17,
        preventZoomOut: true
      });
    },
    formatSurveyDate(d) {
      return formatSurveyDate(d);
    },
    loadStretches() {
      return new Promise((resolve, reject) => {
        this.$store
          .dispatch("imports/loadStretches")
          .then(response => {
            resolve(response);
          })
          .catch(error => {
            reject(error);
          });
      });
    },
    clean() {
      this.id = "";
      this.edit = false;
      this.idEditable = true;
      this.funktion = "";
      this.startrhm = "";
      this.endrhm = "";
      this.objbn = "";
      this.nobjbn = "";
      this.countryCode = "";
      this.date_info = new Date().toISOString().split("T")[0];
      this.source = "";
      this.pipetteStart = false;
      this.pipetteEnd = false;
      this.idError = false;
      this.funktionError = false;
      this.startrhmError = false;
      this.endrhmError = false;
      this.objbnError = false;
      this.nobjbnError = false;
      this.date_infoError = false;
      this.sourceError = false;
      this.countryCodeError = false;
    },
    startEdit() {
      this.clean();
      this.edit = true;
    },
    togglePipette(t) {
      this.$store.commit("map/setLayerVisible", LAYERS.DISTANCEMARKSAXIS);
      if (t === "start") {
        this.pipetteStart = !this.pipetteStart;
        this.pipetteEnd = false;
      } else {
        this.pipetteEnd = !this.pipetteEnd;
        this.pipetteStart = false;
      }
    },
    validate() {
      const fields = [
        "id",
        "funktion",
        "startrhm",
        "endrhm",
        "objbn",
        "nobjbn",
        "countryCode",
        "date_info",
        "source"
      ];
      fields.forEach(field => {
        if (!this[field]) {
          this[field + "Error"] = true;
        } else {
          this[field + "Error"] = false;
        }
      });
    },
    save() {
      this.validate();
      if (!this.pointsValid) return;
      if (
        !this.id ||
        !this.startrhm ||
        !this.endrhm ||
        !this.source ||
        !this.date_info ||
        !this.objbn ||
        !this.countryCode
      )
        return;
      const data = {
        name: this.id,
        from: this.startrhm,
        to: this.endrhm,
        "source-organization": this.source,
        "date-info": this.date_info,
        objnam: this.objbn,
        nobjnam: this.nobjbn,
        countries: this.countryCode.split(",").map(x => {
          return x.trim();
        })
      };
      this.$store
        .dispatch("imports/saveStretch", data)
        .then(() => {
          displayInfo({
            title: this.$gettext("Import"),
            message: this.$gettext("Starting import of stretch")
          });
          this.clean();
          this.loadStretches().then(() => {
            this.edit = false;
          });
        })
        .catch(error => {
          const { status, data } = error.response;
          displayError({
            title: this.$gettext("Backend Error"),
            message: `${status}: ${data.message || data}`
          });
        });
    }
  },
  watch: {
    identifiedFeatures() {
      const filterDistanceMarks = x => {
        return /^distance_marks/.test(x["id_"]);
      };
      const distanceMark = this.identifiedFeatures.filter(filterDistanceMarks);
      if (distanceMark.length > 0) {
        const value = distanceMark[0].getProperties()["location"];
        this.startrhm = this.pipetteStart ? value : this.startrhm;
        this.endrhm = this.pipetteEnd ? value : this.endrhm;
        this.pipetteStart = false;
        this.pipetteEnd = false;
      }
    }
  },
  computed: {
    ...mapState("map", ["identifiedFeatures", "currentMeasurement"]),
    ...mapGetters("user", ["isSysAdmin"]),
    ...mapState("imports", ["stretches", "staging"]),
    stretchesInStaging() {
      const result = [];
      for (let stretch of this.stretches) {
        for (let s of this.staging) {
          if (s.kind == "st" && s.summary.stretch == stretch.properties.name) {
            result.push({ name: s.summary.stretch, id: s.id });
          }
        }
      }
      return result;
    },
    pointsValid() {
      if (!this.startrhm || !this.endrhm) return true;
      const start = this.startrhm.replace(/\D+/g, "") * 1;
      const end = this.endrhm.replace(/\D+/g, "") * 1;
      const result = start < end;
      return result;
    }
  }
};
</script>

<style lang="scss" scoped>
.linkto {
  cursor: pointer;
}
</style>