spine-framework 0.1.0
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/.framework/README.md +129 -0
- package/.framework/cli/bin.cjs +14 -0
- package/.framework/cli/commands/agents.ts +153 -0
- package/.framework/cli/commands/auth.ts +94 -0
- package/.framework/cli/commands/create-app.ts +185 -0
- package/.framework/cli/commands/dev.ts +295 -0
- package/.framework/cli/commands/doctor.ts +442 -0
- package/.framework/cli/commands/generate.ts +332 -0
- package/.framework/cli/commands/init.ts +272 -0
- package/.framework/cli/commands/install-app.ts +391 -0
- package/.framework/cli/commands/items.ts +253 -0
- package/.framework/cli/commands/migrations.ts +141 -0
- package/.framework/cli/commands/pipelines.ts +166 -0
- package/.framework/cli/commands/status.ts +197 -0
- package/.framework/cli/commands/system.ts +184 -0
- package/.framework/cli/commands/test.ts +227 -0
- package/.framework/cli/commands/uninstall-app.ts +166 -0
- package/.framework/cli/context.ts +268 -0
- package/.framework/cli/env-loader.ts +36 -0
- package/.framework/cli/index.ts +106 -0
- package/.framework/cli/welcome.cjs +45 -0
- package/.framework/docs/API.md +384 -0
- package/.framework/docs/STABILITY.md +52 -0
- package/.framework/docs/admin-routes.md +76 -0
- package/.framework/docs/api-docs-progress.md +38 -0
- package/.framework/docs/api-governance.md +146 -0
- package/.framework/docs/api-testing-results.md +212 -0
- package/.framework/docs/apis/admin-configs.md +567 -0
- package/.framework/docs/apis/admin-data.md +272 -0
- package/.framework/docs/apis/index.md +231 -0
- package/.framework/docs/apis/internal.md +295 -0
- package/.framework/docs/apis/runtime.md +537 -0
- package/.framework/docs/assembly-launch-guide.md +138 -0
- package/.framework/docs/audit-results.md +590 -0
- package/.framework/docs/authorization-model.md +170 -0
- package/.framework/docs/db-api-inventory.md +95 -0
- package/.framework/docs/examples/custom-app/README.md +77 -0
- package/.framework/docs/examples/custom-function/README.md +27 -0
- package/.framework/docs/examples/custom-function/handler.ts +48 -0
- package/.framework/docs/examples/custom-webhook/README.md +68 -0
- package/.framework/docs/gap-remediation-backlog.md +103 -0
- package/.framework/docs/guides/cli-guide.md +224 -0
- package/.framework/docs/guides/getting-started.md +103 -0
- package/.framework/docs/guides/import-guide.md +193 -0
- package/.framework/docs/guides/testing-guide.md +229 -0
- package/.framework/docs/permission-examples.md +326 -0
- package/.framework/docs/ui-adoption-verification.md +111 -0
- package/.framework/docs/ui-api-coverage.md +84 -0
- package/.framework/docs/v2-compatibility-audit.md +228 -0
- package/.framework/functions/.gitkeep +1 -0
- package/.framework/functions/_shared/agent-runner.ts +1097 -0
- package/.framework/functions/_shared/app-manifest.ts +184 -0
- package/.framework/functions/_shared/audit.ts +150 -0
- package/.framework/functions/_shared/db.ts +174 -0
- package/.framework/functions/_shared/index.ts +382 -0
- package/.framework/functions/_shared/middleware.ts +490 -0
- package/.framework/functions/_shared/permissions.ts +1325 -0
- package/.framework/functions/_shared/pipeline-runner.ts +731 -0
- package/.framework/functions/_shared/principal.ts +760 -0
- package/.framework/functions/_shared/schema-utils.ts +967 -0
- package/.framework/functions/_shared/testing.ts +258 -0
- package/.framework/functions/_shared/trigger-engine.ts +425 -0
- package/.framework/functions/_shared/webhook-registration.ts +168 -0
- package/.framework/functions/_shared/webhook-registry.ts +129 -0
- package/.framework/functions/account-nodes.ts +111 -0
- package/.framework/functions/admin-data.ts +606 -0
- package/.framework/functions/ai-agents.ts +323 -0
- package/.framework/functions/api-keys.ts +376 -0
- package/.framework/functions/apps.ts +483 -0
- package/.framework/functions/auth.ts +196 -0
- package/.framework/functions/debug-auth.ts +107 -0
- package/.framework/functions/embeddings.ts +556 -0
- package/.framework/functions/integration-routes.ts +523 -0
- package/.framework/functions/integrations.ts +319 -0
- package/.framework/functions/item-progress.ts +272 -0
- package/.framework/functions/logs.ts +438 -0
- package/.framework/functions/observability.ts +275 -0
- package/.framework/functions/pipeline-executions.ts +494 -0
- package/.framework/functions/pipelines.ts +485 -0
- package/.framework/functions/prompt-configs.ts +339 -0
- package/.framework/functions/roles.ts +387 -0
- package/.framework/functions/system-cron.ts +742 -0
- package/.framework/functions/system.ts +323 -0
- package/.framework/functions/tests.ts +119 -0
- package/.framework/functions/timers.ts +357 -0
- package/.framework/functions/triggers.ts +563 -0
- package/.framework/functions/types.ts +604 -0
- package/.framework/migrations/000_foundation.sql +1256 -0
- package/.framework/migrations/001_seed.sql +92 -0
- package/.framework/migrations/002_seed_constraints.sql +13 -0
- package/.framework/migrations/003_auth_user_trigger.sql +59 -0
- package/.framework/src/App.tsx +126 -0
- package/.framework/src/apps/admin/index.tsx +173 -0
- package/.framework/src/components/AppWrapper.tsx +56 -0
- package/.framework/src/components/CustomAppLoader.tsx +116 -0
- package/.framework/src/components/admin/AdminListPage.tsx +151 -0
- package/.framework/src/components/admin/AdminSidebar.tsx +166 -0
- package/.framework/src/components/admin/AdminStatsCard.tsx +62 -0
- package/.framework/src/components/admin/SortableTableHeader.tsx +42 -0
- package/.framework/src/components/app-shell/GenericAppShell.tsx +181 -0
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +200 -0
- package/.framework/src/components/app-shell/GenericListPage.tsx +116 -0
- package/.framework/src/components/app-sidebar.tsx +228 -0
- package/.framework/src/components/auth/ProtectedRoute.tsx +88 -0
- package/.framework/src/components/layout/AppShell.tsx +91 -0
- package/.framework/src/components/layout/Header.tsx +88 -0
- package/.framework/src/components/layout/Layout.tsx +95 -0
- package/.framework/src/components/layout/Sidebar.tsx +329 -0
- package/.framework/src/components/runtime/DataDetailHeader.tsx +77 -0
- package/.framework/src/components/runtime/DataDetailPage.tsx +171 -0
- package/.framework/src/components/runtime/DataFilters.tsx +91 -0
- package/.framework/src/components/runtime/DataHeader.tsx +68 -0
- package/.framework/src/components/runtime/DataListPage.tsx +124 -0
- package/.framework/src/components/runtime/DataStats.tsx +70 -0
- package/.framework/src/components/runtime/DataTable.tsx +174 -0
- package/.framework/src/components/runtime/SchemaDetailForm.tsx +134 -0
- package/.framework/src/components/runtime/index.ts +18 -0
- package/.framework/src/components/search-form.tsx +29 -0
- package/.framework/src/components/shared/AgentView.tsx +213 -0
- package/.framework/src/components/shared/FieldRenderer.tsx +478 -0
- package/.framework/src/components/shared/SchemaFields.tsx +226 -0
- package/.framework/src/components/ui/DataTable.tsx +343 -0
- package/.framework/src/components/ui/Form.tsx +281 -0
- package/.framework/src/components/ui/ItemCard.tsx +296 -0
- package/.framework/src/components/ui/ItemListView.tsx +308 -0
- package/.framework/src/components/ui/LoadingSpinner.tsx +52 -0
- package/.framework/src/components/ui/Modal.tsx +61 -0
- package/.framework/src/components/ui/RichTextEditor.tsx +210 -0
- package/.framework/src/components/ui/accordion.tsx +82 -0
- package/.framework/src/components/ui/alert-dialog.tsx +197 -0
- package/.framework/src/components/ui/alert.tsx +76 -0
- package/.framework/src/components/ui/aspect-ratio.tsx +11 -0
- package/.framework/src/components/ui/avatar.tsx +110 -0
- package/.framework/src/components/ui/badge.tsx +49 -0
- package/.framework/src/components/ui/breadcrumb.tsx +122 -0
- package/.framework/src/components/ui/button-group.tsx +83 -0
- package/.framework/src/components/ui/button.tsx +65 -0
- package/.framework/src/components/ui/calendar.tsx +222 -0
- package/.framework/src/components/ui/card.tsx +100 -0
- package/.framework/src/components/ui/carousel.tsx +240 -0
- package/.framework/src/components/ui/chart.tsx +373 -0
- package/.framework/src/components/ui/checkbox.tsx +31 -0
- package/.framework/src/components/ui/collapsible.tsx +33 -0
- package/.framework/src/components/ui/combobox.tsx +299 -0
- package/.framework/src/components/ui/command.tsx +193 -0
- package/.framework/src/components/ui/context-menu.tsx +261 -0
- package/.framework/src/components/ui/dialog.tsx +165 -0
- package/.framework/src/components/ui/direction.tsx +22 -0
- package/.framework/src/components/ui/drawer.tsx +132 -0
- package/.framework/src/components/ui/dropdown-menu.tsx +269 -0
- package/.framework/src/components/ui/empty.tsx +104 -0
- package/.framework/src/components/ui/field.tsx +238 -0
- package/.framework/src/components/ui/hover-card.tsx +42 -0
- package/.framework/src/components/ui/input-group.tsx +153 -0
- package/.framework/src/components/ui/input-otp.tsx +87 -0
- package/.framework/src/components/ui/input.tsx +19 -0
- package/.framework/src/components/ui/item.tsx +196 -0
- package/.framework/src/components/ui/kbd.tsx +26 -0
- package/.framework/src/components/ui/label.tsx +22 -0
- package/.framework/src/components/ui/menubar.tsx +277 -0
- package/.framework/src/components/ui/native-select.tsx +61 -0
- package/.framework/src/components/ui/navigation-menu.tsx +164 -0
- package/.framework/src/components/ui/pagination.tsx +129 -0
- package/.framework/src/components/ui/popover.tsx +87 -0
- package/.framework/src/components/ui/progress.tsx +31 -0
- package/.framework/src/components/ui/radio-group.tsx +42 -0
- package/.framework/src/components/ui/resizable.tsx +50 -0
- package/.framework/src/components/ui/scroll-area.tsx +53 -0
- package/.framework/src/components/ui/select.tsx +195 -0
- package/.framework/src/components/ui/separator.tsx +26 -0
- package/.framework/src/components/ui/sheet.tsx +145 -0
- package/.framework/src/components/ui/sidebar.tsx +706 -0
- package/.framework/src/components/ui/skeleton.tsx +13 -0
- package/.framework/src/components/ui/slider.tsx +59 -0
- package/.framework/src/components/ui/sonner.tsx +47 -0
- package/.framework/src/components/ui/spinner.tsx +10 -0
- package/.framework/src/components/ui/switch.tsx +33 -0
- package/.framework/src/components/ui/table-primitives.tsx +141 -0
- package/.framework/src/components/ui/table.tsx +114 -0
- package/.framework/src/components/ui/tabs.tsx +90 -0
- package/.framework/src/components/ui/textarea.tsx +18 -0
- package/.framework/src/components/ui/toggle-group.tsx +89 -0
- package/.framework/src/components/ui/toggle.tsx +45 -0
- package/.framework/src/components/ui/tooltip.tsx +57 -0
- package/.framework/src/contexts/AppContext.tsx +133 -0
- package/.framework/src/contexts/AuthContext.tsx +371 -0
- package/.framework/src/hooks/use-mobile.ts +19 -0
- package/.framework/src/hooks/useApi.ts +526 -0
- package/.framework/src/hooks/useApps.ts +114 -0
- package/.framework/src/hooks/useEntityList.ts +190 -0
- package/.framework/src/hooks/useEntityRecord.ts +308 -0
- package/.framework/src/hooks/useForm.ts +307 -0
- package/.framework/src/hooks/useListSchema.ts +264 -0
- package/.framework/src/hooks/useSchemaRecord.ts +223 -0
- package/.framework/src/index.css +128 -0
- package/.framework/src/lib/api.ts +156 -0
- package/.framework/src/lib/supabase.ts +94 -0
- package/.framework/src/lib/utils.ts +317 -0
- package/.framework/src/main.tsx +27 -0
- package/.framework/src/pages/DashboardPage.tsx +181 -0
- package/.framework/src/pages/NotFoundPage.tsx +39 -0
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +161 -0
- package/.framework/src/pages/admin/AIAgentsPage.tsx +318 -0
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +199 -0
- package/.framework/src/pages/admin/APIKeysPage.tsx +303 -0
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +523 -0
- package/.framework/src/pages/admin/AppDetailPage.tsx +493 -0
- package/.framework/src/pages/admin/AppsPage.tsx +355 -0
- package/.framework/src/pages/admin/DesignedPage.tsx +491 -0
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +534 -0
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +424 -0
- package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +176 -0
- package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +109 -0
- package/.framework/src/pages/admin/IntegratedDashboard.tsx +402 -0
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +187 -0
- package/.framework/src/pages/admin/IntegrationsPage.tsx +301 -0
- package/.framework/src/pages/admin/LogsPage.tsx +283 -0
- package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +85 -0
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +470 -0
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +183 -0
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +279 -0
- package/.framework/src/pages/admin/PipelinesPage.tsx +390 -0
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +299 -0
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +292 -0
- package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +434 -0
- package/.framework/src/pages/admin/RoleDetailPage.tsx +273 -0
- package/.framework/src/pages/admin/RolesPage.tsx +292 -0
- package/.framework/src/pages/admin/SelectTestPage.tsx +61 -0
- package/.framework/src/pages/admin/ShadcnTestPage.tsx +588 -0
- package/.framework/src/pages/admin/SimpleDashboard.tsx +387 -0
- package/.framework/src/pages/admin/TestRunDetailPage.tsx +172 -0
- package/.framework/src/pages/admin/TestingDashboard.tsx +257 -0
- package/.framework/src/pages/admin/TimerDetailPage.tsx +151 -0
- package/.framework/src/pages/admin/TimersPage.tsx +376 -0
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +149 -0
- package/.framework/src/pages/admin/TriggersPage.tsx +381 -0
- package/.framework/src/pages/admin/TypeDetailPage.tsx +694 -0
- package/.framework/src/pages/admin/TypesPage.tsx +295 -0
- package/.framework/src/pages/auth/LoginPage.tsx +188 -0
- package/.framework/src/pages/auth/RegisterPage.tsx +163 -0
- package/.framework/src/pages/spine-framework/APIPage.tsx +17 -0
- package/.framework/src/pages/spine-framework/CLIPage.tsx +25 -0
- package/.framework/src/types/auth.ts +125 -0
- package/.framework/src/types/types.ts +407 -0
- package/STRUCTURE.md +150 -0
- package/config/components.json +25 -0
- package/config/deno.lock +108 -0
- package/config/package-lock.json +17183 -0
- package/config/postcss.config.cjs +10 -0
- package/config/tailwind.config.cjs +78 -0
- package/config/tsconfig.build.json +32 -0
- package/config/tsconfig.cli.json +18 -0
- package/config/tsconfig.json +41 -0
- package/config/tsconfig.node.json +17 -0
- package/config/tsconfig.node.tsbuildinfo +1 -0
- package/config/tsconfig.tsbuildinfo +1 -0
- package/config/typedoc.json +16 -0
- package/config/vite.config.d.ts +2 -0
- package/config/vite.config.ts +72 -0
- package/dist/cli/commands/agents.d.ts +39 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/auth.d.ts +36 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/create-app.d.ts +23 -0
- package/dist/cli/commands/create-app.d.ts.map +1 -0
- package/dist/cli/commands/dev.d.ts +39 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/doctor.d.ts +42 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/generate.d.ts +36 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/init.d.ts +30 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/install-app.d.ts +30 -0
- package/dist/cli/commands/install-app.d.ts.map +1 -0
- package/dist/cli/commands/items.d.ts +45 -0
- package/dist/cli/commands/items.d.ts.map +1 -0
- package/dist/cli/commands/migrations.d.ts +41 -0
- package/dist/cli/commands/migrations.d.ts.map +1 -0
- package/dist/cli/commands/pipelines.d.ts +40 -0
- package/dist/cli/commands/pipelines.d.ts.map +1 -0
- package/dist/cli/commands/status.d.ts +23 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/system.d.ts +29 -0
- package/dist/cli/commands/system.d.ts.map +1 -0
- package/dist/cli/commands/test.d.ts +46 -0
- package/dist/cli/commands/test.d.ts.map +1 -0
- package/dist/cli/commands/uninstall-app.d.ts +23 -0
- package/dist/cli/commands/uninstall-app.d.ts.map +1 -0
- package/dist/cli/context.d.ts +88 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/env-loader.d.ts +14 -0
- package/dist/cli/env-loader.d.ts.map +1 -0
- package/dist/cli/index.d.ts +41 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/functions/_shared/agent-runner.d.ts +156 -0
- package/dist/functions/_shared/agent-runner.d.ts.map +1 -0
- package/dist/functions/_shared/app-manifest.d.ts +68 -0
- package/dist/functions/_shared/app-manifest.d.ts.map +1 -0
- package/dist/functions/_shared/audit.d.ts +91 -0
- package/dist/functions/_shared/audit.d.ts.map +1 -0
- package/dist/functions/_shared/db.d.ts +125 -0
- package/dist/functions/_shared/db.d.ts.map +1 -0
- package/dist/functions/_shared/index.d.ts +298 -0
- package/dist/functions/_shared/index.d.ts.map +1 -0
- package/dist/functions/_shared/middleware.d.ts +315 -0
- package/dist/functions/_shared/middleware.d.ts.map +1 -0
- package/dist/functions/_shared/permissions.d.ts +626 -0
- package/dist/functions/_shared/permissions.d.ts.map +1 -0
- package/dist/functions/_shared/pipeline-runner.d.ts +124 -0
- package/dist/functions/_shared/pipeline-runner.d.ts.map +1 -0
- package/dist/functions/_shared/principal.d.ts +284 -0
- package/dist/functions/_shared/principal.d.ts.map +1 -0
- package/dist/functions/_shared/schema-utils.d.ts +181 -0
- package/dist/functions/_shared/schema-utils.d.ts.map +1 -0
- package/dist/functions/_shared/testing.d.ts +172 -0
- package/dist/functions/_shared/testing.d.ts.map +1 -0
- package/dist/functions/_shared/trigger-engine.d.ts +140 -0
- package/dist/functions/_shared/trigger-engine.d.ts.map +1 -0
- package/dist/functions/_shared/webhook-registration.d.ts +81 -0
- package/dist/functions/_shared/webhook-registration.d.ts.map +1 -0
- package/dist/functions/_shared/webhook-registry.d.ts +57 -0
- package/dist/functions/_shared/webhook-registry.d.ts.map +1 -0
- package/dist/functions/account-nodes.d.ts +48 -0
- package/dist/functions/account-nodes.d.ts.map +1 -0
- package/dist/functions/admin-data.d.ts +178 -0
- package/dist/functions/admin-data.d.ts.map +1 -0
- package/dist/functions/ai-agents.d.ts +125 -0
- package/dist/functions/ai-agents.d.ts.map +1 -0
- package/dist/functions/api-keys.d.ts +140 -0
- package/dist/functions/api-keys.d.ts.map +1 -0
- package/dist/functions/apps.d.ts +163 -0
- package/dist/functions/apps.d.ts.map +1 -0
- package/dist/functions/auth.d.ts +74 -0
- package/dist/functions/auth.d.ts.map +1 -0
- package/dist/functions/debug-auth.d.ts +33 -0
- package/dist/functions/debug-auth.d.ts.map +1 -0
- package/dist/functions/embeddings.d.ts +205 -0
- package/dist/functions/embeddings.d.ts.map +1 -0
- package/dist/functions/integration-routes.d.ts +45 -0
- package/dist/functions/integration-routes.d.ts.map +1 -0
- package/dist/functions/integrations.d.ts +124 -0
- package/dist/functions/integrations.d.ts.map +1 -0
- package/dist/functions/item-progress.d.ts +41 -0
- package/dist/functions/item-progress.d.ts.map +1 -0
- package/dist/functions/logs.d.ts +162 -0
- package/dist/functions/logs.d.ts.map +1 -0
- package/dist/functions/observability.d.ts +123 -0
- package/dist/functions/observability.d.ts.map +1 -0
- package/dist/functions/pipeline-executions.d.ts +190 -0
- package/dist/functions/pipeline-executions.d.ts.map +1 -0
- package/dist/functions/pipelines.d.ts +171 -0
- package/dist/functions/pipelines.d.ts.map +1 -0
- package/dist/functions/prompt-configs.d.ts +125 -0
- package/dist/functions/prompt-configs.d.ts.map +1 -0
- package/dist/functions/roles.d.ts +118 -0
- package/dist/functions/roles.d.ts.map +1 -0
- package/dist/functions/system-cron.d.ts +65 -0
- package/dist/functions/system-cron.d.ts.map +1 -0
- package/dist/functions/system.d.ts +29 -0
- package/dist/functions/system.d.ts.map +1 -0
- package/dist/functions/tests.d.ts +28 -0
- package/dist/functions/tests.d.ts.map +1 -0
- package/dist/functions/timers.d.ts +139 -0
- package/dist/functions/timers.d.ts.map +1 -0
- package/dist/functions/triggers.d.ts +203 -0
- package/dist/functions/triggers.d.ts.map +1 -0
- package/dist/functions/types.d.ts +151 -0
- package/dist/functions/types.d.ts.map +1 -0
- package/dist/src/types/types.d.ts +364 -0
- package/dist/src/types/types.d.ts.map +1 -0
- package/package.json +192 -0
- package/scripts/app-install-cli.ts +286 -0
- package/scripts/assemble-frontend.sh +79 -0
- package/scripts/assemble-functions.sh +62 -0
- package/scripts/assemble.sh +35 -0
- package/scripts/boundary-check.sh +106 -0
- package/scripts/build-manifest.sh +80 -0
- package/scripts/check-core-integrity.sh +82 -0
- package/scripts/ingest-chunks.cjs +202 -0
- package/scripts/kb-chunk-parser.cjs +312 -0
- package/scripts/kb-chunk-parser.ts +330 -0
- package/scripts/load-test-app-install.ts +484 -0
- package/scripts/netlify-dev-wrapper.sh +22 -0
- package/scripts/verify-integrity.sh +69 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module src/lib/utils
|
|
3
|
+
* @audience installer
|
|
4
|
+
* @layer frontend-hook
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* General-purpose frontend utility functions. Pure helpers only — no React,
|
|
8
|
+
* no Supabase, no side effects. Safe to use in any component or hook.
|
|
9
|
+
*
|
|
10
|
+
* **Exports:** `cn`, `formatDate`, `formatTime`, `formatDateTime`,
|
|
11
|
+
* `formatRelativeTime`, `formatFileSize`, `formatCurrency`, `formatNumber`,
|
|
12
|
+
* `truncateText`, `generateSlug`, `capitalize`, `debounce`, `throttle`,
|
|
13
|
+
* `isValidUrl`, `getFileExtension`, `generateId`, `deepClone`
|
|
14
|
+
*
|
|
15
|
+
* @seeAlso src/lib/api.ts (fetch utilities)
|
|
16
|
+
* @seeAlso functions/_shared/schema-utils.ts (server-side field formatters
|
|
17
|
+
* for storage-layer data; parallel set to the date/currency helpers here)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { type ClassValue, clsx } from 'clsx'
|
|
21
|
+
import { twMerge } from 'tailwind-merge'
|
|
22
|
+
|
|
23
|
+
// ─── TAILWIND HELPERS ───────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Merges Tailwind class strings with conflict resolution via `tailwind-merge`
|
|
27
|
+
* and conditional class support via `clsx`. The standard utility for
|
|
28
|
+
* composing class names in all Spine components.
|
|
29
|
+
*
|
|
30
|
+
* @param inputs - Any number of class values (strings, arrays, objects)
|
|
31
|
+
* @returns Merged, deduplicated class string
|
|
32
|
+
* @throws never
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* cn('px-4 py-2', isActive && 'bg-blue-500', 'px-6') // → 'py-2 bg-blue-500 px-6'
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function cn(...inputs: ClassValue[]) {
|
|
39
|
+
return twMerge(clsx(inputs))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── DATE FORMATTERS ────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Formats a date as a locale date string ('Jan 15, 2024').
|
|
46
|
+
* Returns `'N/A'` for null/undefined, `'Invalid Date'` for non-parseable input.
|
|
47
|
+
* @param date - ISO string, Date object, null, or undefined
|
|
48
|
+
* @param options - Optional `Intl.DateTimeFormatOptions` overrides
|
|
49
|
+
* @throws never
|
|
50
|
+
*/
|
|
51
|
+
export function formatDate(date: string | Date | null | undefined, options?: Intl.DateTimeFormatOptions) {
|
|
52
|
+
if (!date) return 'N/A'
|
|
53
|
+
|
|
54
|
+
const dateObj = typeof date === 'string' ? new Date(date) : date
|
|
55
|
+
|
|
56
|
+
// Check if date is invalid
|
|
57
|
+
if (!dateObj || isNaN(dateObj.getTime())) return 'Invalid Date'
|
|
58
|
+
|
|
59
|
+
return dateObj.toLocaleDateString('en-US', {
|
|
60
|
+
year: 'numeric',
|
|
61
|
+
month: 'short',
|
|
62
|
+
day: 'numeric',
|
|
63
|
+
...options,
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Formats a date as a locale time string ('02:30 PM').
|
|
69
|
+
* Returns `'N/A'` for null/undefined, `'Invalid Date'` for bad input.
|
|
70
|
+
* @throws never
|
|
71
|
+
*/
|
|
72
|
+
export function formatTime(date: string | Date | null | undefined, options?: Intl.DateTimeFormatOptions) {
|
|
73
|
+
if (!date) return 'N/A'
|
|
74
|
+
|
|
75
|
+
const dateObj = typeof date === 'string' ? new Date(date) : date
|
|
76
|
+
|
|
77
|
+
// Check if date is invalid
|
|
78
|
+
if (!dateObj || isNaN(dateObj.getTime())) return 'Invalid Date'
|
|
79
|
+
|
|
80
|
+
return dateObj.toLocaleTimeString('en-US', {
|
|
81
|
+
hour: '2-digit',
|
|
82
|
+
minute: '2-digit',
|
|
83
|
+
...options,
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Formats a date as a locale date+time string ('Jan 15, 2024, 02:30 PM').
|
|
89
|
+
* Returns `'N/A'` for null/undefined, `'Invalid Date'` for bad input.
|
|
90
|
+
* @throws never
|
|
91
|
+
*/
|
|
92
|
+
export function formatDateTime(date: string | Date | null | undefined, options?: Intl.DateTimeFormatOptions) {
|
|
93
|
+
if (!date) return 'N/A'
|
|
94
|
+
|
|
95
|
+
const dateObj = typeof date === 'string' ? new Date(date) : date
|
|
96
|
+
|
|
97
|
+
// Check if date is invalid
|
|
98
|
+
if (!dateObj || isNaN(dateObj.getTime())) return 'Invalid Date'
|
|
99
|
+
|
|
100
|
+
return dateObj.toLocaleString('en-US', {
|
|
101
|
+
year: 'numeric',
|
|
102
|
+
month: 'short',
|
|
103
|
+
day: 'numeric',
|
|
104
|
+
hour: '2-digit',
|
|
105
|
+
minute: '2-digit',
|
|
106
|
+
...options,
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Formats a date as a human-readable relative string
|
|
112
|
+
* ('just now', '5 minutes ago', '3 days ago', or falls back to `formatDate`
|
|
113
|
+
* for dates older than 7 days).
|
|
114
|
+
* Returns `'N/A'` for null/undefined, `'Invalid Date'` for bad input.
|
|
115
|
+
* @throws never
|
|
116
|
+
*/
|
|
117
|
+
export function formatRelativeTime(date: string | Date | null | undefined) {
|
|
118
|
+
if (!date) return 'N/A'
|
|
119
|
+
|
|
120
|
+
const dateObj = typeof date === 'string' ? new Date(date) : date
|
|
121
|
+
|
|
122
|
+
// Check if date is invalid
|
|
123
|
+
if (isNaN(dateObj.getTime())) return 'Invalid Date'
|
|
124
|
+
|
|
125
|
+
const now = new Date()
|
|
126
|
+
const diffMs = now.getTime() - dateObj.getTime()
|
|
127
|
+
const diffSecs = Math.floor(diffMs / 1000)
|
|
128
|
+
const diffMins = Math.floor(diffSecs / 60)
|
|
129
|
+
const diffHours = Math.floor(diffMins / 60)
|
|
130
|
+
const diffDays = Math.floor(diffHours / 24)
|
|
131
|
+
|
|
132
|
+
if (diffSecs < 60) {
|
|
133
|
+
return 'just now'
|
|
134
|
+
} else if (diffMins < 60) {
|
|
135
|
+
return `${diffMins} minute${diffMins > 1 ? 's' : ''} ago`
|
|
136
|
+
} else if (diffHours < 24) {
|
|
137
|
+
return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`
|
|
138
|
+
} else if (diffDays < 7) {
|
|
139
|
+
return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`
|
|
140
|
+
} else {
|
|
141
|
+
return formatDate(dateObj)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ─── NUMBER FORMATTERS ───────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Formats bytes as a human-readable size string ('1.23 MB').
|
|
149
|
+
* Returns `'0 Bytes'` for 0.
|
|
150
|
+
* @param bytes - Non-negative byte count
|
|
151
|
+
* @param decimals - Decimal places (default 2)
|
|
152
|
+
* @throws never
|
|
153
|
+
*/
|
|
154
|
+
export function formatFileSize(bytes: number, decimals = 2) {
|
|
155
|
+
if (bytes === 0) return '0 Bytes'
|
|
156
|
+
|
|
157
|
+
const k = 1024
|
|
158
|
+
const dm = decimals < 0 ? 0 : decimals
|
|
159
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
|
160
|
+
|
|
161
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
162
|
+
|
|
163
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Formats a number as a locale currency string ('$1,234.56').
|
|
168
|
+
* @param amount - Numeric value
|
|
169
|
+
* @param currency - ISO 4217 code (default 'USD')
|
|
170
|
+
* @param locale - BCP 47 locale tag (default 'en-US')
|
|
171
|
+
* @throws never
|
|
172
|
+
*/
|
|
173
|
+
export function formatCurrency(amount: number, currency = 'USD', locale = 'en-US') {
|
|
174
|
+
return new Intl.NumberFormat(locale, {
|
|
175
|
+
style: 'currency',
|
|
176
|
+
currency,
|
|
177
|
+
}).format(amount)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Formats a number using `Intl.NumberFormat` with optional format options.
|
|
182
|
+
* @throws never
|
|
183
|
+
*/
|
|
184
|
+
export function formatNumber(num: number, options?: Intl.NumberFormatOptions) {
|
|
185
|
+
return new Intl.NumberFormat('en-US', options).format(num)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ─── STRING UTILITIES ────────────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Truncates text to `maxLength` characters, appending `'...'` if truncated.
|
|
192
|
+
* Returns the original string if it fits within `maxLength`.
|
|
193
|
+
* @throws never
|
|
194
|
+
*/
|
|
195
|
+
export function truncateText(text: string, maxLength: number) {
|
|
196
|
+
if (text.length <= maxLength) return text
|
|
197
|
+
return text.slice(0, maxLength) + '...'
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Converts text to a URL-friendly slug: lowercased, diacritics stripped,
|
|
202
|
+
* spaces/underscores/hyphens collapsed to single `-`, leading/trailing
|
|
203
|
+
* hyphens removed.
|
|
204
|
+
* @throws never
|
|
205
|
+
*/
|
|
206
|
+
export function generateSlug(text: string) {
|
|
207
|
+
return text
|
|
208
|
+
.toLowerCase()
|
|
209
|
+
.replace(/[^\w\s-]/g, '')
|
|
210
|
+
.replace(/[\s_-]+/g, '-')
|
|
211
|
+
.replace(/^-+|-+$/g, '')
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Capitalises the first character of a string. @throws never */
|
|
215
|
+
export function capitalize(text: string) {
|
|
216
|
+
return text.charAt(0).toUpperCase() + text.slice(1)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ─── TIMING UTILITIES ────────────────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Returns a debounced wrapper that delays invoking `func` until `wait` ms
|
|
223
|
+
* after the last call. Resets the timer on each call.
|
|
224
|
+
* @param func - The function to debounce
|
|
225
|
+
* @param wait - Delay in milliseconds
|
|
226
|
+
* @throws never (the wrapper itself never throws; `func` may throw)
|
|
227
|
+
* @example
|
|
228
|
+
* ```ts
|
|
229
|
+
* const search = debounce((q: string) => fetchResults(q), 300)
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
export function debounce<T extends (...args: any[]) => any>(
|
|
233
|
+
func: T,
|
|
234
|
+
wait: number
|
|
235
|
+
): (...args: Parameters<T>) => void {
|
|
236
|
+
let timeout: ReturnType<typeof setTimeout>
|
|
237
|
+
return (...args: Parameters<T>) => {
|
|
238
|
+
clearTimeout(timeout)
|
|
239
|
+
timeout = setTimeout(() => func(...args), wait)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Returns a throttled wrapper that invokes `func` at most once per `limit` ms.
|
|
245
|
+
* Subsequent calls within the window are dropped.
|
|
246
|
+
* @param func - The function to throttle
|
|
247
|
+
* @param limit - Throttle window in milliseconds
|
|
248
|
+
* @throws never
|
|
249
|
+
*/
|
|
250
|
+
export function throttle<T extends (...args: any[]) => any>(
|
|
251
|
+
func: T,
|
|
252
|
+
limit: number
|
|
253
|
+
): (...args: Parameters<T>) => void {
|
|
254
|
+
let inThrottle: boolean
|
|
255
|
+
return (...args: Parameters<T>) => {
|
|
256
|
+
if (!inThrottle) {
|
|
257
|
+
func(...args)
|
|
258
|
+
inThrottle = true
|
|
259
|
+
setTimeout(() => (inThrottle = false), limit)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ─── VALIDATION & FILE UTILITIES ────────────────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
/** Returns true if `url` is a valid absolute URL (any protocol). @throws never */
|
|
267
|
+
export function isValidUrl(url: string) {
|
|
268
|
+
try {
|
|
269
|
+
new URL(url)
|
|
270
|
+
return true
|
|
271
|
+
} catch {
|
|
272
|
+
return false
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Extracts the file extension from a filename. Returns an empty string if
|
|
278
|
+
* there is no extension. Works correctly for dotfiles (e.g. `.gitignore` → `''`).
|
|
279
|
+
* @throws never
|
|
280
|
+
*/
|
|
281
|
+
export function getFileExtension(filename: string) {
|
|
282
|
+
return filename.slice((filename.lastIndexOf('.') - 1 >>> 0) + 2)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Generates a random alphanumeric ID of the given length (default 8). @throws never */
|
|
286
|
+
export function generateId(length = 8) {
|
|
287
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
|
288
|
+
let result = ''
|
|
289
|
+
for (let i = 0; i < length; i++) {
|
|
290
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
|
291
|
+
}
|
|
292
|
+
return result
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ─── OBJECT UTILITIES ─────────────────────────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Recursively deep-clones an object, array, or Date. Primitives and null
|
|
299
|
+
* are returned by value. Does not handle Maps, Sets, or class instances
|
|
300
|
+
* with custom prototypes.
|
|
301
|
+
* @throws never
|
|
302
|
+
*/
|
|
303
|
+
export function deepClone<T>(obj: T): T {
|
|
304
|
+
if (obj === null || typeof obj !== 'object') return obj
|
|
305
|
+
if (obj instanceof Date) return new Date(obj.getTime()) as unknown as T
|
|
306
|
+
if (obj instanceof Array) return obj.map(item => deepClone(item)) as unknown as T
|
|
307
|
+
if (typeof obj === 'object') {
|
|
308
|
+
const clonedObj = {} as T
|
|
309
|
+
for (const key in obj) {
|
|
310
|
+
if (obj.hasOwnProperty(key)) {
|
|
311
|
+
clonedObj[key] = deepClone(obj[key])
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return clonedObj
|
|
315
|
+
}
|
|
316
|
+
return obj
|
|
317
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import ReactDOM from 'react-dom/client'
|
|
3
|
+
import { BrowserRouter } from 'react-router-dom'
|
|
4
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
5
|
+
import App from './App'
|
|
6
|
+
import { AuthProvider } from './contexts/AuthContext'
|
|
7
|
+
import './index.css'
|
|
8
|
+
|
|
9
|
+
// Create a client
|
|
10
|
+
const queryClient = new QueryClient({
|
|
11
|
+
defaultOptions: {
|
|
12
|
+
queries: {
|
|
13
|
+
retry: 1,
|
|
14
|
+
refetchOnWindowFocus: false,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
20
|
+
<QueryClientProvider client={queryClient}>
|
|
21
|
+
<BrowserRouter>
|
|
22
|
+
<AuthProvider>
|
|
23
|
+
<App />
|
|
24
|
+
</AuthProvider>
|
|
25
|
+
</BrowserRouter>
|
|
26
|
+
</QueryClientProvider>,
|
|
27
|
+
)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module src/pages/DashboardPage
|
|
3
|
+
* @audience installer
|
|
4
|
+
* @layer frontend-page
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* Admin home page. On mount, fetches all active item types (`kind=item`)
|
|
8
|
+
* from `/api/types` and then fans out parallel requests to
|
|
9
|
+
* `/api/admin-data?action=stats&entity=items&type_slug=…` to collect
|
|
10
|
+
* per-type item counts (capped at 8 types). Renders:
|
|
11
|
+
* - **Entity overview grid** — one stat card per type + a "Configuration"
|
|
12
|
+
* shortcut card
|
|
13
|
+
* - **Quick Actions row** — "New <Type>" buttons for the first 4 types plus
|
|
14
|
+
* a "Configure Types" shortcut
|
|
15
|
+
*
|
|
16
|
+
* @seeAlso src/contexts/AuthContext.tsx (provides current user)
|
|
17
|
+
* @seeAlso src/lib/api.ts (apiFetch)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import React, { useState, useEffect } from 'react'
|
|
21
|
+
import { useAuth } from '../contexts/AuthContext'
|
|
22
|
+
import { useApi } from '../hooks/useApi'
|
|
23
|
+
import { apiFetch } from '../lib/api'
|
|
24
|
+
import { useNavigate } from 'react-router-dom'
|
|
25
|
+
import { Box, UserGroupIcon, FileText, BarChart3, Settings, Plus } from 'lucide-react';
|
|
26
|
+
|
|
27
|
+
interface TypeStat {
|
|
28
|
+
name: string
|
|
29
|
+
count: number
|
|
30
|
+
slug: string
|
|
31
|
+
icon: any
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function DashboardPage() {
|
|
35
|
+
const { user } = useAuth()
|
|
36
|
+
const navigate = useNavigate()
|
|
37
|
+
const [stats, setStats] = useState<Array<{
|
|
38
|
+
title: string
|
|
39
|
+
value: number
|
|
40
|
+
icon: any
|
|
41
|
+
color: string
|
|
42
|
+
href: string
|
|
43
|
+
}>>([])
|
|
44
|
+
const [typeStats, setTypeStats] = useState<TypeStat[]>([])
|
|
45
|
+
const [loading, setLoading] = useState(true)
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const fetchStats = async () => {
|
|
49
|
+
try {
|
|
50
|
+
// Fetch active types with their counts
|
|
51
|
+
const typesResponse = await apiFetch('/api/types?kind=item&is_active=true')
|
|
52
|
+
if (typesResponse.ok) {
|
|
53
|
+
const response = await typesResponse.json()
|
|
54
|
+
const types = response.data || response // Handle both wrapped and unwrapped responses
|
|
55
|
+
|
|
56
|
+
if (!Array.isArray(types)) {
|
|
57
|
+
console.error('Expected array of types, got:', types)
|
|
58
|
+
setTypeStats([])
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Get counts for each type
|
|
63
|
+
const statsPromises = types.map(async (type: any) => {
|
|
64
|
+
const countResponse = await apiFetch(`/api/admin-data?action=stats&entity=items&type_slug=${type.slug}`)
|
|
65
|
+
if (countResponse.ok) {
|
|
66
|
+
const countData = await countResponse.json()
|
|
67
|
+
return {
|
|
68
|
+
name: type.name,
|
|
69
|
+
count: countData.count || 0,
|
|
70
|
+
slug: type.slug,
|
|
71
|
+
icon: CubeIcon // Default icon, could be enhanced to use type.icon
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return null
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const resolvedStats = await Promise.all(statsPromises)
|
|
78
|
+
const validStats = resolvedStats.filter((s): s is NonNullable<typeof s> => s !== null).slice(0, 8) // Limit to 8 types
|
|
79
|
+
setTypeStats(validStats)
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error('Failed to fetch dashboard stats:', error)
|
|
83
|
+
} finally {
|
|
84
|
+
setLoading(false)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fetchStats()
|
|
89
|
+
}, [apiFetch])
|
|
90
|
+
|
|
91
|
+
const handleCreateNewItem = (typeSlug: string) => {
|
|
92
|
+
navigate(`/spine-framework/admin/data/items/create?typeSlug=${typeSlug}`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const handleViewItems = (typeSlug: string) => {
|
|
96
|
+
navigate(`/spine-framework/admin/data/items?typeSlug=${typeSlug}`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (loading) {
|
|
100
|
+
return (
|
|
101
|
+
<div className="flex items-center justify-center h-64">
|
|
102
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
|
103
|
+
</div>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div className="space-y-6">
|
|
109
|
+
{/* Type Stats */}
|
|
110
|
+
<div>
|
|
111
|
+
<h2 className="text-lg font-semibold text-slate-900 mb-4">Entity Overview</h2>
|
|
112
|
+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
113
|
+
{typeStats.map((stat) => {
|
|
114
|
+
const Icon = stat.icon
|
|
115
|
+
return (
|
|
116
|
+
<div
|
|
117
|
+
key={stat.slug}
|
|
118
|
+
className="rounded-xl border border-slate-200 bg-white p-5 hover:shadow-md transition-shadow cursor-pointer"
|
|
119
|
+
onClick={() => handleViewItems(stat.slug)}
|
|
120
|
+
>
|
|
121
|
+
<div className="flex items-center gap-4">
|
|
122
|
+
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-blue-50 text-blue-600">
|
|
123
|
+
<Icon className="h-5 w-5" />
|
|
124
|
+
</div>
|
|
125
|
+
<div>
|
|
126
|
+
<p className="text-sm text-slate-500">{stat.name}</p>
|
|
127
|
+
<p className="text-xl font-semibold text-slate-900">{stat.count.toLocaleString()}</p>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
)
|
|
132
|
+
})}
|
|
133
|
+
|
|
134
|
+
{/* Quick Actions Card */}
|
|
135
|
+
<div
|
|
136
|
+
className="rounded-xl border border-slate-200 bg-white p-5 hover:shadow-md transition-shadow cursor-pointer"
|
|
137
|
+
onClick={() => navigate('/spine-framework/admin/configs/types')}
|
|
138
|
+
>
|
|
139
|
+
<div className="flex items-center gap-4">
|
|
140
|
+
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-green-50 text-green-600">
|
|
141
|
+
<Settings className="h-5 w-5" />
|
|
142
|
+
</div>
|
|
143
|
+
<div>
|
|
144
|
+
<p className="text-sm text-slate-500">Configuration</p>
|
|
145
|
+
<p className="text-xl font-semibold text-slate-900">Manage</p>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
{/* Quick Actions */}
|
|
153
|
+
<div className="rounded-xl border border-slate-200 bg-white p-6">
|
|
154
|
+
<h3 className="text-sm font-semibold text-slate-900 mb-4">
|
|
155
|
+
Quick Actions
|
|
156
|
+
</h3>
|
|
157
|
+
|
|
158
|
+
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
|
159
|
+
{typeStats.slice(0, 4).map((stat) => (
|
|
160
|
+
<button
|
|
161
|
+
key={`create-${stat.slug}`}
|
|
162
|
+
onClick={() => handleCreateNewItem(stat.slug)}
|
|
163
|
+
className="flex flex-col items-center gap-2 rounded-lg border border-slate-200 p-5 text-slate-600 hover:border-blue-200 hover:bg-blue-50 hover:text-blue-700 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
164
|
+
>
|
|
165
|
+
<Plus className="h-6 w-6" />
|
|
166
|
+
<span className="text-sm font-medium">New {stat.name}</span>
|
|
167
|
+
</button>
|
|
168
|
+
))}
|
|
169
|
+
|
|
170
|
+
<button
|
|
171
|
+
onClick={() => navigate('/spine-framework/admin/configs/types')}
|
|
172
|
+
className="flex flex-col items-center gap-2 rounded-lg border border-slate-200 p-5 text-slate-600 hover:border-blue-200 hover:bg-blue-50 hover:text-blue-700 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
173
|
+
>
|
|
174
|
+
<Settings className="h-6 w-6" />
|
|
175
|
+
<span className="text-sm font-medium">Configure Types</span>
|
|
176
|
+
</button>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module src/pages/NotFoundPage
|
|
3
|
+
* @audience installer
|
|
4
|
+
* @layer frontend-page
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* Generic 404 page rendered by the catch-all route. Displays a large
|
|
8
|
+
* "404" heading and a "Go back home" link to `/dashboard`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React from 'react'
|
|
12
|
+
import { Link } from 'react-router-dom'
|
|
13
|
+
import { Home } from 'lucide-react';
|
|
14
|
+
|
|
15
|
+
export function NotFoundPage() {
|
|
16
|
+
return (
|
|
17
|
+
<div className="min-h-screen bg-slate-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
|
18
|
+
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
|
19
|
+
<div className="text-center">
|
|
20
|
+
<h1 className="text-9xl font-bold text-slate-900">404</h1>
|
|
21
|
+
<h2 className="mt-4 text-3xl font-bold text-slate-900">Page not found</h2>
|
|
22
|
+
<p className="mt-2 text-sm text-slate-600">
|
|
23
|
+
Sorry, we couldn't find the page you're looking for.
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<div className="mt-6">
|
|
27
|
+
<Link
|
|
28
|
+
to="/dashboard"
|
|
29
|
+
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
30
|
+
>
|
|
31
|
+
<Home className="w-4 h-4 mr-2" />
|
|
32
|
+
Go back home
|
|
33
|
+
</Link>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
)
|
|
39
|
+
}
|