@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,308 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect } from "react";
|
|
4
|
+
import type { Job, JobLog } from "../../system/job-manager";
|
|
5
|
+
import { jobManager } from "../../system/job-manager";
|
|
6
|
+
// Ideally we would import components from @goerp/core/ui/primitives but we are inside core
|
|
7
|
+
// So we mock or use basic HTML/Tailwind for now to avoid circular deps if structure is complex
|
|
8
|
+
// In a real app we'd use the proper UI components.
|
|
9
|
+
// Assuming this page is used inside an app that provides the context or we use relative imports carefully.
|
|
10
|
+
|
|
11
|
+
// Relative imports to core UI components
|
|
12
|
+
import { Badge } from "../primitives/badge";
|
|
13
|
+
import { Button } from "../primitives/button";
|
|
14
|
+
import { Card, CardHeader, CardTitle, CardContent } from "../primitives/card";
|
|
15
|
+
import { ScrollArea } from "../primitives/scroll-area";
|
|
16
|
+
import {
|
|
17
|
+
Table,
|
|
18
|
+
TableBody,
|
|
19
|
+
TableCell,
|
|
20
|
+
TableHead,
|
|
21
|
+
TableHeader,
|
|
22
|
+
TableRow,
|
|
23
|
+
} from "../primitives/table";
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
Dialog,
|
|
27
|
+
DialogContent,
|
|
28
|
+
DialogHeader,
|
|
29
|
+
DialogTitle,
|
|
30
|
+
DialogTrigger,
|
|
31
|
+
DialogFooter,
|
|
32
|
+
} from "../primitives/dialog";
|
|
33
|
+
import { Input } from "../primitives/input";
|
|
34
|
+
import { Label } from "../primitives/label";
|
|
35
|
+
|
|
36
|
+
export const JobManagementPage = () => {
|
|
37
|
+
const [jobs, setJobs] = useState<Job[]>([]);
|
|
38
|
+
const [selectedJob, setSelectedJob] = useState<string | null>(null);
|
|
39
|
+
const [logs, setLogs] = useState<JobLog[]>([]);
|
|
40
|
+
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
|
41
|
+
const [editingJob, setEditingJob] = useState<Job | null>(null);
|
|
42
|
+
const [editForm, setEditForm] = useState({ schedule: "", description: "" });
|
|
43
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
44
|
+
|
|
45
|
+
// Initial load and periodic refresh
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
setJobs(jobManager.getJobs());
|
|
48
|
+
const interval = setInterval(() => {
|
|
49
|
+
setJobs(jobManager.getJobs());
|
|
50
|
+
if (selectedJob) {
|
|
51
|
+
// Refresh logs if a job is selected
|
|
52
|
+
setLogs(jobManager.getLogs(selectedJob));
|
|
53
|
+
}
|
|
54
|
+
}, 1000); // 1s refresh for demo niceness
|
|
55
|
+
return () => clearInterval(interval);
|
|
56
|
+
}, [refreshTrigger, selectedJob]);
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (selectedJob) {
|
|
60
|
+
setLogs(jobManager.getLogs(selectedJob));
|
|
61
|
+
} else {
|
|
62
|
+
setLogs([]);
|
|
63
|
+
}
|
|
64
|
+
}, [selectedJob]);
|
|
65
|
+
|
|
66
|
+
const handleStart = (id: string) => {
|
|
67
|
+
jobManager.startJob(id);
|
|
68
|
+
setRefreshTrigger((prev) => prev + 1);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const handleStop = (id: string) => {
|
|
72
|
+
jobManager.stopJob(id);
|
|
73
|
+
setRefreshTrigger((prev) => prev + 1);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleEditClick = (job: Job, e: React.MouseEvent) => {
|
|
77
|
+
e.stopPropagation();
|
|
78
|
+
setEditingJob(job);
|
|
79
|
+
setEditForm({
|
|
80
|
+
schedule: job.schedule || "",
|
|
81
|
+
description: job.description || "",
|
|
82
|
+
});
|
|
83
|
+
setIsDialogOpen(true);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleSaveEdit = () => {
|
|
87
|
+
if (editingJob) {
|
|
88
|
+
jobManager.updateJob(editingJob.id, editForm);
|
|
89
|
+
setRefreshTrigger((prev) => prev + 1);
|
|
90
|
+
setIsDialogOpen(false);
|
|
91
|
+
setEditingJob(null);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const getStatusBadge = (status: string) => {
|
|
96
|
+
switch (status) {
|
|
97
|
+
case "running":
|
|
98
|
+
return (
|
|
99
|
+
<Badge
|
|
100
|
+
variant="default"
|
|
101
|
+
className="bg-emerald-500 hover:bg-emerald-600"
|
|
102
|
+
>
|
|
103
|
+
Running
|
|
104
|
+
</Badge>
|
|
105
|
+
);
|
|
106
|
+
case "stopped":
|
|
107
|
+
return <Badge variant="secondary">Stopped</Badge>;
|
|
108
|
+
case "failed":
|
|
109
|
+
return <Badge variant="destructive">Failed</Badge>;
|
|
110
|
+
default:
|
|
111
|
+
return <Badge variant="outline">{status}</Badge>;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const [logSearchTerm, setLogSearchTerm] = useState("");
|
|
116
|
+
|
|
117
|
+
const filteredLogs = logs.filter(
|
|
118
|
+
(log) =>
|
|
119
|
+
log.message.toLowerCase().includes(logSearchTerm.toLowerCase()) ||
|
|
120
|
+
log.level.toLowerCase().includes(logSearchTerm.toLowerCase()),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div className="p-6 space-y-6 h-full flex flex-col">
|
|
125
|
+
<div className="flex justify-between items-center mb-4">
|
|
126
|
+
<div>
|
|
127
|
+
<h1 className="text-2xl font-bold tracking-tight">Job Management</h1>
|
|
128
|
+
<p className="text-muted-foreground text-sm">
|
|
129
|
+
Monitor and control system background jobs.
|
|
130
|
+
</p>
|
|
131
|
+
</div>
|
|
132
|
+
<Button
|
|
133
|
+
onClick={() => setRefreshTrigger((prev) => prev + 1)}
|
|
134
|
+
variant="outline"
|
|
135
|
+
>
|
|
136
|
+
Refresh
|
|
137
|
+
</Button>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 flex-1 min-h-0">
|
|
141
|
+
{/* Job List */}
|
|
142
|
+
<Card className="md:col-span-2 flex flex-col min-h-0">
|
|
143
|
+
<CardHeader>
|
|
144
|
+
<CardTitle>Active Jobs</CardTitle>
|
|
145
|
+
</CardHeader>
|
|
146
|
+
<CardContent className="flex-1 overflow-auto p-0">
|
|
147
|
+
<Table>
|
|
148
|
+
<TableHeader>
|
|
149
|
+
<TableRow>
|
|
150
|
+
<TableHead>Job Name</TableHead>
|
|
151
|
+
<TableHead>Schedule</TableHead>
|
|
152
|
+
<TableHead>Status</TableHead>
|
|
153
|
+
<TableHead>Last Run</TableHead>
|
|
154
|
+
<TableHead className="text-right">Actions</TableHead>
|
|
155
|
+
</TableRow>
|
|
156
|
+
</TableHeader>
|
|
157
|
+
<TableBody>
|
|
158
|
+
{jobs.map((job) => (
|
|
159
|
+
<TableRow
|
|
160
|
+
key={job.id}
|
|
161
|
+
className={`cursor-pointer ${selectedJob === job.id ? "bg-muted/50" : ""}`}
|
|
162
|
+
onClick={() => setSelectedJob(job.id)}
|
|
163
|
+
>
|
|
164
|
+
<TableCell>
|
|
165
|
+
<div className="font-medium">{job.name}</div>
|
|
166
|
+
<div className="text-xs text-muted-foreground">
|
|
167
|
+
{job.description}
|
|
168
|
+
</div>
|
|
169
|
+
</TableCell>
|
|
170
|
+
<TableCell>{job.schedule}</TableCell>
|
|
171
|
+
<TableCell>{getStatusBadge(job.status)}</TableCell>
|
|
172
|
+
<TableCell className="text-muted-foreground">
|
|
173
|
+
{job.lastRun ? job.lastRun.toLocaleTimeString() : "-"}
|
|
174
|
+
</TableCell>
|
|
175
|
+
<TableCell className="text-right">
|
|
176
|
+
<div
|
|
177
|
+
className="flex justify-end gap-2"
|
|
178
|
+
onClick={(e) => e.stopPropagation()}
|
|
179
|
+
>
|
|
180
|
+
<Button
|
|
181
|
+
size="sm"
|
|
182
|
+
variant="outline"
|
|
183
|
+
onClick={(e) => handleEditClick(job, e)}
|
|
184
|
+
>
|
|
185
|
+
Edit
|
|
186
|
+
</Button>
|
|
187
|
+
{job.status === "running" ? (
|
|
188
|
+
<Button
|
|
189
|
+
size="sm"
|
|
190
|
+
variant="destructive"
|
|
191
|
+
onClick={() => handleStop(job.id)}
|
|
192
|
+
>
|
|
193
|
+
Stop
|
|
194
|
+
</Button>
|
|
195
|
+
) : (
|
|
196
|
+
<Button size="sm" onClick={() => handleStart(job.id)}>
|
|
197
|
+
Start
|
|
198
|
+
</Button>
|
|
199
|
+
)}
|
|
200
|
+
</div>
|
|
201
|
+
</TableCell>
|
|
202
|
+
</TableRow>
|
|
203
|
+
))}
|
|
204
|
+
</TableBody>
|
|
205
|
+
</Table>
|
|
206
|
+
</CardContent>
|
|
207
|
+
</Card>
|
|
208
|
+
|
|
209
|
+
{/* Edit Dialog */}
|
|
210
|
+
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
|
211
|
+
<DialogContent>
|
|
212
|
+
<DialogHeader>
|
|
213
|
+
<DialogTitle>Edit Job: {editingJob?.name}</DialogTitle>
|
|
214
|
+
</DialogHeader>
|
|
215
|
+
<div className="space-y-4 py-4">
|
|
216
|
+
<div className="space-y-2">
|
|
217
|
+
<Label htmlFor="schedule">Schedule</Label>
|
|
218
|
+
<Input
|
|
219
|
+
id="schedule"
|
|
220
|
+
value={editForm.schedule}
|
|
221
|
+
onChange={(e) =>
|
|
222
|
+
setEditForm({ ...editForm, schedule: e.target.value })
|
|
223
|
+
}
|
|
224
|
+
/>
|
|
225
|
+
</div>
|
|
226
|
+
<div className="space-y-2">
|
|
227
|
+
<Label htmlFor="description">Description</Label>
|
|
228
|
+
<Input
|
|
229
|
+
id="description"
|
|
230
|
+
value={editForm.description}
|
|
231
|
+
onChange={(e) =>
|
|
232
|
+
setEditForm({ ...editForm, description: e.target.value })
|
|
233
|
+
}
|
|
234
|
+
/>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
<DialogFooter>
|
|
238
|
+
<Button variant="outline" onClick={() => setIsDialogOpen(false)}>
|
|
239
|
+
Cancel
|
|
240
|
+
</Button>
|
|
241
|
+
<Button onClick={handleSaveEdit}>Save Changes</Button>
|
|
242
|
+
</DialogFooter>
|
|
243
|
+
</DialogContent>
|
|
244
|
+
</Dialog>
|
|
245
|
+
|
|
246
|
+
{/* Log Viewer */}
|
|
247
|
+
<Card className="flex flex-col min-h-0 h-full">
|
|
248
|
+
<CardHeader className="pb-3 border-b flex flex-row items-center justify-between space-y-0">
|
|
249
|
+
<CardTitle>
|
|
250
|
+
{selectedJob
|
|
251
|
+
? `Logs: ${jobs.find((j) => j.id === selectedJob)?.name}`
|
|
252
|
+
: "Job Logs"}
|
|
253
|
+
</CardTitle>
|
|
254
|
+
</CardHeader>
|
|
255
|
+
<div className="p-2 border-b">
|
|
256
|
+
<input
|
|
257
|
+
type="text"
|
|
258
|
+
placeholder="Search logs..."
|
|
259
|
+
className="w-full px-3 py-2 text-sm border rounded-md bg-background"
|
|
260
|
+
value={logSearchTerm}
|
|
261
|
+
onChange={(e) => setLogSearchTerm(e.target.value)}
|
|
262
|
+
/>
|
|
263
|
+
</div>
|
|
264
|
+
<CardContent className="flex-1 p-0 min-h-0 bg-slate-50 dark:bg-slate-900/50">
|
|
265
|
+
<ScrollArea className="h-[600px] w-full p-4">
|
|
266
|
+
{!selectedJob && (
|
|
267
|
+
<div className="text-center text-muted-foreground py-10">
|
|
268
|
+
Select a job to view its logs
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
271
|
+
{selectedJob && filteredLogs.length === 0 && (
|
|
272
|
+
<div className="text-center text-muted-foreground py-10">
|
|
273
|
+
{logs.length === 0
|
|
274
|
+
? "No logs available"
|
|
275
|
+
: "No logs match your search"}
|
|
276
|
+
</div>
|
|
277
|
+
)}
|
|
278
|
+
<div className="space-y-2 font-mono text-xs">
|
|
279
|
+
{filteredLogs.map((log) => (
|
|
280
|
+
<div
|
|
281
|
+
key={log.id}
|
|
282
|
+
className="flex gap-2 border-b border-border/50 pb-1 last:border-0"
|
|
283
|
+
>
|
|
284
|
+
<span className="text-muted-foreground whitespace-nowrap">
|
|
285
|
+
{log.timestamp.toLocaleTimeString()}
|
|
286
|
+
</span>
|
|
287
|
+
<span
|
|
288
|
+
className={`font-semibold uppercase w-16 ${
|
|
289
|
+
log.level === "error"
|
|
290
|
+
? "text-red-500"
|
|
291
|
+
: log.level === "warning"
|
|
292
|
+
? "text-yellow-500"
|
|
293
|
+
: "text-blue-500"
|
|
294
|
+
}`}
|
|
295
|
+
>
|
|
296
|
+
[{log.level}]
|
|
297
|
+
</span>
|
|
298
|
+
<span className="break-all">{log.message}</span>
|
|
299
|
+
</div>
|
|
300
|
+
))}
|
|
301
|
+
</div>
|
|
302
|
+
</ScrollArea>
|
|
303
|
+
</CardContent>
|
|
304
|
+
</Card>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
);
|
|
308
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import Image from "next/image";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import { Button } from "../primitives/button";
|
|
4
|
+
|
|
5
|
+
export function NotFoundPage() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="min-h-screen w-full flex flex-col items-center justify-center gap-y-6 text-center text-foreground bg-background p-4">
|
|
8
|
+
<div className="flex flex-col-reverse justify-center items-center gap-y-6 md:flex-row md:text-start">
|
|
9
|
+
<Image
|
|
10
|
+
src="/images/illustrations/characters/character-02.svg"
|
|
11
|
+
alt=""
|
|
12
|
+
height={232}
|
|
13
|
+
width={249}
|
|
14
|
+
priority
|
|
15
|
+
/>
|
|
16
|
+
|
|
17
|
+
<h1 className="inline-grid text-6xl font-black">
|
|
18
|
+
404 <span className="text-3xl font-semibold">Page Not Found</span>
|
|
19
|
+
</h1>
|
|
20
|
+
</div>
|
|
21
|
+
<p className="max-w-prose text-xl text-muted-foreground">
|
|
22
|
+
We couldn't find the page you're looking for. It might have
|
|
23
|
+
been moved or doesn't exist.
|
|
24
|
+
</p>
|
|
25
|
+
<Button size="lg" asChild>
|
|
26
|
+
<Link href="/">Home Page</Link>
|
|
27
|
+
</Button>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva } from "class-variance-authority";
|
|
3
|
+
import type { VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../../utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Badge Variants — Enhanced with Plane-inspired semantic tokens
|
|
8
|
+
*
|
|
9
|
+
* New variants:
|
|
10
|
+
* - danger: Red subtle background for errors/alerts
|
|
11
|
+
* - info: Blue subtle background for informational
|
|
12
|
+
* - accent: Brand color subtle background
|
|
13
|
+
*
|
|
14
|
+
* Updated variants:
|
|
15
|
+
* - success: Now uses semantic token (was hardcoded bg-green-500)
|
|
16
|
+
* - warning: Now uses semantic token (was hardcoded bg-yellow-500)
|
|
17
|
+
*/
|
|
18
|
+
const badgeVariants = cva(
|
|
19
|
+
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
20
|
+
{
|
|
21
|
+
variants: {
|
|
22
|
+
variant: {
|
|
23
|
+
// Existing variants (backwards-compatible)
|
|
24
|
+
default: "border-transparent bg-primary text-primary-foreground",
|
|
25
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
|
26
|
+
destructive:
|
|
27
|
+
"border-transparent bg-destructive text-destructive-foreground",
|
|
28
|
+
outline: "text-foreground",
|
|
29
|
+
|
|
30
|
+
// Enhanced semantic variants (using Design Tokens)
|
|
31
|
+
success:
|
|
32
|
+
"border-transparent bg-success-subtle text-success-text",
|
|
33
|
+
warning:
|
|
34
|
+
"border-transparent bg-warning-subtle text-warning-text",
|
|
35
|
+
danger:
|
|
36
|
+
"border-transparent bg-danger-subtle text-danger-text",
|
|
37
|
+
info:
|
|
38
|
+
"border-transparent bg-info-subtle text-info-text",
|
|
39
|
+
accent:
|
|
40
|
+
"border-transparent bg-accent-primary-subtle text-accent-primary",
|
|
41
|
+
},
|
|
42
|
+
size: {
|
|
43
|
+
default: "",
|
|
44
|
+
sm: "px-1.5 py-0 text-[10px]",
|
|
45
|
+
lg: "px-3 py-1 text-sm",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
defaultVariants: {
|
|
49
|
+
variant: "default",
|
|
50
|
+
size: "default",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
export interface BadgeProps
|
|
56
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
57
|
+
VariantProps<typeof badgeVariants> {}
|
|
58
|
+
|
|
59
|
+
function Badge({ className, variant, size, ...props }: BadgeProps) {
|
|
60
|
+
return (
|
|
61
|
+
<div className={cn(badgeVariants({ variant, size }), className)} {...props} />
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { Badge, badgeVariants };
|
|
66
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
2
|
+
import { ChevronRight, Ellipsis } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import type { ComponentProps } from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../utils";
|
|
7
|
+
|
|
8
|
+
export function Breadcrumb({ ...props }: ComponentProps<"nav">) {
|
|
9
|
+
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function BreadcrumbList({ className, ...props }: ComponentProps<"ol">) {
|
|
13
|
+
return (
|
|
14
|
+
<ol
|
|
15
|
+
data-slot="breadcrumb-list"
|
|
16
|
+
className={cn(
|
|
17
|
+
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function BreadcrumbItem({ className, ...props }: ComponentProps<"li">) {
|
|
26
|
+
return (
|
|
27
|
+
<li
|
|
28
|
+
data-slot="breadcrumb-item"
|
|
29
|
+
className={cn("inline-flex items-center gap-1.5", className)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function BreadcrumbLink({
|
|
36
|
+
asChild,
|
|
37
|
+
className,
|
|
38
|
+
...props
|
|
39
|
+
}: ComponentProps<"a"> & {
|
|
40
|
+
asChild?: boolean;
|
|
41
|
+
}) {
|
|
42
|
+
const Comp = asChild ? Slot : "a";
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Comp
|
|
46
|
+
data-slot="breadcrumb-link"
|
|
47
|
+
className={cn("transition-colors hover:text-foreground", className)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function BreadcrumbPage({
|
|
54
|
+
className,
|
|
55
|
+
...props
|
|
56
|
+
}: ComponentProps<"span">) {
|
|
57
|
+
return (
|
|
58
|
+
<span
|
|
59
|
+
data-slot="breadcrumb-page"
|
|
60
|
+
role="link"
|
|
61
|
+
aria-disabled="true"
|
|
62
|
+
aria-current="page"
|
|
63
|
+
className={cn("font-normal text-foreground", className)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function BreadcrumbSeparator({
|
|
70
|
+
children,
|
|
71
|
+
className,
|
|
72
|
+
...props
|
|
73
|
+
}: ComponentProps<"li">) {
|
|
74
|
+
return (
|
|
75
|
+
<li
|
|
76
|
+
data-slot="breadcrumb-separator"
|
|
77
|
+
role="presentation"
|
|
78
|
+
aria-hidden="true"
|
|
79
|
+
className={cn("[&>svg]:size-3.5", className)}
|
|
80
|
+
{...props}
|
|
81
|
+
>
|
|
82
|
+
{children ?? <ChevronRight />}
|
|
83
|
+
</li>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function BreadcrumbEllipsis({
|
|
88
|
+
className,
|
|
89
|
+
...props
|
|
90
|
+
}: ComponentProps<"span">) {
|
|
91
|
+
return (
|
|
92
|
+
<span
|
|
93
|
+
data-slot="breadcrumb-ellipsis"
|
|
94
|
+
role="presentation"
|
|
95
|
+
aria-hidden="true"
|
|
96
|
+
aria-label="More"
|
|
97
|
+
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
|
98
|
+
{...props}
|
|
99
|
+
>
|
|
100
|
+
<Ellipsis className="h-4 w-4" />
|
|
101
|
+
</span>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva } from "class-variance-authority";
|
|
3
|
+
import type { VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../../utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Button Variants — Enhanced with Plane-inspired semantic tokens
|
|
8
|
+
*
|
|
9
|
+
* New variants:
|
|
10
|
+
* - accent: Brand CTA button (indigo)
|
|
11
|
+
* - danger: Destructive action (red, solid)
|
|
12
|
+
* - danger-outline: Destructive action (red, outlined)
|
|
13
|
+
* - success: Positive action (green)
|
|
14
|
+
*
|
|
15
|
+
* New sizes:
|
|
16
|
+
* - xs: Ultra-compact for ERP table actions
|
|
17
|
+
* - compact: Compact for toolbars
|
|
18
|
+
*/
|
|
19
|
+
const buttonVariants = cva(
|
|
20
|
+
"inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
|
21
|
+
{
|
|
22
|
+
variants: {
|
|
23
|
+
variant: {
|
|
24
|
+
// Existing variants (backwards-compatible)
|
|
25
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
26
|
+
destructive:
|
|
27
|
+
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
28
|
+
outline:
|
|
29
|
+
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
30
|
+
secondary:
|
|
31
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
32
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
33
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
34
|
+
|
|
35
|
+
// New Plane-inspired variants using semantic tokens
|
|
36
|
+
accent:
|
|
37
|
+
"bg-accent-primary text-text-on-color hover:bg-accent-primary-hover active:bg-accent-primary-active disabled:bg-layer-disabled disabled:text-text-disabled",
|
|
38
|
+
danger:
|
|
39
|
+
"bg-danger text-text-on-color hover:bg-danger-hover active:bg-danger-hover disabled:bg-layer-disabled disabled:text-text-disabled",
|
|
40
|
+
"danger-outline":
|
|
41
|
+
"border border-danger bg-layer-2 text-danger-text hover:bg-danger-subtle active:bg-danger-subtle disabled:border-border-subtle disabled:bg-layer-2 disabled:text-text-disabled",
|
|
42
|
+
success:
|
|
43
|
+
"bg-success-semantic text-text-on-color hover:bg-success-semantic/90 active:bg-success-semantic/80 disabled:bg-layer-disabled disabled:text-text-disabled",
|
|
44
|
+
tertiary:
|
|
45
|
+
"bg-layer-3 text-text-secondary hover:bg-layer-3-hover active:bg-layer-3-hover disabled:bg-transparent disabled:text-text-disabled",
|
|
46
|
+
},
|
|
47
|
+
size: {
|
|
48
|
+
// Existing sizes (backwards-compatible)
|
|
49
|
+
default: "h-10 px-4 py-2",
|
|
50
|
+
sm: "h-9 rounded-md px-3",
|
|
51
|
+
lg: "h-11 rounded-md px-8",
|
|
52
|
+
icon: "h-10 w-10",
|
|
53
|
+
|
|
54
|
+
// New ERP-specific compact sizes
|
|
55
|
+
xs: "h-7 rounded-sm px-2 text-xs",
|
|
56
|
+
compact: "h-8 rounded-md px-3 text-xs",
|
|
57
|
+
"icon-sm": "h-8 w-8",
|
|
58
|
+
"icon-xs": "h-7 w-7",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
defaultVariants: {
|
|
62
|
+
variant: "default",
|
|
63
|
+
size: "default",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
export interface ButtonProps
|
|
69
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
70
|
+
VariantProps<typeof buttonVariants> {
|
|
71
|
+
asChild?: boolean;
|
|
72
|
+
loading?: boolean;
|
|
73
|
+
prependIcon?: React.ReactElement;
|
|
74
|
+
appendIcon?: React.ReactElement;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
78
|
+
|
|
79
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
80
|
+
({ className, variant, size, asChild = false, loading = false, prependIcon, appendIcon, children, disabled, ...props }, ref) => {
|
|
81
|
+
if (asChild) {
|
|
82
|
+
return (
|
|
83
|
+
<Slot
|
|
84
|
+
className={cn(buttonVariants({ variant, size }), className)}
|
|
85
|
+
ref={ref}
|
|
86
|
+
{...({ disabled: disabled || loading } as any)}
|
|
87
|
+
{...props}
|
|
88
|
+
>
|
|
89
|
+
{React.isValidElement(children) && (prependIcon || appendIcon) ? (
|
|
90
|
+
React.cloneElement(children as React.ReactElement, {} as any, (
|
|
91
|
+
<>
|
|
92
|
+
{prependIcon && React.cloneElement(prependIcon, {
|
|
93
|
+
className: cn("shrink-0", size === "xs" || size === "compact" ? "size-3.5" : "size-4"),
|
|
94
|
+
} as React.Attributes & Record<string, unknown>)}
|
|
95
|
+
{(children as any).props.children}
|
|
96
|
+
{appendIcon && React.cloneElement(appendIcon, {
|
|
97
|
+
className: cn("shrink-0", size === "xs" || size === "compact" ? "size-3.5" : "size-4"),
|
|
98
|
+
} as React.Attributes & Record<string, unknown>)}
|
|
99
|
+
</>
|
|
100
|
+
))
|
|
101
|
+
) : (
|
|
102
|
+
children
|
|
103
|
+
)}
|
|
104
|
+
</Slot>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<button
|
|
110
|
+
className={cn(buttonVariants({ variant, size }), className)}
|
|
111
|
+
ref={ref}
|
|
112
|
+
disabled={disabled || loading}
|
|
113
|
+
{...props}
|
|
114
|
+
>
|
|
115
|
+
{prependIcon && React.cloneElement(prependIcon, {
|
|
116
|
+
className: cn("shrink-0", size === "xs" || size === "compact" ? "size-3.5" : "size-4"),
|
|
117
|
+
} as React.Attributes & Record<string, unknown>)}
|
|
118
|
+
{children}
|
|
119
|
+
{appendIcon && React.cloneElement(appendIcon, {
|
|
120
|
+
className: cn("shrink-0", size === "xs" || size === "compact" ? "size-3.5" : "size-4"),
|
|
121
|
+
} as React.Attributes & Record<string, unknown>)}
|
|
122
|
+
</button>
|
|
123
|
+
);
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
Button.displayName = "Button";
|
|
127
|
+
|
|
128
|
+
export { Button, buttonVariants };
|
|
129
|
+
|