EN · DE · RU · FR · ES

#2698: FormPage.jsx

projectforge-webapp/src/containers/page/form/FormPage.jsx Type: JavaScript/React · Role: Page · Source: projectforge-webapp/src/containers/page/form/FormPage.jsx 191 lines · 175 code · 4 comments · 12 blank
React form component handling entity editing, validation, field layout, and submit/cancel actions.

Code Structure

Hooks used: Dispatch, Params, Selector, Location, SearchParams, Effect, Memo

Imports from: ../../../actions, ../../../components/base/dynamicLayout, ../../../components/base/page/edit/TabNavigation, ../../../components/design, ../../../components/design/loading-container, ../../../utilities/layout, ../../../utilities/rest, ../../ProjectForge.module.scss, ./history, prop-types, react, react-redux, react-router

Has PropTypes for: FormPage

Uses CSS Modules for styling.

Source Code (abridged)

import PropTypes from 'prop-types';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation, useParams, useSearchParams } from 'react-router';
import {
    callAction,
    loadFormPage,
    setCurrentData,
    setCurrentVariables,
    switchFromCurrentCategory,
} from '../../../actions';
import DynamicLayout from '../../../components/base/dynamicLayout';
import TabNavigation from '../../../components/base/page/edit/TabNavigation';
import { Alert, Container, TabContent, TabPane } from '../../../components/design';
import LoadingContainer from '../../../components/design/loading-container';
import { getTranslation } from '../../../utilities/layout';
import { getServiceURL } from '../../../utilities/rest';
import style from '../../ProjectForge.module.scss';
import FormHistory from './history';

function FormPage(
    {
        isPublic = false,
    },
) {
    const dispatch = useDispatch();
    const onCallAction = (...args) => dispatch(callAction(...args));
    const onCategorySwitch = (...args) => dispatch(switchFromCurrentCategory(...args));
    const onDataChange = (...args) => dispatch(setCurrentData(...args));
    const onNewFormPage = (...args) => dispatch(loadFormPage(...args));
    const onVariablesChange = (...args) => dispatch(setCurrentVariables(...args));
    const {
        type,
        category: currentCategory,
        id,
        tab,
    } = useParams();
    const category = useSelector(({ form }) => form.categories[currentCategory]) || {};
    const {
        data,
        isFetching,
        ui,
        validationErrors,
        variables,
    } = category;
    const location = useLocation();
    const [searchParams] = useSearchParams();
    const { userAccess } = ui || {};

    React.useEffect(
        () => {
            // Check if this is a programmatic navigation with noReload flag
            // Browser reloads should always fetch fresh data
            const isNoReloadNavigation = location.state
                && location.state.noReload
                && window.performance
                && window.performance.navigation.type !== 1; // 1 = TYPE_RELOAD

            if (isNoReloadNavigation) {
                onCategorySwitch(
                    currentCategory,
                    location.state.newVariables || {},
                    location.state.merge,
                );
            } else {
                onNewFormPage(
                    currentCategory,
                    id,
                    getServiceURL(
                        `${isPublic ? '/rsPublic/' : ''}${currentCategory}/${type || 'dynamic'}`,
                        {
                            ...Object.fromEntries(searchParams.entries()),
                            id,
                        },
                    ),
                    location.state,
                );
            }
        },
        [
            currentCategory,
            id,
            location.state && location.state.noReload,
            location.state && location.state.newVariables,
        ],
    );

    const globalValidation = React.useMemo(() => {
        if (validationErrors === undefined) {
            return null;
        }
        const globalErrors = validationErrors.filter((entry) => entry.fieldId === undefined);

        if (globalErrors.length === 0) {
            return null;
        }

        return (
            <Alert color="danger">
                <ul>
                    {globalErrors.map(({ message, messageId }) => (
                        <li key={`form-page-global-validation-${messageId}`}>
                            {message}
                        </li>
                    ))}
                </ul>
            </Alert>
        );
    }, [validationErrors]);

    if (ui === undefined || ui.title === undefined) {
        return <LoadingContainer loading />;
    }

    // Build base URL from current location to preserve nested routes (e.g., /calendar/)
    // Remove /history suffix if present, and remove query parameters
    const formBaseUrl = location.pathname.replace(/\/history$/, '').split('?')[0];
    const tabs = [
        {
            id: 'form',
// ... (truncated, total 191 lines)

Git History

bf988bc6d Eliminate 43 npm vulnerabilities: react-scripts→Vite, ESLint 9, dependency cleanup, bugfixes
d61a30129 FormPage.jsx: spinning wheel of death fixed.
05bcb43b9 Modal dialog handling of history entries fixed.
f02617502 HistoryEntry.jsx: diffSummary, edit user comment only displayed if available.
3490f27f1 fix calendar and timesheet page