added times to calendar; added counters; improved element structure and styles slightly;
This commit is contained in:
parent
55df8e8dad
commit
781ef76416
6 changed files with 150 additions and 34 deletions
56
src/lib/components/counters.svelte
Normal file
56
src/lib/components/counters.svelte
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export let actions = []; // Array of actions passed to the component
|
||||||
|
|
||||||
|
// Stores for each action type
|
||||||
|
let awakeMinutes = writable(0);
|
||||||
|
let asleepMinutes = writable(0);
|
||||||
|
let foodMinutes = writable(0);
|
||||||
|
let diaperMinutes = writable(0);
|
||||||
|
let poopDays = writable(0);
|
||||||
|
|
||||||
|
const getLastActionTime = (type) => {
|
||||||
|
const lastAction = actions.filter(action => action.type === type).pop();
|
||||||
|
return lastAction ? new Date(lastAction.created) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateDiffMinutes = (date) => {
|
||||||
|
const now = new Date();
|
||||||
|
return date ? Math.floor((now - date) / 60000) : null; // Convert milliseconds to minutes
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateDiffDays = (date) => {
|
||||||
|
const now = new Date();
|
||||||
|
return date ? Math.floor((now - date) / (1000 * 60 * 60 * 24)) : null; // Convert milliseconds to days
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateTimes = () => {
|
||||||
|
awakeMinutes.set(calculateDiffMinutes(getLastActionTime("awake")));
|
||||||
|
asleepMinutes.set(calculateDiffMinutes(getLastActionTime("asleep")));
|
||||||
|
foodMinutes.set(calculateDiffMinutes(getLastActionTime("food")));
|
||||||
|
diaperMinutes.set(calculateDiffMinutes(getLastActionTime("diaper")));
|
||||||
|
poopDays.set(calculateDiffDays(getLastActionTime("poop")));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update immediately when the component has access to actions
|
||||||
|
updateTimes();
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const interval = setInterval(updateTimes, 1000); // Update every minute
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval); // Cleanup on component destruction
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="column">
|
||||||
|
{#if $awakeMinutes !== null && $awakeMinutes < $asleepMinutes}<p>Awake for {$awakeMinutes} minutes</p>{/if}
|
||||||
|
{#if $asleepMinutes !== null && $asleepMinutes < $awakeMinutes}<p>Sleeping for {$asleepMinutes} minutes</p>{/if}
|
||||||
|
{#if $foodMinutes !== null}<p>Ate {$foodMinutes} minutes ago</p>{/if}
|
||||||
|
{#if $diaperMinutes !== null}<p>Diaper changed {$diaperMinutes} minutes ago</p>{/if}
|
||||||
|
{#if $poopDays === 0}<p>Pooped today</p>{:else if $poopDays !== null}<p>Pooped {$poopDays} days ago</p>{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
@ -18,12 +18,23 @@
|
||||||
<header>
|
<header>
|
||||||
<h1>Coover Tracker</h1>
|
<h1>Coover Tracker</h1>
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/">Home</a>
|
<ul>
|
||||||
{#if $currentUser}
|
<li><a href="/">Home</a></li>
|
||||||
<a href="/stats">Stats</a>
|
{#if $currentUser}
|
||||||
<a href="/calendar">Calendar</a>
|
<li><a href="/stats">Stats</a></li>
|
||||||
{/if}
|
<li><a href="/calendar">Calendar</a></li>
|
||||||
<a href="/signin">{ $currentUser ? $currentUser.username : 'Sign In'}</a>
|
{/if}
|
||||||
|
<li>
|
||||||
|
<a href="/signin">
|
||||||
|
{#if $currentUser}
|
||||||
|
<strong>( {$currentUser.username} )</strong>
|
||||||
|
{:else}
|
||||||
|
'Sign In'
|
||||||
|
{/if}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { pb, currentUser } from '$lib/pocketbase';
|
import { pb, currentUser } from '$lib/pocketbase';
|
||||||
import { user } from '$lib/userStore';
|
import Counters from '$lib/components/counters.svelte';
|
||||||
|
|
||||||
type Action = {
|
type Action = {
|
||||||
type: string;
|
type: string;
|
||||||
|
|
@ -39,18 +39,25 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $currentUser}
|
{#if $currentUser}
|
||||||
<div>
|
<aside>
|
||||||
<button on:click={() => recordAction('awake')}>Awake</button>
|
<button on:click={() => recordAction('awake')}>Awake</button>
|
||||||
<button on:click={() => recordAction('asleep')}>Asleep</button>
|
<button on:click={() => recordAction('asleep')}>Asleep</button>
|
||||||
<button on:click={() => recordAction('food')}>Food</button>
|
<button on:click={() => recordAction('food')}>Food</button>
|
||||||
<button on:click={() => recordAction('diaper')}>Diaper</button>
|
<button on:click={() => recordAction('diaper')}>Diaper</button>
|
||||||
<button on:click={() => recordAction('poop')}>Poop</button>
|
<button on:click={() => recordAction('poop')}>Poop</button>
|
||||||
|
</aside>
|
||||||
|
|
||||||
<h2>Today</h2>
|
<section>
|
||||||
<ul>
|
<h2>Today</h2>
|
||||||
{#each actions as action}
|
<div class="container">
|
||||||
<li>{action.type} at {new Date(action.created).toLocaleTimeString('en-NL', { hour: '2-digit', minute: '2-digit' })}</li>
|
<div class="row">
|
||||||
{/each}
|
<ul class="column">
|
||||||
</ul>
|
{#each actions as action}
|
||||||
</div>
|
<li>{action.type} at {new Date(action.created).toLocaleTimeString('en-NL', { hour: '2-digit', minute: '2-digit' })}</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
<Counters {actions} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -34,20 +34,26 @@ import { onMount } from 'svelte';
|
||||||
|
|
||||||
// Generate hours for the grid
|
// Generate hours for the grid
|
||||||
const hours = Array.from({ length: 24 }, (_, i) => i);
|
const hours = Array.from({ length: 24 }, (_, i) => i);
|
||||||
|
|
||||||
|
// Function to format hour in HH:mm format
|
||||||
|
const formatHour = (hour: number) => {
|
||||||
|
return `${hour.toString().padStart(2, '0')}:00`;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.calendar {
|
.calendar {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, 1fr);
|
grid-template-columns: 60px repeat(7, 1fr); /* Adjusted for hour column */
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.day-header {
|
.day-header, .hour-label {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
border: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
.hour {
|
.hour {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
min-height: 60px;
|
height: 60px;
|
||||||
}
|
}
|
||||||
.event {
|
.event {
|
||||||
background-color: lightblue;
|
background-color: lightblue;
|
||||||
|
|
@ -55,21 +61,41 @@ import { onMount } from 'svelte';
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.event--food {
|
||||||
|
background-color: lightgreen;
|
||||||
|
}
|
||||||
|
.event--poop {
|
||||||
|
background-color: burlywood;
|
||||||
|
}
|
||||||
|
.event--asleep {
|
||||||
|
background-color: lightblue;
|
||||||
|
}
|
||||||
|
.event--awake {
|
||||||
|
background-color: gold;
|
||||||
|
}
|
||||||
|
.event--diaper {
|
||||||
|
background-color: lightpink;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="calendar">
|
<div class="calendar">
|
||||||
|
<!-- Empty cell for top-left corner -->
|
||||||
|
<div></div>
|
||||||
|
|
||||||
<!-- Day Headers -->
|
<!-- Day Headers -->
|
||||||
{#each daysOfWeek as day}
|
{#each daysOfWeek as day}
|
||||||
<div class="day-header">{day}</div>
|
<div class="day-header">{day}</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<!-- Calendar Grid -->
|
<!-- Hour Labels and Calendar Grid -->
|
||||||
{#each hours as hour}
|
{#each hours as hour}
|
||||||
|
<div class="hour-label">{formatHour(hour)}</div> <!-- Hour label -->
|
||||||
{#each daysOfWeek as _, dayIndex}
|
{#each daysOfWeek as _, dayIndex}
|
||||||
<div class="hour">
|
<div class="hour">
|
||||||
{#each actions as action}
|
{#each actions as action}
|
||||||
{#if getDayOfWeek(action.created) === dayIndex && getHourOfDay(action.created) === hour}
|
{#if getDayOfWeek(action.created) === dayIndex && getHourOfDay(action.created) === hour}
|
||||||
<div class="event">{action.type}</div>
|
<div class="event event--{action.type}">{action.type}</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@
|
||||||
totalDays += interval;
|
totalDays += interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
return totalDays / (poopDates.length - 1);
|
return parseFloat((totalDays / (poopDates.length - 1)).toFixed(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateAvgMealsPerDay(actions: Action[]): number {
|
function calculateAvgMealsPerDay(actions: Action[]): number {
|
||||||
|
|
@ -174,14 +174,14 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<section>
|
||||||
<h2>Stats</h2>
|
<h2>Stats</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Average Sleep per Nap: {stats.avgSleepPerNap} minutes</li>
|
<li>{stats.avgSleepPerNap} min / nap</li>
|
||||||
<li>Average Naps per Day: {stats.avgNapsPerDay} naps</li>
|
<li>{stats.avgNapsPerDay} naps / day</li>
|
||||||
<li>Average Sleep per Day: {stats.avgSleepPerDay} minutes</li>
|
<li>{stats.avgSleepPerDay} min of sleep / day</li>
|
||||||
<li>Average Diaper Changes per Day: {stats.avgDiaperChanges}</li>
|
<li>{stats.avgDiaperChanges} diapers / day</li>
|
||||||
<li>Average Days Between Poops: {stats.avgPoops}</li>
|
<li>{stats.avgPoops} days between poops</li>
|
||||||
<li>Average Number of Meals per Day: {stats.avgEatingTimes}</li>
|
<li>{stats.avgEatingTimes} meals / day</li>
|
||||||
</ul>
|
</ul>
|
||||||
</main>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,20 @@ button,
|
||||||
input[type="submit"] {
|
input[type="submit"] {
|
||||||
background-color: var(--c-primary);
|
background-color: var(--c-primary);
|
||||||
border-color: var(--c-primary);
|
border-color: var(--c-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul {
|
||||||
|
list-style: circle inside;
|
||||||
|
display: flex;
|
||||||
|
list-style: none;
|
||||||
|
gap: 1em;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
padding-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-top: 4rem;
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue