From 346954db3fa07d89e8e02bb798de22c91dc64439 Mon Sep 17 00:00:00 2001 From: Khalid Abdi Date: Fri, 5 Jun 2026 01:09:08 +0300 Subject: [PATCH] Collapse sidebar footer into the user menu; tweak sub-nav & appointments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Footer is now a single user row. The ⌘K command hint moved into the user menu below Theme, and the clinic switcher became a hover submenu below that (revealing the active clinic's name/slug/count, the switch list, and Create clinic). Removed SidebarCommandButton and deleted team-switcher.tsx. - Patients sub-nav (Patients / Appointments & Schedule): no icons, no background change on hover/active — only the text color changes. - Appointments & Schedule: added an "Add" button that opens the create-patient dialog. Co-Authored-By: Claude Opus 4.8 --- .../appointments/appointments-view.tsx | 45 ++++- frontend/components/command-palette.tsx | 33 +--- .../components/sidebar-02/app-sidebar.tsx | 20 +-- frontend/components/sidebar-02/nav-main.tsx | 3 +- frontend/components/sidebar-02/nav-user.tsx | 109 ++++++++++- .../components/sidebar-02/team-switcher.tsx | 170 ------------------ 6 files changed, 145 insertions(+), 235 deletions(-) delete mode 100644 frontend/components/sidebar-02/team-switcher.tsx diff --git a/frontend/components/appointments/appointments-view.tsx b/frontend/components/appointments/appointments-view.tsx index 8c6cea3..9215bf8 100644 --- a/frontend/components/appointments/appointments-view.tsx +++ b/frontend/components/appointments/appointments-view.tsx @@ -1,10 +1,12 @@ "use client"; -import { CalendarClock, Clock, Stethoscope, Users } from "lucide-react"; -import type { ReactNode } from "react"; +import { CalendarClock, Clock, Plus, Stethoscope, Users } from "lucide-react"; +import { type ReactNode, useState } from "react"; +import { PatientFormDialog } from "@/components/chat/patient-form-dialog"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; // All figures here are mock/placeholder data — there is no scheduling backend. @@ -199,15 +201,32 @@ function Section({ } export function AppointmentsView() { + const [addOpen, setAddOpen] = useState(false); + // Bumped on open so the create dialog remounts with a fresh file # / form. + const [addKey, setAddKey] = useState(0); + return (
-
-

- Appointments & Schedule -

-

- Today's clinic schedule and what's coming up. Sample data. -

+
+
+

+ Appointments & Schedule +

+

+ Today's clinic schedule and what's coming up. Sample data. +

+
+
@@ -225,6 +244,14 @@ export function AppointmentsView() { ))} + + setAddOpen(false)} + onOpenChange={setAddOpen} + open={addOpen} + />
); } diff --git a/frontend/components/command-palette.tsx b/frontend/components/command-palette.tsx index 1e954c9..70cc90f 100644 --- a/frontend/components/command-palette.tsx +++ b/frontend/components/command-palette.tsx @@ -1,11 +1,6 @@ "use client"; -import { - ArrowDownIcon, - ArrowUpIcon, - CornerDownLeftIcon, - Search, -} from "lucide-react"; +import { ArrowDownIcon, ArrowUpIcon, CornerDownLeftIcon } from "lucide-react"; import { useRouter } from "next/navigation"; import { createContext, @@ -32,7 +27,6 @@ import { CommandPanel, } from "@/components/ui/command"; import { Kbd, KbdGroup } from "@/components/ui/kbd"; -import { useSidebar } from "@/components/ui/sidebar"; import { navItems } from "@/lib/nav"; type CommandPaletteContextValue = { open: () => void }; @@ -171,28 +165,3 @@ export function CommandPaletteProvider({ children }: { children: ReactNode }) { ); } - -// Sidebar-footer affordance: shows the ⌘K hint and opens the palette on click. -// Hidden when the sidebar is collapsed to its icon rail. -export function SidebarCommandButton() { - const { open } = useCommandPalette(); - const { state } = useSidebar(); - const { t } = useTranslation(); - - if (state === "collapsed") return null; - - return ( - - ); -} diff --git a/frontend/components/sidebar-02/app-sidebar.tsx b/frontend/components/sidebar-02/app-sidebar.tsx index d7f567d..dc28042 100644 --- a/frontend/components/sidebar-02/app-sidebar.tsx +++ b/frontend/components/sidebar-02/app-sidebar.tsx @@ -5,7 +5,6 @@ import { SidebarContent, SidebarFooter, SidebarHeader, - SidebarSeparator, SidebarTrigger, useSidebar, } from "@/components/ui/sidebar"; @@ -20,11 +19,9 @@ import { motion } from "framer-motion"; import Image from "next/image"; import { useTranslation } from "react-i18next"; import type { Route } from "./nav-main"; -import { SidebarCommandButton } from "@/components/command-palette"; import DashboardNavigation from "@/components/sidebar-02/nav-main"; import { NotificationsPopover } from "@/components/sidebar-02/nav-notifications"; import { NavUser } from "@/components/sidebar-02/nav-user"; -import { OrgSwitcher } from "@/components/sidebar-02/team-switcher"; const sampleNotifications = [ { @@ -117,22 +114,7 @@ export function DashboardSidebar() { -
- - - - - -
+
); diff --git a/frontend/components/sidebar-02/nav-main.tsx b/frontend/components/sidebar-02/nav-main.tsx index 2cee71a..c683793 100644 --- a/frontend/components/sidebar-02/nav-main.tsx +++ b/frontend/components/sidebar-02/nav-main.tsx @@ -89,11 +89,10 @@ export default function DashboardNavigation({ routes }: { routes: Route[] }) { {route.subs?.map((subRoute) => ( } > - {subRoute.icon} {subRoute.title} diff --git a/frontend/components/sidebar-02/nav-user.tsx b/frontend/components/sidebar-02/nav-user.tsx index c888577..d39d985 100644 --- a/frontend/components/sidebar-02/nav-user.tsx +++ b/frontend/components/sidebar-02/nav-user.tsx @@ -1,17 +1,33 @@ "use client"; import { + Building2, + Check, ChevronsUpDown, LogOut, Moon, + Plus, + Search, Settings as SettingsIcon, Sun, } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useTheme } from "next-themes"; +import { useState } from "react"; +import { useCommandPalette } from "@/components/command-palette"; +import { CreateClinicForm } from "@/components/clinic/create-clinic-form"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; +import { + Dialog, + DialogDescription, + DialogHeader, + DialogPanel, + DialogPopup, + DialogTitle, +} from "@/components/ui/dialog"; +import { Kbd, KbdGroup } from "@/components/ui/kbd"; import { Menu, MenuGroup, @@ -20,6 +36,9 @@ import { MenuPopup, MenuSeparator, MenuShortcut, + MenuSub, + MenuSubPopup, + MenuSubTrigger, MenuTrigger, } from "@/components/ui/menu"; import { @@ -57,6 +76,9 @@ function GitHubIcon({ className }: { className?: string }) { ); } +// The sidebar's single footer row. Its menu also hosts the command-palette +// shortcut hint (below Theme) and a clinic switcher submenu (below that) whose +// popup reveals the active clinic's details on hover. export function NavUser() { const { isMobile, state } = useSidebar(); const isCollapsed = state === "collapsed"; @@ -64,10 +86,21 @@ export function NavUser() { const { resolvedTheme, setTheme } = useTheme(); const isDark = resolvedTheme === "dark"; const { data } = authClient.useSession(); + const { open: openCommand } = useCommandPalette(); + const { data: orgs } = authClient.useListOrganizations(); + const { data: activeOrg } = authClient.useActiveOrganization(); + + const [createOpen, setCreateOpen] = useState(false); const name = data?.user?.name ?? "Clinician"; const email = data?.user?.email ?? ""; const initials = initialsFromName(name); + const activeName = activeOrg?.name ?? "Select clinic"; + + const setActive = async (organizationId: string) => { + if (organizationId === activeOrg?.id) return; + await authClient.organization.setActive({ organizationId }); + }; const signOut = async () => { const { error } = await authClient.signOut(); @@ -99,7 +132,7 @@ export function NavUser() { <>
{name} - + {email}
@@ -109,7 +142,7 @@ export function NavUser() { @@ -120,7 +153,7 @@ export function NavUser() {
{name} - + {email}
@@ -145,6 +178,61 @@ export function NavUser() { Theme {isDark ? "Dark" : "Light"} + + {/* Command palette: hint + shortcut, sits below Theme. */} + + + Quick nav + + + K + + + + {/* Clinic switcher: hover reveals the active clinic's details. */} + + + + {activeName} + + +
+

+ {activeOrg?.name ?? "No clinic selected"} +

+

+ {activeOrg?.slug ? `/${activeOrg.slug}` : "—"} ·{" "} + {orgs?.length ?? 0}{" "} + {orgs?.length === 1 ? "clinic" : "clinics"} +

+
+ + + + Switch clinic + + {(orgs ?? []).map((org) => ( + setActive(org.id)} + > +
+ +
+ {org.name} + {org.id === activeOrg?.id && } +
+ ))} +
+ + setCreateOpen(true)}> + + Create clinic + +
+
+ @@ -153,6 +241,21 @@ export function NavUser() {
+ + {/* Create a new clinic */} + + + + Create clinic + + Add a new clinic and switch to it. + + + + setCreateOpen(false)} /> + + + ); } diff --git a/frontend/components/sidebar-02/team-switcher.tsx b/frontend/components/sidebar-02/team-switcher.tsx deleted file mode 100644 index 201a4f4..0000000 --- a/frontend/components/sidebar-02/team-switcher.tsx +++ /dev/null @@ -1,170 +0,0 @@ -"use client"; - -import { Building2, ChevronsUpDown, Info, Plus } from "lucide-react"; -import { useState } from "react"; - -import { CreateClinicForm } from "@/components/clinic/create-clinic-form"; -import { - Dialog, - DialogDescription, - DialogHeader, - DialogPanel, - DialogPopup, - DialogTitle, -} from "@/components/ui/dialog"; -import { - Menu, - MenuGroup, - MenuGroupLabel, - MenuItem, - MenuPopup, - MenuSeparator, - MenuTrigger, -} from "@/components/ui/menu"; -import { - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, - useSidebar, -} from "@/components/ui/sidebar"; -import { authClient } from "@/lib/auth-client"; - -function InfoRow({ label, value }: { label: string; value: string }) { - return ( -
- {label} - {value} -
- ); -} - -// Switches the active clinic (organization). Scopes every subsequent patient -// API call. Lives in the sidebar footer; its menu also opens dialogs to view -// clinic info or create a new clinic. -export function OrgSwitcher() { - const { isMobile, state } = useSidebar(); - const isCollapsed = state === "collapsed"; - const { data: orgs } = authClient.useListOrganizations(); - const { data: activeOrg } = authClient.useActiveOrganization(); - - const [infoOpen, setInfoOpen] = useState(false); - const [createOpen, setCreateOpen] = useState(false); - - const setActive = async (organizationId: string) => { - if (organizationId === activeOrg?.id) return; - await authClient.organization.setActive({ organizationId }); - }; - - const activeName = activeOrg?.name ?? "Select clinic"; - - return ( - - - - - } - > -
- -
- {!isCollapsed && ( - <> -
- {activeName} - - Clinic - -
- - - )} -
- - - - Clinics - - {(orgs ?? []).map((org) => ( - setActive(org.id)} - > -
- -
- {org.name} -
- ))} -
- - setInfoOpen(true)} - > -
- -
-
- Clinic info -
-
- setCreateOpen(true)}> -
- -
-
- Create clinic -
-
-
-
-
- - {/* Read-only clinic details */} - - - - {activeOrg?.name ?? "Clinic"} - Clinic information - - - - - - - - - - {/* Create a new clinic (replaces the old /onboarding redirect) */} - - - - Create clinic - - Add a new clinic and switch to it. - - - - setCreateOpen(false)} /> - - - -
- ); -}