petra-tool / frontend / src / components / AddLabels.vue
AddLabels.vue
Raw
<template>
  <v-dialog
    v-model="dialog"
    persistent
    max-width="1200"
    overlay-color="white"
    content-class="rounded-xl"
  >
    <template v-slot:activator="{ on, attrs }">
      <v-btn
        v-model="fab"
        fab
        style="margin-top: -60px; margin-bottom: -40px"
        color="primary"
        class="elevation-3"
        v-bind="attrs"
        v-on="on"
        :disabled="finished"
      >
        <v-icon large>mdi-plus</v-icon>
      </v-btn>
    </template>

    <v-card>
      <v-card-title class="accent white--text pt-10 pb-4 text-h5 pl-9">
        Define Labels
      </v-card-title>
      <v-card-text>
        <v-container fluid>
          <v-form ref="form" v-model="form.valid" lazy-validation>
            <v-row no-gutters class="text--primary body-2 my-5">
              <v-col cols="12">
                Please select predefined labels (e.g. Design, Business, Tech,...) that are relevant for your next prototyping cycle or add custom labels:
              </v-col>
            </v-row>
            <v-row>
              <v-col cols="12">
                <div class="text-h5 text--primary mb-3">Select or Create Labels</div>
                <v-combobox
                  v-model="form.selectedLabels"
                  :items="form.labelOptions"
                  :search-input.sync="form.search"
                  hint="For already existing labels, backlog tasks will be appended"
                  persistent-hint
                  label="Select a label or create a custom label..."
                  return-object
                  multiple
                  outlined
                  chips
                  deletable-chips
                  maxlength="60"
                  :rules="[
                    value => value && form.selectedLabels.length > 0 || 'At least one label is required',
                    value => !!value || 'Label is required.'
                    ]"
                >
                  <template v-slot:no-data>
                    <v-list-item>
                      <v-list-item-content>
                        <v-list-item-title>
                          No results matching "<strong>{{ form.search }}</strong>". Press <kbd>enter</kbd> to create a
                          new
                          one
                        </v-list-item-title>
                      </v-list-item-content>
                    </v-list-item>
                  </template>
                </v-combobox>
              </v-col>
            </v-row>
          </v-form>
        </v-container>
      </v-card-text>
      <v-card-actions class="pa-10">
        <v-btn
          text
          @click="onClose"
        >
          Cancel
        </v-btn>
        <v-spacer></v-spacer>
        <v-btn
          color="primary"
          min-width="150"
          depressed
          @click="onSubmit"
          :loading="loading"
        >
          Continue
        </v-btn>
      </v-card-actions>
    </v-card>
  </v-dialog>
</template>

<script>
import {mapGetters} from "vuex";

export default {
  name: "AddLabels",
  props: ['labels', 'finished', 'cycle'],
  data: () => ({
    cycleId: null,
    backlogLabels: [],
    nextLabelId: 4,
    newLabelName: 'label 1',
    showAlert: false,

    dialog: false,
    loading: false,

    fab: false,

    form: {
      search: null,
      valid: true,
      selectedLabels: [],
      labelOptions: [
        {
          header: 'Select labels from default stack'
        },
        {
          text: 'User & Marketing (Default)',
          name: 'User & Marketing',
        },
        {
          text: 'Tech & Design (Default)',
          name: 'Tech & Design',
        },
        {
          text: 'Business (Default)',
          name: 'Business',
        },
      ],
    },
  }),
  methods: {
    onSubmit() {
      if (this.$refs.form.validate()) {
        this.loading = true
        this.mergeSelectedLabels.forEach(label => {
          if (!label.name) {
            // label comes from user defined input i.e. label is a single string input
            label = {name: label}
          }
          this.$http.post('/board/label', {
            cycle_id: this.cycleId,
            id: label.id, // backlog tasks already have an ID
            text: label.text,
            name: label.name,
            finish: label.finish,
          }).then(response => {
            this.$store.dispatch('site/addLabel', {
              id: response.data.label.id,
              name: label.name,
              tasks: response.data.label.tasks, // list of tasks if backlog o.w. it is an empty list
              finish: !!response.data.label.finish,
              purpose: response.data.label.purpose,
              rating: response.data.label.rating,
            }).then(() => {
              // or delete entire selectedLabels and labelOptions in the end?
              this.form.selectedLabels = this.form.selectedLabels.filter(m => m.name !== label.name)
              this.$refs.form.reset()
              // this.form.labelOptions = this.form.labelOptions.filter(m => m.name !== label.name)

              if (response.data.message) {
                this.$snackbar.showMessage({
                  content: response.data.message, color: 'warning'
                })
              } else {
                this.$snackbar.showMessage({
                  content: 'Labels added and saved!'
                })
              }
            })
          }).catch(() => {
            this.$snackbar.showMessage({
              content: 'Something went wrong, please try again!', color: 'warning'
            })
          })
        })
        this.onClose();
      }
      this.loading = false
    },

    onClose() {
      this.dialog = false
      this.$refs.form.reset()
      this.form.selectedLabels = []
    }
  },
  computed: {
    ...mapGetters('site', ['getBoard']),

    mergeSelectedLabels() {
      // if label with name 'abc' has backlog tasks and one adds label 'abc' from default stack
      // the label is sent twice to the backend on submit and thus appears twice in database.
      // Solving this by removing duplicates in the frontend: sort by backlog > default and
      // merge by name preserving backlog tasks.

      let sorted = [...this.form.selectedLabels].sort((a,b) => (b.id ? 1 : -1))

      return sorted
        .map(e => e['name'])
        .map((e, i, final) => final.indexOf(e) === i && i)
        .filter(obj=> sorted[obj])
        .map(e => sorted[e]);

    }
  },
  watch: {
    dialog(visible) {
      if (visible) {
        // dialog was opened -> load backlog
        this.cycleId = this.getBoard.id
        this.$http.get(`/board/backlog?cycle-id=${this.cycleId}`).then(res => {
          // Requirements

          let requirements = []
          if (['Requirements Selection', 'Function Exploration', 'Embodiment Design'].includes(this.cycle.phase))
            requirements = Object.values(res.data.requirements)

          requirements.forEach(requirement => {
            this.form.labelOptions.unshift({
              id: requirement.id,
              name: requirement.name,
              text: `${requirement.name} (${requirement.purpose.length} ${requirement.purpose.length > 1 ? 'purposes' : 'purpose'} in requirement)`,
              tasks: [],
              finish: requirement.finish
            })
          })

          if (requirements.length > 0) {
            this.form.labelOptions.unshift({
              header: 'Select labels from requirement list',
            })
          }

          // Backlog
          let backlog = Object.values(res.data.backlog)

          backlog.forEach(label => {
            this.form.labelOptions.unshift({
              id: label.id,
              cycleId: label.cycle_id,
              name: label.name,
              // description: label.description,
              text: `${label.name} (${label.tasks.length} ${label.tasks.length > 1 ? 'tasks' : 'task'} in backlog)`,
              tasks: label.tasks,
            })
          })

          if (backlog.length > 0) {
            // add header
            this.form.labelOptions.unshift({
              header: 'Select labels with backlog tasks',
            })
          }
        })
      } else {
        // dialog was closed -> do nothing
      }
    }
  },
  mounted() {

    ////////////////////////// Apply if condition like in Kanban.vue and move to computed!

    // this.form.labelOptions.push(
    //   {
    //     header: 'Select labels from default stack'
    //   })

    // let labelCandidates = [
    //   {
    //     text: 'User & Marketing (Default)',
    //     name: 'User & Marketing',
    //     color: 'teal lighten-4',
    //     source: 'default',
    //   },
    //   {
    //     text: 'Tech & Design (Default)',
    //     name: 'Tech & Design',
    //     color: 'orange lighten-4',
    //     source: 'default',
    //   },
    //   {
    //     text: 'Business (Default)',
    //     name: 'Business',
    //     color: 'lime lighten-4',
    //     source: 'default',
    //   },
    // ]
    // console.log(this.labels)
    // if (this.labels.length === 0) {
    //   console.log('a')
    //   // labels are empty, add all candidates
    //   this.form.labelOptions.push(...labelCandidates)
    // } else {
    //   console.log('b')
    //   // at least one label is in list
    //   labelCandidates.forEach(candidate => {
    //     // check for duplicates
    //     if (this.labels.length && this.checkForDuplicates(candidate.name)) {
    //       // label name not in list of available labels thus add to dropdown
    //       this.form.labelOptions.push(candidate)
    //     }
    //   })
    // }
  },
}
</script>

<style lang="scss" scoped>
@import '~vuetify/src/styles/settings/_variables';

.add-label {
  @media #{map-get($display-breakpoints, 'lg-and-up')} {
    margin-right: 300px
  }
}
</style>