feat(scheduling): rename 通知→干预, allow drill-in on intervened items

- Globally rename user-facing 通知 → 干预 (list badge, detail button, batch
  modal, CSV header, server response messages, db table comment)
- 已干预 row in detail is now clickable — opens SwapPreview which shows
  a read-only summary plus a 取消干预 action (PATCH notify /:id with
  status=cancelled). Sending is blocked while already intervened.
- Selected suggestion now follows the latest data snapshot so status
  changes from within the detail flow propagate immediately.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-04-17 09:14:53 +08:00
parent 1d9f4cb43d
commit 210db7f8ff
8 changed files with 69 additions and 32 deletions

View File

@@ -177,11 +177,13 @@ export default function SchedulingModule() {
useEffect(() => { loadData(); }, [loadData]);
const handleNotifySuccess = useCallback(() => { loadData(); }, [loadData]);
// Close detail modal if selected suggestion is filtered out or no longer exists
// Keep selectedSuggestion synced with latest data so candidate notification
// status changes (登记 / 取消干预) propagate into the open detail modal.
useEffect(() => {
if (!selectedSuggestion || !data) return;
const stillExists = data.suggestions.some(s => s.id === selectedSuggestion.id);
if (!stillExists) setSelectedSuggestion(null);
const fresh = data.suggestions.find(s => s.id === selectedSuggestion.id);
if (!fresh) setSelectedSuggestion(null);
else if (fresh !== selectedSuggestion) setSelectedSuggestion(fresh);
}, [data, selectedSuggestion]);
const toggleSelect = useCallback((id: string) => {
@@ -227,7 +229,7 @@ export default function SchedulingModule() {
exitSelectMode();
} catch (e) {
console.error('batch notify failed:', e);
setBatchResultMsg('批量通知失败,请重试');
setBatchResultMsg('批量干预失败,请重试');
} finally {
setBatchInFlight(false);
}
@@ -534,7 +536,7 @@ export default function SchedulingModule() {
disabled={selectedIds.size === 0}
className="flex items-center gap-1.5 text-xs font-bold bg-blue-600 hover:bg-blue-500 disabled:bg-slate-700 disabled:text-slate-400 text-white px-3 py-1.5 rounded-lg cursor-pointer disabled:cursor-not-allowed transition-colors"
>
<Send size={12} />
<Send size={12} />
</button>
</div>
</motion.div>
@@ -551,7 +553,7 @@ export default function SchedulingModule() {
className="bg-white rounded-t-2xl sm:rounded-2xl shadow-2xl w-full sm:max-w-md overflow-hidden flex flex-col max-h-[80vh] sm:mx-4"
>
<div className="bg-slate-800 px-4 py-3 flex items-center justify-between flex-shrink-0">
<span className="text-white font-bold text-sm"></span>
<span className="text-white font-bold text-sm"></span>
<button
onClick={() => !batchInFlight && setShowBatchConfirm(false)}
disabled={batchInFlight}
@@ -562,7 +564,7 @@ export default function SchedulingModule() {
</div>
<div className="px-4 py-3 overflow-y-auto flex-1">
<p className="text-xs text-slate-500 mb-3">
<span className="font-bold text-slate-800">{batchItems.length}</span>
<span className="font-bold text-slate-800">{batchItems.length}</span>
</p>
<div className="space-y-2">
{batchItems.map(({ suggestion, candidate }) => (
@@ -597,7 +599,7 @@ export default function SchedulingModule() {
disabled={batchInFlight || batchItems.length === 0}
className="flex-1 py-2 text-xs font-bold text-white bg-blue-600 hover:bg-blue-500 rounded-lg cursor-pointer disabled:opacity-50 transition-colors"
>
{batchInFlight ? '发送中...' : `确认发送 ${batchItems.length}`}
{batchInFlight ? '登记中...' : `确认登记 ${batchItems.length}`}
</button>
</div>
</motion.div>