feat: focus electric order drilldowns
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { BatteryCharging, CalendarDays, CheckCircle2, ChevronRight, MapPin, Plug, TrendingUp, TriangleAlert, Truck, Wallet } from 'lucide-react';
|
import { BatteryCharging, CalendarDays, CheckCircle2, ChevronRight, MapPin, Plug, TrendingUp, TriangleAlert, Truck, Wallet, X } from 'lucide-react';
|
||||||
import { motion, AnimatePresence } from 'motion/react';
|
import { motion, AnimatePresence } from 'motion/react';
|
||||||
import TrendBadge from './TrendBadge';
|
import TrendBadge from './TrendBadge';
|
||||||
import { fetchElectricMonthly, fetchElectricOrders } from './api';
|
import { fetchElectricMonthly, fetchElectricOrders } from './api';
|
||||||
@@ -53,6 +53,7 @@ export default function ElectricDaily() {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [orders, setOrders] = useState<ElectricChargeOrderResponse | null>(null);
|
const [orders, setOrders] = useState<ElectricChargeOrderResponse | null>(null);
|
||||||
const [ordersError, setOrdersError] = useState<string | null>(null);
|
const [ordersError, setOrdersError] = useState<string | null>(null);
|
||||||
|
const orderTriggerRef = useRef<HTMLButtonElement | null>(null);
|
||||||
|
|
||||||
const effectiveRange = useMemo(() => normalizeDateRange(dateRange.start, dateRange.end), [dateRange.start, dateRange.end]);
|
const effectiveRange = useMemo(() => normalizeDateRange(dateRange.start, dateRange.end), [dateRange.start, dateRange.end]);
|
||||||
const selectedDate = drillContext.selectedDate;
|
const selectedDate = drillContext.selectedDate;
|
||||||
@@ -150,6 +151,9 @@ export default function ElectricDaily() {
|
|||||||
|
|
||||||
const toggleDate = (date: string) => {
|
const toggleDate = (date: string) => {
|
||||||
const opening = selectedDate !== date;
|
const opening = selectedDate !== date;
|
||||||
|
if (opening && document.activeElement instanceof HTMLButtonElement) {
|
||||||
|
orderTriggerRef.current = document.activeElement;
|
||||||
|
}
|
||||||
commitDrillContext({
|
commitDrillContext({
|
||||||
...drillContext,
|
...drillContext,
|
||||||
vehicleScope,
|
vehicleScope,
|
||||||
@@ -157,6 +161,19 @@ export default function ElectricDaily() {
|
|||||||
endDate: effectiveRange.end,
|
endDate: effectiveRange.end,
|
||||||
selectedDate: opening ? date : undefined,
|
selectedDate: opening ? date : undefined,
|
||||||
}, opening ? 'push' : 'replace');
|
}, opening ? 'push' : 'replace');
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
if (opening) {
|
||||||
|
const details = document.getElementById(`electric-orders-${date}`);
|
||||||
|
details?.focus({ preventScroll: true });
|
||||||
|
details?.scrollIntoView({
|
||||||
|
behavior: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth',
|
||||||
|
block: 'start',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const trigger = orderTriggerRef.current ?? document.getElementById(`electric-date-trigger-${date}`);
|
||||||
|
if (trigger instanceof HTMLElement) trigger.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -291,11 +308,12 @@ export default function ElectricDaily() {
|
|||||||
return (
|
return (
|
||||||
<div key={d.date} className="border-t border-slate-100">
|
<div key={d.date} className="border-t border-slate-100">
|
||||||
<button
|
<button
|
||||||
|
id={`electric-date-trigger-${d.date}`}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => toggleDate(d.date)}
|
onClick={() => toggleDate(d.date)}
|
||||||
className={`grid w-full grid-cols-[minmax(0,1fr)_120px_88px] gap-3 px-3 py-2 pl-9 text-left md:grid-cols-[minmax(0,1fr)_160px_120px] ${abnormalBg}`}
|
className={`grid w-full grid-cols-[minmax(0,1fr)_120px_88px] gap-3 px-3 py-2 pl-9 text-left md:grid-cols-[minmax(0,1fr)_160px_120px] ${abnormalBg}`}
|
||||||
aria-expanded={selectedDate === d.date}
|
aria-expanded={selectedDate === d.date}
|
||||||
aria-controls={selectedDate === d.date ? `electric-orders-${d.date}` : undefined}
|
aria-controls={`electric-orders-${d.date}`}
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-1 text-[12px] font-bold text-slate-600">
|
<span className="flex items-center gap-1 text-[12px] font-bold text-slate-600">
|
||||||
<ChevronRight size={12} className={`text-slate-400 transition-transform ${selectedDate === d.date ? 'rotate-90' : ''}`} />
|
<ChevronRight size={12} className={`text-slate-400 transition-transform ${selectedDate === d.date ? 'rotate-90' : ''}`} />
|
||||||
@@ -307,7 +325,27 @@ export default function ElectricDaily() {
|
|||||||
<span className="text-right"><TrendBadge value={d.chainPct} /></span>
|
<span className="text-right"><TrendBadge value={d.chainPct} /></span>
|
||||||
</button>
|
</button>
|
||||||
{selectedDate === d.date && (
|
{selectedDate === d.date && (
|
||||||
<div id={`electric-orders-${d.date}`} className="border-t border-blue-100 bg-blue-50/40 px-3 py-3 md:pl-9">
|
<div
|
||||||
|
id={`electric-orders-${d.date}`}
|
||||||
|
tabIndex={-1}
|
||||||
|
aria-label={`${d.date} 充电订单明细`}
|
||||||
|
className="scroll-mt-3 border-t border-blue-100 bg-blue-50/40 px-3 py-3 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500 md:pl-9"
|
||||||
|
>
|
||||||
|
<div className="mb-2 flex items-center justify-between gap-3">
|
||||||
|
<div className="flex min-w-0 items-center gap-1.5 text-[11px] font-black text-blue-800">
|
||||||
|
<Plug size={13} className="shrink-0" />
|
||||||
|
<span className="truncate">{d.date} 充电订单</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => toggleDate(d.date)}
|
||||||
|
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-white text-blue-500 ring-1 ring-blue-100 hover:text-blue-700"
|
||||||
|
aria-label={`关闭 ${d.date} 充电订单`}
|
||||||
|
title="关闭订单明细"
|
||||||
|
>
|
||||||
|
<X size={13} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
{orders && reconciliation && (
|
{orders && reconciliation && (
|
||||||
<div
|
<div
|
||||||
role={reconciliation.matches ? 'status' : 'alert'}
|
role={reconciliation.matches ? 'status' : 'alert'}
|
||||||
|
|||||||
Reference in New Issue
Block a user