fix: optimistic DnD updates, focus trap, film_thickness coercion, closed_at reset

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-07 20:29:05 +03:00
parent fcc86fd7b9
commit ff84f89f29
2 changed files with 57 additions and 6 deletions
+35 -3
View File
@@ -18,7 +18,10 @@ const KanbanBoard = () => {
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }));
const [localOverrides, setLocalOverrides] = useState({});
const findOrderStatus = (orderId) => {
if (localOverrides[orderId]) return localOverrides[orderId];
for (const status of STATUSES) {
if (ordersByStatus[status]?.find((o) => o.id === orderId)) return status;
}
@@ -27,7 +30,8 @@ const KanbanBoard = () => {
const handleDragStart = ({ active }) => {
const status = findOrderStatus(active.id);
if (status) setActiveOrder(ordersByStatus[status].find((o) => o.id === active.id));
if (status) setActiveOrder(ordersByStatus[status].find((o) => o.id === active.id) ||
Object.values(ordersByStatus).flat().find((o) => o.id === active.id));
};
const handleDragEnd = async ({ active, over }) => {
@@ -38,10 +42,38 @@ const KanbanBoard = () => {
const sourceStatus = findOrderStatus(active.id);
if (targetStatus && sourceStatus !== targetStatus) {
await updateStatus(active.id, targetStatus, session?.user?.email);
setLocalOverrides((prev) => ({ ...prev, [active.id]: targetStatus }));
const { error } = await updateStatus(active.id, targetStatus, session?.user?.email);
if (error) {
console.error('Status update failed:', error);
setLocalOverrides((prev) => {
const next = { ...prev };
delete next[active.id];
return next;
});
} else {
setLocalOverrides((prev) => {
const next = { ...prev };
delete next[active.id];
return next;
});
}
}
};
const effectiveOrdersByStatus = STATUSES.reduce((acc, status) => {
acc[status] = (ordersByStatus[status] || []).filter(
(o) => !localOverrides[o.id] || localOverrides[o.id] === status
);
// Add orders that were moved here optimistically
const overrideOrders = Object.entries(localOverrides)
.filter(([, s]) => s === status)
.map(([id]) => Object.values(ordersByStatus).flat().find((o) => o.id === id))
.filter(Boolean);
acc[status] = [...acc[status], ...overrideOrders];
return acc;
}, {});
if (loading) {
return (
<div style={{ textAlign: 'center', padding: '60px', color: 'var(--text-muted)', fontFamily: 'var(--font-heading)' }}>
@@ -58,7 +90,7 @@ const KanbanBoard = () => {
<KanbanColumn
key={status}
status={status}
orders={ordersByStatus[status] || []}
orders={effectiveOrdersByStatus[status] || []}
onCardClick={setSelectedOrder}
/>
))}
+22 -3
View File
@@ -57,6 +57,17 @@ const OrderModal = ({ order, onClose, onSaved, mode = 'edit' }) => {
}
}, [order?.id]);
const modalRef = React.useRef(null);
useEffect(() => {
const handleKey = (e) => {
if (e.key === 'Escape') onClose();
};
document.addEventListener('keydown', handleKey);
modalRef.current?.focus();
return () => document.removeEventListener('keydown', handleKey);
}, [onClose]);
const set = (e) => setForm((prev) => ({ ...prev, [e.target.name]: e.target.value }));
const handleSave = async () => {
@@ -76,7 +87,13 @@ const OrderModal = ({ order, onClose, onSaved, mode = 'edit' }) => {
} else {
const statusChanged = form.status !== order.status;
const updates = { ...form };
if (statusChanged && form.status === 'закрыт') updates.closed_at = new Date().toISOString();
if (statusChanged) {
if (form.status === 'закрыт') {
updates.closed_at = new Date().toISOString();
} else if (order.status === 'закрыт') {
updates.closed_at = null;
}
}
const { error } = await supabase.from('orders').update(updates).eq('id', order.id);
if (error) { setError(error.message); setSaving(false); return; }
@@ -110,6 +127,8 @@ const OrderModal = ({ order, onClose, onSaved, mode = 'edit' }) => {
onClick={onClose}
>
<div
ref={modalRef}
tabIndex={-1}
className="glass-panel"
style={{ width: '100%', maxWidth: '620px', maxHeight: '90vh', overflow: 'auto', padding: '30px', margin: '20px' }}
onClick={(e) => e.stopPropagation()}
@@ -157,7 +176,7 @@ const OrderModal = ({ order, onClose, onSaved, mode = 'edit' }) => {
</div>
<div>
<label htmlFor="modal-thickness" style={labelStyle}>Толщина плёнки</label>
<select id="modal-thickness" name="film_thickness" value={form.film_thickness} onChange={set} style={{ ...inputStyle, cursor: 'pointer' }}>
<select id="modal-thickness" name="film_thickness" value={form.film_thickness} onChange={(e) => setForm((prev) => ({ ...prev, film_thickness: Number(e.target.value) }))} style={{ ...inputStyle, cursor: 'pointer' }}>
<option value={200}>200 микрон</option>
<option value={300}>300 микрон</option>
</select>
@@ -205,7 +224,7 @@ const OrderModal = ({ order, onClose, onSaved, mode = 'edit' }) => {
{events.map((e) => (
<div key={e.id} style={{ fontSize: '0.8rem', color: 'var(--text-muted)', display: 'flex', gap: '10px', alignItems: 'baseline' }}>
<span style={{ color: 'var(--accent-blue)', fontWeight: 600, flexShrink: 0 }}>
{new Date(e.created_at).toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' })}
{new Date(e.created_at).toLocaleString('ru-RU', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' })}
</span>
<span style={{ flex: 1 }}>{e.description}</span>
{e.created_by && (