Skip to content

Commit eeb9226

Browse files
committed
Implement copy stop on map
1 parent 71f4475 commit eeb9226

24 files changed

+652
-88
lines changed

ui/src/components/forms/stop/StopForm.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -251,13 +251,27 @@ type StopFormProps = {
251251
readonly className?: string;
252252
readonly editing: boolean;
253253
readonly defaultValues: DefaultValues<StopFormState>;
254-
readonly onSubmit: (changes: CreateChanges | EditChanges) => void;
255-
};
254+
} & (
255+
| {
256+
readonly onSubmit: (changes: CreateChanges | EditChanges) => void;
257+
readonly submitState?: false | never;
258+
}
259+
| {
260+
readonly onSubmit: (
261+
changes: CreateChanges | EditChanges,
262+
state: StopFormState,
263+
) => void;
264+
readonly submitState: true;
265+
}
266+
);
256267

257268
const StopFormComponent: ForwardRefRenderFunction<
258269
HTMLFormElement,
259270
StopFormProps
260-
> = ({ className = '', editing, defaultValues, onSubmit }, ref) => {
271+
> = (
272+
{ className = '', editing, defaultValues, onSubmit, submitState },
273+
ref,
274+
) => {
261275
const { t } = useTranslation();
262276

263277
const methods = useForm<StopFormState>({
@@ -320,7 +334,7 @@ const StopFormComponent: ForwardRefRenderFunction<
320334
? await onEdit(state)
321335
: await onCreate(state);
322336
setIsLoading(false);
323-
return onSubmit(changes);
337+
return submitState ? onSubmit(changes, state) : onSubmit(changes);
324338
} catch (err) {
325339
setIsLoading(false);
326340
return defaultErrorHandler(err as Error);

ui/src/components/forms/stop/components/StopAreaInfoSection.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@ export const StopAreaInfoSection: FC<StopAreaInfoSectionProps> = ({
6565
<span className="mr-2 font-bold">{stopArea.privateCode}</span>
6666
<span className="mr-2">{stopArea.nameFin ?? stopArea.nameSwe}</span>
6767
<span className="mr-2 font-bold">
68-
{`(${formatIsoDateString(stopArea.validityStart)} - ${formatIsoDateString(stopArea.validityEnd)})`}
68+
{`(${formatIsoDateString(stopArea.validityStart)}`}
69+
{stopArea.validityEnd
70+
? ` - ${formatIsoDateString(stopArea.validityEnd)})`
71+
: ' - )'}
6972
</span>
7073
<i className="icon-open-in-new" role="presentation" />
7174
</Link>

ui/src/components/map/FloatingAddModeFooter.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { FC } from 'react';
22
import { useTranslation } from 'react-i18next';
3-
import { useAppDispatch } from '../../hooks';
3+
import { useAppAction, useAppDispatch } from '../../hooks';
44
import {
55
MapEntityEditorViewState,
6+
setCopyStopIdAction,
67
setRouteMetadataFormOpenAction,
78
} from '../../redux';
89
import { FloatingFooter } from './FloatingFooter';
@@ -21,13 +22,17 @@ export const FloatingAddModeFooter: FC<FloatingAddModeFooterProps> = ({
2122
const dispatch = useAppDispatch();
2223

2324
const [mapViewState, setMapViewState] = useMapViewState();
25+
const setCopyStopId = useAppAction(setCopyStopIdAction);
2426

2527
const onCancelAddMode = () => {
2628
setMapViewState({
2729
stops: MapEntityEditorViewState.NONE,
2830
stopAreas: MapEntityEditorViewState.NONE,
2931
terminals: MapEntityEditorViewState.NONE,
3032
});
33+
34+
// Reset copy stop mode
35+
setCopyStopId(undefined);
3136
};
3237

3338
const onCancelDrawMode = () => {
@@ -52,6 +57,9 @@ export const FloatingAddModeFooter: FC<FloatingAddModeFooterProps> = ({
5257
if (mapViewState.stops === MapEntityEditorViewState.PLACE) {
5358
return t('map.addStop');
5459
}
60+
if (mapViewState.stops === MapEntityEditorViewState.PLACECOPY) {
61+
return t('map.copyStop');
62+
}
5563
if (mapViewState.stopAreas === MapEntityEditorViewState.PLACE) {
5664
return t('map.createNewStopArea');
5765
}

ui/src/components/map/Map.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ function useOnClickMap(
140140
return stopsRef.current?.onCreateStop(e);
141141
}
142142

143+
if (mapStopViewState === MapEntityEditorViewState.PLACECOPY) {
144+
return stopsRef.current?.onCopyStop(e);
145+
}
146+
143147
if (mapStopViewState === MapEntityEditorViewState.MOVE) {
144148
return stopsRef.current?.onMoveStop(e);
145149
}

ui/src/components/map/MapFooter.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ function useActiveModes() {
2222

2323
const isInPlaceMode = some(
2424
mapViewState,
25-
(state) => state === MapEntityEditorViewState.PLACE,
25+
(state) =>
26+
state === MapEntityEditorViewState.PLACE ||
27+
state === MapEntityEditorViewState.PLACECOPY,
2628
);
2729

2830
const isInDrawingMode = drawingMode === Mode.Draw && creatingNewRoute;

ui/src/components/map/MapPage.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { FC, useRef } from 'react';
22
import { useAppAction, useNavigateBackSafely } from '../../hooks';
33
import {
44
MapEntityEditorViewState,
5+
setCopyStopIdAction,
56
setSelectedMapStopAreaIdAction,
67
setSelectedStopIdAction,
78
setSelectedTerminalIdAction,
@@ -26,6 +27,7 @@ export const MapPage: FC = () => {
2627

2728
const [, setMapViewState] = useMapViewState();
2829
const setSelectedStopId = useAppAction(setSelectedStopIdAction);
30+
const setCopyStopId = useAppAction(setCopyStopIdAction);
2931
const setSelectedStopAreaId = useAppAction(setSelectedMapStopAreaIdAction);
3032
const setSelectedTerminalId = useAppAction(setSelectedTerminalIdAction);
3133

@@ -41,6 +43,7 @@ export const MapPage: FC = () => {
4143

4244
// Also reset any active selections on exit
4345
setSelectedStopId(undefined);
46+
setCopyStopId(undefined);
4447
setSelectedStopAreaId(undefined);
4548
setSelectedTerminalId(undefined);
4649

ui/src/components/map/refTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export type EditStoplayerRef = {
1818
export type StopsRef = {
1919
readonly onMoveStop: (e: MapLayerMouseEvent) => Promise<void>;
2020
readonly onCreateStop: (e: MapLayerMouseEvent) => Promise<void>;
21+
readonly onCopyStop: (e: MapLayerMouseEvent) => Promise<void>;
2122
};
2223

2324
export type EditStopAreaLayerRef = {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { FC } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import { ConfirmationDialog } from '../../../uiComponents';
4+
5+
type CopyStopConfirmationDialogProps = {
6+
readonly isOpen: boolean;
7+
readonly onConfirm: () => void;
8+
readonly onCancel: () => void;
9+
readonly className?: string;
10+
};
11+
export const CopyStopConfirmationDialog: FC<
12+
CopyStopConfirmationDialogProps
13+
> = ({ isOpen, onConfirm, onCancel, className = '' }) => {
14+
const { t } = useTranslation();
15+
16+
return (
17+
<ConfirmationDialog
18+
className={className}
19+
isOpen={isOpen}
20+
onCancel={onCancel}
21+
onConfirm={onConfirm}
22+
title={t('confirmStopCopyDialog.title')}
23+
description={t('confirmStopCopyDialog.description')}
24+
confirmText={t('confirmStopCopyDialog.confirmText')}
25+
cancelText={t('cancel')}
26+
widthClassName="w-235"
27+
/>
28+
);
29+
};
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { FC, useRef } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import { submitFormByRef } from '../../../utils';
4+
import {
5+
StopFormState as FormState,
6+
StopForm,
7+
StopFormState,
8+
} from '../../forms/stop';
9+
import { CustomOverlay } from '../CustomOverlay';
10+
import { Modal } from '../modal';
11+
import { CreateChanges, EditChanges, isEditChanges } from './hooks';
12+
13+
const testIds = {
14+
modal: 'CopyStopModal',
15+
};
16+
17+
type CopyStopModalProps = {
18+
readonly defaultValues: Partial<FormState>;
19+
readonly onCancel: () => void;
20+
readonly onClose: () => void;
21+
readonly onSubmit: (changes: CreateChanges, state: StopFormState) => void;
22+
};
23+
24+
export const CopyStopModal: FC<CopyStopModalProps> = ({
25+
defaultValues,
26+
onCancel,
27+
onClose,
28+
onSubmit,
29+
}) => {
30+
const { t } = useTranslation();
31+
32+
const formRef = useRef<ExplicitAny>(null);
33+
const onSave = () => submitFormByRef(formRef);
34+
35+
const onStopFormSubmit = (
36+
changes: CreateChanges | EditChanges,
37+
state: StopFormState,
38+
) => {
39+
if (!isEditChanges(changes)) {
40+
onSubmit(changes, state);
41+
}
42+
};
43+
44+
return (
45+
<CustomOverlay
46+
className="min-h-full w-[calc(450px+(2*1.25rem))]"
47+
position="top-left"
48+
>
49+
<Modal
50+
className="pointer-events-auto flex max-h-full flex-col"
51+
headerClassName="*:text-xl px-4 py-4 items-center"
52+
bodyClassName="mx-0 my-0"
53+
footerClassName="px-4 py-2"
54+
testId={testIds.modal}
55+
onSave={onSave}
56+
onCancel={onCancel}
57+
onClose={onClose}
58+
heading={t('stops.createStopCopy', {
59+
stopLabel: defaultValues.publicCode?.value,
60+
})}
61+
navigationContext="StopForm"
62+
>
63+
<StopForm
64+
defaultValues={defaultValues}
65+
editing
66+
submitState
67+
onSubmit={onStopFormSubmit}
68+
ref={formRef}
69+
/>
70+
</Modal>
71+
</CustomOverlay>
72+
);
73+
};

0 commit comments

Comments
 (0)