mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 12:35:25 +02:00
feat: implement manual user validation and user deletion flow
This commit is contained in:
+81
-39
@@ -21,7 +21,11 @@ import { UndoToast } from './components/UndoToast';
|
||||
import { PendingValidationView } from './components/PendingValidationView';
|
||||
import { MissingDataView } from './components/MissingDataView';
|
||||
import { CosmeticItemsView } from './components/CosmeticItemsView';
|
||||
import { ControlDashboardView } from './components/ControlDashboardView';
|
||||
import { UserManagementView } from './components/UserManagementView';
|
||||
import { downloadBusinessCentralItemsExcel, previewBusinessCentralSync, applyBusinessCentralSync, isPreviewTokenMismatchError } from './services/businessCentral';
|
||||
import { ControlTabId, type DashboardDrilldownRequest } from './lib/controlDashboard';
|
||||
|
||||
const FORCED_ZERO_STOCK_SKUS = new Set([
|
||||
'11631VC', '1237VC', '1238VC', '1652VC', '1653VC', '1684VC', '1688VC', '1717VC',
|
||||
'1718VC', '180VC', '1832VC', '2025VC', '2027VC', '2180VC', '2181VC', '2210VC',
|
||||
@@ -92,7 +96,7 @@ export default function App() {
|
||||
hasUnsavedChanges: false,
|
||||
asinColumnIndex: null
|
||||
});
|
||||
const [activeModule, setActiveModule] = useState<'descriptions' | 'article_details' | 'matrix' | 'dimensions' | 'pricing' | 'pending_validation' | 'history' | 'missing_data' | 'cosmetic_items'>('descriptions');
|
||||
const [activeModule, setActiveModule] = useState<ControlTabId>('control_dashboard');
|
||||
const [undoHistory, setUndoHistory] = useState<{ data: ExcelRow[], message: string }[]>([]);
|
||||
const [editingRowIndex, setEditingRowIndex] = useState<number | null>(null);
|
||||
const [isLoadingDefault, setIsLoadingDefault] = useState(true);
|
||||
@@ -104,11 +108,59 @@ export default function App() {
|
||||
const [isSyncingBC, setIsSyncingBC] = useState(false);
|
||||
const [isDownloadingBCExcel, setIsDownloadingBCExcel] = useState(false);
|
||||
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
||||
const [dashboardDrilldown, setDashboardDrilldown] = useState<DashboardDrilldownRequest | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('[App] session changed:', session ? 'logged in' : 'logged out');
|
||||
}, [session]);
|
||||
|
||||
// Background user validation status check
|
||||
useEffect(() => {
|
||||
if (!session) return;
|
||||
|
||||
let isMounted = true;
|
||||
|
||||
const checkUserStatus = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/users-admin', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${session.access_token}`
|
||||
},
|
||||
body: JSON.stringify({ action: 'check-status' })
|
||||
});
|
||||
|
||||
if (!isMounted) return;
|
||||
|
||||
if (!res.ok) {
|
||||
console.warn('[App] Validation check failed, logging out.');
|
||||
handleSignOut();
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
if (!data.validated) {
|
||||
alert('Tu usuario ya no está autorizado o está pendiente de validación.');
|
||||
handleSignOut();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[App] Background validation check error:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// Check immediately on mount/session change
|
||||
checkUserStatus();
|
||||
|
||||
// Check periodically every 5 minutes
|
||||
const interval = setInterval(checkUserStatus, 5 * 60 * 1000);
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [session]);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem(BC_SYNC_QUEUE_STORAGE_KEY, JSON.stringify(bcSyncQueue));
|
||||
@@ -235,30 +287,7 @@ export default function App() {
|
||||
}
|
||||
|
||||
const resolvedCols = resolveColumnIndices(extendedHeaders);
|
||||
const editableColumns = new Set([
|
||||
resolvedCols.CLASSIFICATION,
|
||||
resolvedCols.LONG_DE, resolvedCols.LONG_EN,
|
||||
resolvedCols.SHORT_DE, resolvedCols.SHORT_EN,
|
||||
resolvedCols.DETAILS_DE, resolvedCols.DETAILS_EN,
|
||||
resolvedCols.INNER_L, resolvedCols.INNER_W, resolvedCols.INNER_H,
|
||||
resolvedCols.OUTER_L, resolvedCols.OUTER_W, resolvedCols.OUTER_H,
|
||||
resolvedCols.UNITS_OUTER, resolvedCols.MOQ,
|
||||
resolvedCols.VERIFIED_DIMS,
|
||||
resolvedCols.VALIDATED_CHECK,
|
||||
resolvedCols.VALIDATED_NOTE,
|
||||
resolvedCols.PRODUCT_TYPE,
|
||||
resolvedCols.ITEM_TO_LOGISTIC,
|
||||
resolvedCols.CPNP_NO,
|
||||
]);
|
||||
|
||||
headers.forEach((h: any, i: number) => {
|
||||
const hl = String(h || '').toLowerCase();
|
||||
if (hl.includes('srp') || hl.includes('uvp') || hl.includes('40') || hl.includes('price')) {
|
||||
editableColumns.add(i);
|
||||
}
|
||||
});
|
||||
|
||||
const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
const processedRows = rows.map(row => {
|
||||
const articleNo = String(row[articleNoIdx]);
|
||||
const synced = syncedData[articleNo];
|
||||
@@ -300,17 +329,6 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
}
|
||||
});
|
||||
|
||||
// 2. For Master Data (indices < 100, like UVP/SRP), only restore if it's an active edit (pending)
|
||||
// This protects against the "Column Shift" bug where old saved indices might be wrong.
|
||||
if (synced?.status === 'pending' || synced?.status === 'edited') {
|
||||
editableColumns.forEach(idx => {
|
||||
const value = synced?.data?.[idx];
|
||||
if (idx < 100 && value !== undefined && value !== null) {
|
||||
finalRow[idx] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update row status in UI if it's not the default 'excel'
|
||||
if (synced?.status && synced.status !== 'excel') {
|
||||
setRowStatuses(prev => ({ ...prev, [articleNo]: synced.status! }));
|
||||
@@ -372,7 +390,7 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
hasUnsavedChanges: false,
|
||||
asinColumnIndex: asinIdx !== -1 ? asinIdx : null
|
||||
});
|
||||
setActiveModule('descriptions');
|
||||
setActiveModule('control_dashboard');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load from Dropbox:', err);
|
||||
@@ -465,7 +483,7 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
hasUnsavedChanges: false,
|
||||
asinColumnIndex: null
|
||||
});
|
||||
setActiveModule('descriptions');
|
||||
setActiveModule('control_dashboard');
|
||||
}
|
||||
};
|
||||
reader.readAsBinaryString(file);
|
||||
@@ -694,7 +712,7 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
};
|
||||
|
||||
const handleDiscardSelectedBcQueue = () => {
|
||||
const selectedCount = Object.values(bcSyncQueue).filter(e => e.selected).length;
|
||||
const selectedCount = (Object.values(bcSyncQueue) as BcSyncQueueEntry[]).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 => {
|
||||
@@ -1055,6 +1073,21 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
asinColumnIndex={appState.asinColumnIndex}
|
||||
onEdit={(index) => setEditingRowIndex(index)}
|
||||
rowStatuses={rowStatuses}
|
||||
dashboardDrilldown={dashboardDrilldown}
|
||||
onDashboardDrilldownApplied={() => setDashboardDrilldown(null)}
|
||||
/>
|
||||
)}
|
||||
{activeModule === 'control_dashboard' && (
|
||||
<ControlDashboardView
|
||||
headers={appState.headers}
|
||||
data={appState.data}
|
||||
pendingRows={pendingRows}
|
||||
rowStatuses={rowStatuses}
|
||||
activeModule={activeModule}
|
||||
onDrillDown={(request) => {
|
||||
setDashboardDrilldown(request);
|
||||
setActiveModule(request.tabId);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{activeModule === 'matrix' && (
|
||||
@@ -1081,6 +1114,8 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
onCaptureState={captureState}
|
||||
onEdit={(index) => setEditingRowIndex(index)}
|
||||
rowStatuses={rowStatuses}
|
||||
dashboardDrilldown={dashboardDrilldown}
|
||||
onDashboardDrilldownApplied={() => setDashboardDrilldown(null)}
|
||||
/>
|
||||
)}
|
||||
{activeModule === 'article_details' && (
|
||||
@@ -1088,6 +1123,8 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
data={appState.data}
|
||||
onEdit={(index) => setEditingRowIndex(index)}
|
||||
rowStatuses={rowStatuses}
|
||||
dashboardDrilldown={dashboardDrilldown}
|
||||
onDashboardDrilldownApplied={() => setDashboardDrilldown(null)}
|
||||
/>
|
||||
)}
|
||||
{activeModule === 'pending_validation' && (
|
||||
@@ -1115,6 +1152,8 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
onCaptureState={captureState}
|
||||
rowStatuses={rowStatuses}
|
||||
onQueueBcSync={handleQueueBcSync}
|
||||
dashboardDrilldown={dashboardDrilldown}
|
||||
onDashboardDrilldownApplied={() => setDashboardDrilldown(null)}
|
||||
/>
|
||||
)}
|
||||
{activeModule === 'history' && (
|
||||
@@ -1150,6 +1189,9 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{activeModule === 'user_management' && session && (
|
||||
<UserManagementView session={session} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user