From 2c82509e3ffda98b319136c78dcc8c225fa84e55 Mon Sep 17 00:00:00 2001 From: matgo Date: Thu, 21 Sep 2023 14:31:13 +0200 Subject: [PATCH] feat: #1204557 Boost Preview Grid Component Design system --- .../components/public/locales/en/boost.json | 14 +- .../components/public/locales/en/common.json | 6 +- .../components/public/locales/fr/boost.json | 14 +- .../components/public/locales/fr/common.json | 5 +- .../components/atoms/boost/Boost.stories.tsx | 2 +- .../src/components/atoms/boost/Boost.test.tsx | 2 +- .../src/components/atoms/boost/Boost.tsx | 8 +- .../PreviewGridBoostConfiguration.stories.tsx | 16 + .../form/PreviewGridBoostConfiguration.tsx | 108 +++ .../atoms/form/SearchBar.stories.tsx | 34 + .../src/components/atoms/form/SearchBar.tsx | 40 + .../CustomTable/Pagination/Pagination.tsx | 28 +- .../FiltersPreviewBoostingTab.styled.tsx | 41 + .../FiltersPreviewBoostingTab.tsx | 24 + .../FiltersPreviewBoostingTabs.stories.tsx | 23 + .../FiltersPreviewBoostingTabs.tsx | 86 ++ .../organisms/CustomTable/CustomTable.tsx | 4 + .../CustomTableBody/NonDraggableBody.tsx | 3 + .../CustomTableHeader/CustomTableHeader.tsx | 43 +- .../CustomTableRow/DraggableRow.tsx | 20 +- .../CustomTableRow/NonDraggableRow.tsx | 54 +- .../PreviewBoostingTable.stories.tsx | 22 + .../PreviewBoostingTable.tsx | 213 +++++ .../FieldGuesser/ReadableFieldGuesser.tsx | 28 +- packages/shared/src/constants/graphql.ts | 12 + .../static/boosts_preview_bags_search.json | 751 ++++++++++++++++++ packages/shared/src/types/customTables.ts | 18 +- packages/shared/src/types/field.ts | 4 +- 28 files changed, 1558 insertions(+), 65 deletions(-) create mode 100644 packages/components/src/components/atoms/form/PreviewGridBoostConfiguration.stories.tsx create mode 100644 packages/components/src/components/atoms/form/PreviewGridBoostConfiguration.tsx create mode 100644 packages/components/src/components/atoms/form/SearchBar.stories.tsx create mode 100644 packages/components/src/components/atoms/form/SearchBar.tsx create mode 100644 packages/components/src/components/molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTab.styled.tsx create mode 100644 packages/components/src/components/molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTab.tsx create mode 100644 packages/components/src/components/molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTabs.stories.tsx create mode 100644 packages/components/src/components/molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTabs.tsx create mode 100644 packages/components/src/components/organisms/PreviewBoostingTable/PreviewBoostingTable.stories.tsx create mode 100644 packages/components/src/components/organisms/PreviewBoostingTable/PreviewBoostingTable.tsx create mode 100644 packages/shared/src/mocks/static/boosts_preview_bags_search.json diff --git a/packages/components/public/locales/en/boost.json b/packages/components/public/locales/en/boost.json index 92b0ce98..5320ea64 100644 --- a/packages/components/public/locales/en/boost.json +++ b/packages/components/public/locales/en/boost.json @@ -6,5 +6,17 @@ "title.create": "Creating of boost", "empty.searchLimitations": "No operator was added. Click on the checkbox to select all search terms or add one.", "empty.searchLimitations.queryText": "No search term was added. Click on the checkbox to select all search terms or add one.", - "empty.categoryLimitations": "No category is selected. Click on the checkbox to select all categories or choose at least one from the drop-down list." + "empty.categoryLimitations": "No category is selected. Click on the checkbox to select all categories or choose at least one from the drop-down list.", + "noProductSearch": "No products were found", + "beforeBoosting": "Base results", + "afterBoosting": "Optimized results", + "previewArea": "Preview area", + "searchCatalog": "Search catalog", + "navigationCatalog": "Navigation catalog", + "autocompletionBox": "Autocompletion box", + "search": "Search", + "termSearch": "Search a term", + "selectCategory": "Select a category", + "emptyCategoryMessage": "No categories have been selected, please add one below", + "emptySearchMessage": "No search terms have been entered, please add one below" } diff --git a/packages/components/public/locales/en/common.json b/packages/components/public/locales/en/common.json index d40e5f63..d6da6a21 100644 --- a/packages/components/public/locales/en/common.json +++ b/packages/components/public/locales/en/common.json @@ -58,5 +58,9 @@ "error.format": "Wrong or incomplet format", "placeholder.requestType.search": "Selection of categories", "back": "Back", - "placeholder.default.textFieldTags": "Add text" + "placeholder.default.textFieldTags": "Add text", + "result_zero": "result", + "results": "results", + "result_one": "{{count}} result", + "result_other": "{{count}} results" } diff --git a/packages/components/public/locales/fr/boost.json b/packages/components/public/locales/fr/boost.json index 399ab0f8..e4f8aa9e 100644 --- a/packages/components/public/locales/fr/boost.json +++ b/packages/components/public/locales/fr/boost.json @@ -6,5 +6,17 @@ "title.create": "Création d’un boost", "empty.searchLimitations": "Aucun opérateur n'a été ajouté. Cliquez sur la case à cocher pour selectionner tous les termes de recherche ou ajoutez-en un.", "empty.searchLimitations.queryText": "Aucun terme de recherche n'a été ajouté. Cliquez sur la case à cocher pour selectionner tous les termes de recherche ou ajoutez-en un.", - "empty.categoryLimitations": "Aucune catégorie n'est selectionnée. Cliquez sur la case à cocher pour selectionner toutes les catégories ou choississez en au moins une dans la liste déroulante." + "empty.categoryLimitations": "Aucune catégorie n'est selectionnée. Cliquez sur la case à cocher pour selectionner toutes les catégories ou choississez en au moins une dans la liste déroulante.", + "noProductSearch": "Aucun produit n'a été trouvé", + "beforeBoosting": "Résultats de base", + "afterBoosting": "Résultats optimisés", + "previewArea": "Zone d'aperçu", + "searchCatalog": "Recherche catalogue", + "navigationCatalog": "Navigation catalogue", + "autocompletionBox": "Autocompletion box", + "search": "Recherche", + "termSearch": "Rechercher un terme", + "selectCategory": "Sélectionner une catégorie", + "emptyCategoryMessage": "Aucune catégorie n’a été sélectionnée, veuillez en ajouter une ci-dessous", + "emptySearchMessage": "Aucun terme de recherche n’a été renseigné, veuillez en ajouter un ci-dessous" } diff --git a/packages/components/public/locales/fr/common.json b/packages/components/public/locales/fr/common.json index 33495b1f..218578a1 100644 --- a/packages/components/public/locales/fr/common.json +++ b/packages/components/public/locales/fr/common.json @@ -63,5 +63,8 @@ "All search terms": "Tous les termes de recherches", "Add": "Ajouter", "Thesaurus": "Thésaurus", - "Edit": "Éditer" + "results": "résultats", + "result_zero": "resultat", + "result_one": "{{count}} resultat", + "result_other": "{{count}} resultats" } diff --git a/packages/components/src/components/atoms/boost/Boost.stories.tsx b/packages/components/src/components/atoms/boost/Boost.stories.tsx index fb9113a9..7421e294 100644 --- a/packages/components/src/components/atoms/boost/Boost.stories.tsx +++ b/packages/components/src/components/atoms/boost/Boost.stories.tsx @@ -14,7 +14,7 @@ const Template: ComponentStory = (args) => ( export const Boost = Template.bind({}) Boost.args = { - type: 'down' as 'up' | 'down' | 'no boost', + type: 'down' as 'up' | 'down' | 'straight', boostNumber: 1, boostMultiplicator: 1.1, } diff --git a/packages/components/src/components/atoms/boost/Boost.test.tsx b/packages/components/src/components/atoms/boost/Boost.test.tsx index 26647f7b..80bc40e1 100644 --- a/packages/components/src/components/atoms/boost/Boost.test.tsx +++ b/packages/components/src/components/atoms/boost/Boost.test.tsx @@ -7,7 +7,7 @@ import Boost from './Boost' describe('Boost', () => { it('Should match snapschot', () => { const infos = { - type: 'down' as 'up' | 'down' | 'no boost', + type: 'down' as 'up' | 'down' | 'straight', boostNumber: 1, boostMultiplicator: 1.1, } diff --git a/packages/components/src/components/atoms/boost/Boost.tsx b/packages/components/src/components/atoms/boost/Boost.tsx index 92aebf36..f995ff7a 100644 --- a/packages/components/src/components/atoms/boost/Boost.tsx +++ b/packages/components/src/components/atoms/boost/Boost.tsx @@ -4,7 +4,7 @@ import { Box } from '@mui/system' import IonIcon from '../IonIcon/IonIcon' interface IProps { - type: 'up' | 'down' | 'no boost' + type: 'up' | 'down' | 'straight' boostNumber: number boostMultiplicator: number } @@ -16,7 +16,7 @@ interface IBoostDetails { } function setBoostDetails( - type: 'up' | 'down' | 'no boost', + type: 'up' | 'down' | 'straight', boostNumber: number, boostMultiplicator: number ): IBoostDetails { @@ -35,7 +35,7 @@ function setBoostDetails( boostTextColor: 'error.main', boostIconName: 'trending-down-outline', } - case 'no boost': + case 'straight': return { boostText: 'no boost', boostColor: 'neutral.light', @@ -67,7 +67,7 @@ function Boost(props: IProps): JSX.Element { + +const Template: ComponentStory< + typeof PreviewGridBoostConfigurationComponent +> = () => { + return +} + +export const PreviewGridBoostConfiguration = Template.bind({}) diff --git a/packages/components/src/components/atoms/form/PreviewGridBoostConfiguration.tsx b/packages/components/src/components/atoms/form/PreviewGridBoostConfiguration.tsx new file mode 100644 index 00000000..916c3098 --- /dev/null +++ b/packages/components/src/components/atoms/form/PreviewGridBoostConfiguration.tsx @@ -0,0 +1,108 @@ +import React, { useEffect, useState } from 'react' +import PreviewBoostingTable from '../../organisms/PreviewBoostingTable/PreviewBoostingTable' +import FiltersPreviewBoostingTabs, { + IPreviewBoostFilter, +} from '../../molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTabs' +import { useTranslation } from 'react-i18next' +import { ITreeItem } from '@elastic-suite/gally-admin-shared' +import { styled } from '@mui/material' +import Pagination from '../../molecules/CustomTable/Pagination/Pagination' + +import productsMock from '../../../../public/mocks/boosts_preview_bags_search.json' +import categoriesMock from '../../../../public/mocks/categories.json' + +const PreviewContainer = styled('div')(({ theme }) => ({ + borderRadius: theme.shape.borderRadius, + padding: theme.spacing(2), + paddingBottom: theme.spacing(4), + fontFamily: 'Inter', + backgroundColor: '#E2E6F3', + overflow: 'hidden', + + '.previewArea': { + margin: 0, + padding: 0, + fontSize: '12px', + fontFamily: 'var(--gally-font)', + lineHeight: '18px', + color: theme.palette.colors.neutral['600'], + }, +})) + +export default function PreviewGridBoostConfiguration(): JSX.Element { + const [filter, setFilter] = useState({ + type: 'search', + value: '', + }) + const [products, setProducts] = useState< + | { + id: string + resultsAfter: any + resultsBefore: any + } + | undefined + >(undefined) + const [categories, setCategories] = useState([]) + const [currentPage, setCurrentPage] = useState(0) + const [count, setCount] = useState(0) + const rowsPerPage = 20 + const { t } = useTranslation('boost') + + useEffect(() => { + // TODO : Replace by GRAPHQL request + setProducts(undefined) + if (filter.value) { + setProducts(productsMock.data.previewBoost) + setCount(productsMock.data.previewBoost.resultsAfter.length) + setCurrentPage(0) + } + }, [filter]) + + useEffect(() => { + // TODO: Replace by GRAPHQL request + setCategories(categoriesMock.categories) + }, []) + + return ( + <> + + {Boolean(products) && ( + <> + + +

{t('previewArea')}

+ + +
+ + )} + + ) +} diff --git a/packages/components/src/components/atoms/form/SearchBar.stories.tsx b/packages/components/src/components/atoms/form/SearchBar.stories.tsx new file mode 100644 index 00000000..b93021d7 --- /dev/null +++ b/packages/components/src/components/atoms/form/SearchBar.stories.tsx @@ -0,0 +1,34 @@ +import React, { useState } from 'react' +import { ComponentMeta, ComponentStory } from '@storybook/react' + +import SearchBarComponent from './SearchBar' +import { action } from '@storybook/addon-actions' + +export default { + title: 'Atoms/Form/SearchBar', + component: SearchBarComponent, +} as ComponentMeta + +const Template: ComponentStory = ({ + value, + ...args +}) => { + const [val, setVal] = useState(value) + const sendFilter = action('send Filter') + + return ( + sendFilter(val)} + /> + ) +} + +export const SearchBar = Template.bind({}) + +SearchBar.args = { + value: '', + placeholder: 'Rechercher', +} diff --git a/packages/components/src/components/atoms/form/SearchBar.tsx b/packages/components/src/components/atoms/form/SearchBar.tsx new file mode 100644 index 00000000..17e8ca8a --- /dev/null +++ b/packages/components/src/components/atoms/form/SearchBar.tsx @@ -0,0 +1,40 @@ +import InputText from './InputText' +import { InputAdornment } from '@mui/material' +import IonIcon from '../IonIcon/IonIcon' +import React, { FormEvent } from 'react' + +export default function SearchBar({ + value, + onChange, + onResearch, + placeholder, +}: { + value: string + onChange: (value: string) => void + onResearch: () => void + placeholder: string +}): JSX.Element { + const handleSubmit = (event: FormEvent): void => { + event.preventDefault() + onResearch() + } + + return ( +
+ onChange(value.toString())} + placeholder={placeholder} + endAdornment={ + + + + } + /> + + ) +} diff --git a/packages/components/src/components/molecules/CustomTable/Pagination/Pagination.tsx b/packages/components/src/components/molecules/CustomTable/Pagination/Pagination.tsx index f5712873..82fe2524 100644 --- a/packages/components/src/components/molecules/CustomTable/Pagination/Pagination.tsx +++ b/packages/components/src/components/molecules/CustomTable/Pagination/Pagination.tsx @@ -1,4 +1,4 @@ -import React, { ChangeEvent, MouseEvent, ReactNode } from 'react' +import React, { CSSProperties, ChangeEvent, MouseEvent, ReactNode } from 'react' import { Box, IconButtonProps, @@ -28,7 +28,18 @@ const PaginationTextContainer = styled('span')({ ...paginationStyle, }) +const StyledParagraph = styled('p')({ + fontFamily: 'Inter', + fontSize: '12px', + fontWeight: 400, + color: '#424880', + lineHeight: '18px', + margin: 0, +}) + interface IProps { + style?: CSSProperties + withResults?: boolean isBottom?: boolean count: number currentPage: number @@ -42,6 +53,8 @@ interface IProps { function Pagination(props: IProps): JSX.Element { const { + style, + withResults, isBottom, count, currentPage, @@ -101,8 +114,21 @@ function Pagination(props: IProps): JSX.Element { borderRadius: '8px 8px 0 0', }), bgcolor: 'colors.white', + ...(withResults && { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + backgroundColor: '#fff', + border: '1px solid #E2E6F3', + padding: '0 22px', + }), + ...style, }} > + {Boolean(withResults) && ( + {t(`result`, { count })} + )} !containerProps.includes(prop), +})(({ displayEmptyMessage, theme }) => { + return { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + marginTop: theme.spacing(4), + marginBottom: theme.spacing(8), + ...(Boolean(!displayEmptyMessage) && { + justifyContent: 'flex-start', + alignItems: 'start', + marginTop: theme.spacing(0), + marginBottom: theme.spacing(0), + }), + } +}) + +export const EmptyMessage = styled('p')(({ theme }) => { + return { + width: '533px', + color: 'var(--neutral-900, #151A47)', + textAlign: 'center', + fontFamily: 'Inter', + fontSize: '18px', + fontStyle: 'normal', + fontWeight: 400, + lineHeight: '28px', + margin: `0 0 ${theme.spacing(4)} 0`, + } +}) diff --git a/packages/components/src/components/molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTab.tsx b/packages/components/src/components/molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTab.tsx new file mode 100644 index 00000000..d135172b --- /dev/null +++ b/packages/components/src/components/molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTab.tsx @@ -0,0 +1,24 @@ +import { ITabContentProps } from '@elastic-suite/gally-admin-shared' +import React from 'react' +import { Container, EmptyMessage } from './FiltersPreviewBoostingTab.styled' + +export interface IContainerProps extends ITabContentProps { + emptyMessage?: string | null + displayEmptyMessage: boolean + input: JSX.Element +} + +export default function FiltersPreviewBoostingTab({ + emptyMessage, + displayEmptyMessage, + input, +}: IContainerProps): JSX.Element { + return ( + + {Boolean(emptyMessage && displayEmptyMessage) && ( + {emptyMessage} + )} + {input} + + ) +} diff --git a/packages/components/src/components/molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTabs.stories.tsx b/packages/components/src/components/molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTabs.stories.tsx new file mode 100644 index 00000000..1934b7aa --- /dev/null +++ b/packages/components/src/components/molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTabs.stories.tsx @@ -0,0 +1,23 @@ +import React from 'react' +import { ComponentMeta, ComponentStory } from '@storybook/react' +import FiltersPreviewBoostingTabsComponent from './FiltersPreviewBoostingTabs' +import categoriesMock from '../../../../public/mocks/categories.json' +import { action } from '@storybook/addon-actions' + +export default { + title: 'Molecules/FiltersPreviewBoostingTabs', + component: FiltersPreviewBoostingTabsComponent, +} as ComponentMeta + +const Template: ComponentStory = ( + args +) => { + return +} + +export const FiltersPreviewBoostingTabs = Template.bind({}) + +FiltersPreviewBoostingTabs.args = { + categories: categoriesMock.categories, + onSendFilter: action('send filter'), +} diff --git a/packages/components/src/components/molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTabs.tsx b/packages/components/src/components/molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTabs.tsx new file mode 100644 index 00000000..cef2933e --- /dev/null +++ b/packages/components/src/components/molecules/FiltersPreviewBoostingTabs/FiltersPreviewBoostingTabs.tsx @@ -0,0 +1,86 @@ +import React, { useState } from 'react' +import CustomTabs from '../layout/tabs/CustomTabs' +import TreeSelector from '../../atoms/form/TreeSelector' +import { ITab, ITreeItem } from '@elastic-suite/gally-admin-shared' +import { useTranslation } from 'react-i18next' +import FiltersPreviewBoostingTab, { + IContainerProps, +} from './FiltersPreviewBoostingTab' +import SearchBar from '../../atoms/form/SearchBar' +export interface IPreviewBoostFilter { + type: 'search' | 'category' + value: string | ITreeItem +} + +interface IProps { + onSendFilter: (filter: IPreviewBoostFilter) => void + categories: ITreeItem[] +} + +function FiltersPreviewBoostingTabs({ + onSendFilter, + categories, +}: IProps): JSX.Element { + const [category, setCategory] = useState(null) + const [search, setSearch] = useState('') + const [displayEmptyMessage, setDisplayEmptyMessage] = useState(true) + + const { t } = useTranslation('boost') + const filterByTabId: IPreviewBoostFilter[] = [ + { type: 'search', value: search }, + { type: 'category', value: category }, + ] + + const tabs: ITab[] = [ + { + label: t('searchCatalog'), + Component: FiltersPreviewBoostingTab, + id: 0, + componentProps: { + emptyMessage: t('emptySearchMessage'), + displayEmptyMessage, + input: ( + { + if (displayEmptyMessage) setDisplayEmptyMessage(false) + onSendFilter({ type: 'search', value: search }) + }} + /> + ), + }, + }, + { + label: t('navigationCatalog'), + Component: FiltersPreviewBoostingTab, + id: 1, + componentProps: { + emptyMessage: t('emptyCategoryMessage'), + displayEmptyMessage, + input: ( + { + if (displayEmptyMessage) setDisplayEmptyMessage(false) + setCategory(value) + onSendFilter({ type: 'category', value: category }) + }} + value={category} + placeholder={t('selectCategory').toString()} + /> + ), + }, + }, + ] + return ( + onSendFilter(filterByTabId[id])} + /> + ) +} + +export default FiltersPreviewBoostingTabs diff --git a/packages/components/src/components/organisms/CustomTable/CustomTable.tsx b/packages/components/src/components/organisms/CustomTable/CustomTable.tsx index 0606c544..ee88be08 100644 --- a/packages/components/src/components/organisms/CustomTable/CustomTable.tsx +++ b/packages/components/src/components/organisms/CustomTable/CustomTable.tsx @@ -55,6 +55,7 @@ export interface ICustomTableProps { configuration?: IConfigurations hasEditLink?: boolean editLink?: string + withoutDragableColumn?: boolean } function CustomTable( @@ -79,6 +80,7 @@ function CustomTable( configuration, hasEditLink, editLink, + withoutDragableColumn, } = props const [scrollLength, setScrollLength] = useState(0) @@ -197,6 +199,7 @@ function CustomTable( shadow={shadow} tableHeaders={newHeadersTable} withSelection={withSelection} + withoutDragableColumn={withoutDragableColumn} /> {Boolean(!draggable) && ( )} {Boolean(draggable) && ( diff --git a/packages/components/src/components/organisms/CustomTable/CustomTableBody/NonDraggableBody.tsx b/packages/components/src/components/organisms/CustomTable/CustomTableBody/NonDraggableBody.tsx index ea0cb431..6b50b416 100644 --- a/packages/components/src/components/organisms/CustomTable/CustomTableBody/NonDraggableBody.tsx +++ b/packages/components/src/components/organisms/CustomTable/CustomTableBody/NonDraggableBody.tsx @@ -31,6 +31,7 @@ interface IProps { configuration: IConfigurations hasEditLink?: boolean editLink?: string + withoutDragableColumn?: boolean } function NonDraggableBody(props: IProps): JSX.Element { @@ -50,6 +51,7 @@ function NonDraggableBody(props: IProps): JSX.Element { configuration, hasEditLink, editLink, + withoutDragableColumn, } = props const newTableRows = hasEditLink @@ -79,6 +81,7 @@ function NonDraggableBody(props: IProps): JSX.Element { tableRow={tableRow} withSelection={withSelection} configuration={configuration} + withoutDragableColumn={withoutDragableColumn} /> ))} diff --git a/packages/components/src/components/organisms/CustomTable/CustomTableHeader/CustomTableHeader.tsx b/packages/components/src/components/organisms/CustomTable/CustomTableHeader/CustomTableHeader.tsx index 6cd228ee..88eb9985 100644 --- a/packages/components/src/components/organisms/CustomTable/CustomTableHeader/CustomTableHeader.tsx +++ b/packages/components/src/components/organisms/CustomTable/CustomTableHeader/CustomTableHeader.tsx @@ -23,6 +23,7 @@ interface IProps { shadow: boolean tableHeaders: ITableHeader[] withSelection: boolean + withoutDragableColumn?: boolean } function CustomTableHeader(props: IProps): JSX.Element { @@ -35,6 +36,7 @@ function CustomTableHeader(props: IProps): JSX.Element { shadow, tableHeaders, withSelection, + withoutDragableColumn, } = props const stickyHeaders: ITableHeaderSticky[] = manageStickyHeaders(tableHeaders) @@ -48,25 +50,27 @@ function CustomTableHeader(props: IProps): JSX.Element { backgroundColor: 'neutral.light', }} > - -   - + {Boolean(!withoutDragableColumn || withSelection) && ( + +   + + )} {Boolean(withSelection) && ( {t(header.label)} diff --git a/packages/components/src/components/organisms/CustomTable/CustomTableRow/DraggableRow.tsx b/packages/components/src/components/organisms/CustomTable/CustomTableRow/DraggableRow.tsx index f865e90a..4683320c 100644 --- a/packages/components/src/components/organisms/CustomTable/CustomTableRow/DraggableRow.tsx +++ b/packages/components/src/components/organisms/CustomTable/CustomTableRow/DraggableRow.tsx @@ -132,12 +132,15 @@ function DraggableRow(props: IProps): JSX.Element { {stickyHeaders.map((stickyHeader, i) => ( + !header.sticky) @@ -78,21 +80,23 @@ function NonDraggableRow(props: IProps): JSX.Element { return ( - + {Boolean(!withoutDragableColumn || withSelection) && ( + + )} {Boolean(withSelection) && ( + + +const Template: ComponentStory = ( + args +) => { + return +} + +export const PreviewBoostingTable = Template.bind({}) + +PreviewBoostingTable.args = { + productsBefore: productsMock.data.previewBoost.resultsBefore, + productsAfter: productsMock.data.previewBoost.resultsAfter, +} diff --git a/packages/components/src/components/organisms/PreviewBoostingTable/PreviewBoostingTable.tsx b/packages/components/src/components/organisms/PreviewBoostingTable/PreviewBoostingTable.tsx new file mode 100644 index 00000000..57406138 --- /dev/null +++ b/packages/components/src/components/organisms/PreviewBoostingTable/PreviewBoostingTable.tsx @@ -0,0 +1,213 @@ +import React from 'react' +import { + IPrice, + IScore, + IStock, + ITableHeader, + ITableRow, +} from '@elastic-suite/gally-admin-shared' +import CustomTable from '../CustomTable/CustomTable' +import FieldGuesser from '../../stateful/FieldGuesser/FieldGuesser' +import NoAttributes from '../../atoms/noAttributes/NoAttributes' +import { useTranslation } from 'react-i18next' +import { styled } from '@mui/material' + +const TableContainer = styled('div')(({ theme }) => ({ + '.tableTitles': { + margin: theme.spacing(2), + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-around', + fontFamily: 'Inter', + p: { + fontSize: '14px', + lineHeight: '20px', + color: '#151A47', + margin: 0, + '&.after': { + fontWeight: 600, + }, + }, + }, + table: { + 'th, td': { + fontSize: '14px', + fontFamily: 'Inter', + lineHeight: '20px', + }, + th: { + color: '#2F3674', + fontWeight: 500, + }, + td: { + color: '#151A47', + fontWeight: 400, + }, + }, +})) + +interface IProduct { + id: string | number + image: string + name: string[] + price: IPrice[] + stock: IStock + score: IScore | number +} + +interface IBoostingProducts { + productsBefore: IProduct[] + productsAfter: IProduct[] +} + +const convertProductToRow = ( + productsBefore: IProduct[], + productsAfter: IProduct[] +): ITableRow[] => { + const dataInfo = + productsBefore.length >= productsAfter.length + ? { + field: 'before', + products: productsBefore, + otherProducts: productsAfter, + } + : { + field: 'after', + products: productsAfter, + otherProducts: productsBefore, + } + + return dataInfo.products.map((product, index) => { + const beforeProduct = + dataInfo.field === 'before' ? product : dataInfo.otherProducts[index] + const afterProduct = + dataInfo.field === 'after' ? product : dataInfo.otherProducts[index] + + return { + id: product.id, + beforeImage: beforeProduct.image, + beforeInfo: { + productName: beforeProduct?.name[0], + stockStatus: beforeProduct?.stock.status, + price: beforeProduct?.price[0]?.price, + }, + beforeScore: beforeProduct.score, + afterImage: afterProduct?.image, + afterInfo: { + productName: afterProduct?.name[0], + stockStatus: afterProduct?.stock.status, + price: afterProduct?.price[0]?.price, + }, + afterScore: afterProduct?.score, + } + }) +} + +const globalStyle = { + verticalAlign: 'middle', +} + +const scoreFieldStyle = { + borderLeft: '1px solid #E2E6F3', +} + +const tableHeaders = [ + { + id: 'beforeImage', + name: 'beforeImage', + label: 'Image', + input: 'image', + editable: false, + sticky: false, + cellsStyle: { + ...globalStyle, + paddingRight: 0, + }, + }, + { + id: 'beforeInfo', + name: 'beforeInfo', + label: 'Info', + input: 'productInfo', + editable: false, + sticky: false, + cellsStyle: globalStyle, + }, + { + id: 'beforeScore', + name: 'beforeScore', + label: 'Score', + input: 'score', + editable: false, + sticky: false, + headerStyle: { + borderRight: '2px solid #1812A0', + }, + cellsStyle: { + borderRight: '2px solid #1812A0', + ...globalStyle, + ...scoreFieldStyle, + }, + }, + { + id: 'afterImage', + name: 'afterImage', + label: 'Image', + input: 'image', + editable: false, + sticky: false, + cellsStyle: { + ...globalStyle, + paddingRight: 0, + }, + }, + { + id: 'afterInfo', + name: 'afterInfo', + label: 'Info', + input: 'productInfo', + editable: false, + sticky: false, + cellsStyle: globalStyle, + }, + { + id: 'afterScore', + name: 'afterScore', + label: 'Score', + input: 'score', + editable: false, + sticky: false, + cellsStyle: { ...globalStyle, ...scoreFieldStyle }, + }, +] as ITableHeader[] + +function PreviewBoostingTable({ + productsBefore, + productsAfter, +}: IBoostingProducts): JSX.Element { + const { t } = useTranslation('boost') + const products = convertProductToRow(productsBefore, productsAfter) + + if (products.length === 0) + return + + return ( + <> + +
+

{t('beforeBoosting')}

+

{t('afterBoosting')}

+
+ +
+ + ) +} + +export default PreviewBoostingTable diff --git a/packages/components/src/components/stateful/FieldGuesser/ReadableFieldGuesser.tsx b/packages/components/src/components/stateful/FieldGuesser/ReadableFieldGuesser.tsx index 26be3bc5..0d479432 100644 --- a/packages/components/src/components/stateful/FieldGuesser/ReadableFieldGuesser.tsx +++ b/packages/components/src/components/stateful/FieldGuesser/ReadableFieldGuesser.tsx @@ -5,6 +5,7 @@ import { DataContentType, IFieldGuesserProps, IPrice, + IProductInfo, IScore, IStock, } from '@elastic-suite/gally-admin-shared' @@ -42,7 +43,6 @@ function ReadableFieldGuesser(props: IFieldGuesserProps): JSX.Element { const { t } = useTranslation('common') const language = useAppSelector(selectLanguage) const { localizedCatalogWithDefault } = useContext(catalogContext) - if (value === undefined || value === null) { return null } @@ -61,8 +61,10 @@ function ReadableFieldGuesser(props: IFieldGuesserProps): JSX.Element { } case DataContentType.SCORE: { - const score = { scoreValue: value } as IScore - return + const score = ( + typeof value === 'number' ? { scoreValue: value } : value + ) as IScore + return } case DataContentType.STOCK: { @@ -107,6 +109,26 @@ function ReadableFieldGuesser(props: IFieldGuesserProps): JSX.Element { ) } + case DataContentType.PRODUCTINFO: { + if (!value) { + return null + } + const { price, stockStatus, productName } = value as IProductInfo + return ( + <> +

{productName}

+
+ +
+ + + ) + } + default: { return value instanceof Array ? ( -export type BoostType = 'up' | 'down' | 'no boost' +export type BoostType = 'up' | 'down' | 'straight' export interface IBoost { type: BoostType @@ -105,3 +113,9 @@ export interface IScore { export interface IPrice { price: number } + +export interface IProductInfo { + productName: string + price: IPrice['price'] + stockStatus: IStock['status'] +} diff --git a/packages/shared/src/types/field.ts b/packages/shared/src/types/field.ts index 5fb46b0f..85330c0e 100644 --- a/packages/shared/src/types/field.ts +++ b/packages/shared/src/types/field.ts @@ -1,4 +1,4 @@ -import { SyntheticEvent } from 'react' +import { CSSProperties, SyntheticEvent } from 'react' import { IField } from './api' import { DataContentType, ITableRow } from './customTables' @@ -38,6 +38,8 @@ export interface IFieldConfig extends IFieldState { defaultValue?: unknown helperText?: string error?: boolean + headerStyle?: CSSProperties + cellsStyle?: CSSProperties } export interface IFieldConfigFormWithFieldset {