Make demo search, filters, exports, and controls functional while exposing dedicated KPI detail pages with tested breakdowns. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import {
|
||||
Bell,
|
||||
Boxes,
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
Store,
|
||||
Users,
|
||||
} from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
import { useState, type ReactNode } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const navigation = [
|
||||
@@ -27,8 +27,31 @@ const navigation = [
|
||||
{ href: "/alerts", label: "Alerts", icon: Bell },
|
||||
];
|
||||
|
||||
export function DashboardShell({ children }: { children: ReactNode }) {
|
||||
interface SearchItem {
|
||||
id: string;
|
||||
label: string;
|
||||
detail: string;
|
||||
href: string;
|
||||
keywords: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export function DashboardShell({
|
||||
children,
|
||||
searchItems,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
searchItems: SearchItem[];
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const [query, setQuery] = useState("");
|
||||
const [profileOpen, setProfileOpen] = useState(false);
|
||||
const matches = query.trim().length
|
||||
? searchItems
|
||||
.filter((item) => item.keywords.toLowerCase().includes(query.trim().toLowerCase()))
|
||||
.slice(0, 7)
|
||||
: [];
|
||||
|
||||
if (pathname === "/login") {
|
||||
return <>{children}</>;
|
||||
@@ -109,8 +132,43 @@ export function DashboardShell({ children }: { children: ReactNode }) {
|
||||
<input
|
||||
aria-label="Search products, branches, or people"
|
||||
placeholder="Search products, branches, or people..."
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Escape") setQuery("");
|
||||
}}
|
||||
className="h-10 w-full rounded-xl border border-slate-200 bg-slate-50/80 pl-10 pr-3 text-sm outline-none transition focus:border-emerald-400 focus:bg-white focus:ring-4 focus:ring-emerald-50"
|
||||
/>
|
||||
{query.trim() && (
|
||||
<div className="absolute left-0 right-0 top-12 z-50 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-xl shadow-slate-900/10">
|
||||
{matches.length ? (
|
||||
<div className="p-2">
|
||||
{matches.map((item) => (
|
||||
<button
|
||||
key={`${item.type}-${item.id}`}
|
||||
onClick={() => {
|
||||
router.push(item.href);
|
||||
setQuery("");
|
||||
}}
|
||||
className="flex w-full items-center justify-between rounded-xl px-3 py-2.5 text-left hover:bg-slate-50"
|
||||
>
|
||||
<span>
|
||||
<span className="block text-xs font-bold text-slate-900">{item.label}</span>
|
||||
<span className="mt-0.5 block text-[11px] text-slate-400">{item.detail}</span>
|
||||
</span>
|
||||
<span className="rounded-full bg-emerald-50 px-2 py-1 text-[9px] font-bold uppercase tracking-wider text-emerald-700">
|
||||
{item.type}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="p-5 text-center text-xs text-slate-500">
|
||||
No products, branches, or people match “{query}”.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-auto flex items-center gap-2 sm:gap-4">
|
||||
<Link
|
||||
@@ -121,7 +179,13 @@ export function DashboardShell({ children }: { children: ReactNode }) {
|
||||
<Bell className="size-5" />
|
||||
<span className="absolute right-2 top-2 size-2 rounded-full border-2 border-white bg-amber-500" />
|
||||
</Link>
|
||||
<button className="flex items-center gap-2 rounded-xl p-1.5 pr-2 hover:bg-slate-50">
|
||||
<div className="relative">
|
||||
<button
|
||||
aria-expanded={profileOpen}
|
||||
aria-label="Open account menu"
|
||||
onClick={() => setProfileOpen((open) => !open)}
|
||||
className="flex items-center gap-2 rounded-xl p-1.5 pr-2 hover:bg-slate-50"
|
||||
>
|
||||
<span className="grid size-8 place-items-center rounded-lg bg-slate-900 text-xs font-bold text-white">
|
||||
NC
|
||||
</span>
|
||||
@@ -131,6 +195,20 @@ export function DashboardShell({ children }: { children: ReactNode }) {
|
||||
</span>
|
||||
<ChevronDown className="hidden size-4 text-slate-400 sm:block" />
|
||||
</button>
|
||||
{profileOpen && (
|
||||
<div className="absolute right-0 top-12 z-50 w-52 rounded-xl border border-slate-200 bg-white p-2 shadow-xl shadow-slate-900/10">
|
||||
<p className="px-3 py-2 text-[11px] text-slate-400">Demo account</p>
|
||||
<Link href="/settings/integrations" onClick={() => setProfileOpen(false)} className="block rounded-lg px-3 py-2 text-xs font-semibold hover:bg-slate-50">
|
||||
Workspace settings
|
||||
</Link>
|
||||
<form action="/api/auth/logout" method="post">
|
||||
<button className="w-full rounded-lg px-3 py-2 text-left text-xs font-semibold text-rose-600 hover:bg-rose-50">
|
||||
Sign out
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user