Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions test-db-manager/src/types/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export enum KnownValueKey {
SpeedTramStop = 'speedTramStop',
TrunkLineStop = 'trunkLineStop',
Mirrors = 'mirrors',
StopStateValidityStart = 'stopStateValidityStart',
StopStateValidityEnd = 'stopStateValidityEnd',
}

// Represents the values of hsl_municipality in LegacyHslMunicipalityCode table.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
} from '../../../../graphql';
import { areValidityPeriodsOverlapping } from '../../../../time';
import { Priority } from '../../../../types/enums';
import { StopPlaceState } from '../../../../types/stop-registry';
import {
mapGeoJSONtoFeature,
sortStopsOnInfraLinkComparator,
Expand Down Expand Up @@ -110,6 +111,10 @@ const isStopValidDuringRouteValidity = (
routeValidity: RouteValidityFragment,
) => areValidityPeriodsOverlapping(routeValidity, stop);

const isStopActive = (stop: ScheduledStopPointDefaultFieldsFragment) =>
!stop.newest_quay?.stop_state ||
stop.newest_quay.stop_state === StopPlaceState.InOperation;

/**
* Checks whether a stop instance is along a route's geometry and its traversal is compatible
* @param stop stop instance
Expand Down Expand Up @@ -214,7 +219,8 @@ export const extractJourneyPatternCandidateStops = (
.filter(
(stop) =>
isStopTraversalCompatible(stop, isTraversalForwards) &&
isStopValidDuringRouteValidity(stop, routeMetadata),
isStopValidDuringRouteValidity(stop, routeMetadata) &&
isStopActive(stop),
)
.sort(sortStopsOnInfraLinkComparator(isTraversalForwards));
});
Expand Down
4 changes: 3 additions & 1 deletion ui/src/components/map/stops/hooks/useMapStops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
selectSelectedStopId,
} from '../../../../redux';
import { Priority } from '../../../../types/enums';
import { StopPlaceState } from '../../../../types/stop-registry';
import {
filterHighestPriorityCurrentStops,
isCurrentEntity,
Expand Down Expand Up @@ -105,7 +106,8 @@ export const useMapStops = (displayedRouteIds: ReadonlyArray<string>) => {
const getStopShouldBeGray = useCallback(
(stop: MapStop): boolean =>
(!!selectedRouteId && !usedStopLabels.includes(stop.label)) ||
!isCurrentEntity(observationDate, stop),
!isCurrentEntity(observationDate, stop) ||
stop.stop_state !== StopPlaceState.InOperation,
[selectedRouteId, observationDate, usedStopLabels],
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { DateTime } from 'luxon';
import { FC, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useObservationDateQueryParam } from '../../../../../hooks';
import { mapTransportModeToStopTypeName } from '../../../../../i18n/uiNameMappings';
import { StopWithDetails } from '../../../../../types';
import { StopPlaceState } from '../../../../../types/stop-registry';
import { showSuccessToast, submitFormByRef } from '../../../../../utils';
import { InfoContainer, useInfoContainerControls } from '../../../../common';
import { getEffectiveStopState } from '../getEffectiveStopState';
import {
getContainerColorsByTransportMode,
inactiveInfoContainerColors,
} from '../stopInfoContainerColors';
import { useStopStateChangeConfirmation } from '../useStopStateChangeConfirmation';
import { StopBasicDetailsFormState } from './basic-details-form/schema';
import { StopBasicDetailsForm } from './basic-details-form/StopBasicDetailsForm';
import { BasicDetailsViewCard } from './BasicDetailsViewCard';
import { StopStateChangeConfirmationDialog } from './StopStateChangeConfirmationDialog';
import { useEditStopBasicDetails } from './useEditStopBasicDetails';

const mapStopBasicDetailsDataToFormState = (stop: StopWithDetails) => {
Expand All @@ -30,6 +35,12 @@ const mapStopBasicDetailsDataToFormState = (stop: StopWithDetails) => {
elyNumber: stop.quay?.elyNumber ?? undefined,
timingPlaceId: stop.quay?.timingPlaceId ?? stop.timing_place_id ?? null,
stopState: stop.quay?.stopState ?? undefined,
stopStateValidityStart: stop.quay?.stopStateValidityStart
? DateTime.fromISO(stop.quay.stopStateValidityStart)
: DateTime.now(),
stopStateValidityEnd: stop.quay?.stopStateValidityEnd
? DateTime.fromISO(stop.quay.stopStateValidityEnd)
: DateTime.now().plus({ days: 1 }),
stopTypes: stop.quay?.stopType,
reasonForChange: '',
};
Expand All @@ -46,6 +57,14 @@ export const BasicDetailsSection: FC<BasicDetailsSectionProps> = ({
isHybrid,
}) => {
const { t } = useTranslation();
const { observationDate } = useObservationDateQueryParam();

const effectiveStopState = getEffectiveStopState(
stop.quay?.stopState,
stop.quay?.stopStateValidityStart,
stop.quay?.stopStateValidityEnd,
observationDate,
);

const { saveStopPlaceDetails, defaultErrorHandler } =
useEditStopBasicDetails();
Expand All @@ -57,16 +76,18 @@ export const BasicDetailsSection: FC<BasicDetailsSectionProps> = ({
onSave: () => submitFormByRef(formRef),
});

const onSubmit = async (state: StopBasicDetailsFormState) => {
try {
await saveStopPlaceDetails({ state, stop });

showSuccessToast(t(($) => $.stops.editSuccess));
infoContainerControls.setIsInEditMode(false);
} catch (err) {
defaultErrorHandler(err as Error);
}
};
const { onSubmit, confirmationDialogProps } =
useStopStateChangeConfirmation<StopBasicDetailsFormState>({
currentStopState: stop.quay?.stopState,
stopPlaceRef: stop.stop_place_ref ?? '',
scheduledStopPointId: stop.scheduled_stop_point_id,
doSave: (state) => saveStopPlaceDetails({ state, stop }),
onSuccess: () => {
showSuccessToast(t(($) => $.stops.editSuccess));
infoContainerControls.setIsInEditMode(false);
},
defaultErrorHandler,
});

const defaultValues = mapStopBasicDetailsDataToFormState(stop);

Expand All @@ -83,7 +104,7 @@ export const BasicDetailsSection: FC<BasicDetailsSectionProps> = ({
return (
<InfoContainer
colors={
stop.quay?.stopState === StopPlaceState.InOperation
effectiveStopState === StopPlaceState.InOperation
? getContainerColorsByTransportMode(
stop.stop_place?.transportMode,
stop.quay?.stopType.trunkLineStop,
Expand All @@ -109,6 +130,13 @@ export const BasicDetailsSection: FC<BasicDetailsSectionProps> = ({
) : (
<BasicDetailsViewCard stop={stop} />
)}
<StopStateChangeConfirmationDialog
isOpen={confirmationDialogProps.isOpen}
onConfirm={confirmationDialogProps.onConfirm}
onCancel={confirmationDialogProps.onCancel}
stopLabel={stop.label ?? ''}
affectedRoutes={confirmationDialogProps.affectedRoutes}
/>
</InfoContainer>
);
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { FC } from 'react';
import { useTranslation } from 'react-i18next';
import { useObservationDateQueryParam } from '../../../../../hooks';
import {
mapStopPlaceStateToUiName,
mapStopRegistryTransportModeTypeToUiName,
} from '../../../../../i18n/uiNameMappings';
import { StopWithDetails } from '../../../../../types';
import { StopPlaceState } from '../../../../../types/stop-registry';
import { useGetTimingPlaceLabel } from '../../queries/useGetTimingPlaceLabel';
import { getEffectiveStopState } from '../getEffectiveStopState';
import { DetailRow, LabeledDetail } from '../layout';
import { translateStopTypes } from '../utils';

Expand Down Expand Up @@ -33,9 +36,15 @@ const testIds = {

export const StopDetailsSection: FC<StopDetailsSectionProps> = ({ stop }) => {
const { t } = useTranslation();
const { observationDate } = useObservationDateQueryParam();

const stopState =
stop.quay?.stopState && mapStopPlaceStateToUiName(t, stop.quay.stopState);
const effectiveState = getEffectiveStopState(
stop.quay?.stopState,
stop.quay?.stopStateValidityStart,
stop.quay?.stopStateValidityEnd,
observationDate,
);
const stopState = mapStopPlaceStateToUiName(t, effectiveState);

const transportMode =
stop.stop_place?.transportMode &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { DateTime } from 'luxon';
import { FC } from 'react';
import { useTranslation } from 'react-i18next';
import { MdAccessTime } from 'react-icons/md';
import { useObservationDateQueryParam } from '../../../../../hooks';
import { StopWithDetails } from '../../../../../types';
import { StopPlaceState } from '../../../../../types/stop-registry';
import { getEffectiveStopState } from '../getEffectiveStopState';
import { StopAreaDetailsSection } from './BasicDetailsStopAreaFields';
import { StopDetailsSection } from './BasicDetailsStopFields';

Expand All @@ -10,8 +16,34 @@ type BasicDetailsViewCardProps = {
export const BasicDetailsViewCard: FC<BasicDetailsViewCardProps> = ({
stop,
}) => {
const { t } = useTranslation();
const { observationDate } = useObservationDateQueryParam();

const validityStart = stop.quay?.stopStateValidityStart;
const effectiveState = getEffectiveStopState(
stop.quay?.stopState,
stop.quay?.stopStateValidityStart,
stop.quay?.stopStateValidityEnd,
observationDate,
);
const hasFutureStateChange =
effectiveState === StopPlaceState.InOperation &&
!!validityStart &&
stop.quay?.stopState !== StopPlaceState.InOperation &&
DateTime.fromISO(validityStart) > observationDate;

return (
<>
{hasFutureStateChange && (
<div className="mb-2 flex items-center gap-1 text-sm text-grey">
<MdAccessTime className="text-base" />
<span>
{t(($) => $.stopDetails.basicDetails.stateChangesOn, {
date: DateTime.fromISO(validityStart).toFormat('dd.MM.yyyy'),
})}
</span>
</div>
)}
<StopAreaDetailsSection stop={stop} />
<StopDetailsSection stop={stop} />
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { TFunction } from 'i18next';
import countBy from 'lodash/countBy';
import { FC } from 'react';
import { useTranslation } from 'react-i18next';
import { RouteUniqueFieldsFragment } from '../../../../../generated/graphql';
import { ConfirmationDialog } from '../../../../../uiComponents';

const buildRouteLabels = (
routes: ReadonlyArray<RouteUniqueFieldsFragment>,
t: TFunction,
) => {
const labelsCounted = countBy(routes, 'label');

return Object.entries(labelsCounted).map(([routeLabel, labelCount]) =>
labelCount > 1
? t(($) => $.confirmEditStopDialog.routeWithNVersions, {
routeLabel,
labelCount,
})
: routeLabel,
);
};

type StopStateChangeConfirmationDialogProps = {
readonly isOpen: boolean;
readonly onConfirm: () => void;
readonly onCancel: () => void;
readonly stopLabel: string;
readonly affectedRoutes: ReadonlyArray<RouteUniqueFieldsFragment>;
};

export const StopStateChangeConfirmationDialog: FC<
StopStateChangeConfirmationDialogProps
> = ({ isOpen, onConfirm, onCancel, stopLabel, affectedRoutes }) => {
const { t } = useTranslation();

const description = (
<div className="space-y-4">
<p>
{t(($) => $.confirmStopStateChangeDialog.description, { stopLabel })}
</p>
{affectedRoutes.length > 0 && (
<div>
<p>{t(($) => $.confirmStopStateChangeDialog.affectedRoutes)}</p>
<ul className="mt-1 ml-4 list-disc">
{buildRouteLabels(affectedRoutes, t).map((label) => (
<li key={label}>{label}</li>
))}
</ul>
</div>
)}
<p>{t(($) => $.confirmStopStateChangeDialog.routesNotModified)}</p>
</div>
);

return (
<ConfirmationDialog
isOpen={isOpen}
onCancel={onCancel}
onConfirm={onConfirm}
title={t(($) => $.confirmStopStateChangeDialog.title)}
description={description}
confirmText={t(($) => $.confirmStopStateChangeDialog.confirmText)}
cancelText={t(($) => $.cancel)}
widthClassName="w-235"
/>
);
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { mapStopPlaceStateToUiName } from '../../../../../../i18n/uiNameMappings';
import { Column } from '../../../../../../layoutComponents';
import { StopPlaceState } from '../../../../../../types/stop-registry';
import { EnumDropdown, FormRow, InputField } from '../../../../../forms/common';
import {
DateInputField,
EnumDropdown,
FormRow,
InputField,
} from '../../../../../forms/common';
import { StopBasicDetailsFormState } from './schema';

const testIds = {
Expand All @@ -11,10 +17,16 @@ const testIds = {
locationFin: 'StopBasicDetailsForm::locationFin',
locationSwe: 'StopBasicDetailsForm::locationSwe',
stopPlaceState: 'StopBasicDetailsForm::stopPlaceState',
stopStateValidityStart: 'StopBasicDetailsForm::stopStateValidityStart',
stopStateValidityEnd: 'StopBasicDetailsForm::stopStateValidityEnd',
};

export const StopLabelAndLocationFormRow = () => {
const { t } = useTranslation();
const { watch } = useFormContext<StopBasicDetailsFormState>();
const stopState = watch('stopState');
const showValidityPeriod = stopState !== StopPlaceState.InOperation;

return (
<>
<h3>{t(($) => $.stopDetails.basicDetails.stopInformation)}</h3>
Expand Down Expand Up @@ -55,8 +67,6 @@ export const StopLabelAndLocationFormRow = () => {
/>
</Column>
<Column>
{/* TODO: Currently we can only have one transportMode, so we use dropdown for it.
Later we need custom icon checkbox inputs here for the transportModes */}
<InputField<StopBasicDetailsFormState>
translationPrefix="stopDetails.basicDetails"
fieldPath="stopState"
Expand All @@ -74,6 +84,26 @@ export const StopLabelAndLocationFormRow = () => {
/>
</Column>
</FormRow>
{showValidityPeriod && (
<FormRow mdColumns={5}>
<Column>
<DateInputField<StopBasicDetailsFormState>
translationPrefix="stopDetails.basicDetails"
fieldPath="stopStateValidityStart"
testId={testIds.stopStateValidityStart}
required
/>
</Column>
<Column>
<DateInputField<StopBasicDetailsFormState>
translationPrefix="stopDetails.basicDetails"
fieldPath="stopStateValidityEnd"
testId={testIds.stopStateValidityEnd}
required
/>
</Column>
</FormRow>
)}
</>
);
};
Loading
Loading