refactor(ts): convert SummaryCard + ReleaseNotesDialog to TSX (B9)
SummaryCard (top-level; CardType/CardDef, value: Dollars) and ReleaseNotesDialog (the version-ack modal we hardened; typed useAuth, HTMLElement-cast for activeElement focus). typecheck 0, build green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
8937dc5e25
commit
8a4b51915f
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { APP_VERSION, RELEASE_NOTES } from '@/lib/version';
|
import { RELEASE_NOTES } from '@/lib/version';
|
||||||
import { Sparkles } from 'lucide-react';
|
import { Sparkles } from 'lucide-react';
|
||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
import { api } from '@/api';
|
import { api } from '@/api';
|
||||||
|
|
@ -9,7 +9,7 @@ import { api } from '@/api';
|
||||||
export function ReleaseNotesDialog() {
|
export function ReleaseNotesDialog() {
|
||||||
const { hasNewVersion, setHasNewVersion } = useAuth();
|
const { hasNewVersion, setHasNewVersion } = useAuth();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const titleRef = useRef(null);
|
const titleRef = useRef<HTMLHeadingElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasNewVersion) setOpen(true);
|
if (hasNewVersion) setOpen(true);
|
||||||
|
|
@ -19,7 +19,7 @@ export function ReleaseNotesDialog() {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
setHasNewVersion(false); // optimistic — don't wait for the server
|
setHasNewVersion(false); // optimistic — don't wait for the server
|
||||||
api.acknowledgeVersion().catch(err => console.error('[ReleaseNotesDialog] failed to acknowledge version', err)); // backend stores the seen release version
|
api.acknowledgeVersion().catch(err => console.error('[ReleaseNotesDialog] failed to acknowledge version', err)); // backend stores the seen release version
|
||||||
const prev = document.activeElement;
|
const prev = document.activeElement as HTMLElement | null;
|
||||||
if (prev?.focus) setTimeout(() => prev.focus(), 0);
|
if (prev?.focus) setTimeout(() => prev.focus(), 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1,10 +1,21 @@
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { cn, fmt } from '@/lib/utils';
|
import { cn, fmt } from '@/lib/utils';
|
||||||
import { AlertCircle, CheckCircle2, Clock, TrendingUp } from 'lucide-react';
|
import { AlertCircle, CheckCircle2, Clock, TrendingUp, Settings2, type LucideIcon } from 'lucide-react';
|
||||||
import { Settings2 } from 'lucide-react';
|
import type { Dollars } from '@/lib/money';
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
|
|
||||||
const CARD_DEFS = {
|
type CardType = 'starting' | 'paid' | 'remaining' | 'overdue';
|
||||||
|
|
||||||
|
interface CardDef {
|
||||||
|
label: string;
|
||||||
|
icon: LucideIcon;
|
||||||
|
bar: string;
|
||||||
|
glow: string;
|
||||||
|
borderActive?: string;
|
||||||
|
valueClass: string;
|
||||||
|
activateWhen: (v: number) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CARD_DEFS: Record<CardType, CardDef> = {
|
||||||
starting: {
|
starting: {
|
||||||
label: 'Starting',
|
label: 'Starting',
|
||||||
icon: TrendingUp,
|
icon: TrendingUp,
|
||||||
|
|
@ -41,7 +52,14 @@ const CARD_DEFS = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SummaryCard = React.memo(function SummaryCard({ type, value, onEdit, hint }) {
|
interface SummaryCardProps {
|
||||||
|
type: CardType;
|
||||||
|
value?: Dollars;
|
||||||
|
onEdit?: () => void;
|
||||||
|
hint?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SummaryCard = React.memo(function SummaryCard({ type, value, onEdit, hint }: SummaryCardProps) {
|
||||||
const def = useMemo(() => CARD_DEFS[type], [type]);
|
const def = useMemo(() => CARD_DEFS[type], [type]);
|
||||||
const isActive = useMemo(() => def.activateWhen(value || 0), [def, value]);
|
const isActive = useMemo(() => def.activateWhen(value || 0), [def, value]);
|
||||||
const Icon = useMemo(() => def.icon, [def]);
|
const Icon = useMemo(() => def.icon, [def]);
|
||||||
Loading…
Reference in New Issue