Skip to content

Commit

Permalink
Merge pull request #927 from Tampere/feature/unfinished-form-navigation
Browse files Browse the repository at this point in the history
Include navigation blocking to forms
  • Loading branch information
mmoila authored Jan 19, 2024
2 parents da0a6fc + 35b024d commit 15c0a6b
Show file tree
Hide file tree
Showing 15 changed files with 672 additions and 601 deletions.
2 changes: 1 addition & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const router = createBrowserRouter(
path="investointihanke/:projectId/uusi-kohde"
element={<ProjectObject projectType="investointihanke" />}
/>
<Route path="investointihanke/:projectId/:tabView" element={<InvestmentProject />} />
<Route path="investointihanke/:projectId?tab=:tabView" element={<InvestmentProject />} />
<Route
path="investointihanke/:projectId/kohde/:projectObjectId"
element={<ProjectObject projectType="investointihanke" />}
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ import { Link, Outlet } from 'react-router-dom';
import { useTranslations } from '@frontend/stores/lang';

import { SessionExpiredWarning } from './SessionExpiredWarning';
import { NavigationBlocker } from './components/NavigationBlocker';
import NotificationList from './services/notification';
import { authAtom, sessionExpiredAtom } from './stores/auth';
import { blockerStatusAtom } from './stores/navigationBlocker';

const theme = createTheme(
{
Expand Down Expand Up @@ -218,6 +220,7 @@ function VersionIndicator() {

export function Layout() {
const sessionExpired = useAtomValue(sessionExpiredAtom);
const blockerStatus = useAtomValue(blockerStatusAtom);

const mainLayoutStyle = css`
height: 100vh;
Expand Down Expand Up @@ -245,6 +248,7 @@ export function Layout() {
<NotificationList />
<Box css={mainContentStyle}>
<Outlet />
<NavigationBlocker status={blockerStatus} />
</Box>
<VersionIndicator />
<SessionExpiredWarning />
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/Map/MapWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import {
getGeoJSONFeaturesString,
} from '@frontend/components/Map/mapInteractions';
import { baseLayerIdAtom, selectedWFSLayersAtom } from '@frontend/stores/map';
import { useNavigationBlocker } from '@frontend/stores/navigationBlocker';

import { LayerDrawer } from './LayerDrawer';
import { Map, MapInteraction } from './Map';
import { MapControls } from './MapControls';
import { NavigationBlocker } from './NavigationBlocker';
import { createWFSLayer, createWMTSLayer, getMapProjection } from './mapFunctions';
import { mapOptions } from './mapOptions';

Expand All @@ -47,6 +47,8 @@ export function MapWrapper(props: Props) {
const [zoom, setZoom] = useState(mapOptions.tre.defaultZoom);
const [viewExtent, setViewExtent] = useState<number[]>(mapOptions.tre.extent);

useNavigationBlocker(dirty, 'map');

useEffect(() => {
if (props.onMoveEnd) {
props.onMoveEnd(zoom, viewExtent);
Expand Down Expand Up @@ -262,7 +264,6 @@ export function MapWrapper(props: Props) {
/>
)}
</Map>
<NavigationBlocker condition={dirty} />
</div>
);
}
33 changes: 0 additions & 33 deletions frontend/src/components/Map/NavigationBlocker.tsx

This file was deleted.

23 changes: 20 additions & 3 deletions frontend/src/components/NavigationBlocker.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
import { useBlocker } from 'react-router-dom';

import { useTranslations } from '@frontend/stores/lang';
import { BlockerStatus } from '@frontend/stores/navigationBlocker';

import { ConfirmDialog } from './dialogs/ConfirmDialog';

interface Props {
condition: boolean;
status: BlockerStatus;
}

export function NavigationBlocker({ condition }: Props) {
const splitViewForms = ['investmentForm', 'detailplanForm', 'projectObjectForm'];

export function NavigationBlocker({ status }: Props) {
const tr = useTranslations();

// Note: useBlocker is a singleton so use of multiple NavigationBlockers in the same view is not possible (https://github.com/remix-run/react-router/discussions/9978)
const blocker = useBlocker(({ currentLocation, nextLocation }) => {
return condition && currentLocation.pathname !== nextLocation.pathname;
if (
// User is in split view and has modified left side form and wants to navigate between tabs
currentLocation.pathname === nextLocation.pathname &&
status.dirtyComponents.every((component) => splitViewForms.includes(component))
) {
return false;
}

return (
status.dirtyComponents.length > 0 &&
(currentLocation.pathname !== nextLocation.pathname ||
// Block navigation between modified unsaved tabs
(status.currentComponent !== null && currentLocation.search !== nextLocation.search))
);
});

return (
Expand Down
34 changes: 34 additions & 0 deletions frontend/src/stores/navigationBlocker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { atom, useAtom } from 'jotai';
import { useEffect } from 'react';

const defaultStatus = {
currentComponent: null,
dirtyComponents: [],
};

export interface BlockerStatus {
currentComponent: string | null;
dirtyComponents: string[];
}

export const blockerStatusAtom = atom<BlockerStatus>(defaultStatus);

export function useNavigationBlocker(isDirty: boolean, identifier: string) {
const [, editBlockerStatus] = useAtom(blockerStatusAtom);

useEffect(() => {
if (typeof isDirty === 'boolean') {
editBlockerStatus((prev) => ({
currentComponent: identifier,
dirtyComponents: isDirty ? [...prev.dirtyComponents, identifier] : prev.dirtyComponents,
}));
}

return () => {
editBlockerStatus((prev) => ({
...defaultStatus,
dirtyComponents: prev.dirtyComponents.filter((comp) => comp !== identifier),
}));
};
}, [isDirty]);
}
17 changes: 9 additions & 8 deletions frontend/src/views/DetailplanProject/DetailplanProject.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AccountTree, Mail, Map } from '@mui/icons-material';
import { Box, Breadcrumbs, Chip, Paper, Tab, Tabs, Typography, css } from '@mui/material';
import { ReactElement } from 'react';
import { Link, useParams } from 'react-router-dom';
import { Link, useParams, useSearchParams } from 'react-router-dom';

import { trpc } from '@frontend/client';
import { ErrorPage } from '@frontend/components/ErrorPage';
Expand Down Expand Up @@ -50,13 +50,13 @@ function getTabs(projectId: string): Tab[] {
},
{
tabView: 'sidoshankkeet',
url: `/asemakaavahanke/${projectId}/sidoshankkeet`,
url: `/asemakaavahanke/${projectId}?tab=sidoshankkeet`,
label: 'project.relatedProjectsTabLabel',
icon: <AccountTree fontSize="small" />,
},
{
tabView: 'tiedotus',
url: `/asemakaavahanke/${projectId}/tiedotus`,
url: `/asemakaavahanke/${projectId}?tab=tiedotus`,
label: 'detailplanProject.notification',
icon: <Mail fontSize="small" />,
},
Expand All @@ -65,7 +65,8 @@ function getTabs(projectId: string): Tab[] {

export function DetailplanProject() {
const routeParams = useParams() as { projectId: string; tabView: TabView };
const tabView = routeParams.tabView ?? 'default';
const [searchParams] = useSearchParams();
const tabView = searchParams.get('tab') || 'default';
const tabs = getTabs(routeParams.projectId);
const projectId = routeParams?.projectId;
const tabIndex = tabs.findIndex((tab) => tab.tabView === tabView);
Expand Down Expand Up @@ -146,7 +147,7 @@ export function DetailplanProject() {
))}
</Tabs>

{!routeParams.tabView && (
{tabView === 'default' && (
<Box css={mapContainerStyle}>
<MapWrapper
geoJson={project.data?.geom}
Expand All @@ -157,12 +158,12 @@ export function DetailplanProject() {
</Box>
)}

{routeParams.tabView && (
{tabView !== 'default' && (
<Box sx={{ p: 2, overflowY: 'auto' }}>
{routeParams.tabView === 'sidoshankkeet' && (
{tabView === 'sidoshankkeet' && (
<ProjectRelations projectId={routeParams.projectId} />
)}
{routeParams.tabView === 'tiedotus' && (
{tabView === 'tiedotus' && (
<DetailplanProjectNotification projectId={routeParams.projectId} />
)}
</Box>
Expand Down
Loading

0 comments on commit 15c0a6b

Please sign in to comment.