@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,385 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { useRouter, usePathname, useSearchParams } from "next/navigation";
|
|
5
|
+
import { useViewMode } from "../../hooks";
|
|
6
|
+
import { toast } from "sonner";
|
|
7
|
+
import {
|
|
8
|
+
Table,
|
|
9
|
+
TableBody,
|
|
10
|
+
TableCell,
|
|
11
|
+
TableHead,
|
|
12
|
+
TableHeader,
|
|
13
|
+
TableRow,
|
|
14
|
+
Badge,
|
|
15
|
+
Button,
|
|
16
|
+
} from "../../ui";
|
|
17
|
+
import { Edit2 } from "lucide-react";
|
|
18
|
+
import type { EntityConfig, CrudPermissions } from "../../types";
|
|
19
|
+
|
|
20
|
+
import { UserToolbar } from "../components/user-toolbar";
|
|
21
|
+
import { UserStats } from "../components/user-stats";
|
|
22
|
+
import { UsersCardView } from "../components/users-card-view";
|
|
23
|
+
import { UnifiedProfileDialog } from "../components/unified-profile-dialog";
|
|
24
|
+
|
|
25
|
+
interface UserStats {
|
|
26
|
+
totalUsers: number;
|
|
27
|
+
activeUsers: number;
|
|
28
|
+
employees: number;
|
|
29
|
+
customers: number;
|
|
30
|
+
suppliers: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface UsersClientPageProps {
|
|
34
|
+
initialData: any[];
|
|
35
|
+
config: EntityConfig;
|
|
36
|
+
permissions: CrudPermissions;
|
|
37
|
+
dictionary: any;
|
|
38
|
+
roles: any[];
|
|
39
|
+
stats: UserStats;
|
|
40
|
+
onAssignRoles?: (userId: string, roleCodes: string[]) => Promise<any>;
|
|
41
|
+
onResetPassword?: (userId: string, password: string) => Promise<any>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function UsersClientPage({
|
|
45
|
+
initialData,
|
|
46
|
+
config,
|
|
47
|
+
permissions,
|
|
48
|
+
dictionary,
|
|
49
|
+
roles,
|
|
50
|
+
stats,
|
|
51
|
+
onAssignRoles,
|
|
52
|
+
onResetPassword,
|
|
53
|
+
}: UsersClientPageProps) {
|
|
54
|
+
const router = useRouter();
|
|
55
|
+
const pathname = usePathname();
|
|
56
|
+
|
|
57
|
+
const searchParams = useSearchParams();
|
|
58
|
+
// View Mode
|
|
59
|
+
const [viewModeRaw, handleViewModeChangeRaw] = useViewMode(
|
|
60
|
+
"users-view-mode",
|
|
61
|
+
"grid",
|
|
62
|
+
);
|
|
63
|
+
const viewMode =
|
|
64
|
+
viewModeRaw === "table" || viewModeRaw === "grid"
|
|
65
|
+
? (viewModeRaw as "table" | "grid")
|
|
66
|
+
: "grid";
|
|
67
|
+
const handleViewModeChange = (mode: "table" | "grid") =>
|
|
68
|
+
handleViewModeChangeRaw(mode);
|
|
69
|
+
|
|
70
|
+
// Selection for Quick View
|
|
71
|
+
const [selectedUser, setSelectedUser] = useState<any>(null);
|
|
72
|
+
const [quickViewOpen, setQuickViewOpen] = useState(false);
|
|
73
|
+
|
|
74
|
+
// Dialogs State
|
|
75
|
+
const [userFormOpen, setUserFormOpen] = useState(false);
|
|
76
|
+
const [userFormMode, setUserFormMode] = useState<"create" | "edit">("create");
|
|
77
|
+
const [userFormData, setUserFormData] = useState<any>(null);
|
|
78
|
+
|
|
79
|
+
// Handlers
|
|
80
|
+
const handleSearch = useCallback(
|
|
81
|
+
(value: string) => {
|
|
82
|
+
const params = new URLSearchParams(searchParams.toString());
|
|
83
|
+
if (value) params.set("search", value);
|
|
84
|
+
else params.delete("search");
|
|
85
|
+
router.push(`${pathname}?${params.toString()}`);
|
|
86
|
+
},
|
|
87
|
+
[pathname, router, searchParams],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const handleRoleFilter = useCallback(
|
|
91
|
+
(value: string) => {
|
|
92
|
+
const params = new URLSearchParams(searchParams.toString());
|
|
93
|
+
if (value && value !== "all") params.set("role", value);
|
|
94
|
+
else params.delete("role");
|
|
95
|
+
router.push(`${pathname}?${params.toString()}`);
|
|
96
|
+
},
|
|
97
|
+
[pathname, router, searchParams],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const handleUserTypeFilter = useCallback(
|
|
101
|
+
(value: string) => {
|
|
102
|
+
const params = new URLSearchParams(searchParams.toString());
|
|
103
|
+
if (value && value !== "all") params.set("type", value);
|
|
104
|
+
else params.delete("type");
|
|
105
|
+
router.push(`${pathname}?${params.toString()}`);
|
|
106
|
+
},
|
|
107
|
+
[pathname, router, searchParams],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const handleSelectUser = (user: any) => {
|
|
111
|
+
setSelectedUser(user);
|
|
112
|
+
setQuickViewOpen(true);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const handleCreateNew = () => {
|
|
116
|
+
setUserFormMode("create");
|
|
117
|
+
setUserFormData(null);
|
|
118
|
+
setUserFormOpen(true);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const handleEditUser = (user?: any) => {
|
|
122
|
+
const targetUser = user || selectedUser;
|
|
123
|
+
if (!targetUser) return;
|
|
124
|
+
setSelectedUser(targetUser);
|
|
125
|
+
setQuickViewOpen(true);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Role Assignment
|
|
129
|
+
const handleAssignRoles = async (userId: string, roleCodes: string[]) => {
|
|
130
|
+
try {
|
|
131
|
+
if (onAssignRoles) {
|
|
132
|
+
await onAssignRoles(userId, roleCodes);
|
|
133
|
+
}
|
|
134
|
+
toast.success("Thành công", {
|
|
135
|
+
description: "Đã cập nhật vai trò người dùng",
|
|
136
|
+
});
|
|
137
|
+
router.refresh();
|
|
138
|
+
} catch (error) {
|
|
139
|
+
toast.error("Lỗi", {
|
|
140
|
+
description: "Không thể cập nhật vai trò",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Reset Password
|
|
146
|
+
const handleResetPassword = async (userId: string, password: string) => {
|
|
147
|
+
try {
|
|
148
|
+
if (onResetPassword) {
|
|
149
|
+
await onResetPassword(userId, password);
|
|
150
|
+
}
|
|
151
|
+
toast.success("Thành công", {
|
|
152
|
+
description: "Đã đặt lại mật khẩu",
|
|
153
|
+
});
|
|
154
|
+
} catch (error) {
|
|
155
|
+
toast.error("Lỗi", {
|
|
156
|
+
description: "Không thể đặt lại mật khẩu",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// User Form Submit (Create/Edit)
|
|
162
|
+
const handleUserSubmit = async (data: any, id?: string) => {
|
|
163
|
+
const url = id ? `/api/users/${id}` : "/api/users";
|
|
164
|
+
const method = id ? "PUT" : "POST";
|
|
165
|
+
|
|
166
|
+
const res = await fetch(url, {
|
|
167
|
+
method,
|
|
168
|
+
headers: { "Content-Type": "application/json" },
|
|
169
|
+
body: JSON.stringify(data),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (!res.ok) {
|
|
173
|
+
const resData = await res.json();
|
|
174
|
+
throw new Error(resData.error);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
toast.success("Thành công", {
|
|
178
|
+
description: id ? "Đã cập nhật người dùng" : "Đã tạo người dùng mới",
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
router.refresh();
|
|
182
|
+
setUserFormOpen(false);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<div className="flex flex-col gap-4 relative">
|
|
187
|
+
<UserStats stats={stats} />
|
|
188
|
+
|
|
189
|
+
<UserToolbar
|
|
190
|
+
viewMode={viewMode}
|
|
191
|
+
onViewModeChange={handleViewModeChange}
|
|
192
|
+
onCreateNew={handleCreateNew}
|
|
193
|
+
onSearchChange={handleSearch}
|
|
194
|
+
roles={roles}
|
|
195
|
+
onRoleChange={handleRoleFilter}
|
|
196
|
+
userType={searchParams.get("type") || "all"}
|
|
197
|
+
onUserTypeChange={handleUserTypeFilter}
|
|
198
|
+
permissions={permissions}
|
|
199
|
+
/>
|
|
200
|
+
|
|
201
|
+
<div className="min-h-[400px]">
|
|
202
|
+
{viewMode === "table" ? (
|
|
203
|
+
<div className="border rounded-md">
|
|
204
|
+
<BasicUserTable
|
|
205
|
+
data={initialData}
|
|
206
|
+
onEdit={(u) => handleEditUser(u)}
|
|
207
|
+
/>
|
|
208
|
+
</div>
|
|
209
|
+
) : (
|
|
210
|
+
<UsersCardView data={initialData} onSelect={handleSelectUser} />
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{/* Create User Dialog */}
|
|
215
|
+
<UnifiedProfileDialog
|
|
216
|
+
open={userFormOpen}
|
|
217
|
+
onOpenChange={setUserFormOpen}
|
|
218
|
+
mode="create"
|
|
219
|
+
viewMode="admin"
|
|
220
|
+
roles={roles}
|
|
221
|
+
onSaveProfile={(data) => handleUserSubmit(data)}
|
|
222
|
+
/>
|
|
223
|
+
|
|
224
|
+
{/* Quick View / Edit Dialog */}
|
|
225
|
+
{selectedUser && (
|
|
226
|
+
<UnifiedProfileDialog
|
|
227
|
+
open={quickViewOpen}
|
|
228
|
+
onOpenChange={setQuickViewOpen}
|
|
229
|
+
data={selectedUser}
|
|
230
|
+
mode="edit"
|
|
231
|
+
viewMode="admin"
|
|
232
|
+
roles={roles}
|
|
233
|
+
onSaveProfile={handleUserSubmit}
|
|
234
|
+
onSaveRoles={handleAssignRoles}
|
|
235
|
+
onSavePassword={handleResetPassword}
|
|
236
|
+
/>
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Simple Basic Table Component
|
|
243
|
+
function BasicUserTable({
|
|
244
|
+
data,
|
|
245
|
+
onEdit,
|
|
246
|
+
}: {
|
|
247
|
+
data: any[];
|
|
248
|
+
onEdit: (u: any) => void;
|
|
249
|
+
}) {
|
|
250
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
251
|
+
const pageSize = 10;
|
|
252
|
+
|
|
253
|
+
if (!data.length) return <div className="p-4 text-center">No data</div>;
|
|
254
|
+
|
|
255
|
+
const totalPages = Math.ceil(data.length / pageSize);
|
|
256
|
+
const paginatedData = data.slice((currentPage - 1) * pageSize, currentPage * pageSize);
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<div className="flex flex-col gap-4">
|
|
260
|
+
<div className="bg-background">
|
|
261
|
+
<Table>
|
|
262
|
+
<TableHeader>
|
|
263
|
+
<TableRow className="text-sm border-b">
|
|
264
|
+
<TableHead className="w-[50px] font-semibold text-foreground text-center h-10">STT</TableHead>
|
|
265
|
+
<TableHead className="w-[20%] min-w-[150px] font-semibold text-foreground h-10">Người dùng</TableHead>
|
|
266
|
+
<TableHead className="w-[20%] min-w-[150px] font-semibold text-foreground h-10">Liên hệ</TableHead>
|
|
267
|
+
<TableHead className="w-[15%] min-w-[120px] font-semibold text-foreground h-10">Trạng thái</TableHead>
|
|
268
|
+
<TableHead className="w-[15%] min-w-[120px] font-semibold text-foreground h-10">Phòng ban</TableHead>
|
|
269
|
+
<TableHead className="w-[20%] min-w-[150px] font-semibold text-foreground h-10">Vai trò</TableHead>
|
|
270
|
+
<TableHead className="w-[50px] text-center h-10"></TableHead>
|
|
271
|
+
</TableRow>
|
|
272
|
+
</TableHeader>
|
|
273
|
+
<TableBody>
|
|
274
|
+
{paginatedData.map((user, index) => (
|
|
275
|
+
<TableRow key={user.id} className="border-b group">
|
|
276
|
+
{/* STT */}
|
|
277
|
+
<TableCell className="py-3 text-center text-muted-foreground font-medium text-xs">
|
|
278
|
+
{(currentPage - 1) * pageSize + index + 1}
|
|
279
|
+
</TableCell>
|
|
280
|
+
|
|
281
|
+
{/* User Info */}
|
|
282
|
+
<TableCell className="py-3">
|
|
283
|
+
<div className="flex flex-col">
|
|
284
|
+
<div className="flex items-center gap-2">
|
|
285
|
+
<span className="font-semibold text-sm text-foreground">{user.name}</span>
|
|
286
|
+
{user.userType === "customer" && <span className="text-xs text-foreground bg-muted border px-2.5 py-0.5 rounded-full font-semibold whitespace-nowrap">Khách hàng</span>}
|
|
287
|
+
{user.userType === "supplier" && <span className="text-xs text-background bg-foreground px-2.5 py-0.5 rounded-full font-semibold whitespace-nowrap">Nhà cung cấp</span>}
|
|
288
|
+
{(!user.userType || user.userType === "employee") && <span className="text-xs text-foreground bg-muted border px-2.5 py-0.5 rounded-full font-semibold whitespace-nowrap">Nhân viên</span>}
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
</TableCell>
|
|
292
|
+
|
|
293
|
+
{/* Contact */}
|
|
294
|
+
<TableCell className="py-3">
|
|
295
|
+
<div className="flex flex-col">
|
|
296
|
+
<span className="text-sm text-foreground">{user.email}</span>
|
|
297
|
+
{user.phone && <span className="text-sm text-muted-foreground">{user.phone}</span>}
|
|
298
|
+
</div>
|
|
299
|
+
</TableCell>
|
|
300
|
+
|
|
301
|
+
{/* Status */}
|
|
302
|
+
<TableCell className="py-3">
|
|
303
|
+
<div className="flex items-center gap-1.5">
|
|
304
|
+
<span className={`inline-flex items-center text-xs font-semibold px-2.5 py-1 rounded-full whitespace-nowrap ${
|
|
305
|
+
user.isActive || user.status === "active"
|
|
306
|
+
? "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
|
|
307
|
+
: "bg-muted text-muted-foreground"
|
|
308
|
+
}`}>
|
|
309
|
+
{user.isActive || user.status === "active" ? "Hoạt động" : "Đã khóa"}
|
|
310
|
+
</span>
|
|
311
|
+
</div>
|
|
312
|
+
</TableCell>
|
|
313
|
+
|
|
314
|
+
{/* Department */}
|
|
315
|
+
<TableCell className="py-3 text-sm text-foreground">
|
|
316
|
+
{user.departmentName || "—"}
|
|
317
|
+
</TableCell>
|
|
318
|
+
|
|
319
|
+
{/* Roles */}
|
|
320
|
+
<TableCell className="py-3">
|
|
321
|
+
<div className="flex flex-wrap gap-1.5">
|
|
322
|
+
{user.roleNames?.map((r: string, i: number) => (
|
|
323
|
+
<span
|
|
324
|
+
key={i}
|
|
325
|
+
className="inline-flex items-center bg-muted text-foreground border font-semibold text-xs px-2.5 py-1 rounded-full whitespace-nowrap"
|
|
326
|
+
>
|
|
327
|
+
{r}
|
|
328
|
+
</span>
|
|
329
|
+
))}
|
|
330
|
+
</div>
|
|
331
|
+
</TableCell>
|
|
332
|
+
|
|
333
|
+
{/* Actions */}
|
|
334
|
+
<TableCell className="py-2.5 text-right">
|
|
335
|
+
<Button
|
|
336
|
+
variant="ghost"
|
|
337
|
+
size="icon"
|
|
338
|
+
onClick={(e: React.MouseEvent) => {
|
|
339
|
+
e.stopPropagation();
|
|
340
|
+
onEdit(user);
|
|
341
|
+
}}
|
|
342
|
+
>
|
|
343
|
+
<Edit2 className="h-4 w-4" />
|
|
344
|
+
</Button>
|
|
345
|
+
</TableCell>
|
|
346
|
+
</TableRow>
|
|
347
|
+
))}
|
|
348
|
+
</TableBody>
|
|
349
|
+
</Table>
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
{/* Pagination */}
|
|
353
|
+
{totalPages > 1 && (
|
|
354
|
+
<div className="flex items-center justify-between px-2 pt-2">
|
|
355
|
+
<p className="text-sm text-muted-foreground">
|
|
356
|
+
Hiển thị từ {(currentPage - 1) * pageSize + 1} đến {Math.min(currentPage * pageSize, data.length)} trong tổng số {data.length}
|
|
357
|
+
</p>
|
|
358
|
+
<div className="flex items-center gap-2">
|
|
359
|
+
<Button
|
|
360
|
+
variant="outline"
|
|
361
|
+
size="sm"
|
|
362
|
+
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
|
|
363
|
+
disabled={currentPage === 1}
|
|
364
|
+
className="h-8 rounded-full"
|
|
365
|
+
>
|
|
366
|
+
Trang trước
|
|
367
|
+
</Button>
|
|
368
|
+
<div className="text-sm font-semibold text-foreground px-2">
|
|
369
|
+
Trang {currentPage} / {totalPages}
|
|
370
|
+
</div>
|
|
371
|
+
<Button
|
|
372
|
+
variant="outline"
|
|
373
|
+
size="sm"
|
|
374
|
+
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
|
|
375
|
+
disabled={currentPage === totalPages}
|
|
376
|
+
className="h-8 rounded-full"
|
|
377
|
+
>
|
|
378
|
+
Trang sau
|
|
379
|
+
</Button>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
)}
|
|
383
|
+
</div>
|
|
384
|
+
);
|
|
385
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { UserType } from "./types";
|
|
2
|
+
import type { DictionaryType } from "../hooks";
|
|
3
|
+
|
|
4
|
+
import { DangerousZone } from "./components/dangerous-zone";
|
|
5
|
+
import { ProfileInfo } from "./components/profile-info";
|
|
6
|
+
|
|
7
|
+
export interface ProfilePageProps {
|
|
8
|
+
user: UserType;
|
|
9
|
+
dictionary?: DictionaryType;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ProfilePage({ user, dictionary }: ProfilePageProps) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="grid gap-4">
|
|
15
|
+
<ProfileInfo user={user} dictionary={dictionary} />
|
|
16
|
+
<DangerousZone user={user} dictionary={dictionary} />
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { formatFileSize } from "../utils";
|
|
4
|
+
|
|
5
|
+
export const MAX_AVATAR_SIZE = 50000000; // 50 MB
|
|
6
|
+
export const fomratedAvatarSize = formatFileSize(MAX_AVATAR_SIZE);
|
|
7
|
+
|
|
8
|
+
export const ProfileInfoSchema = z.object({
|
|
9
|
+
firstName: z
|
|
10
|
+
.string()
|
|
11
|
+
.trim()
|
|
12
|
+
.min(2, { message: "First Name must contain at least 2 characters." })
|
|
13
|
+
.max(50, { message: "First Name must contain at most 50 characters." }),
|
|
14
|
+
lastName: z
|
|
15
|
+
.string()
|
|
16
|
+
.trim()
|
|
17
|
+
.min(2, { message: "Last Name must contain at least 2 characters." })
|
|
18
|
+
.max(50, { message: "Last Name must contain at most 50 characters." }),
|
|
19
|
+
username: z
|
|
20
|
+
.string()
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.trim()
|
|
23
|
+
.min(3, { message: "Username must contain at least 3 characters." })
|
|
24
|
+
.max(50, { message: "Username must contain at most 50 characters." }),
|
|
25
|
+
email: z
|
|
26
|
+
.string()
|
|
27
|
+
.email({ message: "Invalid email address" })
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.trim(),
|
|
30
|
+
phoneNumber: z
|
|
31
|
+
.string()
|
|
32
|
+
.min(10, { message: "Phone Number must contain at least 10 digits." })
|
|
33
|
+
.max(15, { message: "Username must contain at most 15 digits." }),
|
|
34
|
+
state: z
|
|
35
|
+
.string()
|
|
36
|
+
.trim()
|
|
37
|
+
.min(2, { message: "State must contain at least 2 characters." })
|
|
38
|
+
.max(50, { message: "State must contain at most 50 characters." }),
|
|
39
|
+
country: z
|
|
40
|
+
.string()
|
|
41
|
+
.trim()
|
|
42
|
+
.min(2, { message: "Country must contain at least 2 characters." })
|
|
43
|
+
.max(56, { message: "Country must contain at most 56 characters." }),
|
|
44
|
+
address: z
|
|
45
|
+
.string()
|
|
46
|
+
.trim()
|
|
47
|
+
.min(2, { message: "Address must contain at least 2 characters." })
|
|
48
|
+
.max(100, { message: "Address must contain at most 100 characters." }),
|
|
49
|
+
zipCode: z
|
|
50
|
+
.string()
|
|
51
|
+
.min(5, { message: "Zip Code must contain at least 5 digits." })
|
|
52
|
+
.max(10, { message: "Zip Code must contain at most 10 characters." }),
|
|
53
|
+
language: z.string(),
|
|
54
|
+
timeZone: z.string(),
|
|
55
|
+
currency: z.string(),
|
|
56
|
+
organization: z.string().trim().optional(),
|
|
57
|
+
avatar: z
|
|
58
|
+
.instanceof(File)
|
|
59
|
+
.refine((avatar) => avatar.size <= MAX_AVATAR_SIZE, {
|
|
60
|
+
message: `Avatar must be ${fomratedAvatarSize} or less.`,
|
|
61
|
+
})
|
|
62
|
+
// .refine((avatar) => avatar.type.startsWith("image/"), {
|
|
63
|
+
// message: "The file must be an image",
|
|
64
|
+
// })
|
|
65
|
+
.optional(),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const DeleteAccountSchema = z.object({});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import type { DeleteAccountSchema, ProfileInfoSchema } from "./schemas";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export interface UserType {
|
|
6
|
+
id: string
|
|
7
|
+
firstName: string
|
|
8
|
+
lastName: string
|
|
9
|
+
name: string
|
|
10
|
+
username: string
|
|
11
|
+
role: string
|
|
12
|
+
avatar: string
|
|
13
|
+
background: string
|
|
14
|
+
status: string
|
|
15
|
+
phoneNumber: string
|
|
16
|
+
email: string
|
|
17
|
+
state: string
|
|
18
|
+
country: string
|
|
19
|
+
address: string
|
|
20
|
+
zipCode: string
|
|
21
|
+
language: string
|
|
22
|
+
timeZone: string
|
|
23
|
+
currency: string
|
|
24
|
+
organization: string
|
|
25
|
+
twoFactorAuth: boolean
|
|
26
|
+
loginAlerts: boolean
|
|
27
|
+
accountReoveryOption?: "email" | "sms" | "codes"
|
|
28
|
+
connections: number
|
|
29
|
+
followers: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type DeleteAccountFormType = z.infer<typeof DeleteAccountSchema>
|
|
33
|
+
|
|
34
|
+
export type ProfileInfoFormType = z.infer<typeof ProfileInfoSchema>
|