@goplusvn/core 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +31 -175
- 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
- package/dist/audit/index.d.mts +0 -115
- package/dist/audit/index.d.ts +0 -115
- package/dist/audit/index.js +0 -204
- package/dist/audit/index.js.map +0 -1
- package/dist/audit/index.mjs +0 -200
- package/dist/audit/index.mjs.map +0 -1
- package/dist/auth/index.d.mts +0 -86
- package/dist/auth/index.d.ts +0 -86
- package/dist/auth/index.js +0 -210
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/index.mjs +0 -198
- package/dist/auth/index.mjs.map +0 -1
- package/dist/button-1dWvP9Ib.d.mts +0 -30
- package/dist/button-1dWvP9Ib.d.ts +0 -30
- package/dist/calendar-2QzdEo1z.d.mts +0 -20
- package/dist/calendar-2QzdEo1z.d.ts +0 -20
- package/dist/code-generation/index.d.mts +0 -30
- package/dist/code-generation/index.d.ts +0 -30
- package/dist/code-generation/index.js +0 -31
- package/dist/code-generation/index.js.map +0 -1
- package/dist/code-generation/index.mjs +0 -28
- package/dist/code-generation/index.mjs.map +0 -1
- package/dist/configs/index.d.mts +0 -175
- package/dist/configs/index.d.ts +0 -175
- package/dist/configs/index.js +0 -254
- package/dist/configs/index.js.map +0 -1
- package/dist/configs/index.mjs +0 -233
- package/dist/configs/index.mjs.map +0 -1
- package/dist/crud/index.d.mts +0 -646
- package/dist/crud/index.d.ts +0 -646
- package/dist/crud/index.js +0 -11772
- package/dist/crud/index.js.map +0 -1
- package/dist/crud/index.mjs +0 -11665
- package/dist/crud/index.mjs.map +0 -1
- package/dist/crud/server.d.mts +0 -20
- package/dist/crud/server.d.ts +0 -20
- package/dist/crud/server.js +0 -123
- package/dist/crud/server.js.map +0 -1
- package/dist/crud/server.mjs +0 -120
- package/dist/crud/server.mjs.map +0 -1
- package/dist/data-table-skeleton-12NA8Mjx.d.mts +0 -39
- package/dist/data-table-skeleton-12NA8Mjx.d.ts +0 -39
- package/dist/dialog-bKfjZMTd.d.mts +0 -22
- package/dist/dialog-bKfjZMTd.d.ts +0 -22
- package/dist/dynamic-icon-DrGIiu2N.d.mts +0 -10
- package/dist/dynamic-icon-DrGIiu2N.d.ts +0 -10
- package/dist/home/index.d.mts +0 -269
- package/dist/home/index.d.ts +0 -269
- package/dist/home/index.js +0 -1678
- package/dist/home/index.js.map +0 -1
- package/dist/home/index.mjs +0 -1635
- package/dist/home/index.mjs.map +0 -1
- package/dist/hooks/index.d.mts +0 -7
- package/dist/hooks/index.d.ts +0 -7
- package/dist/hooks/index.js +0 -8316
- package/dist/hooks/index.js.map +0 -1
- package/dist/hooks/index.mjs +0 -8255
- package/dist/hooks/index.mjs.map +0 -1
- package/dist/index-50hpiPrV.d.ts +0 -116
- package/dist/index-B9zQVEVi.d.mts +0 -116
- package/dist/index.d.mts +0 -5
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -123
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -118
- package/dist/index.mjs.map +0 -1
- package/dist/infrastructure/index.d.mts +0 -423
- package/dist/infrastructure/index.d.ts +0 -423
- package/dist/infrastructure/index.js +0 -633
- package/dist/infrastructure/index.js.map +0 -1
- package/dist/infrastructure/index.mjs +0 -619
- package/dist/infrastructure/index.mjs.map +0 -1
- package/dist/label-DWTEkNPo.d.ts +0 -226
- package/dist/label-LPpdcoBx.d.mts +0 -226
- package/dist/layout/index.d.mts +0 -48
- package/dist/layout/index.d.ts +0 -48
- package/dist/layout/index.js +0 -117
- package/dist/layout/index.js.map +0 -1
- package/dist/layout/index.mjs +0 -90
- package/dist/layout/index.mjs.map +0 -1
- package/dist/navigation/index.d.mts +0 -16
- package/dist/navigation/index.d.ts +0 -16
- package/dist/navigation/index.js +0 -53
- package/dist/navigation/index.js.map +0 -1
- package/dist/navigation/index.mjs +0 -50
- package/dist/navigation/index.mjs.map +0 -1
- package/dist/notification/index.d.mts +0 -105
- package/dist/notification/index.d.ts +0 -105
- package/dist/notification/index.js +0 -278
- package/dist/notification/index.js.map +0 -1
- package/dist/notification/index.mjs +0 -274
- package/dist/notification/index.mjs.map +0 -1
- package/dist/organization/index.d.mts +0 -99
- package/dist/organization/index.d.ts +0 -99
- package/dist/organization/index.js +0 -360
- package/dist/organization/index.js.map +0 -1
- package/dist/organization/index.mjs +0 -352
- package/dist/organization/index.mjs.map +0 -1
- package/dist/plugin/index.d.mts +0 -83
- package/dist/plugin/index.d.ts +0 -83
- package/dist/plugin/index.js +0 -86
- package/dist/plugin/index.js.map +0 -1
- package/dist/plugin/index.mjs +0 -84
- package/dist/plugin/index.mjs.map +0 -1
- package/dist/providers/index.d.mts +0 -25
- package/dist/providers/index.d.ts +0 -25
- package/dist/providers/index.js +0 -84
- package/dist/providers/index.js.map +0 -1
- package/dist/providers/index.mjs +0 -77
- package/dist/providers/index.mjs.map +0 -1
- package/dist/rbac/index.d.mts +0 -226
- package/dist/rbac/index.d.ts +0 -226
- package/dist/rbac/index.js +0 -4784
- package/dist/rbac/index.js.map +0 -1
- package/dist/rbac/index.mjs +0 -4722
- package/dist/rbac/index.mjs.map +0 -1
- package/dist/rbac/permissions.d.mts +0 -26
- package/dist/rbac/permissions.d.ts +0 -26
- package/dist/rbac/permissions.js +0 -94
- package/dist/rbac/permissions.js.map +0 -1
- package/dist/rbac/permissions.mjs +0 -90
- package/dist/rbac/permissions.mjs.map +0 -1
- package/dist/rbac/server.d.mts +0 -1
- package/dist/rbac/server.d.ts +0 -1
- package/dist/rbac/server.js +0 -128
- package/dist/rbac/server.js.map +0 -1
- package/dist/rbac/server.mjs +0 -124
- package/dist/rbac/server.mjs.map +0 -1
- package/dist/schemas/index.d.mts +0 -1257
- package/dist/schemas/index.d.ts +0 -1257
- package/dist/schemas/index.js +0 -572
- package/dist/schemas/index.js.map +0 -1
- package/dist/schemas/index.mjs +0 -523
- package/dist/schemas/index.mjs.map +0 -1
- package/dist/server-QuYCTa89.d.mts +0 -83
- package/dist/server-QuYCTa89.d.ts +0 -83
- package/dist/sonner-C74GlRDQ.d.mts +0 -71
- package/dist/sonner-C74GlRDQ.d.ts +0 -71
- package/dist/status-BOXZgIqX.d.mts +0 -12
- package/dist/status-BOXZgIqX.d.ts +0 -12
- package/dist/system/index.d.mts +0 -77
- package/dist/system/index.d.ts +0 -77
- package/dist/system/index.js +0 -102
- package/dist/system/index.js.map +0 -1
- package/dist/system/index.mjs +0 -100
- package/dist/system/index.mjs.map +0 -1
- package/dist/tabs-C6FfBwPY.d.mts +0 -18
- package/dist/tabs-C6FfBwPY.d.ts +0 -18
- package/dist/tenant-provider-B8eC_Wpb.d.mts +0 -27
- package/dist/tenant-provider-B8eC_Wpb.d.ts +0 -27
- package/dist/types/index.d.mts +0 -469
- package/dist/types/index.d.ts +0 -469
- package/dist/types/index.js +0 -25
- package/dist/types/index.js.map +0 -1
- package/dist/types/index.mjs +0 -21
- package/dist/types/index.mjs.map +0 -1
- package/dist/ui/auth.d.mts +0 -39
- package/dist/ui/auth.d.ts +0 -39
- package/dist/ui/auth.js +0 -4941
- package/dist/ui/auth.js.map +0 -1
- package/dist/ui/auth.mjs +0 -4896
- package/dist/ui/auth.mjs.map +0 -1
- package/dist/ui/crud.d.mts +0 -2
- package/dist/ui/crud.d.ts +0 -2
- package/dist/ui/crud.js +0 -4
- package/dist/ui/crud.js.map +0 -1
- package/dist/ui/crud.mjs +0 -3
- package/dist/ui/crud.mjs.map +0 -1
- package/dist/ui/data-display.d.mts +0 -596
- package/dist/ui/data-display.d.ts +0 -596
- package/dist/ui/data-display.js +0 -5307
- package/dist/ui/data-display.js.map +0 -1
- package/dist/ui/data-display.mjs +0 -5212
- package/dist/ui/data-display.mjs.map +0 -1
- package/dist/ui/feedback.d.mts +0 -55
- package/dist/ui/feedback.d.ts +0 -55
- package/dist/ui/feedback.js +0 -2608
- package/dist/ui/feedback.js.map +0 -1
- package/dist/ui/feedback.mjs +0 -2526
- package/dist/ui/feedback.mjs.map +0 -1
- package/dist/ui/forms.d.mts +0 -309
- package/dist/ui/forms.d.ts +0 -309
- package/dist/ui/forms.js +0 -4656
- package/dist/ui/forms.js.map +0 -1
- package/dist/ui/forms.mjs +0 -4571
- package/dist/ui/forms.mjs.map +0 -1
- package/dist/ui/index.d.mts +0 -331
- package/dist/ui/index.d.ts +0 -331
- package/dist/ui/index.js +0 -16953
- package/dist/ui/index.js.map +0 -1
- package/dist/ui/index.mjs +0 -16598
- package/dist/ui/index.mjs.map +0 -1
- package/dist/ui/primitives/client.d.mts +0 -61
- package/dist/ui/primitives/client.d.ts +0 -61
- package/dist/ui/primitives/client.js +0 -3408
- package/dist/ui/primitives/client.js.map +0 -1
- package/dist/ui/primitives/client.mjs +0 -3256
- package/dist/ui/primitives/client.mjs.map +0 -1
- package/dist/ui/primitives.d.mts +0 -113
- package/dist/ui/primitives.d.ts +0 -113
- package/dist/ui/primitives.js +0 -3356
- package/dist/ui/primitives.js.map +0 -1
- package/dist/ui/primitives.mjs +0 -3227
- package/dist/ui/primitives.mjs.map +0 -1
- package/dist/user/index.d.mts +0 -228
- package/dist/user/index.d.ts +0 -228
- package/dist/user/index.js +0 -4306
- package/dist/user/index.js.map +0 -1
- package/dist/user/index.mjs +0 -4260
- package/dist/user/index.mjs.map +0 -1
- package/dist/utils/index.d.mts +0 -205
- package/dist/utils/index.d.ts +0 -205
- package/dist/utils/index.js +0 -574
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/index.mjs +0 -514
- package/dist/utils/index.mjs.map +0 -1
- package/dist/workflow/index.d.mts +0 -40
- package/dist/workflow/index.d.ts +0 -40
- package/dist/workflow/index.js +0 -3710
- package/dist/workflow/index.js.map +0 -1
- package/dist/workflow/index.mjs +0 -3677
- package/dist/workflow/index.mjs.map +0 -1
|
@@ -0,0 +1,1019 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useMemo } from "react";
|
|
4
|
+
import useSWR from "swr";
|
|
5
|
+
import {
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogHeader,
|
|
9
|
+
DialogTitle,
|
|
10
|
+
Button,
|
|
11
|
+
Input,
|
|
12
|
+
Label,
|
|
13
|
+
Checkbox,
|
|
14
|
+
Badge,
|
|
15
|
+
Avatar,
|
|
16
|
+
AvatarFallback,
|
|
17
|
+
AvatarImage,
|
|
18
|
+
Select,
|
|
19
|
+
SelectContent,
|
|
20
|
+
SelectItem,
|
|
21
|
+
SelectTrigger,
|
|
22
|
+
SelectValue,
|
|
23
|
+
Switch,
|
|
24
|
+
} from "../../ui";
|
|
25
|
+
import { toast } from "sonner";
|
|
26
|
+
import { cn } from "../../utils";
|
|
27
|
+
import {
|
|
28
|
+
X,
|
|
29
|
+
Search,
|
|
30
|
+
Eye,
|
|
31
|
+
EyeOff,
|
|
32
|
+
Loader2,
|
|
33
|
+
Ban,
|
|
34
|
+
UserCog,
|
|
35
|
+
Store,
|
|
36
|
+
CheckCircle2,
|
|
37
|
+
User,
|
|
38
|
+
Briefcase,
|
|
39
|
+
ShieldCheck,
|
|
40
|
+
KeyRound,
|
|
41
|
+
} from "lucide-react";
|
|
42
|
+
|
|
43
|
+
interface UnifiedProfileDialogProps {
|
|
44
|
+
open: boolean;
|
|
45
|
+
onOpenChange: (open: boolean) => void;
|
|
46
|
+
data?: any;
|
|
47
|
+
mode?: "create" | "edit" | "view";
|
|
48
|
+
viewMode: "admin" | "crm" | "srm";
|
|
49
|
+
roles?: any[];
|
|
50
|
+
onSaveProfile?: (data: any, id?: string) => Promise<void>;
|
|
51
|
+
onSaveRoles?: (userId: string, roleCodes: string[]) => Promise<void>;
|
|
52
|
+
onSavePassword?: (userId: string, password: string) => Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const fetcher = async (url: string) => {
|
|
56
|
+
const res = await fetch(url);
|
|
57
|
+
if (!res.ok) throw new Error("Failed to fetch");
|
|
58
|
+
const data = await res.json();
|
|
59
|
+
return Array.isArray(data) ? data : data.items || data.data || [];
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export function UnifiedProfileDialog({
|
|
63
|
+
open,
|
|
64
|
+
onOpenChange,
|
|
65
|
+
data,
|
|
66
|
+
mode = "edit",
|
|
67
|
+
viewMode = "admin",
|
|
68
|
+
roles = [],
|
|
69
|
+
onSaveProfile,
|
|
70
|
+
onSaveRoles,
|
|
71
|
+
onSavePassword,
|
|
72
|
+
}: UnifiedProfileDialogProps) {
|
|
73
|
+
// -- Data Fetching --
|
|
74
|
+
const { data: departments } = useSWR<{ id: string; name: string }[]>(
|
|
75
|
+
"/api/departments?status=active",
|
|
76
|
+
fetcher,
|
|
77
|
+
);
|
|
78
|
+
const { data: jobTitles } = useSWR<{ id: string; name: string }[]>(
|
|
79
|
+
"/api/job-titles?status=active",
|
|
80
|
+
fetcher,
|
|
81
|
+
);
|
|
82
|
+
const { data: suppliers } = useSWR<{ id: string; name: string }[]>(
|
|
83
|
+
"/api/suppliers?pageSize=100",
|
|
84
|
+
fetcher,
|
|
85
|
+
);
|
|
86
|
+
const { data: customerGroups } = useSWR<{ id: string; name: string }[]>(
|
|
87
|
+
"/api/crud/customer-groups?status=active",
|
|
88
|
+
fetcher,
|
|
89
|
+
);
|
|
90
|
+
const { data: branches } = useSWR<{ id: string; name: string }[]>(
|
|
91
|
+
"/api/branches",
|
|
92
|
+
fetcher,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// -- State --
|
|
96
|
+
const [activeSection, setActiveSection] = useState("general");
|
|
97
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
98
|
+
const [error, setError] = useState("");
|
|
99
|
+
|
|
100
|
+
// Form State
|
|
101
|
+
const [userType, setUserType] = useState<string>("employee");
|
|
102
|
+
const [profileData, setProfileData] = useState<any>({});
|
|
103
|
+
const [selectedRoles, setSelectedRoles] = useState<string[]>([]);
|
|
104
|
+
const [selectedBranches, setSelectedBranches] = useState<string[]>([]);
|
|
105
|
+
const [passwordData, setPasswordData] = useState({
|
|
106
|
+
password: "",
|
|
107
|
+
confirm: "",
|
|
108
|
+
});
|
|
109
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
110
|
+
const [roleSearchQuery, setRoleSearchQuery] = useState("");
|
|
111
|
+
const [enableAccount, setEnableAccount] = useState(false);
|
|
112
|
+
|
|
113
|
+
const displayData = data || {};
|
|
114
|
+
|
|
115
|
+
// Derived state
|
|
116
|
+
const isCustomer = viewMode === "crm" || userType === "customer";
|
|
117
|
+
|
|
118
|
+
// -- Navigation Items (Conditional) --
|
|
119
|
+
const navItems = useMemo(() => {
|
|
120
|
+
// For Customers: Only ONE section with all info merged
|
|
121
|
+
if (isCustomer) {
|
|
122
|
+
return [{ id: "general", label: "Thông tin khách hàng", icon: User }];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// For Employees/Suppliers: Multiple sections
|
|
126
|
+
const items: { id: string; label: string; icon: any }[] = [
|
|
127
|
+
{ id: "general", label: "Thông tin chung", icon: User },
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
if (userType === "employee") {
|
|
131
|
+
items.push({ id: "work", label: "Công việc", icon: Briefcase });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (userType === "supplier") {
|
|
135
|
+
items.push({ id: "supplier_link", label: "Nhà cung cấp", icon: Store });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Roles & Security for users with accounts
|
|
139
|
+
items.push({ id: "roles", label: "Phân quyền", icon: ShieldCheck });
|
|
140
|
+
items.push({ id: "security", label: "Bảo mật", icon: KeyRound });
|
|
141
|
+
|
|
142
|
+
return items;
|
|
143
|
+
}, [userType, isCustomer]);
|
|
144
|
+
|
|
145
|
+
// -- Initialization --
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (open) {
|
|
148
|
+
setError("");
|
|
149
|
+
setIsSubmitting(false);
|
|
150
|
+
setActiveSection("general");
|
|
151
|
+
|
|
152
|
+
if (mode === "create") {
|
|
153
|
+
const initialType =
|
|
154
|
+
viewMode === "crm"
|
|
155
|
+
? "customer"
|
|
156
|
+
: viewMode === "srm"
|
|
157
|
+
? "supplier"
|
|
158
|
+
: "employee";
|
|
159
|
+
setUserType(initialType);
|
|
160
|
+
setProfileData({
|
|
161
|
+
status: "active",
|
|
162
|
+
name: "",
|
|
163
|
+
email: "",
|
|
164
|
+
phone: "",
|
|
165
|
+
address: "",
|
|
166
|
+
departmentId: "",
|
|
167
|
+
jobTitleId: "",
|
|
168
|
+
supplierId: "",
|
|
169
|
+
groupId: "",
|
|
170
|
+
idNumber: "",
|
|
171
|
+
gender: "",
|
|
172
|
+
birthday: "",
|
|
173
|
+
});
|
|
174
|
+
setSelectedRoles([]);
|
|
175
|
+
setSelectedBranches([]);
|
|
176
|
+
setPasswordData({ password: "", confirm: "" });
|
|
177
|
+
setEnableAccount(false);
|
|
178
|
+
} else {
|
|
179
|
+
const d = displayData;
|
|
180
|
+
let type = "employee";
|
|
181
|
+
if (viewMode === "crm" || d.userType === "customer" || d.customerId)
|
|
182
|
+
type = "customer";
|
|
183
|
+
else if (d.userType === "supplier" || d.supplierId) type = "supplier";
|
|
184
|
+
else if (d.userType) type = d.userType;
|
|
185
|
+
|
|
186
|
+
setUserType(type);
|
|
187
|
+
setProfileData({
|
|
188
|
+
status: d.status || "active",
|
|
189
|
+
name: d.name || "",
|
|
190
|
+
email: d.email || "",
|
|
191
|
+
phone: d.phone || d.profiles?.phone || "",
|
|
192
|
+
address: d.address || d.profiles?.address || "",
|
|
193
|
+
departmentId: d.departmentId || d.profiles?.departmentId || "",
|
|
194
|
+
jobTitleId: d.jobTitleId || d.profiles?.jobTitleId || "",
|
|
195
|
+
supplierId: d.supplierId || d.profiles?.supplierId || "",
|
|
196
|
+
groupId: d.groupId || "",
|
|
197
|
+
idNumber: d.idNumber || "",
|
|
198
|
+
gender: d.gender || "",
|
|
199
|
+
birthday: d.birthday
|
|
200
|
+
? new Date(d.birthday).toISOString().split("T")[0]
|
|
201
|
+
: "",
|
|
202
|
+
});
|
|
203
|
+
const roles = d.roleCodes || [];
|
|
204
|
+
setSelectedRoles(roles);
|
|
205
|
+
setSelectedBranches(d.branchIds || []);
|
|
206
|
+
setPasswordData({ password: "", confirm: "" });
|
|
207
|
+
// Enable account if user has roles or if it's not a customer (employees always have accounts?)
|
|
208
|
+
// For now, if roles exist, we assume account is enabled.
|
|
209
|
+
setEnableAccount(roles.length > 0 || type !== "customer");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}, [open, data, mode, viewMode]);
|
|
213
|
+
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
if (open && branches?.length === 1) {
|
|
216
|
+
if (mode === "create" || (data?.branchIds?.length || 0) === 0) {
|
|
217
|
+
if (selectedBranches.length === 0) {
|
|
218
|
+
setSelectedBranches([branches[0].id]);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}, [open, branches, mode, data]);
|
|
223
|
+
|
|
224
|
+
// -- Helpers --
|
|
225
|
+
const getInitials = (name: string | null) => {
|
|
226
|
+
if (!name) return "U";
|
|
227
|
+
return name
|
|
228
|
+
.split(" ")
|
|
229
|
+
.map((n) => n[0])
|
|
230
|
+
.join("")
|
|
231
|
+
.toUpperCase()
|
|
232
|
+
.slice(0, 2);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const filteredRoles = useMemo(() => {
|
|
236
|
+
if (!roleSearchQuery.trim()) return roles;
|
|
237
|
+
const query = roleSearchQuery.toLowerCase();
|
|
238
|
+
return roles.filter(
|
|
239
|
+
(r) =>
|
|
240
|
+
r.name.toLowerCase().includes(query) ||
|
|
241
|
+
r.code.toLowerCase().includes(query),
|
|
242
|
+
);
|
|
243
|
+
}, [roles, roleSearchQuery]);
|
|
244
|
+
|
|
245
|
+
// -- Save Handler --
|
|
246
|
+
const handleSave = async () => {
|
|
247
|
+
try {
|
|
248
|
+
setIsSubmitting(true);
|
|
249
|
+
setError("");
|
|
250
|
+
|
|
251
|
+
// Prepare payload
|
|
252
|
+
const payload: any = {
|
|
253
|
+
...profileData,
|
|
254
|
+
userType, // Add userType to payload
|
|
255
|
+
roleCodes: selectedRoles,
|
|
256
|
+
branchIds: selectedBranches,
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// Handle Password for Customer
|
|
260
|
+
if (isCustomer) {
|
|
261
|
+
if (enableAccount) {
|
|
262
|
+
if (passwordData.password) {
|
|
263
|
+
if (passwordData.password !== passwordData.confirm) {
|
|
264
|
+
setError("Mật khẩu xác nhận không khớp");
|
|
265
|
+
setIsSubmitting(false);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (passwordData.password.length < 6) {
|
|
269
|
+
setError("Mật khẩu phải có ít nhất 6 ký tự");
|
|
270
|
+
setIsSubmitting(false);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
payload.password = passwordData.password;
|
|
274
|
+
} else if (mode === "create") {
|
|
275
|
+
// If creating a customer account, password is required
|
|
276
|
+
setError("Mật khẩu là bắt buộc cho tài khoản khách hàng mới");
|
|
277
|
+
setIsSubmitting(false);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
// If account not enabled, default or clear
|
|
282
|
+
if (mode === "create") {
|
|
283
|
+
payload.password = "Goeat@123456";
|
|
284
|
+
} else {
|
|
285
|
+
delete payload.password;
|
|
286
|
+
payload.roleCodes = [];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
if (passwordData.password) {
|
|
291
|
+
if (passwordData.password !== passwordData.confirm) {
|
|
292
|
+
setError("Mật khẩu xác nhận không khớp");
|
|
293
|
+
setIsSubmitting(false);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (passwordData.password.length < 6) {
|
|
297
|
+
setError("Mật khẩu phải có ít nhất 6 ký tự");
|
|
298
|
+
setIsSubmitting(false);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
payload.password = passwordData.password;
|
|
302
|
+
} else if (mode === "create") {
|
|
303
|
+
setError("Mật khẩu là bắt buộc cho tài khoản mới");
|
|
304
|
+
setIsSubmitting(false);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!payload.name) {
|
|
310
|
+
setError("Tên là bắt buộc");
|
|
311
|
+
setIsSubmitting(false);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Call API
|
|
316
|
+
if (mode === "create") {
|
|
317
|
+
if (onSaveProfile) await onSaveProfile(payload);
|
|
318
|
+
toast.success("Đã tạo hồ sơ mới thành công");
|
|
319
|
+
} else {
|
|
320
|
+
// Save all data at once (profile + roles + branches)
|
|
321
|
+
if (onSaveProfile) {
|
|
322
|
+
await onSaveProfile(payload, data.id);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Also save password if provided
|
|
326
|
+
if (payload.password && onSavePassword) {
|
|
327
|
+
await onSavePassword(data.id, payload.password);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
toast.success("Đã lưu thông tin");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
onOpenChange(false);
|
|
334
|
+
} catch (e: any) {
|
|
335
|
+
console.error(e);
|
|
336
|
+
setError(e.message || "Đã xảy ra lỗi");
|
|
337
|
+
toast.error("Đã xảy ra lỗi khi lưu thông tin");
|
|
338
|
+
} finally {
|
|
339
|
+
setIsSubmitting(false);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// -- Render Helpers --
|
|
344
|
+
const isActive = profileData.status === "active";
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
348
|
+
<DialogContent className="w-[100dvw] h-[100dvh] max-w-none max-h-none sm:w-auto sm:max-w-5xl sm:h-[85vh] flex flex-col p-0 gap-0 overflow-hidden border border-[#dee3e9] dark:border-[#1c1e21] shadow-[0_12px_32px_rgba(0,0,0,0.1)] bg-[#ffffff] dark:bg-[#0a1317] rounded-none sm:rounded-[12px] [&>button.absolute]:hidden">
|
|
349
|
+
<DialogHeader className="sr-only">
|
|
350
|
+
<DialogTitle>
|
|
351
|
+
{mode === "create" ? "Tạo mới" : "Chỉnh sửa"}
|
|
352
|
+
</DialogTitle>
|
|
353
|
+
</DialogHeader>
|
|
354
|
+
|
|
355
|
+
<div className="flex flex-col w-full h-full overflow-hidden">
|
|
356
|
+
{/* TOP HEADER: Profile Info + Actions */}
|
|
357
|
+
<div className="shrink-0 flex flex-col border-b border-[#e2e8f0] dark:border-[#334155] bg-[#f8fafc] dark:bg-[#0f172a] px-6 pt-5 pb-0">
|
|
358
|
+
<div className="flex flex-col sm:flex-row sm:items-start justify-between gap-4 pb-5">
|
|
359
|
+
{/* Profile Header */}
|
|
360
|
+
<div className="flex items-center gap-4">
|
|
361
|
+
<div className="relative">
|
|
362
|
+
<Avatar className="h-12 w-12 sm:h-14 sm:w-14 rounded-full border border-[#e2e8f0] dark:border-[#334155]">
|
|
363
|
+
<AvatarImage
|
|
364
|
+
src={displayData.avatar}
|
|
365
|
+
className="object-cover rounded-full"
|
|
366
|
+
/>
|
|
367
|
+
<AvatarFallback className="text-[16px] sm:text-[18px] font-semibold bg-[#1641CE] text-white dark:bg-[#1E40AF] dark:text-white rounded-full">
|
|
368
|
+
{getInitials(profileData.name)}
|
|
369
|
+
</AvatarFallback>
|
|
370
|
+
</Avatar>
|
|
371
|
+
<div
|
|
372
|
+
className={cn(
|
|
373
|
+
"absolute -bottom-0.5 -right-0.5 w-3.5 h-3.5 sm:w-4 sm:h-4 border-[2px] border-[#f8fafc] dark:border-[#0f172a] rounded-full z-10",
|
|
374
|
+
isActive ? "bg-[#10b981]" : "bg-[#cbd5e1]"
|
|
375
|
+
)}
|
|
376
|
+
title={isActive ? "Hoạt động" : "Đã khóa"}
|
|
377
|
+
/>
|
|
378
|
+
</div>
|
|
379
|
+
<div>
|
|
380
|
+
<h2 className="text-[18px] sm:text-[20px] font-semibold leading-tight text-[#0f172a] dark:text-white">
|
|
381
|
+
{profileData.name || "Chưa đặt tên"}
|
|
382
|
+
</h2>
|
|
383
|
+
<p className="mt-1 text-[13px] text-[#64748b] dark:text-[#94a3b8]">
|
|
384
|
+
{profileData.email || profileData.phone || "Chưa có thông tin liên hệ"}
|
|
385
|
+
</p>
|
|
386
|
+
</div>
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
{/* Actions & User Type */}
|
|
390
|
+
<div className="flex items-center gap-2 sm:gap-3 shrink-0">
|
|
391
|
+
{viewMode === "admin" ? (
|
|
392
|
+
<Select
|
|
393
|
+
value={userType}
|
|
394
|
+
onValueChange={(val) => setUserType(val)}
|
|
395
|
+
disabled={mode === "view"}
|
|
396
|
+
>
|
|
397
|
+
<SelectTrigger className="w-[140px] bg-white dark:bg-[#1e293b] border-[#e2e8f0] dark:border-[#334155] focus:ring-[#1641CE] focus:border-[#1641CE] h-9 rounded-md">
|
|
398
|
+
<SelectValue placeholder="Chọn loại" />
|
|
399
|
+
</SelectTrigger>
|
|
400
|
+
<SelectContent>
|
|
401
|
+
<SelectItem value="employee">
|
|
402
|
+
<span className="flex items-center gap-2">
|
|
403
|
+
<UserCog className="h-4 w-4 text-[#1641CE]" />
|
|
404
|
+
Nhân viên
|
|
405
|
+
</span>
|
|
406
|
+
</SelectItem>
|
|
407
|
+
<SelectItem value="customer">
|
|
408
|
+
<span className="flex items-center gap-2">
|
|
409
|
+
<User className="h-4 w-4 text-[#059669]" />
|
|
410
|
+
Khách hàng
|
|
411
|
+
</span>
|
|
412
|
+
</SelectItem>
|
|
413
|
+
<SelectItem value="supplier">
|
|
414
|
+
<span className="flex items-center gap-2">
|
|
415
|
+
<Store className="h-4 w-4 text-[#EA580C]" />
|
|
416
|
+
Nhà cung cấp
|
|
417
|
+
</span>
|
|
418
|
+
</SelectItem>
|
|
419
|
+
</SelectContent>
|
|
420
|
+
</Select>
|
|
421
|
+
) : (
|
|
422
|
+
<div className="flex items-center gap-1.5 shrink-0">
|
|
423
|
+
{userType === "employee" && (
|
|
424
|
+
<span className="text-[12px] px-2.5 py-1 font-semibold rounded-md bg-[#1641CE]/10 text-[#1641CE] border border-[#1641CE]/20 inline-flex items-center">
|
|
425
|
+
<UserCog className="h-3.5 w-3.5 mr-1.5" /> Nhân viên
|
|
426
|
+
</span>
|
|
427
|
+
)}
|
|
428
|
+
{userType === "customer" && (
|
|
429
|
+
<span className="text-[12px] px-2.5 py-1 font-semibold rounded-md bg-[#059669] text-white inline-flex items-center">
|
|
430
|
+
<User className="h-3.5 w-3.5 mr-1.5" /> Khách hàng
|
|
431
|
+
</span>
|
|
432
|
+
)}
|
|
433
|
+
{userType === "supplier" && (
|
|
434
|
+
<span className="text-[12px] px-2.5 py-1 font-semibold rounded-md bg-[#EA580C] text-white inline-flex items-center">
|
|
435
|
+
<Store className="h-3.5 w-3.5 mr-1.5" /> Nhà cung cấp
|
|
436
|
+
</span>
|
|
437
|
+
)}
|
|
438
|
+
</div>
|
|
439
|
+
)}
|
|
440
|
+
|
|
441
|
+
<Button
|
|
442
|
+
variant="outline"
|
|
443
|
+
onClick={() => onOpenChange(false)}
|
|
444
|
+
className="border border-[#e2e8f0] bg-white dark:border-[#334155] text-[#475569] dark:text-white dark:bg-[#1e293b] hover:bg-[#f1f5f9] gap-2 h-9 rounded-md px-4 text-[13px] font-medium"
|
|
445
|
+
>
|
|
446
|
+
<span className="hidden sm:inline">Đóng</span>
|
|
447
|
+
<X className="h-4 w-4 sm:hidden" />
|
|
448
|
+
</Button>
|
|
449
|
+
<Button
|
|
450
|
+
onClick={handleSave}
|
|
451
|
+
disabled={isSubmitting}
|
|
452
|
+
className="bg-primary hover:bg-primary/90 text-primary-foreground min-w-[80px] h-9 px-5 gap-2 font-medium text-[13px] rounded-md"
|
|
453
|
+
>
|
|
454
|
+
{isSubmitting ? (
|
|
455
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
456
|
+
) : (
|
|
457
|
+
<CheckCircle2 className="h-4 w-4" />
|
|
458
|
+
)}
|
|
459
|
+
<span>{mode === "create" ? "Tạo" : "Lưu"}</span>
|
|
460
|
+
</Button>
|
|
461
|
+
</div>
|
|
462
|
+
</div>
|
|
463
|
+
|
|
464
|
+
{/* Corporate Navy Underline Tabs */}
|
|
465
|
+
<nav className="flex gap-6 overflow-x-auto hide-scrollbar">
|
|
466
|
+
{navItems.map((item) => (
|
|
467
|
+
<button
|
|
468
|
+
key={item.id}
|
|
469
|
+
onClick={() => setActiveSection(item.id)}
|
|
470
|
+
className={cn(
|
|
471
|
+
"relative pb-3 text-[14px] font-medium transition-all whitespace-nowrap flex items-center gap-2",
|
|
472
|
+
activeSection === item.id
|
|
473
|
+
? "text-primary dark:text-primary"
|
|
474
|
+
: "text-[#64748b] hover:text-[#0f172a] dark:text-[#94a3b8] dark:hover:text-white"
|
|
475
|
+
)}
|
|
476
|
+
>
|
|
477
|
+
<item.icon
|
|
478
|
+
className={cn(
|
|
479
|
+
"h-[16px] w-[16px]",
|
|
480
|
+
activeSection === item.id
|
|
481
|
+
? "text-primary dark:text-primary"
|
|
482
|
+
: "text-[#94a3b8]"
|
|
483
|
+
)}
|
|
484
|
+
strokeWidth={2}
|
|
485
|
+
/>
|
|
486
|
+
{item.label}
|
|
487
|
+
{activeSection === item.id && (
|
|
488
|
+
<div className="absolute bottom-0 left-0 right-0 h-[2px] bg-primary dark:bg-primary rounded-t-sm" />
|
|
489
|
+
)}
|
|
490
|
+
</button>
|
|
491
|
+
))}
|
|
492
|
+
</nav>
|
|
493
|
+
</div>
|
|
494
|
+
|
|
495
|
+
{/* MAIN CONTENT AREA */}
|
|
496
|
+
<div className="flex-1 bg-white dark:bg-[#0f172a] overflow-y-auto overscroll-contain touch-pan-y" style={{ WebkitOverflowScrolling: 'touch' }}>
|
|
497
|
+
<div className="p-4 sm:p-5 max-w-4xl mx-auto space-y-4 pb-20">
|
|
498
|
+
{/* General Section */}
|
|
499
|
+
{activeSection === "general" && (
|
|
500
|
+
<div className="space-y-4 animate-in fade-in zoom-in-95 duration-200">
|
|
501
|
+
|
|
502
|
+
<div className="grid md:grid-cols-2 gap-x-6 gap-y-4">
|
|
503
|
+
<div className="space-y-1.5">
|
|
504
|
+
<Label className="text-[13px] font-medium text-[#334155] dark:text-[#cbd5e1]">
|
|
505
|
+
Họ và tên <span className="text-[#e41e3f]">*</span>
|
|
506
|
+
</Label>
|
|
507
|
+
<Input
|
|
508
|
+
value={profileData.name}
|
|
509
|
+
onChange={(e) =>
|
|
510
|
+
setProfileData({
|
|
511
|
+
...profileData,
|
|
512
|
+
name: e.target.value,
|
|
513
|
+
})
|
|
514
|
+
}
|
|
515
|
+
placeholder="Nguyễn Văn A"
|
|
516
|
+
className="bg-white dark:bg-[#1e293b] border-[#e2e8f0] dark:border-[#334155] focus:ring-primary focus:border-primary rounded-md h-9 text-[13px]"
|
|
517
|
+
/>
|
|
518
|
+
</div>
|
|
519
|
+
<div className="space-y-1.5">
|
|
520
|
+
<Label className="text-[13px] font-medium text-[#334155] dark:text-[#cbd5e1]">Email</Label>
|
|
521
|
+
<Input
|
|
522
|
+
value={profileData.email}
|
|
523
|
+
onChange={(e) =>
|
|
524
|
+
setProfileData({
|
|
525
|
+
...profileData,
|
|
526
|
+
email: e.target.value,
|
|
527
|
+
})
|
|
528
|
+
}
|
|
529
|
+
placeholder="email@example.com"
|
|
530
|
+
className="bg-white dark:bg-[#1e293b] border-[#e2e8f0] dark:border-[#334155] focus:ring-primary focus:border-primary rounded-md h-9 text-[13px]"
|
|
531
|
+
/>
|
|
532
|
+
</div>
|
|
533
|
+
<div className="space-y-1.5">
|
|
534
|
+
<Label className="text-[13px] font-medium text-[#334155] dark:text-[#cbd5e1]">Số điện thoại</Label>
|
|
535
|
+
<Input
|
|
536
|
+
value={profileData.phone}
|
|
537
|
+
onChange={(e) =>
|
|
538
|
+
setProfileData({
|
|
539
|
+
...profileData,
|
|
540
|
+
phone: e.target.value,
|
|
541
|
+
})
|
|
542
|
+
}
|
|
543
|
+
placeholder="0912..."
|
|
544
|
+
className="bg-white dark:bg-[#1e293b] border-[#e2e8f0] dark:border-[#334155] focus:ring-primary focus:border-primary rounded-md h-9 text-[13px]"
|
|
545
|
+
/>
|
|
546
|
+
</div>
|
|
547
|
+
<div className="space-y-1.5">
|
|
548
|
+
<Label className="text-[13px] font-medium text-[#334155] dark:text-[#cbd5e1]">Địa chỉ</Label>
|
|
549
|
+
<Input
|
|
550
|
+
value={profileData.address}
|
|
551
|
+
onChange={(e) =>
|
|
552
|
+
setProfileData({
|
|
553
|
+
...profileData,
|
|
554
|
+
address: e.target.value,
|
|
555
|
+
})
|
|
556
|
+
}
|
|
557
|
+
placeholder="Số nhà, đường..."
|
|
558
|
+
className="bg-white dark:bg-[#1e293b] border-[#e2e8f0] dark:border-[#334155] focus:ring-primary focus:border-primary rounded-md h-9 text-[13px]"
|
|
559
|
+
/>
|
|
560
|
+
</div>
|
|
561
|
+
{/* Status Toggle */}
|
|
562
|
+
{viewMode === "admin" && (
|
|
563
|
+
<div className="space-y-1.5">
|
|
564
|
+
<Label className="text-[13px] font-medium text-[#334155] dark:text-[#cbd5e1]">Trạng thái</Label>
|
|
565
|
+
<div className="flex items-center gap-3 h-9 px-3 rounded-[6px] border border-[#ced0d4] dark:border-[#333840] bg-[#ffffff] dark:bg-[#0a1317]">
|
|
566
|
+
<Switch
|
|
567
|
+
id="user-active-switch"
|
|
568
|
+
checked={isActive}
|
|
569
|
+
onCheckedChange={(checked) =>
|
|
570
|
+
setProfileData({
|
|
571
|
+
...profileData,
|
|
572
|
+
status: checked ? "active" : "inactive",
|
|
573
|
+
})
|
|
574
|
+
}
|
|
575
|
+
/>
|
|
576
|
+
<span
|
|
577
|
+
className={cn(
|
|
578
|
+
"text-[13px] font-bold tracking-[-0.13px]",
|
|
579
|
+
isActive ? "text-[#31a24c]" : "text-[#444950]",
|
|
580
|
+
)}
|
|
581
|
+
>
|
|
582
|
+
{isActive ? "Hoạt động" : "Đã khóa"}
|
|
583
|
+
</span>
|
|
584
|
+
</div>
|
|
585
|
+
</div>
|
|
586
|
+
)}
|
|
587
|
+
{/* Additional Info for All */}
|
|
588
|
+
<div className="space-y-1.5">
|
|
589
|
+
<Label className="text-[13px] font-medium text-[#334155] dark:text-[#cbd5e1]">Ngày sinh</Label>
|
|
590
|
+
<Input
|
|
591
|
+
type="date"
|
|
592
|
+
value={profileData.birthday}
|
|
593
|
+
onChange={(e) =>
|
|
594
|
+
setProfileData({
|
|
595
|
+
...profileData,
|
|
596
|
+
birthday: e.target.value,
|
|
597
|
+
})
|
|
598
|
+
}
|
|
599
|
+
className="bg-white dark:bg-[#1e293b] border-[#e2e8f0] dark:border-[#334155] focus:ring-primary focus:border-primary rounded-md h-9 text-[13px]"
|
|
600
|
+
/>
|
|
601
|
+
</div>
|
|
602
|
+
<div className="space-y-1.5">
|
|
603
|
+
<Label className="text-[13px] font-medium text-[#334155] dark:text-[#cbd5e1]">Giới tính</Label>
|
|
604
|
+
<Select
|
|
605
|
+
value={profileData.gender}
|
|
606
|
+
onValueChange={(val) =>
|
|
607
|
+
setProfileData({ ...profileData, gender: val })
|
|
608
|
+
}
|
|
609
|
+
>
|
|
610
|
+
<SelectTrigger className="bg-white dark:bg-[#1e293b] border-[#e2e8f0] dark:border-[#334155] focus:ring-primary focus:border-primary rounded-md h-9 text-[13px]">
|
|
611
|
+
<SelectValue placeholder="Chọn giới tính" />
|
|
612
|
+
</SelectTrigger>
|
|
613
|
+
<SelectContent>
|
|
614
|
+
<SelectItem value="male">Nam</SelectItem>
|
|
615
|
+
<SelectItem value="female">Nữ</SelectItem>
|
|
616
|
+
<SelectItem value="other">Khác</SelectItem>
|
|
617
|
+
</SelectContent>
|
|
618
|
+
</Select>
|
|
619
|
+
</div>
|
|
620
|
+
<div className="space-y-1.5">
|
|
621
|
+
<Label className="text-[13px] font-medium text-[#334155] dark:text-[#cbd5e1]">CCCD/CMND</Label>
|
|
622
|
+
<Input
|
|
623
|
+
value={profileData.idNumber}
|
|
624
|
+
onChange={(e) =>
|
|
625
|
+
setProfileData({
|
|
626
|
+
...profileData,
|
|
627
|
+
idNumber: e.target.value,
|
|
628
|
+
})
|
|
629
|
+
}
|
|
630
|
+
className="bg-white dark:bg-[#1e293b] border-[#e2e8f0] dark:border-[#334155] focus:ring-primary focus:border-primary rounded-md h-9 text-[13px]"
|
|
631
|
+
/>
|
|
632
|
+
</div>
|
|
633
|
+
|
|
634
|
+
{/* Customer Specific */}
|
|
635
|
+
{isCustomer && (
|
|
636
|
+
<div className="space-y-1.5">
|
|
637
|
+
<Label className="text-[13px] font-medium text-[#334155] dark:text-[#cbd5e1]">
|
|
638
|
+
Nhóm khách hàng
|
|
639
|
+
</Label>
|
|
640
|
+
<Select
|
|
641
|
+
value={profileData.groupId}
|
|
642
|
+
onValueChange={(val) =>
|
|
643
|
+
setProfileData({ ...profileData, groupId: val })
|
|
644
|
+
}
|
|
645
|
+
>
|
|
646
|
+
<SelectTrigger className="bg-white dark:bg-[#1e293b] border-[#e2e8f0] dark:border-[#334155] focus:ring-primary focus:border-primary rounded-md h-9 text-[13px]">
|
|
647
|
+
<SelectValue placeholder="Chọn nhóm" />
|
|
648
|
+
</SelectTrigger>
|
|
649
|
+
<SelectContent>
|
|
650
|
+
{customerGroups?.map((g) => (
|
|
651
|
+
<SelectItem key={g.id} value={g.id}>
|
|
652
|
+
{g.name}
|
|
653
|
+
</SelectItem>
|
|
654
|
+
))}
|
|
655
|
+
</SelectContent>
|
|
656
|
+
</Select>
|
|
657
|
+
</div>
|
|
658
|
+
)}
|
|
659
|
+
</div>
|
|
660
|
+
|
|
661
|
+
{isCustomer && (
|
|
662
|
+
<div className="pt-4">
|
|
663
|
+
<div className="flex items-center justify-between mb-4 border-b border-[#dee3e9] pb-2">
|
|
664
|
+
<h3 className="text-[18px] font-bold tracking-[-0.14px] text-[#0a1317] dark:text-white">
|
|
665
|
+
Tài khoản đăng nhập
|
|
666
|
+
</h3>
|
|
667
|
+
<Switch
|
|
668
|
+
checked={enableAccount}
|
|
669
|
+
onCheckedChange={setEnableAccount}
|
|
670
|
+
/>
|
|
671
|
+
</div>
|
|
672
|
+
|
|
673
|
+
{enableAccount && (
|
|
674
|
+
<div className="space-y-4 animate-in fade-in slide-in-from-top-2">
|
|
675
|
+
<div className="grid md:grid-cols-2 gap-x-6 gap-y-4">
|
|
676
|
+
<div className="space-y-1.5 col-span-2 md:col-span-1">
|
|
677
|
+
<Label className="text-[13px] font-medium text-[#334155] dark:text-[#cbd5e1]">
|
|
678
|
+
Mật khẩu
|
|
679
|
+
</Label>
|
|
680
|
+
<div className="relative">
|
|
681
|
+
<Input
|
|
682
|
+
type={showPassword ? "text" : "password"}
|
|
683
|
+
value={passwordData.password}
|
|
684
|
+
onChange={(e) =>
|
|
685
|
+
setPasswordData((prev) => ({
|
|
686
|
+
...prev,
|
|
687
|
+
password: e.target.value,
|
|
688
|
+
}))
|
|
689
|
+
}
|
|
690
|
+
placeholder="Nhập mật khẩu"
|
|
691
|
+
className="pr-10 bg-[#ffffff] dark:bg-[#0a1317] border-[#ced0d4] dark:border-[#333840] focus:ring-[#0064e0] focus:border-[#0064e0] rounded-[6px] h-9 text-[14px]"
|
|
692
|
+
/>
|
|
693
|
+
<Button
|
|
694
|
+
type="button"
|
|
695
|
+
variant="ghost"
|
|
696
|
+
size="icon"
|
|
697
|
+
className="absolute right-0 top-0 h-full w-9 text-[#444950] hover:text-[#0a1317]"
|
|
698
|
+
onClick={() =>
|
|
699
|
+
setShowPassword(!showPassword)
|
|
700
|
+
}
|
|
701
|
+
>
|
|
702
|
+
{showPassword ? (
|
|
703
|
+
<EyeOff className="h-4 w-4" />
|
|
704
|
+
) : (
|
|
705
|
+
<Eye className="h-4 w-4" />
|
|
706
|
+
)}
|
|
707
|
+
</Button>
|
|
708
|
+
</div>
|
|
709
|
+
<p className="text-[12px] text-[#444950] mt-1.5 flex items-center gap-1.5">
|
|
710
|
+
<span className="inline-block w-1.5 h-1.5 rounded-full bg-[#0064e0]" />
|
|
711
|
+
Mặc định:{" "}
|
|
712
|
+
<span className="font-mono font-bold text-[#1c1e21] dark:text-white">
|
|
713
|
+
Goeat@123456
|
|
714
|
+
</span>
|
|
715
|
+
</p>
|
|
716
|
+
</div>
|
|
717
|
+
</div>
|
|
718
|
+
|
|
719
|
+
<div className="space-y-2 pt-2">
|
|
720
|
+
<Label className="text-[13px] font-medium text-[#334155] dark:text-[#cbd5e1]">
|
|
721
|
+
Phân quyền
|
|
722
|
+
</Label>
|
|
723
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
724
|
+
{filteredRoles.map((role) => {
|
|
725
|
+
const isSelected = selectedRoles.includes(
|
|
726
|
+
role.code,
|
|
727
|
+
);
|
|
728
|
+
return (
|
|
729
|
+
<div
|
|
730
|
+
key={role.code}
|
|
731
|
+
className={cn(
|
|
732
|
+
"flex items-start gap-3 p-3 rounded-md border cursor-pointer hover:bg-slate-50 transition-colors",
|
|
733
|
+
isSelected
|
|
734
|
+
? "border-blue-500 bg-blue-50/50"
|
|
735
|
+
: "bg-background border-slate-200 dark:border-slate-800",
|
|
736
|
+
)}
|
|
737
|
+
onClick={() => {
|
|
738
|
+
setSelectedRoles((prev) =>
|
|
739
|
+
prev.includes(role.code)
|
|
740
|
+
? prev.filter(
|
|
741
|
+
(c) => c !== role.code,
|
|
742
|
+
)
|
|
743
|
+
: [...prev, role.code],
|
|
744
|
+
);
|
|
745
|
+
}}
|
|
746
|
+
>
|
|
747
|
+
<Checkbox
|
|
748
|
+
checked={isSelected}
|
|
749
|
+
className="mt-1 data-[state=checked]:bg-blue-600 data-[state=checked]:border-blue-600"
|
|
750
|
+
/>
|
|
751
|
+
<div className="space-y-0.5">
|
|
752
|
+
<p className="text-sm font-medium text-slate-900 dark:text-slate-100">
|
|
753
|
+
{role.name}
|
|
754
|
+
</p>
|
|
755
|
+
<p className="text-xs text-muted-foreground line-clamp-1">
|
|
756
|
+
{role.description || role.code}
|
|
757
|
+
</p>
|
|
758
|
+
</div>
|
|
759
|
+
</div>
|
|
760
|
+
);
|
|
761
|
+
})}
|
|
762
|
+
</div>
|
|
763
|
+
</div>
|
|
764
|
+
</div>
|
|
765
|
+
)}
|
|
766
|
+
</div>
|
|
767
|
+
)}
|
|
768
|
+
</div>
|
|
769
|
+
)}
|
|
770
|
+
|
|
771
|
+
{/* Work Section */}
|
|
772
|
+
{activeSection === "work" && (
|
|
773
|
+
<div className="space-y-4 animate-in fade-in zoom-in-95 duration-200">
|
|
774
|
+
<div className="grid md:grid-cols-2 gap-x-8 gap-y-6">
|
|
775
|
+
<div className="space-y-2">
|
|
776
|
+
<Label className="text-[13px] font-medium text-[#41454d] dark:text-[#9297a0]">Phòng ban</Label>
|
|
777
|
+
<Select
|
|
778
|
+
value={profileData.departmentId}
|
|
779
|
+
onValueChange={(val) =>
|
|
780
|
+
setProfileData({
|
|
781
|
+
...profileData,
|
|
782
|
+
departmentId: val,
|
|
783
|
+
})
|
|
784
|
+
}
|
|
785
|
+
>
|
|
786
|
+
<SelectTrigger className="bg-[#ffffff] dark:bg-[#181d26] border-[#dddddd] dark:border-[#333840] focus:ring-[#458fff] focus:border-[#458fff] shadow-sm rounded-[6px]">
|
|
787
|
+
<SelectValue placeholder="Chọn phòng ban" />
|
|
788
|
+
</SelectTrigger>
|
|
789
|
+
<SelectContent>
|
|
790
|
+
{departments?.map((d) => (
|
|
791
|
+
<SelectItem key={d.id} value={d.id}>
|
|
792
|
+
{d.name}
|
|
793
|
+
</SelectItem>
|
|
794
|
+
))}
|
|
795
|
+
</SelectContent>
|
|
796
|
+
</Select>
|
|
797
|
+
</div>
|
|
798
|
+
<div className="space-y-2">
|
|
799
|
+
<Label className="text-[13px] font-medium text-[#41454d] dark:text-[#9297a0]">Chức danh</Label>
|
|
800
|
+
<Select
|
|
801
|
+
value={profileData.jobTitleId}
|
|
802
|
+
onValueChange={(val) =>
|
|
803
|
+
setProfileData({ ...profileData, jobTitleId: val })
|
|
804
|
+
}
|
|
805
|
+
>
|
|
806
|
+
<SelectTrigger className="bg-[#ffffff] dark:bg-[#181d26] border-[#dddddd] dark:border-[#333840] focus:ring-[#458fff] focus:border-[#458fff] shadow-sm rounded-[6px]">
|
|
807
|
+
<SelectValue placeholder="Chọn chức danh" />
|
|
808
|
+
</SelectTrigger>
|
|
809
|
+
<SelectContent>
|
|
810
|
+
{jobTitles?.map((j) => (
|
|
811
|
+
<SelectItem key={j.id} value={j.id}>
|
|
812
|
+
{j.name}
|
|
813
|
+
</SelectItem>
|
|
814
|
+
))}
|
|
815
|
+
</SelectContent>
|
|
816
|
+
</Select>
|
|
817
|
+
</div>
|
|
818
|
+
<div className="col-span-2 space-y-3 pt-2">
|
|
819
|
+
<Label className="text-[13px] font-medium text-[#41454d] dark:text-[#9297a0]">Chi nhánh làm việc</Label>
|
|
820
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
821
|
+
{branches?.map((branch) => {
|
|
822
|
+
const isSelected = selectedBranches.includes(branch.id);
|
|
823
|
+
return (
|
|
824
|
+
<div
|
|
825
|
+
key={branch.id}
|
|
826
|
+
className={cn(
|
|
827
|
+
"flex items-center gap-3 p-3 rounded-md border cursor-pointer hover:bg-slate-50 transition-colors",
|
|
828
|
+
isSelected
|
|
829
|
+
? "border-blue-500 bg-blue-50/50"
|
|
830
|
+
: "bg-background border-slate-200 dark:border-slate-800",
|
|
831
|
+
)}
|
|
832
|
+
onClick={() => {
|
|
833
|
+
setSelectedBranches((prev) =>
|
|
834
|
+
prev.includes(branch.id)
|
|
835
|
+
? prev.filter((id) => id !== branch.id)
|
|
836
|
+
: [...prev, branch.id],
|
|
837
|
+
);
|
|
838
|
+
}}
|
|
839
|
+
>
|
|
840
|
+
<Checkbox
|
|
841
|
+
checked={isSelected}
|
|
842
|
+
className="data-[state=checked]:bg-blue-600 data-[state=checked]:border-blue-600"
|
|
843
|
+
/>
|
|
844
|
+
<span className="text-sm font-medium text-slate-900 dark:text-slate-100">
|
|
845
|
+
{branch.name}
|
|
846
|
+
</span>
|
|
847
|
+
</div>
|
|
848
|
+
);
|
|
849
|
+
})}
|
|
850
|
+
</div>
|
|
851
|
+
</div>
|
|
852
|
+
</div>
|
|
853
|
+
</div>
|
|
854
|
+
)}
|
|
855
|
+
|
|
856
|
+
{activeSection === "supplier_link" && (
|
|
857
|
+
<div className="space-y-4 animate-in fade-in zoom-in-95 duration-200">
|
|
858
|
+
<div className="max-w-2xl space-y-6">
|
|
859
|
+
<div className="space-y-2">
|
|
860
|
+
<Label className="text-[13px] font-medium text-[#41454d] dark:text-[#9297a0]">
|
|
861
|
+
Chọn nhà cung cấp
|
|
862
|
+
</Label>
|
|
863
|
+
<Select
|
|
864
|
+
value={profileData.supplierId}
|
|
865
|
+
onValueChange={(val) =>
|
|
866
|
+
setProfileData({ ...profileData, supplierId: val })
|
|
867
|
+
}
|
|
868
|
+
>
|
|
869
|
+
<SelectTrigger className="bg-[#ffffff] dark:bg-[#181d26] border-[#dddddd] dark:border-[#333840] focus:ring-[#458fff] focus:border-[#458fff] shadow-sm rounded-[6px]">
|
|
870
|
+
<SelectValue placeholder="Chọn nhà cung cấp" />
|
|
871
|
+
</SelectTrigger>
|
|
872
|
+
<SelectContent>
|
|
873
|
+
{suppliers?.map((s) => (
|
|
874
|
+
<SelectItem key={s.id} value={s.id}>
|
|
875
|
+
{s.name}
|
|
876
|
+
</SelectItem>
|
|
877
|
+
))}
|
|
878
|
+
</SelectContent>
|
|
879
|
+
</Select>
|
|
880
|
+
</div>
|
|
881
|
+
<div className="rounded-md bg-blue-50 p-4 text-sm text-blue-700 flex items-start gap-3">
|
|
882
|
+
<Store className="h-5 w-5 mt-0.5 shrink-0" />
|
|
883
|
+
<p>
|
|
884
|
+
Tài khoản này sẽ được liên kết với nhà cung cấp đã
|
|
885
|
+
chọn. Người dùng sẽ có quyền truy cập vào dữ liệu và
|
|
886
|
+
chức năng dành riêng cho nhà cung cấp này.
|
|
887
|
+
</p>
|
|
888
|
+
</div>
|
|
889
|
+
</div>
|
|
890
|
+
</div>
|
|
891
|
+
)}
|
|
892
|
+
|
|
893
|
+
{activeSection === "roles" && (
|
|
894
|
+
<div className="animate-in fade-in zoom-in-95 duration-200">
|
|
895
|
+
<div className="sticky -top-4 sm:-top-5 pt-2 sm:pt-2 pb-4 -mx-4 sm:-mx-5 px-4 sm:px-5 bg-white/95 dark:bg-[#0f172a]/95 backdrop-blur-sm z-10 border-b border-[#e2e8f0] dark:border-[#334155] mb-4 shadow-sm">
|
|
896
|
+
<div className="relative max-w-md">
|
|
897
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[#64748b]" />
|
|
898
|
+
<Input
|
|
899
|
+
placeholder="Tìm kiếm vai trò..."
|
|
900
|
+
value={roleSearchQuery}
|
|
901
|
+
onChange={(e) => setRoleSearchQuery(e.target.value)}
|
|
902
|
+
className="pl-9 bg-white dark:bg-[#1e293b] border-[#e2e8f0] dark:border-[#334155] focus:ring-primary focus:border-primary shadow-sm rounded-md h-9 text-[13px]"
|
|
903
|
+
/>
|
|
904
|
+
</div>
|
|
905
|
+
</div>
|
|
906
|
+
|
|
907
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
908
|
+
{filteredRoles.map((role) => {
|
|
909
|
+
const isSelected = selectedRoles.includes(role.code);
|
|
910
|
+
return (
|
|
911
|
+
<div
|
|
912
|
+
key={role.code}
|
|
913
|
+
className={cn(
|
|
914
|
+
"flex items-start gap-3 p-3 rounded-md border cursor-pointer hover:bg-slate-50 transition-colors",
|
|
915
|
+
isSelected
|
|
916
|
+
? "border-blue-500 bg-blue-50/50"
|
|
917
|
+
: "bg-background border-slate-200 dark:border-slate-800",
|
|
918
|
+
)}
|
|
919
|
+
onClick={() => {
|
|
920
|
+
setSelectedRoles((prev) =>
|
|
921
|
+
prev.includes(role.code)
|
|
922
|
+
? prev.filter((c) => c !== role.code)
|
|
923
|
+
: [...prev, role.code],
|
|
924
|
+
);
|
|
925
|
+
}}
|
|
926
|
+
>
|
|
927
|
+
<Checkbox
|
|
928
|
+
checked={isSelected}
|
|
929
|
+
className="mt-1 data-[state=checked]:bg-blue-600 data-[state=checked]:border-blue-600"
|
|
930
|
+
/>
|
|
931
|
+
<div className="space-y-0.5">
|
|
932
|
+
<p className="text-sm font-medium text-slate-900 dark:text-slate-100">
|
|
933
|
+
{role.name}
|
|
934
|
+
</p>
|
|
935
|
+
<p className="text-xs text-muted-foreground line-clamp-1">
|
|
936
|
+
{role.description || role.code}
|
|
937
|
+
</p>
|
|
938
|
+
</div>
|
|
939
|
+
</div>
|
|
940
|
+
);
|
|
941
|
+
})}
|
|
942
|
+
</div>
|
|
943
|
+
</div>
|
|
944
|
+
)}
|
|
945
|
+
|
|
946
|
+
{activeSection === "security" && (
|
|
947
|
+
<div className="space-y-4 animate-in fade-in zoom-in-95 duration-200">
|
|
948
|
+
<div className="max-w-xl space-y-6">
|
|
949
|
+
<div className="flex items-center gap-3 rounded-md bg-amber-50 p-4 text-sm text-amber-900 border border-amber-100">
|
|
950
|
+
<ShieldCheck className="h-5 w-5 shrink-0 text-amber-600" />
|
|
951
|
+
<span>
|
|
952
|
+
Mật khẩu mạnh giúp bảo vệ tài khoản khỏi truy cập trái
|
|
953
|
+
phép. Sử dụng ít nhất 6 ký tự bao gồm chữ và số.
|
|
954
|
+
</span>
|
|
955
|
+
</div>
|
|
956
|
+
<div className="grid md:grid-cols-2 gap-x-8 gap-y-6">
|
|
957
|
+
<div className="space-y-2">
|
|
958
|
+
<Label className="text-[13px] font-medium text-[#41454d] dark:text-[#9297a0]">Mật khẩu mới</Label>
|
|
959
|
+
<div className="relative">
|
|
960
|
+
<Input
|
|
961
|
+
type={showPassword ? "text" : "password"}
|
|
962
|
+
value={passwordData.password}
|
|
963
|
+
onChange={(e) =>
|
|
964
|
+
setPasswordData((prev) => ({
|
|
965
|
+
...prev,
|
|
966
|
+
password: e.target.value,
|
|
967
|
+
}))
|
|
968
|
+
}
|
|
969
|
+
className="pr-10 bg-[#ffffff] dark:bg-[#181d26] border-[#dddddd] dark:border-[#333840] focus:ring-[#458fff] focus:border-[#458fff] shadow-sm rounded-[6px]"
|
|
970
|
+
/>
|
|
971
|
+
<Button
|
|
972
|
+
type="button"
|
|
973
|
+
variant="ghost"
|
|
974
|
+
size="icon"
|
|
975
|
+
className="absolute right-0 top-0 h-full w-9 text-muted-foreground hover:text-foreground"
|
|
976
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
977
|
+
>
|
|
978
|
+
{showPassword ? (
|
|
979
|
+
<EyeOff className="h-4 w-4" />
|
|
980
|
+
) : (
|
|
981
|
+
<Eye className="h-4 w-4" />
|
|
982
|
+
)}
|
|
983
|
+
</Button>
|
|
984
|
+
</div>
|
|
985
|
+
</div>
|
|
986
|
+
<div className="space-y-2">
|
|
987
|
+
<Label className="text-[13px] font-medium text-[#41454d] dark:text-[#9297a0]">
|
|
988
|
+
Xác nhận mật khẩu
|
|
989
|
+
</Label>
|
|
990
|
+
<Input
|
|
991
|
+
type="password"
|
|
992
|
+
value={passwordData.confirm}
|
|
993
|
+
onChange={(e) =>
|
|
994
|
+
setPasswordData((prev) => ({
|
|
995
|
+
...prev,
|
|
996
|
+
confirm: e.target.value,
|
|
997
|
+
}))
|
|
998
|
+
}
|
|
999
|
+
className="bg-[#ffffff] dark:bg-[#181d26] border-[#dddddd] dark:border-[#333840] focus:ring-[#458fff] focus:border-[#458fff] shadow-sm rounded-[6px]"
|
|
1000
|
+
/>
|
|
1001
|
+
</div>
|
|
1002
|
+
</div>
|
|
1003
|
+
</div>
|
|
1004
|
+
</div>
|
|
1005
|
+
)}
|
|
1006
|
+
|
|
1007
|
+
{error && (
|
|
1008
|
+
<div className="p-4 text-sm text-destructive bg-destructive/5 border border-destructive/20 rounded-md flex items-center gap-2 animate-in fade-in slide-in-from-bottom-2">
|
|
1009
|
+
<Ban className="h-4 w-4" />
|
|
1010
|
+
{error}
|
|
1011
|
+
</div>
|
|
1012
|
+
)}
|
|
1013
|
+
</div>
|
|
1014
|
+
</div>
|
|
1015
|
+
</div>
|
|
1016
|
+
</DialogContent>
|
|
1017
|
+
</Dialog>
|
|
1018
|
+
);
|
|
1019
|
+
}
|