basical functionality complete
This commit is contained in:
parent
543696dd7c
commit
b5ebf99ff2
10 changed files with 545 additions and 3 deletions
42
package-lock.json
generated
42
package-lock.json
generated
|
|
@ -7,6 +7,11 @@
|
|||
"": {
|
||||
"name": "baby-tracker",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"milligram": "^1.4.1",
|
||||
"pocketbase": "^0.20.3",
|
||||
"svelte-calendar": "^3.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.28.1",
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
|
|
@ -1615,6 +1620,11 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.10",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
|
||||
"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
|
|
@ -2494,6 +2504,11 @@
|
|||
"integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/just-throttle": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/just-throttle/-/just-throttle-2.3.1.tgz",
|
||||
"integrity": "sha512-0H4miIAWZYpnpg7oD/Y/PBb77ISSHAETif5xK9EnwIgYCO6oC8ErkJxDumMUTR44shSOwptRIArRuvNuvN/hOw=="
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
|
|
@ -2650,6 +2665,14 @@
|
|||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/milligram": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/milligram/-/milligram-1.4.1.tgz",
|
||||
"integrity": "sha512-RCgh/boHhcXWOUfKJWm3RJRoUeaEguoipDg0mJ31G0tFfvcpWMUlO1Zlqqr12K4kAXfDlllaidu0x7PaL2PTFg==",
|
||||
"dependencies": {
|
||||
"normalize.css": "~8.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||
|
|
@ -2776,6 +2799,11 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize.css": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz",
|
||||
"integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="
|
||||
},
|
||||
"node_modules/npm-run-path": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz",
|
||||
|
|
@ -3007,6 +3035,11 @@
|
|||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/pocketbase": {
|
||||
"version": "0.20.3",
|
||||
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.20.3.tgz",
|
||||
"integrity": "sha512-qembHhE7HumDBZpxWgFIbhJPeaCoUIdwhW59xF/VlMR79pDTYz/LaQ4q89y7GczKo4X9actFgFN8hs4dTl0spQ=="
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.33",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
|
||||
|
|
@ -3567,6 +3600,15 @@
|
|||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-calendar": {
|
||||
"version": "3.1.6",
|
||||
"resolved": "https://registry.npmjs.org/svelte-calendar/-/svelte-calendar-3.1.6.tgz",
|
||||
"integrity": "sha512-jOHiPlxBAa1LGVFQZoczdAGnCSI2RKcuQQHj32TjLbXO/P2Povx4JphPEcP7XjKj/s/jimBC6xQyFhyjD0vxbg==",
|
||||
"dependencies": {
|
||||
"dayjs": "^1.10.6",
|
||||
"just-throttle": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-check": {
|
||||
"version": "3.6.3",
|
||||
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.6.3.tgz",
|
||||
|
|
|
|||
|
|
@ -34,5 +34,10 @@
|
|||
"vite": "^5.0.3",
|
||||
"vitest": "^1.2.0"
|
||||
},
|
||||
"type": "module"
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"milligram": "^1.4.1",
|
||||
"pocketbase": "^0.20.3",
|
||||
"svelte-calendar": "^3.1.6"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
src/lib/pocketbase.ts
Normal file
18
src/lib/pocketbase.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// import PocketBase from 'pocketbase';
|
||||
|
||||
// const client = new PocketBase('https://pb.altweb.me'); //:8090
|
||||
// export default client;
|
||||
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const pb = new PocketBase('https://pb.altweb.me'); // remote
|
||||
// const pb = new PocketBase('http://127.0.0.1:8090'); // local
|
||||
|
||||
export const currentUser = writable(pb.authStore.model);
|
||||
|
||||
pb.authStore.onChange((auth) => {
|
||||
console.log('authStore changed', auth);
|
||||
currentUser.set(pb.authStore.model);
|
||||
});
|
||||
13
src/lib/userStore.ts
Normal file
13
src/lib/userStore.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { writable } from 'svelte/store';
|
||||
|
||||
interface UserRecord {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface User {
|
||||
record: UserRecord;
|
||||
}
|
||||
|
||||
export const user = writable<User>();
|
||||
35
src/routes/+layout.svelte
Normal file
35
src/routes/+layout.svelte
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<svelte:head>
|
||||
<!-- Google Fonts -->
|
||||
<link rel="stylesheet" href="https://api.fonts.coollabs.io/css?family=Roboto:300,300italic,700,700italic">
|
||||
|
||||
<!-- CSS Reset -->
|
||||
<link rel="stylesheet" href="node_modules/normalize.css/normalize.css">
|
||||
|
||||
<!-- Milligram CSS -->
|
||||
<link rel="stylesheet" href="node_modules/milligram/dist/milligram.min.css">
|
||||
|
||||
<link rel="stylesheet" href="src/styles/app.css">
|
||||
<title>Coover Tracker</title>
|
||||
|
||||
</svelte:head>
|
||||
|
||||
<script lang="ts">
|
||||
import { currentUser } from '$lib/pocketbase';
|
||||
</script>
|
||||
|
||||
|
||||
<main class="container">
|
||||
<header>
|
||||
<h1>Coover Tracker</h1>
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
{#if $currentUser}
|
||||
<a href="/stats">Stats</a>
|
||||
<a href="/calendar">Calendar</a>
|
||||
{/if}
|
||||
<a href="/signin">{ $currentUser ? $currentUser.username : 'Sign In'}</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<slot/>
|
||||
</main>
|
||||
|
|
@ -1,2 +1,56 @@
|
|||
<h1>Welcome to SvelteKit</h1>
|
||||
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { pb, currentUser } from '$lib/pocketbase';
|
||||
import { user } from '$lib/userStore';
|
||||
|
||||
type Action = {
|
||||
type: string;
|
||||
created: string;
|
||||
};
|
||||
|
||||
let actions: Action[] = [];
|
||||
|
||||
onMount(async () => {
|
||||
await fetchActions();
|
||||
});
|
||||
|
||||
async function recordAction(actionType: string) {
|
||||
const action = { // No longer specifying a timestamp here
|
||||
type: actionType,
|
||||
};
|
||||
|
||||
// Send action to PocketBase
|
||||
await pb.collection('actions').create(action);
|
||||
|
||||
// Refresh actions list
|
||||
await fetchActions();
|
||||
}
|
||||
|
||||
async function fetchActions() {
|
||||
// Fetch actions for the current day from PocketBase
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const result = await pb.collection('actions').getList<Action>(1, 50, {
|
||||
filter: `created >= '${today}'`
|
||||
});
|
||||
|
||||
// Assuming the items are in the 'items' property of the result
|
||||
actions = result.items;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $currentUser}
|
||||
<div>
|
||||
<button on:click={() => recordAction('awake')}>Awake</button>
|
||||
<button on:click={() => recordAction('asleep')}>Asleep</button>
|
||||
<button on:click={() => recordAction('food')}>Food</button>
|
||||
<button on:click={() => recordAction('diaper')}>Diaper</button>
|
||||
<button on:click={() => recordAction('poop')}>Poop</button>
|
||||
|
||||
<h2>Today</h2>
|
||||
<ul>
|
||||
{#each actions as action}
|
||||
<li>{action.type} at {new Date(action.created).toLocaleTimeString('en-NL', { hour: '2-digit', minute: '2-digit' })}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
79
src/routes/calendar/+page.svelte
Normal file
79
src/routes/calendar/+page.svelte
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
|
||||
type Action = {
|
||||
type: string;
|
||||
created: string;
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
actions = await fetchAllActions();
|
||||
});
|
||||
|
||||
async function fetchAllActions() {
|
||||
// Fetch all actions. Adjust as needed for date range or pagination
|
||||
const result = await pb.collection('actions').getList(1, 1000);
|
||||
return result.items;
|
||||
}
|
||||
|
||||
let actions: Action[] = [];
|
||||
|
||||
// Function to get the day of the week from a date string
|
||||
const getDayOfWeek = (dateString: string) => {
|
||||
return new Date(dateString).getDay();
|
||||
};
|
||||
|
||||
// Function to get the hour of the day from a date string
|
||||
const getHourOfDay = (dateString: string) => {
|
||||
return new Date(dateString).getHours();
|
||||
};
|
||||
|
||||
// Days of the week for headers
|
||||
const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
|
||||
// Generate hours for the grid
|
||||
const hours = Array.from({ length: 24 }, (_, i) => i);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.calendar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
text-align: center;
|
||||
}
|
||||
.day-header {
|
||||
font-weight: bold;
|
||||
}
|
||||
.hour {
|
||||
border: 1px solid #ccc;
|
||||
min-height: 60px;
|
||||
}
|
||||
.event {
|
||||
background-color: lightblue;
|
||||
border-radius: 4px;
|
||||
padding: 2px;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="calendar">
|
||||
<!-- Day Headers -->
|
||||
{#each daysOfWeek as day}
|
||||
<div class="day-header">{day}</div>
|
||||
{/each}
|
||||
|
||||
<!-- Calendar Grid -->
|
||||
{#each hours as hour}
|
||||
{#each daysOfWeek as _, dayIndex}
|
||||
<div class="hour">
|
||||
{#each actions as action}
|
||||
{#if getDayOfWeek(action.created) === dayIndex && getHourOfDay(action.created) === hour}
|
||||
<div class="event">{action.type}</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
91
src/routes/signin/+page.svelte
Normal file
91
src/routes/signin/+page.svelte
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<script lang="ts">
|
||||
import { pb, currentUser } from '$lib/pocketbase';
|
||||
import { goto } from '$app/navigation';
|
||||
import { user } from '$lib/userStore';
|
||||
|
||||
interface UserData {
|
||||
avatar: ImageData
|
||||
collectionId: string
|
||||
collectionName: string
|
||||
created: string
|
||||
email: string
|
||||
emailVisibility: boolean
|
||||
id: string
|
||||
name: string
|
||||
updated: string
|
||||
username: string
|
||||
verified: boolean
|
||||
}
|
||||
|
||||
let username = '';
|
||||
let email = 'irinavandijk@hotmail.com';
|
||||
let password = '0lt*4ZruRaBAO$qo';
|
||||
let passwordConfirm = '';
|
||||
|
||||
async function login(): Promise<void> {
|
||||
try {
|
||||
const userData = await pb.collection('users').authWithPassword(email, password);
|
||||
|
||||
if (userData) {
|
||||
console.log('Login successful');
|
||||
user.set(userData); // Update the user store
|
||||
goto('/'); // Redirect to home page
|
||||
} else {
|
||||
console.error('Login failed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during login:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function signUp() {
|
||||
try {
|
||||
console.log(email);
|
||||
const user = await pb.collection('users').create({
|
||||
username: username,
|
||||
email,
|
||||
password,
|
||||
passwordConfirm: password
|
||||
});
|
||||
await login();
|
||||
// Redirect to sign-in or dashboard
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function signIn() {
|
||||
try {
|
||||
await login();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
function signOut() {
|
||||
pb.authStore.clear();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $currentUser}
|
||||
<p>
|
||||
Signed in as {$currentUser.username}
|
||||
<button on:click={signOut}>Sign Out</button>
|
||||
</p>
|
||||
{:else}
|
||||
<form on:submit|preventDefault={signUp}>
|
||||
<h1>Sign Up</h1>
|
||||
<input type="text" bind:value={username} placeholder="Username" />
|
||||
<input type="email" bind:value={email} placeholder="Email" />
|
||||
<input type="password" bind:value={password} placeholder="Password" />
|
||||
<input type="password" bind:value={passwordConfirm} placeholder="Confirm Password" />
|
||||
<button type="submit">Sign Up</button>
|
||||
</form>
|
||||
|
||||
<form on:submit|preventDefault={signIn}>
|
||||
<h1>Sign In</h1>
|
||||
<input type="email" bind:value={email} placeholder="Email" />
|
||||
<input type="password" bind:value={password} placeholder="Password" />
|
||||
<button type="submit">Sign In</button>
|
||||
</form>
|
||||
{/if}
|
||||
187
src/routes/stats/+page.svelte
Normal file
187
src/routes/stats/+page.svelte
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
|
||||
type Action = {
|
||||
type: string;
|
||||
created: string;
|
||||
};
|
||||
|
||||
let stats = {
|
||||
avgSleepPerNap: 0,
|
||||
avgNapsPerDay: 0,
|
||||
avgSleepPerDay: 0,
|
||||
avgDiaperChanges: 0,
|
||||
avgPoops: 0,
|
||||
avgEatingTimes: 0
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
const actions = await fetchAllActions();
|
||||
calculateStats(actions);
|
||||
});
|
||||
|
||||
async function fetchAllActions() {
|
||||
// Fetch all actions. Adjust as needed for date range or pagination
|
||||
const result = await pb.collection('actions').getList(1, 1000);
|
||||
return result.items;
|
||||
}
|
||||
|
||||
function calculateStats(actions: Action[]) {
|
||||
// Implement the logic to calculate each statistic
|
||||
// This is a placeholder and should be replaced with actual calculation logic
|
||||
stats.avgSleepPerNap = calculateAvgSleepPerNap(actions);
|
||||
stats.avgNapsPerDay = calculateAvgNapsPerDay(actions);
|
||||
stats.avgSleepPerDay = calculateAvgSleepPerDay(actions);
|
||||
stats.avgDiaperChanges = calculateAvgDiaperChanges(actions);
|
||||
stats.avgPoops = calculateAvgDaysBetweenPoops(actions);
|
||||
stats.avgEatingTimes = calculateAvgMealsPerDay(actions);
|
||||
}
|
||||
|
||||
function calculateAvgSleepPerNap(actions: Action[]): number {
|
||||
const sleepActions = actions.filter((a) => a.type === 'asleep' || a.type === 'awake');
|
||||
sleepActions.sort((a, b) => new Date(a.created).getTime() - new Date(b.created).getTime());
|
||||
|
||||
let totalSleepTime = 0;
|
||||
let napCount = 0;
|
||||
|
||||
for (let i = 0; i < sleepActions.length - 1; i++) {
|
||||
if (sleepActions[i].type === 'asleep' && sleepActions[i + 1].type === 'awake') {
|
||||
totalSleepTime +=
|
||||
new Date(sleepActions[i + 1].created).getTime() -
|
||||
new Date(sleepActions[i].created).getTime();
|
||||
napCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Total Sleep Time:', totalSleepTime);
|
||||
console.log('Nap Count:', napCount);
|
||||
|
||||
return Math.round(napCount > 0 ? totalSleepTime / napCount / 1000 / 60 : 0); // returns average time in minutes
|
||||
}
|
||||
|
||||
function calculateAvgNapsPerDay(actions: Action[]): number {
|
||||
const sortedActions = actions.sort(
|
||||
(a, b) => new Date(a.created).getTime() - new Date(b.created).getTime()
|
||||
);
|
||||
const daysMap = new Map<string, number>();
|
||||
|
||||
for (let i = 0; i < sortedActions.length - 1; i++) {
|
||||
if (sortedActions[i].type === 'asleep' && sortedActions[i + 1].type === 'awake') {
|
||||
const asleepDate = new Date(sortedActions[i].created);
|
||||
const awakeDate = new Date(sortedActions[i + 1].created);
|
||||
|
||||
if (asleepDate.toDateString() === awakeDate.toDateString()) {
|
||||
const dayKey = asleepDate.toDateString();
|
||||
daysMap.set(dayKey, (daysMap.get(dayKey) || 0) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const totalNaps = Array.from(daysMap.values()).reduce((acc, naps) => acc + naps, 0);
|
||||
return daysMap.size > 0 ? totalNaps / daysMap.size : 0;
|
||||
}
|
||||
|
||||
function calculateAvgSleepPerDay(actions: Action[]): number {
|
||||
const sortedActions = actions.sort(
|
||||
(a, b) => new Date(a.created).getTime() - new Date(b.created).getTime()
|
||||
);
|
||||
const sleepDurations: number[] = [];
|
||||
|
||||
for (let i = 0; i < sortedActions.length - 1; i++) {
|
||||
if (sortedActions[i].type === 'asleep' && sortedActions[i + 1].type === 'awake') {
|
||||
const asleepDate = new Date(sortedActions[i].created);
|
||||
const awakeDate = new Date(sortedActions[i + 1].created);
|
||||
|
||||
if (asleepDate.toDateString() === awakeDate.toDateString()) {
|
||||
const sleepDuration = (awakeDate.getTime() - asleepDate.getTime()) / 1000 / 60; // Sleep duration in minutes
|
||||
sleepDurations.push(sleepDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sleepDurations.length === 0) return 0;
|
||||
|
||||
const totalSleepDuration = sleepDurations.reduce((a, b) => a + b, 0);
|
||||
return Math.round(totalSleepDuration / sleepDurations.length);
|
||||
}
|
||||
|
||||
function calculateAvgDiaperChanges(actions: Action[]): number {
|
||||
let diaperChangesPerDay = new Map<string, number>();
|
||||
|
||||
for (const action of actions) {
|
||||
if (action.type === 'diaper') {
|
||||
const actionDate = action.created.split('T')[0]; // Extract the date part
|
||||
diaperChangesPerDay.set(actionDate, (diaperChangesPerDay.get(actionDate) || 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (diaperChangesPerDay.size === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const totalDiaperChanges = Array.from(diaperChangesPerDay.values()).reduce(
|
||||
(total, count) => total + count,
|
||||
0
|
||||
);
|
||||
return totalDiaperChanges / diaperChangesPerDay.size;
|
||||
}
|
||||
|
||||
function calculateAvgDaysBetweenPoops(actions: Action[]): number {
|
||||
let poopDates = [];
|
||||
|
||||
// Extract dates of 'poop' actions
|
||||
for (const action of actions) {
|
||||
if (action.type === 'poop') {
|
||||
const actionDate = new Date(action.created.split('T')[0]); // Extract the date part
|
||||
poopDates.push(actionDate);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort dates
|
||||
poopDates.sort((a, b) => a.getTime() - b.getTime());
|
||||
|
||||
if (poopDates.length < 2) {
|
||||
return 0; // Not enough data to calculate intervals
|
||||
}
|
||||
|
||||
let totalDays = 0;
|
||||
for (let i = 1; i < poopDates.length; i++) {
|
||||
const interval =
|
||||
(poopDates[i].getTime() - poopDates[i - 1].getTime()) / (1000 * 60 * 60 * 24); // Convert to days
|
||||
totalDays += interval;
|
||||
}
|
||||
|
||||
return totalDays / (poopDates.length - 1);
|
||||
}
|
||||
|
||||
function calculateAvgMealsPerDay(actions: Action[]): number {
|
||||
let mealsPerDay = new Map<string, number>();
|
||||
|
||||
for (const action of actions) {
|
||||
if (action.type === 'food') {
|
||||
const actionDate = action.created.split('T')[0]; // Extract the date part
|
||||
mealsPerDay.set(actionDate, (mealsPerDay.get(actionDate) || 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (mealsPerDay.size === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const totalMeals = Array.from(mealsPerDay.values()).reduce((total, count) => total + count, 0);
|
||||
return totalMeals / mealsPerDay.size;
|
||||
}
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<h2>Stats</h2>
|
||||
<ul>
|
||||
<li>Average Sleep per Nap: {stats.avgSleepPerNap} minutes</li>
|
||||
<li>Average Naps per Day: {stats.avgNapsPerDay} naps</li>
|
||||
<li>Average Sleep per Day: {stats.avgSleepPerDay} minutes</li>
|
||||
<li>Average Diaper Changes per Day: {stats.avgDiaperChanges}</li>
|
||||
<li>Average Days Between Poops: {stats.avgPoops}</li>
|
||||
<li>Average Number of Meals per Day: {stats.avgEatingTimes}</li>
|
||||
</ul>
|
||||
</main>
|
||||
18
src/styles/app.css
Normal file
18
src/styles/app.css
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
:root {
|
||||
--c-primary: darkorange; /* Replace #yourColor with your desired color */
|
||||
}
|
||||
|
||||
a,
|
||||
input[type="text"]:focus,
|
||||
input[type="email"]:focus,
|
||||
input[type="password"]:focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
color: var(--c-primary);
|
||||
}
|
||||
|
||||
button,
|
||||
input[type="submit"] {
|
||||
background-color: var(--c-primary);
|
||||
border-color: var(--c-primary);
|
||||
}
|
||||
Loading…
Reference in a new issue