diff --git a/package-lock.json b/package-lock.json index 67fa4ae..18744bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@mantine/hooks": "^7.13.1", "@tabler/icons-react": "3.17.0", "dayjs": "^1.11.13", + "nuqs": "^2.2.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.27.0", @@ -1019,9 +1020,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", - "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", + "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3886,9 +3887,9 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -6401,6 +6402,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -6480,6 +6487,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/nuqs": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-2.2.1.tgz", + "integrity": "sha512-P0swZtg6k9LRXzlT9gQAFtQg25Edan2Evt5vwyGvh00Z+rHH19gb2ZajU/E/SfhwE858aM/jb+wJziWbbwSlcg==", + "license": "MIT", + "dependencies": { + "mitt": "^3.0.1" + }, + "peerDependencies": { + "@remix-run/react": ">=2", + "next": ">=14.2.0", + "react": ">=18.2.0 || ^19.0.0-0", + "react-router-dom": ">=6" + }, + "peerDependenciesMeta": { + "@remix-run/react": { + "optional": true + }, + "next": { + "optional": true + }, + "react-router-dom": { + "optional": true + } + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", diff --git a/package.json b/package.json index 62e8e21..40add0d 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@mantine/hooks": "^7.13.1", "@tabler/icons-react": "3.17.0", "dayjs": "^1.11.13", + "nuqs": "^2.2.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.27.0", diff --git a/src/components/SchemaSearch.tsx b/src/components/SchemaSearch.tsx index 5a1c12e..6e7a663 100644 --- a/src/components/SchemaSearch.tsx +++ b/src/components/SchemaSearch.tsx @@ -1,44 +1,24 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; import { MultiSelect } from "@mantine/core"; import "@/css/SchemaSearch.css"; +import { parseAsArrayOf, parseAsString, useQueryState } from "nuqs"; interface SchemaSearch { schemas: string[]; - clear: boolean; } -const SchemaSearch: React.FC = ({ schemas, clear }) => { - const [value, setValue] = useState(""); - const [selectedSchemas, setSelectedSchemas] = useState([]); - - const filteredSchemas = schemas.filter((schema) => - schema.toLowerCase().includes(value.toLowerCase()), +const SchemaSearch: React.FC = ({ schemas }) => { + const [selectedSchemas, setSelectedSchemas] = useQueryState( + "schemas", + parseAsArrayOf(parseAsString).withDefault([]), ); - - - const clearSchema = () => { - setValue(""); - setSelectedSchemas([]) - }; - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === "Enter" && filteredSchemas.length > 0) { - setValue(filteredSchemas[0]); - setValue("") - } - }; - - useEffect(() => { - clearSchema(); - }, [clear]); return (
>; setSearch: React.Dispatch>; } -function SearchBarWithFilter({ - setSearchFilters, - setSearch, -}: SearchBarWithFilterProps) { - const [searchTerm, setSearchTerm] = useState(""); - const [selectedLocation, setSelectedLocation] = useState(""); - const [selectedEventType, setSelectedEventType] = useState(""); - const [beforeDate, setBeforeDate] = useState(""); - const [afterDate, setAfterDate] = useState(""); - const [clearSchemas, setClearSchemas] = useState(false); +function SearchBarWithFilter({ setSearch }: SearchBarWithFilterProps) { + const [searchTerm, setSearchTerm] = useQueryState( + "notes", + parseAsString.withDefault(""), + ); + const [selectedLocation, setSelectedLocation] = useQueryState( + "location", + parseAsString.withDefault(""), + ); + const [selectedEventType, setSelectedEventType] = useQueryState( + "event", + parseAsString.withDefault(""), + ); + const [beforeDate, setBeforeDate] = useQueryState( + "beforeDate", + parseAsString.withDefault(""), + ); + const [afterDate, setAfterDate] = useQueryState( + "afterDate", + parseAsString.withDefault(""), + ); + const [, setSelectedSchemas] = useQueryState( + "schemas", + parseAsArrayOf(parseAsString).withDefault([]), + ); const schemas = ["Schema1", "Schema2", "Schema3", "Schema4"]; - // Handle filter changes - function handleFilterChange( - e: React.ChangeEvent, - ) { - const { name } = e.target; - const { value } = e.target; - - - // Update local state based on input - switch (name) { - case "location": - setSelectedLocation(value); - break; - case "eventType": - setSelectedEventType(value); - break; - case "beforeDate": - setBeforeDate(value); - break; - case "afterDate": - setAfterDate(value); - break; - case "search_text": - setSearchTerm(value); - break; - default: - break; - } - } - // Clear all filters and search term const handleClear = () => { - setSearchTerm(""); - setSearchFilters({ - searchText: "", - location: "", - eventType: "", - beforeDate: "", - afterDate: "", - }); - setSelectedLocation(""); - setSelectedEventType(""); - setBeforeDate(""); - setAfterDate(""); - setClearSchemas((prev) => !prev); + setSearchTerm(null); + setSelectedLocation(null); + setSelectedEventType(null); + setBeforeDate(null); + setAfterDate(null); + setSelectedSchemas(null); }; const handleSearch = () => { @@ -86,9 +64,7 @@ function SearchBarWithFilter({ className="search-bar" placeholder="Search by file name or notes..." value={searchTerm} - onChange={(e) => { - handleFilterChange(e); - }} + onChange={(e) => setSearchTerm(e.target.value)} /> {/* Filter Options */} @@ -98,7 +74,7 @@ function SearchBarWithFilter({ setSelectedEventType(e.target.value)} className="filter-select" > @@ -133,7 +109,7 @@ function SearchBarWithFilter({ type="date" name="beforeDate" value={beforeDate} - onChange={handleFilterChange} + onChange={(e) => setBeforeDate(e.target.value)} className="date-picker" /> @@ -144,18 +120,20 @@ function SearchBarWithFilter({ type="date" name="afterDate" value={afterDate} - onChange={handleFilterChange} + onChange={(e) => setAfterDate(e.target.value)} + //value={afterDate.split("T")[0]} + //onChange={(e) => setAfterDate(e.target.value + "T23:59:59Z07:00")} className="date-picker" /> - +
{/* Clear Button */} diff --git a/src/main.tsx b/src/main.tsx index e512040..28af4bd 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -6,6 +6,7 @@ import { Route, RouterProvider, } from "react-router-dom"; +import { NuqsAdapter } from "nuqs/adapters/react-router"; import Root from "./routes/root"; import Docs from "./routes/docs"; import Changelog from "./routes/changelog"; @@ -27,6 +28,8 @@ const router = createBrowserRouter( createRoot(document.getElementById("root")!).render( - + + + , ); diff --git a/src/routes/root.tsx b/src/routes/root.tsx index cbec9f5..a3510a1 100644 --- a/src/routes/root.tsx +++ b/src/routes/root.tsx @@ -3,33 +3,59 @@ import SearchBar from "@/components/SearchBar"; import "@/css/Root.css"; import DataTable from "@/components/DataTable"; import PreviewCard from "@/components/PreviewCard"; +import { parseAsArrayOf, parseAsString, useQueryState } from "nuqs"; export default function Root() { const [filteredData, setFilteredData] = useState(); const [selectedRow, setSelectedRow] = useState(""); const [selectedData, setSelectedData] = useState(); - const [searchFilters, setSearchFilters] = useState({ - location: "", - eventType: "", - beforeDate: "", - afterDate: "", - searchText: "", - }); + + const [searchTerm] = useQueryState("notes", parseAsString.withDefault("")); + const [selectedLocation] = useQueryState( + "location", + parseAsString.withDefault(""), + ); + const [selectedEventType] = useQueryState( + "event", + parseAsString.withDefault(""), + ); + const [beforeDate] = useQueryState( + "beforeDate", + parseAsString.withDefault(""), + ); + const [afterDate] = useQueryState("afterDate", parseAsString.withDefault("")); + const [selectedSchemas] = useQueryState( + "schemas", + parseAsArrayOf(parseAsString).withDefault([]), + ); + + const searchFilters = { + location: selectedLocation, + date: selectedEventType, + eventType: selectedEventType, + beforeDate, + afterDate, + searchText: searchTerm, + selectedSchemas, + }; + const [search, setSearch] = useState(false); - const formatDate = (dateStr: string) => { + const formatDate = (dateStr: string, time: string) => { const [year, month, day] = dateStr.split("-"); if (month.length !== 2 || day.length !== 2 || year.length !== 4) { - throw new Error(`Invalid date format: ${dateStr}`); + throw new Error(`Invalid date format: ${dateStr}`); } - return new Date(`${year}-${month}-${day}T00:00:00.000Z`).toISOString(); + return new Date(`${year}-${month}-${day}T${time}Z`).toISOString(); }; const fetchData = async (filters: SearchFilter) => { const { location, date, eventType, searchText } = filters; let { afterDate, beforeDate } = filters; - - beforeDate = beforeDate ? formatDate(beforeDate) : undefined; - afterDate = afterDate ? formatDate(afterDate) : undefined; + + beforeDate = beforeDate + ? formatDate(beforeDate, "00:00:00.000") + : undefined; + afterDate = afterDate ? formatDate(afterDate, "23:59:59.999") : undefined; const params = { ...(location ? { location } : {}), ...(eventType ? { eventType } : {}), @@ -44,7 +70,7 @@ export default function Root() { const res = await fetch( `${import.meta.env.VITE_API_URL}/api/v2/mcaps?${queryString}`, ); - + const data = await res.json(); return data.data; }; @@ -52,11 +78,13 @@ export default function Root() { const assignData = async () => { const data = await fetchData(searchFilters); console.log(data); - const sortedData = data.sort((a: MCAPFileInformation, b: MCAPFileInformation) => { - const dateA = new Date(a.date); - const dateB = new Date(b.date); - return dateB.getTime() - dateA.getTime(); - }); + const sortedData = data.sort( + (a: MCAPFileInformation, b: MCAPFileInformation) => { + const dateA = new Date(a.date); + const dateB = new Date(b.date); + return dateB.getTime() - dateA.getTime(); + }, + ); setFilteredData(sortedData); }; @@ -64,17 +92,20 @@ export default function Root() { assignData(); }, []); - // Two useEffects bc of the way we are handling the Search Button D: + // Two useEffects bc of the way we are handling the Search Button D: useEffect(() => { const getData = async () => { - if (search) { // Only fetch data when search is true + if (search) { + // Only fetch data when search is true const data = await fetchData(searchFilters); console.log(data); - const sortedData = data.sort((a: MCAPFileInformation, b: MCAPFileInformation) => { - const dateA = new Date(a.date); - const dateB = new Date(b.date); - return dateB.getTime() - dateA.getTime(); - }); + const sortedData = data.sort( + (a: MCAPFileInformation, b: MCAPFileInformation) => { + const dateA = new Date(a.date); + const dateB = new Date(b.date); + return dateB.getTime() - dateA.getTime(); + }, + ); setFilteredData(sortedData); setSearch(false); @@ -95,7 +126,7 @@ export default function Root() { />
- +