<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 style="z-index: 10" color="primary" class="add-cycle" fab fixed bottom right v-bind="attrs" v-on="on"> <v-icon large>mdi-plus</v-icon> </v-btn> </template> <dialog-alert v-on:on-cancel="onClose" v-on:on-continue="onClose" v-if="disabled"> <template v-slot:text> Maximum number of pending cycles. Close old cycles first. <v-form v-show="false" ref="form" v-model="form.valid" lazy-validation></v-form> </template> </dialog-alert> <dialog-alert v-on:on-cancel="onClose" v-on:on-continue="onClose" v-else-if="addCycleDisabledFromSettings"> <template v-slot:text> Creating new cycles is disabled. Finish your pending cycles first. <v-form v-show="false" ref="form" v-model="form.valid" lazy-validation></v-form> </template> </dialog-alert> <v-card v-else> <v-card-title class="accent white--text pt-10 pb-4 text-h5 pl-9"> Add New Cycle </v-card-title> <v-card-text> <v-container fluid> <v-form ref="form" v-model="form.valid" lazy-validation> <v-row no-gutters> <v-col cols="12" class="text--primary body-2 my-5"> Start your new prototyping cycle by planning your cycle with the following steps: (1) Select a project phase and confirm the prototype milestone, and (2) Define your objective (What do you want to answer?) and the definition of done for the cycle (What is your measure of when you have reached your objective?). </v-col> </v-row> <v-row> <v-col cols="12" md="6" class="py-0" :order="$store.getters['site/getSettings'].includes('phase') ? '' : '11'"> <div class="text-h5 text--primary mb-3">Project Phase</div> <v-select label="Choose a suitable project phase..." :items="phaseOptions" v-model="form.phaseSelected" item-value="text" return-object outlined :rules="[value => !!value || 'Phase is required.']" ></v-select> </v-col> <v-col cols="12" md="6" class="py-0" :order="$store.getters['site/getSettings'].includes('phase') ? '' : '12'"> <div class="text-h5 text--primary mb-3">Prototype Milestone</div> <v-select label="Confirm the prototype milestone..." :items="milestoneOptions" v-model="form.milestoneSelected" outlined :rules="[value => !!value || 'Milestone is required.']" ></v-select> </v-col> <v-col cols="12" class="py-0"> <div class="text-h5 text--primary mb-3">Objective (What is your daily goal?)</div> <v-textarea label="What is your desired outcome for today?" v-model="form.objective" outlined auto-grow rows="3" :rules="[value => !!value || 'Objective is required.']" ></v-textarea> </v-col> <v-col cols="12" class="py-0"> <div class="text-h5 text--primary mb-3">Definition of Done (What do you expect to achieve?)</div> <v-textarea label="What do you need to accomplish to finish the cycle successfully?" v-model="form.definition" outlined auto-grow rows="3" :rules="[value => !!value || 'Definition of Done is required.']" ></v-textarea> </v-col> <v-col cols="12" md="6" class="pt-0" v-if="$store.getters['site/getSettings'].includes('deliverables')"> <v-expand-transition> <div v-show="form.phaseSelected"> <div class="text-h5 text--primary mb-3">Activities <a v-if="visibleDeliverables" class="body-2" @click="visibleDeliverables = false">hide...</a> <a v-else class="body-2" @click="visibleDeliverables = true">show...</a> </div> <v-expand-transition> <div v-show="visibleDeliverables"> <v-row no-gutters v-for="elem in deliverables" :key="elem.id"> <v-col cols="12"> <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> </div> </v-expand-transition> </div> </v-expand-transition> </v-col> <v-col cols="12" md="6" class="pt-0" v-if="$store.getters['site/getSettings'].includes('deliverables')"> <v-expand-transition> <div v-show="form.phaseSelected"> <div class="text-h5 text--primary mb-3">Methods <a v-if="visibleMethods" class="body-2" @click="visibleMethods = false">hide...</a> <a v-else class="body-2" @click="visibleMethods = true">show...</a> </div> <v-expand-transition> <div v-show="visibleMethods"> <v-row no-gutters v-for="elem in methods" :key="elem.id"> <v-col cols="12"> <div> <b>{{ elem.name }}</b>: <span v-if="readMoreMethods[elem.id]">{{ elem.description }} </span> <span v-else>{{ elem.description.substr(0, 120) }} </span> <template v-if="elem.description.length > 120"> <a v-if="!readMoreMethods[elem.id]" @click="showMore(elem.id, 'met')">more...</a> <a v-else @click="showLess(elem.id, 'met')">less...</a> </template> </div> </v-col> </v-row> </div> </v-expand-transition> </div> </v-expand-transition> </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" depressed min-width="150" :loading="loading" @click="onSubmit" > Continue </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! </template> </dialog-alert> </v-dialog> <v-dialog v-model="dialogPhases" max-width="500px" overlay-color="white" style="z-index: 99" > <dialog-alert v-on:on-cancel="dialogPhases = false" v-on:on-continue="dialogPhases = false; ignorePhases = true; onSubmit()"> <template v-slot:title>Linear Prototyping Error</template> <template v-slot:text> Do you really want to go back in phases as your progress will be reset? </template> </dialog-alert> </v-dialog> </v-dialog> </template> <script> import DialogAlert from "@/components/DialogAlert"; import {mapGetters} from "vuex"; export default { name: "AddCycle", props: ['disabled'], components: {DialogAlert}, data() { return { dialog: false, dialogError: null, dialogPhases: false, ignorePhases: false, form: { valid: true, objective: '', definition: '', phaseSelected: '', milestoneSelected: '', }, loading: false, addCycleDisabledFromSettings: false, visibleDeliverables: false, visibleMethods: false, readMoreDeliverables: {}, readMoreMethods: {}, requirementsListDone: false, } }, methods: { setErrors(res) { this.form.errors = res.data }, onSubmit() { if (this.$refs.form.validate()) { this.errors = null this.loading = true this.$http.post(`/cycles?ignore=${ this.$store.getters['site/getSettings'].includes('linear') ? this.ignorePhases : 'false' }`, { objective: this.form.objective, definition: this.form.definition, phase: this.form.phaseSelected.text, milestone: this.form.milestoneSelected, }).then(response => { this.$store.dispatch('site/addCycle', { 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: this.form.objective, definition: this.form.definition, milestone: this.form.milestoneSelected, phase: this.form.phaseSelected.text, // finished: response.data.finished, // reflected: response.data.reflected, // completed: response.data.completed, status: response.data.status, expired: response.data.expired, }) this.$snackbar.showMessage({ content: `Cycle #${ response.data.id } added successfully. Go to Kanban Board to start working.` }) this.dialog = false this.ignorePhases = false this.$store.dispatch('site/fetchProgress') this.$refs.form.reset() }).catch(error => { if (error.response.data) { // no going back in phases rule active, display error this.dialogPhases = true } else { this.$snackbar.showMessage({ content: 'Something went wrong, please try again!', color: 'warning' }) } }).finally(() => (this.loading = false)) } else { this.dialogError = true this.loading = false } }, onClose() { this.dialog = false this.$refs.form.reset() }, 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: { ...mapGetters('site', ['getCycles', 'getTeam']), phaseOptions() { let disablePhaseRules = !this.$store.getters['site/getSettings'].includes('rules') let phaseOptions = [ { header: 'Discover Phase', }, { text: 'Problem Understanding', }, { text: 'Problem Decomposition', }, {divider: true}, { header: 'Define Phase', }, { text: 'Requirements Elicitation', }, { text: 'Requirements Selection', }, ] // if (disablePhaseRules || this.getCycles.some(cycle => cycle.phase === 'Requirements Selection' && cycle.status === 'Done')) if (disablePhaseRules || this.requirementsListDone) phaseOptions.push( {divider: true}, { header: 'Develop Phase', }, { text: 'Function Exploration', }, { text: 'Embodiment Design', }, {divider: true}, { header: 'Launch Phase' }, { text: 'Integration Design', }, { text: 'User Testing and Feedback', }, ) return phaseOptions }, milestoneOptions() { if (!this.form.phaseSelected || typeof this.form.phaseSelected === 'undefined' || typeof this.form.phaseSelected.text === 'undefined') { return [] } else if (this.form.phaseSelected.text === 'Problem Understanding') { return [{ text: 'Problem Pretotype - depiction of the problem', value: 'Problem Pretotype', }] } else if (this.form.phaseSelected.text === 'Problem Decomposition') { return [{ text: 'Problem Prototype - broken down problem into sub-problems', value: 'Problem Prototype', }] } else if (this.form.phaseSelected.text === 'Requirements Elicitation') { return [{ text: 'Solution Pretotype I - mocked-up representation', value: 'Solution Pretotype I', }] } else if (this.form.phaseSelected.text === 'Requirements Selection') { return [{ text: 'Solution Pretotype II - mocked-up representation', value: 'Solution Pretotype II', }] } else if (this.form.phaseSelected.text === 'Function Exploration') { return [{ text: 'Solution Prototype I - solution with features deployed', value: 'Solution Prototype I', }] } else if (this.form.phaseSelected.text === 'Embodiment Design') { return [{ text: 'Solution Prototype II - solution without features visualized', value: 'Solution Prototype II', }] } else if (this.form.phaseSelected.text === 'Integration Design') { return [{ text: 'Integration Prototype - integration of looks and functionality', value: 'Integration Prototype', }] } else if (this.form.phaseSelected.text === 'User Testing and Feedback') { return [{ text: 'Final Prototype - sufficient features to satisfy early adopters', value: 'Final Prototype', }] } else { return [{text:'', value:''}] } }, deliverables() { if (!this.form.phaseSelected || typeof this.form.phaseSelected === 'undefined' || typeof this.form.phaseSelected.text === 'undefined') return [] else return this.$store.getters['site/getDeliverables'].filter(deliverable => deliverable.phase.name === this.form.phaseSelected.text) }, methods() { if (!this.form.phaseSelected || typeof this.form.phaseSelected === 'undefined' || typeof this.form.phaseSelected.text === 'undefined') return [] else return this.$store.getters['site/getMethods'].filter(method => method.phase.name === this.form.phaseSelected.text) } }, watch: { dialog(visible) { if (visible) { // dialog was opened this.$http.get('/settings').then(res => { this.addCycleDisabledFromSettings = !!res.data.settings["addcycle"] }) this.$http.get('/board/backlog').then(res => { // if requirements list is done, unlock the second half of phases this.requirementsListDone = res.data.requirements.some(req => req.finish) }) } else { // dialog was closed } } } } </script> <style lang="scss"> @import '~vuetify/src/styles/settings/_variables'; .v-subheader { font-size: 1.0rem; color: #68A694 !important; } .add-cycle { @media #{map-get($display-breakpoints, 'lg-and-up')} { margin-right: 400px } } </style>