Files
temetro/frontend/components/sidebar-02/app-sidebar.tsx
T
Khalid Abdi a4fc9a4fe9 Add command palette, footer clinic menu, patient Sheet, Analysis page, favicon
Several navigation/UX additions:

- Command palette (⌘K): components/command-palette.tsx wraps the app shell
  (mounted in app/(app)/layout.tsx) with a controlled COSS CommandDialog listing
  the nav pages; a "Quick nav" Kbd button in the sidebar footer also opens it.
  Installed @coss/kbd. Extracted the nav list into lib/nav.ts so the sidebar and
  palette share one source of truth.
- Clinic switcher moved from the sidebar body into the footer; its menu now
  opens a read-only "Clinic info" dialog and a "Create clinic" dialog instead of
  routing to /onboarding. Shared CreateClinicForm (components/clinic/) is reused
  by onboarding.
- Patients: clicking a row opens a right-side Sheet with the full record
  (components/patients/patient-detail-sheet.tsx) instead of navigating to the
  chat; PatientResult gained a vertical "column" layout for the Sheet.
- New Analysis page (app/(app)/analysis/) — a mock dashboard (revenue/profit,
  patient volume, appointments, operations) reusing Sparkline + Card + Badge.
- Logo: set metadata.icons so the new mark appears as the browser-tab favicon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 19:40:11 +03:00

110 lines
3.0 KiB
TypeScript

"use client";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarTrigger,
useSidebar,
} from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";
import { navItems } from "@/lib/nav";
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 = [
{
id: "1",
avatar: "/avatars/01.png",
fallback: "LR",
text: "New lab results are available.",
time: "10m ago",
},
{
id: "2",
avatar: "/avatars/02.png",
fallback: "PC",
text: "A patient chart was updated.",
time: "1h ago",
},
{
id: "3",
avatar: "/avatars/03.png",
fallback: "CT",
text: "New message from the care team.",
time: "2h ago",
},
];
export function DashboardSidebar() {
const { state } = useSidebar();
const { t } = useTranslation();
const isCollapsed = state === "collapsed";
const dashboardRoutes: Route[] = navItems.map((item) => ({
id: item.id,
title: t(item.labelKey),
icon: <item.icon className="size-4" />,
link: item.link,
}));
return (
<Sidebar variant="inset" collapsible="icon">
<SidebarHeader
className={cn(
"flex md:pt-3.5",
isCollapsed
? "flex-row items-center justify-between gap-y-4 md:flex-col md:items-start md:justify-start"
: "flex-row items-center justify-between"
)}
>
<a href="#" className="flex items-center gap-2">
<Image
src="/temetro-logo.png"
alt="temetro"
width={32}
height={32}
className="size-8 shrink-0"
priority
/>
{!isCollapsed && (
<span className="font-semibold text-black dark:text-white">
temetro
</span>
)}
</a>
<motion.div
key={isCollapsed ? "header-collapsed" : "header-expanded"}
className={cn(
"flex items-center gap-2",
isCollapsed ? "flex-row md:flex-col-reverse" : "flex-row"
)}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8 }}
>
<NotificationsPopover notifications={sampleNotifications} />
<SidebarTrigger />
</motion.div>
</SidebarHeader>
<SidebarContent className="gap-4 px-2 py-4">
<DashboardNavigation routes={dashboardRoutes} />
</SidebarContent>
<SidebarFooter className="gap-2 px-2">
<SidebarCommandButton />
<OrgSwitcher />
<NavUser />
</SidebarFooter>
</Sidebar>
);
}