Skip to content

Commit d0a3c3a

Browse files
committed
Add mirrored hybrid stop basic information to parent stop #35370
1 parent a8660a8 commit d0a3c3a

13 files changed

Lines changed: 301 additions & 79 deletions

File tree

ui/src/components/common/info-container/DefaultHeaderButtons.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,14 @@ export const DefaultHeaderButtons: FC<InfoContainerHeaderButtonsProps> = ({
4242
<SimpleButton
4343
shape="slim"
4444
onClick={() => setIsExpanded((expanded) => !expanded)}
45-
inverted={!isExpanded}
45+
inverted={inverted ?? !isExpanded}
4646
testId={testIds.toggle(testIdPrefix)}
4747
>
4848
{isExpanded ? (
49-
<FaChevronUp className="text-white" aria-hidden />
49+
<FaChevronUp
50+
className={inverted ? 'text-tweaked-brand' : 'text-white'}
51+
aria-hidden
52+
/>
5053
) : (
5154
<FaChevronDown className="text-tweaked-brand" aria-hidden />
5255
)}

ui/src/components/stop-registry/stops/queries/useGetMirroredQuay.ts

Lines changed: 0 additions & 65 deletions
This file was deleted.

ui/src/components/stop-registry/stops/stop-details/StopDetailsPage.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { FC, useContext, useState } from 'react';
1+
import compact from 'lodash/compact';
2+
import { FC, useContext, useMemo, useState } from 'react';
23
import { useTranslation } from 'react-i18next';
34
import { MdWarning } from 'react-icons/md';
45
import { Link } from 'react-router';
@@ -22,6 +23,7 @@ import {
2223
} from './DetailTabSelector';
2324
import { EditStopValidityButton } from './EditStopValidityButton';
2425
import { StopExternalLinks } from './external-links/StopExternalLinks';
26+
import { MirroredQuayDetailsCard } from './hybrid-stop';
2527
import { SheltersInfoSpotsSection } from './info-spots/SheltersInfoSpots';
2628
import { LocationDetailsSection } from './location-details';
2729
import { MaintenanceSection } from './maintenance';
@@ -55,11 +57,20 @@ export const StopDetailsPage: FC = () => {
5557
const selectDetailTab = (nextTab: DetailTabType) =>
5658
requestNavigation(() => setActiveDetailTab(nextTab));
5759

58-
const { stopDetails, loading, error } = useGetStopDetails();
60+
const { stopDetails, loading, error, mirroredQuays } = useGetStopDetails();
61+
62+
const mirroredTransportModes = useMemo(
63+
() => compact(mirroredQuays.map((mq) => mq.stopPlace.transportMode)),
64+
[mirroredQuays],
65+
);
5966

6067
return (
6168
<Container testId={testIds.page}>
62-
<StopTitleRow stopDetails={stopDetails} label={label} />
69+
<StopTitleRow
70+
stopDetails={stopDetails}
71+
label={label}
72+
mirroredTransportModes={mirroredTransportModes}
73+
/>
6374
<StopHeaderSummaryRow className="my-2" stopDetails={stopDetails} />
6475
<StopDetailsVersion label={label} />
6576
<hr className="my-4" />
@@ -122,6 +133,13 @@ export const StopDetailsPage: FC = () => {
122133
role="tabpanel"
123134
>
124135
<BasicDetailsSection stop={stopDetails} />
136+
{mirroredQuays.map((mq) => (
137+
<MirroredQuayDetailsCard
138+
key={mq.quay.id}
139+
details={mq}
140+
parentStop={stopDetails}
141+
/>
142+
))}
125143
<LocationDetailsSection stop={stopDetails} />
126144
<SignageDetailsSection stop={stopDetails} />
127145
</div>

ui/src/components/stop-registry/stops/stop-details/basic-details/BasicDetailsSection.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { FC, useRef } from 'react';
1+
import { FC, useMemo, useRef } from 'react';
22
import { useTranslation } from 'react-i18next';
3+
import { mapTransportModeToStopTypeName } from '../../../../../i18n/uiNameMappings';
34
import { StopWithDetails } from '../../../../../types';
45
import { showSuccessToast, submitFormByRef } from '../../../../../utils';
6+
import { isMirrorParent } from '../../../../../utils/stop-registry/mirrorRelation';
57
import { InfoContainer, useInfoContainerControls } from '../../../../common';
68
import { stopInfoContainerColors } from '../stopInfoContainerColors';
79
import { StopBasicDetailsFormState } from './basic-details-form/schema';
@@ -60,11 +62,24 @@ export const BasicDetailsSection: FC<BasicDetailsSectionProps> = ({ stop }) => {
6062

6163
const defaultValues = mapStopBasicDetailsDataToFormState(stop);
6264

65+
const isHybrid = isMirrorParent(stop.quay ?? { keyValues: [] });
66+
const title = useMemo(() => {
67+
const base = t(($) => $.stopDetails.basicDetails.title);
68+
if (!isHybrid || !stop.stop_place?.transportMode) {
69+
return base;
70+
}
71+
const modeName = mapTransportModeToStopTypeName(
72+
t,
73+
stop.stop_place.transportMode,
74+
);
75+
return `${base} | ${modeName}`;
76+
}, [t, isHybrid, stop.stop_place?.transportMode]);
77+
6378
return (
6479
<InfoContainer
6580
colors={stopInfoContainerColors}
6681
controls={infoContainerControls}
67-
title={t(($) => $.stopDetails.basicDetails.title)}
82+
title={title}
6883
testIdPrefix="BasicDetailsSection"
6984
>
7085
{infoContainerControls.isInEditMode && !!defaultValues ? (
@@ -73,6 +88,7 @@ export const BasicDetailsSection: FC<BasicDetailsSectionProps> = ({ stop }) => {
7388
ref={formRef}
7489
onSubmit={onSubmit}
7590
stop={stop}
91+
isHybrid={isHybrid}
7692
onCancel={() => infoContainerControls.setIsInEditMode(false)}
7793
testIdPrefix="BasicDetailsSection"
7894
/>

ui/src/components/stop-registry/stops/stop-details/basic-details/basic-details-form/StopBasicDetailsForm.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type StopBasicDetailsFormComponentProps = {
3030
readonly defaultValues: Partial<StopBasicDetailsFormState>;
3131
readonly onSubmit: (state: StopBasicDetailsFormState) => void;
3232
readonly stop: StopWithDetails;
33+
readonly isHybrid?: boolean;
3334
readonly onCancel: () => void;
3435
readonly testIdPrefix: string;
3536
};
@@ -38,7 +39,15 @@ const StopBasicDetailsFormComponent: ForwardRefRenderFunction<
3839
HTMLFormElement,
3940
StopBasicDetailsFormComponentProps
4041
> = (
41-
{ className, defaultValues, onSubmit, stop, onCancel, testIdPrefix },
42+
{
43+
className,
44+
defaultValues,
45+
onSubmit,
46+
stop,
47+
isHybrid,
48+
onCancel,
49+
testIdPrefix,
50+
},
4251
ref,
4352
) => {
4453
const dispatch = useDispatch();
@@ -69,6 +78,7 @@ const StopBasicDetailsFormComponent: ForwardRefRenderFunction<
6978
<HorizontalSeparator />
7079
<StopOtherDetailsFormRow
7180
onClickOpenTimingSettingsModal={openTimingPlaceModal}
81+
isTransportModeLocked={isHybrid}
7282
/>
7383
<StopTypesFormRow />
7484
<HorizontalSeparator />

ui/src/components/stop-registry/stops/stop-details/basic-details/basic-details-form/StopOtherDetailsFormRow.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ const testIds = {
1717
};
1818
type StopOtherDetailsFormRowProps = {
1919
readonly onClickOpenTimingSettingsModal: () => void;
20+
readonly isTransportModeLocked?: boolean;
2021
};
2122

2223
export const StopOtherDetailsFormRow: FC<StopOtherDetailsFormRowProps> = ({
2324
onClickOpenTimingSettingsModal,
25+
isTransportModeLocked,
2426
}) => {
2527
const { t } = useTranslation();
2628

@@ -45,7 +47,9 @@ export const StopOtherDetailsFormRow: FC<StopOtherDetailsFormRowProps> = ({
4547
uiNameMapper={(value) =>
4648
mapStopRegistryTransportModeTypeToUiName(t, value)
4749
}
48-
disabled={isRailReplacement || isTrunkLine}
50+
disabled={
51+
isRailReplacement || isTrunkLine || isTransportModeLocked
52+
}
4953
// eslint-disable-next-line react/jsx-props-no-spreading
5054
{...props}
5155
/>
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { FC, useMemo, useState } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import {
4+
InfrastructureNetworkDirectionEnum,
5+
StopRegistryTransportModeType,
6+
} from '../../../../../generated/graphql';
7+
import { mapTransportModeToStopTypeName } from '../../../../../i18n/uiNameMappings';
8+
import { StopWithDetails } from '../../../../../types';
9+
import { Priority } from '../../../../../types/enums';
10+
import { ConfirmationDialog, SimpleButton } from '../../../../../uiComponents';
11+
import { showSuccessToast } from '../../../../../utils';
12+
import { InfoContainer, useInfoContainerControls } from '../../../../common';
13+
import { MirroredQuayDetails } from '../useGetStopDetails';
14+
import { StopAreaDetailsSection } from '../basic-details/BasicDetailsStopAreaFields';
15+
import { StopDetailsSection } from '../basic-details/BasicDetailsStopFields';
16+
import { getContainerColorsByTransportMode } from '../stopInfoContainerColors';
17+
import { useRemoveMirrorRelation } from './useRemoveMirrorRelation';
18+
19+
function toStopWithDetails(details: MirroredQuayDetails): StopWithDetails {
20+
const { quay, stopPlace } = details;
21+
const coords = quay.geometry?.coordinates;
22+
return {
23+
scheduled_stop_point_id: '' as UUID,
24+
label: quay.publicCode ?? '',
25+
priority: quay.priority ?? Priority.Standard,
26+
direction: InfrastructureNetworkDirectionEnum.Forward,
27+
validity_start: null,
28+
validity_end: null,
29+
located_on_infrastructure_link_id: '' as UUID,
30+
stop_place_ref: null,
31+
measured_location: {
32+
type: 'Point' as const,
33+
coordinates: coords ?? [0, 0],
34+
},
35+
timing_place_id: (quay.timingPlaceId ?? null) as UUID | null,
36+
timing_place: null,
37+
vehicle_mode_on_scheduled_stop_point: [],
38+
stop_place: stopPlace,
39+
quay,
40+
location: {
41+
longitude: coords?.[0] ?? 0,
42+
latitude: coords?.[1] ?? 0,
43+
},
44+
};
45+
}
46+
47+
type MirroredQuayDetailsCardProps = {
48+
readonly details: MirroredQuayDetails;
49+
readonly parentStop: StopWithDetails;
50+
};
51+
52+
export const MirroredQuayDetailsCard: FC<MirroredQuayDetailsCardProps> = ({
53+
details,
54+
parentStop,
55+
}) => {
56+
const { t } = useTranslation();
57+
const [showRemoveDialog, setShowRemoveDialog] = useState(false);
58+
const { removeMirrorRelation, loading: removing } = useRemoveMirrorRelation();
59+
60+
const { transportMode } = details.stopPlace;
61+
const colors = getContainerColorsByTransportMode(transportMode);
62+
63+
const infoContainerControls = useInfoContainerControls({
64+
isEditable: false,
65+
isExpandable: true,
66+
});
67+
68+
const transportModeName = transportMode
69+
? mapTransportModeToStopTypeName(t, transportMode)
70+
: '';
71+
72+
const title = `${t(($) => $.stopDetails.basicDetails.title)} | ${transportModeName}`;
73+
74+
const pseudoStop = useMemo(() => toStopWithDetails(details), [details]);
75+
76+
const handleRemove = async () => {
77+
const success = await removeMirrorRelation({
78+
parentStop,
79+
childQuayId: details.quay.id ?? '',
80+
childStopPlaceId: details.stopPlace.id ?? '',
81+
});
82+
setShowRemoveDialog(false);
83+
if (success) {
84+
showSuccessToast(t(($) => $.stopDetails.hybrid.removeSuccess));
85+
}
86+
};
87+
88+
return (
89+
<>
90+
<InfoContainer
91+
colors={colors}
92+
controls={infoContainerControls}
93+
title={title}
94+
inverted
95+
testIdPrefix={`MirroredQuayDetails::${details.quay.id}`}
96+
>
97+
<StopAreaDetailsSection stop={pseudoStop} />
98+
<StopDetailsSection stop={pseudoStop} />
99+
<div className="mt-4 flex justify-end border-t border-light-grey pt-4">
100+
<SimpleButton
101+
inverted
102+
onClick={() => setShowRemoveDialog(true)}
103+
testId={`MirroredQuayDetails::${details.quay.id}::remove`}
104+
>
105+
{t(($) => $.stopDetails.hybrid.removeButton)}
106+
</SimpleButton>
107+
</div>
108+
</InfoContainer>
109+
<ConfirmationDialog
110+
isOpen={showRemoveDialog}
111+
onConfirm={handleRemove}
112+
onCancel={() => setShowRemoveDialog(false)}
113+
title={t(($) => $.stopDetails.hybrid.removeConfirmTitle)}
114+
description={t(($) => $.stopDetails.hybrid.removeConfirmDescription)}
115+
confirmText={t(($) => $.stopDetails.hybrid.removeConfirm)}
116+
cancelText={t(($) => $.stopDetails.hybrid.removeCancel)}
117+
isConfirming={removing}
118+
/>
119+
</>
120+
);
121+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { MakeHybridStopModal } from './MakeHybridStopModal';
2+
export { MirroredQuayDetailsCard } from './MirroredQuayDetailsCard';
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,27 @@
1+
import { StopRegistryTransportModeType } from '../../../../generated/graphql';
12
import { theme } from '../../../../generated/theme';
23
import { InfoContainerColors } from '../../../common';
34

45
export const stopInfoContainerColors: InfoContainerColors = {
56
backgroundColor: theme.colors.hslNeutralBlue,
67
borderColor: theme.colors.border.hslBlue,
78
};
9+
10+
export function getContainerColorsByTransportMode(
11+
mode: StopRegistryTransportModeType | null | undefined,
12+
): InfoContainerColors {
13+
switch (mode) {
14+
case StopRegistryTransportModeType.Tram:
15+
return {
16+
backgroundColor: theme.colors.hslTramDarkGreen,
17+
borderColor: theme.colors.border.hslTramGreen,
18+
textColorClassName: 'text-white',
19+
};
20+
default:
21+
return {
22+
backgroundColor: theme.colors.tweakedBrand,
23+
borderColor: theme.colors.border.hslBlue,
24+
textColorClassName: 'text-white',
25+
};
26+
}
27+
}

0 commit comments

Comments
 (0)