mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 16:45:25 +02:00
feat(topbar): add Discard button to BC sync queue dropdown
This commit is contained in:
+38
-3
@@ -52,7 +52,7 @@ const FORCED_ZERO_STOCK_SKUS = new Set([
|
||||
|
||||
const BC_SYNC_QUEUE_STORAGE_KEY = 'craze_bc_sync_queue';
|
||||
|
||||
type BcSyncStatus = 'queued' | 'previewed' | 'syncing' | 'synced' | 'failed';
|
||||
type BcSyncStatus = 'queued' | 'previewed' | 'preview_only' | 'syncing' | 'synced' | 'failed';
|
||||
|
||||
interface BcSyncQueueEntry {
|
||||
rowIndex: number;
|
||||
@@ -63,6 +63,7 @@ interface BcSyncQueueEntry {
|
||||
status: BcSyncStatus;
|
||||
previewToken?: string;
|
||||
error?: string;
|
||||
warning?: string;
|
||||
source: 'save_all' | 'cosmetic_items' | 'manual';
|
||||
}
|
||||
|
||||
@@ -692,6 +693,19 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
});
|
||||
};
|
||||
|
||||
const handleDiscardSelectedBcQueue = () => {
|
||||
const selectedCount = Object.values(bcSyncQueue).filter(e => e.selected).length;
|
||||
if (selectedCount === 0) return;
|
||||
if (!window.confirm(`Discard ${selectedCount} pending BC sync(s)? They will be removed from the queue.`)) return;
|
||||
setBcSyncQueue(prev => {
|
||||
const next: BcSyncQueue = { ...prev };
|
||||
(Object.keys(next) as string[]).forEach(articleNo => {
|
||||
if (next[articleNo].selected) delete next[articleNo];
|
||||
});
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectAllBcQueue = (selected: boolean) => {
|
||||
setBcSyncQueue(prev => {
|
||||
const next: BcSyncQueue = { ...prev };
|
||||
@@ -749,6 +763,7 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
...item,
|
||||
status: 'failed',
|
||||
error: preview.error || 'Preview failed',
|
||||
warning: undefined,
|
||||
};
|
||||
}
|
||||
return;
|
||||
@@ -759,6 +774,8 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
status: 'previewed',
|
||||
previewToken: preview.previewToken,
|
||||
error: undefined,
|
||||
selected: item.selected,
|
||||
warning: undefined,
|
||||
};
|
||||
});
|
||||
return next;
|
||||
@@ -785,7 +802,21 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
});
|
||||
|
||||
for (const [articleNo, item] of selectedBcQueueEntries) {
|
||||
const result = await applyBusinessCentralSync(appState.headers, item.newData, item.previewToken);
|
||||
const preview = await previewBusinessCentralSync(appState.headers, item.newData);
|
||||
if (!preview.success) {
|
||||
setBcSyncQueue(prev => ({
|
||||
...prev,
|
||||
[articleNo]: {
|
||||
...prev[articleNo],
|
||||
status: 'failed',
|
||||
error: preview.error || 'Preview failed',
|
||||
warning: undefined,
|
||||
},
|
||||
}));
|
||||
continue;
|
||||
}
|
||||
|
||||
const result = await applyBusinessCentralSync(appState.headers, item.newData, preview.previewToken);
|
||||
if (!result.success) {
|
||||
if (isPreviewTokenMismatchError(result.error)) {
|
||||
const refreshedPreview = await previewBusinessCentralSync(appState.headers, item.newData);
|
||||
@@ -797,6 +828,7 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
status: 'previewed',
|
||||
previewToken: refreshedPreview.previewToken,
|
||||
error: undefined,
|
||||
warning: undefined,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -817,6 +849,7 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
...prev[articleNo],
|
||||
status: 'failed',
|
||||
error: retryResult.error || 'BC sync failed',
|
||||
warning: retryResult.warning,
|
||||
},
|
||||
}));
|
||||
continue;
|
||||
@@ -828,6 +861,7 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
...prev[articleNo],
|
||||
status: 'failed',
|
||||
error: result.error || 'BC sync failed',
|
||||
warning: result.warning,
|
||||
},
|
||||
}));
|
||||
continue;
|
||||
@@ -959,11 +993,12 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
onRevertRow={handleRevertRow}
|
||||
isSavingAll={isSavingAll}
|
||||
bcQueueCount={Object.keys(bcSyncQueue).length}
|
||||
bcQueueEntries={Object.fromEntries((Object.entries(bcSyncQueue) as Array<[string, BcSyncQueueEntry]>).map(([k, v]) => [k, { articleName: v.articleName, selected: v.selected, status: v.status, error: v.error }]))}
|
||||
bcQueueEntries={Object.fromEntries((Object.entries(bcSyncQueue) as Array<[string, BcSyncQueueEntry]>).map(([k, v]) => [k, { articleName: v.articleName, selected: v.selected, status: v.status, error: v.error, warning: v.warning }]))}
|
||||
onToggleBcQueueSelection={handleToggleBcQueueSelection}
|
||||
onSelectAllBcQueue={handleSelectAllBcQueue}
|
||||
onPreviewSelectedBcSync={handlePreviewSelectedBcSync}
|
||||
onSyncSelectedBcSync={handleSyncSelectedBcSync}
|
||||
onDiscardSelectedBcQueue={handleDiscardSelectedBcQueue}
|
||||
isSyncingBC={isSyncingBC}
|
||||
isDownloadingBCExcel={isDownloadingBCExcel}
|
||||
isMaximized={isMaximized}
|
||||
|
||||
@@ -22,24 +22,36 @@ interface TopBarProps {
|
||||
onRevertRow: (articleNo: string) => void;
|
||||
isSavingAll: boolean;
|
||||
bcQueueCount: number;
|
||||
bcQueueEntries: Record<string, { articleName: string; selected: boolean; status: string; error?: string }>;
|
||||
bcQueueEntries: Record<string, { articleName: string; selected: boolean; status: string; error?: string; warning?: string }>;
|
||||
onToggleBcQueueSelection: (articleNo: string) => void;
|
||||
onSelectAllBcQueue: (selected: boolean) => void;
|
||||
onPreviewSelectedBcSync: () => Promise<void>;
|
||||
onSyncSelectedBcSync: () => Promise<void>;
|
||||
onDiscardSelectedBcQueue: () => void;
|
||||
isSyncingBC: boolean;
|
||||
isDownloadingBCExcel: boolean;
|
||||
isMaximized: boolean;
|
||||
onToggleMaximize: () => void;
|
||||
}
|
||||
|
||||
export function TopBar({ stats, activeModule, onExport, onDownloadBCExcel, onRefresh, hasData, hasUnsavedChanges, userEmail, onSignOut, canUndo, onUndo, undoMessage, undoSteps, pendingCount, pendingChanges, onSaveAll, onRevertRow, isSavingAll, bcQueueCount, bcQueueEntries, onToggleBcQueueSelection, onSelectAllBcQueue, onPreviewSelectedBcSync, onSyncSelectedBcSync, isSyncingBC, isDownloadingBCExcel, isMaximized, onToggleMaximize }: TopBarProps) {
|
||||
export function TopBar({ stats, activeModule, onExport, onDownloadBCExcel, onRefresh, hasData, hasUnsavedChanges, userEmail, onSignOut, canUndo, onUndo, undoMessage, undoSteps, pendingCount, pendingChanges, onSaveAll, onRevertRow, isSavingAll, bcQueueCount, bcQueueEntries, onToggleBcQueueSelection, onSelectAllBcQueue, onPreviewSelectedBcSync, onSyncSelectedBcSync, onDiscardSelectedBcQueue, isSyncingBC, isDownloadingBCExcel, isMaximized, onToggleMaximize }: TopBarProps) {
|
||||
const [showPending, setShowPending] = useState(false);
|
||||
const [showBcQueue, setShowBcQueue] = useState(false);
|
||||
const pendingDropdownRef = useRef<HTMLDivElement>(null);
|
||||
const bcQueueDropdownRef = useRef<HTMLDivElement>(null);
|
||||
const selectedBcQueueCount = Object.values(bcQueueEntries).filter(entry => entry.selected).length;
|
||||
|
||||
const formatQueueStatus = (status: string) => {
|
||||
const normalized = String(status || '').toLowerCase();
|
||||
if (normalized === 'preview_only') return 'Preview only';
|
||||
if (normalized === 'previewed') return 'Previewed';
|
||||
if (normalized === 'syncing') return 'Syncing';
|
||||
if (normalized === 'synced') return 'Synced';
|
||||
if (normalized === 'failed') return 'Failed';
|
||||
if (normalized === 'queued') return 'Queued';
|
||||
return String(status || '');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!showPending) return;
|
||||
const handler = (e: MouseEvent) => {
|
||||
@@ -238,6 +250,13 @@ export function TopBar({ stats, activeModule, onExport, onDownloadBCExcel, onRef
|
||||
>
|
||||
None
|
||||
</button>
|
||||
<button
|
||||
onClick={onDiscardSelectedBcQueue}
|
||||
disabled={isSyncingBC || selectedBcQueueCount === 0}
|
||||
className="text-xs px-2 py-1 rounded bg-slate-700 hover:bg-red-900/50 text-red-400 border border-red-500/30 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
Discard {selectedBcQueueCount > 0 ? `(${selectedBcQueueCount})` : ''}
|
||||
</button>
|
||||
<button
|
||||
onClick={onPreviewSelectedBcSync}
|
||||
disabled={isSyncingBC}
|
||||
@@ -262,7 +281,10 @@ export function TopBar({ stats, activeModule, onExport, onDownloadBCExcel, onRef
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-xs font-mono text-slate-400">{articleNo}</p>
|
||||
<p className="text-sm text-white truncate">{entry.articleName}</p>
|
||||
<p className="text-[10px] text-slate-500 uppercase">{entry.status}{entry.error ? ` · ${entry.error}` : ''}</p>
|
||||
<p className="text-[10px] text-slate-500 uppercase">
|
||||
{formatQueueStatus(entry.status)}
|
||||
{entry.error ? ` · ${entry.error}` : ''}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user