From fea2b8736fef4bbdfd095773ffab5e5e1351da45 Mon Sep 17 00:00:00 2001
From: "Abdullah." <125115953+mabdullahabaid@users.noreply.github.com>
Date: Fri, 26 Jun 2026 20:57:21 +0500
Subject: [PATCH] feat(website): rebuild dashboard visual faithful to
twenty-front (#22218)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Rebuilds the product-feature **DashboardVisual** to mirror
twenty-front's dashboard widgets, with colors traced to twenty-front's
actual source rather than eyeballed.
## Widgets
- **Bar — "Deals by month"**: single-series `blue8` (twenty-front's
`GRAPH_DEFAULT_COLOR`), dashed `4 4` gridlines, value labels, nice
rounded Y-ticks. Replaces the old stacked bar (stacked bars aren't used
in twenty-front).
- **Donut — "Deals by stage"**: the real opportunity pipeline
(New/Screening/Meeting/Proposal/Customer), each segment colored by that
stage option's own color from the metadata
(`red/purple/sky/turquoise/yellow`), with a center total and a paginated
horizontal legend.
- **KPIs**: big-number cards (Revenue YTD / Avg deal size / Win rate).
## Responsive — `mediaUp('md')`
The dashboard is the full-width spotlight tile, whose frame is short
below md and grows to 420px at md+. So the layout keys off md: below it
collapses to **2-up KPIs + a full-width bar** (donut and the 3rd KPI
hidden, smaller breadcrumb), and the spotlight frame's mobile min-height
is bumped so the bar has room; at md+ the full 3-KPI + side-by-side
layout returns. The donut caps at its size and shrinks with its
container.
## Notes
- Follows the `product-feature` conventions (per-visual folder,
one-export-per-file, split types).
- `Tiles.tsx`: one-line spotlight mobile min-height bump (only the
dashboard uses the spotlight tile).
- typecheck + lint + build all green.
---
.../DashboardVisual/DashboardVisual.tsx | 108 +++++++----
.../DashboardVisual/components/BarChart.tsx | 164 +++++++++-------
.../DashboardVisual/components/DonutChart.tsx | 181 ++++++++++++------
.../DashboardVisual/components/KpiCard.tsx | 11 +-
.../data/dashboard-visual-data.ts | 36 ++--
.../DashboardVisual/types/dashboard-kpi.ts | 5 +-
.../DashboardVisual/types/dashboard-month.ts | 7 +-
.../types/dashboard-stage-tone.ts | 7 +-
.../DashboardVisual/types/dashboard-stage.ts | 6 +-
.../product-feature/components/Tiles.tsx | 2 +-
10 files changed, 342 insertions(+), 185 deletions(-)
diff --git a/packages/twenty-website/src/sections/product-feature/DashboardVisual/DashboardVisual.tsx b/packages/twenty-website/src/sections/product-feature/DashboardVisual/DashboardVisual.tsx
index 7d545fb884..2d8cde0c96 100644
--- a/packages/twenty-website/src/sections/product-feature/DashboardVisual/DashboardVisual.tsx
+++ b/packages/twenty-website/src/sections/product-feature/DashboardVisual/DashboardVisual.tsx
@@ -1,10 +1,13 @@
'use client';
+import { msg } from '@lingui/core/macro';
+import { useLingui } from '@lingui/react';
import { styled } from '@linaria/react';
import { IconDotsVertical, IconLayoutDashboard } from '@tabler/icons-react';
import { THEME_LIGHT } from 'twenty-ui/theme';
import { previewFontSize } from '@/app-preview/preview-font-size';
+import { mediaUp } from '@/tokens';
import { BarChart } from './components/BarChart';
import { DonutChart } from './components/DonutChart';
@@ -45,13 +48,21 @@ const BreadcrumbIcon = styled.span`
const BreadcrumbText = styled.span`
color: ${THEME_LIGHT.font.color.secondary};
- font-size: ${previewFontSize(THEME_LIGHT.font.size.md)};
+ font-size: ${previewFontSize(THEME_LIGHT.font.size.sm)};
+
+ ${mediaUp('md')} {
+ font-size: ${previewFontSize(THEME_LIGHT.font.size.md)};
+ }
`;
const BreadcrumbCurrent = styled.span`
color: ${THEME_LIGHT.font.color.primary};
- font-size: ${previewFontSize(THEME_LIGHT.font.size.md)};
+ font-size: ${previewFontSize(THEME_LIGHT.font.size.sm)};
font-weight: ${THEME_LIGHT.font.weight.medium};
+
+ ${mediaUp('md')} {
+ font-size: ${previewFontSize(THEME_LIGHT.font.size.md)};
+ }
`;
const Actions = styled.div`
@@ -83,15 +94,40 @@ const SearchChip = styled.span`
const Body = styled.div`
background-color: ${THEME_LIGHT.background.primary};
- display: grid;
+ display: flex;
flex: 1;
+ flex-direction: column;
gap: 8px;
- grid-template-columns: repeat(3, 1fr);
- grid-template-rows: auto 1fr;
min-height: 0;
padding: 12px;
`;
+const KpiRow = styled.div`
+ display: grid;
+ flex-shrink: 0;
+ gap: 8px;
+ grid-template-columns: repeat(2, 1fr);
+
+ & > :nth-child(3) {
+ display: none;
+ }
+
+ ${mediaUp('md')} {
+ grid-template-columns: repeat(3, 1fr);
+
+ & > :nth-child(3) {
+ display: flex;
+ }
+ }
+`;
+
+const ChartRow = styled.div`
+ display: flex;
+ flex: 1;
+ gap: 8px;
+ min-height: 0;
+`;
+
const WidgetCard = styled.div`
background-color: ${THEME_LIGHT.background.secondary};
border: 1px solid ${THEME_LIGHT.border.color.light};
@@ -99,15 +135,24 @@ const WidgetCard = styled.div`
display: flex;
flex-direction: column;
min-height: 0;
+ min-width: 0;
padding: 8px;
&[data-cell='bar'] {
- grid-column: 1 / 3;
- grid-row: 2;
+ flex: 1;
}
&[data-cell='donut'] {
- grid-column: 3;
- grid-row: 2;
+ display: none;
+ }
+
+ ${mediaUp('md')} {
+ &[data-cell='bar'] {
+ flex: 1.4;
+ }
+ &[data-cell='donut'] {
+ display: flex;
+ flex: 1;
+ }
}
`;
@@ -122,6 +167,8 @@ const WidgetHeader = styled.span`
`;
export function DashboardVisual({ active }: { active: boolean }) {
+ const { i18n } = useLingui();
+
return (
@@ -129,8 +176,10 @@ export function DashboardVisual({ active }: { active: boolean }) {
- Dashboards /
- Sales performance
+ {i18n._(msg`Dashboards`)} /
+
+ {i18n._(msg`Sales performance`)}
+
@@ -140,28 +189,23 @@ export function DashboardVisual({ active }: { active: boolean }) {
- {DASHBOARD_VISUAL_DATA.kpis.map((kpi) => (
-
- {kpi.label}
-
+
+ {DASHBOARD_VISUAL_DATA.kpis.map((kpi) => (
+
+
+
+ ))}
+
+
+
+ {i18n._(msg`Deals by month`)}
+
- ))}
-
- Deals by month
-
-
-
- Deals by stage
-
-
+
+ {i18n._(msg`Deals by stage`)}
+
+
+
);
diff --git a/packages/twenty-website/src/sections/product-feature/DashboardVisual/components/BarChart.tsx b/packages/twenty-website/src/sections/product-feature/DashboardVisual/components/BarChart.tsx
index 978bb417c0..a80451f32f 100644
--- a/packages/twenty-website/src/sections/product-feature/DashboardVisual/components/BarChart.tsx
+++ b/packages/twenty-website/src/sections/product-feature/DashboardVisual/components/BarChart.tsx
@@ -1,5 +1,6 @@
'use client';
+import { useLingui } from '@lingui/react';
import { styled } from '@linaria/react';
import { THEME_LIGHT } from 'twenty-ui/theme';
@@ -7,10 +8,10 @@ import { previewFontSize } from '@/app-preview/preview-font-size';
import { EASING } from '@/tokens';
import { type DashboardMonth } from '../types/dashboard-month';
-import { type DashboardStage } from '../types/dashboard-stage';
-const Y_TICK_COUNT = 5;
const X_LABEL_ROW_HEIGHT = 18;
+const TARGET_TICK_COUNT = 4;
+const BAR_MAX_WIDTH = 26;
const Root = styled.div`
display: flex;
@@ -48,12 +49,25 @@ const Bars = styled.div`
min-width: 0;
`;
+const PlotArea = styled.div`
+ flex: 1;
+ min-height: 0;
+ position: relative;
+`;
+
+const GridLine = styled.div`
+ border-top: 1px dashed ${THEME_LIGHT.border.color.light};
+ left: 0;
+ position: absolute;
+ right: 0;
+`;
+
const BarsRow = styled.div`
align-items: flex-end;
display: flex;
- flex: 1;
gap: 6px;
- min-height: 0;
+ inset: 0;
+ position: absolute;
`;
const BarColumn = styled.div`
@@ -62,41 +76,35 @@ const BarColumn = styled.div`
flex: 1;
height: 100%;
justify-content: center;
+ position: relative;
`;
-const Stack = styled.div`
- border-radius: 3px 3px 0 0;
- display: flex;
- flex-direction: column-reverse;
- min-height: 2px;
- overflow: hidden;
+const Bar = styled.div`
+ background-color: ${THEME_LIGHT.color.blue8};
+ border-radius: ${THEME_LIGHT.border.radius.sm} ${THEME_LIGHT.border.radius.sm}
+ 0 0;
+ max-width: ${BAR_MAX_WIDTH}px;
transform-origin: bottom;
transition:
transform 0.8s ${EASING.standard},
filter 0.15s ease;
- width: 68%;
+ width: 58%;
&:hover {
filter: brightness(1.08);
}
`;
-const Segment = styled.div`
- flex-shrink: 0;
- width: 100%;
-
- &[data-tone='blue'] {
- background-color: ${THEME_LIGHT.color.blue8};
- }
- &[data-tone='purple'] {
- background-color: ${THEME_LIGHT.color.purple8};
- }
- &[data-tone='turquoise'] {
- background-color: ${THEME_LIGHT.color.turquoise8};
- }
- &[data-tone='orange'] {
- background-color: ${THEME_LIGHT.color.orange8};
- }
+const ValueLabel = styled.span`
+ color: ${THEME_LIGHT.font.color.light};
+ font-size: ${previewFontSize(THEME_LIGHT.font.size.xs)};
+ font-variant-numeric: tabular-nums;
+ left: 0;
+ position: absolute;
+ right: 0;
+ text-align: center;
+ transform: translateY(-3px);
+ transition: opacity 0.4s ${EASING.standard};
`;
const XLabels = styled.div`
@@ -113,67 +121,83 @@ const XLabel = styled.span`
text-align: center;
`;
+function getNiceScale(maxValue: number): { maxTick: number; ticks: number[] } {
+ if (!(maxValue > 0)) {
+ return { maxTick: 1, ticks: [0, 1] };
+ }
+ const rawStep = maxValue / TARGET_TICK_COUNT;
+ const magnitude = 10 ** Math.floor(Math.log10(rawStep));
+ const normalized = rawStep / magnitude;
+ const niceNormalized =
+ normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10;
+ const step = niceNormalized * magnitude;
+ const tickCount = Math.ceil(maxValue / step);
+ return {
+ maxTick: step * tickCount,
+ ticks: Array.from({ length: tickCount + 1 }, (_, index) => index * step),
+ };
+}
+
export function BarChart({
active,
months,
- stages,
}: {
active: boolean;
months: DashboardMonth[];
- stages: DashboardStage[];
}) {
- const monthlyTotals = months.map((month) =>
- month.values.reduce((sum, value) => sum + value, 0),
- );
- const tickStep = Math.ceil(
- (Math.max(...monthlyTotals) * 1.05) / Y_TICK_COUNT,
- );
- const topTick = tickStep * Y_TICK_COUNT;
- const yTicks = Array.from(
- { length: Y_TICK_COUNT + 1 },
- (_, tickNumber) => tickNumber * tickStep,
- );
+ const { i18n } = useLingui();
+ const maxValue = Math.max(...months.map((month) => month.value));
+ const { maxTick, ticks } = getNiceScale(maxValue);
return (
- {yTicks.toReversed().map((tick) => (
+ {ticks.toReversed().map((tick) => (
{tick}
))}
-
- {months.map((month, columnNumber) => {
- const total = monthlyTotals[columnNumber];
- return (
-
-
- {stages.map((stage, stageNumber) => (
-
- ))}
-
-
- );
- })}
-
+
+ {ticks.map((tick) => (
+
+ ))}
+
+ {months.map((month, columnNumber) => {
+ const heightPercent = (month.value / maxTick) * 100;
+ return (
+
+
+ {month.value}
+
+
+
+ );
+ })}
+
+
{months.map((month) => (
- {month.label}
+ {i18n._(month.label)}
))}
diff --git a/packages/twenty-website/src/sections/product-feature/DashboardVisual/components/DonutChart.tsx b/packages/twenty-website/src/sections/product-feature/DashboardVisual/components/DonutChart.tsx
index d12ab66da9..6ebff1b3cb 100644
--- a/packages/twenty-website/src/sections/product-feature/DashboardVisual/components/DonutChart.tsx
+++ b/packages/twenty-website/src/sections/product-feature/DashboardVisual/components/DonutChart.tsx
@@ -1,6 +1,10 @@
'use client';
+import { msg } from '@lingui/core/macro';
+import { useLingui } from '@lingui/react';
import { styled } from '@linaria/react';
+import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react';
+import { useState } from 'react';
import { THEME_LIGHT } from 'twenty-ui/theme';
import { previewFontSize } from '@/app-preview/preview-font-size';
@@ -8,33 +12,41 @@ import { EASING } from '@/tokens';
import { type DashboardStage } from '../types/dashboard-stage';
-const SIZE = 96;
-const STROKE = 16;
+const SIZE = 160;
+const STROKE = 18;
const RADIUS = (SIZE - STROKE) / 2;
const CENTER = SIZE / 2;
const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
+const GAP = CIRCUMFERENCE * 0.008;
+const LEGEND_PAGE_SIZE = 3;
const Root = styled.div`
align-items: center;
display: flex;
flex: 1;
flex-direction: column;
- gap: 14px;
+ gap: 16px;
justify-content: center;
min-height: 0;
`;
const ChartArea = styled.div`
- flex-shrink: 0;
+ aspect-ratio: 1;
+ max-height: 100%;
+ max-width: ${SIZE}px;
+ min-height: 0;
position: relative;
+ width: 100%;
`;
const Ring = styled.svg`
display: block;
+ height: 100%;
transform-origin: center;
transition:
opacity 0.5s ${EASING.standard},
transform 0.5s ${EASING.standard};
+ width: 100%;
`;
const Slice = styled.circle`
@@ -46,17 +58,20 @@ const Slice = styled.circle`
filter: brightness(1.1);
}
- &[data-tone='blue'] {
- stroke: ${THEME_LIGHT.color.blue8};
+ &[data-tone='red'] {
+ stroke: ${THEME_LIGHT.color.red8};
}
&[data-tone='purple'] {
stroke: ${THEME_LIGHT.color.purple8};
}
+ &[data-tone='sky'] {
+ stroke: ${THEME_LIGHT.color.sky8};
+ }
&[data-tone='turquoise'] {
stroke: ${THEME_LIGHT.color.turquoise8};
}
- &[data-tone='orange'] {
- stroke: ${THEME_LIGHT.color.orange8};
+ &[data-tone='yellow'] {
+ stroke: ${THEME_LIGHT.color.yellow8};
}
`;
@@ -78,113 +93,159 @@ const CenterValue = styled.span`
`;
const CenterLabel = styled.span`
- color: ${THEME_LIGHT.font.color.light};
+ color: ${THEME_LIGHT.font.color.tertiary};
font-size: ${previewFontSize(THEME_LIGHT.font.size.xs)};
margin-top: 2px;
`;
const Legend = styled.div`
+ align-items: center;
display: flex;
- flex-direction: column;
- gap: 6px;
+ gap: 48px;
+ overflow: hidden;
width: 100%;
`;
-const LegendRow = styled.div`
+const Pager = styled.div`
align-items: center;
display: flex;
- gap: 6px;
+ flex-shrink: 0;
+ gap: 2px;
`;
-const LegendDot = styled.span`
+const PagerArrow = styled.span`
+ align-items: center;
+ color: ${THEME_LIGHT.font.color.light};
+ cursor: pointer;
+ display: inline-flex;
+ transition: color 0.15s ease;
+
+ &:hover {
+ color: ${THEME_LIGHT.font.color.secondary};
+ }
+`;
+
+const PagerText = styled.span`
+ color: ${THEME_LIGHT.font.color.light};
+ font-size: ${previewFontSize(THEME_LIGHT.font.size.sm)};
+ font-variant-numeric: tabular-nums;
+`;
+
+const Item = styled.div`
+ align-items: center;
+ display: flex;
+ flex-shrink: 0;
+ gap: 5px;
+`;
+
+const ItemDot = styled.span`
border-radius: ${THEME_LIGHT.border.radius.xs};
flex-shrink: 0;
- height: 8px;
- width: 8px;
+ height: 9px;
+ width: 9px;
- &[data-tone='blue'] {
- background-color: ${THEME_LIGHT.color.blue8};
+ &[data-tone='red'] {
+ background-color: ${THEME_LIGHT.color.red8};
}
&[data-tone='purple'] {
background-color: ${THEME_LIGHT.color.purple8};
}
+ &[data-tone='sky'] {
+ background-color: ${THEME_LIGHT.color.sky8};
+ }
&[data-tone='turquoise'] {
background-color: ${THEME_LIGHT.color.turquoise8};
}
- &[data-tone='orange'] {
- background-color: ${THEME_LIGHT.color.orange8};
+ &[data-tone='yellow'] {
+ background-color: ${THEME_LIGHT.color.yellow8};
}
`;
-const LegendLabel = styled.span`
- color: ${THEME_LIGHT.font.color.secondary};
- flex: 1;
- font-size: ${previewFontSize(THEME_LIGHT.font.size.md)};
-`;
-
-const LegendValue = styled.span`
- color: ${THEME_LIGHT.font.color.tertiary};
- font-size: ${previewFontSize(THEME_LIGHT.font.size.md)};
- font-variant-numeric: tabular-nums;
+const ItemLabel = styled.span`
+ color: ${THEME_LIGHT.font.color.primary};
+ font-size: ${previewFontSize(THEME_LIGHT.font.size.sm)};
+ font-weight: ${THEME_LIGHT.font.weight.medium};
+ white-space: nowrap;
`;
export function DonutChart({
active,
stages,
- values,
}: {
active: boolean;
stages: DashboardStage[];
- values: number[];
}) {
- const total = values.reduce((sum, value) => sum + value, 0);
+ const { i18n } = useLingui();
+ const [page, setPage] = useState(0);
+ const total = stages.reduce((sum, stage) => sum + stage.value, 0);
+ const pageCount = Math.ceil(stages.length / LEGEND_PAGE_SIZE);
+ const visibleStages = stages.slice(
+ page * LEGEND_PAGE_SIZE,
+ page * LEGEND_PAGE_SIZE + LEGEND_PAGE_SIZE,
+ );
let cumulative = 0;
- const slices = stages.map((stage, stageNumber) => {
- const value = values[stageNumber];
- const arc = (value / total) * CIRCUMFERENCE;
- const startAngle = -90 + (cumulative / total) * 360;
- cumulative += value;
- return { arc, stage, startAngle, value };
+ const slices = stages.map((stage) => {
+ const arc = total > 0 ? (stage.value / total) * CIRCUMFERENCE : 0;
+ const startAngle = -90 + (total > 0 ? cumulative / total : 0) * 360;
+ cumulative += stage.value;
+ return { arc, stage, startAngle };
});
return (
- {slices.map(({ arc, stage, startAngle }) => (
-
- ))}
+ {slices.map(({ arc, stage, startAngle }) => {
+ const visibleArc = stage.value > 0 ? Math.max(arc - GAP, 0.5) : 0;
+ return (
+
+ );
+ })}
{total}
- Deals
+ {i18n._(msg`Total`)}
diff --git a/packages/twenty-website/src/sections/product-feature/DashboardVisual/components/KpiCard.tsx b/packages/twenty-website/src/sections/product-feature/DashboardVisual/components/KpiCard.tsx
index 88c6844ca6..b6187c5603 100644
--- a/packages/twenty-website/src/sections/product-feature/DashboardVisual/components/KpiCard.tsx
+++ b/packages/twenty-website/src/sections/product-feature/DashboardVisual/components/KpiCard.tsx
@@ -1,3 +1,4 @@
+import { useLingui } from '@lingui/react';
import { styled } from '@linaria/react';
import { IconTrendingDown, IconTrendingUp } from '@tabler/icons-react';
import { THEME_LIGHT } from 'twenty-ui/theme';
@@ -15,6 +16,11 @@ const Body = styled.div`
justify-content: center;
`;
+const Label = styled.span`
+ color: ${THEME_LIGHT.font.color.secondary};
+ font-size: ${previewFontSize(THEME_LIGHT.font.size.md)};
+`;
+
const Value = styled.span`
color: ${THEME_LIGHT.font.color.primary};
font-size: ${previewFontSize(THEME_LIGHT.font.size.xxl)};
@@ -42,13 +48,16 @@ const TrendIcon = styled.span`
`;
const TrendPercent = styled.span`
- color: ${THEME_LIGHT.font.color.secondary};
+ color: ${THEME_LIGHT.font.color.light};
font-size: ${previewFontSize(THEME_LIGHT.font.size.xs)};
`;
export function KpiCard({ kpi }: { kpi: DashboardKpi }) {
+ const { i18n } = useLingui();
+
return (
+
{kpi.value}
diff --git a/packages/twenty-website/src/sections/product-feature/DashboardVisual/data/dashboard-visual-data.ts b/packages/twenty-website/src/sections/product-feature/DashboardVisual/data/dashboard-visual-data.ts
index 0ebd9dd9be..7e2eb862dd 100644
--- a/packages/twenty-website/src/sections/product-feature/DashboardVisual/data/dashboard-visual-data.ts
+++ b/packages/twenty-website/src/sections/product-feature/DashboardVisual/data/dashboard-visual-data.ts
@@ -1,3 +1,5 @@
+import { msg } from '@lingui/core/macro';
+
import { type DashboardKpi } from '../types/dashboard-kpi';
import { type DashboardMonth } from '../types/dashboard-month';
import { type DashboardStage } from '../types/dashboard-stage';
@@ -6,39 +8,41 @@ export const DASHBOARD_VISUAL_DATA: {
byMonth: DashboardMonth[];
kpis: DashboardKpi[];
stages: DashboardStage[];
- stageTotals: number[];
} = {
stages: [
- { label: 'New', tone: 'blue' },
- { label: 'Qualified', tone: 'purple' },
- { label: 'Proposal', tone: 'turquoise' },
- { label: 'Won', tone: 'orange' },
+ { id: 'new', label: msg`New`, tone: 'red', value: 47 },
+ { id: 'screening', label: msg`Screening`, tone: 'purple', value: 34 },
+ { id: 'meeting', label: msg`Meeting`, tone: 'sky', value: 27 },
+ { id: 'proposal', label: msg`Proposal`, tone: 'turquoise', value: 20 },
+ { id: 'customer', label: msg`Customer`, tone: 'yellow', value: 12 },
],
- stageTotals: [47, 34, 27, 20],
byMonth: [
- { label: 'Jan', values: [7, 5, 4, 2] },
- { label: 'Feb', values: [9, 6, 5, 3] },
- { label: 'Mar', values: [8, 6, 4, 2] },
- { label: 'Apr', values: [12, 8, 6, 4] },
- { label: 'May', values: [15, 11, 8, 5] },
- { label: 'Jun', values: [13, 9, 7, 4] },
- { label: 'Jul', values: [17, 12, 9, 6] },
+ { id: 'jan', label: msg`Jan`, value: 12 },
+ { id: 'feb', label: msg`Feb`, value: 16 },
+ { id: 'mar', label: msg`Mar`, value: 14 },
+ { id: 'apr', label: msg`Apr`, value: 22 },
+ { id: 'may', label: msg`May`, value: 27 },
+ { id: 'jun', label: msg`Jun`, value: 24 },
+ { id: 'jul', label: msg`Jul`, value: 31 },
],
kpis: [
{
- label: 'Revenue (YTD)',
+ id: 'revenue',
+ label: msg`Revenue (YTD)`,
trendDirection: 'up',
trendPercent: 12,
value: '$1.2M',
},
{
- label: 'Avg deal size',
+ id: 'avg-deal-size',
+ label: msg`Avg deal size`,
trendDirection: 'up',
trendPercent: 5,
value: '$9.4K',
},
{
- label: 'Win rate',
+ id: 'win-rate',
+ label: msg`Win rate`,
trendDirection: 'down',
trendPercent: 3,
value: '34%',
diff --git a/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-kpi.ts b/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-kpi.ts
index 83cfdf57bb..cfa70f20ab 100644
--- a/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-kpi.ts
+++ b/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-kpi.ts
@@ -1,5 +1,8 @@
+import { type MessageDescriptor } from '@lingui/core';
+
export type DashboardKpi = {
- label: string;
+ id: string;
+ label: MessageDescriptor;
trendDirection: 'down' | 'up';
trendPercent: number;
value: string;
diff --git a/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-month.ts b/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-month.ts
index e8b7585a23..71af12a64c 100644
--- a/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-month.ts
+++ b/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-month.ts
@@ -1,4 +1,7 @@
+import { type MessageDescriptor } from '@lingui/core';
+
export type DashboardMonth = {
- label: string;
- values: number[];
+ id: string;
+ label: MessageDescriptor;
+ value: number;
};
diff --git a/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-stage-tone.ts b/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-stage-tone.ts
index ea278bce49..8f0ca3c7ee 100644
--- a/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-stage-tone.ts
+++ b/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-stage-tone.ts
@@ -1 +1,6 @@
-export type DashboardStageTone = 'blue' | 'orange' | 'purple' | 'turquoise';
+export type DashboardStageTone =
+ | 'purple'
+ | 'red'
+ | 'sky'
+ | 'turquoise'
+ | 'yellow';
diff --git a/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-stage.ts b/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-stage.ts
index 9af4be26a0..1883e006fd 100644
--- a/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-stage.ts
+++ b/packages/twenty-website/src/sections/product-feature/DashboardVisual/types/dashboard-stage.ts
@@ -1,6 +1,10 @@
+import { type MessageDescriptor } from '@lingui/core';
+
import { type DashboardStageTone } from './dashboard-stage-tone';
export type DashboardStage = {
- label: string;
+ id: string;
+ label: MessageDescriptor;
tone: DashboardStageTone;
+ value: number;
};
diff --git a/packages/twenty-website/src/sections/product-feature/components/Tiles.tsx b/packages/twenty-website/src/sections/product-feature/components/Tiles.tsx
index 3f1c904af4..cedc694320 100644
--- a/packages/twenty-website/src/sections/product-feature/components/Tiles.tsx
+++ b/packages/twenty-website/src/sections/product-feature/components/Tiles.tsx
@@ -74,7 +74,7 @@ const SpotlightVisual = styled.div`
border: 1px solid ${color('black-20')};
border-radius: ${radius(2)};
margin: ${spacing(4)};
- min-height: 260px;
+ min-height: 300px;
overflow: hidden;
${mediaUp('md')} {