Skip to content
Snippets Groups Projects
Commit e8411034 authored by Victor Ekholt Gunrell Kaste's avatar Victor Ekholt Gunrell Kaste
Browse files

feat: redesigning roadmap for stats integrating endpoints

parent e93da3db
No related branches found
No related tags found
1 merge request!69Feat/redesign roadmap
Pipeline #282278 failed
<script lang="ts"> <script lang="ts">
interface Step { import {CategoryScale, Chart as ChartJS, Legend, LinearScale, LineElement, PointElement, Title, Tooltip} from 'chart.js'
title: string; import {Line} from 'vue-chartjs'
showPanel: boolean; import type {ChallengeDTO, GoalDTO, MarkChallengeDTO} from "@/api";
description: string; import {GoalService} from '@/api'
percentFinished: number;
unFinished: boolean; ChartJS.register(
} CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
)
export default { export default {
components: {
Line
},
data() { data() {
return { return {
image: 'https://th.bing.com/th/id/OIG3.NMbdxmKYKVnxYGLOa0Z0?w=1024&h=1024&rs=1&pid=ImgDetMain' as string, image: 'https://th.bing.com/th/id/OIG3.NMbdxmKYKVnxYGLOa0Z0?w=1024&h=1024&rs=1&pid=ImgDetMain' as string,
altImage: 'https://th.bing.com/th/id/OIG4.gVWUC.rwCb8faTNx31yU?w=1024&h=1024&rs=1&pid=ImgDetMain' as string, altImage: 'https://th.bing.com/th/id/OIG4.gVWUC.rwCb8faTNx31yU?w=1024&h=1024&rs=1&pid=ImgDetMain' as string,
title: 'Spain trip' as string, title: 'Spain trip' as string,
//This will be changed to info gathered from backend bluePanelMaxHeight: 'auto' as string,
steps: [ roadmapSelected: true as boolean,
{ title: 'Challenge 1', showPanel: false, description: 'Save 50kr on coffee in 3 days', percentFinished: 22, unFinished: false }, statsSelected: false as boolean,
{ title: 'Challenge 2', showPanel: false, description: 'Save 500kr on food in 7 days', percentFinished: 73, unFinished: false }, chartData: {
{ title: 'Challenge 3', showPanel: false, description: 'Save 350kr on clothes in 5 days', percentFinished: 50, unFinished: true }, labels: [1, 2, 3, 4, 5, 6, '7'],
{ title: 'Challenge 4', showPanel: false, description: 'Save 150kr on coffee in 4 days', percentFinished: 10, unFinished: true }, datasets: [
{ title: 'Challenge 5', showPanel: false, description: 'Save 50kr on coffee in 3 days', percentFinished: 90, unFinished: true } {
] label: this.selectedGoal.name,
, backgroundColor: '#003A58',
bluePanelMaxHeight: 'auto' as string data: [11, 24, 30, 47, 53, 62, 79]
}
]
},
chartOptions: {
responsive: true,
maintainAspectRatio: true,
scales: {
y: {
min: 0,
max: this.selectedGoal.targetAmount
}
}
},
newPrice: 0,
savedSoFar: 0 as number,
currentChallengeIndex: 0,
}; };
}, },
mounted() { async mounted() {
setTimeout(() => { setTimeout(() => {
this.togglePanel(this.steps[2]); this.findCurrentChallenge()
this.disableAllChecksThatNotCurrent()
this.togglePanel(this.selectedGoal.challenges[this.currentChallengeIndex])
this.calculateSavedSoFar()
this.onLoadDisableChecks(this.selectedGoal)
this.onLoadAddDataToGraph(this.selectedGoal)
}, 500); }, 500);
}, },
computed: { computed: {
computeImageFilter() { computeImageFilter() {
return (step: Step) => { return (challenge: ChallengeDTO) => {
return step.unFinished ? 'none' : 'grayscale(100%)'; return challenge ? 'none' : 'grayscale(100%)';
}; };
} }
}, },
props: {
selectedGoal: {
type: Object,
default: null,
},
},
methods: { methods: {
togglePanel(step: Step) { togglePanel(step: any) {
if (step.showPanel) { if (step.showPanel) {
step.showPanel = false; step.showPanel = false;
} else { } else {
this.steps.forEach((s) => (s.showPanel = false)); this.selectedGoal.challenges.forEach((s: any) => (s.showPanel = false));
step.showPanel = true; step.showPanel = true;
this.scrollToPanel(step); this.scrollToPanel(step);
} }
}, },
scrollToPanel(step: Step) {
scrollToPanel(step: any) {
if (step.showPanel) { if (step.showPanel) {
this.$nextTick(() => { this.$nextTick(() => {
const panel = document.getElementById(`panel-${this.steps.indexOf(step)}`); const panel = document.getElementById(`panel-${this.selectedGoal.challenges.indexOf(step)}`);
if (panel) { if (panel) {
panel.scrollIntoView({ behavior: 'smooth', block: 'center' }); panel.scrollIntoView({ behavior: 'smooth', block: 'center' });
} }
}); });
} }
}, },
changeDisplay() {
if (this.roadmapSelected) {
this.roadmapSelected = false
this.statsSelected = true
} else {
this.roadmapSelected = true
this.statsSelected = false
setTimeout(() => {
this.onLoadDisableChecks(this.selectedGoal)
this.disableAllChecksThatNotCurrent()
}, 100);
}
},
convertTemplateTextToChallengeText(challenge: ChallengeDTO) {
let challengeText: any
challengeText = challenge.challengeTemplate?.text
challengeText = challengeText.replace('{unit_amount}', challenge.challengeTemplate?.amount?.toString())
challengeText = challengeText.replace('{checkDays}', challenge.checkDays?.toString())
challengeText = challengeText.replace('{totalDays}', challenge.totalDays?.toString())
let totalAmount: any
if (challenge.checkDays !== undefined && challenge.amount !== undefined) {
totalAmount = challenge.checkDays * challenge.amount;
} else {
// Handle the case when challenge.checkDays or challenge.amount is undefined
}
challengeText = challengeText.replace('{total_amount}', totalAmount.toString())
challengeText = challengeText.replace('{amount}', challenge.amount?.toString())
return challengeText
},
calculateTotalAmountFromChallenges() {
let totalAmountFromChallenges = 0
for (const challenge of this.selectedGoal.challenges) {
totalAmountFromChallenges += challenge.amount
}
return totalAmountFromChallenges
},
async handleCheckboxClick(challenge: ChallengeDTO, index: number, amount: number) {
this.lockCheckBox(challenge, index)
const markChallengePayload: MarkChallengeDTO = {
id: challenge.id,
day: index,
amount: amount,
};
try {
await GoalService.updateChallenge({ requestBody: markChallengePayload });
const today: Date = new Date();
const dateString: string = today.toISOString().split('T')[0]; // Extract YYYY-MM-DD part
if (challenge.progressList) {
challenge.progressList.push({ day: index, amount: amount, completedAt: dateString })
}
this.addDataToChart(amount, dateString);
this.calculateSavedSoFar();
} catch (error: any) {
console.log(error.message);
}
},
lockCheckBox(challenge: ChallengeDTO, index: number) {
const checkboxId = challenge.id + 'inlineCheckbox' + index
const checkbox = document.getElementById(checkboxId) as HTMLInputElement | null;
if (checkbox) {
// Disable the checkbox
checkbox.disabled = true;
}
},
onLoadDisableChecks(goal: GoalDTO) {
(goal.challenges || []).forEach((challenge: any) => {
challenge.progressList.forEach((progress: any) => {
// Assuming 'amount' is the property you want to add from progressList
const checkBoxId = challenge.id + 'inlineCheckbox' + progress.day
const checkbox = document.getElementById(checkBoxId) as HTMLInputElement | null;
if (checkbox) {
// Disable the checkbox
checkbox.checked = true;
checkbox.disabled = true;
}
});
});
},
onLoadAddDataToGraph(goal: GoalDTO) {
(goal.challenges || []).forEach((challenge: any) => {
challenge.progressList.forEach((progress: any) => {
this.addDataToChart(progress.amount, progress.completedAt);
});
});
},
addDataToChart(data: number, date: string) {
// Find the last dataset
const lastDataset = this.chartData.datasets[this.chartData.datasets.length - 1];
// Calculate the new label based on the last label
const newLabel = date.split('T')[0];
// Calculate the new data point based on the last data point
const lastDataPoint = lastDataset.data[lastDataset.data.length - 1];
const newDataPoint = lastDataPoint + data;
// Add the new label and data point to the chart data
this.chartData.labels.push(newLabel);
lastDataset.data.push(newDataPoint);
},
calculateSavedSoFar() {
this.savedSoFar = 0; // Reset savedSoFar before calculating again
this.selectedGoal.challenges.forEach((challenge: ChallengeDTO) => {
// Check if progressList exists before accessing its elements
if (challenge.progressList) {
challenge.progressList.forEach((progress: any) => {
// Assuming 'amount' is the property you want to add from progressList
this.savedSoFar += progress.amount;
});
}
});
},
findCurrentChallenge() {
const today: Date = new Date();
this.selectedGoal.challenges.forEach((challenge: ChallengeDTO, index: number) => {
const startDate: Date = new Date(challenge.startDate as string);
const endDate: Date = new Date(challenge.endDate as string);
if (today >= startDate && today <= endDate) {
this.currentChallengeIndex = index
}
})
},
disableAllChecksThatNotCurrent() {
this.selectedGoal.challenges.forEach((challenge: ChallengeDTO, index: number) => {
if (index != this.currentChallengeIndex) {
for (let i = 1; i < challenge.checkDays + 1; i++) {
this.lockCheckBox(challenge, i)
}
}
})
}
}, },
}; };
</script> </script>
...@@ -64,52 +250,69 @@ export default { ...@@ -64,52 +250,69 @@ export default {
<template> <template>
<div class="col-lg-8"> <div class="col-lg-8">
<div class="SavingGoalTitle text-center"> <div class="SavingGoalTitle text-center">
{{title}} {{selectedGoal.name}}
<br> <br>
<p class="d-inline-flex gap-1"> <p class="d-inline-flex gap-1">
<button class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample" style="font-size: 25px;"> <button @click="changeDisplay" class="btn btn-primary" type="button" style="font-size: 25px;">
See more info <div v-if="roadmapSelected">
Se statistikk
</div>
<div v-else>
Se sparesti
</div>
</button> </button>
</p> </p>
<div class="collapse" id="collapseExample">
<div class="card card-body bg-primary mx-auto" style="width: 80%;">
Total saved: 20kr
</div>
</div>
</div> </div>
<ul class="timeline"> <div v-if="roadmapSelected">
<li v-for="(step, index) in steps" :key="index" :class="{ 'timeline-inverted': index % 2 !== 0 }"> <ul class="timeline">
<div class="timeline-image z-1" @click="togglePanel(step)"> <li v-for="(challenge, index) in selectedGoal.challenges" :key="index" :class="{ 'timeline-inverted': index % 2 !== 0 }">
<img class="circular-image" :src="step.showPanel ? altImage : image" :style="{ filter: computeImageFilter(step) }" alt=""> <div class="timeline-image z-1" @click="togglePanel(challenge)">
</div> <img class="circular-image" :src="challenge.showPanel ? altImage : image" :style="{ filter: computeImageFilter(challenge) }" alt="">
<div class="timeline-panel z-3" :id="'panel-' + index" v-show="step.showPanel">
<div class="timeline-heading">
<h4>{{ step.title }}</h4>
<h4 class="subheading">{{step.description}}</h4>
</div> </div>
<div class="timeline-body"> <div class="timeline-panel z-3" :id="'panel-' + index" v-show="challenge.showPanel">
<br> <div class="timeline-heading">
<p class=""> <h4>Utfordring {{ index +1 }}</h4>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. <h4 class="subheading">{{convertTemplateTextToChallengeText(challenge)}}</h4>
</p>
<br>
<div class="progress">
<div class="progress-bar" role="progressbar" :style="{ width: step.percentFinished + '%' }" :aria-valuenow="step.percentFinished" aria-valuemin="0" aria-valuemax="100"></div>
</div> </div>
<br> <div class="timeline-body">
<div class="form-check form-check-inline"> <br>
<input class="form-check-input" type="checkbox" id="inlineCheckbox1" value="option1"> <p>
<label class="form-check-label" style="color:white;" for="inlineCheckbox1">Day 1</label> Pris per enhet: {{challenge.challengeTemplate.amount}} kr <img src="../../assets/icons/edit-button.svg" alt="editIcon" data-bs-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false" aria-controls="exampleModal">
</div> </p>
<div class="form-check form-check-inline"> <br>
<input class="form-check-input" type="checkbox" id="inlineCheckbox2" value="option2"> <div class="collapse" id="collapseExample" style="background-color: white; padding: 12px; border-radius: 5px">
<label class="form-check-label" style="color:white;" for="inlineCheckbox1">Day 2</label> <div class="card card-body">
<div class="input-group mb-3">
<span class="input-group-text" id="basic-addon1">Ny pris</span>
<input v-model="newPrice" type="number" class="form-control" placeholder="Pris i kr" aria-label="newPrice" aria-describedby="basic-addon1" required>
</div>
</div>
<br>
<button class="btn btn-success">Bekreft endring</button>
</div>
<br>
<div class="progress">
<div class="progress-bar" role="progressbar" :style="{ width: challenge.percentFinished + '%' }" :aria-valuenow="challenge.percentFinished" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<br>
<div class="checkbox-row">
<div class="form-check form-check-inline" v-for="(day, index) in challenge.checkDays" :key="index">
<input class="form-check-input" @click="handleCheckboxClick(challenge, index+1, challenge.challengeTemplate.amount)" type="checkbox" :id="challenge.id + 'inlineCheckbox' + (index + 1)" :value="day" :disabled="day.checked">
<label class="form-check-label" style="color:white;" :for="'inlineCheckbox' + (index + 1)">{{ day }}</label>
</div>
</div>
</div> </div>
</div> </div>
</div> <div class="line" v-if="index < selectedGoal.challenges.length - 1"></div>
<div class="line" v-if="index < steps.length - 1"></div> </li>
</li> </ul>
</ul> </div>
<div v-else>
<h3 style="font-weight: 600;">Du har så langt spart {{ savedSoFar }} kr</h3>
<h3 style="font-weight: 600;">Ditt penge mål er: {{selectedGoal.targetAmount}} kr</h3>
<h3 style="font-weight: 600;">Utfordringene i denne sparestien vil spare deg {{ calculateTotalAmountFromChallenges() }} kr</h3>
<Line ref="chart" :data="chartData" :options="chartOptions" />
</div>
</div> </div>
</template> </template>
...@@ -127,7 +330,7 @@ export default { ...@@ -127,7 +330,7 @@ export default {
padding-bottom: 10px; padding-bottom: 10px;
color: white; color: white;
border-radius: 1em; border-radius: 1em;
background-color: #0A58CA; background-color: #003A58;
} }
.timeline { .timeline {
...@@ -165,11 +368,12 @@ export default { ...@@ -165,11 +368,12 @@ export default {
.timeline > li .timeline-panel { .timeline > li .timeline-panel {
position: relative; position: relative;
float: left; float: left;
width: 41%; width: 31%;
padding: 0 20px 20px 30px; padding: 0 20px 20px 30px;
text-align: right; text-align: right;
background-color: #0A58CA; background-color: #003A58;
border-radius: 1em; border-radius: 1em;
margin-left: 110px;
} }
.timeline>li .timeline-panel:before { .timeline>li .timeline-panel:before {
...@@ -190,7 +394,7 @@ export default { ...@@ -190,7 +394,7 @@ export default {
z-index: 100; z-index: 100;
position: absolute; position: absolute;
left: 50%; left: 50%;
border: 7px solid #001664; border: 7px solid #003A58;
border-radius: 100%; border-radius: 100%;
background-color: #00ffff; background-color: #00ffff;
box-shadow: 0 0 5px #00e1ff; box-shadow: 0 0 5px #00e1ff;
...@@ -210,6 +414,7 @@ export default { ...@@ -210,6 +414,7 @@ export default {
float: right; float: right;
padding: 0 30px 20px 20px; padding: 0 30px 20px 20px;
text-align: left; text-align: left;
margin-right: 110px;
} }
.timeline>li.timeline-inverted>.timeline-panel:before { .timeline>li.timeline-inverted>.timeline-panel:before {
...@@ -431,4 +636,16 @@ export default { ...@@ -431,4 +636,16 @@ export default {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
} }
.checkbox-row {
display: flex;
flex-wrap: wrap;
margin-bottom: 12px; /* Adjust as needed */
text-align: left !important;
}
.form-check {
flex: 0 0 calc(25% - 10px); /* 20% width for each checkbox, adjust margin accordingly */
margin-right: 10px; /* Adjust as needed */
}
</style> </style>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment