import React, { useDeferredValue, useMemo, useRef } from "react"; import { emptyTaskForm, normalizeIntervals, sanitizeTaskForm } from "./appDefaults.js"; import { formatAccountLabel, formatAccountStatus, formatTimestamp, formatCountdown } from "./utils/formatters.js"; import { copyToClipboard } from "./utils/clipboard.js"; import { explainInviteError, explainTdataError } from "./utils/errorHints.js"; import AppOverlays from "./components/AppOverlays.jsx"; import AppSidebar from "./components/AppSidebar.jsx"; import AppMain from "./components/AppMain.jsx"; import useTaskStatusView from "./hooks/useTaskStatusView.js"; import useNotifications from "./hooks/useNotifications.js"; import useLogsView from "./hooks/useLogsView.js"; import useAccountImport from "./hooks/useAccountImport.js"; import useTaskActions from "./hooks/useTaskActions.js"; import useAccessChecks from "./hooks/useAccessChecks.js"; import useAccountManagement from "./hooks/useAccountManagement.js"; import useTaskPresets from "./hooks/useTaskPresets.js"; import useTaskSelection from "./hooks/useTaskSelection.js"; import useInviteImport from "./hooks/useInviteImport.js"; import useSettingsActions from "./hooks/useSettingsActions.js"; import useTaskLoaders from "./hooks/useTaskLoaders.js"; import useTaskFormActions from "./hooks/useTaskFormActions.js"; import useAccountComputed from "./hooks/useAccountComputed.js"; import useAppOrchestration from "./hooks/useAppOrchestration.js"; import useTaskSelectors from "./hooks/useTaskSelectors.js"; import useCriticalEvents from "./hooks/useCriticalEvents.js"; import useUiComputed from "./hooks/useUiComputed.js"; import useMainUiProps from "./hooks/useMainUiProps.js"; import useAppTabGroups from "./hooks/useAppTabGroups.js"; import useAppLoaders from "./hooks/useAppLoaders.js"; import useAppState from "./hooks/useAppState.js"; import useAppTaskDerived from "./hooks/useAppTaskDerived.js"; import useOpenLogsTabListener from "./hooks/useOpenLogsTabListener.js"; import useAppOutsideClicks from "./hooks/useAppOutsideClicks.js"; import { APP_VERSION } from "./constants/consts.js"; export default function App() { const { settings, setSettings, accounts, setAccounts, accountStats, setAccountStats, accountAssignments, setAccountAssignments, globalStatus, setGlobalStatus, logs, setLogs, invites, setInvites, fallbackList, setFallbackList, confirmQueue, setConfirmQueue, tasks, setTasks, selectedTaskId, setSelectedTaskId, taskForm, setTaskForm, competitorText, setCompetitorText, selectedAccountIds, setSelectedAccountIds, taskAccountRoles, setTaskAccountRoles, activePreset, setActivePreset, presetSignatureRef, taskStatus, setTaskStatus, taskStatusMap, setTaskStatusMap, membershipStatus, setMembershipStatus, groupVisibility, setGroupVisibility, accessStatus, setAccessStatus, inviteAccessStatus, setInviteAccessStatus, inviteAccessCheckedAt, setInviteAccessCheckedAt, confirmAccessStatus, setConfirmAccessStatus, confirmAccessCheckedAt, setConfirmAccessCheckedAt, accountEvents, setAccountEvents, apiTraceLogs, setApiTraceLogs, taskAudit, setTaskAudit, testRun, setTestRun, queueItems, setQueueItems, queueStats, setQueueStats, fileImportForm, setFileImportForm, fileImportResult, setFileImportResult, taskActionLoading, setTaskActionLoading, taskNotice, setTaskNotice, autosaveNote, setAutosaveNote, settingsNotice, setSettingsNotice, tdataNotice, setTdataNotice, notificationsOpen, setNotificationsOpen, notificationsModalRef, importModalOpen, setImportModalOpen, nowExpanded, setNowExpanded, moreActionsOpen, setMoreActionsOpen, moreActionsRef, checklistOpen, setChecklistOpen, manualLoginOpen, setManualLoginOpen, taskSearch, setTaskSearch, taskFilter, setTaskFilter, infoOpen, setInfoOpen, infoTab, setInfoTab, activeTab, setActiveTab, logsTab, setLogsTab, taskSort, setTaskSort, expandedInviteId, setExpandedInviteId, liveConfirmOpen, setLiveConfirmOpen, liveConfirmContext, setLiveConfirmContext, now, setNow, isVisible, setIsVisible, bellRef, settingsAutosaveReady, taskAutosaveReady, taskAutosaveTimer, autosaveNoteTimer, tasksPollInFlight, accountsPollInFlight, logsPollInFlight, eventsPollInFlight } = useAppState(); const liveConfirmResolver = useRef(null); const { competitorGroups, hasSelectedTask, selectedTask, selectedTaskName, canSaveTask } = useAppTaskDerived({ tasks, selectedTaskId, taskForm, competitorText }); const appendTestEvent = async (action, details) => { if (!window.api) return; try { await window.api.addAccountEvent({ accountId: 0, action, details }); } catch (error) { // ignore logging errors } }; const runTest = async (mode) => { const startedAt = new Date().toISOString(); setTestRun({ status: "running", mode, steps: [], startedAt: formatTimestamp(startedAt), finishedAt: "", summary: "" }); await appendTestEvent("test_run_start", `${selectedTaskName} · режим: ${mode}`); const steps = []; const pushStep = (title, status, details) => { const step = { title, status, details }; steps.push(step); setTestRun((prev) => ({ ...prev, steps: [...steps] })); appendTestEvent("test_run_step", `${title} · ${status}${details ? ` · ${details}` : ""}`); }; if (!hasSelectedTask) { pushStep("Выбрана задача", "error", "Задача не выбрана"); } else { pushStep("Выбрана задача", "ok", selectedTaskName); } const missing = []; if (!taskForm.name.trim()) missing.push("название"); if (!taskForm.ourGroup.trim()) missing.push("наша группа"); if (competitorGroups.length === 0) missing.push("группы конкурентов"); if (missing.length) { pushStep("Заполнены поля задачи", "error", `Не заполнено: ${missing.join(", ")}`); } else { pushStep("Заполнены поля задачи", "ok", "Минимально достаточно"); } if (selectedAccountIds.length === 0) { pushStep("Аккаунты в задаче", "error", "Не выбраны аккаунты"); } else { pushStep("Аккаунты в задаче", "ok", `Выбрано: ${selectedAccountIds.length}`); } const inviteCount = roleSummary.invite.length; const monitorCount = roleSummary.monitor.length; const confirmCount = roleSummary.confirm.length; const roleDetails = `мониторинг: ${monitorCount}, инвайт: ${inviteCount}, подтверждение: ${confirmCount}`; if (inviteCount === 0) { pushStep("Роли аккаунтов", "error", `Нет роли инвайта (${roleDetails})`); } else { pushStep("Роли аккаунтов", "ok", roleDetails); } const membershipIds = Object.keys(membershipStatus || {}); const inOurGroup = membershipIds.filter((id) => membershipStatus[id]?.ourGroupMember).length; pushStep("Участие в нашей группе", inOurGroup > 0 ? "ok" : "warn", `В нашей группе: ${inOurGroup}`); if (!inviteAccessStatus || inviteAccessStatus.length === 0) { pushStep("Проверка прав инвайта", "warn", "Права не проверялись"); } else { const okCount = inviteAccessStatus.filter((item) => item && item.ok).length; pushStep("Проверка прав инвайта", okCount > 0 ? "ok" : "warn", `OK: ${okCount} / ${inviteAccessStatus.length}`); } pushStep("Очередь", queueStats.total > 0 ? "ok" : "warn", `В очереди: ${queueStats.total}`); const minInterval = Number(taskForm.minIntervalMinutes || 0); const maxInterval = Number(taskForm.maxIntervalMinutes || 0); const intervalOk = minInterval > 0 && maxInterval > 0 && minInterval <= maxInterval; pushStep("Интервалы и лимиты", intervalOk ? "ok" : "warn", `Мин: ${minInterval || "—"} · Макс: ${maxInterval || "—"}`); if (taskForm.inviteViaAdmins) { pushStep("Инвайт через админов", taskForm.inviteAdminMasterId ? "ok" : "warn", taskForm.inviteAdminMasterId ? "Мастер-админ выбран" : "Не выбран мастер-админ"); } else { pushStep("Инвайт через админов", "ok", "Отключено"); } if (mode === "live") { pushStep("Live: проверка прав групп", "ok", "Запущена"); await checkAccess(); pushStep("Live: проверка прав инвайта", "ok", "Запущена"); await checkInviteAccess(); pushStep("Live: проверка видимости конкурентов", "ok", "Запущена"); const visibilityResult = await window.api.groupVisibilityByTask(selectedTaskId); if (visibilityResult && visibilityResult.ok && Array.isArray(visibilityResult.result)) { const items = visibilityResult.result; const openCount = items.filter((item) => item && item.status === "open").length; const closedCount = items.filter((item) => item && item.status === "closed").length; const unknownCount = items.filter((item) => !item || !item.status || item.status === "unknown").length; pushStep("Live: видимость конкурентов", "ok", `Открытые: ${openCount} · Закрытые: ${closedCount} · Неизвестные: ${unknownCount}`); const closedLinks = items .filter((item) => item && item.status === "closed") .map((item) => item.link || item.source || "") .filter(Boolean); if (closedLinks.length) { const preview = closedLinks.slice(0, 5).join(", "); const suffix = closedLinks.length > 5 ? ` и ещё ${closedLinks.length - 5}` : ""; pushStep("Live: закрытые конкуренты", "warn", `${preview}${suffix}`); } } else { pushStep("Live: видимость конкурентов", "warn", "Не удалось определить видимость"); } pushStep("Live: обновление участия", "ok", "Запущено"); await refreshMembership("test"); if (queueStats.total > 0) { const confirmed = await new Promise((resolve) => { liveConfirmResolver.current = resolve; const item = (queueItems || [])[0] || null; setLiveConfirmContext(item ? { userId: item.user_id, username: item.username, sourceChat: item.source_chat } : null); setLiveConfirmOpen(true); }); if (confirmed) { pushStep("Live: тестовый инвайт", "ok", "Запуск"); const liveResult = await window.api.testInviteOnce({ taskId: selectedTaskId }); if (liveResult && liveResult.ok) { const confirmLabel = liveResult.confirmed === true ? "подтверждено" : liveResult.confirmed === false ? `не подтверждено${liveResult.confirmError ? ` (${liveResult.confirmError})` : ""}` : "не проверено"; pushStep("Live: результат инвайта", "ok", `Успешно · ${confirmLabel}`); } else { pushStep("Live: результат инвайта", "warn", liveResult && liveResult.error ? liveResult.error : "Ошибка инвайта"); } } else { pushStep("Live: тестовый инвайт", "warn", "Отменено пользователем"); } } else { pushStep("Live: тестовый инвайт", "warn", "Очередь пуста"); } } const hasError = steps.some((step) => step.status === "error"); const hasWarn = steps.some((step) => step.status === "warn"); const finishedAt = new Date().toISOString(); const status = hasError ? "error" : hasWarn ? "warn" : "ok"; const summary = hasError ? "Есть ошибки" : hasWarn ? "Есть предупреждения" : "Всё готово"; setTestRun((prev) => ({ ...prev, status, finishedAt: formatTimestamp(finishedAt), summary })); await appendTestEvent("test_run_finish", `${status} · ${summary}`); }; const { toasts, notifications, setNotifications, notificationFilter, setNotificationFilter, filteredNotifications, showNotification, dismissToast } = useNotifications(); const { logSearch, setLogSearch, inviteSearch, setInviteSearch, fallbackSearch, setFallbackSearch, auditSearch, setAuditSearch, confirmSearch, setConfirmSearch, logPage, setLogPage, invitePage, setInvitePage, fallbackPage, setFallbackPage, auditPage, setAuditPage, confirmPage, setConfirmPage, queueSearch, setQueueSearch, queuePage, setQueuePage, inviteFilter, setInviteFilter, logPageCount, invitePageCount, fallbackPageCount, auditPageCount, confirmPageCount, queuePageCount, pagedLogs, pagedInvites, pagedFallback, pagedAudit, pagedConfirmQueue, pagedQueue, inviteStats, mutualContactDiagnostics } = useLogsView({ logs, invites, fallbackList, taskAudit, confirmQueue, queueItems }); const confirmStats = useMemo(() => { const stats = { total: confirmQueue.length, pending: 0, confirmed: 0, failed: 0 }; (confirmQueue || []).forEach((item) => { if (!item) return; if (item.status === "confirmed") stats.confirmed += 1; else if (item.status === "failed") stats.failed += 1; else stats.pending += 1; }); return stats; }, [confirmQueue]); const { checkAccess, checkInviteAccess, checkConfirmAccess } = useAccessChecks({ selectedTaskId, setAccessStatus, setInviteAccessStatus, setInviteAccessCheckedAt, setConfirmAccessStatus, setConfirmAccessCheckedAt, setTaskNotice, showNotification }); const { loadTasks, loadAccountAssignments, loadTaskStatuses, loadBase } = useAppLoaders({ selectedTaskId, setTasks, setSelectedTaskId, setAccountAssignments, setTaskStatusMap, setSettings, setAccounts, setAccountEvents, setAccountStats, setGlobalStatus }); const deferredTaskSearch = useDeferredValue(taskSearch); const { onSettingsChange, saveSettings } = useSettingsActions({ settings, setSettings, setSettingsNotice, showNotification }); const { createTask, selectTask } = useTaskSelection({ taskAutosaveReady, selectedTaskId, setSelectedTaskId, setTaskForm, setCompetitorText, setSelectedAccountIds, setTaskAccountRoles, setAccessStatus, setMembershipStatus }); const { importInviteFile } = useInviteImport({ fileImportForm, setFileImportForm, setFileImportResult, hasSelectedTask, selectedTaskId, showNotification, setInvites, setFallbackList, loadTaskStatuses }); const { loadSelectedTask, refreshMembership } = useTaskLoaders({ taskAutosaveReady, setTaskForm, setCompetitorText, setSelectedAccountIds, setTaskAccountRoles, setLogs, setInvites, setFallbackList, setConfirmQueue, setGroupVisibility, setTaskStatus, setMembershipStatus, showNotification, selectedTaskId }); const { saveTask, deleteTask, startTask, stopTask, startAllTasks, stopAllTasks, parseHistory, checkAll, joinGroupsForTask, clearLogs, clearInvites, clearAccountEvents, exportLogs, exportTaskBundle, exportInvites, exportProblemInvites, exportFallback, updateFallbackStatus, clearFallback, clearConfirmQueue, clearQueue, clearAllTaskLogsAndQueue, clearDatabase, resetSessions } = useTaskActions({ taskForm, setTaskForm, sanitizeTaskForm, taskAccountRoles, setTaskAccountRoles, selectedAccountIds, setSelectedAccountIds, accounts, selectedTaskId, selectedTaskName, competitorGroups, hasSelectedTask, setTaskNotice, showNotification, setAutosaveNote, autosaveNoteTimer, loadTasks, loadAccountAssignments, loadTaskStatuses, refreshMembership, checkInviteAccess, checkAccess, setLogs, setInvites, setTaskStatus, setTaskAudit, setSelectedTaskId, resetTaskForm: () => setTaskForm(emptyTaskForm), setCompetitorText, resetSelectedAccountIds: setSelectedAccountIds, resetTaskAccountRoles: setTaskAccountRoles, setFallbackList, setConfirmQueue, setAccountEvents, setTaskActionLoading, taskActionLoading, loadBase, createTask, setActiveTab, checkConfirmAccess }); const { accountById, accountStatsMap, roleSummary, roleIntersectionCount, assignedAccountCount, assignedAccountMap, filterFreeAccounts, accountBuckets, perAccountInviteSum } = useAccountComputed({ accounts, accountStats, taskAccountRoles, accountAssignments, selectedTaskId, tasks }); const { persistAccountRoles, computeAdminConfirmConfigRisk, fixAdminConfirmConfigRisk, computeConfirmAccessRisk, fixConfirmAccessRisk, resetCooldown, deleteAccount, refreshIdentity, updateAccountRole, updateAccountInviteLimit, setInviteLimitForAllInviters, setAccountRolesAll, applyRolePreset, computeWatcherInviteRisk, fixWatcherInviteRisk, assignAccountsToTask, moveAccountToTask, removeAccountFromTask } = useAccountManagement({ selectedTaskId, taskAccountRoles, setTaskAccountRoles, setTaskForm, selectedAccountIds, setSelectedAccountIds, accounts, accountBuckets, taskForm, hasSelectedTask, loadAccountAssignments, showNotification, setTaskNotice, setAccounts, membershipStatus, refreshMembership, confirmAccessStatus }); const { applyTaskPreset } = useTaskPresets({ hasSelectedTask, accounts, selectedAccountIds, taskForm, setTaskForm, setTaskAccountRoles, setSelectedAccountIds, persistAccountRoles, showNotification, setTaskNotice, setActivePreset, setActiveTab, selectedTaskId, presetSignatureRef }); const { loginForm, setLoginForm, tdataForm, setTdataForm, loginStatus, tdataResult, tdataLoading, startLogin, completeLogin, importTdata } = useAccountImport({ selectedTaskId, hasSelectedTask, assignAccountsToTask, setAccounts, showNotification, explainTdataError, setTdataNotice }); const { taskSummary, filteredTasks } = useTaskSelectors({ tasks, taskStatusMap, deferredTaskSearch, taskFilter, taskSort }); useAppOutsideClicks({ notificationsOpen, notificationsModalRef, bellRef, setNotificationsOpen, moreActionsOpen, moreActionsRef, setMoreActionsOpen }); const formatCountdownWithNowLocal = (value) => formatCountdown(value, now); const { criticalErrorAccounts } = useCriticalEvents({ accountEvents, accounts }); const { monitorLabels, inviteLabels, nowLine, primaryIssue, openFixTab, checklistItems, checklistStats, inviteAccessChecked, inviteAccessOk, inviteAccessWarn, lastEvents } = useTaskStatusView({ taskStatus, taskAccountRoles, accountById, formatAccountLabel, setActiveTab, checkInviteAccess, checkConfirmAccess, parseHistory, refreshMembership, assignedAccountCount, roleSummary, accountEvents, formatCountdownWithNow: formatCountdownWithNowLocal, inviteAccessStatus, confirmAccessStatus, membershipStatus, selectedTask, computeWatcherInviteRisk, fixWatcherInviteRisk }); const { pauseReason, hasPerAccountInviteLimits, formatCountdownWithNow } = useUiComputed({ taskStatus, assignedAccountCount, roleSummary, inviteAccessWarn, taskAccountRoles, taskStatusMap, tasks, formatCountdown, now }); const refreshQueue = React.useCallback(async () => { if (!window.api || selectedTaskId == null) return; try { const queueData = await window.api.listQueue({ limit: 200, taskId: selectedTaskId }); if (queueData && Array.isArray(queueData.items)) setQueueItems(queueData.items); if (queueData && queueData.stats) setQueueStats(queueData.stats); } catch (_error) { // noop: queue refresh errors are non-blocking for UI actions } }, [selectedTaskId, setQueueItems, setQueueStats]); useAppOrchestration({ activeTab, selectedTaskId, isVisible, setIsVisible, setNow, tasksPollInFlight, accountsPollInFlight, logsPollInFlight, eventsPollInFlight, setTasks, loadTaskStatuses, setTaskStatus, setAccounts, setAccountAssignments, setAccountStats, setGlobalStatus, setLogs, setInvites, setFallbackList, setConfirmQueue, setTaskAudit, setAccountEvents, setApiTraceLogs, setQueueItems, setQueueStats, loadBase, loadSelectedTask, setAccessStatus, setInviteAccessStatus, setMembershipStatus, setTaskNotice, setActivePreset, checkAccess, checkInviteAccess, activePreset, taskForm, taskAccountRoles, presetSignatureRef, taskStatus, setTaskStatusMap, checklistStats, setChecklistOpen, hasSelectedTask, roleIntersectionCount, roleSummary, setTaskForm, sanitizeTaskForm, taskNotice, settingsNotice, tdataNotice, setSettingsNotice, setTdataNotice, showNotification, settings, settingsAutosaveReady, taskAutosaveReady, taskAutosaveTimer, competitorText, selectedAccountIds, canSaveTask, saveTask, setSettings }); const clearApiTrace = async () => { if (!window.api) return; try { await window.api.clearApiTrace(selectedTaskId || 0); setApiTraceLogs(await window.api.listApiTrace({ limit: 300, taskId: selectedTaskId || 0 })); showNotification("API трассировка очищена.", "success"); } catch (error) { showNotification(error.message || String(error), "error"); } }; const toggleApiTrace = async () => { if (!window.api) return; try { const next = !Boolean(settings && settings.apiTraceEnabled); const updated = await window.api.saveSettings({ ...settings, apiTraceEnabled: next }); setSettings(updated); showNotification(`Трассировка API ${next ? "включена" : "выключена"}.`, next ? "success" : "info"); } catch (error) { showNotification(error.message || String(error), "error"); } }; const exportApiTraceJson = async () => { if (!window.api) return; try { const result = await window.api.exportApiTraceJson(selectedTaskId || 0); if (result && result.ok) { showNotification(`API трассировка выгружена в JSON: ${result.filePath}`, "success"); } } catch (error) { showNotification(error.message || String(error), "error"); } }; const exportApiTraceCsv = async () => { if (!window.api) return; try { const result = await window.api.exportApiTraceCsv(selectedTaskId || 0); if (result && result.ok) { showNotification(`API трассировка выгружена в CSV: ${result.filePath}`, "success"); } } catch (error) { showNotification(error.message || String(error), "error"); } }; const { applyRoleMode } = useTaskFormActions({ taskForm, setTaskForm }); const { quickActions, nowStatus, checklist, tabs } = useMainUiProps({ selectedTaskName, autosaveNote, taskStatus, hasSelectedTask, canSaveTask, taskActionLoading, saveTask, parseHistory, joinGroupsForTask, checkAll, startTask, stopTask, moreActionsOpen, setMoreActionsOpen, moreActionsRef, clearQueue, clearAllTaskLogsAndQueue, startAllTasks, stopAllTasks, clearDatabase, resetSessions, pauseReason, setActiveTab, tasksLength: tasks.length, runTestSafe: () => runTest("safe"), exportTaskBundle, refreshMembership, refreshIdentity, apiTraceEnabled: Boolean(settings && settings.apiTraceEnabled), toggleApiTrace, setInfoOpen, setInfoTab, nowLine, nowExpanded, setNowExpanded, primaryIssue, openFixTab, monitorLabels, inviteLabels, roleSummary, groupVisibility, lastEvents, formatTimestamp, checklistOpen, setChecklistOpen, checklistStats, checklistItems, activeTab, logsTab, setLogsTab, confirmStats }); const { taskSettings, accountsTab, logsTab: logsTabGroup, queueTab: queueTabGroup, apiTraceTab, eventsTab, settingsTab } = useAppTabGroups({ selectedTaskId, refreshQueue, selectedTaskName, taskForm, setTaskForm, activePreset, setActivePreset, applyTaskPreset, formatAccountLabel, accountById, competitorText, setCompetitorText, applyRoleMode, normalizeIntervals, taskStatus, perAccountInviteSum, hasSelectedTask, inviteAccessStatus, inviteAccessCheckedAt, confirmAccessStatus, confirmAccessCheckedAt, formatTimestamp, checkInviteAccess, checkConfirmAccess, accounts, showNotification, copyToClipboard, sanitizeTaskForm, hasPerAccountInviteLimits, fileImportResult, importInviteFile, fileImportForm, setFileImportForm, criticalErrorAccounts, accountStatsMap, settings, membershipStatus, assignedAccountMap, accountBuckets, filterFreeAccounts, selectedAccountIds, taskAccountRoles, inviteAdminMasterId: taskForm.inviteAdminMasterId, refreshMembership, refreshIdentity, formatAccountStatus, resetCooldown, deleteAccount, updateAccountRole, updateAccountInviteLimit, setInviteLimitForAllInviters, setAccountRolesAll, applyRolePreset, computeWatcherInviteRisk, fixWatcherInviteRisk, removeAccountFromTask, moveAccountToTask, logsTab, setLogsTab, exportLogs, clearLogs, exportInvites, exportProblemInvites, exportFallback, updateFallbackStatus, clearFallback, clearInvites, logSearch, setLogSearch, logPage, setLogPage, logPageCount, pagedLogs, inviteSearch, setInviteSearch, invitePage, setInvitePage, invitePageCount, inviteFilter, setInviteFilter, pagedInvites, fallbackSearch, setFallbackSearch, fallbackPage, setFallbackPage, fallbackPageCount, pagedFallback, confirmQueue, confirmSearch, setConfirmSearch, confirmPage, setConfirmPage, confirmPageCount, pagedConfirmQueue, confirmStats, queueItems, queueStats, queueSearch, setQueueSearch, queuePage, setQueuePage, queuePageCount, pagedQueue, clearConfirmQueue, auditSearch, setAuditSearch, auditPage, setAuditPage, auditPageCount, pagedAudit, explainInviteError, expandedInviteId, setExpandedInviteId, inviteStats, invites, selectedTask, accessStatus, roleSummary, mutualContactDiagnostics, apiTraceLogs, clearApiTrace, exportApiTraceJson, exportApiTraceCsv, accountEvents, clearAccountEvents, onSettingsChange, saveSettings }); useOpenLogsTabListener(setActiveTab); return (