fix: comprehensive bookings admin UX improvements
- #1 Delete confirmation dialog before removing bookings - #2 Error toasts instead of silent .catch(() => {}) - #3 Optimistic rollback — UI reverts on API failure - #4 Loading indicator on reminder status buttons - #5 Search results are now actionable (status change + delete) - #6 New bookings banner instead of full tab remount - #7 Error states for failed data loads - #8 InlineNotes only saves on blur when value changed - #9 AddBookingModal supports Instagram/Telegram fields - #10 Polling pauses when browser tab is hidden - #11 Enter key submits ConfirmModal
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { Loader2, Trash2, Phone, Instagram, Send } from "lucide-react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { Loader2, Trash2, Phone, Instagram, Send, X } from "lucide-react";
|
||||
import { type BookingStatus, type BookingFilter, BOOKING_STATUSES } from "./types";
|
||||
|
||||
export function LoadingSpinner() {
|
||||
@@ -20,16 +22,57 @@ export function EmptyState({ total }: { total: number }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function DeleteBtn({ onClick }: { onClick: () => void }) {
|
||||
// --- #1: Delete with confirmation ---
|
||||
|
||||
export function DeleteBtn({ onClick, name }: { onClick: () => void; name?: string }) {
|
||||
const [confirming, setConfirming] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!confirming) return;
|
||||
function onKey(e: KeyboardEvent) { if (e.key === "Escape") setConfirming(false); }
|
||||
document.addEventListener("keydown", onKey);
|
||||
return () => document.removeEventListener("keydown", onKey);
|
||||
}, [confirming]);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="rounded p-1 text-neutral-500 hover:text-red-400 transition-colors"
|
||||
title="Удалить"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
</button>
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConfirming(true)}
|
||||
className="rounded p-1 text-neutral-500 hover:text-red-400 transition-colors"
|
||||
title="Удалить"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
</button>
|
||||
{confirming && createPortal(
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4" onClick={() => setConfirming(false)}>
|
||||
<div className="absolute inset-0 bg-black/70 backdrop-blur-sm" />
|
||||
<div className="relative w-full max-w-xs rounded-2xl border border-white/[0.08] bg-[#0a0a0a] p-5 shadow-2xl" onClick={(e) => e.stopPropagation()}>
|
||||
<button onClick={() => setConfirming(false)} className="absolute right-3 top-3 flex h-7 w-7 items-center justify-center rounded-full text-neutral-500 hover:bg-white/[0.06] hover:text-white">
|
||||
<X size={16} />
|
||||
</button>
|
||||
<h3 className="text-sm font-bold text-white">Удалить запись?</h3>
|
||||
{name && <p className="mt-1 text-xs text-neutral-400">{name}</p>}
|
||||
<p className="mt-2 text-xs text-neutral-500">Это действие нельзя отменить.</p>
|
||||
<div className="mt-4 flex gap-2">
|
||||
<button
|
||||
onClick={() => setConfirming(false)}
|
||||
className="flex-1 rounded-lg border border-white/10 bg-neutral-800 py-2 text-xs font-medium text-neutral-300 hover:bg-neutral-700 transition-colors"
|
||||
>
|
||||
Отмена
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setConfirming(false); onClick(); }}
|
||||
className="flex-1 rounded-lg bg-red-600 py-2 text-xs font-medium text-white hover:bg-red-500 transition-colors"
|
||||
>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user