From eaca36573ed60fe3ef42640ff43432daef1b4c11 Mon Sep 17 00:00:00 2001 From: Berkay Date: Thu, 27 Nov 2025 20:22:40 +0300 Subject: [PATCH] updated living space --- backend/src/app.module.ts | 2 + backend/src/company/company.resolver.ts | 18 +- .../src/company/dto/create-company.input.ts | 46 ++- .../src/company/dto/update-company.input.ts | 43 ++- backend/src/lib/getListOfModelByIDs.ts | 1 - .../dto/create-living-space.input.ts | 25 ++ .../dto/list-living-space.response.ts | 14 + .../dto/update-living-space.input.ts | 25 ++ .../src/living-space/living-space.module.ts | 11 + .../living-space.resolver.spec.ts | 18 ++ .../src/living-space/living-space.resolver.ts | 37 +++ .../living-space/living-space.service.spec.ts | 18 ++ .../src/living-space/living-space.service.ts | 74 +++++ backend/src/models/base.model.ts | 2 - backend/src/models/company.model.ts | 56 +++- backend/src/models/living-spaces.model.ts | 16 +- frontend/app/api/company/add/route.ts | 21 ++ frontend/app/api/company/add/schema.ts | 26 ++ frontend/app/api/company/delete/route.ts | 23 ++ frontend/app/api/company/list/route.ts | 47 +++ frontend/app/api/company/update/route.ts | 24 ++ frontend/app/api/company/update/schema.ts | 23 ++ frontend/app/api/living-space/a.txt | 0 frontend/app/api/living-space/add/route.ts | 21 ++ frontend/app/api/living-space/add/schema.ts | 14 + frontend/app/api/living-space/delete/route.ts | 23 ++ frontend/app/api/living-space/list/route.ts | 47 +++ frontend/app/api/living-space/update/route.ts | 24 ++ .../app/api/living-space/update/schema.ts | 14 + frontend/app/living-spaces/add/page.tsx | 5 + frontend/app/living-spaces/page.tsx | 4 +- frontend/pages/living-space/add/form.tsx | 137 +++++++++ frontend/pages/living-space/add/page.tsx | 76 +++++ frontend/pages/living-space/add/queries.tsx | 58 ++++ frontend/pages/living-space/add/schema.ts | 33 +++ frontend/pages/living-space/add/types.ts | 11 + .../living-space/tables/builds/columns.tsx | 4 +- .../living-space/tables/builds/data-table.tsx | 6 +- .../living-space/tables/company/columns.tsx | 127 +++++++++ .../tables/company/data-table.tsx | 267 ++++++++++++++++++ .../living-space/tables/company/page.tsx | 25 ++ .../living-space/tables/company/queries.tsx | 19 ++ .../living-space/tables/company/schema.tsx | 27 ++ frontend/pages/living-space/tables/page.tsx | 90 ------ .../living-space/tables/part/columns.tsx | 109 +++++++ .../living-space/tables/part/data-table.tsx | 267 ++++++++++++++++++ .../pages/living-space/tables/part/page.tsx | 35 +++ .../living-space/tables/part/queries.tsx | 18 ++ .../pages/living-space/tables/part/schema.tsx | 28 ++ .../living-space/tables/person/columns.tsx | 146 ++++++++++ .../living-space/tables/person/data-table.tsx | 267 ++++++++++++++++++ .../pages/living-space/tables/person/page.tsx | 24 ++ .../living-space/tables/person/queries.tsx | 19 ++ .../living-space/tables/person/schema.tsx | 27 ++ .../living-space/tables/userType/columns.tsx | 4 +- .../tables/userType/data-table.tsx | 21 +- .../living-space/tables/userType/page.tsx | 14 +- 57 files changed, 2434 insertions(+), 147 deletions(-) create mode 100644 backend/src/living-space/dto/create-living-space.input.ts create mode 100644 backend/src/living-space/dto/list-living-space.response.ts create mode 100644 backend/src/living-space/dto/update-living-space.input.ts create mode 100644 backend/src/living-space/living-space.module.ts create mode 100644 backend/src/living-space/living-space.resolver.spec.ts create mode 100644 backend/src/living-space/living-space.resolver.ts create mode 100644 backend/src/living-space/living-space.service.spec.ts create mode 100644 backend/src/living-space/living-space.service.ts create mode 100644 frontend/app/api/company/add/route.ts create mode 100644 frontend/app/api/company/add/schema.ts create mode 100644 frontend/app/api/company/delete/route.ts create mode 100644 frontend/app/api/company/list/route.ts create mode 100644 frontend/app/api/company/update/route.ts create mode 100644 frontend/app/api/company/update/schema.ts delete mode 100644 frontend/app/api/living-space/a.txt create mode 100644 frontend/app/api/living-space/add/route.ts create mode 100644 frontend/app/api/living-space/add/schema.ts create mode 100644 frontend/app/api/living-space/delete/route.ts create mode 100644 frontend/app/api/living-space/list/route.ts create mode 100644 frontend/app/api/living-space/update/route.ts create mode 100644 frontend/app/api/living-space/update/schema.ts create mode 100644 frontend/app/living-spaces/add/page.tsx create mode 100644 frontend/pages/living-space/add/form.tsx create mode 100644 frontend/pages/living-space/add/page.tsx create mode 100644 frontend/pages/living-space/add/queries.tsx create mode 100644 frontend/pages/living-space/add/schema.ts create mode 100644 frontend/pages/living-space/add/types.ts create mode 100644 frontend/pages/living-space/tables/company/columns.tsx create mode 100644 frontend/pages/living-space/tables/company/data-table.tsx create mode 100644 frontend/pages/living-space/tables/company/page.tsx create mode 100644 frontend/pages/living-space/tables/company/queries.tsx create mode 100644 frontend/pages/living-space/tables/company/schema.tsx delete mode 100644 frontend/pages/living-space/tables/page.tsx create mode 100644 frontend/pages/living-space/tables/part/columns.tsx create mode 100644 frontend/pages/living-space/tables/part/data-table.tsx create mode 100644 frontend/pages/living-space/tables/part/page.tsx create mode 100644 frontend/pages/living-space/tables/part/queries.tsx create mode 100644 frontend/pages/living-space/tables/part/schema.tsx create mode 100644 frontend/pages/living-space/tables/person/columns.tsx create mode 100644 frontend/pages/living-space/tables/person/data-table.tsx create mode 100644 frontend/pages/living-space/tables/person/page.tsx create mode 100644 frontend/pages/living-space/tables/person/queries.tsx create mode 100644 frontend/pages/living-space/tables/person/schema.tsx diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index a8c3df0..32ef5c0 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -15,6 +15,7 @@ import { BuildAddressModule } from './build-address/build-address.module'; import { BuildIbanModule } from './build-iban/build-iban.module'; import { BuildSitesModule } from './build-sites/build-sites.module'; import { CompanyModule } from './company/company.module'; +import { LivingSpaceModule } from './living-space/living-space.module'; @Module({ imports: [ @@ -35,6 +36,7 @@ import { CompanyModule } from './company/company.module'; BuildIbanModule, BuildSitesModule, CompanyModule, + LivingSpaceModule, ], controllers: [AppController], providers: [AppService], diff --git a/backend/src/company/company.resolver.ts b/backend/src/company/company.resolver.ts index 02f8274..7292ffe 100644 --- a/backend/src/company/company.resolver.ts +++ b/backend/src/company/company.resolver.ts @@ -3,22 +3,24 @@ import { CompanyService } from './company.service'; import { Company } from '@/models/company.model'; import { CreateCompanyInput } from './dto/create-company.input'; import { Types } from 'mongoose'; -import graphqlFields from 'graphql-fields'; -import type { GraphQLResolveInfo } from 'graphql'; import { ListCompanyResponse } from './dto/list-company.response'; import { ListArguments } from '@/dto/list.input'; +import { UpdateCompanyInput } from './dto/update-company.input'; +import type { GraphQLResolveInfo } from 'graphql'; +import graphqlFields from 'graphql-fields'; @Resolver() export class CompanyResolver { constructor(private readonly companyService: CompanyService) { } - @Query(() => ListCompanyResponse, { name: 'companies' }) + @Query(() => ListCompanyResponse, { name: 'getCompanies' }) async getCompanies(@Info() info: GraphQLResolveInfo, @Args('input') input: ListArguments): Promise { - const fields = graphqlFields(info); const projection = this.companyService.buildProjection(fields); return this.companyService.findAll(input.skip, input.limit, input.sort, input.filters); + const fields = graphqlFields(info); const projection = this.companyService.buildProjection(fields?.data); const { skip, limit, sort, filters } = input; + return await this.companyService.findAll(projection, skip, limit, sort, filters); } - @Query(() => Company, { name: 'company', nullable: true }) + @Query(() => Company, { name: 'getCompany', nullable: true }) async getCompany(@Args('id', { type: () => ID }) id: string, @Info() info: GraphQLResolveInfo): Promise { const fields = graphqlFields(info); const projection = this.companyService.buildProjection(fields); return this.companyService.findById(new Types.ObjectId(id), projection); } @@ -26,4 +28,10 @@ export class CompanyResolver { @Mutation(() => Company, { name: 'createCompany' }) async createCompany(@Args('input') input: CreateCompanyInput): Promise { return this.companyService.create(input) } + @Mutation(() => Company, { name: 'updateCompany' }) + async updateCompany(@Args('uuid') uuid: string, @Args('input') input: UpdateCompanyInput): Promise { return this.companyService.update(uuid, input) } + + @Mutation(() => Boolean, { name: 'deleteCompany' }) + async deleteCompany(@Args('uuid') uuid: string): Promise { return this.companyService.delete(uuid) } + } diff --git a/backend/src/company/dto/create-company.input.ts b/backend/src/company/dto/create-company.input.ts index 388838c..c876e86 100644 --- a/backend/src/company/dto/create-company.input.ts +++ b/backend/src/company/dto/create-company.input.ts @@ -1,11 +1,49 @@ - +import { ExpiryBaseInput } from '@/models/base.model'; import { InputType, Field, ID } from '@nestjs/graphql'; - @InputType() -export class CreateCompanyInput { +export class CreateCompanyInput extends ExpiryBaseInput { @Field() - name: string; + formal_name: string; + + @Field() + company_type: string; + + @Field() + commercial_type: string; + + @Field() + tax_no: string; + + @Field() + public_name: string; + + @Field() + company_tag: string; + + @Field({ defaultValue: 'TR' }) + default_lang_type: string; + + @Field({ defaultValue: 'TL' }) + default_money_type: string; + + @Field({ defaultValue: false }) + is_commercial?: boolean; + + @Field({ defaultValue: false }) + is_blacklist?: boolean; + + @Field() + parent_id?: string; + + @Field() + workplace_no?: string; + + @Field() + official_address?: string; + + @Field() + top_responsible_company?: string; } diff --git a/backend/src/company/dto/update-company.input.ts b/backend/src/company/dto/update-company.input.ts index c0370bf..19ddc50 100644 --- a/backend/src/company/dto/update-company.input.ts +++ b/backend/src/company/dto/update-company.input.ts @@ -1,9 +1,48 @@ -import { InputType, Field, ID } from '@nestjs/graphql'; +import { InputType, Field } from '@nestjs/graphql'; @InputType() export class UpdateCompanyInput { @Field({ nullable: true }) - name?: string; + formal_name?: string; + + @Field({ nullable: true }) + company_type?: string; + + @Field({ nullable: true }) + commercial_type?: string; + + @Field({ nullable: true }) + tax_no?: string; + + @Field({ nullable: true }) + public_name?: string; + + @Field({ nullable: true }) + company_tag?: string; + + @Field({ nullable: true }) + default_lang_type?: string; + + @Field({ nullable: true }) + default_money_type?: string; + + @Field({ nullable: true }) + is_commercial?: boolean; + + @Field({ nullable: true }) + is_blacklist?: boolean; + + @Field({ nullable: true }) + parent_id?: string; + + @Field({ nullable: true }) + workplace_no?: string; + + @Field({ nullable: true }) + official_address?: string; + + @Field({ nullable: true }) + top_responsible_company?: string; } diff --git a/backend/src/lib/getListOfModelByIDs.ts b/backend/src/lib/getListOfModelByIDs.ts index 3c1e46c..d29a15b 100644 --- a/backend/src/lib/getListOfModelByIDs.ts +++ b/backend/src/lib/getListOfModelByIDs.ts @@ -22,4 +22,3 @@ export async function cleanRefArrayField({ parentModel, refModel, parentId, fiel await parentModel.updateOne({ _id: parentId }, { $set: { [fieldName]: keptIds } }); return { keptIds, removedIds }; } - diff --git a/backend/src/living-space/dto/create-living-space.input.ts b/backend/src/living-space/dto/create-living-space.input.ts new file mode 100644 index 0000000..7ca34c4 --- /dev/null +++ b/backend/src/living-space/dto/create-living-space.input.ts @@ -0,0 +1,25 @@ +import { ExpiryBaseInput } from '@/models/base.model'; +import { InputType, Field } from '@nestjs/graphql'; + +@InputType() +export class CreateLivingSpaceInput extends ExpiryBaseInput { + + @Field() + buildID: string; + + @Field() + collectionToken: string; + + @Field() + partID: string; + + @Field() + userTypeID: string; + + @Field() + companyID: string; + + @Field() + personID: string; + +} diff --git a/backend/src/living-space/dto/list-living-space.response.ts b/backend/src/living-space/dto/list-living-space.response.ts new file mode 100644 index 0000000..e4b886c --- /dev/null +++ b/backend/src/living-space/dto/list-living-space.response.ts @@ -0,0 +1,14 @@ +import { Field, ObjectType } from "@nestjs/graphql"; +import { Int } from "@nestjs/graphql"; +import { LivingSpaces } from "@/models/living-spaces.model"; + +@ObjectType() +export class ListLivingSpaceResponse { + + @Field(() => [LivingSpaces], { nullable: true }) + data?: LivingSpaces[]; + + @Field(() => Int, { nullable: true }) + totalCount?: number; + +} \ No newline at end of file diff --git a/backend/src/living-space/dto/update-living-space.input.ts b/backend/src/living-space/dto/update-living-space.input.ts new file mode 100644 index 0000000..e50ddae --- /dev/null +++ b/backend/src/living-space/dto/update-living-space.input.ts @@ -0,0 +1,25 @@ +import { ExpiryBaseInput } from '@/models/base.model'; +import { InputType, Field } from '@nestjs/graphql'; + +@InputType() +export class UpdateLivingSpaceInput extends ExpiryBaseInput { + + @Field({ nullable: true }) + buildID?: string; + + @Field({ nullable: true }) + collectionToken?: string; + + @Field({ nullable: true }) + partID?: string; + + @Field({ nullable: true }) + userTypeID?: string; + + @Field({ nullable: true }) + companyID?: string; + + @Field({ nullable: true }) + personID?: string; + +} diff --git a/backend/src/living-space/living-space.module.ts b/backend/src/living-space/living-space.module.ts new file mode 100644 index 0000000..9dbd582 --- /dev/null +++ b/backend/src/living-space/living-space.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { LivingSpaceService } from './living-space.service'; +import { LivingSpaceResolver } from './living-space.resolver'; +import { MongooseModule } from '@nestjs/mongoose'; +import { LivingSpaces, LivingSpacesSchema } from '@/models/living-spaces.model'; + +@Module({ + imports: [MongooseModule.forFeature([{ name: LivingSpaces.name, schema: LivingSpacesSchema }])], + providers: [LivingSpaceService, LivingSpaceResolver] +}) +export class LivingSpaceModule { } diff --git a/backend/src/living-space/living-space.resolver.spec.ts b/backend/src/living-space/living-space.resolver.spec.ts new file mode 100644 index 0000000..393727c --- /dev/null +++ b/backend/src/living-space/living-space.resolver.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { LivingSpaceResolver } from './living-space.resolver'; + +describe('LivingSpaceResolver', () => { + let resolver: LivingSpaceResolver; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [LivingSpaceResolver], + }).compile(); + + resolver = module.get(LivingSpaceResolver); + }); + + it('should be defined', () => { + expect(resolver).toBeDefined(); + }); +}); diff --git a/backend/src/living-space/living-space.resolver.ts b/backend/src/living-space/living-space.resolver.ts new file mode 100644 index 0000000..559ff2c --- /dev/null +++ b/backend/src/living-space/living-space.resolver.ts @@ -0,0 +1,37 @@ +import { Resolver, Args, ID, Info, Mutation, Query } from '@nestjs/graphql'; +import { Types } from 'mongoose'; +import { ListArguments } from '@/dto/list.input'; +import { LivingSpaces } from '@/models/living-spaces.model'; +import { ListLivingSpaceResponse } from './dto/list-living-space.response'; +import { CreateLivingSpaceInput } from './dto/create-living-space.input'; +import { UpdateLivingSpaceInput } from './dto/update-living-space.input'; +import { LivingSpaceService } from './living-space.service'; +import type { GraphQLResolveInfo } from 'graphql'; +import graphqlFields from 'graphql-fields'; + +@Resolver() +export class LivingSpaceResolver { + + constructor(private readonly livingSpaceService: LivingSpaceService) { } + + @Query(() => ListLivingSpaceResponse, { name: 'getLivingSpaces' }) + async getLivingSpaces(@Info() info: GraphQLResolveInfo, @Args('input') input: ListArguments): Promise { + const fields = graphqlFields(info); const projection = this.livingSpaceService.buildProjection(fields?.data); const { skip, limit, sort, filters } = input; + return await this.livingSpaceService.findAll(projection, skip, limit, sort, filters); + } + + @Query(() => LivingSpaces, { name: 'getLivingSpace', nullable: true }) + async getLivingSpace(@Args('id', { type: () => ID }) id: string, @Info() info: GraphQLResolveInfo): Promise { + const fields = graphqlFields(info); const projection = this.livingSpaceService.buildProjection(fields); return this.livingSpaceService.findById(new Types.ObjectId(id), projection); + } + + @Mutation(() => LivingSpaces, { name: 'createLivingSpace' }) + async createLivingSpace(@Args('input') input: CreateLivingSpaceInput): Promise { return this.livingSpaceService.create(input) } + + @Mutation(() => LivingSpaces, { name: 'updateLivingSpace' }) + async updateLivingSpace(@Args('uuid') uuid: string, @Args('input') input: UpdateLivingSpaceInput): Promise { return this.livingSpaceService.update(uuid, input) } + + @Mutation(() => Boolean, { name: 'deleteLivingSpace' }) + async deleteLivingSpace(@Args('uuid') uuid: string, @Args('collectionToken') collectionToken: string): Promise { return this.livingSpaceService.delete(uuid, collectionToken) } + +} diff --git a/backend/src/living-space/living-space.service.spec.ts b/backend/src/living-space/living-space.service.spec.ts new file mode 100644 index 0000000..128d179 --- /dev/null +++ b/backend/src/living-space/living-space.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { LivingSpaceService } from './living-space.service'; + +describe('LivingSpaceService', () => { + let service: LivingSpaceService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [LivingSpaceService], + }).compile(); + + service = module.get(LivingSpaceService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/backend/src/living-space/living-space.service.ts b/backend/src/living-space/living-space.service.ts new file mode 100644 index 0000000..86a0af6 --- /dev/null +++ b/backend/src/living-space/living-space.service.ts @@ -0,0 +1,74 @@ +import { Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Types, Model, Connection } from 'mongoose'; +import { getDynamicLivingSpaceModel, LivingSpaces, LivingSpacesDocument } from '@/models/living-spaces.model'; +import { ListLivingSpaceResponse } from './dto/list-living-space.response'; +import { CreateLivingSpaceInput } from './dto/create-living-space.input'; +import { UpdateLivingSpaceInput } from './dto/update-living-space.input'; +import { InjectConnection } from '@nestjs/mongoose'; + +interface UpdateInput { + build?: Types.ObjectId; + collectionToken?: string; + userType?: Types.ObjectId; + part?: Types.ObjectId; + company?: Types.ObjectId; + person?: Types.ObjectId; + expiryStarts?: Date; + expiryEnds?: Date; +} + +@Injectable() +export class LivingSpaceService { + + constructor( + @InjectModel(LivingSpaces.name) private readonly livingSpaceModel: Model, + @InjectConnection() private readonly connection: Connection + ) { } + + async findAll(projection: any, skip: number, limit: number, sort?: Record, filters?: Record): Promise { + if (!filters?.collectionToken) { throw new Error('Collection token is required') } + const query: any = {}; if (filters && Object.keys(filters).length > 0) { Object.assign(query, filters) }; + const totalCount = await this.livingSpaceModel.countDocuments(query).exec(); + const data = await this.livingSpaceModel.find(query, projection, { lean: true }).skip(skip).limit(limit).sort(sort).exec(); + return { data, totalCount }; + } + + async findById(id: Types.ObjectId, projection?: any): Promise { + if (!projection?.collectionToken) { throw new Error('Collection token is required') } + const selectedModel = getDynamicLivingSpaceModel(projection.collectionToken, this.connection); + return selectedModel.findById(id, projection, { lean: false }).populate({ path: 'company', select: projection?.company }).exec(); + } + + async create(input: CreateLivingSpaceInput): Promise { + if (!input.collectionToken) { throw new Error('Collection token is required') } + const LivingSpaceModel = getDynamicLivingSpaceModel(input.collectionToken, this.connection); + const docInput: Partial = { + build: new Types.ObjectId(input.buildID), part: new Types.ObjectId(input.partID), userType: new Types.ObjectId(input.userTypeID), + company: new Types.ObjectId(input.companyID), person: new Types.ObjectId(input.personID), + expiryStarts: input.expiryStarts ? new Date(input.expiryStarts) : new Date(), expiryEnds: input.expiryEnds ? new Date(input.expiryEnds) : new Date('2099-12-31'), + }; const doc = new LivingSpaceModel(docInput); return await doc.save() + } + + async update(uuid: string, input: UpdateLivingSpaceInput): Promise { + if (!input.collectionToken) { throw new Error('Collection token is required') } + const selectedModel = getDynamicLivingSpaceModel(input.collectionToken, this.connection); + const newInput: UpdateInput = {} + if (input?.buildID) { newInput.build = new Types.ObjectId(input.buildID) }; if (input?.userTypeID) { newInput.userType = new Types.ObjectId(input.userTypeID) } + if (input?.partID) { newInput.part = new Types.ObjectId(input.partID) }; if (input?.companyID) { newInput.company = new Types.ObjectId(input.companyID) } + if (input?.personID) { newInput.person = new Types.ObjectId(input.personID) }; + if (input?.expiryStarts) { newInput.expiryStarts = input.expiryStarts }; if (input?.expiryEnds) { newInput.expiryEnds = input.expiryEnds } + const company = await selectedModel.findOne({ uuid }); if (!company) { throw new Error('Company not found') }; company.set(newInput); return company.save(); + } + + async delete(uuid: string, collectionToken: string): Promise { if (!collectionToken) { throw new Error('Collection token is required') } const selectedModel = getDynamicLivingSpaceModel(collectionToken, this.connection); const company = await selectedModel.deleteMany({ uuid }); return company.deletedCount > 0 } + + buildProjection(fields: Record): any { + const projection: any = {}; + for (const key in fields) { + if (key === 'buildSites' && typeof fields[key] === 'object') { for (const subField of Object.keys(fields[key])) { projection[`buildSites.${subField}`] = 1 } } + else { projection[key] = 1 } + }; return projection; + } + +} diff --git a/backend/src/models/base.model.ts b/backend/src/models/base.model.ts index f1c7488..f22db60 100644 --- a/backend/src/models/base.model.ts +++ b/backend/src/models/base.model.ts @@ -90,8 +90,6 @@ export class CreatedBase { } - - @ObjectType({ isAbstract: true }) export class ExpiryBase { diff --git a/backend/src/models/company.model.ts b/backend/src/models/company.model.ts index de2c42c..81f0b04 100644 --- a/backend/src/models/company.model.ts +++ b/backend/src/models/company.model.ts @@ -10,9 +10,61 @@ export class Company extends Base { @Field(() => ID) readonly _id: string; - @Field(() => ID) + @Field() @Prop({ required: true }) - name: string; + formal_name: string; + + @Field() + @Prop({ required: true }) + company_type: string; + + @Field() + @Prop({ required: true }) + commercial_type: string; + + @Field() + @Prop({ required: true }) + tax_no: string; + + @Field() + @Prop({ required: true }) + public_name: string; + + @Field() + @Prop({ required: true }) + company_tag: string; + + @Field() + @Prop({ required: true, default: 'TR' }) + default_lang_type: string; + + @Field() + @Prop({ required: true, default: 'TL' }) + default_money_type: string; + + @Field() + @Prop({ required: true, default: false }) + is_commercial: boolean; + + @Field() + @Prop({ required: true, default: false }) + is_blacklist: boolean; + + @Field() + @Prop({ required: false }) + parent_id: string; + + @Field() + @Prop({ required: false }) + workplace_no: string; + + @Field() + @Prop({ required: false }) + official_address: string; + + @Field() + @Prop({ required: false }) + top_responsible_company: string; } diff --git a/backend/src/models/living-spaces.model.ts b/backend/src/models/living-spaces.model.ts index 6706a65..c13a189 100644 --- a/backend/src/models/living-spaces.model.ts +++ b/backend/src/models/living-spaces.model.ts @@ -1,6 +1,5 @@ -import { Document, Model, Types, Connection, connection as defaultConnection } from 'mongoose'; -import { randomUUID } from 'crypto'; -import { Prop, SchemaFactory } from '@nestjs/mongoose'; +import { Document, Model, Types, Connection } from 'mongoose'; +import { Prop, SchemaFactory, Schema } from '@nestjs/mongoose'; import { Field, ID, ObjectType } from '@nestjs/graphql'; import { Base } from '@/models/base.model'; import { Build } from '@/models/build.model'; @@ -9,9 +8,14 @@ import { Person } from '@/models/person.model'; import { Company } from '@/models/company.model'; import { UserType } from '@/models/user-type.model'; + @ObjectType() +@Schema({ timestamps: true }) export class LivingSpaces extends Base { + @Field(() => ID) + readonly _id: string + @Field(() => ID) @Prop({ type: Types.ObjectId, ref: Build.name, required: true }) build: Types.ObjectId; @@ -37,9 +41,9 @@ export class LivingSpaces extends Base { export type LivingSpacesDocument = LivingSpaces & Document; export const LivingSpacesSchema = SchemaFactory.createForClass(LivingSpaces); -export function getDynamicLivingSpaceModel(collectionToken: string, conn?: Connection): Model { - const connection = conn || defaultConnection; +export function getDynamicLivingSpaceModel(collectionToken: string, connection: Connection): Model { const collectionName = `LivingSpaces${collectionToken}`; + const schema = SchemaFactory.createForClass(LivingSpaces); if (connection.models[collectionName]) { return connection.models[collectionName] as Model } - throw new Error('No Living Spaces is found') + return connection.model(collectionName, schema, collectionName) as unknown as Model; } diff --git a/frontend/app/api/company/add/route.ts b/frontend/app/api/company/add/route.ts new file mode 100644 index 0000000..81cb0d9 --- /dev/null +++ b/frontend/app/api/company/add/route.ts @@ -0,0 +1,21 @@ +'use server'; +import { NextResponse } from 'next/server'; +import { GraphQLClient, gql } from 'graphql-request'; +import { CreateCompanyInputSchema } from './schema'; + +const endpoint = "http://localhost:3001/graphql"; + +export async function POST(request: Request) { + const body = await request.json(); + const validatedBody = CreateCompanyInputSchema.parse(body); + try { + const client = new GraphQLClient(endpoint); + const query = gql`mutation createCompany($input: CreateCompanyInput!) { createCompany(input: $input) {_id}}`; + const variables = { input: validatedBody }; + const data = await client.request(query, variables); + return NextResponse.json({ data: data.createCompany, status: 200 }); + } catch (err: any) { + console.error(err); + return NextResponse.json({ error: err.message }, { status: 500 }); + } +} diff --git a/frontend/app/api/company/add/schema.ts b/frontend/app/api/company/add/schema.ts new file mode 100644 index 0000000..860d6af --- /dev/null +++ b/frontend/app/api/company/add/schema.ts @@ -0,0 +1,26 @@ +import { z } from "zod" + +export const CreateCompanyInputSchema = z.object({ + formal_name: z.string(), + company_type: z.string(), + commercial_type: z.string(), + tax_no: z.string(), + public_name: z.string(), + company_tag: z.string(), + + default_lang_type: z.string().default("TR"), + default_money_type: z.string().default("TL"), + + is_commercial: z.boolean().default(false), + is_blacklist: z.boolean().default(false), + + parent_id: z.string().optional(), + workplace_no: z.string().optional(), + official_address: z.string().optional(), + top_responsible_company: z.string().optional(), + expiryStarts: z.string().optional(), + expiryEnds: z.string().optional(), + +}); + +export type CreateCompanyInput = z.infer; diff --git a/frontend/app/api/company/delete/route.ts b/frontend/app/api/company/delete/route.ts new file mode 100644 index 0000000..0a53c78 --- /dev/null +++ b/frontend/app/api/company/delete/route.ts @@ -0,0 +1,23 @@ +'use server'; +import { NextResponse } from 'next/server'; +import { GraphQLClient, gql } from 'graphql-request'; + +const endpoint = "http://localhost:3001/graphql"; + +export async function GET(request: Request) { + + const searchParams = new URL(request.url).searchParams; + const uuid = searchParams.get('uuid'); + console.dir({ uuid }, { depth: null }); + if (!uuid) { return NextResponse.json({ error: 'UUID not found in search params' }, { status: 400 }) } + try { + const client = new GraphQLClient(endpoint); + const query = gql`mutation DeleteCompany($uuid: String!) { deleteCompany(uuid: $uuid) }`; + const data = await client.request(query, { uuid }); + return NextResponse.json({ data: data.deleteUserType, status: 200 }); + } catch (err: any) { + console.error(err); + return NextResponse.json({ error: err.message }, { status: 500 }); + } + +} diff --git a/frontend/app/api/company/list/route.ts b/frontend/app/api/company/list/route.ts new file mode 100644 index 0000000..2582ac9 --- /dev/null +++ b/frontend/app/api/company/list/route.ts @@ -0,0 +1,47 @@ +'use server'; +import { NextResponse } from 'next/server'; +import { GraphQLClient, gql } from 'graphql-request'; + +const endpoint = "http://localhost:3001/graphql"; + +export async function POST(request: Request) { + const body = await request.json(); + const { limit, skip, sort, filters } = body; + try { + const client = new GraphQLClient(endpoint); + const query = gql` + query GetCompanies($input: ListArguments!) { + getCompanies(input:$input) { + data { + _id + uuid + company_type + commercial_type + tax_no + default_lang_type + default_money_type + is_commercial + is_blacklist + workplace_no + official_address + top_responsible_company + parent_id + company_tag + formal_name + public_name + parent_id + expiryStarts + expiryEnds + } + totalCount + } + } + `; + const variables = { input: { limit, skip, sort, filters } }; + const data = await client.request(query, variables); + return NextResponse.json({ data: data.getCompanies.data, totalCount: data.getCompanies.totalCount }); + } catch (err: any) { + console.error(err); + return NextResponse.json({ error: err.message }, { status: 500 }); + } +} diff --git a/frontend/app/api/company/update/route.ts b/frontend/app/api/company/update/route.ts new file mode 100644 index 0000000..947c1ca --- /dev/null +++ b/frontend/app/api/company/update/route.ts @@ -0,0 +1,24 @@ +'use server'; +import { NextResponse } from 'next/server'; +import { GraphQLClient, gql } from 'graphql-request'; +import { UpdateCompanyInputSchema } from './schema'; + +const endpoint = "http://localhost:3001/graphql"; + +export async function POST(request: Request) { + const searchUrl = new URL(request.url); + const uuid = searchUrl.searchParams.get("uuid") || ""; + if (uuid === "") { return NextResponse.json({ error: "UUID is required" }, { status: 400 }) } + const body = await request.json(); + const validatedBody = UpdateCompanyInputSchema.parse(body); + try { + const client = new GraphQLClient(endpoint); + const query = gql`mutation UpdateCompany($uuid: String!, $input: UpdateCompanyInput!) { updateCompany(uuid:$uuid, input: $input) {_id} }`; + const variables = { uuid: uuid, input: { ...validatedBody } }; + const data = await client.request(query, variables); + return NextResponse.json({ data: data.updateCompany, status: 200 }); + } catch (err: any) { + console.error(err); + return NextResponse.json({ error: err.message }, { status: 500 }); + } +} diff --git a/frontend/app/api/company/update/schema.ts b/frontend/app/api/company/update/schema.ts new file mode 100644 index 0000000..081dbb0 --- /dev/null +++ b/frontend/app/api/company/update/schema.ts @@ -0,0 +1,23 @@ +import { z } from "zod" + +export const UpdateCompanyInputSchema = z.object({ + formal_name: z.string(), + company_type: z.string(), + commercial_type: z.string(), + tax_no: z.string(), + public_name: z.string(), + company_tag: z.string(), + default_lang_type: z.string().default("TR"), + default_money_type: z.string().default("TL"), + is_commercial: z.boolean().default(false), + is_blacklist: z.boolean().default(false), + parent_id: z.string().optional(), + workplace_no: z.string().optional(), + official_address: z.string().optional(), + top_responsible_company: z.string().optional(), + expiryStarts: z.string().optional(), + expiryEnds: z.string().optional(), + +}); + +export type UpdateCompanyInput = z.infer; diff --git a/frontend/app/api/living-space/a.txt b/frontend/app/api/living-space/a.txt deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/app/api/living-space/add/route.ts b/frontend/app/api/living-space/add/route.ts new file mode 100644 index 0000000..b150bdb --- /dev/null +++ b/frontend/app/api/living-space/add/route.ts @@ -0,0 +1,21 @@ +'use server'; +import { NextResponse } from 'next/server'; +import { GraphQLClient, gql } from 'graphql-request'; +import { CreateLivingSpaceInputSchema } from './schema'; + +const endpoint = "http://localhost:3001/graphql"; + +export async function POST(request: Request) { + const body = await request.json(); + const validatedBody = CreateLivingSpaceInputSchema.parse(body); + try { + const client = new GraphQLClient(endpoint); + const query = gql`mutation CreateLivingSpace($input: CreateLivingSpaceInput!) { createLivingSpace(input:$input) { _id }}`; + const variables = { input: validatedBody }; + const data = await client.request(query, variables); + return NextResponse.json({ data: data.createLivingSpace, status: 200 }); + } catch (err: any) { + console.error(err); + return NextResponse.json({ error: err.message }, { status: 500 }); + } +} diff --git a/frontend/app/api/living-space/add/schema.ts b/frontend/app/api/living-space/add/schema.ts new file mode 100644 index 0000000..1cef649 --- /dev/null +++ b/frontend/app/api/living-space/add/schema.ts @@ -0,0 +1,14 @@ +import { z } from "zod" + +export const CreateLivingSpaceInputSchema = z.object({ + buildID: z.string(), + collectionToken: z.string(), + partID: z.string(), + userTypeID: z.string(), + companyID: z.string(), + personID: z.string(), + expiryStarts: z.string().optional(), + expiryEnds: z.string().optional(), +}); + +export type CreateLivingSpaceInput = z.infer; diff --git a/frontend/app/api/living-space/delete/route.ts b/frontend/app/api/living-space/delete/route.ts new file mode 100644 index 0000000..0a53c78 --- /dev/null +++ b/frontend/app/api/living-space/delete/route.ts @@ -0,0 +1,23 @@ +'use server'; +import { NextResponse } from 'next/server'; +import { GraphQLClient, gql } from 'graphql-request'; + +const endpoint = "http://localhost:3001/graphql"; + +export async function GET(request: Request) { + + const searchParams = new URL(request.url).searchParams; + const uuid = searchParams.get('uuid'); + console.dir({ uuid }, { depth: null }); + if (!uuid) { return NextResponse.json({ error: 'UUID not found in search params' }, { status: 400 }) } + try { + const client = new GraphQLClient(endpoint); + const query = gql`mutation DeleteCompany($uuid: String!) { deleteCompany(uuid: $uuid) }`; + const data = await client.request(query, { uuid }); + return NextResponse.json({ data: data.deleteUserType, status: 200 }); + } catch (err: any) { + console.error(err); + return NextResponse.json({ error: err.message }, { status: 500 }); + } + +} diff --git a/frontend/app/api/living-space/list/route.ts b/frontend/app/api/living-space/list/route.ts new file mode 100644 index 0000000..2582ac9 --- /dev/null +++ b/frontend/app/api/living-space/list/route.ts @@ -0,0 +1,47 @@ +'use server'; +import { NextResponse } from 'next/server'; +import { GraphQLClient, gql } from 'graphql-request'; + +const endpoint = "http://localhost:3001/graphql"; + +export async function POST(request: Request) { + const body = await request.json(); + const { limit, skip, sort, filters } = body; + try { + const client = new GraphQLClient(endpoint); + const query = gql` + query GetCompanies($input: ListArguments!) { + getCompanies(input:$input) { + data { + _id + uuid + company_type + commercial_type + tax_no + default_lang_type + default_money_type + is_commercial + is_blacklist + workplace_no + official_address + top_responsible_company + parent_id + company_tag + formal_name + public_name + parent_id + expiryStarts + expiryEnds + } + totalCount + } + } + `; + const variables = { input: { limit, skip, sort, filters } }; + const data = await client.request(query, variables); + return NextResponse.json({ data: data.getCompanies.data, totalCount: data.getCompanies.totalCount }); + } catch (err: any) { + console.error(err); + return NextResponse.json({ error: err.message }, { status: 500 }); + } +} diff --git a/frontend/app/api/living-space/update/route.ts b/frontend/app/api/living-space/update/route.ts new file mode 100644 index 0000000..71effe8 --- /dev/null +++ b/frontend/app/api/living-space/update/route.ts @@ -0,0 +1,24 @@ +'use server'; +import { NextResponse } from 'next/server'; +import { GraphQLClient, gql } from 'graphql-request'; +import { UpdateLivingSpaceInputSchema } from './schema'; + +const endpoint = "http://localhost:3001/graphql"; + +export async function POST(request: Request) { + const searchUrl = new URL(request.url); + const uuid = searchUrl.searchParams.get("uuid") || ""; + if (uuid === "") { return NextResponse.json({ error: "UUID is required" }, { status: 400 }) } + const body = await request.json(); + const validatedBody = UpdateLivingSpaceInputSchema.parse(body); + try { + const client = new GraphQLClient(endpoint); + const query = gql`mutation UpdateLivingSpace($uuid: String!, $input: UpdateLivingSpaceInput!) { updateLivingSpace(uuid:$uuid, input: $input) {_id} }`; + const variables = { uuid: uuid, input: { ...validatedBody } }; + const data = await client.request(query, variables); + return NextResponse.json({ data: data.updateLivingSpace, status: 200 }); + } catch (err: any) { + console.error(err); + return NextResponse.json({ error: err.message }, { status: 500 }); + } +} diff --git a/frontend/app/api/living-space/update/schema.ts b/frontend/app/api/living-space/update/schema.ts new file mode 100644 index 0000000..19721ac --- /dev/null +++ b/frontend/app/api/living-space/update/schema.ts @@ -0,0 +1,14 @@ +import { z } from "zod" + +export const UpdateLivingSpaceInputSchema = z.object({ + buildID: z.string(), + collectionToken: z.string(), + partID: z.string(), + userTypeID: z.string(), + companyID: z.string(), + personID: z.string(), + expiryStarts: z.string().optional(), + expiryEnds: z.string().optional(), +}); + +export type UpdateLivingSpaceInput = z.infer; diff --git a/frontend/app/living-spaces/add/page.tsx b/frontend/app/living-spaces/add/page.tsx new file mode 100644 index 0000000..805b504 --- /dev/null +++ b/frontend/app/living-spaces/add/page.tsx @@ -0,0 +1,5 @@ +import { PageLivingSpaceAdd } from "@/pages/living-space/add/page"; + +const SSRPageLivingSpaceAdd = () => { return <> } + +export default SSRPageLivingSpaceAdd; diff --git a/frontend/app/living-spaces/page.tsx b/frontend/app/living-spaces/page.tsx index a1f3e30..5355cc1 100644 --- a/frontend/app/living-spaces/page.tsx +++ b/frontend/app/living-spaces/page.tsx @@ -1,5 +1,5 @@ -import { PageLivingSpace } from "@/pages/living-space/tables/page" +// import { PageLivingSpace } from "@/pages/living-space/add/page" -const SSRPageLivingSpace = () => { return <> } +const SSRPageLivingSpace = () => { return <>
PageLivingSpace
} export default SSRPageLivingSpace; diff --git a/frontend/pages/living-space/add/form.tsx b/frontend/pages/living-space/add/form.tsx new file mode 100644 index 0000000..b6d7744 --- /dev/null +++ b/frontend/pages/living-space/add/form.tsx @@ -0,0 +1,137 @@ +import { Card, CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { XCircle } from "lucide-react"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { DateTimePicker } from "@/components/ui/date-time-picker"; +import { FormValues } from "./schema"; +import { Label } from "@/components/ui/label"; + +const FormAddNewLivingSpace = ({ form, onSubmit, deleteAllSelections }: { form: any, onSubmit: (data: FormValues) => void, deleteAllSelections: () => void }) => { + return <> + + +
+ +
+ ( + + Build ID + + + + + + )} + /> + ( + + Collection Token + + + + + + )} + /> + ( + + User Type ID + + + + + + )} + /> + ( + + Part ID + + + + + + )} + /> + ( + + Company ID + + + + + + )} + /> + ( + + Person ID + + + + + + )} + /> +
+ {/* EXPIRY DATES */} +
+ ( + + Expiry Starts + + + + + + )} + /> + ( + + Expiry Ends + + + + + + )} + /> +
+
+ + +
+
+ +
+
+ +} + +export { FormAddNewLivingSpace }; \ No newline at end of file diff --git a/frontend/pages/living-space/add/page.tsx b/frontend/pages/living-space/add/page.tsx new file mode 100644 index 0000000..5a14f66 --- /dev/null +++ b/frontend/pages/living-space/add/page.tsx @@ -0,0 +1,76 @@ +'use client'; +import { useState, useEffect } from "react"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { FormValues, createForm } from "./schema"; +import { FormAddNewLivingSpace } from "./form"; + +import PageLivingSpaceBuildsTableSection from "../tables/builds/page"; +import PageLivingSpaceUserTypesTableSection from "../tables/userType/page"; +import PageLivingSpacePartsTableSection from "../tables/part/page"; +import PageLivingSpacePersonTableSection from "../tables/person/page"; +import PageLivingSpaceCompanyTableSection from "../tables/company/page"; +import { useAddLivingSpaceMutation } from "./queries"; + +const PageLivingSpaceAdd = () => { + const [collectionToken, setCollectionToken] = useState(null); + const [buildID, setBuildID] = useState(null); + const [userTypeID, setUserTypeID] = useState(null); + const [partID, setPartID] = useState(null); + const [companyID, setCompanyID] = useState(null); + const [personID, setPersonID] = useState(null); + + const form = createForm({ buildID, collectionToken, userTypeID, partID, companyID, personID }); + + useEffect(() => { + form.setValue("buildID", buildID || ""); + form.setValue("collectionToken", collectionToken || ""); + form.setValue("userTypeID", userTypeID || ""); + form.setValue("partID", partID || ""); + form.setValue("companyID", companyID || ""); + form.setValue("personID", personID || ""); + }, [buildID, collectionToken, userTypeID, partID, companyID, personID, form]); + + const mutation = useAddLivingSpaceMutation(); + function onSubmit(values: FormValues) { mutation.mutate({ data: values }) } + console.dir({ input: form.getValues() }) + + const [isUserTypeEnabled, setIsUserTypeEnabled] = useState(false); + const [isPartsEnabled, setIsPartsEnabled] = useState(false); + const [isHandleCompanyAndPersonEnable, setIsHandleCompanyAndPersonEnable] = useState(false); + + const tabsClassName = "border border-gray-300 rounded-sm h-10" + const deleteAllSelections = () => { + setBuildID(null); setCollectionToken(null); setUserTypeID(null); setPartID(null); setCompanyID(null); setPersonID(null); + setIsUserTypeEnabled(false); setIsPartsEnabled(false); setIsHandleCompanyAndPersonEnable(false) + } + + return <> +
+ + + Builds + {isUserTypeEnabled && User Type} + {isPartsEnabled && Parts} + {isHandleCompanyAndPersonEnable && Company} + {isHandleCompanyAndPersonEnable && Person} + +
+ + + + {isUserTypeEnabled && + + } + {isPartsEnabled && buildID && + + } + {isHandleCompanyAndPersonEnable && } + {isHandleCompanyAndPersonEnable && } +
+
+
+
+ +} + +export { PageLivingSpaceAdd }; \ No newline at end of file diff --git a/frontend/pages/living-space/add/queries.tsx b/frontend/pages/living-space/add/queries.tsx new file mode 100644 index 0000000..415697e --- /dev/null +++ b/frontend/pages/living-space/add/queries.tsx @@ -0,0 +1,58 @@ +'use client' +import { z } from 'zod' +import { useMutation } from '@tanstack/react-query' +import { toISOIfNotZ } from '@/lib/utils' + +export const formSchema = z.object({ + buildID: z.string({ error: "Build ID is required" }), + collectionToken: z.string({ error: "Collection Token is required" }), + userTypeID: z.string({ error: "User Type ID is required" }), + partID: z.string({ error: "Part ID is required" }), + companyID: z.string({ error: "Company ID is required" }), + personID: z.string({ error: "Person ID is required" }), + expiryStarts: z.string().optional(), + expiryEnds: z.string().optional(), +}); + +export type schemaType = z.infer; + +const fetchGraphQlLivingSpaceAdd = async (record: schemaType): Promise<{ data: schemaType | null; status: number }> => { + console.log('Fetching test data from local API'); + record.expiryStarts = record.expiryStarts ? toISOIfNotZ(record.expiryStarts) : undefined; + record.expiryEnds = record.expiryEnds ? toISOIfNotZ(record.expiryEnds) : undefined; + console.dir({ record }) + try { + const res = await fetch('/api/living-spaces/add', { method: 'POST', cache: 'no-store', credentials: "include", body: JSON.stringify(record) }); + if (!res.ok) { const errorText = await res.text(); console.error('Test data API error:', errorText); throw new Error(`API error: ${res.status} ${res.statusText}`) } + const data = await res.json(); + return { data: data.data, status: res.status } + } catch (error) { console.error('Error fetching test data:', error); throw error } +}; + +const fetchGraphQlLivingSpaceUpdate = async (record: schemaType, uuid: string): Promise<{ data: schemaType | null; status: number }> => { + console.log('Fetching test data from local API'); + record.expiryStarts = record.expiryStarts ? toISOIfNotZ(record.expiryStarts) : undefined; + record.expiryEnds = record.expiryEnds ? toISOIfNotZ(record.expiryEnds) : undefined; + try { + const res = await fetch(`/api/living-spaces/update?uuid=${uuid || ''}`, { method: 'POST', cache: 'no-store', credentials: "include", body: JSON.stringify(record) }); + if (!res.ok) { const errorText = await res.text(); console.error('Test data API error:', errorText); throw new Error(`API error: ${res.status} ${res.statusText}`) } + const data = await res.json(); + return { data: data.data, status: res.status } + } catch (error) { console.error('Error fetching test data:', error); throw error } +}; + +export function useAddLivingSpaceMutation() { + return useMutation({ + mutationFn: ({ data }: { data: schemaType }) => fetchGraphQlLivingSpaceAdd(data), + onSuccess: () => { console.log("Living Space added successfully") }, + onError: (error) => { console.error("Add Living Space add failed:", error) }, + }) +} + +export function useUpdateLivingSpaceMutation() { + return useMutation({ + mutationFn: ({ data, uuid }: { data: schemaType, uuid: string }) => fetchGraphQlLivingSpaceUpdate(data, uuid), + onSuccess: () => { console.log("Living Space updated successfully") }, + onError: (error) => { console.error("Update Living Space update failed:", error) }, + }) +} diff --git a/frontend/pages/living-space/add/schema.ts b/frontend/pages/living-space/add/schema.ts new file mode 100644 index 0000000..6a78576 --- /dev/null +++ b/frontend/pages/living-space/add/schema.ts @@ -0,0 +1,33 @@ +import * as z from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { FormProps } from "./types"; + +export const formSchema = z.object({ + buildID: z.string({ error: "Build ID is required" }), + collectionToken: z.string({ error: "Collection Token is required" }), + userTypeID: z.string({ error: "User Type ID is required" }), + partID: z.string({ error: "Part ID is required" }), + companyID: z.string({ error: "Company ID is required" }), + personID: z.string({ error: "Person ID is required" }), + expiryStarts: z.string().optional(), + expiryEnds: z.string().optional(), +}); + +export type FormValues = z.infer; + +export function createForm({ buildID, collectionToken, userTypeID, partID, companyID, personID }: FormProps) { + return useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + buildID: buildID || "", + collectionToken: collectionToken || "", + userTypeID: userTypeID || "", + partID: partID || "", + companyID: companyID || "", + personID: personID || "", + expiryStarts: "", + expiryEnds: "" + } + }); +} diff --git a/frontend/pages/living-space/add/types.ts b/frontend/pages/living-space/add/types.ts new file mode 100644 index 0000000..d5c0ba0 --- /dev/null +++ b/frontend/pages/living-space/add/types.ts @@ -0,0 +1,11 @@ +interface FormProps { + buildID: string | null; + collectionToken: string | null; + userTypeID: string | null; + partID: string | null; + companyID: string | null; + personID: string | null; +} + + +export type { FormProps }; \ No newline at end of file diff --git a/frontend/pages/living-space/tables/builds/columns.tsx b/frontend/pages/living-space/tables/builds/columns.tsx index 231e76f..9c6ac39 100644 --- a/frontend/pages/living-space/tables/builds/columns.tsx +++ b/frontend/pages/living-space/tables/builds/columns.tsx @@ -10,11 +10,11 @@ import { dateToLocaleString } from "@/lib/utils" import { Pencil, Trash } from "lucide-react" import { IconHandClick } from "@tabler/icons-react" -export function DraggableRow({ row }: { row: Row> }) { +export function DraggableRow({ row, selectedID }: { row: Row>; selectedID: string }) { const { transform, transition, setNodeRef, isDragging } = useSortable({ id: row.original._id }) return ( {row.getVisibleCells().map((cell) => ( diff --git a/frontend/pages/living-space/tables/builds/data-table.tsx b/frontend/pages/living-space/tables/builds/data-table.tsx index 0e6e14f..c21f6f0 100644 --- a/frontend/pages/living-space/tables/builds/data-table.tsx +++ b/frontend/pages/living-space/tables/builds/data-table.tsx @@ -171,10 +171,6 @@ export function LivingSpaceBuildDataTable({ })} - {/* */} @@ -196,7 +192,7 @@ export function LivingSpaceBuildDataTable({ {table.getRowModel().rows?.length ? ( - {table.getRowModel().rows.map((row) => )} + {table.getRowModel().rows.map((row) => )} ) : ( No results. )} diff --git a/frontend/pages/living-space/tables/company/columns.tsx b/frontend/pages/living-space/tables/company/columns.tsx new file mode 100644 index 0000000..c5aaa31 --- /dev/null +++ b/frontend/pages/living-space/tables/company/columns.tsx @@ -0,0 +1,127 @@ +"use client" +import { z } from "zod" +import { Button } from "@/components/ui/button" +import { Drawer, DrawerClose, DrawerContent, DrawerFooter, DrawerHeader, DrawerTrigger } from "@/components/ui/drawer" +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { useSortable } from "@dnd-kit/sortable" +import { IconGripVertical, IconHandClick } from "@tabler/icons-react" +import { useIsMobile } from "@/hooks/use-mobile" +import { Separator } from "@/components/ui/separator" +import { ColumnDef, flexRender, Row } from "@tanstack/react-table" +import { TableCell, TableRow } from "@/components/ui/table" +import { CSS } from "@dnd-kit/utilities" +import { schema, schemaType } from "./schema" +import { dateToLocaleString } from "@/lib/utils" +import { Pencil, Trash } from "lucide-react" + + +export function DraggableRow({ row, selectedID }: { row: Row>; selectedID: string }) { + const { transform, transition, setNodeRef, isDragging } = useSortable({ id: row.original._id }) + return ( + + {row.getVisibleCells().map((cell) => ( + {flexRender(cell.column.columnDef.cell, cell.getContext())} + ))} + + ) +} + +function getColumns(selectionHandler: (id: string) => void): ColumnDef[] { + return [ + { + accessorKey: "uuid", + header: "UUID", + cell: ({ getValue }) => (
{String(getValue())}
), + }, + { + accessorKey: "formal_name", + header: "Formal Name", + cell: ({ getValue }) => getValue(), + }, + { + accessorKey: "company_type", + header: "Company Type", + cell: ({ getValue }) => getValue(), + }, + { + accessorKey: "commercial_type", + header: "Commercial Type", + cell: ({ getValue }) => getValue(), + }, + { + accessorKey: "tax_no", + header: "Tax No", + cell: ({ getValue }) => getValue(), + }, + { + accessorKey: "public_name", + header: "Public Name", + cell: ({ getValue }) => getValue(), + }, + { + accessorKey: "company_tag", + header: "Company Tag", + cell: ({ getValue }) => getValue(), + }, + { + accessorKey: "default_lang_type", + header: "Default Language", + cell: ({ getValue }) => getValue(), + }, + { + accessorKey: "default_money_type", + header: "Default Money Type", + cell: ({ getValue }) => getValue(), + }, + { + accessorKey: "is_commercial", + header: "Is Commercial", + cell: ({ getValue }) => getValue() ? (
Yes
) : (
No
), + }, + { + accessorKey: "is_blacklist", + header: "Is Blacklist", + cell: ({ getValue }) => getValue() ? (
Yes
) : (
No
), + }, + { + accessorKey: "createdAt", + header: "Created", + cell: ({ getValue }) => dateToLocaleString(getValue() as string), + }, + { + accessorKey: "updatedAt", + header: "Updated", + cell: ({ getValue }) => dateToLocaleString(getValue() as string), + }, + { + accessorKey: "expiryStarts", + header: "Expiry Starts", + cell: ({ getValue }) => getValue() ? dateToLocaleString(getValue() as string) : "-", + }, + { + accessorKey: "expiryEnds", + header: "Expiry Ends", + cell: ({ getValue }) => getValue() ? dateToLocaleString(getValue() as string) : "-", + }, + { + id: "actions", + header: "Actions", + cell: ({ row }) => { + return ( +
+ +
+ ); + }, + } + ] +} + +export { getColumns }; \ No newline at end of file diff --git a/frontend/pages/living-space/tables/company/data-table.tsx b/frontend/pages/living-space/tables/company/data-table.tsx new file mode 100644 index 0000000..fafe315 --- /dev/null +++ b/frontend/pages/living-space/tables/company/data-table.tsx @@ -0,0 +1,267 @@ +"use client" + +import * as React from "react" +import { + closestCenter, + DndContext, + KeyboardSensor, + MouseSensor, + TouchSensor, + useSensor, + useSensors, + type UniqueIdentifier, +} from "@dnd-kit/core" +import { restrictToVerticalAxis } from "@dnd-kit/modifiers" +import { + SortableContext, + verticalListSortingStrategy, +} from "@dnd-kit/sortable" +import { + IconChevronDown, + IconChevronLeft, + IconChevronRight, + IconChevronsLeft, + IconChevronsRight, + IconLayoutColumns, + IconPlus, +} from "@tabler/icons-react" +import { + ColumnFiltersState, + flexRender, + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getSortedRowModel, + SortingState, + useReactTable, + VisibilityState, +} from "@tanstack/react-table" + +import { Button } from "@/components/ui/button" +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { Label } from "@/components/ui/label" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/components/ui/tabs" +import { schemaType } from "./schema" +import { getColumns, DraggableRow } from "./columns" +import { useRouter } from "next/navigation" + +export function LivingSpaceCompanyDataTable({ + data, + totalCount, + currentPage = 1, + pageSize = 10, + onPageChange, + onPageSizeChange, + refetchTable, + companyID, + setCompanyID, +}: { + data: schemaType[], + totalCount: number, + currentPage?: number, + pageSize?: number, + onPageChange: (page: number) => void, + onPageSizeChange: (size: number) => void, + refetchTable: () => void, + companyID: string, + setCompanyID: (id: string | null) => void, +}) { + + const [rowSelection, setRowSelection] = React.useState({}) + const [columnVisibility, setColumnVisibility] = React.useState({}) + const [columnFilters, setColumnFilters] = React.useState([]) + const [sorting, setSorting] = React.useState([]) + const sortableId = React.useId() + const sensors = useSensors(useSensor(MouseSensor, {}), useSensor(TouchSensor, {}), useSensor(KeyboardSensor, {})) + const dataIds = React.useMemo(() => data?.map(({ _id }) => _id) || [], [data]) + + const selectionHandler = (id: string) => { setCompanyID(id); } + const columns = getColumns(selectionHandler); + const pagination = React.useMemo(() => ({ pageIndex: currentPage - 1, pageSize: pageSize }), [currentPage, pageSize]) + const totalPages = Math.ceil(totalCount / pageSize) + + const table = useReactTable({ + data, columns, pageCount: totalPages, + state: { sorting, columnVisibility, rowSelection, columnFilters, pagination }, + manualPagination: true, + getRowId: (row) => row._id.toString(), + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onColumnVisibilityChange: setColumnVisibility, + onPaginationChange: (updater) => { const nextPagination = typeof updater === "function" ? updater(pagination) : updater; onPageChange(nextPagination.pageIndex + 1); onPageSizeChange(nextPagination.pageSize) }, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getSortedRowModel: getSortedRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + }) + + const handlePageSizeChange = (value: string) => { const newSize = Number(value); onPageSizeChange(newSize); onPageChange(1) } + + return ( + +
+ + +
+ + + + + + {table.getAllColumns().filter((column) => typeof column.accessorFn !== "undefined" && column.getCanHide()).map((column) => { + return ( + column.toggleVisibility(!!value)} > + {column.id} + + ) + })} + + +
+
+ +
+ + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + + ) + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + {table.getRowModel().rows.map((row) => )} + ) : ( + No results. + )} + +
+
+
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of{" "} + {table.getFilteredRowModel().rows.length} row(s) selected. +
+
+
+ + +
+
+ Page {currentPage} of {totalPages} +
+
+ Total Count: {totalCount} +
+
+ + + + + + + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+ ) +} \ No newline at end of file diff --git a/frontend/pages/living-space/tables/company/page.tsx b/frontend/pages/living-space/tables/company/page.tsx new file mode 100644 index 0000000..dc3edf4 --- /dev/null +++ b/frontend/pages/living-space/tables/company/page.tsx @@ -0,0 +1,25 @@ +'use client'; +import { useState } from "react"; +import { useGraphQlCompanyList } from "./queries"; +import { LivingSpaceCompanyDataTable } from "./data-table"; + +const PageLivingSpaceCompanyTableSection = ({ companyID, setCompanyID }: { companyID: string | null; setCompanyID: (id: string | null) => void }) => { + const [page, setPage] = useState(1); + const [limit, setLimit] = useState(10); + const [sort, setSort] = useState({ createdAt: 'desc' }); + const [filters, setFilters] = useState({}); + const { data, isLoading, error, refetch } = useGraphQlCompanyList({ limit, skip: (page - 1) * limit, sort, filters }); + + const handlePageChange = (newPage: number) => { setPage(newPage) }; + const handlePageSizeChange = (newSize: number) => { setLimit(newSize); setPage(1) }; + if (isLoading) { return
Loading...
} + if (error) { return
Error loading users
} + + return < LivingSpaceCompanyDataTable + data={data?.data || []} totalCount={data?.totalCount || 0} currentPage={page} pageSize={limit} onPageChange={handlePageChange} + onPageSizeChange={handlePageSizeChange} refetchTable={refetch} companyID={companyID || ""} setCompanyID={setCompanyID} + /> + +} + +export default PageLivingSpaceCompanyTableSection; diff --git a/frontend/pages/living-space/tables/company/queries.tsx b/frontend/pages/living-space/tables/company/queries.tsx new file mode 100644 index 0000000..b1d5c88 --- /dev/null +++ b/frontend/pages/living-space/tables/company/queries.tsx @@ -0,0 +1,19 @@ +'use client' +import { useQuery } from '@tanstack/react-query' +import { ListArguments } from '@/types/listRequest' +import { schemaType } from "./schema"; + +const fetchGraphQlCompanyList = async (params: ListArguments): Promise<{ data: schemaType[], totalCount: number }> => { + console.log('Fetching test data from local API'); + const { limit, skip, sort, filters } = params; + try { + const res = await fetch('/api/company/list', { method: 'POST', cache: 'no-store', credentials: "include", body: JSON.stringify({ limit, skip, sort, filters }) }); + if (!res.ok) { const errorText = await res.text(); console.error('Test data API error:', errorText); throw new Error(`API error: ${res.status} ${res.statusText}`) } + const data = await res.json(); + return { data: data.data, totalCount: data.totalCount } + } catch (error) { console.error('Error fetching test data:', error); throw error } +}; + +export function useGraphQlCompanyList(params: ListArguments) { + return useQuery({ queryKey: ['graphql-company-list', params], queryFn: () => fetchGraphQlCompanyList(params) }) +} diff --git a/frontend/pages/living-space/tables/company/schema.tsx b/frontend/pages/living-space/tables/company/schema.tsx new file mode 100644 index 0000000..7b150ae --- /dev/null +++ b/frontend/pages/living-space/tables/company/schema.tsx @@ -0,0 +1,27 @@ +import { z } from "zod"; + +export const schema = z.object({ + _id: z.string(), + uuid: z.string(), + formal_name: z.string(), + company_type: z.string(), + commercial_type: z.string(), + tax_no: z.string(), + public_name: z.string(), + company_tag: z.string(), + default_lang_type: z.string().default("TR"), + default_money_type: z.string().default("TL"), + is_commercial: z.boolean().default(false), + is_blacklist: z.boolean().default(false), + parent_id: z.string().optional(), + workplace_no: z.string().optional(), + official_address: z.string().optional(), + top_responsible_company: z.string().optional(), + expiryStarts: z.string().optional(), + expiryEnds: z.string().optional(), + createdAt: z.string().nullable().optional(), + updatedAt: z.string().nullable().optional(), +}); + + +export type schemaType = z.infer; \ No newline at end of file diff --git a/frontend/pages/living-space/tables/page.tsx b/frontend/pages/living-space/tables/page.tsx deleted file mode 100644 index 1fbb6c6..0000000 --- a/frontend/pages/living-space/tables/page.tsx +++ /dev/null @@ -1,90 +0,0 @@ -'use client'; -import { useState, useEffect, useRef } from "react"; -import { Card, CardContent } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { XCircle } from "lucide-react"; -import PageLivingSpaceBuildsTableSection from "./builds/page"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import PageLivingSpaceUserTypesTableSection from "./userType/page"; - - -const PageLivingSpace = () => { - - const [collectionToken, setCollectionToken] = useState(null); - const [buildID, setBuildID] = useState(null); - const [userTypeID, setUserTypeID] = useState(null); - const [partID, setPartID] = useState(null); - const [companyID, setCompanyID] = useState(null); - const [personID, setPersonID] = useState(null); - - const [isUserTypeEnabled, setIsUserTypeEnabled] = useState(false); - const [isPartsEnabled, setIsPartsEnabled] = useState(false); - const [isCompanyEnabled, setIsCompanyEnabled] = useState(false); - const [isPersonEnabled, setIsPersonEnabled] = useState(false); - - - const tabsClassName = "border border-gray-300 rounded-sm h-10" - const deleteAllSelections = () => { - setBuildID(null); setCollectionToken(null); setUserTypeID(null); setPartID(null); setCompanyID(null); setPersonID(null); - setIsUserTypeEnabled(false); setIsPartsEnabled(false); setIsCompanyEnabled(false); setIsPersonEnabled(false); - } - return <> - - -
-
- Build ID: - {buildID || '-'} -
-
- Collection Token: - {collectionToken || '-'} -
-
- User Type ID: - {userTypeID || '-'} -
-
- Part ID: - {partID || '-'} -
-
- Company ID: - {companyID || '-'} -
-
- Person ID: - {personID || '-'} -
-
- -
-
-
- - - Builds - {isUserTypeEnabled && User Type} - {isPartsEnabled && Parts} - {isCompanyEnabled && Company} - {isPersonEnabled && Person} - -
- - - - {isUserTypeEnabled && - - } - {isPartsEnabled && {/* Add Parts section component here */}} - {isCompanyEnabled && {/* Add Company section component here */}} - {isPersonEnabled && {/* Add Person section component here */}} -
-
-
- -} - -export { PageLivingSpace }; \ No newline at end of file diff --git a/frontend/pages/living-space/tables/part/columns.tsx b/frontend/pages/living-space/tables/part/columns.tsx new file mode 100644 index 0000000..7e20e92 --- /dev/null +++ b/frontend/pages/living-space/tables/part/columns.tsx @@ -0,0 +1,109 @@ +"use client" +import { z } from "zod" +import { Button } from "@/components/ui/button" +import { useSortable } from "@dnd-kit/sortable" +import { ColumnDef, flexRender, Row } from "@tanstack/react-table" +import { TableCell, TableRow } from "@/components/ui/table" +import { CSS } from "@dnd-kit/utilities" +import { schema, schemaType } from "./schema" +import { dateToLocaleString } from "@/lib/utils" +import { Pencil, Trash } from "lucide-react" +import { IconHandClick } from "@tabler/icons-react" + +export function DraggableRow({ row, selectedID }: { row: Row>; selectedID: string }) { + const { transform, transition, setNodeRef, isDragging } = useSortable({ id: row.original._id }) + return ( + + {row.getVisibleCells().map((cell) => ( + {flexRender(cell.column.columnDef.cell, cell.getContext())} + ))} + + ) +} + +function getColumns(selectionHandler: (id: string) => void): ColumnDef[] { + return [ + { + accessorKey: "addressGovCode", + header: "Address Gov Code", + }, + { + accessorKey: "no", + header: "No", + }, + { + accessorKey: "level", + header: "Level", + }, + { + accessorKey: "code", + header: "Code", + }, + { + accessorKey: "grossSize", + header: "Gross Size", + }, + { + accessorKey: "netSize", + header: "Net Size", + }, + { + accessorKey: "defaultAccessory", + header: "Default Accessory", + }, + { + accessorKey: "humanLivability", + header: "Human Livability", + }, + { + accessorKey: "key", + header: "Key", + }, + { + accessorKey: "directionId", + header: "Direction ID", + }, + { + accessorKey: "typeId", + header: "Type ID", + }, + { + accessorKey: "createdAt", + header: "Created", + cell: ({ getValue }) => dateToLocaleString(getValue() as string), + }, + { + accessorKey: "updatedAt", + header: "Updated", + cell: ({ getValue }) => dateToLocaleString(getValue() as string), + }, + { + accessorKey: "expiryStarts", + header: "Expiry Starts", + cell: ({ getValue }) => getValue() ? dateToLocaleString(getValue() as string) : "-", + }, + { + accessorKey: "expiryEnds", + header: "Expiry Ends", + cell: ({ getValue }) => getValue() ? dateToLocaleString(getValue() as string) : "-", + }, + { + id: "actions", + header: "Actions", + cell: ({ row }) => { + return ( +
+ +
+ ); + }, + } + ] +} + +export { getColumns }; \ No newline at end of file diff --git a/frontend/pages/living-space/tables/part/data-table.tsx b/frontend/pages/living-space/tables/part/data-table.tsx new file mode 100644 index 0000000..18e0043 --- /dev/null +++ b/frontend/pages/living-space/tables/part/data-table.tsx @@ -0,0 +1,267 @@ +"use client" + +import * as React from "react" +import { + closestCenter, + DndContext, + KeyboardSensor, + MouseSensor, + TouchSensor, + useSensor, + useSensors, + type UniqueIdentifier, +} from "@dnd-kit/core" +import { restrictToVerticalAxis } from "@dnd-kit/modifiers" +import { + SortableContext, + verticalListSortingStrategy, +} from "@dnd-kit/sortable" +import { + IconChevronDown, + IconChevronLeft, + IconChevronRight, + IconChevronsLeft, + IconChevronsRight, + IconLayoutColumns, + IconPlus, +} from "@tabler/icons-react" +import { + ColumnFiltersState, + flexRender, + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getSortedRowModel, + SortingState, + useReactTable, + VisibilityState, +} from "@tanstack/react-table" +import { Button } from "@/components/ui/button" +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { Label } from "@/components/ui/label" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/components/ui/tabs" +import { schemaType } from "./schema" +import { getColumns, DraggableRow } from "./columns" + +export function LivingSpaceBuildPartsDataTable({ + data, + totalCount, + currentPage = 1, + pageSize = 10, + onPageChange, + onPageSizeChange, + refetchTable, + buildId, + partID, + setPartID, +}: { + data: schemaType[], + totalCount: number, + currentPage?: number, + pageSize?: number, + onPageChange: (page: number) => void, + onPageSizeChange: (size: number) => void, + refetchTable: () => void, + buildId: string, + partID: string | null, + setPartID: (id: string | null) => void +}) { + + const [rowSelection, setRowSelection] = React.useState({}) + const [columnVisibility, setColumnVisibility] = React.useState({}) + const [columnFilters, setColumnFilters] = React.useState([]) + const [sorting, setSorting] = React.useState([]) + const sortableId = React.useId() + const sensors = useSensors(useSensor(MouseSensor, {}), useSensor(TouchSensor, {}), useSensor(KeyboardSensor, {})) + const dataIds = React.useMemo(() => data?.map(({ _id }) => _id) || [], [data]) + const selectionHandler = (id: string) => { setPartID(id); } + const columns = getColumns(selectionHandler); + const pagination = React.useMemo(() => ({ pageIndex: currentPage - 1, pageSize: pageSize }), [currentPage, pageSize]) + const totalPages = Math.ceil(totalCount / pageSize) + + const table = useReactTable({ + data, + columns, + pageCount: totalPages, + state: { sorting, columnVisibility, rowSelection, columnFilters, pagination }, + manualPagination: true, + getRowId: (row) => row._id.toString(), + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onColumnVisibilityChange: setColumnVisibility, + onPaginationChange: (updater) => { const nextPagination = typeof updater === "function" ? updater(pagination) : updater; onPageChange(nextPagination.pageIndex + 1); onPageSizeChange(nextPagination.pageSize) }, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getSortedRowModel: getSortedRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + }) + const handlePageSizeChange = (value: string) => { const newSize = Number(value); onPageSizeChange(newSize); onPageChange(1) } + + return ( + +
+ + +
+ + + + + + {table.getAllColumns().filter((column) => typeof column.accessorFn !== "undefined" && column.getCanHide()).map((column) => { + return ( + column.toggleVisibility(!!value)} > + {column.id} + + ) + })} + + +
+
+ +
+ + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + + ) + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + + {table.getRowModel().rows.map((row) => )} + ) : ( + No results. + )} + +
+
+
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of{" "} + {table.getFilteredRowModel().rows.length} row(s) selected. +
+
+
+ + +
+
+ Page {currentPage} of {totalPages} +
+
+ Total Count: {totalCount} +
+
+ + + + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+ ) +} \ No newline at end of file diff --git a/frontend/pages/living-space/tables/part/page.tsx b/frontend/pages/living-space/tables/part/page.tsx new file mode 100644 index 0000000..e60dbe7 --- /dev/null +++ b/frontend/pages/living-space/tables/part/page.tsx @@ -0,0 +1,35 @@ +'use client'; +import { useState } from "react"; +import { useGraphQlBuildPartsList } from "./queries"; +import { LivingSpaceBuildPartsDataTable } from "./data-table"; + + +const PageLivingSpacePartsTableSection = ( + { buildId, partID, setPartID, setIsPartsEnabled }: { + buildId: string; + partID: string | null; + setPartID: (id: string | null) => void; + setIsPartsEnabled: (enabled: boolean) => void; + } +) => { + const [page, setPage] = useState(1); + const [limit, setLimit] = useState(10); + const [sort, setSort] = useState({ createdAt: 'desc' }); + const [filters, setFilters] = useState({}); + + const { data, isLoading, error, refetch } = useGraphQlBuildPartsList({ limit, skip: (page - 1) * limit, sort, filters: { ...filters, buildId: buildId } }); + + const handlePageChange = (newPage: number) => { setPage(newPage) }; + const handlePageSizeChange = (newSize: number) => { setLimit(newSize); setPage(1) }; + if (isLoading) { return
Loading...
} + if (error) { return
Error loading users
} + + return <> + + ; + +} + +export default PageLivingSpacePartsTableSection; \ No newline at end of file diff --git a/frontend/pages/living-space/tables/part/queries.tsx b/frontend/pages/living-space/tables/part/queries.tsx new file mode 100644 index 0000000..74972f8 --- /dev/null +++ b/frontend/pages/living-space/tables/part/queries.tsx @@ -0,0 +1,18 @@ +'use client' +import { useQuery, useMutation } from '@tanstack/react-query' +import { ListArguments } from '@/types/listRequest' + +const fetchGraphQlBuildPartsList = async (params: ListArguments): Promise => { + console.log('Fetching test data from local API'); + const { limit, skip, sort, filters } = params; + try { + const res = await fetch('/api/builds-parts/list', { method: 'POST', cache: 'no-store', credentials: "include", body: JSON.stringify({ limit, skip, sort, filters }) }); + if (!res.ok) { const errorText = await res.text(); console.error('Test data API error:', errorText); throw new Error(`API error: ${res.status} ${res.statusText}`) } + const data = await res.json(); + return { data: data.data, totalCount: data.totalCount } + } catch (error) { console.error('Error fetching test data:', error); throw error } +}; + +export function useGraphQlBuildPartsList(params: ListArguments) { + return useQuery({ queryKey: ['graphql-build-parts-list', params], queryFn: () => fetchGraphQlBuildPartsList(params) }) +} diff --git a/frontend/pages/living-space/tables/part/schema.tsx b/frontend/pages/living-space/tables/part/schema.tsx new file mode 100644 index 0000000..aeeba05 --- /dev/null +++ b/frontend/pages/living-space/tables/part/schema.tsx @@ -0,0 +1,28 @@ +import { z } from "zod"; + +export const schema = z.object({ + + _id: z.string(), + uuid: z.string(), + buildId: z.string(), + addressGovCode: z.string(), + no: z.number(), + level: z.number(), + code: z.string(), + grossSize: z.number(), + netSize: z.number(), + defaultAccessory: z.string(), + humanLivability: z.boolean(), + key: z.string(), + directionId: z.string().nullable().optional(), + typeId: z.string().nullable().optional(), + createdAt: z.string(), + updatedAt: z.string(), + expiryStarts: z.string(), + expiryEnds: z.string(), + active: z.boolean(), + isConfirmed: z.boolean(), + +}); + +export type schemaType = z.infer; diff --git a/frontend/pages/living-space/tables/person/columns.tsx b/frontend/pages/living-space/tables/person/columns.tsx new file mode 100644 index 0000000..509d894 --- /dev/null +++ b/frontend/pages/living-space/tables/person/columns.tsx @@ -0,0 +1,146 @@ +"use client" +import { z } from "zod" +import { Button } from "@/components/ui/button" +import { Drawer, DrawerClose, DrawerContent, DrawerFooter, DrawerHeader, DrawerTrigger } from "@/components/ui/drawer" +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { useSortable } from "@dnd-kit/sortable" +import { IconGripVertical, IconHandClick } from "@tabler/icons-react" +import { useIsMobile } from "@/hooks/use-mobile" +import { Separator } from "@/components/ui/separator" +import { ColumnDef, flexRender, Row } from "@tanstack/react-table" +import { TableCell, TableRow } from "@/components/ui/table" +import { CSS } from "@dnd-kit/utilities" +import { schema, schemaType } from "./schema" +import { dateToLocaleString } from "@/lib/utils" +import { Pencil, Trash } from "lucide-react" + + +function DragHandle({ id }: { id: number }) { + const { attributes, listeners } = useSortable({ id }) + return ( + + ) +} + +export function DraggableRow({ row, selectedID }: { row: Row>; selectedID: string }) { + const { transform, transition, setNodeRef, isDragging } = useSortable({ id: row.original._id }) + return ( + + {row.getVisibleCells().map((cell) => ( + {flexRender(cell.column.columnDef.cell, cell.getContext())} + ))} + + ) +} + +function getColumns(selectionHandler: (id: string) => void): ColumnDef[] { + return [ + { + accessorKey: "uuid", + header: "UUID", + cell: ({ getValue }) => (
{String(getValue())}
), + }, + { + accessorKey: "firstName", + header: "First Name", + }, + { + accessorKey: "surname", + header: "Surname", + }, + { + accessorKey: "middleName", + header: "Middle Name", + }, + { + accessorKey: "sexCode", + header: "Sex", + }, + { + accessorKey: "personRef", + header: "Person Ref", + }, + { + accessorKey: "personTag", + header: "Person Tag", + }, + { + accessorKey: "fatherName", + header: "Father Name", + }, + { + accessorKey: "motherName", + header: "Mother Name", + }, + { + accessorKey: "countryCode", + header: "Country", + }, + { + accessorKey: "nationalIdentityId", + header: "National ID", + }, + { + accessorKey: "birthPlace", + header: "Birth Place", + }, + { + accessorKey: "active", + header: "Active", + cell: ({ getValue }) => getValue() ? (
Yes
) : (
No
), + }, + { + accessorKey: "isConfirmed", + header: "Confirmed", + cell: ({ getValue }) => getValue() ? (
Yes
) : (
No
), + }, + { + accessorKey: "birthDate", + header: "Birth Date", + cell: ({ getValue }) => dateToLocaleString(getValue() as string), + }, + { + accessorKey: "createdAt", + header: "Created", + cell: ({ getValue }) => dateToLocaleString(getValue() as string), + }, + { + accessorKey: "updatedAt", + header: "Updated", + cell: ({ getValue }) => dateToLocaleString(getValue() as string), + }, + { + accessorKey: "expiryStarts", + header: "Expiry Starts", + cell: ({ getValue }) => getValue() ? dateToLocaleString(getValue() as string) : "-", + }, + { + accessorKey: "expiryEnds", + header: "Expiry Ends", + cell: ({ getValue }) => getValue() ? dateToLocaleString(getValue() as string) : "-", + }, + { + id: "actions", + header: "Actions", + cell: ({ row }) => { + return ( +
+ +
+ ); + }, + } + ] +} + +export { getColumns }; \ No newline at end of file diff --git a/frontend/pages/living-space/tables/person/data-table.tsx b/frontend/pages/living-space/tables/person/data-table.tsx new file mode 100644 index 0000000..8c03c5b --- /dev/null +++ b/frontend/pages/living-space/tables/person/data-table.tsx @@ -0,0 +1,267 @@ +"use client" + +import * as React from "react" +import { + closestCenter, + DndContext, + KeyboardSensor, + MouseSensor, + TouchSensor, + useSensor, + useSensors, + type UniqueIdentifier, +} from "@dnd-kit/core" +import { restrictToVerticalAxis } from "@dnd-kit/modifiers" +import { + SortableContext, + verticalListSortingStrategy, +} from "@dnd-kit/sortable" +import { + IconChevronDown, + IconChevronLeft, + IconChevronRight, + IconChevronsLeft, + IconChevronsRight, + IconLayoutColumns, + IconPlus, +} from "@tabler/icons-react" +import { + ColumnFiltersState, + flexRender, + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getSortedRowModel, + SortingState, + useReactTable, + VisibilityState, +} from "@tanstack/react-table" + +import { Button } from "@/components/ui/button" +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { Label } from "@/components/ui/label" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/components/ui/tabs" +import { schemaType } from "./schema" +import { getColumns, DraggableRow } from "./columns" +import { useRouter } from "next/navigation" + +export function LivingSpacePeopleDataTable({ + data, + totalCount, + currentPage = 1, + pageSize = 10, + onPageChange, + onPageSizeChange, + refetchTable, + personID, + setPersonID, +}: { + data: schemaType[], + totalCount: number, + currentPage?: number, + pageSize?: number, + onPageChange: (page: number) => void, + onPageSizeChange: (size: number) => void, + refetchTable: () => void, + personID: string, + setPersonID: (id: string | null) => void, +}) { + + const [rowSelection, setRowSelection] = React.useState({}) + const [columnVisibility, setColumnVisibility] = React.useState({}) + const [columnFilters, setColumnFilters] = React.useState([]) + const [sorting, setSorting] = React.useState([]) + const sortableId = React.useId() + const sensors = useSensors(useSensor(MouseSensor, {}), useSensor(TouchSensor, {}), useSensor(KeyboardSensor, {})) + const dataIds = React.useMemo(() => data?.map(({ _id }) => _id) || [], [data]) + + const selectionHandler = (id: string) => { setPersonID(id); } + const columns = getColumns(selectionHandler); + const pagination = React.useMemo(() => ({ pageIndex: currentPage - 1, pageSize: pageSize }), [currentPage, pageSize]) + const totalPages = Math.ceil(totalCount / pageSize) + + const table = useReactTable({ + data, columns, pageCount: totalPages, + state: { sorting, columnVisibility, rowSelection, columnFilters, pagination }, + manualPagination: true, + getRowId: (row) => row._id.toString(), + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onColumnVisibilityChange: setColumnVisibility, + onPaginationChange: (updater) => { const nextPagination = typeof updater === "function" ? updater(pagination) : updater; onPageChange(nextPagination.pageIndex + 1); onPageSizeChange(nextPagination.pageSize) }, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getSortedRowModel: getSortedRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + }) + + const handlePageSizeChange = (value: string) => { const newSize = Number(value); onPageSizeChange(newSize); onPageChange(1) } + + return ( + +
+ + +
+ + + + + + {table.getAllColumns().filter((column) => typeof column.accessorFn !== "undefined" && column.getCanHide()).map((column) => { + return ( + column.toggleVisibility(!!value)} > + {column.id} + + ) + })} + + +
+
+ +
+ + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + + ) + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + {table.getRowModel().rows.map((row) => )} + ) : ( + No results. + )} + +
+
+
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of{" "} + {table.getFilteredRowModel().rows.length} row(s) selected. +
+
+
+ + +
+
+ Page {currentPage} of {totalPages} +
+
+ Total Count: {totalCount} +
+
+ + + + + + + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+ ) +} \ No newline at end of file diff --git a/frontend/pages/living-space/tables/person/page.tsx b/frontend/pages/living-space/tables/person/page.tsx new file mode 100644 index 0000000..eac64c3 --- /dev/null +++ b/frontend/pages/living-space/tables/person/page.tsx @@ -0,0 +1,24 @@ +'use client'; +import { useState } from "react"; +import { useGraphQlPeopleList } from "./queries"; +import { LivingSpacePeopleDataTable } from "./data-table"; + +const PageLivingSpacePersonTableSection = ({ personID, setPersonID }: { personID: string | null, setPersonID: (id: string | null) => void }) => { + const [page, setPage] = useState(1); + const [limit, setLimit] = useState(10); + const [sort, setSort] = useState({ createdAt: 'desc' }); + const [filters, setFilters] = useState({}); + const { data, isLoading, error, refetch } = useGraphQlPeopleList({ limit, skip: (page - 1) * limit, sort, filters }); + + const handlePageChange = (newPage: number) => { setPage(newPage) }; + const handlePageSizeChange = (newSize: number) => { setLimit(newSize); setPage(1) }; + if (isLoading) { return
Loading...
} + if (error) { return
Error loading users
} + + return < LivingSpacePeopleDataTable + data={data?.data || []} totalCount={data?.totalCount || 0} currentPage={page} pageSize={limit} onPageChange={handlePageChange} + onPageSizeChange={handlePageSizeChange} refetchTable={refetch} personID={personID || ""} setPersonID={setPersonID} + /> +} + +export default PageLivingSpacePersonTableSection; diff --git a/frontend/pages/living-space/tables/person/queries.tsx b/frontend/pages/living-space/tables/person/queries.tsx new file mode 100644 index 0000000..eb56242 --- /dev/null +++ b/frontend/pages/living-space/tables/person/queries.tsx @@ -0,0 +1,19 @@ +'use client' +import { useQuery } from '@tanstack/react-query' +import { ListArguments } from '@/types/listRequest' +import { schemaType } from "./schema"; + +const fetchGraphQlPeopleList = async (params: ListArguments): Promise<{ data: schemaType[], totalCount: number }> => { + console.log('Fetching test data from local API'); + const { limit, skip, sort, filters } = params; + try { + const res = await fetch('/api/people/list', { method: 'POST', cache: 'no-store', credentials: "include", body: JSON.stringify({ limit, skip, sort, filters }) }); + if (!res.ok) { const errorText = await res.text(); console.error('Test data API error:', errorText); throw new Error(`API error: ${res.status} ${res.statusText}`) } + const data = await res.json(); + return { data: data.data, totalCount: data.totalCount } + } catch (error) { console.error('Error fetching test data:', error); throw error } +}; + +export function useGraphQlPeopleList(params: ListArguments) { + return useQuery({ queryKey: ['graphql-people-list', params], queryFn: () => fetchGraphQlPeopleList(params) }) +} diff --git a/frontend/pages/living-space/tables/person/schema.tsx b/frontend/pages/living-space/tables/person/schema.tsx new file mode 100644 index 0000000..02d3e98 --- /dev/null +++ b/frontend/pages/living-space/tables/person/schema.tsx @@ -0,0 +1,27 @@ +import { z } from "zod"; + +export const schema = z.object({ + _id: z.string(), + uuid: z.string(), + firstName: z.string().nullable().optional(), + surname: z.string().nullable().optional(), + middleName: z.string().nullable().optional(), + sexCode: z.string().nullable().optional(), + personRef: z.string().nullable().optional(), + personTag: z.string().nullable().optional(), + fatherName: z.string().nullable().optional(), + motherName: z.string().nullable().optional(), + countryCode: z.string().nullable().optional(), + nationalIdentityId: z.string().nullable().optional(), + birthPlace: z.string().nullable().optional(), + birthDate: z.string().nullable().optional(), + taxNo: z.string().nullable().optional(), + birthname: z.string().nullable().optional(), + expiryStarts: z.string().nullable().optional(), + expiryEnds: z.string().nullable().optional(), + createdAt: z.string().nullable().optional(), + updatedAt: z.string().nullable().optional(), +}); + + +export type schemaType = z.infer; \ No newline at end of file diff --git a/frontend/pages/living-space/tables/userType/columns.tsx b/frontend/pages/living-space/tables/userType/columns.tsx index 5bb5625..756a3b0 100644 --- a/frontend/pages/living-space/tables/userType/columns.tsx +++ b/frontend/pages/living-space/tables/userType/columns.tsx @@ -16,11 +16,11 @@ import { schema, schemaType } from "./schema" import { dateToLocaleString } from "@/lib/utils" import { Pencil, Trash, TextSelect } from "lucide-react" -export function DraggableRow({ row }: { row: Row> }) { +export function DraggableRow({ row, selectedID }: { row: Row>; selectedID: string }) { const { transform, transition, setNodeRef, isDragging } = useSortable({ id: row.original._id }) return ( {row.getVisibleCells().map((cell) => ( diff --git a/frontend/pages/living-space/tables/userType/data-table.tsx b/frontend/pages/living-space/tables/userType/data-table.tsx index 4e0d5aa..a73db90 100644 --- a/frontend/pages/living-space/tables/userType/data-table.tsx +++ b/frontend/pages/living-space/tables/userType/data-table.tsx @@ -86,8 +86,7 @@ export function LivingSpaceUserTypesDataTable({ userTypeID, setUserTypeID, setIsPartsEnabled, - setIsCompanyEnabled, - setIsPersonEnabled, + setIsHandleCompanyAndPersonEnable, }: { data: schemaType[], totalCount: number, @@ -99,12 +98,8 @@ export function LivingSpaceUserTypesDataTable({ userTypeID: string | null; setUserTypeID: (id: string | null) => void; setIsPartsEnabled: (enabled: boolean) => void; - setIsCompanyEnabled: (enabled: boolean) => void; - setIsPersonEnabled: (enabled: boolean) => void; + setIsHandleCompanyAndPersonEnable: (enabled: boolean) => void, }) { - - const router = useRouter(); - const [rowSelection, setRowSelection] = React.useState({}) const [columnVisibility, setColumnVisibility] = React.useState({}) const [columnFilters, setColumnFilters] = React.useState([]) @@ -112,17 +107,12 @@ export function LivingSpaceUserTypesDataTable({ const sortableId = React.useId() const sensors = useSensors(useSensor(MouseSensor, {}), useSensor(TouchSensor, {}), useSensor(KeyboardSensor, {})) const dataIds = React.useMemo(() => data?.map(({ _id }) => _id) || [], [data]) - const setSelection = (id: string, isProperty: boolean) => { - setUserTypeID(id); isProperty ? setIsPartsEnabled(true) : setIsPartsEnabled(false); setIsCompanyEnabled(true); setIsPersonEnabled(true) - } + const setSelection = (id: string, isProperty: boolean) => { setUserTypeID(id); isProperty ? setIsPartsEnabled(true) : setIsPartsEnabled(false); setIsHandleCompanyAndPersonEnable(true) } const columns = getColumns(setSelection); const pagination = React.useMemo(() => ({ pageIndex: currentPage - 1, pageSize: pageSize }), [currentPage, pageSize]) const totalPages = Math.ceil(totalCount / pageSize) - const table = useReactTable({ - data, - columns, - pageCount: totalPages, + data, columns, pageCount: totalPages, state: { sorting, columnVisibility, rowSelection, columnFilters, pagination }, manualPagination: true, getRowId: (row) => row._id.toString(), @@ -138,7 +128,6 @@ export function LivingSpaceUserTypesDataTable({ getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues(), }) - const handlePageSizeChange = (value: string) => { const newSize = Number(value); onPageSizeChange(newSize); onPageChange(1) } return ( @@ -197,7 +186,7 @@ export function LivingSpaceUserTypesDataTable({ {table.getRowModel().rows?.length ? ( - {table.getRowModel().rows.map((row) => )} + {table.getRowModel().rows.map((row) => )} ) : ( No results. )} diff --git a/frontend/pages/living-space/tables/userType/page.tsx b/frontend/pages/living-space/tables/userType/page.tsx index d9926fa..8940797 100644 --- a/frontend/pages/living-space/tables/userType/page.tsx +++ b/frontend/pages/living-space/tables/userType/page.tsx @@ -4,33 +4,31 @@ import { useGraphQlUserTypesList } from "./queries"; import { LivingSpaceUserTypesDataTable } from "./data-table"; const PageLivingSpaceUserTypesTableSection = ( - { userTypeID, setUserTypeID, setIsPartsEnabled, setIsCompanyEnabled, setIsPersonEnabled }: { + { userTypeID, setUserTypeID, setIsPartsEnabled, setIsHandleCompanyAndPersonEnable }: { userTypeID: string | null; setUserTypeID: (id: string | null) => void; setIsPartsEnabled: (enabled: boolean) => void; - setIsCompanyEnabled: (enabled: boolean) => void; - setIsPersonEnabled: (enabled: boolean) => void; + setIsHandleCompanyAndPersonEnable: (enabled: boolean) => void, } ) => { + const [page, setPage] = useState(1); const [limit, setLimit] = useState(10); const [sort, setSort] = useState({ createdAt: 'desc' }); const [filters, setFilters] = useState({}); const { data, isLoading, error, refetch } = useGraphQlUserTypesList({ limit, skip: (page - 1) * limit, sort, filters }); - const handlePageChange = (newPage: number) => { setPage(newPage) }; const handlePageSizeChange = (newSize: number) => { setLimit(newSize); setPage(1) }; if (isLoading) { return
Loading...
} if (error) { return
Error loading users
} - return <> + refetchTable={refetch} userTypeID={userTypeID || ""} setUserTypeID={setUserTypeID} setIsPartsEnabled={setIsPartsEnabled} setIsHandleCompanyAndPersonEnable={setIsHandleCompanyAndPersonEnable} + /> ; } -export default PageLivingSpaceUserTypesTableSection; \ No newline at end of file +export default PageLivingSpaceUserTypesTableSection;