Mercurial > gemma
view client/src/components/importoverview/ImportOverview.vue @ 4877:e89c1140f0a8
import_review: display review buttons in same line in firefox
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Mon, 03 Feb 2020 14:00:26 +0100 |
parents | 6b054b91d9b2 |
children | db134712519b |
line wrap: on
line source
<template> <div class="overview"> <UIBoxHeader icon="clipboard-check" :title="importReviewLabel" :closeCallback="$parent.close" :actions="[ { callback: saveImportsView, icon: 'download' }, { 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">–</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: '70px' }, { id: 'kind', title: `${kindLabel}`, width: '50px' }, { 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: 'changed', title: `${changedLabel}`, width: '138px' }, { 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 app from "@/main"; import { saveAs } from "file-saver"; 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"); }, changedLabel() { return this.$gettext("Changed"); }, 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: { saveImportsView() { const content = ""; this.$store.commit("application/popup", { icon: "clipboard-check", title: this.$gettext("Export logs"), padding: false, big: true, content: content, confirm: { icon: "check", callback: dates => { const { from, to } = dates; displayInfo({ title: "Generating CSV", message: `${from} - ${to}`, options: { timeout: 0, buttons: [{ text: "Ok", action: null, bold: true }] } }); HTTP.get( `/imports/export?from=${encodeURIComponent( format(startOfDay(new Date(from)), "YYYY-MM-DDTHH:mm:ssZ") )}&to=${encodeURIComponent( format(endOfDay(new Date(to)), "YYYY-MM-DDTHH:mm:ssZ") )}&query=`, { headers: { "X-Gemma-Auth": localStorage.getItem("token") } } ) .then(response => { const imports = response.data; app.$snotify.clear(); if (!imports) return; const csvFile = new Blob([new TextEncoder().encode(imports)], { type: "text/csv" }); saveAs(csvFile, "log.csv"); }) .catch(error => { console.log(error); const { status, data } = error.response; app.$snotify.clear(); displayError({ title: this.$gettext("Backend Error"), message: `${status}: ${data.message || data}` }); }); } }, cancel: { label: this.$gettext("Cancel"), icon: "times" }, dateSelection: true }); }, 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 => { this.loading = false; let message = "Backend not reachable"; if (error.response) { const { status, data } = error.response; message = `${status}: ${data.message || data}`; } displayError({ title: this.$gettext("Backend Error"), message: message }); }); }, 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 approved = STATES.APPROVED === r.status; popupContent += `<tr> <td>${r.id}</td> <td>${r.kind.toUpperCase()}</td> <td>${this.$options.filters.dateTime(r.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 => { let message = "Backend not reachable"; if (error.response) { const { status, data } = error.response; message = `${status}: ${data.message || data}`; } displayError({ title: this.$gettext("Backend Error"), message: message }); }); } }, cancel: { label: this.$gettext("Cancel"), icon: "times" } }); } }, mounted() { this.loadUpdatedLogs(); this.$store.dispatch("usermanagement/loadUsers").catch(error => { let message = "Backend not reachable"; if (error.response) { const { status, data } = error.response; message = `${status}: ${data.message || data}`; } displayError({ title: this.$gettext("Backend Error"), message: message }); }); const { id } = this.$route.params; if (id) { this.showSingleRessource(id); } else { this.$store.commit("application/searchQuery", ""); this.loadLogs(); } }, activated() { this.loadUpdatedLogs(); } }; </script>