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.
Files changed (271) hide show
  1. package/.eslintrc.json +43 -0
  2. package/.github/workflows/build.yml +64 -0
  3. package/.github/workflows/ci.yml +39 -0
  4. package/.vscode/extensions.json +3 -0
  5. package/Current.md +97 -0
  6. package/DocHub_Image.png +0 -0
  7. package/README.md +666 -0
  8. package/USER_GUIDE.md +1173 -0
  9. package/Updater.md +311 -0
  10. package/build/256x256.png +0 -0
  11. package/build/512x512.png +0 -0
  12. package/build/app-update.yml +4 -0
  13. package/build/create-icon.js +208 -0
  14. package/build/icon.ico +0 -0
  15. package/build/icon.png +0 -0
  16. package/build/icon_1024x1024.png +0 -0
  17. package/dist/assets/Analytics-BpsG9895.js +1 -0
  18. package/dist/assets/Card-IAZin8kp.js +1 -0
  19. package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
  20. package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
  21. package/dist/assets/Documents-CqZ25axS.js +1 -0
  22. package/dist/assets/Input-l89xwXBi.js +1 -0
  23. package/dist/assets/Reporting-DqdHJY_a.js +1 -0
  24. package/dist/assets/Search-XNbu5z_3.js +1 -0
  25. package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
  26. package/dist/assets/Sessions-ClZOPYNc.js +1 -0
  27. package/dist/assets/Settings-DUEHGURa.js +11 -0
  28. package/dist/assets/index-8xUe8ptc.js +24 -0
  29. package/dist/assets/index-RYyJqF7O.css +1 -0
  30. package/dist/assets/path-BkOl0AGO.js +1 -0
  31. package/dist/assets/promises-ID_B9S-h.js +1 -0
  32. package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
  33. package/dist/assets/useToast-yRSO1dkm.js +1 -0
  34. package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
  35. package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
  36. package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
  37. package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
  38. package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
  39. package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
  40. package/dist/electron/main-CXkNtyv-.js +19789 -0
  41. package/dist/electron/main.js +5 -0
  42. package/dist/electron/preload.js +1 -0
  43. package/dist/icon.png +0 -0
  44. package/dist/index.html +27 -0
  45. package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
  46. package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
  47. package/docs/README.md +115 -0
  48. package/docs/TOC_WIRING_GUIDE.md +344 -0
  49. package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
  50. package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
  51. package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
  52. package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
  53. package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
  54. package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
  55. package/docs/analysis/List_Implementation.md +206 -0
  56. package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
  57. package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
  58. package/docs/analysis/RefactorStyles.md +852 -0
  59. package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
  60. package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
  61. package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
  62. package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
  63. package/docs/analysis/github-issues-to-create.md +237 -0
  64. package/docs/api/API_README.md +538 -0
  65. package/docs/api/API_REFERENCE.md +751 -0
  66. package/docs/api/TYPE_DEFINITIONS.md +869 -0
  67. package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
  68. package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
  69. package/docs/docxmlater-readme.md +1341 -0
  70. package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
  71. package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
  72. package/docs/fixes/README.md +37 -0
  73. package/docs/github-issues/issue-1-body.md +125 -0
  74. package/docs/github-issues/issue-10-body.md +850 -0
  75. package/docs/github-issues/issue-2-body.md +200 -0
  76. package/docs/github-issues/issue-3-body.md +270 -0
  77. package/docs/github-issues/issue-4-body.md +169 -0
  78. package/docs/github-issues/issue-5-body.md +173 -0
  79. package/docs/github-issues/issue-6-body.md +158 -0
  80. package/docs/github-issues/issue-7-body.md +171 -0
  81. package/docs/github-issues/issue-8-body.md +407 -0
  82. package/docs/github-issues/issue-9-body.md +515 -0
  83. package/docs/github-issues/issue-tracker.md +274 -0
  84. package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
  85. package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
  86. package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
  87. package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
  88. package/docs/implementation/RefactorStyles.md +588 -0
  89. package/docs/implementation/implement-plan.md +489 -0
  90. package/docs/implementation/missing-helpers-implementation.md +391 -0
  91. package/docs/implementation/refactor-plan.md +520 -0
  92. package/docs/implementation/session-implementation-complete.md +233 -0
  93. package/docs/implementation/session-management-plan.md +250 -0
  94. package/docs/setup-checklist.md +77 -0
  95. package/docs/versions/changelog.md +345 -0
  96. package/electron/customUpdater.ts +656 -0
  97. package/electron/main.ts +2441 -0
  98. package/electron/memoryConfig.ts +187 -0
  99. package/electron/preload.ts +394 -0
  100. package/electron/proxyConfig.ts +340 -0
  101. package/electron/services/BackupService.ts +452 -0
  102. package/electron/services/DictionaryService.ts +402 -0
  103. package/electron/services/LocalDictionaryLookupService.ts +147 -0
  104. package/electron/services/PowerAutomateApiService.ts +231 -0
  105. package/electron/services/SharePointSyncService.ts +474 -0
  106. package/electron/windowsCertStore.ts +427 -0
  107. package/electron/zscalerConfig.ts +381 -0
  108. package/eslint.config.js +92 -0
  109. package/jest.config.js +52 -0
  110. package/package.json +214 -0
  111. package/postcss.config.mjs +6 -0
  112. package/public/icon.png +0 -0
  113. package/publish-release.ps1 +5 -0
  114. package/renovate.json +30 -0
  115. package/src/App.tsx +216 -0
  116. package/src/__mocks__/p-limit.js +12 -0
  117. package/src/__mocks__/styleMock.js +1 -0
  118. package/src/components/common/BugReportButton.tsx +44 -0
  119. package/src/components/common/BugReportDialog.tsx +193 -0
  120. package/src/components/common/Button.tsx +153 -0
  121. package/src/components/common/Card.tsx +86 -0
  122. package/src/components/common/ColorPickerDialog.tsx +177 -0
  123. package/src/components/common/ConfirmDialog.tsx +96 -0
  124. package/src/components/common/DebugConsole.tsx +275 -0
  125. package/src/components/common/EmptyState.tsx +183 -0
  126. package/src/components/common/ErrorBoundary.tsx +98 -0
  127. package/src/components/common/ErrorDetailsDialog.tsx +153 -0
  128. package/src/components/common/ErrorFallback.tsx +218 -0
  129. package/src/components/common/Input.tsx +109 -0
  130. package/src/components/common/Skeleton.tsx +184 -0
  131. package/src/components/common/SplashScreen.tsx +81 -0
  132. package/src/components/common/Toast.tsx +155 -0
  133. package/src/components/common/Tooltip.tsx +79 -0
  134. package/src/components/common/UpdateNotification.tsx +320 -0
  135. package/src/components/comparison/ComparisonWindow.tsx +374 -0
  136. package/src/components/comparison/SideBySideDiff.tsx +486 -0
  137. package/src/components/comparison/index.ts +8 -0
  138. package/src/components/document/DocumentUploader.tsx +288 -0
  139. package/src/components/document/HyperlinkPreview.tsx +430 -0
  140. package/src/components/document/HyperlinkService.md +1484 -0
  141. package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
  142. package/src/components/document/InlineChangesView.tsx +707 -0
  143. package/src/components/document/ProcessingProgress.tsx +303 -0
  144. package/src/components/document/ProcessingResults.tsx +256 -0
  145. package/src/components/document/TrackedChangesDetail.tsx +530 -0
  146. package/src/components/document/TrackedChangesPanel.tsx +546 -0
  147. package/src/components/document/VirtualDocumentList.tsx +240 -0
  148. package/src/components/editor/DocumentEditor.tsx +723 -0
  149. package/src/components/editor/DocumentEditorModal.tsx +640 -0
  150. package/src/components/editor/EditorQuickActions.tsx +502 -0
  151. package/src/components/editor/EditorToolbar.tsx +312 -0
  152. package/src/components/editor/TableEditor.tsx +926 -0
  153. package/src/components/editor/index.ts +18 -0
  154. package/src/components/layout/Header.tsx +190 -0
  155. package/src/components/layout/Sidebar.tsx +313 -0
  156. package/src/components/layout/TitleBar.tsx +190 -0
  157. package/src/components/navigation/CommandPalette.tsx +233 -0
  158. package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
  159. package/src/components/sessions/ChangeItem.tsx +408 -0
  160. package/src/components/sessions/ChangeViewer.tsx +1155 -0
  161. package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
  162. package/src/components/sessions/ProcessingOptions.tsx +297 -0
  163. package/src/components/sessions/ReplacementsTab.tsx +438 -0
  164. package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
  165. package/src/components/sessions/SessionManager.tsx +188 -0
  166. package/src/components/sessions/StylesEditor.tsx +1335 -0
  167. package/src/components/sessions/TabContainer.tsx +151 -0
  168. package/src/components/sessions/VirtualSessionList.tsx +157 -0
  169. package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
  170. package/src/components/settings/CertificateManager.tsx +410 -0
  171. package/src/components/settings/SegmentedControl.tsx +88 -0
  172. package/src/components/settings/SettingRow.tsx +52 -0
  173. package/src/contexts/GlobalStatsContext.tsx +396 -0
  174. package/src/contexts/SessionContext.tsx +2129 -0
  175. package/src/contexts/ThemeContext.tsx +428 -0
  176. package/src/contexts/UserSettingsContext.tsx +290 -0
  177. package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
  178. package/src/global.d.ts +273 -0
  179. package/src/hooks/useDocumentQueue.tsx +210 -0
  180. package/src/hooks/useToast.tsx +55 -0
  181. package/src/main.tsx +10 -0
  182. package/src/pages/Analytics.tsx +386 -0
  183. package/src/pages/CurrentSession.tsx +1174 -0
  184. package/src/pages/Dashboard.tsx +319 -0
  185. package/src/pages/Documents.tsx +317 -0
  186. package/src/pages/Projects.tsx +250 -0
  187. package/src/pages/Reporting.tsx +386 -0
  188. package/src/pages/Search.tsx +349 -0
  189. package/src/pages/Sessions.tsx +285 -0
  190. package/src/pages/Settings.tsx +2662 -0
  191. package/src/services/HyperlinkService.ts +1085 -0
  192. package/src/services/document/DocXMLaterProcessor.ts +617 -0
  193. package/src/services/document/DocumentProcessingComparison.ts +856 -0
  194. package/src/services/document/DocumentSnapshotService.ts +575 -0
  195. package/src/services/document/WordDocumentProcessor.ts +10509 -0
  196. package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
  197. package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
  198. package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
  199. package/src/services/document/blanklines/BlankLineManager.ts +658 -0
  200. package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
  201. package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
  202. package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
  203. package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
  204. package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
  205. package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
  206. package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
  207. package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
  208. package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
  209. package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
  210. package/src/services/document/blanklines/index.ts +67 -0
  211. package/src/services/document/blanklines/rules/additionRules.ts +337 -0
  212. package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
  213. package/src/services/document/blanklines/rules/removalRules.ts +362 -0
  214. package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
  215. package/src/services/document/blanklines/types.ts +29 -0
  216. package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
  217. package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
  218. package/src/services/document/helpers/whitespace.ts +117 -0
  219. package/src/services/document/list/ListNormalizer.ts +947 -0
  220. package/src/services/document/list/index.ts +45 -0
  221. package/src/services/document/list/list-detection.ts +275 -0
  222. package/src/services/document/list/list-types.ts +162 -0
  223. package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
  224. package/src/services/document/processors/ListProcessor.ts +257 -0
  225. package/src/services/document/processors/StructureProcessor.ts +176 -0
  226. package/src/services/document/processors/StyleProcessor.ts +389 -0
  227. package/src/services/document/processors/TableProcessor.ts +2238 -0
  228. package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
  229. package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
  230. package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
  231. package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
  232. package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
  233. package/src/services/document/processors/index.ts +28 -0
  234. package/src/services/document/types/docx-processing.ts +310 -0
  235. package/src/services/editor/EditorActionHandlers.ts +901 -0
  236. package/src/services/editor/index.ts +13 -0
  237. package/src/setupTests.ts +47 -0
  238. package/src/styles/global.css +782 -0
  239. package/src/types/backup.ts +132 -0
  240. package/src/types/dictionary.ts +125 -0
  241. package/src/types/document-processing.ts +331 -0
  242. package/src/types/docxmlater-augments.d.ts +142 -0
  243. package/src/types/editor.ts +280 -0
  244. package/src/types/electron.ts +340 -0
  245. package/src/types/globalStats.ts +155 -0
  246. package/src/types/hyperlink.ts +471 -0
  247. package/src/types/operations.ts +354 -0
  248. package/src/types/session.ts +427 -0
  249. package/src/types/settings.ts +112 -0
  250. package/src/utils/MemoryMonitor.ts +248 -0
  251. package/src/utils/cn.ts +6 -0
  252. package/src/utils/colorConvert.ts +306 -0
  253. package/src/utils/diffUtils.ts +347 -0
  254. package/src/utils/documentUtils.ts +202 -0
  255. package/src/utils/electronGuard.ts +62 -0
  256. package/src/utils/indexedDB.ts +915 -0
  257. package/src/utils/logger.ts +717 -0
  258. package/src/utils/pathSecurity.ts +232 -0
  259. package/src/utils/pathValidator.ts +236 -0
  260. package/src/utils/processingTimeEstimator.ts +153 -0
  261. package/src/utils/safeJsonParse.ts +62 -0
  262. package/src/utils/textSanitizer.ts +162 -0
  263. package/src/utils/urlHelpers.ts +304 -0
  264. package/src/utils/urlPatterns.ts +198 -0
  265. package/src/utils/urlSanitizer.ts +152 -0
  266. package/src/vite-env.d.ts +11 -0
  267. package/tsconfig.electron.json +19 -0
  268. package/tsconfig.json +36 -0
  269. package/tsconfig.node.json +12 -0
  270. package/typedoc.json +45 -0
  271. package/vite.config.ts +152 -0
@@ -0,0 +1,349 @@
1
+ import { useState, useMemo, useEffect, useRef, memo } from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import Fuse from 'fuse.js';
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from '@/components/common/Card';
11
+ import { Input } from '@/components/common/Input';
12
+ import { Button } from '@/components/common/Button';
13
+ import { useSession } from '@/contexts/SessionContext';
14
+ import { cn } from '@/utils/cn';
15
+ import {
16
+ Search as SearchIcon,
17
+ Filter,
18
+ FileText,
19
+ Calendar,
20
+ FolderOpen,
21
+ CheckCircle2,
22
+ Clock,
23
+ XCircle,
24
+ ChevronRight,
25
+ } from 'lucide-react';
26
+ import { useNavigate } from 'react-router-dom';
27
+ import { Document } from '@/types/session';
28
+
29
+ type StatusFilter = 'all' | 'completed' | 'pending' | 'error';
30
+ type DateRange = 'all' | 'today' | 'week' | 'month' | 'custom';
31
+
32
+ const containerVariants = {
33
+ hidden: { opacity: 0 },
34
+ visible: {
35
+ opacity: 1,
36
+ transition: {
37
+ staggerChildren: 0.05,
38
+ },
39
+ },
40
+ };
41
+
42
+ const itemVariants = {
43
+ hidden: { opacity: 0, y: 10 },
44
+ visible: {
45
+ opacity: 1,
46
+ y: 0,
47
+ transition: {
48
+ duration: 0.3,
49
+ },
50
+ },
51
+ };
52
+
53
+ // PERFORMANCE: Wrap in memo to prevent re-renders when parent state changes
54
+ export const Search = memo(function Search() {
55
+ const [searchQuery, setSearchQuery] = useState('');
56
+ const [statusFilter, setStatusFilter] = useState<StatusFilter>('all');
57
+ const [selectedSessionId, setSelectedSessionId] = useState<string>('all');
58
+ const [selectedIndex, setSelectedIndex] = useState(0);
59
+ const [showFilters, setShowFilters] = useState(false);
60
+
61
+ const { sessions } = useSession();
62
+ const navigate = useNavigate();
63
+ const resultsRef = useRef<HTMLDivElement>(null);
64
+
65
+ // Flatten all documents from all sessions
66
+ const allDocuments = useMemo(() => {
67
+ const docs: Array<Document & { sessionId: string; sessionName: string }> = [];
68
+ sessions.forEach((session) => {
69
+ session.documents.forEach((doc) => {
70
+ docs.push({
71
+ ...doc,
72
+ sessionId: session.id,
73
+ sessionName: session.name,
74
+ });
75
+ });
76
+ });
77
+ return docs;
78
+ }, [sessions]);
79
+
80
+ // Configure Fuse.js for fuzzy search
81
+ const fuse = useMemo(() => {
82
+ return new Fuse(allDocuments, {
83
+ keys: [
84
+ { name: 'name', weight: 2 },
85
+ { name: 'sessionName', weight: 1 },
86
+ { name: 'path', weight: 0.5 },
87
+ ],
88
+ threshold: 0.4,
89
+ includeScore: true,
90
+ });
91
+ }, [allDocuments]);
92
+
93
+ // Filter and search documents
94
+ const filteredDocuments = useMemo(() => {
95
+ let results = searchQuery
96
+ ? fuse.search(searchQuery).map((result) => result.item)
97
+ : allDocuments;
98
+
99
+ // Apply status filter
100
+ if (statusFilter !== 'all') {
101
+ results = results.filter((doc) => doc.status === statusFilter);
102
+ }
103
+
104
+ // Apply session filter
105
+ if (selectedSessionId !== 'all') {
106
+ results = results.filter((doc) => doc.sessionId === selectedSessionId);
107
+ }
108
+
109
+ return results;
110
+ }, [searchQuery, statusFilter, selectedSessionId, fuse, allDocuments]);
111
+
112
+ // Keyboard navigation
113
+ useEffect(() => {
114
+ const handleKeyDown = (e: KeyboardEvent) => {
115
+ if (!filteredDocuments.length) return;
116
+
117
+ if (e.key === 'ArrowDown') {
118
+ e.preventDefault();
119
+ setSelectedIndex((prev) => (prev < filteredDocuments.length - 1 ? prev + 1 : prev));
120
+ } else if (e.key === 'ArrowUp') {
121
+ e.preventDefault();
122
+ setSelectedIndex((prev) => (prev > 0 ? prev - 1 : prev));
123
+ } else if (e.key === 'Enter' && filteredDocuments[selectedIndex]) {
124
+ e.preventDefault();
125
+ handleOpenDocument(filteredDocuments[selectedIndex]);
126
+ }
127
+ };
128
+
129
+ window.addEventListener('keydown', handleKeyDown);
130
+ return () => window.removeEventListener('keydown', handleKeyDown);
131
+ }, [filteredDocuments, selectedIndex]);
132
+
133
+ // Reset selected index when results change
134
+ useEffect(() => {
135
+ setSelectedIndex(0);
136
+ }, [filteredDocuments]);
137
+
138
+ // Auto-scroll selected item into view
139
+ useEffect(() => {
140
+ if (resultsRef.current) {
141
+ const selectedElement = resultsRef.current.querySelector(`[data-index="${selectedIndex}"]`);
142
+ if (selectedElement) {
143
+ selectedElement.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
144
+ }
145
+ }
146
+ }, [selectedIndex]);
147
+
148
+ const handleOpenDocument = (doc: Document & { sessionId: string; sessionName: string }) => {
149
+ navigate(`/session/${doc.sessionId}`);
150
+ };
151
+
152
+ const getStatusIcon = (status: string) => {
153
+ switch (status) {
154
+ case 'completed':
155
+ return <CheckCircle2 className="w-4 h-4 text-green-500" />;
156
+ case 'pending':
157
+ return <Clock className="w-4 h-4 text-yellow-500" />;
158
+ case 'error':
159
+ return <XCircle className="w-4 h-4 text-red-500" />;
160
+ default:
161
+ return <FileText className="w-4 h-4 text-muted-foreground" />;
162
+ }
163
+ };
164
+
165
+ const statusOptions = [
166
+ { value: 'all' as StatusFilter, label: 'All Status', count: allDocuments.length },
167
+ {
168
+ value: 'completed' as StatusFilter,
169
+ label: 'Completed',
170
+ count: allDocuments.filter((d) => d.status === 'completed').length,
171
+ },
172
+ {
173
+ value: 'pending' as StatusFilter,
174
+ label: 'Pending',
175
+ count: allDocuments.filter((d) => d.status === 'pending').length,
176
+ },
177
+ {
178
+ value: 'error' as StatusFilter,
179
+ label: 'Error',
180
+ count: allDocuments.filter((d) => d.status === 'error').length,
181
+ },
182
+ ];
183
+
184
+ return (
185
+ <motion.div
186
+ variants={containerVariants}
187
+ initial="hidden"
188
+ animate="visible"
189
+ className="p-6 space-y-6 max-w-[1200px] mx-auto"
190
+ >
191
+ {/* Header */}
192
+ <motion.div variants={itemVariants}>
193
+ <h1 className="text-3xl font-bold mb-2 flex items-center gap-2">
194
+ <SearchIcon className="w-8 h-8" />
195
+ Search Documents
196
+ </h1>
197
+ <p className="text-muted-foreground">
198
+ Find documents across all sessions with advanced filtering
199
+ </p>
200
+ </motion.div>
201
+
202
+ {/* Search Input */}
203
+ <motion.div variants={itemVariants} className="space-y-4">
204
+ <div className="relative">
205
+ <Input
206
+ type="text"
207
+ placeholder="Search by document name, session, or path..."
208
+ value={searchQuery}
209
+ onChange={(e) => setSearchQuery(e.target.value)}
210
+ className="w-full pl-10 pr-12 h-12 text-lg"
211
+ autoFocus
212
+ />
213
+ <SearchIcon className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-muted-foreground" />
214
+ <Button
215
+ variant={showFilters ? 'default' : 'ghost'}
216
+ size="icon"
217
+ className="absolute right-2 top-1/2 -translate-y-1/2"
218
+ onClick={() => setShowFilters(!showFilters)}
219
+ >
220
+ <Filter className="w-4 h-4" />
221
+ </Button>
222
+ </div>
223
+
224
+ {/* Filters */}
225
+ {showFilters && (
226
+ <motion.div
227
+ initial={{ opacity: 0, height: 0 }}
228
+ animate={{ opacity: 1, height: 'auto' }}
229
+ exit={{ opacity: 0, height: 0 }}
230
+ className="grid grid-cols-1 md:grid-cols-2 gap-4 p-4 bg-muted/30 rounded-lg border border-border"
231
+ >
232
+ {/* Status Filter */}
233
+ <div className="space-y-2">
234
+ <label className="text-sm font-medium">Status</label>
235
+ <div className="flex gap-2 flex-wrap">
236
+ {statusOptions.map((option) => (
237
+ <Button
238
+ key={option.value}
239
+ variant={statusFilter === option.value ? 'default' : 'outline'}
240
+ size="sm"
241
+ onClick={() => setStatusFilter(option.value)}
242
+ className="gap-2"
243
+ >
244
+ {option.label}
245
+ <span className="text-xs opacity-70">({option.count})</span>
246
+ </Button>
247
+ ))}
248
+ </div>
249
+ </div>
250
+
251
+ {/* Session Filter */}
252
+ <div className="space-y-2">
253
+ <label className="text-sm font-medium">Session</label>
254
+ <select
255
+ value={selectedSessionId}
256
+ onChange={(e) => setSelectedSessionId(e.target.value)}
257
+ className="w-full h-9 px-3 rounded-md border border-input bg-background"
258
+ >
259
+ <option value="all">All Sessions</option>
260
+ {sessions.map((session) => (
261
+ <option key={session.id} value={session.id}>
262
+ {session.name} ({session.documents.length})
263
+ </option>
264
+ ))}
265
+ </select>
266
+ </div>
267
+ </motion.div>
268
+ )}
269
+ </motion.div>
270
+
271
+ {/* Results Summary */}
272
+ <motion.div variants={itemVariants} className="flex items-center justify-between">
273
+ <p className="text-sm text-foreground">
274
+ {filteredDocuments.length} {filteredDocuments.length === 1 ? 'result' : 'results'} found
275
+ {searchQuery && (
276
+ <span className="ml-1">
277
+ for "<span className="font-semibold text-foreground">{searchQuery}</span>"
278
+ </span>
279
+ )}
280
+ </p>
281
+ <p className="text-xs text-muted-foreground">
282
+ Use <kbd className="px-1.5 py-0.5 bg-muted rounded text-xs">↑</kbd>
283
+ <kbd className="px-1.5 py-0.5 bg-muted rounded text-xs ml-1">↓</kbd> to navigate,
284
+ <kbd className="px-1.5 py-0.5 bg-muted rounded text-xs ml-1">Enter</kbd> to open
285
+ </p>
286
+ </motion.div>
287
+
288
+ {/* Results */}
289
+ <motion.div variants={itemVariants} className="space-y-2" ref={resultsRef}>
290
+ {filteredDocuments.length === 0 ? (
291
+ <Card className="border-dashed">
292
+ <CardContent className="pt-12 pb-12 text-center">
293
+ <SearchIcon className="w-12 h-12 mx-auto mb-4 text-muted-foreground opacity-50" />
294
+ <p className="text-lg font-medium mb-1">No documents found</p>
295
+ <p className="text-sm text-muted-foreground">
296
+ Try adjusting your search query or filters
297
+ </p>
298
+ </CardContent>
299
+ </Card>
300
+ ) : (
301
+ filteredDocuments.map((doc, index) => (
302
+ <motion.div
303
+ key={`${doc.sessionId}-${doc.id}`}
304
+ data-index={index}
305
+ variants={itemVariants}
306
+ onClick={() => handleOpenDocument(doc)}
307
+ onMouseEnter={() => setSelectedIndex(index)}
308
+ className={cn(
309
+ 'p-4 rounded-lg border cursor-pointer transition-all duration-150',
310
+ 'hover:border-primary hover:bg-accent/50',
311
+ selectedIndex === index
312
+ ? 'border-primary bg-accent/50 shadow-xs'
313
+ : 'border-border bg-card'
314
+ )}
315
+ >
316
+ <div className="flex items-start justify-between gap-4">
317
+ <div className="flex-1 min-w-0">
318
+ <div className="flex items-center gap-2 mb-1">
319
+ {getStatusIcon(doc.status)}
320
+ <h3 className="font-medium truncate">{doc.name}</h3>
321
+ </div>
322
+ <div className="flex items-center gap-4 text-sm text-foreground">
323
+ <span className="flex items-center gap-1">
324
+ <FolderOpen className="w-3.5 h-3.5" />
325
+ {doc.sessionName}
326
+ </span>
327
+ {doc.processedAt && (
328
+ <span className="flex items-center gap-1">
329
+ <Calendar className="w-3.5 h-3.5" />
330
+ {new Date(doc.processedAt).toLocaleDateString()}
331
+ </span>
332
+ )}
333
+ </div>
334
+ <p className="text-xs text-muted-foreground mt-1 truncate">{doc.path}</p>
335
+ </div>
336
+ <ChevronRight
337
+ className={cn(
338
+ 'w-5 h-5 text-muted-foreground transition-transform',
339
+ selectedIndex === index && 'text-primary translate-x-1'
340
+ )}
341
+ />
342
+ </div>
343
+ </motion.div>
344
+ ))
345
+ )}
346
+ </motion.div>
347
+ </motion.div>
348
+ );
349
+ });
@@ -0,0 +1,285 @@
1
+ import { Button } from '@/components/common/Button';
2
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/common/Card';
3
+ import { Input } from '@/components/common/Input';
4
+ import { SessionManager } from '@/components/sessions/SessionManager';
5
+ import { useSession } from '@/contexts/SessionContext';
6
+ import { cn } from '@/utils/cn';
7
+ import { motion } from 'framer-motion';
8
+ import { Calendar, Clock, FileText, FolderOpen, Grid, List, Plus, Trash2 } from 'lucide-react';
9
+ import { useState } from 'react';
10
+ import { useNavigate } from 'react-router-dom';
11
+
12
+ const containerVariants = {
13
+ hidden: { opacity: 0 },
14
+ visible: {
15
+ opacity: 1,
16
+ transition: {
17
+ staggerChildren: 0.1,
18
+ },
19
+ },
20
+ };
21
+
22
+ const itemVariants = {
23
+ hidden: { opacity: 0, y: 20 },
24
+ visible: {
25
+ opacity: 1,
26
+ y: 0,
27
+ transition: {
28
+ duration: 0.5,
29
+ },
30
+ },
31
+ };
32
+
33
+ export function Sessions() {
34
+ const navigate = useNavigate();
35
+ const { sessions, loadSession, deleteSession } = useSession();
36
+ const [searchQuery, setSearchQuery] = useState('');
37
+ const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
38
+ const [showSessionManager, setShowSessionManager] = useState(false);
39
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState<string | null>(null);
40
+
41
+ const filteredSessions = sessions.filter((session) =>
42
+ session.name.toLowerCase().includes(searchQuery.toLowerCase())
43
+ );
44
+
45
+ const handleSessionClick = (sessionId: string) => {
46
+ loadSession(sessionId);
47
+ navigate(`/session/${sessionId}`);
48
+ };
49
+
50
+ const handleDeleteSession = (sessionId: string, e: React.MouseEvent) => {
51
+ e.stopPropagation();
52
+ setShowDeleteConfirm(sessionId);
53
+ };
54
+
55
+ const confirmDelete = (sessionId: string) => {
56
+ deleteSession(sessionId);
57
+ setShowDeleteConfirm(null);
58
+ };
59
+
60
+ const formatDate = (date: Date) => {
61
+ return new Intl.DateTimeFormat('en-US', {
62
+ month: 'short',
63
+ day: 'numeric',
64
+ year: 'numeric',
65
+ }).format(date);
66
+ };
67
+
68
+ const handleNewSession = () => {
69
+ setShowSessionManager(true);
70
+ };
71
+
72
+ const handleSessionCreated = (sessionId: string) => {
73
+ navigate(`/session/${sessionId}`);
74
+ };
75
+
76
+ return (
77
+ <motion.div
78
+ className="p-6 space-y-6"
79
+ variants={containerVariants}
80
+ initial="hidden"
81
+ animate="visible"
82
+ >
83
+ <motion.div
84
+ className="flex flex-col sm:flex-row gap-4 justify-between"
85
+ variants={itemVariants}
86
+ >
87
+ <Input
88
+ type="search"
89
+ placeholder="Search sessions..."
90
+ value={searchQuery}
91
+ onChange={(e) => setSearchQuery(e.target.value)}
92
+ onClear={() => setSearchQuery('')}
93
+ className="max-w-md"
94
+ />
95
+
96
+ <div className="flex gap-2">
97
+ <div className="flex rounded-md border border-border">
98
+ <button
99
+ onClick={() => setViewMode('grid')}
100
+ className={cn(
101
+ 'p-2 rounded-l-md transition-colors',
102
+ viewMode === 'grid' ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'
103
+ )}
104
+ aria-label="Grid view"
105
+ >
106
+ <Grid className="w-4 h-4" />
107
+ </button>
108
+ <button
109
+ onClick={() => setViewMode('list')}
110
+ className={cn(
111
+ 'p-2 rounded-r-md transition-colors',
112
+ viewMode === 'list' ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'
113
+ )}
114
+ aria-label="List view"
115
+ >
116
+ <List className="w-4 h-4" />
117
+ </button>
118
+ </div>
119
+ <Button icon={<Plus className="w-4 h-4" />} onClick={handleNewSession}>
120
+ New Session
121
+ </Button>
122
+ </div>
123
+ </motion.div>
124
+
125
+ <motion.div
126
+ className={cn(
127
+ 'grid gap-4',
128
+ viewMode === 'grid' ? 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3' : 'grid-cols-1'
129
+ )}
130
+ variants={containerVariants}
131
+ >
132
+ {filteredSessions.map((session) => (
133
+ <motion.div key={session.id} variants={itemVariants}>
134
+ <Card
135
+ interactive
136
+ variant="bordered"
137
+ className="cursor-pointer relative group"
138
+ onClick={() => handleSessionClick(session.id)}
139
+ >
140
+ <CardHeader>
141
+ <div className="flex items-start justify-between">
142
+ <div className="flex items-center gap-3">
143
+ <div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
144
+ <FolderOpen className="w-5 h-5 text-primary" />
145
+ </div>
146
+ <div>
147
+ <CardTitle className="text-xl font-semibold">{session.name}</CardTitle>
148
+ <span
149
+ className={cn(
150
+ 'text-xs px-2 py-1 rounded-full mt-1 inline-block',
151
+ session.status === 'active'
152
+ ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
153
+ : 'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400'
154
+ )}
155
+ >
156
+ {session.status}
157
+ </span>
158
+ </div>
159
+ </div>
160
+ <div className="opacity-0 group-hover:opacity-100 transition-opacity">
161
+ <button
162
+ onClick={(e) => handleDeleteSession(session.id, e)}
163
+ className="p-1 hover:bg-muted rounded"
164
+ >
165
+ <Trash2 className="w-4 h-4 text-muted-foreground hover:text-destructive" />
166
+ </button>
167
+ </div>
168
+ </div>
169
+ </CardHeader>
170
+ <CardContent>
171
+ <div className="space-y-3">
172
+ <div className="grid grid-cols-2 gap-2 text-base">
173
+ <div className="flex items-center gap-1 text-foreground">
174
+ <FileText className="w-3 h-3" />
175
+ <span>{session.documents.length} documents</span>
176
+ </div>
177
+ <div className="flex items-center gap-1 text-foreground">
178
+ <Clock className="w-3 h-3" />
179
+ <span>{session.stats.timeSaved}m saved</span>
180
+ </div>
181
+ </div>
182
+
183
+ <div className="pt-2 border-t border-border">
184
+ <div className="flex items-center justify-between text-sm text-muted-foreground">
185
+ <div className="flex items-center gap-1">
186
+ <Calendar className="w-3 h-3" />
187
+ <span>Created {formatDate(session.createdAt)}</span>
188
+ </div>
189
+ </div>
190
+ <div className="flex items-center justify-between text-sm text-muted-foreground mt-1">
191
+ <span>Last modified {formatDate(session.lastModified)}</span>
192
+ </div>
193
+ </div>
194
+
195
+ {viewMode === 'list' && (
196
+ <div className="pt-2 grid grid-cols-4 gap-2 text-sm">
197
+ <div>
198
+ <p className="text-muted-foreground">Processed</p>
199
+ <p className="font-medium">{session.stats.documentsProcessed}</p>
200
+ </div>
201
+ <div>
202
+ <p className="text-muted-foreground">Links</p>
203
+ <p className="font-medium">{session.stats.hyperlinksChecked}</p>
204
+ </div>
205
+ <div>
206
+ <p className="text-muted-foreground">Feedback</p>
207
+ <p className="font-medium">{session.stats.feedbackImported}</p>
208
+ </div>
209
+ <div>
210
+ <p className="text-muted-foreground">Time</p>
211
+ <p className="font-medium">{session.stats.timeSaved}m</p>
212
+ </div>
213
+ </div>
214
+ )}
215
+ </div>
216
+ </CardContent>
217
+ </Card>
218
+
219
+ {/* Delete Confirmation Dialog */}
220
+ {showDeleteConfirm === session.id && (
221
+ <motion.div
222
+ initial={{ opacity: 0 }}
223
+ animate={{ opacity: 1 }}
224
+ className="fixed inset-0 bg-background/80 backdrop-blur-xs z-50 flex items-center justify-center p-4"
225
+ onClick={() => setShowDeleteConfirm(null)}
226
+ >
227
+ <motion.div
228
+ initial={{ scale: 0.95 }}
229
+ animate={{ scale: 1 }}
230
+ className="bg-card rounded-lg shadow-xl border border-border p-6 max-w-sm"
231
+ onClick={(e) => e.stopPropagation()}
232
+ >
233
+ <h3 className="text-lg font-semibold mb-2">Delete Session</h3>
234
+ <p className="text-sm text-muted-foreground mb-4">
235
+ Are you sure you want to delete "{session.name}"? This action cannot be undone.
236
+ </p>
237
+ <div className="flex gap-2 justify-end">
238
+ <Button variant="outline" size="sm" onClick={() => setShowDeleteConfirm(null)}>
239
+ Cancel
240
+ </Button>
241
+ <Button
242
+ variant="destructive"
243
+ size="sm"
244
+ onClick={() => confirmDelete(session.id)}
245
+ >
246
+ Delete
247
+ </Button>
248
+ </div>
249
+ </motion.div>
250
+ </motion.div>
251
+ )}
252
+ </motion.div>
253
+ ))}
254
+ </motion.div>
255
+
256
+ {filteredSessions.length === 0 && (
257
+ <motion.div variants={itemVariants} className="text-center py-12">
258
+ <FolderOpen className="w-12 h-12 mx-auto text-muted-foreground mb-4" />
259
+ <h3 className="text-lg font-medium mb-2">
260
+ {searchQuery ? 'No sessions found' : 'No sessions yet'}
261
+ </h3>
262
+ <p className="text-muted-foreground mb-4">
263
+ {searchQuery
264
+ ? 'Try adjusting your search query'
265
+ : 'Create your first session to start processing documents'}
266
+ </p>
267
+ {!searchQuery && (
268
+ <Button onClick={handleNewSession} icon={<Plus className="w-4 h-4" />}>
269
+ Create First Session
270
+ </Button>
271
+ )}
272
+ </motion.div>
273
+ )}
274
+
275
+ {showSessionManager && (
276
+ <SessionManager
277
+ mode="new"
278
+ onClose={() => setShowSessionManager(false)}
279
+ onSessionCreated={handleSessionCreated}
280
+ onSessionLoaded={handleSessionCreated}
281
+ />
282
+ )}
283
+ </motion.div>
284
+ );
285
+ }