File "properties.js"

Full Path: /home/fresvfqn/waterdamagerestorationandrepairsmithtown.com/wp-content/plugins/surerank/src/apps/admin-general/schema/edit/properties.js
File size: 7.86 KB
MIME-type: text/x-java
Charset: utf-8

import { useState, useEffect } from '@wordpress/element';
import { useSuspenseSelect, useDispatch } from '@wordpress/data';
import { STORE_NAME } from '@AdminStore/constants';
import { Label } from '@bsf/force-ui';
import { Info } from 'lucide-react';
import {
	renderFieldCommon,
	renderCloneableField,
	GroupFieldRenderer,
	renderHelpText,
	renderCloneableGroupField,
} from '@AdminComponents/schema-utils/render-helper';
import { noFieldsAlert } from '@AdminComponents/schema-utils/utils';
import { SeoPopupTooltip } from '@AdminComponents/tooltip';

const Properties = ( { schema, type, handleFieldUpdate, schemaId } ) => {
	const { setMetaSetting } = useDispatch( STORE_NAME );
	const [ schemaType, setSchemaType ] = useState( type );
	const [ fieldItemIds, setFieldItemIds ] = useState( {} );
	const { metaSettingsObject } = useSuspenseSelect( ( select ) => {
		const { getMetaSettings } = select( STORE_NAME );
		return {
			metaSettingsObject: getMetaSettings() || { schemas: {} },
		};
	}, [] );

	const metaSettings = {
		schemas: metaSettingsObject.schemas || {},
	};

	const schemaTypeData = surerank_globals?.schema_type_data || {};

	const [ fields, setFields ] = useState( [] );

	// Helper to process default fields
	const processFields = ( fieldsData ) => {
		return fieldsData.reduce( ( acc, field ) => {
			if ( field.type === 'Group' && field.fields ) {
				if ( field.cloneable ) {
					// For cloneable groups, create an array with one default item
					const defaultItem = {};
					field.fields.forEach( ( subField ) => {
						if ( subField.type === 'Group' && subField.fields ) {
							// Handle nested groups recursively
							// Create an object with all required fields properly initialized
							const nestedGroup = {};
							subField.fields.forEach( ( nestedField ) => {
								nestedGroup[ nestedField.id ] =
									nestedField.std !== undefined
										? nestedField.std
										: '';
							} );
							defaultItem[ subField.id ] = nestedGroup;
						} else {
							defaultItem[ subField.id ] =
								subField.std !== undefined ? subField.std : '';
						}
					} );
					acc[ field.id ] = [ defaultItem ];
				} else {
					// For non-cloneable groups, process recursively
					acc[ field.id ] = processFields( field.fields );
				}
			} else {
				acc[ field.id ] = field.std !== undefined ? field.std : '';
			}
			return acc;
		}, {} );
	};

	// Initialize schema if missing
	useEffect( () => {
		if ( schemaTypeData[ schema ] ) {
			const currentSchema = metaSettings.schemas[ schemaId ] || {};
			const existingFields = currentSchema.fields || {};
			const defaultFields = processFields( schemaTypeData[ schema ] );
			const mergedFields = { ...defaultFields, ...existingFields };

			if ( Object.keys( existingFields ).length === 1 ) {
				setMetaSetting( 'schemas', {
					...metaSettings.schemas,
					[ schemaId ]: {
						...currentSchema,
						type,
						title: schema,
						fields: mergedFields,
						show_on: currentSchema.show_on || {
							rules: [],
							specific: [],
							specificText: [],
						},
					},
				} );
			}
		}
	}, [
		schema,
		schemaId,
		schemaTypeData,
		metaSettings.schemas,
		setMetaSetting,
		type,
	] );

	// Update fields to render based on schema type
	useEffect( () => {
		if ( schemaTypeData[ schema ] ) {
			const currentSchema = metaSettings.schemas[ schemaId ] || {};
			const existingFields = currentSchema.fields || {};
			const updatedFields = ( schemaTypeData[ schema ] || [] ).filter(
				( field ) =>
					// Keep fields that either exist or are required
					existingFields[ field.id ] !== undefined || field.required
			);

			setFields( updatedFields );
		}
	}, [ schema, schemaId, schemaTypeData, metaSettings.schemas ] );

	// We can wrap your original handleUpdate logic here:
	const getFieldValue = ( fieldId, parent = null ) => {
		if ( ! parent ) {
			return metaSettings.schemas[ schemaId ]?.fields?.[ fieldId ] || '';
		}

		return (
			metaSettings.schemas[ schemaId ]?.fields?.[ parent ]?.[ fieldId ] ||
			''
		);
	};

	const onFieldChange = ( fieldId, newValue, parent = null ) => {
		handleFieldUpdate( fieldId, newValue );

		const currentSchema = metaSettings.schemas[ schemaId ] || {};
		const existingFields = currentSchema.fields || {};

		const updatedFields = { ...existingFields };

		if ( parent ) {
			const groupFields =
				schemaTypeData[ schema ].find( ( f ) => f.id === parent )
					?.fields || [];
			const groupType =
				fieldId === '@type'
					? newValue
					: getFieldValue( '@type', parent );
			const filteredGroupFields = groupFields.reduce( ( acc, field ) => {
				// Include field if it has no 'main' or matches the selected groupType
				if ( ! field.main || field.main === groupType ) {
					acc[ field.id ] =
						existingFields[ parent ]?.[ field.id ] ||
						field.std ||
						'';
				}
				return acc;
			}, {} );

			// Update only the relevant field
			filteredGroupFields[ fieldId ] = newValue;

			updatedFields[ parent ] = filteredGroupFields;
		} else {
			updatedFields[ fieldId ] = newValue;
		}

		setMetaSetting( 'schemas', {
			...metaSettings.schemas,
			[ schemaId ]: {
				...currentSchema,
				fields: updatedFields,
			},
		} );

		if ( fieldId === '@type' ) {
			setSchemaType( newValue );
		}
	};

	const hiddenFields = fields.filter(
		( field ) => field.type === 'Hidden' || field.hidden
	);
	const visibleFields = fields.filter(
		( field ) => ! hiddenFields.includes( field )
	);

	if ( visibleFields.length === 0 ) {
		return noFieldsAlert;
	}

	const variableSuggestions = Object.entries(
		surerank_globals?.schema_variables || {}
	).map( ( [ value, label ] ) => ( { value, label } ) );

	// Function to render the field input based on field type
	const renderFieldInput = ( field ) => {
		// Handle cloneable Group fields (like FAQ items)
		if ( field.type === 'Group' && field.cloneable ) {
			return (
				<div className="flex flex-col w-full">
					{ renderCloneableGroupField( {
						field,
						schemaId,
						getFieldValue,
						onFieldChange,
						variableSuggestions,
						fieldItemIds,
						setFieldItemIds,
						renderHelpTextFunction: renderHelpText,
					} ) }
				</div>
			);
		}

		if ( field.type === 'Group' ) {
			return (
				<GroupFieldRenderer
					field={ field }
					schemaType={ schemaType }
					getFieldValue={ getFieldValue }
					onFieldChange={ onFieldChange }
					variableSuggestions={ variableSuggestions }
				/>
			);
		}

		if ( field.cloneable ) {
			return (
				<div className="flex items-center justify-start gap-1.5 w-full">
					{ renderCloneableField( {
						field,
						schemaType,
						getFieldValue,
						onFieldChange,
						variableSuggestions,
						renderAsGroupComponent: true,
					} ) }
				</div>
			);
		}

		return (
			<div className="flex items-center justify-start gap-1.5 w-full">
				{ renderFieldCommon( {
					field,
					schemaType,
					getFieldValue,
					onFieldChange,
					variableSuggestions,
					renderAsGroupComponent: true,
				} ) }
			</div>
		);
	};

	return (
		<div className="space-y-4 w-full">
			{ visibleFields.map( ( field ) => {
				if ( ! field.required && ! field.show ) {
					return null;
				}

				return (
					<div key={ field.id } className="space-y-1.5 p-2 w-full">
						{ /* Label row */ }
						<div className="flex items-center justify-start gap-1.5 w-full">
							<Label
								tag="span"
								size="sm"
								className="space-x-0.5"
								required={ field.required }
							>
								<span>{ field.label }</span>
							</Label>
							{ field.tooltip && (
								<SeoPopupTooltip
									content={ field.tooltip }
									placement="top"
									arrow
									className="z-[99999]"
								>
									<Info
										className="size-4 text-icon-secondary"
										title={ field.tooltip }
									/>
								</SeoPopupTooltip>
							) }
						</div>

						{ /* Field input row */ }
						{ renderFieldInput( field ) }

						{ renderHelpText( field ) }
					</div>
				);
			} ) }
		</div>
	);
};

export default Properties;