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

There is a newer version: 1.0.6
Show 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} from 'frontend-js-components-web';
import {fetch, openModal} from 'frontend-js-web';
import fuzzy from 'fuzzy';
import React, {useEffect, useState} from 'react';

import FieldSelectModalContent, {
	visit,
} from '../../../components/FieldSelectModalContent';
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 '../../../../css/TableVisualizationMode.scss';

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

import sortItems from '../../../utils/sortItems';
import {
	EFieldType,
	IFDSField,
	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: IFDSField;
	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}
			)}
		
	);
};
interface IEditFDSFieldModalContentProps {
	closeModal: Function;
	fdsClientExtensionCellRenderers: IClientExtensionRenderer[];
	fdsField: IFDSField;
	namespace: string;
	onSaveButtonClick: Function;
	sortable: boolean;
}

const EditFDSFieldModalContent = ({
	closeModal,
	fdsClientExtensionCellRenderers,
	fdsField,
	namespace,
	onSaveButtonClick,
	sortable,
}: IEditFDSFieldModalContentProps) => {
	const [selectedFDSFieldRenderer, setSelectedFDSFieldRenderer] = useState(
		fdsField.renderer ?? 'default'
	);

	const [fdsFieldSortable, setFSDFieldSortable] = useState(
		fdsField.sortable
	);

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

	const fdsFieldTranslations = fdsField.label_i18n;

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

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

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

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

			return;
		}

		const editedFDSField = await response.json();

		closeModal();

		onSaveButtonClick({editedFDSField});

		openDefaultSuccessToast();
	};

	const fdsFieldNameInputId = `${namespace}fdsFieldNameInput`;
	const fdsFieldLabelInputId = `${namespace}fdsFieldLabelInput`;
	const fdsFieldRendererSelectId = `${namespace}fdsFieldRendererSelectId`;

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

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

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

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

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

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

			
				
					

					
				

				
					
				

				
					

					
							setSelectedFDSFieldRenderer(item)
						}
					/>
				

				
					
							setFSDFieldSortable(checked)
						}
					/>

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

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

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

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

	const [fdsFields, setFDSFields] = 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_SECTION_ID} eq '${dataSet.id}')&nestedFields=${OBJECT_RELATIONSHIP.DATA_SET_TABLE_SECTION}&sort=dateCreated:asc`,
			{
				headers: DEFAULT_FETCH_HEADERS,
			}
		);

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

			return null;
		}

		const responseJSON = await response.json();

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

		if (!storedFDSFields) {
			openDefaultFailureToast();

			return null;
		}

		const fdsFieldsOrder =

			// @ts-ignore

			storedFDSFields?.[0]?.[OBJECT_RELATIONSHIP.DATA_SET_TABLE_SECTION]
				?.fdsFieldsOrder;

		setFDSFields(sortItems(storedFDSFields, fdsFieldsOrder) as IFDSField[]);
	};

	const onDeleteButtonClick = ({item}: {item: IFDSField}) => {
		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: 'warning',
					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();

						setFDSFields(
							fdsFields?.filter(
								(fdsField: IFDSField) => fdsField.id !== item.id
							) || []
						);
					},
				},
			],
			status: 'warning',
			title: Liferay.Language.get('delete-filter'),
		});
	};

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

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

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

		fdsFields?.forEach((fdsField) => {
			if (!fields.find((field) => field.name === fdsField.name)) {
				deletionIds.push(fdsField.id);
			}
		});

		const formData = new FormData();

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

		deletionIds.forEach((id) => {
			formData.append(`${namespace}deletionIds`, String(id));
		});

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

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

		setSaveButtonDisabled(false);

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

			return;
		}

		const createdFDSFields: Array = await response.json();

		closeModal();

		const newFDSFields: Array = [];

		fdsFields?.forEach((fdsField) => {
			if (!deletionIds.includes(fdsField.id)) {
				newFDSFields.push(fdsField);
			}
		});

		createdFDSFields.forEach((fdsField) => {
			newFDSFields.push(fdsField);
		});

		setFDSFields(newFDSFields);

		openDefaultSuccessToast();
	};

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

		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?.fdsFieldsOrder;

		if (
			fdsFields &&
			storedFDSFieldsOrder &&
			storedFDSFieldsOrder === fdsFieldsOrder
		) {
			setFDSFields(
				sortItems(fdsFields, storedFDSFieldsOrder) as IFDSField[]
			);

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

	useEffect(() => {
		getFDSFields();

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

	const onCreationButtonClick = () => {
		openModal({
			contentComponent: ({closeModal}: {closeModal: Function}) => (
				;
					}) => {
						saveFDSFields({closeModal, fields: selectedFields});
					}}
					saveButtonDisabled={saveButtonDisabled}
					selectedFields={
						fdsFields
							? fdsFields.map((fdsField) => ({
									id: String(fdsField.id),
									name: fdsField.name,
								}))
							: []
					}
					selectionMode="multiple"
				/>
			),
			size: 'full-screen',
		});
	};

	const onEditButtonClick = ({item}: {item: IFDSField}) => {
		openModal({
			className: 'overflow-auto',
			contentComponent: ({closeModal}: {closeModal: Function}) => (
				 {
						setFDSFields(
							fdsFields?.map((fdsField) => {
								if (fdsField.name === editedFDSField.name) {
									return editedFDSField;
								}

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

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

			 (
								
							),
							textMatch: (item: IFDSField) =>
								getRendererLabel({
									cetRenderers:
										fdsClientExtensionCellRenderers,
									rendererName: item.renderer,
								}),
						},
						label: Liferay.Language.get('renderer'),
						name: 'renderer',
					},
					{
						label: Liferay.Language.get('sortable'),
						name: 'sortable',
					},
				]}
				items={fdsFields}
				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({
						fdsFieldsOrder: order,
					});
				}}
				title={title}
			/>
		
	) : (
		
	);
}

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

function isSortable(
	fieldTreeItems: Array,
	selectedItem: IFDSField
): boolean {
	let isSortable = false;
	visit(fieldTreeItems, (fieldTreeItem: IFieldTreeItem) => {
		if (fieldTreeItem.name === selectedItem.name) {
			isSortable = fieldTreeItem.sortable || false;

			return;
		}
	});

	return isSortable;
}

export default Table;