diff --git a/bun.lock b/bun.lock index ba88731..f71c8e2 100644 --- a/bun.lock +++ b/bun.lock @@ -21,6 +21,7 @@ "react": "19.2.3", "react-dom": "19.2.3", "react-map-gl": "^8.1.0", + "react-photo-view": "^1.2.7", "tailwind-merge": "^3.4.0", "tw-animate-css": "^1.4.0", "zustand": "^5.0.9", @@ -922,6 +923,8 @@ "react-map-gl": ["react-map-gl@8.1.0", "", { "dependencies": { "@vis.gl/react-mapbox": "8.1.0", "@vis.gl/react-maplibre": "8.1.0" }, "peerDependencies": { "mapbox-gl": ">=1.13.0", "maplibre-gl": ">=1.13.0", "react": ">=16.3.0", "react-dom": ">=16.3.0" }, "optionalPeers": ["mapbox-gl", "maplibre-gl"] }, "sha512-vDx/QXR3Tb+8/ap/z6gdMjJQ8ZEyaZf6+uMSPz7jhWF5VZeIsKsGfPvwHVPPwGF43Ryn+YR4bd09uEFNR5OPdg=="], + "react-photo-view": ["react-photo-view@1.2.7", "", { "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-MfOWVPxuibncRLaycZUNxqYU8D9IA+rbGDDaq6GM8RIoGJal592hEJoRAyRSI7ZxyyJNJTLMUWWL3UIXHJJOpw=="], + "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], diff --git a/components/sidebar/intersection-sidebar.tsx b/components/sidebar/intersection-sidebar.tsx index 5234d35..a5e8bee 100644 --- a/components/sidebar/intersection-sidebar.tsx +++ b/components/sidebar/intersection-sidebar.tsx @@ -1,5 +1,6 @@ "use client"; +import { useState, useEffect, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { useMapStore } from "@/stores/map-store"; import { X } from "lucide-react"; @@ -9,6 +10,43 @@ import { PhotoProvider } from "react-photo-view"; export function IntersectionSidebar() { const { selectedHotspot, selectHotspot } = useMapStore(); + const [activeTab, setActiveTab] = useState("overview"); + const [direction, setDirection] = useState(1); + + // Tab order for directional animations + const tabOrder = ["overview", "audit", "reimagine"]; + + // Reset to overview tab when hotspot changes + useEffect(() => { + if (selectedHotspot) { + setActiveTab("overview"); + setDirection(1); + } + }, [selectedHotspot?.id]); + + // Handle tab change + const handleTabChange = (newTab: string) => { + const currentIndex = tabOrder.indexOf(activeTab); + const newIndex = tabOrder.indexOf(newTab); + const newDirection = newIndex > currentIndex ? 1 : -1; + setDirection(newDirection); + setActiveTab(newTab); + }; + + const variants = { + enter: (direction: number) => ({ + x: direction > 0 ? 80 : -80, + opacity: 0, + }), + center: { + x: 0, + opacity: 1, + }, + exit: (direction: number) => ({ + x: direction < 0 ? 80 : -80, + opacity: 0, + }), + }; return ( @@ -16,12 +54,12 @@ export function IntersectionSidebar() { -
-
-

- {selectedHotspot.intersection} -

-

- {selectedHotspot.total_count} collisions recorded -

-
- -
+
+
+

+ {selectedHotspot.intersection} +

+

+ {selectedHotspot.total_count} collisions recorded +

+
+ +
-
- - - - Overview - Safety Audit - Re-imagine - +
+ + + + + Overview + + + Safety Audit + + + Re-imagine + + - - - +
+ + {activeTab === "overview" && ( + + + + )} - -
+ {activeTab === "audit" && ( +

Generate a safety audit to see AI analysis

-
-
+ + )} - -
+ {activeTab === "reimagine" && ( +

Re-imagine this intersection with AI

-
-
- - -
- + + )} + +
+
+
+
+
)}
diff --git a/components/sidebar/persona-sidebar.tsx b/components/sidebar/persona-sidebar.tsx index 4629fca..6db3df4 100644 --- a/components/sidebar/persona-sidebar.tsx +++ b/components/sidebar/persona-sidebar.tsx @@ -208,12 +208,12 @@ export function PersonaSidebar() { connectionType: "websocket", dynamicVariables: selectedHotspot ? { - intersection_name: selectedHotspot.intersection || selectedHotspot.address, - collision_count: selectedHotspot.total_count.toString(), - fatal_count: selectedHotspot.fatal_count.toString(), - cyclist_count: selectedHotspot.cyclist_count.toString(), - pedestrian_count: selectedHotspot.pedestrian_count.toString(), - } + intersection_name: selectedHotspot.intersection || selectedHotspot.address, + collision_count: selectedHotspot.total_count.toString(), + fatal_count: selectedHotspot.fatal_count.toString(), + cyclist_count: selectedHotspot.cyclist_count.toString(), + pedestrian_count: selectedHotspot.pedestrian_count.toString(), + } : undefined, }); } catch (error) { @@ -247,7 +247,7 @@ export function PersonaSidebar() { {currentView === "list" ? ( @@ -256,6 +256,10 @@ export function PersonaSidebar() { initial={{ opacity: 0, x: -20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }} + transition={{ + duration: 0.3, + ease: "easeInOut", + }} className="flex flex-col h-full" > {/* Header */} @@ -267,11 +271,18 @@ export function PersonaSidebar() { {/* Agent List */} -
- {PERSONAS.map((persona) => ( +
+ {PERSONAS.map((persona, index) => ( selectPersona(persona)} + initial={{ opacity: 0, y: 20 }} + animate={{ opacity: 1, y: 0 }} + transition={{ + duration: 0.3, + delay: index * 0.1, + ease: "easeOut", + }} whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} className="w-full bg-zinc-900 hover:bg-zinc-800 border border-zinc-800 hover:border-zinc-700 rounded-xl p-4 transition-all text-left group" @@ -327,17 +338,23 @@ export function PersonaSidebar() { initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: 20 }} + transition={{ + duration: 0.3, + ease: "easeInOut", + }} className="flex flex-col h-full" > {/* Header with Back Button */}
- +

{selectedPersona.name} @@ -363,7 +380,7 @@ export function PersonaSidebar() {

{/* Transcript Area */} -
+
{transcriptMessages.length === 0 && !isCallInProgress && (
- {transcriptMessages.map((message) => ( + {transcriptMessages.map((message, index) => (
diff --git a/next.config.ts b/next.config.ts index fa67b35..cb651cd 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,9 +1,5 @@ import type { NextConfig } from "next"; -const nextConfig: NextConfig = { - turbopack: { - root: __dirname, - }, -}; +const nextConfig: NextConfig = {}; export default nextConfig;