Mercurial > gemma
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> |