petra-tool / frontend / src / components / CycleTable.vue
CycleTable.vue
Raw
<template>
  <div class="cycle-table">

    <div class="text-h5 mb-0">
      <span>Cycles</span>
      <dialog-info>
        <template v-slot:title>Cycles</template>
        <template v-slot:text>
          In the 'Cycles' section, you'll find all ongoing and completed cycles. 
          Cycles have statuses like 'Done,' 'Reflect,' or 'Ongoing'. 
          Once a cycle ends, you go through a reflection of the current cycle before proceeding to the next cycle.
        </template>
      </dialog-info>
    </div>

    <v-container class="my-0">

      <v-row no-gutters>
        <v-col cols="6" class="text-left">
          <v-chip v-for="(status, i) in statusOptions" :key="i"
                  dark small label
                  @click="filterCycles(status.key)"
                  :class="`status ${status.key} ma-1`">
            {{ status.name }}
          </v-chip>
          <v-fade-transition>
            <v-chip v-if="getCycleFilter"
                    class="ma-1"
                    small label
                    @click="removeFilter()"
            >
              All</v-chip>
          </v-fade-transition>

        </v-col>
        <v-col cols="6" class="text-right">

          <v-menu bottom right offset-y :close-on-content-click="false">
            <template v-slot:activator="{ on, attrs }">
              <v-chip small label class="ma-1"
                v-bind="attrs"
                v-on="on"
              >
                Sort by
                <v-icon right>mdi-menu-down</v-icon>
              </v-chip>
            </template>

            <v-list width="200">
              <v-list-item-group
                v-model="sortBySelected"
                active-class=""
              >
                <v-list-item dense
                  v-for="(item, i) in sortByOptions"
                  :key="i"
                >
                  <template v-slot:default="{ active }">
                    <v-list-item-action>
                      <v-checkbox :input-value="active"></v-checkbox>
                    </v-list-item-action>
                    <v-list-item-content>
                      <v-list-item-title>{{ item.name }}</v-list-item-title>
                    </v-list-item-content>
                  </template>
                </v-list-item>
              </v-list-item-group>
            </v-list>
          </v-menu>

        </v-col>
      </v-row>

      <v-container>
        <v-row no-gutters class="cycle body-2 font-weight-bold">
          <v-col cols="3" sm="1" class="px-2">
            ID
          </v-col>
          <v-col cols="9" sm="2" class="px-2">
            Start Date
          </v-col>
          <v-col cols="12" sm="2" class="px-2">
            Milestone
          </v-col>
          <v-col class="px-2">
            Objective
          </v-col>
          <v-col cols="12" sm="auto"></v-col>
        </v-row>
      </v-container>

      <Cycle v-for="cycle in sortedCycles" :key="cycle.id"
             :cycle="cycle"
      ></Cycle>

      <add-cycle :disabled="!addCycleAllowed || addCycleDisabledFromSettings" />

      <v-dialog
        v-model="getExpiredCycleDialog"
        max-width="500px"
        overlay-color="white"
        style="z-index: 99"
        v-if="$store.getters['site/getSettings'].includes('expiration')"
      >
        <dialog-alert v-on:on-cancel="$store.commit('site/setExpiredCycleDialog', false)"
                      v-on:on-continue="$store.commit('site/setExpiredCycleDialog', false)">
          <template v-slot:text>
            Cycle time passed. Close pending cycles soon.
          </template>
        </dialog-alert>
      </v-dialog>

    </v-container>
  </div>
</template>

<script>
import Cycle from './Cycle';
import AddCycle from "@/components/AddCycle";
import DialogInfo from "@/components/DialogInfo";
import {mapGetters} from "vuex";
import DialogAlert from "@/components/DialogAlert";

export default {
  name: "CycleTable",
  components: {
    DialogAlert,
    DialogInfo,
    AddCycle,
    Cycle,
  },
  props: ['cycles', 'teams'],
  data: () => ({
    maxAddCycleAllowed: 2,
    teamFilterSelected: [],
    addCycleDisabledFromSettings: false,

    sortBySelected: 0,
    sortByOptions: [
      {name: 'Cycle', key: 'id'},
      {name: 'Milestone', key: 'milestone'},
      {name: 'Objective', key: 'objective'},
    ],

    statusOptions: [
      {name: 'Done', key: 'complete'},
      {name: 'Reflect', key: 'reflect'},
      {name: 'Ongoing', key: 'todo'},
    ],
  }),
  methods: {
    filterCycles(status) {
      this.$store.dispatch('site/filterCyclesByStatus', status)
    },
    removeFilter() {
      this.$store.dispatch('site/removeCycleFilter')
    }
  },
  computed: {
    ...mapGetters('site', ['getCycles', 'getCycleFilter', 'getFilteredCycles', 'getExpiredCycleDialog']),
    addCycleAllowed() {
      if (this.$store.getters['site/getSettings'].includes('limit')) {
        const allowed = this.cycles // maxAddCycleAllowed cycles are allowed
          .filter(cycle => cycle.status !== 'Done')
          .length < this.maxAddCycleAllowed
        this.$emit('update-add-cycle-allowed', allowed)
        return allowed
      } else {
        return true
      }
    },

    sortedCycles() {
      let prop = this.sortByOptions[this.sortBySelected].key
      if (prop !== 'id')
        return this.getFilteredCycles.slice().sort((a, b) => a[prop].localeCompare(b[prop]))
      else
        // Always reverse order (descending) for id
        return this.getFilteredCycles.slice().sort((a, b) => a[prop] > b[prop] ? -1 : 1)
    },
  },
  created() {
    this.teamFilterSelected = Array(this.teams.length).fill(0).map((x, index) => index)

    if (this.$store.getters['site/getExpiredCycleDialog'] === null && this.cycles.some(cycle => cycle.expired)) {
      this.$store.commit('site/setExpiredCycleDialog', true)
    }
  },
}
</script>

<style scoped>
.status.v-chip.complete {
  background: #F29829;
}

.status.v-chip.reflect {
  background: #156389;
}

.status.v-chip.todo {
  background: #F25C05;
}
</style>