comparison client/src/components/importoverview/staging/StagingDetail.vue @ 2403:a4f36c481f4b staging_consolidation

wip
author Thomas Junk <thomas.junk@intevation.de>
date Wed, 27 Feb 2019 16:21:45 +0100
parents
children 40bd6bb7886b
comparison
equal deleted inserted replaced
2402:7600bb49e158 2403:a4f36c481f4b
1 <template>
2 <div :class="detail">
3 <div class="d-flex flex-row">
4 <div class="mt-auto d-flex flex-row mb-auto small name text-left">
5 <a
6 v-if="isSoundingResult(data.kind.toUpperCase())"
7 class="text-left"
8 @click="zoomTo()"
9 href="#"
10 >{{ data.summary.bottleneck }}</a
11 >
12 <span v-if="isBottleneck(data.kind.toUpperCase())" class="text-left"
13 ><translate>Bottlenecks</translate> ({{
14 data.summary.bottlenecks.length
15 }})</span
16 >
17 <a
18 v-if="isApprovedGaugeMeasurement(data.kind.toUpperCase())"
19 class="text-left"
20 ><translate>Approved Gauge Measurements</translate> ({{
21 data.summary.length
22 }})</a
23 >
24 <span
25 class="text-left"
26 v-if="isFairwayDimension(data.kind.toUpperCase())"
27 >{{ data.summary["source-organization"] }} (LOS:
28 {{ data.summary.los }})</span
29 >
30 <a
31 href="#"
32 class="text-left"
33 @click="zoomToStretch(data.summary.stretch)"
34 v-if="isStretch(data.kind.toUpperCase())"
35 >{{ data.summary.stretch }}</a
36 >
37 </div>
38 <div class="mt-auto mb-auto small text-left type">
39 {{ data.kind.toUpperCase() }}
40 </div>
41 <div v-if="data.summary" class="mt-auto mb-auto small text-left date">
42 {{ formatSurveyDate(data.summary.date) }}
43 </div>
44 <div v-else class="mt-auto mb-auto small text-left date">-</div>
45 <div class="mt-auto mb-auto small text-left imported">
46 {{ formatSurveyDate(data.enqueued.split("T")[0]) }}
47 </div>
48 <div class="mt-auto mb-auto small text-left username">
49 {{ data.user }}
50 </div>
51 <div class="controls d-flex flex-row justify-content-end">
52 <div>
53 <button
54 :class="{
55 'ml-3': true,
56 'mr-3': true,
57 btn: true,
58 'btn-sm': true,
59 'btn-outline-success': needsApproval(data) || isRejected(data),
60 'btn-success': isApproved(data)
61 }"
62 @click="toggleApproval(data.id, $options.STATES.APPROVED)"
63 >
64 <font-awesome-icon icon="check"></font-awesome-icon>
65 </button>
66 </div>
67 <div>
68 <button
69 :class="{
70 'mr-3': true,
71 btn: true,
72 'btn-sm': true,
73 'btn-outline-danger': needsApproval(data) || isApproved(data),
74 'btn-danger': isRejected(data)
75 }"
76 @click="toggleApproval(data.id, $options.STATES.REJECTED)"
77 >
78 <font-awesome-icon icon="times" class="pointer"></font-awesome-icon>
79 </button>
80 </div>
81 <div
82 v-if="
83 !isBottleneck(data.kind.toUpperCase()) ||
84 isApprovedGaugeMeasurement(data.kind.toUpperCase())
85 "
86 class="expander"
87 ></div>
88 <div v-if="isBottleneck(data.kind.toUpperCase())">
89 <div class="mt-auto mb-auto text-info text-left">
90 <font-awesome-icon
91 class="pointer"
92 @click="showDetails()"
93 v-if="show"
94 icon="angle-up"
95 fixed-width
96 ></font-awesome-icon>
97 <font-awesome-icon
98 class="pointer"
99 @click="showDetails()"
100 v-if="loading"
101 icon="spinner"
102 fixed-width
103 ></font-awesome-icon>
104 <font-awesome-icon
105 @click="showDetails()"
106 class="pointer"
107 v-if="!show && !loading"
108 icon="angle-down"
109 fixed-width
110 ></font-awesome-icon>
111 </div>
112 </div>
113 <div v-if="isApprovedGaugeMeasurement(data.kind.toUpperCase())">
114 <div
115 @click="showAGMDetails = !showAGMDetails"
116 class="mt-auto mb-auto text-info text-left"
117 >
118 <font-awesome-icon
119 class="pointer"
120 v-if="showAGMDetails"
121 icon="angle-up"
122 fixed-width
123 ></font-awesome-icon>
124 <font-awesome-icon
125 class="pointer"
126 v-if="!showAGMDetails"
127 icon="angle-down"
128 fixed-width
129 ></font-awesome-icon>
130 </div>
131 </div>
132 <div v-else class="empty"></div>
133 </div>
134 </div>
135 <div v-if="show && bottlenecks.length > 0" class="bottlenecksdetails">
136 <div
137 v-for="(bottleneck, index) in bottlenecks"
138 :key="index"
139 class="d-flex flex-row"
140 >
141 <div class="d-flex flex-column">
142 <div class="d-flex flex-row">
143 <a @click="moveToBottleneck(index)" class="small" href="#">{{
144 bottleneck.properties.objnam
145 }}</a>
146 <div
147 @click="showBottleneckDetails(index)"
148 class="small mt-auto mb-auto text-info text-left"
149 >
150 <font-awesome-icon
151 class="pointer"
152 v-if="showBottleneckDetail === index"
153 icon="angle-up"
154 fixed-width
155 ></font-awesome-icon>
156 <font-awesome-icon
157 class="pointer"
158 v-if="!(showBottleneckDetail === index)"
159 icon="angle-down"
160 fixed-width
161 ></font-awesome-icon>
162 </div>
163 </div>
164
165 <div class="d-flex flex-row" v-if="showBottleneckDetail === index">
166 <table>
167 <tr
168 v-for="(info, index) in Object.keys(bottleneck.properties)"
169 :key="index"
170 class="mr-1 small text-muted"
171 >
172 <td class="condensed text-left">{{ info }}</td>
173 <td class="condensed pl-3 text-left">
174 {{ bottleneck.properties[info] }}
175 </td>
176 </tr>
177 </table>
178 </div>
179 </div>
180 </div>
181 </div>
182 <div v-if="showAGMDetails">
183 <div class="pl-3 d-flex flex-row">
184 <span class="condensed agmcode text-left"
185 ><small><translate>ISRS Code</translate></small></span
186 >
187 <span class="condensed agmdetail text-left"
188 ><small><translate>Date of measurement</translate></small></span
189 >
190 </div>
191 <div class="diffs">
192 <div v-for="(result, index) in data.summary" :key="index">
193 <div class="pl-3 d-flex flex-row">
194 <span
195 v-if="result.versions.length == 1"
196 class="condensed agmcode text-left"
197 ><small
198 >{{ result["fk-gauge-id"] }}
199 <translate>( New )</translate></small
200 ></span
201 >
202 <span
203 v-if="result.versions.length == 2"
204 class="condensed agmcode text-left"
205 ><small>{{ result["fk-gauge-id"] }}</small></span
206 >
207 <span class="condensed agmdetail text-left"
208 ><small>{{ formatDateTime(result["measure-date"]) }}</small></span
209 >
210 <div
211 @click="toggleDiff(index)"
212 class="small ml-auto mt-auto mb-auto text-info text-left"
213 >
214 <font-awesome-icon
215 class="pointer"
216 v-if="showDiff == index"
217 icon="angle-up"
218 fixed-width
219 ></font-awesome-icon>
220 <font-awesome-icon
221 class="pointer"
222 v-if="showDiff != index"
223 icon="angle-down"
224 fixed-width
225 ></font-awesome-icon>
226 </div>
227 </div>
228 <div v-if="showDiff == index" class="pl-3 d-flex flex-row">
229 <div>
230 <div class="d-flex flex-row condensed pl-3 text-left">
231 <div class="header border-bottom agmdetailskeys">
232 <small><translate>Value</translate></small>
233 </div>
234 <div
235 v-if="result.versions.length == 2"
236 class="header border-bottom agmdetailsvalues"
237 >
238 <small><translate>Old</translate></small>
239 </div>
240 <div class="header border-bottom agmdetailsvalues">
241 <small><translate>New</translate></small>
242 </div>
243 </div>
244 <div
245 class="d-flex flex-row condensed pl-3 text-left"
246 v-for="(entry, index) in Object.keys(result.versions[0])"
247 :key="index"
248 >
249 <div
250 v-if="
251 result.versions.length == 1 ||
252 result.versions[0][entry] != result.versions[1][entry]
253 "
254 class="agmdetailskeys"
255 >
256 <small>{{ entry }}</small>
257 </div>
258 <div
259 v-if="
260 result.versions.length == 1 ||
261 result.versions[0][entry] != result.versions[1][entry]
262 "
263 class="agmdetailsvalues"
264 >
265 <small>{{ result.versions[0][entry] }}</small>
266 </div>
267 <div
268 v-if="
269 result.versions.length == 2 &&
270 result.versions[0][entry] != result.versions[1][entry]
271 "
272 class="agmdetailsvalues"
273 >
274 <small>{{ result.versions[1][entry] }}</small>
275 </div>
276 </div>
277 </div>
278 </div>
279 </div>
280 </div>
281 </div>
282 </div>
283 </template>
284
285 <script>
286 /* This is Free Software under GNU Affero General Public License v >= 3.0
287 * without warranty, see README.md and license for details.
288 *
289 * SPDX-License-Identifier: AGPL-3.0-or-later
290 * License-Filename: LICENSES/AGPL-3.0.txt
291 *
292 * Copyright (C) 2018 by via donau
293 * – Österreichische Wasserstraßen-Gesellschaft mbH
294 * Software engineering by Intevation GmbH
295 *
296 * Author(s):
297 * Thomas Junk <thomas.junk@intevation.de>
298 */
299
300 import { formatSurveyDate, formatDateTime } from "@/lib/date.js";
301 import { STATES } from "@/store/imports.js";
302 import { HTTP } from "@/lib/http";
303 import { WFS } from "ol/format.js";
304 import { or as orFilter, equalTo as equalToFilter } from "ol/format/filter.js";
305 import { displayError } from "@/lib/errors.js";
306 import { mapState } from "vuex";
307 import { LAYERS } from "@/store/map.js";
308
309 const NO_DIFF = -1;
310 const NO_BOTTLENECK = -1;
311
312 export default {
313 name: "stagingdetail",
314 props: ["data"],
315 data() {
316 return {
317 showDiff: NO_DIFF,
318 showAGMDetails: false,
319 showBottleneckDetail: NO_BOTTLENECK,
320 show: false,
321 loading: false,
322 bottlenecks: []
323 };
324 },
325 mounted() {
326 this.bottlenecks = [];
327 const { id } = this.$route.params;
328 this.$store.commit("imports/setImportToReview", id);
329 if (this.open) this.showDetails();
330 },
331 computed: {
332 ...mapState("imports", ["importToReview"]),
333 open() {
334 return this.importToReview == this.data.id;
335 },
336 detail() {
337 return [
338 "pb-2",
339 "pt-2",
340 "d-flex",
341 "flex-column",
342 "w-100",
343 {
344 highlight: this.open && this.needsApproval(this.data)
345 }
346 ];
347 }
348 },
349 watch: {
350 showAGMDetails() {
351 if (!this.showAGMDetails) this.showDiff = NO_DIFF;
352 },
353 open() {
354 this.show = this.open;
355 },
356 $route() {
357 const { id } = this.$route.params;
358 this.$store.commit("imports/setImportToReview", id);
359 if (this.open) this.showDetails();
360 }
361 },
362 methods: {
363 showBottleneckDetails(index) {
364 if (index == this.showBottleneckDetail) {
365 this.showBottleneckDetail = NO_BOTTLENECK;
366 return;
367 }
368 this.showBottleneckDetail = index;
369 },
370 toggleDiff(number) {
371 if (this.showDiff !== number || this.showDiff == -1) {
372 this.showDiff = number;
373 } else {
374 this.showDiff = -1;
375 }
376 },
377 zoomToStretch(name) {
378 this.$store.commit("map/setLayerVisible", LAYERS.STRETCHES);
379 this.$store
380 .dispatch("imports/loadStretch", name)
381 .then(response => {
382 if (response.data.features.length < 1)
383 throw new Error("no feaures found for: " + name);
384 this.moveToExtent(response.data.features[0]);
385 })
386 .catch(error => {
387 console.log(error);
388 const { status, data } = error.response;
389 displayError({
390 title: this.$gettext("Backend Error"),
391 message: `${status}: ${data.message || data}`
392 });
393 });
394 },
395 showDetails() {
396 if (!this.isBottleneck(this.data.kind.toUpperCase())) return;
397 if (this.show) {
398 this.show = false;
399 return;
400 }
401 if (this.bottlenecks.length > 0) {
402 this.show = true;
403 return;
404 }
405 this.loading = true;
406 const generateFilter = () => {
407 const { bottlenecks } = this.data.summary;
408 if (bottlenecks.length === 1)
409 return equalToFilter("bottleneck_id", bottlenecks[0]);
410 const orExpressions = bottlenecks.map(x => {
411 return equalToFilter("bottleneck_id", x);
412 });
413 return orFilter(...orExpressions);
414 };
415 const filterExpression = generateFilter();
416 const bottleneckFeatureCollectionRequest = new WFS().writeGetFeature({
417 srsName: "EPSG:4326",
418 featureNS: "gemma",
419 featurePrefix: "gemma",
420 featureTypes: ["bottlenecks_geoserver"],
421 outputFormat: "application/json",
422 filter: filterExpression
423 });
424 HTTP.post(
425 "/internal/wfs",
426 new XMLSerializer().serializeToString(
427 bottleneckFeatureCollectionRequest
428 ),
429 {
430 headers: {
431 "X-Gemma-Auth": localStorage.getItem("token"),
432 "Content-type": "text/xml; charset=UTF-8"
433 }
434 }
435 )
436 .then(response => {
437 this.bottlenecks = response.data.features;
438 this.show = true;
439 this.loading = false;
440 })
441 .catch(error => {
442 const { status, data } = error.response;
443 displayError({
444 title: this.$gettext("Backend Error"),
445 message: `${status}: ${data.message || data}`
446 });
447 });
448 },
449 isFairwayDimension(kind) {
450 return kind === "FD";
451 },
452 isApprovedGaugeMeasurement(kind) {
453 return kind === "AGM";
454 },
455 isBottleneck(kind) {
456 return kind === "BN" || kind === "UBN";
457 },
458 isStretch(kind) {
459 return kind === "ST";
460 },
461 isSoundingResult(kind) {
462 return kind === "SR";
463 },
464 formatSurveyDate(date) {
465 return formatSurveyDate(date);
466 },
467 formatDateTime(date) {
468 return formatDateTime(date);
469 },
470 needsApproval(item) {
471 return item.status === STATES.NEEDSAPPROVAL;
472 },
473 isRejected(item) {
474 return item.status === STATES.REJECTED;
475 },
476 isApproved(item) {
477 return item.status === STATES.APPROVED;
478 },
479 moveToBottleneck(index) {
480 this.$store.commit("map/setLayerVisible", LAYERS.BOTTLENECKS);
481 this.moveToExtent(this.bottlenecks[index]);
482 },
483 moveToExtent(feature) {
484 this.$store.commit("map/moveToExtent", {
485 feature: feature,
486 zoom: 17,
487 preventZoomOut: true
488 });
489 },
490 moveMap(coordinates) {
491 this.$store.commit("map/moveMap", {
492 coordinates: coordinates,
493 zoom: 17,
494 preventZoomOut: true
495 });
496 },
497 zoomTo() {
498 const { lat, lon, bottleneck, date } = this.data.summary;
499 const coordinates = [lat, lon];
500 this.moveMap(coordinates);
501 this.$store
502 .dispatch("bottlenecks/setSelectedBottleneck", bottleneck)
503 .then(() => {
504 this.$store.commit("bottlenecks/setSelectedSurveyByDate", date);
505 });
506 },
507 toggleApproval(id, newStatus) {
508 this.$store.commit("imports/toggleApproval", {
509 id: id,
510 newStatus: newStatus
511 });
512 }
513 },
514 STATES: STATES
515 };
516 </script>
517
518 <style lang="scss" scoped></style>