documentation-hub 5.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +43 -0
- package/.github/workflows/build.yml +64 -0
- package/.github/workflows/ci.yml +39 -0
- package/.vscode/extensions.json +3 -0
- package/Current.md +97 -0
- package/DocHub_Image.png +0 -0
- package/README.md +666 -0
- package/USER_GUIDE.md +1173 -0
- package/Updater.md +311 -0
- package/build/256x256.png +0 -0
- package/build/512x512.png +0 -0
- package/build/app-update.yml +4 -0
- package/build/create-icon.js +208 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/build/icon_1024x1024.png +0 -0
- package/dist/assets/Analytics-BpsG9895.js +1 -0
- package/dist/assets/Card-IAZin8kp.js +1 -0
- package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
- package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
- package/dist/assets/Documents-CqZ25axS.js +1 -0
- package/dist/assets/Input-l89xwXBi.js +1 -0
- package/dist/assets/Reporting-DqdHJY_a.js +1 -0
- package/dist/assets/Search-XNbu5z_3.js +1 -0
- package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
- package/dist/assets/Sessions-ClZOPYNc.js +1 -0
- package/dist/assets/Settings-DUEHGURa.js +11 -0
- package/dist/assets/index-8xUe8ptc.js +24 -0
- package/dist/assets/index-RYyJqF7O.css +1 -0
- package/dist/assets/path-BkOl0AGO.js +1 -0
- package/dist/assets/promises-ID_B9S-h.js +1 -0
- package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
- package/dist/assets/useToast-yRSO1dkm.js +1 -0
- package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
- package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
- package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
- package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
- package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
- package/dist/electron/main-CXkNtyv-.js +19789 -0
- package/dist/electron/main.js +5 -0
- package/dist/electron/preload.js +1 -0
- package/dist/icon.png +0 -0
- package/dist/index.html +27 -0
- package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
- package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
- package/docs/README.md +115 -0
- package/docs/TOC_WIRING_GUIDE.md +344 -0
- package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
- package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
- package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
- package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
- package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
- package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
- package/docs/analysis/List_Implementation.md +206 -0
- package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
- package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
- package/docs/analysis/RefactorStyles.md +852 -0
- package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
- package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
- package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
- package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
- package/docs/analysis/github-issues-to-create.md +237 -0
- package/docs/api/API_README.md +538 -0
- package/docs/api/API_REFERENCE.md +751 -0
- package/docs/api/TYPE_DEFINITIONS.md +869 -0
- package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
- package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
- package/docs/docxmlater-readme.md +1341 -0
- package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
- package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
- package/docs/fixes/README.md +37 -0
- package/docs/github-issues/issue-1-body.md +125 -0
- package/docs/github-issues/issue-10-body.md +850 -0
- package/docs/github-issues/issue-2-body.md +200 -0
- package/docs/github-issues/issue-3-body.md +270 -0
- package/docs/github-issues/issue-4-body.md +169 -0
- package/docs/github-issues/issue-5-body.md +173 -0
- package/docs/github-issues/issue-6-body.md +158 -0
- package/docs/github-issues/issue-7-body.md +171 -0
- package/docs/github-issues/issue-8-body.md +407 -0
- package/docs/github-issues/issue-9-body.md +515 -0
- package/docs/github-issues/issue-tracker.md +274 -0
- package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
- package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
- package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
- package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
- package/docs/implementation/RefactorStyles.md +588 -0
- package/docs/implementation/implement-plan.md +489 -0
- package/docs/implementation/missing-helpers-implementation.md +391 -0
- package/docs/implementation/refactor-plan.md +520 -0
- package/docs/implementation/session-implementation-complete.md +233 -0
- package/docs/implementation/session-management-plan.md +250 -0
- package/docs/setup-checklist.md +77 -0
- package/docs/versions/changelog.md +345 -0
- package/electron/customUpdater.ts +656 -0
- package/electron/main.ts +2441 -0
- package/electron/memoryConfig.ts +187 -0
- package/electron/preload.ts +394 -0
- package/electron/proxyConfig.ts +340 -0
- package/electron/services/BackupService.ts +452 -0
- package/electron/services/DictionaryService.ts +402 -0
- package/electron/services/LocalDictionaryLookupService.ts +147 -0
- package/electron/services/PowerAutomateApiService.ts +231 -0
- package/electron/services/SharePointSyncService.ts +474 -0
- package/electron/windowsCertStore.ts +427 -0
- package/electron/zscalerConfig.ts +381 -0
- package/eslint.config.js +92 -0
- package/jest.config.js +52 -0
- package/package.json +214 -0
- package/postcss.config.mjs +6 -0
- package/public/icon.png +0 -0
- package/publish-release.ps1 +5 -0
- package/renovate.json +30 -0
- package/src/App.tsx +216 -0
- package/src/__mocks__/p-limit.js +12 -0
- package/src/__mocks__/styleMock.js +1 -0
- package/src/components/common/BugReportButton.tsx +44 -0
- package/src/components/common/BugReportDialog.tsx +193 -0
- package/src/components/common/Button.tsx +153 -0
- package/src/components/common/Card.tsx +86 -0
- package/src/components/common/ColorPickerDialog.tsx +177 -0
- package/src/components/common/ConfirmDialog.tsx +96 -0
- package/src/components/common/DebugConsole.tsx +275 -0
- package/src/components/common/EmptyState.tsx +183 -0
- package/src/components/common/ErrorBoundary.tsx +98 -0
- package/src/components/common/ErrorDetailsDialog.tsx +153 -0
- package/src/components/common/ErrorFallback.tsx +218 -0
- package/src/components/common/Input.tsx +109 -0
- package/src/components/common/Skeleton.tsx +184 -0
- package/src/components/common/SplashScreen.tsx +81 -0
- package/src/components/common/Toast.tsx +155 -0
- package/src/components/common/Tooltip.tsx +79 -0
- package/src/components/common/UpdateNotification.tsx +320 -0
- package/src/components/comparison/ComparisonWindow.tsx +374 -0
- package/src/components/comparison/SideBySideDiff.tsx +486 -0
- package/src/components/comparison/index.ts +8 -0
- package/src/components/document/DocumentUploader.tsx +288 -0
- package/src/components/document/HyperlinkPreview.tsx +430 -0
- package/src/components/document/HyperlinkService.md +1484 -0
- package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
- package/src/components/document/InlineChangesView.tsx +707 -0
- package/src/components/document/ProcessingProgress.tsx +303 -0
- package/src/components/document/ProcessingResults.tsx +256 -0
- package/src/components/document/TrackedChangesDetail.tsx +530 -0
- package/src/components/document/TrackedChangesPanel.tsx +546 -0
- package/src/components/document/VirtualDocumentList.tsx +240 -0
- package/src/components/editor/DocumentEditor.tsx +723 -0
- package/src/components/editor/DocumentEditorModal.tsx +640 -0
- package/src/components/editor/EditorQuickActions.tsx +502 -0
- package/src/components/editor/EditorToolbar.tsx +312 -0
- package/src/components/editor/TableEditor.tsx +926 -0
- package/src/components/editor/index.ts +18 -0
- package/src/components/layout/Header.tsx +190 -0
- package/src/components/layout/Sidebar.tsx +313 -0
- package/src/components/layout/TitleBar.tsx +190 -0
- package/src/components/navigation/CommandPalette.tsx +233 -0
- package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
- package/src/components/sessions/ChangeItem.tsx +408 -0
- package/src/components/sessions/ChangeViewer.tsx +1155 -0
- package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
- package/src/components/sessions/ProcessingOptions.tsx +297 -0
- package/src/components/sessions/ReplacementsTab.tsx +438 -0
- package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
- package/src/components/sessions/SessionManager.tsx +188 -0
- package/src/components/sessions/StylesEditor.tsx +1335 -0
- package/src/components/sessions/TabContainer.tsx +151 -0
- package/src/components/sessions/VirtualSessionList.tsx +157 -0
- package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
- package/src/components/settings/CertificateManager.tsx +410 -0
- package/src/components/settings/SegmentedControl.tsx +88 -0
- package/src/components/settings/SettingRow.tsx +52 -0
- package/src/contexts/GlobalStatsContext.tsx +396 -0
- package/src/contexts/SessionContext.tsx +2129 -0
- package/src/contexts/ThemeContext.tsx +428 -0
- package/src/contexts/UserSettingsContext.tsx +290 -0
- package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
- package/src/global.d.ts +273 -0
- package/src/hooks/useDocumentQueue.tsx +210 -0
- package/src/hooks/useToast.tsx +55 -0
- package/src/main.tsx +10 -0
- package/src/pages/Analytics.tsx +386 -0
- package/src/pages/CurrentSession.tsx +1174 -0
- package/src/pages/Dashboard.tsx +319 -0
- package/src/pages/Documents.tsx +317 -0
- package/src/pages/Projects.tsx +250 -0
- package/src/pages/Reporting.tsx +386 -0
- package/src/pages/Search.tsx +349 -0
- package/src/pages/Sessions.tsx +285 -0
- package/src/pages/Settings.tsx +2662 -0
- package/src/services/HyperlinkService.ts +1085 -0
- package/src/services/document/DocXMLaterProcessor.ts +617 -0
- package/src/services/document/DocumentProcessingComparison.ts +856 -0
- package/src/services/document/DocumentSnapshotService.ts +575 -0
- package/src/services/document/WordDocumentProcessor.ts +10509 -0
- package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
- package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
- package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
- package/src/services/document/blanklines/BlankLineManager.ts +658 -0
- package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
- package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
- package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
- package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
- package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
- package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
- package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
- package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
- package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
- package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
- package/src/services/document/blanklines/index.ts +67 -0
- package/src/services/document/blanklines/rules/additionRules.ts +337 -0
- package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
- package/src/services/document/blanklines/rules/removalRules.ts +362 -0
- package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
- package/src/services/document/blanklines/types.ts +29 -0
- package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
- package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
- package/src/services/document/helpers/whitespace.ts +117 -0
- package/src/services/document/list/ListNormalizer.ts +947 -0
- package/src/services/document/list/index.ts +45 -0
- package/src/services/document/list/list-detection.ts +275 -0
- package/src/services/document/list/list-types.ts +162 -0
- package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
- package/src/services/document/processors/ListProcessor.ts +257 -0
- package/src/services/document/processors/StructureProcessor.ts +176 -0
- package/src/services/document/processors/StyleProcessor.ts +389 -0
- package/src/services/document/processors/TableProcessor.ts +2238 -0
- package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
- package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
- package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
- package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
- package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
- package/src/services/document/processors/index.ts +28 -0
- package/src/services/document/types/docx-processing.ts +310 -0
- package/src/services/editor/EditorActionHandlers.ts +901 -0
- package/src/services/editor/index.ts +13 -0
- package/src/setupTests.ts +47 -0
- package/src/styles/global.css +782 -0
- package/src/types/backup.ts +132 -0
- package/src/types/dictionary.ts +125 -0
- package/src/types/document-processing.ts +331 -0
- package/src/types/docxmlater-augments.d.ts +142 -0
- package/src/types/editor.ts +280 -0
- package/src/types/electron.ts +340 -0
- package/src/types/globalStats.ts +155 -0
- package/src/types/hyperlink.ts +471 -0
- package/src/types/operations.ts +354 -0
- package/src/types/session.ts +427 -0
- package/src/types/settings.ts +112 -0
- package/src/utils/MemoryMonitor.ts +248 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/colorConvert.ts +306 -0
- package/src/utils/diffUtils.ts +347 -0
- package/src/utils/documentUtils.ts +202 -0
- package/src/utils/electronGuard.ts +62 -0
- package/src/utils/indexedDB.ts +915 -0
- package/src/utils/logger.ts +717 -0
- package/src/utils/pathSecurity.ts +232 -0
- package/src/utils/pathValidator.ts +236 -0
- package/src/utils/processingTimeEstimator.ts +153 -0
- package/src/utils/safeJsonParse.ts +62 -0
- package/src/utils/textSanitizer.ts +162 -0
- package/src/utils/urlHelpers.ts +304 -0
- package/src/utils/urlPatterns.ts +198 -0
- package/src/utils/urlSanitizer.ts +152 -0
- package/src/vite-env.d.ts +11 -0
- package/tsconfig.electron.json +19 -0
- package/tsconfig.json +36 -0
- package/tsconfig.node.json +12 -0
- package/typedoc.json +45 -0
- package/vite.config.ts +152 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
createContext,
|
|
3
|
+
useContext,
|
|
4
|
+
useState,
|
|
5
|
+
useEffect,
|
|
6
|
+
ReactNode,
|
|
7
|
+
useCallback,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef,
|
|
10
|
+
} from 'react';
|
|
11
|
+
import {
|
|
12
|
+
GlobalStats,
|
|
13
|
+
GlobalStatsContextType,
|
|
14
|
+
StatsUpdate,
|
|
15
|
+
DailyStats,
|
|
16
|
+
WeeklyStats,
|
|
17
|
+
MonthlyStats,
|
|
18
|
+
createDefaultGlobalStats,
|
|
19
|
+
createEmptyDailyStats,
|
|
20
|
+
createEmptyWeeklyStats,
|
|
21
|
+
createEmptyMonthlyStats,
|
|
22
|
+
} from '@/types/globalStats';
|
|
23
|
+
import { loadGlobalStats, saveGlobalStats, resetGlobalStats } from '@/utils/indexedDB';
|
|
24
|
+
import { logger } from '@/utils/logger';
|
|
25
|
+
|
|
26
|
+
const GlobalStatsContext = createContext<GlobalStatsContextType | undefined>(undefined);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Formats a date to YYYY-MM-DD string in local timezone.
|
|
30
|
+
* Using local timezone prevents off-by-one errors that occur when using
|
|
31
|
+
* toISOString() which uses UTC (e.g., 11pm local could be next day in UTC).
|
|
32
|
+
*/
|
|
33
|
+
const formatLocalDate = (date: Date): string => {
|
|
34
|
+
const year = date.getFullYear();
|
|
35
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
36
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
37
|
+
return `${year}-${month}-${day}`;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Formats a date to YYYY-MM string in local timezone.
|
|
42
|
+
*/
|
|
43
|
+
const formatLocalMonth = (date: Date): string => {
|
|
44
|
+
const year = date.getFullYear();
|
|
45
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
46
|
+
return `${year}-${month}`;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export function GlobalStatsProvider({ children }: { children: ReactNode }) {
|
|
50
|
+
const log = logger.namespace('GlobalStats');
|
|
51
|
+
const [stats, setStats] = useState<GlobalStats>(createDefaultGlobalStats());
|
|
52
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
53
|
+
|
|
54
|
+
// RACE CONDITION FIX: Debounce save operations to prevent concurrent writes
|
|
55
|
+
// When updateStats is called rapidly, we only want to save once after updates settle
|
|
56
|
+
const saveTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
57
|
+
const latestStatsRef = useRef<GlobalStats>(stats);
|
|
58
|
+
|
|
59
|
+
// Keep latestStatsRef updated for debounced saves
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
latestStatsRef.current = stats;
|
|
62
|
+
}, [stats]);
|
|
63
|
+
|
|
64
|
+
// Initialize GlobalStats - Load from IndexedDB using connection pool
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
let isMounted = true;
|
|
67
|
+
|
|
68
|
+
const initStats = async () => {
|
|
69
|
+
try {
|
|
70
|
+
// Load existing stats using connection pool
|
|
71
|
+
const existingStats = await loadGlobalStats();
|
|
72
|
+
|
|
73
|
+
// Only update state if component is still mounted
|
|
74
|
+
if (!isMounted) return;
|
|
75
|
+
|
|
76
|
+
if (existingStats) {
|
|
77
|
+
// Check if we need to roll over to new day/week/month
|
|
78
|
+
const updatedStats = checkAndRollOverPeriods(existingStats);
|
|
79
|
+
setStats(updatedStats);
|
|
80
|
+
|
|
81
|
+
// Save rolled-over stats if changed
|
|
82
|
+
if (updatedStats !== existingStats) {
|
|
83
|
+
await saveGlobalStats(updatedStats);
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
// No existing stats, save defaults
|
|
87
|
+
const defaultStats = createDefaultGlobalStats();
|
|
88
|
+
await saveGlobalStats(defaultStats);
|
|
89
|
+
setStats(defaultStats);
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (isMounted) {
|
|
93
|
+
log.error('Failed to initialize GlobalStats:', error);
|
|
94
|
+
}
|
|
95
|
+
} finally {
|
|
96
|
+
if (isMounted) {
|
|
97
|
+
setIsLoading(false);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
initStats();
|
|
103
|
+
|
|
104
|
+
// Cleanup: mark component as unmounted (connection pool handles db cleanup)
|
|
105
|
+
return () => {
|
|
106
|
+
isMounted = false;
|
|
107
|
+
// Clear any pending debounced save to prevent memory leaks
|
|
108
|
+
if (saveTimerRef.current) {
|
|
109
|
+
clearTimeout(saveTimerRef.current);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}, []); // Empty deps = runs once on mount, cleanup on unmount
|
|
113
|
+
|
|
114
|
+
// Check if we need to roll over to new day/week/month
|
|
115
|
+
const checkAndRollOverPeriods = (currentStats: GlobalStats): GlobalStats => {
|
|
116
|
+
const now = new Date();
|
|
117
|
+
// Use local timezone to prevent off-by-one errors
|
|
118
|
+
const today = formatLocalDate(now);
|
|
119
|
+
const currentMonth = formatLocalMonth(now);
|
|
120
|
+
|
|
121
|
+
let updated = { ...currentStats };
|
|
122
|
+
|
|
123
|
+
// Check if new day
|
|
124
|
+
if (updated.today.date !== today) {
|
|
125
|
+
// Archive yesterday's stats
|
|
126
|
+
updated.dailyHistory = [updated.today, ...updated.dailyHistory].slice(0, 30);
|
|
127
|
+
|
|
128
|
+
// Create new today
|
|
129
|
+
updated.today = createEmptyDailyStats(today);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check if new week (use local timezone for consistency)
|
|
133
|
+
const monday = formatLocalDate(getMonday(now));
|
|
134
|
+
if (updated.currentWeek.weekStart !== monday) {
|
|
135
|
+
// Archive last week's stats
|
|
136
|
+
updated.weeklyHistory = [updated.currentWeek, ...updated.weeklyHistory].slice(0, 12);
|
|
137
|
+
|
|
138
|
+
// Create new week
|
|
139
|
+
const sunday = formatLocalDate(getSunday(now));
|
|
140
|
+
updated.currentWeek = createEmptyWeeklyStats(monday, sunday);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check if new month
|
|
144
|
+
if (updated.currentMonth.month !== currentMonth) {
|
|
145
|
+
// Archive last month's stats
|
|
146
|
+
updated.monthlyHistory = [updated.currentMonth, ...updated.monthlyHistory].slice(0, 12);
|
|
147
|
+
|
|
148
|
+
// Create new month
|
|
149
|
+
updated.currentMonth = createEmptyMonthlyStats(currentMonth);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return updated;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Update stats
|
|
156
|
+
const updateStats = useCallback(
|
|
157
|
+
async (update: StatsUpdate) => {
|
|
158
|
+
setStats((prevStats) => {
|
|
159
|
+
const updatedStats = { ...prevStats };
|
|
160
|
+
|
|
161
|
+
// Update all-time totals
|
|
162
|
+
if (update.documentsProcessed) {
|
|
163
|
+
updatedStats.allTime.documentsProcessed += update.documentsProcessed;
|
|
164
|
+
}
|
|
165
|
+
if (update.hyperlinksChecked) {
|
|
166
|
+
updatedStats.allTime.hyperlinksChecked += update.hyperlinksChecked;
|
|
167
|
+
}
|
|
168
|
+
if (update.feedbackImported) {
|
|
169
|
+
updatedStats.allTime.feedbackImported += update.feedbackImported;
|
|
170
|
+
}
|
|
171
|
+
if (update.timeSaved) {
|
|
172
|
+
updatedStats.allTime.timeSaved += update.timeSaved;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Set first/last activity dates
|
|
176
|
+
const now = new Date().toISOString();
|
|
177
|
+
if (!updatedStats.allTime.firstActivityDate) {
|
|
178
|
+
updatedStats.allTime.firstActivityDate = now;
|
|
179
|
+
}
|
|
180
|
+
updatedStats.allTime.lastActivityDate = now;
|
|
181
|
+
|
|
182
|
+
// Update today's stats
|
|
183
|
+
if (update.documentsProcessed) {
|
|
184
|
+
updatedStats.today.documentsProcessed += update.documentsProcessed;
|
|
185
|
+
}
|
|
186
|
+
if (update.hyperlinksChecked) {
|
|
187
|
+
updatedStats.today.hyperlinksChecked += update.hyperlinksChecked;
|
|
188
|
+
}
|
|
189
|
+
if (update.feedbackImported) {
|
|
190
|
+
updatedStats.today.feedbackImported += update.feedbackImported;
|
|
191
|
+
}
|
|
192
|
+
if (update.timeSaved) {
|
|
193
|
+
updatedStats.today.timeSaved += update.timeSaved;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Update current week stats
|
|
197
|
+
if (update.documentsProcessed) {
|
|
198
|
+
updatedStats.currentWeek.documentsProcessed += update.documentsProcessed;
|
|
199
|
+
}
|
|
200
|
+
if (update.hyperlinksChecked) {
|
|
201
|
+
updatedStats.currentWeek.hyperlinksChecked += update.hyperlinksChecked;
|
|
202
|
+
}
|
|
203
|
+
if (update.feedbackImported) {
|
|
204
|
+
updatedStats.currentWeek.feedbackImported += update.feedbackImported;
|
|
205
|
+
}
|
|
206
|
+
if (update.timeSaved) {
|
|
207
|
+
updatedStats.currentWeek.timeSaved += update.timeSaved;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Update current month stats
|
|
211
|
+
if (update.documentsProcessed) {
|
|
212
|
+
updatedStats.currentMonth.documentsProcessed += update.documentsProcessed;
|
|
213
|
+
}
|
|
214
|
+
if (update.hyperlinksChecked) {
|
|
215
|
+
updatedStats.currentMonth.hyperlinksChecked += update.hyperlinksChecked;
|
|
216
|
+
}
|
|
217
|
+
if (update.feedbackImported) {
|
|
218
|
+
updatedStats.currentMonth.feedbackImported += update.feedbackImported;
|
|
219
|
+
}
|
|
220
|
+
if (update.timeSaved) {
|
|
221
|
+
updatedStats.currentMonth.timeSaved += update.timeSaved;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
updatedStats.lastUpdated = now;
|
|
225
|
+
|
|
226
|
+
// DEBOUNCED SAVE: Clear any pending save and schedule a new one
|
|
227
|
+
// This prevents race conditions when updateStats is called rapidly
|
|
228
|
+
if (saveTimerRef.current) {
|
|
229
|
+
clearTimeout(saveTimerRef.current);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
saveTimerRef.current = setTimeout(() => {
|
|
233
|
+
// Use latestStatsRef to ensure we save the most recent state
|
|
234
|
+
saveGlobalStats(latestStatsRef.current).catch((error: Error) =>
|
|
235
|
+
log.error('Failed to save stats:', error)
|
|
236
|
+
);
|
|
237
|
+
}, 1000); // 1 second debounce
|
|
238
|
+
|
|
239
|
+
return updatedStats;
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
[log]
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// Get methods
|
|
246
|
+
const getTodayStats = useCallback((): DailyStats => stats.today, [stats]);
|
|
247
|
+
const getWeekStats = useCallback((): WeeklyStats => stats.currentWeek, [stats]);
|
|
248
|
+
const getMonthStats = useCallback((): MonthlyStats => stats.currentMonth, [stats]);
|
|
249
|
+
|
|
250
|
+
const getDailyHistory = useCallback(
|
|
251
|
+
(days: number = 30): DailyStats[] => {
|
|
252
|
+
return [stats.today, ...stats.dailyHistory].slice(0, days);
|
|
253
|
+
},
|
|
254
|
+
[stats]
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const getWeeklyHistory = useCallback(
|
|
258
|
+
(weeks: number = 12): WeeklyStats[] => {
|
|
259
|
+
return [stats.currentWeek, ...stats.weeklyHistory].slice(0, weeks);
|
|
260
|
+
},
|
|
261
|
+
[stats]
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const getMonthlyHistory = useCallback(
|
|
265
|
+
(months: number = 12): MonthlyStats[] => {
|
|
266
|
+
return [stats.currentMonth, ...stats.monthlyHistory].slice(0, months);
|
|
267
|
+
},
|
|
268
|
+
[stats]
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
// Comparison methods
|
|
272
|
+
const getTodayChange = useCallback((): StatsUpdate => {
|
|
273
|
+
const yesterday = stats.dailyHistory[0];
|
|
274
|
+
if (!yesterday) {
|
|
275
|
+
return {
|
|
276
|
+
documentsProcessed: stats.today.documentsProcessed,
|
|
277
|
+
hyperlinksChecked: stats.today.hyperlinksChecked,
|
|
278
|
+
feedbackImported: stats.today.feedbackImported,
|
|
279
|
+
timeSaved: stats.today.timeSaved,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
documentsProcessed: stats.today.documentsProcessed - yesterday.documentsProcessed,
|
|
285
|
+
hyperlinksChecked: stats.today.hyperlinksChecked - yesterday.hyperlinksChecked,
|
|
286
|
+
feedbackImported: stats.today.feedbackImported - yesterday.feedbackImported,
|
|
287
|
+
timeSaved: stats.today.timeSaved - yesterday.timeSaved,
|
|
288
|
+
};
|
|
289
|
+
}, [stats]);
|
|
290
|
+
|
|
291
|
+
const getWeekChange = useCallback((): StatsUpdate => {
|
|
292
|
+
const lastWeek = stats.weeklyHistory[0];
|
|
293
|
+
if (!lastWeek) {
|
|
294
|
+
return {
|
|
295
|
+
documentsProcessed: stats.currentWeek.documentsProcessed,
|
|
296
|
+
hyperlinksChecked: stats.currentWeek.hyperlinksChecked,
|
|
297
|
+
feedbackImported: stats.currentWeek.feedbackImported,
|
|
298
|
+
timeSaved: stats.currentWeek.timeSaved,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
documentsProcessed: stats.currentWeek.documentsProcessed - lastWeek.documentsProcessed,
|
|
304
|
+
hyperlinksChecked: stats.currentWeek.hyperlinksChecked - lastWeek.hyperlinksChecked,
|
|
305
|
+
feedbackImported: stats.currentWeek.feedbackImported - lastWeek.feedbackImported,
|
|
306
|
+
timeSaved: stats.currentWeek.timeSaved - lastWeek.timeSaved,
|
|
307
|
+
};
|
|
308
|
+
}, [stats]);
|
|
309
|
+
|
|
310
|
+
const getMonthChange = useCallback((): StatsUpdate => {
|
|
311
|
+
const lastMonth = stats.monthlyHistory[0];
|
|
312
|
+
if (!lastMonth) {
|
|
313
|
+
return {
|
|
314
|
+
documentsProcessed: stats.currentMonth.documentsProcessed,
|
|
315
|
+
hyperlinksChecked: stats.currentMonth.hyperlinksChecked,
|
|
316
|
+
feedbackImported: stats.currentMonth.feedbackImported,
|
|
317
|
+
timeSaved: stats.currentMonth.timeSaved,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
documentsProcessed: stats.currentMonth.documentsProcessed - lastMonth.documentsProcessed,
|
|
323
|
+
hyperlinksChecked: stats.currentMonth.hyperlinksChecked - lastMonth.hyperlinksChecked,
|
|
324
|
+
feedbackImported: stats.currentMonth.feedbackImported - lastMonth.feedbackImported,
|
|
325
|
+
timeSaved: stats.currentMonth.timeSaved - lastMonth.timeSaved,
|
|
326
|
+
};
|
|
327
|
+
}, [stats]);
|
|
328
|
+
|
|
329
|
+
// Reset all stats
|
|
330
|
+
const resetAllStats = useCallback(async () => {
|
|
331
|
+
const freshStats = createDefaultGlobalStats();
|
|
332
|
+
setStats(freshStats);
|
|
333
|
+
await resetGlobalStats(freshStats);
|
|
334
|
+
}, []);
|
|
335
|
+
|
|
336
|
+
// PERFORMANCE FIX: Memoize provider value to prevent unnecessary re-renders
|
|
337
|
+
// This prevents all consumers (Dashboard, Analytics, etc.) from re-rendering
|
|
338
|
+
// on every stats update when the methods haven't changed
|
|
339
|
+
const contextValue = useMemo(
|
|
340
|
+
() => ({
|
|
341
|
+
stats,
|
|
342
|
+
updateStats,
|
|
343
|
+
getTodayStats,
|
|
344
|
+
getWeekStats,
|
|
345
|
+
getMonthStats,
|
|
346
|
+
getDailyHistory,
|
|
347
|
+
getWeeklyHistory,
|
|
348
|
+
getMonthlyHistory,
|
|
349
|
+
getTodayChange,
|
|
350
|
+
getWeekChange,
|
|
351
|
+
getMonthChange,
|
|
352
|
+
resetAllStats,
|
|
353
|
+
isLoading,
|
|
354
|
+
}),
|
|
355
|
+
[
|
|
356
|
+
stats,
|
|
357
|
+
updateStats,
|
|
358
|
+
getTodayStats,
|
|
359
|
+
getWeekStats,
|
|
360
|
+
getMonthStats,
|
|
361
|
+
getDailyHistory,
|
|
362
|
+
getWeeklyHistory,
|
|
363
|
+
getMonthlyHistory,
|
|
364
|
+
getTodayChange,
|
|
365
|
+
getWeekChange,
|
|
366
|
+
getMonthChange,
|
|
367
|
+
resetAllStats,
|
|
368
|
+
isLoading,
|
|
369
|
+
]
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
return <GlobalStatsContext.Provider value={contextValue}>{children}</GlobalStatsContext.Provider>;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function useGlobalStats() {
|
|
376
|
+
const context = useContext(GlobalStatsContext);
|
|
377
|
+
if (context === undefined) {
|
|
378
|
+
throw new Error('useGlobalStats must be used within a GlobalStatsProvider');
|
|
379
|
+
}
|
|
380
|
+
return context;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Helper functions
|
|
384
|
+
function getMonday(date: Date): Date {
|
|
385
|
+
const d = new Date(date);
|
|
386
|
+
const day = d.getDay();
|
|
387
|
+
const diff = d.getDate() - day + (day === 0 ? -6 : 1);
|
|
388
|
+
return new Date(d.setDate(diff));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function getSunday(date: Date): Date {
|
|
392
|
+
const monday = getMonday(date);
|
|
393
|
+
const sunday = new Date(monday);
|
|
394
|
+
sunday.setDate(monday.getDate() + 6);
|
|
395
|
+
return sunday;
|
|
396
|
+
}
|