Skip to content

Commit bc627d7

Browse files
committed
graph stuff
1 parent 7184799 commit bc627d7

1 file changed

Lines changed: 114 additions & 105 deletions

File tree

apps/web/src/app/(dashboard)/dashboard/email-chart.tsx

Lines changed: 114 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,18 @@ const STACK_ORDER: string[] = [
3232
] as const;
3333

3434
type StackKey = (typeof STACK_ORDER)[number];
35-
36-
function createRoundedTopShape(currentKey: StackKey) {
37-
const currentIndex = STACK_ORDER.indexOf(currentKey);
35+
function createRoundedTopShape(
36+
currentKey: StackKey,
37+
visibleStackOrder: StackKey[],
38+
) {
39+
const currentIndex = visibleStackOrder.indexOf(currentKey);
3840
return (props: any) => {
3941
const payload = props.payload as
4042
| Partial<Record<StackKey, number>>
4143
| undefined;
4244
let hasAbove = false;
43-
for (let i = currentIndex + 1; i < STACK_ORDER.length; i++) {
44-
const key = STACK_ORDER[i];
45+
for (let i = currentIndex + 1; i < visibleStackOrder.length; i++) {
46+
const key = visibleStackOrder[i];
4547
const val = key ? (payload?.[key] ?? 0) : 0;
4648
if (val > 0) {
4749
hasAbove = true;
@@ -55,6 +57,7 @@ function createRoundedTopShape(currentKey: StackKey) {
5557
}
5658

5759
export default function EmailChart({ days, domain }: EmailChartProps) {
60+
const [selectedMetrics, setSelectedMetrics] = React.useState<StackKey[]>([]);
5861
const domainId = domain ? Number(domain) : undefined;
5962
const statusQuery = api.dashboard.emailTimeSeries.useQuery({
6063
days: days,
@@ -63,6 +66,32 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
6366

6467
const currentColors = useColors();
6568

69+
const metricMeta: Record<StackKey, { label: string; color: string }> = {
70+
delivered: { label: "Delivered", color: currentColors.delivered },
71+
bounced: { label: "Bounced", color: currentColors.bounced },
72+
complained: { label: "Complained", color: currentColors.complained },
73+
opened: { label: "Opened", color: currentColors.opened },
74+
clicked: { label: "Clicked", color: currentColors.clicked },
75+
};
76+
77+
const visibleMetrics =
78+
selectedMetrics.length === 0
79+
? STACK_ORDER
80+
: STACK_ORDER.filter((key) => selectedMetrics.includes(key));
81+
82+
const toggleMetric = (metric: StackKey) => {
83+
setSelectedMetrics((prev) => {
84+
const exists = prev.includes(metric);
85+
86+
if (exists) {
87+
return prev.filter((key) => key !== metric);
88+
}
89+
90+
const nextSet = new Set([...prev, metric]);
91+
return STACK_ORDER.filter((key) => nextSet.has(key));
92+
});
93+
};
94+
6695
return (
6796
<div className="flex flex-col gap-16">
6897
{!statusQuery.isLoading && statusQuery.data ? (
@@ -75,6 +104,8 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
75104
status={"total"}
76105
count={statusQuery.data.totalCounts.sent}
77106
percentage={100}
107+
isActive={selectedMetrics.length === 0}
108+
isClickable={false}
78109
/>
79110
<EmailChartItem
80111
status={EmailStatus.DELIVERED}
@@ -83,6 +114,11 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
83114
statusQuery.data.totalCounts.delivered /
84115
statusQuery.data.totalCounts.sent
85116
}
117+
isActive={
118+
selectedMetrics.length === 0 ||
119+
selectedMetrics.includes("delivered")
120+
}
121+
onClick={() => toggleMetric("delivered")}
86122
/>
87123
<EmailChartItem
88124
status={EmailStatus.BOUNCED}
@@ -91,6 +127,11 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
91127
statusQuery.data.totalCounts.bounced /
92128
statusQuery.data.totalCounts.sent
93129
}
130+
isActive={
131+
selectedMetrics.length === 0 ||
132+
selectedMetrics.includes("bounced")
133+
}
134+
onClick={() => toggleMetric("bounced")}
94135
/>
95136
<EmailChartItem
96137
status={EmailStatus.COMPLAINED}
@@ -99,6 +140,11 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
99140
statusQuery.data.totalCounts.complained /
100141
statusQuery.data.totalCounts.sent
101142
}
143+
isActive={
144+
selectedMetrics.length === 0 ||
145+
selectedMetrics.includes("complained")
146+
}
147+
onClick={() => toggleMetric("complained")}
102148
/>
103149
<EmailChartItem
104150
status={EmailStatus.CLICKED}
@@ -107,6 +153,11 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
107153
statusQuery.data.totalCounts.clicked /
108154
statusQuery.data.totalCounts.sent
109155
}
156+
isActive={
157+
selectedMetrics.length === 0 ||
158+
selectedMetrics.includes("clicked")
159+
}
160+
onClick={() => toggleMetric("clicked")}
110161
/>
111162
<EmailChartItem
112163
status={EmailStatus.OPENED}
@@ -115,6 +166,11 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
115166
statusQuery.data.totalCounts.opened /
116167
statusQuery.data.totalCounts.sent
117168
}
169+
isActive={
170+
selectedMetrics.length === 0 ||
171+
selectedMetrics.includes("opened")
172+
}
173+
onClick={() => toggleMetric("opened")}
118174
/>
119175
</div>
120176
</div>
@@ -157,11 +213,10 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
157213
if (!data) return null;
158214

159215
const hasAnyData =
160-
(data.delivered || 0) > 0 ||
161-
(data.bounced || 0) > 0 ||
162-
(data.complained || 0) > 0 ||
163-
(data.opened || 0) > 0 ||
164-
(data.clicked || 0) > 0;
216+
visibleMetrics.reduce(
217+
(sum, key) => sum + (data[key] || 0),
218+
0,
219+
) > 0;
165220

166221
if (!hasAnyData) return null;
167222

@@ -170,105 +225,43 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
170225
<p className="text-sm text-muted-foreground">
171226
{data.date}
172227
</p>
173-
{data.delivered ? (
174-
<div className="flex gap-2 items-center">
175-
<div
176-
className="w-2.5 h-2.5 rounded-[2px]"
177-
style={{ backgroundColor: currentColors.delivered }}
178-
></div>
179-
<p className="text-xs text-muted-foreground w-[70px]">
180-
Delivered
181-
</p>
182-
<p className="text-xs font-mono">{data.delivered}</p>
183-
</div>
184-
) : null}
185-
{data.bounced ? (
186-
<div className="flex gap-2 items-center">
187-
<div
188-
className="w-2.5 h-2.5 rounded-[2px]"
189-
style={{ backgroundColor: currentColors.bounced }}
190-
></div>
191-
<p className="text-xs text-muted-foreground w-[70px]">
192-
Bounced
193-
</p>
194-
<p className="text-xs font-mono">{data.bounced}</p>
195-
</div>
196-
) : null}
197-
{data.complained ? (
198-
<div className="flex gap-2 items-center">
199-
<div
200-
className="w-2.5 h-2.5 rounded-[2px]"
201-
style={{
202-
backgroundColor: currentColors.complained,
203-
}}
204-
></div>
205-
<p className="text-xs text-muted-foreground w-[70px]">
206-
Complained
207-
</p>
208-
<p className="text-xs font-mono">{data.complained}</p>
209-
</div>
210-
) : null}
211-
{data.opened ? (
212-
<div className="flex gap-2 items-center">
213-
<div
214-
className="w-2.5 h-2.5 rounded-[2px]"
215-
style={{ backgroundColor: currentColors.opened }}
216-
></div>
217-
<p className="text-xs text-muted-foreground w-[70px]">
218-
Opened
219-
</p>
220-
<p className="text-xs font-mono">{data.opened}</p>
221-
</div>
222-
) : null}
223-
{data.clicked ? (
224-
<div className="flex gap-2 items-center">
228+
{visibleMetrics.map((metricKey) => {
229+
const metricValue = data[metricKey] || 0;
230+
if (!metricValue) return null;
231+
232+
return (
225233
<div
226-
className="w-2.5 h-2.5 rounded-[2px]"
227-
style={{ backgroundColor: currentColors.clicked }}
228-
></div>
229-
<p className="text-xs text-muted-foreground w-[70px]">
230-
Clicked
231-
</p>
232-
<p className="text-xs font-mono">{data.clicked}</p>
233-
</div>
234-
) : null}
234+
key={metricKey}
235+
className="flex gap-2 items-center"
236+
>
237+
<div
238+
className="w-2.5 h-2.5 rounded-[2px]"
239+
style={{
240+
backgroundColor: metricMeta[metricKey].color,
241+
}}
242+
></div>
243+
<p className="text-xs text-muted-foreground w-[70px]">
244+
{metricMeta[metricKey].label}
245+
</p>
246+
<p className="text-xs font-mono">{metricValue}</p>
247+
</div>
248+
);
249+
})}
235250
</div>
236251
);
237252
}}
238253
cursor={false}
239254
/>
240-
{/* <Legend /> */}
241-
<Bar
242-
barSize={20}
243-
dataKey="delivered"
244-
stackId="a"
245-
fill={currentColors.delivered}
246-
shape={createRoundedTopShape("delivered")}
247-
/>
248-
<Bar
249-
dataKey="bounced"
250-
stackId="a"
251-
fill={currentColors.bounced}
252-
shape={createRoundedTopShape("bounced")}
253-
/>
254-
<Bar
255-
dataKey="complained"
256-
stackId="a"
257-
fill={currentColors.complained}
258-
shape={createRoundedTopShape("complained")}
259-
/>
260-
<Bar
261-
dataKey="opened"
262-
stackId="a"
263-
fill={currentColors.opened}
264-
shape={createRoundedTopShape("opened")}
265-
/>
266-
<Bar
267-
dataKey="clicked"
268-
stackId="a"
269-
fill={currentColors.clicked}
270-
shape={createRoundedTopShape("clicked")}
271-
/>
255+
{visibleMetrics.map((metricKey) => (
256+
<Bar
257+
key={metricKey}
258+
barSize={20}
259+
dataKey={metricKey}
260+
stackId="a"
261+
fill={metricMeta[metricKey].color}
262+
shape={createRoundedTopShape(metricKey, visibleMetrics)}
263+
/>
264+
))}
272265
</BarChart>
273266
</ResponsiveContainer>
274267
</div>
@@ -283,6 +276,9 @@ type DashboardItemCardProps = {
283276
status: EmailStatus | "total";
284277
count: number;
285278
percentage: number;
279+
onClick?: () => void;
280+
isActive?: boolean;
281+
isClickable?: boolean;
286282
};
287283

288284
const DashboardItemCard: React.FC<DashboardItemCardProps> = ({
@@ -314,6 +310,9 @@ const EmailChartItem: React.FC<DashboardItemCardProps> = ({
314310
status,
315311
count,
316312
percentage,
313+
onClick,
314+
isActive = false,
315+
isClickable = true,
317316
}) => {
318317
const currentColors = useColors();
319318

@@ -336,7 +335,17 @@ const EmailChartItem: React.FC<DashboardItemCardProps> = ({
336335
};
337336

338337
return (
339-
<div className="flex gap-3 items-stretch font-mono">
338+
<button
339+
type="button"
340+
onClick={onClick}
341+
disabled={!isClickable}
342+
aria-pressed={isClickable ? isActive : undefined}
343+
className={`flex gap-3 items-stretch font-mono transition-opacity ${
344+
isClickable ? "cursor-pointer" : "cursor-default"
345+
} ${isActive ? "opacity-100" : "opacity-45 hover:opacity-100"} ${
346+
isClickable ? "" : "pointer-events-none"
347+
}`}
348+
>
340349
<div>
341350
<div className=" flex items-center gap-2">
342351
<div
@@ -357,6 +366,6 @@ const EmailChartItem: React.FC<DashboardItemCardProps> = ({
357366
</span>
358367
</div>
359368
</div>
360-
</div>
369+
</button>
361370
);
362371
};

0 commit comments

Comments
 (0)