Skip to content

Commit

Permalink
7640 px results (#7689)
Browse files Browse the repository at this point in the history
* PX: update styling of no qualified templates message

* apply new criteria message

* px modal styling update

* [MultiSelectList] hideFilters config

* [MultiSelectList] custom classes for item and item container

* [Px - Extractor Modal] Custom classes update for item and item container

* [Px - Extractor Modal] update styling for step 2

* update translations

* [MultiSelectList] set default item class names

* [Table] set default message when data passed is empty

* [Table] remove displaying of boolean result

* [PX] table, delete modal and pagination

* 7640: px ui updates

* use updated react-router, comment prepared lines for filtering, updated translations

* fix eslint errors

* remove duplicates

* fix and update unit test screenshot

* fix login credentials and empty table

* revert password change
  • Loading branch information
josh-huridocs authored Feb 24, 2025
1 parent 4d42a55 commit 66b75dc
Show file tree
Hide file tree
Showing 51 changed files with 1,798 additions and 732 deletions.
Binary file added app/api/csv/specs/zipData/test.zip
Binary file not shown.
12 changes: 4 additions & 8 deletions app/react/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,13 @@ import { CustomUploads, customUploadsLoader } from 'V2/Routes/Settings/CustomUpl
import { FiltersTable, filtersLoader } from 'V2/Routes/Settings/Filters';
import { RouteErrorBoundary, GeneralError } from 'V2/Components/ErrorHandling';
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';
} from 'app/V2/Routes/Settings/ParagraphExtraction/PXLoaders';
import { ParagraphExtractorDashboard } from 'app/V2/Routes/Settings/ParagraphExtraction/ParagraphExtraction';
import { PXEntityDashboard } from 'app/V2/Routes/Settings/ParagraphExtraction/PXEntities';
import { PXParagraphDashboard } from 'app/V2/Routes/Settings/ParagraphExtraction/PXParagraphs';
import {
loggedInUsersRoute,
adminsOnlyRoute,
Expand Down
88 changes: 50 additions & 38 deletions app/react/V2/Components/Forms/MultiselectList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ interface MultiselectListProps {
startOnSelected?: boolean;
search?: string;
suggestions?: boolean;
itemClassName?: string | null;
itemContainerClassName?: string | null;
hideFilters?: boolean;
blankState?: string | React.ReactNode;
}

Expand All @@ -52,6 +55,9 @@ const MultiselectList = ({
startOnSelected = false,
search = '',
suggestions = false,
hideFilters = false,
itemClassName = null,
itemContainerClassName = null,
blankState = <Translate>No items available</Translate>,
}: MultiselectListProps) => {
const [selectedItems, setSelectedItems] = useState<string[]>(value || []);
Expand Down Expand Up @@ -200,11 +206,11 @@ const MultiselectList = ({

const selected = selectedItems.includes(item.value);
const borderSyles = selected
? 'border-sucess-200'
? 'border-success-200'
: 'border-transparent hover:border-primary-300';

return (
<li key={item.value} className="mb-4">
<li key={item.value} className={`${itemClassName ?? 'bg-gray-50 rounded-lg mb-4'}`}>
<button
type="button"
className={`w-full flex text-left p-2.5 border ${borderSyles} rounded-lg items-center`}
Expand All @@ -229,7 +235,7 @@ const MultiselectList = ({
return (
<li
key={item.value}
className={`mb-2 ${!selected && searchTerm && !showAll ? 'opacity-70' : ''}`}
className={`${!selected && searchTerm && !showAll ? 'opacity-70' : ''} ${itemClassName ?? ' bg-gray-50 rounded-lg mb-2'} `}
>
<Checkbox
name={item.value}
Expand Down Expand Up @@ -258,7 +264,7 @@ const MultiselectList = ({
const isOpen = isGroupOpen(group.value);
if (foldableGroups) {
return (
<li key={group.value} className="mb-4">
<li key={group.value} className={`${itemClassName ?? 'bg-gray-50 rounded-lg mb-4'}`}>
<div
className={`flex justify-between p-3 mb-4 rounded-lg ${isOpen ? 'bg-indigo-50' : 'bg-gray-50'}`}
onClick={() => handleGroupToggle(group.value)}
Expand All @@ -274,15 +280,19 @@ const MultiselectList = ({
<Translate>Group</Translate>
</button>
</div>
{isOpen && <ul className="pl-4">{group.items?.map(renderItem)}</ul>}
{isOpen && (
<ul className={`${itemContainerClassName ?? 'pl-4 '}`}>
{group.items?.map(renderItem)}
</ul>
)}
</li>
);
}

return (
<li key={group.value} className="mb-4">
<li key={group.value} className={`${itemClassName ?? 'bg-gray-50 rounded-lg mb-4'}`}>
<span className="block mb-4 text-sm font-bold text-gray-900">{group.label}</span>
<ul className="">{group.items?.map(renderItem)}</ul>
<ul className={`${itemContainerClassName ?? ''}`}>{group.items?.map(renderItem)}</ul>
</li>
);
};
Expand Down Expand Up @@ -319,44 +329,46 @@ const MultiselectList = ({
value={searchTerm}
clearFieldAction={() => setSearchTerm('')}
/>
<div className="flex mx-1 my-4 flex-nowrap">
<RadioSelect
name="filter"
orientation="horizontal"
options={[
{
label: <Translate data-testid="multiselectlist-show-all">All</Translate>,
value: 'true',
defaultChecked: !startOnSelected,
},
{
label: renderSelectedLabel(),
value: 'false',
disabled: selectedOrSuggestedItems.size === 0,
defaultChecked: startOnSelected,
},
]}
onChange={applyFilter}
className="flex-grow"
/>
{allowSelelectAll && (
<button
type="button"
className="text-gray-400 underline"
onClick={() => handleSelectAll()}
>
<Translate>Select all</Translate>
</button>
)}
</div>
{!hideFilters && (
<div className="flex mx-1 my-4 flex-nowrap" data-testid="multiselectlist-filters">
<RadioSelect
name="filter"
orientation="horizontal"
options={[
{
label: <Translate data-testid="multiselectlist-show-all">All</Translate>,
value: 'true',
defaultChecked: !startOnSelected,
},
{
label: renderSelectedLabel(),
value: 'false',
disabled: selectedOrSuggestedItems.size === 0,
defaultChecked: startOnSelected,
},
]}
onChange={applyFilter}
className="flex-grow"
/>
{allowSelelectAll && (
<button
type="button"
className="text-gray-400 underline"
onClick={() => handleSelectAll()}
>
<Translate>Select all</Translate>
</button>
)}
</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}>
<ul className={`${itemContainerClassName ?? ' w-full px-2 pt-2 grow '}`} ref={optionsRef}>
{filteredItems.map(renderItem)}
</ul>
</div>
Expand Down
148 changes: 148 additions & 0 deletions app/react/V2/Components/Forms/specs/MultiselectList.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -245,4 +245,152 @@ describe('MultiselectList.cy.tsx', () => {
cy.contains('no items string').should('be.visible');
});
});

describe('hide filters property', () => {
it('should load/show filters when hideFilters is not set', () => {
cy.viewport(450, 650);
mount(
<div className="p-2 tw-content">
<MultiselectList onChange={() => {}} items={[]} />
</div>
);
cy.get('[data-testid="multiselectlist-filters"]').should('exist');
});

it('should not load/show filters when hideFilters is true', () => {
cy.viewport(450, 650);
mount(
<div className="p-2 tw-content">
<MultiselectList onChange={() => {}} items={[]} hideFilters />
</div>
);
cy.get('[data-testid="multiselectlist-filters"]').should('not.exist');
});
});

describe('custom class name properties', () => {
it('should apply a custom class name to each item', () => {
cy.viewport(450, 650);
mount(
<div className="p-2 tw-content">
<MultiselectList
onChange={() => {}}
items={[
{
label: 'Item 1',
searchLabel: 'Item 1',
value: 'item1',
},
{
label: 'Item 2',
searchLabel: 'Item 2',
value: 'item2',
},
]}
itemClassName="bg-gray-50"
/>
</div>
);
cy.get('li').should('have.class', 'bg-gray-50');
});

it('should apply the default class name to each item if no custom class name is provided', () => {
cy.viewport(450, 650);
mount(
<div className="p-2 tw-content">
<MultiselectList
onChange={() => {}}
items={[
{
label: 'Item 1',
searchLabel: 'Item 1',
value: 'item1',
},
{
label: 'Item 2',
searchLabel: 'Item 2',
value: 'item2',
},
]}
/>
</div>
);
cy.get('li').should('have.class', 'bg-gray-50');
});

it('should apply a custom class name to each checkbox item', () => {
cy.viewport(450, 650);
mount(
<div className="p-2 tw-content">
<MultiselectList
onChange={() => {}}
items={[
{
label: 'Item 1',
searchLabel: 'Item 1',
value: 'item1',
},
{
label: 'Item 2',
searchLabel: 'Item 2',
value: 'item2',
},
]}
checkboxes
itemClassName="bg-gray-50"
/>
</div>
);
cy.get('li').should('have.class', 'bg-gray-50');
});

it('should apply the default class name to each checkbox item if no custom class name is provided', () => {
cy.viewport(450, 650);
mount(
<div className="p-2 tw-content">
<MultiselectList
onChange={() => {}}
items={[
{
label: 'Item 1',
searchLabel: 'Item 1',
value: 'item1',
},
{
label: 'Item 2',
searchLabel: 'Item 2',
value: 'item2',
},
]}
checkboxes
/>
</div>
);
cy.get('li').should('have.class', 'mb-2');
});

it('should apply a custom class name to the items container', () => {
cy.viewport(450, 650);
mount(
<div className="p-2 tw-content">
<MultiselectList
onChange={() => {}}
items={[]}
itemContainerClassName="custom-container-class"
/>
</div>
);
cy.get('ul').should('have.class', 'custom-container-class');
});

it('should apply the default class name to the items container if no custom class name for the container is provided', () => {
cy.viewport(450, 650);
mount(
<div className="p-2 tw-content">
<MultiselectList onChange={() => {}} items={[]} />
</div>
);
cy.get('ul').should('have.class', 'w-full px-2 pt-2 grow');
});
});
});
4 changes: 2 additions & 2 deletions app/react/V2/Components/Layouts/SettingsContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ SettingsContent.Footer = ({
highlighted = false,
}: SettingsContentFooterProps) => (
<div
className={`bottom-0 left-0 w-full px-4 py-3 bg-white border-t border-gray-200 sticky z-1 ${className} ${
highlighted ? 'bg-primary-50' : 'bg-white'
className={`bottom-0 left-0 w-full px-4 py-3 border-t border-gray-200 sticky z-1 ${className} ${
highlighted ? 'bg-indigo-50' : 'bg-white'
}`}
data-testid="settings-content-footer"
>
Expand Down
5 changes: 4 additions & 1 deletion app/react/V2/Components/UI/Pill.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

type PillColor = 'primary' | 'gray' | 'yellow' | 'green' | 'blue' | 'red';
type PillColor = 'primary' | 'gray' | 'yellow' | 'green' | 'blue' | 'red' | 'indigo';
interface PillProps {
children: string | React.ReactNode;
color: PillColor;
Expand All @@ -27,6 +27,9 @@ const Pill = ({ children, color = 'gray', className }: PillProps) => {
case 'red':
pillColors = 'bg-red-100 text-red-800';
break;
case 'indigo':
pillColors = 'bg-indigo-100 text-indigo-800';
break;

default:
pillColors = 'bg-primary-100 text-primary-800';
Expand Down
10 changes: 10 additions & 0 deletions app/react/V2/Components/UI/Table/DefaultNoDataMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import { Translate } from 'app/I18N';

const DefaultNoDataMessage = () => (
<div className="p-10 text-center">
<Translate className="text-gray-500">NO DATA AVAILABLE</Translate>
</div>
);

export { DefaultNoDataMessage };
14 changes: 14 additions & 0 deletions app/react/V2/Components/UI/Table/NoDataRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';

interface NoDataRowProps {
colSpan: number;
DisplayElement: string | React.ReactNode;
}

const NoDataRow = ({ colSpan, DisplayElement }: NoDataRowProps) => (
<tr>
<td colSpan={colSpan}>{DisplayElement}</td>
</tr>
);

export { NoDataRow };
Loading

0 comments on commit 66b75dc

Please sign in to comment.