Files
pn-new-crm/src/components/dashboard/website-glance-tile.tsx
Matt c8ea9ec0a0 fix(audit-wave-10): aria-hidden sweep on decorative Lucide icons (#69)
Mechanical codemod added \`aria-hidden\` to 444 self-closing single-line
Lucide icon JSX elements across 267 .tsx files in:

- shared/, layout/, dashboard/
- admin/ (all sections)
- clients/, berths/, yachts/, companies/, interests/, documents/
- reminders/, reservations/, residential/, expenses/, email/

The regex targeted only the safe pattern \`<IconName className="..." />\`
(no other props, self-closing, capitalized component name). Every match
inspected is a decorative companion to visible text or sits inside a
button whose accessible name comes from \`aria-label\` / sr-only text
— the icon itself should not be announced.

Screen readers no longer double-read the icon + the adjacent label
text (e.g. "Pencil Pencil Edit" → just "Edit"). The existing
@axe-core/playwright smoke test (\`20-accessibility.spec.ts\`) continues
to pass.

Test suite stays at 1315/1315 vitest. typescript clean.

Closes task #69 (aria-hidden sweep) from the AUDIT-2026-05-12 follow-ups
backlog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:37:22 +02:00

79 lines
3.2 KiB
TypeScript

'use client';
/**
* Compact "Website at a glance" tile for the main sales dashboard. Shows
* pageviews today + active visitors right now + a deep-link to the full
* /website-analytics page. Soft-fails (renders nothing) when Umami isn't
* configured for this port - so the dashboard doesn't get cluttered with
* a "configure Umami" prompt that the user already saw on the dedicated
* page.
*/
import Link from 'next/link';
import { Globe, ArrowRight } from 'lucide-react';
import { useUIStore } from '@/stores/ui-store';
import { Card } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import {
useUmamiActive,
useUmamiStats,
} from '@/components/website-analytics/use-website-analytics';
export function WebsiteGlanceTile() {
const portSlug = useUIStore((s) => s.currentPortSlug);
const stats = useUmamiStats('today');
const active = useUmamiActive('today');
// Hide the tile entirely if Umami isn't configured - this dashboard is
// for sales, not for prompting the operator into integration setup.
// The API surfaces `notConfigured: true` on a 200 response so React
// Query doesn't retry-loop (a prior 409-throw caused server hangs).
if (stats.data?.notConfigured || active.data?.notConfigured) {
return null;
}
const today = stats.data?.data?.pageviews?.value ?? 0;
const activeNow = active.data?.data?.visitors ?? 0;
const loading = stats.isLoading || active.isLoading;
return (
<Link
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
href={portSlug ? (`/${portSlug}/website-analytics` as any) : ('/' as any)}
className="block group"
>
<Card className="relative overflow-hidden p-3 sm:p-5 transition-shadow hover:shadow-md">
<div className="absolute inset-x-0 top-0 h-1 bg-mint" aria-hidden />
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-1.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground sm:text-xs">
<Globe className="h-3 w-3" aria-hidden />
Website today
</div>
{loading ? (
<Skeleton className="mt-2 h-7 w-20" aria-hidden />
) : (
<div className="mt-1 flex items-baseline gap-2 text-lg font-semibold tabular-nums sm:mt-2 sm:text-2xl">
{today.toLocaleString()}
<span className="text-xs font-normal text-muted-foreground">pageviews</span>
</div>
)}
<div className="mt-1 flex items-center gap-1.5 text-xs text-muted-foreground">
<span className="relative flex h-1.5 w-1.5">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
<span className="relative inline-flex h-1.5 w-1.5 rounded-full bg-emerald-500" />
</span>
{activeNow} active right now
</div>
</div>
<ArrowRight
className="h-4 w-4 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5"
aria-hidden
/>
</div>
</Card>
</Link>
);
}