Code edited in Lovable Code Editor

Edited UI in Lovable

Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-17 02:47:13 +00:00
co-authored by renee-png
parent ea487c02f9
commit ff3afeef11
+14 -115
View File
@@ -1,28 +1,9 @@
import { Link, useLocation, useNavigate } from "@tanstack/react-router";
import { useAuth } from "@/lib/auth";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { HeaderTimer } from "@/components/timer/header-timer";
import { QuickAddTime, QuickAddExpense } from "@/components/quick-add/quick-add";
import { supabase } from "@/integrations/supabase/client";
import {
Briefcase,
Users,
Receipt,
ShieldCheck,
LogOut,
Scale,
LayoutDashboard,
Settings,
Wallet,
FileSignature,
FolderArchive,
ClipboardList,
Contact,
MessageSquare,
} from "lucide-react";
import { Briefcase, Users, FileText, Receipt, ShieldCheck, LogOut, Scale, LayoutDashboard } from "lucide-react";
import { cn } from "@/lib/utils";
import { useEffect, useState, useCallback, type ReactNode } from "react";
import type { ReactNode } from "react";
interface NavItem {
to: string;
@@ -35,71 +16,14 @@ const NAV: NavItem[] = [
{ to: "/", label: "Dashboard", icon: LayoutDashboard },
{ to: "/clients", label: "Clients", icon: Users },
{ to: "/cases", label: "Cases", icon: Briefcase },
{ to: "/contacts", label: "Contacts", icon: Contact },
{ to: "/messages", label: "Messages", icon: MessageSquare },
{ to: "/status", label: "Status Updates", icon: ClipboardList },
{ to: "/collections", label: "Collections", icon: Wallet },
{ to: "/documents", label: "Documents", icon: FileSignature },
{ to: "/files", label: "Files", icon: FolderArchive },
{ to: "/invoices", label: "Invoices", icon: Receipt },
{ to: "/admin/users", label: "Users", icon: ShieldCheck, adminOnly: true },
{ to: "/settings", label: "Settings", icon: Settings, adminOnly: true },
];
function useUnreadMessagesCount() {
const { user } = useAuth();
const uid = user?.id;
const [count, setCount] = useState(0);
const refresh = useCallback(async () => {
if (!uid) {
setCount(0);
return;
}
const { data: mems } = await supabase
.from("conversation_members")
.select("conversation_id, last_read_at")
.eq("user_id", uid);
if (!mems || mems.length === 0) {
setCount(0);
return;
}
let total = 0;
await Promise.all(
mems.map(async (m) => {
const { count: c } = await supabase
.from("messages")
.select("id", { count: "exact", head: true })
.eq("conversation_id", m.conversation_id)
.gt("created_at", m.last_read_at)
.neq("sender_id", uid);
total += c ?? 0;
}),
);
setCount(total);
}, [uid]);
useEffect(() => {
refresh();
if (!uid) return;
const ch = supabase
.channel("sidebar-unread")
.on("postgres_changes", { event: "*", schema: "public", table: "messages" }, () => refresh())
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "conversation_members", filter: `user_id=eq.${uid}` }, () => refresh())
.subscribe();
return () => {
supabase.removeChannel(ch);
};
}, [uid, refresh]);
return count;
}
export function AppShell({ children }: { children: ReactNode }) {
const { user, signOut, isAdmin, roles } = useAuth();
const location = useLocation();
const navigate = useNavigate();
const unreadMessages = useUnreadMessagesCount();
const handleSignOut = async () => {
await signOut();
@@ -115,21 +39,15 @@ export function AppShell({ children }: { children: ReactNode }) {
<Scale className="h-5 w-5 text-sidebar-primary-foreground" />
</div>
<div className="leading-tight">
<div className="font-serif text-lg">Counsel</div>
<div className="text-[10px] uppercase tracking-widest text-sidebar-foreground/60">
Law Firm Suite
</div>
<div className="font-serif text-lg">Stage Law Firm, PLLC</div>
<div className="text-[10px] uppercase tracking-widest text-sidebar-foreground/60">Law Firm Suite</div>
</div>
</div>
<nav className="flex-1 px-3 py-4 space-y-0.5">
{NAV.filter((n) => !n.adminOnly || isAdmin).map((item) => {
const active =
item.to === "/"
? location.pathname === "/"
: location.pathname.startsWith(item.to);
const active = item.to === "/" ? location.pathname === "/" : location.pathname.startsWith(item.to);
const Icon = item.icon;
const showBadge = item.to === "/messages" && unreadMessages > 0;
return (
<Link
key={item.to}
@@ -142,12 +60,7 @@ export function AppShell({ children }: { children: ReactNode }) {
)}
>
<Icon className="h-4 w-4" />
<span className="flex-1">{item.label}</span>
{showBadge && (
<Badge variant="destructive" className="h-5 px-1.5 text-[10px]">
{unreadMessages}
</Badge>
)}
{item.label}
</Link>
);
})}
@@ -171,34 +84,20 @@ export function AppShell({ children }: { children: ReactNode }) {
</aside>
{/* Mobile top bar */}
<div className="md:hidden fixed top-0 inset-x-0 h-14 bg-sidebar text-sidebar-foreground border-b border-sidebar-border flex items-center justify-between px-4 z-30 gap-2">
<Link to="/" className="flex items-center gap-2 min-w-0">
<Scale className="h-5 w-5 shrink-0" />
<span className="font-serif text-lg truncate">Counsel</span>
<div className="md:hidden fixed top-0 inset-x-0 h-14 bg-sidebar text-sidebar-foreground border-b border-sidebar-border flex items-center justify-between px-4 z-30">
<Link to="/" className="flex items-center gap-2">
<Scale className="h-5 w-5" />
<span className="font-serif text-lg">Counsel</span>
</Link>
<div className="flex items-center gap-1.5">
<QuickAddTime />
<QuickAddExpense />
<HeaderTimer />
<Button variant="ghost" size="sm" onClick={handleSignOut} className="text-sidebar-foreground">
<LogOut className="h-4 w-4" />
</Button>
</div>
<Button variant="ghost" size="sm" onClick={handleSignOut} className="text-sidebar-foreground">
<LogOut className="h-4 w-4" />
</Button>
</div>
<main className="flex-1 min-w-0 md:ml-0 mt-14 md:mt-0">
{/* Desktop top bar with quick-add and timer */}
<div className="hidden md:flex h-14 items-center justify-end gap-2 px-6 border-b bg-card">
<QuickAddTime />
<QuickAddExpense />
<HeaderTimer />
</div>
<div className="md:hidden flex overflow-x-auto gap-1 px-3 py-2 border-b bg-card">
{NAV.filter((n) => !n.adminOnly || isAdmin).map((item) => {
const active =
item.to === "/"
? location.pathname === "/"
: location.pathname.startsWith(item.to);
const active = item.to === "/" ? location.pathname === "/" : location.pathname.startsWith(item.to);
return (
<Link
key={item.to}