changeset 2971:7a51fdfead2d unified_import

merge with default
author Thomas Junk <thomas.junk@intevation.de>
date Mon, 08 Apr 2019 09:18:58 +0200
parents 149a8f81f99e (current diff) 972400e56e4a (diff)
children 6f351e00e579
files client/src/components/Contextbox.vue
diffstat 20 files changed, 229 insertions(+), 278 deletions(-) [+]
line wrap: on
line diff
--- a/client/src/components/Bottlenecks.vue	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/components/Bottlenecks.vue	Mon Apr 08 09:18:58 2019 +0200
@@ -18,7 +18,7 @@
           title: `${latestmeasurementLabel}`,
           width: '150px'
         },
-        { id: 'properties.from', title: `${chainageLabel}`, width: '150px' }
+        { id: 'properties.from', title: `${chainageLabel}`, width: '135px' }
       ]"
     />
     <UITableBody
@@ -38,7 +38,7 @@
         <div class="table-cell" style="width: 150px">
           {{ bottleneck.properties.current | surveyDate }}
         </div>
-        <div class="table-cell" style="width: 150px">
+        <div class="table-cell" style="width: 135px">
           {{
             displayCurrentChainage(
               bottleneck.properties.from,
@@ -46,7 +46,7 @@
             )
           }}
         </div>
-        <div class="table-cell center" style="width: 30px">
+        <div class="table-cell center" style="flex-grow: 1">
           <UISpinnerButton
             @click="loadSurveys(bottleneck)"
             :loading="loading === bottleneck"
--- a/client/src/components/Contextbox.vue	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/components/Contextbox.vue	Mon Apr 08 09:18:58 2019 +0200
@@ -88,7 +88,6 @@
 
 .contextboxextended {
   max-width: 660px;
-  max-height: 95vh;
 }
 
 .close-contextbox {
--- a/client/src/components/Maplayer.vue	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/components/Maplayer.vue	Mon Apr 08 09:18:58 2019 +0200
@@ -129,7 +129,6 @@
       return loader;
     },
     updateBottleneckFilter(bottleneck_id, datestr) {
-      console.log("updating filter with", bottleneck_id, datestr);
       const layer = this.getLayerByName(LAYERS.BOTTLENECKISOLINE);
       const wmsSrc = layer.data.getSource();
       const exists = bottleneck_id != "does_not_exist";
@@ -144,7 +143,6 @@
             "'"
         });
       }
-      layer.isVisible = exists;
       layer.data.setVisible(exists);
     }
   },
@@ -282,7 +280,6 @@
 
       layer = this.getLayerByName(los);
       layer.data.getSource().setLoader(loader);
-      layer.data.setVisible(layer.isVisible);
     });
 
     // load following layers with bboxStrategy (using our request builder)
@@ -301,7 +298,6 @@
         layer.data.getSource()
       )
     );
-    layer.data.setVisible(layer.isVisible);
 
     layer = this.getLayerByName(LAYERS.WATERWAYAXIS);
     layer.data.getSource().setLoader(
@@ -316,7 +312,6 @@
         layer.data.getSource()
       )
     );
-    layer.data.setVisible(layer.isVisible);
 
     layer = this.getLayerByName(LAYERS.WATERWAYPROFILES);
     layer.data.getSource().setLoader(
@@ -331,7 +326,6 @@
         layer.data.getSource()
       )
     );
-    layer.data.setVisible(layer.isVisible);
 
     layer = this.getLayerByName(LAYERS.DISTANCEMARKS);
     layer.data.getSource().setLoader(
@@ -346,7 +340,6 @@
         layer.data.getSource()
       )
     );
-    layer.data.setVisible(layer.isVisible);
 
     layer = this.getLayerByName(LAYERS.DISTANCEMARKSAXIS);
     layer.data.getSource().setLoader(
@@ -361,7 +354,6 @@
         layer.data.getSource()
       )
     );
-    layer.data.setVisible(layer.isVisible);
 
     layer = this.getLayerByName(LAYERS.GAUGES);
     layer.data.getSource().setLoader(
@@ -376,7 +368,6 @@
         layer.data.getSource()
       )
     );
-    layer.data.setVisible(layer.isVisible);
 
     layer = this.getLayerByName(LAYERS.STRETCHES);
     layer.data.getSource().setLoader(
@@ -397,7 +388,6 @@
         }
       )
     );
-    layer.data.setVisible(layer.isVisible);
 
     layer = this.getLayerByName(LAYERS.BOTTLENECKSTATUS);
     layer.data.getSource().setLoader(
@@ -426,7 +416,6 @@
         layer.data.getSource()
       )
     );
-    layer.data.setVisible(layer.isVisible);
     HTTP.get("/system/style/Bottlenecks/stroke", {
       headers: { "X-Gemma-Auth": localStorage.getItem("token") }
     })
--- a/client/src/components/Pdftool.vue	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/components/Pdftool.vue	Mon Apr 08 09:18:58 2019 +0200
@@ -98,7 +98,7 @@
 import "@/lib/font-linbiolinum.js";
 import { getPointResolution } from "ol/proj.js";
 import locale2 from "locale2";
-import { HTTP } from "../lib/http";
+import { HTTP } from "@/lib/http";
 import { displayError } from "@/lib/errors.js";
 import { LAYERS } from "@/store/map.js";
 
@@ -754,7 +754,7 @@
       if (
         this.selectedBottleneck &&
         this.selectedSurvey &&
-        this.getLayerByName(LAYERS.BOTTLENECKISOLINE).isVisible
+        this.getLayerByName(LAYERS.BOTTLENECKISOLINE).data.getVisible()
       ) {
         // transforming into an HTMLImageElement only to find out
         // the width x height of the legend image
@@ -793,7 +793,7 @@
       if (
         this.selectedBottleneck &&
         this.selectedSurvey &&
-        this.getLayerByName(LAYERS.BOTTLENECKISOLINE).isVisible
+        this.getLayerByName(LAYERS.BOTTLENECKISOLINE).data.getVisible()
       ) {
         let survey = this.selectedSurvey;
 
--- a/client/src/components/importoverview/AdditionalLog.vue	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/components/importoverview/AdditionalLog.vue	Mon Apr 08 09:18:58 2019 +0200
@@ -49,7 +49,6 @@
 
 <style lang="sass" scoped>
 .additionallog
-  max-height: 70vh
   overflow-y: auto
   &.split
     max-height: 35vh
--- a/client/src/components/importoverview/ApprovedGaugeMeasurementDetail.vue	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/components/importoverview/ApprovedGaugeMeasurementDetail.vue	Mon Apr 08 09:18:58 2019 +0200
@@ -7,74 +7,53 @@
     }"
   >
     <div v-for="(result, index) in details.summary" :key="index">
-      <div class="pl-2 d-flex">
-        <div @click="toggleDiff(index)" class="my-auto text-left">
-          <UISpinnerButton
-            :state="showDiff === index"
-            :icons="['angle-right', 'angle-down']"
-            classes="text-info"
-          />
+      <div class="px-2 d-flex justify-content-between">
+        <div class="d-flex">
+          <div @click="toggleDiff(index)" class="my-auto text-left">
+            <UISpinnerButton
+              :state="showDiff === index"
+              :icons="['angle-right', 'angle-down']"
+              classes="text-info"
+            />
+          </div>
+          <div>
+            {{ result["fk-gauge-id"] }}
+            <sup v-if="isNew(result)" class="text-success">
+              (<translate>New</translate>)
+            </sup>
+          </div>
         </div>
-        <span v-if="result.versions.length == 1" class="agmcode text-left"
-          ><div>
-            {{ result["fk-gauge-id"] }} <translate>( New )</translate>
-          </div></span
-        >
-        <span v-if="result.versions.length == 2" class="agmcode text-left"
-          ><div>{{ result["fk-gauge-id"] }}</div></span
+        <div>{{ result["measure-date"] | dateTime }}</div>
+      </div>
+      <div v-if="showDiff === index" class="compare-table">
+        <div class="row no-gutters px-4 text-left font-weight-bold">
+          <div :class="isNew(result) ? 'col-6' : 'col-4'">
+            <translate>Value</translate>
+          </div>
+          <div v-if="isOld(result)" class="col-4">
+            <translate>Old</translate>
+          </div>
+          <div :class="isNew(result) ? 'col-6' : 'col-4'">
+            <translate>New</translate>
+          </div>
+        </div>
+        <div
+          class="row no-gutters px-4 text-left"
+          v-for="(entry, index) in Object.keys(result.versions[0])"
+          :key="index"
+          v-if="isNew(result) || isDifferent(result, entry)"
         >
-        <span class="text-left"
-          ><div>{{ result["measure-date"] | dateTime }}</div></span
-        >
-      </div>
-      <div v-if="showDiff === index" class="pl-3 d-flex flex-row">
-        <div class="w-100">
-          <div class="d-flex flex-row pl-3 text-left w-95">
-            <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 :class="isNew(result) ? 'col-6' : 'col-4'">
+            {{ entry }}
+          </div>
+          <div :class="isNew(result) ? 'col-6' : 'col-4'">
+            {{ result.versions[0][entry] }}
           </div>
           <div
-            class="line d-flex flex-row pl-3 text-left w-95"
-            v-for="(entry, index) in Object.keys(result.versions[0])"
-            :key="index"
+            v-if="isOld(result) && isDifferent(result, entry)"
+            :class="isNew(result) ? 'col-6' : 'col-4'"
           >
-            <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>
+            {{ result.versions[1][entry] }}
           </div>
         </div>
       </div>
@@ -90,18 +69,21 @@
     border-top: dashed 1px #dee2e6
     &:first-child
       border-top: none
-
-.line:nth-child(odd)
-  background: #f8f8f8
-
-.agmcode
-  width: 35%
-
-.agmdetailskeys
-  width: 33%
-
-.agmdetailsvalues
-  width: 33%
+    .compare-table
+      position: relative
+      overflow: hidden
+      &::after
+        content: ''
+        position: absolute
+        top: 0
+        right: -5px
+        bottom: 0
+        left: -5px
+        box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.4)
+      > div
+        font-size: 0.7rem
+        &:nth-child(odd)
+          background-color: #f8f9fa
 
 .split
   max-height: 35vh
@@ -127,8 +109,6 @@
 import { mapState } from "vuex";
 
 export default {
-  name: "agmdetails",
-  props: ["entry"],
   data() {
     return {
       showDiff: 0 // open first item by default
@@ -144,6 +124,18 @@
       } else {
         this.showDiff = false;
       }
+    },
+    isNew(result) {
+      return result && result.versions && result.versions.length === 1;
+    },
+    isOld(result) {
+      return !this.isNew(result);
+    },
+    isDifferent(result, entry) {
+      return (
+        this.isOld(result) &&
+        result.versions[0][entry] != result.versions[1][entry]
+      );
     }
   }
 };
--- a/client/src/components/importoverview/BottleneckDetail.vue	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/components/importoverview/BottleneckDetail.vue	Mon Apr 08 09:18:58 2019 +0200
@@ -6,12 +6,8 @@
       split: showLogs
     }"
   >
-    <div
-      v-for="(bottleneck, index) in bottlenecks"
-      :key="index"
-      class="d-flex flex-column w-100"
-    >
-      <div class="d-flex flex-row pl-2">
+    <div v-for="(bottleneck, index) in bottlenecks" :key="index">
+      <div class="d-flex pl-2">
         <div
           @click="showBottleneckDetails(index)"
           class="mt-auto mb-auto text-info text-left"
@@ -27,15 +23,14 @@
         </a>
       </div>
 
-      <div class="ml-3 d-flex flex-row" v-if="showBottleneckDetail === index">
-        <table>
+      <div class="d-flex properties" v-if="showBottleneckDetail === index">
+        <table class="w-100">
           <tr
             v-for="(info, index) in Object.keys(bottleneck.properties)"
             :key="index"
-            class="mr-1 condensed  text-muted"
           >
-            <td class="text-left">{{ info }}</td>
-            <td class="pl-3 text-left">
+            <td class="pl-4">{{ info }}</td>
+            <td>
               {{ bottleneck.properties[info] }}
             </td>
           </tr>
@@ -46,13 +41,6 @@
 </template>
 
 <style lang="sass" scoped>
-
-table
-  width: 100%
-
-tr:nth-child(even)
-  background: #f8f8f8
-
 .bottleneckdetails
   width: 100%
   overflow-y: auto
@@ -60,12 +48,24 @@
     border-top: dashed 1px #dee2e6
     &:first-child
       border-top: none
+    .properties
+      position: relative
+      overflow: hidden
+      &::after
+        content: ''
+        position: absolute
+        top: 0
+        right: -5px
+        bottom: 0
+        left: -5px
+        box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.4)
+      tr
+        font-size: 0.7rem
+        &:nth-child(odd)
+          background-color: #f8f9fa
 
 .split
   max-height: 35vh
-
-.full
-  max-height: 70vh
 </style>
 
 <script>
@@ -91,8 +91,6 @@
 import { mapState } from "vuex";
 
 export default {
-  name: "bottleneckdetails",
-  props: ["entry"],
   data() {
     return {
       bottlenecks: [],
--- a/client/src/components/importoverview/ImportOverview.vue	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/components/importoverview/ImportOverview.vue	Mon Apr 08 09:18:58 2019 +0200
@@ -30,9 +30,13 @@
           <translate>Earlier</translate>
         </button>
         <div class="d-flex align-items-center small">
-          {{ interval[0] | dateTime }}
-          <span class="mx-2">&ndash;</span>
-          {{ interval[1] | dateTime }}
+          {{ interval[0] | dateTime(selectedInterval !== $options.LAST_HOUR) }}
+          <template v-if="selectedInterval !== $options.TODAY">
+            <span class="mx-2">&ndash;</span>
+            {{
+              interval[1] | dateTime(selectedInterval !== $options.LAST_HOUR)
+            }}
+          </template>
           <select
             style="width: 75px; height: 24px"
             class="form-control form-control-sm small ml-2"
@@ -187,32 +191,7 @@
       if (id) this.showSingleRessource(id);
     },
     selectedInterval() {
-      const now = new Date();
-      switch (this.selectedInterval) {
-        case this.$options.LAST_HOUR:
-          this.$store.commit("imports/setStartDate", startOfHour(now));
-          this.$store.commit("imports/setEndDate", now);
-          break;
-        case this.$options.TODAY:
-          this.$store.commit("imports/setStartDate", startOfDay(now));
-          this.$store.commit("imports/setEndDate", now);
-          break;
-        case this.$options.LAST_7_DAYS:
-          this.$store.commit(
-            "imports/setStartDate",
-            subDays(startOfDay(now), 7)
-          );
-          this.$store.commit("imports/setEndDate", now);
-          break;
-        case this.$options.LAST_30_DAYS:
-          this.$store.commit(
-            "imports/setStartDate",
-            subDays(startOfDay(now), 30)
-          );
-          this.$store.commit("imports/setEndDate", now);
-          break;
-      }
-      this.loadLogs();
+      this.loadUpdatedLogs();
     },
     imports() {
       if (this.imports.length == 0) {
@@ -308,9 +287,31 @@
       return this.imports;
     },
     loadUpdatedLogs() {
-      const [start, end] = this.determineInterval(new Date());
-      this.$store.commit("imports/setStartDate", start);
-      this.$store.commit("imports/setEndDate", end);
+      const now = new Date();
+      switch (this.selectedInterval) {
+        case this.$options.LAST_HOUR:
+          this.$store.commit("imports/setStartDate", startOfHour(now));
+          this.$store.commit("imports/setEndDate", now);
+          break;
+        case this.$options.TODAY:
+          this.$store.commit("imports/setStartDate", startOfDay(now));
+          this.$store.commit("imports/setEndDate", now);
+          break;
+        case this.$options.LAST_7_DAYS:
+          this.$store.commit(
+            "imports/setStartDate",
+            subDays(startOfDay(now), 7)
+          );
+          this.$store.commit("imports/setEndDate", now);
+          break;
+        case this.$options.LAST_30_DAYS:
+          this.$store.commit(
+            "imports/setStartDate",
+            subDays(startOfDay(now), 30)
+          );
+          this.$store.commit("imports/setEndDate", now);
+          break;
+      }
       this.loadLogs();
     },
     loadLogs() {
--- a/client/src/components/importoverview/LogDetail.vue	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/components/importoverview/LogDetail.vue	Mon Apr 08 09:18:58 2019 +0200
@@ -34,8 +34,8 @@
       v-if="entry.id === showAdditional && isPending && (isFD || isAGM || isBN)"
       class="d-flex border-bottom"
     >
-      <FairwayDimensionDetail :entry="entry" v-if="isFD" />
-      <ApprovedGaugeMeasurementDetail :entry="entry" v-if="isAGM" />
+      <FairwayDimensionDetail v-if="isFD" />
+      <ApprovedGaugeMeasurementDetail v-if="isAGM" />
       <BottleneckDetail :entry="entry" v-if="isBN" />
     </div>
     <div class="d-flex" style="padding-left: 3px;">
--- a/client/src/components/layers/Layers.vue	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/components/layers/Layers.vue	Mon Apr 08 09:18:58 2019 +0200
@@ -13,12 +13,10 @@
       />
       <div class="box-body small">
         <Layerselect
-          v-for="(layer, index) in layersForLegend"
-          :layerindex="index"
-          :layername="layer.name"
-          :key="layer.name"
-          :isVisible="layer.isVisible"
-          @visibilityToggled="visibilityToggled"
+          v-for="(layer, name) in layersForLegend"
+          :layer="layer"
+          :name="name"
+          :key="name"
         ></Layerselect>
       </div>
     </div>
@@ -55,15 +53,14 @@
       return this.$gettext("Layers");
     },
     layersForLegend() {
-      return this.$options.LAYOUT.map(el => this.layers[el]);
+      let orderedLayers = {};
+      this.$options.LAYOUT.forEach(el => (orderedLayers[el] = this.layers[el]));
+      return orderedLayers;
     }
   },
   methods: {
     close() {
       this.$store.commit("application/showLayers", false);
-    },
-    visibilityToggled(layername) {
-      this.$store.commit("map/toggleVisibilityByName", layername);
     }
   },
   LAYOUT: [
--- a/client/src/components/layers/Layerselect.vue	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/components/layers/Layerselect.vue	Mon Apr 08 09:18:58 2019 +0200
@@ -1,41 +1,30 @@
 <template>
   <div>
-    <div class="form-check d-flex flex-row flex-start selection">
+    <div class="form-check d-flex flex-start">
       <input
         class="form-check-input"
         @change="visibilityToggled"
-        :id="layername"
+        :id="name"
         type="checkbox"
-        :checked="isVisible"
+        :checked="layer.data.getVisible()"
       />
-      <LegendElement
-        :layername="layername"
-        :layerindex="layerindex"
-      ></LegendElement>
+      <LegendElement :name="name"></LegendElement>
       <label
-        class="pointer layername form-check-label"
+        class="pointer layername form-check-label ml-2"
         @click="visibilityToggled"
-        >{{ layername }}</label
       >
+        {{ name }}
+      </label>
     </div>
-    <div v-if="isVisible && isBottleneckIsolineLayer">
+    <div v-if="layer.data.getVisible() && isBottleneckIsolineLayer">
       <img class="rounded my-1 d-block" :src="isolinesLegendImgDataURL" />
     </div>
-    <div v-if="isVisible && isBottleneckDifferences">
+    <div v-if="layer.data.getVisible() && isBottleneckDifferences">
       <img class="rounded my-1 d-block" :src="differencesLegendImgDataURL" />
     </div>
   </div>
 </template>
 
-<style lang="scss" scoped>
-.selection {
-  text-align: left;
-}
-.layername {
-  margin-left: $small-offset;
-}
-</style>
-
 <script>
 /* This is Free Software under GNU Affero General Public License v >= 3.0
  * without warranty, see README.md and license for details.
@@ -56,7 +45,7 @@
 import { LAYERS } from "@/store/map.js";
 
 export default {
-  props: ["layername", "layerindex", "isVisible"],
+  props: ["layer", "name"],
   name: "layerselect",
   components: {
     LegendElement: () => import("./LegendElement.vue")
@@ -67,15 +56,15 @@
       "differencesLegendImgDataURL"
     ]),
     isBottleneckIsolineLayer() {
-      return this.layername == LAYERS.BOTTLENECKISOLINE;
+      return this.name == LAYERS.BOTTLENECKISOLINE;
     },
     isBottleneckDifferences() {
-      return this.layername == LAYERS.DIFFERENCES;
+      return this.name == LAYERS.DIFFERENCES;
     }
   },
   methods: {
     visibilityToggled() {
-      this.$emit("visibilityToggled", this.layername);
+      this.$store.commit("map/toggleVisibilityByName", this.name);
     }
   },
   created() {
--- a/client/src/components/layers/LegendElement.vue	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/components/layers/LegendElement.vue	Mon Apr 08 09:18:58 2019 +0200
@@ -27,7 +27,7 @@
 
 export default {
   name: "legendelement",
-  props: ["layername", "layerindex"],
+  props: ["name"],
   data: function() {
     return {
       myMap: null,
@@ -37,7 +37,7 @@
   computed: {
     ...mapGetters("map", ["getLayerByName"]),
     id() {
-      return "legendelement" + this.layerindex;
+      return "legendelement-" + this.name;
     },
     mstyle() {
       if (this.mapLayer && this.mapLayer.data.getStyle) {
@@ -57,7 +57,7 @@
     }
   },
   mounted() {
-    this.mapLayer = this.getLayerByName(this.layername);
+    this.mapLayer = this.getLayerByName(this.name);
     if (this.mapLayer.data.getType() == "VECTOR") {
       this.initMap();
     } else {
--- a/client/src/components/toolbar/Profiles.vue	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/components/toolbar/Profiles.vue	Mon Apr 08 09:18:58 2019 +0200
@@ -32,7 +32,7 @@
   computed: {
     ...mapState("application", ["showProfiles"]),
     label() {
-      return this.$gettext("Profiles");
+      return this.$gettext("Fairway Profiles");
     }
   }
 };
--- a/client/src/lib/errors.js	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/lib/errors.js	Mon Apr 08 09:18:58 2019 +0200
@@ -12,7 +12,7 @@
  * Thomas Junk <thomas.junk@intevation.de>
  */
 
-import app from "../main";
+import app from "@/main";
 
 let displayOptions = {
   timeout: 2500,
--- a/client/src/lib/filters.js	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/lib/filters.js	Mon Apr 08 09:18:58 2019 +0200
@@ -25,20 +25,22 @@
         })
       : "";
   },
-  dateTime(date) {
+  dateTime(date, hideTime) {
     if (!date) return "";
     const d = new Date(date);
-    return (
-      d.toLocaleDateString(locale2, {
-        day: "2-digit",
-        month: "2-digit",
-        year: "numeric"
-      }) +
-      " - " +
-      d.toLocaleTimeString(locale2, {
-        hour12: false
-      })
-    );
+    let dateString = d.toLocaleDateString(locale2, {
+      day: "2-digit",
+      month: "2-digit",
+      year: "numeric"
+    });
+    if (!hideTime) {
+      dateString +=
+        " - " +
+        d.toLocaleTimeString(locale2, {
+          hour12: false
+        });
+    }
+    return dateString;
   },
   sortTable(data, sortColumn, sortDirection, page, pageSize) {
     // clone the array and leave the original intact
--- a/client/src/store/fairway.js	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/store/fairway.js	Mon Apr 08 09:18:58 2019 +0200
@@ -13,13 +13,13 @@
  * Markus Kottländer <markuks.kottlaender@intevation.de>
  */
 import Vue from "vue";
-import { HTTP } from "../lib/http";
-import { prepareProfile } from "../lib/geo";
+import { HTTP } from "@/lib/http";
+import { prepareProfile } from "@/lib/geo";
 import LineString from "ol/geom/LineString.js";
-import { generateFeatureRequest } from "../lib/geo.js";
+import { generateFeatureRequest } from "@/lib/geo.js";
 import { getLength } from "ol/sphere.js";
-import { displayError } from "../lib/errors.js";
-import { featureToFairwayCoordinates } from "../lib/geo.js";
+import { displayError } from "@/lib/errors.js";
+import { featureToFairwayCoordinates } from "@/lib/geo.js";
 import { LAYERS } from "@/store/map.js";
 
 // initial state
--- a/client/src/store/map.js	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/store/map.js	Mon Apr 08 09:18:58 2019 +0200
@@ -24,14 +24,14 @@
 import VectorSource from "ol/source/Vector.js";
 import Point from "ol/geom/Point.js";
 import { bbox as bboxStrategy } from "ol/loadingstrategy";
-import { HTTP } from "../lib/http";
+import { HTTP } from "@/lib/http";
 import { fromLonLat } from "ol/proj";
 import { getLength, getArea } from "ol/sphere.js";
 import { unByKey } from "ol/Observable";
 import { getCenter } from "ol/extent";
 import { transformExtent } from "ol/proj.js";
 import bbox from "@turf/bbox";
-import app from "../main";
+import app from "@/main";
 
 const LAYERS = {
   OPENSTREETMAP: "Open Streetmap",
@@ -83,27 +83,25 @@
     differencesLegendImgDataURL: "",
     layers: {
       [LAYERS.OPENSTREETMAP]: {
-        name: LAYERS.OPENSTREETMAP,
         data: new TileLayer({
+          visible: true,
           source: new OSM()
-        }),
-        isVisible: true
+        })
       },
       [LAYERS.INLANDECDIS]: {
-        name: LAYERS.INLANDECDIS,
         data: new TileLayer({
+          visible: true,
           source: new TileWMS({
             preload: 1,
             url: "https://service.d4d-portal.info/wms/",
             crossOrigin: "anonymous",
             params: { LAYERS: "d4d", VERSION: "1.1.1", TILED: true }
           })
-        }),
-        isVisible: true
+        })
       },
       [LAYERS.WATERWAYAREA]: {
-        name: LAYERS.WATERWAYAREA,
         data: new VectorLayer({
+          visible: true,
           source: new VectorSource({
             strategy: bboxStrategy
           }),
@@ -113,12 +111,11 @@
               width: 2
             })
           })
-        }),
-        isVisible: true
+        })
       },
       [LAYERS.STRETCHES]: {
-        name: LAYERS.STRETCHES,
         data: new VectorLayer({
+          visible: false,
           source: new VectorSource({
             strategy: bboxStrategy
           }),
@@ -146,12 +143,11 @@
 
             return style;
           }
-        }),
-        isVisible: false
+        })
       },
       [LAYERS.FAIRWAYDIMENSIONSLOS3]: {
-        name: LAYERS.FAIRWAYDIMENSIONSLOS3,
         data: new VectorLayer({
+          visible: true,
           source: new VectorSource(),
           style: function() {
             return [
@@ -177,12 +173,11 @@
               })
             ];
           }
-        }),
-        isVisible: true
+        })
       },
       [LAYERS.FAIRWAYDIMENSIONSLOS2]: {
-        name: LAYERS.FAIRWAYDIMENSIONSLOS2,
         data: new VectorLayer({
+          visible: false,
           source: new VectorSource(),
           style: function() {
             return [
@@ -210,12 +205,11 @@
               })
             ];
           }
-        }),
-        isVisible: false
+        })
       },
       [LAYERS.FAIRWAYDIMENSIONSLOS1]: {
-        name: LAYERS.FAIRWAYDIMENSIONSLOS1,
         data: new VectorLayer({
+          visible: false,
           source: new VectorSource(),
           style: function() {
             return [
@@ -243,12 +237,11 @@
               })
             ];
           }
-        }),
-        isVisible: false
+        })
       },
       [LAYERS.WATERWAYAXIS]: {
-        name: LAYERS.WATERWAYAXIS,
         data: new VectorLayer({
+          visible: true,
           source: new VectorSource({
             strategy: bboxStrategy
           }),
@@ -263,12 +256,11 @@
           // resolution.
           maxResolution: 5,
           minResolution: 0
-        }),
-        isVisible: true
+        })
       },
       [LAYERS.WATERWAYPROFILES]: {
-        name: LAYERS.WATERWAYPROFILES,
         data: new VectorLayer({
+          visible: true,
           source: new VectorSource({
             strategy: bboxStrategy
           }),
@@ -281,12 +273,11 @@
           }),
           maxResolution: 2.5,
           minResolution: 0
-        }),
-        isVisible: true
+        })
       },
       [LAYERS.BOTTLENECKS]: {
-        name: LAYERS.BOTTLENECKS,
         data: new VectorLayer({
+          visible: true,
           source: new VectorSource({
             strategy: bboxStrategy
           }),
@@ -301,12 +292,11 @@
               })
             });
           }
-        }),
-        isVisible: true
+        })
       },
       [LAYERS.BOTTLENECKISOLINE]: {
-        name: LAYERS.BOTTLENECKISOLINE,
         data: new TileLayer({
+          visible: false,
           source: new TileWMS({
             preload: 0,
             projection: "EPSG:3857",
@@ -328,12 +318,11 @@
               });
             } // TODO  tile.setState(TileState.ERROR);
           })
-        }),
-        isVisible: false
+        })
       },
       [LAYERS.DIFFERENCES]: {
-        name: LAYERS.DIFFERENCES,
         data: new TileLayer({
+          visible: false,
           source: new TileWMS({
             preload: 0,
             projection: "EPSG:4326",
@@ -355,13 +344,12 @@
               });
             } // TODO  tile.setState(TileState.ERROR);
           })
-        }),
-        isVisible: false
+        })
       },
       [LAYERS.BOTTLENECKSTATUS]: {
-        name: LAYERS.BOTTLENECKSTATUS,
         forLegendStyle: { point: true, resolution: 16 },
         data: new VectorLayer({
+          visible: true,
           source: new VectorSource({
             strategy: bboxStrategy
           }),
@@ -373,7 +361,7 @@
                 new Style({
                   geometry: new Point(bnCenter),
                   image: new Icon({
-                    src: require("../assets/marker-bottleneck-critical.png"),
+                    src: require("@/assets/marker-bottleneck-critical.png"),
                     anchor: [0.5, 0.5],
                     scale: isLegend ? 0.5 : 1
                   })
@@ -392,23 +380,21 @@
             }
             return styles;
           }
-        }),
-        isVisible: true
+        })
       },
       [LAYERS.DISTANCEMARKS]: {
-        name: LAYERS.DISTANCEMARKS,
         forLegendStyle: { point: true, resolution: 8 },
         data: new VectorLayer({
+          visible: false,
           source: new VectorSource({
             strategy: bboxStrategy
           })
-        }),
-        isVisible: false
+        })
       },
       [LAYERS.DISTANCEMARKSAXIS]: {
-        name: LAYERS.DISTANCEMARKSAXIS,
         forLegendStyle: { point: true, resolution: 8 },
         data: new VectorLayer({
+          visible: true,
           source: new VectorSource({
             strategy: bboxStrategy
           }),
@@ -438,13 +424,12 @@
               return [];
             }
           }
-        }),
-        isVisible: true
+        })
       },
       [LAYERS.GAUGES]: {
-        name: LAYERS.GAUGES,
         forLegendStyle: { point: true, resolution: 8 },
         data: new VectorLayer({
+          visible: true,
           source: new VectorSource({
             strategy: bboxStrategy
           }),
@@ -452,7 +437,7 @@
             return [
               new Style({
                 image: new Icon({
-                  src: require("../assets/marker-gauge.png"),
+                  src: require("@/assets/marker-gauge.png"),
                   anchor: [0.5, isLegend ? 0.5 : 1],
                   scale: isLegend ? 0.5 : 1
                 }),
@@ -480,12 +465,11 @@
           },
           maxResolution: 100,
           minResolution: 0
-        }),
-        isVisible: true
+        })
       },
       [LAYERS.DRAWTOOL]: {
-        name: LAYERS.DRAWTOOL,
         data: new VectorLayer({
+          visible: true,
           source: new VectorSource({ wrapX: false }),
           style: function(feature) {
             // adapted from OpenLayer's LineString Arrow Example
@@ -511,7 +495,7 @@
                     geometry: new Point(end),
                     image: new Icon({
                       // we need to make sure the image is loaded by Vue Loader
-                      src: require("../assets/linestring_arrow.png"),
+                      src: require("@/assets/linestring_arrow.png"),
                       // fiddling with the anchor's y value does not help to
                       // position the image more centered on the line ending, as the
                       // default line style seems to be slightly uncentered in the
@@ -527,12 +511,11 @@
             }
             return styles;
           }
-        }),
-        isVisible: true
+        })
       },
       [LAYERS.CUTTOOL]: {
-        name: LAYERS.CUTTOOL,
         data: new VectorLayer({
+          visible: true,
           source: new VectorSource({ wrapX: false }),
           style: function(feature) {
             // adapted from OpenLayer's LineString Arrow Example
@@ -559,7 +542,7 @@
                     geometry: new Point(end),
                     image: new Icon({
                       // we need to make sure the image is loaded by Vue Loader
-                      src: require("../assets/linestring_arrow_grey.png"),
+                      src: require("@/assets/linestring_arrow_grey.png"),
                       // fiddling with the anchor's y value does not help to
                       // position the image more centered on the line ending, as the
                       // default line style seems to be slightly uncentered in the
@@ -575,8 +558,7 @@
             }
             return styles;
           }
-        }),
-        isVisible: true
+        })
       }
     }
   };
@@ -605,16 +587,13 @@
       state.extent = extent;
     },
     setLayerVisible: (state, name) => {
-      state.layers[name].isVisible = true;
       state.layers[name].data.setVisible(true);
     },
     setLayerInvisible: (state, name) => {
-      state.layers[name].isVisible = false;
       state.layers[name].data.setVisible(false);
     },
     toggleVisibilityByName: (state, name) => {
-      state.layers[name].isVisible = !state.layers[name].isVisible;
-      state.layers[name].data.setVisible(state.layers[name].isVisible);
+      state.layers[name].data.setVisible(!state.layers[name].data.getVisible());
     },
     openLayersMap: (state, map) => {
       state.openLayersMap = map;
--- a/client/src/store/user.js	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/store/user.js	Mon Apr 08 09:18:58 2019 +0200
@@ -13,8 +13,8 @@
  * Markus Kottländer <markus@intevation.de>
  */
 
-import { HTTP } from "../lib/http";
-import { toMillisFromString } from "../lib/session";
+import { HTTP } from "@/lib/http";
+import { toMillisFromString } from "@/lib/session";
 
 const init = () => {
   return {
--- a/client/src/store/usermanagement.js	Fri Apr 05 10:04:45 2019 +0200
+++ b/client/src/store/usermanagement.js	Mon Apr 08 09:18:58 2019 +0200
@@ -13,7 +13,7 @@
  * Markus Kottländer <markus@intevation.de>
  */
 
-import { HTTP } from "../lib/http";
+import { HTTP } from "@/lib/http";
 
 // initial state
 const init = () => {
--- a/pkg/controllers/importqueue.go	Fri Apr 05 10:04:45 2019 +0200
+++ b/pkg/controllers/importqueue.go	Mon Apr 08 09:18:58 2019 +0200
@@ -579,12 +579,18 @@
 	err = tx.QueryRowContext(ctx, isPendingSQL, id).Scan(&pending, &kind)
 	switch {
 	case err == sql.ErrNoRows:
-		err = fmt.Errorf("cannot find import #%d", id)
+		err = JSONError{
+			Code:    http.StatusNotFound,
+			Message: fmt.Sprintf("cannot find import #%d", id),
+		}
 		return
 	case err != nil:
 		return
 	case !pending:
-		err = fmt.Errorf("import %d is not pending", id)
+		err = JSONError{
+			Code:    http.StatusConflict,
+			Message: fmt.Sprintf("import #%d is not pending", id),
+		}
 		return
 	}