diff --git a/src/components/custom-object-form/attribute-field.tsx b/src/components/custom-object-form/attribute-field.tsx index d4e3745..fa609f7 100644 --- a/src/components/custom-object-form/attribute-field.tsx +++ b/src/components/custom-object-form/attribute-field.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react'; +import { FC, useState } from 'react'; import get from 'lodash/get'; import { useIntl } from 'react-intl'; import { FieldArray } from 'formik'; @@ -7,7 +7,12 @@ import SecondaryButton from '@commercetools-uikit/secondary-button'; import SecondaryIconButton from '@commercetools-uikit/secondary-icon-button'; import Card from '@commercetools-uikit/card'; import Constraints from '@commercetools-uikit/constraints'; -import { BinLinearIcon, PlusBoldIcon } from '@commercetools-uikit/icons'; +import { + BinLinearIcon, + EyeIcon, + PlusBoldIcon, + SearchIcon, +} from '@commercetools-uikit/icons'; import Spacings from '@commercetools-uikit/spacings'; import { closestCenter, @@ -30,6 +35,7 @@ import AttributeLabel from './attribute-label'; import AttributeInput from './attribute-input'; import messages from './messages'; import { SortableItem } from './sortable-item'; +import { CustomObjectsModal } from './custom-objects-modal'; import styles from './attribute-field.module.css'; type Props = { @@ -73,6 +79,8 @@ const AttributeField: FC = ({ dataLocale: context.dataLocale ?? '', }) ); + const [searchModalIndex, setSearchModalIndex] = useState(null); + const emptyValue = getValueByType( type, attributes, @@ -82,19 +90,15 @@ const AttributeField: FC = ({ ); const selectOptions = type === TYPES.LocalizedEnum - ? options?.map((option) => { - return { - value: option.value, - label: option.label[dataLocale], - }; - }) + ? options?.map((option) => ({ + value: option.value, + label: option.label[dataLocale], + })) : options; const sensors = useSensors( useSensor(PointerSensor), - useSensor(KeyboardSensor, { - coordinateGetter: sortableKeyboardCoordinates, - }) + useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) ); return ( @@ -103,6 +107,27 @@ const AttributeField: FC = ({ { + const handleSelect = (selectedId: string) => { + const newItem = { + typeId: 'key-value-document', + key: selectedId, + }; + + const updatedValue = [...(value || [])]; + const emptyIndex = updatedValue.findIndex( + (item: any) => item?.key === '' + ); + + if (emptyIndex !== -1) { + updatedValue[emptyIndex] = newItem; + } else { + updatedValue.push(newItem); + } + + form.setFieldValue(name, updatedValue); + setSearchModalIndex(null); + }; + const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; if (active.id !== over?.id) { @@ -142,10 +167,10 @@ const AttributeField: FC = ({ onDragEnd={handleDragEnd} > `${i}`)} + items={value?.map((_: any, i: number) => `${i}`)} strategy={verticalListSortingStrategy} > - {value.map((val: any, index: number) => ( + {value?.map((val: any, index: number) => ( = ({ options={selectOptions} /> + : } + label="Search" + onClick={() => setSearchModalIndex(index)} + /> } @@ -184,6 +214,14 @@ const AttributeField: FC = ({ ))} + {searchModalIndex !== null && ( + setSearchModalIndex(null)} + handleSelect={handleSelect} + objectId={value?.[searchModalIndex]?.key} + /> + )} ); }} diff --git a/src/components/custom-object-form/custom-objects-modal.tsx b/src/components/custom-object-form/custom-objects-modal.tsx new file mode 100644 index 0000000..3afd6ca --- /dev/null +++ b/src/components/custom-object-form/custom-objects-modal.tsx @@ -0,0 +1,205 @@ +import { useEffect, useState } from 'react'; +import { + InfoModalPage, + PageNotFound, +} from '@commercetools-frontend/application-components'; +import LoadingSpinner from '@commercetools-uikit/loading-spinner'; +import { + useDataTableSortingState, + usePaginationState, +} from '@commercetools-uikit/hooks'; +import Grid from '@commercetools-uikit/grid'; +import { customProperties } from '@commercetools-uikit/design-system'; +import Card from '@commercetools-uikit/card'; +import Spacings from '@commercetools-uikit/spacings'; +import Text from '@commercetools-uikit/text'; +import { FormattedMessage, useIntl } from 'react-intl'; +import Constraints from '@commercetools-uikit/constraints'; +import SelectInput from '@commercetools-uikit/select-input'; +import map from 'lodash/map'; +import SecondaryButton from '@commercetools-uikit/secondary-button'; +import { EditIcon } from '@commercetools-uikit/icons'; +import { useHistory, useRouteMatch } from 'react-router-dom'; +import { + useCustomObjectsFetcher, + useCustomObjectFetcher, +} from '../../hooks/use-custom-object-connector/use-custom-object-connector'; +import messages from '../container-list/messages'; +import customObjectsMessages from '../custom-objects-list/messages'; +import { useContainerContext } from '../../context/container-context'; +import TextFilter from '../custom-objects-list/text-filter'; +import { renderObject } from '../custom-objects-list/render-object'; + +export const CustomObjectsModal = ({ + isOpen, + close, + handleSelect, + objectId, +}: { + isOpen: boolean; + close: () => void; + handleSelect: (value: string) => void; + objectId?: string; +}) => { + const { hasContainers, containers } = useContainerContext(); + const intl = useIntl(); + const match = useRouteMatch(); + const { replace } = useHistory(); + + const { page, perPage } = usePaginationState(); + const [container, setContainer] = useState( + containers.map((item) => item.key)[0] || '' + ); + const [key, setKey] = useState(''); + const tableSorting = useDataTableSortingState({ key: 'key', order: 'asc' }); + + // FETCH FOR SINGLE OBJECT (IF objectId provided) + const { + customObject, + loading: singleLoading, + error: singleError, + refetch: refetchSingleObject, + } = useCustomObjectFetcher({ + id: objectId, + }); + + useEffect(() => { + if (objectId) { + refetchSingleObject(); + } + }, [objectId, refetchSingleObject]); + + // FETCH FOR LIST (if no objectId) + const { customObjectsPaginatedResult, loading } = useCustomObjectsFetcher({ + limit: perPage.value, + offset: (page.value - 1) * perPage.value, + sort: [`${tableSorting.value.key} ${tableSorting.value.order}`], + container: container, + where: key && key !== '' ? `key="${key}"` : undefined, + }); + + const containerOptions = map(containers, ({ key: containerKey }) => ({ + label: containerKey, + value: containerKey, + })); + + function filterByContainer(event: any) { + const { value } = event.target; + setContainer(value); + } + + const handleSelection = (value: string) => { + handleSelect(value); + }; + + const renderSingleObject = () => { + if (singleLoading) return ; + if (singleError || !customObject) return ; + + return ( +
+ {renderObject(customObject)} + + + } + as="a" + onClick={() => { + const baseUrl = match.url.split('/').slice(0, -1).join('/'); + const newUrl = `${baseUrl}/${customObject.id}`; + + replace(newUrl); + }} + label="Edit Custom Object" + /> + + +
+ ); + }; + + const renderList = () => { + if ( + !loading && + !hasContainers && + (!customObjectsPaginatedResult || !customObjectsPaginatedResult.results) + ) { + return ; + } + + if (loading) return ; + + return ( + + + + + + + + + + + + + + {customObjectsPaginatedResult?.results && + customObjectsPaginatedResult?.results.map(({ id, key, value }) => { + return ( + handleSelection(id)}> + + + {key} + + + )?.length, + }} + {...messages.attributesLabel} + /> + + + + ); + })} + + + ); + }; + + return ( + + {objectId ? renderSingleObject() : renderList()} + + ); +}; diff --git a/src/components/custom-objects-list/custom-objects-list.tsx b/src/components/custom-objects-list/custom-objects-list.tsx index ce0c3de..878c606 100644 --- a/src/components/custom-objects-list/custom-objects-list.tsx +++ b/src/components/custom-objects-list/custom-objects-list.tsx @@ -1,12 +1,10 @@ import { lazy, useState } from 'react'; -import { FormattedDate, FormattedMessage, useIntl } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; import camelCase from 'lodash/camelCase'; import includes from 'lodash/includes'; import isEmpty from 'lodash/isEmpty'; import isPlainObject from 'lodash/isPlainObject'; -import isString from 'lodash/isString'; import map from 'lodash/map'; -import startCase from 'lodash/startCase'; import LoadingSpinner from '@commercetools-uikit/loading-spinner'; import { ContentNotification } from '@commercetools-uikit/notifications'; import SpacingsStack from '@commercetools-uikit/spacings-stack'; @@ -41,6 +39,7 @@ import { columnDefinitions, COLUMN_KEYS } from './column-definitions'; import messages from './messages'; import styles from './custom-objects-list.module.css'; import TextFilter from './text-filter'; +import { renderObject } from './render-object'; const CreateCustomObject = lazy(() => import('../create-custom-object')); @@ -96,73 +95,6 @@ const CustomObjectsList = () => { return ; } - function renderValue(value: any) { - if (isPlainObject(value)) { - return ( -
- {renderObject(value)} -
- ); - } - - if (Array.isArray(value)) { - return ( -
- {map(value, (val, index) => ( -
- {renderValue(val)} -
- ))} -
- ); - } - - const dateRegex = /\d{4}-\d{2}-\d{2}/; - if (isString(value) && value.match(dateRegex)) { - return value.indexOf('T') >= 0 ? ( - - ) : ( - - ); - } - - return value.toString(); - } - - function renderObject(value: { [key: string]: unknown }) { - const result = Object.entries(value).map(([key, value]) => { - return ( -
- - {startCase(key)}: - -   - {renderValue(value)} -
- ); - }); - - return result; - } - function getDisplayAttributes( attributes: Array ): Array { diff --git a/src/components/custom-objects-list/render-object.tsx b/src/components/custom-objects-list/render-object.tsx new file mode 100644 index 0000000..915a888 --- /dev/null +++ b/src/components/custom-objects-list/render-object.tsx @@ -0,0 +1,70 @@ +import { FormattedDate } from 'react-intl'; +import isPlainObject from 'lodash/isPlainObject'; +import isString from 'lodash/isString'; +import map from 'lodash/map'; +import startCase from 'lodash/startCase'; +import Text from '@commercetools-uikit/text'; +import styles from './custom-objects-list.module.css'; + +function renderValue(value: any) { + if (isPlainObject(value)) { + return ( +
+ {renderObject(value)} +
+ ); + } + + if (Array.isArray(value)) { + return ( +
+ {map(value, (val, index) => ( +
+ {renderValue(val)} +
+ ))} +
+ ); + } + + const dateRegex = /\d{4}-\d{2}-\d{2}/; + if (isString(value) && value.match(dateRegex)) { + return value.indexOf('T') >= 0 ? ( + + ) : ( + + ); + } + + return value.toString(); +} + +export function renderObject(value: { [key: string]: unknown }) { + const result = Object.entries(value).map(([key, value]) => { + return ( +
+ + {startCase(key)}: + +   + {renderValue(value)} +
+ ); + }); + + return result; +}