-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New Netmanager: The Analytics Page and the export-data page #2386
Changes from 24 commits
582956b
4a389cd
cdf7dd1
16907e9
38414d0
f0e1082
d064627
fed335d
47918d8
cb2cacd
c083efd
0c61f6c
937ff70
fb60179
22a8f5a
9e770c4
c6a3e70
38cced5
ad11f3b
77e08c9
56c6c79
78b61ba
088b612
ccb453c
4cbcd2c
2a6bec6
ab4baa9
da659a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,8 @@ | ||
import React from "react"; | ||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; | ||
import React from 'react' | ||
import Analytics from '@/components/Analytics' | ||
|
||
export default function Analytics() { | ||
export default function AnalyticsPage() { | ||
return ( | ||
<div className="p-6"> | ||
<h1 className="text-3xl font-semibold text-foreground mb-6">Dashboard</h1> | ||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> | ||
<Card> | ||
<CardHeader> | ||
<CardTitle>Total Devices</CardTitle> | ||
</CardHeader> | ||
<CardContent> | ||
<p className="text-4xl font-bold">156</p> | ||
</CardContent> | ||
</Card> | ||
<Card> | ||
<CardHeader> | ||
<CardTitle>Active Sites</CardTitle> | ||
</CardHeader> | ||
<CardContent> | ||
<p className="text-4xl font-bold">42</p> | ||
</CardContent> | ||
</Card> | ||
<Card> | ||
<CardHeader> | ||
<CardTitle>Data Points Collected</CardTitle> | ||
</CardHeader> | ||
<CardContent> | ||
<p className="text-4xl font-bold">1.2M</p> | ||
</CardContent> | ||
</Card> | ||
</div> | ||
</div> | ||
); | ||
<Analytics /> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
'use client' | ||
|
||
import { useState } from 'react' | ||
import { Card } from "@/components/ui/card" | ||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" | ||
import ExportForm from '@/components/export-data/ExportForm' | ||
import { ExportType } from '@/app/types/export' | ||
|
||
export default function ExportData() { | ||
const [activeTab, setActiveTab] = useState<ExportType>('sites') | ||
|
||
return ( | ||
<div className="container mx-auto p-4"> | ||
<h1 className="text-2xl font-bold text-center mb-4">Export Data</h1> | ||
<p className="mb-4 text-center text-gray-600"> | ||
Customize the data you want to download. We recommend downloading data for shorter time | ||
periods like a week or a month to avoid timeouts. | ||
</p> | ||
<Card> | ||
<Tabs value={activeTab} onValueChange={(value) => setActiveTab(value as ExportType)}> | ||
<TabsList className="grid w-full grid-cols-4"> | ||
<TabsTrigger value="sites">Sites</TabsTrigger> | ||
<TabsTrigger value="devices">Devices</TabsTrigger> | ||
<TabsTrigger value="airqlouds">AirQlouds</TabsTrigger> | ||
</TabsList> | ||
<TabsContent value={activeTab}> | ||
<ExportForm exportType={activeTab} /> | ||
</TabsContent> | ||
</Tabs> | ||
</Card> | ||
</div> | ||
) | ||
} | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,74 @@ | ||||||
export interface Cohort { | ||||||
_id: string; | ||||||
visibility: boolean; | ||||||
cohort_tags: string[]; | ||||||
cohort_codes: string[]; | ||||||
name: string; | ||||||
network: string; | ||||||
groups: string[]; | ||||||
numberOfDevices: number; | ||||||
devices: Device[]; | ||||||
} | ||||||
|
||||||
interface Grid { | ||||||
_id: string; | ||||||
visibility: boolean; | ||||||
name: string; | ||||||
admin_level: string; | ||||||
network: string; | ||||||
long_name: string; | ||||||
createdAt: string; | ||||||
sites: Site[]; | ||||||
} | ||||||
|
||||||
interface Site { | ||||||
_id: string; | ||||||
isOnline: boolean; | ||||||
formatted_name: string; | ||||||
location_name: string; | ||||||
search_name: string; | ||||||
city: string; | ||||||
district: string; | ||||||
county: string; | ||||||
region: string; | ||||||
country: string; | ||||||
latitude: number; | ||||||
longitude: number; | ||||||
name: string; | ||||||
approximate_latitude: number; | ||||||
approximate_longitude: number; | ||||||
generated_name: string; | ||||||
data_provider: string; | ||||||
description: string; | ||||||
site_category: SiteCategory; | ||||||
groups: string[]; | ||||||
grids: Grid[]; | ||||||
devices: Device[]; | ||||||
airqlouds: unknown[]; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Specify the type for airqlouds array. Using - airqlouds: unknown[];
+ airqlouds: Array<{ _id: string; name: string; /* other properties */ }>; 📝 Committable suggestion
Suggested change
|
||||||
createdAt: string; | ||||||
updatedAt?: string; | ||||||
} | ||||||
|
||||||
interface SiteCategory { | ||||||
tags: string[]; | ||||||
area_name: string; | ||||||
category: string; | ||||||
highway: string; | ||||||
landuse: string; | ||||||
latitude: number; | ||||||
longitude: number; | ||||||
natural: string; | ||||||
search_radius: number; | ||||||
waterway: string; | ||||||
} | ||||||
|
||||||
interface Device { | ||||||
_id: string; | ||||||
name: string; | ||||||
network: string; | ||||||
groups: string[]; | ||||||
authRequired: boolean; | ||||||
serial_number: string; | ||||||
api_code: string; | ||||||
long_name: string; | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { Option } from "@/components/ui/multi-select" | ||
|
||
export type ExportType = 'sites' | 'devices' | 'airqlouds' | 'regions' | ||
|
||
export interface FormData { | ||
startDateTime: string; | ||
endDateTime: string; | ||
sites?: Option[]; | ||
devices?: Option[]; | ||
cities?: Option[]; | ||
regions?: Option[]; | ||
pollutants?: Option[]; | ||
frequency: string | ||
fileType: string | ||
outputFormat: string | ||
dataType: string | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
export interface Grid { | ||
_id: string; | ||
visibility: boolean; | ||
name: string; | ||
admin_level: string; | ||
network: string; | ||
long_name: string; | ||
createdAt: string; | ||
sites: Site[]; | ||
} | ||
Comment on lines
+1
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider breaking potential circular dependency between Grid and Site interfaces The Consider:
+interface GridReference {
+ _id: string;
+ name: string;
+ long_name: string;
+}
interface Site {
// ... other fields ...
- grids: Grid[];
+ grids: GridReference[];
} Also applies to: 12-38 |
||
|
||
interface Site { | ||
_id: string; | ||
isOnline: boolean; | ||
formatted_name: string; | ||
location_name: string; | ||
search_name: string; | ||
city: string; | ||
district: string; | ||
county: string; | ||
region: string; | ||
country: string; | ||
latitude: number; | ||
longitude: number; | ||
name: string; | ||
approximate_latitude: number; | ||
approximate_longitude: number; | ||
generated_name: string; | ||
data_provider: string; | ||
description: string; | ||
site_category: SiteCategory; | ||
groups: string[]; | ||
grids: Grid[]; | ||
devices: Device[]; | ||
airqlouds: unknown[]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve type safety for airqlouds array The Consider defining an - airqlouds: unknown[];
+ airqlouds: Airqloud[]; Also applies to: 12-38 |
||
createdAt: string; | ||
updatedAt?: string; | ||
} | ||
|
||
interface SiteCategory { | ||
tags: string[]; | ||
area_name: string; | ||
category: string; | ||
highway: string; | ||
landuse: string; | ||
latitude: number; | ||
longitude: number; | ||
natural: string; | ||
search_radius: number; | ||
waterway: string; | ||
} | ||
|
||
export interface Device { | ||
_id: string; | ||
group: string; | ||
authRequired: boolean; | ||
serial_number: string; | ||
api_code: string; | ||
groups: string[]; | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,81 @@ | ||||||||||||||||||||||||||||||||||||||||||||
'use client' | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
import React, { useState } from 'react' | ||||||||||||||||||||||||||||||||||||||||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" | ||||||||||||||||||||||||||||||||||||||||||||
import { Grid } from '@/app/types/grids' | ||||||||||||||||||||||||||||||||||||||||||||
import { Cohort } from '@/app/types/cohorts' | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
interface AnalyticsAirqloudsDropDownProps { | ||||||||||||||||||||||||||||||||||||||||||||
isCohort: boolean | ||||||||||||||||||||||||||||||||||||||||||||
airqloudsData?: Cohort[] | Grid[] | ||||||||||||||||||||||||||||||||||||||||||||
onSelect: (id: string) => void | ||||||||||||||||||||||||||||||||||||||||||||
selectedId: string | null | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
const AnalyticsAirqloudsDropDown =({ | ||||||||||||||||||||||||||||||||||||||||||||
isCohort, | ||||||||||||||||||||||||||||||||||||||||||||
airqloudsData = [], | ||||||||||||||||||||||||||||||||||||||||||||
onSelect, | ||||||||||||||||||||||||||||||||||||||||||||
selectedId | ||||||||||||||||||||||||||||||||||||||||||||
}: AnalyticsAirqloudsDropDownProps) => { | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
const handleAirqloudChange = (value: string) => { | ||||||||||||||||||||||||||||||||||||||||||||
const selectedAirqloud = airqloudsData.find((a) => a._id === value); | ||||||||||||||||||||||||||||||||||||||||||||
if (selectedAirqloud) { | ||||||||||||||||||||||||||||||||||||||||||||
const storageKey = isCohort ? "activeCohort" : "activeGrid"; | ||||||||||||||||||||||||||||||||||||||||||||
localStorage.setItem(storageKey, JSON.stringify(selectedAirqloud)); | ||||||||||||||||||||||||||||||||||||||||||||
onSelect(value); | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+22
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add error handling for localStorage operations. The const handleAirqloudChange = (value: string) => {
const selectedAirqloud = airqloudsData.find((a) => a._id === value);
if (selectedAirqloud) {
const storageKey = isCohort ? "activeCohort" : "activeGrid";
- localStorage.setItem(storageKey, JSON.stringify(selectedAirqloud));
- onSelect(value);
+ try {
+ localStorage.setItem(storageKey, JSON.stringify(selectedAirqloud));
+ onSelect(value);
+ } catch (error) {
+ console.error('Failed to save selection to localStorage:', error);
+ onSelect(value); // Still update the UI even if storage fails
+ }
}
}; 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
const formatString = (string: string) => { | ||||||||||||||||||||||||||||||||||||||||||||
return string | ||||||||||||||||||||||||||||||||||||||||||||
.replace(/_/g, ' ') | ||||||||||||||||||||||||||||||||||||||||||||
.replace(/\w\S*/g, (txt) => { | ||||||||||||||||||||||||||||||||||||||||||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() | ||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||
.replace('Id', 'ID') | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||
<div className="relative w-full"> | ||||||||||||||||||||||||||||||||||||||||||||
{airqloudsData.length === 0 ? ( | ||||||||||||||||||||||||||||||||||||||||||||
<p className="text-gray-500">{isCohort ? 'No Cohorts' : 'No Grids'} data available</p> | ||||||||||||||||||||||||||||||||||||||||||||
) : ( | ||||||||||||||||||||||||||||||||||||||||||||
<Select onValueChange={handleAirqloudChange} value={selectedId || undefined}> | ||||||||||||||||||||||||||||||||||||||||||||
<SelectTrigger className="w-full h-11 bg-white border-gray-200 text-blue-600 font-bold capitalize"> | ||||||||||||||||||||||||||||||||||||||||||||
<SelectValue placeholder={`Select a ${isCohort ? 'Cohort' : 'Grid'}`}> | ||||||||||||||||||||||||||||||||||||||||||||
{selectedId && formatString(airqloudsData.find(a => a._id === selectedId)?.name || '')} | ||||||||||||||||||||||||||||||||||||||||||||
</SelectValue> | ||||||||||||||||||||||||||||||||||||||||||||
</SelectTrigger> | ||||||||||||||||||||||||||||||||||||||||||||
<SelectContent> | ||||||||||||||||||||||||||||||||||||||||||||
{airqloudsData.map((airqloud) => ( | ||||||||||||||||||||||||||||||||||||||||||||
<SelectItem | ||||||||||||||||||||||||||||||||||||||||||||
key={airqloud._id} | ||||||||||||||||||||||||||||||||||||||||||||
value={airqloud._id} | ||||||||||||||||||||||||||||||||||||||||||||
className="cursor-pointer" | ||||||||||||||||||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||||||||||||||||||
<div className="grid grid-cols-12 gap-2 w-full items-center"> | ||||||||||||||||||||||||||||||||||||||||||||
<div className="col-span-9"> | ||||||||||||||||||||||||||||||||||||||||||||
<span className="font-medium truncate ">{formatString(airqloud.name)}</span> | ||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||
<div className="col-span-3 text-sm text-gray-500 text-right"> | ||||||||||||||||||||||||||||||||||||||||||||
<span className=" text-sm text-gray-500 text-right"> | ||||||||||||||||||||||||||||||||||||||||||||
{isCohort | ||||||||||||||||||||||||||||||||||||||||||||
? 'devices' in airqloud | ||||||||||||||||||||||||||||||||||||||||||||
? `${airqloud.devices?.length || 0} devices` | ||||||||||||||||||||||||||||||||||||||||||||
: '' | ||||||||||||||||||||||||||||||||||||||||||||
: 'sites' in airqloud ? `${airqloud.sites?.length || 0} sites` : ''} | ||||||||||||||||||||||||||||||||||||||||||||
</span> | ||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||
</SelectItem> | ||||||||||||||||||||||||||||||||||||||||||||
))} | ||||||||||||||||||||||||||||||||||||||||||||
</SelectContent> | ||||||||||||||||||||||||||||||||||||||||||||
</Select> | ||||||||||||||||||||||||||||||||||||||||||||
)} | ||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
export default AnalyticsAirqloudsDropDown; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Export the Grid interface.
The Grid interface should be exported as it's likely to be used in other files.
📝 Committable suggestion