Refactor DashboardViewConfig enum to use explicit types for default height and width

This commit is contained in:
Simon Larsen 2024-10-24 18:34:31 +01:00
parent b31ffab577
commit 75ab4bd946
No known key found for this signature in database
GPG Key ID: 96C5DCA24769DBCA
12 changed files with 216 additions and 30 deletions

View File

@ -1,7 +1,8 @@
import { ObjectType } from "../../JSON";
import ObjectID from "../../ObjectID";
export default interface DashboardBaseComponent {
_type: string;
_type: ObjectType;
componentId: ObjectID;
topInDashboardUnits: number;
leftInDashboardUnits: number;

View File

@ -1,9 +1,10 @@
import { ObjectType } from "../../JSON";
import ObjectID from "../../ObjectID";
import ChartType from "../Chart/ChartType";
import BaseComponent from "./DashboardBaseComponent";
export default interface DashboardChartComponent extends BaseComponent {
_type: "DashboardChartComponent";
_type: ObjectType.DashboardChartComponent;
componentId: ObjectID;
chartType: ChartType;
}

View File

@ -1,8 +1,9 @@
import { ObjectType } from "../../JSON";
import ObjectID from "../../ObjectID";
import BaseComponent from "./DashboardBaseComponent";
export default interface DashboardTextComponent extends BaseComponent {
_type: "DashboardTextComponent";
_type: ObjectType.DashboardTextComponent;
componentId: ObjectID;
text: string;
}

View File

@ -1,7 +1,8 @@
import { ObjectType } from "../../JSON";
import ObjectID from "../../ObjectID";
import BaseComponent from "./DashboardBaseComponent";
export default interface DashboardValueComponent extends BaseComponent {
_type: "DashboardValueComponent";
_type: ObjectType.DashboardValueComponent;
componentId: ObjectID;
}

View File

@ -1,6 +1,9 @@
import { ObjectType } from "../JSON";
import DashboardBaseComponent from "./DashboardComponents/DashboardBaseComponent";
export default interface DashboardViewConfig {
_type: "DashboardViewConfig";
_type: ObjectType.DashboardViewConfig;
components: Array<DashboardBaseComponent>;
heightInDashboardUnits: number;
widthInDashboardUnits: number;
}

View File

@ -66,6 +66,7 @@ export enum ObjectType {
Includes = "Includes",
// Dashboard Components.
DashboardViewConfig = "DashboardViewConfig",
DashboardTextComponent = "DashboardTextComponent",
DashboardValueComponent = "DashboardValueComponent",

View File

@ -1,12 +1,65 @@
import DashboardViewConfig from "../../Types/Dashboard/DashboardViewConfig";
import { ObjectType } from "../../Types/JSON";
import DashboardSize from "../../Types/Dashboard/DashboardSize";
import DashboardBaseComponent from "../../Types/Dashboard/DashboardComponents/DashboardBaseComponent";
export default class DashboardViewConfigUtil {
public static createDefaultDashboardViewConfig(): DashboardViewConfig {
return {
_type: "DashboardViewConfig",
_type: ObjectType.DashboardViewConfig,
components: [],
widthInDashboardUnits: DashboardSize.widthInDashboardUnits,
heightInDashboardUnits: DashboardSize.heightInDashboardUnits,
};
}
public static addDefaultChartComponent(): DashboardViewConfig {}
public static addComponentToDashboard(data: {
component: DashboardBaseComponent;
dashboardViewConfig: DashboardViewConfig;
}): DashboardViewConfig {
const heightOfComponent: number = data.component.heightInDashboardUnits;
// find the last row that has enough space to fit the component. If there is no such row, create a new row or rows to fit the component.
const allComponentsFromDashboard: Array<DashboardBaseComponent> =
data.dashboardViewConfig.components;
let componentTopPosition: number = 0;
let componentLeftPosition: number = 0;
// find the last row that has the component.
let lastRowThatHasComponent: number = 0;
for (const dashboardComponent of allComponentsFromDashboard) {
if (dashboardComponent.topInDashboardUnits < componentTopPosition) {
lastRowThatHasComponent = componentTopPosition;
}
}
componentTopPosition = lastRowThatHasComponent + 1;
// check height of the component. If it is bigger than the last row that has the component, create more rows and udate the height of dashboardViewConfig.
if (
componentTopPosition + heightOfComponent >
data.dashboardViewConfig.heightInDashboardUnits
) {
data.dashboardViewConfig.heightInDashboardUnits =
componentTopPosition + heightOfComponent;
}
// left position of the component is always 0.
componentLeftPosition = 0;
const newComponent: DashboardBaseComponent = {
...data.component,
topInDashboardUnits: componentTopPosition,
leftInDashboardUnits: componentLeftPosition,
};
// Add the new component to the dashboard configuration
data.dashboardViewConfig.components.push(newComponent);
return { ...data.dashboardViewConfig };
}
}

View File

@ -1,29 +1,34 @@
import React, { FunctionComponent, ReactElement } from "react";
import DefaultDashboardSize from "Common/Types/Dashboard/DashboardSize";
import BlankRowElement from "./BlankRow";
import DashboardViewConfig from "Common/Types/Dashboard/DashboardViewConfig";
export interface ComponentProps {
dashboardViewConfig: DashboardViewConfig;
onDrop: (top: number, left: number) => void;
}
const BlankCanvasElement: FunctionComponent<ComponentProps> = (
props: ComponentProps,
porps: ComponentProps,
): ReactElement => {
const defaultHeight: number = DefaultDashboardSize.heightInDashboardUnits;
const height: number =
porps.dashboardViewConfig.heightInDashboardUnits ||
DefaultDashboardSize.heightInDashboardUnits;
return (
<div className="">
{Array.from(Array(defaultHeight).keys()).map(
(_: number, index: number) => {
return (
<BlankRowElement
key={index}
rowNumber={index}
onDrop={props.onDrop}
/>
);
},
)}
{Array.from(Array(height).keys()).map((_: number, index: number) => {
return (
<BlankRowElement
dashboardViewConfig={porps.dashboardViewConfig}
key={index}
rowNumber={index}
onDrop={(top: number, left: number) => {
porps.onDrop(top, left);
}}
/>
);
})}
</div>
);
};

View File

@ -1,16 +1,20 @@
import React, { FunctionComponent, ReactElement } from "react";
import DefaultDashboardSize from "Common/Types/Dashboard/DashboardSize";
import DashboardUnitElement from "./DashboardUnit";
import DashboardViewConfig from "Common/Types/Dashboard/DashboardViewConfig";
export interface ComponentProps {
rowNumber: number;
onDrop: (top: number, left: number) => void;
dashboardViewConfig: DashboardViewConfig;
}
const BlankRowElement: FunctionComponent<ComponentProps> = (
props: ComponentProps,
): ReactElement => {
const defaultRowLength: number = DefaultDashboardSize.widthInDashboardUnits;
const defaultRowLength: number =
props.dashboardViewConfig.widthInDashboardUnits ||
DefaultDashboardSize.widthInDashboardUnits;
return (
<div className="flex">

View File

@ -1,12 +1,20 @@
import React, { FunctionComponent, ReactElement } from "react";
import BlankCanvasElement from "./BlankCanvas";
import DashboardViewConfig from "Common/Types/Dashboard/DashboardViewConfig";
export interface ComponentProps {}
export interface ComponentProps {
dashboardViewConfig: DashboardViewConfig;
onDashboardViewConfigChange: (newConfig: DashboardViewConfig) => void;
}
const DashboardCanvas: FunctionComponent<ComponentProps> = (
_props: ComponentProps,
props: ComponentProps,
): ReactElement => {
return <BlankCanvasElement onDrop={() => {}} />;
if (!props.dashboardViewConfig) {
return <BlankCanvasElement onDrop={() => {}} dashboardViewConfig={props.dashboardViewConfig} />;
}
return <BlankCanvasElement onDrop={() => {}} dashboardViewConfig={props.dashboardViewConfig} />;
};
export default DashboardCanvas;

View File

@ -1,18 +1,93 @@
import React, { FunctionComponent, ReactElement, useState } from "react";
import React, {
FunctionComponent,
ReactElement,
useEffect,
useState,
} from "react";
import DashboardToolbar from "./Toolbar/DashboardToolbar";
import DashboardCanvas from "./Canvas/Index";
import DashboardMode from "Common/Types/Dashboard/DashboardMode";
import DashboardComponentType from "Common/Types/Dashboard/DashboardComponentType";
import DashboardViewConfig from "Common/Types/Dashboard/DashboardViewConfig";
import { ObjectType } from "Common/Types/JSON";
import DashboardBaseComponent from "Common/Types/Dashboard/DashboardComponents/DashboardBaseComponent";
import DashboardChartComponentUtil from "Common/Utils/Dashboard/Components/DashboardChartComponent";
import DashboardValueComponentUtil from "Common/Utils/Dashboard/Components/DashboardValueComponent";
import DashboardTextComponentUtil from "Common/Utils/Dashboard/Components/DashboardTextComponent";
import BadDataException from "Common/Types/Exception/BadDataException";
import ObjectID from "Common/Types/ObjectID";
import Dashboard from "Common/Models/DatabaseModels/Dashboard";
import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI";
import API from "Common/UI/Utils/API/API";
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
import PageLoader from "Common/UI/Components/Loader/PageLoader";
import DashboardViewConfigUtil from "Common/Utils/Dashboard/DashboardViewConfig";
import DefaultDashboardSize from "Common/Types/Dashboard/DashboardSize";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
export interface ComponentProps {}
export interface ComponentProps {
dashboardId: ObjectID;
}
const DashboardViewer: FunctionComponent<ComponentProps> = (
_props: ComponentProps,
props: ComponentProps,
): ReactElement => {
const [dashboardMode, setDashboardMode] = useState<DashboardMode>(
DashboardMode.View,
);
const [dashboardViewConfig, setDashboardViewConfig] =
useState<DashboardViewConfig>({
_type: ObjectType.DashboardViewConfig,
components: [],
heightInDashboardUnits: DefaultDashboardSize.heightInDashboardUnits,
widthInDashboardUnits: DefaultDashboardSize.widthInDashboardUnits,
});
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const fetchDashboardViewConfig: PromiseVoidFunction = async (): Promise<void> => {
try {
setIsLoading(true);
const dashboard: Dashboard | null = await ModelAPI.getItem({
modelType: Dashboard,
id: props.dashboardId,
select: {
dashboardViewConfig: true,
name: true,
description: true,
},
});
if (!dashboard) {
setError("Dashboard not found");
return;
}
setDashboardViewConfig(dashboard.dashboardViewConfig!);
} catch (err) {
setError(API.getFriendlyErrorMessage(err as Error));
}
setIsLoading(false);
};
useEffect(() => {
// Fetch the dashboard view config from the server
fetchDashboardViewConfig().catch((err: Error) => {
setError(API.getFriendlyErrorMessage(err as Error));
});
}, []);
if (error) {
return <ErrorMessage error={error} />;
}
if (isLoading) {
return <PageLoader isVisible={true} />;
}
return (
<div>
<DashboardToolbar
@ -26,9 +101,42 @@ const DashboardViewer: FunctionComponent<ComponentProps> = (
onEditClick={() => {
setDashboardMode(DashboardMode.Edit);
}}
onAddComponentClick={(componentType: DashboardComponentType) => {}}
onAddComponentClick={(componentType: DashboardComponentType) => {
let newComponent: DashboardBaseComponent | null = null;
if (componentType === DashboardComponentType.Chart) {
newComponent = DashboardChartComponentUtil.getDefaultComponent();
}
if (componentType === DashboardComponentType.Value) {
newComponent = DashboardValueComponentUtil.getDefaultComponent();
}
if (componentType === DashboardComponentType.Text) {
newComponent = DashboardTextComponentUtil.getDefaultComponent();
}
if (!newComponent) {
throw new BadDataException(
`Unknown component type: ${componentType}`,
);
}
const newDashboardConfig: DashboardViewConfig =
DashboardViewConfigUtil.addComponentToDashboard({
component: newComponent,
dashboardViewConfig: dashboardViewConfig,
});
setDashboardViewConfig(newDashboardConfig);
}}
/>
<DashboardCanvas
dashboardViewConfig={dashboardViewConfig}
onDashboardViewConfigChange={(newConfig: DashboardViewConfig) => {
setDashboardViewConfig(newConfig);
}}
/>
<DashboardCanvas />
</div>
);
};

View File

@ -118,7 +118,7 @@ const DashboardView: FunctionComponent<
}}
/>
<DashboardViewer />
<DashboardViewer dashboardId={modelId} />
</Fragment>
);
};