diff --git a/.changeset/stale-shrimps-lie.md b/.changeset/stale-shrimps-lie.md
new file mode 100644
index 0000000000..6a1883f4bb
--- /dev/null
+++ b/.changeset/stale-shrimps-lie.md
@@ -0,0 +1,6 @@
+---
+"nextjs-website": minor
+"storybook-app": minor
+---
+
+Add chatbot chat history detail layout
diff --git a/apps/nextjs-website/public/icons/chatbotChatUserBorder.svg b/apps/nextjs-website/public/icons/chatbotChatUserBorder.svg
new file mode 100644
index 0000000000..0f0c5daf76
--- /dev/null
+++ b/apps/nextjs-website/public/icons/chatbotChatUserBorder.svg
@@ -0,0 +1,11 @@
+
diff --git a/apps/nextjs-website/src/components/atoms/ChatHistoryMessage/ChatHistoryMessage.tsx b/apps/nextjs-website/src/components/atoms/ChatHistoryMessage/ChatHistoryMessage.tsx
new file mode 100644
index 0000000000..c6cdb9c431
--- /dev/null
+++ b/apps/nextjs-website/src/components/atoms/ChatHistoryMessage/ChatHistoryMessage.tsx
@@ -0,0 +1,101 @@
+import { Stack, Typography, useTheme } from '@mui/material';
+import { defaultLocale } from '@/config';
+import IconWrapper from '@/components/atoms/IconWrapper/IconWrapper';
+import { parseChatMessage } from '@/helpers/chatMessageParser.helper';
+
+type DateFormatOptions = {
+ locale?: string;
+ options?: Intl.DateTimeFormatOptions;
+};
+
+const DEFAULT_DATE_FORMAT = {
+ locale: defaultLocale,
+ options: {
+ timeStyle: 'short',
+ hourCycle: 'h23',
+ },
+} satisfies DateFormatOptions;
+
+type ChatMessageProps = {
+ text: string;
+ sender: string;
+ isQuestion: boolean;
+ timestamp?: string;
+};
+
+const ChatHistoryMessage = ({
+ text,
+ timestamp,
+ isQuestion,
+ sender,
+}: ChatMessageProps) => {
+ const { palette } = useTheme();
+ const textColor = palette.text.primary;
+ const parsedChatMessage = parseChatMessage(text);
+ const iconSize = 28;
+
+ const timeLabel =
+ timestamp &&
+ new Intl.DateTimeFormat(
+ DEFAULT_DATE_FORMAT.locale,
+ DEFAULT_DATE_FORMAT.options
+ ).format(new Date(timestamp));
+
+ return (
+
+
+ {isQuestion ? (
+
+ ) : (
+
+ )}
+
+ {sender}
+
+ {timeLabel && (
+
+ {timeLabel}
+
+ )}
+
+
+ {parsedChatMessage}
+
+
+ );
+};
+
+export default ChatHistoryMessage;
diff --git a/apps/nextjs-website/src/components/atoms/ChatbotHistoryNavigationLink/ChatbotHistoryNavigationLink.tsx b/apps/nextjs-website/src/components/atoms/ChatbotHistoryNavigationLink/ChatbotHistoryNavigationLink.tsx
new file mode 100644
index 0000000000..631782dd4f
--- /dev/null
+++ b/apps/nextjs-website/src/components/atoms/ChatbotHistoryNavigationLink/ChatbotHistoryNavigationLink.tsx
@@ -0,0 +1,36 @@
+import { Link, useTheme } from '@mui/material';
+
+type ChatbotHistoryNavigationLinkProps = {
+ sessionId: string;
+ sessionTitle: string;
+};
+
+const ChatbotHistoryNavigationLink = ({
+ sessionId,
+ sessionTitle,
+}: ChatbotHistoryNavigationLinkProps) => {
+ const { palette, typography } = useTheme();
+ const textColor = palette.text.secondary;
+
+ return (
+ {
+ // TODO: Implement the navigation to the chatbot history session
+ console.log(`Navigating to chatbot history session: ${sessionId}`);
+ }}
+ >
+ {sessionTitle}
+
+ );
+};
+
+export default ChatbotHistoryNavigationLink;
diff --git a/apps/nextjs-website/src/components/atoms/ChatbotHistoryNavigationMenu/ChatbotHistoryNavigationMenu.tsx b/apps/nextjs-website/src/components/atoms/ChatbotHistoryNavigationMenu/ChatbotHistoryNavigationMenu.tsx
new file mode 100644
index 0000000000..cc7ab37121
--- /dev/null
+++ b/apps/nextjs-website/src/components/atoms/ChatbotHistoryNavigationMenu/ChatbotHistoryNavigationMenu.tsx
@@ -0,0 +1,58 @@
+import { ArrowBack, ArrowForward } from '@mui/icons-material';
+import { Stack, Typography, useTheme } from '@mui/material';
+import { useTranslations } from 'next-intl';
+import ChatbotHistoryNavigationLink from '@/components/atoms/ChatbotHistoryNavigationLink/ChatbotHistoryNavigationLink';
+
+export type SessionNavigationData = {
+ sessionId: string;
+ sessionTitle: string;
+};
+
+type ChatbotHistoryNavigationMenuProps = {
+ previousSession?: SessionNavigationData;
+ nextSession?: SessionNavigationData;
+};
+
+const ChatbotHistoryNavigationMenu = ({
+ previousSession,
+ nextSession,
+}: ChatbotHistoryNavigationMenuProps) => {
+ const t = useTranslations();
+ const { palette } = useTheme();
+ const textColor = palette.text.secondary;
+
+ return (
+
+ {previousSession && (
+
+
+ {t('chatBot.previousChat')}
+
+
+
+
+
+
+ )}
+ {nextSession && (
+
+
+ {t('chatBot.previousChat')}
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default ChatbotHistoryNavigationMenu;
diff --git a/apps/nextjs-website/src/components/molecules/ChatbotHistoryMessages/ChatbotHistoryMessages.tsx b/apps/nextjs-website/src/components/molecules/ChatbotHistoryMessages/ChatbotHistoryMessages.tsx
new file mode 100644
index 0000000000..c93f4aa1c1
--- /dev/null
+++ b/apps/nextjs-website/src/components/molecules/ChatbotHistoryMessages/ChatbotHistoryMessages.tsx
@@ -0,0 +1,41 @@
+import ChatHistoryMessage from '@/components/atoms/ChatHistoryMessage/ChatHistoryMessage';
+import { Query } from '@/lib/chatbot/queries';
+import { Stack } from '@mui/material';
+import { useTranslations } from 'next-intl';
+
+type ChatbotHistoryMessagesProps = {
+ queries: Query[];
+ userName: string;
+};
+
+const ChatbotHistoryMessages = ({
+ queries,
+ userName,
+}: ChatbotHistoryMessagesProps) => {
+ const t = useTranslations();
+
+ return (
+
+ {queries.map((query) => (
+
+
+ {query.answer && query.createdAt && (
+
+ )}
+
+ ))}
+
+ );
+};
+
+export default ChatbotHistoryMessages;
diff --git a/apps/nextjs-website/src/components/organisms/ChatbotHistoryDetailLayout/ChatbotHistoryDetailLayout.tsx b/apps/nextjs-website/src/components/organisms/ChatbotHistoryDetailLayout/ChatbotHistoryDetailLayout.tsx
new file mode 100644
index 0000000000..d2ceac45db
--- /dev/null
+++ b/apps/nextjs-website/src/components/organisms/ChatbotHistoryDetailLayout/ChatbotHistoryDetailLayout.tsx
@@ -0,0 +1,99 @@
+import ChatbotHistoryNavigationMenu, {
+ SessionNavigationData,
+} from '@/components/atoms/ChatbotHistoryNavigationMenu/ChatbotHistoryNavigationMenu';
+import ChatbotHistoryMessages from '@/components/molecules/ChatbotHistoryMessages/ChatbotHistoryMessages';
+import { defaultLocale } from '@/config';
+import { Query } from '@/lib/chatbot/queries';
+import { Delete } from '@mui/icons-material';
+import { Box, Button, Stack, Typography, useTheme } from '@mui/material';
+import { useTranslations } from 'next-intl';
+
+type DateFormatOptions = {
+ locale?: string;
+ options?: Intl.DateTimeFormatOptions;
+};
+
+const DEFAULT_DATE_FORMAT = {
+ locale: defaultLocale,
+ options: {
+ day: '2-digit',
+ month: 'long',
+ year: 'numeric',
+ },
+} satisfies DateFormatOptions;
+
+type ChatbotHistoryDetailLayoutProps = {
+ queries: Query[];
+ userName: string;
+ previousSession?: SessionNavigationData;
+ nextSession?: SessionNavigationData;
+ onDeleteChatSession: (sessionId: string) => null;
+};
+
+const ChatbotHistoryDetailLayout = ({
+ queries,
+ userName,
+ previousSession,
+ nextSession,
+ onDeleteChatSession,
+}: ChatbotHistoryDetailLayoutProps) => {
+ const t = useTranslations();
+ const { palette } = useTheme();
+
+ const date = new Intl.DateTimeFormat(
+ DEFAULT_DATE_FORMAT.locale,
+ DEFAULT_DATE_FORMAT.options
+ ).format(new Date(queries[0].queriedAt));
+
+ return (
+
+ {queries[0].question}
+
+
+ {date}
+
+ }
+ color='error'
+ sx={{ display: { xs: 'none', xl: 'flex' } }}
+ >
+ {t('chatBot.deleteChat')}
+
+
+
+
+ }
+ color='error'
+ onClick={() => onDeleteChatSession(queries[0].sessionId)}
+ >
+ {t('chatBot.deleteChat')}
+
+
+
+
+
+
+ );
+};
+
+export default ChatbotHistoryDetailLayout;
diff --git a/apps/nextjs-website/src/messages/it.json b/apps/nextjs-website/src/messages/it.json
index 77fe6b6b72..08fa0e5503 100644
--- a/apps/nextjs-website/src/messages/it.json
+++ b/apps/nextjs-website/src/messages/it.json
@@ -604,6 +604,9 @@
"copied": "Copiato",
"welcomeMessage": "**Benvenuto nel Dev Portal!**\n\nSono _Discovery_, il tuo assistente virtuale per la documentazione tecnica. Posso aiutarti con informazioni su API, guide, e altro.\n\n_Nota_: Discovery è in versione beta, quindi alcune risposte potrebbero non essere accurate. Verifica sempre le informazioni importanti con la documentazione ufficiale.\n\nCome posso aiutarti oggi?",
"guestMessage": "Ciao sono _Discovery_ , il chatbot di DevPortal!\n\nPosso aiutarti a trovare in modo semplice e rapido le informazioni presenti nella documentazione del Portale.\n\nPer poter accedere al servizio, ti invito a [iscriverti a PagoPA DevPortal]({host}/auth/login)",
+ "deleteChat": "Elimina Chat",
+ "previousChat": "Chat Precedente",
+ "nextChat": "Chat Successiva",
"errors": {
"title": "Errore",
"serviceDown": "Il chatbot al momento non è disponibile. Riprovare più tardi.",
diff --git a/apps/storybook-app/public/icons/chatbotChatUserBorder.svg b/apps/storybook-app/public/icons/chatbotChatUserBorder.svg
new file mode 100644
index 0000000000..0f0c5daf76
--- /dev/null
+++ b/apps/storybook-app/public/icons/chatbotChatUserBorder.svg
@@ -0,0 +1,11 @@
+
diff --git a/apps/storybook-app/stories/atoms/ChatHistoryMessage.stories.tsx b/apps/storybook-app/stories/atoms/ChatHistoryMessage.stories.tsx
new file mode 100644
index 0000000000..ea8a3bf0a2
--- /dev/null
+++ b/apps/storybook-app/stories/atoms/ChatHistoryMessage.stories.tsx
@@ -0,0 +1,41 @@
+import { Decorator, Meta, StoryObj } from '@storybook/react';
+import ChatHistoryMessage from 'nextjs-website/src/components/atoms/ChatHistoryMessage/ChatHistoryMessage';
+import React from 'react';
+import { nextIntlContextDecorator } from '../next-intl-context.helper';
+import { mockText } from '../mock-content.helper';
+
+const meta: Meta = {
+ title: 'Atoms/ChatHistoryMessage',
+ component: ChatHistoryMessage,
+};
+
+const decorator: Decorator = (story) => (
+ {story()}
+);
+
+export default meta;
+
+export const ChatBotMessage: StoryObj = {
+ args: {
+ text: `
+ GPD gestisce i pagamenti spontanei attraverso il nodo dei pagamenti.
+
+ Rif:
+ [PagoPA DevPortal | Overview delle componenti](https://developer.pagopa.it/pago-pa/guides/sanp/specifiche-attuative-del-nodo-dei-pagamenti-spc/funzionamento-generale/overview-delle-componenti)
+ `,
+ timestamp: '2024-07-24T17:14:07.129Z',
+ sender: 'Discovery',
+ isQuestion: false,
+ },
+ decorators: [decorator, nextIntlContextDecorator],
+};
+
+export const UserMessage: StoryObj = {
+ args: {
+ text: mockText(23),
+ timestamp: '2024-07-24T17:14:08.129Z',
+ sender: 'John Doe',
+ isQuestion: true,
+ },
+ decorators: [decorator, nextIntlContextDecorator],
+};
diff --git a/apps/storybook-app/stories/atoms/ChatbotHistoryNavigationMenu.stories.tsx b/apps/storybook-app/stories/atoms/ChatbotHistoryNavigationMenu.stories.tsx
new file mode 100644
index 0000000000..cc7b4da5c7
--- /dev/null
+++ b/apps/storybook-app/stories/atoms/ChatbotHistoryNavigationMenu.stories.tsx
@@ -0,0 +1,25 @@
+import { Meta, StoryObj } from '@storybook/react';
+import ChatbotHistoryNavigationMenu from '../../../nextjs-website/src/components/atoms/ChatbotHistoryNavigationMenu/ChatbotHistoryNavigationMenu';
+import { mockText } from '../mock-content.helper';
+import { nextIntlContextDecorator } from '../next-intl-context.helper';
+
+const meta: Meta = {
+ title: 'Atoms/ChatbotHistoryNavigationMenu',
+ component: ChatbotHistoryNavigationMenu,
+};
+
+export default meta;
+
+export const Showcase: StoryObj = {
+ args: {
+ nextSession: {
+ sessionId: '1',
+ sessionTitle: mockText(10),
+ },
+ previousSession: {
+ sessionId: '2',
+ sessionTitle: mockText(5),
+ },
+ },
+ decorators: [nextIntlContextDecorator],
+};
diff --git a/apps/storybook-app/stories/fixtures/chatbotFixtures.tsx b/apps/storybook-app/stories/fixtures/chatbotFixtures.tsx
new file mode 100644
index 0000000000..135ed4d903
--- /dev/null
+++ b/apps/storybook-app/stories/fixtures/chatbotFixtures.tsx
@@ -0,0 +1,100 @@
+import { mockText } from '../mock-content.helper';
+
+export const sessionsList = [
+ {
+ id: '111',
+ title: mockText(25),
+ createdAt: '2024-07-24T17:14:07.129Z',
+ },
+ {
+ id: '123',
+ title: mockText(25),
+ createdAt: '2024-07-25T17:14:07.129Z',
+ },
+ {
+ id: '456',
+ title: mockText(80),
+ createdAt: '2024-07-26T17:14:07.129Z',
+ },
+ {
+ id: '789',
+ title: mockText(25),
+ createdAt: '2024-06-24T17:14:07.129Z',
+ },
+ {
+ id: '321',
+ title: mockText(25),
+ createdAt: '2024-06-25T17:14:07.129Z',
+ },
+ {
+ id: '654',
+ title: mockText(80),
+ createdAt: '2024-06-26T17:14:07.129Z',
+ },
+ {
+ id: '987',
+ title: mockText(25),
+ createdAt: '2024-04-24T17:14:07.129Z',
+ },
+ {
+ id: '543',
+ title: mockText(25),
+ createdAt: '2024-04-25T17:14:07.129Z',
+ },
+ {
+ id: '333',
+ title: mockText(80),
+ createdAt: '2024-04-26T17:14:07.129Z',
+ },
+ {
+ id: '1221',
+ title: mockText(25),
+ createdAt: '2024-03-24T17:14:07.129Z',
+ },
+ {
+ id: '2352',
+ title: mockText(25),
+ createdAt: '2024-03-25T17:14:07.129Z',
+ },
+ {
+ id: '45664',
+ title: mockText(80),
+ createdAt: '2024-03-26T17:14:07.129Z',
+ },
+];
+
+export const chatbotChatSession = [
+ {
+ sessionId: 'sessionID',
+ question: mockText(12),
+ queriedAt: '2024-07-24T17:14:07.129Z',
+ answer: mockText(24),
+ createdAt: '2024-07-24T17:14:07.129Z',
+ },
+ {
+ sessionId: 'sessionID',
+ question: mockText(23),
+ queriedAt: '2024-07-24T17:14:07.129Z',
+ answer: mockText(44),
+ createdAt: '2024-07-24T17:14:07.129Z',
+ },
+ {
+ sessionId: 'sessionID',
+ question: mockText(16),
+ queriedAt: '2024-07-24T17:14:07.129Z',
+ answer: mockText(30),
+ createdAt: '2024-07-24T17:14:07.129Z',
+ },
+ {
+ sessionId: 'sessionID',
+ question: mockText(20),
+ queriedAt: '2024-07-24T17:14:07.129Z',
+ answer: mockText(24),
+ createdAt: '2024-07-24T17:14:07.129Z',
+ },
+ {
+ sessionId: 'sessionID',
+ question: mockText(10),
+ queriedAt: '2024-07-24T17:14:07.129Z',
+ },
+];
diff --git a/apps/storybook-app/stories/molecules/ChatbotHistoryMessages.stories.tsx b/apps/storybook-app/stories/molecules/ChatbotHistoryMessages.stories.tsx
new file mode 100644
index 0000000000..a33223c0cc
--- /dev/null
+++ b/apps/storybook-app/stories/molecules/ChatbotHistoryMessages.stories.tsx
@@ -0,0 +1,47 @@
+import { Decorator, Meta, StoryObj } from '@storybook/react';
+import ChatbotHistoryMessages from '../../../nextjs-website/src/components/molecules/ChatbotHistoryMessages/ChatbotHistoryMessages';
+import React from 'react';
+import { nextIntlContextDecorator } from '../next-intl-context.helper';
+import { mockText } from '../mock-content.helper';
+
+const meta: Meta = {
+ title: 'Molecules/ChatbotHistoryMessages',
+ component: ChatbotHistoryMessages,
+};
+
+const decorator: Decorator = (story) => (
+ {story()}
+);
+
+export default meta;
+
+export const Showcase: StoryObj = {
+ args: {
+ queries: [
+ {
+ id: '1',
+ sessionId: '1',
+ question: mockText(10),
+ queriedAt: '2024-07-24T17:14:07.129Z',
+ answer: mockText(80),
+ createdAt: '2024-07-24T17:14:07.129Z',
+ },
+ {
+ id: '2',
+ sessionId: '1',
+ question: mockText(8),
+ queriedAt: '2024-07-24T17:14:07.129Z',
+ answer: mockText(30),
+ createdAt: '2024-07-24T17:14:07.129Z',
+ },
+ {
+ id: '3',
+ sessionId: '1',
+ question: mockText(50),
+ queriedAt: '2024-07-24T17:14:07.129Z',
+ },
+ ],
+ userName: 'John Doe',
+ },
+ decorators: [decorator, nextIntlContextDecorator],
+};
diff --git a/apps/storybook-app/stories/organisms/ChatbotHistoryDetailLayout.stories.tsx b/apps/storybook-app/stories/organisms/ChatbotHistoryDetailLayout.stories.tsx
new file mode 100644
index 0000000000..91d2aeab1d
--- /dev/null
+++ b/apps/storybook-app/stories/organisms/ChatbotHistoryDetailLayout.stories.tsx
@@ -0,0 +1,37 @@
+import { Decorator, Meta, StoryObj } from '@storybook/react';
+import ChatbotHistoryDetailLayout from 'nextjs-website/src/components/organisms/ChatbotHistoryDetailLayout/ChatbotHistoryDetailLayout';
+import { chatbotChatSession } from '../fixtures/chatbotFixtures';
+import React from 'react';
+import { mockText } from '../mock-content.helper';
+import { nextIntlContextDecorator } from '../next-intl-context.helper';
+
+const meta: Meta = {
+ title: 'Organisms/ChatbotHistoryDetailLayout',
+ component: ChatbotHistoryDetailLayout,
+};
+
+const decorator: Decorator = (story) => (
+ {story()}
+);
+
+export default meta;
+
+export const Showcase: StoryObj = {
+ args: {
+ queries: chatbotChatSession,
+ userName: 'John Doe',
+ nextSession: {
+ sessionId: '1',
+ sessionTitle: mockText(10),
+ },
+ previousSession: {
+ sessionId: '2',
+ sessionTitle: mockText(5),
+ },
+ onDeleteChatSession: (sessionId: string) => {
+ console.log(sessionId);
+ return null;
+ },
+ },
+ decorators: [decorator, nextIntlContextDecorator],
+};