Skip to content

Commit 4ff5421

Browse files
Info Chip #43 (#86)
* Added basic embedded map in newly created components folder under EmbeddedMap.tsx. this is using the google maps API, using a personal API key that I would love to change. * Implemented basic embed map * created react map * Created changed to * Fixed Embedded Map to include the react integration library ( vis.gl/react-google-map). Created example map of Syndey with map pointers to different locations. * Created Party Demo test page * First mockup of party list props, and page created to be displayed in party demo * Added popup text box and color to map * Removed package and package lock in root directory, updated .env.template * Created functioning dropdown for party list * Modified styling to be accurate with the Lo-Fi wireframe * Changed API keys and added basic sidebar functionality * Working global sidebar * Created generic implementation of info chips * Finished implementation of generic info chip details, added specific chip details for students and locations, and created webpage to display all of these elements. Also cleaned PartyDemo to remove new ticket's contents. * Minor changes to PartyList before merge with main * fixed type erros * Resolved corrections suggested in PR review * Changes to sidebar functionality and chip information * Reverted to old non-shadCN sidebar implementation * chore: update package.json * chore: reverted whitespace changes to prevent merge conflicts --------- Co-authored-by: Nicolas Asanov <[email protected]> Co-authored-by: Nick A <[email protected]>
1 parent 85fdc78 commit 4ff5421

File tree

13 files changed

+3950
-76
lines changed

13 files changed

+3950
-76
lines changed

frontend/package-lock.json

Lines changed: 3357 additions & 74 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
"lint": "eslint"
1010
},
1111
"dependencies": {
12-
"@vis.gl/react-google-maps": "^1.6.1",
1312
"@hookform/resolvers": "^5.2.2",
1413
"@radix-ui/react-checkbox": "^1.3.3",
1514
"@radix-ui/react-dropdown-menu": "^2.1.16",
@@ -21,6 +20,7 @@
2120
"@radix-ui/react-tabs": "^1.1.13",
2221
"@tanstack/react-query": "^5.90.7",
2322
"@tanstack/react-table": "^8.21.3",
23+
"@vis.gl/react-google-maps": "^1.6.1",
2424
"axios": "^1.12.2",
2525
"class-variance-authority": "^0.7.1",
2626
"clsx": "^2.1.1",
@@ -33,6 +33,8 @@
3333
"react-day-picker": "^9.11.1",
3434
"react-dom": "19.1.0",
3535
"react-hook-form": "^7.65.0",
36+
"react-icons": "^5.5.0",
37+
"shadcn": "^3.5.0",
3638
"tailwind-merge": "^3.3.1",
3739
"validator": "^13.15.23",
3840
"zod": "^4.1.12"
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"use client";
2+
3+
import { GenericInfoChip } from "@/components/GenericInfoChip";
4+
import LocationInfoChipDetails from "@/components/LocationInfoChipDetails";
5+
import { useSidebar } from "@/components/SidebarContext";
6+
import StudentInfoChipDetails from "@/components/StudentInfoChipDetails";
7+
import { Location } from "@/types/api/location";
8+
import { Student } from "@/types/api/student";
9+
10+
const Page = () => {
11+
const { openSidebar } = useSidebar();
12+
const { student: defaultStudent, location: defaultLocation } =
13+
getTestChipData();
14+
15+
return (
16+
<div className="font-sans min-h-screen flex flex-col items-center justify-center p-8 sm:p-20 max-w-2xl mx-auto">
17+
<h1 className="text-3xl font-bold mb-4">Info Chip Demo Page</h1>
18+
19+
<h2 className="text-2xl font-bold mb-4">Test Sidebar</h2>
20+
<button
21+
className="bg-gray-200 px-3 py-1 rounded mb-6"
22+
onClick={() =>
23+
openSidebar(
24+
"test-sidebar",
25+
"Example",
26+
"Example Description",
27+
<div>Hello from Sidebar!</div>
28+
)
29+
}
30+
>
31+
Open Sidebar
32+
</button>
33+
34+
<h2 className="text-2xl font-bold mb-4">Student Info Chip</h2>
35+
<GenericInfoChip
36+
chipKey="student-1"
37+
shortName={`${defaultStudent.firstName} ${defaultStudent.lastName}`}
38+
title="Student Information"
39+
description="Detailed information about the selected student"
40+
sidebarContent={<StudentInfoChipDetails data={defaultStudent} />}
41+
/>
42+
43+
<h2 className="text-2xl font-bold mt-6 mb-4">Location Info Chip</h2>
44+
<GenericInfoChip
45+
chipKey="location-1"
46+
title="Location Information"
47+
description="Detailed information about the selected location"
48+
shortName={defaultLocation.formattedAddress}
49+
sidebarContent={<LocationInfoChipDetails data={defaultLocation} />}
50+
/>
51+
</div>
52+
);
53+
};
54+
55+
export function getTestChipData() {
56+
const student: Student = {
57+
id: 1,
58+
firstName: "Mr",
59+
lastName: "Beast",
60+
contactPreference: "text",
61+
62+
pid: "123456789",
63+
lastRegistered: new Date("2023-08-15"),
64+
phoneNumber: "555-1234",
65+
};
66+
67+
const location: Location = {
68+
id: 1,
69+
warningCount: 2,
70+
citationCount: 1,
71+
hasActiveHold: true,
72+
holdExpirationDate: new Date("2025-12-31"),
73+
googlePlaceId: "abcd1234",
74+
formattedAddress: "123 Main St, Chapel Hill, NC",
75+
latitude: 35.9132,
76+
longitude: -79.0558,
77+
streetNumber: "123",
78+
streetName: "Main St",
79+
unit: "Apt 4",
80+
city: "Chapel Hill",
81+
county: "Orange",
82+
state: "NC",
83+
country: "USA",
84+
zipCode: "27514",
85+
};
86+
87+
return { student, location };
88+
}
89+
90+
export default Page;

frontend/src/app/providers.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"use client";
22

3+
import GenericSidebar from "@/components/GenericSidebar";
4+
import { SidebarProvider } from "@/components/SidebarContext";
35
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
46
import { SessionProvider } from "next-auth/react";
57

@@ -8,7 +10,12 @@ const queryClient = new QueryClient();
810
export default function Providers({ children }: { children: React.ReactNode }) {
911
return (
1012
<SessionProvider>
11-
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
13+
<SidebarProvider>
14+
<QueryClientProvider client={queryClient}>
15+
{children}
16+
</QueryClientProvider>
17+
<GenericSidebar />
18+
</SidebarProvider>
1219
</SessionProvider>
1320
);
1421
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"use client";
2+
3+
import { ReactNode } from "react";
4+
5+
interface GenericChipDetailsProps<T> {
6+
data: T;
7+
title: string;
8+
description: string;
9+
renderView: (value: T) => ReactNode;
10+
}
11+
12+
export function GenericChipDetails<T>({
13+
data,
14+
title,
15+
description,
16+
renderView,
17+
}: GenericChipDetailsProps<T>) {
18+
return (
19+
<div className="space-y-3">
20+
<h1 className="text-lg font-bold">{title}</h1>
21+
<h2 className="text-lg">{description}</h2>
22+
{renderView(data)}
23+
</div>
24+
);
25+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"use client";
2+
3+
import { useSidebar } from "@/components/SidebarContext";
4+
import { Badge } from "@/components/ui/badge";
5+
import { ReactNode } from "react";
6+
7+
interface GenericInfoChipProps {
8+
chipKey: string;
9+
shortName: string;
10+
title: string;
11+
description: string;
12+
sidebarContent: ReactNode;
13+
}
14+
15+
export function GenericInfoChip({
16+
chipKey,
17+
shortName,
18+
title,
19+
description,
20+
sidebarContent,
21+
}: GenericInfoChipProps) {
22+
const { openSidebar, selectedKey, closeSidebar } = useSidebar();
23+
const isSelected = selectedKey === chipKey;
24+
25+
const handleOpen = () => {
26+
if (isSelected) {
27+
closeSidebar();
28+
return;
29+
} else {
30+
openSidebar(chipKey, title, description, sidebarContent);
31+
}
32+
};
33+
34+
return (
35+
<Badge
36+
onClick={handleOpen}
37+
className={`cursor-pointer px-3 py-1 ${
38+
isSelected ? "bg-blue-500 text-white" : "bg-gray-200 text-black"
39+
}`}
40+
>
41+
{shortName}
42+
</Badge>
43+
);
44+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use client";
2+
import { useSidebar } from "@/components/SidebarContext";
3+
4+
function GenericSidebar() {
5+
const { isOpen, closeSidebar, content } = useSidebar();
6+
7+
if (!isOpen) return null;
8+
9+
return (
10+
<div className="fixed top-0 right-0 w-96 h-full bg-white shadow-lg p-4 overflow-auto z-50">
11+
<button
12+
className="mb-4 px-3 py-1 bg-gray-200 rounded"
13+
onClick={closeSidebar}
14+
>
15+
Close
16+
</button>
17+
{content}
18+
</div>
19+
);
20+
}
21+
22+
export default GenericSidebar;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"use client";
2+
3+
import { Location } from "@/types/api/location";
4+
import { GenericChipDetails } from "./GenericChipDetails";
5+
6+
interface LocationInfoChipDetailsProps {
7+
data: Location;
8+
}
9+
10+
function LocationInfoChipDetails({ data }: LocationInfoChipDetailsProps) {
11+
return (
12+
<GenericChipDetails<Location>
13+
data={data}
14+
title={"Info about the Location"}
15+
description={"View information on the Location you just clicked on"}
16+
renderView={(d) => (
17+
<div className="space-y-3">
18+
<div>
19+
<label className="block text-sm font-medium">Address</label>
20+
<p className="p-2 border rounded">{d.formattedAddress}</p>
21+
</div>
22+
<div>
23+
<label className="block text-sm font-medium">Warning Count</label>
24+
<p className="p-2 border rounded">{d.warningCount}</p>
25+
</div>
26+
<div>
27+
<label className="block text-sm font-medium">Citation Count</label>
28+
<p className="p-2 border rounded">{d.citationCount}</p>
29+
</div>
30+
<div>
31+
<label className="block text-sm font-medium">Active Hold</label>
32+
<p className="p-2 border rounded">
33+
{d.hasActiveHold
34+
? "Active: Expires " + d.holdExpirationDate?.toDateString()
35+
: "No"}
36+
</p>
37+
</div>
38+
</div>
39+
)}
40+
/>
41+
);
42+
}
43+
44+
export default LocationInfoChipDetails;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"use client";
2+
3+
import { Party } from "@/types/api/party";
4+
import { GenericChipDetails } from "./GenericChipDetails";
5+
6+
interface PartyInfoChipDetailsProps {
7+
data: Party;
8+
}
9+
10+
export function PartyInfoChipDetails({ data }: PartyInfoChipDetailsProps) {
11+
return (
12+
<GenericChipDetails<Party>
13+
data={data}
14+
title={"Info about the Party"}
15+
description={"View information on the Party you just clicked on"}
16+
renderView={(d) => (
17+
<div className="space-y-3">
18+
<div>
19+
<label className="block text-sm font-medium">Address</label>
20+
<p className="p-2 border rounded">{d.location.formattedAddress}</p>
21+
</div>
22+
<div>
23+
<label className="block text-sm font-medium">Date</label>
24+
<p className="p-2 border rounded">{d.datetime.toDateString()}</p>
25+
</div>
26+
<div>
27+
<label className="block text-sm font-medium">First name</label>
28+
<p className="p-2 border rounded">{d.contactOne.firstName}</p>
29+
</div>
30+
<div>
31+
<label className="block text-sm font-medium">Last Name</label>
32+
<p className="p-2 border rounded">{d.contactOne.lastName}</p>
33+
</div>
34+
</div>
35+
)}
36+
/>
37+
);
38+
}
39+
40+
export default PartyInfoChipDetails;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"use client";
2+
import { createContext, ReactNode, useContext, useState } from "react";
3+
4+
type SidebarContextType = {
5+
isOpen: boolean;
6+
title: string | null;
7+
description: string | null;
8+
content: ReactNode | null;
9+
selectedKey: string | null;
10+
openSidebar: (
11+
key: string,
12+
title: string,
13+
description: string,
14+
content: ReactNode
15+
) => void;
16+
closeSidebar: () => void;
17+
};
18+
interface SidebarProviderProps {
19+
children: ReactNode;
20+
}
21+
22+
const SidebarContext = createContext<SidebarContextType | undefined>(undefined);
23+
24+
export function SidebarProvider({ children }: SidebarProviderProps) {
25+
const [isOpen, setIsOpen] = useState(false);
26+
const [title, setTitle] = useState<string | null>(null);
27+
const [description, setDescription] = useState<string | null>(null);
28+
const [content, setContent] = useState<ReactNode | null>(null);
29+
const [selectedKey, setSelectedKey] = useState<string | null>(null);
30+
31+
const openSidebar = (
32+
key: string,
33+
title: string,
34+
description: string,
35+
content: ReactNode
36+
) => {
37+
setContent(content);
38+
setTitle(title);
39+
setDescription(description);
40+
setSelectedKey(key);
41+
setIsOpen(true);
42+
};
43+
44+
const closeSidebar = () => {
45+
setIsOpen(false);
46+
setContent(null);
47+
setSelectedKey(null);
48+
setTitle(null);
49+
setDescription(null);
50+
};
51+
52+
return (
53+
<SidebarContext.Provider
54+
value={{
55+
isOpen,
56+
title,
57+
description,
58+
content,
59+
selectedKey,
60+
openSidebar,
61+
closeSidebar,
62+
}}
63+
>
64+
{children}
65+
</SidebarContext.Provider>
66+
);
67+
}
68+
69+
export const useSidebar = () => {
70+
const context = useContext(SidebarContext);
71+
if (!context)
72+
throw new Error("useSidebar must be used within SidebarProvider");
73+
return context;
74+
};

0 commit comments

Comments
 (0)