diff --git a/ui/src/components/stop-registry/stops/versions/utils/accordionClassNames.ts b/ui/src/components/common/accordionClassNames.ts similarity index 100% rename from ui/src/components/stop-registry/stops/versions/utils/accordionClassNames.ts rename to ui/src/components/common/accordionClassNames.ts diff --git a/ui/src/components/common/index.ts b/ui/src/components/common/index.ts index e56ecb707f..4bb6c11643 100644 --- a/ui/src/components/common/index.ts +++ b/ui/src/components/common/index.ts @@ -6,5 +6,7 @@ export * from './RedirectWithQuery'; export * from './RouteLineTableRow'; export * from './RouteTableRow'; export * from './TimeRangeControl'; +export * from './versions'; export * from './search'; export * from './info-container'; +export * from './accordionClassNames'; diff --git a/ui/src/components/stop-registry/stops/versions/components/DateRangeInputs.tsx b/ui/src/components/common/versions/DateRangeInputs.tsx similarity index 91% rename from ui/src/components/stop-registry/stops/versions/components/DateRangeInputs.tsx rename to ui/src/components/common/versions/DateRangeInputs.tsx index 4a6dca9a1a..fb3718d285 100644 --- a/ui/src/components/stop-registry/stops/versions/components/DateRangeInputs.tsx +++ b/ui/src/components/common/versions/DateRangeInputs.tsx @@ -4,9 +4,9 @@ import { DateTime } from 'luxon'; import { Dispatch, FC, SetStateAction } from 'react'; import { useTranslation } from 'react-i18next'; import { twMerge } from 'tailwind-merge'; -import { Row } from '../../../../../layoutComponents'; -import { DateRange } from '../../../../../types'; -import { DateInput } from '../../../../common'; +import { Row } from '../../../layoutComponents'; +import { DateRange } from '../../../types'; +import { DateInput } from '../DateInput'; const testIds = { startDate: 'ScheduledVersionsContainer::DateRangeInputs::startDate', diff --git a/ui/src/components/common/versions/EmptyColumnHeader.tsx b/ui/src/components/common/versions/EmptyColumnHeader.tsx new file mode 100644 index 0000000000..c834226188 --- /dev/null +++ b/ui/src/components/common/versions/EmptyColumnHeader.tsx @@ -0,0 +1,12 @@ +import { FC } from 'react'; + +type EmptyColumnHeaderProps = { + readonly className?: string; +}; + +export const EmptyColumnHeader: FC = ({ + className, +}) => ( + // eslint-disable-next-line jsx-a11y/control-has-associated-label + +); diff --git a/ui/src/components/common/versions/NoVersionRow.tsx b/ui/src/components/common/versions/NoVersionRow.tsx new file mode 100644 index 0000000000..99771a2375 --- /dev/null +++ b/ui/src/components/common/versions/NoVersionRow.tsx @@ -0,0 +1,19 @@ +import { FC } from 'react'; + +type NoVersionRowProps = { + readonly noVersionsText: string; + readonly colSpan: number; +}; + +export const NoVersionRow: FC = ({ + noVersionsText, + colSpan, +}) => { + return ( + + + {noVersionsText} + + + ); +}; diff --git a/ui/src/components/stop-registry/stops/versions/types/StopVersionStatus.ts b/ui/src/components/common/versions/VersionStatus.ts similarity index 74% rename from ui/src/components/stop-registry/stops/versions/types/StopVersionStatus.ts rename to ui/src/components/common/versions/VersionStatus.ts index 514be4cabd..d6e5d61c2e 100644 --- a/ui/src/components/stop-registry/stops/versions/types/StopVersionStatus.ts +++ b/ui/src/components/common/versions/VersionStatus.ts @@ -1,4 +1,4 @@ -export enum StopVersionStatus { +export enum VersionStatus { ACTIVE = 'ACTIVE', STANDARD = 'STANDARD', TEMPORARY = 'TEMPORARY', diff --git a/ui/src/components/stop-registry/stops/versions/types/StopVersionTableColumn.ts b/ui/src/components/common/versions/VersionTableColumn.ts similarity index 74% rename from ui/src/components/stop-registry/stops/versions/types/StopVersionTableColumn.ts rename to ui/src/components/common/versions/VersionTableColumn.ts index ceb1f85118..0a36cf9561 100644 --- a/ui/src/components/stop-registry/stops/versions/types/StopVersionTableColumn.ts +++ b/ui/src/components/common/versions/VersionTableColumn.ts @@ -1,4 +1,4 @@ -export type StopVersionTableColumn = +export type VersionTableColumn = | 'STATUS' | 'VALIDITY_START' | 'VALIDITY_END' diff --git a/ui/src/components/stop-registry/stops/versions/components/StopVersionTableHeaderSortableCell.tsx b/ui/src/components/common/versions/VersionTableHeaderSortableCell.tsx similarity index 55% rename from ui/src/components/stop-registry/stops/versions/components/StopVersionTableHeaderSortableCell.tsx rename to ui/src/components/common/versions/VersionTableHeaderSortableCell.tsx index ae81396cc8..705e9fe451 100644 --- a/ui/src/components/stop-registry/stops/versions/components/StopVersionTableHeaderSortableCell.tsx +++ b/ui/src/components/common/versions/VersionTableHeaderSortableCell.tsx @@ -2,15 +2,10 @@ import { TFunction } from 'i18next'; import { AriaAttributes, Dispatch, FC, SetStateAction } from 'react'; import { useTranslation } from 'react-i18next'; import { twJoin } from 'tailwind-merge'; -import { SortOrder } from '../../../../../types'; -import { ExpandButton } from '../../../../../uiComponents'; -import { StopVersionTableColumn, StopVersionTableSortingInfo } from '../types'; - -const testIds = { - column: (type: StopVersionTableColumn) => - `StopVersionTableHeaderSortableCell::${type.toLowerCase()}`, - sortButton: 'StopVersionTableHeaderSortableCell::sortButton', -}; +import { SortOrder } from '../../../types'; +import { ExpandButton } from '../../../uiComponents'; +import { VersionTableSortingInfo } from './useVersionContainerControls'; +import { VersionTableColumn } from './VersionTableColumn'; function getAriaSortValue( active: boolean, @@ -23,39 +18,45 @@ function getAriaSortValue( return undefined; } -function trColumnName(t: TFunction, columnType: StopVersionTableColumn) { +function trColumnName(t: TFunction, columnType: VersionTableColumn) { switch (columnType) { case 'CHANGED': - return t(($) => $.stopVersion.header.changed); + return t(($) => $.versions.header.changed); case 'CHANGED_BY': - return t(($) => $.stopVersion.header.changed_by); + return t(($) => $.versions.header.changed_by); case 'STATUS': - return t(($) => $.stopVersion.header.status); + return t(($) => $.versions.header.status); case 'VALIDITY_END': - return t(($) => $.stopVersion.header.validity_end); + return t(($) => $.versions.header.validity_end); case 'VALIDITY_START': - return t(($) => $.stopVersion.header.validity_start); + return t(($) => $.versions.header.validity_start); case 'VERSION_COMMENT': - return t(($) => $.stopVersion.header.version_comment); + return t(($) => $.versions.header.version_comment); default: return ''; } } -type StopVersionTableHeaderSortableCellProps = { +type VersionTableHeaderSortableCellProps = { readonly className?: string; readonly tdClassName?: string; - readonly columnType: StopVersionTableColumn; - readonly sortingInfo: StopVersionTableSortingInfo; - readonly setSortingInfo: Dispatch< - SetStateAction - >; + readonly columnType: VersionTableColumn; + readonly sortingInfo: VersionTableSortingInfo; + readonly setSortingInfo: Dispatch>; + readonly testIdPrefix?: string; }; -export const StopVersionTableHeaderSortableCell: FC< - StopVersionTableHeaderSortableCellProps -> = ({ className, tdClassName, columnType, sortingInfo, setSortingInfo }) => { +export const VersionTableHeaderSortableCell: FC< + VersionTableHeaderSortableCellProps +> = ({ + className, + tdClassName, + columnType, + sortingInfo, + setSortingInfo, + testIdPrefix = 'VersionTableHeaderSortableCell', +}) => { const { t } = useTranslation(); const active = sortingInfo.sortBy === columnType; @@ -81,7 +82,7 @@ export const StopVersionTableHeaderSortableCell: FC< ); diff --git a/ui/src/components/common/versions/index.ts b/ui/src/components/common/versions/index.ts new file mode 100644 index 0000000000..4dc461c7ab --- /dev/null +++ b/ui/src/components/common/versions/index.ts @@ -0,0 +1,11 @@ +export * from './DateRangeInputs'; +export * from './NoVersionRow'; +export * from './useFilterVersionsByDateRange'; +export * from './useVersionContainerControls'; +export * from './useSortedVersions'; +export * from './statusToCellClasses'; +export * from './VersionStatus'; +export * from './trStatus'; +export * from './VersionTableColumn'; +export * from './EmptyColumnHeader'; +export * from './VersionTableHeaderSortableCell'; diff --git a/ui/src/components/common/versions/statusToCellClasses.ts b/ui/src/components/common/versions/statusToCellClasses.ts new file mode 100644 index 0000000000..663da25e6e --- /dev/null +++ b/ui/src/components/common/versions/statusToCellClasses.ts @@ -0,0 +1,20 @@ +import { VersionStatus } from './VersionStatus'; + +export function statusToCellClasses(status: VersionStatus): string { + switch (status) { + case VersionStatus.ACTIVE: + return 'bg-hsl-dark-green text-white'; + + case VersionStatus.STANDARD: + return 'bg-tweaked-brand text-white'; + + case VersionStatus.TEMPORARY: + return 'bg-city-bicycle-yellow'; + + case VersionStatus.DRAFT: + return 'bg-background'; + + default: + return ''; + } +} diff --git a/ui/src/components/common/versions/trStatus.ts b/ui/src/components/common/versions/trStatus.ts new file mode 100644 index 0000000000..5961b224d6 --- /dev/null +++ b/ui/src/components/common/versions/trStatus.ts @@ -0,0 +1,21 @@ +import { TFunction } from 'i18next'; +import { VersionStatus } from './VersionStatus'; + +export function trStatus(t: TFunction, status: VersionStatus): string { + switch (status) { + case VersionStatus.ACTIVE: + return t(($) => $.versions.status.active); + + case VersionStatus.STANDARD: + return t(($) => $.versions.status.standard); + + case VersionStatus.TEMPORARY: + return t(($) => $.versions.status.temporary); + + case VersionStatus.DRAFT: + return t(($) => $.versions.status.draft); + + default: + return ''; + } +} diff --git a/ui/src/components/common/versions/useFilterVersionsByDateRange.ts b/ui/src/components/common/versions/useFilterVersionsByDateRange.ts new file mode 100644 index 0000000000..1caeee72c0 --- /dev/null +++ b/ui/src/components/common/versions/useFilterVersionsByDateRange.ts @@ -0,0 +1,29 @@ +import { DateTime } from 'luxon'; +import { useMemo } from 'react'; +import { DateRange } from '../../../types'; + +type VersionWithValidity = { + readonly validity_start: DateTime; + readonly validity_end: DateTime | null; +}; + +export function useFilterVersionsByDateRange( + versions: ReadonlyArray, + dateRange: DateRange, +): ReadonlyArray { + const from = dateRange.startDate.valueOf(); + const to = dateRange.endDate.valueOf(); + + return useMemo(() => { + return versions.filter((version) => { + const versionFrom = version.validity_start.valueOf(); + const versionTo = + version.validity_end?.valueOf() ?? Number.POSITIVE_INFINITY; + + return !( + // End before range start + (versionTo < from || versionFrom > to) // Starts after range end + ); + }); + }, [versions, from, to]); +} diff --git a/ui/src/components/common/versions/useSortedVersions.ts b/ui/src/components/common/versions/useSortedVersions.ts new file mode 100644 index 0000000000..a654cc42b9 --- /dev/null +++ b/ui/src/components/common/versions/useSortedVersions.ts @@ -0,0 +1,80 @@ +import { DateTime } from 'luxon'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { parseDate } from '../../../time'; +import { SortOrder } from '../../../types'; +import { trStatus } from './trStatus'; +import { VersionTableSortingInfo } from './useVersionContainerControls'; +import { VersionStatus } from './VersionStatus'; + +type VersionWithSortableFields = { + readonly status: VersionStatus; + readonly validity_start: DateTime; + readonly validity_end: DateTime | null; + readonly version_comment: string; + readonly changed: string; + readonly changedByUserName: string | null; +}; + +function compareDates( + dateA: DateTime | null, + dateB: DateTime | null, + nullsLast = false, +): number { + if (dateA === null && dateB === null) { + return 0; + } + + if (dateA === null) { + return nullsLast ? 1 : -1; + } + + if (dateB === null) { + return nullsLast ? -1 : 1; + } + + return dateA.valueOf() - dateB.valueOf(); +} + +export function useSortedVersions( + sortingInfo: VersionTableSortingInfo, + versions: ReadonlyArray, +): ReadonlyArray { + const { t } = useTranslation(); + + const collator = useMemo( + () => new Intl.Collator(t(($) => $.languages.intlLangCode)), + [t], + ); + + return useMemo(() => { + const compare = (a: TVersion, b: TVersion): number => { + switch (sortingInfo.sortBy) { + case 'STATUS': + return collator.compare(trStatus(t, a.status), trStatus(t, b.status)); + case 'VALIDITY_START': + return compareDates(a.validity_start, b.validity_start); + case 'VALIDITY_END': + return compareDates(a.validity_end, b.validity_end, true); + case 'VERSION_COMMENT': + return collator.compare(a.version_comment, b.version_comment); + case 'CHANGED': + return compareDates(parseDate(a.changed), parseDate(b.changed)); + case 'CHANGED_BY': + return collator.compare( + a.changedByUserName ?? '', + b.changedByUserName ?? '', + ); + default: + return 0; + } + }; + + const orderedCompare = + sortingInfo.sortOrder === SortOrder.ASCENDING + ? compare + : (a: TVersion, b: TVersion) => -compare(a, b); + + return versions.toSorted(orderedCompare); + }, [sortingInfo, collator, t, versions]); +} diff --git a/ui/src/components/common/versions/useVersionContainerControls.ts b/ui/src/components/common/versions/useVersionContainerControls.ts new file mode 100644 index 0000000000..553e8b48da --- /dev/null +++ b/ui/src/components/common/versions/useVersionContainerControls.ts @@ -0,0 +1,32 @@ +import { DateTime } from 'luxon'; +import { useState } from 'react'; +import { DateRange, SortOrder } from '../../../types'; +import { VersionTableColumn } from './VersionTableColumn'; + +export type VersionTableSortingInfo = { + readonly sortBy: VersionTableColumn; + readonly sortOrder: SortOrder; +}; + +export function useVersionContainerControls() { + const [expanded, setExpanded] = useState(true); + + const [dateRange, setDateRange] = useState(() => ({ + startDate: DateTime.now().minus({ month: 1 }).startOf('month'), + endDate: DateTime.now().plus({ months: 12 }).endOf('month'), + })); + + const [sortingInfo, setSortingInfo] = useState({ + sortBy: 'VALIDITY_START', + sortOrder: SortOrder.ASCENDING, + }); + + return { + expanded, + setExpanded, + dateRange, + setDateRange, + setSortingInfo, + sortingInfo, + }; +} diff --git a/ui/src/components/forms/stop/components/StopAreaInfoSection.tsx b/ui/src/components/forms/stop/components/StopAreaInfoSection.tsx index ef4cc8cfe1..1b04d1c30e 100644 --- a/ui/src/components/forms/stop/components/StopAreaInfoSection.tsx +++ b/ui/src/components/forms/stop/components/StopAreaInfoSection.tsx @@ -8,8 +8,8 @@ import { useObservationDateQueryParam } from '../../../../hooks'; import { mapVehicleModeToUiName } from '../../../../i18n/uiNameMappings'; import { Path, routeDetails } from '../../../../router/routeDetails'; import { ExpandButton } from '../../../../uiComponents'; +import { accordionClassNames } from '../../../common'; import { LabeledDetail } from '../../../stop-registry/stops/stop-details/layout'; -import { accordionClassNames } from '../../../stop-registry/stops/versions/utils'; import { StopFormState } from '../types'; import { formatIsoDateString } from '../utils'; diff --git a/ui/src/components/map/stop-areas/StopAreaEnglishNames.tsx b/ui/src/components/map/stop-areas/StopAreaEnglishNames.tsx index 866dfd7fc0..91e3a808bf 100644 --- a/ui/src/components/map/stop-areas/StopAreaEnglishNames.tsx +++ b/ui/src/components/map/stop-areas/StopAreaEnglishNames.tsx @@ -3,9 +3,9 @@ import { FC, useState } from 'react'; import { useFormContext } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { ExpandButton } from '../../../uiComponents'; +import { accordionClassNames } from '../../common'; import { FormRow, InputField } from '../../forms/common'; import { StopAreaFormState } from '../../forms/stop-area'; -import { accordionClassNames } from '../../stop-registry/stops/versions/utils'; const ID = 'StopAreaEngNameSection'; const HeaderId = 'StopAreaEngNameSection::Header'; diff --git a/ui/src/components/map/stop-areas/StopAreaNames.tsx b/ui/src/components/map/stop-areas/StopAreaNames.tsx index 58b360be59..5fceb0c7d4 100644 --- a/ui/src/components/map/stop-areas/StopAreaNames.tsx +++ b/ui/src/components/map/stop-areas/StopAreaNames.tsx @@ -3,9 +3,9 @@ import { FC, useState } from 'react'; import { useFormContext } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { ExpandButton } from '../../../uiComponents'; +import { accordionClassNames } from '../../common'; import { FormRow, InputField } from '../../forms/common'; import { StopAreaFormState } from '../../forms/stop-area'; -import { accordionClassNames } from '../../stop-registry/stops/versions/utils'; import { StopAreaEnglishNames } from './StopAreaEnglishNames'; const ID = 'StopAreaNameSection'; diff --git a/ui/src/components/map/terminals/TerminalNames.tsx b/ui/src/components/map/terminals/TerminalNames.tsx index a9950f098e..8cd69fe11a 100644 --- a/ui/src/components/map/terminals/TerminalNames.tsx +++ b/ui/src/components/map/terminals/TerminalNames.tsx @@ -3,8 +3,8 @@ import { FC, useState } from 'react'; import { useFormContext } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { ExpandButton } from '../../../uiComponents'; +import { accordionClassNames } from '../../common'; import { FormRow, InputField } from '../../forms/common'; -import { accordionClassNames } from '../../stop-registry/stops/versions/utils'; import { TerminalFormState } from '../../stop-registry/terminals/components/basic-details/basic-details-form/schema'; const ID = 'TerminalNameSection'; diff --git a/ui/src/components/stop-registry/stops/versions/components/DraftVersionsContainer.tsx b/ui/src/components/stop-registry/stops/versions/components/DraftVersionsContainer.tsx index b93471f401..53d724bad0 100644 --- a/ui/src/components/stop-registry/stops/versions/components/DraftVersionsContainer.tsx +++ b/ui/src/components/stop-registry/stops/versions/components/DraftVersionsContainer.tsx @@ -1,11 +1,12 @@ import { Transition } from '@headlessui/react'; -import { FC, useState } from 'react'; +import { FC } from 'react'; import { useTranslation } from 'react-i18next'; import { Row } from '../../../../../layoutComponents'; -import { SortOrder } from '../../../../../types'; import { ExpandButton } from '../../../../../uiComponents'; -import { StopVersion, StopVersionTableSortingInfo } from '../types'; -import { accordionClassNames, useSortedStopVersions } from '../utils'; +import { accordionClassNames } from '../../../../common'; +import { useVersionContainerControls } from '../../../../common/versions'; +import { StopVersion } from '../types'; +import { useSortedStopVersions } from '../utils'; import { StopVersionTable } from './StopVersionTable'; const ID = 'DraftVersionsContainer'; @@ -16,22 +17,6 @@ const testIds = { versionTable: 'DraftVersionsContainer::versionTable', }; -function useControls() { - const [expanded, setExpanded] = useState(true); - - const [sortingInfo, setSortingInfo] = useState({ - sortBy: 'VALIDITY_START', - sortOrder: SortOrder.ASCENDING, - }); - - return { - expanded, - setExpanded, - setSortingInfo, - sortingInfo, - }; -} - type DraftVersionsContainerProps = { readonly className?: string; readonly publicCode: string; @@ -45,7 +30,8 @@ export const DraftVersionsContainer: FC = ({ }) => { const { t } = useTranslation(); - const { expanded, setExpanded, sortingInfo, setSortingInfo } = useControls(); + const { expanded, setExpanded, sortingInfo, setSortingInfo } = + useVersionContainerControls(); const sortedStopVersions = useSortedStopVersions(sortingInfo, stopVersions); diff --git a/ui/src/components/stop-registry/stops/versions/components/NoVersionRow.tsx b/ui/src/components/stop-registry/stops/versions/components/NoVersionRow.tsx deleted file mode 100644 index 512c14adb5..0000000000 --- a/ui/src/components/stop-registry/stops/versions/components/NoVersionRow.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { FC } from 'react'; - -type NoVersionRowProps = { readonly noVersionsText: string }; - -export const NoVersionRow: FC = ({ noVersionsText }) => { - return ( - - - {noVersionsText} - - - ); -}; diff --git a/ui/src/components/stop-registry/stops/versions/components/ScheduledVersionsContainer.tsx b/ui/src/components/stop-registry/stops/versions/components/ScheduledVersionsContainer.tsx index 7974ccee59..48e6bafb1c 100644 --- a/ui/src/components/stop-registry/stops/versions/components/ScheduledVersionsContainer.tsx +++ b/ui/src/components/stop-registry/stops/versions/components/ScheduledVersionsContainer.tsx @@ -1,13 +1,16 @@ import { Transition } from '@headlessui/react'; -import { DateTime } from 'luxon'; -import { FC, useMemo, useState } from 'react'; +import { FC } from 'react'; import { useTranslation } from 'react-i18next'; import { Column } from '../../../../../layoutComponents'; -import { DateRange, SortOrder } from '../../../../../types'; import { ExpandButton } from '../../../../../uiComponents'; -import { StopVersion, StopVersionTableSortingInfo } from '../types'; -import { accordionClassNames, useSortedStopVersions } from '../utils'; -import { DateRangeInputs } from './DateRangeInputs'; +import { accordionClassNames } from '../../../../common'; +import { + DateRangeInputs, + useFilterVersionsByDateRange, + useVersionContainerControls, +} from '../../../../common/versions'; +import { StopVersion } from '../types'; +import { useSortedStopVersions } from '../utils'; import { StopVersionTable } from './StopVersionTable'; const ID = 'ScheduledVersionsContainer'; @@ -18,50 +21,6 @@ const testIds = { versionTable: 'ScheduledVersionsContainer::versionTable', }; -function useFilterVersionsByDateRange( - stopVersions: ReadonlyArray, - dateRange: DateRange, -): ReadonlyArray { - const from = dateRange.startDate.valueOf(); - const to = dateRange.endDate.valueOf(); - - return useMemo(() => { - return stopVersions.filter((stopVersion) => { - const stopFrom = stopVersion.validity_start.valueOf(); - const stopTo = - stopVersion.validity_end?.valueOf() ?? Number.POSITIVE_INFINITY; - - return !( - // End before range start - (stopTo < from || stopFrom > to) // Starts after range end - ); - }); - }, [stopVersions, from, to]); -} - -function useControls() { - const [expanded, setExpanded] = useState(true); - - const [dateRange, setDateRange] = useState(() => ({ - startDate: DateTime.now().minus({ month: 1 }).startOf('month'), - endDate: DateTime.now().plus({ months: 12 }).endOf('month'), - })); - - const [sortingInfo, setSortingInfo] = useState({ - sortBy: 'VALIDITY_START', - sortOrder: SortOrder.ASCENDING, - }); - - return { - expanded, - setExpanded, - dateRange, - setDateRange, - setSortingInfo, - sortingInfo, - }; -} - type ScheduledVersionsContainerProps = { readonly className?: string; readonly publicCode: string; @@ -80,7 +39,7 @@ export const ScheduledVersionsContainer: FC< setDateRange, sortingInfo, setSortingInfo, - } = useControls(); + } = useVersionContainerControls(); const filteredStopVersions = useFilterVersionsByDateRange( useSortedStopVersions(sortingInfo, stopVersions), diff --git a/ui/src/components/stop-registry/stops/versions/components/StopVersionRow.tsx b/ui/src/components/stop-registry/stops/versions/components/StopVersionRow.tsx index f5ad8ffd55..24380dbbe8 100644 --- a/ui/src/components/stop-registry/stops/versions/components/StopVersionRow.tsx +++ b/ui/src/components/stop-registry/stops/versions/components/StopVersionRow.tsx @@ -2,10 +2,11 @@ import { FC } from 'react'; import { useTranslation } from 'react-i18next'; import { twJoin, twMerge } from 'tailwind-merge'; import { mapToShortDate, mapToShortDateTime } from '../../../../../time'; +import { statusToCellClasses } from '../../../../common/versions'; +import { trStatus } from '../../../../common/versions/trStatus'; import { LocatorActionButton } from '../../../components'; -import { StopVersion, StopVersionStatus } from '../types'; +import { StopVersion } from '../types'; import { ActionMenuStop } from '../types/ActionMenuStop'; -import { trStatus } from '../utils'; import { StopVersionActionMenu } from './StopVersionActionMenu'; const testIds = { @@ -17,25 +18,6 @@ const testIds = { changedBy: 'StopVersionRow::changedBy', }; -function statusToCellClasses(status: StopVersionStatus): string { - switch (status) { - case StopVersionStatus.ACTIVE: - return 'bg-hsl-dark-green text-white'; - - case StopVersionStatus.STANDARD: - return 'bg-tweaked-brand text-white'; - - case StopVersionStatus.TEMPORARY: - return 'bg-city-bicycle-yellow'; - - case StopVersionStatus.DRAFT: - return 'bg-background'; - - default: - return ''; - } -} - type StopVersionRowProps = { readonly className?: string; readonly publicCode: string; diff --git a/ui/src/components/stop-registry/stops/versions/components/StopVersionTable.tsx b/ui/src/components/stop-registry/stops/versions/components/StopVersionTable.tsx index 6fa9e163d0..bab88d7dcd 100644 --- a/ui/src/components/stop-registry/stops/versions/components/StopVersionTable.tsx +++ b/ui/src/components/stop-registry/stops/versions/components/StopVersionTable.tsx @@ -1,7 +1,10 @@ import { Dispatch, FC, SetStateAction } from 'react'; import { twMerge } from 'tailwind-merge'; -import { StopVersion, StopVersionTableSortingInfo } from '../types'; -import { NoVersionRow } from './NoVersionRow'; +import { + NoVersionRow, + VersionTableSortingInfo, +} from '../../../../common/versions'; +import { StopVersion } from '../types'; import { StopVersionRow } from './StopVersionRow'; import { StopVersionTableHeader } from './StopVersionTableHeader'; @@ -10,10 +13,8 @@ type StopVersionTableProps = { readonly noVersionsText: string; readonly publicCode: string; readonly stopVersions: ReadonlyArray; - readonly sortingInfo: StopVersionTableSortingInfo; - readonly setSortingInfo: Dispatch< - SetStateAction - >; + readonly sortingInfo: VersionTableSortingInfo; + readonly setSortingInfo: Dispatch>; readonly testId: string; }; @@ -47,7 +48,7 @@ export const StopVersionTable: FC = ({ /> )) ) : ( - + )} diff --git a/ui/src/components/stop-registry/stops/versions/components/StopVersionTableHeader.tsx b/ui/src/components/stop-registry/stops/versions/components/StopVersionTableHeader.tsx index eb7419b670..45c5e887f6 100644 --- a/ui/src/components/stop-registry/stops/versions/components/StopVersionTableHeader.tsx +++ b/ui/src/components/stop-registry/stops/versions/components/StopVersionTableHeader.tsx @@ -1,20 +1,14 @@ import { Dispatch, FC, SetStateAction } from 'react'; -import { StopVersionTableSortingInfo } from '../types'; -import { StopVersionTableHeaderSortableCell } from './StopVersionTableHeaderSortableCell'; - -const EmptyColumnHeader: FC<{ readonly className?: string }> = ({ - className, -}) => ( - // eslint-disable-next-line jsx-a11y/control-has-associated-label - -); +import { + EmptyColumnHeader, + VersionTableHeaderSortableCell, + VersionTableSortingInfo, +} from '../../../../common/versions'; type StopVersionTableHeaderProps = { readonly className?: string; - readonly sortingInfo: StopVersionTableSortingInfo; - readonly setSortingInfo: Dispatch< - SetStateAction - >; + readonly sortingInfo: VersionTableSortingInfo; + readonly setSortingInfo: Dispatch>; }; export const StopVersionTableHeader: FC = ({ @@ -25,49 +19,55 @@ export const StopVersionTableHeader: FC = ({ return ( - - - - - - diff --git a/ui/src/components/stop-registry/stops/versions/queries/useGetStopVersionPageInfo.ts b/ui/src/components/stop-registry/stops/versions/queries/useGetStopVersionPageInfo.ts index 02642c1265..7d21ea026d 100644 --- a/ui/src/components/stop-registry/stops/versions/queries/useGetStopVersionPageInfo.ts +++ b/ui/src/components/stop-registry/stops/versions/queries/useGetStopVersionPageInfo.ts @@ -1,15 +1,16 @@ -import { StopPlaceName, StopVersion, StopVersionStatus } from '../types'; +import { VersionStatus } from '../../../../common'; +import { StopPlaceName, StopVersion } from '../types'; import { useGetStopPlaceName } from './useGetStopPlaceName'; import { useGetStopVersions } from './useGetStopVersions'; function active(stopVersion: StopVersion) { - return stopVersion.status === StopVersionStatus.ACTIVE; + return stopVersion.status === VersionStatus.ACTIVE; } function properVersion(stopVersion: StopVersion) { return ( - stopVersion.status === StopVersionStatus.STANDARD || - stopVersion.status === StopVersionStatus.TEMPORARY + stopVersion.status === VersionStatus.STANDARD || + stopVersion.status === VersionStatus.TEMPORARY ); } diff --git a/ui/src/components/stop-registry/stops/versions/queries/useGetStopVersions.ts b/ui/src/components/stop-registry/stops/versions/queries/useGetStopVersions.ts index 5f3c9d66e3..d1e85bd59c 100644 --- a/ui/src/components/stop-registry/stops/versions/queries/useGetStopVersions.ts +++ b/ui/src/components/stop-registry/stops/versions/queries/useGetStopVersions.ts @@ -16,7 +16,8 @@ import { numberEnumValues, requireValue, } from '../../../../../utils'; -import { StopVersion, StopVersionStatus } from '../types'; +import { VersionStatus } from '../../../../common'; +import { StopVersion } from '../types'; const GQL_GET_QUAY_VERSIONS = gql` query GetQuayVersions($publicCode: String!) { @@ -64,14 +65,14 @@ function parsePriority(prioStr: string | null | undefined): Priority { : Priority.Standard; } -function mapPriorityToStopVersionStatus(priority: Priority): StopVersionStatus { +function mapPriorityToStopVersionStatus(priority: Priority): VersionStatus { switch (priority) { case Priority.Draft: - return StopVersionStatus.DRAFT; + return VersionStatus.DRAFT; case Priority.Temporary: - return StopVersionStatus.TEMPORARY; + return VersionStatus.TEMPORARY; default: - return StopVersionStatus.STANDARD; + return VersionStatus.STANDARD; } } @@ -83,7 +84,7 @@ function mapQuayToStopVersionInfoItem( const priority = parsePriority(rawQuay.priority); const status = rawQuay.id === activeVersionId - ? StopVersionStatus.ACTIVE + ? VersionStatus.ACTIVE : mapPriorityToStopVersionStatus(priority); return { diff --git a/ui/src/components/stop-registry/stops/versions/types/StopVersion.ts b/ui/src/components/stop-registry/stops/versions/types/StopVersion.ts index e8b325920d..9e8b58d4a4 100644 --- a/ui/src/components/stop-registry/stops/versions/types/StopVersion.ts +++ b/ui/src/components/stop-registry/stops/versions/types/StopVersion.ts @@ -1,7 +1,7 @@ import { DateTime } from 'luxon'; import { Point } from '../../../../../types'; import { Priority } from '../../../../../types/enums'; -import { StopVersionStatus } from './StopVersionStatus'; +import { VersionStatus } from '../../../../common'; export type StopVersion = { readonly id: number; @@ -12,7 +12,7 @@ export type StopVersion = { readonly validity_start: DateTime; readonly validity_end: DateTime | null; readonly priority: Priority; - readonly status: StopVersionStatus; + readonly status: VersionStatus; readonly location: Point; readonly changed: string; readonly changedByUserName: string | null; diff --git a/ui/src/components/stop-registry/stops/versions/types/StopVersionTableSortingInfo.ts b/ui/src/components/stop-registry/stops/versions/types/StopVersionTableSortingInfo.ts deleted file mode 100644 index 6104183837..0000000000 --- a/ui/src/components/stop-registry/stops/versions/types/StopVersionTableSortingInfo.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { SortOrder } from '../../../../../types'; -import { StopVersionTableColumn } from './StopVersionTableColumn'; - -export type StopVersionTableSortingInfo = { - readonly sortBy: StopVersionTableColumn; - readonly sortOrder: SortOrder; -}; diff --git a/ui/src/components/stop-registry/stops/versions/types/index.ts b/ui/src/components/stop-registry/stops/versions/types/index.ts index 3d5f8f9527..0177efef21 100644 --- a/ui/src/components/stop-registry/stops/versions/types/index.ts +++ b/ui/src/components/stop-registry/stops/versions/types/index.ts @@ -1,5 +1,2 @@ export * from './StopPlaceName'; export * from './StopVersion'; -export * from './StopVersionStatus'; -export * from './StopVersionTableColumn'; -export * from './StopVersionTableSortingInfo'; diff --git a/ui/src/components/stop-registry/stops/versions/utils/index.ts b/ui/src/components/stop-registry/stops/versions/utils/index.ts index 680cbf8712..844c784c7a 100644 --- a/ui/src/components/stop-registry/stops/versions/utils/index.ts +++ b/ui/src/components/stop-registry/stops/versions/utils/index.ts @@ -1,3 +1 @@ -export * from './accordionClassNames'; -export * from './trStatus'; export * from './useSortedStopVersions'; diff --git a/ui/src/components/stop-registry/stops/versions/utils/trStatus.ts b/ui/src/components/stop-registry/stops/versions/utils/trStatus.ts deleted file mode 100644 index 44b62874af..0000000000 --- a/ui/src/components/stop-registry/stops/versions/utils/trStatus.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { TFunction } from 'i18next'; -import { StopVersionStatus } from '../types'; - -export function trStatus(t: TFunction, status: StopVersionStatus): string { - switch (status) { - case StopVersionStatus.ACTIVE: - return t(($) => $.stopVersion.status.active); - - case StopVersionStatus.STANDARD: - return t(($) => $.stopVersion.status.standard); - - case StopVersionStatus.TEMPORARY: - return t(($) => $.stopVersion.status.temporary); - - case StopVersionStatus.DRAFT: - return t(($) => $.stopVersion.status.draft); - - default: - return ''; - } -} diff --git a/ui/src/components/stop-registry/stops/versions/utils/useSortedStopVersions.ts b/ui/src/components/stop-registry/stops/versions/utils/useSortedStopVersions.ts index aaaa5fac6c..fd353ba33f 100644 --- a/ui/src/components/stop-registry/stops/versions/utils/useSortedStopVersions.ts +++ b/ui/src/components/stop-registry/stops/versions/utils/useSortedStopVersions.ts @@ -1,83 +1,12 @@ -import { DateTime } from 'luxon'; -import { useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { parseDate } from '../../../../../time'; -import { SortOrder } from '../../../../../types'; import { - StopVersion, - StopVersionTableColumn, - StopVersionTableSortingInfo, -} from '../types'; -import { trStatus } from './trStatus'; - -type Comparator = (versionA: StopVersion, versionB: StopVersion) => number; - -function compareDates( - dateA: DateTime | null, - dateB: DateTime | null, - nullsLast: boolean = false, -): number { - if (dateA === null && dateB === null) { - return 0; - } - - if (dateA === null) { - return nullsLast ? 1 : -1; - } - - if (dateB === null) { - return nullsLast ? -1 : 1; - } - - return dateA.valueOf() - dateB.valueOf(); -} - -function useComparator(orderBy: StopVersionTableColumn): Comparator { - const { t } = useTranslation(); - - const collator = useMemo( - () => new Intl.Collator(t(($) => $.languages.intlLangCode)), - [t], - ); - - switch (orderBy) { - case 'STATUS': - return (a, b) => - collator.compare(trStatus(t, a.status), trStatus(t, b.status)); - - case 'VALIDITY_START': - return (a, b) => compareDates(a.validity_start, b.validity_start); - - case 'VALIDITY_END': - return (a, b) => compareDates(a.validity_end, b.validity_end, true); - - case 'VERSION_COMMENT': - return (a, b) => collator.compare(a.version_comment, b.version_comment); - - case 'CHANGED': - return (a, b) => compareDates(parseDate(a.changed), parseDate(b.changed)); - - case 'CHANGED_BY': - return (a, b) => - collator.compare(a.changedByUserName ?? '', b.changedByUserName ?? ''); - - default: - return () => 0; - } -} + VersionTableSortingInfo, + useSortedVersions, +} from '../../../../common/versions'; +import { StopVersion } from '../types'; export function useSortedStopVersions( - sortingInfo: StopVersionTableSortingInfo, + sortingInfo: VersionTableSortingInfo, stopVersions: ReadonlyArray, ): ReadonlyArray { - const comparator = useComparator(sortingInfo.sortBy); - - return useMemo(() => { - const orderedComparator: Comparator = - sortingInfo.sortOrder === SortOrder.ASCENDING - ? comparator - : (a, b) => -comparator(a, b); - - return stopVersions.toSorted(orderedComparator); - }, [sortingInfo, comparator, stopVersions]); + return useSortedVersions(sortingInfo, stopVersions); } diff --git a/ui/src/locales/en-US/common.json b/ui/src/locales/en-US/common.json index eea46b2b94..6e883a0f56 100644 --- a/ui/src/locales/en-US/common.json +++ b/ui/src/locales/en-US/common.json @@ -1501,6 +1501,14 @@ "show": "Show drafts", "hide": "Hide drafts", "noVersions": "The stop has no draft verisons." + } + }, + "versions": { + "status": { + "active": "Active", + "standard": "$t(priority.standard)", + "temporary": "$t(priority.temporary)", + "draft": "$t(priority.draft)" }, "header": { "status": "Status", @@ -1509,12 +1517,6 @@ "version_comment": "Version name", "changed": "Changed", "changed_by": "Changed by" - }, - "status": { - "active": "Active", - "standard": "$t(priority.standard)", - "temporary": "$t(priority.temporary)", - "draft": "$t(priority.draft)" } }, "changeHistory": { diff --git a/ui/src/locales/fi-FI/common.json b/ui/src/locales/fi-FI/common.json index aaa4d57746..b5a547f377 100644 --- a/ui/src/locales/fi-FI/common.json +++ b/ui/src/locales/fi-FI/common.json @@ -1501,6 +1501,14 @@ "show": "Näytä luonnokset", "hide": "Piilota luonnokset", "noVersions": "Pysäkiin ei liity yhtään luonnosta." + } + }, + "versions": { + "status": { + "active": "Voimassa", + "standard": "$t(priority.standard)", + "temporary": "$t(priority.temporary)", + "draft": "$t(priority.draft)" }, "header": { "status": "Status", @@ -1509,12 +1517,6 @@ "version_comment": "Version nimi", "changed": "Muokattu", "changed_by": "Muokkaaja" - }, - "status": { - "active": "Voimassa", - "standard": "$t(priority.standard)", - "temporary": "$t(priority.temporary)", - "draft": "$t(priority.draft)" } }, "changeHistory": {