Mercurial > gemma
view client/src/components/staging/StagingDetail.vue @ 2333:eb82b273c615
staging: adapt to new routing concept. fix of problems with opening bottlenecks
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Tue, 19 Feb 2019 16:44:38 +0100 |
parents | 256f98e1d954 |
children | 0f3b0937e7c1 |
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())"> <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"> <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 = !showBottleneckDetails" class="small mt-auto mb-auto text-info text-left" > <font-awesome-icon class="pointer" v-if="showBottleneckDetails" icon="angle-up" fixed-width ></font-awesome-icon> <font-awesome-icon class="pointer" v-if="!showBottleneckDetails" icon="angle-down" fixed-width ></font-awesome-icon> </div> </div> <div class="d-flex flex-row" v-if="showBottleneckDetails"> <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>{{ formatSurveyDate(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 } 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; export default { name: "stagingdetail", props: ["data"], data() { return { showDiff: NO_DIFF, showAGMDetails: false, showBottleneckDetails: false, 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: { 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.data.kind.toUpperCase() !== "BN") 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"; }, isStretch(kind) { return kind === "ST"; }, isSoundingResult(kind) { return kind === "SR"; }, formatSurveyDate(date) { return formatSurveyDate(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> .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>