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,153 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as Dialog from '@radix-ui/react-dialog';
|
|
3
|
+
import { Button } from './Button';
|
|
4
|
+
import { AlertCircle, X } from 'lucide-react';
|
|
5
|
+
import { cn } from '@/utils/cn';
|
|
6
|
+
|
|
7
|
+
export interface ErrorDetailsDialogProps {
|
|
8
|
+
open: boolean;
|
|
9
|
+
onOpenChange: (open: boolean) => void;
|
|
10
|
+
documentName: string;
|
|
11
|
+
errors: string[];
|
|
12
|
+
errorType?: 'file_locked' | 'api_timeout' | 'word_compatibility' | 'general';
|
|
13
|
+
processedAt?: Date;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ErrorDetailsDialog({
|
|
17
|
+
open,
|
|
18
|
+
onOpenChange,
|
|
19
|
+
documentName,
|
|
20
|
+
errors,
|
|
21
|
+
errorType,
|
|
22
|
+
processedAt,
|
|
23
|
+
}: ErrorDetailsDialogProps) {
|
|
24
|
+
return (
|
|
25
|
+
<Dialog.Root open={open} onOpenChange={onOpenChange}>
|
|
26
|
+
<Dialog.Portal>
|
|
27
|
+
<Dialog.Overlay className="fixed inset-0 bg-black/50 backdrop-blur-xs data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 z-50" />
|
|
28
|
+
<Dialog.Content
|
|
29
|
+
className={cn(
|
|
30
|
+
'fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%]',
|
|
31
|
+
'w-full max-w-lg rounded-lg border border-border bg-card p-6 shadow-lg',
|
|
32
|
+
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
33
|
+
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
34
|
+
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
35
|
+
'data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]',
|
|
36
|
+
'data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]'
|
|
37
|
+
)}
|
|
38
|
+
>
|
|
39
|
+
{/* Header */}
|
|
40
|
+
<div className="flex items-start justify-between mb-4">
|
|
41
|
+
<div className="flex items-center gap-2">
|
|
42
|
+
<AlertCircle className="w-5 h-5 text-red-500" />
|
|
43
|
+
<Dialog.Title className="text-lg font-semibold text-foreground">
|
|
44
|
+
Processing Error
|
|
45
|
+
</Dialog.Title>
|
|
46
|
+
</div>
|
|
47
|
+
<Dialog.Close className="rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none">
|
|
48
|
+
<X className="h-4 w-4" />
|
|
49
|
+
<span className="sr-only">Close</span>
|
|
50
|
+
</Dialog.Close>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
{/* Document Info */}
|
|
54
|
+
<div className="mb-4">
|
|
55
|
+
<p className="font-medium text-foreground">{documentName}</p>
|
|
56
|
+
{processedAt && (
|
|
57
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
58
|
+
{new Date(processedAt).toLocaleString()}
|
|
59
|
+
</p>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
{/* Error Type Badge */}
|
|
64
|
+
<div className="mb-4">
|
|
65
|
+
{errorType === 'file_locked' ? (
|
|
66
|
+
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400">
|
|
67
|
+
File Locked
|
|
68
|
+
</span>
|
|
69
|
+
) : errorType === 'api_timeout' ? (
|
|
70
|
+
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400">
|
|
71
|
+
API Timeout
|
|
72
|
+
</span>
|
|
73
|
+
) : errorType === 'word_compatibility' ? (
|
|
74
|
+
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400">
|
|
75
|
+
Compatibility Mode
|
|
76
|
+
</span>
|
|
77
|
+
) : (
|
|
78
|
+
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400">
|
|
79
|
+
Error
|
|
80
|
+
</span>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
{/* Error Messages */}
|
|
85
|
+
<div className="mb-4">
|
|
86
|
+
<p className="text-sm font-medium text-foreground mb-2">Error Details:</p>
|
|
87
|
+
<div className="max-h-48 overflow-y-auto rounded-md bg-muted/50 p-3 space-y-2">
|
|
88
|
+
{errors.length > 0 ? (
|
|
89
|
+
errors.map((error, index) => (
|
|
90
|
+
<p key={index} className="text-sm text-red-600 dark:text-red-400">
|
|
91
|
+
{error}
|
|
92
|
+
</p>
|
|
93
|
+
))
|
|
94
|
+
) : (
|
|
95
|
+
<p className="text-sm text-muted-foreground">No error details available.</p>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
{/* Helpful Tip for File Locked */}
|
|
101
|
+
{errorType === 'file_locked' && (
|
|
102
|
+
<div className="mb-4 p-3 rounded-md bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800">
|
|
103
|
+
<p className="text-sm text-yellow-800 dark:text-yellow-200">
|
|
104
|
+
<strong>Tip:</strong> Please close the document in Microsoft Word and try processing again.
|
|
105
|
+
</p>
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
|
|
109
|
+
{/* Helpful Tip for API Timeout */}
|
|
110
|
+
{errorType === 'api_timeout' && (
|
|
111
|
+
<div className="mb-4 p-3 rounded-md bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800">
|
|
112
|
+
<p className="text-sm text-orange-800 dark:text-orange-200">
|
|
113
|
+
<strong>Tip:</strong> The Power Automate service timed out. Please wait a moment and try again.
|
|
114
|
+
</p>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
{/* Helpful Tip for Word Compatibility Mode */}
|
|
119
|
+
{errorType === 'word_compatibility' && (
|
|
120
|
+
<div className="mb-4 p-3 rounded-md bg-purple-50 dark:bg-purple-900/20 border border-purple-200 dark:border-purple-800">
|
|
121
|
+
<p className="text-sm text-purple-800 dark:text-purple-200 mb-2">
|
|
122
|
+
<strong>How to Convert Your Document:</strong>
|
|
123
|
+
</p>
|
|
124
|
+
<p className="text-sm text-purple-700 dark:text-purple-300 mb-2">
|
|
125
|
+
It appears the Word document you are trying to process was using outdated functions
|
|
126
|
+
from an old Word version. Please convert the file by following these steps:
|
|
127
|
+
</p>
|
|
128
|
+
<ol className="text-sm text-purple-700 dark:text-purple-300 list-decimal list-inside space-y-1">
|
|
129
|
+
<li>Open the document in Microsoft Word</li>
|
|
130
|
+
<li>Click on the word <strong>"Compatibility"</strong> in the Title Bar</li>
|
|
131
|
+
<li>Click <strong>"Convert"</strong></li>
|
|
132
|
+
<li>Save and close the document</li>
|
|
133
|
+
<li>Return here and click <strong>Retry</strong></li>
|
|
134
|
+
</ol>
|
|
135
|
+
<p className="text-xs text-purple-600 dark:text-purple-400 mt-3 italic">
|
|
136
|
+
Be cautious: Even Word's own conversion methods aren't always accurate, so some
|
|
137
|
+
odd formatting may occur as a result. Thoroughly compare the original to the
|
|
138
|
+
processed file to ensure everything aligns with our Documentation Standards.
|
|
139
|
+
</p>
|
|
140
|
+
</div>
|
|
141
|
+
)}
|
|
142
|
+
|
|
143
|
+
{/* Actions */}
|
|
144
|
+
<div className="flex justify-end">
|
|
145
|
+
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
146
|
+
Close
|
|
147
|
+
</Button>
|
|
148
|
+
</div>
|
|
149
|
+
</Dialog.Content>
|
|
150
|
+
</Dialog.Portal>
|
|
151
|
+
</Dialog.Root>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Fallback UI Component
|
|
3
|
+
*
|
|
4
|
+
* Displays when an error is caught by the ErrorBoundary.
|
|
5
|
+
* Provides error details and recovery options.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { ErrorInfo } from 'react';
|
|
9
|
+
import { AlertTriangle, RefreshCw, Home, Bug } from 'lucide-react';
|
|
10
|
+
import { Button } from './Button';
|
|
11
|
+
import logger from '@/utils/logger';
|
|
12
|
+
|
|
13
|
+
interface ErrorFallbackProps {
|
|
14
|
+
error: Error | null;
|
|
15
|
+
errorInfo: ErrorInfo | null;
|
|
16
|
+
onReset?: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function ErrorFallback({ error, errorInfo, onReset }: ErrorFallbackProps) {
|
|
20
|
+
const handleReload = () => {
|
|
21
|
+
window.location.reload();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const handleGoHome = () => {
|
|
25
|
+
// HASH ROUTER FIX: Use hash-based navigation for Electron app
|
|
26
|
+
// window.location.href = '/' doesn't work with createHashRouter
|
|
27
|
+
window.location.hash = '#/';
|
|
28
|
+
window.location.reload();
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleReportBug = () => {
|
|
32
|
+
// In production, this could open a bug report dialog or navigate to a support page
|
|
33
|
+
const errorDetails = {
|
|
34
|
+
error: error?.toString(),
|
|
35
|
+
stack: error?.stack,
|
|
36
|
+
componentStack: errorInfo?.componentStack,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
logger.debug('Bug Report Details:', errorDetails);
|
|
40
|
+
// You could integrate with your bug tracking system here
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="flex min-h-screen items-center justify-center bg-background p-4">
|
|
45
|
+
<div className="w-full max-w-2xl rounded-lg border border-destructive/20 bg-card p-8 shadow-lg">
|
|
46
|
+
{/* Header */}
|
|
47
|
+
<div className="mb-6 flex items-start gap-4">
|
|
48
|
+
<div className="rounded-full bg-destructive/10 p-3">
|
|
49
|
+
<AlertTriangle className="h-8 w-8 text-destructive" />
|
|
50
|
+
</div>
|
|
51
|
+
<div className="flex-1">
|
|
52
|
+
<h1 className="text-2xl font-bold text-foreground">Oops! Something went wrong</h1>
|
|
53
|
+
<p className="mt-2 text-muted-foreground">
|
|
54
|
+
The application encountered an unexpected error. Don't worry, your data is safe.
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{/* Error Details (collapsible) */}
|
|
60
|
+
{error && (
|
|
61
|
+
<details className="mb-6 rounded-md border border-border bg-muted/30 p-4">
|
|
62
|
+
<summary className="cursor-pointer font-medium text-foreground">Error Details</summary>
|
|
63
|
+
<div className="mt-3 space-y-2">
|
|
64
|
+
<div className="rounded bg-destructive/5 p-3">
|
|
65
|
+
<p className="text-sm font-mono text-destructive">{error.toString()}</p>
|
|
66
|
+
</div>
|
|
67
|
+
{error.stack && (
|
|
68
|
+
<div className="max-h-48 overflow-y-auto rounded bg-muted p-3">
|
|
69
|
+
<pre className="text-xs text-muted-foreground">{error.stack}</pre>
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
72
|
+
{errorInfo?.componentStack && (
|
|
73
|
+
<div className="max-h-48 overflow-y-auto rounded bg-muted p-3">
|
|
74
|
+
<p className="mb-2 text-xs font-semibold text-foreground">Component Stack:</p>
|
|
75
|
+
<pre className="text-xs text-muted-foreground">{errorInfo.componentStack}</pre>
|
|
76
|
+
</div>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
</details>
|
|
80
|
+
)}
|
|
81
|
+
|
|
82
|
+
{/* Recovery Actions */}
|
|
83
|
+
<div className="space-y-3">
|
|
84
|
+
<p className="text-sm text-muted-foreground">What would you like to do?</p>
|
|
85
|
+
|
|
86
|
+
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
87
|
+
{onReset && (
|
|
88
|
+
<Button onClick={onReset} variant="outline" className="w-full">
|
|
89
|
+
<RefreshCw className="mr-2 h-4 w-4" />
|
|
90
|
+
Try Again
|
|
91
|
+
</Button>
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
<Button onClick={handleReload} variant="default" className="w-full">
|
|
95
|
+
<RefreshCw className="mr-2 h-4 w-4" />
|
|
96
|
+
Reload Application
|
|
97
|
+
</Button>
|
|
98
|
+
|
|
99
|
+
<Button onClick={handleGoHome} variant="outline" className="w-full">
|
|
100
|
+
<Home className="mr-2 h-4 w-4" />
|
|
101
|
+
Go to Dashboard
|
|
102
|
+
</Button>
|
|
103
|
+
|
|
104
|
+
<Button onClick={handleReportBug} variant="outline" className="w-full">
|
|
105
|
+
<Bug className="mr-2 h-4 w-4" />
|
|
106
|
+
Report Issue
|
|
107
|
+
</Button>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{/* Additional Help */}
|
|
112
|
+
<div className="mt-6 rounded-md bg-muted/50 p-4">
|
|
113
|
+
<p className="text-sm text-muted-foreground">
|
|
114
|
+
<strong>Tip:</strong> If this error persists, try restarting the application or clearing
|
|
115
|
+
your browser cache. Your session data is automatically saved.
|
|
116
|
+
</p>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Context-specific Error Fallback
|
|
125
|
+
*
|
|
126
|
+
* A simplified error fallback for context provider errors.
|
|
127
|
+
* Shows which context failed and provides recovery options.
|
|
128
|
+
*/
|
|
129
|
+
interface ContextErrorFallbackProps {
|
|
130
|
+
context: 'theme' | 'settings' | 'stats' | 'session';
|
|
131
|
+
error?: Error | null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const contextInfo: Record<string, { name: string; description: string }> = {
|
|
135
|
+
theme: {
|
|
136
|
+
name: 'Theme System',
|
|
137
|
+
description: 'Unable to load theme settings. The application will use default styling.',
|
|
138
|
+
},
|
|
139
|
+
settings: {
|
|
140
|
+
name: 'User Settings',
|
|
141
|
+
description: 'Unable to load your preferences. Default settings will be used.',
|
|
142
|
+
},
|
|
143
|
+
stats: {
|
|
144
|
+
name: 'Statistics',
|
|
145
|
+
description: 'Unable to load statistics data. Your session data is still safe.',
|
|
146
|
+
},
|
|
147
|
+
session: {
|
|
148
|
+
name: 'Session Manager',
|
|
149
|
+
description: 'Unable to load session data. Please try reloading the application.',
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export function ContextErrorFallback({ context, error }: ContextErrorFallbackProps) {
|
|
154
|
+
const info = contextInfo[context] || { name: 'Unknown', description: 'An error occurred.' };
|
|
155
|
+
|
|
156
|
+
const handleReload = () => {
|
|
157
|
+
window.location.reload();
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const handleClearAndReload = () => {
|
|
161
|
+
// Clear potentially corrupted data for this context
|
|
162
|
+
if (context === 'theme') {
|
|
163
|
+
localStorage.removeItem('theme');
|
|
164
|
+
localStorage.removeItem('density');
|
|
165
|
+
localStorage.removeItem('accentColor');
|
|
166
|
+
} else if (context === 'settings') {
|
|
167
|
+
localStorage.removeItem('userSettings');
|
|
168
|
+
} else if (context === 'stats') {
|
|
169
|
+
// Stats are in IndexedDB, just reload
|
|
170
|
+
} else if (context === 'session') {
|
|
171
|
+
localStorage.removeItem('sessions-emergency-backup');
|
|
172
|
+
}
|
|
173
|
+
window.location.reload();
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<div className="flex min-h-screen items-center justify-center bg-gray-100 dark:bg-gray-900 p-4">
|
|
178
|
+
<div className="w-full max-w-md rounded-lg border border-red-200 dark:border-red-800 bg-white dark:bg-gray-800 p-6 shadow-lg">
|
|
179
|
+
<div className="flex items-center gap-3 mb-4">
|
|
180
|
+
<AlertTriangle className="h-6 w-6 text-red-500" />
|
|
181
|
+
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
|
|
182
|
+
{info.name} Error
|
|
183
|
+
</h2>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<p className="text-sm text-gray-600 dark:text-gray-300 mb-4">{info.description}</p>
|
|
187
|
+
|
|
188
|
+
{error && (
|
|
189
|
+
<details className="mb-4 rounded border border-gray-200 dark:border-gray-700 p-2">
|
|
190
|
+
<summary className="cursor-pointer text-sm text-gray-500 dark:text-gray-400">
|
|
191
|
+
Technical Details
|
|
192
|
+
</summary>
|
|
193
|
+
<pre className="mt-2 text-xs text-red-600 dark:text-red-400 overflow-auto max-h-32">
|
|
194
|
+
{error.message}
|
|
195
|
+
</pre>
|
|
196
|
+
</details>
|
|
197
|
+
)}
|
|
198
|
+
|
|
199
|
+
<div className="flex gap-2">
|
|
200
|
+
<button
|
|
201
|
+
onClick={handleReload}
|
|
202
|
+
className="flex-1 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 text-sm font-medium"
|
|
203
|
+
>
|
|
204
|
+
Reload App
|
|
205
|
+
</button>
|
|
206
|
+
<button
|
|
207
|
+
onClick={handleClearAndReload}
|
|
208
|
+
className="flex-1 px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-200 rounded hover:bg-gray-300 dark:hover:bg-gray-600 text-sm font-medium"
|
|
209
|
+
>
|
|
210
|
+
Reset & Reload
|
|
211
|
+
</button>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export default ErrorFallback;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { forwardRef, useId, useState } from 'react';
|
|
2
|
+
import { cn } from '@/utils/cn';
|
|
3
|
+
import { Eye, EyeOff, Search, X } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
6
|
+
label?: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
helperText?: string;
|
|
9
|
+
leftIcon?: React.ReactNode;
|
|
10
|
+
rightIcon?: React.ReactNode;
|
|
11
|
+
onClear?: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
15
|
+
(
|
|
16
|
+
{ className, type = 'text', label, error, helperText, leftIcon, rightIcon, onClear, id, ...props },
|
|
17
|
+
ref
|
|
18
|
+
) => {
|
|
19
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
20
|
+
const generatedId = useId();
|
|
21
|
+
const inputId = id || generatedId;
|
|
22
|
+
const isPassword = type === 'password';
|
|
23
|
+
const isSearch = type === 'search';
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="w-full">
|
|
27
|
+
{label && (
|
|
28
|
+
<label htmlFor={inputId} className="block text-sm font-medium text-foreground mb-1.5">
|
|
29
|
+
{label}
|
|
30
|
+
{props.required && <span className="text-destructive ml-1">*</span>}
|
|
31
|
+
</label>
|
|
32
|
+
)}
|
|
33
|
+
|
|
34
|
+
<div className="relative">
|
|
35
|
+
{leftIcon && (
|
|
36
|
+
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground">
|
|
37
|
+
{leftIcon}
|
|
38
|
+
</div>
|
|
39
|
+
)}
|
|
40
|
+
|
|
41
|
+
{isSearch && (
|
|
42
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
|
43
|
+
)}
|
|
44
|
+
|
|
45
|
+
<input
|
|
46
|
+
id={inputId}
|
|
47
|
+
type={isPassword && showPassword ? 'text' : type}
|
|
48
|
+
className={cn(
|
|
49
|
+
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm',
|
|
50
|
+
'ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium',
|
|
51
|
+
'placeholder:text-muted-foreground',
|
|
52
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
53
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
54
|
+
'transition-colors duration-200',
|
|
55
|
+
error && 'border-destructive focus-visible:ring-destructive',
|
|
56
|
+
(leftIcon || isSearch) && 'pl-10',
|
|
57
|
+
(rightIcon || isPassword || (onClear && props.value)) && 'pr-10',
|
|
58
|
+
className
|
|
59
|
+
)}
|
|
60
|
+
ref={ref}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
|
|
64
|
+
{isPassword && (
|
|
65
|
+
<button
|
|
66
|
+
type="button"
|
|
67
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
68
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
|
|
69
|
+
tabIndex={-1}
|
|
70
|
+
>
|
|
71
|
+
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
|
72
|
+
</button>
|
|
73
|
+
)}
|
|
74
|
+
|
|
75
|
+
{onClear && props.value && !isPassword && (
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
onClick={onClear}
|
|
79
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
|
|
80
|
+
tabIndex={-1}
|
|
81
|
+
>
|
|
82
|
+
<X className="w-4 h-4" />
|
|
83
|
+
</button>
|
|
84
|
+
)}
|
|
85
|
+
|
|
86
|
+
{rightIcon && !isPassword && !onClear && (
|
|
87
|
+
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground">
|
|
88
|
+
{rightIcon}
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
{error && (
|
|
94
|
+
<p className="mt-1.5 text-sm text-destructive" role="alert">
|
|
95
|
+
{error}
|
|
96
|
+
</p>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
{helperText && !error && (
|
|
100
|
+
<p className="mt-1.5 text-sm text-muted-foreground">{helperText}</p>
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
Input.displayName = 'Input';
|
|
108
|
+
|
|
109
|
+
export { Input };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { cn } from '@/utils/cn';
|
|
2
|
+
|
|
3
|
+
interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
variant?: 'default' | 'circle' | 'text' | 'card';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function Skeleton({ className, variant = 'default', ...props }: SkeletonProps) {
|
|
8
|
+
const variants = {
|
|
9
|
+
default: '',
|
|
10
|
+
circle: 'rounded-full',
|
|
11
|
+
text: 'h-4 rounded',
|
|
12
|
+
card: 'h-32 rounded-lg',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div
|
|
17
|
+
className={cn('skeleton', 'bg-muted animate-pulse', variants[variant], className)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function SkeletonCard() {
|
|
24
|
+
return (
|
|
25
|
+
<div className="rounded-lg border border-border p-6 space-y-4">
|
|
26
|
+
<div className="space-y-2">
|
|
27
|
+
<Skeleton variant="text" className="w-3/4" />
|
|
28
|
+
<Skeleton variant="text" className="w-1/2" />
|
|
29
|
+
</div>
|
|
30
|
+
<Skeleton className="h-20" />
|
|
31
|
+
<div className="flex gap-2">
|
|
32
|
+
<Skeleton className="h-9 w-20" />
|
|
33
|
+
<Skeleton className="h-9 w-20" />
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function SkeletonList({ count = 3 }: { count?: number }) {
|
|
40
|
+
return (
|
|
41
|
+
<div className="space-y-3">
|
|
42
|
+
{Array.from({ length: count }).map((_, i) => (
|
|
43
|
+
<div key={i} className="flex items-center gap-3">
|
|
44
|
+
<Skeleton variant="circle" className="w-10 h-10" />
|
|
45
|
+
<div className="space-y-2 flex-1">
|
|
46
|
+
<Skeleton variant="text" className="w-1/3" />
|
|
47
|
+
<Skeleton variant="text" className="w-2/3" />
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Document row skeleton for document lists
|
|
56
|
+
export function SkeletonDocumentRow() {
|
|
57
|
+
return (
|
|
58
|
+
<div className="flex items-center justify-between p-3 rounded-lg border border-border">
|
|
59
|
+
<div className="flex items-center gap-3">
|
|
60
|
+
<Skeleton className="w-8 h-8 rounded" />
|
|
61
|
+
<div className="space-y-1.5">
|
|
62
|
+
<Skeleton variant="text" className="w-40" />
|
|
63
|
+
<Skeleton variant="text" className="w-24 h-3" />
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
<div className="flex items-center gap-2">
|
|
67
|
+
<Skeleton className="w-16 h-6 rounded-full" />
|
|
68
|
+
<Skeleton className="w-8 h-8 rounded" />
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Session card skeleton
|
|
75
|
+
export function SkeletonSessionCard() {
|
|
76
|
+
return (
|
|
77
|
+
<div className="rounded-xl border border-border p-5 space-y-4">
|
|
78
|
+
<div className="flex items-start justify-between">
|
|
79
|
+
<div className="space-y-2 flex-1">
|
|
80
|
+
<Skeleton variant="text" className="w-2/3 h-5" />
|
|
81
|
+
<Skeleton variant="text" className="w-1/3 h-3" />
|
|
82
|
+
</div>
|
|
83
|
+
<Skeleton className="w-20 h-6 rounded-full" />
|
|
84
|
+
</div>
|
|
85
|
+
<div className="grid grid-cols-3 gap-3">
|
|
86
|
+
<Skeleton className="h-16 rounded-lg" />
|
|
87
|
+
<Skeleton className="h-16 rounded-lg" />
|
|
88
|
+
<Skeleton className="h-16 rounded-lg" />
|
|
89
|
+
</div>
|
|
90
|
+
<div className="flex gap-2">
|
|
91
|
+
<Skeleton className="h-9 flex-1 rounded-lg" />
|
|
92
|
+
<Skeleton className="h-9 w-9 rounded-lg" />
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Chart skeleton for analytics
|
|
99
|
+
export function SkeletonChart({ height = 200 }: { height?: number }) {
|
|
100
|
+
return (
|
|
101
|
+
<div className="rounded-xl border border-border p-5 space-y-4">
|
|
102
|
+
<div className="flex items-center justify-between">
|
|
103
|
+
<Skeleton variant="text" className="w-32 h-5" />
|
|
104
|
+
<Skeleton className="w-24 h-8 rounded-lg" />
|
|
105
|
+
</div>
|
|
106
|
+
<div
|
|
107
|
+
className="relative bg-muted/30 rounded-lg overflow-hidden"
|
|
108
|
+
style={{ height }}
|
|
109
|
+
>
|
|
110
|
+
{/* Fake bar chart skeleton */}
|
|
111
|
+
<div className="absolute bottom-0 left-0 right-0 flex items-end justify-around gap-2 p-4">
|
|
112
|
+
{[60, 80, 45, 90, 70, 55, 85].map((h, i) => (
|
|
113
|
+
<Skeleton
|
|
114
|
+
key={i}
|
|
115
|
+
className="flex-1 rounded-t"
|
|
116
|
+
style={{ height: `${h}%` }}
|
|
117
|
+
/>
|
|
118
|
+
))}
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Settings section skeleton
|
|
126
|
+
export function SkeletonSettingsSection() {
|
|
127
|
+
return (
|
|
128
|
+
<div className="space-y-4">
|
|
129
|
+
<div className="flex items-center gap-3 mb-6">
|
|
130
|
+
<Skeleton className="w-10 h-10 rounded-lg" />
|
|
131
|
+
<div className="space-y-1.5">
|
|
132
|
+
<Skeleton variant="text" className="w-32 h-5" />
|
|
133
|
+
<Skeleton variant="text" className="w-48 h-3" />
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
<div className="space-y-3">
|
|
137
|
+
{Array.from({ length: 4 }).map((_, i) => (
|
|
138
|
+
<div key={i} className="flex items-center justify-between py-3">
|
|
139
|
+
<div className="flex items-center gap-3">
|
|
140
|
+
<Skeleton className="w-8 h-8 rounded-lg" />
|
|
141
|
+
<div className="space-y-1.5">
|
|
142
|
+
<Skeleton variant="text" className="w-28" />
|
|
143
|
+
<Skeleton variant="text" className="w-40 h-3" />
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
<Skeleton className="w-10 h-5 rounded-full" />
|
|
147
|
+
</div>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Table skeleton
|
|
155
|
+
export function SkeletonTable({ rows = 5, columns = 4 }: { rows?: number; columns?: number }) {
|
|
156
|
+
return (
|
|
157
|
+
<div className="rounded-lg border border-border overflow-hidden">
|
|
158
|
+
{/* Header */}
|
|
159
|
+
<div className="bg-muted/50 px-4 py-3 flex gap-4">
|
|
160
|
+
{Array.from({ length: columns }).map((_, i) => (
|
|
161
|
+
<Skeleton key={i} variant="text" className="flex-1 h-4" />
|
|
162
|
+
))}
|
|
163
|
+
</div>
|
|
164
|
+
{/* Rows */}
|
|
165
|
+
<div className="divide-y divide-border">
|
|
166
|
+
{Array.from({ length: rows }).map((_, rowIndex) => (
|
|
167
|
+
<div key={rowIndex} className="px-4 py-3 flex gap-4 items-center">
|
|
168
|
+
{Array.from({ length: columns }).map((_, colIndex) => (
|
|
169
|
+
<Skeleton
|
|
170
|
+
key={colIndex}
|
|
171
|
+
variant="text"
|
|
172
|
+
className={cn(
|
|
173
|
+
'flex-1',
|
|
174
|
+
colIndex === 0 && 'w-1/4',
|
|
175
|
+
colIndex === columns - 1 && 'w-20'
|
|
176
|
+
)}
|
|
177
|
+
/>
|
|
178
|
+
))}
|
|
179
|
+
</div>
|
|
180
|
+
))}
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|