@pattern-stack/frontend-patterns 0.0.3 → 0.0.5
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/dist/frontend-patterns.css +1 -1
- package/dist/index.es.js +1918 -3
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +1917 -1
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
- package/src/App.css +42 -0
- package/src/App.tsx +64 -0
- package/src/__tests__/README.md +221 -0
- package/src/__tests__/atoms/hooks/simple-hooks.test.ts +44 -0
- package/src/__tests__/atoms/ui/button.test.tsx +68 -0
- package/src/__tests__/atoms/utils/simple.test.ts +18 -0
- package/src/__tests__/atoms/utils/utils.test.ts +77 -0
- package/src/__tests__/features/auth/simple-auth.test.tsx +40 -0
- package/src/__tests__/molecules/layout/simple-layout.test.tsx +81 -0
- package/src/__tests__/organisms/showcase/simple-showcase.test.tsx +167 -0
- package/src/__tests__/setup.ts +51 -0
- package/src/__tests__/utils.tsx +123 -0
- package/src/atoms/composed/Accordion/Accordion.tsx +271 -0
- package/{dist/atoms/composed/Accordion/index.d.ts → src/atoms/composed/Accordion/index.ts} +1 -2
- package/src/atoms/composed/Alert/Alert.tsx +132 -0
- package/src/atoms/composed/Alert/index.ts +1 -0
- package/src/atoms/composed/Breadcrumb/Breadcrumb.tsx +83 -0
- package/src/atoms/composed/Breadcrumb/index.ts +1 -0
- package/src/atoms/composed/Chart/Chart.tsx +425 -0
- package/{dist/atoms/composed/Chart/index.d.ts → src/atoms/composed/Chart/index.ts} +1 -2
- package/src/atoms/composed/ColorSwatch/ColorSwatch.tsx +72 -0
- package/{dist/atoms/composed/ColorSwatch/index.d.ts → src/atoms/composed/ColorSwatch/index.ts} +1 -2
- package/src/atoms/composed/DarkModeToggle.tsx +66 -0
- package/src/atoms/composed/DataBadge/DataBadge.tsx +81 -0
- package/src/atoms/composed/DataBadge/index.ts +1 -0
- package/src/atoms/composed/DataTable/DataTable.tsx +394 -0
- package/src/atoms/composed/DataTable/TableCellWithTooltip.tsx +41 -0
- package/src/atoms/composed/DataTable/index.ts +2 -0
- package/src/atoms/composed/DateTimePicker/DateTimePicker.tsx +611 -0
- package/src/atoms/composed/DateTimePicker/index.ts +2 -0
- package/src/atoms/composed/DetailedCard/DetailedCard.tsx +181 -0
- package/src/atoms/composed/DetailedCard/index.ts +2 -0
- package/src/atoms/composed/EmptyState/EmptyState.tsx +90 -0
- package/src/atoms/composed/EmptyState/index.ts +1 -0
- package/src/atoms/composed/FileUpload/FileUpload.tsx +477 -0
- package/{dist/atoms/composed/FileUpload/index.d.ts → src/atoms/composed/FileUpload/index.ts} +1 -2
- package/src/atoms/composed/FormField/FormField.tsx +92 -0
- package/src/atoms/composed/FormField/index.ts +1 -0
- package/src/atoms/composed/GlobalSearch/GlobalSearch.tsx +37 -0
- package/src/atoms/composed/GlobalSearch/index.ts +1 -0
- package/src/atoms/composed/IconBadge/IconBadge.tsx +95 -0
- package/src/atoms/composed/IconBadge/index.ts +2 -0
- package/src/atoms/composed/Modal/Modal.tsx +223 -0
- package/src/atoms/composed/Modal/index.ts +2 -0
- package/src/atoms/composed/PaletteSwitcher.tsx +386 -0
- package/src/atoms/composed/ProgressBar/ProgressBar.tsx +116 -0
- package/{dist/atoms/composed/ProgressBar/index.d.ts → src/atoms/composed/ProgressBar/index.ts} +1 -2
- package/src/atoms/composed/SalesPanel/SalesPanel.tsx +116 -0
- package/src/atoms/composed/SalesPanel/index.ts +1 -0
- package/src/atoms/composed/SalesPanel/mockSalesData.ts +151 -0
- package/src/atoms/composed/StatCard/StatCard.tsx +219 -0
- package/src/atoms/composed/StatCard/index.ts +1 -0
- package/src/atoms/composed/StyleGuide.tsx +717 -0
- package/src/atoms/composed/Toast/Toast.tsx +219 -0
- package/{dist/atoms/composed/Toast/index.d.ts → src/atoms/composed/Toast/index.ts} +1 -2
- package/src/atoms/composed/Tooltip/Tooltip.tsx +213 -0
- package/src/atoms/composed/Tooltip/index.ts +1 -0
- package/src/atoms/composed/UserAvatar/UserAvatar.tsx +139 -0
- package/src/atoms/composed/UserAvatar/index.ts +1 -0
- package/src/atoms/composed/UserMenu/UserMenu.tsx +16 -0
- package/src/atoms/composed/UserMenu/index.ts +1 -0
- package/{dist/atoms/composed/index.d.ts → src/atoms/composed/index.ts} +7 -2
- package/src/atoms/hooks/useApi.ts +80 -0
- package/src/atoms/hooks/useHealth.ts +17 -0
- package/{dist/atoms/index.d.ts → src/atoms/index.ts} +6 -2
- package/src/atoms/services/api/client.ts +134 -0
- package/src/atoms/services/auth-service.ts +248 -0
- package/src/atoms/services/health.ts +15 -0
- package/{dist/atoms/services/index.d.ts → src/atoms/services/index.ts} +1 -2
- package/src/atoms/shared/config/constants.ts +17 -0
- package/src/atoms/shared/config/dashboard-sizes.ts +111 -0
- package/src/atoms/shared/config/environment.ts +10 -0
- package/src/atoms/shared/index.ts +4 -0
- package/src/atoms/shared/styles/color-palettes.css +566 -0
- package/src/atoms/types/auth.ts +62 -0
- package/src/atoms/types/entity-config.ts +127 -0
- package/{dist/atoms/types/generated.d.ts → src/atoms/types/generated.ts} +1 -1
- package/{dist/atoms/types/index.d.ts → src/atoms/types/index.ts} +2 -1
- package/{dist/atoms/types/loading.d.ts → src/atoms/types/loading.ts} +10 -8
- package/src/atoms/ui/Badge.tsx +30 -0
- package/src/atoms/ui/ErrorBoundary.tsx +59 -0
- package/src/atoms/ui/Select.tsx +53 -0
- package/src/atoms/ui/Switch.tsx +42 -0
- package/src/atoms/ui/Tabs.tsx +118 -0
- package/src/atoms/ui/avatar.tsx +48 -0
- package/src/atoms/ui/button.tsx +70 -0
- package/src/atoms/ui/card.tsx +76 -0
- package/src/atoms/ui/dropdown-menu.tsx +199 -0
- package/{dist/atoms/ui/index.d.ts → src/atoms/ui/index.ts} +27 -3
- package/src/atoms/ui/input.tsx +23 -0
- package/src/atoms/ui/label.tsx +23 -0
- package/src/atoms/ui/skeleton.tsx +13 -0
- package/src/atoms/ui/spinner.tsx +49 -0
- package/src/atoms/ui/table.tsx +116 -0
- package/src/atoms/utils/animations.ts +135 -0
- package/src/atoms/utils/metric-engine.ts +236 -0
- package/src/atoms/utils/tooltip-helpers.ts +140 -0
- package/src/atoms/utils/utils.ts +10 -0
- package/src/features/auth/components/LoginForm.tsx +168 -0
- package/src/features/auth/components/LogoutButton.tsx +19 -0
- package/src/features/auth/components/ProtectedRoute.tsx +60 -0
- package/{dist/features/auth/components/index.d.ts → src/features/auth/components/index.ts} +1 -1
- package/src/features/auth/hooks/index.ts +2 -0
- package/src/features/auth/hooks/useAuth.tsx +205 -0
- package/src/features/auth/hooks/usePermissions.ts +35 -0
- package/src/features/auth/index.ts +2 -0
- package/src/features/index.ts +2 -0
- package/src/index.css +704 -0
- package/{dist/index.d.ts → src/index.ts} +5 -2
- package/src/main.tsx +48 -0
- package/src/molecules/.gitkeep +0 -0
- package/src/molecules/forms/FormGroup.tsx +75 -0
- package/src/molecules/forms/SearchInput.tsx +259 -0
- package/src/molecules/forms/index.ts +4 -0
- package/src/molecules/index.ts +4 -0
- package/src/molecules/layout/AppHeader/AppHeader.tsx +42 -0
- package/src/molecules/layout/AppHeader/index.ts +1 -0
- package/src/molecules/layout/AppLayout.tsx +29 -0
- package/src/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.tsx +42 -0
- package/src/molecules/layout/DashboardWithSidePanel/index.ts +1 -0
- package/src/molecules/layout/PageTemplate.tsx +87 -0
- package/src/molecules/layout/SectionHeader/SectionHeader.tsx +87 -0
- package/{dist/molecules/layout/SectionHeader/index.d.ts → src/molecules/layout/SectionHeader/index.ts} +1 -2
- package/src/molecules/layout/ShowcaseSection.tsx +57 -0
- package/src/molecules/layout/Sidebar.tsx +152 -0
- package/src/molecules/layout/SidebarButton/SidebarButton.tsx +99 -0
- package/src/molecules/layout/SidebarButton/index.ts +1 -0
- package/src/molecules/layout/SidebarContext.tsx +31 -0
- package/{dist/molecules/layout/index.d.ts → src/molecules/layout/index.ts} +2 -2
- package/src/molecules/navigation/NavMenu.tsx +188 -0
- package/src/molecules/navigation/Pagination.tsx +172 -0
- package/src/molecules/navigation/index.ts +4 -0
- package/src/organisms/entity/CategoryBreakdownPanel.tsx +427 -0
- package/src/organisms/entity/EntityListPanel.tsx +339 -0
- package/src/organisms/entity/MetricsOverviewPanel.tsx +236 -0
- package/src/organisms/entity/TrendAnalysisPanel.tsx +337 -0
- package/src/organisms/entity/index.ts +4 -0
- package/src/organisms/index.ts +8 -0
- package/src/organisms/showcase/ComponentShowcasePage.tsx +2496 -0
- package/src/organisms/showcase/index.ts +1 -0
- package/src/pages/AdminShowcase/AdminCRUDShowcase.tsx +242 -0
- package/src/pages/AdminShowcase/AdminDashboardShowcase.tsx +173 -0
- package/src/pages/AdminShowcase/AdminDetailShowcase.tsx +385 -0
- package/src/pages/AdminShowcase/SalesPerformanceDashboard.tsx +158 -0
- package/src/pages/AdminShowcase/index.tsx +4 -0
- package/src/pages/ComponentShowcase/BadgesShowcase.tsx +188 -0
- package/src/pages/ComponentShowcase/CardsShowcase.tsx +392 -0
- package/src/pages/ComponentShowcase/PalettesShowcase.tsx +207 -0
- package/src/pages/ComponentShowcase/StatesShowcase.tsx +485 -0
- package/src/pages/ComponentShowcase/TablesShowcase.tsx +134 -0
- package/src/pages/ComponentShowcase/TypographyShowcase.tsx +255 -0
- package/src/pages/ComponentShowcase/index.tsx +188 -0
- package/src/pages/EntityShowcase/EntityManagementShowcase.tsx +137 -0
- package/src/pages/EntityShowcase/EntityPerformanceShowcase.tsx +117 -0
- package/src/pages/EntityShowcase/index.ts +2 -0
- package/src/pages/EntityTemplateExample.tsx +229 -0
- package/src/pages/TestEntityTemplate.tsx +40 -0
- package/src/pages/index.ts +3 -0
- package/src/templates/AuthTemplate.tsx +216 -0
- package/src/templates/ComponentShowcaseTemplate.tsx +173 -0
- package/src/templates/DashboardTemplate.tsx +232 -0
- package/src/templates/DataTemplate.tsx +319 -0
- package/src/templates/admin/AdminCRUDTemplate.tsx +630 -0
- package/src/templates/admin/AdminDashboardTemplate.tsx +351 -0
- package/src/templates/admin/AdminDetailTemplate.tsx +563 -0
- package/src/templates/admin/index.ts +29 -0
- package/src/templates/entity/EntityManagementTemplate.tsx +430 -0
- package/src/templates/entity/EntityPerformanceDashboardTemplate.tsx +277 -0
- package/src/templates/entity/configs/financial-config.ts +141 -0
- package/src/templates/entity/configs/index.ts +1 -0
- package/src/templates/entity/index.ts +3 -0
- package/src/templates/factory.tsx +169 -0
- package/src/templates/financial/FinancialDashboardTemplate.tsx +326 -0
- package/src/templates/index.ts +40 -0
- package/src/vite-env.d.ts +1 -0
- package/dist/atoms/composed/Accordion/Accordion.d.ts +0 -20
- package/dist/atoms/composed/Accordion/Accordion.d.ts.map +0 -1
- package/dist/atoms/composed/Accordion/index.d.ts.map +0 -1
- package/dist/atoms/composed/Alert/Alert.d.ts +0 -25
- package/dist/atoms/composed/Alert/Alert.d.ts.map +0 -1
- package/dist/atoms/composed/Alert/index.d.ts +0 -2
- package/dist/atoms/composed/Alert/index.d.ts.map +0 -1
- package/dist/atoms/composed/Breadcrumb/Breadcrumb.d.ts +0 -17
- package/dist/atoms/composed/Breadcrumb/Breadcrumb.d.ts.map +0 -1
- package/dist/atoms/composed/Breadcrumb/index.d.ts +0 -2
- package/dist/atoms/composed/Breadcrumb/index.d.ts.map +0 -1
- package/dist/atoms/composed/Chart/Chart.d.ts +0 -37
- package/dist/atoms/composed/Chart/Chart.d.ts.map +0 -1
- package/dist/atoms/composed/Chart/index.d.ts.map +0 -1
- package/dist/atoms/composed/ColorSwatch/ColorSwatch.d.ts +0 -19
- package/dist/atoms/composed/ColorSwatch/ColorSwatch.d.ts.map +0 -1
- package/dist/atoms/composed/ColorSwatch/index.d.ts.map +0 -1
- package/dist/atoms/composed/DarkModeToggle.d.ts +0 -4
- package/dist/atoms/composed/DarkModeToggle.d.ts.map +0 -1
- package/dist/atoms/composed/DataBadge/DataBadge.d.ts +0 -13
- package/dist/atoms/composed/DataBadge/DataBadge.d.ts.map +0 -1
- package/dist/atoms/composed/DataBadge/index.d.ts +0 -2
- package/dist/atoms/composed/DataBadge/index.d.ts.map +0 -1
- package/dist/atoms/composed/DataTable/DataTable.d.ts +0 -28
- package/dist/atoms/composed/DataTable/DataTable.d.ts.map +0 -1
- package/dist/atoms/composed/DataTable/TableCellWithTooltip.d.ts +0 -10
- package/dist/atoms/composed/DataTable/TableCellWithTooltip.d.ts.map +0 -1
- package/dist/atoms/composed/DataTable/index.d.ts +0 -3
- package/dist/atoms/composed/DataTable/index.d.ts.map +0 -1
- package/dist/atoms/composed/DateTimePicker/DateTimePicker.d.ts +0 -45
- package/dist/atoms/composed/DateTimePicker/DateTimePicker.d.ts.map +0 -1
- package/dist/atoms/composed/DateTimePicker/index.d.ts +0 -3
- package/dist/atoms/composed/DateTimePicker/index.d.ts.map +0 -1
- package/dist/atoms/composed/DetailedCard/DetailedCard.d.ts +0 -30
- package/dist/atoms/composed/DetailedCard/DetailedCard.d.ts.map +0 -1
- package/dist/atoms/composed/DetailedCard/index.d.ts +0 -3
- package/dist/atoms/composed/DetailedCard/index.d.ts.map +0 -1
- package/dist/atoms/composed/EmptyState/EmptyState.d.ts +0 -18
- package/dist/atoms/composed/EmptyState/EmptyState.d.ts.map +0 -1
- package/dist/atoms/composed/EmptyState/index.d.ts +0 -2
- package/dist/atoms/composed/EmptyState/index.d.ts.map +0 -1
- package/dist/atoms/composed/FileUpload/FileUpload.d.ts +0 -46
- package/dist/atoms/composed/FileUpload/FileUpload.d.ts.map +0 -1
- package/dist/atoms/composed/FileUpload/index.d.ts.map +0 -1
- package/dist/atoms/composed/FormField/FormField.d.ts +0 -23
- package/dist/atoms/composed/FormField/FormField.d.ts.map +0 -1
- package/dist/atoms/composed/FormField/index.d.ts +0 -2
- package/dist/atoms/composed/FormField/index.d.ts.map +0 -1
- package/dist/atoms/composed/GlobalSearch/GlobalSearch.d.ts +0 -8
- package/dist/atoms/composed/GlobalSearch/GlobalSearch.d.ts.map +0 -1
- package/dist/atoms/composed/GlobalSearch/index.d.ts +0 -2
- package/dist/atoms/composed/GlobalSearch/index.d.ts.map +0 -1
- package/dist/atoms/composed/IconBadge/IconBadge.d.ts +0 -16
- package/dist/atoms/composed/IconBadge/IconBadge.d.ts.map +0 -1
- package/dist/atoms/composed/IconBadge/index.d.ts +0 -3
- package/dist/atoms/composed/IconBadge/index.d.ts.map +0 -1
- package/dist/atoms/composed/Modal/Modal.d.ts +0 -18
- package/dist/atoms/composed/Modal/Modal.d.ts.map +0 -1
- package/dist/atoms/composed/Modal/index.d.ts +0 -3
- package/dist/atoms/composed/Modal/index.d.ts.map +0 -1
- package/dist/atoms/composed/PaletteSwitcher.d.ts +0 -7
- package/dist/atoms/composed/PaletteSwitcher.d.ts.map +0 -1
- package/dist/atoms/composed/ProgressBar/ProgressBar.d.ts +0 -25
- package/dist/atoms/composed/ProgressBar/ProgressBar.d.ts.map +0 -1
- package/dist/atoms/composed/ProgressBar/index.d.ts.map +0 -1
- package/dist/atoms/composed/StatCard/StatCard.d.ts +0 -21
- package/dist/atoms/composed/StatCard/StatCard.d.ts.map +0 -1
- package/dist/atoms/composed/StatCard/index.d.ts +0 -2
- package/dist/atoms/composed/StatCard/index.d.ts.map +0 -1
- package/dist/atoms/composed/StyleGuide.d.ts +0 -3
- package/dist/atoms/composed/StyleGuide.d.ts.map +0 -1
- package/dist/atoms/composed/Toast/Toast.d.ts +0 -40
- package/dist/atoms/composed/Toast/Toast.d.ts.map +0 -1
- package/dist/atoms/composed/Toast/index.d.ts.map +0 -1
- package/dist/atoms/composed/Tooltip/Tooltip.d.ts +0 -16
- package/dist/atoms/composed/Tooltip/Tooltip.d.ts.map +0 -1
- package/dist/atoms/composed/Tooltip/index.d.ts +0 -2
- package/dist/atoms/composed/Tooltip/index.d.ts.map +0 -1
- package/dist/atoms/composed/UserAvatar/UserAvatar.d.ts +0 -8
- package/dist/atoms/composed/UserAvatar/UserAvatar.d.ts.map +0 -1
- package/dist/atoms/composed/UserAvatar/index.d.ts +0 -2
- package/dist/atoms/composed/UserAvatar/index.d.ts.map +0 -1
- package/dist/atoms/composed/UserMenu/UserMenu.d.ts +0 -8
- package/dist/atoms/composed/UserMenu/UserMenu.d.ts.map +0 -1
- package/dist/atoms/composed/UserMenu/index.d.ts +0 -2
- package/dist/atoms/composed/UserMenu/index.d.ts.map +0 -1
- package/dist/atoms/composed/index.d.ts.map +0 -1
- package/dist/atoms/hooks/useApi.d.ts +0 -25
- package/dist/atoms/hooks/useApi.d.ts.map +0 -1
- package/dist/atoms/hooks/useHealth.d.ts +0 -19
- package/dist/atoms/hooks/useHealth.d.ts.map +0 -1
- package/dist/atoms/index.d.ts.map +0 -1
- package/dist/atoms/services/api/client.d.ts +0 -20
- package/dist/atoms/services/api/client.d.ts.map +0 -1
- package/dist/atoms/services/auth-service.d.ts +0 -24
- package/dist/atoms/services/auth-service.d.ts.map +0 -1
- package/dist/atoms/services/health.d.ts +0 -7
- package/dist/atoms/services/health.d.ts.map +0 -1
- package/dist/atoms/services/index.d.ts.map +0 -1
- package/dist/atoms/shared/config/constants.d.ts +0 -15
- package/dist/atoms/shared/config/constants.d.ts.map +0 -1
- package/dist/atoms/shared/config/dashboard-sizes.d.ts +0 -83
- package/dist/atoms/shared/config/dashboard-sizes.d.ts.map +0 -1
- package/dist/atoms/shared/config/environment.d.ts +0 -10
- package/dist/atoms/shared/config/environment.d.ts.map +0 -1
- package/dist/atoms/shared/index.d.ts +0 -4
- package/dist/atoms/shared/index.d.ts.map +0 -1
- package/dist/atoms/types/auth.d.ts +0 -56
- package/dist/atoms/types/auth.d.ts.map +0 -1
- package/dist/atoms/types/generated.d.ts.map +0 -1
- package/dist/atoms/types/index.d.ts.map +0 -1
- package/dist/atoms/types/loading.d.ts.map +0 -1
- package/dist/atoms/ui/Badge.d.ts +0 -10
- package/dist/atoms/ui/Badge.d.ts.map +0 -1
- package/dist/atoms/ui/ErrorBoundary.d.ts +0 -18
- package/dist/atoms/ui/ErrorBoundary.d.ts.map +0 -1
- package/dist/atoms/ui/Select.d.ts +0 -28
- package/dist/atoms/ui/Select.d.ts.map +0 -1
- package/dist/atoms/ui/Switch.d.ts +0 -9
- package/dist/atoms/ui/Switch.d.ts.map +0 -1
- package/dist/atoms/ui/Tabs.d.ts +0 -30
- package/dist/atoms/ui/Tabs.d.ts.map +0 -1
- package/dist/atoms/ui/avatar.d.ts +0 -7
- package/dist/atoms/ui/avatar.d.ts.map +0 -1
- package/dist/atoms/ui/button.d.ts +0 -14
- package/dist/atoms/ui/button.d.ts.map +0 -1
- package/dist/atoms/ui/card.d.ts +0 -12
- package/dist/atoms/ui/card.d.ts.map +0 -1
- package/dist/atoms/ui/dropdown-menu.d.ts +0 -28
- package/dist/atoms/ui/dropdown-menu.d.ts.map +0 -1
- package/dist/atoms/ui/index.d.ts.map +0 -1
- package/dist/atoms/ui/input.d.ts +0 -5
- package/dist/atoms/ui/input.d.ts.map +0 -1
- package/dist/atoms/ui/label.d.ts +0 -6
- package/dist/atoms/ui/label.d.ts.map +0 -1
- package/dist/atoms/ui/skeleton.d.ts +0 -3
- package/dist/atoms/ui/skeleton.d.ts.map +0 -1
- package/dist/atoms/ui/spinner.d.ts +0 -14
- package/dist/atoms/ui/spinner.d.ts.map +0 -1
- package/dist/atoms/ui/table.d.ts +0 -11
- package/dist/atoms/ui/table.d.ts.map +0 -1
- package/dist/atoms/utils/animations.d.ts +0 -65
- package/dist/atoms/utils/animations.d.ts.map +0 -1
- package/dist/atoms/utils/tooltip-helpers.d.ts +0 -71
- package/dist/atoms/utils/tooltip-helpers.d.ts.map +0 -1
- package/dist/atoms/utils/utils.d.ts +0 -4
- package/dist/atoms/utils/utils.d.ts.map +0 -1
- package/dist/features/auth/components/LoginForm.d.ts +0 -2
- package/dist/features/auth/components/LoginForm.d.ts.map +0 -1
- package/dist/features/auth/components/LogoutButton.d.ts +0 -2
- package/dist/features/auth/components/LogoutButton.d.ts.map +0 -1
- package/dist/features/auth/components/ProtectedRoute.d.ts +0 -10
- package/dist/features/auth/components/ProtectedRoute.d.ts.map +0 -1
- package/dist/features/auth/components/index.d.ts.map +0 -1
- package/dist/features/auth/hooks/index.d.ts +0 -3
- package/dist/features/auth/hooks/index.d.ts.map +0 -1
- package/dist/features/auth/hooks/useAuth.d.ts +0 -10
- package/dist/features/auth/hooks/useAuth.d.ts.map +0 -1
- package/dist/features/auth/hooks/usePermissions.d.ts +0 -13
- package/dist/features/auth/hooks/usePermissions.d.ts.map +0 -1
- package/dist/features/auth/index.d.ts +0 -3
- package/dist/features/auth/index.d.ts.map +0 -1
- package/dist/features/index.d.ts +0 -2
- package/dist/features/index.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/molecules/forms/FormGroup.d.ts +0 -17
- package/dist/molecules/forms/FormGroup.d.ts.map +0 -1
- package/dist/molecules/forms/SearchInput.d.ts +0 -36
- package/dist/molecules/forms/SearchInput.d.ts.map +0 -1
- package/dist/molecules/forms/index.d.ts +0 -3
- package/dist/molecules/forms/index.d.ts.map +0 -1
- package/dist/molecules/index.d.ts +0 -4
- package/dist/molecules/index.d.ts.map +0 -1
- package/dist/molecules/layout/AppHeader/AppHeader.d.ts +0 -7
- package/dist/molecules/layout/AppHeader/AppHeader.d.ts.map +0 -1
- package/dist/molecules/layout/AppHeader/index.d.ts +0 -2
- package/dist/molecules/layout/AppHeader/index.d.ts.map +0 -1
- package/dist/molecules/layout/AppLayout.d.ts +0 -2
- package/dist/molecules/layout/AppLayout.d.ts.map +0 -1
- package/dist/molecules/layout/PageTemplate.d.ts +0 -19
- package/dist/molecules/layout/PageTemplate.d.ts.map +0 -1
- package/dist/molecules/layout/SectionHeader/SectionHeader.d.ts +0 -24
- package/dist/molecules/layout/SectionHeader/SectionHeader.d.ts.map +0 -1
- package/dist/molecules/layout/SectionHeader/index.d.ts.map +0 -1
- package/dist/molecules/layout/ShowcaseSection.d.ts +0 -22
- package/dist/molecules/layout/ShowcaseSection.d.ts.map +0 -1
- package/dist/molecules/layout/Sidebar.d.ts +0 -6
- package/dist/molecules/layout/Sidebar.d.ts.map +0 -1
- package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts +0 -13
- package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts.map +0 -1
- package/dist/molecules/layout/SidebarButton/index.d.ts +0 -2
- package/dist/molecules/layout/SidebarButton/index.d.ts.map +0 -1
- package/dist/molecules/layout/SidebarContext.d.ts +0 -12
- package/dist/molecules/layout/SidebarContext.d.ts.map +0 -1
- package/dist/molecules/layout/index.d.ts.map +0 -1
- package/dist/molecules/navigation/NavMenu.d.ts +0 -20
- package/dist/molecules/navigation/NavMenu.d.ts.map +0 -1
- package/dist/molecules/navigation/Pagination.d.ts +0 -14
- package/dist/molecules/navigation/Pagination.d.ts.map +0 -1
- package/dist/molecules/navigation/index.d.ts +0 -3
- package/dist/molecules/navigation/index.d.ts.map +0 -1
- package/dist/organisms/index.d.ts +0 -2
- package/dist/organisms/index.d.ts.map +0 -1
- package/dist/organisms/showcase/ComponentShowcasePage.d.ts +0 -3
- package/dist/organisms/showcase/ComponentShowcasePage.d.ts.map +0 -1
- package/dist/templates/AuthTemplate.d.ts +0 -68
- package/dist/templates/AuthTemplate.d.ts.map +0 -1
- package/dist/templates/ComponentShowcaseTemplate.d.ts +0 -53
- package/dist/templates/ComponentShowcaseTemplate.d.ts.map +0 -1
- package/dist/templates/DashboardTemplate.d.ts +0 -62
- package/dist/templates/DashboardTemplate.d.ts.map +0 -1
- package/dist/templates/DataTemplate.d.ts +0 -78
- package/dist/templates/DataTemplate.d.ts.map +0 -1
- package/dist/templates/admin/AdminCRUDTemplate.d.ts +0 -105
- package/dist/templates/admin/AdminCRUDTemplate.d.ts.map +0 -1
- package/dist/templates/admin/AdminDashboardTemplate.d.ts +0 -89
- package/dist/templates/admin/AdminDashboardTemplate.d.ts.map +0 -1
- package/dist/templates/admin/AdminDetailTemplate.d.ts +0 -132
- package/dist/templates/admin/AdminDetailTemplate.d.ts.map +0 -1
- package/dist/templates/admin/index.d.ts +0 -4
- package/dist/templates/admin/index.d.ts.map +0 -1
- package/dist/templates/factory.d.ts +0 -28
- package/dist/templates/factory.d.ts.map +0 -1
- package/dist/templates/index.d.ts +0 -7
- package/dist/templates/index.d.ts.map +0 -1
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import type { MetricConfig, MetricValue, EntityData, FormatConfig, TrendDataPoint } from '../types';
|
|
2
|
+
|
|
3
|
+
export class MetricCalculationEngine {
|
|
4
|
+
static calculateMetric(
|
|
5
|
+
config: MetricConfig,
|
|
6
|
+
data: EntityData[],
|
|
7
|
+
previousData?: EntityData[]
|
|
8
|
+
): MetricValue {
|
|
9
|
+
const currentValue = this.aggregateValue(config, data);
|
|
10
|
+
const previousValue = previousData ? this.aggregateValue(config, previousData) : undefined;
|
|
11
|
+
|
|
12
|
+
const trend = this.calculateTrend(currentValue, previousValue);
|
|
13
|
+
const formattedValue = this.formatValue(currentValue, config.format, config.type);
|
|
14
|
+
|
|
15
|
+
const target = typeof config.target === 'function'
|
|
16
|
+
? config.target(data)
|
|
17
|
+
: config.target;
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
current: currentValue,
|
|
21
|
+
previous: previousValue,
|
|
22
|
+
trend,
|
|
23
|
+
target,
|
|
24
|
+
formattedValue
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private static aggregateValue(config: MetricConfig, data: EntityData[]): number {
|
|
29
|
+
if (!data.length) return 0;
|
|
30
|
+
|
|
31
|
+
const values = data
|
|
32
|
+
.map(item => {
|
|
33
|
+
const value = this.extractValue(item, config.key);
|
|
34
|
+
return typeof value === 'number' ? value : 0;
|
|
35
|
+
})
|
|
36
|
+
.filter(value => !isNaN(value));
|
|
37
|
+
|
|
38
|
+
if (!values.length) return 0;
|
|
39
|
+
|
|
40
|
+
switch (config.aggregation || 'sum') {
|
|
41
|
+
case 'sum':
|
|
42
|
+
return values.reduce((sum, value) => sum + value, 0);
|
|
43
|
+
case 'avg':
|
|
44
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
45
|
+
case 'count':
|
|
46
|
+
return values.length;
|
|
47
|
+
case 'min':
|
|
48
|
+
return Math.min(...values);
|
|
49
|
+
case 'max':
|
|
50
|
+
return Math.max(...values);
|
|
51
|
+
default:
|
|
52
|
+
return values.reduce((sum, value) => sum + value, 0);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private static extractValue(item: EntityData, key: string): unknown {
|
|
57
|
+
return key.split('.').reduce((obj: any, k: string) => obj?.[k], item);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private static calculateTrend(current: number, previous?: number): 'up' | 'down' | 'neutral' {
|
|
61
|
+
if (previous === undefined || previous === 0) return 'neutral';
|
|
62
|
+
|
|
63
|
+
const change = ((current - previous) / Math.abs(previous)) * 100;
|
|
64
|
+
|
|
65
|
+
if (change > 1) return 'up';
|
|
66
|
+
if (change < -1) return 'down';
|
|
67
|
+
return 'neutral';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private static formatValue(value: number, format?: FormatConfig, type?: string): string {
|
|
71
|
+
let formatted = value;
|
|
72
|
+
|
|
73
|
+
if (format?.decimals !== undefined) {
|
|
74
|
+
formatted = Number(value.toFixed(format.decimals));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let result = formatted.toString();
|
|
78
|
+
|
|
79
|
+
if (format?.thousands !== false && Math.abs(formatted) >= 1000) {
|
|
80
|
+
result = formatted.toLocaleString();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
switch (type) {
|
|
84
|
+
case 'currency':
|
|
85
|
+
result = new Intl.NumberFormat('en-US', {
|
|
86
|
+
style: 'currency',
|
|
87
|
+
currency: 'USD',
|
|
88
|
+
minimumFractionDigits: format?.decimals ?? 2,
|
|
89
|
+
maximumFractionDigits: format?.decimals ?? 2
|
|
90
|
+
}).format(formatted);
|
|
91
|
+
break;
|
|
92
|
+
case 'percentage':
|
|
93
|
+
result = `${formatted.toFixed(format?.decimals ?? 1)}%`;
|
|
94
|
+
break;
|
|
95
|
+
case 'duration':
|
|
96
|
+
result = this.formatDuration(formatted);
|
|
97
|
+
break;
|
|
98
|
+
case 'ratio':
|
|
99
|
+
result = `${formatted.toFixed(format?.decimals ?? 2)}:1`;
|
|
100
|
+
break;
|
|
101
|
+
default:
|
|
102
|
+
if (format?.prefix) result = format.prefix + result;
|
|
103
|
+
if (format?.suffix) result = result + format.suffix;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private static formatDuration(minutes: number): string {
|
|
110
|
+
const hours = Math.floor(minutes / 60);
|
|
111
|
+
const mins = Math.floor(minutes % 60);
|
|
112
|
+
|
|
113
|
+
if (hours > 0) {
|
|
114
|
+
return `${hours}h ${mins}m`;
|
|
115
|
+
}
|
|
116
|
+
return `${mins}m`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static calculateTrendData(
|
|
120
|
+
config: MetricConfig,
|
|
121
|
+
data: EntityData[],
|
|
122
|
+
dateField: string = 'date',
|
|
123
|
+
periods: number = 12
|
|
124
|
+
): TrendDataPoint[] {
|
|
125
|
+
const groupedData = this.groupByPeriod(data, dateField);
|
|
126
|
+
const sortedDates = Object.keys(groupedData).sort();
|
|
127
|
+
|
|
128
|
+
return sortedDates.slice(-periods).map(date => ({
|
|
129
|
+
date,
|
|
130
|
+
value: this.aggregateValue(config, groupedData[date]),
|
|
131
|
+
label: this.formatDateLabel(date)
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private static groupByPeriod(data: EntityData[], dateField: string): Record<string, EntityData[]> {
|
|
136
|
+
return data.reduce((groups, item) => {
|
|
137
|
+
const date = this.extractValue(item, dateField);
|
|
138
|
+
if (!date) return groups;
|
|
139
|
+
|
|
140
|
+
const period = new Date(date as string | number | Date).toISOString().split('T')[0];
|
|
141
|
+
|
|
142
|
+
if (!groups[period]) groups[period] = [];
|
|
143
|
+
groups[period].push(item);
|
|
144
|
+
|
|
145
|
+
return groups;
|
|
146
|
+
}, {} as Record<string, EntityData[]>);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private static formatDateLabel(dateString: string): string {
|
|
150
|
+
const date = new Date(dateString);
|
|
151
|
+
return date.toLocaleDateString('en-US', {
|
|
152
|
+
month: 'short',
|
|
153
|
+
day: 'numeric'
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
static calculateCategoryBreakdown(
|
|
158
|
+
data: EntityData[],
|
|
159
|
+
categoryField: string,
|
|
160
|
+
valueField: string,
|
|
161
|
+
maxCategories: number = 8
|
|
162
|
+
) {
|
|
163
|
+
const groups = data.reduce((acc, item) => {
|
|
164
|
+
const category = String(this.extractValue(item, categoryField) || 'Unknown');
|
|
165
|
+
const value = this.extractValue(item, valueField) || 0;
|
|
166
|
+
|
|
167
|
+
if (!acc[category]) acc[category] = 0;
|
|
168
|
+
acc[category] += typeof value === 'number' ? value : 0;
|
|
169
|
+
|
|
170
|
+
return acc;
|
|
171
|
+
}, {} as Record<string, number>);
|
|
172
|
+
|
|
173
|
+
const total = Object.values(groups).reduce((sum, value) => sum + value, 0);
|
|
174
|
+
|
|
175
|
+
let categories = Object.entries(groups)
|
|
176
|
+
.map(([category, value]) => ({
|
|
177
|
+
category,
|
|
178
|
+
value,
|
|
179
|
+
percentage: total > 0 ? (value / total) * 100 : 0
|
|
180
|
+
}))
|
|
181
|
+
.sort((a, b) => b.value - a.value);
|
|
182
|
+
|
|
183
|
+
if (categories.length > maxCategories) {
|
|
184
|
+
const topCategories = categories.slice(0, maxCategories - 1);
|
|
185
|
+
const otherValue = categories.slice(maxCategories - 1)
|
|
186
|
+
.reduce((sum, cat) => sum + cat.value, 0);
|
|
187
|
+
|
|
188
|
+
topCategories.push({
|
|
189
|
+
category: 'Other',
|
|
190
|
+
value: otherValue,
|
|
191
|
+
percentage: total > 0 ? (otherValue / total) * 100 : 0
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
categories = topCategories;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return categories;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
static detectInsights(
|
|
201
|
+
metrics: Record<string, MetricValue>,
|
|
202
|
+
thresholds: Record<string, { warning: number; critical: number }> = {}
|
|
203
|
+
) {
|
|
204
|
+
const insights = [];
|
|
205
|
+
|
|
206
|
+
for (const [key, metric] of Object.entries(metrics)) {
|
|
207
|
+
const threshold = thresholds[key];
|
|
208
|
+
|
|
209
|
+
if (metric.target && metric.current < metric.target * 0.8) {
|
|
210
|
+
insights.push({
|
|
211
|
+
type: 'negative' as const,
|
|
212
|
+
message: `${key} is significantly below target`,
|
|
213
|
+
value: metric.current
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (metric.trend === 'up' && metric.previous && metric.current > metric.previous * 1.2) {
|
|
218
|
+
insights.push({
|
|
219
|
+
type: 'positive' as const,
|
|
220
|
+
message: `${key} showing strong growth`,
|
|
221
|
+
value: metric.current
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (threshold && metric.current >= threshold.critical) {
|
|
226
|
+
insights.push({
|
|
227
|
+
type: 'negative' as const,
|
|
228
|
+
message: `${key} has reached critical threshold`,
|
|
229
|
+
value: metric.current
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return insights.slice(0, 5);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { useRef, useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook to detect if an element's text is truncated/overflowing
|
|
5
|
+
* Useful for conditionally showing tooltips only when needed
|
|
6
|
+
*/
|
|
7
|
+
export const useTextOverflow = () => {
|
|
8
|
+
const ref = useRef<HTMLElement>(null);
|
|
9
|
+
const [isOverflowing, setIsOverflowing] = useState(false);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const checkOverflow = () => {
|
|
13
|
+
const element = ref.current;
|
|
14
|
+
if (!element) return;
|
|
15
|
+
|
|
16
|
+
// Check if text is truncated
|
|
17
|
+
const isOverflow = element.scrollWidth > element.clientWidth ||
|
|
18
|
+
element.scrollHeight > element.clientHeight;
|
|
19
|
+
setIsOverflowing(isOverflow);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
checkOverflow();
|
|
23
|
+
|
|
24
|
+
// Re-check on resize
|
|
25
|
+
window.addEventListener('resize', checkOverflow);
|
|
26
|
+
|
|
27
|
+
// Use ResizeObserver if available for more accurate detection
|
|
28
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
29
|
+
const resizeObserver = new ResizeObserver(checkOverflow);
|
|
30
|
+
if (ref.current) {
|
|
31
|
+
resizeObserver.observe(ref.current);
|
|
32
|
+
}
|
|
33
|
+
return () => {
|
|
34
|
+
resizeObserver.disconnect();
|
|
35
|
+
window.removeEventListener('resize', checkOverflow);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return () => window.removeEventListener('resize', checkOverflow);
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
return { ref, isOverflowing };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Formats large numbers with abbreviations
|
|
47
|
+
* Useful for StatCard values with tooltips showing full numbers
|
|
48
|
+
*/
|
|
49
|
+
export const formatNumberWithTooltip = (value: number): { display: string; tooltip: string } => {
|
|
50
|
+
const formatter = new Intl.NumberFormat('en-US');
|
|
51
|
+
const fullNumber = formatter.format(value);
|
|
52
|
+
|
|
53
|
+
if (value >= 1_000_000_000) {
|
|
54
|
+
return {
|
|
55
|
+
display: `${(value / 1_000_000_000).toFixed(1)}B`,
|
|
56
|
+
tooltip: fullNumber
|
|
57
|
+
};
|
|
58
|
+
} else if (value >= 1_000_000) {
|
|
59
|
+
return {
|
|
60
|
+
display: `${(value / 1_000_000).toFixed(1)}M`,
|
|
61
|
+
tooltip: fullNumber
|
|
62
|
+
};
|
|
63
|
+
} else if (value >= 10_000) {
|
|
64
|
+
return {
|
|
65
|
+
display: `${(value / 1_000).toFixed(1)}K`,
|
|
66
|
+
tooltip: fullNumber
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
display: fullNumber,
|
|
72
|
+
tooltip: '' // No tooltip needed for small numbers
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Standard tooltip content for common UI patterns
|
|
78
|
+
*/
|
|
79
|
+
export const tooltipContent = {
|
|
80
|
+
// Icon button tooltips
|
|
81
|
+
refresh: 'Refresh data',
|
|
82
|
+
download: 'Download',
|
|
83
|
+
upload: 'Upload file',
|
|
84
|
+
edit: 'Edit',
|
|
85
|
+
delete: 'Delete',
|
|
86
|
+
view: 'View details',
|
|
87
|
+
filter: 'Filter',
|
|
88
|
+
search: 'Search',
|
|
89
|
+
settings: 'Settings',
|
|
90
|
+
help: 'Help',
|
|
91
|
+
export: 'Export data',
|
|
92
|
+
import: 'Import data',
|
|
93
|
+
add: 'Add new',
|
|
94
|
+
create: 'Create new',
|
|
95
|
+
back: 'Go back',
|
|
96
|
+
forward: 'Go forward',
|
|
97
|
+
menu: 'Open menu',
|
|
98
|
+
more: 'More options',
|
|
99
|
+
copy: 'Copy',
|
|
100
|
+
paste: 'Paste',
|
|
101
|
+
share: 'Share',
|
|
102
|
+
print: 'Print',
|
|
103
|
+
|
|
104
|
+
// Status tooltips
|
|
105
|
+
success: 'Operation completed successfully',
|
|
106
|
+
warning: 'Requires attention',
|
|
107
|
+
error: 'Error occurred',
|
|
108
|
+
info: 'Information',
|
|
109
|
+
pending: 'Pending',
|
|
110
|
+
|
|
111
|
+
// Common actions
|
|
112
|
+
save: 'Save changes',
|
|
113
|
+
cancel: 'Cancel',
|
|
114
|
+
close: 'Close',
|
|
115
|
+
expand: 'Expand',
|
|
116
|
+
collapse: 'Collapse',
|
|
117
|
+
minimize: 'Minimize',
|
|
118
|
+
maximize: 'Maximize',
|
|
119
|
+
|
|
120
|
+
// Navigation
|
|
121
|
+
previous: 'Previous',
|
|
122
|
+
next: 'Next',
|
|
123
|
+
first: 'Go to first',
|
|
124
|
+
last: 'Go to last',
|
|
125
|
+
home: 'Home',
|
|
126
|
+
|
|
127
|
+
// Data operations
|
|
128
|
+
sort: 'Sort',
|
|
129
|
+
sortAsc: 'Sort ascending',
|
|
130
|
+
sortDesc: 'Sort descending',
|
|
131
|
+
clearSort: 'Clear sorting',
|
|
132
|
+
clearFilters: 'Clear all filters',
|
|
133
|
+
clearSearch: 'Clear search',
|
|
134
|
+
|
|
135
|
+
// Authentication
|
|
136
|
+
login: 'Sign in',
|
|
137
|
+
logout: 'Sign out',
|
|
138
|
+
profile: 'Profile',
|
|
139
|
+
account: 'Account settings',
|
|
140
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { clsx, type ClassValue } from "clsx"
|
|
2
|
+
import { twMerge } from "tailwind-merge"
|
|
3
|
+
|
|
4
|
+
export function cn(...inputs: ClassValue[]) {
|
|
5
|
+
return twMerge(clsx(inputs))
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Re-export tooltip helpers
|
|
9
|
+
export * from './tooltip-helpers';
|
|
10
|
+
export * from './metric-engine';
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useAuth } from '../hooks/useAuth';
|
|
3
|
+
import { Button } from '../../../atoms/ui/button';
|
|
4
|
+
import { Input } from '../../../atoms/ui/input';
|
|
5
|
+
import { Label } from '../../../atoms/ui/label';
|
|
6
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../../atoms/ui/card';
|
|
7
|
+
import { cn } from '../../../atoms/utils/utils';
|
|
8
|
+
import { Database, Shield, TrendingUp, Mail, Lock, Sparkles } from 'lucide-react';
|
|
9
|
+
|
|
10
|
+
export function LoginForm() {
|
|
11
|
+
const [email, setEmail] = useState('');
|
|
12
|
+
const [password, setPassword] = useState('');
|
|
13
|
+
const [error, setError] = useState('');
|
|
14
|
+
const { login, isLoading } = useAuth();
|
|
15
|
+
|
|
16
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
setError('');
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
await login({ email, password });
|
|
22
|
+
} catch (err) {
|
|
23
|
+
setError(err instanceof Error ? err.message : 'Login failed');
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="min-h-screen relative overflow-hidden bg-gradient-to-br from-muted/20 via-background to-muted/40">
|
|
29
|
+
{/* Background Pattern */}
|
|
30
|
+
<div className="absolute inset-0 bg-muted/5 opacity-5" />
|
|
31
|
+
|
|
32
|
+
{/* Floating Elements */}
|
|
33
|
+
<div className="absolute top-20 left-20 w-32 h-32 bg-primary/10 rounded-full blur-xl animate-pulse" />
|
|
34
|
+
<div className="absolute top-40 right-32 w-24 h-24 bg-secondary/10 rounded-full blur-lg animate-pulse delay-1000" />
|
|
35
|
+
<div className="absolute bottom-40 left-32 w-40 h-40 bg-accent/10 rounded-full blur-2xl animate-pulse delay-500" />
|
|
36
|
+
|
|
37
|
+
<div className="min-h-screen flex items-center justify-center p-4 relative z-10">
|
|
38
|
+
<div className="w-full max-w-md space-y-8">
|
|
39
|
+
{/* Header */}
|
|
40
|
+
<div className="text-center space-y-6 animate-slide-up">
|
|
41
|
+
<div className="flex justify-center">
|
|
42
|
+
<div className="relative">
|
|
43
|
+
<div className="w-16 h-16 bg-gradient-to-br from-primary to-secondary rounded flex items-center justify-center shadow">
|
|
44
|
+
<Database className="w-8 h-8 text-primary-foreground" />
|
|
45
|
+
</div>
|
|
46
|
+
<div className="absolute -top-1 -right-1 w-6 h-6 bg-gradient-to-br from-accent to-accent-foreground rounded-full flex items-center justify-center">
|
|
47
|
+
<Sparkles className="w-3 h-3 text-accent-foreground" />
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div className="space-y-2">
|
|
53
|
+
<h1 className="text-3xl font-bold bg-gradient-to-r from-foreground via-primary to-secondary bg-clip-text text-transparent">
|
|
54
|
+
Data Trust Navigator
|
|
55
|
+
</h1>
|
|
56
|
+
<p className="text-muted-foreground text-lg">
|
|
57
|
+
Explore, analyze, and trust your data ecosystem
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
{/* Login Card */}
|
|
63
|
+
<Card className="card-container animate-slide-up border-0 shadow bg-background/80 backdrop-blur-sm">
|
|
64
|
+
<CardHeader className="text-center pb-4">
|
|
65
|
+
<CardTitle className="text-xl font-semibold">Welcome Back</CardTitle>
|
|
66
|
+
<CardDescription>
|
|
67
|
+
Sign in to access your analytics dashboard
|
|
68
|
+
</CardDescription>
|
|
69
|
+
</CardHeader>
|
|
70
|
+
|
|
71
|
+
<CardContent className="space-y-6">
|
|
72
|
+
<form onSubmit={handleSubmit} className="space-y-5">
|
|
73
|
+
<div className="space-y-2">
|
|
74
|
+
<Label htmlFor="email" className="text-data-label">Email Address</Label>
|
|
75
|
+
<div className="relative">
|
|
76
|
+
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
|
|
77
|
+
<Input
|
|
78
|
+
id="email"
|
|
79
|
+
type="email"
|
|
80
|
+
value={email}
|
|
81
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
82
|
+
required
|
|
83
|
+
placeholder="Enter your email"
|
|
84
|
+
className="pl-10 h-11 transition-all duration-200 focus:ring-2 focus:ring-ring focus:border-ring"
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div className="space-y-2">
|
|
90
|
+
<Label htmlFor="password" className="text-data-label">Password</Label>
|
|
91
|
+
<div className="relative">
|
|
92
|
+
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
|
|
93
|
+
<Input
|
|
94
|
+
id="password"
|
|
95
|
+
type="password"
|
|
96
|
+
value={password}
|
|
97
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
98
|
+
required
|
|
99
|
+
placeholder="Enter your password"
|
|
100
|
+
className="pl-10 h-11 transition-all duration-200 focus:ring-2 focus:ring-ring focus:border-ring"
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{error && (
|
|
106
|
+
<div className="bg-destructive/10 border border-destructive/20 text-destructive text-sm p-3 rounded animate-shake">
|
|
107
|
+
{error}
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
<Button
|
|
112
|
+
type="submit"
|
|
113
|
+
className={cn(
|
|
114
|
+
"w-full h-11 bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-primary-foreground font-medium transition-all duration-200 hover:shadow-lg hover:scale-[1.02] active:scale-[0.98]"
|
|
115
|
+
)}
|
|
116
|
+
disabled={isLoading}
|
|
117
|
+
>
|
|
118
|
+
{isLoading ? (
|
|
119
|
+
<div className="flex items-center gap-2">
|
|
120
|
+
<div className="w-4 h-4 border-2 border-primary-foreground/30 border-t-primary-foreground rounded-full animate-spin" />
|
|
121
|
+
Signing in...
|
|
122
|
+
</div>
|
|
123
|
+
) : (
|
|
124
|
+
<div className="flex items-center gap-2">
|
|
125
|
+
<Shield className="w-4 h-4" />
|
|
126
|
+
Sign In
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
</Button>
|
|
130
|
+
</form>
|
|
131
|
+
|
|
132
|
+
{/* Demo Notice */}
|
|
133
|
+
<div className="bg-muted/50 border border-border rounded p-4">
|
|
134
|
+
<div className="flex items-start gap-3">
|
|
135
|
+
<div className="w-6 h-6 bg-primary rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
|
|
136
|
+
<TrendingUp className="w-3 h-3 text-primary-foreground" />
|
|
137
|
+
</div>
|
|
138
|
+
<div className="space-y-1">
|
|
139
|
+
<p className="text-sm font-medium text-foreground">Demo Environment</p>
|
|
140
|
+
<p className="text-xs text-muted-foreground">
|
|
141
|
+
Use any email and password to explore the analytics platform
|
|
142
|
+
</p>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</CardContent>
|
|
147
|
+
</Card>
|
|
148
|
+
|
|
149
|
+
{/* Features */}
|
|
150
|
+
<div className="grid grid-cols-3 gap-4 animate-slide-up">
|
|
151
|
+
{[
|
|
152
|
+
{ icon: Database, label: 'Data Models', bgClass: 'bg-primary/10', textClass: 'text-primary' },
|
|
153
|
+
{ icon: Shield, label: 'Quality Tests', bgClass: 'bg-secondary/10', textClass: 'text-secondary-foreground' },
|
|
154
|
+
{ icon: TrendingUp, label: 'Analytics', bgClass: 'bg-accent/10', textClass: 'text-accent-foreground' }
|
|
155
|
+
].map((feature) => (
|
|
156
|
+
<div key={feature.label} className="text-center space-y-2">
|
|
157
|
+
<div className={cn('w-10 h-10 rounded-lg flex items-center justify-center mx-auto', feature.bgClass)}>
|
|
158
|
+
<feature.icon className={cn('w-5 h-5', feature.textClass)} />
|
|
159
|
+
</div>
|
|
160
|
+
<p className="text-xs text-muted-foreground font-medium">{feature.label}</p>
|
|
161
|
+
</div>
|
|
162
|
+
))}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useAuth } from '../hooks/useAuth';
|
|
2
|
+
import { Button } from '../../../atoms/ui/button';
|
|
3
|
+
|
|
4
|
+
export function LogoutButton() {
|
|
5
|
+
const { logout, user } = useAuth();
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<div className="flex items-center gap-2">
|
|
9
|
+
<span className="text-sm text-muted-foreground">Welcome, {(user as any)?.name || user?.email}</span>
|
|
10
|
+
<Button
|
|
11
|
+
variant="outline"
|
|
12
|
+
size="sm"
|
|
13
|
+
onClick={logout}
|
|
14
|
+
>
|
|
15
|
+
Logout
|
|
16
|
+
</Button>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { useAuth } from '../hooks/useAuth';
|
|
3
|
+
import { LoginForm } from './LoginForm';
|
|
4
|
+
|
|
5
|
+
interface ProtectedRouteProps {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
requirePermission?: string;
|
|
8
|
+
requireRole?: string;
|
|
9
|
+
fallback?: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ProtectedRoute({
|
|
13
|
+
children,
|
|
14
|
+
requirePermission,
|
|
15
|
+
requireRole,
|
|
16
|
+
fallback
|
|
17
|
+
}: ProtectedRouteProps) {
|
|
18
|
+
const { isAuthenticated, isLoading, hasPermission, hasRole } = useAuth();
|
|
19
|
+
|
|
20
|
+
if (isLoading) {
|
|
21
|
+
return (
|
|
22
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
23
|
+
<div className="text-center">
|
|
24
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto"></div>
|
|
25
|
+
<p className="mt-2 text-muted-foreground">Loading...</p>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!isAuthenticated) {
|
|
32
|
+
return <LoginForm />;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check permission if required
|
|
36
|
+
if (requirePermission && !hasPermission(requirePermission)) {
|
|
37
|
+
return fallback || (
|
|
38
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
39
|
+
<div className="text-center">
|
|
40
|
+
<h2 className="text-xl font-semibold text-destructive mb-2">Access Denied</h2>
|
|
41
|
+
<p className="text-muted-foreground">You don't have permission to access this resource.</p>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check role if required
|
|
48
|
+
if (requireRole && !hasRole(requireRole)) {
|
|
49
|
+
return fallback || (
|
|
50
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
51
|
+
<div className="text-center">
|
|
52
|
+
<h2 className="text-xl font-semibold text-destructive mb-2">Access Denied</h2>
|
|
53
|
+
<p className="text-muted-foreground">Your role doesn't have access to this resource.</p>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return <>{children}</>;
|
|
60
|
+
}
|