All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.resources.js.data_set.visualization_modes.modes.Table.tsx Maven / Gradle / Ivy

The newest version!
/**
 * SPDX-FileCopyrightText: (c) 2000 Liferay, Inc. https://liferay.com
 * SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
 */

import ClayButton from '@clayui/button';
import ClayDropDown from '@clayui/drop-down';
import ClayForm, {ClayCheckbox, ClayInput} from '@clayui/form';
import ClayLabel from '@clayui/label';
import ClayLayout from '@clayui/layout';
import ClayLoadingIndicator from '@clayui/loading-indicator';
import ClayModal from '@clayui/modal';
import {
	FDS_INTERNAL_CELL_RENDERERS,
	IClientExtensionRenderer,
	IInternalRenderer,
} from '@liferay/frontend-data-set-web';
import {InputLocalized, openModal} from 'frontend-js-components-web';
import {fetch} from 'frontend-js-web';
import fuzzy from 'fuzzy';
import React, {useEffect, useState} from 'react';

import FieldSelectModalContent, {
	visit,
} from '../../../components/AddDataSourceFieldsModalContent';
import OrderableTable from '../../../components/OrderableTable';
import {
	API_URL,
	DEFAULT_FETCH_HEADERS,
	FUZZY_OPTIONS,
	OBJECT_RELATIONSHIP,
} from '../../../utils/constants';
import openDefaultFailureToast from '../../../utils/openDefaultFailureToast';
import openDefaultSuccessToast from '../../../utils/openDefaultSuccessToast';
import {IDataSetSectionProps} from '../../DataSet';
import AddCustomFieldModalContent from '../components/AddCustomFieldModalContent';

import '../../../../css/TableVisualizationMode.scss';

import ClayAlert from '@clayui/alert';
import ClayIcon from '@clayui/icon';

import sortItems from '../../../utils/sortItems';
import {
	EFieldType,
	IDataSetTableSection,
	IField,
	IFieldTreeItem,
} from '../../../utils/types';

const defaultLanguageId = Liferay.ThemeDisplay.getDefaultLanguageId();

const getRendererLabel = ({
	cetRenderers = [],
	rendererName,
}: {
	cetRenderers?: IClientExtensionRenderer[];
	rendererName: string;
}): string => {
	let clientExtensionRenderer;

	const internalRenderer = FDS_INTERNAL_CELL_RENDERERS.find(
		(renderer: IInternalRenderer) => {
			return renderer.name === rendererName;
		}
	);

	if (internalRenderer?.label) {
		return internalRenderer.label;
	}
	else {
		clientExtensionRenderer = cetRenderers.find(
			(renderer: IClientExtensionRenderer) => {
				return renderer.externalReferenceCode === rendererName;
			}
		);

		if (clientExtensionRenderer?.name) {
			return clientExtensionRenderer.name;
		}

		return rendererName;
	}
};

interface IRendererLabelCellRendererComponentProps {
	cetRenderers?: IClientExtensionRenderer[];
	item: IDataSetTableSection;
	query: string;
}

const RendererLabelCellRendererComponent = ({
	cetRenderers = [],
	item,
	query,
}: IRendererLabelCellRendererComponentProps) => {
	const itemFieldValue = getRendererLabel({
		cetRenderers,
		rendererName: item.renderer,
	});

	const fuzzyMatch = fuzzy.match(query, itemFieldValue, FUZZY_OPTIONS);

	return (
		
			{fuzzyMatch ? (
				
			) : (
				{itemFieldValue}
			)}
		
	);
};

const EditTableSectionModalContent = ({
	cellClientExtensionRenderers,
	closeModal,
	namespace,
	onSaveButtonClick,
	sortable,
	tableSection,
}: {
	cellClientExtensionRenderers: IClientExtensionRenderer[];
	closeModal: Function;
	namespace: string;
	onSaveButtonClick: Function;
	sortable: boolean;
	tableSection: IDataSetTableSection;
}) => {
	const [selectedCellRenderer, setSelectedCellRenderer] = useState(
		tableSection.renderer ?? 'default'
	);
	const [tableSectionSortable, setTableSectionSortable] = useState(
		tableSection.sortable
	);

	const fdsInternalCellRendererNames = FDS_INTERNAL_CELL_RENDERERS.map(
		(cellRenderer: IInternalRenderer) => cellRenderer.name
	);

	const tableSectionTranslations = tableSection.label_i18n;

	const [i18nFieldLabels, setI18nFieldLabels] = useState(
		tableSectionTranslations
	);

	const editTableSection = async () => {
		const body = {
			label_i18n: i18nFieldLabels,
			renderer: selectedCellRenderer,
			rendererType: !fdsInternalCellRendererNames.includes(
				selectedCellRenderer
			)
				? 'clientExtension'
				: 'internal',
			sortable: tableSectionSortable,
		};

		const response = await fetch(
			`${API_URL.TABLE_SECTIONS}/by-external-reference-code/${tableSection.externalReferenceCode}`,
			{
				body: JSON.stringify(body),
				headers: DEFAULT_FETCH_HEADERS,
				method: 'PATCH',
			}
		);

		if (!response.ok) {
			openDefaultFailureToast();

			return;
		}

		const editedTableSection = await response.json();

		closeModal();

		onSaveButtonClick({editedTableSection});

		openDefaultSuccessToast();
	};

	const tableSectionNameInputId = `${namespace}tableSectionNameInput`;
	const tableSectionLabelInputId = `${namespace}tableSectionLabelInput`;
	const tableSectionRendererSelectId = `${namespace}tableSectionRendererSelectId`;

	const options = FDS_INTERNAL_CELL_RENDERERS.map(
		(renderer: IInternalRenderer) => ({
			label: renderer.label!,
			value: renderer.name!,
		})
	);

	options.push(
		...cellClientExtensionRenderers.map((item) => ({
			label: item.name!,
			value: item.externalReferenceCode!,
		}))
	);

	const CellRendererDropdown = ({
		cellRenderers,
		namespace,
		onItemClick,
	}: {
		cellRenderers: {
			label: string;
			value: string;
		}[];
		namespace: string;
		onItemClick: Function;
	}) => {
		const cellClientExtensionRenderersERCs =
			cellClientExtensionRenderers.map(
				(cellRendererCET) => cellRendererCET.externalReferenceCode
			);

		return (
			
						{selectedCellRenderer
							? getRendererLabel({
									cetRenderers: cellClientExtensionRenderers,
									rendererName: selectedCellRenderer,
								})
							: Liferay.Language.get('choose-an-option')}
					
				}
			>
				
					{cellRenderers.map((cellRenderer) => (
						 onItemClick(cellRenderer.value)}
							roleItem="option"
						>
							{cellRenderer.label}

							{cellClientExtensionRenderersERCs.includes(
								cellRenderer.value
							) && (
								
									{Liferay.Language.get('client-extension')}
								
							)}
						
					))}
				
			
		);
	};

	return (
		<>
			
				{Liferay.Util.sub(
					Liferay.Language.get('edit-x'),
					tableSection.label_i18n[defaultLanguageId] ??
						tableSection.fieldName
				)}
			

			
				
					

					
				

				
					
				

				
					

					
							setSelectedCellRenderer(item)
						}
					/>
				

				
					
							setTableSectionSortable(checked)
						}
					/>

					{tableSection.type !== EFieldType.OBJECT && (
						
							
						
					)}
				
			

			
						 editTableSection()}>
							{Liferay.Language.get('save')}
						

						 closeModal()}
						>
							{Liferay.Language.get('cancel')}
						
					
				}
			/>
		
	);
};

function Table(props: IDataSetSectionProps & {title?: string}) {
	const {
		cellClientExtensionRenderers,
		dataSet,
		fieldTreeItems,
		namespace,
		saveDataSetTableSectionsURL,
		title,
	} = props;

	const [tableSections, setTableSections] =
		useState | null>(null);
	const [saveButtonDisabled, setSaveButtonDisabled] = useState(false);

	const getFDSFields = async () => {
		const response = await fetch(
			`${API_URL.TABLE_SECTIONS}?filter=(${OBJECT_RELATIONSHIP.DATA_SET_TABLE_SECTIONS_ID} eq '${dataSet.id}')&nestedFields=${OBJECT_RELATIONSHIP.DATA_SET_TABLE_SECTIONS}&sort=dateCreated:asc`,
			{
				headers: DEFAULT_FETCH_HEADERS,
			}
		);

		if (!response.ok) {
			openDefaultFailureToast();

			return null;
		}

		const responseJSON = await response.json();

		const storedFDSFields: IDataSetTableSection[] = responseJSON?.items;

		if (!storedFDSFields) {
			openDefaultFailureToast();

			return null;
		}

		const tableSectionsOrder =

			// @ts-ignore

			storedFDSFields?.[0]?.[OBJECT_RELATIONSHIP.DATA_SET_TABLE_SECTIONS]
				?.tableSectionsOrder;

		setTableSections(
			sortItems(
				storedFDSFields,
				tableSectionsOrder
			) as IDataSetTableSection[]
		);
	};

	const onDeleteButtonClick = ({item}: {item: IDataSetTableSection}) => {
		openModal({
			bodyHTML: Liferay.Language.get(
				'are-you-sure-you-want-to-delete-this-field?-fragments-using-it-will-be-affected'
			),
			buttons: [
				{
					autoFocus: true,
					displayType: 'secondary',
					label: Liferay.Language.get('cancel'),
					type: 'cancel',
				},
				{
					displayType: 'danger',
					label: Liferay.Language.get('delete'),
					onClick: async ({
						processClose,
					}: {
						processClose: Function;
					}) => {
						processClose();

						const url = `${API_URL.TABLE_SECTIONS}/${item.id}`;

						const response = await fetch(url, {method: 'DELETE'});

						if (!response.ok) {
							openDefaultFailureToast();

							return;
						}

						openDefaultSuccessToast();

						setTableSections(
							tableSections?.filter(
								(tableSection: IDataSetTableSection) =>
									tableSection.id !== item.id
							) || []
						);
					},
				},
			],
			status: 'danger',
			title: Liferay.Language.get('delete-field'),
		});
	};

	const saveDataSetTableColumns = async ({
		closeModal,
		fields,
	}: {
		closeModal: Function;
		fields: Array;
	}) => {
		setSaveButtonDisabled(true);

		const creationData: Array<{
			name: string;
			sortable: boolean;
			type: string;
		}> = [];

		fields.forEach((field) => {
			if (!field.id) {
				creationData.push({
					name: field.name,
					sortable: field.sortable || false,
					type: field.type || 'string',
				});
			}
		});

		const formData = new FormData();

		formData.append(
			`${namespace}creationData`,
			JSON.stringify(creationData)
		);

		formData.append(`${namespace}dataSetId`, dataSet.id);

		const response = await fetch(saveDataSetTableSectionsURL, {
			body: formData,
			method: 'POST',
		});

		setSaveButtonDisabled(false);

		if (!response.ok) {
			openDefaultFailureToast();

			return;
		}

		getFDSFields();

		closeModal();

		openDefaultSuccessToast();
	};

	const updateFDSFieldsOrder = async ({
		tableSectionsOrder,
	}: {
		tableSectionsOrder: string;
	}) => {
		const body = {
			tableSectionsOrder,
		};

		const response = await fetch(
			`${API_URL.DATA_SETS}/by-external-reference-code/${dataSet.externalReferenceCode}`,
			{
				body: JSON.stringify(body),
				headers: DEFAULT_FETCH_HEADERS,
				method: 'PATCH',
			}
		);

		if (!response.ok) {
			openDefaultFailureToast();

			return null;
		}

		const responseJSON = await response.json();

		const storedFDSFieldsOrder = responseJSON?.tableSectionsOrder;

		if (
			tableSections &&
			storedFDSFieldsOrder &&
			storedFDSFieldsOrder === tableSectionsOrder
		) {
			setTableSections(
				sortItems(
					tableSections,
					storedFDSFieldsOrder
				) as IDataSetTableSection[]
			);

			openDefaultSuccessToast();
		}
		else {
			openDefaultFailureToast();
		}
	};

	useEffect(() => {
		getFDSFields();

		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, []);

	const onCreationFromDataSourceButtonClick = () => {
		openModal({
			className: 'modal-height-full',
			contentComponent: ({closeModal}: {closeModal: Function}) => (
				;
					}) => {
						saveDataSetTableColumns({
							closeModal,
							fields: selectedFields,
						});
					}}
					saveButtonDisabled={saveButtonDisabled}
					selectedFields={
						tableSections
							? tableSections.map((tableSection) => ({
									id: String(tableSection.id),
									name: tableSection.fieldName,
								}))
							: []
					}
					selectionMode="multiple"
				/>
			),
			size: 'lg',
		});
	};

	const onCreationFieldButtonClick = () => {
		openModal({
			contentComponent: ({closeModal}: {closeModal: Function}) => (
				 {
						saveDataSetTableColumns({
							closeModal,
							fields: [{name, sortable: true}] as IField[],
						});
					}}
				/>
			),
			title: Liferay.Language.get('delete-data-set'),
		});
	};

	const onEditButtonClick = ({item}: {item: IDataSetTableSection}) => {
		openModal({
			className: 'overflow-auto',
			contentComponent: ({closeModal}: {closeModal: Function}) => (
				 {
						setTableSections(
							tableSections?.map((tableSection) => {
								if (
									tableSection.fieldName ===
									editedTableSection.fieldName
								) {
									return editedTableSection;
								}

								return tableSection;
							}) || null
						);
					}}
					sortable={isSortable(fieldTreeItems, item)}
					tableSection={item}
				/>
			),
		});
	};

	return tableSections ? (
		
			
				{Liferay.Language.get(
					'this-visualization-mode-will-not-be-shown-until-you-assign-at-least-one-field'
				)}
			

			 (
								
							),
							textMatch: (item: IDataSetTableSection) =>
								getRendererLabel({
									cetRenderers: cellClientExtensionRenderers,
									rendererName: item.renderer,
								}),
						},
						label: Liferay.Language.get('renderer'),
						name: 'renderer',
					},
					{
						label: Liferay.Language.get('sortable'),
						name: 'sortable',
					},
				]}
				items={tableSections}
				noItemsButtonLabel={Liferay.Language.get('add-fields')}
				noItemsDescription={Liferay.Language.get(
					'add-fields-to-show-in-your-view'
				)}
				noItemsTitle={Liferay.Language.get('no-fields-added-yet')}
				onOrderChange={({order}: {order: string}) => {
					updateFDSFieldsOrder({
						tableSectionsOrder: order,
					});
				}}
				title={title}
			/>
		
	) : (
		
	);
}

export function Fields(props: IDataSetSectionProps) {
	return (
		
			
	);
}

function isSortable(
	fieldTreeItems: Array,
	selectedItem: IDataSetTableSection
): boolean {
	let isSortable = false;

	visit(fieldTreeItems, (fieldTreeItem: IFieldTreeItem) => {
		if (fieldTreeItem.name === selectedItem.fieldName) {
			isSortable = fieldTreeItem.sortable || false;

			return;
		}
	});

	return isSortable;
}

export default Table;