Skip to content

Commit

Permalink
Merge pull request #292 from COS301-SE-2024/feat/web/Settings-page
Browse files Browse the repository at this point in the history
Feat/web/settings page
  • Loading branch information
Tinashe-Austin authored Aug 12, 2024
2 parents 415d071 + f1c56a8 commit debc271
Show file tree
Hide file tree
Showing 42 changed files with 1,945 additions and 537 deletions.
Binary file modified frontend/occupi-web/bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions frontend/occupi-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
"jest": "^29.7.0",
"jest-canvas-mock": "^2.5.2",
"jest-transform-stub": "^2.0.0",
"msw": "^2.3.5",
"postcss": "^8.4.39",
"react-use-cookie": "^1.6.1",
"tailwindcss-animate": "^1.0.7",
Expand Down
77 changes: 31 additions & 46 deletions frontend/occupi-web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,9 @@
import {
LoginForm,
OtpPage,
Settings,
Dashboard,
Analysis,
Visitation,
Faq,
AiDashboard,
Rooms,
} from "@pages/index";
import {
Appearance,
OverviewComponent,
BookingComponent,
PDFReport,
} from "@components/index";
import { LoginForm, OtpPage, Settings, Dashboard, Analysis, Visitation, Faq, AiDashboard, Rooms, AboutPage, SecurityPage } from "@pages/index";
import { Appearance, OverviewComponent, BookingComponent, PDFReport, ProfileView } from "@components/index";
import { Layout } from "@layouts/index";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { useEffect, useState } from "react";
import ProtectedRoutes from "@components/protectedRoutes/ProtectedRoutes";
import { NotificationsSettings } from "@pages/notificationsSettings/NotificationsSettings";

function App() {
// Initialize the theme state with system preference
Expand Down Expand Up @@ -51,36 +36,36 @@ function App() {
<Routes>
<Route path="/" element={<LoginForm />} />
<Route path="/otp" element={<OtpPage />} />
<Route
path="/*"
element={
<ProtectedRoutes>
<Layout>
<Routes>
<Route path="dashboard/*" element={<Dashboard />}>
<Route path="overview" element={<OverviewComponent />} />
<Route path="bookings" element={<BookingComponent />} />
<Route path="visitations" element={<Visitation />} />
<Route path="analysis" element={<Analysis />} />
</Route>

<Route path="reports" element={<PDFReport />} />
<Route path="faq" element={<Faq />} />
<Route path="ai-dashboard" element={<AiDashboard />} />
<Route path="rooms" element={<Rooms />} />
<Route path="/*" element={
<Layout>
<Routes>
<Route path="dashboard/*" element={<Dashboard />} >
<Route path="overview" element={<OverviewComponent />} />
<Route path="bookings" element={<BookingComponent />} />*attach appropriate component
<Route path="visitations" element={<Visitation />} />{/**attach appropriate component */}
<Route path="analysis" element={<Analysis/>} />{}
</Route>

<Route path="settings/*" element={<Settings />}>
<Route path="profile" element={<Appearance />} />
<Route path="appearance" element={<Appearance />} />
<Route path="privacy" element={<Appearance />} />
<Route path="help" element={<Appearance />} />
<Route path="about" element={<Appearance />} />
</Route>
</Routes>
</Layout>
</ProtectedRoutes>
}
/>
<Route path="reports" element={<PDFReport />} />{/**attach appropriate component */}
<Route path="faq" element={ <Faq/> } />{/**attach appropriate component */}
<Route path="ai-dashboard" element={<AiDashboard />} />{/**consider making ths its own page */}
<Route path="rooms" element={<Rooms />} />{/**attach appropriate component */}
{/* <Route path="notifications" element={<Notifications />} />*attach appropriate component */}




<Route path="settings/*" element={<Settings />}>
<Route path="profile" element={<ProfileView />} />{/**attach appropriate component */}
<Route path="appearance" element={<Appearance />} />
<Route path="notifications" element={<NotificationsSettings />} />{/**attach appropriate component */}
<Route path="security" element={<SecurityPage />} />{/**attach appropriate component */}
<Route path="about" element={<AboutPage />} />{/**attach appropriate component */}
</Route>
</Routes>
</Layout>}>
</Route>
</Routes>
</Router>
);
Expand Down
35 changes: 34 additions & 1 deletion frontend/occupi-web/src/AuthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,36 @@ const AuthService = {
}
},

getUserDetails: async (email: string) => {


updateUserDetails: async (userDetails: {
email: string;
name: string;
dob: string;
gender: string;
session_email: string;
employeeid: string;
number: string;
pronouns: string;
}) => {
try {
const response = await axios.post(`${API_USER_URL}/update-user`, userDetails);
if (response.data.status === 200) {
return response.data;
} else {
throw new Error(response.data.message || 'Failed to update user details');
}
} catch (error) {
console.error("Error in updateUserDetails:", error);
if (axios.isAxiosError(error) && error.response) {
throw error.response.data;
}
throw new Error('An unexpected error occurred while updating user details');
}
},


getUserDetails : async (email: string) => {
try {
console.log(API_USER_URL);
const response = await axios.get(
Expand Down Expand Up @@ -268,4 +297,8 @@ function bufferDecode(value: string | null): Uint8Array | null {
return Uint8Array.from(atob(value), (c) => c.charCodeAt(0));
}





export default AuthService;
62 changes: 62 additions & 0 deletions frontend/occupi-web/src/CapacityService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// src/services/capacityService.ts

import axios from "axios";

interface ResponseItem {
Date: string;
Day_of_Week: number;
Day_of_month: number;
Is_Weekend: boolean;
Month: number;
Predicted_Attendance_Level: string;
Predicted_Class: number;
Special_Event: number;
}

export interface CapacityData {
day: string;
predicted: number;
date: string;
dayOfMonth: number;
isWeekend: boolean;
month: number;
predictedClass: number;
specialEvent: boolean;
}

const API_URL = "https://ai.occupi.tech/predict_week";

const convertRangeToNumber = (range: string) => {
if (!range) return 0;
const [min, max] = range.split("-").map(Number);
return (min + max) / 2;
};

const getDayName = (dayOfWeek: number): string => {
return ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][dayOfWeek];
};

export const fetchCapacityData = async (): Promise<CapacityData[]> => {
try {
const response = await axios.get<ResponseItem[]>(API_URL);
return response.data.map((item: ResponseItem) => ({
day: getDayName(item.Day_of_Week),
predicted: convertRangeToNumber(item.Predicted_Attendance_Level),
date: item.Date,
dayOfMonth: item.Day_of_month,
isWeekend: item.Is_Weekend,
month: item.Month,
predictedClass: item.Predicted_Class,
specialEvent: item.Special_Event === 1,
}));
} catch (error) {
console.error("Error fetching capacity data:", error);
throw error;
}
};

// Additional function to get only the data needed for the CapacityComparisonGraph
export const getCapacityComparisonData = async (): Promise<Pick<CapacityData, 'day' | 'predicted'>[]> => {
const fullData = await fetchCapacityData();
return fullData.map(({ day, predicted }) => ({ day, predicted }));
};
80 changes: 80 additions & 0 deletions frontend/occupi-web/src/NotificationsService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import axios from 'axios';
import { useUserStore } from 'userStore';

const API_USER_URL = '/api'; // Adjust this if needed

interface NotificationResponseItem {
id: number;
message: string;
title: string;
unreadEmails: string[];
timestamp: string;
type: string;
}

export interface Notification {
id: number;
message: string;
read: boolean;
timestamp: string;
type: 'booking' | 'capacity' | 'maintenance';
}

const NotificationService = {
fetchNotifications: async (): Promise<Notification[]> => {
const userDetails = useUserStore.getState().userDetails;
const email = userDetails?.email;

if (!email) {
throw new Error('User email is not available in the store.');
}

const filter = JSON.stringify({ emails: [email] });
const url = `${API_USER_URL}/get-notifications?filter=${encodeURIComponent(filter)}&projection=message,title,unreadEmails&limit=50&page=1`;

try {
const response = await axios.get<{ data: NotificationResponseItem[] }>(url);

return response.data.data.map((item) => ({
id: item.id,
message: item.message,
read: item.unreadEmails.length === 0,
timestamp: item.timestamp,
type: item.type as 'booking' | 'capacity' | 'maintenance',
}));
} catch (error) {
console.error("Error fetching notifications:", error);
if (axios.isAxiosError(error) && error.response?.data) {
throw error.response.data;
}
throw new Error('An unexpected error occurred while fetching notifications');
}
},

markNotificationAsRead: async (notificationId: number): Promise<void> => {
try {
const response = await axios.post(`${API_USER_URL}/mark-notification-read`, { id: notificationId });
if (response.status !== 200) {
throw new Error('Failed to mark notification as read');
}
} catch (error) {
console.error("Error marking notification as read:", error);
if (axios.isAxiosError(error) && error.response?.data) {
throw error.response.data;
}
throw new Error('An unexpected error occurred while marking notification as read');
}
},

getNotificationSummary: async (): Promise<Pick<Notification, 'id' | 'message' | 'type'>[]> => {
try {
const fullData = await NotificationService.fetchNotifications();
return fullData.map(({ id, message, type }) => ({ id, message, type }));
} catch (error) {
console.error("Error getting notification summary:", error);
throw error;
}
}
};

export default NotificationService;
9 changes: 9 additions & 0 deletions frontend/occupi-web/src/assets/icons/Upload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const Upload = () => {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 15.2044V18.8925C4 19.4514 4.21071 19.9875 4.58579 20.3827C4.96086 20.778 5.46957 21 6 21H18C18.5304 21 19.0391 20.778 19.4142 20.3827C19.7893 19.9875 20 19.4514 20 18.8925V15.2044M12.0007 14.9425L12.0007 3M12.0007 3L7.42931 7.56318M12.0007 3L16.5722 7.56318" stroke="var(--primary-alt)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
)
}

export default Upload
Loading

0 comments on commit debc271

Please sign in to comment.