From a7198cd1d539eb7185065080b3ace9c4a96e8620 Mon Sep 17 00:00:00 2001
From: Brieuc Dubois <git@bhasher.com>
Date: Wed, 13 Nov 2024 03:45:36 +0200
Subject: [PATCH] First sketch of home page

---
 frontend/package-lock.json                    |  17 +-
 frontend/package.json                         |   1 +
 frontend/src/app.css                          |   9 +
 frontend/src/lang/fr.json                     |  17 +-
 .../lib/components/users/weeklySurvey.svelte  |   7 +-
 frontend/src/lib/services/i18n.ts             |   8 +-
 frontend/src/lib/utils/date.ts                |  44 ++++-
 frontend/src/routes/+page.svelte              | 173 ++++++++----------
 8 files changed, 158 insertions(+), 118 deletions(-)

diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 85e434b8..0fe6f39d 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -8,6 +8,7 @@
 			"name": "frontend",
 			"version": "0.0.1",
 			"dependencies": {
+				"@sveltekit-i18n/parser-icu": "^1.0.8",
 				"dayjs": "^1.11.13",
 				"emoji-picker-element": "^1.23.0",
 				"linkify-html": "^4.1.3",
@@ -655,7 +656,6 @@
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz",
 			"integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==",
-			"dev": true,
 			"license": "MIT",
 			"dependencies": {
 				"@formatjs/intl-localematcher": "0.5.4",
@@ -666,7 +666,6 @@
 			"version": "2.2.0",
 			"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz",
 			"integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==",
-			"dev": true,
 			"license": "MIT",
 			"dependencies": {
 				"tslib": "^2.4.0"
@@ -676,7 +675,6 @@
 			"version": "2.7.8",
 			"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz",
 			"integrity": "sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==",
-			"dev": true,
 			"license": "MIT",
 			"dependencies": {
 				"@formatjs/ecma402-abstract": "2.0.0",
@@ -688,7 +686,6 @@
 			"version": "1.8.2",
 			"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz",
 			"integrity": "sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==",
-			"dev": true,
 			"license": "MIT",
 			"dependencies": {
 				"@formatjs/ecma402-abstract": "2.0.0",
@@ -699,7 +696,6 @@
 			"version": "0.5.4",
 			"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz",
 			"integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==",
-			"dev": true,
 			"license": "MIT",
 			"dependencies": {
 				"tslib": "^2.4.0"
@@ -1439,6 +1435,15 @@
 			"dev": true,
 			"license": "MIT"
 		},
+		"node_modules/@sveltekit-i18n/parser-icu": {
+			"version": "1.0.8",
+			"resolved": "https://registry.npmjs.org/@sveltekit-i18n/parser-icu/-/parser-icu-1.0.8.tgz",
+			"integrity": "sha512-/LnvE1EJv+higIxB5cWIV+9neiOe+CfC7VKhpv9mnU35NcZO3yOhEZ8y6F8nHHkMYIABLcqr15yk4hSvmRGWDw==",
+			"license": "MIT",
+			"dependencies": {
+				"intl-messageformat": "^10.1.1"
+			}
+		},
 		"node_modules/@types/cookie": {
 			"version": "0.6.0",
 			"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
@@ -3326,7 +3331,6 @@
 			"version": "10.5.14",
 			"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.14.tgz",
 			"integrity": "sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==",
-			"dev": true,
 			"license": "BSD-3-Clause",
 			"dependencies": {
 				"@formatjs/ecma402-abstract": "2.0.0",
@@ -5327,7 +5331,6 @@
 			"version": "2.8.1",
 			"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
 			"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
-			"dev": true,
 			"license": "0BSD"
 		},
 		"node_modules/type": {
diff --git a/frontend/package.json b/frontend/package.json
index 786f5fa7..6de874ce 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -46,6 +46,7 @@
 	},
 	"type": "module",
 	"dependencies": {
+		"@sveltekit-i18n/parser-icu": "^1.0.8",
 		"dayjs": "^1.11.13",
 		"emoji-picker-element": "^1.23.0",
 		"linkify-html": "^4.1.3",
diff --git a/frontend/src/app.css b/frontend/src/app.css
index fac86420..124f1564 100644
--- a/frontend/src/app.css
+++ b/frontend/src/app.css
@@ -26,3 +26,12 @@
 .input {
 	@apply border-2 border-gray-300 rounded-md p-2;
 }
+
+.no-scrollbar::-webkit-scrollbar {
+	display: none;
+}
+
+.no-scrollbar {
+	-ms-overflow-style: none; /* IE and Edge */
+	scrollbar-width: none; /* Firefox */
+}
diff --git a/frontend/src/lang/fr.json b/frontend/src/lang/fr.json
index c2bd5c0e..fa3c6049 100644
--- a/frontend/src/lang/fr.json
+++ b/frontend/src/lang/fr.json
@@ -225,10 +225,10 @@
 				"title": "Questionnaire hebdomadaire",
 				"description": "Au cours des 7 derniers jours...",
 				"questions": [
-					"Combien d'heures de <span class='font-bold'>cours</span> de {TARGET_LANGUAGE} avez vous suivies ?",
-					"Combien d'heures avez-vous <span class='font-bold'>regardé des vidéos</span> en {TARGET_LANGUAGE} (films, séries, Youtube...) ou <span class='font-bold'>écouté des contenus</span> en {TARGET_LANGUAGE} (podcasts, radio, cours universitaires...) ?",
-					"Combien d'heures avez-vous <span class='font-bold'>lu des textes</span> en {TARGET_LANGUAGE} (livre, journal, BD, sites web...) ?",
-					"Combien d'heures avez-vous <span class='font-bold'>parlé</span> en {TARGET_LANGUAGE} (discussions avec amis, famille, collègues...) ?"
+					"Combien d'heures de <span class='font-bold'>cours</span> de {lang} avez vous suivies ?",
+					"Combien d'heures avez-vous <span class='font-bold'>regardé des vidéos</span> en {lang} (films, séries, Youtube...) ou <span class='font-bold'>écouté des contenus</span> en {lang} (podcasts, radio, cours universitaires...) ?",
+					"Combien d'heures avez-vous <span class='font-bold'>lu des textes</span> en {lang} (livre, journal, BD, sites web...) ?",
+					"Combien d'heures avez-vous <span class='font-bold'>parlé</span> en {lang} (discussions avec amis, famille, collègues...) ?"
 				],
 				"answers": {
 					"placeholder": "",
@@ -302,6 +302,15 @@
 			"5": "Samedi",
 			"6": "Dimanche"
 		},
+		"past": {
+			"year": "Il y a {n, plural, one {# an} other {# ans}}",
+			"month": "Il y a {n, plural, one {# mois} other {# mois}}",
+			"day": "Il y a {n, plural, one {# jour} other {# jours}}",
+			"hour": "Il y a {n, plural, one {# heure} other {# heures}}",
+			"today": "Aujourd'hui",
+			"yesterday": "Hier",
+			"justNow": "Il y a moins d'une heure"
+		},
 		"language": {
 			"fr": "Français",
 			"en": "Anglais",
diff --git a/frontend/src/lib/components/users/weeklySurvey.svelte b/frontend/src/lib/components/users/weeklySurvey.svelte
index 76790f85..8143e783 100644
--- a/frontend/src/lib/components/users/weeklySurvey.svelte
+++ b/frontend/src/lib/components/users/weeklySurvey.svelte
@@ -49,10 +49,9 @@
 			<label class="form-control w-full">
 				<div class="label">
 					<span class="label-text"
-						>{@html $t('session.modal.weekly.questions.' + i).replaceAll(
-							'{TARGET_LANGUAGE}',
-							$t('utils.language.' + $user?.target_language).toLowerCase()
-						)}</span
+						>{@html $t('session.modal.weekly.questions.' + i, {
+							lang: $t('utils.language.' + $user?.target_language).toLowerCase()
+						})}</span
 					>
 				</div>
 				<select id={'questions-' + i} class="select select-bordered">
diff --git a/frontend/src/lib/services/i18n.ts b/frontend/src/lib/services/i18n.ts
index 7158bc8f..f5e350b0 100644
--- a/frontend/src/lib/services/i18n.ts
+++ b/frontend/src/lib/services/i18n.ts
@@ -1,6 +1,12 @@
-import i18n, { type Config } from 'sveltekit-i18n';
+import i18n from '@sveltekit-i18n/base';
+import parser from '@sveltekit-i18n/parser-icu';
+
+import type { Config } from '@sveltekit-i18n/parser-icu';
 
 const config: Config = {
+	parser: parser({
+		ignoreTag: true
+	}),
 	loaders: [
 		{
 			locale: 'en',
diff --git a/frontend/src/lib/utils/date.ts b/frontend/src/lib/utils/date.ts
index c8de2aa1..5e791ed0 100644
--- a/frontend/src/lib/utils/date.ts
+++ b/frontend/src/lib/utils/date.ts
@@ -72,19 +72,21 @@ export function displayTime(date: Date): string {
 		now.getFullYear() === date.getFullYear() &&
 		now.getMonth() === date.getMonth()
 	) {
-		return hours + ':' + minutes;
+		return ('0' + hours).slice(-2) + ':' + ('0' + minutes).slice(-2);
 	}
 
 	const day = date.getDate().toString();
 	const month = getFullMonth(date.getMonth());
 
 	if (now.getFullYear() === date.getFullYear()) {
-		return day + ' ' + month + ' ' + hours + ':' + minutes;
+		return day + ' ' + month + ' ' + ('0' + hours).slice(-2) + ':' + ('0' + minutes).slice(-2);
 	}
 
 	const year = date.getFullYear().toString();
 
-	return day + ' ' + month + ' ' + year + ' ' + hours + ':' + minutes;
+	return (
+		day + ' ' + month + ' ' + year + ' ' + ('0' + hours).slice(-2) + ':' + ('0' + minutes).slice(-2)
+	);
 }
 
 export function displayDuration(start: Date, end: Date): string | null {
@@ -106,3 +108,39 @@ export function parseToLocalDate(dateStr: string): Date {
 export function formatToUTCDate(date: Date): string {
 	return date.toISOString().split('Z')[0];
 }
+
+export function displayShortTime(date: Date): string {
+	return (
+		('0' + date.getDate()).slice(-2) +
+		'/' +
+		('0' + (date.getMonth() + 1)).slice(-2) +
+		'/' +
+		date.getFullYear() +
+		' ' +
+		('0' + date.getHours()).slice(-2) +
+		':' +
+		('0' + date.getMinutes()).slice(-2)
+	);
+}
+
+export function displayTimeSince(date: Date): string {
+	const now = new Date();
+	const diff = now.getTime() - date.getTime();
+	const seconds = Math.floor(diff / 1000);
+	const minutes = Math.floor(seconds / 60);
+	const hours = Math.floor(minutes / 60);
+	const days = Math.floor(hours / 24);
+	const months = Math.floor(days / 30);
+	const years = Math.floor(months / 12);
+	if (years > 0) {
+		return get(t)('utils.past.year', { n: years });
+	} else if (months > 0) {
+		return get(t)('utils.past.month', { n: months });
+	} else if (days === 1) {
+		return get(t)('utils.past.yesterday');
+	} else if (days > 0) {
+		return get(t)('utils.past.day', { n: days });
+	} else {
+		return get(t)('utils.past.today');
+	}
+}
diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte
index 5acdde5d..07ddf577 100644
--- a/frontend/src/routes/+page.svelte
+++ b/frontend/src/routes/+page.svelte
@@ -1,13 +1,15 @@
 <script lang="ts">
 	import Session from '$lib/types/session';
 	import { onMount } from 'svelte';
-	import { displayTime } from '$lib/utils/date';
+	import { displayShortTime, displayTime, displayTimeSince } from '$lib/utils/date';
 	import {
 		AcademicCap,
 		Sparkles,
 		Icon,
 		User as UserIcon,
-		MagnifyingGlass
+		MagnifyingGlass,
+		ArrowRight,
+		ArrowRightCircle
 	} from 'svelte-hero-icons';
 	import { t } from '$lib/services/i18n';
 	import User, { user } from '$lib/types/user';
@@ -34,7 +36,9 @@
 			return;
 		}
 
-		contactSessions = Session.parseAll(await getUserContactSessionsAPI($user!.id, contact.id));
+		contactSessions = Session.parseAll(await getUserContactSessionsAPI($user!.id, contact.id)).sort(
+			(a, b) => b.start_time.getTime() - a.start_time.getTime()
+		);
 	}
 
 	onMount(async () => {
@@ -103,7 +107,9 @@
 					return;
 				}
 				toastSuccess(get(t)('home.bookingSuccessful'));
-				contactSessions = Session.parseAll(await getUserContactSessionsAPI($user!.id, contact.id));
+				contactSessions = Session.parseAll(
+					await getUserContactSessionsAPI($user!.id, contact.id)
+				).sort((a, b) => b.start_time.getTime() - a.start_time.getTime());
 			}
 		});
 	});
@@ -113,7 +119,9 @@
 		let session = await Session.create();
 		if (!session) return;
 		await session.addUser(contact);
-		contactSessions = [...contactSessions, session];
+		contactSessions = [...contactSessions, session].sort(
+			(a, b) => b.start_time.getTime() - a.start_time.getTime()
+		);
 	}
 
 	async function searchNickname() {
@@ -136,105 +144,72 @@
 </svelte:head>
 
 {#if ready}
-	<div class="h-full w-full flex">
-		<ul class="h-full [width:_clamp(200px,25%,500px)] overflow-y-scroll border-r-2 flex flex-col">
-			<div class="flex-grow">
-				{#each contacts as c (c.id)}
-					<li
-						class="h-20 flex border-gray-300 border-b-2 hover:bg-gray-200 hover:cursor-pointer"
-						class:bg-gray-200={c.id === contact?.id}
-						on:click={() => selectContact(c)}
-						role="button"
-						aria-label={c.nickname}
-						tabindex="0"
-						on:keydown={(e) => e.key === 'Enter' && selectContact(c)}
-					>
-						<div class="w-16 ml-2 mr-4 p-4 avatar bg-gray-300 mask-squircle mask">
-							{#if c.type == 0}
-								<Icon src={Sparkles} class="mask mask-squircle" />
-							{:else if c.type == 1}
-								<Icon src={AcademicCap} />
-							{:else}
-								<Icon src={UserIcon} />
-							{/if}
-						</div>
-						<div class="flex items-center text-lg">
-							{c.nickname}
-						</div>
-					</li>
-				{/each}
+	<div class="flex-col flex p-4 lg:w-[64rem] lg:mx-auto">
+		{#if contact}
+			<div>
+				<button on:click|preventDefault={createSession} class="button float-start mr-2">
+					{$t('home.createSession')}
+				</button>
+				<button
+					class="button float-start"
+					class:btn-disabled={!contact || !contact.calcom_link}
+					data-cal-link={`${contact.calcom_link}?email=${$user?.email}&name=${$user?.nickname}`}
+				>
+					{$t('home.bookSession')}
+				</button>
 			</div>
-			<button
-				class="h-20 w-full flex justify-center items-center text-lg border-gray-200 border-t hover:bg-gray-200"
-				on:click={() => (modalNew = true)}
+			<div
+				class="border p-4 mt-4 rounded-xl shadow-[0_0_6px_0_rgba(0,14,156,.2)] overflow-y-scroll no-scrollbar"
 			>
-				+
-			</button>
-		</ul>
-		<div class="flex-grow flex-col flex">
-			{#if contact}
-				<div class="p-4 pr-8">
-					<button on:click|preventDefault={createSession} class="button float-end">
-						{$t('home.createSession')}
-					</button>
-					<div class="size-4 float-end"></div>
-					<button
-						class="button float-end"
-						class:btn-disabled={!contact || !contact.calcom_link}
-						data-cal-link={`${contact.calcom_link}?email=${$user?.email}&name=${$user?.nickname}`}
-					>
-						{$t('home.bookSession')}
-					</button>
-				</div>
-				<div class="flex-grow p-2">
-					<h2 class="text-xl my-4 font-bold">{$t('home.currentSessions')}</h2>
-					<ul>
+				<table class="divide-y divide-neutral-300 text-center w-full">
+					<thead>
+						<tr>
+							<th scope="col" class="text-left">Date</th>
+							<th scope="col">Status</th>
+							<th scope="col">Participants</th>
+							<th scope="col"># messages</th>
+							<th scope="col"></th>
+						</tr>
+					</thead>
+					<tbody class="divide-y divide-neutral-200">
 						{#each contactSessions as s (s.id)}
-							{#if s.start_time <= new Date() && s.end_time >= new Date()}
-								<li>
-									<a
-										class="block p-4 m-1 mx-4 rounded-md w-[calc(100%-32px)] border-2 hover:bg-gray-200 text-center"
-										href={`/session?id=${s.id}`}
-									>
-										{displayTime(s.start_time)} - {displayTime(s.end_time)}
+							<tr>
+								<td class="py-2 text-left">
+									<div>
+										{displayShortTime(s.start_time)}
+									</div>
+									<div class="text-sm italic text-gray-600">
+										{displayTimeSince(s.start_time)}
+									</div>
+								</td>
+								<td class="py-2">
+									{#if s.start_time >= new Date(2024, 10, 13, 3) && s.start_time <= new Date()}
+										<span class="bg-purple-200 rounded-lg px-2 py-1">En ligne</span>
+									{:else if s.start_time <= new Date() && s.end_time >= new Date()}
+										<span class="bg-green-200 rounded-lg px-2 py-1">En cours</span>
+									{:else if s.start_time > new Date()}
+										<span class="bg-orange-200 rounded-lg px-2 py-1">Programmée</span>
+									{:else}
+										<span class="bg-red-200 rounded-lg px-2 py-1">Terminée</span>
+									{/if}
+								</td>
+								<td class="py-2">{s.otherUsersList(5)}</td>
+								<td class="py-2">4 messages</td>
+								<td class="py-2">
+									<a href="/session?id={s.id}">
+										<Icon
+											src={ArrowRightCircle}
+											size="32"
+											class="text-accent float-end hover:text-white hover:bg-accent hover:cursor-pointer rounded-full"
+										/>
 									</a>
-								</li>
-							{/if}
+								</td>
+							</tr>
 						{/each}
-					</ul>
-					<h2 class="text-xl my-4 font-bold">{$t('home.plannedSessions')}</h2>
-					<ul>
-						{#each contactSessions as s (s.id)}
-							{#if s.start_time > new Date()}
-								<li>
-									<a
-										class="block p-4 m-1 mx-4 rounded-md w-[calc(100%-32px)] border-2 hover:bg-gray-200 text-center"
-										href={`/session?id=${s.id}`}
-									>
-										{displayTime(s.start_time)} - {displayTime(s.end_time)}
-									</a>
-								</li>
-							{/if}
-						{/each}
-					</ul>
-					<h2 class="text-xl my-4 font-bold">{$t('home.pastSessions')}</h2>
-					<ul>
-						{#each contactSessions as s (s.id)}
-							{#if s.end_time < new Date()}
-								<li>
-									<a
-										class="block p-4 m-1 mx-4 rounded-md w-[calc(100%-32px)] border-2 hover:bg-gray-200 text-center"
-										href={`/session?id=${s.id}`}
-									>
-										{displayTime(s.start_time)} - {displayTime(s.end_time)}
-									</a>
-								</li>
-							{/if}
-						{/each}
-					</ul>
-				</div>
-			{/if}
-		</div>
+					</tbody>
+				</table>
+			</div>
+		{/if}
 	</div>
 {/if}
 
-- 
GitLab