+ );
+}
diff --git a/netmanager-app/app/(authenticated)/sites/create-site-form.tsx b/netmanager-app/app/(authenticated)/sites/create-site-form.tsx
index d0a3a137fb..f5dfed78b5 100644
--- a/netmanager-app/app/(authenticated)/sites/create-site-form.tsx
+++ b/netmanager-app/app/(authenticated)/sites/create-site-form.tsx
@@ -1,7 +1,6 @@
"use client";
import { useState } from "react";
-import { useRouter } from "next/navigation";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
@@ -26,7 +25,7 @@ import {
DialogTrigger,
} from "@/components/ui/dialog";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Loader2, MapPin, Check } from "lucide-react";
+import { Loader2, Check } from "lucide-react";
import { cn } from "@/lib/utils";
import { sites } from "@/core/apis/sites";
import { useAppSelector } from "@/core/redux/hooks";
@@ -35,8 +34,6 @@ import { MapContainer, TileLayer, Marker, useMap } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import L from "leaflet";
import { useApproximateCoordinates } from "@/core/hooks/useSites";
-import { AxiosError } from "axios";
-import Error from "next/error";
const siteFormSchema = z.object({
name: z.string().min(2, {
diff --git a/netmanager-app/app/types/grids.ts b/netmanager-app/app/types/grids.ts
index d1c4bc2478..fd87641892 100644
--- a/netmanager-app/app/types/grids.ts
+++ b/netmanager-app/app/types/grids.ts
@@ -1,3 +1,4 @@
+import { Position } from "@/core/redux/slices/gridsSlice";
import { Site } from "./sites";
export interface CreateGrid {
@@ -5,7 +6,7 @@ export interface CreateGrid {
admin_level: string;
shape: {
type: "MultiPolygon" | "Polygon";
- coordinates: number[][][][];
+ coordinates: Position[][] | Position[][][];
};
network: string;
}
diff --git a/netmanager-app/components/Analytics/index.tsx b/netmanager-app/components/Analytics/index.tsx
index e40875704a..dc6e0be80b 100644
--- a/netmanager-app/components/Analytics/index.tsx
+++ b/netmanager-app/components/Analytics/index.tsx
@@ -20,7 +20,7 @@ import { useToast } from "@/components/ui/use-toast";
import { useAppSelector } from "@/core/redux/hooks";
import { Cohort } from "@/app/types/cohorts";
import { Grid } from "@/app/types/grids";
-import { useDevices } from "@/core/hooks/useDevices";
+import { useMapReadings } from "@/core/hooks/useDevices";
import { transformDataToGeoJson } from "@/lib/utils";
const NewAnalytics: React.FC = () => {
@@ -43,7 +43,7 @@ const NewAnalytics: React.FC = () => {
activeNetwork?.net_name ?? ""
);
const { cohorts, isLoading: isCohortsLoading } = useCohorts();
- const { mapReadings, isLoading: isReadingsLoading } = useDevices();
+ const { mapReadings, isLoading } = useMapReadings();
const airqloudsData = isCohort ? cohorts : grids;
diff --git a/netmanager-app/components/Settings/MyProfile.tsx b/netmanager-app/components/Settings/MyProfile.tsx
index dc7b2a663a..966f11e48f 100644
--- a/netmanager-app/components/Settings/MyProfile.tsx
+++ b/netmanager-app/components/Settings/MyProfile.tsx
@@ -58,7 +58,8 @@ export default function MyProfile() {
setError(null);
try {
- const userData = currentUser;
+ const response = await users.getUserDetails(currentUser._id);
+ const userData = response?.users?.[0];
if (userData) {
setProfile({
diff --git a/netmanager-app/components/cohorts/assign-cohort-devices.tsx b/netmanager-app/components/cohorts/assign-cohort-devices.tsx
new file mode 100644
index 0000000000..4e40487990
--- /dev/null
+++ b/netmanager-app/components/cohorts/assign-cohort-devices.tsx
@@ -0,0 +1,155 @@
+"use client";
+
+import { useState } from "react";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useForm } from "react-hook-form";
+import * as z from "zod";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from "@/components/ui/form";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/components/ui/command";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { Check, ChevronsUpDown } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+const devices = [
+ { value: "aq_40", label: "Aq_40" },
+ { value: "aq_41", label: "Aq_41" },
+ { value: "aq_42", label: "Aq_42" },
+ { value: "airqo_g5364", label: "Airqo_g5364" },
+];
+
+const formSchema = z.object({
+ devices: z.array(z.string()).min(1, {
+ message: "Please select at least one device.",
+ }),
+});
+
+export function AddDevicesDialog() {
+ const [open, setOpen] = useState(false);
+
+ const form = useForm>({
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ devices: [],
+ },
+ });
+
+ function onSubmit(values: z.infer) {
+ console.log(values);
+ setOpen(false);
+ }
+
+ return (
+
+ );
+}
diff --git a/netmanager-app/components/cohorts/create-cohort.tsx b/netmanager-app/components/cohorts/create-cohort.tsx
new file mode 100644
index 0000000000..746847f0e9
--- /dev/null
+++ b/netmanager-app/components/cohorts/create-cohort.tsx
@@ -0,0 +1,197 @@
+"use client";
+
+import { useState } from "react";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useForm } from "react-hook-form";
+import * as z from "zod";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/components/ui/command";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { Check, ChevronsUpDown } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+const devices = [
+ { value: "aq_29", label: "Aq_29" },
+ { value: "aq_34", label: "Aq_34" },
+ { value: "aq_35", label: "Aq_35" },
+ { value: "airqo_g5363", label: "Airqo_g5363" },
+];
+
+const formSchema = z.object({
+ name: z.string().min(2, {
+ message: "Cohort name must be at least 2 characters.",
+ }),
+ network: z.string().min(1, {
+ message: "Please select a network.",
+ }),
+ devices: z.array(z.string()).min(1, {
+ message: "Please select at least one device.",
+ }),
+});
+
+export function CreateCohortDialog() {
+ const [open, setOpen] = useState(false);
+ // const [selectedDevices, setSelectedDevices] = useState([])
+
+ const form = useForm>({
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ name: "",
+ network: "airqo",
+ devices: [],
+ },
+ });
+
+ function onSubmit(values: z.infer) {
+ console.log(values);
+ setOpen(false);
+ }
+
+ return (
+
+ );
+}
diff --git a/netmanager-app/components/export-data/ExportForm.tsx b/netmanager-app/components/export-data/ExportForm.tsx
index e6fdd37de3..91af641bdd 100644
--- a/netmanager-app/components/export-data/ExportForm.tsx
+++ b/netmanager-app/components/export-data/ExportForm.tsx
@@ -3,7 +3,13 @@
import { useState, useEffect } from "react";
import { useForm, Controller } from "react-hook-form";
import { Button } from "@/components/ui/button";
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
import { MultiSelect, Option } from "react-multi-select-component";
import { useToast } from "@/components/ui/use-toast";
import { ExportType, FormData } from "@/app/types/export";
@@ -18,9 +24,9 @@ import { useAppSelector } from "@/core/redux/hooks";
import { Grid } from "@/app/types/grids";
const pollutantOptions = [
- { value: "pm2.5", label: "PM2.5" },
- { value: "pm10", label: "PM10" },
- { value: "no2", label: "NO2" },
+ { value: "pm2.5", label: "PM2.5" },
+ { value: "pm10", label: "PM10" },
+ { value: "no2", label: "NO2" },
];
interface ExportFormProps {
@@ -48,7 +54,6 @@ const options = {
],
};
-
export const roundToEndOfDay = (dateISOString: string): Date => {
const end = new Date(dateISOString);
end.setUTCHours(23, 59, 59, 999);
@@ -63,22 +68,25 @@ const getvalue = (options: Option[]): string => {
return options[0].value;
};
-
export const roundToStartOfDay = (dateISOString: string): Date => {
const start = new Date(dateISOString);
start.setUTCHours(0, 0, 0, 1);
return start;
};
-
export default function ExportForm({ exportType }: ExportFormProps) {
- const [loading, setLoading] = useState(false);
- const { sites } = useSites();
- const { grids } = useGrids();
- const { devices } = useDevices();
- const activeNetwork = useAppSelector((state) => state.user.activeNetwork);
-
- const { control, handleSubmit, formState: { errors }, reset } = useForm({
+ const [loading, setLoading] = useState(false);
+ const activeNetwork = useAppSelector((state) => state.user.activeNetwork);
+ const { sites } = useSites();
+ const { grids } = useGrids(activeNetwork?.net_name || "");
+ const { devices } = useDevices();
+
+ const {
+ control,
+ handleSubmit,
+ formState: { errors },
+ reset,
+ } = useForm({
defaultValues: {
startDateTime: undefined,
endDateTime: undefined,
@@ -99,32 +107,49 @@ export default function ExportForm({ exportType }: ExportFormProps) {
useEffect(() => {
let isSubscribed = true;
if (sites?.length) {
- const newSiteOptions = sites.map((site: Site) => ({ value: site._id, label: site.name }));
- if (isSubscribed && JSON.stringify(newSiteOptions) !== JSON.stringify(siteOptions)) {
+ const newSiteOptions = sites.map((site: Site) => ({
+ value: site._id,
+ label: site.name,
+ }));
+ if (
+ isSubscribed &&
+ JSON.stringify(newSiteOptions) !== JSON.stringify(siteOptions)
+ ) {
setSiteOptions(newSiteOptions);
}
-
}
if (grids?.length) {
- const newCityOptions = grids.map((grid: Grid) => ({ value: grid._id, label: grid.name }));
- if (isSubscribed && JSON.stringify(newCityOptions) !== JSON.stringify(cityOptions)) {
+ const newCityOptions = grids.map((grid: Grid) => ({
+ value: grid._id,
+ label: grid.name,
+ }));
+ if (
+ isSubscribed &&
+ JSON.stringify(newCityOptions) !== JSON.stringify(cityOptions)
+ ) {
setCityOptions(newCityOptions);
}
}
-
+
if (devices?.length) {
- const newDeviceOptions = devices.map((device: Device) => ({ value: device._id, label: device.name }));
- if (isSubscribed && JSON.stringify(newDeviceOptions) !== JSON.stringify(deviceOptions)) {
+ const newDeviceOptions = devices.map((device: Device) => ({
+ value: device._id,
+ label: device.name,
+ }));
+ if (
+ isSubscribed &&
+ JSON.stringify(newDeviceOptions) !== JSON.stringify(deviceOptions)
+ ) {
setDeviceOptions(newDeviceOptions);
}
}
return () => {
- isSubscribed = false;
+ isSubscribed = false;
};
}, [sites, grids, devices]);
const exportData = (data: string, filename: string, type: string) => {
- const sanitizedFilename = filename.replace(/[^a-zA-Z0-9.-]/g, '_');
+ const sanitizedFilename = filename.replace(/[^a-zA-Z0-9.-]/g, "_");
const blob = new Blob([data], { type });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
@@ -144,13 +169,13 @@ export default function ExportForm({ exportType }: ExportFormProps) {
if (response) {
if (data.fileType === "csv") {
if (typeof response !== "string") {
- throw new Error('Invalid CSV data format.');
+ throw new Error("Invalid CSV data format.");
}
exportData(response, filename, "text/csv;charset=utf-8;");
}
if (data.fileType === "json") {
- const jsonString = JSON.stringify(response.data)
+ const jsonString = JSON.stringify(response.data);
exportData(jsonString, filename, "application/json");
}
@@ -158,41 +183,42 @@ export default function ExportForm({ exportType }: ExportFormProps) {
title: "Data exported successfully",
description: "Your data has been exported and is ready for download.",
variant: "success",
- })
-
+ });
} else {
toast({
title: "Error exporting data",
- description: "An error occurred while exporting your data. Please try again later.",
+ description:
+ "An error occurred while exporting your data. Please try again later.",
variant: "destructive",
});
}
-
} catch (error: any) {
console.error("Error exporting data", error);
- let errorMessage
+ let errorMessage;
if (error.response) {
-
if (error.response.status >= 500) {
- errorMessage = "An error occurred while exporting your data. Please try again later.";
-
+ errorMessage =
+ "An error occurred while exporting your data. Please try again later.";
} else {
- if (error.response.data.status === 'success') {
+ if (error.response.data.status === "success") {
toast({
title: "Error exporting data",
- description: 'No data found for the selected parameters',
+ description: "No data found for the selected parameters",
variant: "default",
- })
+ });
return;
}
- errorMessage = typeof error.response.data.message === 'string' ? error.response.data : 'An error occurred while downloading data';
+ errorMessage =
+ typeof error.response.data.message === "string"
+ ? error.response.data
+ : "An error occurred while downloading data";
}
-
- }else if (error.request) {
- errorMessage = 'No response received from server';;
+ } else if (error.request) {
+ errorMessage = "No response received from server";
} else {
- errorMessage = 'An error occurred while exporting your data. Please try again later.';
+ errorMessage =
+ "An error occurred while exporting your data. Please try again later.";
}
toast({
@@ -203,13 +229,11 @@ export default function ExportForm({ exportType }: ExportFormProps) {
} finally {
setLoading(false);
}
-
};
-
const onSubmit = async (data: any) => {
setLoading(true);
-
+
if (data.startDateTime > data.endDateTime) {
toast({
title: "Invalid date range",
@@ -219,40 +243,41 @@ export default function ExportForm({ exportType }: ExportFormProps) {
setLoading(false);
return;
}
-
- const Difference_In_Time = new Date(data.endDateTime).getTime() - new Date(data.startDateTime).getTime();
+
+ const Difference_In_Time =
+ new Date(data.endDateTime).getTime() -
+ new Date(data.startDateTime).getTime();
const Difference_In_Days = Difference_In_Time / (1000 * 3600 * 24);
-
+
if (Difference_In_Days > 28) {
toast({
title: "Invalid date range",
- description: "For time periods greater than a month, please reduce the time difference to a week to avoid timeouts!",
+ description:
+ "For time periods greater than a month, please reduce the time difference to a week to avoid timeouts!",
variant: "default",
});
setLoading(false);
return;
}
-
+
let body: FormData = {
startDateTime: roundToStartOfDay(data.startDateTime).toISOString(),
endDateTime: roundToEndOfDay(data.endDateTime).toISOString(),
- sites: getValues(data.sites),
+ sites: getValues(data.sites),
devices: getValues(data.devices || []),
cities: getValues(data.cities || []),
regions: getValues(data.regions || []),
- network: activeNetwork?.net_name || '',
+ network: activeNetwork?.net_name || "",
dataType: getvalue(data.dataType || []),
pollutants: getValues(data.pollutants),
- frequency:data.frequency,
+ frequency: data.frequency,
fileType: data.fileType,
outputFormat: data.outputFormat,
minimum: true,
};
-
+
await downloadData(body);
};
-
-
const renderSelect = (
name: keyof FormData,
@@ -311,11 +336,17 @@ export default function ExportForm({ exportType }: ExportFormProps) {
const renderFieldBasedOnTab = () => {
switch (exportType) {
case "sites":
- return renderMultiSelect("sites", siteOptions, "Select sites", { required: "At least one site must be selected" });
+ return renderMultiSelect("sites", siteOptions, "Select sites", {
+ required: "At least one site must be selected",
+ });
case "devices":
- return renderMultiSelect("devices", deviceOptions, "Select devices", { required: "At least one device must be selected" });
+ return renderMultiSelect("devices", deviceOptions, "Select devices", {
+ required: "At least one device must be selected",
+ });
case "airqlouds":
- return renderMultiSelect("cities", cityOptions, "Select Grids", { required: "At least one Grids must be selected" });
+ return renderMultiSelect("cities", cityOptions, "Select Grids", {
+ required: "At least one Grids must be selected",
+ });
default:
return null;
}
@@ -329,8 +360,11 @@ export default function ExportForm({ exportType }: ExportFormProps) {
control={control}
rules={{ required: "Start date is required" }}
render={({ field }) => (
-
-
+
)}
/>
(
-
-
+
)}
/>
{renderFieldBasedOnTab()}
- {renderMultiSelect("pollutants", pollutantOptions, "Select pollutants", { required: "At least one pollutant must be selected" })}
+ {renderMultiSelect(
+ "pollutants",
+ pollutantOptions,
+ "Select pollutants",
+ { required: "At least one pollutant must be selected" }
+ )}
- {renderSelect("frequency", options.frequency, "Select frequency", { required: "Frequency is required" })}
- {renderSelect("fileType", options.fileType, "Select file type", { required: "File type is required" })}
+ {renderSelect("frequency", options.frequency, "Select frequency", {
+ required: "Frequency is required",
+ })}
+ {renderSelect("fileType", options.fileType, "Select file type", {
+ required: "File type is required",
+ })}
- {renderSelect("outputFormat", options.outputFormat, "Select output format", { required: "Output format is required" })}
- {renderSelect("dataType", options.dataType, "Select data type", { required: "Data type is required" })}
+ {renderSelect(
+ "outputFormat",
+ options.outputFormat,
+ "Select output format",
+ { required: "Output format is required" }
+ )}
+ {renderSelect("dataType", options.dataType, "Select data type", {
+ required: "Data type is required",
+ })}