diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6974f0a878..7b8dd3ead6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,22 +8,19 @@ updates: - dependency-name: "@types/*" - dependency-name: "@sentry/node" versions: [">= 7.114.0"] + - dependency-name: flowbite #Flowbite & flowbite-datepicker upgrade #6993 + versions: [">= 2.3.1"] + - dependency-name: flowbite-datepicker #Flowbite & flowbite-datepicker upgrade #6993 + versions: [">= 1.2.8"] + - dependency-name: flowbite-react #Flowbite & flowbite-datepicker upgrade #6993 + versions: [">= 0.10.1"] + - dependency-name: Mongoose #Mongoose upgrade #7017 + versions: [">= 8.4.3"] open-pull-requests-limit: 5 labels: - dependencies rebase-strategy: disabled groups: - dev-minor-dependencies: - applies-to: version-updates - update-types: [minor, patch] - patterns: - - "*" - exclude-patterns: - - "@babel*" - - "@storybook*" - - "@sentry*" - - "@dnd-kit" - - socket.io babel: applies-to: version-updates patterns: @@ -47,4 +44,31 @@ updates: eslint: applies-to: version-updates patterns: - - socket.io* \ No newline at end of file + - eslint* + dev-minor-dependencies: + applies-to: version-updates + update-types: [minor, patch] + patterns: + - "*" + exclude-patterns: + - "@babel*" + - "@storybook*" + - "@sentry*" + - "@dnd-kit" + - socket.io + - recharts + - eslint + - flowbite + - flowbite-react + - flowbite-datepicker + - flowbite-typography + - Mongoose + - react-datepicker + - "@headlessui/react" + dev-major-dependencies: + applies-to: version-updates + update-types: [major] + patterns: + - "*" + exclude-patterns: + - cookie diff --git a/app/react/App/App.js b/app/react/App/App.js index 516de6fae6..05d22199c6 100644 --- a/app/react/App/App.js +++ b/app/react/App/App.js @@ -1,21 +1,21 @@ +/* eslint-disable import/no-named-as-default */ import React, { useState, useMemo } from 'react'; import PropTypes from 'prop-types'; import { Outlet, useLocation, useParams } from 'react-router-dom'; -import { useSetAtom } from 'jotai'; +import { useAtom } from 'jotai'; import Notifications from 'app/Notifications'; import Cookiepopup from 'app/App/Cookiepopup'; import { TranslateForm, t } from 'app/I18N'; import { Icon } from 'UI'; import { socket } from 'app/socket'; import { NotificationsContainer } from 'V2/Components/UI'; -import { Matomo } from 'app/V2/Components/Analitycs'; +import { Matomo, CleanInsights } from 'app/V2/Components/Analitycs'; import { settingsAtom } from 'V2/atoms/settingsAtom'; import Confirm from './Confirm'; import { Menu } from './Menu'; import { AppMainContext } from './AppMainContext'; import SiteName from './SiteName'; import GoogleAnalytics from './GoogleAnalytics'; -import { CleanInsights } from 'app/V2/Components/Analitycs'; import 'react-widgets/dist/css/react-widgets.css'; import 'bootstrap/dist/css/bootstrap.css'; import 'nprogress/nprogress.css'; @@ -27,13 +27,15 @@ import 'flowbite'; const App = ({ customParams }) => { const [showMenu, setShowMenu] = useState(false); const [confirmOptions, setConfirmOptions] = useState({}); - const setSettings = useSetAtom(settingsAtom); + const [settings, setSettings] = useAtom(settingsAtom); const location = useLocation(); const params = useParams(); const sharedId = params.sharedId || customParams?.sharedId; + + const possibleLanguages = settings.languages?.map(l => l.key) || []; const shouldAddAppClassName = - ['/', `/${params.lang}/`].includes(location.pathname) || + ['/', ...possibleLanguages.map(lang => `/${lang}/`)].includes(location.pathname) || location.pathname.match(/\/page\/.*\/.*/g) || location.pathname.match(/\/entity\/.*/g); @@ -58,8 +60,8 @@ const App = ({ customParams }) => { const appClassName = shouldAddAppClassName && sharedId ? `pageId_${sharedId}` : ''; - socket.on('updateSettings', settings => { - setSettings(settings); + socket.on('updateSettings', _settings => { + setSettings(_settings); }); return ( diff --git a/app/react/App/styles/globals.css b/app/react/App/styles/globals.css index 97deede448..f1d3c6f4d5 100644 --- a/app/react/App/styles/globals.css +++ b/app/react/App/styles/globals.css @@ -1827,6 +1827,13 @@ input[type="range"]::-ms-fill-lower { margin-top: 1.5rem; } +.line-clamp-2 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + .block { display: block; } @@ -1867,6 +1874,16 @@ input[type="range"]::-ms-fill-lower { display: none; } +.size-\[20px\] { + width: 20px; + height: 20px; +} + +.size-\[28px\] { + width: 28px; + height: 28px; +} + .h-10 { height: 2.5rem; } @@ -2133,6 +2150,10 @@ input[type="range"]::-ms-fill-lower { width: 75%; } +.w-\[105px\] { + width: 105px; +} + .w-\[17px\] { width: 17px; } @@ -2159,6 +2180,14 @@ input[type="range"]::-ms-fill-lower { width: max-content; } +.w-\[120px\] { + width: 120px; +} + +.w-5\/6 { + width: 83.333333%; +} + .min-w-20 { min-width: 5rem; } diff --git a/app/react/Documents/components/DocumentSidePanel.js b/app/react/Documents/components/DocumentSidePanel.js index cf6888272e..c210b62ed5 100644 --- a/app/react/Documents/components/DocumentSidePanel.js +++ b/app/react/Documents/components/DocumentSidePanel.js @@ -794,7 +794,7 @@ DocumentSidePanel.propTypes = { search: PropTypes.string, }).isRequired, navigate: PropTypes.func.isRequired, - selectedDocument: PropTypes.instanceOf(Immutable), + selectedDocument: PropTypes.instanceOf(Immutable.Map), // relationships v2 newRelationshipsEnabled: PropTypes.bool, }; diff --git a/app/react/Routes.tsx b/app/react/Routes.tsx index a4b66e2ed5..fa26bd2bdc 100644 --- a/app/react/Routes.tsx +++ b/app/react/Routes.tsx @@ -51,6 +51,14 @@ import { ParagraphExtractorDashboard, ParagraphExtractorLoader, } from 'app/V2/Routes/Settings/ParagraphExtraction/ParagraphExtraction'; +import { + PXEntityDashboard, + PXEntityLoader, +} from 'app/V2/Routes/Settings/ParagraphExtraction/PXEntities'; +import { + PXParagraphDashboard, + PXParagraphLoader, +} from 'app/V2/Routes/Settings/ParagraphExtraction/PXParagraphs'; import { loggedInUsersRoute, adminsOnlyRoute, @@ -142,12 +150,22 @@ const getRoutesLayout = ( } /> - + )} /> + )} + /> + )} + /> ); +const languageLayout = (langKey: string, layout: React.JSX.Element) => ( + + {layout} + } /> + +); + const getRoutes = ( settings: ClientSettings | undefined, userId: string | undefined, @@ -221,13 +246,11 @@ const getRoutes = ( ) => { const { element, parameters } = getIndexElement(settings, userId); const layout = getRoutesLayout(settings, element, headers); + const languageKeys = settings?.languages?.map(lang => lang.key) || []; return createRoutesFromElements( }> {layout} - - {layout} - } /> - + {languageKeys.map(langKey => languageLayout(langKey, layout))} } /> ); diff --git a/app/react/Settings/components/SettingsNavigation.tsx b/app/react/Settings/components/SettingsNavigation.tsx index f24d0fc700..b28ecf1e87 100644 --- a/app/react/Settings/components/SettingsNavigation.tsx +++ b/app/react/Settings/components/SettingsNavigation.tsx @@ -84,7 +84,7 @@ const SettingsNavigationComponent = ({ allowcustomJS }: { allowcustomJS: boolean diff --git a/app/react/V2/Components/Forms/specs/MultiselectList.cy.tsx b/app/react/V2/Components/Forms/specs/MultiselectList.cy.tsx index 05071b64d7..cc7ea4a01e 100644 --- a/app/react/V2/Components/Forms/specs/MultiselectList.cy.tsx +++ b/app/react/V2/Components/Forms/specs/MultiselectList.cy.tsx @@ -224,7 +224,6 @@ describe('MultiselectList.cy.tsx', () => { }); }); - // add blank state test describe('blank state property', () => { it('should show blank state property if there is no items passed to the component', () => { cy.viewport(450, 650); diff --git a/app/react/V2/Routes/Settings/Pages/PageEditor.tsx b/app/react/V2/Routes/Settings/Pages/PageEditor.tsx index d0f52c110d..0c96a21739 100644 --- a/app/react/V2/Routes/Settings/Pages/PageEditor.tsx +++ b/app/react/V2/Routes/Settings/Pages/PageEditor.tsx @@ -31,7 +31,7 @@ const pageEditorLoader = (headers?: IncomingHttpHeaders): LoaderFunction => async ({ params }) => { if (params.sharedId) { - const page = await pagesAPI.getBySharedId(params.sharedId, params.lang || 'en', headers); + const page = await pagesAPI.getBySharedId(params.sharedId, headers); return page; } @@ -136,8 +136,8 @@ const PageEditor = () => { Basic}>
-
-
+
+
Enable this page to be used as an entity view page: @@ -162,7 +162,7 @@ const PageEditor = () => { : '' } label={URL} - className="mb-4 w-full" + className="w-full mb-4" id="page-url" /> @@ -184,9 +184,9 @@ const PageEditor = () => { Markdown}> -
+
-
+
{ Javascript}> -
+
-
+
{ -
+
diff --git a/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx b/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx index 46e5e627e1..c18637a126 100644 --- a/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx +++ b/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx @@ -19,17 +19,13 @@ const ActionCell = ({ cell }: CellContext) => { const isEntityView = cell.row.original.entityView; return ( -
- +
+ - + @@ -54,7 +50,7 @@ const UrlCell = ({ cell }: CellContext) => { }; const List = ({ items }: { items: TablePage[] }) => ( -
    +
      {items.map(item => (
    • {item.title}
    • ))} diff --git a/app/react/V2/Routes/Settings/ParagraphExtraction/PXEntities.tsx b/app/react/V2/Routes/Settings/ParagraphExtraction/PXEntities.tsx new file mode 100644 index 0000000000..e6104a2522 --- /dev/null +++ b/app/react/V2/Routes/Settings/ParagraphExtraction/PXEntities.tsx @@ -0,0 +1,88 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { IncomingHttpHeaders } from 'http'; +import { LoaderFunction, useLoaderData } from 'react-router-dom'; +import * as pxEntitiesApi from 'app/V2/api/paragraphExtractor/entities'; +import { SettingsContent } from 'V2/Components/Layouts/SettingsContent'; +import { Table } from 'V2/Components/UI'; +import { RequestParams } from 'app/utils/RequestParams'; +import { Template } from 'app/apiResponseTypes'; +import { I18NApi } from 'app/I18N'; +import { LanguageSchema } from 'shared/types/commonTypes'; +import { templatesAtom } from 'V2/atoms'; +import { useAtomValue } from 'jotai'; +import { tableColumns } from './components/PXEntityTableElements'; +import { TableTitle } from './components/TableTitle'; +import { PXEntityTable, PXEntityApiResponse } from './types'; +import { getTemplateName } from './utils/getTemplateName'; + +const formatEntityData = ( + entities: PXEntityApiResponse[], + templates: Template[], + languagePool: LanguageSchema[] +): PXEntityTable[] => + entities.map(entity => { + const templateName = getTemplateName(templates, entity.templateId); + const languages = entity.languages.map(language => { + const { label = language } = languagePool.find(pool => pool.key === language) || {}; + return label; + }); + return { + ...entity, + rowId: entity._id || '', + templateName, + languages, + }; + }); + +const PXEntityDashboard = () => { + const [templatesFrom, setTemplatesFrom] = useState([]); + const { entities = [], languages = [] } = useLoaderData() as { + entities: PXEntityApiResponse[]; + languages: LanguageSchema[]; + }; + const templates = useAtomValue(templatesAtom); + const pxEntitiesData = useMemo( + () => formatEntityData(entities, templates, languages), + [entities, templates, languages] + ); + + useEffect(() => { + const uniqueTemplateNames = [...new Set(pxEntitiesData.map(datum => datum.templateName))]; + setTemplatesFrom(uniqueTemplateNames.filter(Boolean)); + }, [pxEntitiesData]); + + return ( +
      + + + + } + defaultSorting={[{ id: '_id', desc: false }]} + /> + + + + ); +}; + +const PXEntityLoader = + (headers?: IncomingHttpHeaders): LoaderFunction => + async ({ params: { extractorId = '' } }) => { + const [entities, languages] = await Promise.all([ + pxEntitiesApi.getByParagraphExtractorId(extractorId), + I18NApi.getLanguages(new RequestParams({}, headers)), + ]); + return { entities, languages }; + }; + +export { PXEntityDashboard, PXEntityLoader }; diff --git a/app/react/V2/Routes/Settings/ParagraphExtraction/PXParagraphs.tsx b/app/react/V2/Routes/Settings/ParagraphExtraction/PXParagraphs.tsx new file mode 100644 index 0000000000..7845d66c3e --- /dev/null +++ b/app/react/V2/Routes/Settings/ParagraphExtraction/PXParagraphs.tsx @@ -0,0 +1,145 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { IncomingHttpHeaders } from 'http'; +import { LoaderFunction, useLoaderData } from 'react-router-dom'; +import * as pxParagraphApi from 'app/V2/api/paragraphExtractor/paragraphs'; +import { SettingsContent } from 'V2/Components/Layouts/SettingsContent'; +import { Table, Button } from 'V2/Components/UI'; +import { Sidepanel } from 'V2/Components/UI/Sidepanel'; +import { Template } from 'app/apiResponseTypes'; +import { Translate, I18NApi } from 'app/I18N'; +import { LanguageSchema } from 'shared/types/commonTypes'; +import { RequestParams } from 'app/utils/RequestParams'; +import { tableBuilder } from './components/PXParagraphTableElements'; +import { TableTitle } from './components/TableTitle'; +import { PXParagraphTable, PXParagraphApiResponse, PXEntityApiResponse } from './types'; +import { getTemplateName } from './utils/getTemplateName'; +import { ViewParagraph } from './components/ViewParagraph'; +import { templatesAtom } from 'V2/atoms'; +import { useAtomValue } from 'jotai'; + +const formatParagraphData = ( + paragraphs: PXParagraphApiResponse[], + templates: Template[], + languagePool: LanguageSchema[] +): PXParagraphTable[] => + paragraphs.map(paragraph => { + const templateName = getTemplateName(templates, paragraph.templateId); + const languages = paragraph.languages.map(language => { + const { label = language } = languagePool.find(pool => pool.key === language) || {}; + return label; + }); + return { + ...paragraph, + rowId: paragraph._id || '', + templateName, + languages, + }; + }); + +const PXParagraphDashboard = () => { + const { + paragraphs = [], + extractorId, + languages = [], + } = useLoaderData() as { + extractorId: string; + entity: PXEntityApiResponse; + paragraphs: PXParagraphApiResponse[]; + languages: LanguageSchema[]; + }; + + const [sidePanel, setSidePanel] = useState(false); + const [paragraphOnView, setParagraphOnView] = useState(undefined); + const [paragraphInfo, setParagraphInfo] = useState(undefined); + const templates = useAtomValue(templatesAtom); + + const pxParagraphData = useMemo( + () => formatParagraphData(paragraphs, templates, languages), + [paragraphs, templates, languages] + ); + + useEffect(() => { + if (pxParagraphData.length) { + setParagraphInfo(pxParagraphData[0]); + } + }, [pxParagraphData]); + + const openSidePanel = (id: string): void => { + setSidePanel(true); + const targetParagraph = pxParagraphData.find(paragraph => paragraph._id === id); + setParagraphOnView(targetParagraph); + }; + + return ( +
      + + + +
      + ) + } + defaultSorting={[{ id: '_id', desc: false }]} + /> + + + { + setSidePanel(false); + }} + title={ + + Entity + + } + > + + {paragraphOnView && } + + +
      + +
      +
      +
      + + ); +}; + +const PXParagraphLoader = + (headers?: IncomingHttpHeaders): LoaderFunction => + async ({ params: { extractorId = '' } }) => { + const [paragraphs = [], languages] = await Promise.all([ + pxParagraphApi.getByParagraphExtractorId(extractorId), + I18NApi.getLanguages(new RequestParams({}, headers)), + ]); + return { paragraphs, extractorId, languages }; + }; + +export { PXParagraphDashboard, PXParagraphLoader }; diff --git a/app/react/V2/Routes/Settings/ParagraphExtraction/ParagraphExtraction.tsx b/app/react/V2/Routes/Settings/ParagraphExtraction/ParagraphExtraction.tsx index 053d6302a0..ce73466cb3 100644 --- a/app/react/V2/Routes/Settings/ParagraphExtraction/ParagraphExtraction.tsx +++ b/app/react/V2/Routes/Settings/ParagraphExtraction/ParagraphExtraction.tsx @@ -3,27 +3,22 @@ import React, { useMemo, useState } from 'react'; import { IncomingHttpHeaders } from 'http'; import { LoaderFunction, useLoaderData, useRevalidator } from 'react-router-dom'; import * as extractorsAPI from 'app/V2/api/paragraphExtractor/extractors'; -import * as templatesAPI from 'V2/api/templates'; import { SettingsContent } from 'V2/Components/Layouts/SettingsContent'; -import { ClientTemplateSchema } from 'app/istore'; import { Button, Table, ConfirmationModal } from 'V2/Components/UI'; import { Translate } from 'app/I18N'; -import { useSetAtom } from 'jotai'; -import { notificationAtom } from 'V2/atoms'; -import { extractorsTableColumns } from './components/TableElements'; -import { TableParagraphExtractor, ParagraphExtractorApiResponse } from './types'; +import { Template } from 'app/apiResponseTypes'; +import { useSetAtom, useAtomValue } from 'jotai'; +import { notificationAtom, templatesAtom } from 'V2/atoms'; +import { tableColumns } from './components/PXTableElements'; +import { PXTable, ParagraphExtractorApiResponse } from './types'; import { List } from './components/List'; import { ExtractorModal } from './components/ExtractorModal'; - -const getTemplateName = (templates: ClientTemplateSchema[], targetId: string) => { - const foundTemplate = templates.find(template => template._id === targetId); - return foundTemplate?.name || targetId; -}; +import { getTemplateName } from './utils/getTemplateName'; const formatExtractors = ( extractors: ParagraphExtractorApiResponse[], - templates: ClientTemplateSchema[] -): TableParagraphExtractor[] => + templates: Template[] +): PXTable[] => extractors.map(extractor => { const targetTemplateName = getTemplateName(templates, extractor.templateTo); const originTemplateNames = (extractor.templatesFrom || []).map(templateFrom => @@ -39,14 +34,14 @@ const formatExtractors = ( }); const ParagraphExtractorDashboard = () => { - const { extractors = [], templates } = useLoaderData() as { + const { extractors = [] } = useLoaderData() as { extractors: ParagraphExtractorApiResponse[]; - templates: ClientTemplateSchema[]; }; + const templates = useAtomValue(templatesAtom); const revalidator = useRevalidator(); const [isSaving, setIsSaving] = useState(false); - const [selected, setSelected] = useState([]); + const [selected, setSelected] = useState([]); const [confirmModal, setConfirmModal] = useState(false); const [extractorModal, setExtractorModal] = useState(false); const setNotifications = useSetAtom(notificationAtom); @@ -94,11 +89,10 @@ const ParagraphExtractorDashboard = () => { > -
      Extractors @@ -108,8 +102,7 @@ const ParagraphExtractorDashboard = () => { onChange={({ selectedRows }) => { setSelected(() => paragraphExtractorData.filter(ex => ex.rowId in selectedRows)); }} - // what default sorting to use? - defaultSorting={[{ id: 'status', desc: false }]} + defaultSorting={[{ id: '_id', desc: false }]} /> @@ -168,9 +161,8 @@ const ParagraphExtractorDashboard = () => { const ParagraphExtractorLoader = (headers?: IncomingHttpHeaders): LoaderFunction => async () => { - const extractors = await extractorsAPI.get(); - const templates = await templatesAPI.get(headers); - return { extractors, templates }; + const extractors = await extractorsAPI.get(headers); + return { extractors }; }; export { ParagraphExtractorDashboard, ParagraphExtractorLoader }; diff --git a/app/react/V2/Routes/Settings/ParagraphExtraction/components/DisplayPills.tsx b/app/react/V2/Routes/Settings/ParagraphExtraction/components/DisplayPills.tsx new file mode 100644 index 0000000000..61086a3a8e --- /dev/null +++ b/app/react/V2/Routes/Settings/ParagraphExtraction/components/DisplayPills.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { Pill } from 'app/V2/Components/UI'; + +const DisplayPills = ({ items = [] }: { items: string[] }) => ( + <> + {items.map(item => ( + + {item} + + ))} + +); + +export { DisplayPills }; diff --git a/app/react/V2/Routes/Settings/ParagraphExtraction/components/ExtractorModal.tsx b/app/react/V2/Routes/Settings/ParagraphExtraction/components/ExtractorModal.tsx index d52d8bd24d..05221b45c7 100644 --- a/app/react/V2/Routes/Settings/ParagraphExtraction/components/ExtractorModal.tsx +++ b/app/react/V2/Routes/Settings/ParagraphExtraction/components/ExtractorModal.tsx @@ -5,19 +5,20 @@ import * as extractorsAPI from 'app/V2/api/paragraphExtractor/extractors'; import { ArrowRightIcon } from '@heroicons/react/20/solid'; import { Modal, Button, MultiselectList } from 'V2/Components/UI'; import { Translate } from 'app/I18N'; -import { ClientTemplateSchema } from 'app/istore'; +import { Template } from 'app/apiResponseTypes'; import { ParagraphExtractorApiPayload } from '../types'; import { NoQualifiedTemplatesMessage } from './NoQualifiedTemplate'; +import { Link } from 'react-router-dom'; interface ExtractorModalProps { setShowModal: React.Dispatch>; onClose: () => void; onAccept: () => void; - templates: ClientTemplateSchema[]; + templates: Template[]; extractor?: ParagraphExtractorApiPayload; } -const formatOptions = (templates: ClientTemplateSchema[]) => +const formatOptions = (templates: Template[]) => templates.map(template => { const option = { label: template.name, @@ -29,11 +30,14 @@ const formatOptions = (templates: ClientTemplateSchema[]) => return option; }); -const templatesWithParagraph = (template: ClientTemplateSchema) => - template.properties.some(({ name }) => name === 'rich_text'); +const templatesWithParagraph = (template: Template) => + template.properties?.some(({ name }) => name === 'rich_text'); const isActiveStepClassName = (isActive: boolean) => (isActive ? 'bg-indigo-700' : 'bg-gray-200'); +const linkPXTemplateCriteria = + 'https://uwazi.readthedocs.io/en/latest/admin-docs/paragraph-extraction.html'; + const ExtractorModal = ({ setShowModal, onClose, @@ -118,13 +122,15 @@ const ExtractorModal = ({
      - {/* duplicate structure, can be a function */}
      {templateToOptions.length !== 0 && step === 1 && ( - Templates meeting required criteria + Templates meeting required criteria.{' '} + + Read Documentation + )}
      diff --git a/app/react/V2/Routes/Settings/ParagraphExtraction/components/List.tsx b/app/react/V2/Routes/Settings/ParagraphExtraction/components/List.tsx index f81526ba0b..edef99406b 100644 --- a/app/react/V2/Routes/Settings/ParagraphExtraction/components/List.tsx +++ b/app/react/V2/Routes/Settings/ParagraphExtraction/components/List.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { Translate } from 'app/I18N'; -import { TableParagraphExtractor } from '../types'; +import { PXTable } from '../types'; -const List = ({ items }: { items: TableParagraphExtractor[] }) => ( +const List = ({ items }: { items: PXTable[] }) => (
        {items.map(item => (
      • diff --git a/app/react/V2/Routes/Settings/ParagraphExtraction/components/PXEntityTableElements.tsx b/app/react/V2/Routes/Settings/ParagraphExtraction/components/PXEntityTableElements.tsx new file mode 100644 index 0000000000..f3ab7a235d --- /dev/null +++ b/app/react/V2/Routes/Settings/ParagraphExtraction/components/PXEntityTableElements.tsx @@ -0,0 +1,112 @@ +/* eslint-disable max-lines */ +/* eslint-disable react/no-multi-comp */ +import React from 'react'; +import { CellContext, createColumnHelper } from '@tanstack/react-table'; +import { Link } from 'react-router-dom'; +import { Translate } from 'app/I18N'; +import { Button, Pill } from 'V2/Components/UI'; +import { PXEntityTable } from '../types'; + +const pxColumnHelper = createColumnHelper(); + +const TableHeaderContainer = ({ children }: { children: React.ReactNode }) => ( + {children} +); +const TemplateHeader = () => ( + + Template + +); +const EntityHeader = () => ( + + Entity + +); +const DocumentHeader = () => ( + + Document + +); +const LanguageHeader = () => ( + + Language + +); +const ParagraphCountHeader = () => ( + + Paragraphs + +); +const ActionHeader = () => ( + + Action + +); + +const DisplayCell = ({ + cell, +}: CellContext< + PXEntityTable, + PXEntityTable['title'] | PXEntityTable['document'] | PXEntityTable['paragraphCount'] +>) => {cell.getValue()}; + +const LanguagesCell = ({ cell }: CellContext) => ( +
        + {cell.getValue().map(value => ( +
        + {value} +
        + ))} +
        +); + +const LinkButton = ({ cell }: CellContext) => ( + + + +); + +const TemplateCell = ({ cell }: CellContext) => ( +
        +
        + {cell.getValue()} +
        +
        +); + +const tableColumns = [ + pxColumnHelper.accessor('templateName', { + header: TemplateHeader, + enableSorting: true, + cell: TemplateCell, + }), + pxColumnHelper.accessor('title', { + header: EntityHeader, + enableSorting: true, + cell: DisplayCell, + }), + pxColumnHelper.accessor('document', { + header: DocumentHeader, + enableSorting: true, + cell: DisplayCell, + }), + pxColumnHelper.accessor('languages', { + header: LanguageHeader, + enableSorting: true, + cell: LanguagesCell, + }), + pxColumnHelper.accessor('paragraphCount', { + header: ParagraphCountHeader, + enableSorting: true, + cell: DisplayCell, + }), + pxColumnHelper.accessor('_id', { + header: ActionHeader, + enableSorting: true, + cell: LinkButton, + }), +]; + +export { tableColumns }; diff --git a/app/react/V2/Routes/Settings/ParagraphExtraction/components/PXParagraphTableElements.tsx b/app/react/V2/Routes/Settings/ParagraphExtraction/components/PXParagraphTableElements.tsx new file mode 100644 index 0000000000..c908d210c7 --- /dev/null +++ b/app/react/V2/Routes/Settings/ParagraphExtraction/components/PXParagraphTableElements.tsx @@ -0,0 +1,73 @@ +/* eslint-disable max-lines */ +/* eslint-disable react/no-multi-comp */ +import React from 'react'; +import { CellContext, createColumnHelper } from '@tanstack/react-table'; +import { Translate } from 'app/I18N'; +import { Button } from 'V2/Components/UI'; +import { PXParagraphTable } from '../types'; + +const pxColumnHelper = createColumnHelper(); + +const TableHeaderContainer = ({ children }: { children: React.ReactNode }) => ( + {children} +); +const TemplateHeader = () => ( + + Paragraph # + +); +const EntityHeader = () => ( + + Text + +); +const ActionHeader = () => ( + + Action + +); + +const DisplayCell = ({ cell }: CellContext) => ( + + {cell.getValue()} + +); + +const ParagraphNoCell = ({ cell }: CellContext) => ( + + {cell.getValue()} + +); + +const ViewButton = (action: () => void) => ( + +); + +const tableBuilder = ({ onViewAction }: { onViewAction: (paragraphId: string) => void }) => [ + pxColumnHelper.accessor('rowId', { + header: TemplateHeader, + cell: ParagraphNoCell, + enableSorting: false, + }), + pxColumnHelper.accessor('text', { + header: EntityHeader, + cell: DisplayCell, + meta: { headerClassName: 'w-5/6' }, + enableSorting: false, + }), + pxColumnHelper.accessor('_id', { + header: ActionHeader, + cell: props => + ViewButton(() => { + const paragraphId = props.cell.getValue(); + if (paragraphId) { + onViewAction(paragraphId); + } + }), + enableSorting: false, + }), +]; + +export { tableBuilder }; diff --git a/app/react/V2/Routes/Settings/ParagraphExtraction/components/PXTableElements.tsx b/app/react/V2/Routes/Settings/ParagraphExtraction/components/PXTableElements.tsx new file mode 100644 index 0000000000..34f1907aeb --- /dev/null +++ b/app/react/V2/Routes/Settings/ParagraphExtraction/components/PXTableElements.tsx @@ -0,0 +1,103 @@ +/* eslint-disable max-lines */ +/* eslint-disable react/no-multi-comp */ +import React from 'react'; +import { CellContext, createColumnHelper } from '@tanstack/react-table'; +import { Link } from 'react-router-dom'; +import { Translate } from 'app/I18N'; +import { Button, Pill } from 'V2/Components/UI'; +import { PXTable } from '../types'; +import { TableTitle } from './TableTitle'; + +const extractorColumnHelper = createColumnHelper(); + +const TableHeaderContainer = ({ children }: { children: React.ReactNode }) => ( + {children} +); + +const TemplateFromHeader = () => ( + + Template + +); +const TemplateToHeader = () => ( + + Target Template + +); +const DocumentsHeader = () => ( + + Documents + +); +const GeneratedEntitiesHeader = () => ( + + Generated Entities + +); +const ActionHeader = () => ( + + Action + +); + +const NumericCell = ({ + cell, +}: CellContext) => ( + {cell.getValue()} +); + +const TemplatesCell = ({ cell }: CellContext) => ( +
        +
        + {cell.getValue()} +
        +
        +); + +const TemplateFromCell = ({ cell }: CellContext) => ( +
        + {cell.getValue().map(value => ( +
        + {value} +
        + ))} +
        +); + +const LinkButton = ({ cell }: CellContext) => ( + + + +); + +const tableColumns = [ + extractorColumnHelper.accessor('originTemplateNames', { + header: TemplateFromHeader, + enableSorting: true, + cell: TemplateFromCell, + }), + extractorColumnHelper.accessor('targetTemplateName', { + header: TemplateToHeader, + enableSorting: true, + cell: TemplatesCell, + }), + extractorColumnHelper.accessor('documents', { + header: DocumentsHeader, + enableSorting: true, + cell: NumericCell, + }), + extractorColumnHelper.accessor('generatedEntities', { + header: GeneratedEntitiesHeader, + enableSorting: true, + cell: NumericCell, + }), + extractorColumnHelper.accessor('_id', { + header: ActionHeader, + enableSorting: true, + cell: LinkButton, + }), +]; + +export { tableColumns, TableTitle }; diff --git a/app/react/V2/Routes/Settings/ParagraphExtraction/components/TableElements.tsx b/app/react/V2/Routes/Settings/ParagraphExtraction/components/TableElements.tsx deleted file mode 100644 index 1a1df4d762..0000000000 --- a/app/react/V2/Routes/Settings/ParagraphExtraction/components/TableElements.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* eslint-disable max-lines */ -/* eslint-disable react/no-multi-comp */ -import React from 'react'; -import { CellContext, createColumnHelper } from '@tanstack/react-table'; -import { Link } from 'react-router-dom'; -import { Translate } from 'app/I18N'; -import { Button, Pill } from 'V2/Components/UI'; -import { TableParagraphExtractor } from '../types'; - -const extractorColumnHelper = createColumnHelper(); - -const TemplateFromHeader = () => Template; -const TemplateToHeader = () => Target Template; -const DocumentsHeader = () => Documents; -const GeneratedEntitiesHeader = () => Generated Entities; -const ActionHeader = () => Action; - -const NumericCell = ({ - cell, -}: CellContext< - TableParagraphExtractor, - TableParagraphExtractor['documents'] | TableParagraphExtractor['generatedEntities'] ->) => {cell.getValue()}; - -const TemplatesCell = ({ - cell, -}: CellContext) => ( -
        -
        - {cell.getValue()} -
        -
        -); - -const TemplateFromCell = ({ - cell, -}: CellContext) => ( -
        - {cell.getValue().map(value => ( -
        - {value} -
        - ))} -
        -); - -const LinkButton = ({ - cell, -}: CellContext) => ( - - - -); - -const extractorsTableColumns = [ - extractorColumnHelper.accessor('originTemplateNames', { - header: TemplateFromHeader, - enableSorting: true, - cell: TemplateFromCell, - meta: { headerClassName: 'w-4/6' }, - }), - extractorColumnHelper.accessor('targetTemplateName', { - header: TemplateToHeader, - enableSorting: true, - cell: TemplatesCell, - meta: { headerClassName: 'w-4/6' }, - }), - extractorColumnHelper.accessor('documents', { - header: DocumentsHeader, - enableSorting: true, - cell: NumericCell, - meta: { headerClassName: 'w-4/6' }, - }), - extractorColumnHelper.accessor('generatedEntities', { - header: GeneratedEntitiesHeader, - enableSorting: true, - cell: NumericCell, - meta: { headerClassName: 'w-4/6' }, - }), - extractorColumnHelper.accessor('_id', { - header: ActionHeader, - enableSorting: true, - cell: LinkButton, - meta: { headerClassName: '' }, - }), -]; - -export { extractorsTableColumns }; diff --git a/app/react/V2/Routes/Settings/ParagraphExtraction/components/TableTitle.tsx b/app/react/V2/Routes/Settings/ParagraphExtraction/components/TableTitle.tsx new file mode 100644 index 0000000000..e1a1e51545 --- /dev/null +++ b/app/react/V2/Routes/Settings/ParagraphExtraction/components/TableTitle.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Icon } from 'app/UI'; +import { Translate } from 'app/I18N'; +import { DisplayPills } from './DisplayPills'; + +const TableTitle = ({ items = [], icon = 'user' }: { items: string[]; icon?: string }) => ( +
        +
        + +
        +
        + Paragraphs{' '} + for +
        + +
        +); + +export { TableTitle }; diff --git a/app/react/V2/Routes/Settings/ParagraphExtraction/components/ViewParagraph.tsx b/app/react/V2/Routes/Settings/ParagraphExtraction/components/ViewParagraph.tsx new file mode 100644 index 0000000000..b76a9d50d5 --- /dev/null +++ b/app/react/V2/Routes/Settings/ParagraphExtraction/components/ViewParagraph.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { Translate } from 'app/I18N'; +import { PXParagraphTable } from '../types'; +import { DisplayPills } from './DisplayPills'; + +const ViewParagraph = ({ paragraphData }: { paragraphData: PXParagraphTable }) => ( +
        +
        +
        + Entity title - #{paragraphData._id} -{' '} + {paragraphData.languages.join(', ')} +
        +
        + {paragraphData.templateName} +
        +
        +
        + ID: + + {paragraphData.document} + +
        +
        + Language: +
        +
        + Paragraph: {paragraphData.paragraphCount} +
        +
        +
        + Text +
        +
        {paragraphData.text}
        +
        +
        +); + +export { ViewParagraph }; diff --git a/app/react/V2/Routes/Settings/ParagraphExtraction/types.ts b/app/react/V2/Routes/Settings/ParagraphExtraction/types.ts index 45b8cc9381..2388664c7a 100644 --- a/app/react/V2/Routes/Settings/ParagraphExtraction/types.ts +++ b/app/react/V2/Routes/Settings/ParagraphExtraction/types.ts @@ -9,8 +9,38 @@ export type ParagraphExtractorApiResponse = ParagraphExtractorApiPayload & { generatedEntities: number; }; -export type TableParagraphExtractor = ParagraphExtractorApiResponse & { +export type PXTable = ParagraphExtractorApiResponse & { rowId: string; targetTemplateName: string; originTemplateNames: string[]; }; + +// +export type PXEntityApiResponse = { + _id: string; + title: string; + templateId: string; + document: string; + languages: string[]; + paragraphCount: number; +}; + +export type PXEntityTable = PXEntityApiResponse & { + rowId: string; + templateName: string; +}; + +export type PXParagraphApiResponse = { + _id: string; + title: string; + templateId: string; + document: string; + languages: string[]; + paragraphCount: number; + text: string; +}; + +export type PXParagraphTable = PXParagraphApiResponse & { + rowId: string; + templateName: string; +}; diff --git a/app/react/V2/Routes/Settings/ParagraphExtraction/utils/getTemplateName.ts b/app/react/V2/Routes/Settings/ParagraphExtraction/utils/getTemplateName.ts new file mode 100644 index 0000000000..65172d94cf --- /dev/null +++ b/app/react/V2/Routes/Settings/ParagraphExtraction/utils/getTemplateName.ts @@ -0,0 +1,8 @@ +import { Template } from 'app/apiResponseTypes'; + +const getTemplateName = (templates: Template[], targetId: string) => { + const foundTemplate = templates.find(template => template._id === targetId); + return foundTemplate?.name || targetId; +}; + +export { getTemplateName }; diff --git a/app/react/V2/Routes/Settings/RelationshipTypes/RelationshipTypes.tsx b/app/react/V2/Routes/Settings/RelationshipTypes/RelationshipTypes.tsx index 8fd9250b1d..284edc3479 100644 --- a/app/react/V2/Routes/Settings/RelationshipTypes/RelationshipTypes.tsx +++ b/app/react/V2/Routes/Settings/RelationshipTypes/RelationshipTypes.tsx @@ -8,8 +8,7 @@ import { useSetAtom, useAtomValue } from 'jotai'; import { Translate } from 'app/I18N'; import * as relationshipTypesAPI from 'app/V2/api/relationshiptypes'; import { Template } from 'app/apiResponseTypes'; -import { notificationAtom, templatesAtom } from 'app/V2/atoms'; -import { relationshipTypesAtom } from 'app/V2/atoms/relationshipTypes'; +import { notificationAtom, templatesAtom, relationshipTypesAtom } from 'app/V2/atoms'; import { Button, Table, Sidepanel, ConfirmationModal } from 'app/V2/Components/UI'; import { SettingsContent } from 'app/V2/Components/Layouts/SettingsContent'; import { columns, Relationships, TableRelationshipType } from './components/TableComponents'; @@ -134,7 +133,7 @@ const RelationshipTypes = () => { {selectedItems.length > 0 && ( -
        +