insforge 0.3.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/.dockerignore +58 -0
- package/.env.example +49 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +79 -0
- package/.github/copilot-instructions.md +147 -0
- package/.github/workflows/build-image.yml +65 -0
- package/.github/workflows/ci-premerge-check.yml +24 -0
- package/.github/workflows/deploy-aws.yml +130 -0
- package/.github/workflows/lint-and-format.yml +33 -0
- package/.prettierignore +65 -0
- package/.prettierrc +9 -0
- package/CHANGELOG.md +3 -0
- package/CONTRIBUTING.md +126 -0
- package/Dockerfile +27 -0
- package/GITHUB_OAUTH_SETUP.md +49 -0
- package/GOOGLE_OAUTH_SETUP.md +148 -0
- package/LICENSE +201 -0
- package/README.md +134 -0
- package/assets/Dark.svg +23 -0
- package/assets/archDiagram.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/mcpInstallv2.png +0 -0
- package/assets/sampleResponse.png +0 -0
- package/assets/signin.png +0 -0
- package/assets/userflow.png +0 -0
- package/backend/migrations/000_create-base-tables.sql +142 -0
- package/backend/migrations/001_create-helper-functions.sql +41 -0
- package/backend/migrations/002_rename-auth-tables.sql +30 -0
- package/backend/migrations/003_create-users-table.sql +56 -0
- package/backend/migrations/004_add-reload-postgrest-func.sql +24 -0
- package/backend/migrations/005_enable-project-admin-modify-users.sql +30 -0
- package/backend/migrations/006_modify-ai-usage-table.sql +25 -0
- package/backend/migrations/007_drop-metadata-table.sql +2 -0
- package/backend/migrations/008_add-system-tables.sql +77 -0
- package/backend/migrations/009_add-function-secrets.sql +24 -0
- package/backend/migrations/010_modify-ai-config-modalities.sql +93 -0
- package/backend/migrations/011_refactor-secrets-table.sql +15 -0
- package/backend/migrations/012_add-storage-uploaded-by.sql +8 -0
- package/backend/package.json +75 -0
- package/backend/src/api/middleware/auth.ts +240 -0
- package/backend/src/api/middleware/error.ts +231 -0
- package/backend/src/api/middleware/upload.ts +59 -0
- package/backend/src/api/routes/agent.ts +29 -0
- package/backend/src/api/routes/ai.ts +472 -0
- package/backend/src/api/routes/auth.oauth.ts +482 -0
- package/backend/src/api/routes/auth.ts +386 -0
- package/backend/src/api/routes/database.advance.ts +275 -0
- package/backend/src/api/routes/database.records.ts +246 -0
- package/backend/src/api/routes/database.tables.ts +161 -0
- package/backend/src/api/routes/docs.ts +66 -0
- package/backend/src/api/routes/functions.ts +183 -0
- package/backend/src/api/routes/logs.ts +150 -0
- package/backend/src/api/routes/metadata.ts +160 -0
- package/backend/src/api/routes/openapi.ts +82 -0
- package/backend/src/api/routes/secrets.ts +199 -0
- package/backend/src/api/routes/storage.ts +547 -0
- package/backend/src/api/routes/usage.ts +96 -0
- package/backend/src/core/ai/chat.ts +207 -0
- package/backend/src/core/ai/client.ts +242 -0
- package/backend/src/core/ai/config.ts +187 -0
- package/backend/src/core/ai/image.ts +156 -0
- package/backend/src/core/ai/model.ts +117 -0
- package/backend/src/core/ai/usage.ts +290 -0
- package/backend/src/core/auth/auth.ts +781 -0
- package/backend/src/core/auth/oauth.ts +398 -0
- package/backend/src/core/database/advance.ts +1074 -0
- package/backend/src/core/database/manager.ts +178 -0
- package/backend/src/core/database/table.ts +772 -0
- package/backend/src/core/documentation/agent.ts +689 -0
- package/backend/src/core/documentation/openapi.ts +856 -0
- package/backend/src/core/functions/functions.ts +310 -0
- package/backend/src/core/logs/analytics.ts +76 -0
- package/backend/src/core/logs/audit.ts +255 -0
- package/backend/src/core/logs/providers/base.provider.ts +83 -0
- package/backend/src/core/logs/providers/cloudwatch.provider.ts +510 -0
- package/backend/src/core/logs/providers/localdb.provider.ts +246 -0
- package/backend/src/core/secrets/encryption.ts +58 -0
- package/backend/src/core/secrets/secrets.ts +410 -0
- package/backend/src/core/socket/socket.ts +388 -0
- package/backend/src/core/socket/types.ts +79 -0
- package/backend/src/core/storage/storage.ts +923 -0
- package/backend/src/server.ts +288 -0
- package/backend/src/types/ai.ts +46 -0
- package/backend/src/types/auth.ts +90 -0
- package/backend/src/types/database.ts +136 -0
- package/backend/src/types/error-constants.ts +86 -0
- package/backend/src/types/logs.ts +47 -0
- package/backend/src/types/profile.ts +55 -0
- package/backend/src/types/storage.ts +23 -0
- package/backend/src/utils/cloud-token.ts +39 -0
- package/backend/src/utils/constants.ts +1 -0
- package/backend/src/utils/environment.ts +35 -0
- package/backend/src/utils/helpers.ts +49 -0
- package/backend/src/utils/logger.ts +13 -0
- package/backend/src/utils/response.ts +62 -0
- package/backend/src/utils/seed.ts +205 -0
- package/backend/src/utils/sql-parser.ts +63 -0
- package/backend/src/utils/uuid.ts +9 -0
- package/backend/src/utils/validations.ts +129 -0
- package/backend/tests/README.md +134 -0
- package/backend/tests/cleanup-all-test-data.sh +231 -0
- package/backend/tests/cloud/test-s3-multitenant.sh +132 -0
- package/backend/tests/local/comprehensive-curl-tests.sh +156 -0
- package/backend/tests/local/test-auth-router.sh +144 -0
- package/backend/tests/local/test-database-router.sh +222 -0
- package/backend/tests/local/test-e2e.sh +241 -0
- package/backend/tests/local/test-fk-errors.sh +97 -0
- package/backend/tests/local/test-id-field.sh +201 -0
- package/backend/tests/local/test-public-bucket.sh +265 -0
- package/backend/tests/local/test-secrets.sh +248 -0
- package/backend/tests/local/test-serverless-functions.sh.disabled +325 -0
- package/backend/tests/local/test-traditional-rest.sh +209 -0
- package/backend/tests/manual/README.md +51 -0
- package/backend/tests/manual/create-large-table-simple.sql +11 -0
- package/backend/tests/manual/seed-large-table.sql +101 -0
- package/backend/tests/manual/setup-large-table-extras.sql +34 -0
- package/backend/tests/manual/test-better-auth.sh +303 -0
- package/backend/tests/manual/test-bulk-upsert.sh +410 -0
- package/backend/tests/manual/test-database-advance.sh +297 -0
- package/backend/tests/manual/test-postgrest-stability.sh +192 -0
- package/backend/tests/manual/test-rawsql-export-import.sh +412 -0
- package/backend/tests/manual/test-universal-storage.sh +264 -0
- package/backend/tests/manual/test-users.sql +18 -0
- package/backend/tests/run-all-tests.sh +140 -0
- package/backend/tests/setup.ts +22 -0
- package/backend/tests/test-config.sh +303 -0
- package/backend/tsconfig.json +23 -0
- package/backend/tsup.config.ts +18 -0
- package/backend/vitest.config.ts +22 -0
- package/docker-compose.prod.yml +145 -0
- package/docker-compose.yml +167 -0
- package/docker-init/db/db-init.sql +125 -0
- package/docker-init/db/jwt.sql +5 -0
- package/docker-init/db/logs.sql +9 -0
- package/docker-init/db/postgresql.conf +17 -0
- package/docs/deprecated/insforge-auth-api.md +215 -0
- package/docs/deprecated/insforge-auth-sdk.md +100 -0
- package/docs/deprecated/insforge-db-api.md +359 -0
- package/docs/deprecated/insforge-db-sdk.md +140 -0
- package/docs/deprecated/insforge-debug-sdk.md +157 -0
- package/docs/deprecated/insforge-debug.md +65 -0
- package/docs/deprecated/insforge-instructions.md +124 -0
- package/docs/deprecated/insforge-project.md +118 -0
- package/docs/deprecated/insforge-storage-api.md +279 -0
- package/docs/deprecated/insforge-storage-sdk.md +159 -0
- package/docs/insforge-instructions-sdk.md +407 -0
- package/eslint.config.js +317 -0
- package/examples/oauth/frontend-oauth-example.html +251 -0
- package/examples/response-examples.md +444 -0
- package/frontend/README.md +112 -0
- package/frontend/components.json +17 -0
- package/frontend/index.html +13 -0
- package/frontend/package.json +63 -0
- package/frontend/public/favicon.ico +0 -0
- package/frontend/src/App.tsx +106 -0
- package/frontend/src/assets/icons/checkbox_checked.svg +6 -0
- package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -0
- package/frontend/src/assets/icons/checked.svg +3 -0
- package/frontend/src/assets/icons/error.svg +3 -0
- package/frontend/src/assets/icons/pencil.svg +4 -0
- package/frontend/src/assets/icons/refresh.svg +4 -0
- package/frontend/src/assets/icons/step_active.svg +3 -0
- package/frontend/src/assets/icons/step_inactive.svg +11 -0
- package/frontend/src/assets/icons/warning.svg +3 -0
- package/frontend/src/assets/logos/amazon.svg +1 -0
- package/frontend/src/assets/logos/claude_code.svg +3 -0
- package/frontend/src/assets/logos/cline.svg +6 -0
- package/frontend/src/assets/logos/cursor.svg +20 -0
- package/frontend/src/assets/logos/discord.svg +9 -0
- package/frontend/src/assets/logos/gemini.svg +19 -0
- package/frontend/src/assets/logos/github.svg +5 -0
- package/frontend/src/assets/logos/google.svg +13 -0
- package/frontend/src/assets/logos/grok.svg +10 -0
- package/frontend/src/assets/logos/insforge_dark.svg +15 -0
- package/frontend/src/assets/logos/insforge_light.svg +15 -0
- package/frontend/src/assets/logos/openai.svg +10 -0
- package/frontend/src/assets/logos/roo_code.svg +9 -0
- package/frontend/src/assets/logos/trae.svg +3 -0
- package/frontend/src/assets/logos/windsurf.svg +10 -0
- package/frontend/src/components/ButtonWithLoading.tsx +27 -0
- package/frontend/src/components/Checkbox.tsx +61 -0
- package/frontend/src/components/CodeBlock.tsx +32 -0
- package/frontend/src/components/ConfirmDialog.tsx +96 -0
- package/frontend/src/components/CopyButton.tsx +69 -0
- package/frontend/src/components/DeleteActionButton.tsx +42 -0
- package/frontend/src/components/EmptyState.tsx +41 -0
- package/frontend/src/components/ErrorState.tsx +35 -0
- package/frontend/src/components/FeatureSidebar.tsx +126 -0
- package/frontend/src/components/FeatureSidebarItem.tsx +101 -0
- package/frontend/src/components/JsonHighlight.tsx +61 -0
- package/frontend/src/components/LoadingState.tsx +16 -0
- package/frontend/src/components/PaginationControls.tsx +54 -0
- package/frontend/src/components/PromptDialog.tsx +68 -0
- package/frontend/src/components/SearchInput.tsx +90 -0
- package/frontend/src/components/SelectionClearButton.tsx +26 -0
- package/frontend/src/components/Stepper.tsx +139 -0
- package/frontend/src/components/ThemeToggle.tsx +58 -0
- package/frontend/src/components/TypeBadge.tsx +20 -0
- package/frontend/src/components/datagrid/DataGrid.tsx +264 -0
- package/frontend/src/components/datagrid/DefaultCellRenderer.tsx +114 -0
- package/frontend/src/components/datagrid/IdCell.tsx +44 -0
- package/frontend/src/components/datagrid/SortableHeader.tsx +74 -0
- package/frontend/src/components/datagrid/cell-editors/BooleanCellEditor.tsx +54 -0
- package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +483 -0
- package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +362 -0
- package/frontend/src/components/datagrid/cell-editors/TextCellEditor.tsx +38 -0
- package/frontend/src/components/datagrid/cell-editors/index.ts +14 -0
- package/frontend/src/components/datagrid/cell-editors/types.ts +43 -0
- package/frontend/src/components/datagrid/datagridTypes.tsx +72 -0
- package/frontend/src/components/datagrid/index.tsx +20 -0
- package/frontend/src/components/index.ts +39 -0
- package/frontend/src/components/layout/AppHeader.tsx +146 -0
- package/frontend/src/components/layout/AppSidebar.tsx +190 -0
- package/frontend/src/components/layout/CloudLayout.tsx +95 -0
- package/frontend/src/components/layout/Layout.tsx +43 -0
- package/frontend/src/components/radix/Alert.tsx +45 -0
- package/frontend/src/components/radix/AlertDialog.tsx +115 -0
- package/frontend/src/components/radix/Avatar.tsx +45 -0
- package/frontend/src/components/radix/Badge.tsx +33 -0
- package/frontend/src/components/radix/Button.tsx +50 -0
- package/frontend/src/components/radix/Card.tsx +58 -0
- package/frontend/src/components/radix/Dialog.tsx +98 -0
- package/frontend/src/components/radix/DropdownMenu.tsx +185 -0
- package/frontend/src/components/radix/Form.tsx +167 -0
- package/frontend/src/components/radix/Input.tsx +22 -0
- package/frontend/src/components/radix/Label.tsx +19 -0
- package/frontend/src/components/radix/Popover.tsx +29 -0
- package/frontend/src/components/radix/ScrollArea.tsx +44 -0
- package/frontend/src/components/radix/Select.tsx +151 -0
- package/frontend/src/components/radix/Separator.tsx +26 -0
- package/frontend/src/components/radix/Sheet.tsx +119 -0
- package/frontend/src/components/radix/Skeleton.tsx +7 -0
- package/frontend/src/components/radix/Switch.tsx +29 -0
- package/frontend/src/components/radix/Tabs.tsx +50 -0
- package/frontend/src/components/radix/Textarea.tsx +21 -0
- package/frontend/src/components/radix/Tooltip.tsx +28 -0
- package/frontend/src/features/ai/components/AIConfigCard.tsx +154 -0
- package/frontend/src/features/ai/components/AIConfigDialog.tsx +76 -0
- package/frontend/src/features/ai/components/AIConfigForm.tsx +222 -0
- package/frontend/src/features/ai/components/AIEmptyState.tsx +18 -0
- package/frontend/src/features/ai/components/fields/ModalityField.tsx +87 -0
- package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +134 -0
- package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +33 -0
- package/frontend/src/features/ai/helpers.ts +155 -0
- package/frontend/src/features/ai/hooks/useAIConfigs.ts +221 -0
- package/frontend/src/features/ai/hooks/useAIUsage.ts +77 -0
- package/frontend/src/features/ai/page/AIPage.tsx +178 -0
- package/frontend/src/features/ai/services/ai.service.ts +148 -0
- package/frontend/src/features/auth/components/AddOAuthDialog.tsx +106 -0
- package/frontend/src/features/auth/components/AuthMethodTab.tsx +238 -0
- package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +303 -0
- package/frontend/src/features/auth/components/OAuthEmptyState.tsx +15 -0
- package/frontend/src/features/auth/components/UserFormDialog.tsx +248 -0
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +183 -0
- package/frontend/src/features/auth/components/UsersTab.tsx +114 -0
- package/frontend/src/features/auth/hooks/useOAuthConfig.ts +129 -0
- package/frontend/src/features/auth/hooks/useUsers.ts +57 -0
- package/frontend/src/features/auth/index.ts +9 -0
- package/frontend/src/features/auth/page/AuthenticationPage.tsx +169 -0
- package/frontend/src/features/auth/services/auth.service.ts +112 -0
- package/frontend/src/features/auth/services/oauth.service.ts +49 -0
- package/frontend/src/features/dashboard/page/DashboardPage.tsx +194 -0
- package/frontend/src/features/database/components/ColumnTypeSelect.tsx +64 -0
- package/frontend/src/features/database/components/DatabaseDataGrid.tsx +282 -0
- package/frontend/src/features/database/components/ForeignKeyCell.tsx +187 -0
- package/frontend/src/features/database/components/ForeignKeyPopover.tsx +378 -0
- package/frontend/src/features/database/components/LinkRecordModal.tsx +288 -0
- package/frontend/src/features/database/components/RecordFormDialog.tsx +164 -0
- package/frontend/src/features/database/components/RecordFormField.tsx +568 -0
- package/frontend/src/features/database/components/TableEmptyState.tsx +21 -0
- package/frontend/src/features/database/components/TableForm.tsx +656 -0
- package/frontend/src/features/database/components/TableFormColumn.tsx +137 -0
- package/frontend/src/features/database/components/TableListSkeleton.tsx +9 -0
- package/frontend/src/features/database/components/TableSidebar.tsx +47 -0
- package/frontend/src/features/database/constants.ts +26 -0
- package/frontend/src/features/database/helpers.ts +125 -0
- package/frontend/src/features/database/hooks/UseLinkModal.tsx +78 -0
- package/frontend/src/features/database/index.ts +12 -0
- package/frontend/src/features/database/page/DatabasePage.tsx +626 -0
- package/frontend/src/features/database/schema.ts +25 -0
- package/frontend/src/features/database/services/database.service.ts +216 -0
- package/frontend/src/features/functions/components/FunctionEmptyState.tsx +15 -0
- package/frontend/src/features/functions/components/FunctionRow.tsx +71 -0
- package/frontend/src/features/functions/components/FunctionViewer.tsx +46 -0
- package/frontend/src/features/functions/components/FunctionsContent.tsx +88 -0
- package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -0
- package/frontend/src/features/functions/components/SecretEmptyState.tsx +23 -0
- package/frontend/src/features/functions/components/SecretRow.tsx +68 -0
- package/frontend/src/features/functions/components/SecretsContent.tsx +120 -0
- package/frontend/src/features/functions/hooks/useFunctions.ts +106 -0
- package/frontend/src/features/functions/page/FunctionsPage.tsx +28 -0
- package/frontend/src/features/functions/services/functions.service.ts +48 -0
- package/frontend/src/features/login/components/AuthErrorBoundary.tsx +87 -0
- package/frontend/src/features/login/components/PrivateRoute.tsx +24 -0
- package/frontend/src/features/login/page/CloudLoginPage.tsx +93 -0
- package/frontend/src/features/login/page/LoginPage.tsx +174 -0
- package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +313 -0
- package/frontend/src/features/logs/components/LogsTable.tsx +199 -0
- package/frontend/src/features/logs/hooks/useAuditLogs.ts +39 -0
- package/frontend/src/features/logs/index.ts +5 -0
- package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +530 -0
- package/frontend/src/features/logs/page/AuditsPage.tsx +192 -0
- package/frontend/src/features/logs/services/log.service.ts +171 -0
- package/frontend/src/features/metadata/hooks/useMetadata.ts +53 -0
- package/frontend/src/features/metadata/index.ts +0 -0
- package/frontend/src/features/metadata/page/MetadataPage.tsx +136 -0
- package/frontend/src/features/metadata/services/metadata.service.ts +17 -0
- package/frontend/src/features/onboard/components/CompletionCard.tsx +41 -0
- package/frontend/src/features/onboard/components/OnboardButton.tsx +84 -0
- package/frontend/src/features/onboard/components/StepContent.tsx +91 -0
- package/frontend/src/features/onboard/components/TestConnectionStep.tsx +53 -0
- package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +35 -0
- package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +144 -0
- package/frontend/src/features/onboard/components/mcp/index.ts +4 -0
- package/frontend/src/features/onboard/components/mcp/mcp-helper.tsx +98 -0
- package/frontend/src/features/onboard/index.ts +3 -0
- package/frontend/src/features/onboard/page/OnBoardPage.tsx +104 -0
- package/frontend/src/features/onboard/types.ts +8 -0
- package/frontend/src/features/secrets/hooks/useSecrets.ts +139 -0
- package/frontend/src/features/secrets/services/secrets.service.ts +57 -0
- package/frontend/src/features/storage/components/BucketEmptyState.tsx +19 -0
- package/frontend/src/features/storage/components/BucketFormDialog.tsx +194 -0
- package/frontend/src/features/storage/components/BucketListSkeleton.tsx +17 -0
- package/frontend/src/features/storage/components/FilePreviewDialog.tsx +287 -0
- package/frontend/src/features/storage/components/StorageDataGrid.tsx +239 -0
- package/frontend/src/features/storage/components/StorageManager.tsx +236 -0
- package/frontend/src/features/storage/components/StorageSidebar.tsx +44 -0
- package/frontend/src/features/storage/components/UploadToast.tsx +46 -0
- package/frontend/src/features/storage/index.ts +3 -0
- package/frontend/src/features/storage/page/StoragePage.tsx +553 -0
- package/frontend/src/features/storage/services/storage.service.ts +144 -0
- package/frontend/src/features/visualizer/components/AuthNode.tsx +107 -0
- package/frontend/src/features/visualizer/components/BucketNode.tsx +34 -0
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +359 -0
- package/frontend/src/features/visualizer/components/TableNode.tsx +152 -0
- package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +24 -0
- package/frontend/src/features/visualizer/components/index.ts +5 -0
- package/frontend/src/features/visualizer/page/VisualizerPage.tsx +127 -0
- package/frontend/src/index.css +248 -0
- package/frontend/src/lib/api/client.ts +163 -0
- package/frontend/src/lib/contexts/AuthContext.tsx +157 -0
- package/frontend/src/lib/contexts/OnboardStepContext.tsx +68 -0
- package/frontend/src/lib/contexts/SocketContext.tsx +303 -0
- package/frontend/src/lib/contexts/ThemeContext.tsx +125 -0
- package/frontend/src/lib/hooks/useAuth.ts +4 -0
- package/frontend/src/lib/hooks/useConfirm.ts +55 -0
- package/frontend/src/lib/hooks/useInterval.ts +27 -0
- package/frontend/src/lib/hooks/useMediaQuery.ts +59 -0
- package/frontend/src/lib/hooks/useOnboardingCompletion.ts +29 -0
- package/frontend/src/lib/hooks/usePagination.ts +27 -0
- package/frontend/src/lib/hooks/useTimeout.ts +27 -0
- package/frontend/src/lib/hooks/useToast.tsx +229 -0
- package/frontend/src/lib/utils/constants.ts +38 -0
- package/frontend/src/lib/utils/utils.ts +165 -0
- package/frontend/src/lib/utils/validation-schemas.ts +126 -0
- package/frontend/src/main.tsx +16 -0
- package/frontend/src/rdg.css +194 -0
- package/frontend/src/vite-env.d.ts +12 -0
- package/frontend/tailwind.config.js +97 -0
- package/frontend/tsconfig.json +26 -0
- package/frontend/tsconfig.node.json +10 -0
- package/frontend/vite.config.ts +37 -0
- package/frontend/vitest.config.ts +36 -0
- package/functions/deno.json +25 -0
- package/functions/server.ts +290 -0
- package/functions/worker-template.js +126 -0
- package/openapi/ai.yaml +689 -0
- package/openapi/auth.yaml +563 -0
- package/openapi/functions.yaml +476 -0
- package/openapi/health.yaml +30 -0
- package/openapi/logs.yaml +224 -0
- package/openapi/metadata.yaml +178 -0
- package/openapi/records.yaml +382 -0
- package/openapi/secrets.yaml +371 -0
- package/openapi/storage.yaml +876 -0
- package/openapi/tables.yaml +464 -0
- package/package.json +88 -0
- package/shared-schemas/package.json +31 -0
- package/shared-schemas/src/ai-api.schema.ts +167 -0
- package/shared-schemas/src/ai.schema.ts +54 -0
- package/shared-schemas/src/auth-api.schema.ts +193 -0
- package/shared-schemas/src/auth.schema.ts +94 -0
- package/shared-schemas/src/database-api.schema.ts +259 -0
- package/shared-schemas/src/database.schema.ts +69 -0
- package/shared-schemas/src/functions-api.schema.ts +25 -0
- package/shared-schemas/src/functions.schema.ts +16 -0
- package/shared-schemas/src/index.ts +13 -0
- package/shared-schemas/src/logs-api.schema.ts +49 -0
- package/shared-schemas/src/logs.schema.ts +14 -0
- package/shared-schemas/src/metadata.schema.ts +56 -0
- package/shared-schemas/src/storage-api.schema.ts +65 -0
- package/shared-schemas/src/storage.schema.ts +19 -0
- package/shared-schemas/tsconfig.json +21 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import { useQuery } from '@tanstack/react-query';
|
|
3
|
+
import { Button } from '@/components/radix/Button';
|
|
4
|
+
import { Label } from '@/components/radix/Label';
|
|
5
|
+
import { Dialog, DialogContent, DialogTitle, DialogDescription } from '@/components/radix/Dialog';
|
|
6
|
+
import { Select, SelectContent, SelectItem, SelectTrigger } from '@/components/radix/Select';
|
|
7
|
+
import { useMetadata } from '@/features/metadata/hooks/useMetadata';
|
|
8
|
+
import { databaseService } from '@/features/database/services/database.service';
|
|
9
|
+
import { UseFormReturn } from 'react-hook-form';
|
|
10
|
+
import { TableFormSchema, TableFormForeignKeySchema } from '../schema';
|
|
11
|
+
import { ColumnSchema, OnDeleteActionSchema, OnUpdateActionSchema } from '@insforge/shared-schemas';
|
|
12
|
+
import { cn } from '@/lib/utils/utils';
|
|
13
|
+
|
|
14
|
+
interface ForeignKeyPopoverProps {
|
|
15
|
+
form: UseFormReturn<TableFormSchema>;
|
|
16
|
+
mode: 'create' | 'edit';
|
|
17
|
+
editTableName?: string;
|
|
18
|
+
open: boolean;
|
|
19
|
+
onOpenChange: (open: boolean) => void;
|
|
20
|
+
onAddForeignKey: (fk: TableFormForeignKeySchema) => void;
|
|
21
|
+
initialValue?: TableFormForeignKeySchema;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function ForeignKeyPopover({
|
|
25
|
+
form,
|
|
26
|
+
mode,
|
|
27
|
+
editTableName,
|
|
28
|
+
open,
|
|
29
|
+
onOpenChange,
|
|
30
|
+
onAddForeignKey,
|
|
31
|
+
initialValue,
|
|
32
|
+
}: ForeignKeyPopoverProps) {
|
|
33
|
+
const [newForeignKey, setNewForeignKey] = useState<TableFormForeignKeySchema>({
|
|
34
|
+
columnName: '',
|
|
35
|
+
referenceTable: '',
|
|
36
|
+
referenceColumn: '',
|
|
37
|
+
onDelete: 'NO ACTION',
|
|
38
|
+
onUpdate: 'NO ACTION',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const columns = form.watch('columns');
|
|
42
|
+
|
|
43
|
+
// Set initial values when editing
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (open && initialValue) {
|
|
46
|
+
setNewForeignKey({
|
|
47
|
+
columnName: initialValue.columnName,
|
|
48
|
+
referenceTable: initialValue.referenceTable,
|
|
49
|
+
referenceColumn: initialValue.referenceColumn,
|
|
50
|
+
onDelete: initialValue.onDelete,
|
|
51
|
+
onUpdate: initialValue.onUpdate,
|
|
52
|
+
});
|
|
53
|
+
} else if (!open) {
|
|
54
|
+
// Reset when closing
|
|
55
|
+
setNewForeignKey({
|
|
56
|
+
columnName: '',
|
|
57
|
+
referenceTable: '',
|
|
58
|
+
referenceColumn: '',
|
|
59
|
+
onDelete: 'NO ACTION',
|
|
60
|
+
onUpdate: 'NO ACTION',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}, [open, initialValue]);
|
|
64
|
+
|
|
65
|
+
// Get available tables
|
|
66
|
+
const { tables } = useMetadata({ enabled: open });
|
|
67
|
+
|
|
68
|
+
const availableTables = tables.filter(
|
|
69
|
+
(tableName) => mode === 'create' || tableName !== editTableName
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Get columns for selected reference table
|
|
73
|
+
const { data: referenceTableSchema } = useQuery({
|
|
74
|
+
queryKey: ['table-schema', newForeignKey.referenceTable],
|
|
75
|
+
queryFn: async () => {
|
|
76
|
+
if (!newForeignKey.referenceTable) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return await databaseService.getTableSchema(newForeignKey.referenceTable);
|
|
80
|
+
},
|
|
81
|
+
enabled: !!newForeignKey.referenceTable && open,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Get the type of the selected source column
|
|
85
|
+
const getSourceFieldType = useMemo(() => {
|
|
86
|
+
if (!newForeignKey.columnName) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const sourceColumn = columns.find((col) => col.columnName === newForeignKey.columnName);
|
|
90
|
+
return sourceColumn?.type || null;
|
|
91
|
+
}, [newForeignKey.columnName, columns]);
|
|
92
|
+
|
|
93
|
+
// Calculate if the button should be enabled
|
|
94
|
+
const isAddButtonEnabled = Boolean(
|
|
95
|
+
newForeignKey.columnName && newForeignKey.referenceTable && newForeignKey.referenceColumn
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const handleAddForeignKey = () => {
|
|
99
|
+
if (newForeignKey.columnName && newForeignKey.referenceTable && newForeignKey.referenceColumn) {
|
|
100
|
+
onAddForeignKey(newForeignKey);
|
|
101
|
+
setNewForeignKey({
|
|
102
|
+
columnName: '',
|
|
103
|
+
referenceTable: '',
|
|
104
|
+
referenceColumn: '',
|
|
105
|
+
onDelete: 'NO ACTION',
|
|
106
|
+
onUpdate: 'NO ACTION',
|
|
107
|
+
});
|
|
108
|
+
onOpenChange(false);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handleCancelAddForeignKey = () => {
|
|
113
|
+
setNewForeignKey({
|
|
114
|
+
columnName: '',
|
|
115
|
+
referenceTable: '',
|
|
116
|
+
referenceColumn: '',
|
|
117
|
+
onDelete: 'NO ACTION',
|
|
118
|
+
onUpdate: 'NO ACTION',
|
|
119
|
+
});
|
|
120
|
+
onOpenChange(false);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
125
|
+
<DialogContent className="max-w-[520px] p-0 gap-0">
|
|
126
|
+
<div className="flex flex-col">
|
|
127
|
+
{/* Header */}
|
|
128
|
+
<div className="flex flex-col gap-1 px-6 py-3 border-b border-zinc-200 dark:border-neutral-700">
|
|
129
|
+
<DialogTitle className="text-lg font-semibold dark:text-white">
|
|
130
|
+
{initialValue ? 'Edit Foreign Key' : 'Add Foreign Key'}
|
|
131
|
+
</DialogTitle>
|
|
132
|
+
<DialogDescription className="text-sm text-zinc-500 dark:text-neutral-400">
|
|
133
|
+
{initialValue
|
|
134
|
+
? 'Modify the relationship between tables'
|
|
135
|
+
: 'Create a relationship between this table and another table'}
|
|
136
|
+
</DialogDescription>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
{/* Form Content */}
|
|
140
|
+
<div className="flex flex-col gap-6 p-6">
|
|
141
|
+
{/* Column selector */}
|
|
142
|
+
<div className="flex flex-row gap-10 items-center">
|
|
143
|
+
<Label className="text-sm text-black dark:text-white flex-1">Column</Label>
|
|
144
|
+
<Select
|
|
145
|
+
value={newForeignKey.columnName}
|
|
146
|
+
onValueChange={(value) =>
|
|
147
|
+
setNewForeignKey((prev) => ({ ...prev, columnName: value }))
|
|
148
|
+
}
|
|
149
|
+
>
|
|
150
|
+
<SelectTrigger className="w-70 h-10 border-zinc-200 shadow-sm dark:border-neutral-700 dark:bg-neutral-900">
|
|
151
|
+
<span
|
|
152
|
+
className={cn(
|
|
153
|
+
'text-sm text-muted-foreground dark:text-neutral-400',
|
|
154
|
+
newForeignKey.columnName && 'text-black dark:text-white'
|
|
155
|
+
)}
|
|
156
|
+
>
|
|
157
|
+
{newForeignKey.columnName || 'Select column'}
|
|
158
|
+
</span>
|
|
159
|
+
</SelectTrigger>
|
|
160
|
+
<SelectContent>
|
|
161
|
+
{columns
|
|
162
|
+
.filter((col) => col.columnName)
|
|
163
|
+
.map((col, index) => (
|
|
164
|
+
<SelectItem
|
|
165
|
+
key={col.columnName || index}
|
|
166
|
+
value={col.columnName}
|
|
167
|
+
disabled={col.isSystemColumn}
|
|
168
|
+
>
|
|
169
|
+
<div className="flex flex-row items-center justify-between gap-2">
|
|
170
|
+
<span className="truncate max-w-[160px] block">{col.columnName}</span>
|
|
171
|
+
<span className="text-xs text-muted-foreground dark:text-neutral-400 flex-shrink-0">
|
|
172
|
+
({col.type})
|
|
173
|
+
</span>
|
|
174
|
+
</div>
|
|
175
|
+
</SelectItem>
|
|
176
|
+
))}
|
|
177
|
+
</SelectContent>
|
|
178
|
+
</Select>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
{/* Reference Table selector */}
|
|
182
|
+
<div className="flex flex-row gap-10 items-center">
|
|
183
|
+
<Label className="text-sm text-black dark:text-white flex-1">Reference Table</Label>
|
|
184
|
+
<Select
|
|
185
|
+
value={newForeignKey.referenceTable}
|
|
186
|
+
onValueChange={(value) => {
|
|
187
|
+
setNewForeignKey((prev) => ({
|
|
188
|
+
...prev,
|
|
189
|
+
referenceTable: value,
|
|
190
|
+
referenceColumn: '', // Reset column when table changes
|
|
191
|
+
}));
|
|
192
|
+
}}
|
|
193
|
+
>
|
|
194
|
+
<SelectTrigger className="w-70 h-10 border-zinc-200 shadow-sm dark:border-neutral-700 dark:bg-neutral-900">
|
|
195
|
+
<span
|
|
196
|
+
className={cn(
|
|
197
|
+
'text-sm text-muted-foreground dark:text-neutral-400',
|
|
198
|
+
newForeignKey.referenceTable && 'text-black dark:text-white'
|
|
199
|
+
)}
|
|
200
|
+
>
|
|
201
|
+
{newForeignKey.referenceTable || 'Select table'}
|
|
202
|
+
</span>
|
|
203
|
+
</SelectTrigger>
|
|
204
|
+
<SelectContent>
|
|
205
|
+
{availableTables.map((tableName) => (
|
|
206
|
+
<SelectItem key={tableName} value={tableName}>
|
|
207
|
+
{tableName}
|
|
208
|
+
</SelectItem>
|
|
209
|
+
))}
|
|
210
|
+
</SelectContent>
|
|
211
|
+
</Select>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{/* Reference Column selector - only shown after table and source column are selected */}
|
|
215
|
+
{newForeignKey.referenceTable && newForeignKey.columnName && (
|
|
216
|
+
<div className="flex flex-row gap-10 items-center">
|
|
217
|
+
<Label className="text-sm text-black dark:text-white flex-1">
|
|
218
|
+
Reference Column
|
|
219
|
+
</Label>
|
|
220
|
+
<Select
|
|
221
|
+
key={`column-select-${newForeignKey.referenceTable}`}
|
|
222
|
+
value={newForeignKey.referenceColumn}
|
|
223
|
+
onValueChange={(value) =>
|
|
224
|
+
setNewForeignKey((prev) => ({ ...prev, referenceColumn: value }))
|
|
225
|
+
}
|
|
226
|
+
>
|
|
227
|
+
<SelectTrigger className="w-70 h-10 border-zinc-200 shadow-sm dark:border-neutral-700 dark:bg-neutral-900">
|
|
228
|
+
<span
|
|
229
|
+
className={cn(
|
|
230
|
+
'text-sm text-muted-foreground dark:text-neutral-400',
|
|
231
|
+
newForeignKey.referenceColumn && 'text-black dark:text-white'
|
|
232
|
+
)}
|
|
233
|
+
>
|
|
234
|
+
{newForeignKey.referenceColumn || 'Select column'}
|
|
235
|
+
</span>
|
|
236
|
+
</SelectTrigger>
|
|
237
|
+
<SelectContent className="max-w-[360px]">
|
|
238
|
+
{(() => {
|
|
239
|
+
const allColumns = referenceTableSchema?.columns || [];
|
|
240
|
+
if (allColumns.length > 0) {
|
|
241
|
+
const sourceType = getSourceFieldType;
|
|
242
|
+
|
|
243
|
+
return allColumns.map((col: ColumnSchema) => {
|
|
244
|
+
// Check if types match exactly (sourceType should always exist at this point since we require columnName)
|
|
245
|
+
const typesMatch =
|
|
246
|
+
sourceType && col.type.toLowerCase() === sourceType.toLowerCase();
|
|
247
|
+
|
|
248
|
+
// Disable if not a valid reference or types don't match
|
|
249
|
+
const isDisabled = !col.isUnique || !typesMatch;
|
|
250
|
+
|
|
251
|
+
// Determine what to show on the right side
|
|
252
|
+
let rightText = '';
|
|
253
|
+
if (!col.isUnique) {
|
|
254
|
+
rightText = 'Not unique';
|
|
255
|
+
} else if (!typesMatch) {
|
|
256
|
+
rightText = 'Columntype not match';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<SelectItem
|
|
261
|
+
key={col.columnName}
|
|
262
|
+
value={col.columnName}
|
|
263
|
+
disabled={isDisabled}
|
|
264
|
+
className="relative flex items-center justify-between pr-16"
|
|
265
|
+
>
|
|
266
|
+
<div className="flex flex-row items-center justify-between gap-2">
|
|
267
|
+
<span
|
|
268
|
+
className="flex-1 truncate max-w-[180px] block"
|
|
269
|
+
title={col.columnName}
|
|
270
|
+
>
|
|
271
|
+
{col.columnName}
|
|
272
|
+
</span>
|
|
273
|
+
<span className="text-xs text-muted-foreground dark:text-neutral-400">
|
|
274
|
+
({col.type})
|
|
275
|
+
</span>
|
|
276
|
+
{rightText && (
|
|
277
|
+
<span className="text-right text-xs text-muted-foreground dark:text-neutral-400">
|
|
278
|
+
{rightText}
|
|
279
|
+
</span>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
</SelectItem>
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return (
|
|
288
|
+
<div className="px-2 py-1.5 text-sm text-muted-foreground">
|
|
289
|
+
No columns available
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
})()}
|
|
293
|
+
</SelectContent>
|
|
294
|
+
</Select>
|
|
295
|
+
</div>
|
|
296
|
+
)}
|
|
297
|
+
|
|
298
|
+
{/* On Update action */}
|
|
299
|
+
<div className="flex flex-row gap-10 items-center">
|
|
300
|
+
<Label className="text-sm text-black dark:text-white flex-1">On Update</Label>
|
|
301
|
+
<Select
|
|
302
|
+
value={newForeignKey.onUpdate}
|
|
303
|
+
onValueChange={(value) =>
|
|
304
|
+
setNewForeignKey((prev) => ({
|
|
305
|
+
...prev,
|
|
306
|
+
onUpdate: value as OnUpdateActionSchema,
|
|
307
|
+
}))
|
|
308
|
+
}
|
|
309
|
+
>
|
|
310
|
+
<SelectTrigger className="w-70 h-10 border-zinc-200 shadow-sm dark:border-neutral-700 dark:bg-neutral-900">
|
|
311
|
+
<span className="text-sm text-black dark:text-white">
|
|
312
|
+
{newForeignKey.onUpdate}
|
|
313
|
+
</span>
|
|
314
|
+
</SelectTrigger>
|
|
315
|
+
<SelectContent>
|
|
316
|
+
<SelectItem value="NO ACTION">No Action</SelectItem>
|
|
317
|
+
<SelectItem value="CASCADE">Cascade</SelectItem>
|
|
318
|
+
<SelectItem value="RESTRICT">Restrict</SelectItem>
|
|
319
|
+
</SelectContent>
|
|
320
|
+
</Select>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
{/* On Delete action */}
|
|
324
|
+
<div className="flex flex-row gap-10 items-center">
|
|
325
|
+
<Label className="text-sm text-black dark:text-white flex-1">On Delete</Label>
|
|
326
|
+
<Select
|
|
327
|
+
value={newForeignKey.onDelete}
|
|
328
|
+
onValueChange={(value) =>
|
|
329
|
+
setNewForeignKey((prev) => ({
|
|
330
|
+
...prev,
|
|
331
|
+
onDelete: value as OnDeleteActionSchema,
|
|
332
|
+
}))
|
|
333
|
+
}
|
|
334
|
+
>
|
|
335
|
+
<SelectTrigger className="w-70 h-10 border-zinc-200 shadow-sm dark:border-neutral-700 dark:bg-neutral-900">
|
|
336
|
+
<span className="text-sm text-black dark:text-white">
|
|
337
|
+
{newForeignKey.onDelete}
|
|
338
|
+
</span>
|
|
339
|
+
</SelectTrigger>
|
|
340
|
+
<SelectContent>
|
|
341
|
+
<SelectItem value="NO ACTION">No Action</SelectItem>
|
|
342
|
+
<SelectItem value="CASCADE">Cascade</SelectItem>
|
|
343
|
+
<SelectItem value="SET NULL">Set Null</SelectItem>
|
|
344
|
+
<SelectItem value="SET DEFAULT">Set Default</SelectItem>
|
|
345
|
+
<SelectItem value="RESTRICT">Restrict</SelectItem>
|
|
346
|
+
</SelectContent>
|
|
347
|
+
</Select>
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
|
|
351
|
+
{/* Footer */}
|
|
352
|
+
<div className="flex justify-end gap-3 p-6 border-t border-zinc-200 dark:border-neutral-700">
|
|
353
|
+
<Button
|
|
354
|
+
type="button"
|
|
355
|
+
variant="outline"
|
|
356
|
+
onClick={handleCancelAddForeignKey}
|
|
357
|
+
className="h-10 px-4 dark:bg-neutral-600 dark:text-white dark:border-transparent dark:hover:bg-neutral-700"
|
|
358
|
+
>
|
|
359
|
+
Cancel
|
|
360
|
+
</Button>
|
|
361
|
+
<Button
|
|
362
|
+
type="button"
|
|
363
|
+
onClick={handleAddForeignKey}
|
|
364
|
+
disabled={!isAddButtonEnabled}
|
|
365
|
+
className={`h-10 px-4 ${
|
|
366
|
+
!isAddButtonEnabled
|
|
367
|
+
? 'bg-zinc-950/40 dark:bg-emerald-300/40'
|
|
368
|
+
: 'bg-zinc-950 dark:text-zinc-950 dark:bg-emerald-300 dark:hover:bg-emerald-400'
|
|
369
|
+
} text-white dark:text-zinc-950 shadow-sm`}
|
|
370
|
+
>
|
|
371
|
+
{initialValue ? 'Update Foreign Key' : 'Add Foreign Key'}
|
|
372
|
+
</Button>
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
</DialogContent>
|
|
376
|
+
</Dialog>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo, useCallback } from 'react';
|
|
2
|
+
import { useQuery } from '@tanstack/react-query';
|
|
3
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/radix/Dialog';
|
|
4
|
+
import { Button } from '@/components/radix/Button';
|
|
5
|
+
import { databaseService } from '@/features/database/services/database.service';
|
|
6
|
+
import { convertSchemaToColumns } from '@/features/database/components/DatabaseDataGrid';
|
|
7
|
+
import { SearchInput, DataGrid, TypeBadge } from '@/components';
|
|
8
|
+
import {
|
|
9
|
+
type CellMouseEvent,
|
|
10
|
+
type CellClickArgs,
|
|
11
|
+
type RenderCellProps,
|
|
12
|
+
type RenderHeaderCellProps,
|
|
13
|
+
type SortColumn,
|
|
14
|
+
SortableHeaderRenderer,
|
|
15
|
+
type DatabaseRecord,
|
|
16
|
+
type ConvertedValue,
|
|
17
|
+
type DataGridRowType,
|
|
18
|
+
} from '@/components/datagrid';
|
|
19
|
+
import { formatValueForDisplay } from '@/lib/utils/utils';
|
|
20
|
+
import { ColumnType } from '@insforge/shared-schemas';
|
|
21
|
+
|
|
22
|
+
const PAGE_SIZE = 50;
|
|
23
|
+
|
|
24
|
+
interface LinkRecordModalProps {
|
|
25
|
+
open: boolean;
|
|
26
|
+
onOpenChange: (open: boolean) => void;
|
|
27
|
+
referenceTable: string;
|
|
28
|
+
referenceColumn: string;
|
|
29
|
+
onSelectRecord: (record: DatabaseRecord) => void;
|
|
30
|
+
currentValue?: string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function LinkRecordModal({
|
|
34
|
+
open,
|
|
35
|
+
onOpenChange,
|
|
36
|
+
referenceTable,
|
|
37
|
+
referenceColumn,
|
|
38
|
+
onSelectRecord,
|
|
39
|
+
}: LinkRecordModalProps) {
|
|
40
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
41
|
+
const [selectedRecord, setSelectedRecord] = useState<DatabaseRecord | null>(null);
|
|
42
|
+
const [sortColumns, setSortColumns] = useState<SortColumn[]>([]);
|
|
43
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
44
|
+
|
|
45
|
+
// Fetch table schema
|
|
46
|
+
const { data: schema } = useQuery({
|
|
47
|
+
queryKey: ['table-schema', referenceTable],
|
|
48
|
+
queryFn: () => databaseService.getTableSchema(referenceTable),
|
|
49
|
+
enabled: open,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Fetch records from the reference table
|
|
53
|
+
const { data: recordsData, isLoading } = useQuery({
|
|
54
|
+
queryKey: [
|
|
55
|
+
'table',
|
|
56
|
+
referenceTable,
|
|
57
|
+
currentPage,
|
|
58
|
+
PAGE_SIZE,
|
|
59
|
+
searchQuery,
|
|
60
|
+
JSON.stringify(sortColumns),
|
|
61
|
+
],
|
|
62
|
+
queryFn: async () => {
|
|
63
|
+
const offset = (currentPage - 1) * PAGE_SIZE;
|
|
64
|
+
const [schema, records] = await Promise.all([
|
|
65
|
+
databaseService.getTableSchema(referenceTable),
|
|
66
|
+
databaseService.getTableRecords(
|
|
67
|
+
referenceTable,
|
|
68
|
+
PAGE_SIZE,
|
|
69
|
+
offset,
|
|
70
|
+
searchQuery || undefined,
|
|
71
|
+
sortColumns
|
|
72
|
+
),
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
schema,
|
|
77
|
+
records: records.records,
|
|
78
|
+
totalRecords: records.pagination.total || schema.recordCount,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
enabled: open,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Reset page when search query changes
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
setCurrentPage(1);
|
|
87
|
+
}, [searchQuery]);
|
|
88
|
+
|
|
89
|
+
const records = useMemo(
|
|
90
|
+
(): DataGridRowType[] => recordsData?.records || [],
|
|
91
|
+
[recordsData?.records]
|
|
92
|
+
);
|
|
93
|
+
const totalRecords = recordsData?.totalRecords || 0;
|
|
94
|
+
const totalPages = Math.ceil(totalRecords / PAGE_SIZE);
|
|
95
|
+
|
|
96
|
+
// Create selected rows set for highlighting
|
|
97
|
+
const selectedRows = useMemo(() => {
|
|
98
|
+
if (!selectedRecord) {
|
|
99
|
+
return new Set<string>();
|
|
100
|
+
}
|
|
101
|
+
return new Set([String(selectedRecord.id || '')]);
|
|
102
|
+
}, [selectedRecord]);
|
|
103
|
+
|
|
104
|
+
// Handle cell click to select record - only for reference column
|
|
105
|
+
const handleCellClick = useCallback(
|
|
106
|
+
(args: CellClickArgs<DataGridRowType>, event: CellMouseEvent) => {
|
|
107
|
+
// Only allow selection when clicking on the reference column
|
|
108
|
+
if (args.column.key !== referenceColumn) {
|
|
109
|
+
// Prevent the default selection behavior for non-reference columns
|
|
110
|
+
event?.preventDefault();
|
|
111
|
+
event?.stopPropagation();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const record = records.find((r: DatabaseRecord) => String(r.id) === String(args.row.id));
|
|
116
|
+
if (record) {
|
|
117
|
+
setSelectedRecord(record);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
[records, referenceColumn]
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Convert schema to columns for the DataGrid with visual distinction
|
|
124
|
+
const columns = useMemo(() => {
|
|
125
|
+
const cols = convertSchemaToColumns(schema);
|
|
126
|
+
// Add visual indication for the reference column (clickable column)
|
|
127
|
+
return cols.map((col) => {
|
|
128
|
+
const baseCol = {
|
|
129
|
+
...col,
|
|
130
|
+
width: 210,
|
|
131
|
+
minWidth: 210,
|
|
132
|
+
resizable: true,
|
|
133
|
+
editable: false,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Helper function to render cell value properly based on type
|
|
137
|
+
const renderCellValue = (value: ConvertedValue, type: ColumnType | undefined) => {
|
|
138
|
+
return formatValueForDisplay(value, type);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
if (col.key === referenceColumn) {
|
|
142
|
+
return {
|
|
143
|
+
...baseCol,
|
|
144
|
+
renderCell: (props: RenderCellProps<DataGridRowType>) => {
|
|
145
|
+
const displayValue = renderCellValue(String(props.row[col.key]), col.type);
|
|
146
|
+
return (
|
|
147
|
+
<div className="w-full h-full flex items-center cursor-pointer">
|
|
148
|
+
<span className="truncate font-medium" title={displayValue}>
|
|
149
|
+
{displayValue}
|
|
150
|
+
</span>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
},
|
|
154
|
+
renderHeaderCell: (props: RenderHeaderCellProps<DataGridRowType>) => (
|
|
155
|
+
<SortableHeaderRenderer
|
|
156
|
+
column={col}
|
|
157
|
+
sortDirection={props.sortDirection}
|
|
158
|
+
columnType={col.type}
|
|
159
|
+
showTypeBadge={true}
|
|
160
|
+
mutedHeader={false}
|
|
161
|
+
/>
|
|
162
|
+
),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
...baseCol,
|
|
168
|
+
cellClass: 'link-modal-disabled-cell',
|
|
169
|
+
renderCell: (props: RenderCellProps<DataGridRowType>) => {
|
|
170
|
+
const displayValue = renderCellValue(String(props.row[col.key]), col.type);
|
|
171
|
+
return (
|
|
172
|
+
<div className="w-full h-full flex items-center cursor-not-allowed relative">
|
|
173
|
+
<div className="absolute inset-0 pointer-events-none opacity-0 hover:opacity-10 bg-gray-200 dark:bg-gray-600 transition-opacity z-5" />
|
|
174
|
+
<span className="truncate dark:text-zinc-300 opacity-70" title={displayValue}>
|
|
175
|
+
{displayValue}
|
|
176
|
+
</span>
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
},
|
|
180
|
+
renderHeaderCell: (props: RenderHeaderCellProps<DataGridRowType>) => (
|
|
181
|
+
<SortableHeaderRenderer
|
|
182
|
+
column={col}
|
|
183
|
+
sortDirection={props.sortDirection}
|
|
184
|
+
columnType={col.type}
|
|
185
|
+
showTypeBadge={true}
|
|
186
|
+
mutedHeader={true}
|
|
187
|
+
/>
|
|
188
|
+
),
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
}, [schema, referenceColumn]);
|
|
192
|
+
|
|
193
|
+
const handleConfirmSelection = () => {
|
|
194
|
+
if (selectedRecord) {
|
|
195
|
+
onSelectRecord(selectedRecord);
|
|
196
|
+
onOpenChange(false);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const handleCancel = () => {
|
|
201
|
+
onOpenChange(false);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
206
|
+
<DialogContent className="max-w-4xl h-[calc(100vh-48px)] p-0 gap-0 flex flex-col">
|
|
207
|
+
<DialogHeader className="px-6 py-3 border-b border-zinc-200 dark:border-neutral-700 flex-shrink-0 flex flex-col gap-1">
|
|
208
|
+
<DialogTitle className="text-lg font-semibold text-zinc-950 dark:text-white">
|
|
209
|
+
Link Record
|
|
210
|
+
</DialogTitle>
|
|
211
|
+
<div className="flex items-center gap-1.5">
|
|
212
|
+
<span className="text-sm text-zinc-500 dark:text-neutral-400">
|
|
213
|
+
Select a record to reference from
|
|
214
|
+
</span>
|
|
215
|
+
<TypeBadge
|
|
216
|
+
type={`${referenceTable}.${referenceColumn}`}
|
|
217
|
+
className="dark:bg-neutral-700"
|
|
218
|
+
/>
|
|
219
|
+
</div>
|
|
220
|
+
</DialogHeader>
|
|
221
|
+
|
|
222
|
+
{/* Search Bar */}
|
|
223
|
+
<div className="p-3">
|
|
224
|
+
<SearchInput
|
|
225
|
+
value={searchQuery}
|
|
226
|
+
onChange={setSearchQuery}
|
|
227
|
+
placeholder="Search records..."
|
|
228
|
+
className="w-60 dark:text-white dark:bg-neutral-900 dark:border-neutral-700"
|
|
229
|
+
debounceTime={300}
|
|
230
|
+
/>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
{/* Records DataGrid */}
|
|
234
|
+
<div className="flex-1 overflow-hidden">
|
|
235
|
+
<DataGrid
|
|
236
|
+
data={records}
|
|
237
|
+
columns={columns}
|
|
238
|
+
loading={isLoading && !records.length}
|
|
239
|
+
selectedRows={selectedRows}
|
|
240
|
+
onSelectedRowsChange={(newSelectedRows) => {
|
|
241
|
+
// Handle selection changes from cell clicks
|
|
242
|
+
const selectedId = Array.from(newSelectedRows)[0];
|
|
243
|
+
if (selectedId) {
|
|
244
|
+
const record = records.find((r: DatabaseRecord) => String(r.id) === selectedId);
|
|
245
|
+
if (record) {
|
|
246
|
+
setSelectedRecord(record);
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
setSelectedRecord(null);
|
|
250
|
+
}
|
|
251
|
+
}}
|
|
252
|
+
sortColumns={sortColumns}
|
|
253
|
+
onSortColumnsChange={setSortColumns}
|
|
254
|
+
onCellClick={handleCellClick}
|
|
255
|
+
currentPage={currentPage}
|
|
256
|
+
totalPages={totalPages}
|
|
257
|
+
pageSize={PAGE_SIZE}
|
|
258
|
+
totalRecords={totalRecords}
|
|
259
|
+
onPageChange={setCurrentPage}
|
|
260
|
+
showSelection={false}
|
|
261
|
+
showPagination={true}
|
|
262
|
+
emptyStateTitle={
|
|
263
|
+
searchQuery ? 'No records match your search criteria' : 'No records found'
|
|
264
|
+
}
|
|
265
|
+
/>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
{/* Footer */}
|
|
269
|
+
<div className="px-6 py-4 border-t border-zinc-200 dark:border-neutral-700 flex justify-end gap-3 flex-shrink-0">
|
|
270
|
+
<Button
|
|
271
|
+
variant="outline"
|
|
272
|
+
onClick={handleCancel}
|
|
273
|
+
className="dark:bg-neutral-600 dark:text-white dark:border-transparent dark:hover:bg-neutral-700"
|
|
274
|
+
>
|
|
275
|
+
Cancel
|
|
276
|
+
</Button>
|
|
277
|
+
<Button
|
|
278
|
+
onClick={handleConfirmSelection}
|
|
279
|
+
disabled={!selectedRecord}
|
|
280
|
+
className="bg-zinc-950 hover:bg-zinc-800 text-white dark:bg-emerald-300 dark:text-zinc-950 dark:hover:bg-emerald-400"
|
|
281
|
+
>
|
|
282
|
+
Add Record
|
|
283
|
+
</Button>
|
|
284
|
+
</div>
|
|
285
|
+
</DialogContent>
|
|
286
|
+
</Dialog>
|
|
287
|
+
);
|
|
288
|
+
}
|