diff --git a/src/components/KnownFacts/KnownFacts.css b/src/components/KnownFacts/KnownFacts.css new file mode 100644 index 00000000..09eca21f --- /dev/null +++ b/src/components/KnownFacts/KnownFacts.css @@ -0,0 +1,19 @@ +.memori-known-facts-modal { + z-index: 10000; +} + +.memori-known-facts-actions { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: flex-end; + margin: 1rem 0; +} + +.memori-known-facts-actions .memori-button[disabled] { + opacity: 0.4; +} + +.memori-known-facts-delete-selected { + margin-left: auto; +} \ No newline at end of file diff --git a/src/components/KnownFacts/KnownFacts.stories.tsx b/src/components/KnownFacts/KnownFacts.stories.tsx new file mode 100644 index 00000000..1edb7037 --- /dev/null +++ b/src/components/KnownFacts/KnownFacts.stories.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { Meta, Story } from '@storybook/react'; +import { memori, sessionID, knownFact } from '../../mocks/data'; +import I18nWrapper from '../../I18nWrapper'; +import KnownFacts, { Props } from './KnownFacts'; + +import './KnownFacts.css'; + +const meta: Meta = { + title: 'Known Facts', + component: KnownFacts, + argTypes: { + visible: { + control: { + type: 'boolean', + }, + }, + }, + parameters: { + controls: { expanded: true }, + }, +}; + +export default meta; + +const Template: Story = args => ( + + {}} + {...args} + /> + +); + +// By passing using the Args format for exported stories, you can control the props for a component for reuse in a test +// https://storybook.js.org/docs/react/workflows/unit-testing +export const Default = Template.bind({}); +Default.args = { + visible: true, +}; + +export const WithData = Template.bind({}); +WithData.args = { + visible: true, + initialKnownFacts: [knownFact], +}; + +export const WithPaginatedData = Template.bind({}); +WithPaginatedData.args = { + visible: true, + initialKnownFacts: new Array(26).fill(knownFact).map((fact, index) => ({ + ...fact, + knownFactID: fact.knownFactID + index, + })), +}; + +export const WithRealDataLocalhost = Template.bind({}); +WithRealDataLocalhost.args = { + visible: true, + sessionID: '5841f5f9-3315-4a5a-9b62-33b13d5a27fd', + apiURL: 'http://localhost:7778', + memori: { + memoriName: 'test memori', + ownerUserName: 'nicola', + memoriID: '1a9c75e8-57aa-4ce3-8ea5-256185fa79a7', + ownerUserID: '04a8cff9-13d6-4367-9cb2-72b9af9ee494', + tenantID: 'app.memorytwin.com', + apiURL: 'http://localhost:7778', + baseURL: 'http://localhost:3000', + uiLang: 'EN', + lang: 'IT', + layout: 'FULLPAGE', + showShare: 'true', + integrationID: '82f017cc-450b-4c47-acf5-47910d336ce9', + }, +}; diff --git a/src/components/KnownFacts/KnownFacts.test.tsx b/src/components/KnownFacts/KnownFacts.test.tsx new file mode 100644 index 00000000..2ef34bae --- /dev/null +++ b/src/components/KnownFacts/KnownFacts.test.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import KnownFacts from './KnownFacts'; +import { knownFact, memori, sessionID } from '../../mocks/data'; + +beforeEach(() => { + // @ts-ignore + window.IntersectionObserver = jest.fn(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), + takeRecords: jest.fn(), + })); +}); + +it('renders KnownFacts hidden unchanged', () => { + const { container } = render( + + ); + expect(container).toMatchSnapshot(); +}); + +it('renders KnownFacts visible unchanged', () => { + const { container } = render( + + ); + expect(container).toMatchSnapshot(); +}); + +it('renders KnownFacts with data unchanged', () => { + const { container } = render( + + ); + expect(container).toMatchSnapshot(); +}); diff --git a/src/components/KnownFacts/KnownFacts.tsx b/src/components/KnownFacts/KnownFacts.tsx new file mode 100644 index 00000000..031c1d67 --- /dev/null +++ b/src/components/KnownFacts/KnownFacts.tsx @@ -0,0 +1,449 @@ +import { KnownFact, Memori } from '@memori.ai/memori-api-client/dist/types'; +import { useEffect, useState } from 'react'; +import memoriApiClient from '@memori.ai/memori-api-client'; +import Button from '../ui/Button'; +import Drawer from '../ui/Drawer'; +import Spin from '../ui/Spin'; +import Modal from '../ui/Modal'; +import toast from 'react-hot-toast'; +import { getErrori18nKey } from '../../helpers/error'; +import { useTranslation } from 'react-i18next'; +import Delete from '../icons/Delete'; +import Checkbox from '../ui/Checkbox'; +import Select from '../ui/Select'; +import ChevronLeft from '../icons/ChevronLeft'; +import ChevronRight from '../icons/ChevronRight'; + +export interface Props { + apiURL: string; + sessionID: string; + memori: Memori; + initialKnownFacts?: KnownFact[]; + visible?: boolean; + closeDrawer: () => void; +} + +const KnownFacts = ({ + apiURL, + sessionID, + memori, + visible = true, + initialKnownFacts = [], + closeDrawer, +}: Props) => { + const { t } = useTranslation(); + + const client = memoriApiClient(apiURL); + const { getKnownFactsPaginated, deleteKnownFact } = client.knownFacts; + + const [knownFacts, setKnownFacts] = useState(initialKnownFacts); + const [numberOfResults, setNumberOfResults] = useState(25); + const [pageIndex, setPageIndex] = useState(0); + const [knownFactsCount, setKnownFactsCount] = useState( + initialKnownFacts?.length ?? 0 + ); + const [loading, setLoading] = useState(false); + + /** + * Fetch known facts + */ + const fetchKnownFacts = async ( + sessionId?: string, + from?: number, + howMany?: number + ) => { + if (!sessionID && !sessionId) return; + setLoading(true); + try { + const { knownFacts, count, ...response } = await getKnownFactsPaginated( + sessionId ?? sessionID, + from ?? pageIndex, + howMany ?? numberOfResults + ); + + setKnownFacts(knownFacts ?? []); + setKnownFactsCount(count ?? 0); + + if (response.resultCode !== 0) { + console.error(response); + toast.error(t(getErrori18nKey(response.resultCode))); + } + } catch (err) { + console.error('KNOWN_FACTS/FETCH', err); + setKnownFacts(initialKnownFacts ?? []); + } + + setLoading(false); + }; + useEffect(() => { + fetchKnownFacts(); + }, []); + + /** + * Table selection + */ + const [bulkDeleteModalVisible, setBulkDeleteModalVisible] = useState(false); + const [deleteModalVisibleFor, setDeleteModalVisibleFor] = useState(); + const [selectedRowKeys, setSelectedRowKeys] = useState<(string | number)[]>( + [] + ); + // const rowSelection = { + // selectedRowKeys, + // onChange: setSelectedRowKeys, + // }; + + // const columns = [ + // { + // title: t('knownFacts.text'), + // dataIndex: 'text', + // }, + // { + // title: t('createdAt'), + // dataIndex: 'creationTimestamp', + // sorter: (a: KnownFact, b: KnownFact) => + // new Date(a.creationTimestamp ?? Date.now()).getTime() - + // new Date(b.creationTimestamp ?? Date.now()).getTime(), + // render: (creationTimestamp: Date) => ( + //
+ // + // {creationTimestamp + // ? new Intl.DateTimeFormat('it', { + // dateStyle: 'short', + // timeStyle: 'short', + // }).format(new Date(creationTimestamp)) + // : '-'} + // + //
+ // ), + // }, + // { + // title: t('actions'), + // key: 'action', + // render: (_value: any, kf: KnownFact) => ( + //
+ //
+ // ), + // }, + // ]; + + return ( + closeDrawer()} + title={t('knownFacts.title')} + > +

+ {t('knownFacts.description', { + memoriName: memori.name, + })} +

+ + +
+ + 1 + ? t('knownFacts.deleteSelectedConfirmTitle') + : t('knownFacts.deleteConfirmTitle') + } + description={ + selectedRowKeys.length > 1 + ? t('knownFacts.deleteSelectedConfirmMessage', { + number: selectedRowKeys.length, + }) + : t('knownFacts.deleteConfirmMessage') + } + onClose={() => { + setBulkDeleteModalVisible(false); + }} + footer={ + <> + + + + } + /> +
+ + {knownFactsCount > 25 && ( +