Open stop details in a modal from the Stops admin table

Clicking a stop (or the Edit link) in /admin/stops now opens a modal
with Details/Products/Message tabs instead of navigating to
/admin/stops/[id].

- Add getStopDetails server action (returns stop, brand, candidate
  products, assigned products, brand list in one round trip).
- Add StopDetailModal component with tabbed panels that reuse the
  existing StopEditForm, StopProductAssignment, and
  MessageCustomersSection.
- Add an optional onSaved callback to StopEditForm so the modal can
  refetch + router.refresh() after a save.
- StopTableClient now opens the modal on row click; the /admin/stops/[id]
  route is kept in place for direct links/bookmarks.
This commit is contained in:
2026-06-04 16:33:57 +00:00
parent 9b51f5ae29
commit bd623020d5
4 changed files with 443 additions and 8 deletions
+13 -7
View File
@@ -15,6 +15,7 @@ import {
} from "@/components/admin/design-system";
import ScheduleImportModal from "@/components/admin/ScheduleImportModal";
import AddStopModal from "@/components/admin/AddStopModal";
import StopDetailModal from "@/components/admin/StopDetailModal";
type Stop = {
id: string;
@@ -55,6 +56,7 @@ export default function StopTableClient({ stops }: Props) {
const [bulkPublishing, setBulkPublishing] = useState(false);
const [showImport, setShowImport] = useState(false);
const [showAdd, setShowAdd] = useState(false);
const [openStopId, setOpenStopId] = useState<string | null>(null);
const PAGE_SIZE = 50;
useEffect(() => {
@@ -349,6 +351,7 @@ export default function StopTableClient({ stops }: Props) {
e.stopPropagation();
toggleStopSelection(stop.id);
}}
onOpen={() => setOpenStopId(stop.id)}
/>
))
)}
@@ -369,6 +372,13 @@ export default function StopTableClient({ stops }: Props) {
brandId={stops[0]?.brand_id ?? ""}
onSuccess={refresh}
/>
{openStopId && (
<StopDetailModal
stopId={openStopId}
onClose={() => setOpenStopId(null)}
/>
)}
</>
);
}
@@ -400,16 +410,16 @@ function StopRowBase({
onDeleteError,
isSelected,
onToggleSelect,
onOpen,
}: {
stop: Stop;
onDeleted: () => void;
onDeleteError: (msg: string) => void;
isSelected: boolean;
onToggleSelect: (e: React.MouseEvent) => void;
onOpen: () => void;
}) {
const router = useRouter();
const { success: showSuccess, error: showError } = useToast();
const [, startTransition] = useTransition();
const [openMenu, setOpenMenu] = useState<string | null>(null);
const [deletingId, setDeletingId] = useState<string | null>(null);
const [confirmDelete, setConfirmDelete] = useState<string | null>(null);
@@ -418,10 +428,6 @@ function StopRowBase({
const { date, weekday } = formatDate(stop.date);
const time = formatTime(stop.time);
function goToEdit() {
startTransition(() => router.push(`/admin/stops/${stop.id}`));
}
async function handlePublish() {
setPublishingId(stop.id);
const result = await publishStop(stop.id, stop.brand_id);
@@ -461,7 +467,7 @@ function StopRowBase({
return (
<tr
onClick={goToEdit}
onClick={onOpen}
className={`group cursor-pointer transition-colors ${isSelected ? "bg-emerald-50/60" : "hover:bg-[var(--admin-bg-subtle)]/60"}`}
>
<td className="px-4 py-3.5" onClick={(e) => e.stopPropagation()}>