view client/src/components/importoverview/ImportOverview.vue @ 4488:bff6c5c1db4f

client: pdf-gen: improve adding bottleneck info to pdf * Check if the bottleneck is in the current view to add its info to the exported pdf and the pdf filename, this avoid wrong filename and wrong info in pdf in case view has been changed to another location. * Set the bottleneck to print after moving to it in map.
author Fadi Abbud <fadi.abbud@intevation.de>
date Fri, 27 Sep 2019 11:15:02 +0200
parents ec8c9793dbb7
children e3d436052b7c
line wrap: on
line source

<template>
  <div class="overview">
    <UIBoxHeader
      icon="clipboard-check"
      :title="importReviewLabel"
      :closeCallback="$parent.close"
      :actions="[{ callback: loadUpdatedLogs, icon: 'sync' }]"
    />
    <div class="position-relative">
      <UISpinnerOverlay v-if="loading" />
      <div class="border-bottom p-2 d-flex justify-content-between">
        <Filters></Filters>
        <button
          class="btn btn-xs btn-info"
          :disabled="!reviewed.length"
          @click="save"
        >
          <translate>Commit</translate> {{ reviewed.length }}
        </button>
      </div>
      <div
        class="p-2 d-flex align-items-center justify-content-between border-bottom"
      >
        <button
          :disabled="!this.prev"
          @click="earlier"
          class="btn btn-xs btn-outline-secondary"
        >
          <font-awesome-icon icon="angle-left" fixed-width />
          <translate>Earlier</translate>
        </button>
        <div class="d-flex align-items-center small">
          {{ 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"
            v-model="selectedInterval"
          >
            <option :value="$options.LAST_HOUR">
              <translate>Hour</translate>
            </option>
            <option :value="$options.TODAY"><translate>Day</translate></option>
            <option :value="$options.LAST_7_DAYS">
              <translate>7 days</translate>
            </option>
            <option :value="$options.LAST_30_DAYS">
              <translate>30 Days</translate>
            </option>
          </select>
        </div>
        <div class="btn-group">
          <button
            :disabled="!this.next"
            @click="later"
            class="btn btn-xs btn-outline-secondary"
          >
            <translate>Later</translate>
            <font-awesome-icon icon="angle-right" fixed-width />
          </button>
          <button
            :disabled="!this.next"
            @click="now"
            class="btn btn-xs btn-outline-secondary"
          >
            <font-awesome-icon icon="angle-double-right" fixed-width />
          </button>
        </div>
      </div>
      <UITableHeader
        :columns="[
          { id: 'id', title: `${idLabel}`, width: '75px' },
          { id: 'kind', title: `${kindLabel}`, width: '53px' },
          { id: 'enqueued', title: `${enqueuedLabel}`, width: '138px' },
          { id: 'user', title: `${ownerLabel}`, width: '80px' },
          { id: 'country', title: `${countryLabel}`, width: '55px' },
          { id: 'signer', title: `${signerLabel}`, width: '80px' },
          { id: 'state', title: `${statusLabel}`, width: '72px' },
          { id: 'warnings', icon: 'exclamation-triangle', width: '44px' }
        ]"
      />
      <!--
      For server-side sorting, etc simply don't use the sortTable filter.
      Instead you could just pass a function that loads the imports, like:
      :data="loadImports(sortColumn, sortDirection)"
     -->
      <UITableBody
        :data="filteredImports() | sortTable(sortColumn, sortDirection)"
        :isActive="item => item.id === this.show"
        maxHeight="70vh"
      >
        <template v-slot:row="{ item: entry }">
          <LogEntry :entry="entry"></LogEntry>
        </template>
        <template v-slot:expand="{ item: entry }">
          <LogDetail :entry="entry"></LogDetail>
        </template>
      </UITableBody>
    </div>
  </div>
</template>

<style lang="sass" scoped>
.spinner-overlay
  top: 110px
</style>

<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>
 * Markus Kottländer <markus.kottlaender@intevation.de>
 */

import { mapState, mapGetters } from "vuex";
import { displayError, displayInfo } from "@/lib/errors";
import { STATES } from "@/store/imports";
import { sortTable } from "@/lib/mixins";
import { HTTP } from "@/lib/http";
import {
  startOfDay,
  startOfHour,
  endOfHour,
  endOfDay,
  addDays,
  subDays,
  format
} from "date-fns";

export default {
  components: {
    Filters: () => import("./Filters"),
    LogEntry: () => import("./LogEntry"),
    LogDetail: () => import("./LogDetail")
  },
  mixins: [sortTable],
  LAST_HOUR: "lasthour",
  TODAY: "today",
  LAST_7_DAYS: "lastsevendays",
  LAST_30_DAYS: "lastthirtydays",
  data() {
    return {
      loading: false,
      selectedInterval: this.$options.LAST_HOUR
    };
  },
  computed: {
    ...mapState("application", ["searchQuery"]),
    ...mapState("imports", [
      "show",
      "imports",
      "reviewed",
      "startDate",
      "endDate",
      "prev",
      "next"
    ]),
    ...mapGetters("usermanagement", ["userCountries"]),
    ...mapGetters("imports", ["filters"]),
    countryLabel() {
      return this.$gettext("Country");
    },
    importReviewLabel() {
      return this.$gettext("Import review");
    },
    idLabel() {
      return this.$gettext("Id");
    },
    kindLabel() {
      return this.$gettext("Kind");
    },
    enqueuedLabel() {
      return this.$gettext("Enqueued");
    },
    ownerLabel() {
      return this.$gettext("Owner");
    },
    signerLabel() {
      return this.$gettext("Signer");
    },
    statusLabel() {
      return this.$gettext("Status");
    },
    interval() {
      return [this.startDate, this.endDate];
    }
  },
  watch: {
    $route() {
      const { id } = this.$route.params;
      if (id) this.showSingleRessource(id);
    },
    selectedInterval() {
      this.loadUpdatedLogs();
    },
    imports() {
      if (this.imports.length == 0) {
        if (this.next) {
          const [start, end] = this.determineInterval(this.next);
          this.$store.commit("imports/setStartDate", start);
          this.$store.commit("imports/setEndDate", end);
          this.loadLogs();
        } else if (this.prev) {
          const [start, end] = this.determineInterval(this.prev);
          this.$store.commit("imports/setStartDate", start);
          this.$store.commit("imports/setEndDate", end);
          this.loadLogs();
        }
      }
    },
    filters() {
      this.loadLogs();
    }
  },
  methods: {
    showSingleRessource(id) {
      id = id * 1;
      this.loadDetails(id)
        .then(response => {
          this.$store.commit("imports/setCurrentDetails", response.data);
          const { enqueued } = response.data;
          this.$store.commit("imports/setStartDate", startOfHour(enqueued));
          this.$store.commit("imports/setEndDate", endOfHour(enqueued));
          this.$store.commit("imports/showDetailsFor", id);
          this.loadLogs();
        })
        .catch(error => {
          this.loading = false;
          this.$store.commit("imports/setCurrentDetails", {});
          const { status, data } = error.response;
          displayError({
            title: this.$gettext("Backend Error"),
            message: `${status}: ${data.message || data}`
          });
        });
    },
    loadDetails(id) {
      return new Promise((resolve, reject) => {
        HTTP.get("/imports/" + id, {
          headers: { "X-Gemma-Auth": localStorage.getItem("token") }
        })
          .then(response => {
            resolve(response);
          })
          .catch(error => {
            reject(error);
          });
      });
    },
    determineInterval(pointInTime) {
      let start, end;
      switch (this.selectedInterval) {
        case this.$options.LAST_HOUR:
          start = startOfHour(pointInTime);
          end = endOfHour(pointInTime);
          break;
        case this.$options.TODAY:
          start = startOfDay(pointInTime);
          end = endOfDay(pointInTime);
          break;
        case this.$options.LAST_7_DAYS:
          start = startOfDay(pointInTime);
          end = endOfDay(addDays(start, 7));
          break;
        case this.$options.LAST_30_DAYS:
          start = startOfDay(pointInTime);
          end = endOfDay(addDays(start, 30));
          break;
      }
      return [start, end];
    },
    earlier() {
      if (!this.prev) return;
      const [start, end] = this.determineInterval(this.prev);
      this.$store.commit("imports/setStartDate", start);
      this.$store.commit("imports/setEndDate", end);
      this.loadLogs();
    },
    later() {
      if (!this.next) return;
      const [start, end] = this.determineInterval(this.next);
      this.$store.commit("imports/setStartDate", start);
      this.$store.commit("imports/setEndDate", end);
      this.loadLogs();
    },
    now() {
      if (!this.next) return;
      const [start, end] = this.determineInterval(new Date());
      this.$store.commit("imports/setStartDate", start);
      this.$store.commit("imports/setEndDate", end);
      this.loadLogs();
    },
    filteredImports() {
      return this.imports.map(x => {
        x["country"] = this.userCountries[x.user];
        return x;
      });
    },
    loadUpdatedLogs() {
      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() {
      this.loading = true;
      this.$store
        .dispatch("imports/getImports", {
          filter: this.filters,
          from: encodeURIComponent(
            format(this.startDate, "YYYY-MM-DDTHH:mm:ssZ")
          ),
          to: encodeURIComponent(format(this.endDate, "YYYY-MM-DDTHH:mm:ssZ")),
          query: this.searchQuery
        })
        .then(() => {
          if (this.show) {
            this.loadDetails(this.show)
              .then(response => {
                this.$store.commit("imports/setCurrentDetails", response.data);
                this.loading = false;
              })
              .catch(error => {
                this.loading = false;
                this.$store.commit("imports/setCurrentDetails", {});
                const { status, data } = error.response;
                displayError({
                  title: this.$gettext("Backend Error"),
                  message: `${status}: ${data.message || data}`
                });
              });
          } else {
            this.loading = false;
          }
        })
        .catch(error => {
          const { status, data } = error.response;
          this.loading = false;
          displayError({
            title: this.$gettext("Backend Error"),
            message: `${status}: ${data.message || data}`
          });
        });
    },
    save() {
      if (!this.reviewed.length) return;

      let popupContent = `<table class="table table-sm small mb-0 border-0" style="margin-top: -1px;">`;
      this.reviewed.forEach(r => {
        let imp = this.imports.find(i => i.id === r.id);
        let approved = STATES.APPROVED === r.status;
        popupContent += `<tr>
          <td>${imp.id}</td>
          <td>${imp.kind.toUpperCase()}</td>
          <td>${this.$options.filters.dateTime(imp.enqueued)}</td>
          <td class="text-${approved ? "success" : "danger"}">
            ${this.$gettext(approved ? "approved" : "declined")}
          </td>
        </tr>`;
      });
      popupContent += "</table>";

      this.$store.commit("application/popup", {
        icon: "clipboard-check",
        title: this.$gettext("Finish Review"),
        padding: false,
        big: true,
        content: popupContent,
        confirm: {
          icon: "check",
          callback: () => {
            let data = this.reviewed.map(r => ({
              id: r.id,
              state: r.status
            }));
            this.$store
              .dispatch("imports/confirmReview", data)
              .then(response => {
                this.loadLogs();
                this.$store.commit("imports/setReviewed", []);
                this.$store.commit("map/startRefreshLayers");
                this.$store.commit("gauges/deleteNashSutcliffeCache");
                this.$store.dispatch("map/refreshLayers");
                this.$store.dispatch("imports/loadStagingNotifications");
                this.$store.dispatch("imports/loadStretches");
                this.$store.dispatch("imports/loadSections");
                this.$store.commit("map/reviewActive", false);
                this.$nextTick(() => {
                  this.$store.commit("map/finishRefreshLayers");
                });
                const messages = response.data
                  .map(x => {
                    if (x.message) return x.message;
                    if (x.error) return x.error;
                  })
                  .join("\n\n");
                displayInfo({
                  title: "Staging Area",
                  message: messages,
                  options: {
                    timeout: 0,
                    buttons: [{ text: "Ok", action: null, bold: true }]
                  }
                });
              })
              .catch(error => {
                const { status, data } = error.response;
                displayError({
                  title: "Backend Error",
                  message: `${status}: ${data.message || data}`
                });
              });
          }
        },
        cancel: {
          label: this.$gettext("Cancel"),
          icon: "times"
        }
      });
    }
  },
  mounted() {
    this.loadUpdatedLogs();
    this.$store.dispatch("usermanagement/loadUsers").catch(error => {
      const { status, data } = error.response;
      displayError({
        title: this.$gettext("Backend Error"),
        message: `${status}: ${data.message || data}`
      });
    });
    const { id } = this.$route.params;
    if (id) {
      this.showSingleRessource(id);
    } else {
      this.$store.commit("application/searchQuery", "");
      this.loadLogs();
    }
  },
  activated() {
    this.loadUpdatedLogs();
  }
};
</script>