@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,245 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
|
|
4
|
+
import { Check, ChevronRight, Dot } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
import type { ComponentProps } from "react";
|
|
7
|
+
|
|
8
|
+
import { cn } from "../../utils";
|
|
9
|
+
|
|
10
|
+
export function ContextMenu({
|
|
11
|
+
...props
|
|
12
|
+
}: ComponentProps<typeof ContextMenuPrimitive.Root>) {
|
|
13
|
+
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ContextMenuTrigger({
|
|
17
|
+
...props
|
|
18
|
+
}: ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
|
|
19
|
+
return (
|
|
20
|
+
<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function ContextMenuGroup({
|
|
25
|
+
...props
|
|
26
|
+
}: ComponentProps<typeof ContextMenuPrimitive.Group>) {
|
|
27
|
+
return (
|
|
28
|
+
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function ContextMenuPortal({
|
|
33
|
+
...props
|
|
34
|
+
}: ComponentProps<typeof ContextMenuPrimitive.Portal>) {
|
|
35
|
+
return (
|
|
36
|
+
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function ContextMenuSub({
|
|
41
|
+
...props
|
|
42
|
+
}: ComponentProps<typeof ContextMenuPrimitive.Sub>) {
|
|
43
|
+
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function ContextMenuRadioGroup({
|
|
47
|
+
...props
|
|
48
|
+
}: ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
|
|
49
|
+
return (
|
|
50
|
+
<ContextMenuPrimitive.RadioGroup
|
|
51
|
+
data-slot="context-menu-radio-group"
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type ContextMenuSubTriggerProps = ComponentProps<
|
|
58
|
+
typeof ContextMenuPrimitive.SubTrigger
|
|
59
|
+
> & {
|
|
60
|
+
inset?: boolean;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export function ContextMenuSubTrigger({
|
|
64
|
+
className,
|
|
65
|
+
inset,
|
|
66
|
+
children,
|
|
67
|
+
...props
|
|
68
|
+
}: ContextMenuSubTriggerProps) {
|
|
69
|
+
return (
|
|
70
|
+
<ContextMenuPrimitive.SubTrigger
|
|
71
|
+
data-slot="context-menu-sub-trigger"
|
|
72
|
+
className={cn(
|
|
73
|
+
"flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
|
74
|
+
inset && "ps-8",
|
|
75
|
+
className,
|
|
76
|
+
)}
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
{children}
|
|
80
|
+
<ChevronRight className="ml-auto h-4 w-4" />
|
|
81
|
+
</ContextMenuPrimitive.SubTrigger>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function ContextMenuSubContent({
|
|
86
|
+
className,
|
|
87
|
+
...props
|
|
88
|
+
}: ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
|
|
89
|
+
return (
|
|
90
|
+
<ContextMenuPrimitive.SubContent
|
|
91
|
+
data-slot="context-menu-sub-content"
|
|
92
|
+
className={cn(
|
|
93
|
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
94
|
+
className,
|
|
95
|
+
)}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function ContextMenuContent({
|
|
102
|
+
className,
|
|
103
|
+
...props
|
|
104
|
+
}: ComponentProps<typeof ContextMenuPrimitive.Content>) {
|
|
105
|
+
return (
|
|
106
|
+
<ContextMenuPrimitive.Portal>
|
|
107
|
+
<ContextMenuPrimitive.Content
|
|
108
|
+
data-slot="context-menu-content"
|
|
109
|
+
className={cn(
|
|
110
|
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
111
|
+
className,
|
|
112
|
+
)}
|
|
113
|
+
{...props}
|
|
114
|
+
/>
|
|
115
|
+
</ContextMenuPrimitive.Portal>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
type ContextMenuItemProps = ComponentProps<typeof ContextMenuPrimitive.Item> & {
|
|
120
|
+
inset?: boolean;
|
|
121
|
+
variant?: "default" | "destructive";
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export function ContextMenuItem({
|
|
125
|
+
className,
|
|
126
|
+
inset,
|
|
127
|
+
variant = "default",
|
|
128
|
+
...props
|
|
129
|
+
}: ContextMenuItemProps) {
|
|
130
|
+
return (
|
|
131
|
+
<ContextMenuPrimitive.Item
|
|
132
|
+
data-slot="context-menu-item"
|
|
133
|
+
data-inset={inset}
|
|
134
|
+
data-variant={variant}
|
|
135
|
+
className={cn(
|
|
136
|
+
"relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground",
|
|
137
|
+
className,
|
|
138
|
+
)}
|
|
139
|
+
{...props}
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function ContextMenuCheckboxItem({
|
|
145
|
+
className,
|
|
146
|
+
children,
|
|
147
|
+
checked,
|
|
148
|
+
...props
|
|
149
|
+
}: ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
|
|
150
|
+
return (
|
|
151
|
+
<ContextMenuPrimitive.CheckboxItem
|
|
152
|
+
data-slot="context-menu-checkbox-item"
|
|
153
|
+
className={cn(
|
|
154
|
+
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 ps-8 pe-2 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
|
|
155
|
+
className,
|
|
156
|
+
)}
|
|
157
|
+
checked={checked}
|
|
158
|
+
{...props}
|
|
159
|
+
>
|
|
160
|
+
<span className="absolute start-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
161
|
+
<ContextMenuPrimitive.ItemIndicator>
|
|
162
|
+
<Check className="h-4 w-4" />
|
|
163
|
+
</ContextMenuPrimitive.ItemIndicator>
|
|
164
|
+
</span>
|
|
165
|
+
{children}
|
|
166
|
+
</ContextMenuPrimitive.CheckboxItem>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function ContextMenuRadioItem({
|
|
171
|
+
className,
|
|
172
|
+
children,
|
|
173
|
+
...props
|
|
174
|
+
}: ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
|
|
175
|
+
return (
|
|
176
|
+
<ContextMenuPrimitive.RadioItem
|
|
177
|
+
data-slot="context-menu-radio-item"
|
|
178
|
+
className={cn(
|
|
179
|
+
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 ps-8 pe-2 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
|
|
180
|
+
className,
|
|
181
|
+
)}
|
|
182
|
+
{...props}
|
|
183
|
+
>
|
|
184
|
+
<span className="absolute start-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
185
|
+
<ContextMenuPrimitive.ItemIndicator>
|
|
186
|
+
<Dot className="h-4 w-4 fill-current" />
|
|
187
|
+
</ContextMenuPrimitive.ItemIndicator>
|
|
188
|
+
</span>
|
|
189
|
+
{children}
|
|
190
|
+
</ContextMenuPrimitive.RadioItem>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
type ContextMenuLabelProps = ComponentProps<
|
|
195
|
+
typeof ContextMenuPrimitive.Label
|
|
196
|
+
> & {
|
|
197
|
+
inset?: boolean;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export function ContextMenuLabel({
|
|
201
|
+
className,
|
|
202
|
+
inset,
|
|
203
|
+
...props
|
|
204
|
+
}: ContextMenuLabelProps) {
|
|
205
|
+
return (
|
|
206
|
+
<ContextMenuPrimitive.Label
|
|
207
|
+
data-slot="context-menu-label"
|
|
208
|
+
className={cn(
|
|
209
|
+
"px-2 py-1.5 text-sm font-semibold text-foreground",
|
|
210
|
+
inset && "ps-8",
|
|
211
|
+
className,
|
|
212
|
+
)}
|
|
213
|
+
{...props}
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function ContextMenuSeparator({
|
|
219
|
+
className,
|
|
220
|
+
...props
|
|
221
|
+
}: ComponentProps<typeof ContextMenuPrimitive.Separator>) {
|
|
222
|
+
return (
|
|
223
|
+
<ContextMenuPrimitive.Separator
|
|
224
|
+
data-slot="context-menu-separator"
|
|
225
|
+
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
|
226
|
+
{...props}
|
|
227
|
+
/>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function ContextMenuShortcut({
|
|
232
|
+
className,
|
|
233
|
+
...props
|
|
234
|
+
}: ComponentProps<"span">) {
|
|
235
|
+
return (
|
|
236
|
+
<span
|
|
237
|
+
data-slot="context-menu-shortcut"
|
|
238
|
+
className={cn(
|
|
239
|
+
"ms-auto text-sm tracking-widest text-muted-foreground",
|
|
240
|
+
className,
|
|
241
|
+
)}
|
|
242
|
+
{...props}
|
|
243
|
+
/>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Drawer as DrawerPrimitive } from "vaul";
|
|
4
|
+
|
|
5
|
+
import type { ComponentProps } from "react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../utils";
|
|
8
|
+
|
|
9
|
+
export function Drawer({
|
|
10
|
+
shouldScaleBackground = true,
|
|
11
|
+
...props
|
|
12
|
+
}: ComponentProps<typeof DrawerPrimitive.Root>) {
|
|
13
|
+
return (
|
|
14
|
+
<DrawerPrimitive.Root
|
|
15
|
+
data-slot="drawer"
|
|
16
|
+
shouldScaleBackground={shouldScaleBackground}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function DrawerTrigger({
|
|
23
|
+
className,
|
|
24
|
+
...props
|
|
25
|
+
}: ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
|
26
|
+
return (
|
|
27
|
+
<DrawerPrimitive.Trigger
|
|
28
|
+
data-slot="drawer-trigger"
|
|
29
|
+
className={cn("cursor-pointer", className)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function DrawerPortal({
|
|
36
|
+
children,
|
|
37
|
+
...props
|
|
38
|
+
}: Omit<ComponentProps<typeof DrawerPrimitive.Portal>, "children"> & { children?: React.ReactNode }) {
|
|
39
|
+
const Portal = DrawerPrimitive.Portal as any;
|
|
40
|
+
return <Portal data-slot="drawer-portal" {...props}>{children as any}</Portal>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function DrawerClose({
|
|
44
|
+
...props
|
|
45
|
+
}: ComponentProps<typeof DrawerPrimitive.Close>) {
|
|
46
|
+
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function DrawerOverlay({
|
|
50
|
+
className,
|
|
51
|
+
...props
|
|
52
|
+
}: ComponentProps<typeof DrawerPrimitive.Overlay>) {
|
|
53
|
+
return (
|
|
54
|
+
<DrawerPrimitive.Overlay
|
|
55
|
+
data-slot="drawer-overlay"
|
|
56
|
+
className={cn("fixed inset-0 z-50 bg-black/80", className)}
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function DrawerContent({
|
|
63
|
+
className,
|
|
64
|
+
children,
|
|
65
|
+
...props
|
|
66
|
+
}: ComponentProps<typeof DrawerPrimitive.Content>) {
|
|
67
|
+
return (
|
|
68
|
+
<DrawerPortal data-slot="drawer-portal">
|
|
69
|
+
<DrawerOverlay />
|
|
70
|
+
<DrawerPrimitive.Content
|
|
71
|
+
data-slot="drawer-content"
|
|
72
|
+
className={cn(
|
|
73
|
+
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
|
|
74
|
+
className,
|
|
75
|
+
)}
|
|
76
|
+
{...props}
|
|
77
|
+
>
|
|
78
|
+
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
|
79
|
+
{children}
|
|
80
|
+
</DrawerPrimitive.Content>
|
|
81
|
+
</DrawerPortal>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function DrawerHeader({ className, ...props }: ComponentProps<"div">) {
|
|
86
|
+
return (
|
|
87
|
+
<div
|
|
88
|
+
data-slot="drawer-header"
|
|
89
|
+
className={cn("grid gap-1.5 p-4 text-center sm:text-start", className)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function DrawerFooter({ className, ...props }: ComponentProps<"div">) {
|
|
96
|
+
return (
|
|
97
|
+
<div
|
|
98
|
+
data-slot="drawer-footer"
|
|
99
|
+
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
|
100
|
+
{...props}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function DrawerTitle({
|
|
106
|
+
className,
|
|
107
|
+
...props
|
|
108
|
+
}: ComponentProps<typeof DrawerPrimitive.Title>) {
|
|
109
|
+
return (
|
|
110
|
+
<DrawerPrimitive.Title
|
|
111
|
+
data-slot="drawer-title"
|
|
112
|
+
className={cn(
|
|
113
|
+
"text-lg font-semibold leading-none tracking-tight",
|
|
114
|
+
className,
|
|
115
|
+
)}
|
|
116
|
+
{...props}
|
|
117
|
+
/>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function DrawerDescription({
|
|
122
|
+
className,
|
|
123
|
+
...props
|
|
124
|
+
}: ComponentProps<typeof DrawerPrimitive.Description>) {
|
|
125
|
+
return (
|
|
126
|
+
<DrawerPrimitive.Description
|
|
127
|
+
data-slot="drawer-description"
|
|
128
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
129
|
+
{...props}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
useState,
|
|
7
|
+
useCallback,
|
|
8
|
+
useEffect,
|
|
9
|
+
} from "react";
|
|
10
|
+
import type { ReactNode } from "react";
|
|
11
|
+
import {
|
|
12
|
+
Dialog,
|
|
13
|
+
DialogContent,
|
|
14
|
+
DialogHeader,
|
|
15
|
+
DialogTitle,
|
|
16
|
+
DialogDescription,
|
|
17
|
+
DialogFooter,
|
|
18
|
+
Button,
|
|
19
|
+
ScrollArea,
|
|
20
|
+
} from "../primitives";
|
|
21
|
+
import { AlertTriangle, Check, ChevronDown, ChevronRight, ClipboardCopy, Copy } from "lucide-react";
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Types
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
export interface ErrorDialogData {
|
|
28
|
+
title?: string;
|
|
29
|
+
description?: ReactNode;
|
|
30
|
+
trace?: string;
|
|
31
|
+
code?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ErrorDialogContextType {
|
|
35
|
+
showError: (data: ErrorDialogData | string | Error) => void;
|
|
36
|
+
dismiss: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Event Emitter for Global Access
|
|
41
|
+
// ============================================================================
|
|
42
|
+
|
|
43
|
+
type ErrorEventListener = (data: ErrorDialogData) => void;
|
|
44
|
+
|
|
45
|
+
class GlobalErrorEmitter {
|
|
46
|
+
private listeners: ErrorEventListener[] = [];
|
|
47
|
+
|
|
48
|
+
subscribe(listener: ErrorEventListener) {
|
|
49
|
+
this.listeners.push(listener);
|
|
50
|
+
return () => {
|
|
51
|
+
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
emit(data: ErrorDialogData) {
|
|
56
|
+
this.listeners.forEach((listener) => listener(data));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const globalError = new GlobalErrorEmitter();
|
|
61
|
+
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// Context
|
|
64
|
+
// ============================================================================
|
|
65
|
+
|
|
66
|
+
const ErrorDialogContext = createContext<ErrorDialogContextType | null>(null);
|
|
67
|
+
|
|
68
|
+
export function useErrorDialog() {
|
|
69
|
+
const context = useContext(ErrorDialogContext);
|
|
70
|
+
if (!context) {
|
|
71
|
+
throw new Error("useErrorDialog must be used within ErrorDialogProvider");
|
|
72
|
+
}
|
|
73
|
+
return context;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// Component
|
|
78
|
+
// ============================================================================
|
|
79
|
+
|
|
80
|
+
export function ErrorDialogProvider({ children }: { children: ReactNode }) {
|
|
81
|
+
const [open, setOpen] = useState(false);
|
|
82
|
+
const [data, setData] = useState<ErrorDialogData>({});
|
|
83
|
+
const [expanded, setExpanded] = useState(false);
|
|
84
|
+
const [copied, setCopied] = useState(false);
|
|
85
|
+
|
|
86
|
+
// Subscribe to global events
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const unsubscribe = globalError.subscribe((errorData) => {
|
|
89
|
+
setData(errorData);
|
|
90
|
+
setOpen(true);
|
|
91
|
+
setExpanded(false);
|
|
92
|
+
setCopied(false);
|
|
93
|
+
});
|
|
94
|
+
return unsubscribe;
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
const showError = useCallback((input: ErrorDialogData | string | Error) => {
|
|
98
|
+
let errorData: ErrorDialogData = {};
|
|
99
|
+
|
|
100
|
+
if (input instanceof Error) {
|
|
101
|
+
errorData = {
|
|
102
|
+
title: input.name || "Error",
|
|
103
|
+
description: input.message,
|
|
104
|
+
trace: input.stack,
|
|
105
|
+
};
|
|
106
|
+
} else if (typeof input === "string") {
|
|
107
|
+
errorData = {
|
|
108
|
+
title: "Error",
|
|
109
|
+
description: input,
|
|
110
|
+
};
|
|
111
|
+
} else {
|
|
112
|
+
errorData = input;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
setData(errorData);
|
|
116
|
+
setOpen(true);
|
|
117
|
+
setExpanded(false);
|
|
118
|
+
setCopied(false);
|
|
119
|
+
}, []);
|
|
120
|
+
|
|
121
|
+
const dismiss = useCallback(() => {
|
|
122
|
+
setOpen(false);
|
|
123
|
+
}, []);
|
|
124
|
+
|
|
125
|
+
/** Build full diagnostic text for copy */
|
|
126
|
+
const buildFullDiagnostic = useCallback(() => {
|
|
127
|
+
// If trace already contains formatted diagnostic, use it directly
|
|
128
|
+
if (data.trace && data.trace.startsWith("=== Thông tin lỗi")) {
|
|
129
|
+
return data.trace;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Otherwise build from available fields
|
|
133
|
+
const parts: string[] = [
|
|
134
|
+
`=== Thông tin lỗi ===`,
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
if (data.code) parts.push(`Mã lỗi: ${data.code}`);
|
|
138
|
+
|
|
139
|
+
parts.push(`Thời gian: ${new Date().toLocaleString("vi-VN", {
|
|
140
|
+
timeZone: "Asia/Ho_Chi_Minh",
|
|
141
|
+
day: "2-digit", month: "2-digit", year: "numeric",
|
|
142
|
+
hour: "2-digit", minute: "2-digit", second: "2-digit",
|
|
143
|
+
})}`);
|
|
144
|
+
|
|
145
|
+
if (data.title) parts.push(`Lỗi: ${data.title}`);
|
|
146
|
+
|
|
147
|
+
if (data.description && typeof data.description === "string") {
|
|
148
|
+
parts.push(`Mô tả: ${data.description}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (data.trace) {
|
|
152
|
+
parts.push(`\nChi tiết:\n${data.trace}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return parts.join("\n");
|
|
156
|
+
}, [data]);
|
|
157
|
+
|
|
158
|
+
const copyAll = useCallback(async () => {
|
|
159
|
+
const text = buildFullDiagnostic();
|
|
160
|
+
try {
|
|
161
|
+
await navigator.clipboard.writeText(text);
|
|
162
|
+
setCopied(true);
|
|
163
|
+
setTimeout(() => setCopied(false), 2000);
|
|
164
|
+
} catch {
|
|
165
|
+
// Fallback for older browsers
|
|
166
|
+
const textarea = document.createElement("textarea");
|
|
167
|
+
textarea.value = text;
|
|
168
|
+
textarea.style.position = "fixed";
|
|
169
|
+
textarea.style.opacity = "0";
|
|
170
|
+
document.body.appendChild(textarea);
|
|
171
|
+
textarea.select();
|
|
172
|
+
document.execCommand("copy");
|
|
173
|
+
document.body.removeChild(textarea);
|
|
174
|
+
setCopied(true);
|
|
175
|
+
setTimeout(() => setCopied(false), 2000);
|
|
176
|
+
}
|
|
177
|
+
}, [buildFullDiagnostic]);
|
|
178
|
+
|
|
179
|
+
const copyTrace = () => {
|
|
180
|
+
if (data.trace) {
|
|
181
|
+
navigator.clipboard.writeText(data.trace);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<ErrorDialogContext.Provider value={{ showError, dismiss }}>
|
|
187
|
+
{children}
|
|
188
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
189
|
+
<DialogContent className="max-w-md md:max-w-xl sm:max-w-lg border-destructive/20">
|
|
190
|
+
<DialogHeader>
|
|
191
|
+
<div className="flex items-center gap-2 text-destructive">
|
|
192
|
+
<div className="p-2 bg-destructive/10 rounded-full">
|
|
193
|
+
<AlertTriangle className="h-6 w-6" />
|
|
194
|
+
</div>
|
|
195
|
+
<DialogTitle className="text-xl">
|
|
196
|
+
{data.title || "Đã xảy ra lỗi"}
|
|
197
|
+
</DialogTitle>
|
|
198
|
+
</div>
|
|
199
|
+
<DialogDescription className="text-base pt-2 text-foreground font-medium">
|
|
200
|
+
{data.description}
|
|
201
|
+
</DialogDescription>
|
|
202
|
+
{data.code && (
|
|
203
|
+
<p className="text-xs text-muted-foreground font-mono mt-1">
|
|
204
|
+
Mã lỗi: {data.code}
|
|
205
|
+
</p>
|
|
206
|
+
)}
|
|
207
|
+
</DialogHeader>
|
|
208
|
+
|
|
209
|
+
{data.trace && (
|
|
210
|
+
<div className="mt-2 border rounded-md bg-muted/30 overflow-hidden">
|
|
211
|
+
<button
|
|
212
|
+
onClick={() => setExpanded(!expanded)}
|
|
213
|
+
className="w-full flex items-center justify-between p-2 text-sm font-medium hover:bg-muted/50 transition-colors"
|
|
214
|
+
>
|
|
215
|
+
<div className="flex items-center gap-1">
|
|
216
|
+
{expanded ? (
|
|
217
|
+
<ChevronDown className="h-4 w-4" />
|
|
218
|
+
) : (
|
|
219
|
+
<ChevronRight className="h-4 w-4" />
|
|
220
|
+
)}
|
|
221
|
+
<span>Chi tiết lỗi & Trace Log</span>
|
|
222
|
+
</div>
|
|
223
|
+
</button>
|
|
224
|
+
|
|
225
|
+
{expanded && (
|
|
226
|
+
<div className="relative border-t bg-muted/50">
|
|
227
|
+
<ScrollArea className="h-[200px] w-full p-4">
|
|
228
|
+
<pre className="text-xs font-mono whitespace-pre-wrap break-all text-muted-foreground">
|
|
229
|
+
{data.trace}
|
|
230
|
+
</pre>
|
|
231
|
+
</ScrollArea>
|
|
232
|
+
<Button
|
|
233
|
+
variant="ghost"
|
|
234
|
+
size="icon"
|
|
235
|
+
className="absolute top-2 right-2 h-6 w-6"
|
|
236
|
+
onClick={copyTrace}
|
|
237
|
+
title="Copy Trace Log"
|
|
238
|
+
>
|
|
239
|
+
<Copy className="h-3 w-3" />
|
|
240
|
+
</Button>
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
243
|
+
</div>
|
|
244
|
+
)}
|
|
245
|
+
|
|
246
|
+
<DialogFooter className="flex-row gap-2 sm:justify-between">
|
|
247
|
+
<Button
|
|
248
|
+
variant="outline"
|
|
249
|
+
size="sm"
|
|
250
|
+
onClick={copyAll}
|
|
251
|
+
className="gap-1.5 text-xs"
|
|
252
|
+
>
|
|
253
|
+
{copied ? (
|
|
254
|
+
<>
|
|
255
|
+
<Check className="h-3.5 w-3.5 text-green-600" />
|
|
256
|
+
Đã sao chép
|
|
257
|
+
</>
|
|
258
|
+
) : (
|
|
259
|
+
<>
|
|
260
|
+
<ClipboardCopy className="h-3.5 w-3.5" />
|
|
261
|
+
Sao chép thông tin lỗi
|
|
262
|
+
</>
|
|
263
|
+
)}
|
|
264
|
+
</Button>
|
|
265
|
+
<Button variant="outline" onClick={dismiss}>
|
|
266
|
+
Đóng
|
|
267
|
+
</Button>
|
|
268
|
+
</DialogFooter>
|
|
269
|
+
</DialogContent>
|
|
270
|
+
</Dialog>
|
|
271
|
+
</ErrorDialogContext.Provider>
|
|
272
|
+
);
|
|
273
|
+
}
|