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,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module src/components/ui/ItemCard
|
|
3
|
+
* @audience installer
|
|
4
|
+
* @layer frontend-component
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* Item display components for grid-based views.
|
|
8
|
+
*
|
|
9
|
+
* **`ItemCard`** — renders a single `Item` in either full (`compact=false`)
|
|
10
|
+
* or compact (`compact=true`) form. Derives its display values from
|
|
11
|
+
* `item.title`, `item.description`, and the first 3 entries of `item.data`.
|
|
12
|
+
* Optional action buttons (`onView`, `onEdit`, `onDelete`) are shown when
|
|
13
|
+
* `showActions=true`.
|
|
14
|
+
*
|
|
15
|
+
* **`ItemGrid`** — renders a responsive grid of `ItemCard` instances.
|
|
16
|
+
* Shows a spinner on `loading=true` and an empty state with a doc icon
|
|
17
|
+
* when `items` is empty.
|
|
18
|
+
*
|
|
19
|
+
* @seeAlso src/components/ui/ItemListView.tsx (combines with DataTable)
|
|
20
|
+
* @seeAlso src/types/types.ts (Item, ItemType)
|
|
21
|
+
* @seeAlso src/lib/utils.ts (formatDateTime, truncateText)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import React from 'react'
|
|
25
|
+
import { Item, ItemType } from '../../types/types'
|
|
26
|
+
import { Badge } from './badge'
|
|
27
|
+
import { Button } from './button'
|
|
28
|
+
import { Box, FileText, Calendar, User, MoreVertical } from 'lucide-react';
|
|
29
|
+
import { formatDateTime, truncateText } from '../../lib/utils'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Props for `ItemCard`.
|
|
33
|
+
*
|
|
34
|
+
* @prop item - `Item` with optional resolved `item_type` relation
|
|
35
|
+
* @prop onEdit - Edit action callback
|
|
36
|
+
* @prop onDelete - Delete action callback
|
|
37
|
+
* @prop onView - View action callback
|
|
38
|
+
* @prop showActions - Shows action buttons (default: `true`)
|
|
39
|
+
* @prop compact - Condensed single-line layout (default: `false`)
|
|
40
|
+
*/
|
|
41
|
+
interface ItemCardProps {
|
|
42
|
+
item: Item & { item_type?: ItemType | string }
|
|
43
|
+
onEdit?: (item: Item) => void
|
|
44
|
+
onDelete?: (item: Item) => void
|
|
45
|
+
onView?: (item: Item) => void
|
|
46
|
+
showActions?: boolean
|
|
47
|
+
compact?: boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Item card with optional action buttons.
|
|
52
|
+
*
|
|
53
|
+
* @param props - `ItemCardProps`
|
|
54
|
+
* @returns White rounded card in full or compact layout
|
|
55
|
+
* @sideEffects none (delegates actions to `onEdit` / `onDelete` / `onView`)
|
|
56
|
+
*/
|
|
57
|
+
export function ItemCard({
|
|
58
|
+
item,
|
|
59
|
+
onEdit,
|
|
60
|
+
onDelete,
|
|
61
|
+
onView,
|
|
62
|
+
showActions = true,
|
|
63
|
+
compact = false
|
|
64
|
+
}: ItemCardProps) {
|
|
65
|
+
const getPrimaryValue = () => item.title || 'Untitled Item'
|
|
66
|
+
|
|
67
|
+
const getSecondaryValue = () => {
|
|
68
|
+
if (item.description) return truncateText(item.description, 100)
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const getTypeIcon = () => {
|
|
73
|
+
return <Box className="h-5 w-5 text-blue-500" />
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const getTypeName = () => {
|
|
77
|
+
if (!item.item_type) return item.item_type_slug || ''
|
|
78
|
+
if (typeof item.item_type === 'string') return item.item_type
|
|
79
|
+
return (item.item_type as ItemType).name || ''
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const getTypeBadgeColor = () => {
|
|
83
|
+
if (!item.item_type || typeof item.item_type === 'string') return 'info'
|
|
84
|
+
return (item.item_type as ItemType).is_active ? 'success' : 'default'
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (compact) {
|
|
88
|
+
return (
|
|
89
|
+
<div className="bg-white border border-slate-200 rounded-lg p-4 hover:shadow-md transition-shadow">
|
|
90
|
+
<div className="flex items-start justify-between">
|
|
91
|
+
<div className="flex items-start space-x-3 flex-1">
|
|
92
|
+
<div className="flex-shrink-0">
|
|
93
|
+
{getTypeIcon()}
|
|
94
|
+
</div>
|
|
95
|
+
<div className="flex-1 min-w-0">
|
|
96
|
+
<div className="flex items-center space-x-2">
|
|
97
|
+
<h3 className="text-sm font-medium text-slate-900 truncate">
|
|
98
|
+
{getPrimaryValue()}
|
|
99
|
+
</h3>
|
|
100
|
+
<Badge variant={getTypeBadgeColor()}>
|
|
101
|
+
{getTypeName()}
|
|
102
|
+
</Badge>
|
|
103
|
+
</div>
|
|
104
|
+
{getSecondaryValue() && (
|
|
105
|
+
<p className="text-xs text-slate-500 mt-1">
|
|
106
|
+
{getSecondaryValue()}
|
|
107
|
+
</p>
|
|
108
|
+
)}
|
|
109
|
+
<div className="flex items-center space-x-4 mt-2 text-xs text-slate-500">
|
|
110
|
+
<span>Updated {formatDateTime(item.updated_at)}</span>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{showActions && (
|
|
116
|
+
<div className="flex items-center space-x-1">
|
|
117
|
+
{onView && (
|
|
118
|
+
<Button
|
|
119
|
+
variant="ghost"
|
|
120
|
+
size="sm"
|
|
121
|
+
onClick={() => onView(item)}
|
|
122
|
+
>
|
|
123
|
+
View
|
|
124
|
+
</Button>
|
|
125
|
+
)}
|
|
126
|
+
<Button
|
|
127
|
+
variant="ghost"
|
|
128
|
+
size="sm"
|
|
129
|
+
>
|
|
130
|
+
<MoreVertical className="h-4 w-4" />
|
|
131
|
+
</Button>
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div className="bg-white border border-slate-200 rounded-lg p-6 hover:shadow-md transition-shadow">
|
|
141
|
+
<div className="flex items-start justify-between">
|
|
142
|
+
<div className="flex items-start space-x-4 flex-1">
|
|
143
|
+
<div className="flex-shrink-0">
|
|
144
|
+
{getTypeIcon()}
|
|
145
|
+
</div>
|
|
146
|
+
<div className="flex-1 min-w-0">
|
|
147
|
+
<div className="flex items-center space-x-2 mb-2">
|
|
148
|
+
<h3 className="text-lg font-medium text-slate-900 truncate">
|
|
149
|
+
{getPrimaryValue()}
|
|
150
|
+
</h3>
|
|
151
|
+
<Badge variant={getTypeBadgeColor()}>
|
|
152
|
+
{getTypeName()}
|
|
153
|
+
</Badge>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{getSecondaryValue() && (
|
|
157
|
+
<p className="text-sm text-slate-600 mb-3">
|
|
158
|
+
{getSecondaryValue()}
|
|
159
|
+
</p>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
{/* Additional data fields */}
|
|
163
|
+
{Object.keys(item.data || {}).length > 0 && (
|
|
164
|
+
<div className="space-y-2 mb-4">
|
|
165
|
+
{Object.entries(item.data)
|
|
166
|
+
.filter(([, v]) => v !== undefined && v !== null && v !== '')
|
|
167
|
+
.slice(0, 3)
|
|
168
|
+
.map(([key, value]) => (
|
|
169
|
+
<div key={key} className="flex items-center space-x-2">
|
|
170
|
+
<span className="text-xs font-medium text-slate-500">{key}:</span>
|
|
171
|
+
<span className="text-xs text-slate-900">
|
|
172
|
+
{truncateText(typeof value === 'boolean' ? (value ? 'Yes' : 'No') : String(value), 50)}
|
|
173
|
+
</span>
|
|
174
|
+
</div>
|
|
175
|
+
))}
|
|
176
|
+
</div>
|
|
177
|
+
)}
|
|
178
|
+
|
|
179
|
+
{/* Metadata */}
|
|
180
|
+
<div className="flex items-center justify-between text-xs text-slate-500">
|
|
181
|
+
<div className="flex items-center space-x-4">
|
|
182
|
+
<span className="flex items-center">
|
|
183
|
+
<Calendar className="h-3 w-3 mr-1" />
|
|
184
|
+
Created {formatDateTime(item.created_at)}
|
|
185
|
+
</span>
|
|
186
|
+
<span className="flex items-center">
|
|
187
|
+
<User className="h-3 w-3 mr-1" />
|
|
188
|
+
ID: {item.id.slice(0, 8)}...
|
|
189
|
+
</span>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
{showActions && (
|
|
196
|
+
<div className="flex items-center space-x-2 ml-4">
|
|
197
|
+
{onView && (
|
|
198
|
+
<Button
|
|
199
|
+
variant="outline"
|
|
200
|
+
size="sm"
|
|
201
|
+
onClick={() => onView(item)}
|
|
202
|
+
>
|
|
203
|
+
View
|
|
204
|
+
</Button>
|
|
205
|
+
)}
|
|
206
|
+
{onEdit && (
|
|
207
|
+
<Button
|
|
208
|
+
variant="outline"
|
|
209
|
+
size="sm"
|
|
210
|
+
onClick={() => onEdit(item)}
|
|
211
|
+
>
|
|
212
|
+
Edit
|
|
213
|
+
</Button>
|
|
214
|
+
)}
|
|
215
|
+
<Button
|
|
216
|
+
variant="ghost"
|
|
217
|
+
size="sm"
|
|
218
|
+
>
|
|
219
|
+
<MoreVertical className="h-4 w-4" />
|
|
220
|
+
</Button>
|
|
221
|
+
</div>
|
|
222
|
+
)}
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Props for `ItemGrid`.
|
|
230
|
+
*
|
|
231
|
+
* @prop items - Typed item array with resolved `item_type`
|
|
232
|
+
* @prop loading - Shows spinner while true
|
|
233
|
+
* @prop compact - Passes compact mode to each `ItemCard`
|
|
234
|
+
* @prop emptyMessage - Text shown when `items` is empty
|
|
235
|
+
*/
|
|
236
|
+
interface ItemGridProps {
|
|
237
|
+
items: (Item & { item_type: ItemType })[]
|
|
238
|
+
loading?: boolean
|
|
239
|
+
onEdit?: (item: Item) => void
|
|
240
|
+
onDelete?: (item: Item) => void
|
|
241
|
+
onView?: (item: Item) => void
|
|
242
|
+
compact?: boolean
|
|
243
|
+
emptyMessage?: string
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Responsive card grid for items.
|
|
248
|
+
*
|
|
249
|
+
* @param props - `ItemGridProps`
|
|
250
|
+
* @returns Responsive grid, loading spinner, or empty state
|
|
251
|
+
* @sideEffects none (delegates actions to `onEdit` / `onDelete` / `onView`)
|
|
252
|
+
*/
|
|
253
|
+
export function ItemGrid({
|
|
254
|
+
items,
|
|
255
|
+
loading = false,
|
|
256
|
+
onEdit,
|
|
257
|
+
onDelete,
|
|
258
|
+
onView,
|
|
259
|
+
compact = false,
|
|
260
|
+
emptyMessage = 'No items found'
|
|
261
|
+
}: ItemGridProps) {
|
|
262
|
+
if (loading) {
|
|
263
|
+
return (
|
|
264
|
+
<div className="flex justify-center py-8">
|
|
265
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
|
266
|
+
</div>
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (items.length === 0) {
|
|
271
|
+
return (
|
|
272
|
+
<div className="text-center py-8">
|
|
273
|
+
<FileText className="mx-auto h-12 w-12 text-slate-400" />
|
|
274
|
+
<h3 className="mt-2 text-sm font-medium text-slate-900">{emptyMessage}</h3>
|
|
275
|
+
</div>
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<div className={compact
|
|
281
|
+
? "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
|
|
282
|
+
: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
|
|
283
|
+
}>
|
|
284
|
+
{items.map((item) => (
|
|
285
|
+
<ItemCard
|
|
286
|
+
key={item.id}
|
|
287
|
+
item={item}
|
|
288
|
+
onEdit={onEdit}
|
|
289
|
+
onDelete={onDelete}
|
|
290
|
+
onView={onView}
|
|
291
|
+
compact={compact}
|
|
292
|
+
/>
|
|
293
|
+
))}
|
|
294
|
+
</div>
|
|
295
|
+
)
|
|
296
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module src/components/ui/ItemListView
|
|
3
|
+
* @audience installer
|
|
4
|
+
* @layer frontend-component
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* Switchable item view widget and page shell.
|
|
8
|
+
*
|
|
9
|
+
* **`ItemListView`** — renders items in either a compact `ItemGrid` or a
|
|
10
|
+
* full `DataTable` view, toggled by icon buttons. Derives unique type
|
|
11
|
+
* filter options from the items array and builds a fixed table column
|
|
12
|
+
* set. The primary display field is resolved from the first `text` or
|
|
13
|
+
* `textarea` field in the item type's `design_schema`.
|
|
14
|
+
*
|
|
15
|
+
* **`ItemListPage`** — wraps `ItemListView` with a page-level title and
|
|
16
|
+
* description header. Use as a standalone page component.
|
|
17
|
+
*
|
|
18
|
+
* @seeAlso src/components/ui/ItemCard.tsx
|
|
19
|
+
* @seeAlso src/components/ui/DataTable.tsx
|
|
20
|
+
* @seeAlso src/types/types.ts (Item, ItemType)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import React, { useState } from 'react'
|
|
24
|
+
import { Item, ItemType } from '../../types/types'
|
|
25
|
+
import { DataTable } from './DataTable'
|
|
26
|
+
import { ItemCard, ItemGrid } from './ItemCard'
|
|
27
|
+
import { Badge } from './badge'
|
|
28
|
+
import { Button } from './button'
|
|
29
|
+
import { Squares2X2Icon, List, Plus } from 'lucide-react';
|
|
30
|
+
import { formatDateTime } from '../../lib/utils'
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Props for `ItemListView`.
|
|
34
|
+
*
|
|
35
|
+
* @prop items - Typed item array with resolved `item_type`
|
|
36
|
+
* @prop loading - Shows spinner while fetching
|
|
37
|
+
* @prop onEdit / onDelete / onView - Row/card action callbacks
|
|
38
|
+
* @prop onCreate - Create button callback
|
|
39
|
+
* @prop searchable - Enables search in table view (default: `true`)
|
|
40
|
+
* @prop filterable - Enables column filters in table view (default: `true`)
|
|
41
|
+
* @prop emptyMessage - Empty-state label
|
|
42
|
+
* @prop showCreateButton - Shows the create button (default: `true`)
|
|
43
|
+
* @prop createButtonText - Create button label (default: `'Create Item'`)
|
|
44
|
+
*/
|
|
45
|
+
interface ItemListViewProps {
|
|
46
|
+
items: (Item & { item_type: ItemType })[]
|
|
47
|
+
loading?: boolean
|
|
48
|
+
onEdit?: (item: Item) => void
|
|
49
|
+
onDelete?: (item: Item) => void
|
|
50
|
+
onView?: (item: Item) => void
|
|
51
|
+
onCreate?: () => void
|
|
52
|
+
searchable?: boolean
|
|
53
|
+
filterable?: boolean
|
|
54
|
+
emptyMessage?: string
|
|
55
|
+
showCreateButton?: boolean
|
|
56
|
+
createButtonText?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Switchable grid/table item view.
|
|
61
|
+
*
|
|
62
|
+
* @param props - `ItemListViewProps`
|
|
63
|
+
* @returns Header with view-mode toggle + `ItemGrid` or `DataTable`
|
|
64
|
+
* @sideEffects none (delegates actions and creation to prop callbacks)
|
|
65
|
+
*/
|
|
66
|
+
export function ItemListView({
|
|
67
|
+
items,
|
|
68
|
+
loading = false,
|
|
69
|
+
onEdit,
|
|
70
|
+
onDelete,
|
|
71
|
+
onView,
|
|
72
|
+
onCreate,
|
|
73
|
+
searchable = true,
|
|
74
|
+
filterable = true,
|
|
75
|
+
emptyMessage = 'No items found',
|
|
76
|
+
showCreateButton = true,
|
|
77
|
+
createButtonText = 'Create Item'
|
|
78
|
+
}: ItemListViewProps) {
|
|
79
|
+
const [viewMode, setViewMode] = useState<'grid' | 'table'>('grid')
|
|
80
|
+
|
|
81
|
+
// Get unique types for filtering
|
|
82
|
+
const typesMap = new Map<string, ItemType>()
|
|
83
|
+
items.forEach(item => {
|
|
84
|
+
typesMap.set(item.item_type.id, item.item_type)
|
|
85
|
+
})
|
|
86
|
+
const types = Array.from(typesMap.values()).map(type => ({
|
|
87
|
+
value: type.id,
|
|
88
|
+
label: type.name
|
|
89
|
+
}))
|
|
90
|
+
|
|
91
|
+
// Table columns
|
|
92
|
+
const tableColumns = [
|
|
93
|
+
{
|
|
94
|
+
key: 'data' as const,
|
|
95
|
+
title: 'Item',
|
|
96
|
+
sortable: true,
|
|
97
|
+
render: (value: Record<string, any>, item: Item & { item_type: ItemType }) => {
|
|
98
|
+
const primaryField = item.item_type.design_schema?.fields ? Object.entries(item.item_type.design_schema.fields).find(([_, f]: [string, any]) =>
|
|
99
|
+
f.data_type === 'text' || f.data_type === 'textarea'
|
|
100
|
+
) : null
|
|
101
|
+
const fieldEntry = primaryField ? primaryField : null
|
|
102
|
+
const fieldName = fieldEntry ? fieldEntry[0] : 'name'
|
|
103
|
+
const displayValue = value[fieldName] || 'Untitled Item'
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div>
|
|
107
|
+
<div className="font-medium text-slate-900">{displayValue}</div>
|
|
108
|
+
<div className="text-sm text-slate-500">
|
|
109
|
+
<Badge variant={item.item_type.is_active ? 'success' : 'default'}>
|
|
110
|
+
{item.item_type.name}
|
|
111
|
+
</Badge>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
key: 'item_type' as const,
|
|
119
|
+
title: 'Type',
|
|
120
|
+
filterable: true,
|
|
121
|
+
filterOptions: types,
|
|
122
|
+
render: (value: ItemType) => (
|
|
123
|
+
<Badge variant={value.is_active ? 'success' : 'default'}>
|
|
124
|
+
{value.name}
|
|
125
|
+
</Badge>
|
|
126
|
+
)
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
key: 'created_at' as const,
|
|
130
|
+
title: 'Created',
|
|
131
|
+
sortable: true,
|
|
132
|
+
render: (value: string) => formatDateTime(value)
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
key: 'updated_at' as const,
|
|
136
|
+
title: 'Updated',
|
|
137
|
+
sortable: true,
|
|
138
|
+
render: (value: string) => formatDateTime(value)
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
key: 'id' as const,
|
|
142
|
+
title: 'Actions',
|
|
143
|
+
render: (_: any, item: Item & { item_type: ItemType }) => (
|
|
144
|
+
<div className="flex items-center space-x-2">
|
|
145
|
+
{onView && (
|
|
146
|
+
<Button
|
|
147
|
+
variant="outline"
|
|
148
|
+
size="sm"
|
|
149
|
+
onClick={() => onView(item)}
|
|
150
|
+
>
|
|
151
|
+
View
|
|
152
|
+
</Button>
|
|
153
|
+
)}
|
|
154
|
+
{onEdit && (
|
|
155
|
+
<Button
|
|
156
|
+
variant="outline"
|
|
157
|
+
size="sm"
|
|
158
|
+
onClick={() => onEdit(item)}
|
|
159
|
+
>
|
|
160
|
+
Edit
|
|
161
|
+
</Button>
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<div className="space-y-4">
|
|
170
|
+
{/* Header */}
|
|
171
|
+
<div className="flex items-center justify-between">
|
|
172
|
+
<div className="flex items-center space-x-4">
|
|
173
|
+
<h2 className="text-lg font-medium text-slate-900">
|
|
174
|
+
Items ({items.length})
|
|
175
|
+
</h2>
|
|
176
|
+
|
|
177
|
+
{/* View Mode Toggle */}
|
|
178
|
+
<div className="flex items-center bg-slate-100 rounded-lg p-1">
|
|
179
|
+
<button
|
|
180
|
+
onClick={() => setViewMode('grid')}
|
|
181
|
+
className={`
|
|
182
|
+
px-3 py-1.5 rounded-md text-sm font-medium transition-colors
|
|
183
|
+
${viewMode === 'grid'
|
|
184
|
+
? 'bg-white text-slate-900 shadow-sm'
|
|
185
|
+
: 'text-slate-600 hover:text-slate-900'
|
|
186
|
+
}
|
|
187
|
+
`}
|
|
188
|
+
>
|
|
189
|
+
<Squares2X2Icon className="h-4 w-4" />
|
|
190
|
+
</button>
|
|
191
|
+
<button
|
|
192
|
+
onClick={() => setViewMode('table')}
|
|
193
|
+
className={`
|
|
194
|
+
px-3 py-1.5 rounded-md text-sm font-medium transition-colors
|
|
195
|
+
${viewMode === 'table'
|
|
196
|
+
? 'bg-white text-slate-900 shadow-sm'
|
|
197
|
+
: 'text-slate-600 hover:text-slate-900'
|
|
198
|
+
}
|
|
199
|
+
`}
|
|
200
|
+
>
|
|
201
|
+
<List className="h-4 w-4" />
|
|
202
|
+
</button>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
{showCreateButton && onCreate && (
|
|
207
|
+
<Button onClick={onCreate}>
|
|
208
|
+
<Plus className="h-4 w-4 mr-2" />
|
|
209
|
+
{createButtonText}
|
|
210
|
+
</Button>
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{/* Content */}
|
|
215
|
+
{viewMode === 'grid' ? (
|
|
216
|
+
<ItemGrid
|
|
217
|
+
items={items}
|
|
218
|
+
loading={loading}
|
|
219
|
+
onEdit={onEdit}
|
|
220
|
+
onDelete={onDelete}
|
|
221
|
+
onView={onView}
|
|
222
|
+
compact={true}
|
|
223
|
+
emptyMessage={emptyMessage}
|
|
224
|
+
/>
|
|
225
|
+
) : (
|
|
226
|
+
<DataTable
|
|
227
|
+
data={items}
|
|
228
|
+
columns={tableColumns as any}
|
|
229
|
+
loading={loading}
|
|
230
|
+
searchable={searchable}
|
|
231
|
+
filterable={filterable}
|
|
232
|
+
emptyMessage={emptyMessage}
|
|
233
|
+
/>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Props for `ItemListPage`.
|
|
241
|
+
*
|
|
242
|
+
* @prop title - Page heading (default: `'Items'`)
|
|
243
|
+
* @prop description - Page subtitle (default: `'Manage and organize your items'`)
|
|
244
|
+
* (other props same as `ItemListViewProps`)
|
|
245
|
+
*/
|
|
246
|
+
interface ItemListPageProps {
|
|
247
|
+
items: (Item & { item_type: ItemType })[]
|
|
248
|
+
loading?: boolean
|
|
249
|
+
onEdit?: (item: Item) => void
|
|
250
|
+
onDelete?: (item: Item) => void
|
|
251
|
+
onView?: (item: Item) => void
|
|
252
|
+
onCreate?: () => void
|
|
253
|
+
searchable?: boolean
|
|
254
|
+
filterable?: boolean
|
|
255
|
+
emptyMessage?: string
|
|
256
|
+
showCreateButton?: boolean
|
|
257
|
+
createButtonText?: string
|
|
258
|
+
title?: string
|
|
259
|
+
description?: string
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Full item list page with title header and switchable view.
|
|
264
|
+
*
|
|
265
|
+
* @param props - `ItemListPageProps`
|
|
266
|
+
* @returns Page header + `ItemListView`
|
|
267
|
+
* @sideEffects none (pure rendering)
|
|
268
|
+
*/
|
|
269
|
+
export function ItemListPage({
|
|
270
|
+
items,
|
|
271
|
+
loading = false,
|
|
272
|
+
onEdit,
|
|
273
|
+
onDelete,
|
|
274
|
+
onView,
|
|
275
|
+
onCreate,
|
|
276
|
+
searchable = true,
|
|
277
|
+
filterable = true,
|
|
278
|
+
emptyMessage = 'No items found',
|
|
279
|
+
showCreateButton = true,
|
|
280
|
+
createButtonText = 'Create Item',
|
|
281
|
+
title = 'Items',
|
|
282
|
+
description = 'Manage and organize your items'
|
|
283
|
+
}: ItemListPageProps) {
|
|
284
|
+
return (
|
|
285
|
+
<div className="space-y-6">
|
|
286
|
+
{/* Page Header */}
|
|
287
|
+
<div>
|
|
288
|
+
<h1 className="text-2xl font-bold text-slate-900">{title}</h1>
|
|
289
|
+
<p className="mt-1 text-sm text-slate-600">{description}</p>
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
{/* Item List View */}
|
|
293
|
+
<ItemListView
|
|
294
|
+
items={items}
|
|
295
|
+
loading={loading}
|
|
296
|
+
onEdit={onEdit}
|
|
297
|
+
onDelete={onDelete}
|
|
298
|
+
onView={onView}
|
|
299
|
+
onCreate={onCreate}
|
|
300
|
+
searchable={searchable}
|
|
301
|
+
filterable={filterable}
|
|
302
|
+
emptyMessage={emptyMessage}
|
|
303
|
+
showCreateButton={showCreateButton}
|
|
304
|
+
createButtonText={createButtonText}
|
|
305
|
+
/>
|
|
306
|
+
</div>
|
|
307
|
+
)
|
|
308
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module src/components/ui/LoadingSpinner
|
|
3
|
+
* @audience installer
|
|
4
|
+
* @layer frontend-component
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* Animated CSS spinner used to indicate loading state inline or as a
|
|
8
|
+
* centred overlay. Uses `border-t-blue-600` as the active segment colour
|
|
9
|
+
* and `border-slate-200` for the track.
|
|
10
|
+
*
|
|
11
|
+
* @seeAlso src/lib/utils.ts (cn)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import React from 'react'
|
|
15
|
+
import { Loader2 } from 'lucide-react'
|
|
16
|
+
import { cn } from '../../lib/utils'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Props for `LoadingSpinner`.
|
|
20
|
+
*
|
|
21
|
+
* @prop size - Diameter: `'sm'` (16px), `'md'` (24px), `'lg'` (32px)
|
|
22
|
+
* @prop className - Additional Tailwind classes
|
|
23
|
+
*/
|
|
24
|
+
interface LoadingSpinnerProps {
|
|
25
|
+
className?: string
|
|
26
|
+
size?: 'sm' | 'md' | 'lg'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Inline animated spinner.
|
|
31
|
+
*
|
|
32
|
+
* @param props - `LoadingSpinnerProps`
|
|
33
|
+
* @returns `<Loader2>` styled as a spinning ring
|
|
34
|
+
* @sideEffects none (pure rendering)
|
|
35
|
+
*/
|
|
36
|
+
export function LoadingSpinner({ className, size = 'md' }: LoadingSpinnerProps) {
|
|
37
|
+
const sizeClasses = {
|
|
38
|
+
sm: 'h-4 w-4',
|
|
39
|
+
md: 'h-6 w-6',
|
|
40
|
+
lg: 'h-8 w-8',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Loader2
|
|
45
|
+
className={cn(
|
|
46
|
+
'animate-spin',
|
|
47
|
+
sizeClasses[size],
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
}
|