geo
This commit is contained in:
@@ -27,7 +27,7 @@ export default function GeoAnalytics({ data }: GeoAnalyticsProps) {
|
|||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Location
|
IP Address
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Visits
|
Visits
|
||||||
@@ -41,30 +41,38 @@ export default function GeoAnalytics({ data }: GeoAnalyticsProps) {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{sortedData.map((item, index) => (
|
{sortedData.length > 0 ? (
|
||||||
<tr key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
|
sortedData.map((item, index) => (
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
<tr key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
|
||||||
{item.location || 'Unknown'}
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
</td>
|
{item.location || 'Unknown'}
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
</td>
|
||||||
{formatNumber(item.visits)}
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
</td>
|
{formatNumber(item.visits)}
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
</td>
|
||||||
{formatNumber(item.visitors)}
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
</td>
|
{formatNumber(item.visitors)}
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
</td>
|
||||||
<div className="flex items-center">
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
<div className="w-24 bg-gray-200 rounded-full h-2">
|
<div className="flex items-center">
|
||||||
<div
|
<div className="w-24 bg-gray-200 rounded-full h-2">
|
||||||
className="bg-blue-600 h-2 rounded-full"
|
<div
|
||||||
style={{ width: `${item.percentage || 0}%` }}
|
className="bg-blue-600 h-2 rounded-full"
|
||||||
/>
|
style={{ width: `${item.percentage || 0}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2">{formatPercent(item.percentage)}%</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="ml-2">{formatPercent(item.percentage)}%</span>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={4} className="px-6 py-4 text-center text-sm text-gray-500">
|
||||||
|
No IP data available
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -763,7 +763,7 @@ export default function HomePage() {
|
|||||||
<div className="bg-white rounded-lg shadow p-6 mb-8">
|
<div className="bg-white rounded-lg shadow p-6 mb-8">
|
||||||
<h2 className="text-lg font-semibold text-gray-900 mb-4">Geographic Distribution</h2>
|
<h2 className="text-lg font-semibold text-gray-900 mb-4">Geographic Distribution</h2>
|
||||||
<GeoAnalytics data={geoData} />
|
<GeoAnalytics data={geoData} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -215,23 +215,22 @@ export async function getGeoAnalytics(params: {
|
|||||||
startTime?: string;
|
startTime?: string;
|
||||||
endTime?: string;
|
endTime?: string;
|
||||||
linkId?: string;
|
linkId?: string;
|
||||||
groupBy?: 'country' | 'city';
|
|
||||||
teamIds?: string[];
|
teamIds?: string[];
|
||||||
projectIds?: string[];
|
projectIds?: string[];
|
||||||
tagIds?: string[];
|
tagIds?: string[];
|
||||||
}): Promise<GeoData[]> {
|
}): Promise<GeoData[]> {
|
||||||
const filter = buildFilter(params);
|
const filter = buildFilter(params);
|
||||||
const groupByField = params.groupBy === 'city' ? 'city' : 'country';
|
|
||||||
|
|
||||||
|
// Use IP address as the grouping field
|
||||||
const query = `
|
const query = `
|
||||||
SELECT
|
SELECT
|
||||||
${groupByField} as location,
|
ip_address as location,
|
||||||
count() as visits,
|
count() as visits,
|
||||||
uniq(ip_address) as visitors,
|
uniq(ip_address) as visitors,
|
||||||
count() * 100.0 / sum(count()) OVER () as percentage
|
count() * 100.0 / sum(count()) OVER () as percentage
|
||||||
FROM events
|
FROM events
|
||||||
${filter}
|
${filter}
|
||||||
GROUP BY ${groupByField}
|
GROUP BY location
|
||||||
HAVING location != ''
|
HAVING location != ''
|
||||||
ORDER BY visits DESC
|
ORDER BY visits DESC
|
||||||
LIMIT 20
|
LIMIT 20
|
||||||
|
|||||||
Reference in New Issue
Block a user