mirror of
https://github.com/makeplane/plane.git
synced 2026-02-25 04:35:21 +01:00
refactor: dashboards components to resolve char related bugfixes (#2676)
This commit is contained in:
committed by
GitHub
parent
78aea5a9ba
commit
3a249cfdbb
@@ -361,3 +361,8 @@ export const TEXT_WIDGET_Y_AXIS_METRICS_LIST: EWidgetYAxisMetric[] = [
|
||||
EWidgetYAxisMetric.WORK_ITEM_DUE_THIS_WEEK_COUNT,
|
||||
EWidgetYAxisMetric.WORK_ITEM_DUE_TODAY_COUNT,
|
||||
];
|
||||
|
||||
export const TO_CAPITALIZE_PROPERTIES: EWidgetXAxisProperty[] = [
|
||||
EWidgetXAxisProperty.PRIORITY,
|
||||
EWidgetXAxisProperty.STATE_GROUPS,
|
||||
];
|
||||
|
||||
@@ -998,7 +998,7 @@
|
||||
"common": {
|
||||
"add_widget": "Add widget",
|
||||
"widget_title": {
|
||||
"label": "Widget title",
|
||||
"label": "Name this widget",
|
||||
"placeholder": "e.g., \"To-do yesterday\", \"All Complete\""
|
||||
},
|
||||
"chart_type": "Chart type",
|
||||
|
||||
@@ -19,6 +19,7 @@ export const PieChart = React.memo(<K extends string, T extends string>(props: T
|
||||
outerRadius,
|
||||
showTooltip = true,
|
||||
showLabel,
|
||||
customLabel,
|
||||
centerLabel,
|
||||
} = props;
|
||||
|
||||
@@ -56,7 +57,30 @@ export const PieChart = React.memo(<K extends string, T extends string>(props: T
|
||||
cy="50%"
|
||||
innerRadius={innerRadius}
|
||||
outerRadius={outerRadius}
|
||||
label={!!showLabel}
|
||||
label={
|
||||
showLabel
|
||||
? (payload) => {
|
||||
const { cx, cy, fill, innerRadius, midAngle, outerRadius, value } = payload;
|
||||
const RADIAN = Math.PI / 180;
|
||||
const radius = 25 + innerRadius + (outerRadius - innerRadius);
|
||||
const x = cx + radius * Math.cos(-midAngle * RADIAN);
|
||||
const y = cy + radius * Math.sin(-midAngle * RADIAN);
|
||||
|
||||
return (
|
||||
<text
|
||||
className="text-xs font-medium"
|
||||
x={x}
|
||||
y={y}
|
||||
fill={fill}
|
||||
textAnchor={x > cx ? "start" : "end"}
|
||||
dominantBaseline="central"
|
||||
>
|
||||
{customLabel?.(value) ?? value}
|
||||
</text>
|
||||
);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{renderCells}
|
||||
{centerLabel && (
|
||||
|
||||
1
packages/types/src/charts.d.ts
vendored
1
packages/types/src/charts.d.ts
vendored
@@ -105,6 +105,7 @@ export type TPieChartProps<K extends string, T extends string> = Pick<
|
||||
innerRadius?: number;
|
||||
outerRadius?: number;
|
||||
showLabel: boolean;
|
||||
customLabel?: (value: any) => string;
|
||||
centerLabel?: {
|
||||
className?: string;
|
||||
fill: string;
|
||||
|
||||
@@ -28,7 +28,8 @@ export const DashboardsWidgetsGridRoot: React.FC<Props> = observer((props) => {
|
||||
const { getDashboardById } = useDashboards();
|
||||
// derived values
|
||||
const dashboardDetails = getDashboardById(dashboardId);
|
||||
const { allWidgetIds, layoutItems, updateWidgetsLayout } = dashboardDetails?.widgetsStore ?? {};
|
||||
const { canCurrentUserEditDashboard, isViewModeEnabled, widgetsStore } = dashboardDetails ?? {};
|
||||
const { allWidgetIds, layoutItems, updateWidgetsLayout } = widgetsStore ?? {};
|
||||
|
||||
const handleLayoutChange = useCallback(
|
||||
async (_: Layout[], allLayouts: Layouts) => {
|
||||
@@ -71,8 +72,8 @@ export const DashboardsWidgetsGridRoot: React.FC<Props> = observer((props) => {
|
||||
margin={[32, 32]}
|
||||
containerPadding={[0, 0]}
|
||||
draggableHandle=".widget-drag-handle"
|
||||
isDraggable
|
||||
isResizable
|
||||
isDraggable={!isViewModeEnabled && canCurrentUserEditDashboard && activeBreakpoint === EWidgetGridBreakpoints.MD}
|
||||
isResizable={!isViewModeEnabled && canCurrentUserEditDashboard && activeBreakpoint === EWidgetGridBreakpoints.MD}
|
||||
onBreakpointChange={handleBreakpointChange}
|
||||
onLayoutChange={handleLayoutChange}
|
||||
>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTheme } from "next-themes";
|
||||
import { CHART_COLOR_PALETTES, DEFAULT_WIDGET_COLOR, EWidgetChartModels } from "@plane/constants";
|
||||
import { TAreaChartWidgetConfig, TAreaItem } from "@plane/types";
|
||||
// local imports
|
||||
import { parseWidgetData, generateExtendedColors, TWidgetComponentProps } from ".";
|
||||
import { generateExtendedColors, TWidgetComponentProps } from ".";
|
||||
|
||||
const AreaChart = dynamic(() =>
|
||||
import("@plane/propel/charts/area-chart").then((mod) => ({
|
||||
@@ -15,13 +15,12 @@ const AreaChart = dynamic(() =>
|
||||
);
|
||||
|
||||
export const DashboardAreaChartWidget: React.FC<TWidgetComponentProps> = observer((props) => {
|
||||
const { widget } = props;
|
||||
const { parsedData, widget } = props;
|
||||
// derived values
|
||||
const { chart_model, data } = widget ?? {};
|
||||
const { chart_model } = widget ?? {};
|
||||
const widgetConfig = widget?.config as TAreaChartWidgetConfig | undefined;
|
||||
const showLegends = !!widgetConfig?.show_legends;
|
||||
const isComparisonModel = chart_model === EWidgetChartModels.COMPARISON;
|
||||
const parsedData = parseWidgetData(data);
|
||||
// next-themes
|
||||
const { resolvedTheme } = useTheme();
|
||||
// Get current palette colors and extend if needed
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTheme } from "next-themes";
|
||||
import { CHART_COLOR_PALETTES, DEFAULT_WIDGET_COLOR, EWidgetChartModels } from "@plane/constants";
|
||||
import { TBarChartWidgetConfig, TBarItem } from "@plane/types";
|
||||
// local imports
|
||||
import { generateExtendedColors, parseWidgetData, TWidgetComponentProps } from ".";
|
||||
import { generateExtendedColors, TWidgetComponentProps } from ".";
|
||||
|
||||
const BarChart = dynamic(() =>
|
||||
import("@plane/propel/charts/bar-chart").then((mod) => ({
|
||||
@@ -15,12 +15,11 @@ const BarChart = dynamic(() =>
|
||||
);
|
||||
|
||||
export const DashboardBarChartWidget: React.FC<TWidgetComponentProps> = observer((props) => {
|
||||
const { widget } = props;
|
||||
const { parsedData, widget } = props;
|
||||
// derived values
|
||||
const { chart_model, data } = widget ?? {};
|
||||
const { chart_model } = widget ?? {};
|
||||
const widgetConfig = widget?.config as TBarChartWidgetConfig | undefined;
|
||||
const showLegends = !!widgetConfig?.show_legends;
|
||||
const parsedData = parseWidgetData(data);
|
||||
// next-themes
|
||||
const { resolvedTheme } = useTheme();
|
||||
// Get current palette colors and extend if needed
|
||||
|
||||
@@ -41,7 +41,7 @@ export const parseDonutChartData = (
|
||||
};
|
||||
|
||||
export const DashboardDonutChartWidget: React.FC<TWidgetComponentProps> = observer((props) => {
|
||||
const { widget } = props;
|
||||
const { parsedData, widget } = props;
|
||||
// derived values
|
||||
const { chart_model, data, height, width } = widget ?? {};
|
||||
const widgetConfig = widget?.config as TDonutChartWidgetConfig | undefined;
|
||||
@@ -50,7 +50,10 @@ export const DashboardDonutChartWidget: React.FC<TWidgetComponentProps> = observ
|
||||
const showLegends = !!widgetConfig?.show_legends && !isOfUnitHeight;
|
||||
const legendPosition = (width ?? 1) >= (height ?? 1) ? "right" : "bottom";
|
||||
const showCenterLabel = !!widgetConfig?.center_value;
|
||||
const parsedData = useMemo(() => parseDonutChartData(data?.data, chart_model), [chart_model, data?.data]);
|
||||
const donutParsedData = useMemo(() => {
|
||||
const secondParse = parseDonutChartData(parsedData.data, chart_model);
|
||||
return secondParse;
|
||||
}, [chart_model, parsedData]);
|
||||
const totalCount = data?.data?.reduce((acc, curr) => acc + curr.count, 0);
|
||||
const totalCountDigits = totalCount?.toString().length ?? 1;
|
||||
// next-themes
|
||||
@@ -62,10 +65,10 @@ export const DashboardDonutChartWidget: React.FC<TWidgetComponentProps> = observ
|
||||
|
||||
const cells: TCellItem<string>[] = useMemo(() => {
|
||||
let parsedCells: TCellItem<string>[];
|
||||
const extendedColors = generateExtendedColors(baseColors ?? [], parsedData.length);
|
||||
const extendedColors = generateExtendedColors(baseColors ?? [], donutParsedData.length);
|
||||
|
||||
if (chart_model === EWidgetChartModels.BASIC) {
|
||||
parsedCells = parsedData.map((datum, index) => ({
|
||||
parsedCells = donutParsedData.map((datum, index) => ({
|
||||
key: datum.key,
|
||||
className: "stroke-transparent",
|
||||
fill: extendedColors[index],
|
||||
@@ -87,7 +90,7 @@ export const DashboardDonutChartWidget: React.FC<TWidgetComponentProps> = observ
|
||||
parsedCells = [];
|
||||
}
|
||||
return parsedCells;
|
||||
}, [baseColors, chart_model, parsedData, resolvedTheme, widgetConfig]);
|
||||
}, [baseColors, chart_model, donutParsedData, resolvedTheme, widgetConfig]);
|
||||
|
||||
if (!widget) return null;
|
||||
|
||||
@@ -100,7 +103,7 @@ export const DashboardDonutChartWidget: React.FC<TWidgetComponentProps> = observ
|
||||
bottom: isOfUnitHeight ? 12 : 20,
|
||||
left: 16,
|
||||
}}
|
||||
data={parsedData}
|
||||
data={donutParsedData}
|
||||
dataKey="count"
|
||||
cells={cells}
|
||||
innerRadius={isOfUnitHeight ? 10 : (height ?? 1) * 20}
|
||||
|
||||
@@ -84,16 +84,17 @@ export const DashboardWidgetHeader: React.FC<Props> = observer((props) => {
|
||||
<h5 className="text-sm font-medium text-custom-text-200 truncate">{widget.name}</h5>
|
||||
</div>
|
||||
<div className="flex-shrink-0 hidden group-hover/widget:flex items-center">
|
||||
{!isViewModeEnabled && (
|
||||
{!isViewModeEnabled && canCurrentUserEditWidget && (
|
||||
<Tooltip tooltipContent="Edit">
|
||||
<button
|
||||
type="button"
|
||||
className="grid place-items-center p-1 rounded text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80"
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!widget.id) return;
|
||||
toggleEditWidget?.(widget.id);
|
||||
}}
|
||||
disabled={!canCurrentUserEditWidget}
|
||||
>
|
||||
<Pencil className="size-3.5" />
|
||||
</button>
|
||||
@@ -103,7 +104,11 @@ export const DashboardWidgetHeader: React.FC<Props> = observer((props) => {
|
||||
<button
|
||||
type="button"
|
||||
className="grid place-items-center p-1 rounded text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80"
|
||||
onClick={fetchWidgetData}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
fetchWidgetData();
|
||||
}}
|
||||
disabled={isFetchingData}
|
||||
>
|
||||
<RotateCw
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
export * from "./root";
|
||||
|
||||
// plane imports
|
||||
import { EWidgetXAxisProperty, TO_CAPITALIZE_PROPERTIES } from "@plane/constants";
|
||||
import { TDashboardWidgetData, TDashboardWidgetDatum } from "@plane/types";
|
||||
import { cn, hexToHsl, hslToHex } from "@plane/utils";
|
||||
import { capitalizeFirstLetter, cn, hexToHsl, hslToHex } from "@plane/utils";
|
||||
// plane web store
|
||||
import { DashboardWidgetInstance } from "@/plane-web/store/dashboards/widget";
|
||||
|
||||
export type TWidgetComponentProps = {
|
||||
parsedData: TDashboardWidgetData;
|
||||
widget: DashboardWidgetInstance | undefined;
|
||||
};
|
||||
|
||||
type TArgs = {
|
||||
className?: string;
|
||||
isEditingEnabled: boolean;
|
||||
isSelected: boolean;
|
||||
isResizingDisabled: boolean;
|
||||
};
|
||||
|
||||
export const WIDGET_HEADER_HEIGHT = 36;
|
||||
export const WIDGET_Y_SPACING = 8;
|
||||
|
||||
export const commonWidgetClassName = (args: TArgs) => {
|
||||
const { className, isSelected } = args;
|
||||
const { className, isEditingEnabled, isSelected, isResizingDisabled } = args;
|
||||
|
||||
const commonClassName = cn(
|
||||
"group/widget dashboard-widget-item size-full rounded-lg bg-custom-background-100 border border-custom-border-200 transition-colors",
|
||||
{
|
||||
"selected border-custom-primary-100": isSelected,
|
||||
"cursor-pointer": isEditingEnabled,
|
||||
disabled: isResizingDisabled,
|
||||
},
|
||||
className
|
||||
);
|
||||
@@ -93,7 +99,11 @@ export const generateExtendedColors = (baseColorSet: string[], targetCount: numb
|
||||
return colors.slice(0, targetCount);
|
||||
};
|
||||
|
||||
export const parseWidgetData = (data: TDashboardWidgetData | null | undefined): TDashboardWidgetData => {
|
||||
export const parseWidgetData = (
|
||||
data: TDashboardWidgetData | null | undefined,
|
||||
xAxisProperty: EWidgetXAxisProperty | null | undefined,
|
||||
groupByProperty: EWidgetXAxisProperty | null | undefined
|
||||
): TDashboardWidgetData => {
|
||||
if (!data) {
|
||||
return {
|
||||
data: [],
|
||||
@@ -106,13 +116,28 @@ export const parseWidgetData = (data: TDashboardWidgetData | null | undefined):
|
||||
const keys = Object.keys(datum);
|
||||
const missingKeys = allKeys.filter((key) => !keys.includes(key));
|
||||
const missingValues: Record<string, number> = missingKeys.reduce((acc, key) => ({ ...acc, [key]: 0 }), {});
|
||||
|
||||
// capitalize first letter if xAxisProperty is PRIORITY or STATE_GROUPS and no groupByProperty is set
|
||||
if (!groupByProperty && xAxisProperty && TO_CAPITALIZE_PROPERTIES.includes(xAxisProperty)) {
|
||||
datum.name = capitalizeFirstLetter(datum.name);
|
||||
}
|
||||
|
||||
return {
|
||||
...datum,
|
||||
...missingValues,
|
||||
};
|
||||
});
|
||||
|
||||
// capitalize first letter if groupByProperty is PRIORITY or STATE_GROUPS
|
||||
const updatedSchema = schema;
|
||||
if (groupByProperty && TO_CAPITALIZE_PROPERTIES.includes(groupByProperty)) {
|
||||
Object.keys(updatedSchema).forEach((key) => {
|
||||
updatedSchema[key] = capitalizeFirstLetter(updatedSchema[key]);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
data: updatedWidgetData,
|
||||
schema,
|
||||
schema: updatedSchema,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTheme } from "next-themes";
|
||||
import { CHART_COLOR_PALETTES, DEFAULT_WIDGET_COLOR, EWidgetChartModels } from "@plane/constants";
|
||||
import { TLineChartWidgetConfig, TLineItem } from "@plane/types";
|
||||
// local imports
|
||||
import { parseWidgetData, generateExtendedColors, TWidgetComponentProps } from ".";
|
||||
import { generateExtendedColors, TWidgetComponentProps } from ".";
|
||||
|
||||
const LineChart = dynamic(() =>
|
||||
import("@plane/propel/charts/line-chart").then((mod) => ({
|
||||
@@ -15,12 +15,11 @@ const LineChart = dynamic(() =>
|
||||
);
|
||||
|
||||
export const DashboardLineChartWidget: React.FC<TWidgetComponentProps> = observer((props) => {
|
||||
const { widget } = props;
|
||||
const { parsedData, widget } = props;
|
||||
// derived values
|
||||
const { chart_model, data } = widget ?? {};
|
||||
const { chart_model } = widget ?? {};
|
||||
const widgetConfig = widget?.config as TLineChartWidgetConfig | undefined;
|
||||
const showLegends = !!widgetConfig?.show_legends;
|
||||
const parsedData = parseWidgetData(data);
|
||||
// next-themes
|
||||
const { resolvedTheme } = useTheme();
|
||||
// Get current palette colors and extend if needed
|
||||
|
||||
@@ -57,14 +57,17 @@ const parsePieChartData = (
|
||||
};
|
||||
|
||||
export const DashboardPieChartWidget: React.FC<TWidgetComponentProps> = observer((props) => {
|
||||
const { widget } = props;
|
||||
const { parsedData, widget } = props;
|
||||
// derived values
|
||||
const { data, height, width } = widget ?? {};
|
||||
const { height, width } = widget ?? {};
|
||||
const widgetConfig = widget?.config as TPieChartWidgetConfig | undefined;
|
||||
const showLabels = !!widgetConfig?.show_values && height !== 1;
|
||||
const showLegends = !!widgetConfig?.show_legends;
|
||||
const legendPosition = (width ?? 1) >= (height ?? 1) ? "right" : "bottom";
|
||||
const parsedData = useMemo(() => parsePieChartData(data?.data, widgetConfig), [data?.data, widgetConfig]);
|
||||
const pieParsedData = useMemo(() => {
|
||||
const secondParse = parsePieChartData(parsedData.data, widgetConfig);
|
||||
return secondParse;
|
||||
}, [parsedData, widgetConfig]);
|
||||
// next-themes
|
||||
const { resolvedTheme } = useTheme();
|
||||
// Get current palette colors and extend if needed
|
||||
@@ -73,8 +76,8 @@ export const DashboardPieChartWidget: React.FC<TWidgetComponentProps> = observer
|
||||
];
|
||||
|
||||
const cells: TCellItem<string>[] = useMemo(() => {
|
||||
const extendedColors = generateExtendedColors(baseColors ?? [], parsedData.length);
|
||||
const parsedCells = parsedData.map((datum, index) => ({
|
||||
const extendedColors = generateExtendedColors(baseColors ?? [], pieParsedData.length);
|
||||
const parsedCells = pieParsedData.map((datum, index) => ({
|
||||
key: datum.key,
|
||||
className: "stroke-transparent",
|
||||
fill: extendedColors[index],
|
||||
@@ -83,7 +86,7 @@ export const DashboardPieChartWidget: React.FC<TWidgetComponentProps> = observer
|
||||
if (widgetConfig?.group_thin_pieces) {
|
||||
for (let i = 0; i < parsedCells.length; i++) {
|
||||
const cellKey = parsedCells[i].key;
|
||||
const doesKeyExist = parsedData.find((datum) => datum.key === cellKey);
|
||||
const doesKeyExist = pieParsedData.find((datum) => datum.key === cellKey);
|
||||
if (!doesKeyExist) {
|
||||
parsedCells.splice(i, 1);
|
||||
}
|
||||
@@ -97,7 +100,7 @@ export const DashboardPieChartWidget: React.FC<TWidgetComponentProps> = observer
|
||||
}
|
||||
}
|
||||
return parsedCells;
|
||||
}, [baseColors, parsedData, widgetConfig]);
|
||||
}, [baseColors, pieParsedData, widgetConfig]);
|
||||
|
||||
if (!widget) return null;
|
||||
|
||||
@@ -110,7 +113,7 @@ export const DashboardPieChartWidget: React.FC<TWidgetComponentProps> = observer
|
||||
bottom: 20,
|
||||
left: 16,
|
||||
}}
|
||||
data={parsedData}
|
||||
data={pieParsedData}
|
||||
dataKey="count"
|
||||
cells={cells}
|
||||
legend={
|
||||
@@ -125,6 +128,12 @@ export const DashboardPieChartWidget: React.FC<TWidgetComponentProps> = observer
|
||||
}
|
||||
showTooltip={!!widgetConfig?.show_tooltip}
|
||||
showLabel={showLabels}
|
||||
customLabel={(val) => {
|
||||
if (widgetConfig?.value_type === "percentage") {
|
||||
return `${val}%`;
|
||||
}
|
||||
return val;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useRef } from "react";
|
||||
import { useMemo, useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
@@ -15,7 +15,7 @@ import { DashboardWidgetHeader } from "./header";
|
||||
import { DashboardLineChartWidget } from "./line-chart";
|
||||
import { DashboardPieChartWidget } from "./pie-chart";
|
||||
import { DashboardTextWidget } from "./text";
|
||||
import { commonWidgetClassName, TWidgetComponentProps } from "./";
|
||||
import { commonWidgetClassName, parseWidgetData, TWidgetComponentProps } from "./";
|
||||
|
||||
type Props = {
|
||||
activeBreakpoint: EWidgetGridBreakpoints;
|
||||
@@ -31,13 +31,22 @@ export const DashboardWidgetRoot: React.FC<Props> = observer((props) => {
|
||||
const { getDashboardById } = useDashboards();
|
||||
// derived values
|
||||
const dashboardDetails = getDashboardById(dashboardId);
|
||||
const { getWidgetById, isEditingWidget } = dashboardDetails?.widgetsStore ?? {};
|
||||
const { isViewModeEnabled, widgetsStore } = dashboardDetails ?? {};
|
||||
const { getWidgetById, isEditingWidget, toggleEditWidget } = widgetsStore ?? {};
|
||||
const widget = getWidgetById?.(widgetId);
|
||||
const { chart_type, data, fetchWidgetData, isConfigurationMissing } = widget ?? {};
|
||||
const {
|
||||
canCurrentUserEditWidget,
|
||||
chart_type,
|
||||
data,
|
||||
fetchWidgetData,
|
||||
isConfigurationMissing,
|
||||
x_axis_property,
|
||||
group_by,
|
||||
} = widget ?? {};
|
||||
const isWidgetSelected = isEditingWidget === widgetId;
|
||||
const isWidgetConfigured = !isConfigurationMissing;
|
||||
|
||||
console.log("Re-rendering widget root");
|
||||
const isEditingEnabled = !isViewModeEnabled && !!canCurrentUserEditWidget;
|
||||
const parsedData = useMemo(() => parseWidgetData(data, x_axis_property, group_by), [data, group_by, x_axis_property]);
|
||||
|
||||
useSWR(
|
||||
isWidgetConfigured && widgetId ? `WIDGET_DATA_${widgetId}` : null,
|
||||
@@ -84,8 +93,15 @@ export const DashboardWidgetRoot: React.FC<Props> = observer((props) => {
|
||||
<div
|
||||
ref={widgetRef}
|
||||
className={commonWidgetClassName({
|
||||
isEditingEnabled,
|
||||
isSelected: isWidgetSelected,
|
||||
isResizingDisabled: !isEditingEnabled || activeBreakpoint === EWidgetGridBreakpoints.XXS,
|
||||
})}
|
||||
onClick={() => {
|
||||
if (!isEditingEnabled || isEditingWidget === widgetId) return;
|
||||
toggleEditWidget?.(widgetId);
|
||||
}}
|
||||
role={isEditingEnabled ? "button" : "none"}
|
||||
>
|
||||
<DashboardWidgetHeader dashboardId={dashboardId} widget={widget} widgetRef={widgetRef} />
|
||||
<DashboardWidgetContent
|
||||
@@ -95,7 +111,7 @@ export const DashboardWidgetRoot: React.FC<Props> = observer((props) => {
|
||||
isDataEmpty={data?.data.length === 0}
|
||||
widget={widget}
|
||||
>
|
||||
<WidgetComponent widget={widget} />
|
||||
<WidgetComponent parsedData={parsedData} widget={widget} />
|
||||
</DashboardWidgetContent>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -171,27 +171,44 @@ export class DashboardWidgetsStore implements IDashboardWidgetsStore {
|
||||
});
|
||||
|
||||
get layoutItems() {
|
||||
const widgetDetails = (this.allWidgetIds ?? []).map((widgetId) => {
|
||||
const details = this.getWidgetById?.(widgetId);
|
||||
return {
|
||||
id: widgetId,
|
||||
x: details?.x_axis_coord ?? 0,
|
||||
y: details?.y_axis_coord ?? 0,
|
||||
width: details?.width ?? 1,
|
||||
height: details?.height ?? 1,
|
||||
};
|
||||
});
|
||||
|
||||
// sort widgets by y-axis first, then x-axis for XXS layout
|
||||
const sortedWidgets = [...widgetDetails].sort((a, b) => {
|
||||
if (a.y !== b.y) {
|
||||
return a.y - b.y; // primary sort by y position
|
||||
}
|
||||
return a.x - b.x; // secondary sort by x position when y is the same
|
||||
});
|
||||
|
||||
const layouts: Layouts = {
|
||||
[EWidgetGridBreakpoints.XXS]: (this.allWidgetIds ?? []).map((widgetId, index) => ({
|
||||
i: widgetId,
|
||||
[EWidgetGridBreakpoints.XXS]: sortedWidgets.map((widget, index) => ({
|
||||
i: widget.id,
|
||||
x: 0,
|
||||
y: index * 2,
|
||||
w: 1,
|
||||
h: 2,
|
||||
resizeHandles: [],
|
||||
})),
|
||||
[EWidgetGridBreakpoints.MD]: (this.allWidgetIds ?? []).map((widgetId) => {
|
||||
const widgetDetails = this.getWidgetById?.(widgetId);
|
||||
return {
|
||||
i: widgetId,
|
||||
x: widgetDetails?.x_axis_coord ?? 0,
|
||||
y: widgetDetails?.y_axis_coord ?? 0,
|
||||
w: widgetDetails?.width ?? 1,
|
||||
h: widgetDetails?.height ?? 1,
|
||||
resizeHandles: ["nw", "ne", "se", "sw"],
|
||||
};
|
||||
}),
|
||||
[EWidgetGridBreakpoints.MD]: widgetDetails.map((widget) => ({
|
||||
i: widget.id,
|
||||
x: widget.x,
|
||||
y: widget.y,
|
||||
w: widget.width,
|
||||
h: widget.height,
|
||||
resizeHandles: ["nw", "ne", "se", "sw"],
|
||||
})),
|
||||
};
|
||||
|
||||
return layouts;
|
||||
}
|
||||
|
||||
|
||||
@@ -948,26 +948,26 @@ html.todesktop .header button {
|
||||
transition: opacity 0.2s ease;
|
||||
|
||||
&.react-resizable-handle-nw {
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
top: 2px !important;
|
||||
left: 2px !important;
|
||||
transform: translate(-50%, -50%) !important;
|
||||
cursor: nwse-resize !important;
|
||||
}
|
||||
&.react-resizable-handle-ne {
|
||||
top: 0 !important;
|
||||
right: 0 !important;
|
||||
top: 2px !important;
|
||||
right: 2px !important;
|
||||
transform: translate(50%, -50%) !important;
|
||||
cursor: nesw-resize !important;
|
||||
}
|
||||
&.react-resizable-handle-se {
|
||||
bottom: 0 !important;
|
||||
right: 0 !important;
|
||||
bottom: 2px !important;
|
||||
right: 2px !important;
|
||||
transform: translate(50%, 50%) !important;
|
||||
cursor: nwse-resize !important;
|
||||
}
|
||||
&.react-resizable-handle-sw {
|
||||
bottom: 0 !important;
|
||||
left: 0 !important;
|
||||
bottom: 2px !important;
|
||||
left: 2px !important;
|
||||
transform: translate(-50%, 50%) !important;
|
||||
cursor: nesw-resize !important;
|
||||
}
|
||||
@@ -983,7 +983,7 @@ html.todesktop .header button {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
&:not(.resizing) .dashboard-widget-item:not(.selected) {
|
||||
&:not(.resizing) .dashboard-widget-item:not(.selected):not(.disabled) {
|
||||
border-color: rgba(var(--color-border-400)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user