Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
F
frontend
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
idatt2106-2024-07
frontend
Commits
e8411034
Commit
e8411034
authored
10 months ago
by
Victor Ekholt Gunrell Kaste
Browse files
Options
Downloads
Patches
Plain Diff
feat: redesigning roadmap for stats integrating endpoints
parent
e93da3db
No related branches found
Branches containing commit
No related tags found
1 merge request
!69
Feat/redesign roadmap
Pipeline
#282278
failed
10 months ago
Stage: install
Stage: build
Stage: test
Stage: lint
Changes
1
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
src/components/SavingGoalComponents/SavingGoalRoadmap.vue
+284
-67
284 additions, 67 deletions
src/components/SavingGoalComponents/SavingGoalRoadmap.vue
with
284 additions
and
67 deletions
src/components/SavingGoalComponents/SavingGoalRoadmap.vue
+
284
−
67
View file @
e8411034
<
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
.
s
tep
s
.
forEach
((
s
)
=>
(
s
.
showPanel
=
false
));
this
.
s
electedGoal
.
challenge
s
.
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
.
s
tep
s
.
indexOf
(
step
)}
`
);
const
panel
=
document
.
getElementById
(
`panel-
${
this
.
s
electedGoal
.
challenge
s
.
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"
>
{{
titl
e
}}
{{
selectedGoal
.
nam
e
}}
<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
:
#0A58
CA
;
background-color
:
#0
03
A58
;
}
}
.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
:
4
1%
;
width
:
3
1%
;
padding
:
0
20px
20px
30px
;
padding
:
0
20px
20px
30px
;
text-align
:
right
;
text-align
:
right
;
background-color
:
#0A58
CA
;
background-color
:
#0
03
A58
;
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
#00
1664
;
border
:
7px
solid
#00
3A58
;
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
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment