diff --git a/front/src/modules/ui/table/options/components/TableViewsDropdownButton.tsx b/front/src/modules/ui/table/options/components/TableViewsDropdownButton.tsx index 7bc7e9f444..20ec286b76 100644 --- a/front/src/modules/ui/table/options/components/TableViewsDropdownButton.tsx +++ b/front/src/modules/ui/table/options/components/TableViewsDropdownButton.tsx @@ -96,7 +96,7 @@ export const TableViewsDropdownButton = ({ const handleViewSelect = useRecoilCallback( ({ set, snapshot }) => - async (viewId?: string) => { + async (viewId: string) => { const savedColumns = await snapshot.getPromise( savedTableColumnsScopedState(viewId), ); @@ -164,7 +164,7 @@ export const TableViewsDropdownButton = ({ {currentView?.name || defaultViewName}{' '} - · {views.length + 1} + · {views.length} } @@ -175,10 +175,6 @@ export const TableViewsDropdownButton = ({ HotkeyScope={HotkeyScope} > - handleViewSelect(undefined)}> - - {defaultViewName} - {views.map((view) => ( [], viewId = currentViewId, ) => { - if (!columns.length) return; + if (!viewId || !columns.length) return; return createViewFieldsMutation({ variables: { @@ -84,7 +84,7 @@ export const useTableViewFields = ({ const updateViewFields = useCallback( (columns: ViewFieldDefinition[]) => { - if (!columns.length) return; + if (!currentViewId || !columns.length) return; return Promise.all( columns.map((column) => @@ -100,10 +100,11 @@ export const useTableViewFields = ({ ), ); }, - [updateViewFieldMutation], + [currentViewId, updateViewFieldMutation], ); const { refetch } = useGetViewFieldsQuery({ + skip: !currentViewId, variables: { orderBy: { index: SortOrder.Asc }, where: { @@ -139,6 +140,8 @@ export const useTableViewFields = ({ }); const persistColumns = useCallback(async () => { + if (!currentViewId) return; + const viewFieldsToUpdate = columns.filter( (column) => savedColumnsById[column.id] && @@ -148,7 +151,7 @@ export const useTableViewFields = ({ await updateViewFields(viewFieldsToUpdate); return refetch(); - }, [columns, refetch, savedColumnsById, updateViewFields]); + }, [columns, currentViewId, refetch, savedColumnsById, updateViewFields]); return { createViewFields, persistColumns }; }; diff --git a/front/src/modules/views/hooks/useViews.ts b/front/src/modules/views/hooks/useViews.ts index 8b4a62e8fd..358312e7cd 100644 --- a/front/src/modules/views/hooks/useViews.ts +++ b/front/src/modules/views/hooks/useViews.ts @@ -2,6 +2,7 @@ import { useCallback } from 'react'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; import { + currentTableViewIdState, type TableView, tableViewsByIdState, tableViewsState, @@ -24,6 +25,10 @@ export const useViews = ({ objectId: 'company' | 'person'; onViewCreate: (viewId: string) => Promise; }) => { + const [currentViewId, setCurrentViewId] = useRecoilScopedState( + currentTableViewIdState, + TableRecoilScopeContext, + ); const [views, setViews] = useRecoilScopedState( tableViewsState, TableRecoilScopeContext, @@ -102,6 +107,8 @@ export const useViews = ({ })); if (!isDeeplyEqual(views, nextViews)) setViews(nextViews); + + if (nextViews.length && !currentViewId) setCurrentViewId(nextViews[0].id); }, }); diff --git a/server/src/core/view/seed-data/views.json b/server/src/core/view/seed-data/views.json new file mode 100644 index 0000000000..8f61b2498a --- /dev/null +++ b/server/src/core/view/seed-data/views.json @@ -0,0 +1,111 @@ +[ + { + "name": "All Companies", + "objectId": "company", + "type": "Table", + "fields": [ + { + "fieldName": "Name", + "sizeInPx": 180, + "isVisible": true + }, + { + "fieldName": "URL", + "sizeInPx": 100, + "isVisible": true + }, + { + "fieldName": "Account Owner", + "sizeInPx": 150, + "isVisible": true + }, + { + "fieldName": "Creation", + "sizeInPx": 150, + "isVisible": true + }, + { + "fieldName": "Employees", + "sizeInPx": 150, + "isVisible": true + }, + { + "fieldName": "LinkedIn", + "sizeInPx": 170, + "isVisible": true + }, + { + "fieldName": "Address", + "sizeInPx": 170, + "isVisible": true + }, + { + "fieldName": "ICP", + "sizeInPx": 150, + "isVisible": false + }, + { + "fieldName": "ARR", + "sizeInPx": 150, + "isVisible": true + }, + { + "fieldName": "Twitter", + "sizeInPx": 150, + "isVisible": false + } + ] + }, + { + "name": "All People", + "objectId": "person", + "type": "Table", + "fields": [ + { + "fieldName": "People", + "sizeInPx": 210, + "isVisible": true + }, + { + "fieldName": "Email", + "sizeInPx": 150, + "isVisible": true + }, + { + "fieldName": "Company", + "sizeInPx": 150, + "isVisible": true + }, + { + "fieldName": "Phone", + "sizeInPx": 150, + "isVisible": true + }, + { + "fieldName": "Creation", + "sizeInPx": 150, + "isVisible": true + }, + { + "fieldName": "City", + "sizeInPx": 150, + "isVisible": true + }, + { + "fieldName": "Job title", + "sizeInPx": 150, + "isVisible": true + }, + { + "fieldName": "LinkedIn", + "sizeInPx": 150, + "isVisible": true + }, + { + "fieldName": "Twitter", + "sizeInPx": 150, + "isVisible": true + } + ] + } +] \ No newline at end of file diff --git a/server/src/core/view/services/view.service.ts b/server/src/core/view/services/view.service.ts index cdd0f3d44f..bf4b2c0a92 100644 --- a/server/src/core/view/services/view.service.ts +++ b/server/src/core/view/services/view.service.ts @@ -1,6 +1,9 @@ import { Injectable } from '@nestjs/common'; +import type { ViewType } from '@prisma/client'; + import { PrismaService } from 'src/database/prisma.service'; +import seedViews from 'src/core/view/seed-data/views.json'; @Injectable() export class ViewService { @@ -36,4 +39,29 @@ export class ViewService { // GroupBy groupBy = this.prismaService.client.view.groupBy; + + // Custom + createDefaultViews({ workspaceId }: { workspaceId: string }) { + return Promise.all( + seedViews.map(async ({ fields, ...viewInput }) => { + const view = await this.create({ + data: { + ...viewInput, + type: viewInput.type as ViewType, + workspace: { connect: { id: workspaceId } }, + }, + }); + + await this.prismaService.client.viewField.createMany({ + data: fields.map((viewField, index) => ({ + ...viewField, + objectName: view.objectId, + index: index + 1, + viewId: view.id, + workspaceId, + })), + }); + }), + ); + } } diff --git a/server/src/core/view/view.module.ts b/server/src/core/view/view.module.ts index 681d34fa1a..9cd8d16c3d 100644 --- a/server/src/core/view/view.module.ts +++ b/server/src/core/view/view.module.ts @@ -20,5 +20,6 @@ import { ViewFilterResolver } from './resolvers/view-filter.resolver'; ViewFilterResolver, ViewSortResolver, ], + exports: [ViewService], }) export class ViewModule {} diff --git a/server/src/core/workspace/services/workspace.service.spec.ts b/server/src/core/workspace/services/workspace.service.spec.ts index 9bb373cc59..1d6a760dac 100644 --- a/server/src/core/workspace/services/workspace.service.spec.ts +++ b/server/src/core/workspace/services/workspace.service.spec.ts @@ -7,6 +7,7 @@ import { PipelineStageService } from 'src/core/pipeline/services/pipeline-stage. import { PersonService } from 'src/core/person/person.service'; import { CompanyService } from 'src/core/company/company.service'; import { PipelineProgressService } from 'src/core/pipeline/services/pipeline-progress.service'; +import { ViewService } from 'src/core/view/services/view.service'; import { WorkspaceService } from './workspace.service'; @@ -41,6 +42,10 @@ describe('WorkspaceService', () => { provide: PipelineProgressService, useValue: {}, }, + { + provide: ViewService, + useValue: {}, + }, ], }).compile(); diff --git a/server/src/core/workspace/services/workspace.service.ts b/server/src/core/workspace/services/workspace.service.ts index d706bcffd6..127fabb0e1 100644 --- a/server/src/core/workspace/services/workspace.service.ts +++ b/server/src/core/workspace/services/workspace.service.ts @@ -1,14 +1,15 @@ import { Injectable } from '@nestjs/common'; -import { v4 } from 'uuid'; import { Prisma } from '@prisma/client'; +import { v4 } from 'uuid'; -import { PipelineStageService } from 'src/core/pipeline/services/pipeline-stage.service'; -import { PipelineProgressService } from 'src/core/pipeline/services/pipeline-progress.service'; -import { PipelineService } from 'src/core/pipeline/services/pipeline.service'; -import { PrismaService } from 'src/database/prisma.service'; import { CompanyService } from 'src/core/company/company.service'; import { PersonService } from 'src/core/person/person.service'; +import { PipelineProgressService } from 'src/core/pipeline/services/pipeline-progress.service'; +import { PipelineStageService } from 'src/core/pipeline/services/pipeline-stage.service'; +import { PipelineService } from 'src/core/pipeline/services/pipeline.service'; +import { ViewService } from 'src/core/view/services/view.service'; +import { PrismaService } from 'src/database/prisma.service'; import { assert } from 'src/utils/assert'; @Injectable() @@ -20,6 +21,7 @@ export class WorkspaceService { private readonly personService: PersonService, private readonly pipelineStageService: PipelineStageService, private readonly pipelineProgressService: PipelineProgressService, + private readonly viewService: ViewService, ) {} // Find @@ -83,6 +85,11 @@ export class WorkspaceService { workspaceId: workspace.id, }); + // Create default views + await this.viewService.createDefaultViews({ + workspaceId: workspace.id, + }); + return workspace; } diff --git a/server/src/core/workspace/workspace.module.ts b/server/src/core/workspace/workspace.module.ts index 35b7bf957b..62a53d775e 100644 --- a/server/src/core/workspace/workspace.module.ts +++ b/server/src/core/workspace/workspace.module.ts @@ -4,6 +4,7 @@ import { FileUploadService } from 'src/core/file/services/file-upload.service'; import { PipelineModule } from 'src/core/pipeline/pipeline.module'; import { CompanyModule } from 'src/core/company/company.module'; import { PersonModule } from 'src/core/person/person.module'; +import { ViewModule } from 'src/core/view/view.module'; import { WorkspaceService } from './services/workspace.service'; import { WorkspaceMemberService } from './services/workspace-member.service'; @@ -11,7 +12,7 @@ import { WorkspaceMemberResolver } from './resolvers/workspace-member.resolver'; import { WorkspaceResolver } from './resolvers/workspace.resolver'; @Module({ - imports: [PipelineModule, CompanyModule, PersonModule], + imports: [PipelineModule, CompanyModule, PersonModule, ViewModule], providers: [ WorkspaceService, FileUploadService, diff --git a/server/src/database/seeds/index.ts b/server/src/database/seeds/index.ts index d2a42cae31..1a905dc57b 100644 --- a/server/src/database/seeds/index.ts +++ b/server/src/database/seeds/index.ts @@ -6,6 +6,7 @@ import { seedPeople } from './people'; import { seedComments } from './comments'; import { seedUsers } from './users'; import { seedPipelines } from './pipelines'; +import { seedViews } from './views'; const seed = async () => { const prisma = new PrismaClient(); @@ -15,6 +16,7 @@ const seed = async () => { await seedPeople(prisma); await seedComments(prisma); await seedPipelines(prisma); + await seedViews(prisma); await prisma.$disconnect(); }; diff --git a/server/src/database/seeds/views.ts b/server/src/database/seeds/views.ts new file mode 100644 index 0000000000..f2ed1d28a5 --- /dev/null +++ b/server/src/database/seeds/views.ts @@ -0,0 +1,179 @@ +import { PrismaClient } from '@prisma/client'; + +export const seedViews = async (prisma: PrismaClient) => { + const workspaceId = 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'; + const companyViewId = 'twenty-5e924b69-a619-41bf-bd31-a9e8551fc9d1'; + const personViewId = 'twenty-db9e6c85-c091-4fd6-88b1-c1830f5e90d1'; + + await prisma.view.upsert({ + where: { id: companyViewId }, + update: {}, + create: { + id: companyViewId, + name: 'All Companies', + objectId: 'company', + type: 'Table', + workspaceId, + }, + }); + + await Promise.all( + [ + { + id: 'twenty-388833ba-1343-49d7-9092-065f92e0e5fa', + fieldName: 'Name', + sizeInPx: 180, + isVisible: true, + }, + { + id: 'twenty-fdbb7a60-18ac-4d52-83b8-399eb6055ec6', + fieldName: 'URL', + sizeInPx: 100, + isVisible: true, + }, + { + id: 'twenty-cc77beef-af99-4cd2-86dd-0230f8565ed5', + fieldName: 'Account Owner', + sizeInPx: 150, + isVisible: true, + }, + { + id: 'twenty-28537b67-8b78-4885-903d-f749f34883b1', + fieldName: 'Creation', + sizeInPx: 150, + isVisible: true, + }, + { + id: 'twenty-59e6624d-9a4d-492d-a0f2-52f51f69d004', + fieldName: 'Employees', + sizeInPx: 150, + isVisible: true, + }, + { + id: 'twenty-2c4ee8b9-aacd-42dd-b422-22eca03aab5a', + fieldName: 'LinkedIn', + sizeInPx: 170, + isVisible: true, + }, + { + id: 'twenty-b83e299f-7098-4748-a39e-431cca2907ab', + fieldName: 'Address', + sizeInPx: 170, + isVisible: true, + }, + { + id: 'twenty-acef1246-8461-4e34-96b9-f326d598d655', + fieldName: 'ICP', + sizeInPx: 150, + isVisible: false, + }, + { + id: 'twenty-971828c5-8167-4997-ae13-3b7895faa6f2', + fieldName: 'ARR', + sizeInPx: 150, + isVisible: true, + }, + { + id: 'twenty-90977d8a-328d-4f69-98e8-8c69723c5a18', + fieldName: 'Twitter', + sizeInPx: 150, + isVisible: false, + }, + ].map((viewField, index) => + prisma.viewField.upsert({ + where: { id: viewField.id }, + update: {}, + create: { + ...viewField, + index: index + 1, + objectName: 'company', + viewId: companyViewId, + workspaceId, + }, + }), + ), + ); + + await prisma.view.upsert({ + where: { id: personViewId }, + update: {}, + create: { + id: personViewId, + name: 'All People', + objectId: 'person', + type: 'Table', + workspaceId, + }, + }); + + await Promise.all( + [ + { + id: 'twenty-fc3461b4-661d-492e-8907-61004a41cca6', + fieldName: 'People', + sizeInPx: 210, + isVisible: true, + }, + { + id: 'twenty-4724d413-4343-4528-b8c4-4431910722f8', + fieldName: 'Email', + sizeInPx: 150, + isVisible: true, + }, + { + id: 'twenty-fbb16b08-5a58-4a69-8bd0-a6d267994042', + fieldName: 'Company', + sizeInPx: 150, + isVisible: true, + }, + { + id: 'twenty-1bad57bb-6627-40f8-8c75-bb5902892273', + fieldName: 'Phone', + sizeInPx: 150, + isVisible: true, + }, + { + id: 'twenty-3544d797-740b-4e0b-8226-134bf38da256', + fieldName: 'Creation', + sizeInPx: 150, + isVisible: true, + }, + { + id: 'twenty-4b6d48fb-17e2-4071-8565-d512f84656d5', + fieldName: 'City', + sizeInPx: 150, + isVisible: true, + }, + { + id: 'twenty-418849cc-aa5c-4835-822b-c0dfb076106b', + fieldName: 'Job title', + sizeInPx: 150, + isVisible: true, + }, + { + id: 'twenty-7591af5d-e081-4afa-94bb-09bd0e517850', + fieldName: 'LinkedIn', + sizeInPx: 150, + isVisible: true, + }, + { + id: 'twenty-e7baeb3d-8ef3-4e61-89d6-60f64b1d52c5', + fieldName: 'Twitter', + sizeInPx: 150, + isVisible: true, + }, + ].map((viewField, index) => + prisma.viewField.upsert({ + where: { id: viewField.id }, + update: {}, + create: { + ...viewField, + index: index + 1, + objectName: 'person', + viewId: personViewId, + workspaceId, + }, + }), + ), + ); +};