FormPage.jsxHooks 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.
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)
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