Skip to content

Commit

Permalink
6841 create/update paragraph extractor modal (#7342)
Browse files Browse the repository at this point in the history
* multiselectlist blank state property

* create/update modal for paragraph extractor

* filter templateTo from templatesFrom list

* update csv translations for paragraph extractor feature, fix type errors

* fix lint errors

* update translations

* update translations

* remove escaped quotes
  • Loading branch information
josh-huridocs authored Oct 11, 2024
1 parent 93bad26 commit 9454fa0
Show file tree
Hide file tree
Showing 20 changed files with 425 additions and 51 deletions.
12 changes: 12 additions & 0 deletions app/react/App/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -1824,6 +1824,10 @@ input[type="range"]::-ms-fill-lower {
margin-top: 1rem;
}

.mt-5 {
margin-top: 1.25rem;
}

.mt-6 {
margin-top: 1.5rem;
}
Expand Down Expand Up @@ -1981,6 +1985,14 @@ input[type="range"]::-ms-fill-lower {
max-height: 100svh;
}

.min-h-\[300px\] {
min-height: 300px;
}

.min-h-\[327px\] {
min-height: 327px;
}

.min-h-fit {
min-height: -moz-fit-content;
min-height: fit-content;
Expand Down
11 changes: 11 additions & 0 deletions app/react/V2/Components/Forms/MultiselectList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import React, { useEffect, useState, useRef } from 'react';
import { Translate } from 'app/I18N';
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline';
import { isString } from 'lodash';
import { InputField, RadioSelect } from '.';
import { Pill } from '../UI/Pill';
import { Label } from './Label';
Expand All @@ -31,8 +32,12 @@ interface MultiselectListProps {
startOnSelected?: boolean;
search?: string;
suggestions?: boolean;
blankState?: string | React.ReactNode;
}

const renderChild = (child: string | React.ReactNode) =>
isString(child) ? <Translate>{child}</Translate> : child;

const MultiselectList = ({
items,
onChange,
Expand All @@ -47,6 +52,7 @@ const MultiselectList = ({
startOnSelected = false,
search = '',
suggestions = false,
blankState = <Translate>No items available</Translate>,
}: MultiselectListProps) => {
const [selectedItems, setSelectedItems] = useState<string[]>(value || []);
const [showAll, setShowAll] = useState<boolean>(!(startOnSelected && selectedItems.length));
Expand Down Expand Up @@ -345,6 +351,11 @@ const MultiselectList = ({
</div>
</div>

{items.length === 0 && (
<div className="flex w-full h-full items-center justify-center min-h-[300px]">
{renderChild(blankState)}
</div>
)}
<ul className="w-full px-2 pt-2 grow" ref={optionsRef}>
{filteredItems.map(renderItem)}
</ul>
Expand Down
43 changes: 43 additions & 0 deletions app/react/V2/Components/Forms/specs/MultiselectList.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,47 @@ describe('MultiselectList.cy.tsx', () => {
cy.contains('Pepperoni').should('be.visible');
});
});

// 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);
mount(
<Provider store={createStore()}>
<div className="p-2 tw-content">
<MultiselectList onChange={() => {}} items={[]} />
</div>
</Provider>
);
cy.contains('No items available').should('be.visible');
});

it('should accept a blank state string', () => {
cy.viewport(450, 650);
mount(
<Provider store={createStore()}>
<div className="p-2 tw-content">
<MultiselectList onChange={() => {}} items={[]} blankState="nada" />
</div>
</Provider>
);
cy.contains('nada').should('be.visible');
});

it('should accept a blank state component', () => {
cy.viewport(450, 650);
mount(
<Provider store={createStore()}>
<div className="p-2 tw-content">
<MultiselectList
onChange={() => {}}
items={[]}
blankState={<div>no items string</div>}
/>
</div>
</Provider>
);
cy.contains('no items string').should('be.visible');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,44 @@ import { Translate } from 'app/I18N';
import { useSetAtom } from 'jotai';
import { notificationAtom } from 'V2/atoms';
import { extractorsTableColumns } from './components/TableElements';
import { TableExtractor, Extractor } from './types';
import { TableParagraphExtractor, 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;
};

const formatExtractors = (
extractors: Extractor[],
extractors: ParagraphExtractorApiResponse[],
templates: ClientTemplateSchema[]
): TableExtractor[] =>
(extractors || []).map(extractor => {
): TableParagraphExtractor[] =>
extractors.map(extractor => {
const targetTemplateName = getTemplateName(templates, extractor.templateTo);
const originTemplateNames = extractor.templateFrom.map(templateFrom =>
const originTemplateNames = (extractor.templatesFrom || []).map(templateFrom =>
getTemplateName(templates, templateFrom)
);

return { ...extractor, rowId: extractor._id, originTemplateNames, targetTemplateName };
return {
...extractor,
rowId: extractor._id || '',
originTemplateNames,
targetTemplateName,
};
});

const ParagraphExtractorDashboard = () => {
const { extractors = [], templates } = useLoaderData() as {
extractors: TableExtractor[];
extractors: ParagraphExtractorApiResponse[];
templates: ClientTemplateSchema[];
};
const [isSaving, setIsSaving] = useState(false);

const revalidator = useRevalidator();
const [selected, setSelected] = useState<TableExtractor[]>([]);
const [isSaving, setIsSaving] = useState(false);
const [selected, setSelected] = useState<TableParagraphExtractor[]>([]);
const [confirmModal, setConfirmModal] = useState(false);
const [extractorModal, setExtractorModal] = useState(false);
const setNotifications = useSetAtom(notificationAtom);

const deleteExtractors = async () => {
Expand All @@ -64,7 +72,15 @@ const ParagraphExtractorDashboard = () => {
setIsSaving(false);
}
};
// const handleSave = async (extractor: IXExtractorInfo) => {};

const handleSave = async () => {
revalidator.revalidate();
setNotifications({
type: 'success',
text: <Translate>Paragraph Extractor added</Translate>,
});
};

const paragraphExtractorData = useMemo(
() => formatExtractors(extractors, templates),
[extractors, templates]
Expand All @@ -73,14 +89,13 @@ const ParagraphExtractorDashboard = () => {
return (
<div
className="tw-content"
data-testid="settings-ix"
data-testid="settings-paragraph-extractor"
style={{ width: '100%', overflowY: 'auto' }}
>
<SettingsContent>
<SettingsContent.Header title="Paragraph extraction" />

<SettingsContent.Body>
{/* should create a component for empty data? */}
<Table
data={paragraphExtractorData}
columns={extractorsTableColumns}
Expand All @@ -100,7 +115,7 @@ const ParagraphExtractorDashboard = () => {

<SettingsContent.Footer className="flex gap-2">
{selected?.length === 1 ? (
<Button type="button" onClick={() => {}} disabled={isSaving}>
<Button type="button" onClick={() => setExtractorModal(true)} disabled={isSaving}>
<Translate>Edit Extractor</Translate>
</Button>
) : undefined}
Expand All @@ -115,7 +130,7 @@ const ParagraphExtractorDashboard = () => {
<Translate>Delete</Translate>
</Button>
) : (
<Button type="button" onClick={() => {}} disabled={isSaving}>
<Button type="button" onClick={() => setExtractorModal(true)} disabled={isSaving}>
<Translate>Create Extractor</Translate>
</Button>
)}
Expand All @@ -136,6 +151,16 @@ const ParagraphExtractorDashboard = () => {
dangerStyle
/>
)}

{extractorModal && (
<ExtractorModal
setShowModal={setExtractorModal}
onClose={() => setExtractorModal(false)}
onAccept={handleSave}
templates={templates}
extractor={selected?.length ? selected[0] : undefined}
/>
)}
</div>
);
};
Expand Down
Loading

0 comments on commit 9454fa0

Please sign in to comment.