-
{{ field.label }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Calendar Explanation
+
+
+
+ 1
+
+
+ Period in Progress
+
-
+
+ 1
+
+
+
+
+ Amount of cervical mucus
+
-
+
+
+
+
+
+
+
+
+ Additional Notes
+
+
+
+
+ Follicular phase
+ Luteal phase
+
+
+ Ovulation is detected by sustained temperate elevation.
+ Ovulation can only be determined in hindsight, as progesterone is what makes the temperatures rise, and progesterone is only produced in abundance post-ovulation.
+
+
+ Some people may find that their temperature drops the day before or the day their period arrives. This is a very helpful sign in knowing that your period is on it’s way!
+
+
+ About halfway through your menstrual cycle, your hormones tell one of your ovaries to release a mature egg — this is called ovulation. Most people don’t feel it when they ovulate, but some ovulation symptoms are bloating, spotting, or a little pain in your lower belly that you may only feel on one side.
+
+
+ If you do get pregnant, your body needs the lining — that’s why your period stops during pregnancy. Your period comes back when you’re not pregnant anymore.
+
+
+
+
+
+
+
+
+
@@ -103,24 +425,50 @@
import axios from 'axios'
+ var BASAL_TEMP = 'BT'
+
export default {
- name: 'CycleTracking',
+ name: 'MetricTracking',
components: {
'logo':require('@/components/LogoComponent.vue').default,
},
data () {
return {
- appWorkingDate: null,
+ folded:[],
+ working: true,
+ showNotes: false,
fields:[],
- defaultFields:[
- {'type':'float','label':'Basil Temp', 'id':'BT'},
- {'type':'range','label':'Cervical Mucus', 'id':'CM'},
- {'type':'text','label':'Notes', 'id':'NO'},
- ],
+ fieldTypes:{
+ 'float':'Precise Number',
+ 'shortRange':'Options 1-5',
+ 'longRange':'Options 1-10',
+ 'text':'Text Input',
+ 'boolean':'Yes, No',
+ 'period':'No, 1. Light, 2. Normal, 3. Heavy, 4. Irregular, 5. Painful',
+ 'mucus':'None, 1. Watery, 2. Eggwhite, 3. Creamy, 4. Sticky',
+ 'sex':'None, With contraception, Without contraception',
+ 'pms':'No, 1. Maybe, 2. A little, 3. Real Cranky',
+ },
+ defaultFields:{
+ 'BT': {'type':'float','label':'Basal Temp','icon':'thermometer half','width':'sixteen wide column'},
+ 'CM': {'type':'mucus','label':'Cervical Mucus','icon':'','width':'eight wide column'},
+ 'PE': {'type':'period','label':'Having Period','icon':'','width':'eight wide column'},
+ 'SE': {'type':'sex','label':'Sex','icon':'','width':'eight wide column'},
+ 'NO': {'type':'text','label':'Notes','icon':'','width':'sixteen wide column'},
+ 'OV': {'type':'boolean','label':'Suspect Ovulation','icon':'','width':''},
+ 'PM': {'type':'pms','label':'PMS','icon':'','width':''},
+ 'HO': {'type':'boolean','label':'Horny','icon':'','width':''},
+ 'DI': {'type':'text','label':'Diet','icon':'','width':''},
+ 'EX': {'type':'text','label':'Exercise','icon':'','width':''},
+ },
cycleData: {},
- dateCode: null,
- openDay: [],
+ totalEntries: 0,
+ openDay: {},
+ saveDataDebounce:null,
+ saving: 0, // 0 blank, 1 modified, 2 saving, 3 saved
calendar: {
+ dateObject: null,
+ dateCode: null,
monthName: '',
month: '',
year: '',
@@ -128,6 +476,7 @@
weekdays: ['S','M','T','W','T','F','S'],
today: 0,
},
+ tempChartDays: 60,
}
},
beforeCreate() {
@@ -141,25 +490,178 @@
return
}
- // setup date code
- // day - month - year
- const now = new Date()
- this.appWorkingDate = now
- const dateSetup = [
- now.getDate(), // 1-31 (Day)
- now.getMonth()+1, // 0-11 (Month)
- now.getFullYear(), // 1888-2022 (Year)
- ]
+ // set up reactive open day object
+ Object.keys(this.defaultFields).forEach(fieldId => {
+ this.$set(this.openDay, fieldId, '')
+ })
- this.dateCode = dateSetup.join('.')
+ // Include JS libraries
+ let graphs = document.createElement('script')
+ graphs.setAttribute('src', '//cdnjs.cloudflare.com/ajax/libs/dygraph/2.1.0/dygraph.min.js')
+ document.head.appendChild(graphs)
- this.setupCalendar(this.appWorkingDate)
+ // setup date to today
+ this.setupCalendar()
this.fetchCycleData()
},
+ computed: {
+ getToday(){
+ return this.generateDateCode(new Date())
+ },
+ getPreviousDay(){
+ const workingDate = this.calendar.dateObject || new Date()
+ workingDate.setDate(workingDate.getDate()-1)
+
+ return this.generateDateCode(workingDate)
+ },
+ getNextDay(){
+ const workingDate = this.calendar.dateObject || new Date()
+ workingDate.setDate(workingDate.getDate()+1)
+
+ return this.generateDateCode(workingDate)
+ },
+ getNextMonth(){
+ const workingDate = this.calendar.dateObject || new Date()
+ workingDate.setMonth(workingDate.getMonth() +1)
+
+ return this.generateDateCode(workingDate)
+ },
+ getPreviousMonth(){
+ const workingDate = this.calendar.dateObject || new Date()
+ workingDate.setMonth(workingDate.getMonth() -1)
+
+ return this.generateDateCode(workingDate)
+ },
+ },
methods: {
+ toggleFolded(key){
+ const index = this.folded.indexOf(key)
+ if(index == -1){
+ this.folded.push(key)
+ return
+ }
+
+ this.folded.splice(index,1)
+ },
+ showDayDataColor(day){
+ // Determine if day has any data set
+ if(day == ''){
+ return false
+ }
+ return !(this.cycleData[`${day}.${this.calendar.month}.${this.calendar.year}`])
+ },
+ isPeriod(day){
+ const data = this.cycleData[`${day}.${this.calendar.month}.${this.calendar.year}`]
+ if(data?.PE > 0){
+ return true
+ }
+ },
+ isSex(day){
+ const data = this.cycleData[`${day}.${this.calendar.month}.${this.calendar.year}`]
+ return data?.SE
+ },
+ isMucus(day){
+ const data = this.cycleData[`${day}.${this.calendar.month}.${this.calendar.year}`]
+ return data?.CM
+ },
+ isNotes(day){
+ const data = this.cycleData[`${day}.${this.calendar.month}.${this.calendar.year}`]
+ return data?.NO
+ },
+ isTemp(day){
+ const data = this.cycleData[`${day}.${this.calendar.month}.${this.calendar.year}`]
+ return data?.BT
+ },
+ fieldRemove(field){
+ for (let i = this.fields.length - 1; i >= 0; i--) {
+ if(field == this.fields[i]){
+ this.fields.splice(i,1)
+ break
+ }
+ }
+ this.saveCycleData()
+ },
+ fieldAdd(fieldId){
+ this.fields.push(fieldId)
+ this.saveCycleData()
+ },
+ graphCurrentData(){
+
+ // CSV or path to a CSV file.
+ let dataString = "Date,Temperature,Average\n"
+
+ // Excel date format YYYYMMDD
+ const convertToExcelDate = (dateCode) => {
+ return dateCode
+ .split('.')
+ .reverse()
+ .map(item => String(item).padStart(2,0))
+ .join('')
+ }
+
+ const dataKeys = Object.keys(this.cycleData)
+
+ // calculate average
+ let average = 0.0
+ let totalTemps = 0
+ for (var i = 0; i < dataKeys.length; i++) {
+ const current = this.cycleData[dataKeys[i]]
+ if(current.BT){
+ average += parseFloat(current.BT)
+ totalTemps++
+ }
+ }
+ average = (average/totalTemps)
+
+ // build CSV data
+ for (var i = 0; i < dataKeys.length; i++) {
+ const current = this.cycleData[dataKeys[i]]
+ let nextFragment = []
+
+ // push date code
+ nextFragment.push(convertToExcelDate(dataKeys[i]))
+
+ if(current.BT){
+ // parse temp to fixed length float 00.00
+ nextFragment.push(parseFloat(current.BT).toFixed(2))
+ } else {
+ continue
+ }
+
+ nextFragment.push(average)
+
+ dataString += nextFragment.join(',') + "\n"
+
+ if(i >= this.tempChartDays){
+ break
+ }
+ }
+
+ let graphDiv = document.getElementById("graphdiv")
+ const graphOptions = {
+ animatedZoom: true,
+ }
+
+ const g = new Dygraph(graphDiv, dataString ,graphOptions)
+ },
saveField(fieldId, value){
- this.openDay[fieldId] = value
+
+ // Dont save value if it hasn't changed
+ if(this.openDay[fieldId] == value){ return }
+
+ // update field to be reactive
+ this.$set(this.openDay, fieldId, value)
+
+ // remove debounce and set to modified
+ this.$nextTick(() => {
+ //0 blank, 1 modified, 2 saving, 3 saved
+ this.saving = 1
+ clearTimeout(this.saveDataDebounce)
+ this.saveDataDebounce = setTimeout(() => {
+ this.saveDayData()
+ }, 500)
+ })
},
openDayData(dateCode){
@@ -168,16 +670,14 @@
return
}
- this.dateCode = dateCode || this.dateCode
+ this.setupCalendar(this.dateCodeToDate(dateCode))
- let currentDay = this.cycleData[this.dateCode] || {}
-
- //Set up each entry empty or with current value
- this.fields.forEach(field => {
- currentDay[field.id] = currentDay[field.id] || ''
+ // open day has all fields defined, just set values
+ let currentDay = this.cycleData[dateCode] || {}
+ Object.keys(this.openDay).forEach(fieldId => {
+ this.openDay[fieldId] = currentDay[fieldId] || ''
})
-
- this.openDay = currentDay
+
},
saveDayData(){
@@ -185,15 +685,18 @@
// remove empty keys
let cleanDayData = {}
Object.keys(this.openDay).forEach(key => {
- if(this.openDay[key] != ''){
+ if(this.openDay[key] != '' && this.openDay[key] != 0){
cleanDayData[key] = this.openDay[key]
}
})
- this.cycleData[this.dateCode] = cleanDayData
+ // Only save entry if there is data
+ delete this.cycleData[this.calendar.dateCode]
+ if(Object.keys(cleanDayData).length > 0){
+ this.cycleData[this.calendar.dateCode] = cleanDayData
+ }
- // Update calendar
- this.setupCalendar(this.appWorkingDate)
+ this.graphCurrentData()
this.saveCycleData()
@@ -211,14 +714,20 @@
console.log('Didnt parse json')
}
- console.log(appData)
+ // console.clear()
+ // console.log(appData)
this.cycleData = appData?.cycleData || {}
this.fields = appData?.fields || []
this.$nextTick(() => {
+ this.totalEntries = Object.keys(this.cycleData).length
this.setupFields()
- this.openDayData(this.dateCode)
+ this.openDayData(this.calendar.dateCode)
+
+ this.graphCurrentData()
+
+ this.generateTonsOfRandomData()
})
}
})
@@ -231,9 +740,17 @@
fields: this.fields,
cycleData: this.cycleData,
})
+
+ // 0 blank, 1 modified, 2 saving, 3 saved
+ this.saving = 2 // Working
+ this.totalEntries = Object.keys(this.cycleData).length
axios.post('/api/cycle-tracking/save', { cycleData:appData })
.then(response => {
- { this.$bus.$emit('notification', 'Data Saved') }
+ // { this.$bus.$emit('notification', 'Data Saved') }
+ this.saving = 3 //Saved
+ setTimeout(() => {
+ this.saving = 0 //Reset
+ }, 2000)
})
.catch(error => { this.$bus.$emit('notification', error) })
},
@@ -241,18 +758,54 @@
axios.post('/api/cycle-tracking/save', { cycleData:'' })
.then(response => {
{ this.$bus.$emit('notification', 'Data Deleted') }
+ this.fetchCycleData()
})
},
setupFields(){
// push the first 3 default fields to users set
if(this.fields.length == 0){
- for (let i = 0; i < 3; i++) {
- this.fields.push(this.defaultFields[i])
+ const fieldKeys = Object.keys(this.defaultFields)
+ console.log('Setup default fierds')
+ for (let i = 0; i < 5; i++) {
+ this.fields.push(fieldKeys[i])
}
}
},
+ generateDateCode(date){
+
+ const dateSetup = [
+ date.getDate(), // 1-31 (Day)
+ date.getMonth()+1, // 0-11 (Month)
+ date.getFullYear(), // 1888-2022 (Year)
+ ]
+
+ return dateSetup.join('.')
+ },
+ dateCodeToDate(dateCode){
+
+ const dateChunk = dateCode.split('.')
+ return new Date(dateChunk[2], dateChunk[1]-1, dateChunk[0])
+ },
setupCalendar(date){
+
+ 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)
+
+
// ------------
// setup calendar display
var y = date.getFullYear()
@@ -272,7 +825,7 @@
const daysInCurrentMonth = getDaysInMonth(currentYear, currentMonth);
const monthStartDay = firstDay.getDay()
- let days = Array(monthStartDay).fill("."); // Pad days to start on correct weekday
+ let days = Array(monthStartDay).fill(""); // Pad days to start on correct weekday
for (let i = 0; i < daysInCurrentMonth; i++) {
days.push(i+1)
}
@@ -291,7 +844,33 @@
*/
// -------
- }
+ },
+ generateTonsOfRandomData(){
+
+ return
+
+ let workingDate = new Date()
+
+ for (var i = 0; i < 365 * 2; i++) {
+
+ const cycleTime = (i%30)+1
+ const randomInt = Math.floor(Math.random() * cycleTime+20) + (cycleTime);
+ let randomTemp = parseFloat(`97.${randomInt}`)
+ const randomFive = Math.floor(Math.random() * 4) + 0;
+ const randomHundo = Math.floor(Math.random() * 100) + 1;
+ const randUnoOrDuo = Math.floor(Math.random() * 2) + 1;
+
+ this.cycleData[this.generateDateCode(workingDate)] = {
+ 'BT':randomTemp,
+ 'CM':randomFive,
+ 'SE':randomHundo > 90 ? randUnoOrDuo : 0,
+ }
+
+ workingDate.setDate(workingDate.getDate()-1)
+ }
+
+ this.graphCurrentData(5000)
+ },
}
}
\ No newline at end of file
diff --git a/client/src/stores/mainStore.js b/client/src/stores/mainStore.js
index c24dec8..37d698e 100644
--- a/client/src/stores/mainStore.js
+++ b/client/src/stores/mainStore.js
@@ -48,7 +48,7 @@ export default new Vuex.Store({
'small_element_bg_color': '#000',
'text_color': '#FFF',
'dark_border_color': '#555',//'#ACACAC', //Lighter color to accent elemnts user can interact with
- 'border_color': '#0b0110',
+ 'border_color': '#505050',
'menu-accent': '#626262',
'menu-text': '#d9d9d9',
},
diff --git a/configs/dev nginx sites available default.cfg b/configs/dev nginx sites available default.cfg
index 623dad0..104b445 100644
--- a/configs/dev nginx sites available default.cfg
+++ b/configs/dev nginx sites available default.cfg
@@ -2,12 +2,22 @@
# Working dev server config
#
+server {
+ listen 80;
+ listen [::]:80;
+ server_name 192.168.1.164;
+ return 301 https://$host$request_uri;
+}
+
+
server {
listen 443 ssl;
- ssl_certificate /home/mab/ss/client/certs/192.168.1.164+4.pem;
- ssl_certificate_key /home/mab/ss/client/certs/192.168.1.164+4-key.pem;
+ ssl_certificate /home/mab/ss/client/certs/nginx-selfsigned.crt;
+ ssl_certificate_key /home/mab/ss/client/certs/nginx-selfsigned.key;
+ ssl_dhparam /home/mab/ss/client/certs/dhparam.pem;
+
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_protocols TLSV1.1 TLSV1.2 TLSV1.3;
@@ -67,77 +77,6 @@ server {
}
-##
-## Working Copy below --------------------------------------------
-##
-
-server {
-
- listen 443 ssl;
-
- ssl_certificate /home/mab/ss/client/certs/192.168.1.164+4.pem;
- ssl_certificate_key /home/mab/ss/client/certs/192.168.1.164+4-key.pem;
- ssl_session_cache shared:SSL:1m;
- ssl_session_timeout 5m;
- ssl_protocols TLSV1.1 TLSV1.2 TLSV1.3;
-
- ssl_ciphers HIGH:!aNULL:!MD5;
- ssl_prefer_server_ciphers on;
-
- access_log /var/log/nginx/httpslocalhost.access.log;
- error_log /var/log/nginx/httpslocalhost.error.log;
-
- client_max_body_size 20M;
-
- location / {
- proxy_pass https://127.0.0.1:8081;
- proxy_set_header Host localhost;
- proxy_set_header X-Forwarded-Host localhost;
- proxy_set_header X-Forwarded-Server localhost;
- proxy_set_header X-Forwarded-Proto $scheme;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $remote_addr;
- proxy_redirect off;
- proxy_connect_timeout 90s;
- proxy_read_timeout 90s;
- proxy_send_timeout 90s;
- proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
- }
-
- location /sockjs-node {
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header Host $http_host;
- proxy_set_header X-NginX-Proxy true;
-
- proxy_pass https://127.0.0.1:8081;
- proxy_redirect off;
- proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
- }
-
- location /api {
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header Host $http_host;
- proxy_set_header X-NginX-Proxy true;
-
- proxy_pass http://127.0.0.1:3000;
- proxy_redirect off;
- proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
- }
-
- location /socket {
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header Host $http_host;
- proxy_set_header X-NginX-Proxy true;
-
- proxy_pass http://127.0.0.1:3001;
- proxy_redirect off;
- proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
- }
-
-}
# Prod settings to serve static index
diff --git a/server/models/Note.js b/server/models/Note.js
index a842648..ad1f97a 100644
--- a/server/models/Note.js
+++ b/server/models/Note.js
@@ -994,7 +994,8 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
LEFT JOIN tag ON (tag.id = note_tag.tag_id)
LEFT JOIN attachment ON (note.id = attachment.note_id AND attachment.visible = 1)
LEFT JOIN user as shareUser ON (note.share_user_id = shareUser.id)
- WHERE note.user_id = ?
+ WHERE note.user_id = ?
+ AND note.quick_note <= 1
`
//If text search returned results, limit search to those ids