feat(mileage): add daily operations report

This commit is contained in:
kkfluous
2026-08-12 23:05:07 +08:00
parent 17c839a35e
commit bd0627d00f
19 changed files with 2320 additions and 149 deletions
+22 -9
View File
@@ -15,6 +15,7 @@ export function PageFrame({
children,
maxWidth = 'max-w-6xl',
compactInfo = false,
hideHeader = false,
}: {
title: string;
subtitle?: string;
@@ -25,13 +26,14 @@ export function PageFrame({
children: ReactNode;
maxWidth?: string;
compactInfo?: boolean;
hideHeader?: boolean;
}) {
const [infoOpen, setInfoOpen] = useState(false);
return (
<div className="min-h-screen bg-[var(--app-bg)] text-slate-800 font-sans p-3 md:p-6 relative" style={{ overflowX: 'clip' }}>
<div className={cn(maxWidth, 'mx-auto flex flex-col gap-4 pb-20 md:pb-6')}>
<motion.header
{!hideHeader ? <motion.header
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.28, ease: 'easeOut' }}
@@ -106,7 +108,7 @@ export function PageFrame({
{actions ? <div className="flex shrink-0 flex-wrap items-center gap-2">{actions}</div> : null}
</div>
)}
</motion.header>
</motion.header> : null}
{children}
</div>
</div>
@@ -120,7 +122,7 @@ export function SurfaceCard({
children,
className,
}: {
title?: string;
title?: ReactNode;
subtitle?: string;
actions?: ReactNode;
children: ReactNode;
@@ -149,6 +151,8 @@ export function MetricTile({
helper,
icon: Icon,
tone = 'blue',
className,
compact = false,
}: {
label: string;
value: ReactNode;
@@ -156,6 +160,8 @@ export function MetricTile({
helper?: string;
icon?: SurfaceIcon;
tone?: 'blue' | 'emerald' | 'amber' | 'rose' | 'slate';
className?: string;
compact?: boolean;
}) {
const toneClass = {
blue: 'bg-blue-50 text-blue-600 ring-blue-100',
@@ -166,22 +172,29 @@ export function MetricTile({
}[tone];
return (
<div className="rounded-2xl border border-slate-100 bg-white p-4 shadow-sm transition-all hover:-translate-y-0.5 hover:shadow-md">
<div className={cn(
'rounded-2xl border border-slate-100 bg-white shadow-sm transition-all hover:-translate-y-0.5 hover:shadow-md',
compact ? 'p-3' : 'p-4',
className,
)}>
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 text-[11px] font-black text-slate-400">{label}</div>
{Icon ? (
<span className={cn('inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-xl ring-1', toneClass)}>
<Icon size={18} />
<span className={cn('inline-flex shrink-0 items-center justify-center rounded-xl ring-1', compact ? 'h-8 w-8' : 'h-9 w-9', toneClass)}>
<Icon size={compact ? 16 : 18} />
</span>
) : null}
</div>
<div className="mt-3 flex min-w-0 items-end gap-1">
<span className="min-w-0 whitespace-nowrap text-[clamp(1.65rem,6vw,2rem)] font-black leading-none tracking-tight text-slate-950 tabular-nums">
<div className={cn('flex min-w-0 items-end gap-1', compact ? 'mt-2' : 'mt-3')}>
<span className={cn(
'min-w-0 whitespace-nowrap font-black leading-none tracking-tight text-slate-950 tabular-nums',
compact ? 'text-[clamp(1.45rem,6vw,1.7rem)]' : 'text-[clamp(1.65rem,6vw,2rem)]',
)}>
{value}
</span>
{unit ? <span className="shrink-0 pb-0.5 text-[11px] font-black text-slate-400">{unit}</span> : null}
</div>
{helper ? <div className="mt-3 text-[11px] font-bold leading-relaxed text-slate-500">{helper}</div> : null}
{helper ? <div className={cn('font-bold leading-relaxed text-slate-500', compact ? 'mt-2 text-[10px]' : 'mt-3 text-[11px]')}>{helper}</div> : null}
</div>
);
}