fix: NavPill and Sidebar UI refinements, trackerService adjustments

This commit is contained in:
null 2026-06-04 01:00:29 -05:00
parent 1ea6979903
commit 46bcf83d22
3 changed files with 48 additions and 11 deletions

View File

@ -1,8 +1,9 @@
import React, { useMemo } from 'react';
import { NavLink } from 'react-router-dom';
import { cn } from '@/lib/utils';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
export const NavPill = React.memo(function NavPill({ item, onNavigate, badge }) {
export const NavPill = React.memo(function NavPill({ item, onNavigate, badge, badgeNames = [] }) {
const Icon = useMemo(() => item.icon, [item.icon]);
const to = useMemo(() => item.to, [item.to]);
const end = useMemo(() => item.end, [item.end]);
@ -24,9 +25,24 @@ export const NavPill = React.memo(function NavPill({ item, onNavigate, badge })
<Icon className="h-4 w-4" />
<span>{label}</span>
{badge > 0 && (
<span className="ml-0.5 flex h-4 min-w-[1rem] items-center justify-center rounded-full bg-rose-500 px-1 text-[10px] font-bold leading-none text-white">
{badge > 99 ? '99+' : badge}
</span>
<TooltipProvider delayDuration={300}>
<Tooltip>
<TooltipTrigger asChild>
<span className="ml-0.5 flex h-4 min-w-[1rem] items-center justify-center rounded-full bg-rose-500 px-1 text-[10px] font-bold leading-none text-white">
{badge > 99 ? '99+' : badge}
</span>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-[200px]">
<p className="font-semibold mb-1">{badge} past due</p>
{badgeNames.slice(0, 5).map(name => (
<p key={name} className="text-xs opacity-80">· {name}</p>
))}
{badgeNames.length > 5 && (
<p className="text-xs opacity-60">+{badgeNames.length - 5} more</p>
)}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</NavLink>
);

View File

@ -10,6 +10,7 @@ import { useAuth } from '@/hooks/useAuth';
import { useOverdueCount } from '@/hooks/useQueries';
import { ThemeToggle } from '@/components/ui/theme-toggle';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import {
DropdownMenu,
DropdownMenuContent,
@ -44,7 +45,7 @@ const trackerItems = [
{ to: '/payoff', icon: Calculator, label: 'Payoff' },
];
function TrackerMenu({ onNavigate, badge }) {
function TrackerMenu({ onNavigate, badge, badgeNames = [] }) {
const location = useLocation();
const navigate = useNavigate();
const isTrackerActive = useMemo(() => trackerItems.some(item => (
@ -68,9 +69,24 @@ function TrackerMenu({ onNavigate, badge }) {
<LayoutGrid className="h-4 w-4" />
Tracker
{badge > 0 && (
<span className="flex h-4 min-w-[1rem] items-center justify-center rounded-full bg-rose-500 px-1 text-[10px] font-bold leading-none text-white">
{badge > 99 ? '99+' : badge}
</span>
<TooltipProvider delayDuration={300}>
<Tooltip>
<TooltipTrigger asChild>
<span className="flex h-4 min-w-[1rem] items-center justify-center rounded-full bg-rose-500 px-1 text-[10px] font-bold leading-none text-white">
{badge > 99 ? '99+' : badge}
</span>
</TooltipTrigger>
<TooltipContent side="right" className="max-w-[200px]">
<p className="font-semibold mb-1">{badge} past due</p>
{badgeNames.slice(0, 5).map(name => (
<p key={name} className="text-xs opacity-80">· {name}</p>
))}
{badgeNames.length > 5 && (
<p className="text-xs opacity-60">+{badgeNames.length - 5} more</p>
)}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
<ChevronDown className="h-3.5 w-3.5 opacity-75" />
</button>
@ -178,6 +194,7 @@ export default function Sidebar({ adminMode = false }) {
const items = useMemo(() => adminMode ? adminNavItems : userNavItems, [adminMode]);
const { data: overdueData } = useOverdueCount();
const overdueCount = (!adminMode && overdueData?.count) ? overdueData.count : 0;
const overdueNames = (!adminMode && overdueData?.names) ? overdueData.names : [];
return (
<header className="sticky top-0 z-40 border-b border-border/80 bg-card/95 shadow-sm shadow-foreground/10 backdrop-blur-md supports-[backdrop-filter]:bg-card/90">
@ -185,7 +202,7 @@ export default function Sidebar({ adminMode = false }) {
<BrandBlock adminMode={adminMode} />
<nav className="hidden items-center gap-1 lg:flex">
{!adminMode && <TrackerMenu badge={overdueCount} />}
{!adminMode && <TrackerMenu badge={overdueCount} badgeNames={overdueNames} />}
{items.map(item => (
<NavPill key={item.to} item={item} />
))}
@ -232,6 +249,7 @@ export default function Sidebar({ adminMode = false }) {
item={item}
onNavigate={() => setMobileOpen(false)}
badge={item.to === '/' ? overdueCount : undefined}
badgeNames={item.to === '/' ? overdueNames : undefined}
/>
))}
{items.map(item => (

View File

@ -537,21 +537,24 @@ function getOverdueCount(userId, now = new Date()) {
`).all(year, month, rangeStart, rangeEnd, userId);
let count = 0;
const overdueNames = [];
for (const bill of bills) {
if (bill.is_skipped) continue;
if (bill.snoozed_until && bill.snoozed_until > todayStr) continue;
if (bill.autopay_enabled && bill.autodraft_status === 'assumed_paid') continue;
const dueDate = resolveDueDate(bill, year, month);
if (!dueDate || dueDate > todayStr) continue;
// Use >= so bills due TODAY are not counted as overdue — only strictly past dates
if (!dueDate || dueDate >= todayStr) continue;
const threshold = bill.actual_amount != null ? bill.actual_amount : bill.expected_amount;
if (threshold > 0 && bill.total_paid >= threshold) continue;
count++;
overdueNames.push(bill.name);
}
return { count, month, year, today: todayStr };
return { count, names: overdueNames, month, year, today: todayStr };
}
module.exports = {