Skip to content

Commit

Permalink
1073 enable date/time pattern selection through ColumnSettings (#1087)
Browse files Browse the repository at this point in the history
* #1073 enable date/time pattern selection through ColumnSettings

- 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.

* #1073 add ability to switch inferred column type for long columns

- users can now declare the type of long columns as plain number or
  date/time.
- removed isSimpleColumnType as its no longer needed.
- added a timestamps (long) column in showcase's instruments-extended
  table.

* #1073 use unformatted initial value for input-cell renderers

* #1073 only show supported cell renderers for boolean columns
  • Loading branch information
junaidzm13 authored Jan 2, 2024
1 parent 733068a commit 4e88476
Show file tree
Hide file tree
Showing 31 changed files with 458 additions and 173 deletions.
13 changes: 7 additions & 6 deletions vuu-ui/cypress/pages/ShellWithNewTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@ import { SHELL_WITH_NEW_THEME_URL } from "../support/e2e/constants";
export class ShellWithNewTheme {
visit: () => void = () => {
cy.visit(SHELL_WITH_NEW_THEME_URL);
}
};

getContextMenuButton: () => Cypress.Chainable<JQuery<HTMLElement>> = () => {
return cy
.findByRole("tablist", { name: "data tabs" })
.findAllByRole("tab")
.first()
.findByRole("button", { name: "context menu" });
}
};

getSaveLayoutButton: () => Cypress.Chainable<JQuery<HTMLElement>> = () => {
return cy.findByRole("menuitem", { name: "Save Layout" });
}
};

getMyLayoutsButton: () => Cypress.Chainable<JQuery<HTMLElement>> = () => {
return cy.findByRole("tab", { name: "MY LAYOUTS" });
}
};

getLayoutTile: (
layoutName: string,
Expand All @@ -33,12 +33,13 @@ export class ShellWithNewTheme {
creator: string,
date: Date
) => {
const layoutTileName = `${layoutName} ${creator}, ${formatDate(date)}`;
const formattedDate = formatDate({ date: "dd.mm.yyyy" })(date);
const layoutTileName = `${layoutName} ${creator}, ${formattedDate}`;

return cy
.findByRole("listbox", { name: "my layouts" })
.findByRole("list", { name: group })
.findByRole("listitem", { name: layoutTileName })
.findByRole("button");
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { instrumentsData } from "./instruments";
const instrumentsExtendedData = instrumentsData.map((row) =>
(row as VuuRowDataItemType[])
.slice(0, -1)
.concat([random(0, 1) === 1, random(0, 1) === 1])
.concat([random(0, 1) === 1, random(0, 1) === 1, new Date().getTime()])
);

const instrumentsExtendedTable = new Table(
Expand Down
1 change: 1 addition & 0 deletions vuu-ui/packages/vuu-data-test/src/simul/simul-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const schemas: Readonly<Record<SimulTableName, Readonly<TableSchema>>> =
{ name: "ric", serverDataType: "string" },
{ name: "supported", serverDataType: "boolean" },
{ name: "wishlist", serverDataType: "boolean" },
{ name: "lastUpdated", serverDataType: "long" },
],
key: "ric",
table: { module: "SIMUL", table: "instruments" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class LocalPersistenceManager implements PersistenceManager {
const newMetadata: LayoutMetadata = {
...metadata,
id,
created: formatDate("dd.mm.yyyy")(new Date()),
created: formatDate({ date: "dd.mm.yyyy" })(new Date()),
};

this.saveLayoutsWithMetadata(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const persistenceManager = new LocalPersistenceManager();

const existingId = "existing_id";

const newDate = formatDate("dd.mm.yyyy")(new Date());
const newDate = formatDate({ date: "dd.mm.yyyy" })(new Date());

const existingMetadata: LayoutMetadata = {
id: existingId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FormField, FormFieldLabel, Input, Switch } from "@salt-ds/core";
import { ColumnDescriptor, ColumnTypeFormatting } from "@finos/vuu-table-types";
import { ColumnTypeFormatting } from "@finos/vuu-table-types";
import { getTypeFormattingFromColumn } from "@finos/vuu-utils";
import {
ChangeEvent,
Expand All @@ -8,18 +8,14 @@ import {
useCallback,
useState,
} from "react";
import { FormattingSettingsProps } from "./types";

const classBase = "vuuFormattingSettings";

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

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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
ColumnDescriptor,
ColumnDescriptorCustomRenderer,
ColumnTypeFormatting,
ColumnTypeRendering,
} from "@finos/vuu-table-types";
import { Dropdown, SingleSelectionHandler } from "@finos/vuu-ui-controls";
Expand All @@ -16,15 +15,17 @@ import {
import { FormField, FormFieldLabel } from "@salt-ds/core";
import cx from "clsx";
import { HTMLAttributes, useCallback, useMemo } from "react";
import { NumericFormattingSettings } from "./NumericFormattingSettings";
import { BaseNumericFormattingSettings } from "./BaseNumericFormattingSettings";
import { LongTypeFormattingSettings } from "./LongTypeFormattingSettings";
import { FormattingSettingsProps } from "./types";

const classBase = "vuuColumnFormattingPanel";

export interface ColumnFormattingPanelProps
extends HTMLAttributes<HTMLDivElement> {
extends HTMLAttributes<HTMLDivElement>,
FormattingSettingsProps {
availableRenderers: CellRendererDescriptor[];
column: ColumnDescriptor;
onChangeFormatting: (formatting: ColumnTypeFormatting) => void;
onChangeRendering: (renderProps: ColumnTypeRendering) => void;
}

Expand All @@ -35,24 +36,15 @@ export const ColumnFormattingPanel = ({
className,
column,
onChangeFormatting,
onChangeType,
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, onChangeFormatting, onChangeType }),
[column, onChangeFormatting, onChangeType]
);

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,17 @@ export const ColumnFormattingPanel = ({
</div>
);
};

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

switch (column.serverDataType) {
case "double":
case "int":
return <BaseNumericFormattingSettings {...props} />;
case "long":
return <LongTypeFormattingSettings {...props} />;
default:
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React, { SyntheticEvent, useCallback, useMemo, useState } from "react";
import { Dropdown, SingleSelectionHandler } from "@finos/vuu-ui-controls";
import {
DateTimePattern,
defaultPatternsByType,
fallbackDateTimePattern,
getTypeFormattingFromColumn,
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";

export const DateTimeFormattingSettings: React.FC<
FormattingSettingsProps<DateTimeColumnDescriptor>
> = ({ column, onChangeFormatting: onChange }) => {
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, pattern, fallbackState]
);

return (
<>
<FormField labelPlacement="left">
<FormFieldLabel>{"Display"}</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]} pattern`}</FormFieldLabel>
<Dropdown<Required<DateTimePattern>[typeof v]>
onSelectionChange={onDropdownChange(v)}
selected={pattern[v]}
source={supportedDateTimePatterns[v]}
width="100%"
/>
</FormField>
))}
</>
);
};

const labelByType = { date: "Date", time: "Time" } as const;

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

type ToggleValue = (typeof toggleValues)[number];

function getToggleValue(pattern: DateTimePattern): ToggleValue {
return !pattern.time ? "date" : !pattern.date ? "time" : "both";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.vuuLongColumnFormattingSettings {
display: flex;
flex-direction: column;
gap: 4px;
padding-top: 6px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useCallback } from "react";
import {
FormField,
FormFieldLabel,
ToggleButton,
ToggleButtonGroup,
} from "@salt-ds/core";
import { isDateTimeColumn, isTypeDescriptor } from "@finos/vuu-utils";
import { DateTimeFormattingSettings } from "./DateTimeFormattingSettings";
import { BaseNumericFormattingSettings } from "./BaseNumericFormattingSettings";
import { FormattingSettingsProps } from "./types";

import "./LongTypeFormattingSettings.css";

const classBase = "vuuLongColumnFormattingSettings";

export const LongTypeFormattingSettings: React.FC<FormattingSettingsProps> = (
props
) => {
const { column, onChangeType } = props;
const type = isTypeDescriptor(column.type) ? column.type.name : column.type;

const handleToggleChange = useCallback(
(event: React.SyntheticEvent<HTMLButtonElement, Event>) => {
const value = event.currentTarget.value as ToggleValue;
onChangeType(value);
},
[onChangeType]
);

return (
<div className={classBase}>
<FormField>
<FormFieldLabel>{"Type inferred as"}</FormFieldLabel>
<ToggleButtonGroup
className="vuuToggleButtonGroup"
onChange={handleToggleChange}
value={type ?? "number"}
>
{toggleValues.map((v) => (
<ToggleButton key={v} value={v}>
{v.toUpperCase()}
</ToggleButton>
))}
</ToggleButtonGroup>
</FormField>

{isDateTimeColumn(column) ? (
<DateTimeFormattingSettings {...props} column={column} />
) : (
<BaseNumericFormattingSettings {...props} />
)}
</div>
);
};

const toggleValues = ["number", "date/time"] as const;
type ToggleValue = (typeof toggleValues)[number];
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./ColumnFormattingPanel";
export * from "./NumericFormattingSettings";
export * from "./BaseNumericFormattingSettings";
export * from "./DateTimeFormattingSettings";
Loading

0 comments on commit 4e88476

Please sign in to comment.