Skip to content

Commit

Permalink
Add timeline chart in rapid response table
Browse files Browse the repository at this point in the history
  • Loading branch information
frozenhelium committed Dec 11, 2023
1 parent 0b8d11e commit f961c7b
Show file tree
Hide file tree
Showing 12 changed files with 332 additions and 54 deletions.
4 changes: 2 additions & 2 deletions src/components/DateOutput/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useMemo } from 'react';
import { _cs } from '@togglecorp/fujs';
import { formatDate } from '#utils/common';
import { DateLike, formatDate } from '#utils/common';

import styles from './styles.module.css';

export interface Props {
className?: string;
value?: string | number | null;
value: DateLike | undefined | null;
format?: string;
invalidText?: React.ReactNode;
}
Expand Down
43 changes: 43 additions & 0 deletions src/components/Table/ColumnShortcuts/TimelineHeader/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import DateOutput from '#components/DateOutput';
import { _cs } from '@togglecorp/fujs';

import HeaderCell, { HeaderCellProps } from '../../HeaderCell';

import styles from './styles.module.css';

export interface Props extends HeaderCellProps {
className?: string;
dateRange: {
start: Date,
end: Date,
} | undefined;
}

function TimelineHeader(props: Props) {
const {
className,
dateRange,
...otherProps
} = props;

return (
<HeaderCell
// eslint-disable-next-line react/jsx-props-no-spreading
{...otherProps}
className={_cs(styles.timelineHeader, className)}
titleClassName={styles.title}
title={(
<>
<DateOutput
value={dateRange?.start}
/>
<DateOutput
value={dateRange?.end}
/>
</>
)}
/>
);
}

export default TimelineHeader;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.timeline-header {
.title {
display: flex;
flex-grow: 1;
justify-content: space-between;
}
}
88 changes: 88 additions & 0 deletions src/components/Table/ColumnShortcuts/TimelineItem/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { _cs, isNotDefined } from '@togglecorp/fujs';

import Tooltip from '#components/Tooltip';
import TextOutput from '#components/TextOutput';

import { type DateLike, isValidDate } from '#utils/common';

import styles from './styles.module.css';

export interface Props {
className?: string;
startDate: DateLike | null | undefined;
endDate: DateLike | null | undefined;
dateRange: {
start: Date,
end: Date,
} | undefined;
}

function TimelineItem(props: Props) {
const {
className,
startDate,
endDate,
dateRange,
} = props;

if (isNotDefined(dateRange)) {
return null;
}

if (!isValidDate(startDate)) {
return null;
}

if (!isValidDate(endDate)) {
return null;
}

const domainWidth = dateRange.end.getTime() - dateRange.start.getTime();

const start = 1 - (dateRange.end.getTime() - new Date(startDate).getTime()) / domainWidth;
const end = (dateRange.end.getTime() - new Date(endDate).getTime()) / domainWidth;

const today = 1 - (dateRange.end.getTime() - new Date().getTime()) / domainWidth;

return (
<>
<div className={_cs(styles.timelineItem, className)}>
<div className={styles.startDateMarker} />
<div className={styles.endDateMarker} />
<div
className={styles.todayMarker}
style={{
left: `${100 * today}%`,
}}
/>
<div
className={styles.timelineProgress}
style={{
left: `${100 * start}%`,
right: `${100 * end}%`,
}}
/>
</div>
<Tooltip
description={(
<>
<TextOutput
valueType="date"
// FIXME: use translation
label="Start Date"
value={startDate}
/>
<TextOutput
// FIXME: use translation
label="End Date"
value={endDate}
valueType="date"
/>
</>
)}
/>
</>
);
}

export default TimelineItem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
.timeline-item {
position: absolute;
top: 0;
left: var(--go-ui-spacing-sm);
width: calc(100% - 2 * var(--go-ui-spacing-sm));
height: 100%;

.timeline-progress {
position: absolute;
top: 50%;
transform: translateY(-50%);
border-radius: 0.25em;
background-color: var(--go-ui-color-primary-red);
height: 0.5rem;
}

.today-marker {
position: absolute;
border-left: var(--go-ui-width-separator-sm) dashed var(--go-ui-color-primary-blue);
height: 100%;
}

.start-date-marker {
position: absolute;
left: 0;
border-left: var(--go-ui-width-separator-sm) dashed var(--go-ui-color-separator);
height: 100%;
}

.end-date-marker {
position: absolute;
right: 0;
border-left: var(--go-ui-width-separator-sm) dashed var(--go-ui-color-separator);
height: 100%;
}
}
82 changes: 57 additions & 25 deletions src/components/Table/ColumnShortcuts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,13 @@ import {
randomString,
} from '@togglecorp/fujs';

import DateOutput from '#components/DateOutput';
import { type Props as DateOutputProps } from '#components/DateOutput';
import DateRangeOutput from '#components/DateRangeOutput';
import { type Props as DateRangeOutputProps } from '#components/DateRangeOutput';
import NumberOutput from '#components/NumberOutput';
import { type Props as NumberOutputProps } from '#components/NumberOutput';
import BooleanOutput from '#components/BooleanOutput';
import { type Props as BooleanOutputProps } from '#components/BooleanOutput';
import ProgressBar from '#components/ProgressBar';
import { type Props as ProgressBarProps } from '#components/ProgressBar';
import ReducedListDisplay, {
Props as ReducedListDisplayProps,
} from '#components/ReducedListDisplay';
import { type Props as LinkProps } from '#components/Link';
import Link from '#components/Link';
import DateOutput, { type Props as DateOutputProps } from '#components/DateOutput';
import DateRangeOutput, { type Props as DateRangeOutputProps } from '#components/DateRangeOutput';
import NumberOutput, { type Props as NumberOutputProps } from '#components/NumberOutput';
import BooleanOutput, { type Props as BooleanOutputProps } from '#components/BooleanOutput';
import ProgressBar, { type Props as ProgressBarProps } from '#components/ProgressBar';
import ReducedListDisplay, { Props as ReducedListDisplayProps } from '#components/ReducedListDisplay';
import Link, { type Props as LinkProps } from '#components/Link';
import { numericIdSelector } from '#utils/selectors';
import { type GoApiResponse } from '#utils/restRequest';

Expand All @@ -38,6 +30,8 @@ import { type ExpandButtonProps } from './ExpandButton';
import ExpansionIndicator from './ExpansionIndicator';
import { type Props as ExpansionIndicatorProps } from './ExpansionIndicator';
import CountryLink from './CountryLink';
import TimelineItem, { type Props as TimelineItemProps } from './TimelineItem';
import TimelineHeader, { type Props as TimelineHeaderProps } from './TimelineHeader';

import type { Props as CountryLinkProps } from './CountryLink';
import RegionLink from './RegionLink';
Expand Down Expand Up @@ -383,10 +377,10 @@ export function createExpandColumn<D, K>(
return item;
}

export function createExpansionIndicatorColumn<D, K>(
export function createExpansionIndicatorColumn<DATUM, KEY>(
isExpanded?: boolean,
) {
const item: Column<D, K, ExpansionIndicatorProps, HeaderCellProps> = {
const item: Column<DATUM, KEY, ExpansionIndicatorProps, HeaderCellProps> = {
id: randomString(),
title: '',
headerCellRenderer: HeaderCell,
Expand Down Expand Up @@ -417,14 +411,14 @@ export function createExpansionIndicatorColumn<D, K>(
return item;
}

export function createElementColumn<DATA, KEY, ELEMENT_PROPS>(
export function createElementColumn<DATUM, KEY, ELEMENT_PROPS>(
id: string,
title: string,
renderer: React.ComponentType<ELEMENT_PROPS>,
rendererParams: (key: KEY, datum: DATA) => ELEMENT_PROPS,
options?: Options<DATA, KEY, ELEMENT_PROPS, HeaderCellProps>,
rendererParams: (key: KEY, datum: DATUM) => ELEMENT_PROPS,
options?: Options<DATUM, KEY, ELEMENT_PROPS, HeaderCellProps>,
) {
const item: Column<DATA, KEY, ELEMENT_PROPS, HeaderCellProps> = {
const item: Column<DATUM, KEY, ELEMENT_PROPS, HeaderCellProps> = {
id,
title,
headerCellRenderer: HeaderCell,
Expand All @@ -446,12 +440,50 @@ export function createElementColumn<DATA, KEY, ELEMENT_PROPS>(
return item;
}

export function createActionColumn<D, K>(
export function createTimelineColumn<DATUM, KEY>(
id: string,
rendererParams: (datum: D) => TableActionsProps,
options?: Options<D, K, TableActionsProps, HeaderCellProps>,
dateRange: {
start: Date,
end: Date,
} | undefined,
rendererParams: (datum: DATUM) => Omit<TimelineItemProps, 'dateRange'>,
options?: Options<DATUM, KEY, TableActionsProps, HeaderCellProps>,
) {
const item: Column<DATUM, KEY, TimelineItemProps, TimelineHeaderProps> = {
id,
title: '',
headerCellRenderer: TimelineHeader,
headerCellRendererParams: {
dateRange,
sortable: options?.sortable,
},
cellRenderer: TimelineItem,
cellRendererParams: (_, datum) => ({
dateRange,
...rendererParams(datum),
}),
headerContainerClassName: options?.headerContainerClassName,
cellRendererClassName: options?.cellRendererClassName,
columnClassName: options?.columnClassName,
headerCellRendererClassName: options?.headerCellRendererClassName,
cellContainerClassName: _cs(
options?.cellContainerClassName,
styles.timelineCellContainer,
),
columnWidth: options?.columnWidth,
columnStretch: options?.columnStretch,
columnStyle: options?.columnStyle,
};

return item;
}

export function createActionColumn<DATUM, KEY>(
id: string,
rendererParams: (datum: DATUM) => TableActionsProps,
options?: Options<DATUM, KEY, TableActionsProps, HeaderCellProps>,
) {
const item: Column<D, K, TableActionsProps, HeaderCellProps> = {
const item: Column<DATUM, KEY, TableActionsProps, HeaderCellProps> = {
id,
title: '',
headerCellRenderer: HeaderCell,
Expand Down
4 changes: 4 additions & 0 deletions src/components/Table/ColumnShortcuts/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
.expansion-indicator-cell-container {
position: relative;
}

.timeline-cell-container {
position: relative;
}
2 changes: 1 addition & 1 deletion src/components/Table/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface BaseHeader {
name: string;
index: number;

title?: string;
title?: React.ReactNode;
}

export interface BaseCell {
Expand Down
28 changes: 19 additions & 9 deletions src/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,16 +373,10 @@ export function formatNumber(
return newValue;
}

export type DateLike = string | number | Date;

export function formatDate(
value: null | undefined,
format?: string,
): undefined;
export function formatDate(
value: Date | string | number,
format?: string,
): string | undefined;
export function formatDate(
value: Date | string | number | null | undefined,
value: DateLike | null | undefined,
format = DEFAULT_DATE_FORMAT,
) {
if (isNotDefined(value)) {
Expand Down Expand Up @@ -668,3 +662,19 @@ export function addNumMonthsToDate(

return dateSafe;
}

export function isValidDate<T extends DateLike>(
value: T | null | undefined,
): value is T {
if (isNotDefined(value)) {
return false;
}

const date = new Date(value);

if (Number.isNaN(date.getTime())) {
return false;
}

return true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
"strings": {
"rapidResponseTitle": "Rapid Response Personnel",
"rapidResponse": "Rapid Response",
"personnelTableStartDate": "Start Date",
"personnelTableEndDate": "End Date",
"personnelTableName": "Name",
"personnelTablePosition": "Position",
"personnelTableType": "Type",
Expand Down
Loading

0 comments on commit f961c7b

Please sign in to comment.