├── data
└── ignore
├── .env
├── .dockerignore
├── .npmrc
├── src
├── app.css
├── lib
│ ├── index.ts
│ ├── stores
│ │ └── store.ts
│ ├── components
│ │ ├── ThemeSelector.svelte
│ │ ├── CreateTag.svelte
│ │ └── TagDistribution.svelte
│ ├── timeframes.ts
│ └── server
│ │ └── db
│ │ └── index.ts
├── routes
│ ├── +page.server.ts
│ ├── +layout.svelte
│ ├── api
│ │ ├── update
│ │ │ └── +server.ts
│ │ ├── weekly
│ │ │ └── +server.ts
│ │ ├── monthly
│ │ │ └── +server.ts
│ │ ├── yearly
│ │ │ └── +server.ts
│ │ ├── daily
│ │ │ └── +server.ts
│ │ └── tags
│ │ │ └── +server.ts
│ └── +page.svelte
├── app.d.ts
└── app.html
├── static
├── borat.png
├── favicon.png
└── on_time.mp3
├── readmeAssests
├── custom.gif
├── analytics.png
└── homeScreen.png
├── postcss.config.js
├── .gitignore
├── docker-compose.yml
├── vite.config.ts
├── tailwind.config.js
├── Dockerfile
├── tsconfig.json
├── svelte.config.js
├── package.json
└── README.md
/data/ignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | DB_PATH = "data/tempus.db"
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .svelte-kit
3 | build
4 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 | resolution-mode=highest
3 |
--------------------------------------------------------------------------------
/src/app.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
--------------------------------------------------------------------------------
/static/borat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrevorSatori/tempus/HEAD/static/borat.png
--------------------------------------------------------------------------------
/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrevorSatori/tempus/HEAD/static/favicon.png
--------------------------------------------------------------------------------
/static/on_time.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrevorSatori/tempus/HEAD/static/on_time.mp3
--------------------------------------------------------------------------------
/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | // place files you want to import through the `$lib` alias in this folder.
2 |
--------------------------------------------------------------------------------
/readmeAssests/custom.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrevorSatori/tempus/HEAD/readmeAssests/custom.gif
--------------------------------------------------------------------------------
/readmeAssests/analytics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrevorSatori/tempus/HEAD/readmeAssests/analytics.png
--------------------------------------------------------------------------------
/readmeAssests/homeScreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrevorSatori/tempus/HEAD/readmeAssests/homeScreen.png
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/stores/store.ts:
--------------------------------------------------------------------------------
1 | import { writable } from "svelte/store";
2 |
3 | // Create a writable store for addTag
4 | export const addTagStore = writable(false);
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env.*
7 | !.env.example
8 | vite.config.js.timestamp-*
9 | vite.config.ts.timestamp-*
10 | data/tempus.db
11 | build
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | app:
4 | image: satoridigital/tempus:latest
5 | ports:
6 | - '9111:9111'
7 | volumes:
8 | - './data:/data'
9 | restart: unless-stopped
--------------------------------------------------------------------------------
/src/routes/+page.server.ts:
--------------------------------------------------------------------------------
1 | import { createDB } from '$lib/server/db';
2 | import type { PageServerLoad } from './$types';
3 |
4 | export const load = (async () => {
5 | createDB();
6 | return {};
7 | }) satisfies PageServerLoad;
--------------------------------------------------------------------------------
/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import { defineConfig } from 'vite';
3 |
4 | export default defineConfig({
5 | plugins: [sveltekit()],
6 | server: {
7 | port: 9111,
8 | strictPort: true,
9 | }
10 | });
11 |
--------------------------------------------------------------------------------
/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://kit.svelte.dev/docs/types#app
2 | // for information about these interfaces
3 | declare global {
4 | namespace App {
5 | // interface Error {}
6 | // interface Locals {}
7 | // interface PageData {}
8 | // interface Platform {}
9 | }
10 | }
11 |
12 | export {};
13 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ['./src/**/*.{html,js,svelte,ts}'],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [require("daisyui")],
8 |
9 |
10 | daisyui: {
11 | themes: ["light", "dark", "synthwave", "luxury", "forest", "dracula"],
12 | },
13 |
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/routes/api/update/+server.ts:
--------------------------------------------------------------------------------
1 | import { addRecord } from '$lib/server/db';
2 | import {json} from '@sveltejs/kit';
3 |
4 | import type { RequestEvent, RequestHandler } from './$types';
5 |
6 | export const POST: RequestHandler = async ({request}) => {
7 |
8 | const {date, selectedTag, totalTime} = await request.json();
9 | addRecord(date, selectedTag, totalTime);
10 |
11 | return new Response();
12 | };
--------------------------------------------------------------------------------
/src/routes/api/weekly/+server.ts:
--------------------------------------------------------------------------------
1 | import { json } from '@sveltejs/kit';
2 | import { getWeekly } from '$lib/server/db';
3 | import type { RequestHandler } from './$types';
4 |
5 | export const GET: RequestHandler = async (request) => {
6 |
7 |
8 | const date = request.url.searchParams.get('date');
9 | let entries;
10 |
11 | if (date !== null){
12 | entries = getWeekly(date);
13 | }
14 |
15 | return json(entries);
16 | };
--------------------------------------------------------------------------------
/src/routes/api/monthly/+server.ts:
--------------------------------------------------------------------------------
1 | import { json } from '@sveltejs/kit';
2 | import { getMonthly } from '$lib/server/db';
3 | import type { RequestHandler } from './$types';
4 |
5 | export const GET: RequestHandler = async (request) => {
6 |
7 | const date = request.url.searchParams.get('date');
8 | let entries;
9 |
10 | if (date !== null){
11 | entries = getMonthly(date);
12 | }
13 |
14 |
15 | return json(entries);
16 | };
--------------------------------------------------------------------------------
/src/routes/api/yearly/+server.ts:
--------------------------------------------------------------------------------
1 | import { json } from '@sveltejs/kit';
2 | import { getYearly } from '$lib/server/db';
3 | import type { RequestHandler } from './$types';
4 |
5 | export const GET: RequestHandler = async (request) => {
6 |
7 |
8 | const date = request.url.searchParams.get('date');
9 | let entries;
10 |
11 | if (date !== null){
12 | entries = getYearly(date);
13 | }
14 |
15 | return json(entries);
16 | };
--------------------------------------------------------------------------------
/src/routes/api/daily/+server.ts:
--------------------------------------------------------------------------------
1 | import type { RequestHandler } from './$types';
2 | import { getDaily } from '$lib/server/db';
3 | import { json } from '@sveltejs/kit';
4 |
5 |
6 |
7 | export const GET: RequestHandler = async (request) => {
8 |
9 | const date = request.url.searchParams.get('date');
10 | let entries;
11 |
12 | if (date !== null){
13 | entries = getDaily(date);
14 | }
15 |
16 |
17 | return json(entries);
18 | };
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:lts-slim as builder
2 |
3 | WORKDIR /app
4 |
5 | COPY . .
6 |
7 | RUN echo "DB_PATH=/data/tempus.db" > .env
8 |
9 | RUN npm install
10 |
11 | RUN npm run build
12 |
13 | FROM node:lts-slim
14 |
15 | WORKDIR /app
16 |
17 | COPY --from=builder /app/build /app/build
18 | COPY --from=builder /app/package.json /app/package-lock.json /app/
19 |
20 | RUN npm install
21 |
22 | ENV PORT=9111
23 |
24 | EXPOSE 9111
25 |
26 | CMD ["node", "build/index.js"]
--------------------------------------------------------------------------------
/src/routes/api/tags/+server.ts:
--------------------------------------------------------------------------------
1 | import { json } from '@sveltejs/kit';
2 | import { addTag, getAllTags } from '$lib/server/db';
3 | import type { RequestHandler } from './$types';
4 |
5 |
6 | export const GET: RequestHandler = async () => {
7 |
8 | const allTags = getAllTags();
9 | return json(allTags);
10 | };
11 |
12 |
13 | export const POST: RequestHandler = async ({request}) => {
14 |
15 | const newTag = await request.json();
16 | addTag(newTag.newTag);
17 |
18 | return new Response();
19 | };
--------------------------------------------------------------------------------
/src/lib/components/ThemeSelector.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true
12 | }
13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
14 | //
15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
16 | // from the referenced tsconfig.json - TypeScript does not merge them in
17 | }
18 |
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-node';
2 | import { vitePreprocess } from '@sveltejs/kit/vite';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors
7 | // for more information about preprocessors
8 | preprocess: vitePreprocess(),
9 |
10 | kit: {
11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
12 | // If your environment is not supported or you settled on a specific environment, switch out the adapter.
13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters.
14 | adapter: adapter(),
15 |
16 | }
17 | };
18 |
19 | export default config;
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tempus",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite dev",
7 | "build": "vite build",
8 | "preview": "vite preview",
9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
11 | "start": "npm run dev"
12 | },
13 | "devDependencies": {
14 | "@sveltejs/adapter-auto": "^2.0.0",
15 | "@sveltejs/adapter-node": "^1.3.1",
16 | "@sveltejs/kit": "^1.20.4",
17 | "@types/better-sqlite3": "^7.6.4",
18 | "@types/d3": "^7.4.0",
19 | "autoprefixer": "^10.4.14",
20 | "daisyui": "^3.5.0",
21 | "postcss": "^8.4.27",
22 | "svelte": "^4.0.5",
23 | "svelte-check": "^3.4.3",
24 | "tailwindcss": "^3.3.3",
25 | "tslib": "^2.4.1",
26 | "typescript": "^5.0.0",
27 | "vite": "^4.4.2"
28 | },
29 | "type": "module",
30 | "dependencies": {
31 | "better-sqlite3": "^8.5.0",
32 | "d3": "^7.8.5",
33 | "theme-change": "^2.5.0"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/lib/components/CreateTag.svelte:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 |
31 |
38 | {#if isValidTagInput && newTag.length > 0}
39 |
40 | {:else}
41 |
Only letters and spaces can be used for a tag.
42 | {/if}
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/lib/timeframes.ts:
--------------------------------------------------------------------------------
1 | export function getRollingSevenDayPeriod(currentDay: string){
2 | const today = new Date(currentDay);
3 | const sevenDaysAgo = new Date(today);
4 | sevenDaysAgo.setDate(today.getDate() - 6);
5 |
6 | return {
7 | sevenDaysAgo: sevenDaysAgo.toISOString().split('T')[0],
8 | today: today.toISOString().split('T')[0],
9 | };
10 | }
11 |
12 | export function getFirstAndLastDateOfCurrentMonth(month: string): { firstDate: string; lastDate: string } {
13 |
14 | // get first day of month
15 | const today = new Date(month);
16 | const firstDate = new Date(today.getFullYear(), today.getMonth(), 1).toISOString().split('T')[0];
17 |
18 | // get last day of month
19 | const lastDate = new Date(today.getFullYear(), today.getMonth() + 1, 1).toISOString().split('T')[0];
20 | return { firstDate, lastDate };
21 |
22 | }
23 |
24 | export function getFirstAndLastDateOfYear(year: string): { firstDate: string; lastDate: string } {
25 | const firstDate = new Date(year);
26 | firstDate.setMonth(0); // January
27 | firstDate.setDate(0); // First day of the year
28 |
29 | const lastDate = new Date(year);
30 | lastDate.setFullYear(lastDate.getFullYear()); // Current year
31 | lastDate.setMonth(11); // December
32 | lastDate.setDate(31); // Last day of the year
33 |
34 | return {
35 | firstDate: firstDate.toISOString().split('T')[0],
36 | lastDate: lastDate.toISOString().split('T')[0],
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/src/lib/components/TagDistribution.svelte:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
Tag Distribution
33 |
35 |
36 |
37 |
38 | {#each tagData as item}
39 |
40 | | {item.label} |
41 | {item.value}% |
42 |
43 | {#if convertData(item.time_focused)[1] === 0}
44 | {convertData(item.time_focused)[0]} M
45 | {:else}
46 | {convertData(item.time_focused)[1]} H {convertData(item.time_focused)[0]} M
47 | {/if}
48 | |
49 |
50 | {/each}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🚀 Boost Your Productivity with our Self-Hosted Study Tracker! 📚
2 |
3 | Are you tired of constantly juggling different productivity apps, only to find none of them truly cater to your study needs? Look no further – we're thrilled to introduce our powerful Self-Hosted Study Tracker, designed to revolutionize your study routine and help you excel like never before.
4 |
5 | 
6 |
7 |
8 | # 🌟 Why Choose Our Self-Hosted Study Tracker?
9 |
10 | ✅ Total Control: Hosting your own instance means you're in complete control of your data. No more worries about your information being stored on external servers – it's all on your terms.
11 |
12 | ✅ Tailored to You: Our study tracker is fully customizable to fit your unique workflow. From session durations and study goals to advanced analytics, everything can be tailored to meet your preferences.
13 |
14 | ✅ Privacy Matters: Your study data is sensitive – that's why our self-hosted solution ensures your information remains private and secure at all times.
15 |
16 | ✅ Community Support: Join a vibrant community of like-minded learners and enthusiasts. Share tips, tricks, and ideas to enhance your study strategy even further.
17 |
18 |
19 | # Installation Guide
20 |
21 | Discover seamless ways to install and unleash the potential of our productivity manager on your server. Choose the method that suits you best and take control of your study routine like never before.
22 |
23 | ## Docker Installation
24 |
25 | 1. **Retrieve the Latest Docker Image**
26 |
27 | Open your terminal and enter:
28 |
29 | ```sh
30 | docker pull satoridigital/tempus:latest
31 | ```
32 |
33 | 2. **Configure a docker-compose file**
34 |
35 | ```yaml
36 | version: '3'
37 | services:
38 | app:
39 | image: satoridigital/tempus:latest
40 | ports:
41 | - '9111:9111'
42 | volumes:
43 | - './data:/data'
44 | restart: unless-stopped
45 | ```
46 |
47 | 3. **Run & Enjoy**
48 |
49 | ```sh
50 | docker-compose up
51 | ```
52 |
53 |
54 |
55 | alternatively if you want to run on homeserver in daemon mode
56 |
57 | ```sh
58 | docker-compose up -d
59 | ```
60 |
61 | ### Build from source
62 |
63 |
64 | 1. Clone the repo
65 | ```sh
66 | git clone https://github.com/TrevorSatori/tempus.git
67 | ```
68 |
69 | 2. change directory to the repo
70 | ```sh
71 | cd tempus
72 | ```
73 |
74 | 3. Install the dependencies
75 | ```sh
76 | npm install
77 | ```
78 |
79 | 4.build an optimized version of the project
80 | ```sh
81 | npm run build
82 | ```
83 |
84 | 5. run and enjoy
85 | ```sh
86 | npm run start
87 | ```
88 |
89 |
90 | ## Analytics
91 |
92 | 
93 |
94 |
95 | ## Customizable
96 |
97 | 
98 |
--------------------------------------------------------------------------------
/src/lib/server/db/index.ts:
--------------------------------------------------------------------------------
1 | import { DB_PATH } from '$env/static/private';
2 | import Database from 'better-sqlite3';
3 | import * as fs from 'fs';
4 | import { getRollingSevenDayPeriod, getFirstAndLastDateOfCurrentMonth, getFirstAndLastDateOfYear } from '$lib/timeframes';
5 |
6 | // if database file doesn't exist, creates it. Else connection is made.
7 | let db: any;
8 |
9 | // if Database has not been created, create one. For debug { verbose: console.log }
10 | export function createDB(){
11 |
12 | if (!fs.existsSync(DB_PATH)){
13 | db = new Database(DB_PATH,);
14 | const base_partition = db.prepare("Create TABLE focus (id INTEGER PRIMARY KEY, session datetime default current_timestamp, tag_id TEXT, time_focused INTEGER);");
15 | const tagsTable = db.prepare("Create TABLE tags (id INTEGER PRIMARY KEY, name TEXT UNIQUE);");
16 | base_partition.run();
17 | tagsTable.run();
18 | addTag("Work");
19 | addTag("Study");
20 | } else {
21 | db = new Database(DB_PATH);
22 | }
23 | }
24 |
25 |
26 | // add time studied to database
27 | export function addRecord(session: Date, tag_id: string, time_focused: number){
28 | const stmt = db.prepare("INSERT INTO focus (session, tag_id, time_focused) VALUES (?, ?, ?)");
29 | stmt.run(session, tag_id, time_focused);
30 | }
31 |
32 |
33 | export function getDaily(day: string){
34 |
35 | // const day = new Date().toISOString().split('T')[0]
36 | const stmt = db.prepare("SELECT session, time_focused, tag_id FROM focus WHERE date(session) = ?");
37 | let entries = stmt.all(day);
38 |
39 | return entries;
40 | }
41 |
42 |
43 | export function getWeekly(currentDay: string){
44 |
45 | const {sevenDaysAgo, today} = getRollingSevenDayPeriod(currentDay);
46 | const stmt = db.prepare("SELECT session, time_focused, tag_id FROM focus WHERE date(session) >= ? AND date(session) <= ?");
47 | let entries = stmt.all(sevenDaysAgo, today);
48 |
49 | return entries;
50 | }
51 |
52 |
53 | export function getMonthly(month: string){
54 |
55 |
56 | const { firstDate, lastDate } = getFirstAndLastDateOfCurrentMonth(month);
57 | const stmt = db.prepare("SELECT session, time_focused, tag_id FROM focus WHERE date(session) >= ? AND date(session) < ?");
58 | let entries = stmt.all(firstDate, lastDate);
59 | return entries;
60 | }
61 |
62 |
63 |
64 | export function getYearly(year: string){
65 |
66 | const {firstDate, lastDate} = getFirstAndLastDateOfYear(year);
67 | const stmt = db.prepare("SELECT session, time_focused, tag_id FROM focus WHERE date(session) >= ? AND date(session) < ?");
68 | let entries = stmt.all(firstDate, lastDate);
69 |
70 | return entries;
71 | }
72 |
73 |
74 |
75 | export function addTag(name: string){
76 | const stmt = db.prepare("INSERT INTO tags (name) VALUES (?)");
77 | stmt.run(name);
78 | }
79 |
80 |
81 | // returns all tags in database
82 | export function getAllTags(){
83 | const stmt = db.prepare("SELECT name from tags");
84 | let tags = stmt.all();
85 |
86 | return tags;
87 | }
88 |
--------------------------------------------------------------------------------
/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 | {#each tags as tag}
431 |
432 |
433 |
434 | - selectedTag = tag.name}>{tag.name}
435 | {/each}
436 |
437 |
438 |
439 |
440 |
441 | {#if isTimer && totalTime === 0 && showAlert}
442 |
443 |
444 |
Your session has been completed!
445 |
446 | {/if}
447 |
448 | {#if ($addTagStore === true)}
449 |
450 | {/if}
451 |
452 |
453 |
454 |
455 |
456 |
457 |

463 |
464 |
465 |
{selectedTag}
466 |
467 | {#if isTimer}
468 |
469 | :
470 | :
471 |
472 |
473 | {:else}
474 |
475 | :
476 | :
477 |
478 |
479 | {/if}
480 | {#if totalTime < (60 * 5) && isFocused}
481 |
(Sessions less than five minutes won't be recorded)
482 | {/if}
483 |
484 |
485 | {#if !isFocused}
486 |
487 |
Stopwatch
488 |
489 |
Timer
490 |
491 |
492 | {#if isTimer}
493 |
494 | {/if}
495 | {/if}
496 |
497 | {#if !isFocused}
498 |
499 | {:else if (isFocused && totalTime !== 0) || (isFocused && !isTimer)}
500 |
501 | {:else if isFocused && totalTime === 0 && isTimer}
502 |
503 | {/if}
504 |
505 |
506 |
507 |
508 | {#if !isFocused && wannaSee}
509 |
510 |
511 |
512 |
513 |
514 |
515 |
518 | {#if selectedAnalysis === Analysis.Daily}
519 |
{selectedDate.toISOString().split('T')[0]}
520 | {:else if selectedAnalysis === Analysis.Weekly}
521 |
{startTimeFrame} - {endTimeFrame}
522 | {:else if selectedAnalysis === Analysis.Monthly}
523 |
{selectedDate.toLocaleDateString('en-US', {month: 'short', year: 'numeric'})}
524 | {:else if selectedAnalysis === Analysis.Yearly}
525 |
{selectedDate.toLocaleDateString('en-US', {year: 'numeric'})}
526 | {/if}
527 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 | {#if hoursAnalytics < 1}
541 |
You've studied a total of {minutesAnalytics} minutes over {sessions} sessions
542 | {:else}
543 |
You've studied a total of {hoursAnalytics} hours {minutesAnalytics - (hoursAnalytics * 60)} minutes over {sessions} sessions
544 | {/if}
545 |
546 | {/if}
547 |
548 |
562 |
--------------------------------------------------------------------------------