Skip to content

Commit

Permalink
feat: bearing ranges, disableds in dropdowns and menus
Browse files Browse the repository at this point in the history
* feat: bearing ranges plus tests

* feat: added disabled option and disabled title to dropdown

* Added disabled option to drop downs
  • Loading branch information
matttdawson authored Oct 27, 2022
1 parent e9e1647 commit 64e926d
Show file tree
Hide file tree
Showing 12 changed files with 118 additions and 32 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"test": "react-scripts test --passWithNoTests",
"eject": "react-scripts eject",
"lint": "eslint ./src --ext .js,.ts,.tsx --fix --cache",
"storybook": "start-storybook --no-manager-cache -p 6006 -s public",
"storybook": "start-storybook -p 6006 -s public",
"build-storybook": "build-storybook -s public",
"deploy-storybook": "npx --yes -p @storybook/storybook-deployer storybook-to-ghpages",
"chromatic": "chromatic --exit-zero-on-changes",
Expand Down
11 changes: 9 additions & 2 deletions src/components/gridForm/GridFormDropDown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface GridPopoutEditDropDownSelectedItem<RowType, ValueType> {
interface FinalSelectOption<ValueType> {
value: ValueType;
label?: JSX.Element | string;
disabled?: boolean | string;
}

export const MenuSeparatorString = "_____MENU_SEPARATOR_____";
Expand Down Expand Up @@ -78,7 +79,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(props:

const optionsList = optionsConf?.map((item) => {
if (item == null || typeof item == "string" || typeof item == "number") {
item = { value: item as ValueType, label: item } as FinalSelectOption<ValueType>;
item = { value: item as ValueType, label: item, disabled: false } as FinalSelectOption<ValueType>;
}
return item;
}) as any as FinalSelectOption<ValueType>[];
Expand Down Expand Up @@ -180,7 +181,13 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(props:
item.value === MenuSeparatorString ? (
<MenuDivider key={`$$divider_${index}`} />
) : filteredValues.includes(item.value) ? null : (
<MenuItem key={`${item.value}`} value={item.value} onClick={() => selectItemHandler(item.value)}>
<MenuItem
key={`${item.value}`}
disabled={!!item.disabled}
title={item.disabled && typeof item.disabled !== "boolean" ? item.disabled : ""}
value={item.value}
onClick={() => selectItemHandler(item.value)}
>
{item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)}
</MenuItem>
),
Expand Down
3 changes: 2 additions & 1 deletion src/components/gridForm/GridFormEditBearing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useGridPopoverHook } from "../GridPopoverHook";

export interface GridFormEditBearingProps<RowType extends GridBaseRow> extends GenericCellEditorParams<RowType> {
placeHolder: string;
range?: (value: number | null) => string | null;
onSave?: (selectedRows: RowType[], value: number | null) => Promise<boolean>;
}

Expand Down Expand Up @@ -55,7 +56,7 @@ export const GridFormEditBearing = <RowType extends GridBaseRow>(props: GridForm
onKeyDown: async (e) => e.key === "Enter" && triggerSave().then(),
}}
formatted={bearingStringValidator(value) ? "?" : convertDDToDMS(bearingNumberParser(value))}
error={bearingStringValidator(value)}
error={bearingStringValidator(value, formProps.range)}
/>
</div>,
);
Expand Down
8 changes: 5 additions & 3 deletions src/components/gridForm/GridFormPopoutMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ interface MenuSeparatorType {

export interface MenuOption<RowType> {
label: JSX.Element | string | MenuSeparatorType;
action: (selectedRows: RowType[]) => Promise<boolean>;
action?: (selectedRows: RowType[]) => Promise<boolean>;
disabled?: string | boolean;
multiEdit: boolean;
}

Expand Down Expand Up @@ -54,7 +55,7 @@ export const GridFormPopoutMenu = <RowType extends GridBaseRow>(props: GridFormP
const actionClick = useCallback(
async (menuOption: MenuOption<any>) => {
return await updatingCells({ selectedRows: props.selectedRows, field: props.field }, async (selectedRows) => {
await menuOption.action(selectedRows);
menuOption.action && (await menuOption.action(selectedRows));
return true;
});
},
Expand All @@ -78,7 +79,8 @@ export const GridFormPopoutMenu = <RowType extends GridBaseRow>(props: GridFormP
<MenuItem
key={`${item.label}`}
onClick={() => actionClick(item)}
disabled={!filteredOptions?.includes(item)}
disabled={!!item.disabled || !filteredOptions?.includes(item)}
title={item.disabled && typeof item.disabled !== "boolean" ? item.disabled : ""}
>
{item.label as JSX.Element | string}
</MenuItem>
Expand Down
2 changes: 1 addition & 1 deletion src/components/gridForm/GridFormTextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const GridFormTextArea = <RowType extends GridBaseRow>(props: GridFormPro
return formProps.validate(value);
}
return null;
}, [formProps.maxlength, formProps.required, value.length]);
}, [formProps, value]);

const save = useCallback(
async (selectedRows: any[]): Promise<boolean> => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/gridForm/GridFormTextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const GridFormTextInput = <RowType extends GridBaseRow>(props: GridFormPr
return formProps.validate(value);
}
return null;
}, [formProps.maxlength, formProps.required, value.length]);
}, [formProps, value]);

const save = useCallback(
async (selectedRows: any[]): Promise<boolean> => {
Expand Down
30 changes: 28 additions & 2 deletions src/components/gridPopoverEdit/GridPopoverEditBearing.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { GenericMultiEditCellClass } from "../GenericCellClass";
import { GenericCellColDef } from "../gridRender/GridRenderGenericCell";
import { bearingValueFormatter } from "@utils/bearing";
import { bearingCorrectionValueFormatter, bearingValueFormatter } from "@utils/bearing";
import { GridCell } from "../GridCell";
import { GridFormEditBearing, GridFormEditBearingProps } from "../gridForm/GridFormEditBearing";
import { GridBaseRow } from "../Grid";

export const GridPopoverEditBearing = <RowType extends GridBaseRow>(
export const GridPopoverEditBearingLike = <RowType extends GridBaseRow>(
colDef: GenericCellColDef<RowType, GridFormEditBearingProps<RowType>>,
) =>
GridCell<RowType, GridFormEditBearingProps<RowType>>({
Expand All @@ -21,3 +21,29 @@ export const GridPopoverEditBearing = <RowType extends GridBaseRow>(
},
}),
});

export const GridPopoverEditBearing = <RowType extends GridBaseRow>(
colDef: GenericCellColDef<RowType, GridFormEditBearingProps<RowType>>,
) => ({
...GridPopoverEditBearingLike(colDef),
valueFormatter: bearingValueFormatter,
range: (value: number | null) => {
if (value === null) return "Bearing is required";
if (value >= 360) return "Bearing must be less than 360 degrees";
if (value < 0) return "Bearing must not be negative";
return null;
},
});

export const GridPopoverEditBearingCorrection = <RowType extends GridBaseRow>(
colDef: GenericCellColDef<RowType, GridFormEditBearingProps<RowType>>,
) => ({
...GridPopoverEditBearingLike(colDef),
valueFormatter: bearingCorrectionValueFormatter,
range: (value: number | null) => {
if (value === null) return "Bearing is required";
if (value >= 360) return "Bearing must be less than 360 degrees";
if (value <= -180) return "Bearing must be greater then -180 degrees";
return null;
},
});
23 changes: 13 additions & 10 deletions src/stories/components/GridPopoutBearing.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import { UpdatingContextProvider } from "@contexts/UpdatingContextProvider";
import { GridContextProvider } from "@contexts/GridContextProvider";
import { Grid, GridProps } from "@components/Grid";
import { GridCell } from "@components/GridCell";
import { GridPopoverEditBearing } from "@components/gridPopoverEdit/GridPopoverEditBearing";
import {
GridPopoverEditBearing,
GridPopoverEditBearingCorrection,
} from "@components/gridPopoverEdit/GridPopoverEditBearing";
import { wait } from "@utils/util";

export default {
Expand All @@ -34,7 +37,7 @@ export default {
interface ITestRow {
id: number;
bearing1: number | null;
bearing2: number | null;
bearingCorrection: number | null;
}

const GridReadOnlyTemplate: ComponentStory<typeof Grid> = (props: GridProps) => {
Expand All @@ -59,15 +62,15 @@ const GridReadOnlyTemplate: ComponentStory<typeof Grid> = (props: GridProps) =>
placeHolder: "Enter Bearing",
},
}),
GridPopoverEditBearing<ITestRow>({
field: "bearing2",
headerName: "Bearing onSave",
GridPopoverEditBearingCorrection<ITestRow>({
field: "bearingCorrection",
headerName: "Bearing Correction",
cellEditorParams: {
multiEdit: true,
placeHolder: "Enter Bearing",
onSave: async (selectedRows: ITestRow[], value: ITestRow["bearing2"]) => {
onSave: async (selectedRows: ITestRow[], value: ITestRow["bearingCorrection"]) => {
await wait(1000);
selectedRows.forEach((row) => (row["bearing2"] = value));
selectedRows.forEach((row) => (row["bearingCorrection"] = value));
return true;
},
},
Expand All @@ -79,9 +82,9 @@ const GridReadOnlyTemplate: ComponentStory<typeof Grid> = (props: GridProps) =>
const rowData = useMemo(
() =>
[
{ id: 1000, bearing1: 1.234, bearing2: 90 },
{ id: 1001, bearing1: 1.565, bearing2: 240 },
{ id: 1002, bearing1: null, bearing2: 355.1 },
{ id: 1000, bearing1: 1.234, bearingCorrection: 90 },
{ id: 1001, bearing1: 1.565, bearingCorrection: 240 },
{ id: 1002, bearing1: null, bearingCorrection: 355.1 },
] as ITestRow[],
[],
);
Expand Down
7 changes: 4 additions & 3 deletions src/stories/components/GridPopoutEditDropDown.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,12 @@ const GridEditDropDownTemplate: ComponentStory<typeof Grid> = (props: GridProps)
options: [
{
value: "1",
label: <span style={{ border: "2px dashed blue" }}>One</span>,
label: "One",
disabled: "Disabled for test",
},
{ value: "2", label: <span style={{ border: "2px dashed red" }}>Two</span> },
{ value: "2", label: "Two" },
MenuSeparator,
{ value: "3", label: <span style={{ border: "2px dashed green" }}>Three</span> },
{ value: "3", label: "Three" },
],
},
}),
Expand Down
5 changes: 5 additions & 0 deletions src/stories/components/GridReadOnly.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ const GridReadOnlyTemplate: ComponentStory<typeof Grid> = (props: GridProps) =>
},
multiEdit: true,
},
{
label: "Disabled item",
disabled: "Disabled for test",
multiEdit: true,
},
];
},
},
Expand Down
30 changes: 30 additions & 0 deletions src/utils/bearing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { convertDDToDMS } from "./bearing";

describe("convertDDToDMS", () => {
test("converts decimal-ish degrees to DMS", () => {
expect(convertDDToDMS(-0.001, false, false)).toBe("-0° 00' 10\"");
expect(convertDDToDMS(-10.001, false, false)).toBe("-10° 00' 10\"");
expect(convertDDToDMS(-370.001, false, false)).toBe("-10° 00' 10\"");
expect(convertDDToDMS(359.595999, false, false)).toBe("0° 00'");
expect(convertDDToDMS(369.696999, false, false)).toBe("10° 10' 10\"");
expect(convertDDToDMS(221.555999, false, false)).toBe("221° 56'");
expect(convertDDToDMS(221.555999, false, true)).toBe("221° 56' 00.0\"");
expect(convertDDToDMS(5)).toBe("+5° 00' 00.0\"");
expect(convertDDToDMS(5.0)).toBe("+5° 00' 00.0\"");
expect(convertDDToDMS(5.00001)).toBe("+5° 00' 00.1\"");
expect(convertDDToDMS(5.1)).toBe("+5° 10' 00.0\"");
expect(convertDDToDMS(5.12345)).toBe("+5° 12' 34.5\"");
expect(convertDDToDMS(5.12345, false)).toBe("5° 12' 34.5\"");

expect(convertDDToDMS(300)).toBe("+300° 00' 00.0\"");
expect(convertDDToDMS(300.0)).toBe("+300° 00' 00.0\"");
expect(convertDDToDMS(300.00001)).toBe("+300° 00' 00.1\"");
expect(convertDDToDMS(300.1)).toBe("+300° 10' 00.0\"");
expect(convertDDToDMS(300.12345)).toBe("+300° 12' 34.5\"");
expect(convertDDToDMS(300.12345, false)).toBe("300° 12' 34.5\"");

expect(convertDDToDMS(300, false, false)).toBe("300° 00'");
expect(convertDDToDMS(300.1, false, false)).toBe("300° 10'");
expect(convertDDToDMS(0, false)).toBe("0° 00'");
});
});
27 changes: 19 additions & 8 deletions src/utils/bearing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,46 @@ export const bearingValueFormatter = (params: ValueFormatterParams): string => {
if (value == null) {
return "-";
}
return convertDDToDMS(value);
return convertDDToDMS(value, false, false);
};

export const bearingCorrectionValueFormatter = (params: ValueFormatterParams): string => {
const value = params.value;
if (value == null) {
return "-";
}
return convertDDToDMS(value, true, true);
};

export const bearingNumberParser = (value: string): number | null => {
if (value === "") return null;
return parseFloat(value);
};

const validMaskForDmsBearing = /^(\d+)?(\.([0-5](\d([0-5](\d(\d+)?)?)?)?)?)?$/;
export const bearingStringValidator = (value: string): string | null => {
const validMaskForDmsBearing = /^((-)?\d+)?(\.([0-5](\d([0-5](\d(\d+)?)?)?)?)?)?$/;
export const bearingStringValidator = (
value: string,
customValidate?: (value: number | null) => string | null,
): string | null => {
value = value.trim();
if (value === "") return null;
const match = value.match(validMaskForDmsBearing);
if (!match) return "Bearing must be a positive number in D.MMSSS format";
const decimalPart = match[3];
const decimalPart = match[4];
if (decimalPart != null && decimalPart.length > 5) {
return "Bearing has a maximum of 5 decimal places";
}

const bearing = parseFloat(value);
if (bearing >= 360) return "Bearing must be between 0 and 360 inclusive";
return null;

return customValidate ? customValidate(bearing) : null;
};

// Decimal-ish degrees to Degrees Minutes Seconds converter
export const convertDDToDMS = (dd: number | null, showPositiveSymbol = true, addTrailingZeros = true): string => {
if (dd == null) return "–";

if (dd === 0) addTrailingZeros = true;
if (dd === 0) addTrailingZeros = false;

// toFixed rounds parts up greater than 60, which has to be corrected below
const [bearingWholeString, beringDecimalString] = dd.toFixed(5).split(".");
Expand Down Expand Up @@ -64,7 +75,7 @@ export const convertDDToDMS = (dd: number | null, showPositiveSymbol = true, add
dmsString += `\xa0${minString}'\xa0${secString}.${deciSecString}"`; // "\xa0" is here for non-breaking space
} else if (secNumeric != 0) {
dmsString += `\xa0${minString}'\xa0${secString}"`;
} else if (minNumeric != 0) {
} else {
dmsString += `\xa0${minString}'`;
}

Expand Down

0 comments on commit 64e926d

Please sign in to comment.