changeset 2462:9ae2a2f758bb

client: make use of new table header/body components
author Markus Kottlaender <markus@intevation.de>
date Mon, 04 Mar 2019 14:50:23 +0100
parents b60efa7d4cee
children 8cc3cd1b27f2
files client/src/components/Bottlenecks.vue client/src/components/ImportStretches.vue client/src/components/importschedule/Importschedule.vue client/src/components/systemconfiguration/PDFTemplates.vue client/src/components/ui/UITableBody.vue
diffstat 5 files changed, 251 insertions(+), 328 deletions(-) [+]
line wrap: on
line diff
--- a/client/src/components/Bottlenecks.vue	Mon Mar 04 13:53:09 2019 +0100
+++ b/client/src/components/Bottlenecks.vue	Mon Mar 04 14:50:23 2019 +0100
@@ -5,123 +5,109 @@
       title="Bottlenecks"
       :closeCallback="$parent.close"
     />
-    <div class="row p-2 text-left small">
-      <div class="col-5">
-        <a href="#" @click="sortBy('name')" class="sort-link">
-          <translate>Name</translate>
-        </a>
-        <font-awesome-icon
-          :icon="sortIcon"
-          class="ml-1"
-          v-if="sortColumn === 'name'"
-        ></font-awesome-icon>
+    <UITableHeader
+      :columns="[
+        { id: 'name', title: 'Name', class: 'col-4' },
+        {
+          id: 'latestMeasurement',
+          title: 'Latest Measurement',
+          class: 'col-3'
+        },
+        { id: 'chainage', title: 'Chainage', class: 'col-3' }
+      ]"
+      @sortingChanged="sortBy"
+    />
+    <UITableBody
+      :data="filteredAndSortedBottlenecks()"
+      :maxHeight="(showSplitscreen ? 18 : 35) + 'rem'"
+      :active="openBottleneck"
+      v-slot="{ item: bottleneck }"
+    >
+      <div class="col-4 py-2 text-left">
+        <a href="#" @click="selectBottleneck(bottleneck)">{{
+          bottleneck.properties.name
+        }}</a>
       </div>
-      <div class="col-2">
-        <a href="#" @click="sortBy('latestMeasurement')" class="sort-link">
-          <translate>Latest</translate> <br />
-          <translate>Measurement</translate>
-        </a>
-        <font-awesome-icon
-          :icon="sortIcon"
-          class="ml-1"
-          v-if="sortColumn === 'latestMeasurement'"
-        ></font-awesome-icon>
+      <div class="col-3 py-2">
+        {{ formatSurveyDate(bottleneck.properties.current) }}
+      </div>
+      <div class="col-3 py-2">
+        {{
+          displayCurrentChainage(
+            bottleneck.properties.from,
+            bottleneck.properties.to
+          )
+        }}
       </div>
-      <div class="col-3">
-        <a href="#" @click="sortBy('chainage')" class="sort-link">
-          <translate>Chainage</translate>
+      <div class="col-2 pr-0 text-right d-flex flex-column">
+        <a
+          class="text-info mt-auto mb-auto mr-2"
+          @click="loadSurveys(bottleneck)"
+          v-if="bottleneck.properties.current"
+        >
+          <font-awesome-icon
+            class="pointer"
+            icon="spinner"
+            fixed-width
+            spin
+            v-if="loading === bottleneck"
+          ></font-awesome-icon>
+          <font-awesome-icon
+            class="pointer"
+            icon="angle-down"
+            fixed-width
+            v-if="loading !== bottleneck && openBottleneck !== bottleneck"
+          ></font-awesome-icon>
+          <font-awesome-icon
+            class="pointer"
+            icon="angle-up"
+            fixed-width
+            v-if="loading !== bottleneck && openBottleneck === bottleneck"
+          ></font-awesome-icon>
         </a>
-        <font-awesome-icon
-          :icon="sortIcon"
-          class="ml-1"
-          v-if="sortColumn === 'chainage'"
-        ></font-awesome-icon>
       </div>
-      <div class="col-2"></div>
-    </div>
-    <div
-      class="bottleneck-list small text-left"
-      :style="'max-height: ' + (showSplitscreen ? 18 : 35) + 'rem'"
-      v-if="filteredAndSortedBottlenecks().length"
-    >
       <div
-        v-for="bottleneck in filteredAndSortedBottlenecks()"
-        :key="bottleneck.properties.name"
-        class="border-top row bottleneck-row mx-0"
+        :class="[
+          'col-12 p-0',
+          'surveys',
+          { open: openBottleneck === bottleneck }
+        ]"
       >
-        <div class="col-5 py-2 text-left">
-          <a href="#" @click="selectBottleneck(bottleneck)">{{
-            bottleneck.properties.name
-          }}</a>
-        </div>
-        <div class="col-2 py-2">
-          {{ formatSurveyDate(bottleneck.properties.current) }}
-        </div>
-        <div class="col-3 py-2">
-          {{
-            displayCurrentChainage(
-              bottleneck.properties.from,
-              bottleneck.properties.to
-            )
-          }}
-        </div>
-        <div class="col-2 pr-0 text-right d-flex flex-column">
-          <a
-            class="text-info mt-auto mb-auto mr-2"
-            @click="loadSurveys(bottleneck.properties.name)"
-            v-if="bottleneck.properties.current"
-          >
-            <font-awesome-icon
-              class="pointer"
-              icon="spinner"
-              fixed-width
-              spin
-              v-if="loading === bottleneck.properties.name"
-            ></font-awesome-icon>
-            <font-awesome-icon
-              class="pointer"
-              icon="angle-down"
-              fixed-width
-              v-if="
-                loading !== bottleneck.properties.name &&
-                  openBottleneck !== bottleneck.properties.name
-              "
-            ></font-awesome-icon>
-            <font-awesome-icon
-              class="pointer"
-              icon="angle-up"
-              fixed-width
-              v-if="
-                loading !== bottleneck.properties.name &&
-                  openBottleneck === bottleneck.properties.name
-              "
-            ></font-awesome-icon>
-          </a>
-        </div>
-        <div
-          :class="[
-            'col-12 p-0',
-            'surveys',
-            { open: openBottleneck === bottleneck.properties.name }
-          ]"
+        <a
+          href="#"
+          class="d-inline-block px-3 py-2"
+          v-for="(survey, index) in openBottleneckSurveys"
+          :key="index"
+          @click="selectSurvey(survey, bottleneck)"
         >
-          <a
-            href="#"
-            class="d-block px-3 py-2"
-            v-for="(survey, index) in openBottleneckSurveys"
-            :key="index"
-            @click="selectSurvey(survey, bottleneck)"
-            >{{ formatSurveyDate(survey.date_info) }}</a
-          >
-        </div>
+          {{ formatSurveyDate(survey.date_info) }}
+        </a>
       </div>
-    </div>
-    <div v-else class="small text-center py-3 border-top">
-      <translate>No results.</translate>
-    </div>
+    </UITableBody>
   </div>
 </template>
 
+<style lang="sass" scoped>
+.table-body
+  .row
+    > div:not(:last-child)
+      transition: background-color 0.3s, color 0.3s
+    &.active
+      > div:not(:last-child)
+        background-color: $color-info
+        color: #fff
+        a
+          color: #fff !important
+      .surveys
+        border-bottom: solid 1px $color-info
+    .surveys
+      overflow: hidden
+      max-height: 0
+      &.open
+        overflow-y: auto
+        max-height: 5rem
+</style>
+
 <script>
 /* This is Free Software under GNU Affero General Public License v >= 3.0
  * without warranty, see README.md and license for details.
@@ -249,19 +235,18 @@
           });
         });
     },
-    sortBy(column) {
-      this.sortColumn = column;
-      this.sortDirection = this.sortDirection === "ASC" ? "DESC" : "ASC";
+    sortBy(sorting) {
+      this.sortColumn = sorting.sortColumn;
+      this.sortDirection = sorting.sortDirection;
     },
-    loadSurveys(name) {
-      this.openBottleneckSurveys = null;
-      if (name === this.openBottleneck) {
+    loadSurveys(bottleneck) {
+      if (bottleneck === this.openBottleneck) {
         this.openBottleneck = null;
+        this.openBottleneckSurveys = null;
       } else {
-        this.openBottleneck = name;
-        this.loading = name;
+        this.loading = bottleneck;
 
-        HTTP.get("/surveys/" + name, {
+        HTTP.get("/surveys/" + bottleneck.properties.name, {
           headers: {
             "X-Gemma-Auth": localStorage.getItem("token"),
             "Content-type": "text/xml; charset=UTF-8"
@@ -271,6 +256,7 @@
             this.openBottleneckSurveys = response.data.surveys.sort((a, b) => {
               return a.date_info < b.date_info ? 1 : -1;
             });
+            this.openBottleneck = bottleneck;
           })
           .catch(error => {
             const { status, data } = error.response;
@@ -291,37 +277,3 @@
   }
 };
 </script>
-
-<style lang="scss" scoped>
-.bottleneck-list {
-  overflow-y: auto;
-}
-
-.bottleneck-list .bottleneck-row a {
-  text-decoration: none;
-}
-
-.bottleneck-list .bottleneck-row:hover {
-  background: #fbfbfb;
-}
-
-.surveys {
-  max-height: 0;
-  min-height: 0;
-  overflow: hidden;
-}
-
-.surveys a:hover {
-  background: #f3f3f3;
-}
-
-.surveys.open {
-  max-height: 250px;
-  overflow: auto;
-}
-
-.sort-link {
-  color: #444;
-  font-weight: bold;
-}
-</style>
--- a/client/src/components/ImportStretches.vue	Mon Mar 04 13:53:09 2019 +0100
+++ b/client/src/components/ImportStretches.vue	Mon Mar 04 14:50:23 2019 +0100
@@ -5,60 +5,52 @@
       title="Define Stretches"
       :closeCallback="$parent.close"
     />
-    <div v-if="!edit" class="mb-3 mr-3 ml-3 text-left">
-      <table v-if="stretches.length > 0" class="table">
-        <thead>
-          <tr>
-            <th class="header"><translate>Name</translate></th>
-            <th class="header"><translate>Datum</translate></th>
-            <th class="header"><translate>Source organization</translate></th>
-            <th></th>
-          </tr>
-        </thead>
-        <tbody>
-          <tr class="small" v-for="(stretch, index) in stretches" :key="index">
-            <td class="">
-              <a
-                class="linkto text-info"
-                v-if="isInStaging(stretch.properties.name)"
-                @click="gotoStaging(getStagingLink(stretch.properties.name))"
-              >
-                {{ stretch.properties.name
-                }}<font-awesome-icon
-                  class="ml-1 text-danger"
-                  icon="exclamation-triangle"
-                  fixed-width
-                ></font-awesome-icon
-                ><small class="ml-1">review</small>
-              </a>
-              <a v-else @click="moveMapToStretch(index)" href="#">{{
-                stretch.properties.name
-              }}</a>
-            </td>
-            <td class="">
-              {{ formatSurveyDate(stretch.properties["date_info"]) }}
-            </td>
-            <td>{{ stretch.properties["source_organization"] }}</td>
-            <td class="text-right">
-              <button
-                class="btn btn-sm btn-dark mr-1"
-                @click="editStretch(index)"
-              >
-                <font-awesome-icon icon="pencil-alt" fixed-width />
-              </button>
-              <button
-                class="btn btn-sm btn-dark"
-                @click="deleteStretch(stretch)"
-              >
-                <font-awesome-icon icon="trash" fixed-width />
-              </button>
-            </td>
-          </tr>
-        </tbody>
-      </table>
-      <div class="mt-3" v-if="stretches.length == 0">
-        <translate>No results.</translate>
-      </div>
+    <div v-if="!edit" class="mb-3">
+      <UITableHeader
+        :columns="[
+          { id: 'name', title: 'Name', class: 'col-4' },
+          { id: 'date', title: 'Date', class: 'col-2' },
+          { id: 'srcorg', title: 'Source organization', class: 'col-3' }
+        ]"
+        :sortable="false"
+      />
+      <UITableBody :data="stretches" v-slot="{ item: stretch }">
+        <div class="py-2 col-4 ">
+          <a
+            class="linkto text-info"
+            v-if="isInStaging(stretch.properties.name)"
+            @click="gotoStaging(getStagingLink(stretch.properties.name))"
+          >
+            {{ stretch.properties.name
+            }}<font-awesome-icon
+              class="ml-1 text-danger"
+              icon="exclamation-triangle"
+              fixed-width
+            ></font-awesome-icon
+            ><small class="ml-1">review</small>
+          </a>
+          <a v-else @click="moveMapToStretch(stretch)" href="#">{{
+            stretch.properties.name
+          }}</a>
+        </div>
+        <div class="py-2 col-2">
+          {{ formatSurveyDate(stretch.properties["date_info"]) }}
+        </div>
+        <div class="py-2 col-3">
+          {{ stretch.properties["source_organization"] }}
+        </div>
+        <div class="py-2 col text-right">
+          <button
+            class="btn btn-sm btn-dark mr-1"
+            @click="editStretch(stretch)"
+          >
+            <font-awesome-icon icon="pencil-alt" fixed-width />
+          </button>
+          <button class="btn btn-sm btn-dark" @click="deleteStretch(stretch)">
+            <font-awesome-icon icon="trash" fixed-width />
+          </button>
+        </div>
+      </UITableBody>
     </div>
     <div v-if="edit">
       <div class="ml-3 mr-3">
@@ -337,8 +329,8 @@
           });
       });
     },
-    editStretch(index) {
-      const properties = this.stretches[index].properties;
+    editStretch(stretch) {
+      const properties = stretch.properties;
       this.date_info = properties.date_info.split("T")[0];
       this.id = properties.name;
       this.nobjbn = properties.nobjnam;
@@ -376,10 +368,10 @@
         }
       });
     },
-    moveMapToStretch(index) {
+    moveMapToStretch(stretch) {
       this.$store.commit("map/setLayerVisible", LAYERS.STRETCHES);
       this.$store.commit("map/moveToExtent", {
-        feature: this.stretches[index],
+        feature: stretch,
         zoom: 17,
         preventZoomOut: true
       });
--- a/client/src/components/importschedule/Importschedule.vue	Mon Mar 04 13:53:09 2019 +0100
+++ b/client/src/components/importschedule/Importschedule.vue	Mon Mar 04 14:50:23 2019 +0100
@@ -4,100 +4,81 @@
     <div class="mt-3 w-100">
       <div class="card flex-grow-1 schedulecard shadow-xs">
         <UIBoxHeader icon="clock" title="Imports" />
-        <div class="card-body schedulecardbody">
-          <div class="card-body schedulecardbody">
-            <div class="searchandfilter mb-3  w-50 d-flex flex-row">
-              <div class="searchgroup input-group">
-                <div class="input-group-prepend">
-                  <span class="input-group-text" id="search">
-                    <font-awesome-icon icon="search"></font-awesome-icon>
-                  </span>
-                </div>
-                <input
-                  v-model="searchQuery"
-                  type="text"
-                  class="form-control"
-                  placeholder
-                  aria-label="Search"
-                  aria-describedby="search"
-                />
-              </div>
+        <div class="searchandfilter p-3 w-50 mx-auto">
+          <div class="searchgroup input-group">
+            <div class="input-group-prepend">
+              <span class="input-group-text" id="search">
+                <font-awesome-icon icon="search"></font-awesome-icon>
+              </span>
             </div>
-            <transition name="fade">
-              <table v-if="schedules.length" class="table table-hover">
-                <thead>
-                  <tr>
-                    <th><translate>ID</translate></th>
-                    <th><translate>Type</translate></th>
-                    <th><translate>Author</translate></th>
-                    <th><translate>Schedule</translate></th>
-                    <th><translate>Email</translate></th>
-                    <th style="width: 140px"></th>
-                  </tr>
-                </thead>
-                <transition-group name="fade" tag="tbody">
-                  <tr v-for="schedule in schedules" :key="schedule.id">
-                    <td>{{ schedule.id }}</td>
-                    <td>{{ schedule.kind.toUpperCase() }}</td>
-                    <td>{{ schedule.user }}</td>
-                    <td>{{ schedule.config.cron }}</td>
-                    <td>
-                      <font-awesome-icon
-                        v-if="schedule.config['send-email']"
-                        class="fa-fw mr-2"
-                        fixed-width
-                        icon="check"
-                      ></font-awesome-icon>
-                    </td>
-                    <td class="text-right">
-                      <button
-                        @click="editSchedule(schedule.id)"
-                        class="btn btn-sm btn-dark mr-1"
-                        :disabled="importScheduleDetailVisible"
-                      >
-                        <font-awesome-icon
-                          icon="pencil-alt"
-                          fixed-width
-                        ></font-awesome-icon>
-                      </button>
-                      <button
-                        @click="deleteSchedule(schedule)"
-                        class="btn btn-sm btn-dark mr-1"
-                        :disabled="importScheduleDetailVisible"
-                      >
-                        <font-awesome-icon
-                          icon="trash"
-                          fixed-width
-                        ></font-awesome-icon>
-                      </button>
-                      <button
-                        @click="triggerManualImport(schedule.id)"
-                        class="btn btn-sm btn-dark"
-                        :disabled="importScheduleDetailVisible"
-                      >
-                        <font-awesome-icon
-                          icon="play"
-                          fixed-width
-                        ></font-awesome-icon>
-                      </button>
-                    </td>
-                  </tr>
-                </transition-group>
-              </table>
-              <div v-else class="mt-4 small text-center py-3">
-                <translate>No scheduled imports</translate>
-              </div>
-            </transition>
-            <div class="text-right">
-              <button
-                :disabled="importScheduleDetailVisible"
-                @click="newImport"
-                class="btn btn-info newbutton"
-              >
-                <translate>New Import</translate>
-              </button>
-            </div>
+            <input
+              v-model="searchQuery"
+              type="text"
+              class="form-control"
+              placeholder
+              aria-label="Search"
+              aria-describedby="search"
+            />
+          </div>
+        </div>
+        <UITableHeader
+          :columns="[
+            { id: 'id', title: 'ID', class: 'col-1' },
+            { id: 'type', title: 'Type', class: 'col-2' },
+            { id: 'author', title: 'Author', class: 'col-2' },
+            { id: 'schedule', title: 'Schedule', class: 'col-2' },
+            { id: 'email', title: 'Email', class: 'col-2' }
+          ]"
+          :sortable="false"
+        />
+        <UITableBody :data="schedules" v-slot="{ item: schedule }">
+          <div class="py-2 col-1">{{ schedule.id }}</div>
+          <div class="py-2 col-2">{{ schedule.kind.toUpperCase() }}</div>
+          <div class="py-2 col-2">{{ schedule.user }}</div>
+          <div class="py-2 col-2">{{ schedule.config.cron }}</div>
+          <div class="py-2 col-2">
+            <font-awesome-icon
+              v-if="schedule.config['send-email']"
+              class="fa-fw mr-2"
+              fixed-width
+              icon="check"
+            ></font-awesome-icon>
           </div>
+          <div class="py-2 col text-right">
+            <button
+              @click="editSchedule(schedule.id)"
+              class="btn btn-sm btn-dark mr-1"
+              :disabled="importScheduleDetailVisible"
+            >
+              <font-awesome-icon
+                icon="pencil-alt"
+                fixed-width
+              ></font-awesome-icon>
+            </button>
+            <button
+              @click="deleteSchedule(schedule)"
+              class="btn btn-sm btn-dark mr-1"
+              :disabled="importScheduleDetailVisible"
+            >
+              <font-awesome-icon icon="trash" fixed-width></font-awesome-icon>
+            </button>
+            <button
+              @click="triggerManualImport(schedule.id)"
+              class="btn btn-sm btn-dark"
+              :disabled="importScheduleDetailVisible"
+            >
+              <font-awesome-icon icon="play" fixed-width></font-awesome-icon>
+            </button>
+          </div>
+        </UITableBody>
+        <div class="p-3 text-right">
+          <button
+            :disabled="importScheduleDetailVisible"
+            @click="newImport"
+            class="btn btn-info newbutton"
+          >
+            <translate>New Import</translate>
+          </button>
         </div>
       </div>
     </div>
--- a/client/src/components/systemconfiguration/PDFTemplates.vue	Mon Mar 04 13:53:09 2019 +0100
+++ b/client/src/components/systemconfiguration/PDFTemplates.vue	Mon Mar 04 14:50:23 2019 +0100
@@ -11,34 +11,27 @@
       />
     </div>
     <div class="mt-1 border-bottom pb-4">
-      <transition name="fade">
-        <table class="table table-sm table-hover" v-if="templates.length">
-          <thead>
-            <tr>
-              <th><translate>Name</translate></th>
-              <th><translate>Date</translate></th>
-              <th><translate>Country</translate></th>
-              <th></th>
-            </tr>
-          </thead>
-          <transition-group name="fade" tag="tbody">
-            <tr v-for="template in templates" :key="template.name">
-              <td>{{ template.name }}</td>
-              <td>{{ template.time }}</td>
-              <td v-if="template.country">{{ template.country }}</td>
-              <td v-else><i>global</i></td>
-              <td class="text-right">
-                <button
-                  class="btn btn-sm btn-dark"
-                  @click="deleteTemplate(template)"
-                >
-                  <font-awesome-icon icon="trash" />
-                </button>
-              </td>
-            </tr>
-          </transition-group>
-        </table>
-      </transition>
+      <UITableHeader
+        :columns="[
+          { id: 'name', title: 'Name', class: 'col-4' },
+          { id: 'date', title: 'Date', class: 'col-4' },
+          { id: 'country', title: 'Country', class: 'col-2' }
+        ]"
+        :sortable="false"
+      />
+      <UITableBody :data="templates" v-slot="{ item: template }">
+        <div class="py-2 col-4">{{ template.name }}</div>
+        <div class="py-2 col-4">{{ template.time }}</div>
+        <div class="py-2 col-2" v-if="template.country">
+          {{ template.country }}
+        </div>
+        <div class="py-2 col-2" v-else><i>global</i></div>
+        <div class="col py-2 text-right">
+          <button class="btn btn-sm btn-dark" @click="deleteTemplate(template)">
+            <font-awesome-icon icon="trash" />
+          </button>
+        </div>
+      </UITableBody>
       <button class="btn btn-info mt-2" @click="$refs.uploadTemplate.click()">
         <font-awesome-icon
           icon="spinner"
--- a/client/src/components/ui/UITableBody.vue	Mon Mar 04 13:53:09 2019 +0100
+++ b/client/src/components/ui/UITableBody.vue	Mon Mar 04 14:50:23 2019 +0100
@@ -8,7 +8,7 @@
   >
     <div
       v-for="(item, index) in data"
-      :key="index"
+      :key="key(index)"
       :class="['border-top row mx-0', { active: active === item }]"
     >
       <slot :item="item" :index="index"></slot>
@@ -46,6 +46,11 @@
     active: {
       type: [Object, Array]
     }
+  },
+  methods: {
+    key(index) {
+      return index;
+    }
   }
 };
 </script>