+17
-5
frontend/src/components/StatisticsModal.tsx
+17
-5
frontend/src/components/StatisticsModal.tsx
···
10
10
} from "recharts";
11
11
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
12
12
import { toast } from "@/hooks/use-toast";
13
-
import { useState, useEffect } from "react";
13
+
import { useState, useEffect, useMemo } from "react";
14
14
15
15
import { getLinkClickStats, getLinkSourceStats } from "../api/client";
16
16
import { ClickStats, SourceStats } from "../types/api";
···
97
97
}
98
98
}, [isOpen, linkId]);
99
99
100
+
const aggregatedSources = useMemo(() => {
101
+
const sourceMap = sourcesData.reduce<Record<string, number>>(
102
+
(acc, { source, count }) => ({
103
+
...acc,
104
+
[source]: (acc[source] || 0) + count
105
+
}),
106
+
{}
107
+
);
108
+
109
+
return Object.entries(sourceMap)
110
+
.map(([source, count]) => ({ source, count }))
111
+
.sort((a, b) => b.count - a.count);
112
+
}, [sourcesData]);
113
+
100
114
return (
101
115
<Dialog open={isOpen} onOpenChange={onClose}>
102
116
<DialogContent className="max-w-3xl">
···
138
152
</CardHeader>
139
153
<CardContent>
140
154
<ul className="space-y-2">
141
-
{sourcesData.map((source, index) => (
155
+
{aggregatedSources.map((source, index) => (
142
156
<li
143
157
key={source.source}
144
158
className="flex items-center justify-between py-2 border-b last:border-0"
···
149
163
</span>
150
164
{source.source}
151
165
</span>
152
-
<span className="text-sm font-medium">
153
-
{source.count} clicks
154
-
</span>
166
+
<span className="text-sm font-medium">{source.count} clicks</span>
155
167
</li>
156
168
))}
157
169
</ul>