@goplusvn/core 0.1.0 → 0.1.1
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/package.json +2 -1
- package/src/assets/erp_wallpaper.png +0 -0
- package/src/assets/goeat_logo.png +0 -0
- package/src/audit/audit-manager.ts +139 -0
- package/src/audit/index.ts +11 -0
- package/src/audit/memory-audit-logger.ts +86 -0
- package/src/audit/types.ts +50 -0
- package/src/auth/auth-service.ts +97 -0
- package/src/auth/index.ts +266 -0
- package/src/code-generation/index.ts +69 -0
- package/src/configs/auth-routes.ts +17 -0
- package/src/configs/crud.ts +136 -0
- package/src/configs/data/navigations.ts +781 -0
- package/src/configs/data/oauth-links.ts +10 -0
- package/src/configs/entities/material-categories.config.ts +125 -0
- package/src/configs/i18n.ts +12 -0
- package/src/configs/index.ts +26 -0
- package/src/configs/status.ts +25 -0
- package/src/configs/themes.ts +100 -0
- package/src/crud/components/crud-bulk-actions.tsx +91 -0
- package/src/crud/components/crud-card-view.tsx +241 -0
- package/src/crud/components/crud-context.tsx +122 -0
- package/src/crud/components/crud-delete-dialog.tsx +145 -0
- package/src/crud/components/crud-dialog.tsx +406 -0
- package/src/crud/components/crud-empty-state.tsx +104 -0
- package/src/crud/components/crud-export-button.tsx +170 -0
- package/src/crud/components/crud-field-renderer.tsx +653 -0
- package/src/crud/components/crud-filter-chips.tsx +102 -0
- package/src/crud/components/crud-filters/checkbox-filter.tsx +97 -0
- package/src/crud/components/crud-filters/datetime-filter.tsx +83 -0
- package/src/crud/components/crud-filters/filter-builder.tsx +66 -0
- package/src/crud/components/crud-filters/index.tsx +76 -0
- package/src/crud/components/crud-filters/radio-filter.tsx +86 -0
- package/src/crud/components/crud-filters/select-filter.tsx +141 -0
- package/src/crud/components/crud-filters/text-filter.tsx +86 -0
- package/src/crud/components/crud-form.tsx +642 -0
- package/src/crud/components/crud-import-dialog.tsx +440 -0
- package/src/crud/components/crud-infinite-scroll.tsx +116 -0
- package/src/crud/components/crud-page.tsx +1017 -0
- package/src/crud/components/crud-provider.tsx +277 -0
- package/src/crud/components/crud-row-actions.tsx +189 -0
- package/src/crud/components/crud-search.tsx +82 -0
- package/src/crud/components/crud-sheet.tsx +336 -0
- package/src/crud/components/crud-table-skeleton.tsx +26 -0
- package/src/crud/components/crud-table-toolbar.tsx +91 -0
- package/src/crud/components/crud-table.tsx +352 -0
- package/src/crud/components/crud-virtual-table.tsx +55 -0
- package/src/crud/components/index.tsx +20 -0
- package/src/crud/crud-filters/checkbox-filter.tsx +87 -0
- package/src/crud/crud-filters/datetime-filter.tsx +82 -0
- package/src/crud/crud-filters/filter-builder.tsx +64 -0
- package/src/crud/crud-filters/index.tsx +78 -0
- package/src/crud/crud-filters/radio-filter.tsx +79 -0
- package/src/crud/crud-filters/select-filter.tsx +148 -0
- package/src/crud/crud-filters/text-filter.tsx +81 -0
- package/src/crud/index.ts +43 -0
- package/src/crud/lib/crud-service.test.ts +334 -0
- package/src/crud/lib/crud-service.ts +358 -0
- package/src/crud/lib/crud-utils.test.ts +354 -0
- package/src/crud/lib/crud-utils.ts +299 -0
- package/src/crud/lib/crud-validator.ts +247 -0
- package/src/crud/lib/data-loader.ts +234 -0
- package/src/crud/lib/field-calculator.ts +241 -0
- package/src/crud/lib/field-formatter.ts +240 -0
- package/src/crud/lib/import-export-service.test.ts +290 -0
- package/src/crud/lib/import-export-service.ts +352 -0
- package/src/crud/lib/import-server-utils.ts +109 -0
- package/src/crud/lib/lazy-loader.ts +241 -0
- package/src/crud/lib/parse-filters.ts +85 -0
- package/src/crud/lib/permissions.ts +52 -0
- package/src/crud/lib/serialize-config.ts +60 -0
- package/src/crud/lib/stream-loader.ts +145 -0
- package/src/crud/lib/translate-config.ts +335 -0
- package/src/crud/lib/types.ts +11 -0
- package/src/crud/pages/entity-crud-page.tsx +144 -0
- package/src/crud/server.ts +8 -0
- package/src/home/constants.tsx +142 -0
- package/src/home/feature-showcase.tsx +171 -0
- package/src/home/home-page.tsx +191 -0
- package/src/home/hooks/index.ts +1 -0
- package/src/home/hooks/useWidgetPreferences.ts +167 -0
- package/src/home/index.ts +33 -0
- package/src/home/quick-access-dialog.tsx +271 -0
- package/src/home/quick-access-menu.tsx +267 -0
- package/src/home/types.ts +140 -0
- package/src/home/welcome-card.tsx +92 -0
- package/src/home/widget-container.tsx +258 -0
- package/src/home/widgets/base-widget.tsx +200 -0
- package/src/home/widgets/customers-widget.tsx +74 -0
- package/src/home/widgets/index.ts +6 -0
- package/src/home/widgets/orders-widget.tsx +87 -0
- package/src/home/widgets/revenue-widget.tsx +71 -0
- package/src/home/widgets/stock-widget.tsx +109 -0
- package/src/hooks/index.tsx +598 -0
- package/src/hooks/use-tenant.test.tsx +30 -0
- package/src/hooks/use-tenant.ts +5 -0
- package/src/index.ts +17 -0
- package/src/infrastructure/__tests__/architecture-verification.spec.ts +103 -0
- package/src/infrastructure/api-service.ts +317 -0
- package/src/infrastructure/cache/cache-manager.ts +107 -0
- package/src/infrastructure/cache/cache.ts +120 -0
- package/src/infrastructure/cache/index.ts +8 -0
- package/src/infrastructure/cache/types.ts +48 -0
- package/src/infrastructure/cron/cron-manager.ts +239 -0
- package/src/infrastructure/cron/index.ts +6 -0
- package/src/infrastructure/cron/types.ts +41 -0
- package/src/infrastructure/event-bus/event-bus.ts +145 -0
- package/src/infrastructure/event-bus/index.ts +2 -0
- package/src/infrastructure/event-bus/types.ts +22 -0
- package/src/infrastructure/index.ts +32 -0
- package/src/infrastructure/lock/decorators.ts +67 -0
- package/src/infrastructure/lock/index.ts +2 -0
- package/src/infrastructure/lock/lock-manager.ts +33 -0
- package/src/infrastructure/logger/index.ts +2 -0
- package/src/infrastructure/logger/logger.ts +96 -0
- package/src/infrastructure/logger/types.ts +25 -0
- package/src/layout/index.tsx +185 -0
- package/src/navigation/index.ts +91 -0
- package/src/notification/index.ts +14 -0
- package/src/notification/notification-service.ts +120 -0
- package/src/notification/storage/in-memory.ts +56 -0
- package/src/notification/storage/index.ts +1 -0
- package/src/notification/types.ts +51 -0
- package/src/organization/branch-service.ts +299 -0
- package/src/organization/branches.config.ts +154 -0
- package/src/organization/index.ts +5 -0
- package/src/plugin/apps-registry.ts +97 -0
- package/src/plugin/index.ts +5 -0
- package/src/plugin/types.ts +41 -0
- package/src/providers/index.tsx +109 -0
- package/src/providers/tenant-provider.tsx +45 -0
- package/src/rbac/components/roles/role-card.tsx +158 -0
- package/src/rbac/components/roles/role-stats-cards.tsx +29 -0
- package/src/rbac/components/roles/role-toolbar.tsx +123 -0
- package/src/rbac/hooks/use-role-operations.ts +159 -0
- package/src/rbac/hooks/use-roles-data.ts +59 -0
- package/src/rbac/index.ts +297 -0
- package/src/rbac/lib/permission-helpers.ts +63 -0
- package/src/rbac/pages/action-list-page.tsx +25 -0
- package/src/rbac/pages/resource-list-page.tsx +25 -0
- package/src/rbac/pages/role-list-page.tsx +378 -0
- package/src/rbac/permission-service.ts +140 -0
- package/src/rbac/permissions.ts +135 -0
- package/src/rbac/resource-service.ts +115 -0
- package/src/rbac/resource-validator.ts +119 -0
- package/src/rbac/role-service.ts +165 -0
- package/src/rbac/server.ts +16 -0
- package/src/rbac/types.ts +38 -0
- package/src/schemas/action.schema.ts +66 -0
- package/src/schemas/branch.schema.ts +52 -0
- package/src/schemas/coming-soon-schema.ts +9 -0
- package/src/schemas/company.schema.ts +44 -0
- package/src/schemas/forgot-passward-schema.ts +9 -0
- package/src/schemas/index.ts +30 -0
- package/src/schemas/material-category.schema.ts +43 -0
- package/src/schemas/material-pricing.schema.ts +74 -0
- package/src/schemas/material.schema.ts +76 -0
- package/src/schemas/materials.ts +52 -0
- package/src/schemas/new-passward-schema.ts +15 -0
- package/src/schemas/partner-company.schema.ts +149 -0
- package/src/schemas/register-schema.ts +36 -0
- package/src/schemas/resource.schema.ts +133 -0
- package/src/schemas/role.schema.ts +11 -0
- package/src/schemas/sign-in-schema.ts +24 -0
- package/src/schemas/supplier-pricing.schema.ts +15 -0
- package/src/schemas/supplier.schema.ts +120 -0
- package/src/schemas/system-category-group.schema.ts +67 -0
- package/src/schemas/system-category.schema.ts +77 -0
- package/src/schemas/system-config.schema.ts +118 -0
- package/src/schemas/uom.schema.ts +75 -0
- package/src/schemas/user-supplier.schema.ts +179 -0
- package/src/schemas/user.schema.ts +18 -0
- package/src/schemas/verify-email-schema.ts +9 -0
- package/src/schemas/warehouse.schema.ts +49 -0
- package/src/system/components/categories/category-list.tsx +529 -0
- package/src/system/components/categories/category-manager.tsx +89 -0
- package/src/system/components/categories/group-sidebar.tsx +308 -0
- package/src/system/components/settings/setting-dialogs.tsx +197 -0
- package/src/system/components/settings/setting-field.tsx +291 -0
- package/src/system/components/settings/setting-form-dialog.tsx +308 -0
- package/src/system/components/settings/settings-groups.ts +80 -0
- package/src/system/components/settings/settings-search.tsx +71 -0
- package/src/system/components/settings/settings-section.tsx +74 -0
- package/src/system/components/settings/settings-sidebar.tsx +81 -0
- package/src/system/constants.ts +3 -0
- package/src/system/index.ts +150 -0
- package/src/system/job-manager.ts +176 -0
- package/src/system/pages/components/categories/category-list.tsx +537 -0
- package/src/system/pages/components/categories/category-manager.tsx +90 -0
- package/src/system/pages/components/categories/group-sidebar.tsx +311 -0
- package/src/system/pages/components/settings/sales-rules-settings.tsx +222 -0
- package/src/system/pages/components/settings/setting-dialogs.tsx +197 -0
- package/src/system/pages/components/settings/setting-field.tsx +292 -0
- package/src/system/pages/components/settings/setting-form-dialog.tsx +308 -0
- package/src/system/pages/components/settings/settings-groups.ts +87 -0
- package/src/system/pages/components/settings/settings-page.tsx +372 -0
- package/src/system/pages/components/settings/settings-search.tsx +71 -0
- package/src/system/pages/components/settings/settings-section.tsx +74 -0
- package/src/system/pages/components/settings/settings-sidebar.tsx +81 -0
- package/src/system/pages/components/settings/system-settings.tsx +244 -0
- package/src/system/pages/system-category-page.tsx +15 -0
- package/src/system/pages/system-settings-page.tsx +380 -0
- package/src/system/schemas/system-category-group.schema.ts +46 -0
- package/src/system/schemas/system-category.schema.ts +56 -0
- package/src/system/services/settings-service.ts +127 -0
- package/src/system/services/system-category-service.ts +63 -0
- package/src/system/types.ts +45 -0
- package/src/types/index.ts +703 -0
- package/src/ui/auth/auth-layout.tsx +135 -0
- package/src/ui/auth/forgot-password-form.tsx +98 -0
- package/src/ui/auth/index.tsx +7 -0
- package/src/ui/auth/new-password-form.tsx +107 -0
- package/src/ui/auth/oauth-links.tsx +30 -0
- package/src/ui/auth/register-form.tsx +202 -0
- package/src/ui/auth/sign-in-form.tsx +238 -0
- package/src/ui/auth/verify-email-form.tsx +104 -0
- package/src/ui/crud/index.tsx +10 -0
- package/src/ui/data-display/accordion.tsx +65 -0
- package/src/ui/data-display/aspect-ratio.tsx +11 -0
- package/src/ui/data-display/avatar.tsx +163 -0
- package/src/ui/data-display/bento-grid.tsx +77 -0
- package/src/ui/data-display/carousel.tsx +249 -0
- package/src/ui/data-display/chart.tsx +363 -0
- package/src/ui/data-display/code-block-highlight.tsx +54 -0
- package/src/ui/data-display/collapsible.tsx +42 -0
- package/src/ui/data-display/compact-stat-bar.tsx +149 -0
- package/src/ui/data-display/data-table/data-table-context.tsx +255 -0
- package/src/ui/data-display/data-table/data-table-empty-state.tsx +133 -0
- package/src/ui/data-display/data-table/data-table-skeleton.tsx +145 -0
- package/src/ui/data-display/data-table/data-table-toolbar.tsx +353 -0
- package/src/ui/data-display/data-table/data-table.tsx +597 -0
- package/src/ui/data-display/data-table/index.ts +44 -0
- package/src/ui/data-display/data-table-column-header.tsx +75 -0
- package/src/ui/data-display/data-table-pagination.tsx +130 -0
- package/src/ui/data-display/data-table-view-options.tsx +59 -0
- package/src/ui/data-display/formatted-number-input.tsx +210 -0
- package/src/ui/data-display/highlight.tsx +20 -0
- package/src/ui/data-display/hover-card.tsx +48 -0
- package/src/ui/data-display/index.tsx +50 -0
- package/src/ui/data-display/iphone-15-pro.tsx +114 -0
- package/src/ui/data-display/kanban/index.ts +4 -0
- package/src/ui/data-display/kanban/kanban-board.tsx +192 -0
- package/src/ui/data-display/kanban/kanban-column.tsx +74 -0
- package/src/ui/data-display/kanban/kanban-item.tsx +50 -0
- package/src/ui/data-display/kanban/kanban-types.ts +21 -0
- package/src/ui/data-display/kpi-card.tsx +68 -0
- package/src/ui/data-display/media-grid.tsx +110 -0
- package/src/ui/data-display/safari.tsx +175 -0
- package/src/ui/data-display/show-more-text.tsx +55 -0
- package/src/ui/data-display/tabs.tsx +68 -0
- package/src/ui/data-display/timeline.tsx +256 -0
- package/src/ui/feedback/alert.tsx +60 -0
- package/src/ui/feedback/context-menu.tsx +245 -0
- package/src/ui/feedback/drawer.tsx +132 -0
- package/src/ui/feedback/error-dialog.tsx +273 -0
- package/src/ui/feedback/index.tsx +183 -0
- package/src/ui/feedback/progress.tsx +32 -0
- package/src/ui/feedback/sheet.tsx +148 -0
- package/src/ui/feedback/sonner.tsx +36 -0
- package/src/ui/forms/command.tsx +157 -0
- package/src/ui/forms/date-picker.tsx +73 -0
- package/src/ui/forms/date-range-picker.tsx +76 -0
- package/src/ui/forms/date-time-picker.tsx +109 -0
- package/src/ui/forms/editor/editor-menu-bar.tsx +394 -0
- package/src/ui/forms/editor/index.tsx +130 -0
- package/src/ui/forms/editor/multi-select-example.tsx +1234 -0
- package/src/ui/forms/emoji-picker.tsx +109 -0
- package/src/ui/forms/file-dropzone.tsx +169 -0
- package/src/ui/forms/file-thumbnail.tsx +29 -0
- package/src/ui/forms/index.tsx +201 -0
- package/src/ui/forms/input-file.tsx +99 -0
- package/src/ui/forms/input-group.tsx +46 -0
- package/src/ui/forms/input-otp.tsx +81 -0
- package/src/ui/forms/input-phone.tsx +172 -0
- package/src/ui/forms/input-spin.tsx +116 -0
- package/src/ui/forms/input-tags.tsx +219 -0
- package/src/ui/forms/input-time.tsx +42 -0
- package/src/ui/forms/multi-select.tsx +629 -0
- package/src/ui/forms/multiple-date-picker.tsx +74 -0
- package/src/ui/forms/radio-group.tsx +42 -0
- package/src/ui/forms/rating.tsx +158 -0
- package/src/ui/forms/time-picker.tsx +57 -0
- package/src/ui/index.tsx +17 -0
- package/src/ui/layout/animated-list.tsx +77 -0
- package/src/ui/layout/animated-sidebar.tsx +294 -0
- package/src/ui/layout/command-menu.tsx +355 -0
- package/src/ui/layout/customizer.tsx +324 -0
- package/src/ui/layout/footer.tsx +43 -0
- package/src/ui/layout/full-screen-toggle.tsx +52 -0
- package/src/ui/layout/header-breadcrumb.tsx +77 -0
- package/src/ui/layout/horizontal-layout-header.tsx +83 -0
- package/src/ui/layout/horizontal-layout.tsx +50 -0
- package/src/ui/layout/index.tsx +25 -0
- package/src/ui/layout/language-dropdown.tsx +103 -0
- package/src/ui/layout/logo.tsx +63 -0
- package/src/ui/layout/main-layout.tsx +57 -0
- package/src/ui/layout/mode-dropdown.tsx +58 -0
- package/src/ui/layout/notification-dropdown.tsx +127 -0
- package/src/ui/layout/page-tabs.tsx +306 -0
- package/src/ui/layout/route-cache.tsx +214 -0
- package/src/ui/layout/sidebar-group-icon-menu.tsx +195 -0
- package/src/ui/layout/sidebar.tsx +279 -0
- package/src/ui/layout/tab-content-cache.tsx +201 -0
- package/src/ui/layout/tab-navigation-provider.tsx +536 -0
- package/src/ui/layout/toggle-mobile-sidebar.tsx +33 -0
- package/src/ui/layout/top-bar-header-menubar.tsx +412 -0
- package/src/ui/layout/user-dropdown.tsx +188 -0
- package/src/ui/layout/vertical-layout-header.tsx +65 -0
- package/src/ui/layout/vertical-layout.tsx +47 -0
- package/src/ui/management/audit-log-page.tsx +209 -0
- package/src/ui/management/cache-management.tsx +349 -0
- package/src/ui/management/index.ts +3 -0
- package/src/ui/management/job-management.tsx +308 -0
- package/src/ui/pages/not-found.tsx +30 -0
- package/src/ui/primitives/badge.tsx +66 -0
- package/src/ui/primitives/breadcrumb.tsx +103 -0
- package/src/ui/primitives/button.tsx +129 -0
- package/src/ui/primitives/calendar.tsx +74 -0
- package/src/ui/primitives/card.tsx +86 -0
- package/src/ui/primitives/checkbox.tsx +31 -0
- package/src/ui/primitives/client.ts +30 -0
- package/src/ui/primitives/combobox.tsx +290 -0
- package/src/ui/primitives/dialog.tsx +121 -0
- package/src/ui/primitives/dropdown-menu.tsx +239 -0
- package/src/ui/primitives/dynamic-icon.tsx +24 -0
- package/src/ui/primitives/index.tsx +134 -0
- package/src/ui/primitives/input-number.tsx +131 -0
- package/src/ui/primitives/input.tsx +22 -0
- package/src/ui/primitives/keyboard.tsx +23 -0
- package/src/ui/primitives/label.tsx +24 -0
- package/src/ui/primitives/menubar.tsx +262 -0
- package/src/ui/primitives/navigation-menu.tsx +157 -0
- package/src/ui/primitives/pagination.tsx +118 -0
- package/src/ui/primitives/popover.tsx +56 -0
- package/src/ui/primitives/prefetch-link.tsx +60 -0
- package/src/ui/primitives/resizable.tsx +59 -0
- package/src/ui/primitives/scroll-area.tsx +63 -0
- package/src/ui/primitives/select.tsx +172 -0
- package/src/ui/primitives/separator.tsx +51 -0
- package/src/ui/primitives/sidebar.tsx +844 -0
- package/src/ui/primitives/slider.tsx +27 -0
- package/src/ui/primitives/status-badge.tsx +47 -0
- package/src/ui/primitives/sticky-layout.tsx +50 -0
- package/src/ui/primitives/switch.tsx +29 -0
- package/src/ui/primitives/table.tsx +116 -0
- package/src/ui/primitives/tabs.tsx +55 -0
- package/src/ui/primitives/toggle-group.tsx +70 -0
- package/src/ui/primitives/toggle.tsx +47 -0
- package/src/ui/primitives/tooltip.tsx +59 -0
- package/src/user/components/dangerous-zone.tsx +34 -0
- package/src/user/components/delete-account-form.tsx +40 -0
- package/src/user/components/index.ts +4 -0
- package/src/user/components/profile-info-form.tsx +390 -0
- package/src/user/components/profile-info.tsx +32 -0
- package/src/user/components/unified-profile-dialog.tsx +1019 -0
- package/src/user/components/user-stats.tsx +27 -0
- package/src/user/components/user-toolbar.tsx +137 -0
- package/src/user/components/users-card-view.tsx +253 -0
- package/src/user/index.ts +11 -0
- package/src/user/pages/user-list-page.tsx +234 -0
- package/src/user/pages/users-client-page.tsx +385 -0
- package/src/user/profile-page.tsx +19 -0
- package/src/user/schemas.ts +68 -0
- package/src/user/types.ts +34 -0
- package/src/user/user-service.ts +538 -0
- package/src/utils/index.ts +906 -0
- package/src/workflow/activity-timeline.tsx +412 -0
- package/src/workflow/approval-workflow.tsx +31 -0
- package/src/workflow/index.ts +2 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useEffect, useState } from "react";
|
|
4
|
+
import useEmblaCarousel from "embla-carousel-react";
|
|
5
|
+
import Autoplay from "embla-carousel-autoplay";
|
|
6
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
7
|
+
import { cn } from "../utils";
|
|
8
|
+
import { ERP_FEATURES } from "./types";
|
|
9
|
+
import type { ERPFeature } from "./types";
|
|
10
|
+
|
|
11
|
+
interface FeatureShowcaseProps {
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function FeatureShowcase({ className }: FeatureShowcaseProps) {
|
|
16
|
+
const [emblaRef, emblaApi] = useEmblaCarousel(
|
|
17
|
+
{ loop: true, align: "start" },
|
|
18
|
+
[Autoplay({ delay: 5000, stopOnInteraction: true })],
|
|
19
|
+
);
|
|
20
|
+
const [prevBtnEnabled, setPrevBtnEnabled] = useState(false);
|
|
21
|
+
const [nextBtnEnabled, setNextBtnEnabled] = useState(false);
|
|
22
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
23
|
+
const [scrollSnaps, setScrollSnaps] = useState<number[]>([]);
|
|
24
|
+
|
|
25
|
+
const scrollPrev = useCallback(
|
|
26
|
+
() => emblaApi && emblaApi.scrollPrev(),
|
|
27
|
+
[emblaApi],
|
|
28
|
+
);
|
|
29
|
+
const scrollNext = useCallback(
|
|
30
|
+
() => emblaApi && emblaApi.scrollNext(),
|
|
31
|
+
[emblaApi],
|
|
32
|
+
);
|
|
33
|
+
const scrollTo = useCallback(
|
|
34
|
+
(index: number) => emblaApi && emblaApi.scrollTo(index),
|
|
35
|
+
[emblaApi],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const onSelect = useCallback(() => {
|
|
39
|
+
if (!emblaApi) return;
|
|
40
|
+
setSelectedIndex(emblaApi.selectedScrollSnap());
|
|
41
|
+
setPrevBtnEnabled(emblaApi.canScrollPrev());
|
|
42
|
+
setNextBtnEnabled(emblaApi.canScrollNext());
|
|
43
|
+
}, [emblaApi]);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!emblaApi) return;
|
|
47
|
+
onSelect();
|
|
48
|
+
setScrollSnaps(emblaApi.scrollSnapList());
|
|
49
|
+
emblaApi.on("select", onSelect);
|
|
50
|
+
emblaApi.on("reInit", onSelect);
|
|
51
|
+
return () => {
|
|
52
|
+
emblaApi.off("select", onSelect);
|
|
53
|
+
emblaApi.off("reInit", onSelect);
|
|
54
|
+
};
|
|
55
|
+
}, [emblaApi, onSelect]);
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div
|
|
59
|
+
className={cn(
|
|
60
|
+
"bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 shadow-sm relative overflow-hidden flex flex-col h-full",
|
|
61
|
+
className,
|
|
62
|
+
)}
|
|
63
|
+
>
|
|
64
|
+
{/* Header */}
|
|
65
|
+
<div className="flex items-center justify-between px-6 py-4 border-b border-slate-100 dark:border-slate-700/50 bg-white/50 dark:bg-slate-800/50 backdrop-blur-sm z-20">
|
|
66
|
+
<div className="flex items-center gap-2">
|
|
67
|
+
<div className="bg-blue-100 dark:bg-blue-900/30 text-primary p-1 rounded-md">
|
|
68
|
+
<svg
|
|
69
|
+
className="w-[18px] h-[18px]"
|
|
70
|
+
viewBox="0 0 24 24"
|
|
71
|
+
fill="none"
|
|
72
|
+
stroke="currentColor"
|
|
73
|
+
strokeWidth="2"
|
|
74
|
+
>
|
|
75
|
+
<path d="M12 2v20M2 12h20M12 2a10 10 0 0 1 10 10 10 10 0 0 1-10 10 10 10 0 0 1-10-10 10 10 0 0 1 10-10z" />
|
|
76
|
+
</svg>
|
|
77
|
+
</div>
|
|
78
|
+
<span className="text-sm font-bold text-slate-700 dark:text-slate-200">
|
|
79
|
+
Feature Tour
|
|
80
|
+
</span>
|
|
81
|
+
</div>
|
|
82
|
+
<button className="text-xs font-medium text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-slate-200 px-3 py-1.5 rounded hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">
|
|
83
|
+
Skip Tour
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
{/* Carousel Content */}
|
|
88
|
+
<div
|
|
89
|
+
className="flex-1 relative overflow-hidden bg-slate-50/50 dark:bg-slate-800/50"
|
|
90
|
+
ref={emblaRef}
|
|
91
|
+
>
|
|
92
|
+
<div className="flex touch-pan-y h-full">
|
|
93
|
+
{ERP_FEATURES.map((feature, index) => (
|
|
94
|
+
<div key={feature.id} className="flex-[0_0_100%] min-w-0">
|
|
95
|
+
<div className="p-6 sm:p-8 flex flex-col items-center text-center gap-6 h-full justify-center">
|
|
96
|
+
<div className="mb-2 text-xs font-semibold text-primary uppercase tracking-wider">
|
|
97
|
+
Step {index + 1} of {ERP_FEATURES.length}
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div
|
|
101
|
+
className={cn(
|
|
102
|
+
"flex h-16 w-16 shrink-0 items-center justify-center rounded-2xl bg-gradient-to-br text-3xl shadow-md",
|
|
103
|
+
feature.color,
|
|
104
|
+
)}
|
|
105
|
+
>
|
|
106
|
+
{feature.icon}
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<h2 className="text-2xl md:text-3xl font-bold text-slate-900 dark:text-white">
|
|
110
|
+
{feature.title}
|
|
111
|
+
</h2>
|
|
112
|
+
|
|
113
|
+
<p className="text-slate-500 dark:text-slate-400 text-sm md:text-base leading-relaxed max-w-md">
|
|
114
|
+
{feature.description}
|
|
115
|
+
</p>
|
|
116
|
+
|
|
117
|
+
<div className="flex gap-2 justify-center flex-wrap">
|
|
118
|
+
{feature.subFeatures.map((sub) => (
|
|
119
|
+
<span
|
|
120
|
+
key={sub}
|
|
121
|
+
className="px-2 py-1 bg-white dark:bg-slate-700 border border-slate-200 dark:border-slate-600 rounded-md text-xs font-medium text-slate-600 dark:text-slate-300"
|
|
122
|
+
>
|
|
123
|
+
{sub}
|
|
124
|
+
</span>
|
|
125
|
+
))}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
))}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{/* Footer Navigation */}
|
|
134
|
+
<div className="px-6 py-4 bg-white dark:bg-slate-800 border-t border-slate-100 dark:border-slate-700 flex items-center justify-between z-20">
|
|
135
|
+
{/* Dots */}
|
|
136
|
+
<div className="flex gap-1.5">
|
|
137
|
+
{scrollSnaps.map((_, index) => (
|
|
138
|
+
<button
|
|
139
|
+
key={index}
|
|
140
|
+
className={cn(
|
|
141
|
+
"h-1.5 rounded-full transition-all duration-300",
|
|
142
|
+
index === selectedIndex
|
|
143
|
+
? "w-6 bg-primary"
|
|
144
|
+
: "w-1.5 bg-slate-300 dark:bg-slate-600",
|
|
145
|
+
)}
|
|
146
|
+
onClick={() => scrollTo(index)}
|
|
147
|
+
/>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{/* Buttons */}
|
|
152
|
+
<div className="flex gap-3">
|
|
153
|
+
<button
|
|
154
|
+
onClick={scrollPrev}
|
|
155
|
+
disabled={!prevBtnEnabled}
|
|
156
|
+
className="w-9 h-9 flex items-center justify-center rounded-lg border border-slate-200 dark:border-slate-700 text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
157
|
+
>
|
|
158
|
+
<ChevronLeft className="w-4 h-4" />
|
|
159
|
+
</button>
|
|
160
|
+
<button
|
|
161
|
+
onClick={scrollNext}
|
|
162
|
+
className="h-9 px-4 rounded-lg bg-primary text-white text-sm font-medium flex items-center gap-2 hover:bg-primary/90 transition-colors shadow-sm"
|
|
163
|
+
>
|
|
164
|
+
Next
|
|
165
|
+
<ChevronRight className="w-4 h-4" />
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useRouter } from "next/navigation";
|
|
4
|
+
import useSWR from "swr";
|
|
5
|
+
import { LayoutDashboard, Settings } from "lucide-react";
|
|
6
|
+
import { cn } from "../utils";
|
|
7
|
+
import { WelcomeCard } from "./welcome-card";
|
|
8
|
+
import { WidgetContainer } from "./widget-container";
|
|
9
|
+
import { FeatureShowcase } from "./feature-showcase";
|
|
10
|
+
import { QuickAccessMenu, DEFAULT_QUICK_ACCESS } from "./quick-access-menu";
|
|
11
|
+
import { useWidgetPreferences } from "./hooks/useWidgetPreferences";
|
|
12
|
+
import type { QuickAccessItem, WidgetConfig } from "./types";
|
|
13
|
+
|
|
14
|
+
interface HomePageProps {
|
|
15
|
+
userName?: string;
|
|
16
|
+
userId?: string;
|
|
17
|
+
widgetData?: {
|
|
18
|
+
revenue?: { total: number; change?: number };
|
|
19
|
+
orders?: {
|
|
20
|
+
total: number;
|
|
21
|
+
pending?: number;
|
|
22
|
+
completed?: number;
|
|
23
|
+
change?: number;
|
|
24
|
+
};
|
|
25
|
+
customers?: { total: number; newToday?: number; change?: number };
|
|
26
|
+
stock?: {
|
|
27
|
+
totalItems: number;
|
|
28
|
+
lowStockCount: number;
|
|
29
|
+
lowStockItems?: Array<{
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
quantity: number;
|
|
33
|
+
minStock: number;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
availableFeatures?: Array<{ label: string; href: string; icon?: string }>;
|
|
38
|
+
widgetDataLoading?: boolean;
|
|
39
|
+
showFeatureShowcase?: boolean;
|
|
40
|
+
className?: string;
|
|
41
|
+
basePath?: string;
|
|
42
|
+
dashboardApiUrl?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function HomePage({
|
|
46
|
+
userName = "Bạn",
|
|
47
|
+
userId,
|
|
48
|
+
widgetData: initialWidgetData,
|
|
49
|
+
availableFeatures,
|
|
50
|
+
widgetDataLoading = false,
|
|
51
|
+
showFeatureShowcase = false,
|
|
52
|
+
className,
|
|
53
|
+
basePath = "",
|
|
54
|
+
dashboardApiUrl,
|
|
55
|
+
}: HomePageProps & { dashboardApiUrl?: string }) {
|
|
56
|
+
const router = useRouter();
|
|
57
|
+
|
|
58
|
+
// ... hook logic remains ...
|
|
59
|
+
const { data: fetchedWidgetData, isLoading: isFetching } = useSWR(
|
|
60
|
+
initialWidgetData ? null : dashboardApiUrl,
|
|
61
|
+
async (url) => {
|
|
62
|
+
const res = await fetch(url);
|
|
63
|
+
if (!res.ok) throw new Error("Failed to fetch widget data");
|
|
64
|
+
return res.json();
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
revalidateOnFocus: false,
|
|
68
|
+
dedupingInterval: 60000, // 1 minute
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const widgetData = initialWidgetData || fetchedWidgetData;
|
|
73
|
+
const isLoading =
|
|
74
|
+
widgetDataLoading || (dashboardApiUrl && !initialWidgetData && isFetching);
|
|
75
|
+
|
|
76
|
+
const {
|
|
77
|
+
widgets,
|
|
78
|
+
quickAccess,
|
|
79
|
+
loading: prefsLoading,
|
|
80
|
+
updateWidgets,
|
|
81
|
+
updateQuickAccess,
|
|
82
|
+
} = useWidgetPreferences({
|
|
83
|
+
userId,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Filter out Home from available features to prevent adding it as a shortcut
|
|
87
|
+
// and to exclude it from defaults
|
|
88
|
+
const filteredFeatures = availableFeatures?.filter(
|
|
89
|
+
(f) =>
|
|
90
|
+
f.href !== "/home" && f.href !== basePath + "/home" && f.href !== "/",
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const handleQuickAccessClick = (item: QuickAccessItem) => {
|
|
94
|
+
router.push(`${basePath}${item.href}`);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const handleWidgetsChange = async (newWidgets: WidgetConfig[]) => {
|
|
98
|
+
await updateWidgets(newWidgets);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Calculate effective quick access items
|
|
102
|
+
// If user has saved preferences (length > 0), use them.
|
|
103
|
+
// Otherwise, if availableFeatures are provided, generate a default list from them (up to 5 items).
|
|
104
|
+
// Finally, fallback to DEFAULT_QUICK_ACCESS.
|
|
105
|
+
const getAuthorizedDefaults = (): QuickAccessItem[] => {
|
|
106
|
+
if (filteredFeatures && filteredFeatures.length > 0) {
|
|
107
|
+
const colors = [
|
|
108
|
+
"blue",
|
|
109
|
+
"green",
|
|
110
|
+
"purple",
|
|
111
|
+
"orange",
|
|
112
|
+
"teal",
|
|
113
|
+
"pink",
|
|
114
|
+
"indigo",
|
|
115
|
+
"red",
|
|
116
|
+
];
|
|
117
|
+
return filteredFeatures.slice(0, 5).map((feature, index) => ({
|
|
118
|
+
id: `default-${index}`,
|
|
119
|
+
label: feature.label,
|
|
120
|
+
href: feature.href,
|
|
121
|
+
icon: feature.icon || "document-text",
|
|
122
|
+
color: colors[index % colors.length],
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
// If no authorized features, return empty to avoid showing unauthorized default items
|
|
126
|
+
return [];
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const displayQuickAccess =
|
|
130
|
+
quickAccess && quickAccess.length > 0
|
|
131
|
+
? quickAccess
|
|
132
|
+
: getAuthorizedDefaults();
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div
|
|
136
|
+
className={cn(
|
|
137
|
+
"min-h-screen bg-slate-50/50 dark:bg-slate-900/50",
|
|
138
|
+
className,
|
|
139
|
+
)}
|
|
140
|
+
>
|
|
141
|
+
<div className="w-full mx-auto px-4 md:px-6 lg:px-8 py-6 space-y-8">
|
|
142
|
+
{/* Header Section */}
|
|
143
|
+
<WelcomeCard userName={userName} />
|
|
144
|
+
|
|
145
|
+
{/* My Workspace Section - Temporarily Hidden via separate prop or conditional */}
|
|
146
|
+
{/* Added explicit check to hide widgets if requested */}
|
|
147
|
+
{false && (
|
|
148
|
+
<div className="flex flex-col gap-4 animate-in fade-in slide-in-from-bottom-4 duration-500 delay-150">
|
|
149
|
+
<div className="flex items-center justify-between px-1">
|
|
150
|
+
<div className="flex items-center gap-2">
|
|
151
|
+
<LayoutDashboard className="w-5 h-5 text-primary" />
|
|
152
|
+
<h3 className="text-lg font-bold text-slate-900 dark:text-white">
|
|
153
|
+
My Workspace
|
|
154
|
+
</h3>
|
|
155
|
+
</div>
|
|
156
|
+
<button className="text-xs font-medium text-slate-500 hover:text-primary transition-colors flex items-center gap-1 group">
|
|
157
|
+
<Settings className="w-4 h-4 group-hover:rotate-180 transition-transform duration-500" />
|
|
158
|
+
Configure Widgets
|
|
159
|
+
</button>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<WidgetContainer
|
|
163
|
+
widgets={widgets}
|
|
164
|
+
onWidgetsChange={handleWidgetsChange}
|
|
165
|
+
widgetData={widgetData}
|
|
166
|
+
loading={isLoading || prefsLoading}
|
|
167
|
+
/>
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
|
|
171
|
+
{/* Quick Access Section */}
|
|
172
|
+
<div className="animate-in fade-in slide-in-from-bottom-4 duration-500 delay-300">
|
|
173
|
+
<QuickAccessMenu
|
|
174
|
+
items={displayQuickAccess}
|
|
175
|
+
onItemClick={handleQuickAccessClick}
|
|
176
|
+
editable={true}
|
|
177
|
+
onItemsChange={updateQuickAccess}
|
|
178
|
+
availableFeatures={filteredFeatures}
|
|
179
|
+
/>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
{/* Feature Tour Section - Hidden by default prop or explicit false */}
|
|
183
|
+
{showFeatureShowcase && (
|
|
184
|
+
<div className="animate-in fade-in slide-in-from-bottom-4 duration-500 delay-500 min-h-[400px]">
|
|
185
|
+
<FeatureShowcase />
|
|
186
|
+
</div>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useWidgetPreferences } from "./useWidgetPreferences";
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback } from "react";
|
|
4
|
+
import useSWR from "swr";
|
|
5
|
+
import type {
|
|
6
|
+
WidgetConfig,
|
|
7
|
+
UserHomePreferences,
|
|
8
|
+
QuickAccessItem,
|
|
9
|
+
} from "../types";
|
|
10
|
+
import { DEFAULT_WIDGETS } from "../types";
|
|
11
|
+
|
|
12
|
+
interface UseWidgetPreferencesOptions {
|
|
13
|
+
userId?: string;
|
|
14
|
+
apiEndpoint?: string;
|
|
15
|
+
fallbackToLocalStorage?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface UseWidgetPreferencesReturn {
|
|
19
|
+
widgets: WidgetConfig[];
|
|
20
|
+
quickAccess: QuickAccessItem[];
|
|
21
|
+
loading: boolean;
|
|
22
|
+
error: string | null;
|
|
23
|
+
updateWidgets: (widgets: WidgetConfig[]) => Promise<void>;
|
|
24
|
+
updateQuickAccess: (items: QuickAccessItem[]) => Promise<void>;
|
|
25
|
+
resetToDefaults: () => Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const LOCAL_STORAGE_KEY = "goerp_home_preferences";
|
|
29
|
+
|
|
30
|
+
async function fetchPreferences(url: string): Promise<UserHomePreferences> {
|
|
31
|
+
const res = await fetch(url);
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
throw new Error("Failed to fetch preferences");
|
|
34
|
+
}
|
|
35
|
+
return res.json();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useWidgetPreferences({
|
|
39
|
+
userId,
|
|
40
|
+
apiEndpoint = "/api/user/preferences",
|
|
41
|
+
fallbackToLocalStorage = false,
|
|
42
|
+
}: UseWidgetPreferencesOptions = {}): UseWidgetPreferencesReturn {
|
|
43
|
+
const [localPreferences, setLocalPreferences] =
|
|
44
|
+
useState<UserHomePreferences | null>(null);
|
|
45
|
+
|
|
46
|
+
// Use SWR for API fetching when userId is provided
|
|
47
|
+
const { data, error, isLoading, mutate } = useSWR<UserHomePreferences>(
|
|
48
|
+
userId ? `${apiEndpoint}?userId=${userId}` : null,
|
|
49
|
+
fetchPreferences,
|
|
50
|
+
{
|
|
51
|
+
revalidateOnFocus: false,
|
|
52
|
+
// Only use localPreferences as fallback if explicitly enabled
|
|
53
|
+
fallbackData: fallbackToLocalStorage
|
|
54
|
+
? localPreferences || undefined
|
|
55
|
+
: undefined,
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Use a user-specific key for localStorage to prevent data leakage between users
|
|
60
|
+
const storageKey = userId
|
|
61
|
+
? `${LOCAL_STORAGE_KEY}_${userId}`
|
|
62
|
+
: LOCAL_STORAGE_KEY;
|
|
63
|
+
|
|
64
|
+
// Load from localStorage ONLY if fallback is enabled
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (fallbackToLocalStorage && typeof window !== "undefined") {
|
|
67
|
+
try {
|
|
68
|
+
const stored = localStorage.getItem(storageKey);
|
|
69
|
+
if (stored) {
|
|
70
|
+
setLocalPreferences(JSON.parse(stored));
|
|
71
|
+
} else {
|
|
72
|
+
setLocalPreferences(null);
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// console.warn("Failed to load preferences from localStorage");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}, [fallbackToLocalStorage, storageKey]);
|
|
79
|
+
|
|
80
|
+
// Get the current preferences
|
|
81
|
+
const preferences = data ||
|
|
82
|
+
localPreferences || {
|
|
83
|
+
widgets: DEFAULT_WIDGETS,
|
|
84
|
+
quickAccess: [],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Save preferences
|
|
88
|
+
const savePreferences = useCallback(
|
|
89
|
+
async (newPreferences: UserHomePreferences) => {
|
|
90
|
+
// Optimistic update
|
|
91
|
+
mutate(newPreferences, false);
|
|
92
|
+
|
|
93
|
+
// Save to localStorage (only if enabled)
|
|
94
|
+
if (fallbackToLocalStorage && typeof window !== "undefined") {
|
|
95
|
+
try {
|
|
96
|
+
localStorage.setItem(storageKey, JSON.stringify(newPreferences));
|
|
97
|
+
setLocalPreferences(newPreferences);
|
|
98
|
+
} catch {
|
|
99
|
+
// console.warn("Failed to save preferences to localStorage");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Save to API if userId is provided
|
|
104
|
+
if (userId) {
|
|
105
|
+
try {
|
|
106
|
+
const res = await fetch(apiEndpoint, {
|
|
107
|
+
method: "PUT",
|
|
108
|
+
headers: { "Content-Type": "application/json" },
|
|
109
|
+
body: JSON.stringify({
|
|
110
|
+
userId,
|
|
111
|
+
...newPreferences,
|
|
112
|
+
}),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!res.ok) {
|
|
116
|
+
throw new Error("Failed to save preferences");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Revalidate from server
|
|
120
|
+
mutate();
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error("Failed to save preferences to API:", err);
|
|
123
|
+
// Rollback optimistic update
|
|
124
|
+
mutate();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
[userId, apiEndpoint, fallbackToLocalStorage, mutate, storageKey],
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const updateWidgets = useCallback(
|
|
132
|
+
async (widgets: WidgetConfig[]) => {
|
|
133
|
+
await savePreferences({
|
|
134
|
+
...preferences,
|
|
135
|
+
widgets,
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
[preferences, savePreferences],
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const updateQuickAccess = useCallback(
|
|
142
|
+
async (quickAccess: QuickAccessItem[]) => {
|
|
143
|
+
await savePreferences({
|
|
144
|
+
...preferences,
|
|
145
|
+
quickAccess,
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
[preferences, savePreferences],
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const resetToDefaults = useCallback(async () => {
|
|
152
|
+
await savePreferences({
|
|
153
|
+
widgets: DEFAULT_WIDGETS,
|
|
154
|
+
quickAccess: [],
|
|
155
|
+
});
|
|
156
|
+
}, [savePreferences]);
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
widgets: preferences.widgets,
|
|
160
|
+
quickAccess: preferences.quickAccess,
|
|
161
|
+
loading: isLoading,
|
|
162
|
+
error: error?.message || null,
|
|
163
|
+
updateWidgets,
|
|
164
|
+
updateQuickAccess,
|
|
165
|
+
resetToDefaults,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Home Module - Main exports
|
|
2
|
+
export { HomePage } from "./home-page";
|
|
3
|
+
export { WelcomeCard } from "./welcome-card";
|
|
4
|
+
export { FeatureShowcase } from "./feature-showcase";
|
|
5
|
+
export { WidgetContainer } from "./widget-container";
|
|
6
|
+
export { QuickAccessMenu, DEFAULT_QUICK_ACCESS } from "./quick-access-menu";
|
|
7
|
+
|
|
8
|
+
// Widget exports
|
|
9
|
+
export {
|
|
10
|
+
BaseWidget,
|
|
11
|
+
WidgetStat,
|
|
12
|
+
RevenueWidget,
|
|
13
|
+
OrdersWidget,
|
|
14
|
+
CustomersWidget,
|
|
15
|
+
StockWidget,
|
|
16
|
+
} from "./widgets";
|
|
17
|
+
|
|
18
|
+
// Hook exports
|
|
19
|
+
export { useWidgetPreferences } from "./hooks";
|
|
20
|
+
|
|
21
|
+
// Type exports
|
|
22
|
+
export type {
|
|
23
|
+
WidgetConfig,
|
|
24
|
+
WidgetType,
|
|
25
|
+
WidgetSize,
|
|
26
|
+
WidgetProps,
|
|
27
|
+
WidgetData,
|
|
28
|
+
QuickAccessItem,
|
|
29
|
+
UserHomePreferences,
|
|
30
|
+
ERPFeature,
|
|
31
|
+
} from "./types";
|
|
32
|
+
|
|
33
|
+
export { DEFAULT_WIDGETS, WIDGET_LABELS, ERP_FEATURES } from "./types";
|