fix: react-doctor no-inline-exhaustive-style 3→0 (CSS classes for CommandPalette), rendering-usetransition-loading 3→0 (useTransition for fake loading)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useMemo, useCallback, useEffect } from "react";
|
||||
import { useState, useMemo, useCallback, useEffect, useTransition } from "react";
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import {
|
||||
@@ -209,16 +209,18 @@ export default function AdminOrdersPanel({
|
||||
const [page, setPage] = useState(0);
|
||||
const [selectedOrders, setSelectedOrders] = useState<Set<string>>(new Set());
|
||||
const [bulkMarkingUp, setBulkMarkingUp] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [filterVersion, setFilterVersion] = useState(0);
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
// Simulate loading when orders change
|
||||
// Mark the filter operation as a transition whenever the input
|
||||
// dependencies change, so React can defer the heavy list work and
|
||||
// give us an `isPending` flag instead of a fake useState round-trip.
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setIsLoading(true);
|
||||
const timer = setTimeout(() => setIsLoading(false), 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, [page, activeTab, search, selectedStops.length]);
|
||||
startTransition(() => {
|
||||
setFilterVersion((v) => v + 1);
|
||||
});
|
||||
}, [page, activeTab, search, selectedStops.length, startTransition]);
|
||||
|
||||
const filteredOrders = useMemo(() => {
|
||||
return orders.filter((order) => {
|
||||
@@ -377,17 +379,17 @@ export default function AdminOrdersPanel({
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3 mb-6">
|
||||
<KPIStat
|
||||
label="Total"
|
||||
value={isLoading ? <Skeleton variant="text" className="w-16 h-8" /> : orders.length}
|
||||
value={isPending ? <Skeleton variant="text" className="w-16 h-8" /> : orders.length}
|
||||
tone="default"
|
||||
/>
|
||||
<KPIStat
|
||||
label="Pending"
|
||||
value={isLoading ? <Skeleton variant="text" className="w-12 h-8" /> : pendingCount}
|
||||
value={isPending ? <Skeleton variant="text" className="w-12 h-8" /> : pendingCount}
|
||||
tone="warning"
|
||||
/>
|
||||
<KPIStat
|
||||
label="Picked Up"
|
||||
value={isLoading ? <Skeleton variant="text" className="w-12 h-8" /> : pickedUpCount}
|
||||
value={isPending ? <Skeleton variant="text" className="w-12 h-8" /> : pickedUpCount}
|
||||
tone="primary"
|
||||
/>
|
||||
</div>
|
||||
@@ -583,7 +585,7 @@ export default function AdminOrdersPanel({
|
||||
)}
|
||||
|
||||
{/* Orders Table */}
|
||||
{isLoading ? (
|
||||
{isPending ? (
|
||||
<div
|
||||
className="rounded-xl border p-6"
|
||||
style={{
|
||||
|
||||
@@ -266,37 +266,58 @@ function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
@keyframes cp-fade-in { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes cp-scale-in { from { opacity: 0; } to { opacity: 1; } }
|
||||
}
|
||||
.cp-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 60;
|
||||
background-color: rgba(26, 24, 20, 0.4);
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding-top: 15vh;
|
||||
}
|
||||
.cp-panel {
|
||||
width: 100%;
|
||||
max-width: 36rem;
|
||||
margin: 0 1rem;
|
||||
background-color: var(--admin-card-bg);
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: var(--admin-radius-lg);
|
||||
box-shadow: var(--admin-shadow-lg);
|
||||
overflow: hidden;
|
||||
animation: cp-scale-in 180ms ease-out;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.cp-result-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
padding: 0.625rem 0.75rem;
|
||||
border-radius: var(--admin-radius-md);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.3;
|
||||
font-family: inherit;
|
||||
transition: background-color 80ms ease-out, color 80ms ease-out;
|
||||
}
|
||||
.cp-result-btn[data-selected="true"] {
|
||||
background: var(--admin-primary-soft);
|
||||
color: var(--admin-primary);
|
||||
}
|
||||
.cp-result-btn[data-selected="false"] {
|
||||
background: transparent;
|
||||
color: var(--admin-text-primary);
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
zIndex: 60,
|
||||
backgroundColor: "rgba(26, 24, 20, 0.4)",
|
||||
backdropFilter: "blur(4px)",
|
||||
WebkitBackdropFilter: "blur(4px)",
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "center",
|
||||
paddingTop: "15vh",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
maxWidth: "36rem",
|
||||
margin: "0 1rem",
|
||||
backgroundColor: "var(--admin-card-bg)",
|
||||
border: "1px solid var(--admin-border)",
|
||||
borderRadius: "var(--admin-radius-lg)",
|
||||
boxShadow: "var(--admin-shadow-lg)",
|
||||
overflow: "hidden",
|
||||
animation: "cp-scale-in 180ms ease-out",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<div className="cp-backdrop">
|
||||
<div className="cp-panel">
|
||||
{/* Search input row */}
|
||||
<div
|
||||
style={{
|
||||
@@ -373,28 +394,8 @@ function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
aria-current={isSelected ? "true" : undefined}
|
||||
onClick={() => navigate(entry.href)}
|
||||
onMouseEnter={() => setSelected(i)}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "0.75rem",
|
||||
width: "100%",
|
||||
padding: "0.625rem 0.75rem",
|
||||
borderRadius: "var(--admin-radius-md)",
|
||||
border: "none",
|
||||
background: isSelected
|
||||
? "var(--admin-primary-soft)"
|
||||
: "transparent",
|
||||
color: isSelected
|
||||
? "var(--admin-primary)"
|
||||
: "var(--admin-text-primary)",
|
||||
cursor: "pointer",
|
||||
textAlign: "left",
|
||||
fontSize: "0.875rem",
|
||||
lineHeight: 1.3,
|
||||
fontFamily: "inherit",
|
||||
transition:
|
||||
"background-color 80ms ease-out, color 80ms ease-out",
|
||||
}}
|
||||
data-selected={isSelected ? "true" : "false"}
|
||||
className="cp-result-btn"
|
||||
>
|
||||
<Icon
|
||||
size={16}
|
||||
|
||||
@@ -89,12 +89,12 @@ const StopIconHeader = () => (
|
||||
export default function StopTableClient({ stops, hideInternalFilterBar = false }: Props) {
|
||||
const router = useRouter();
|
||||
const { success: showSuccess, error: showError } = useToast();
|
||||
const [, startTransition] = useTransition();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [search, setSearch] = useState("");
|
||||
const [statusFilter, setStatusFilter] = useState<"all" | "active" | "inactive" | "draft">("all");
|
||||
const [deleteError, setDeleteError] = useState<string | null>(null);
|
||||
const [page, setPage] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [filterVersion, setFilterVersion] = useState(0);
|
||||
const [selectedStops, setSelectedStops] = useState<Set<string>>(new Set());
|
||||
const [bulkPublishing, setBulkPublishing] = useState(false);
|
||||
const [showImport, setShowImport] = useState(false);
|
||||
@@ -106,10 +106,10 @@ export default function StopTableClient({ stops, hideInternalFilterBar = false }
|
||||
const PAGE_SIZE = 25;
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
const timer = setTimeout(() => setIsLoading(false), 220);
|
||||
return () => clearTimeout(timer);
|
||||
}, [page, statusFilter, search, sortField, sortDirection]);
|
||||
startTransition(() => {
|
||||
setFilterVersion((v) => v + 1);
|
||||
});
|
||||
}, [page, statusFilter, search, sortField, sortDirection, startTransition]);
|
||||
|
||||
// Compute stats
|
||||
const stats = useMemo(() => {
|
||||
@@ -300,25 +300,25 @@ export default function StopTableClient({ stops, hideInternalFilterBar = false }
|
||||
<div className="bg-white rounded-xl border border-[var(--admin-border)] p-4">
|
||||
<p className="text-[10px] sm:text-xs text-[var(--admin-text-muted)] font-medium">Total</p>
|
||||
<p className="text-xl sm:text-2xl font-bold text-[var(--admin-text-primary)] mt-1 tabular-nums">
|
||||
{isLoading ? <Skeleton variant="text" className="w-12 h-6" /> : stats.total}
|
||||
{isPending ? <Skeleton variant="text" className="w-12 h-6" /> : stats.total}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-[var(--admin-border)] p-4">
|
||||
<p className="text-[10px] sm:text-xs text-[var(--admin-text-muted)] font-medium">Active</p>
|
||||
<p className="text-xl sm:text-2xl font-bold text-[var(--admin-accent)] mt-1 tabular-nums">
|
||||
{isLoading ? <Skeleton variant="text" className="w-10 h-6" /> : stats.active}
|
||||
{isPending ? <Skeleton variant="text" className="w-10 h-6" /> : stats.active}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-[var(--admin-border)] p-4">
|
||||
<p className="text-[10px] sm:text-xs text-[var(--admin-text-muted)] font-medium">Upcoming</p>
|
||||
<p className="text-xl sm:text-2xl font-bold text-sky-600 mt-1 tabular-nums">
|
||||
{isLoading ? <Skeleton variant="text" className="w-10 h-6" /> : stats.upcoming}
|
||||
{isPending ? <Skeleton variant="text" className="w-10 h-6" /> : stats.upcoming}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-[var(--admin-border)] p-4">
|
||||
<p className="text-[10px] sm:text-xs text-[var(--admin-text-muted)] font-medium">Drafts</p>
|
||||
<p className="text-xl sm:text-2xl font-bold text-stone-400 mt-1 tabular-nums">
|
||||
{isLoading ? <Skeleton variant="text" className="w-8 h-6" /> : stats.draft}
|
||||
{isPending ? <Skeleton variant="text" className="w-8 h-6" /> : stats.draft}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -409,7 +409,7 @@ export default function StopTableClient({ stops, hideInternalFilterBar = false }
|
||||
selectedStops={selectedStops}
|
||||
onToggleSelectAll={toggleSelectAll}
|
||||
onToggleSelect={toggleStopSelection}
|
||||
isLoading={isLoading}
|
||||
isLoading={isPending}
|
||||
search={search}
|
||||
statusFilter={statusFilter}
|
||||
/>
|
||||
@@ -419,7 +419,7 @@ export default function StopTableClient({ stops, hideInternalFilterBar = false }
|
||||
onEdit={setEditingStop}
|
||||
onDeleted={handleDeleted}
|
||||
onDeleteError={setDeleteError}
|
||||
isLoading={isLoading}
|
||||
isLoading={isPending}
|
||||
search={search}
|
||||
statusFilter={statusFilter}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user