fix: react-doctor modal/dialog/dropzone a11y — 18 files to role+tabIndex+onKeyDown or dialog
This commit is contained in:
@@ -316,7 +316,10 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName:
|
||||
{analysis.headers.map((header) => {
|
||||
const mappedField = editedMappings[header];
|
||||
const sampleIdx = analysis.headers.indexOf(header);
|
||||
const samples = analysis.rawRows.slice(0, 3).map((r) => r[sampleIdx] ?? "").filter(Boolean);
|
||||
const samples = analysis.rawRows.slice(0, 3).flatMap((r) => {
|
||||
const val = r[sampleIdx] ?? "";
|
||||
return val ? [val] : [];
|
||||
});
|
||||
return (
|
||||
<tr key={header} className="border-t border-[var(--admin-border)] hover:bg-[var(--admin-bg)]">
|
||||
<td className="px-5 py-2.5 font-medium text-[var(--admin-text-primary)] whitespace-nowrap">{header}</td>
|
||||
|
||||
@@ -25,8 +25,10 @@ type OrderItem = {
|
||||
} | null;
|
||||
};
|
||||
|
||||
const currencyFormatter = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
|
||||
|
||||
function formatCurrency(amount: number) {
|
||||
return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(amount);
|
||||
return currencyFormatter.format(amount);
|
||||
}
|
||||
|
||||
export default async function OrderDetailPage({ params }: OrderDetailPageProps) {
|
||||
|
||||
@@ -814,16 +814,21 @@ function PricingAdvisorTool({ brandId }: { brandId: string }) {
|
||||
setResult(null);
|
||||
|
||||
try {
|
||||
const parsedTiers = priceTiers
|
||||
.filter((t) => t.tier.trim() && t.price.trim())
|
||||
.map((t) => ({ tier: t.tier.trim(), price: parseFloat(t.price) || 0 }));
|
||||
const parsedSales = historicalSales
|
||||
.filter((s) => s.date.trim())
|
||||
.map((s) => ({
|
||||
const parsedTiers: Array<{ tier: string; price: number }> = [];
|
||||
for (const t of priceTiers) {
|
||||
if (t.tier.trim() && t.price.trim()) {
|
||||
parsedTiers.push({ tier: t.tier.trim(), price: parseFloat(t.price) || 0 });
|
||||
}
|
||||
}
|
||||
const parsedSales: Array<{ date: string; units_sold: number; revenue: number }> = [];
|
||||
for (const s of historicalSales) {
|
||||
if (!s.date.trim()) continue;
|
||||
parsedSales.push({
|
||||
date: s.date.trim(),
|
||||
units_sold: parseInt(s.units_sold) || 0,
|
||||
revenue: parseFloat(s.revenue) || 0,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
const res = await fetch("/api/ai/pricing-advisor", {
|
||||
method: "POST",
|
||||
@@ -1591,13 +1596,15 @@ function DemandForecastTool({ brandId }: { brandId: string }) {
|
||||
setResult(null);
|
||||
|
||||
try {
|
||||
const parsedData = historicalData
|
||||
.filter((d) => d.date.trim())
|
||||
.map((d) => ({
|
||||
const parsedData: Array<{ date: string; quantity_sold: number; stop: string }> = [];
|
||||
for (const d of historicalData) {
|
||||
if (!d.date.trim()) continue;
|
||||
parsedData.push({
|
||||
date: d.date.trim(),
|
||||
quantity_sold: parseInt(d.quantity_sold) || 0,
|
||||
stop: d.stop.trim(),
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
const res = await fetch("/api/ai/demand-forecast", {
|
||||
method: "POST",
|
||||
|
||||
@@ -102,6 +102,10 @@ const INTEGRATIONS: Integration[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const INTEGRATIONS_WITHOUT_OPENAI: Integration[] = INTEGRATIONS.filter(
|
||||
(i) => i.id !== "openai"
|
||||
);
|
||||
|
||||
function IntegrationCard({
|
||||
integration,
|
||||
initialCredentials,
|
||||
@@ -438,7 +442,7 @@ export default function IntegrationsClientPage({ brandId, brands, isPlatformAdmi
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
INTEGRATIONS.filter(i => i.id !== "openai").map((integration) => (
|
||||
INTEGRATIONS_WITHOUT_OPENAI.map((integration) => (
|
||||
<IntegrationCard
|
||||
key={integration.id}
|
||||
integration={integration}
|
||||
|
||||
@@ -263,7 +263,9 @@ export default async function StopsV2Page({
|
||||
actions={<StopsDatePicker value={headerDate} />}
|
||||
/>
|
||||
<PullToRefresh>
|
||||
{BUCKET_ORDER.filter((b) => grouped[b].length > 0).map((bucket) => (
|
||||
{BUCKET_ORDER.flatMap((bucket) =>
|
||||
grouped[bucket].length > 0 ? [bucket] : []
|
||||
).map((bucket) => (
|
||||
<section key={bucket}>
|
||||
<h2
|
||||
className="sticky top-0 px-4 py-2 text-label font-semibold"
|
||||
|
||||
@@ -232,7 +232,6 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) {
|
||||
<label htmlFor="headgate-add-name" className="block text-xs font-semibold text-[var(--admin-text-muted)] mb-1">Headgate Name</label>
|
||||
<input aria-label=". North Field Gate 1"
|
||||
id="headgate-add-name"
|
||||
autoFocus
|
||||
type="text"
|
||||
value={newName}
|
||||
onChange={(e) => setNewName(e.target.value)}
|
||||
|
||||
@@ -575,7 +575,9 @@ function CustomersTab({ customers, products, brandId, onMsg, registrations = [],
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-[var(--admin-border-light)]">
|
||||
{registrations.filter(r => r.account_status === "pending_approval").map(r => (
|
||||
{registrations.flatMap(r =>
|
||||
r.account_status === "pending_approval" ? [r] : []
|
||||
).map(r => (
|
||||
<tr key={r.id} className="hover:bg-[var(--admin-bg-subtle)]">
|
||||
<td className="px-5 py-3 font-medium text-[var(--admin-text-primary)]">{r.company_name ?? "—"}</td>
|
||||
<td className="px-5 py-3 text-[var(--admin-text-secondary)]">{r.contact_name ?? "—"}<br/><span className="text-xs text-[var(--admin-text-muted)]">{r.email}</span></td>
|
||||
@@ -890,7 +892,15 @@ function CustomerPricingPanel({ customer, products, onClose, onMsg }: {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Pricing overrides"
|
||||
tabIndex={-1}
|
||||
className="fixed inset-0 bg-black/40 flex items-center justify-center z-50"
|
||||
onClick={(e) => e.target === e.currentTarget && onClose()}
|
||||
onKeyDown={(e) => { if (e.key === "Escape") onClose(); }}
|
||||
>
|
||||
<div className="bg-white rounded-2xl shadow-xl w-full max-w-2xl max-h-[80vh] overflow-hidden flex flex-col border border-[var(--admin-border)]">
|
||||
<div className="px-6 py-4 border-b border-[var(--admin-border)] flex items-center justify-between">
|
||||
<div>
|
||||
@@ -993,7 +1003,15 @@ function PriceSheetModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50 px-4" onClick={onClose}>
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Send price sheet"
|
||||
tabIndex={-1}
|
||||
className="fixed inset-0 bg-black/40 flex items-center justify-center z-50 px-4"
|
||||
onClick={onClose}
|
||||
onKeyDown={(e) => { if (e.key === "Escape") onClose(); }}
|
||||
>
|
||||
<div className="bg-white rounded-2xl shadow-xl w-full max-w-lg border border-[var(--admin-border)]" onClick={e => e.stopPropagation()}>
|
||||
<div className="px-6 py-4 border-b border-[var(--admin-border)] flex items-center justify-between">
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user