diff --git a/frontend/src/lang/fr.json b/frontend/src/lang/fr.json index eee4865522a74c44068d5c2aa041240617a58b6d..c2bd5c0e34b94fce939fcb4e07e0148f31fb9fc9 100644 --- a/frontend/src/lang/fr.json +++ b/frontend/src/lang/fr.json @@ -212,6 +212,7 @@ "availability": "Disponibilités" }, "session": { + "feedbackInline": "Un feedback a été ajouté à ", "modal": { "satisfy": { "title": "Questionnaire de satisfaction", diff --git a/frontend/src/lib/api/sessions.ts b/frontend/src/lib/api/sessions.ts index 00078a5d99306e5d2b4a7465099594e0ef1e9c8d..7dfee6c9ca0437b55539fa1c34f04d0f6cb55a74 100644 --- a/frontend/src/lib/api/sessions.ts +++ b/frontend/src/lib/api/sessions.ts @@ -45,7 +45,7 @@ export async function createMessageAPI( id: number, content: string, metadata: { message: string; date: number }[] -): Promise<number | null> { +): Promise<any | null> { const response = await axiosInstance.post(`/sessions/${id}/messages`, { content, metadata }); if (response.status !== 201) { diff --git a/frontend/src/lib/components/sessions/chatbox.svelte b/frontend/src/lib/components/sessions/chatbox.svelte index e1b68000c5f7516db374fd5acbadbdb1cdccec4d..d4f6ad2a6f30ef9610137c321faa0fddb3030ec5 100644 --- a/frontend/src/lib/components/sessions/chatbox.svelte +++ b/frontend/src/lib/components/sessions/chatbox.svelte @@ -7,6 +7,8 @@ import { t } from '$lib/services/i18n'; import { toastSuccess } from '$lib/utils/toasts'; import { Icon, PencilSquare } from 'svelte-hero-icons'; + import Message from '$lib/types/message'; + import Feedback from '$lib/types/feedback'; export let token: string; @@ -15,10 +17,15 @@ session.messages.subscribe((newMessages) => { let news = newMessages - .filter((m) => !messages.find((m2) => m2.message_id === m.message_id)) + .filter( + (m) => + !messages.find( + (m2) => m instanceof Message && m2 instanceof Message && m2.message_id === m.message_id + ) + ) .at(-1); messages = newMessages; - if (!news) return; + if (!news || !(news instanceof Message)) return; if (document.hidden) { new Notification(news.user.nickname, { @@ -120,13 +127,21 @@ }); </script> -<div class="flex flex-col w-full h-full border-x-2"> +<div class="flex flex-col w-full h-full border-x-2 scroll-smooth"> <div class="flex-grow h-48 overflow-auto flex-col-reverse px-4 flex mb-2"> <div class:hidden={!isTyping}> <span class="loading loading-dots loading-md"></span> </div> - {#each messages.sort((a, b) => b.created_at.getTime() - a.created_at.getTime()) as message (message.message_id)} - <MessageC {message} /> + {#each messages.sort((a, b) => b.created_at.getTime() - a.created_at.getTime()) as message (message.uuid)} + {#if message instanceof Message} + <MessageC {message} /> + {:else if message instanceof Feedback} + <a class="text-center italic text-gray-500 my-2" href="#{message.message.uuid}"> + {$t('session.feedbackInline')} "{message.message.content.length > 20 + ? message.message.content.substring(0, 20) + '...' + : message.message.content}" + </a> + {/if} {/each} </div> {#if !wsConnected} diff --git a/frontend/src/lib/components/sessions/message.svelte b/frontend/src/lib/components/sessions/message.svelte index f5a5cc5a3574a1de50d83f6bfb2a299741ffd09f..7817b15cf84e9e470dd40fe6ffa36d3d0ec2fc4e 100644 --- a/frontend/src/lib/components/sessions/message.svelte +++ b/frontend/src/lib/components/sessions/message.svelte @@ -159,7 +159,12 @@ const isSender = message.user.id == $user?.id; </script> -<div class="chat group" class:chat-start={!isSender} class:chat-end={isSender}> +<div + class="chat group scroll-smooth target:bg-gray-200 rounded-xl" + id={message.uuid} + class:chat-start={!isSender} + class:chat-end={isSender} +> <div class="rounded-full mx-2 chat-image size-12" title={message.user.nickname}> <Gravatar email={message.user.email} diff --git a/frontend/src/lib/types/feedback.ts b/frontend/src/lib/types/feedback.ts index 0d676f21e25f25f158eb290866225e7abd4adb8b..a8c49131e292983fd89095e4efa52ee4c9b81951 100644 --- a/frontend/src/lib/types/feedback.ts +++ b/frontend/src/lib/types/feedback.ts @@ -1,3 +1,4 @@ +import { parseToLocalDate } from '$lib/utils/date'; import { toastAlert } from '$lib/utils/toasts'; import Message from './message'; @@ -7,19 +8,22 @@ export default class Feedback { private _start: number; private _end: number; private _content: string | null; + private _date: Date; public constructor( id: number, message: Message, start: number, end: number, - content: string | null + content: string | null, + date: Date ) { this._id = id; this._message = message; this._start = start; this._end = end; this._content = content; + this._date = date; } get id(): number { @@ -42,6 +46,18 @@ export default class Feedback { return this._content; } + get date(): Date { + return this._date; + } + + get created_at(): Date { + return this._date; + } + + get uuid(): string { + return `feedback-${this._id}`; + } + static parse(json: any, message: Message): Feedback | null { if (json === null || json == undefined) { toastAlert('Failed to parse feedback: json is null'); @@ -53,7 +69,14 @@ export default class Feedback { return null; } - const feedback = new Feedback(json.id, message, json.start, json.end, json.content); + const feedback = new Feedback( + json.id, + message, + json.start, + json.end, + json.content, + parseToLocalDate(json.date) + ); return feedback; } diff --git a/frontend/src/lib/types/message.ts b/frontend/src/lib/types/message.ts index 5be486240e85d3f3133f6ce44cbd3b3bed975946..a92c9ad7e11a286d2b328a68b7586e0c4c9dc1be 100644 --- a/frontend/src/lib/types/message.ts +++ b/frontend/src/lib/types/message.ts @@ -70,6 +70,10 @@ export default class Message { return this._feedbacks; } + get uuid(): string { + return `message-${this._message_id}`; + } + async update(content: string, metadata: { message: string; date: number }[]): Promise<boolean> { const response = await updateMessageAPI(this._session.id, this._message_id, content, metadata); if (response == null || response.id == null) return false; @@ -174,7 +178,9 @@ export default class Message { if (!m) continue; - const prev = messages.find((msg) => msg.message_id == m?.message_id); + const prev = messages.find( + (msg) => msg instanceof Message && msg.message_id == m?.message_id + ); if (!prev) { messages.push(m); diff --git a/frontend/src/lib/types/session.ts b/frontend/src/lib/types/session.ts index 2fbdbb65211ae4376f927c328b393e6a3272ccb6..0f15247e574bff6b7e93192c5498e042dbd3fc78 100644 --- a/frontend/src/lib/types/session.ts +++ b/frontend/src/lib/types/session.ts @@ -1,4 +1,4 @@ -import { toastAlert, toastWarning } from '$lib/utils/toasts'; +import { toastAlert } from '$lib/utils/toasts'; import { get, writable, type Writable } from 'svelte/store'; import User, { user } from './user'; import { axiosInstance } from '$lib/api/apiInstance'; @@ -28,7 +28,8 @@ export default class Session { private _token: string; private _is_active: boolean; private _users: User[]; - private _messages: Writable<Message[]>; + private _messages: Writable<(Message | Feedback)[]>; + private _created_at: Date; private _start_time: Date; private _end_time: Date; @@ -95,7 +96,7 @@ export default class Session { return this._language; } - get messages(): Writable<Message[]> { + get messages(): Writable<(Message | Feedback)[]> { return this._messages; } @@ -204,10 +205,12 @@ export default class Session { const message = new Message(json.id, json.message_id, content, new Date(), sender, this); this._messages.update((messages) => { - if (!messages.find((m) => m.message_id === message.message_id)) { + if (!messages.find((m) => m instanceof Message && m.message_id === message.message_id)) { return [...messages, message]; } - return messages.map((m) => (m.message_id === message.message_id ? message : m)); + return messages.map((m) => + m instanceof Message && m.message_id === message.message_id ? message : m + ); }); return message; @@ -262,10 +265,14 @@ export default class Session { const message = Message.parse(data['data']); if (message) { this._messages.update((messages) => { - if (!messages.find((m) => m.message_id === message.message_id)) { + if ( + !messages.find((m) => m instanceof Message && m.message_id === message.message_id) + ) { return [...messages, message]; } - return messages.map((m) => (m.message_id === message.message_id ? message : m)); + return messages.map((m) => + m instanceof Message && m.message_id === message.message_id ? message : m + ); }); return; @@ -273,19 +280,23 @@ export default class Session { } else if (data['action'] === 'update') { const message = Message.parse(data['data']); if (message) { - if (get(this._messages).find((m) => m.id === message.id)) { + if (get(this._messages).find((m) => m instanceof Message && m.id === message.id)) { this._messages.update((messages) => { - const mEdited = messages.find((m) => m.id === message.id); - if (!mEdited) return messages; + const mEdited = messages.find((m) => m instanceof Message && m.id === message.id); + if (!mEdited || !(mEdited instanceof Message)) return messages; mEdited.localUpdate(message.content, true); return messages.map((m) => (m.id === message.id ? mEdited : m)); }); } else { this._messages.update((messages) => { - const mEdited = messages.find((m) => m.message_id === message.message_id); - if (!mEdited) return messages; + const mEdited = messages.find( + (m) => m instanceof Message && m.message_id === message.message_id + ); + if (!mEdited || !(mEdited instanceof Message)) return messages; mEdited.localUpdate(message.content); - return messages.map((m) => (m.message_id === message.message_id ? mEdited : m)); + return messages.map((m) => + m instanceof Message && m.message_id === message.message_id ? mEdited : m + ); }); return; @@ -295,11 +306,14 @@ export default class Session { this._lastTyping.set(new Date()); return; } else if (data['action'] == 'feedback') { - const message = get(this._messages).find((m) => m.id === data['data']['message_id']); - if (message) { + const message = get(this._messages).find( + (m) => m instanceof Message && m.id === data['data']['message_id'] + ); + if (message && message instanceof Message) { const feedback = Feedback.parse(data['data'], message); if (feedback) { feedback.message.localFeedback(feedback); + this._messages.update((ms) => [...ms, feedback]); return; } }