<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--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>