fix(web/channel): stabilize inline priority updates (#6415)
* fix(channel): debounce inline priority updates * fix(channel): commit spinner edits only on Enter or focus leave Blurring the inline edit input to the +/- buttons flushed the pending priority update immediately, bypassing the container focus-containment check and defeating the debounce. Commit now happens via the container blur handler or explicitly on Enter. Add unit tests for the priority update scheduler. * fix(channel): preserve row identity when priority updates reorder channels
This commit is contained in:
@@ -27,7 +27,7 @@ import {
|
||||
Shuffle,
|
||||
SlidersHorizontal,
|
||||
} from 'lucide-react'
|
||||
import { useState, useMemo, useContext } from 'react'
|
||||
import { useState, useMemo, useContext, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
@@ -71,6 +71,7 @@ import {
|
||||
handleUpdateChannelField,
|
||||
handleUpdateTagField,
|
||||
handleUpdateChannelBalance,
|
||||
createChannelPriorityUpdateScheduler,
|
||||
isTagAggregateRow,
|
||||
type TagRow,
|
||||
} from '../lib'
|
||||
@@ -172,55 +173,77 @@ function UpstreamUpdateTags({ channel }: { channel: Channel }) {
|
||||
* Priority cell component with inline editing
|
||||
*/
|
||||
function PriorityCell({ channel }: { channel: Channel }) {
|
||||
if (isTagAggregateRow(channel)) {
|
||||
return <TagPriorityCell channel={channel} />
|
||||
}
|
||||
|
||||
return <ChannelPriorityCell channel={channel} />
|
||||
}
|
||||
|
||||
function TagPriorityCell({ channel }: { channel: TagRow }) {
|
||||
const { t } = useTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
const isTagRow = isTagAggregateRow(channel)
|
||||
const priority = channel.priority
|
||||
const [confirmOpen, setConfirmOpen] = useState(false)
|
||||
const [pendingValue, setPendingValue] = useState<number | null>(null)
|
||||
const tag = channel.tag || ''
|
||||
const channelCount = channel.children?.length || 0
|
||||
|
||||
// Tag row - editable with confirmation for all tag channels
|
||||
if (isTagRow) {
|
||||
const tag = channel.tag || ''
|
||||
const channelCount = channel.children?.length || 0
|
||||
return (
|
||||
<>
|
||||
<NumericSpinnerInput
|
||||
value={priority ?? 0}
|
||||
onChange={(value) => {
|
||||
setPendingValue(value)
|
||||
setConfirmOpen(true)
|
||||
}}
|
||||
min={-999}
|
||||
/>
|
||||
<ConfirmDialog
|
||||
open={confirmOpen}
|
||||
onOpenChange={setConfirmOpen}
|
||||
title={t('Confirm Batch Update')}
|
||||
desc={t(
|
||||
'This will update the priority to {{value}} for all {{count}} channel(s) with tag "{{tag}}". Continue?',
|
||||
{ value: pendingValue, count: channelCount, tag }
|
||||
)}
|
||||
confirmText={t('Update')}
|
||||
handleConfirm={() => {
|
||||
if (pendingValue !== null) {
|
||||
handleUpdateTagField(tag, 'priority', pendingValue, queryClient)
|
||||
}
|
||||
setConfirmOpen(false)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<NumericSpinnerInput
|
||||
value={priority ?? 0}
|
||||
onChange={(value) => {
|
||||
setPendingValue(value)
|
||||
setConfirmOpen(true)
|
||||
}}
|
||||
min={-999}
|
||||
/>
|
||||
<ConfirmDialog
|
||||
open={confirmOpen}
|
||||
onOpenChange={setConfirmOpen}
|
||||
title={t('Confirm Batch Update')}
|
||||
desc={t(
|
||||
'This will update the priority to {{value}} for all {{count}} channel(s) with tag "{{tag}}". Continue?',
|
||||
{ value: pendingValue, count: channelCount, tag }
|
||||
)}
|
||||
confirmText={t('Update')}
|
||||
handleConfirm={() => {
|
||||
if (pendingValue !== null) {
|
||||
handleUpdateTagField(tag, 'priority', pendingValue, queryClient)
|
||||
}
|
||||
setConfirmOpen(false)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
function ChannelPriorityCell({ channel }: { channel: Channel }) {
|
||||
const queryClient = useQueryClient()
|
||||
const priorityUpdateScheduler = useMemo(
|
||||
() =>
|
||||
createChannelPriorityUpdateScheduler((value) => {
|
||||
void handleUpdateChannelField(
|
||||
channel.id,
|
||||
'priority',
|
||||
value,
|
||||
queryClient
|
||||
)
|
||||
}),
|
||||
[channel.id, queryClient]
|
||||
)
|
||||
|
||||
useEffect(
|
||||
() => () => priorityUpdateScheduler.flush(),
|
||||
[priorityUpdateScheduler]
|
||||
)
|
||||
|
||||
// Regular channel row - editable
|
||||
return (
|
||||
<NumericSpinnerInput
|
||||
value={priority ?? 0}
|
||||
onChange={(value) => {
|
||||
handleUpdateChannelField(channel.id, 'priority', value, queryClient)
|
||||
}}
|
||||
value={channel.priority ?? 0}
|
||||
onChange={priorityUpdateScheduler.schedule}
|
||||
onCommit={priorityUpdateScheduler.flush}
|
||||
min={-999}
|
||||
/>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user