mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 12:45:24 +02:00
feat(history): add Discard selected button to cancel pending BC syncs
This commit is contained in:
@@ -15,13 +15,14 @@ interface HistoryViewProps {
|
||||
onEdit?: (rowIndex: number) => void;
|
||||
}
|
||||
|
||||
type HistorySyncStatus = 'bc_pending' | 'previewed' | 'syncing' | 'synced' | 'failed';
|
||||
type HistorySyncStatus = 'bc_pending' | 'previewed' | 'preview_only' | 'syncing' | 'synced' | 'failed';
|
||||
|
||||
interface HistorySyncRecord {
|
||||
selected: boolean;
|
||||
status: HistorySyncStatus;
|
||||
previewToken?: string;
|
||||
error?: string;
|
||||
warning?: string;
|
||||
}
|
||||
|
||||
type HistorySyncMap = Record<string, HistorySyncRecord>;
|
||||
@@ -45,7 +46,7 @@ export function HistoryView({ headers, data, onRevert, onEdit }: HistoryViewProp
|
||||
const [expandedId, setExpandedId] = useState<string | null>(null);
|
||||
const [search, setSearch] = usePersistentState('history-search', '');
|
||||
const [userFilter, setUserFilter] = usePersistentState('history-userFilter', '');
|
||||
const [statusFilter, setStatusFilter] = usePersistentState<'all' | 'bc_pending' | 'previewed' | 'synced' | 'failed'>('history-statusFilter', 'all');
|
||||
const [statusFilter, setStatusFilter] = usePersistentState<'all' | 'bc_pending' | 'previewed' | 'preview_only' | 'synced' | 'failed'>('history-statusFilter', 'all');
|
||||
const [bcHistorySync, setBcHistorySync] = useState<HistorySyncMap>(() => readStoredHistorySyncMap());
|
||||
const [bcSyncBusy, setBcSyncBusy] = useState(false);
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
@@ -163,7 +164,7 @@ export function HistoryView({ headers, data, onRevert, onEdit }: HistoryViewProp
|
||||
);
|
||||
|
||||
const statusCounts = useMemo(() => {
|
||||
const counts = { bc_pending: 0, previewed: 0, synced: 0, failed: 0 };
|
||||
const counts = { bc_pending: 0, previewed: 0, preview_only: 0, synced: 0, failed: 0 };
|
||||
history.forEach(entry => {
|
||||
const status = getEntryStatus(entry);
|
||||
if (status in counts) counts[status as keyof typeof counts] += 1;
|
||||
@@ -196,12 +197,26 @@ export function HistoryView({ headers, data, onRevert, onEdit }: HistoryViewProp
|
||||
status: next[key]?.status ?? 'bc_pending',
|
||||
previewToken: next[key]?.previewToken,
|
||||
error: next[key]?.error,
|
||||
warning: next[key]?.warning,
|
||||
};
|
||||
});
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const discardSelected = () => {
|
||||
if (selectedEntries.length === 0) return;
|
||||
if (!window.confirm(`Discard ${selectedEntries.length} pending sync(s)? They will be reset to "Pending BC" and will not be synced to Business Central.`)) return;
|
||||
setBcHistorySync(prev => {
|
||||
const next = { ...prev };
|
||||
selectedEntries.forEach(entry => {
|
||||
const key = getEntryKey(entry);
|
||||
next[key] = { selected: false, status: 'bc_pending' };
|
||||
});
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const previewSelected = async () => {
|
||||
if (selectedEntries.length === 0) return;
|
||||
setBcSyncBusy(true);
|
||||
@@ -213,13 +228,14 @@ export function HistoryView({ headers, data, onRevert, onEdit }: HistoryViewProp
|
||||
})) {
|
||||
const preview = await previewBusinessCentralSync(headers, entry.new_data);
|
||||
if (!preview.success) {
|
||||
updateEntry(entry, { status: 'failed', error: preview.error || 'Preview failed', selected: true });
|
||||
updateEntry(entry, { status: 'failed', error: preview.error || 'Preview failed', warning: undefined, selected: true });
|
||||
continue;
|
||||
}
|
||||
updateEntry(entry, {
|
||||
status: 'previewed',
|
||||
previewToken: preview.previewToken,
|
||||
error: undefined,
|
||||
warning: undefined,
|
||||
selected: true,
|
||||
});
|
||||
}
|
||||
@@ -247,11 +263,11 @@ export function HistoryView({ headers, data, onRevert, onEdit }: HistoryViewProp
|
||||
if (!previewToken) {
|
||||
const preview = await previewBusinessCentralSync(headers, entry.new_data);
|
||||
if (!preview.success) {
|
||||
updateEntry(entry, { status: 'failed', error: preview.error || 'Preview failed', selected: true });
|
||||
updateEntry(entry, { status: 'failed', error: preview.error || 'Preview failed', warning: undefined, selected: true });
|
||||
continue;
|
||||
}
|
||||
previewToken = preview.previewToken;
|
||||
updateEntry(entry, { status: 'previewed', previewToken, error: undefined, selected: true });
|
||||
updateEntry(entry, { status: 'previewed', previewToken, error: undefined, warning: undefined, selected: true });
|
||||
}
|
||||
|
||||
const apply = await applyBusinessCentralSync(headers, entry.new_data, previewToken);
|
||||
@@ -263,6 +279,7 @@ export function HistoryView({ headers, data, onRevert, onEdit }: HistoryViewProp
|
||||
status: 'previewed',
|
||||
previewToken: refreshedPreview.previewToken,
|
||||
error: undefined,
|
||||
warning: undefined,
|
||||
selected: true,
|
||||
});
|
||||
const retryApply = await applyBusinessCentralSync(headers, entry.new_data, refreshedPreview.previewToken);
|
||||
@@ -270,22 +287,24 @@ export function HistoryView({ headers, data, onRevert, onEdit }: HistoryViewProp
|
||||
updateEntry(entry, {
|
||||
status: 'synced',
|
||||
error: undefined,
|
||||
warning: retryApply.warning,
|
||||
selected: false,
|
||||
previewToken: retryApply.previewToken || refreshedPreview.previewToken,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
updateEntry(entry, { status: 'failed', error: retryApply.error || 'BC sync failed', selected: true });
|
||||
updateEntry(entry, { status: 'failed', error: retryApply.error || 'BC sync failed', warning: retryApply.warning, selected: true });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
updateEntry(entry, { status: 'failed', error: apply.error || 'BC sync failed', selected: true });
|
||||
updateEntry(entry, { status: 'failed', error: apply.error || 'BC sync failed', warning: apply.warning, selected: true });
|
||||
continue;
|
||||
}
|
||||
|
||||
updateEntry(entry, {
|
||||
status: 'synced',
|
||||
error: undefined,
|
||||
warning: apply.warning,
|
||||
selected: false,
|
||||
previewToken: apply.previewToken || previewToken,
|
||||
});
|
||||
@@ -392,6 +411,7 @@ export function HistoryView({ headers, data, onRevert, onEdit }: HistoryViewProp
|
||||
<span className="text-slate-600">·</span>
|
||||
<span>{statusCounts.bc_pending} pending</span>
|
||||
<span>{statusCounts.previewed} previewed</span>
|
||||
<span>{statusCounts.preview_only} preview only</span>
|
||||
<span>{statusCounts.synced} synced</span>
|
||||
<span>{statusCounts.failed} failed</span>
|
||||
</div>
|
||||
@@ -404,6 +424,7 @@ export function HistoryView({ headers, data, onRevert, onEdit }: HistoryViewProp
|
||||
<option value="all">All statuses</option>
|
||||
<option value="bc_pending">Pending BC</option>
|
||||
<option value="previewed">Previewed</option>
|
||||
<option value="preview_only">Preview only</option>
|
||||
<option value="synced">Synced BC</option>
|
||||
<option value="failed">Failed</option>
|
||||
</select>
|
||||
@@ -419,6 +440,18 @@ export function HistoryView({ headers, data, onRevert, onEdit }: HistoryViewProp
|
||||
>
|
||||
Clear selection
|
||||
</button>
|
||||
<button
|
||||
onClick={discardSelected}
|
||||
disabled={selectedEntries.length === 0}
|
||||
className={cn(
|
||||
"px-3 py-2 rounded-md text-xs border transition-colors",
|
||||
selectedEntries.length === 0
|
||||
? "bg-slate-700 text-slate-500 border-slate-600 cursor-not-allowed"
|
||||
: "bg-slate-800 hover:bg-red-900/40 text-red-400 border-red-500/30 hover:border-red-500/60"
|
||||
)}
|
||||
>
|
||||
Discard selected
|
||||
</button>
|
||||
<button
|
||||
onClick={previewSelected}
|
||||
disabled={bcSyncBusy || selectedEntries.length === 0}
|
||||
|
||||
Reference in New Issue
Block a user