From 8104d1bab8efd4eb0de3e8d2cb2f2a829979b65c Mon Sep 17 00:00:00 2001 From: woleary2 Date: Tue, 2 Dec 2025 19:19:42 -0500 Subject: [PATCH 1/3] implemented school name standardization and began working on settings page --- src/app/settings/page.tsx | 192 ++++++++++++++++++++++++++++ src/components/NavBar.tsx | 1 + src/components/ui/multi-select.tsx | 196 +++++++++++++++++++++++++++++ src/lib/standardize.ts | 42 +++++++ 4 files changed, 431 insertions(+) create mode 100644 src/app/settings/page.tsx create mode 100644 src/components/ui/multi-select.tsx create mode 100644 src/lib/standardize.ts diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx new file mode 100644 index 0000000..4d18d5f --- /dev/null +++ b/src/app/settings/page.tsx @@ -0,0 +1,192 @@ +"use client"; + +import { useState } from "react"; +import { MultiSelect } from "../../components/ui/multi-select"; + +export default function Settings() { + const [selectedCities, setSelectedCities] = useState([]); + + // Placeholder city options (City 1, City 2, etc.) + const cityOptions = [ + { value: "city-1", label: "City 1" }, + { value: "city-2", label: "City 2" }, + { value: "city-3", label: "City 3" }, + { value: "city-4", label: "City 4" }, + { value: "city-5", label: "City 5" }, + { value: "city-6", label: "City 6" }, + { value: "city-7", label: "City 7" }, + { value: "city-8", label: "City 8" }, + ]; + + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + + const handleCityChange = (values: string[]) => { + setSelectedCities(values); + setHasUnsavedChanges(true); + }; + + const handleSave = () => { + console.log("Saving cities:", selectedCities); + setHasUnsavedChanges(false); + }; + + return ( +
+
+

Settings

+
+ +
+

Preferences

+

+ How would you like to view charts +

+
+ +
+
+

Configuration

+

+ These settings configure how data is calculated. Only + edit these settings if you really mean to. +

+
+ +
+ {/* Gateway Cities Section */} +
+
+

+ Gateway Cities +

+
+ + + + {selectedCities.length > 0 && ( +

+ {selectedCities.length}{" "} + {selectedCities.length === 1 + ? "city" + : "cities"}{" "} + selected +

+ )} +
+ + {/* Permitted Users Section */} +
+
+

+ Permitted Users +

+ +
+ +

+ These emails are permitted to log into the system, + users can also transfer access. +

+ +
+ + + + + + + + + + {[ + { + email: "something@gmail.com", + lastSignIn: "2 days ago", + }, + { + email: "something@gmail.com", + lastSignIn: "2 days ago", + }, + { + email: "something@gmail.com", + lastSignIn: "2 days ago", + }, + ].map((user, i) => ( + + + + + + ))} + +
+ Email + + Last Signed In + + Remove +
+ {user.email} + + {user.lastSignIn} + + +
+
+
+
+ + {/* Save Section */} +
+ + +
+
+
+ ); +} diff --git a/src/components/NavBar.tsx b/src/components/NavBar.tsx index 37b59a4..8ea630d 100644 --- a/src/components/NavBar.tsx +++ b/src/components/NavBar.tsx @@ -5,6 +5,7 @@ export default function NavBar() { ); diff --git a/src/components/ui/multi-select.tsx b/src/components/ui/multi-select.tsx new file mode 100644 index 0000000..8752a32 --- /dev/null +++ b/src/components/ui/multi-select.tsx @@ -0,0 +1,196 @@ +import React from "react"; +import { CheckIcon, ChevronDown, XIcon } from "lucide-react"; + +export interface MultiSelectOption { + label: string; + value: string; +} + +export interface MultiSelectProps { + options: MultiSelectOption[]; + value?: string[]; + defaultValue?: string[]; + onValueChange: (v: string[]) => void; + placeholder?: string; + className?: string; + disabled?: boolean; + searchable?: boolean; +} + +export const MultiSelect: React.FC = ({ + options, + value, + defaultValue = [], + onValueChange, + placeholder = "Select options", + className, + disabled = false, + searchable = true, +}) => { + const isControlled = value !== undefined; + const [internal, setInternal] = React.useState(defaultValue); + const selected = isControlled ? value! : internal; + + const [open, setOpen] = React.useState(false); + const [search, setSearch] = React.useState(""); + + const triggerRef = React.useRef(null); + + const setSelected = (vals: string[]) => { + if (!isControlled) setInternal(vals); + onValueChange(vals); + }; + + const toggle = (val: string) => { + if (selected.includes(val)) { + setSelected(selected.filter((x) => x !== val)); + } else { + setSelected([...selected, val]); + } + }; + + const clear = () => setSelected([]); + + const filtered = search + ? options.filter((o) => + o.label.toLowerCase().includes(search.toLowerCase()), + ) + : options; + + // Close dropdown when clicking outside + React.useEffect(() => { + const handler = (e: MouseEvent) => { + if ( + triggerRef.current && + !triggerRef.current.contains(e.target as Node) + ) { + setOpen(false); + } + }; + document.addEventListener("mousedown", handler); + return () => document.removeEventListener("mousedown", handler); + }, []); + + return ( +
+ {/* Trigger */} +
!disabled && setOpen(!open)} + className={` + border rounded px-3 py-2 flex items-center justify-between cursor-pointer + ${disabled ? "opacity-50 cursor-not-allowed" : ""} + `} + > + {/* Selected Items */} + {selected.length === 0 ? ( + {placeholder} + ) : ( +
+ {selected.map((val) => { + const opt = options.find((o) => o.value === val); + if (!opt) return null; + + return ( + e.stopPropagation()} + > + {opt.label} + toggle(val)} + /> + + ); + })} +
+ )} + + +
+ + {/* Dropdown */} + {open && ( +
+ {/* Search */} + {searchable && ( + setSearch(e.target.value)} + placeholder="Search..." + className="w-full border rounded px-2 py-1 mb-2" + /> + )} + +
+ {/* Select All */} +
+ selected.length === options?.length + ? clear() + : setSelected(options?.map((o) => o.value)) + } + className="flex items-center gap-2 px-2 py-1 hover:bg-gray-100 cursor-pointer" + > +
+ +
+ Select All +
+ + {/* Options */} + {filtered?.map((o) => { + const isSelected = selected.includes(o.value); + + return ( +
toggle(o.value)} + > +
+ +
+ {o.label} +
+ ); + })} + +
+ + {/* Clear */} + {selected.length > 0 && ( +
+ Clear +
+ )} + + {/* Close */} +
setOpen(false)} + > + Close +
+
+
+ )} +
+ ); +}; diff --git a/src/lib/standardize.ts b/src/lib/standardize.ts new file mode 100644 index 0000000..1204b78 --- /dev/null +++ b/src/lib/standardize.ts @@ -0,0 +1,42 @@ +const words_to_remove: string[] = [ + "public", + "school", + "schools", + "district", + "the", + "of", + "+", + "-", + "=", + "and", + "at", + "!", + "@", + "#", + "$", + "%", + "^", + "&", + "*", + "(", + ")", + ".", +]; + +export function standardize(name: string) { + // Trim whitespace + var school_name = name.trim(); + + // Convert to lowercase + school_name = school_name.toLowerCase(); + + const words = school_name.split(" "); + + // Filtering out extraneous words referring to the array above + // Remove the extraneous words + const filtered = words.filter((word) => { + return !words_to_remove.includes(word); + }); + + return filtered.join(""); +} From 6d82e23620d3f369b2515a02c60070ef2a247a6f Mon Sep 17 00:00:00 2001 From: woleary2 Date: Sat, 6 Dec 2025 13:20:49 -0500 Subject: [PATCH 2/3] Finished settings page and school name standardization --- src/app/settings/page.tsx | 154 ++++++++++++++---- ...ndardize.ts => school_name_standardize.ts} | 12 ++ 2 files changed, 130 insertions(+), 36 deletions(-) rename src/lib/{standardize.ts => school_name_standardize.ts} (66%) diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 4d18d5f..48c0714 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -1,12 +1,24 @@ +/*************************************************************** + * + * page.tsx + * + * Author: Will and Hansini + * Date: 12/6/2025 + * + * Summary: Basic outline of settings page + * + **************************************************************/ + "use client"; import { useState } from "react"; import { MultiSelect } from "../../components/ui/multi-select"; +import { Trash, Plus } from "lucide-react"; export default function Settings() { const [selectedCities, setSelectedCities] = useState([]); - // Placeholder city options (City 1, City 2, etc.) + // Placeholder city options, eventually will be pulled from db const cityOptions = [ { value: "city-1", label: "City 1" }, { value: "city-2", label: "City 2" }, @@ -30,6 +42,17 @@ export default function Settings() { setHasUnsavedChanges(false); }; + const handleDeleteCity = (cityValue: string) => { + setSelectedCities((prevCities) => + prevCities.filter((value) => value !== cityValue), + ); + setHasUnsavedChanges(true); + }; + + const getCityLabel = (value: string) => { + return cityOptions.find((opt) => opt.value === value)?.label || value; + }; + return (
@@ -53,22 +76,21 @@ export default function Settings() {
- {/* Gateway Cities Section */}

Gateway Cities

- - - +
+ +
{selectedCities.length > 0 && (

{selectedCities.length}{" "} @@ -78,6 +100,52 @@ export default function Settings() { selected

)} +
+ + + + + + + + + + {selectedCities.map((cityValue) => ( + + + + + ))} + +
+ City + + Actions +
+ {getCityLabel(cityValue)} + + +
+
{/* Permitted Users Section */} @@ -86,15 +154,29 @@ export default function Settings() {

Permitted Users

-

- These emails are permitted to log into the system, - users can also transfer access. + These emails are permitted to sign into the + platform. Here you can also revoke access

+
+ + +
@@ -162,29 +244,29 @@ export default function Settings() { {/* Save Section */} -
+
- +
+ + +
diff --git a/src/lib/standardize.ts b/src/lib/school_name_standardize.ts similarity index 66% rename from src/lib/standardize.ts rename to src/lib/school_name_standardize.ts index 1204b78..97bb650 100644 --- a/src/lib/standardize.ts +++ b/src/lib/school_name_standardize.ts @@ -1,3 +1,15 @@ +/*************************************************************** + * + * school_name_standardize.ts + * + * Author: Will and Hansini + * Date: 12/6/2025 + * + * Summary: Standardizes a school's name by removing + * certain words, characters, and white space + * + **************************************************************/ + const words_to_remove: string[] = [ "public", "school", From 2dca2032f714232ff87f73c67bdb19a85e49e218 Mon Sep 17 00:00:00 2001 From: woleary2 Date: Sat, 6 Dec 2025 13:22:07 -0500 Subject: [PATCH 3/3] Minor style fixes --- src/app/settings/page.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 48c0714..032267b 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -220,19 +220,7 @@ export default function Settings() {