petra-tool / frontend / src / components / ReflectCycle.vue
ReflectCycle.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
        dark
        depressed
        x-small
        color="accent"
        class="mr-2 body-2"
        min-width="130"
        @click.native.stop.prevent="dialog = true"
        v-bind="attrs"
        v-on="on"
      >
        <!--        <v-icon left>mdi-head-check</v-icon>-->
        Reflect
      </v-btn>
    </template>

    <v-card>
      <v-card-title class="accent white--text pt-10 text-h5 pl-9">
        Reflect Cycle #{{ cycle.id }}
      </v-card-title>
      <v-tabs v-model="tab" dark background-color="accent" fixed-tabs>
        <v-tab class="body-1">{{ (checkboxRequirementList || checkboxFinalize) ? 1.1 : 1 }}. Outcome - Insight - Rating</v-tab>
        <v-tab class="body-1" v-show="checkboxRequirementList || checkboxFinalize">1.2 Requirement List</v-tab>
        <v-tab class="body-1">2. Upload</v-tab>
        <v-tab class="body-1" v-show="$store.getters['site/getSettings'].includes('reflect')">3. Prototyping</v-tab>
      </v-tabs>
      <v-card-text>
        <v-container fluid>
          <v-form ref="form" v-model="form.valid" lazy-validation>
<!--            <v-row v-if="tab < 3">-->
<!--              <v-col cols="12" class="text&#45;&#45;primary body-2 my-5">-->
<!--                Reflect your prototyping cycle by documenting the results, experiments,-->
<!--                human-centered design lens, and assigning a cycle score by the team and-->
<!--                project manager. Relevant data can be attached to the cycle.-->
<!--              </v-col>-->
<!--            </v-row>-->
            <v-tabs-items v-model="tab" touchless>
              <v-tab-item eager>
                <v-row no-gutters>
                  <v-col cols="12">
                    <div class="text-h5 text--primary mb-3">Outcomes & Experiments</div>
                    <v-textarea
                      label="Describe the results and experiments you used for this prototyping cycle..."
                      v-model="form.outcome"
                      outlined
                      auto-grow
                      rows="3"
                      :rules="[value => !!value || 'Outcome is required.']"
                    ></v-textarea>
                  </v-col>
                </v-row>
                <v-row no-gutters>
                  <v-col cols="12">
                    <div class="text-h5 text--primary mb-3">Key Insight</div>
                    <v-textarea
                      label=" Describe your key learnings from this prototyping cycle..."
                      v-model="form.insight"
                      outlined
                      auto-grow
                      rows="3"
                      :rules="[value => !!value || 'Key Insight is required.']"
                    ></v-textarea>
                  </v-col>
                </v-row>
                <!--
                <v-row no-gutters v-if="cycle.phase === 'Requirements Elicitation' || cycle.phase === 'Requirements Selection'">
                  <v-col cols="12">
                    <div class="text-h5 text--primary">Requirement List</div>
                    <v-checkbox v-if="cycle.phase === 'Requirements Elicitation'"
                                v-model="checkboxRequirementList"
                                color="accent"
                                label="Submit Requirement List?"
                    ></v-checkbox>
                    <v-checkbox v-if="cycle.phase === 'Requirements Selection'"
                                v-model="checkboxFinalize"
                                color="accent"
                                label="Finalize Requirement List?"
                    ></v-checkbox>
                  </v-col>
                </v-row>
                -->
                <v-row no-gutters>
                  <v-col cols="12">
                    <div class="text-h5 text--primary">Cycle Rating</div>
                    <div class="mb-1">
                      Rate the the cycle for met expectations in this prototyping cycle (Definitions of Done = Expectations).
                    </div>
                    <div class="mb-3">
                      <span class="font-weight-bold">Definitions of done</span>: {{cycle.definition}}
                    </div>
                    <v-row>
                      <v-col cols="12" md="6">
                    <v-select
                      label="Team cycle rating for this prototyping cycle..."
                      :items="form.successOptions"
                      v-model="form.successSelectedTeam"
                      outlined
                      :rules="[value => !!value || 'Cycle Rating from Team is required.']"
                    ></v-select>
                      </v-col>
                      <v-col cols="12" md="6">
                        <v-select
                          label="Project Manager cycle rating for this prototyping cycle..."
                          :items="form.successOptions"
                          v-model="form.successSelectedPM"
                          outlined
                          :rules="[value => !!value || 'Cycle Rating from Project Manager is required.']"
                        ></v-select>
                      </v-col>
                    </v-row>
                  </v-col>
                </v-row>
              </v-tab-item>
              <v-tab-item>
                <v-row no-gutters>
                  <v-col cols="12">
                    <add-requirement-list :requirements="form.requirements" :cycle-id="cycle.id" :finish="checkboxFinalize" :requirements-deleted="form.requirementsDeleted" v-on:rating-changed="ratingChanged"></add-requirement-list>
                  </v-col>
                </v-row>
              </v-tab-item>
              <v-tab-item eager>
                <v-row no-gutters>
                  <v-col cols="12">
                    <div class="text-h5 text--primary mb-1">Uploads</div>
                    <div class="mb-1 red--text font-weight-bold">
                      Please add a representative Picture of the Prototype built in this cycle. In case you have multiple files to upload, please zip them.
                    </div>
                  </v-col>
                  <v-col cols="12">
                    <upload-form :parentFiles="form.files" v-on:updateFiles="updateFiles"
                                 ref="uploadForm" :error="error" :upload-error="uploadError"></upload-form>
                  </v-col>
                </v-row>
              </v-tab-item>
              <v-tab-item>
                <v-row>
                  <v-col cols="12" class="text--primary body-2 my-5">
                    Please select which (1) deliverables were addressed and (2) which prototyping methods were used in this cycle. Multiple selections are possible.
                  </v-col>
                  <v-col cols="12" md="6">
                    <div class="text-h5 text--primary mb-3">Phase Deliverables</div>
                    <v-row no-gutters v-for="elem in deliverables" :key="elem.id">
                      <v-col cols="1">
                        <v-checkbox v-model="elem.checked"
                                    dense :ripple="false" class="py-0 my-0"
                        ></v-checkbox>
                      </v-col>
                      <v-col cols="11">
                        <div>
                          <b>{{ elem.name }}</b>:
                          <span v-if="readMoreDeliverables[elem.id]">{{ elem.description }} </span>
                          <span v-else>{{ elem.description.substr(0, 120) }} </span>
                          <a v-if="!readMoreDeliverables[elem.id]" @click="showMore(elem.id, 'del')">more...</a>
                          <a v-else @click="showLess(elem.id, 'del')">less...</a>
                        </div>
                      </v-col>
                    </v-row>
                  </v-col>
                  <v-col cols="12" md="6">
                    <div class="text-h5 text--primary mb-3">Prototyping Methods</div>
                    <v-row no-gutters v-for="elem in methods" :key="elem.id">
                      <v-col cols="1">
                        <v-checkbox v-model="elem.checked"
                                    dense :ripple="false" class="py-0 my-0"
                        ></v-checkbox>
                      </v-col>
                      <v-col cols="11">
                        <div>
                          <b>{{ elem.name }}</b>:
                          <span v-if="readMoreMethods[elem.id]">{{ elem.description }} </span>
                          <span v-else>{{ elem.description.substr(0, 120) }} </span>
                          <a v-if="!readMoreMethods[elem.id]" @click="showMore(elem.id, 'met')">more...</a>
                          <a v-else @click="showLess(elem.id, 'met')">less...</a>
                        </div>
                      </v-col>
                    </v-row>
                  </v-col>

                </v-row>
              </v-tab-item>
            </v-tabs-items>
          </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 v-if="tab !== ($store.getters['site/getSettings'].includes('reflect') ? 3 : 2)"
               color="primary"
               min-width="150"
               depressed
               @click="changeTab"
        >Continue
        </v-btn>
        <v-btn v-else
               color="primary"
               min-width="150"
               depressed
               :loading="loading"
               @click="onSubmit"
        >
          Submit
        </v-btn>
      </v-card-actions>

    </v-card>

    <v-dialog
      v-model="dialogError"
      max-width="500px"
      overlay-color="white"
      style="z-index: 99"
    >
      <dialog-alert v-on:on-cancel="dialogError = false" v-on:on-continue="dialogError = false">
        <template v-slot:title>Validation Error</template>
        <template v-slot:text>
          Some fields are not valid. Fix errors to continue!<br>
          {{ dialogMessage }}
        </template>
      </dialog-alert>
    </v-dialog>

  </v-dialog>
</template>

<script>
import UploadForm from "@/components/UploadForm";
import DialogAlert from "@/components/DialogAlert";
import AddRequirementList from "@/components/AddRequirementList";

export default {
  name: "ReflectCycle",
  components: {
    AddRequirementList,
    UploadForm,
    DialogAlert,
  },
  props: ['cycle'],
  data() {
    return {
      dialog: false,
      dialogError: null,
      dialogMessage: '',
      uploadError: '',
      loading: false,
      error: false,
      tab: null,
      checkboxRequirementList: false,
      checkboxFinalize: false,
      readMoreDeliverables: {},
      readMoreMethods: {},
      form: {
        valid: true,
        outcome: '',
        test: '',
        // fidelityOptions: [
        //   {text: 'Low - quickly created prototype without function', value: 'Low'},
        //   {text: 'Medium - looks-like & works-like prototypes', value: 'Medium'},
        //   {text: 'High combining works- and looks-like', value: 'High'},
        // ],
        // fidelitySelected: '',
        insight: '',
        successOptions: [
          {text: 'No Expectations met (x = 0%)', value: '1'},
          {text: 'Less than half of Expectations met (0% ≤ x < 50%)', value: '2'},
          {text: 'All Expectations addressed, more than half completed (50% ≤ x < 100%)', value: '3'},
          {text: 'All Expectations met (x = 100%)', value: '4'},
          {text: 'Exceeded Expectations (x > 100%)', value: '5'},
        ],
        successSelectedTeam: '',
        successSelectedPM: '',
        files: [],
        requirements: [],
        requirementsDeleted: [],
      },

    }
  },
  methods: {
    onSubmit() {
      this.error = false
      this.loading = true

      if (this.$refs.form.validate() && this.validateFileUpload()) {
        this.$http.put(`/cycles`, {
          id: this.cycle.id,
          outcome: this.form.outcome,
          // fidelity: this.form.fidelitySelected,
          insight: this.form.insight,
          success_team: Number(this.form.successSelectedTeam),
          success_pm: Number(this.form.successSelectedPM),
          files: this.form.files,
          requirements: this.checkboxFinalize || this.checkboxRequirementList ? this.form.requirements : [],
          requirements_deleted: this.form.requirementsDeleted,
          finished: true,
          deliverables: this.deliverables.filter(elem => elem.checked).map(elem => elem.id),
          methods: this.methods.filter(elem => elem.checked).map(elem => elem.id),
        }).then(response => {
          this.$store.dispatch('site/updateCycle', {
            id: response.data.id,
            team: response.data.team,
            date_start: response.data.date_start,
            date_start_pretty: response.data.date_start_pretty,
            date_end: response.data.date_end,
            objective: response.data.objective,
            definition: response.data.definition,
            milestone: response.data.milestone,
            phase: response.data.phase,
            outcome: response.data.outcome,
            // fidelity: response.data.fidelity,
            insight: response.data.insight,
            rating_team: response.data['rating_team'],
            rating_pm: response.data['rating_pm'],
            attachments: response.data.attachments,
            deliverables: response.data.deliverables,
            methods: response.data.methods,
            // finished: response.data.finished,
            // reflected: response.data.reflected,
            // completed: response.data.completed,
            status: response.data.status,
            expired: response.data.expired,
            isActive: true,
          })
          this.$snackbar.showMessage({
            content: `Cycle #${ this.cycle.id } successfully reflected.
            Continue by adding new cycles or by finishing pending cycles.`
          })
          this.$store.dispatch('site/fetchMatrix')
          this.$store.dispatch('site/fetchProgress')
          this.onClose()
          // this.$router.push({name: 'site-dashboard', params: {'reflectedCycleId': response.data.id}})
        }).catch(() => {
          this.$snackbar.showMessage({
            content: 'Something went wrong, please try again!', color: 'warning'
          })
        }).finally(() => this.loading = false)
      } else if (!this.$refs.form.validate() && this.validateFileUpload()) {
        // do nothing as no file is selected but the form is invalid
        this.dialogError = true
        this.loading = false
      } else {
        // set error alert visible
        this.dialogError = true
        this.error = true
        this.loading = false
      }
    },
    onClose() {
      this.dialog = false
      this.$refs.form.reset()

      this.form.files.forEach(this.$refs.uploadForm.$refs.upload.remove)
      this.form.files = []

      this.form.requirements = []
      this.tab = null
    },
    changeTab() {
      if (this.tab === 0) {
        if (this.checkboxRequirementList || this.checkboxFinalize)
          this.tab = 1
        else
          this.tab = 2
      } else {
        this.tab += 1
      }
    },
    ratingChanged() {
      this.$refs.form.validate()
    },

    validateFileUpload() {
      if (this.form.files.length > 0) {
        // files available; check if upload successful
        if (this.form.files.filter(file => file.success === false).length === 0) {
          // upload successful
          return true
        } else {
          // upload not performed
          this.dialogMessage = 'Start upload before submit'
          this.uploadError = 'upload'
          return false
        }
      } else {
        // no files to upload
        this.dialogMessage = 'Upload representative Picture of the Prototype built in this cycle.'
        this.uploadError = 'missing'
        return false
      }
    },
    updateFiles(updatedFiles) {
      this.form.files = updatedFiles
    },
    showMore(id, type) {
      // https://stackoverflow.com/a/61381609
      if (type === 'del')
        this.$set(this.readMoreDeliverables, id, true)
      if (type === 'met')
        this.$set(this.readMoreMethods, id, true)
    },
    showLess(id, type) {
      if (type === 'del')
        this.$set(this.readMoreDeliverables, id, false)
      if (type === 'met')
        this.$set(this.readMoreMethods, id, false)
    },
  },
  computed: {
    deliverables() {
      return this.$store.getters['site/getDeliverables'].filter(deliverable => deliverable.phase.name === this.cycle.phase).map(elem => ({...elem, readMore: false}))
    },
    methods() {
      return this.$store.getters['site/getMethods'].filter(method => method.phase.name === this.cycle.phase)
    }
  },
  created() {

    // For development only
    // this.form.outcome = 'the outcome'
    // this.form.insight = 'the insight'
    // this.form.lensSelected = [{text: 'Desirability - What do people desire?', value: 'Desirability'},{text: 'Viability', value: 'Viability'},]
    // this.form.successSelectedTeam = '5'
    // this.form.successSelectedPM = '5'

    // let file = new window.File(['foo'], 'foo.txt', {type: "text/plain",})
    // this.form.files.unshift(file)
  },
}
</script>

<style scoped>

</style>