@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,25 @@
|
|
|
1
|
+
// Logger Types
|
|
2
|
+
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error";
|
|
3
|
+
|
|
4
|
+
export interface LoggerOptions {
|
|
5
|
+
/** Logger name/context for identification */
|
|
6
|
+
name?: string;
|
|
7
|
+
/** Minimum log level */
|
|
8
|
+
level?: LogLevel;
|
|
9
|
+
/** Enable file output */
|
|
10
|
+
enableFile?: boolean;
|
|
11
|
+
/** Directory for log files */
|
|
12
|
+
dirname?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface LogContext {
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface Logger {
|
|
20
|
+
trace(message: string, context?: LogContext): void;
|
|
21
|
+
debug(message: string, context?: LogContext): void;
|
|
22
|
+
info(message: string, context?: LogContext): void;
|
|
23
|
+
warn(message: string, context?: LogContext): void;
|
|
24
|
+
error(message: string, context?: LogContext): void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// @goerp/core/layout
|
|
2
|
+
// Layout components for GoERP applications
|
|
3
|
+
//
|
|
4
|
+
// NOTE: This module contains basic layout shells.
|
|
5
|
+
// Full layout components (sidebar, header, navigation) should be migrated
|
|
6
|
+
// from packages/shared/layout as needed.
|
|
7
|
+
|
|
8
|
+
"use client";
|
|
9
|
+
|
|
10
|
+
import * as React from "react";
|
|
11
|
+
import { cn } from "../utils";
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// AppShell - Main application container
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
export interface AppShellProps {
|
|
18
|
+
children: React.ReactNode;
|
|
19
|
+
sidebar?: React.ReactNode;
|
|
20
|
+
header?: React.ReactNode;
|
|
21
|
+
footer?: React.ReactNode;
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function AppShell({
|
|
26
|
+
children,
|
|
27
|
+
sidebar,
|
|
28
|
+
header,
|
|
29
|
+
footer,
|
|
30
|
+
className,
|
|
31
|
+
}: AppShellProps) {
|
|
32
|
+
return (
|
|
33
|
+
<div className={cn("flex min-h-screen", className)}>
|
|
34
|
+
{sidebar}
|
|
35
|
+
<div className="flex flex-1 flex-col">
|
|
36
|
+
{header}
|
|
37
|
+
<main className="flex-1">{children}</main>
|
|
38
|
+
{footer}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Header
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
export interface HeaderProps {
|
|
49
|
+
children?: React.ReactNode;
|
|
50
|
+
className?: string;
|
|
51
|
+
sticky?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function Header({ children, className, sticky = true }: HeaderProps) {
|
|
55
|
+
return (
|
|
56
|
+
<header
|
|
57
|
+
className={cn(
|
|
58
|
+
"border-b bg-background",
|
|
59
|
+
sticky && "sticky top-0 z-50",
|
|
60
|
+
className,
|
|
61
|
+
)}
|
|
62
|
+
>
|
|
63
|
+
{children}
|
|
64
|
+
</header>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Sidebar
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
export interface SidebarProps {
|
|
73
|
+
children?: React.ReactNode;
|
|
74
|
+
className?: string;
|
|
75
|
+
collapsed?: boolean;
|
|
76
|
+
width?: string | number;
|
|
77
|
+
collapsedWidth?: string | number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function Sidebar({
|
|
81
|
+
children,
|
|
82
|
+
className,
|
|
83
|
+
collapsed = false,
|
|
84
|
+
width = 280,
|
|
85
|
+
collapsedWidth = 68,
|
|
86
|
+
}: SidebarProps) {
|
|
87
|
+
const sidebarWidth = collapsed ? collapsedWidth : width;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<aside
|
|
91
|
+
className={cn("border-r bg-background transition-all", className)}
|
|
92
|
+
style={{
|
|
93
|
+
width:
|
|
94
|
+
typeof sidebarWidth === "number" ? `${sidebarWidth}px` : sidebarWidth,
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
{children}
|
|
98
|
+
</aside>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// Footer
|
|
104
|
+
// ============================================================================
|
|
105
|
+
|
|
106
|
+
export interface FooterProps {
|
|
107
|
+
children?: React.ReactNode;
|
|
108
|
+
className?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function Footer({ children, className }: FooterProps) {
|
|
112
|
+
return (
|
|
113
|
+
<footer className={cn("border-t bg-background", className)}>
|
|
114
|
+
{children}
|
|
115
|
+
</footer>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ============================================================================
|
|
120
|
+
// PageContainer
|
|
121
|
+
// ============================================================================
|
|
122
|
+
|
|
123
|
+
export interface PageContainerProps {
|
|
124
|
+
children: React.ReactNode;
|
|
125
|
+
className?: string;
|
|
126
|
+
title?: string;
|
|
127
|
+
description?: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function PageContainer({
|
|
131
|
+
children,
|
|
132
|
+
className,
|
|
133
|
+
title,
|
|
134
|
+
description,
|
|
135
|
+
}: PageContainerProps) {
|
|
136
|
+
return (
|
|
137
|
+
<div className={cn("w-full px-4 md:px-6 lg:px-8 py-6", className)}>
|
|
138
|
+
{(title || description) && (
|
|
139
|
+
<div className="mb-6">
|
|
140
|
+
{title && <h1 className="text-2xl font-bold">{title}</h1>}
|
|
141
|
+
{description && (
|
|
142
|
+
<p className="text-muted-foreground">{description}</p>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
{children}
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ============================================================================
|
|
152
|
+
// Breadcrumb
|
|
153
|
+
// ============================================================================
|
|
154
|
+
|
|
155
|
+
export interface BreadcrumbItem {
|
|
156
|
+
label: string;
|
|
157
|
+
href?: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface BreadcrumbProps {
|
|
161
|
+
items: BreadcrumbItem[];
|
|
162
|
+
className?: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function Breadcrumb({ items, className }: BreadcrumbProps) {
|
|
166
|
+
return (
|
|
167
|
+
<nav className={cn("flex items-center space-x-2 text-sm", className)}>
|
|
168
|
+
{items.map((item, index) => (
|
|
169
|
+
<React.Fragment key={index}>
|
|
170
|
+
{index > 0 && <span className="text-muted-foreground">/</span>}
|
|
171
|
+
{item.href ? (
|
|
172
|
+
<a
|
|
173
|
+
href={item.href}
|
|
174
|
+
className="text-muted-foreground hover:text-foreground"
|
|
175
|
+
>
|
|
176
|
+
{item.label}
|
|
177
|
+
</a>
|
|
178
|
+
) : (
|
|
179
|
+
<span className="font-medium">{item.label}</span>
|
|
180
|
+
)}
|
|
181
|
+
</React.Fragment>
|
|
182
|
+
))}
|
|
183
|
+
</nav>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// @goerp/core/navigation
|
|
2
|
+
// Core navigation utilities for GoERP platform
|
|
3
|
+
// Consolidated from packages/shared/navigation
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
NavigationType,
|
|
7
|
+
NavigationRootItem,
|
|
8
|
+
NavigationNestedItem,
|
|
9
|
+
} from "../types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Filter navigation items based on permissions recursively
|
|
13
|
+
*/
|
|
14
|
+
export function filterNavigationItems(
|
|
15
|
+
items: NavigationRootItem[],
|
|
16
|
+
accessibleCodes: Set<string>,
|
|
17
|
+
): NavigationRootItem[];
|
|
18
|
+
export function filterNavigationItems(
|
|
19
|
+
items: NavigationNestedItem[],
|
|
20
|
+
accessibleCodes: Set<string>,
|
|
21
|
+
): NavigationNestedItem[];
|
|
22
|
+
export function filterNavigationItems(
|
|
23
|
+
items: NavigationRootItem[] | NavigationNestedItem[],
|
|
24
|
+
accessibleCodes: Set<string>,
|
|
25
|
+
): (NavigationRootItem | NavigationNestedItem)[] {
|
|
26
|
+
return items.reduce<(NavigationRootItem | NavigationNestedItem)[]>(
|
|
27
|
+
(acc, item) => {
|
|
28
|
+
// If admin (special wildcard), show everything
|
|
29
|
+
if (accessibleCodes.has("*")) {
|
|
30
|
+
acc.push(item);
|
|
31
|
+
return acc;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Default Deny: If resource is defined AND resource is not accessible
|
|
35
|
+
// If resource is missing, we consider it public (as per comment below)
|
|
36
|
+
if (item.resource && !accessibleCodes.has(item.resource)) {
|
|
37
|
+
return acc;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Handle nested items
|
|
41
|
+
if ("items" in item && item.items) {
|
|
42
|
+
const filteredChildren = filterNavigationItems(
|
|
43
|
+
item.items as (NavigationRootItem | NavigationNestedItem)[],
|
|
44
|
+
accessibleCodes,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Only add parent if it has visible children
|
|
48
|
+
if (filteredChildren.length > 0) {
|
|
49
|
+
acc.push({
|
|
50
|
+
...item,
|
|
51
|
+
items: filteredChildren,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
// Leaf node (link) - already checked resource above
|
|
56
|
+
// If no resource defined, assume public/visible
|
|
57
|
+
acc.push(item);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return acc;
|
|
61
|
+
},
|
|
62
|
+
[],
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Filter navigation tree based on permissions
|
|
68
|
+
*/
|
|
69
|
+
export function filterNavigationTree(
|
|
70
|
+
navigation: NavigationType[],
|
|
71
|
+
accessibleCodes: Set<string>,
|
|
72
|
+
): NavigationType[] {
|
|
73
|
+
if (accessibleCodes.has("*")) {
|
|
74
|
+
return navigation;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const filteredNavigation: NavigationType[] = [];
|
|
78
|
+
|
|
79
|
+
for (const group of navigation) {
|
|
80
|
+
const filteredItems = filterNavigationItems(group.items, accessibleCodes);
|
|
81
|
+
|
|
82
|
+
if (filteredItems.length > 0) {
|
|
83
|
+
filteredNavigation.push({
|
|
84
|
+
...group,
|
|
85
|
+
items: filteredItems,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return filteredNavigation;
|
|
91
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
notificationService,
|
|
3
|
+
NotificationServiceImpl,
|
|
4
|
+
} from "./notification-service";
|
|
5
|
+
export { InMemoryStorage } from "./storage";
|
|
6
|
+
export type {
|
|
7
|
+
Notification,
|
|
8
|
+
NotificationType,
|
|
9
|
+
NotificationChannel,
|
|
10
|
+
NotificationStatus,
|
|
11
|
+
CreateNotificationInput,
|
|
12
|
+
NotificationStorage,
|
|
13
|
+
NotificationServiceOptions,
|
|
14
|
+
} from "./types";
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CreateNotificationInput,
|
|
3
|
+
Notification,
|
|
4
|
+
NotificationServiceOptions,
|
|
5
|
+
NotificationStorage,
|
|
6
|
+
} from "./types";
|
|
7
|
+
import { InMemoryStorage } from "./storage/in-memory";
|
|
8
|
+
import { createLogger } from "../infrastructure/logger";
|
|
9
|
+
import { eventBus } from "../infrastructure/event-bus";
|
|
10
|
+
|
|
11
|
+
const logger = createLogger("NotificationService");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* NotificationService - sends and manages user notifications
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { notificationService } from '@goerp/core/notification';
|
|
19
|
+
*
|
|
20
|
+
* // Send notification
|
|
21
|
+
* await notificationService.send({
|
|
22
|
+
* userId: 'manager-123',
|
|
23
|
+
* title: 'Purchase Order cần duyệt',
|
|
24
|
+
* content: 'PO-001 chờ phê duyệt',
|
|
25
|
+
* type: 'approval',
|
|
26
|
+
* link: '/purchase-orders/PO-001'
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* // Get unread notifications
|
|
30
|
+
* const unread = await notificationService.getUnread('manager-123');
|
|
31
|
+
*
|
|
32
|
+
* // Mark as read
|
|
33
|
+
* await notificationService.markAsRead('notification-id');
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
class NotificationServiceImpl {
|
|
37
|
+
private storage: NotificationStorage;
|
|
38
|
+
|
|
39
|
+
constructor(options: NotificationServiceOptions = {}) {
|
|
40
|
+
this.storage = options.storage || new InMemoryStorage();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Set custom storage implementation */
|
|
44
|
+
setStorage(storage: NotificationStorage): void {
|
|
45
|
+
this.storage = storage;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Send a notification */
|
|
49
|
+
async send(input: CreateNotificationInput): Promise<Notification> {
|
|
50
|
+
const notification: Notification = {
|
|
51
|
+
id: crypto.randomUUID(),
|
|
52
|
+
userId: input.userId,
|
|
53
|
+
title: input.title,
|
|
54
|
+
content: input.content,
|
|
55
|
+
type: input.type || "info",
|
|
56
|
+
channel: input.channel || "in-app",
|
|
57
|
+
status: "sent",
|
|
58
|
+
link: input.link,
|
|
59
|
+
metadata: input.metadata,
|
|
60
|
+
createdAt: new Date(),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
await this.storage.save(notification);
|
|
64
|
+
|
|
65
|
+
logger.debug("Notification sent", {
|
|
66
|
+
id: notification.id,
|
|
67
|
+
userId: notification.userId,
|
|
68
|
+
type: notification.type,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Emit event for real-time updates
|
|
72
|
+
eventBus.emit("notification.sent", { notification });
|
|
73
|
+
|
|
74
|
+
return notification;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Get all notifications for a user */
|
|
78
|
+
async getAll(userId: string): Promise<Notification[]> {
|
|
79
|
+
return this.storage.findByUserId(userId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Get unread notifications for a user */
|
|
83
|
+
async getUnread(userId: string): Promise<Notification[]> {
|
|
84
|
+
return this.storage.findByUserId(userId, { unreadOnly: true });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Get unread count for a user */
|
|
88
|
+
async getUnreadCount(userId: string): Promise<number> {
|
|
89
|
+
const unread = await this.getUnread(userId);
|
|
90
|
+
return unread.length;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Get a notification by ID */
|
|
94
|
+
async getById(id: string): Promise<Notification | null> {
|
|
95
|
+
return this.storage.findById(id);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Mark a notification as read */
|
|
99
|
+
async markAsRead(id: string): Promise<void> {
|
|
100
|
+
await this.storage.markAsRead(id);
|
|
101
|
+
eventBus.emit("notification.read", { id });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Mark all notifications as read for a user */
|
|
105
|
+
async markAllAsRead(userId: string): Promise<void> {
|
|
106
|
+
await this.storage.markAllAsRead(userId);
|
|
107
|
+
eventBus.emit("notification.allRead", { userId });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Delete a notification */
|
|
111
|
+
async delete(id: string): Promise<void> {
|
|
112
|
+
await this.storage.delete(id);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Singleton instance
|
|
117
|
+
export const notificationService = new NotificationServiceImpl();
|
|
118
|
+
|
|
119
|
+
// Export class for testing
|
|
120
|
+
export { NotificationServiceImpl };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Notification, NotificationStorage } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* In-memory notification storage for development/testing
|
|
5
|
+
* In production, replace with database storage
|
|
6
|
+
*/
|
|
7
|
+
export class InMemoryStorage implements NotificationStorage {
|
|
8
|
+
private notifications = new Map<string, Notification>();
|
|
9
|
+
|
|
10
|
+
async save(notification: Notification): Promise<void> {
|
|
11
|
+
this.notifications.set(notification.id, notification);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async findByUserId(
|
|
15
|
+
userId: string,
|
|
16
|
+
options?: { unreadOnly?: boolean },
|
|
17
|
+
): Promise<Notification[]> {
|
|
18
|
+
const result: Notification[] = [];
|
|
19
|
+
|
|
20
|
+
for (const notification of this.notifications.values()) {
|
|
21
|
+
if (notification.userId === userId) {
|
|
22
|
+
if (options?.unreadOnly && notification.status === "read") {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
result.push(notification);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return result.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async findById(id: string): Promise<Notification | null> {
|
|
33
|
+
return this.notifications.get(id) || null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async markAsRead(id: string): Promise<void> {
|
|
37
|
+
const notification = this.notifications.get(id);
|
|
38
|
+
if (notification) {
|
|
39
|
+
notification.status = "read";
|
|
40
|
+
notification.readAt = new Date();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async markAllAsRead(userId: string): Promise<void> {
|
|
45
|
+
for (const notification of this.notifications.values()) {
|
|
46
|
+
if (notification.userId === userId && notification.status !== "read") {
|
|
47
|
+
notification.status = "read";
|
|
48
|
+
notification.readAt = new Date();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async delete(id: string): Promise<void> {
|
|
54
|
+
this.notifications.delete(id);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { InMemoryStorage } from "./in-memory";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Notification Types
|
|
2
|
+
export type NotificationType =
|
|
3
|
+
| "info"
|
|
4
|
+
| "warning"
|
|
5
|
+
| "error"
|
|
6
|
+
| "success"
|
|
7
|
+
| "approval";
|
|
8
|
+
export type NotificationChannel = "in-app" | "email" | "sms";
|
|
9
|
+
export type NotificationStatus = "pending" | "sent" | "read" | "failed";
|
|
10
|
+
|
|
11
|
+
export interface Notification {
|
|
12
|
+
id: string;
|
|
13
|
+
userId: string;
|
|
14
|
+
title: string;
|
|
15
|
+
content: string;
|
|
16
|
+
type: NotificationType;
|
|
17
|
+
channel: NotificationChannel;
|
|
18
|
+
status: NotificationStatus;
|
|
19
|
+
/** Link to navigate when clicked */
|
|
20
|
+
link?: string;
|
|
21
|
+
/** Additional metadata */
|
|
22
|
+
metadata?: Record<string, unknown>;
|
|
23
|
+
createdAt: Date;
|
|
24
|
+
readAt?: Date;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CreateNotificationInput {
|
|
28
|
+
userId: string;
|
|
29
|
+
title: string;
|
|
30
|
+
content: string;
|
|
31
|
+
type?: NotificationType;
|
|
32
|
+
channel?: NotificationChannel;
|
|
33
|
+
link?: string;
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface NotificationStorage {
|
|
38
|
+
save(notification: Notification): Promise<void>;
|
|
39
|
+
findByUserId(
|
|
40
|
+
userId: string,
|
|
41
|
+
options?: { unreadOnly?: boolean },
|
|
42
|
+
): Promise<Notification[]>;
|
|
43
|
+
findById(id: string): Promise<Notification | null>;
|
|
44
|
+
markAsRead(id: string): Promise<void>;
|
|
45
|
+
markAllAsRead(userId: string): Promise<void>;
|
|
46
|
+
delete(id: string): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface NotificationServiceOptions {
|
|
50
|
+
storage?: NotificationStorage;
|
|
51
|
+
}
|