552 lines
13 KiB
Vue
552 lines
13 KiB
Vue
<style type="text/css" scoped>
|
|
div.calendar {
|
|
width: calc(100% - 4px);
|
|
min-height: 350px;
|
|
display: flex;
|
|
margin: 5px 8px 15px;
|
|
flex-wrap: wrap;
|
|
flex-direction: row;
|
|
justify-content: flex-start;
|
|
}
|
|
.day {
|
|
flex: 0 0 calc(14.28% - 2px);
|
|
min-height: 50px;
|
|
border: 1px solid var(--border_color);
|
|
font-size: 1.2em;
|
|
overflow: hidden;
|
|
box-sizing: border-box;
|
|
position: relative;
|
|
line-height: 1em;
|
|
|
|
display: flex;
|
|
align-items: flex-end;
|
|
}
|
|
.today {
|
|
font-weight: bold;
|
|
text-decoration: underline;
|
|
}
|
|
.active-entry {
|
|
outline: #07f4f4;
|
|
outline-style: none;
|
|
outline-width: medium;
|
|
outline-style: none;
|
|
outline-offset: -1px;
|
|
outline-style: solid;
|
|
outline-width: 3px;
|
|
}
|
|
.day ~ .has-data {
|
|
|
|
}
|
|
.day ~ .no-data {
|
|
background: #c7c7c787;
|
|
opacity: 0.6;
|
|
}
|
|
.day > .number {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 5px;
|
|
z-index: 10;
|
|
opacity: 0.4;
|
|
}
|
|
.day > .sex {
|
|
font-size: 0.7em;
|
|
border-radius: 5px;
|
|
background: rgba(249, 0, 0, 0.15);
|
|
color: white;
|
|
padding: 0 0 0 4px;
|
|
z-index: 10;
|
|
position: absolute;
|
|
left: 0;
|
|
height: 26px;
|
|
}
|
|
.day > .period {
|
|
position: absolute;
|
|
bottom: 1px;
|
|
left: 1px;
|
|
right: 1px;
|
|
height: 5px;
|
|
background: red;
|
|
z-index: 10;
|
|
}
|
|
.day > .mucus {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
min-height: 10px;
|
|
background: #abecff7d;
|
|
z-index: 2;
|
|
}
|
|
.day > .notes {
|
|
|
|
}
|
|
|
|
.pill-container {
|
|
width: 100%;
|
|
}
|
|
.pill {
|
|
width: calc(100% - 8px);
|
|
min-height: 2px;
|
|
margin: 0 4px;
|
|
box-sizing: border-box;
|
|
display: inline-block;
|
|
background: rgb(50 218 255 / 44%);
|
|
border-radius: 40px;
|
|
text-align: center;
|
|
line-height: 1em;
|
|
position: relative;
|
|
color: white;
|
|
font-size: 0.7em;
|
|
padding: 2px;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
}
|
|
.pill.did-last {
|
|
margin-left: 0;
|
|
border-top-left-radius: 0;
|
|
border-bottom-left-radius: 0;
|
|
width: calc(100% - 5px);
|
|
}
|
|
.pill.did-next {
|
|
margin-right: 0;
|
|
border-top-right-radius: 0;
|
|
border-bottom-right-radius: 0;
|
|
width: calc(100% - 5px);
|
|
}
|
|
.pill.did-next.did-last {
|
|
width: 100%;
|
|
}
|
|
/* .last-high:after {
|
|
content: '';
|
|
width: 0;
|
|
height: 0;
|
|
border-top: 15px solid transparent;
|
|
border-bottom: 3px solid transparent;
|
|
border-left: 10px solid rgb(50 218 255 / 44%);
|
|
position: absolute;
|
|
left: 0;
|
|
top: -13px;
|
|
}
|
|
.next-high:before {
|
|
content: '';
|
|
width: 0;
|
|
height: 0;
|
|
border-top: 15px solid transparent;
|
|
border-bottom: 3px solid transparent;
|
|
border-right: 10px solid rgb(50 218 255 / 44%);
|
|
position: absolute;
|
|
right: 0;
|
|
top: -13px;
|
|
}*/
|
|
.big-day {
|
|
display: inline-block;
|
|
width: 100%;
|
|
min-height: 2px;
|
|
margin: 0 auto;
|
|
text-align: center;
|
|
}
|
|
.zero-day {
|
|
opacity: 0.5;
|
|
}
|
|
.icon-spacer {
|
|
display: inline-block;
|
|
background-color: greenyellow;
|
|
width: 20px;
|
|
height: 2px;
|
|
}
|
|
|
|
.past-entries {
|
|
width: 100%;
|
|
display: flex;
|
|
justify-content: space-around;
|
|
/* padding: 0 10px;*/
|
|
overflow-x: scroll;
|
|
overflow-y: hidden;
|
|
}
|
|
.past-entry {
|
|
position: relative;
|
|
text-align: center;
|
|
border: 1px solid;
|
|
border-color: var(--dark_border_color);
|
|
color: var(--text_color);
|
|
flex-grow: 1;
|
|
cursor: pointer;
|
|
font-weight: bold;
|
|
min-width: 40px;
|
|
min-height: 40px;
|
|
margin: 5px 0 10px;
|
|
line-height: 2.3em;
|
|
}
|
|
|
|
.day-list {
|
|
width: 100%;
|
|
height: 80px;
|
|
background-color: green;
|
|
display: flex;
|
|
justify-content: space-around;
|
|
overflow-x: scroll;
|
|
overflow-y: hidden;
|
|
}
|
|
.day-list-item {
|
|
flex-grow: 1;
|
|
border: 1px solid black;
|
|
width: 25px;
|
|
}
|
|
|
|
.pill.red { background-color: #db2828 }
|
|
.pill.orange { background-color: #f2711c }
|
|
.pill.yellow { background-color: #fbbd08 }
|
|
.pill.olive { background-color: #b5cc18 }
|
|
.pill.green { background-color: #21ba45 }
|
|
.pill.teal { background-color: #00b5ad }
|
|
.pill.blue { background-color: #2185d0 }
|
|
.pill.violet { background-color: #6435c9 }
|
|
.pill.purple { background-color: #a333c8 }
|
|
.pill.pink { background-color: #e03997 }
|
|
.pill.brown { background-color: #a5673f }
|
|
.pill.grey { background-color: #767676 }
|
|
.pill.black { background-color: #1b1c1d }
|
|
|
|
</style>
|
|
|
|
<template>
|
|
<div>
|
|
<div class="calendar">
|
|
|
|
<div v-for="day in calendar.weekdays" class="day">
|
|
{{ day }}
|
|
</div>
|
|
<div v-for="day in calendar.days" class="day"
|
|
:class="{
|
|
'today':day == calendar.today,
|
|
'active-entry':calendar.dateCode == `${day}.${calendar.month}.${calendar.year}`,
|
|
'has-data':cycleData[`${day}.${calendar.month}.${calendar.year}`],
|
|
'no-data':showDayDataColor(day),
|
|
}">
|
|
<!-- v-on:click="openDayData(`${day}.${calendar.month}.${calendar.year}`)" -->
|
|
<span class="number">{{ day }}</span>
|
|
<!-- {{ `${day}.${calendar.month}.${calendar.year}` }} -->
|
|
|
|
|
|
|
|
<template v-for="(entry, dateCode) in getChartData" :key="dateCode">
|
|
<span class="pill-container" v-if="dateCode == `${day}.${calendar.month}.${calendar.year}`">
|
|
<template v-for="(dayData, fieldId) in entry" :key="fieldId">
|
|
<span
|
|
v-if="showZeroValuesCheck(dayData.value, fieldId)"
|
|
class="pill"
|
|
:class="[getFieldColor(fieldId), {
|
|
'did-next':dayData.didNext,
|
|
'did-last':dayData.didLast,
|
|
'last-high':dayData.lastHigh,
|
|
'next-high':dayData.nextHigh,
|
|
}]">
|
|
<!-- 'zero-day':dayData.value == lowestGraphValue, -->
|
|
<!-- <i v-if="dayData.value != 0" :class="`tiny ${getFieldColor(fieldId)} ${getFieldIcon(fieldId)} icon`"></i> -->
|
|
<!-- <span v-else class="icon-spacer"></span>
|
|
:style="{height:(Math.round(dayData.value*5)+'px')}"
|
|
|
|
-->
|
|
<span v-if="dayData.value > lowestGraphValue-1" class="big-day">
|
|
<i v-if="!hideIcons" :class="`tiny white ${getFieldIcon(fieldId)} icon`"></i>
|
|
<span v-if="!hideValues">
|
|
{{ getDayValue(fieldId, dayData.value) }}
|
|
</span>
|
|
</span>
|
|
</span>
|
|
</template>
|
|
</span>
|
|
</template>
|
|
<!-- <span v-for="fieldId in graph.fieldIds"></span> -->
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
|
|
|
|
// let chartData = {}
|
|
|
|
export default {
|
|
inject: ['getFieldColor', 'getFieldIcon'],
|
|
props: [
|
|
'graph', // options associated with this graph
|
|
'userFields', // all field attributes
|
|
'tempChartDays', // number of days to display
|
|
'cycleData', // all users metric data
|
|
'editGraphs', // display additional edit options
|
|
// Graph options
|
|
'showZeroValues', // Hide graph data with value of zero
|
|
'showTextValues', // Show button text or button value
|
|
'connectDays', // Calculates next and previous day connections.
|
|
'hideValues', // Hide all values on the graph
|
|
'hideIcons', // option to hide icons
|
|
],
|
|
data: function(){
|
|
return {
|
|
openModel:true,
|
|
calendar: {
|
|
dateObject: null,
|
|
dateCode: null,
|
|
monthName: '',
|
|
dayName:'',
|
|
daysAgo:0,
|
|
month: '',
|
|
year: '',
|
|
days: [],
|
|
weekdays: ['S','M','T','W','T','F','S'],
|
|
today: 0,
|
|
},
|
|
chartDateCodes: [], // array of date codes in chart
|
|
listDateCodes: [],
|
|
dayList: true,
|
|
lowestGraphValue: 0,
|
|
|
|
|
|
}
|
|
},
|
|
mounted(){
|
|
this.setupCalendar(new Date())
|
|
},
|
|
computed: {
|
|
getChartData(){
|
|
|
|
let chartData = {}
|
|
let chartValues = []
|
|
|
|
// iterate every day in month by day code
|
|
this.chartDateCodes.forEach((chartDayCode, codeIndex) => {
|
|
|
|
// lookup data for that day
|
|
const cycleDayData = this.cycleData[chartDayCode]
|
|
|
|
// if chart data is set for this day
|
|
if( cycleDayData && Object.keys(cycleDayData).length > 0){
|
|
chartData[chartDayCode] = {}
|
|
|
|
// go over each field to be displayed on graph
|
|
this.graph.fieldIds.forEach((graphFieldId) => {
|
|
|
|
if( cycleDayData[graphFieldId] == undefined ){
|
|
return
|
|
}
|
|
|
|
// track all chart values
|
|
chartValues.push(cycleDayData[graphFieldId])
|
|
|
|
chartData[chartDayCode][graphFieldId] = {
|
|
didLast: false,
|
|
lastHigh: false,
|
|
didNext: false,
|
|
nextHigh: false,
|
|
value: cycleDayData[graphFieldId]
|
|
}
|
|
})
|
|
|
|
}
|
|
})
|
|
|
|
this.lowestGraphValue = Math.min(...chartValues)
|
|
|
|
// determine next and previous states for display
|
|
this.chartDateCodes.forEach((chartDayCode, codeIndex) => {
|
|
if(chartData[chartDayCode] && this.connectDays){
|
|
|
|
const previousDateCode = this.chartDateCodes[codeIndex-1]
|
|
const nextDateCode = this.chartDateCodes[codeIndex+1]
|
|
|
|
Object.keys(chartData[chartDayCode]).forEach((graphFieldId) => {
|
|
|
|
const currentValue = chartData[chartDayCode][graphFieldId].value
|
|
|
|
// check for previous entry
|
|
if( chartData[previousDateCode] && chartData[previousDateCode][graphFieldId] ){
|
|
|
|
chartData[chartDayCode][graphFieldId].didLast = true
|
|
|
|
// set low value flag
|
|
const lastHigh = chartData[previousDateCode][graphFieldId].value > 0
|
|
chartData[chartDayCode][graphFieldId].lastHigh = lastHigh && currentValue == 0
|
|
}
|
|
|
|
// check for next entry
|
|
if( chartData[nextDateCode] && chartData[nextDateCode][graphFieldId] ){
|
|
|
|
chartData[chartDayCode][graphFieldId].didNext = true
|
|
|
|
// set low value flag
|
|
const nextHigh = chartData[nextDateCode][graphFieldId].value > 0
|
|
chartData[chartDayCode][graphFieldId].nextHigh = nextHigh && currentValue == 0
|
|
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
// console.log(chartData)
|
|
|
|
return chartData
|
|
},
|
|
},
|
|
methods: {
|
|
showZeroValuesCheck(dayValue, fieldId){
|
|
|
|
// if graph type is boolean or there are two options
|
|
let isBooleanField = this.userFields[fieldId].type == 'boolean'
|
|
if(this.userFields[fieldId].customOptions){
|
|
let options = this.userFields[fieldId].customOptions
|
|
|
|
isBooleanField = options.split(',').length == 2
|
|
}
|
|
|
|
if(isBooleanField && !this.showZeroValues){
|
|
|
|
const parsedValue = this.getDayValue(fieldId, dayValue)
|
|
if(parsedValue == 'Yes'){
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
|
|
}
|
|
|
|
|
|
return this.showZeroValues || dayValue > this.lowestGraphValue
|
|
},
|
|
getDayValue(fieldId, value){
|
|
|
|
if( !this.showTextValues ){
|
|
return value
|
|
}
|
|
|
|
let options = 'error, Yes, No'
|
|
|
|
if(this.userFields[fieldId].customOptions){
|
|
options = this.userFields[fieldId].customOptions
|
|
}
|
|
|
|
|
|
|
|
const values = options.split(',')
|
|
const selection = String(values[value]).trim()
|
|
|
|
return selection
|
|
},
|
|
displayDayFromCode(dateCode){
|
|
|
|
const parts = dateCode.split('.')
|
|
return `${parts[0]}`
|
|
},
|
|
showDayDataColor(day){
|
|
// Determine if day has any data set
|
|
if(day == ''){
|
|
return false
|
|
}
|
|
return !(this.cycleData[`${day}.${this.calendar.month}.${this.calendar.year}`])
|
|
},
|
|
generateDateCode(date){
|
|
|
|
const dateSetup = [
|
|
date.getDate(), // 1-31 (Day)
|
|
date.getMonth()+1, // 0-11 (Month)
|
|
date.getFullYear(), // 1888-2022 (Year)
|
|
]
|
|
|
|
return dateSetup.join('.')
|
|
},
|
|
setupCalendar(date){
|
|
|
|
// visualize each day change
|
|
this.working = true
|
|
setTimeout(() => {
|
|
this.working = false
|
|
}, 500)
|
|
|
|
if(!date && this.dateObject){
|
|
date = this.dateObject
|
|
}
|
|
if(!date){
|
|
date = new Date()
|
|
}
|
|
|
|
this.calendar.dateObject = date
|
|
|
|
this.calendar.dateCode = this.generateDateCode(date)
|
|
|
|
// calculate days ago since current date
|
|
const now = new Date()
|
|
const diffSeconds = Math.floor((now - date) / 1000) // subtract unix timestamps, convert MS to S
|
|
const dayInterval = diffSeconds / 86400 // seconds in a day
|
|
this.calendar.daysAgo = Math.floor(dayInterval)
|
|
|
|
|
|
|
|
// ------------
|
|
// setup calendar display
|
|
var y = date.getFullYear()
|
|
var m = date.getMonth()
|
|
|
|
var firstDay = new Date(y, m, 1);
|
|
var lastDay = new Date(y, m + 1, 0);
|
|
|
|
function getDaysInMonth(year, month) {
|
|
return new Date(year, month, 0).getDate();
|
|
}
|
|
|
|
const currentYear = date.getFullYear();
|
|
const currentMonth = date.getMonth() + 1;
|
|
this.calendar.monthName = date.toLocaleString("en-US", { month: "long" });
|
|
this.calendar.dayName = date.toLocaleString("en-US", { weekday: "long" });
|
|
this.calendar.year = currentYear
|
|
const daysInCurrentMonth = getDaysInMonth(currentYear, currentMonth);
|
|
|
|
const monthStartDay = firstDay.getDay()
|
|
let days = Array(monthStartDay).fill(""); // Pad days to start on correct weekday
|
|
for (let i = 0; i < daysInCurrentMonth; i++) {
|
|
days.push(i+1)
|
|
}
|
|
this.calendar.days = days
|
|
|
|
// set today
|
|
this.calendar.today = date.getDate()
|
|
this.calendar.month = date.getMonth()+1
|
|
|
|
// setup date codes for key matching on calendar
|
|
this.calendar.days.forEach((day) => {
|
|
if( day !== "" ){
|
|
let dateDay = new Date(y, m, day);
|
|
let dayCode = this.generateDateCode(dateDay)
|
|
this.chartDateCodes.push(dayCode)
|
|
}
|
|
})
|
|
|
|
// generate past date codes for list
|
|
for (let i = 0; i < this.tempChartDays; i++) {
|
|
|
|
const now = new Date()
|
|
const pastDate = now.setDate(now.getDate() - i)
|
|
const pastDateObj = new Date(pastDate)
|
|
const newCode = this.generateDateCode(pastDateObj)
|
|
this.listDateCodes.push(newCode)
|
|
}
|
|
|
|
|
|
// return codes.reverse()
|
|
|
|
|
|
/*
|
|
October 2022
|
|
S M T W T F S
|
|
1 2 3 4 5 6
|
|
7 8 9
|
|
*/
|
|
|
|
// -------
|
|
},
|
|
}
|
|
}
|
|
</script> |