From 5d2188225961fb64ba0bd7b2c83a24d582d8eb82 Mon Sep 17 00:00:00 2001
From: Brieuc Dubois <git@bhasher.com>
Date: Fri, 15 Nov 2024 20:54:37 +0200
Subject: [PATCH] Update home page look

---
 backend/app/crud.py              |  10 +-
 frontend/src/lang/fr.json        |  29 ++++-
 frontend/src/lib/utils/date.ts   |  43 +++++-
 frontend/src/routes/+page.svelte | 216 +++++++++++++++++++++----------
 4 files changed, 216 insertions(+), 82 deletions(-)

diff --git a/backend/app/crud.py b/backend/app/crud.py
index 3736f804..a750cb73 100644
--- a/backend/app/crud.py
+++ b/backend/app/crud.py
@@ -77,12 +77,20 @@ def create_user_survey_weekly(db: Session, user_id: int, survey: schemas.SurveyC
 
 
 def get_contact_sessions(db: Session, user_id: int, contact_id: int):
-    return (
+    sessions = (
         db.query(models.Session)
         .filter(models.Session.users.any(models.User.id == user_id))
         .filter(models.Session.users.any(models.User.id == contact_id))
         .all()
     )
+    for session in sessions:
+        session.length = (
+            db.query(models.Message)
+            .filter(models.Message.session_id == session.id)
+            .count()
+        )
+
+    return sessions
 
 
 def create_session(db: Session, user: schemas.User):
diff --git a/frontend/src/lang/fr.json b/frontend/src/lang/fr.json
index fa3c6049..fbd33103 100644
--- a/frontend/src/lang/fr.json
+++ b/frontend/src/lang/fr.json
@@ -22,10 +22,7 @@
 		"history": "Historique"
 	},
 	"home": {
-		"date": "Date",
-		"participants": "Participants",
 		"email": "E-mail",
-		"actions": "Actions",
 		"add": "Ajouter",
 		"deleteSessionConfirm": "Êtes-vous sûr de vouloir supprimer cette session ? Cette action est irréversible.",
 		"createSession": "Session immédiate",
@@ -42,7 +39,9 @@
 		"pastSessions": "Sessions terminées",
 		"newContact": "Ajouter un contact",
 		"bookingSuccessful": "Session réservée avec succès",
-		"bookingFailed": "Erreur lors de la réservation de la session"
+		"bookingFailed": "Erreur lors de la réservation de la session",
+		"noCurrentOrFutureSessions": "Aucune session en cours ou planifiée",
+		"noSessions": "Aucune session"
 	},
 	"login": {
 		"email": "E-mail",
@@ -286,6 +285,20 @@
 			"november": "novembre",
 			"december": "décembre"
 		},
+		"shortMonth": {
+			"january": "janv.",
+			"february": "févr.",
+			"march": "mars",
+			"april": "avr.",
+			"may": "mai",
+			"june": "juin",
+			"july": "juil.",
+			"august": "août",
+			"september": "sept.",
+			"october": "oct.",
+			"november": "nov.",
+			"december": "déc."
+		},
 		"days": {
 			"monday": "Lundi",
 			"tuesday": "Mardi",
@@ -324,7 +337,13 @@
 		"words": {
 			"date": "Date",
 			"messages": "Messages",
-			"actions": "Actions"
+			"actions": "Actions",
+			"participants": "Participants",
+			"status": "Statut",
+			"date": "Date",
+			"programed": "Programmée",
+			"inProgress": "En cours",
+			"finished": "Terminée"
 		}
 	},
 	"inputs": {
diff --git a/frontend/src/lib/utils/date.ts b/frontend/src/lib/utils/date.ts
index 5e791ed0..c276e2d2 100644
--- a/frontend/src/lib/utils/date.ts
+++ b/frontend/src/lib/utils/date.ts
@@ -32,6 +32,37 @@ export function getFullMonth(id: number): string {
 	}
 }
 
+export function getShortMonth(id: number): string {
+	switch (id) {
+		case 0:
+			return get(t)('utils.shortMonth.january');
+		case 1:
+			return get(t)('utils.shortMonth.february');
+		case 2:
+			return get(t)('utils.shortMonth.march');
+		case 3:
+			return get(t)('utils.shortMonth.april');
+		case 4:
+			return get(t)('utils.shortMonth.may');
+		case 5:
+			return get(t)('utils.shortMonth.june');
+		case 6:
+			return get(t)('utils.shortMonth.july');
+		case 7:
+			return get(t)('utils.shortMonth.august');
+		case 8:
+			return get(t)('utils.shortMonth.september');
+		case 9:
+			return get(t)('utils.shortMonth.october');
+		case 10:
+			return get(t)('utils.shortMonth.november');
+		case 11:
+			return get(t)('utils.shortMonth.december');
+		default:
+			return '??';
+	}
+}
+
 export function displayDate(date: Date): string {
 	if (date === null) return '';
 
@@ -110,16 +141,16 @@ export function formatToUTCDate(date: Date): string {
 }
 
 export function displayShortTime(date: Date): string {
+	const now = new Date();
+
 	return (
 		('0' + date.getDate()).slice(-2) +
-		'/' +
-		('0' + (date.getMonth() + 1)).slice(-2) +
-		'/' +
-		date.getFullYear() +
+		' ' +
+		getShortMonth(date.getMonth()) +
+		(date.getFullYear() != now.getFullYear() ? ' ' + date.getFullYear() : '') +
 		' ' +
 		('0' + date.getHours()).slice(-2) +
-		':' +
-		('0' + date.getMinutes()).slice(-2)
+		'h'
 	);
 }
 
diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte
index 07ddf577..ed9f6fef 100644
--- a/frontend/src/routes/+page.svelte
+++ b/frontend/src/routes/+page.svelte
@@ -1,14 +1,13 @@
 <script lang="ts">
 	import Session from '$lib/types/session';
 	import { onMount } from 'svelte';
-	import { displayShortTime, displayTime, displayTimeSince } from '$lib/utils/date';
+	import { displayShortTime, displayTimeSince } from '$lib/utils/date';
 	import {
 		AcademicCap,
 		Sparkles,
 		Icon,
 		User as UserIcon,
 		MagnifyingGlass,
-		ArrowRight,
 		ArrowRightCircle
 	} from 'svelte-hero-icons';
 	import { t } from '$lib/services/i18n';
@@ -29,7 +28,10 @@
 	let modalNew = false;
 	let nickname = '';
 
+	let showTerminatedSessions = false;
+
 	async function selectContact(c: User | null) {
+		showTerminatedSessions = false;
 		contact = c;
 		if (!contact) {
 			contactSessions = [];
@@ -138,87 +140,160 @@
 	}
 </script>
 
-<svelte:head>
-	<script>
-	</script>
-</svelte:head>
-
 {#if ready}
-	<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 class="flex-row h-full flex py-4 flex-grow overflow-y-hidden">
+		<div class="flex flex-col border shadow-[0_0_6px_0_rgba(0,14,156,.2)] min-w-72 rounded-r-xl">
+			<div class="flex-grow">
+				{#each contacts as c (c.id)}
+					<div
+						class="h-24 flex border-gray-300 border-b-2 hover:bg-gray-200 hover:cursor-pointer p-4"
+						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 bg-gray-300 rounded-2xl">
+							{#if c.type == 0}
+								<Icon src={Sparkles} class="mask mask-squircle" />
+							{:else if c.type == 1}
+								<Icon src={AcademicCap} class="" />
+							{:else}
+								<Icon src={UserIcon} />
+							{/if}
+						</div>
+						<div class="text-lg font-bold capitalize flex items-center">
+							{c.nickname}
+						</div>
+					</div>
+				{/each}
 			</div>
-			<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
+				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)}
 			>
-				<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)}
+				+
+			</button>
+		</div>
+		{#if contact}
+			<div class="flex flex-col xl:mx-auto xl:w-[60rem] m-4">
+				<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>
+				<div
+					class="border p-4 mt-4 rounded-xl shadow-[0_0_6px_0_rgba(0,14,156,.2)] overflow-y-scroll no-scrollbar"
+				>
+					<table class="divide-y divide-neutral-300 text-center w-full table-fixed">
+						<thead>
 							<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>
-								</td>
+								<th scope="col" class="text-left">{$t('utils.words.date')}</th>
+								<th scope="col">{$t('utils.words.status')}</th>
+								<th scope="col"># {$t('utils.words.messages').toLowerCase()}</th>
+								<th scope="col">{$t('utils.words.actions')}</th>
 							</tr>
-						{/each}
-					</tbody>
-				</table>
+						</thead>
+						<tbody class="divide-y divide-neutral-200">
+							{#if contactSessions.length === 0}
+								<tr>
+									<td colspan="4" class="py-5 text-gray-500">{$t('home.noSessions')}</td>
+								</tr>
+							{:else}
+								{#if !showTerminatedSessions && contactSessions.filter((s) => s.end_time >= new Date()).length === 0}
+									<tr>
+										<td colspan="4" class="py-5 text-gray-500"
+											>{$t('home.noCurrentOrFutureSessions')}</td
+										>
+									</tr>
+								{/if}
+								{#each contactSessions as s (s.id)}
+									{#if showTerminatedSessions || s.end_time >= new Date()}
+										<tr>
+											<td class="py-2 text-left space-y-1">
+												<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() && s.end_time >= new Date()}
+													<span class="bg-green-200 rounded-lg px-2 py-1"
+														>{$t('utils.words.inProgress')}</span
+													>
+												{:else if s.start_time > new Date()}
+													<span class="bg-orange-200 rounded-lg px-2 py-1"
+														>{$t('utils.words.programed')}</span
+													>
+												{:else}
+													<span class="bg-red-200 rounded-lg px-2 py-1"
+														>{$t('utils.words.finished')}</span
+													>
+												{/if}
+											</td>
+											<td class="py-2">{s.length} {$t('utils.words.messages').toLowerCase()}</td>
+											<td class="py-2">
+												<a href="/session?id={s.id}" class="group">
+													<Icon
+														src={ArrowRightCircle}
+														size="32"
+														class="text-accent mx-auto group-hover:text-white group-hover:bg-accent rounded-full"
+													/>
+												</a>
+											</td>
+										</tr>
+									{/if}
+								{/each}
+								<tr>
+									<td
+										class="py-2 hover:cursor-pointer"
+										colspan="4"
+										on:click={() => (showTerminatedSessions = !showTerminatedSessions)}
+									>
+										<button aria-label={showTerminatedSessions ? 'Hide' : 'Show'}>
+											<svg
+												class="size-3 ms-3"
+												aria-hidden="true"
+												xmlns="http://www.w3.org/2000/svg"
+												fill="none"
+												viewBox="0 0 10 6"
+												class:rotate-180={showTerminatedSessions}
+											>
+												<path
+													stroke="currentColor"
+													stroke-linecap="round"
+													stroke-linejoin="round"
+													stroke-width="2"
+													d="m1 1 4 4 4-4"
+												/>
+											</svg>
+										</button>
+									</td>
+								</tr>
+							{/if}
+						</tbody>
+					</table>
+				</div>
 			</div>
 		{/if}
 	</div>
 {/if}
 
 <dialog
-	class="modal"
+	class="modal bg-black bg-opacity-50"
 	open={modalNew}
 	on:close={() => (modalNew = false)}
-	on:keydown={(e) => e.key === 'Escape' && (modalNew = false)}
-	tabindex="0"
+	tabindex="-1"
 >
 	<div class="modal-box">
 		<h2 class="text-xl font-bold mb-4">{$t('home.newContact')}</h2>
@@ -228,6 +303,7 @@
 				placeholder={$t('home.email')}
 				bind:value={nickname}
 				class="input flex-grow mr-2"
+				on:keydown={(e) => e.key === 'Escape' && (modalNew = false)}
 			/>
 			<button class="button w-16" on:click={searchNickname}>
 				<Icon src={MagnifyingGlass} />
-- 
GitLab