import { Link, useLocation, useNavigate } from "@tanstack/react-router"; import { useEffect, useState } from "react"; import { useAuth } from "@/lib/auth"; import { Button } from "@/components/ui/button"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { supabase } from "@/integrations/supabase/client"; import { Briefcase, Users, Receipt, ShieldCheck, LogOut, Scale, LayoutDashboard, CheckSquare, MessageSquare, Activity, DollarSign, FileText, FolderOpen, Settings as SettingsIcon, } from "lucide-react"; import { cn } from "@/lib/utils"; import type { ReactNode } from "react"; import { useBubbleCounts } from "@/lib/use-bubble-counts"; import { NotificationBell } from "@/components/notifications/notification-bell"; import { DateCalculator } from "@/components/date-calculator"; import { HeaderTimer } from "@/components/timer/header-timer"; import { QuickAddTime, QuickAddExpense } from "@/components/quick-add/quick-add"; function initials(name: string, email: string) { const src = (name || email || "").trim(); return src.split(/\s+/).map((p) => p[0]).filter(Boolean).slice(0, 2).join("").toUpperCase(); } interface NavItem { to: string; label: string; icon: typeof Briefcase; adminOnly?: boolean; bubbleKey?: "messages" | "tasks"; } const NAV: NavItem[] = [ { to: "/", label: "Dashboard", icon: LayoutDashboard }, { to: "/clients", label: "Clients", icon: Users }, { to: "/cases", label: "Cases", icon: Briefcase }, { to: "/tasks", label: "Tasks", icon: CheckSquare, bubbleKey: "tasks" }, { to: "/messages", label: "Messages", icon: MessageSquare, bubbleKey: "messages" }, { to: "/status", label: "Status Updates", icon: Activity }, { to: "/collections", label: "Collections", icon: DollarSign }, { to: "/documents", label: "Documents", icon: FileText }, { to: "/files", label: "Files", icon: FolderOpen }, { to: "/invoices", label: "Invoices", icon: Receipt }, { to: "/settings", label: "Settings", icon: SettingsIcon }, { to: "/admin/users", label: "Users", icon: ShieldCheck, adminOnly: true }, ]; function Bubble({ count }: { count: number }) { if (!count) return null; return ( {count > 99 ? "99+" : count} ); } export function AppShell({ children }: { children: ReactNode }) { const { user, signOut, isAdmin, roles } = useAuth(); const location = useLocation(); const navigate = useNavigate(); const counts = useBubbleCounts(); const [profile, setProfile] = useState<{ full_name: string; avatar_url: string | null } | null>(null); useEffect(() => { if (!user) return; supabase .from("profiles") .select("full_name, avatar_url") .eq("id", user.id) .maybeSingle() .then(({ data }) => { if (data) setProfile({ full_name: data.full_name || "", avatar_url: (data as any).avatar_url ?? null }); }); const ch = supabase .channel(`profile-self-${user.id}-${Math.random().toString(36).slice(2)}`) .on( "postgres_changes", { event: "UPDATE", schema: "public", table: "profiles", filter: `id=eq.${user.id}` }, (payload) => { const r = payload.new as any; setProfile({ full_name: r.full_name || "", avatar_url: r.avatar_url ?? null }); }, ) .subscribe(); return () => { supabase.removeChannel(ch); }; }, [user]); const handleSignOut = async () => { await signOut(); navigate({ to: "/login" }); }; return (
{/* Sidebar */} {/* Mobile top bar */}
Counsel
{/* Desktop top toolbar */}
{NAV.filter((n) => !n.adminOnly || isAdmin).map((item) => { const active = item.to === "/" ? location.pathname === "/" : location.pathname.startsWith(item.to); const bubble = item.bubbleKey ? counts[item.bubbleKey] : 0; return ( {item.label} {bubble > 0 && ( {bubble > 99 ? "99+" : bubble} )} ); })}
{children}
); } export function PageHeader({ title, description, actions, }: { title: string; description?: string; actions?: ReactNode; }) { return (

{title}

{description &&

{description}

}
{actions &&
{actions}
}
); } export function PageContainer({ children }: { children: ReactNode }) { return
{children}
; }