diff --git a/apps/web/src/app/(dashboard)/dashboard/dashboard-filters.tsx b/apps/web/src/app/(dashboard)/dashboard/dashboard-filters.tsx
index a7e8e507..ddc4eab2 100644
--- a/apps/web/src/app/(dashboard)/dashboard/dashboard-filters.tsx
+++ b/apps/web/src/app/(dashboard)/dashboard/dashboard-filters.tsx
@@ -29,33 +29,38 @@ export default function DashboardFilters({
};
return (
-
-
- setDays(value)}>
-
-
- 7 Days
-
-
- 30 Days
-
-
-
-
- );
+
+
+ setDays(value)}>
+
+
+ 7 Days
+
+
+ 30 Days
+
+
+
+
+ );
}
diff --git a/apps/web/src/app/(dashboard)/dashboard/email-chart.tsx b/apps/web/src/app/(dashboard)/dashboard/email-chart.tsx
index 8c02e208..8821e045 100644
--- a/apps/web/src/app/(dashboard)/dashboard/email-chart.tsx
+++ b/apps/web/src/app/(dashboard)/dashboard/email-chart.tsx
@@ -32,16 +32,18 @@ const STACK_ORDER: string[] = [
] as const;
type StackKey = (typeof STACK_ORDER)[number];
-
-function createRoundedTopShape(currentKey: StackKey) {
- const currentIndex = STACK_ORDER.indexOf(currentKey);
+function createRoundedTopShape(
+ currentKey: StackKey,
+ visibleStackOrder: StackKey[],
+) {
+ const currentIndex = visibleStackOrder.indexOf(currentKey);
return (props: any) => {
const payload = props.payload as
| Partial>
| undefined;
let hasAbove = false;
- for (let i = currentIndex + 1; i < STACK_ORDER.length; i++) {
- const key = STACK_ORDER[i];
+ for (let i = currentIndex + 1; i < visibleStackOrder.length; i++) {
+ const key = visibleStackOrder[i];
const val = key ? (payload?.[key] ?? 0) : 0;
if (val > 0) {
hasAbove = true;
@@ -55,6 +57,7 @@ function createRoundedTopShape(currentKey: StackKey) {
}
export default function EmailChart({ days, domain }: EmailChartProps) {
+ const [selectedMetrics, setSelectedMetrics] = React.useState([]);
const domainId = domain ? Number(domain) : undefined;
const statusQuery = api.dashboard.emailTimeSeries.useQuery({
days: days,
@@ -63,6 +66,32 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
const currentColors = useColors();
+ const metricMeta: Record = {
+ delivered: { label: "Delivered", color: currentColors.delivered },
+ bounced: { label: "Bounced", color: currentColors.bounced },
+ complained: { label: "Complained", color: currentColors.complained },
+ opened: { label: "Opened", color: currentColors.opened },
+ clicked: { label: "Clicked", color: currentColors.clicked },
+ };
+
+ const visibleMetrics =
+ selectedMetrics.length === 0
+ ? STACK_ORDER
+ : STACK_ORDER.filter((key) => selectedMetrics.includes(key));
+
+ const toggleMetric = (metric: StackKey) => {
+ setSelectedMetrics((prev) => {
+ const exists = prev.includes(metric);
+
+ if (exists) {
+ return prev.filter((key) => key !== metric);
+ }
+
+ const nextSet = new Set([...prev, metric]);
+ return STACK_ORDER.filter((key) => nextSet.has(key));
+ });
+ };
+
return (
{!statusQuery.isLoading && statusQuery.data ? (
@@ -75,6 +104,8 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
status={"total"}
count={statusQuery.data.totalCounts.sent}
percentage={100}
+ isActive={selectedMetrics.length === 0}
+ isClickable={false}
/>
toggleMetric("delivered")}
/>
toggleMetric("bounced")}
/>
toggleMetric("complained")}
/>
toggleMetric("clicked")}
/>
toggleMetric("opened")}
/>
@@ -135,6 +191,9 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
fontSize={12}
className="font-mono"
stroke={currentColors.xaxis}
+ tick={{ fill: currentColors.xaxis, fillOpacity: 0.65 }}
+ axisLine={false}
+ tickLine={false}
/>
{/* */}
0 ||
- (data.bounced || 0) > 0 ||
- (data.complained || 0) > 0 ||
- (data.opened || 0) > 0 ||
- (data.clicked || 0) > 0;
+ visibleMetrics.reduce(
+ (sum, key) => sum + (data[key] || 0),
+ 0,
+ ) > 0;
if (!hasAnyData) return null;
@@ -167,105 +225,43 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
{data.date}
- {data.delivered ? (
-
-
-
- Delivered
-
-
{data.delivered}
-
- ) : null}
- {data.bounced ? (
-
-
-
- Bounced
-
-
{data.bounced}
-
- ) : null}
- {data.complained ? (
-
-
-
- Complained
-
-
{data.complained}
-
- ) : null}
- {data.opened ? (
-
-
-
- Opened
-
-
{data.opened}
-
- ) : null}
- {data.clicked ? (
-
+ {visibleMetrics.map((metricKey) => {
+ const metricValue = data[metricKey] || 0;
+ if (!metricValue) return null;
+
+ return (
-
- Clicked
-
-
{data.clicked}
-
- ) : null}
+ key={metricKey}
+ className="flex gap-2 items-center"
+ >
+
+
+ {metricMeta[metricKey].label}
+
+ {metricValue}
+
+ );
+ })}
);
}}
cursor={false}
/>
- {/* */}
-
-
-
-
-
+ {visibleMetrics.map((metricKey) => (
+
+ ))}
@@ -280,6 +276,9 @@ type DashboardItemCardProps = {
status: EmailStatus | "total";
count: number;
percentage: number;
+ onClick?: () => void;
+ isActive?: boolean;
+ isClickable?: boolean;
};
const DashboardItemCard: React.FC = ({
@@ -311,6 +310,9 @@ const EmailChartItem: React.FC = ({
status,
count,
percentage,
+ onClick,
+ isActive = false,
+ isClickable = true,
}) => {
const currentColors = useColors();
@@ -333,7 +335,17 @@ const EmailChartItem: React.FC = ({
};
return (
-
+
);
};
diff --git a/apps/web/src/app/(dashboard)/dashboard/page.tsx b/apps/web/src/app/(dashboard)/dashboard/page.tsx
index 1bad2759..1c50420f 100644
--- a/apps/web/src/app/(dashboard)/dashboard/page.tsx
+++ b/apps/web/src/app/(dashboard)/dashboard/page.tsx
@@ -7,7 +7,7 @@ import { useUrlState } from "~/hooks/useUrlState";
import { ReputationMetrics } from "./reputation-metrics";
export default function Dashboard() {
- const [days, setDays] = useUrlState("days", "7");
+ const [days, setDays] = useUrlState("days", "30");
const [domain, setDomain] = useUrlState("domain");
return (
@@ -16,16 +16,16 @@ export default function Dashboard() {
Analytics
-
+
-
+
diff --git a/apps/web/src/server/service/dashboard-service.ts b/apps/web/src/server/service/dashboard-service.ts
index 90228111..117c100f 100644
--- a/apps/web/src/server/service/dashboard-service.ts
+++ b/apps/web/src/server/service/dashboard-service.ts
@@ -3,15 +3,15 @@ import { format, subDays } from "date-fns";
import { Prisma, Team } from "@prisma/client";
type EmailTimeSeries = {
- days?: number;
- domain?: number
- team: Team
+ days?: number;
+ domain?: number;
+ team: Team;
};
export async function emailTimeSeries(input: EmailTimeSeries) {
- const days = input.days !== 7 ? 30 : 7;
- const { domain, team } = input
- const startDate = new Date();
+ const days = input.days !== 7 ? 30 : 7;
+ const { domain, team } = input;
+ const startDate = new Date();
startDate.setDate(startDate.getDate() - days);
const isoStartDate = startDate.toISOString().split("T")[0];
@@ -87,22 +87,21 @@ export async function emailTimeSeries(input: EmailTimeSeries) {
clicked: 0,
bounced: 0,
complained: 0,
- }
+ },
);
return { result: filledResult, totalCounts };
}
-
type ReputationMetricsData = {
- domain?: number
- team: Team
+ domain?: number;
+ team: Team;
};
export async function reputationMetricsData(input: ReputationMetricsData) {
- const { domain, team } = input
+ const { domain, team } = input;
- const reputations = await db.cumulatedMetrics.findMany({
+ const reputations = await db.cumulatedMetrics.findMany({
where: {
teamId: team.id,
...(domain ? { domainId: domain } : {}),
@@ -116,7 +115,7 @@ export async function reputationMetricsData(input: ReputationMetricsData) {
acc.complained += Number(curr.complained);
return acc;
},
- { delivered: 0, hardBounced: 0, complained: 0 }
+ { delivered: 0, hardBounced: 0, complained: 0 },
);
const resultWithRates = {
diff --git a/packages/ui/styles/globals.css b/packages/ui/styles/globals.css
index 24f13825..43a0cc0b 100644
--- a/packages/ui/styles/globals.css
+++ b/packages/ui/styles/globals.css
@@ -122,10 +122,10 @@
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
- --sidebar-background: 225 3% 94%;
- --sidebar-foreground: 240 5.3% 26.1%;
- --sidebar-primary: 240 5.9% 10%;
- --sidebar-primary-foreground: 0 0% 98%;
+ --sidebar-background: 220 2% 96%;
+ --sidebar-foreground: 234 16% 35%;
+ --sidebar-primary: 167 34% 20%;
+ --sidebar-primary-foreground: 210 40% 98%;
--sidebar-accent: 240 11% 88%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 240 11% 88%;
@@ -182,10 +182,10 @@
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
- --sidebar-background: 240 23% 9%;
+ --sidebar-background: 240 21% 12%;
--sidebar-foreground: 226 64% 88%;
- --sidebar-primary: 224.3 76.3% 48%;
- --sidebar-primary-foreground: 0 0% 100%;
+ --sidebar-primary: 167 64% 94%;
+ --sidebar-primary-foreground: 240 23% 9%;
--sidebar-accent: 237 17% 20%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 21% 15%;