Skip to content

Commit

Permalink
Merge pull request #402 from COS301-SE-2024/fix/web/general-polishing-up
Browse files Browse the repository at this point in the history
Fix/web/general polishing up
  • Loading branch information
Tinashe-Austin authored Sep 26, 2024
2 parents 83fc7ea + 42d9956 commit ee3d0ee
Show file tree
Hide file tree
Showing 19 changed files with 817 additions and 304 deletions.
16 changes: 12 additions & 4 deletions frontend/occupi-web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LoginForm, OtpPage, Settings, Dashboard, Analysis, Visitation, Faq, AiDashboard, Rooms, AboutPage, SecurityPage,BookingStats ,WorkerStatsDashboard} from "@pages/index";
import { LoginForm, OtpPage, Settings, Dashboard, Faq, AiDashboard, Rooms, AboutPage, SecurityPage, BookingStats, WorkerStatsDashboard, BookingsDashboardPage } 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';
Expand Down Expand Up @@ -36,18 +36,26 @@ function App() {
<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="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="bookingStats" element={<BookingStats />} />{/**attach appropriate component */}
<Route path="worker-dashboard" element={<WorkerStatsDashboard />} />{/**attach appropriate component */}
<Route path="bookings" element={<BookingComponent />} />*attach appropriate component
{/* <Route path="bookingsDashboard" element={<BookingsDashboard />} />*attach appropriate component */}


<Route path="bookingStats/*" element={<BookingsDashboardPage />} >
{/* <Route path="bookings" element={<BookingComponent />} />*attach appropriate component */}

</Route>




Expand Down
22 changes: 22 additions & 0 deletions frontend/occupi-web/src/assets/icons/Employee.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const Employee = () => {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g id="user-profile-square">
<path
id="Icon"
d="M4.1999 19.8003C4.54426 19.4146 6.85775 17.5977 8.00766 16.702C8.42646 16.3757 8.94017 16.2003 9.47104 16.2003C10.7547 16.2003 13.2283 16.2003 14.5181 16.2003C15.0552 16.2003 15.574 16.3832 16.0065 16.7017C17.4912 17.7956 18.8832 18.6106 20.3999 19.8003M5.9999 21.6H17.9999C19.9881 21.6 21.5999 19.9882 21.5999 18V6.00002C21.5999 4.0118 19.9881 2.40002 17.9999 2.40002H5.9999C4.01168 2.40002 2.3999 4.0118 2.3999 6.00002V18C2.3999 19.9882 4.01168 21.6 5.9999 21.6ZM15.4385 9.27206C15.4385 7.44055 13.8923 5.94411 11.9999 5.94411C10.1075 5.94411 8.56135 7.44055 8.56135 9.27206C8.56135 11.1036 10.1075 12.6 11.9999 12.6C13.8923 12.6 15.4385 11.1036 15.4385 9.27206Z"
stroke="var(--primary-alt)"
stroke-width="2"
/>
</g>
</svg>
);
};

export default Employee;
23 changes: 16 additions & 7 deletions frontend/occupi-web/src/assets/icons/UserProfileGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
const UserProfileGroup = () => {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="" xmlns="http://www.w3.org/2000/svg">
<path d="M18.088 16.8638C17.5434 16.9553 17.176 17.471 17.2675 18.0157C17.359 18.5603 17.8747 18.9277 18.4194 18.8362L18.088 16.8638ZM17.0152 11.3824C16.4629 11.3824 16.0152 11.8301 16.0152 12.3824C16.0152 12.9347 16.4629 13.3824 17.0152 13.3824V11.3824ZM16.0924 10.2827C17.5684 10.2827 18.7646 9.0858 18.7646 7.60999H16.7646C16.7646 7.98176 16.4633 8.28265 16.0924 8.28265V10.2827ZM13.4202 7.60999C13.4202 9.0858 14.6163 10.2827 16.0924 10.2827V8.28265C15.7214 8.28265 15.4202 7.98176 15.4202 7.60999H13.4202ZM16.0924 4.93734C14.6163 4.93734 13.4202 6.13419 13.4202 7.60999H15.4202C15.4202 7.23823 15.7214 6.93734 16.0924 6.93734V4.93734ZM18.7646 7.60999C18.7646 6.13419 17.5684 4.93734 16.0924 4.93734V6.93734C16.4633 6.93734 16.7646 7.23823 16.7646 7.60999H18.7646ZM20 15.1641C20 15.5763 19.8701 15.9126 19.6161 16.1792C19.3531 16.4551 18.8849 16.73 18.088 16.8638L18.4194 18.8362C19.524 18.6506 20.429 18.225 21.0638 17.5591C21.7075 16.8838 22 16.0369 22 15.1641H20ZM17.0152 13.3824C17.9362 13.3824 18.7269 13.6446 19.2605 14.0171C19.7974 14.3919 20 14.8131 20 15.1641H22C22 13.9788 21.3105 13.0091 20.4053 12.3771C19.4967 11.7429 18.295 11.3824 17.0152 11.3824V13.3824ZM10.2625 7.42936C10.2625 8.46511 9.58382 9.03069 8.89183 9.03069V11.0307C10.8184 11.0307 12.2625 9.43497 12.2625 7.42936H10.2625ZM8.89183 9.03069C8.19984 9.03069 7.52113 8.46511 7.52113 7.42936H5.52113C5.52113 9.43497 6.96521 11.0307 8.89183 11.0307V9.03069ZM7.52113 7.42936C7.52113 6.91225 7.69567 6.57237 7.9127 6.36129C8.1359 6.14421 8.4696 6 8.89183 6V4C8.00476 4 7.15311 4.31013 6.51827 4.92756C5.87728 5.55099 5.52113 6.42578 5.52113 7.42936H7.52113ZM8.89183 6C9.31406 6 9.64776 6.14421 9.87096 6.36129C10.088 6.57237 10.2625 6.91225 10.2625 7.42936H12.2625C12.2625 6.42578 11.9064 5.55099 11.2654 4.92756C10.6306 4.31013 9.7789 4 8.89183 4V6ZM14.0703 16.1679C14.0703 16.4806 13.9159 16.8937 13.1677 17.2838C12.3865 17.6911 11.0606 18 9.03513 18L9.03513 20C11.216 20 12.9076 19.6749 14.0924 19.0572C15.3103 18.4222 16.0703 17.4193 16.0703 16.1679H14.0703ZM9.03513 18C7.00965 18 5.68372 17.6911 4.90252 17.2838C4.1544 16.8937 4 16.4806 4 16.1679H2C2 17.4193 2.76001 18.4222 3.97789 19.0572C5.16268 19.6749 6.85432 20 9.03513 20L9.03513 18ZM4 16.1679C4 16.0022 4.18138 15.545 5.19247 15.0705C6.12422 14.6333 7.48172 14.3357 9.03513 14.3357V12.3357C7.25544 12.3357 5.59537 12.6722 4.34283 13.26C3.16963 13.8105 2 14.7695 2 16.1679H4ZM9.03513 14.3357C10.5886 14.3357 11.9461 14.6333 12.8778 15.0705C13.8889 15.545 14.0703 16.0022 14.0703 16.1679H16.0703C16.0703 14.7695 14.9006 13.8105 13.7274 13.26C12.4749 12.6722 10.8148 12.3357 9.03513 12.3357V14.3357Z" fill="var(--primary-alt)"/>
</svg>
)
}
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill=""
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18.088 16.8638C17.5434 16.9553 17.176 17.471 17.2675 18.0157C17.359 18.5603 17.8747 18.9277 18.4194 18.8362L18.088 16.8638ZM17.0152 11.3824C16.4629 11.3824 16.0152 11.8301 16.0152 12.3824C16.0152 12.9347 16.4629 13.3824 17.0152 13.3824V11.3824ZM16.0924 10.2827C17.5684 10.2827 18.7646 9.0858 18.7646 7.60999H16.7646C16.7646 7.98176 16.4633 8.28265 16.0924 8.28265V10.2827ZM13.4202 7.60999C13.4202 9.0858 14.6163 10.2827 16.0924 10.2827V8.28265C15.7214 8.28265 15.4202 7.98176 15.4202 7.60999H13.4202ZM16.0924 4.93734C14.6163 4.93734 13.4202 6.13419 13.4202 7.60999H15.4202C15.4202 7.23823 15.7214 6.93734 16.0924 6.93734V4.93734ZM18.7646 7.60999C18.7646 6.13419 17.5684 4.93734 16.0924 4.93734V6.93734C16.4633 6.93734 16.7646 7.23823 16.7646 7.60999H18.7646ZM20 15.1641C20 15.5763 19.8701 15.9126 19.6161 16.1792C19.3531 16.4551 18.8849 16.73 18.088 16.8638L18.4194 18.8362C19.524 18.6506 20.429 18.225 21.0638 17.5591C21.7075 16.8838 22 16.0369 22 15.1641H20ZM17.0152 13.3824C17.9362 13.3824 18.7269 13.6446 19.2605 14.0171C19.7974 14.3919 20 14.8131 20 15.1641H22C22 13.9788 21.3105 13.0091 20.4053 12.3771C19.4967 11.7429 18.295 11.3824 17.0152 11.3824V13.3824ZM10.2625 7.42936C10.2625 8.46511 9.58382 9.03069 8.89183 9.03069V11.0307C10.8184 11.0307 12.2625 9.43497 12.2625 7.42936H10.2625ZM8.89183 9.03069C8.19984 9.03069 7.52113 8.46511 7.52113 7.42936H5.52113C5.52113 9.43497 6.96521 11.0307 8.89183 11.0307V9.03069ZM7.52113 7.42936C7.52113 6.91225 7.69567 6.57237 7.9127 6.36129C8.1359 6.14421 8.4696 6 8.89183 6V4C8.00476 4 7.15311 4.31013 6.51827 4.92756C5.87728 5.55099 5.52113 6.42578 5.52113 7.42936H7.52113ZM8.89183 6C9.31406 6 9.64776 6.14421 9.87096 6.36129C10.088 6.57237 10.2625 6.91225 10.2625 7.42936H12.2625C12.2625 6.42578 11.9064 5.55099 11.2654 4.92756C10.6306 4.31013 9.7789 4 8.89183 4V6ZM14.0703 16.1679C14.0703 16.4806 13.9159 16.8937 13.1677 17.2838C12.3865 17.6911 11.0606 18 9.03513 18L9.03513 20C11.216 20 12.9076 19.6749 14.0924 19.0572C15.3103 18.4222 16.0703 17.4193 16.0703 16.1679H14.0703ZM9.03513 18C7.00965 18 5.68372 17.6911 4.90252 17.2838C4.1544 16.8937 4 16.4806 4 16.1679H2C2 17.4193 2.76001 18.4222 3.97789 19.0572C5.16268 19.6749 6.85432 20 9.03513 20L9.03513 18ZM4 16.1679C4 16.0022 4.18138 15.545 5.19247 15.0705C6.12422 14.6333 7.48172 14.3357 9.03513 14.3357V12.3357C7.25544 12.3357 5.59537 12.6722 4.34283 13.26C3.16963 13.8105 2 14.7695 2 16.1679H4ZM9.03513 14.3357C10.5886 14.3357 11.9461 14.6333 12.8778 15.0705C13.8889 15.545 14.0703 16.0022 14.0703 16.1679H16.0703C16.0703 14.7695 14.9006 13.8105 13.7274 13.26C12.4749 12.6722 10.8148 12.3357 9.03513 12.3357V14.3357Z"
fill="var(--primary-alt)"
/>
</svg>
);
};

export default UserProfileGroup
export default UserProfileGroup;
4 changes: 2 additions & 2 deletions frontend/occupi-web/src/assets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ import AI_loader from "./images/AI_loader.gif";
import LCPW from "./images/LCPW.gif";
import ailoader from "./images/ailoader.gif";
import Worker from "./icons/Worker";

import Employee from "./icons/Employee";
export {
loginpng, OccupiLogo, login_image, CheckSquareContained, CloseDrawer, OpenDrawer,
Grid, PieChart, ColorSwatch, Home, UserProfileGroup, Bell, SettingsIcon, Logout,
Loading,SettingsImg, Macbook1, Macbook2, Macbook3,
Userprofile, Pallete, Privacy, AlertIcon, HelpIcon, GraphCol, Cal,
ChevronDown, ChevronLeft, ChevronRight, Bf, Uptrend, DownTrend, SearchIcon,ChevronDownIcon,PlusIcon,VerticalDotsIcon,EditIcon,EyeIcon,DeleteIcon,occupiLogo,Report,Faq
,UploadButton,Upload,Bar,AI_loader,LCPW,ailoader,Worker,LoadingSM
,UploadButton,Upload,Bar,AI_loader,LCPW,ailoader,Worker,LoadingSM,Employee
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { SearchIcon } from "@assets/index";
import { ChevronDownIcon } from "@assets/index";
import { columns, users, statusOptions } from "../data/Data";
import { capitalize } from "../data/Utils";
import { OccupancyModal } from "@components/index";
import { OccupancyModal,TopNav } from "@components/index";

const statusColorMap: Record<string, ChipProps["color"]> = {
ONSITE: "success",
Expand Down Expand Up @@ -370,8 +370,20 @@ export default function App() {
initial={{ opacity: 0, scale: 0.7 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.2 }}
className="w-full overflow-auto"
>

<TopNav
mainComponent={<div className="text-text_col font-semibold text-2xl ml-5">
Employees
<span className="block text-sm opacity-65 text-text_col_secondary_alt ">
Manage your Employees, and view their occupancy statistics
</span>
</div>} searchQuery={""} onChange={function (): void {
throw new Error("Function not implemented.");
} }

/>

<div data-testid="table" className="max-w-[95%] mx-auto">
<Table
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useState } from "react";
import { Tabs, Tab } from "@nextui-org/react";

import {
HistoricalBookingsBento,
CurrentBookingsBento,
TopBookingsBento,
} from "@components/index";

const BookingsDashboard = () => {
const [selected, setSelected] = useState("top");

return (
<div className="flex flex-col w-auto ml-3">


<Tabs
className="mt-5 bg-text_col_alt"
aria-label="Bookings tabs"
selectedKey={selected}
onSelectionChange={(key) => setSelected(key as string)}

>
<Tab key="top" title="Top Bookings">
<TopBookingsBento />
</Tab>
<Tab key="current" title="Current Bookings">
<CurrentBookingsBento />
</Tab>
<Tab key="historical" title="Historical Bookings">
<HistoricalBookingsBento />
</Tab>

</Tabs>
</div>
);
};

export default BookingsDashboard;
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { useState, useEffect } from 'react';
import { Card, CardBody, CardHeader, Input, Button, Popover, PopoverTrigger, PopoverContent, Spinner } from "@nextui-org/react";
import { Calendar, Clock, Users, Search, ChevronDown } from 'lucide-react';

interface Booking {
checkedIn: boolean;
creators: string;
date: string;
emails: string[];
end: string;
floorNo: string;
occupiID: string;
roomId: string;
roomName: string;
start: string;
}

interface ApiResponse {
data: Booking[] | null;
message: string;
meta: {
currentPage: number;
totalPages: number;
totalResults: number;
};
status: number;
}

const CurrentBookingsBento = () => {
const [bookings, setBookings] = useState<Booking[]>([]);
const [filteredBookings, setFilteredBookings] = useState<Booking[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [searchRoom, setSearchRoom] = useState('');

useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('/analytics/bookings-current');
const data: ApiResponse = await response.json();
if (data.data) {
setBookings(data.data);
setFilteredBookings(data.data);
} else {
setBookings([]);
setFilteredBookings([]);
}
setIsLoading(false);
} catch (err) {
setError('Failed to fetch data');
setIsLoading(false);
}
};

fetchData();
}, []);

useEffect(() => {
if (searchRoom) {
const filtered = bookings.filter(booking =>
booking.roomName.toLowerCase().includes(searchRoom.toLowerCase())
);
setFilteredBookings(filtered);
} else {
setFilteredBookings(bookings);
}
}, [searchRoom, bookings]);

const formatTime = (timeString: string) => {
return new Date(timeString).toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit'
});
};

if (isLoading) return <Spinner label="Loading..." />;
if (error) return <div>Error: {error}</div>;

return (
<div className="space-y-4 mt-5">
<Input
type="text"
placeholder="Search by room name"
value={searchRoom}
onChange={(e) => setSearchRoom(e.target.value)}
startContent={<Search className="text-default-400" />}
/>
{filteredBookings.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{filteredBookings.slice(0, 3).map((booking) => (
<Card key={booking.occupiID} className="w-full" shadow="sm">
<CardHeader className="flex justify-between">
<h4 className="text-large font-bold">{booking.roomName}</h4>
<span className="text-small text-default-500">Floor {booking.floorNo}</span>
</CardHeader>
<CardBody>
<div className="space-y-2">
<div className="flex items-center">
<Calendar className="mr-2 h-4 w-4" />
<span>{new Date(booking.date).toLocaleDateString()}</span>
</div>
<div className="flex items-center">
<Clock className="mr-2 h-4 w-4" />
<span>{formatTime(booking.start)} - {formatTime(booking.end)}</span>
</div>
<div className="flex items-center">
<Users className="mr-2 h-4 w-4" />
<span>{booking.emails.length} participant(s)</span>
</div>
<Popover placement="bottom">
<PopoverTrigger>
<Button
endContent={<ChevronDown className="h-4 w-4" />}
variant="flat"
>
View Details
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px]">
<div className="px-1 py-2">
<h3 className="text-small font-bold">Creator:</h3>
<p className="text-tiny">{booking.creators}</p>
<h3 className="text-small font-bold mt-2">Participants:</h3>
<ul className="list-disc pl-4">
{booking.emails.map((email, i) => (
<li key={i} className="text-tiny">{email}</li>
))}
</ul>
<p className="text-tiny mt-2">
Checked In: {booking.checkedIn ? 'Yes' : 'No'}
</p>
</div>
</PopoverContent>
</Popover>
</div>
</CardBody>
</Card>
))}
</div>
) : (
<Card>
<CardBody>
<p>No current bookings available.</p>
</CardBody>
</Card>
)}
{filteredBookings.length > 3 && (
<Popover placement="bottom">
<PopoverTrigger>
<Button
endContent={<ChevronDown className="h-4 w-4" />}
variant="flat"
>
View More Bookings
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px]">
<div className="px-1 py-2 max-h-[400px] overflow-y-auto">
{filteredBookings.slice(3).map((booking) => (
<div key={booking.occupiID} className="mb-4">
<h3 className="text-small font-bold">{booking.roomName}</h3>
<p className="text-tiny">{new Date(booking.date).toLocaleDateString()}</p>
<p className="text-tiny">{formatTime(booking.start)} - {formatTime(booking.end)}</p>
</div>
))}
</div>
</PopoverContent>
</Popover>
)}
</div>
);
};

export default CurrentBookingsBento;
Loading

0 comments on commit ee3d0ee

Please sign in to comment.