Skip to content

Commit

Permalink
finos#1073 enable date/time pattern selection through ColumnSettings
Browse files Browse the repository at this point in the history
- converges date and time column types to one unified date/time type, consistent
  with how some major programming languages handles datetime.
- date/time pattern selection is only available for columns with "date/time" type.
- changes DateTimePattern to be an object instead of strings to enable
  simultaneous selection of both date and time patterns.
- also moves DateTimeColumnDescriptor to vuu-table-types package for consistency.
  • Loading branch information
junaidzm13 committed Dec 28, 2023
1 parent 0de5af5 commit 6f09305
Show file tree
Hide file tree
Showing 19 changed files with 355 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import {
getCellRendererOptions,
getConfigurationEditor,
isColumnTypeRenderer,
isDateTimeColumn,
isTypeDescriptor,
} from "@finos/vuu-utils";
import { FormField, FormFieldLabel } from "@salt-ds/core";
import cx from "clsx";
import { HTMLAttributes, useCallback, useMemo } from "react";
import { NumericFormattingSettings } from "./NumericFormattingSettings";
import { DateTimeFormattingSettings } from "./DateTimeFormattingSettings";
import { FormattingSettingsProps } from "./types";

const classBase = "vuuColumnFormattingPanel";

Expand All @@ -38,21 +41,10 @@ export const ColumnFormattingPanel = ({
onChangeRendering,
...htmlAttributes
}: ColumnFormattingPanelProps) => {
const contentForType = useMemo(() => {
switch (column.serverDataType) {
case "double":
case "int":
case "long":
return (
<NumericFormattingSettings
column={column}
onChange={onChangeFormatting}
/>
);
default:
return null;
}
}, [column, onChangeFormatting]);
const formattingSettingsForType = useMemo(
() => formattingSettingsByColType({ column, onChange: onChangeFormatting }),
[column, onChangeFormatting]
);

const ConfigEditor = useMemo<
React.FC<ConfigurationEditorProps> | undefined
Expand Down Expand Up @@ -81,7 +73,7 @@ export const ColumnFormattingPanel = ({
const handleChangeRenderer = useCallback<
SingleSelectionHandler<CellRendererDescriptor>
>(
(evt, cellRendererDescriptor) => {
(_, cellRendererDescriptor) => {
const renderProps: ColumnTypeRendering = {
name: cellRendererDescriptor.name,
};
Expand Down Expand Up @@ -112,7 +104,7 @@ export const ColumnFormattingPanel = ({
<div
className={cx(classBase, className, `${classBase}-${serverDataType}`)}
>
{contentForType}
{formattingSettingsForType}
{ConfigEditor ? (
<ConfigEditor
column={column as ColumnDescriptorCustomRenderer}
Expand All @@ -123,3 +115,20 @@ export const ColumnFormattingPanel = ({
</div>
);
};

function formattingSettingsByColType(props: FormattingSettingsProps) {
const { column } = props;

if (isDateTimeColumn(column)) {
return <DateTimeFormattingSettings {...props} column={column} />;
}

switch (column.serverDataType) {
case "double":
case "int":
case "long":
return <NumericFormattingSettings {...props} />;
default:
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.vuuDateTimeFormattingSettings {
display: flex;
flex-direction: column;
gap: 4px;
padding-top: 6px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React, { SyntheticEvent, useCallback, useMemo, useState } from "react";
import { Dropdown, SingleSelectionHandler } from "@finos/vuu-ui-controls";
import {
DateTimePattern,
defaultPatternsByType,
fallbackDateTimePattern,
getTypeFormattingFromColumn,
isNotNullOrUndefined,
supportedDateTimePatterns,
} from "@finos/vuu-utils";
import {
FormField,
FormFieldLabel,
ToggleButton,
ToggleButtonGroup,
} from "@salt-ds/core";
import { DateTimeColumnDescriptor } from "@finos/vuu-table-types";
import { FormattingSettingsProps } from "./types";

import "./DateTimeFormattingSettings.css";

const classBase = "vuuDateTimeFormattingSettings";

export const DateTimeFormattingSettings: React.FC<
FormattingSettingsProps<DateTimeColumnDescriptor>
> = (props) => {
const { column, onChange } = props;
const formatting = getTypeFormattingFromColumn(column);
const { pattern = fallbackDateTimePattern } = formatting;
const toggleValue = useMemo(() => getToggleValue(pattern), [pattern]);

const [fallbackState, setFallbackState] = useState<Required<DateTimePattern>>(
{
time: pattern.time ?? defaultPatternsByType.time,
date: pattern.date ?? defaultPatternsByType.date,
}
);

const onPatternChange = useCallback(
(pattern: DateTimePattern) => onChange({ ...formatting, pattern }),
[onChange, formatting]
);

const onDropdownChange = useCallback<
<T extends keyof DateTimePattern>(
key: T
) => SingleSelectionHandler<Required<DateTimePattern>[T]>
>(
(key) => (_, p) => {
const updatedPattern = { ...(pattern ?? {}), [key]: p };
setFallbackState((s) => ({
time: updatedPattern.time ?? s.time,
date: updatedPattern.date ?? s.date,
}));
onPatternChange(updatedPattern);
},
[onPatternChange, pattern]
);

const onToggleChange = useCallback(
(evnt: SyntheticEvent<HTMLButtonElement, Event>) => {
const value = evnt.currentTarget.value as ToggleValue;
switch (value) {
case "time":
return onPatternChange({
[value]: pattern[value] ?? fallbackState[value],
});
case "date":
return onPatternChange({
[value]: pattern[value] ?? fallbackState[value],
});
case "both":
return onPatternChange({
time: pattern.time ?? fallbackState.time,
date: pattern.date ?? fallbackState.date,
});
}
},
[onPatternChange]
);

return (
<div className={classBase}>
<FormField>
<FormFieldLabel>{"Date/Time pattern"}</FormFieldLabel>
<ToggleButtonGroup
className="vuuToggleButtonGroup"
onChange={onToggleChange}
value={toggleValue}
>
{toggleValues.map((v) => (
<ToggleButton key={v} value={v}>
{v.toUpperCase()}
</ToggleButton>
))}
</ToggleButtonGroup>
</FormField>

{(["date", "time"] as const)
.filter((v) => !!pattern[v])
.map((v) => (
<FormField labelPlacement="left" key={v}>
<FormFieldLabel>{`${labelByType[v]}`}</FormFieldLabel>
<Dropdown<Required<DateTimePattern>[typeof v]>
onSelectionChange={onDropdownChange(v)}
selected={pattern[v]}
source={supportedDateTimePatterns[v]}
width="100%"
/>
</FormField>
))}
</div>
);
};

const labelByType: Record<keyof DateTimePattern, string> = {
date: "Date",
time: "Time",
};

const toggleValues = ["date", "time", "both"] as const;

type ToggleValue = (typeof toggleValues)[number];

function getToggleValue(pattern: DateTimePattern): ToggleValue {
const { date, time } = pattern;

return !isNotNullOrUndefined(time)
? "date"
: !isNotNullOrUndefined(date)
? "time"
: "both";
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,11 @@ import {
useCallback,
useState,
} from "react";
import { FormattingSettingsProps } from "./types";

const classBase = "vuuFormattingSettings";

export interface NumericFormattingSettingsProps {
column: ColumnDescriptor;
onChange: (formatting: ColumnTypeFormatting) => void;
}

export const NumericFormattingSettings = ({
column,
onChange,
}: NumericFormattingSettingsProps) => {
export const NumericFormattingSettings = ({column, onChange}: FormattingSettingsProps) => {
const [formattingSettings, setFormattingSettings] =
useState<ColumnTypeFormatting>(getTypeFormattingFromColumn(column));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./ColumnFormattingPanel";
export * from "./NumericFormattingSettings";
export * from "./DateTimeFormattingSettings";
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ColumnDescriptor, ColumnTypeFormatting } from "@finos/vuu-table-types";

export interface FormattingSettingsProps<T extends ColumnDescriptor = ColumnDescriptor> {
column: T;
onChange: (formatting: ColumnTypeFormatting) => void;
}
21 changes: 17 additions & 4 deletions vuu-ui/packages/vuu-table-types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
import type { VuuDataRow } from "@finos/vuu-protocol-types";
import type { ValueFormatter } from "@finos/vuu-table";
import type { ClientSideValidationChecker } from "@finos/vuu-ui-controls";
import type { DateTimePattern } from "@finos/vuu-utils";
import type { FunctionComponent, MouseEvent } from "react";
import type { HTMLAttributes } from "react";

Expand Down Expand Up @@ -77,7 +78,7 @@ export interface GridConfig extends TableConfig {
export declare type ColumnTypeFormatting = {
alignOnDecimals?: boolean;
decimals?: number;
pattern?: string;
pattern?: DateTimePattern;
zeroPad?: boolean;
};

Expand Down Expand Up @@ -128,7 +129,19 @@ export interface ValueListRenderer {
values: string[];
}

export declare type DateTimeColumnTypeSimple = "date" | "time";
export declare type DateTimeColumnTypeSimple = "date/time";

type DateTimeColumnType =
| DateTimeColumnTypeSimple
| (Omit<ColumnTypeDescriptor, "name"> & { name: DateTimeColumnTypeSimple });

export declare type DateTimeColumnDescriptor = Omit<
ColumnDescriptor,
"type"
> & {
type: DateTimeColumnType;
};

export declare type ColumnTypeSimple =
| "string"
| "number"
Expand Down Expand Up @@ -180,9 +193,9 @@ export interface ColumnDescriptor {
colHeaderLabelRenderer?: string;
editable?: boolean;
flex?: number;
/**
/**
Optional additional level(s) of heading to display above label.
May span multiple columns, if multiple adjacent columns declare
May span multiple columns, if multiple adjacent columns declare
same heading at same level.
*/
heading?: string[];
Expand Down
17 changes: 3 additions & 14 deletions vuu-ui/packages/vuu-utils/src/column-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import type {
ColumnTypeFormatting,
ColumnTypeRendering,
ColumnTypeWithValidationRules,
DateTimeColumnTypeSimple,
DefaultColumnConfiguration,
GroupColumnDescriptor,
LookupRenderer,
Expand All @@ -32,6 +31,7 @@ import type {
TableHeading,
TableHeadings,
ValueListRenderer,
DateTimeColumnDescriptor,
} from "@finos/vuu-table-types";
import type { CSSProperties } from "react";
import { moveItem } from "./array-utils";
Expand Down Expand Up @@ -131,22 +131,11 @@ export const isNumericColumn = ({ serverDataType, type }: ColumnDescriptor) => {
return false;
};

type DateTimeColumnType =
| DateTimeColumnTypeSimple
| (Omit<ColumnTypeDescriptor, "name"> & { name: DateTimeColumnTypeSimple });

export type DateTimeColumnDescriptor = Omit<ColumnDescriptor, "type"> & {
type: DateTimeColumnType;
};

export const isDateColumn = ({ type }: ColumnDescriptor) =>
(isTypeDescriptor(type) ? type.name : type) === "date";
export const isTimeColumn = ({ type }: ColumnDescriptor) =>
(isTypeDescriptor(type) ? type.name : type) === "time";
export const isDateTimeColumn = (
column: ColumnDescriptor
): column is DateTimeColumnDescriptor =>
isDateColumn(column) || isTimeColumn(column);
(isTypeDescriptor(column.type) ? column.type.name : column.type) ===
"date/time";

export const isPinned = (column: ColumnDescriptor) =>
typeof column.pin === "string";
Expand Down
41 changes: 30 additions & 11 deletions vuu-ui/packages/vuu-utils/src/date/formatter.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isNotNullOrUndefined } from "../ts-utils";
import { DatePattern, DateTimePattern, TimePattern } from "./types";

type DateTimeFormatConfig = {
Expand Down Expand Up @@ -55,18 +56,36 @@ const formatConfigByDatePatterns: Record<DatePattern, DateTimeFormatConfig> = {
},
};

const formatConfigByDateTimePatterns: Record<
DateTimePattern,
DateTimeFormatConfig
> = { ...formatConfigByDatePatterns, ...formatConfigByTimePatterns };
function getFormatConfigs(pattern: DateTimePattern) {
return [
isNotNullOrUndefined(pattern["date"])
? formatConfigByDatePatterns[pattern["date"]]
: null,
isNotNullOrUndefined(pattern["time"])
? formatConfigByTimePatterns[pattern["time"]]
: null,
];
}

function applyFormatting(
d: Date,
opts: Pick<DateTimeFormatConfig, "postProcessor"> & {
dateTimeFormat: Intl.DateTimeFormat;
}
): string {
const { dateTimeFormat, postProcessor } = opts;
const dateStr = dateTimeFormat.format(d);
return postProcessor ? postProcessor(dateStr) : dateStr;
}

export function formatDate(pattern: DateTimePattern): (d: Date) => string {
const { locale, options, postProcessor } =
formatConfigByDateTimePatterns[pattern];
const dateTimeFormat = Intl.DateTimeFormat(locale, options);
const formattingOpts = getFormatConfigs(pattern)
.filter(isNotNullOrUndefined)
.map((c) => ({
dateTimeFormat: Intl.DateTimeFormat(c.locale, c.options),
postProcessor: c.postProcessor,
}));

return (d) => {
const dateStr = dateTimeFormat.format(d);
return postProcessor ? postProcessor(dateStr) : dateStr;
};
return (d) =>
formattingOpts.map((opts) => applyFormatting(d, opts)).join(" ");
}
Loading

0 comments on commit 6f09305

Please sign in to comment.