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