diff --git a/src/app/(main)/boards/[boardId]/BoardColumn.module.css b/src/app/(main)/boards/[boardId]/BoardColumn.module.css
new file mode 100644
index 000000000..895e62058
--- /dev/null
+++ b/src/app/(main)/boards/[boardId]/BoardColumn.module.css
@@ -0,0 +1,17 @@
+.column {
+ position: relative;
+}
+
+.columnAction {
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity 120ms ease;
+}
+
+.column:hover .columnAction,
+.column:focus-within .columnAction {
+ opacity: 1;
+ visibility: visible;
+ pointer-events: auto;
+}
diff --git a/src/app/(main)/boards/[boardId]/BoardColumn.tsx b/src/app/(main)/boards/[boardId]/BoardColumn.tsx
index 8be9c02da..0db07b2cc 100644
--- a/src/app/(main)/boards/[boardId]/BoardColumn.tsx
+++ b/src/app/(main)/boards/[boardId]/BoardColumn.tsx
@@ -8,10 +8,11 @@ import {
Tooltip,
TooltipTrigger,
} from '@umami/react-zen';
-import { useState } from 'react';
+import { useMemo, useState } from 'react';
import { useBoard, useMessages } from '@/components/hooks';
import { Pencil, Plus, X } from '@/components/icons';
import type { BoardComponentConfig } from '@/lib/types';
+import styles from './BoardColumn.module.css';
import { BoardComponentRenderer } from './BoardComponentRenderer';
import { BoardComponentSelect } from './BoardComponentSelect';
@@ -34,6 +35,13 @@ export function BoardColumn({
const { board } = useBoard();
const { t, labels } = useMessages();
const websiteId = board?.parameters?.websiteId;
+ const renderedComponent = useMemo(() => {
+ if (!component || !websiteId) {
+ return null;
+ }
+
+ return ;
+ }, [component, websiteId]);
const handleSelect = (config: BoardComponentConfig) => {
onSetComponent?.(id, config);
@@ -50,11 +58,18 @@ export function BoardColumn({
justifyContent="center"
backgroundColor="surface-sunken"
position="relative"
+ className={styles.column}
>
{editing && canRemove && (
-
+
-
)}
- {component && websiteId ? (
+ {renderedComponent ? (
<>
-
+ {renderedComponent}
{editing && (
-
+
- setShowSelect(true)}>
+ setShowSelect(true)}>
@@ -94,7 +115,7 @@ export function BoardColumn({
;
}
+
+export const BoardComponentRenderer = memo(
+ BoardComponentRendererComponent,
+ (prevProps, nextProps) =>
+ prevProps.websiteId === nextProps.websiteId && prevProps.config === nextProps.config,
+);
+
+BoardComponentRenderer.displayName = 'BoardComponentRenderer';
diff --git a/src/components/charts/BarChart.tsx b/src/components/charts/BarChart.tsx
index 7bfc72d21..92d34db43 100644
--- a/src/components/charts/BarChart.tsx
+++ b/src/components/charts/BarChart.tsx
@@ -1,5 +1,5 @@
import { useTheme } from '@umami/react-zen';
-import { useMemo, useState } from 'react';
+import { memo, useCallback, useMemo, useState } from 'react';
import { Chart, type ChartProps } from '@/components/charts/Chart';
import { ChartTooltip } from '@/components/charts/ChartTooltip';
import { useLocale } from '@/components/hooks';
@@ -8,6 +8,8 @@ import { getThemeColors } from '@/lib/colors';
import { DATE_FORMATS, formatDate } from '@/lib/date';
import { formatLongCurrency, formatLongNumber } from '@/lib/format';
+const MemoChart = memo(Chart);
+
const dateFormats = {
millisecond: 'T',
second: 'pp',
@@ -32,7 +34,13 @@ export interface BarChartProps extends ChartProps {
maxDate?: Date;
}
-export function BarChart({
+interface TooltipState {
+ title: string;
+ color?: string;
+ value: string;
+}
+
+function BarChartComponent({
chartData,
renderXLabel,
renderYLabel,
@@ -45,7 +53,7 @@ export function BarChart({
currency,
...props
}: BarChartProps) {
- const [tooltip, setTooltip] = useState(null);
+ const [tooltip, setTooltip] = useState(null);
const { theme } = useTheme();
const { locale } = useLocale();
const { colors } = useMemo(() => getThemeColors(theme), [theme]);
@@ -94,13 +102,23 @@ export function BarChart({
},
},
};
- }, [chartData, colors, unit, stacked, renderXLabel, renderYLabel]);
+ }, [
+ colors,
+ unit,
+ stacked,
+ renderXLabel,
+ renderYLabel,
+ minDate,
+ maxDate,
+ locale,
+ XAxisType,
+ YAxisType,
+ ]);
- const handleTooltip = ({ tooltip }: { tooltip: any }) => {
- const { opacity, labelColors, dataPoints } = tooltip;
-
- setTooltip(
- opacity
+ const handleTooltip = useCallback(
+ ({ tooltip }: { tooltip: any }) => {
+ const { opacity, labelColors, dataPoints } = tooltip;
+ const nextTooltip = opacity
? {
title: formatDate(
new Date(dataPoints[0].raw?.d || dataPoints[0].raw?.x || dataPoints[0].raw),
@@ -112,13 +130,26 @@ export function BarChart({
? formatLongCurrency(dataPoints[0].raw.y, currency)
: `${formatLongNumber(dataPoints[0].raw.y)} ${dataPoints[0].dataset.label}`,
}
- : null,
- );
- };
+ : null;
+
+ setTooltip(prev => {
+ if (
+ prev?.title === nextTooltip?.title &&
+ prev?.color === nextTooltip?.color &&
+ prev?.value === nextTooltip?.value
+ ) {
+ return prev;
+ }
+
+ return nextTooltip;
+ });
+ },
+ [currency, locale, unit],
+ );
return (
<>
-
);
}
+
+export const BarChart = memo(BarChartComponent);
+
+BarChart.displayName = 'BarChart';