+
{icon}
-
{title}
-
+
{title}
+
{value}
- {trend && {trend}}
+ {trend && {trend}}
{/* Chart Container */}
-
-
- {variant === 'line' ? (
-
-
-
-
- ) : (
-
-
-
-
- )}
+
+
+
+ {variant === 'line' ? (
+
+
+
+
+ }
+ />
+
+
+ ) : (
+
+
+
+
+ }
+ />
+
+
+ )}
+
diff --git a/ui-v2/src/components/tiles/PieChartTile.tsx b/ui-v2/src/components/tiles/PieChartTile.tsx
index a161339f..6d0e2d2a 100644
--- a/ui-v2/src/components/tiles/PieChartTile.tsx
+++ b/ui-v2/src/components/tiles/PieChartTile.tsx
@@ -1,7 +1,7 @@
-import React from 'react';
+import React, { useState } from 'react';
import { useTimelineStyles } from '../../hooks/useTimelineStyles';
import { ChartConfig, ChartContainer } from "@/components/ui/chart";
-import { PieChart, Pie, Cell, Tooltip, Legend } from 'recharts';
+import { PieChart, Pie, Cell, Sector, ResponsiveContainer } from 'recharts';
interface PieChartSegment {
value: number;
@@ -16,6 +16,90 @@ interface PieChartTileProps {
date?: Date;
}
+// Custom label renderer with connecting lines
+const renderCustomizedLabel = ({
+ cx,
+ cy,
+ midAngle,
+ innerRadius,
+ outerRadius,
+ percent,
+ payload,
+ fill,
+}: any) => {
+ const RADIAN = Math.PI / 180;
+ const sin = Math.sin(-RADIAN * midAngle);
+ const cos = Math.cos(-RADIAN * midAngle);
+
+ // Adjust these values to position labels closer to the pie
+ const labelOffset = 12;
+ const labelDistance = 18;
+
+ // Calculate positions with shorter distances
+ const mx = cx + (outerRadius + labelOffset) * cos;
+ const my = cy + (outerRadius + labelOffset) * sin;
+ const ex = mx + (cos >= 0 ? 1 : -1) * labelDistance;
+ const ey = my;
+
+ // Text anchor based on which side of the pie we're on
+ const textAnchor = cos >= 0 ? "start" : "end";
+
+ // Calculate percentage
+ const value = (percent * 100).toFixed(0);
+
+ // Determine if label should be on top or bottom half for potential y-offset
+ const isTopHalf = my < cy;
+ const yOffset = isTopHalf ? -2 : 2;
+
+ // Force specific adjustments for "In Progress" label if needed
+ const isInProgress = payload.name === "In Progress";
+ const adjustedEx = isInProgress ? ex - 5 : ex;
+
+ return (
+
+ {/* Label line - using absolute coordinates for reliability */}
+
+ {/* Label text with adjusted position */}
+ = 0 ? 5 : -5)}
+ y={ey + yOffset}
+ textAnchor={textAnchor}
+ fill="var(--text-default)"
+ className="text-[10px]"
+ style={{
+ pointerEvents: 'none',
+ }}
+ >
+ {payload.name} ({value}%)
+
+
+ );
+};
+
+// Active shape renderer for hover effect
+const renderActiveShape = (props: any) => {
+ const { cx, cy, innerRadius, outerRadius, startAngle, endAngle, fill } = props;
+
+ return (
+
+ );
+};
+
export default function PieChartTile({
title,
icon,
@@ -23,102 +107,96 @@ export default function PieChartTile({
date
}: PieChartTileProps) {
const { contentCardStyle } = useTimelineStyles(date);
+ const [activeIndex, setActiveIndex] = useState
(0);
- // Convert segments to the format expected by recharts
- const chartData = segments.map(segment => ({
+ // Convert segments to the format expected by recharts and assign chart colors
+ const chartData = segments.map((segment, index) => ({
name: segment.label,
- value: segment.value
+ value: segment.value,
+ chartColor: `var(--chart-${index + 1})` // Use chart-1, chart-2, chart-3, etc.
}));
- // Create chart configuration with theme colors
- const chartConfig = segments.reduce((config, segment) => {
- config[segment.label] = {
- label: segment.label,
- color: segment.color
- };
- return config;
- }, {} as ChartConfig);
+ // Create chart configuration using the chart color variables
+ const chartConfig = {
+ [segments[0].label.toLowerCase()]: {
+ label: segments[0].label,
+ color: 'var(--chart-1)'
+ },
+ [segments[1].label.toLowerCase()]: {
+ label: segments[1].label,
+ color: 'var(--chart-2)'
+ },
+ [segments[2].label.toLowerCase()]: {
+ label: segments[2].label,
+ color: 'var(--chart-3)'
+ }
+ } satisfies ChartConfig;
- // Custom tooltip formatter
- const tooltipFormatter = (value: number, name: string) => {
- const total = segments.reduce((sum, segment) => sum + segment.value, 0);
- const percentage = ((value / total) * 100).toFixed(1);
- return [`${percentage}%`, name];
+ const onPieEnter = (_: any, index: number) => {
+ setActiveIndex(index);
};
return (
{/* Header */}
-
+
{icon}
-
{/* Pie Chart */}
-
-
+
+
-
-
- {segments.map((segment, index) => (
- |
- ))}
-
-
-
+
+
+
+ {chartData.map((entry, index) => (
+ |
+ ))}
+
+
+
-
- {/* Legend */}
-
- {segments.map((segment, index) => {
- const percentage = ((segment.value / segments.reduce((sum, s) => sum + s.value, 0)) * 100).toFixed(1);
- return (
-
-
-
-
- {segment.label}
-
-
-
- {percentage}%
-
-
- );
- })}
-
);
diff --git a/ui-v2/src/styles/main.css b/ui-v2/src/styles/main.css
index 30758fb3..997da1b4 100644
--- a/ui-v2/src/styles/main.css
+++ b/ui-v2/src/styles/main.css
@@ -3,167 +3,257 @@
@custom-variant dark (&:is(.dark *));
-@layer base {
- :root {
- overflow: hidden;
- --spring-easing: linear(
- 0,
- 0.009,
- 0.035 2.1%,
- 0.141,
- 0.281 6.7%,
- 0.723 12.9%,
- 0.938 16.7%,
- 1.017,
- 1.077,
- 1.121,
- 1.149 24.3%,
- 1.159,
- 1.163,
- 1.161,
- 1.154 29.9%,
- 1.129 32.8%,
- 1.051 39.6%,
- 1.017 43.1%,
- 0.991,
- 0.977 51%,
- 0.974 53.8%,
- 0.975 57.1%,
- 0.997 69.8%,
- 1.003 76.9%,
- 1.004 83.8%,
- 1
- );
- --spring-duration: 1.333s;
+@theme {
+ /* reset */
+ --color-*: initial;
- /* custom slate */
- --slate: #393838;
+ /* constants */
+ --color-white: #ffffff;
+ --color-black: #000000;
- /* block */
- --block-teal: #13bbaf;
- --block-orange: #ff4f00;
+ /* slate */
+ --color-slate-100: #f0f2f8;
+ --color-slate-200: #c8cdd6;
+ --color-slate-300: #a1a7b0;
+ --color-slate-400: #6e747e;
+ --color-slate-500: #4a4e54;
+ --color-slate-600: #2e2e2e;
- /* start arcade colors */
- --constant-white: #ffffff;
- --constant-black: #000000;
- --grey-10: #101010;
- --grey-20: #1e1e1e;
- --grey-50: #666666;
- --grey-60: #959595;
- --grey-80: #cccccc;
- --grey-85: #dadada;
- --grey-90: #e8e8e8;
- --grey-95: #f0f0f0;
- --dark-grey-15: #1a1a1a;
- --dark-grey-25: #232323;
- --dark-grey-30: #2a2a2a;
- --dark-grey-40: #333333;
- --dark-grey-45: #595959;
- --dark-grey-60: #878787;
- --dark-grey-90: #e1e1e1;
+ /* utility */
+ --color-red-100: #ff6b6b;
+ --color-red-200: #f94b4b;
- --background-app: var(--constant-white);
- --background-prominent: var(--grey-80);
- --background-standard: var(--grey-90);
- --background-subtle: var(--grey-95);
- --background-app-inverse: var(--constant-black);
- --background-subtle-inverse: var(--dark-grey-15);
- --background-standard-inverse: var(--dark-grey-25);
- --background-prominent-inverse: var(--dark-grey-40);
+ --color-blue-100: #7cacff;
+ --color-blue-200: #5c98f9;
- --border-divider: var(--grey-90);
- --border-inverse: var(--constant-white);
- --border-prominent: var(--grey-10);
- --border-standard: var(--grey-60);
- --border-subtle: var(--grey-90);
+ --color-green-100: #a3d795;
+ --color-green-200: #91cb80;
- --icon-disabled: var(--grey-60);
- --icon-extra-subtle: var(--grey-60);
- --icon-inverse: var(--constant-white);
- --icon-prominent: var(--grey-10);
- --icon-standard: var(--grey-20);
- --icon-subtle: var(--grey-50);
+ --color-yellow-100: #ffd966;
+ --color-yellow-200: #fbcd44;
+}
- --text-placeholder: var(--grey-60);
- --text-prominent: var(--grey-10);
- --text-standard: var(--grey-20);
- --text-standard-inverse: var(--dark-grey-90);
- --text-subtle: var(--grey-50);
- --text-subtle-inverse: var(--dark-grey-60);
- --text-prominent-inverse: var(--constant-white);
- }
+:root {
+ overflow: hidden;
- @media (prefers-color-scheme: dark) {
- :root {
- --background-app: var(--constant-black);
- --background-prominent: var(--dark-grey-40);
- --background-standard: var(--dark-grey-25);
- --background-subtle: var(--dark-grey-15);
- --background-app-inverse: var(--constant-white);
- --background-subtle-inverse: var(--grey-95);
- --background-standard-inverse: var(--grey-90);
- --background-prominent-inverse: var(--grey-80);
+ /* shape */
+ --radius: 0.5rem;
- --border-divider: var(--dark-grey-25);
- --border-inverse: var(--constant-black);
- --border-prominent: var(--constant-white);
- --border-standard: var(--dark-grey-45);
- --border-subtle: var(--dark-grey-25);
+ /* theming accents */
+ --background-accent: var(--color-black);
+ --border-accent: var(--color-black);
+ --text-accent: var(--color-black);
- --icon-disabled: var(--dark-grey-45);
- --icon-extra-subtle: var(--dark-grey-45);
- --icon-inverse: var(--constant-black);
- --icon-prominent: var(--constant-white);
- --icon-standard: var(--dark-grey-90);
- --icon-subtle: var(--dark-grey-60);
+ /* Semantic */
+ --background-default: var(--color-white);
+ --background-medium: var(--color-slate-200);
+ --background-muted: var(--color-slate-100);
+ --background-inverse: var(--color-black);
+ --background-danger: var(--color-red-200);
+ --background-success: var(--color-green-200);
+ --background-info: var(--color-blue-200);
+ --background-warning: var(--color-yellow-200);
- --text-placeholder: var(--dark-grey-45);
- --text-prominent: var(--constant-white);
- --text-standard: var(--dark-grey-90);
- --text-standard-inverse: var(--grey-20);
- --text-subtle: var(--dark-grey-60);
- --text-subtle-inverse: var(--grey-50);
- --text-prominent-inverse: var(--grey-20);
- }
- }
+ --border-default: var(--color-slate-200);
+ --border-input: var(--color-slate-200);
+ --border-strong: var(--color-slate-300);
+ --border-inverse: var(--color-black);
+ --border-danger: var(--color-red-200);
+ --border-success: var(--color-green-200);
+ --border-warning: var(--color-yellow-200);
+ --border-info: var(--color-blue-200);
- body {
- background-color: var(--background-app);
- }
+ --text-default: var(--color-slate-600);
+ --text-muted: var(--color-slate-400);
+ --text-inverse: var(--color-white);
+ --text-danger: var(--color-red-200);
+ --text-success: var(--color-green-200);
+ --text-warning: var(--color-yellow-200);
+ --text-info: var(--color-blue-200);
+
+ --ring: var(--border-strong);
+
+ --chart-1: #f6b44a;
+ --chart-2: #7585ff;
+ --chart-3: #d76a6a;
+ --chart-4: #d185e0;
+ --chart-5: #91cb80;
+
+ --sidebar: var(--background-default);
+ --sidebar-foreground: var(--text-default);
+ --sidebar-primary: var(--background-accent);
+ --sidebar-primary-foreground: var(--text-inverse);
+ --sidebar-accent: var(--background-muted);
+ --sidebar-accent-foreground: var(--text-default);
+ --sidebar-border: var(--border-default);
+ --sidebar-ring: var(--border-default);
+}
+
+.dark {
+ /* theming accents */
+ --background-accent: var(--color-white);
+ --border-accent: var(--color-white);
+ --text-accent: var(--color-white);
+
+ /* semantic */
+ --background-default: var(--color-black);
+ --background-medium: var(--color-slate-500);
+ --background-muted: var(--color-slate-600);
+ --background-inverse: var(--color-white);
+ --background-danger: var(--color-red-100);
+ --background-success: var(--color-green-100);
+ --background-info: var(--color-blue-100);
+ --background-warning: var(--color-yellow-100);
+
+ --border-default: var(--color-slate-600);
+ --border-input: var(--color-slate-600);
+ --border-strong: var(--color-slate-200);
+ --border-inverse: var(--color-white);
+ --border-danger: var(--color-red-200);
+ --border-success: var(--color-green-200);
+ --border-warning: var(--color-yellow-200);
+ --border-info: var(--color-blue-200);
+
+ --text-default: var(--color-white);
+ --text-muted: var(--color-slate-300);
+ --text-inverse: var(--color-black);
+ --text-danger: var(--color-red-100);
+ --text-success: var(--color-green-100);
+ --text-warning: var(--color-yellow-100);
+ --text-info: var(--color-blue-100);
+
+ --ring: var(--border-strong);
+
+ --chart-1: #f6b44a;
+ --chart-2: #7585ff;
+ --chart-3: #d76a6a;
+ --chart-4: #d185e0;
+ --chart-5: #91cb80;
+
+ --sidebar: var(--background-default);
+ --sidebar-foreground: var(--text-default);
+ --sidebar-primary: var(--background-accent);
+ --sidebar-primary-foreground: var(--text-inverse);
+ --sidebar-accent: var(--background-muted);
+ --sidebar-accent-foreground: var(--text-default);
+ --sidebar-border: var(--border-default);
+ --sidebar-ring: var(--border-default);
+}
+
+@theme inline {
+ /* semantic */
+ --color-background-default: var(--background-default);
+ --color-background-medium: var(--background-medium);
+ --color-background-inverse: var(--background-inverse);
+ --color-background-muted: var(--background-muted);
+ --color-background-danger: var(--background-danger);
+ --color-background-success: var(--background-success);
+ --color-background-info: var(--background-info);
+ --color-background-warning: var(--background-warning);
+
+ --color-background-accent: var(--background-accent);
+ --color-border-accent: var(--border-accent);
+ --color-text-accent: var(--text-accent);
+
+ --color-border-default: var(--border-default);
+ --color-border-input: var(--border-input);
+ --color-border-strong: var(--border-strong);
+ --color-border-inverse: var(--border-inverse);
+ --color-border-danger: var(--border-danger);
+ --color-border-success: var(--border-success);
+ --color-border-warning: var(--border-warning);
+ --color-border-info: var(--border-info);
+
+ --color-text-default: var(--text);
+ --color-text-muted: var(--text-muted);
+ --color-text-inverse: var(--text-inverse);
+ --color-text-danger: var(--text-danger);
+ --color-text-success: var(--text-success);
+ --color-text-warning: var(--text-warning);
+ --color-text-info: var(--text-info);
+
+ /* fonts */
+ --font-sans: "Cash Sans", sans-serif;
+ --font-mono: "Cash Sans Mono", monospace;
+ --font-serif: serif;
+
+ /* shape */
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+
+ --color-ring: var(--ring);
+
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
}
@font-face {
- font-family: 'Cash Sans';
- src:
- url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Regular.woff2)
- format('woff2'),
- url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff/CashSans-Regular.woff)
- format('woff');
+ font-family: "Cash Sans";
+ src: url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Light.woff2)
+ format("woff2"),
+ url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff/CashSans-Light.woff)
+ format("woff");
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "Cash Sans";
+ src: url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Regular.woff2)
+ format("woff2"),
+ url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff/CashSans-Regular.woff)
+ format("woff");
font-weight: 400;
font-style: normal;
}
@font-face {
- font-family: 'Cash Sans';
- src:
- url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Medium.woff2)
- format('woff2'),
- url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff/CashSans-Medium.woff) format('woff');
+ font-family: "Cash Sans";
+ src: url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Medium.woff2)
+ format("woff2"),
+ url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff/CashSans-Medium.woff)
+ format("woff");
font-weight: 500;
font-style: normal;
}
@font-face {
- font-family: 'Cash Sans Mono';
- src:
- url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSansMono-Regular.woff2)
- format('woff2'),
- url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff/CashSansMono-Regular.woff)
- format('woff');
+ font-family: "Cash Sans Mono";
+ src: url(../assets/fonts/CashSansMono-Light.woff2) format("woff2");
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "Cash Sans Mono";
+ src: url(../assets/fonts/CashSansMono-Regular.woff2) format("woff2");
font-weight: 400;
font-style: normal;
}
+@layer base {
+ * {
+ @apply border-border-default;
+ }
+ body {
+ @apply bg-background-default text-text-default;
+ }
+}
+
.titlebar-drag-region {
-webkit-app-region: drag;
height: 32px;
@@ -173,119 +263,3 @@
left: 0;
z-index: 50;
}
-
-@theme inline {
- --radius-sm: calc(var(--radius) - 4px);
- --radius-md: calc(var(--radius) - 2px);
- --radius-lg: var(--radius);
- --radius-xl: calc(var(--radius) + 4px);
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --color-card: var(--card);
- --color-card-foreground: var(--card-foreground);
- --color-popover: var(--popover);
- --color-popover-foreground: var(--popover-foreground);
- --color-primary: var(--primary);
- --color-primary-foreground: var(--primary-foreground);
- --color-secondary: var(--secondary);
- --color-secondary-foreground: var(--secondary-foreground);
- --color-muted: var(--muted);
- --color-muted-foreground: var(--muted-foreground);
- --color-accent: var(--accent);
- --color-accent-foreground: var(--accent-foreground);
- --color-destructive: var(--destructive);
- --color-border: var(--border);
- --color-input: var(--input);
- --color-ring: var(--ring);
- --color-chart-1: var(--chart-1);
- --color-chart-2: var(--chart-2);
- --color-chart-3: var(--chart-3);
- --color-chart-4: var(--chart-4);
- --color-chart-5: var(--chart-5);
- --color-sidebar: var(--sidebar);
- --color-sidebar-foreground: var(--sidebar-foreground);
- --color-sidebar-primary: var(--sidebar-primary);
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
- --color-sidebar-accent: var(--sidebar-accent);
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
- --color-sidebar-border: var(--sidebar-border);
- --color-sidebar-ring: var(--sidebar-ring);
-}
-
-:root {
- --radius: 0.625rem;
- --background: oklch(1 0 0);
- --foreground: oklch(0.145 0 0);
- --card: oklch(1 0 0);
- --card-foreground: oklch(0.145 0 0);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.145 0 0);
- --primary: oklch(0.205 0 0);
- --primary-foreground: oklch(0.985 0 0);
- --secondary: oklch(0.97 0 0);
- --secondary-foreground: oklch(0.205 0 0);
- --muted: oklch(0.97 0 0);
- --muted-foreground: oklch(0.556 0 0);
- --accent: oklch(0.97 0 0);
- --accent-foreground: oklch(0.205 0 0);
- --destructive: oklch(0.577 0.245 27.325);
- --border: oklch(0.922 0 0);
- --input: oklch(0.922 0 0);
- --ring: oklch(0.708 0 0);
- --chart-1: oklch(0.646 0.222 41.116);
- --chart-2: oklch(0.6 0.118 184.704);
- --chart-3: oklch(0.398 0.07 227.392);
- --chart-4: oklch(0.828 0.189 84.429);
- --chart-5: oklch(0.769 0.188 70.08);
- --sidebar: oklch(0.985 0 0);
- --sidebar-foreground: oklch(0.145 0 0);
- --sidebar-primary: oklch(0.205 0 0);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.97 0 0);
- --sidebar-accent-foreground: oklch(0.205 0 0);
- --sidebar-border: oklch(0.922 0 0);
- --sidebar-ring: oklch(0.708 0 0);
-}
-
-.dark {
- --background: oklch(0.145 0 0);
- --foreground: oklch(0.985 0 0);
- --card: oklch(0.205 0 0);
- --card-foreground: oklch(0.985 0 0);
- --popover: oklch(0.205 0 0);
- --popover-foreground: oklch(0.985 0 0);
- --primary: oklch(0.922 0 0);
- --primary-foreground: oklch(0.205 0 0);
- --secondary: oklch(0.269 0 0);
- --secondary-foreground: oklch(0.985 0 0);
- --muted: oklch(0.269 0 0);
- --muted-foreground: oklch(0.708 0 0);
- --accent: oklch(0.269 0 0);
- --accent-foreground: oklch(0.985 0 0);
- --destructive: oklch(0.704 0.191 22.216);
- --border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
- --ring: oklch(0.556 0 0);
- --chart-1: oklch(0.488 0.243 264.376);
- --chart-2: oklch(0.696 0.17 162.48);
- --chart-3: oklch(0.769 0.188 70.08);
- --chart-4: oklch(0.627 0.265 303.9);
- --chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.205 0 0);
- --sidebar-foreground: oklch(0.985 0 0);
- --sidebar-primary: oklch(0.488 0.243 264.376);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.269 0 0);
- --sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(1 0 0 / 10%);
- --sidebar-ring: oklch(0.556 0 0);
-}
-
-@layer base {
- * {
- @apply border-border outline-ring/50;
- }
- body {
- @apply bg-background text-foreground;
- }
-}