diff --git a/backend/src/living-space/dto/create-living-space.input.ts b/backend/src/living-space/dto/create-living-space.input.ts index 7ca34c4..511cecb 100644 --- a/backend/src/living-space/dto/create-living-space.input.ts +++ b/backend/src/living-space/dto/create-living-space.input.ts @@ -7,9 +7,6 @@ export class CreateLivingSpaceInput extends ExpiryBaseInput { @Field() buildID: string; - @Field() - collectionToken: string; - @Field() partID: string; diff --git a/backend/src/living-space/dto/update-living-space.input.ts b/backend/src/living-space/dto/update-living-space.input.ts index e50ddae..a75bc11 100644 --- a/backend/src/living-space/dto/update-living-space.input.ts +++ b/backend/src/living-space/dto/update-living-space.input.ts @@ -4,12 +4,6 @@ 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; diff --git a/backend/src/living-space/living-space.module.ts b/backend/src/living-space/living-space.module.ts index 9dbd582..a30180b 100644 --- a/backend/src/living-space/living-space.module.ts +++ b/backend/src/living-space/living-space.module.ts @@ -3,9 +3,13 @@ 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'; +import { UserType, UserTypeSchema } from '@/models/user-type.model'; @Module({ - imports: [MongooseModule.forFeature([{ name: LivingSpaces.name, schema: LivingSpacesSchema }])], + imports: [MongooseModule.forFeature([ + { name: LivingSpaces.name, schema: LivingSpacesSchema }, + { name: UserType.name, schema: UserTypeSchema }, + ])], providers: [LivingSpaceService, LivingSpaceResolver] }) export class LivingSpaceModule { } diff --git a/backend/src/living-space/living-space.resolver.ts b/backend/src/living-space/living-space.resolver.ts index 559ff2c..4263d42 100644 --- a/backend/src/living-space/living-space.resolver.ts +++ b/backend/src/living-space/living-space.resolver.ts @@ -15,23 +15,23 @@ export class LivingSpaceResolver { constructor(private readonly livingSpaceService: LivingSpaceService) { } @Query(() => ListLivingSpaceResponse, { name: 'getLivingSpaces' }) - async getLivingSpaces(@Info() info: GraphQLResolveInfo, @Args('input') input: ListArguments): Promise { + async getLivingSpaces(@Info() info: GraphQLResolveInfo, @Args('buildID') buildID: string, @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); + return await this.livingSpaceService.findAll(buildID, 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); + async getLivingSpace(@Args('id', { type: () => ID }) id: string, @Args('buildID') buildID: string, @Info() info: GraphQLResolveInfo): Promise { + const fields = graphqlFields(info); const projection = this.livingSpaceService.buildProjection(fields); return this.livingSpaceService.findById(buildID, 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) } + async updateLivingSpace(@Args('buildID') buildID: string, @Args('uuid') uuid: string, @Args('input') input: UpdateLivingSpaceInput): Promise { return this.livingSpaceService.update(buildID, uuid, input) } @Mutation(() => Boolean, { name: 'deleteLivingSpace' }) - async deleteLivingSpace(@Args('uuid') uuid: string, @Args('collectionToken') collectionToken: string): Promise { return this.livingSpaceService.delete(uuid, collectionToken) } + async deleteLivingSpace(@Args('buildID') buildID: string, @Args('uuid') uuid: string): Promise { return this.livingSpaceService.delete(buildID, uuid) } } diff --git a/backend/src/living-space/living-space.service.ts b/backend/src/living-space/living-space.service.ts index 86a0af6..cd520b8 100644 --- a/backend/src/living-space/living-space.service.ts +++ b/backend/src/living-space/living-space.service.ts @@ -1,17 +1,15 @@ 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 { Types, Connection, Model } from 'mongoose'; +import { getDynamicLivingSpaceModel, 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'; +import { InjectConnection, InjectModel } from '@nestjs/mongoose'; +import { UserTypeDocument } from '@/models/user-type.model'; interface UpdateInput { - build?: Types.ObjectId; - collectionToken?: string; userType?: Types.ObjectId; - part?: Types.ObjectId; + part?: Types.ObjectId | null; company?: Types.ObjectId; person?: Types.ObjectId; expiryStarts?: Date; @@ -22,27 +20,26 @@ interface UpdateInput { export class LivingSpaceService { constructor( - @InjectModel(LivingSpaces.name) private readonly livingSpaceModel: Model, - @InjectConnection() private readonly connection: Connection + @InjectConnection() private readonly connection: Connection, + @InjectModel('UserType') private readonly userTypeModel: Model ) { } - async findAll(projection: any, skip: number, limit: number, sort?: Record, filters?: Record): Promise { - if (!filters?.collectionToken) { throw new Error('Collection token is required') } + async findAll(buildID: string, projection: any, skip: number, limit: number, sort?: Record, filters?: Record): Promise { + const selectedModel = getDynamicLivingSpaceModel(buildID, this.connection); 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(); + const totalCount = await selectedModel.countDocuments(query).exec(); + const data = await selectedModel.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); + async findById(buildID: string, id: Types.ObjectId, projection?: any): Promise { + const selectedModel = getDynamicLivingSpaceModel(buildID, 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); + if (!input.buildID) { throw new Error('Build ID is required') } + const LivingSpaceModel = getDynamicLivingSpaceModel(input.buildID, 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), @@ -50,18 +47,18 @@ export class LivingSpaceService { }; 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) }; + async update(buildID: string, uuid: string, input: UpdateLivingSpaceInput): Promise { + if (!buildID) { throw new Error('Build ID is required') } + const selectedModel = getDynamicLivingSpaceModel(buildID, this.connection); + const livingSpace = await selectedModel.findOne({ uuid }); const userTypeSelected = await this.userTypeModel.findById(new Types.ObjectId(input.userTypeID)).exec(); const newInput: UpdateInput = {} + if (userTypeSelected?.isProperty) { if (input?.partID) { newInput.part = new Types.ObjectId(input.partID) } } else { newInput.part = null } + if (input?.companyID) { newInput.company = new Types.ObjectId(input.companyID) } + if (input?.personID) { newInput.person = new Types.ObjectId(input.personID) }; if (input?.userTypeID) { newInput.userType = new Types.ObjectId(input.userTypeID) } 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(); + if (!livingSpace) { throw new Error('Company not found') }; livingSpace.set(newInput); return livingSpace.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 } + async delete(buildID: string, uuid: string): Promise { if (!buildID) { throw new Error('Build ID is required') } const selectedModel = getDynamicLivingSpaceModel(buildID, this.connection); const company = await selectedModel.deleteMany({ uuid }); return company.deletedCount > 0 } buildProjection(fields: Record): any { const projection: any = {}; diff --git a/backend/src/models/living-spaces.model.ts b/backend/src/models/living-spaces.model.ts index c13a189..9e54b91 100644 --- a/backend/src/models/living-spaces.model.ts +++ b/backend/src/models/living-spaces.model.ts @@ -20,9 +20,9 @@ export class LivingSpaces extends Base { @Prop({ type: Types.ObjectId, ref: Build.name, required: true }) build: Types.ObjectId; - @Field(() => ID) - @Prop({ type: Types.ObjectId, ref: BuildParts.name, required: true }) - part: Types.ObjectId; + @Field(() => ID, { nullable: true }) + @Prop({ type: Types.ObjectId, ref: BuildParts.name, required: false }) + part?: Types.ObjectId | null; @Field(() => ID) @Prop({ type: Types.ObjectId, ref: UserType.name, required: true }) @@ -41,8 +41,8 @@ export class LivingSpaces extends Base { export type LivingSpacesDocument = LivingSpaces & Document; export const LivingSpacesSchema = SchemaFactory.createForClass(LivingSpaces); -export function getDynamicLivingSpaceModel(collectionToken: string, connection: Connection): Model { - const collectionName = `LivingSpaces${collectionToken}`; +export function getDynamicLivingSpaceModel(buildID: string, connection: Connection): Model { + const collectionName = `LivingSpaces${buildID}`; const schema = SchemaFactory.createForClass(LivingSpaces); if (connection.models[collectionName]) { return connection.models[collectionName] as Model } return connection.model(collectionName, schema, collectionName) as unknown as Model; diff --git a/frontend/app/api/living-space/add/schema.ts b/frontend/app/api/living-space/add/schema.ts index 1cef649..8704368 100644 --- a/frontend/app/api/living-space/add/schema.ts +++ b/frontend/app/api/living-space/add/schema.ts @@ -2,7 +2,6 @@ import { z } from "zod" export const CreateLivingSpaceInputSchema = z.object({ buildID: z.string(), - collectionToken: z.string(), partID: z.string(), userTypeID: z.string(), companyID: z.string(), diff --git a/frontend/app/api/living-space/delete/route.ts b/frontend/app/api/living-space/delete/route.ts index 0a53c78..609b642 100644 --- a/frontend/app/api/living-space/delete/route.ts +++ b/frontend/app/api/living-space/delete/route.ts @@ -8,12 +8,13 @@ export async function GET(request: Request) { const searchParams = new URL(request.url).searchParams; const uuid = searchParams.get('uuid'); - console.dir({ uuid }, { depth: null }); + const buildID = searchParams.get('buildID'); + console.dir({ uuid, buildID }, { 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 }); + const query = gql`mutation DeleteCompany($buildID: String!, $uuid: String!) { deleteCompany(buildID: $buildID, uuid: $uuid) }`; + const data = await client.request(query, { buildID, uuid }); return NextResponse.json({ data: data.deleteUserType, status: 200 }); } catch (err: any) { console.error(err); diff --git a/frontend/app/api/living-space/list/route.ts b/frontend/app/api/living-space/list/route.ts index 2582ac9..9163bd8 100644 --- a/frontend/app/api/living-space/list/route.ts +++ b/frontend/app/api/living-space/list/route.ts @@ -6,40 +6,36 @@ const endpoint = "http://localhost:3001/graphql"; export async function POST(request: Request) { const body = await request.json(); - const { limit, skip, sort, filters } = body; + const { limit, skip, sort, filters, buildID } = body; try { const client = new GraphQLClient(endpoint); const query = gql` - query GetCompanies($input: ListArguments!) { - getCompanies(input:$input) { + query GetLivingSpaces($input: ListArguments!, $buildID: String!) { + getLivingSpaces(input: $input, buildID: $buildID) { 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 + build + part + userType + company + person + isNotificationSend expiryEnds + expiryStarts + createdAt + updatedAt + isConfirmed + deleted + } totalCount } } `; - const variables = { input: { limit, skip, sort, filters } }; + const variables = { input: { limit, skip, sort, filters }, buildID }; const data = await client.request(query, variables); - return NextResponse.json({ data: data.getCompanies.data, totalCount: data.getCompanies.totalCount }); + return NextResponse.json({ data: data.getLivingSpaces.data, totalCount: data.getLivingSpaces.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 index 71effe8..c6a0c75 100644 --- a/frontend/app/api/living-space/update/route.ts +++ b/frontend/app/api/living-space/update/route.ts @@ -8,13 +8,15 @@ const endpoint = "http://localhost:3001/graphql"; export async function POST(request: Request) { const searchUrl = new URL(request.url); const uuid = searchUrl.searchParams.get("uuid") || ""; + const buildID = searchUrl.searchParams.get("buildID") || ""; if (uuid === "") { return NextResponse.json({ error: "UUID is required" }, { status: 400 }) } + if (buildID === "") { return NextResponse.json({ error: "Build ID 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 query = gql`mutation UpdateLivingSpace($buildID:String!, $uuid: String!, $input: UpdateLivingSpaceInput!) { updateLivingSpace(buildID: $buildID, uuid: $uuid, input: $input) {_id}} `; + const variables = { uuid: uuid, buildID: buildID, input: { ...validatedBody } }; const data = await client.request(query, variables); return NextResponse.json({ data: data.updateLivingSpace, status: 200 }); } catch (err: any) { diff --git a/frontend/app/api/living-space/update/schema.ts b/frontend/app/api/living-space/update/schema.ts index 19721ac..a385b51 100644 --- a/frontend/app/api/living-space/update/schema.ts +++ b/frontend/app/api/living-space/update/schema.ts @@ -1,8 +1,6 @@ import { z } from "zod" export const UpdateLivingSpaceInputSchema = z.object({ - buildID: z.string(), - collectionToken: z.string(), partID: z.string(), userTypeID: z.string(), companyID: z.string(), diff --git a/frontend/app/living-spaces/page.tsx b/frontend/app/living-spaces/page.tsx index 5355cc1..df99090 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/add/page" +import { PageLivingSpaceList } from "@/pages/living-space/list/page"; -const SSRPageLivingSpace = () => { return <>
PageLivingSpace
} +const SSRPageLivingSpace = () => { return <>
} export default SSRPageLivingSpace; diff --git a/frontend/app/living-spaces/update/page.tsx b/frontend/app/living-spaces/update/page.tsx new file mode 100644 index 0000000..ad227bf --- /dev/null +++ b/frontend/app/living-spaces/update/page.tsx @@ -0,0 +1,5 @@ +import { PageLivingSpaceUpdate } from "@/pages/living-space/update/page"; + +const LivingSpaceUpdatePage = () => { return } + +export default LivingSpaceUpdatePage; \ No newline at end of file diff --git a/frontend/pages/living-space/add/form.tsx b/frontend/pages/living-space/add/form.tsx index b6d7744..63b1c05 100644 --- a/frontend/pages/living-space/add/form.tsx +++ b/frontend/pages/living-space/add/form.tsx @@ -26,19 +26,6 @@ const FormAddNewLivingSpace = ({ form, onSubmit, deleteAllSelections }: { form: )} /> - ( - - Collection Token - - - - - - )} - /> { - const [collectionToken, setCollectionToken] = useState(null); + + const router = useRouter(); 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 }); + const form = createForm({ buildID, 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]); + form.setValue("buildID", buildID || ""); form.setValue("userTypeID", userTypeID || ""); form.setValue("partID", partID || ""); form.setValue("companyID", companyID || ""); form.setValue("personID", personID || ""); + }, [buildID, userTypeID, partID, companyID, personID, form]); const mutation = useAddLivingSpaceMutation(); function onSubmit(values: FormValues) { mutation.mutate({ data: values }) } @@ -40,13 +39,15 @@ const PageLivingSpaceAdd = () => { 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) + setBuildID(null); setUserTypeID(null); setPartID(null); setCompanyID(null); setPersonID(null); setIsUserTypeEnabled(false); setIsPartsEnabled(false); setIsHandleCompanyAndPersonEnable(false) } return <>
+ Builds {isUserTypeEnabled && User Type} @@ -56,7 +57,7 @@ const PageLivingSpaceAdd = () => {
- + {isUserTypeEnabled && diff --git a/frontend/pages/living-space/add/queries.tsx b/frontend/pages/living-space/add/queries.tsx index 415697e..f8ca96c 100644 --- a/frontend/pages/living-space/add/queries.tsx +++ b/frontend/pages/living-space/add/queries.tsx @@ -5,7 +5,6 @@ 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" }), @@ -22,7 +21,7 @@ const fetchGraphQlLivingSpaceAdd = async (record: schemaType): Promise<{ data: s 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) }); + const res = await fetch('/api/living-space/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 } @@ -34,7 +33,7 @@ const fetchGraphQlLivingSpaceUpdate = async (record: schemaType, uuid: string): 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) }); + const res = await fetch(`/api/living-space/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 } diff --git a/frontend/pages/living-space/add/schema.ts b/frontend/pages/living-space/add/schema.ts index 6a78576..1662885 100644 --- a/frontend/pages/living-space/add/schema.ts +++ b/frontend/pages/living-space/add/schema.ts @@ -5,7 +5,6 @@ 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" }), @@ -16,12 +15,11 @@ export const formSchema = z.object({ export type FormValues = z.infer; -export function createForm({ buildID, collectionToken, userTypeID, partID, companyID, personID }: FormProps) { +export function createForm({ buildID, userTypeID, partID, companyID, personID }: FormProps) { return useForm({ resolver: zodResolver(formSchema), defaultValues: { buildID: buildID || "", - collectionToken: collectionToken || "", userTypeID: userTypeID || "", partID: partID || "", companyID: companyID || "", diff --git a/frontend/pages/living-space/add/types.ts b/frontend/pages/living-space/add/types.ts index d5c0ba0..7722822 100644 --- a/frontend/pages/living-space/add/types.ts +++ b/frontend/pages/living-space/add/types.ts @@ -1,6 +1,5 @@ interface FormProps { buildID: string | null; - collectionToken: string | null; userTypeID: string | null; partID: string | null; companyID: string | null; diff --git a/frontend/pages/living-space/list/page.tsx b/frontend/pages/living-space/list/page.tsx new file mode 100644 index 0000000..58053b2 --- /dev/null +++ b/frontend/pages/living-space/list/page.tsx @@ -0,0 +1,42 @@ +'use client'; +import { useEffect, useState } from "react"; +import PageLivingSpaceBuildsTableSection from "../tables/builds/page"; +import { Button } from "@/components/ui/button"; +import { useRouter } from "next/navigation"; +import { IconPlus } from "@tabler/icons-react"; +import { LivingSpaceDataTable } from "../tables/living-spaces/list/data-table"; +import { useGraphQlLivingSpaceList } from "./queries"; + +const PageLivingSpaceList = () => { + + const router = useRouter(); + const [buildID, setBuildID] = useState(null); + // const [collectionToken, setCollectionToken] = useState(null); + const [isUserTypeEnabled, setIsUserTypeEnabled] = useState(false); + const [page, setPage] = useState(1); + const [limit, setLimit] = useState(10); + const [sort, setSort] = useState({ createdAt: 'desc' }); + const [filters, setFilters] = useState({}); + + useEffect(() => { console.log(`buildID: ${buildID} is changed.`) }, [buildID]); + const additionButtons = <> + + + const { data, isLoading, refetch } = useGraphQlLivingSpaceList(buildID || '', { limit, skip: (page - 1) * limit, sort, filters }); + const handlePageChange = (newPage: number) => { setPage(newPage) }; + const handlePageSizeChange = (newSize: number) => { setLimit(newSize); setPage(1) }; + + return <> +
+ +

Living Space Added to selected Build with collectionToken:

{buildID}

+ { + buildID && + } +
+ ; +} + +export { PageLivingSpaceList }; diff --git a/frontend/pages/living-space/list/queries.tsx b/frontend/pages/living-space/list/queries.tsx new file mode 100644 index 0000000..03158f9 --- /dev/null +++ b/frontend/pages/living-space/list/queries.tsx @@ -0,0 +1,16 @@ +import { useQuery } from '@tanstack/react-query' +import { ListArguments } from '@/types/listRequest' + +const fetchGraphQlLivingSpaceList = async (buildID: string, params: ListArguments): Promise => { + console.log('Fetching test data from local API'); + const { limit, skip, sort, filters } = params; + try { + const res = await fetch('/api/living-space/list', { method: 'POST', cache: 'no-store', credentials: "include", body: JSON.stringify({ buildID, 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 useGraphQlLivingSpaceList(buildID: string, params: ListArguments) { + return useQuery({ queryKey: ['graphql-living-space-list', buildID, params], queryFn: () => fetchGraphQlLivingSpaceList(buildID, params) }) +} diff --git a/frontend/pages/living-space/tables/builds/columns.tsx b/frontend/pages/living-space/tables/builds/columns.tsx index 9c6ac39..f280da5 100644 --- a/frontend/pages/living-space/tables/builds/columns.tsx +++ b/frontend/pages/living-space/tables/builds/columns.tsx @@ -26,6 +26,10 @@ export function DraggableRow({ row, selectedID }: { row: Row void): ColumnDef[] { return [ + { + accessorKey: "_id", + header: "ID", + }, { accessorKey: "buildType.token", header: "Token", diff --git a/frontend/pages/living-space/tables/builds/data-table.tsx b/frontend/pages/living-space/tables/builds/data-table.tsx index c21f6f0..8f916c9 100644 --- a/frontend/pages/living-space/tables/builds/data-table.tsx +++ b/frontend/pages/living-space/tables/builds/data-table.tsx @@ -80,9 +80,8 @@ export function LivingSpaceBuildDataTable({ refetchTable, buildId, setBuildId, - collectionToken, - setCollectionToken, setIsUserTypeEnabled, + additionButtons }: { data: schemaType[], totalCount: number, @@ -93,9 +92,8 @@ export function LivingSpaceBuildDataTable({ refetchTable: () => void, buildId: string, setBuildId: (id: string) => void, - collectionToken: string, - setCollectionToken: (collectionToken: string) => void, setIsUserTypeEnabled: (enabled: boolean) => void, + additionButtons: React.ReactNode }) { const router = useRouter(); @@ -107,7 +105,7 @@ export function LivingSpaceBuildDataTable({ const sensors = useSensors(useSensor(MouseSensor, {}), useSensor(TouchSensor, {}), useSensor(KeyboardSensor, {})) const dataIds = React.useMemo(() => data?.map(({ _id }) => _id) || [], [data]) - const setSelection = (id: string, token: string) => { setBuildId(id); setCollectionToken(token); setIsUserTypeEnabled(true); } + const setSelection = (id: string) => { setBuildId(id); setIsUserTypeEnabled(true); } const columns = getColumns(setSelection); const pagination = React.useMemo(() => ({ pageIndex: currentPage - 1, pageSize: pageSize }), [currentPage, pageSize]) const totalPages = Math.ceil(totalCount / pageSize) @@ -170,6 +168,7 @@ export function LivingSpaceBuildDataTable({ ) })} + {additionButtons && additionButtons}
diff --git a/frontend/pages/living-space/tables/builds/page.tsx b/frontend/pages/living-space/tables/builds/page.tsx index 4496909..4ebdec1 100644 --- a/frontend/pages/living-space/tables/builds/page.tsx +++ b/frontend/pages/living-space/tables/builds/page.tsx @@ -4,12 +4,11 @@ import { useGraphQlBuildsList } from "./queries"; import { LivingSpaceBuildDataTable } from "./data-table"; const PageLivingSpaceBuildsTableSection = ( - { buildID, setBuildID, collectionToken, setCollectionToken, setIsUserTypeEnabled }: { + { buildID, setBuildID, setIsUserTypeEnabled, additionButtons }: { buildID: string | null; setBuildID: (id: string | null) => void; - collectionToken: string | null; - setCollectionToken: (token: string | null) => void; setIsUserTypeEnabled: (enabled: boolean) => void; + additionButtons?: React.ReactNode | null; } ) => { const [page, setPage] = useState(1); @@ -27,8 +26,8 @@ const PageLivingSpaceBuildsTableSection = ( return <> + refetchTable={refetch} buildId={buildID || ""} setBuildId={setBuildID} + setIsUserTypeEnabled={setIsUserTypeEnabled} additionButtons={additionButtons} /> ; } diff --git a/frontend/pages/living-space/tables/living-spaces/a.txt b/frontend/pages/living-space/tables/living-spaces/a.txt new file mode 100644 index 0000000..e69de29 diff --git a/frontend/pages/living-space/tables/living-spaces/list/columns.tsx b/frontend/pages/living-space/tables/living-spaces/list/columns.tsx new file mode 100644 index 0000000..7bec001 --- /dev/null +++ b/frontend/pages/living-space/tables/living-spaces/list/columns.tsx @@ -0,0 +1,91 @@ +"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" + +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(router: any, deleteHandler: (id: string) => void): ColumnDef[] { + return [ + { + accessorKey: "uuid", + header: "UUID", + }, + { + accessorKey: "build", + header: "Build", + }, + { + accessorKey: "part", + header: "Part", + }, + { + accessorKey: "userType", + header: "User Type", + }, + { + accessorKey: "company", + header: "Company", + }, + { + accessorKey: "person", + header: "Person", + }, + { + 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/living-spaces/list/data-table.tsx b/frontend/pages/living-space/tables/living-spaces/list/data-table.tsx new file mode 100644 index 0000000..9fd9bb9 --- /dev/null +++ b/frontend/pages/living-space/tables/living-spaces/list/data-table.tsx @@ -0,0 +1,265 @@ +"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" +import { useDeleteLivingSpacesMutation } from "./queries" + +export function LivingSpaceDataTable({ + data, + totalCount, + currentPage = 1, + pageSize = 10, + onPageChange, + onPageSizeChange, + refetchTable, +}: { + data: schemaType[], + totalCount: number, + currentPage?: number, + pageSize?: number, + onPageChange: (page: number) => void, + onPageSizeChange: (size: number) => void, + refetchTable: () => void, +}) { + + const router = useRouter() + 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 deleteMutation = useDeleteLivingSpacesMutation() + const deleteHandler = (id: string) => { deleteMutation.mutate({ uuid: id }); setTimeout(() => { refetchTable() }, 400) } + const columns = getColumns(router, deleteHandler); + 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/living-spaces/list/queries.tsx b/frontend/pages/living-space/tables/living-spaces/list/queries.tsx new file mode 100644 index 0000000..5451252 --- /dev/null +++ b/frontend/pages/living-space/tables/living-spaces/list/queries.tsx @@ -0,0 +1,21 @@ +'use client' +import { useMutation } from '@tanstack/react-query' + +const fetchGraphQlDeleteLivingSpace = async (uuid: string): Promise => { + console.log('Fetching test data from local API'); + try { + const res = await fetch(`/api/living-space/delete?uuid=${uuid}`, { method: 'GET', cache: 'no-store', credentials: "include" }); + 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 + } catch (error) { console.error('Error fetching test data:', error); throw error } +}; + +export function useDeleteLivingSpacesMutation() { + return useMutation({ + mutationFn: ({ uuid }: { uuid: string }) => fetchGraphQlDeleteLivingSpace(uuid), + onSuccess: () => { console.log("Living space deleted successfully") }, + onError: (error) => { console.error("Delete living space failed:", error) }, + }) +} + diff --git a/frontend/pages/living-space/tables/living-spaces/list/schema.tsx b/frontend/pages/living-space/tables/living-spaces/list/schema.tsx new file mode 100644 index 0000000..9691eef --- /dev/null +++ b/frontend/pages/living-space/tables/living-spaces/list/schema.tsx @@ -0,0 +1,19 @@ +import { z } from "zod"; + +export const schema = z.object({ + + _id: z.string(), + uuid: z.string(), + build: z.string(), + part: z.string(), + userType: z.string(), + company: z.string(), + person: z.string(), + createdAt: z.string(), + updatedAt: z.string(), + expiryEnds: z.string(), + expiryStarts: z.string(), + +}); + +export type schemaType = z.infer; diff --git a/frontend/pages/living-space/tables/userType/data-table.tsx b/frontend/pages/living-space/tables/userType/data-table.tsx index a73db90..ff47252 100644 --- a/frontend/pages/living-space/tables/userType/data-table.tsx +++ b/frontend/pages/living-space/tables/userType/data-table.tsx @@ -73,7 +73,6 @@ import { } from "@/components/ui/tabs" import { schemaType } from "./schema" import { getColumns, DraggableRow } from "./columns" -import { useRouter } from "next/navigation" export function LivingSpaceUserTypesDataTable({ data, @@ -84,6 +83,7 @@ export function LivingSpaceUserTypesDataTable({ onPageSizeChange, refetchTable, userTypeID, + setIsProperty, setUserTypeID, setIsPartsEnabled, setIsHandleCompanyAndPersonEnable, @@ -96,6 +96,7 @@ export function LivingSpaceUserTypesDataTable({ onPageSizeChange: (size: number) => void, refetchTable: () => void, userTypeID: string | null; + setIsProperty: (enabled: boolean | null) => void; setUserTypeID: (id: string | null) => void; setIsPartsEnabled: (enabled: boolean) => void; setIsHandleCompanyAndPersonEnable: (enabled: boolean) => void, @@ -107,7 +108,7 @@ 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); setIsHandleCompanyAndPersonEnable(true) } + const setSelection = (id: string, isProperty: boolean) => { setUserTypeID(id); isProperty ? setIsPartsEnabled(true) : setIsPartsEnabled(false); setIsHandleCompanyAndPersonEnable(true); setIsProperty(isProperty) } const columns = getColumns(setSelection); const pagination = React.useMemo(() => ({ pageIndex: currentPage - 1, pageSize: pageSize }), [currentPage, pageSize]) const totalPages = Math.ceil(totalCount / pageSize) diff --git a/frontend/pages/living-space/tables/userType/page.tsx b/frontend/pages/living-space/tables/userType/page.tsx index 8940797..a103c20 100644 --- a/frontend/pages/living-space/tables/userType/page.tsx +++ b/frontend/pages/living-space/tables/userType/page.tsx @@ -4,8 +4,9 @@ import { useGraphQlUserTypesList } from "./queries"; import { LivingSpaceUserTypesDataTable } from "./data-table"; const PageLivingSpaceUserTypesTableSection = ( - { userTypeID, setUserTypeID, setIsPartsEnabled, setIsHandleCompanyAndPersonEnable }: { + { userTypeID, setUserTypeID, setIsPartsEnabled, setIsHandleCompanyAndPersonEnable, setIsProperty }: { userTypeID: string | null; + setIsProperty: (enabled: boolean | null) => void; setUserTypeID: (id: string | null) => void; setIsPartsEnabled: (enabled: boolean) => void; setIsHandleCompanyAndPersonEnable: (enabled: boolean) => void, @@ -24,7 +25,7 @@ const PageLivingSpaceUserTypesTableSection = ( if (error) { return
Error loading users
} return <> ; diff --git a/frontend/pages/living-space/update/form.tsx b/frontend/pages/living-space/update/form.tsx new file mode 100644 index 0000000..2da1893 --- /dev/null +++ b/frontend/pages/living-space/update/form.tsx @@ -0,0 +1,131 @@ +import { Card, CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +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 FormUpdateNewLivingSpace = ({ form, onSubmit }: { form: any, onSubmit: (data: FormValues) => void }) => { + return <> + + +
+ +
+ {/* ( + + Build ID + + + + + + )} + /> */} + {/* ( + + Collection Token + + + + + + )} + /> */} + ( + + User Type ID + + + + + + )} + /> + ( + + Part ID + + + + + + )} + /> + ( + + Company ID + + + + + + )} + /> + ( + + Person ID + + + + + + )} + /> +
+ {/* EXPIRY DATES */} +
+ ( + + Expiry Starts + + + + + + )} + /> + ( + + Expiry Ends + + + + + + )} + /> +
+
+
+ +
+
+ +} + +export { FormUpdateNewLivingSpace }; \ No newline at end of file diff --git a/frontend/pages/living-space/update/page.tsx b/frontend/pages/living-space/update/page.tsx new file mode 100644 index 0000000..8385671 --- /dev/null +++ b/frontend/pages/living-space/update/page.tsx @@ -0,0 +1,92 @@ +'use client'; +import { useState, useEffect } from "react"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { FormValues, createForm } from "./schema"; +import { FormUpdateNewLivingSpace } from "./form"; +import { useUpdateLivingSpaceMutation } from "./queries"; +import { Button } from "@/components/ui/button"; +import { IconArrowLeftToArc } from "@tabler/icons-react"; +import { useRouter } from "next/navigation"; +import { useGraphQlLivingSpaceList } from "../list/queries"; +import { useSearchParams } from "next/navigation"; + +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"; + +const PageLivingSpaceUpdate = () => { + + const router = useRouter(); + const searchParams = useSearchParams() + const uuid = searchParams?.get('uuid') || null + const buildIDFromUrl = searchParams?.get('buildID') || null + const [page, setPage] = useState(1); + const [limit, setLimit] = useState(10); + const [sort, setSort] = useState({ createdAt: 'desc' }); + const [filters, setFilters] = useState({}); + + const backToBuildAddress = <>
UUID not found in search params
+ const { data, isLoading, refetch } = useGraphQlLivingSpaceList(buildIDFromUrl || '', { limit, skip: (page - 1) * limit, sort, filters: { ...filters, uuid } }); + const initData = data?.data?.[0] || null; + const [userTypeID, setUserTypeID] = useState(null); + const isPartInit = initData?.part !== null ? true : false; + const [isProperty, setIsProperty] = useState(isPartInit); + const [partID, setPartID] = useState(null); + const [companyID, setCompanyID] = useState(null); + const [personID, setPersonID] = useState(null); + const form = createForm({ userTypeID: initData?.userType || "", partID: initData?.part || "", companyID: initData?.company || "", personID: initData?.person || "" }); + + useEffect(() => { + if (initData) { + form.setValue("userTypeID", initData?.userType || ""); form.setValue("partID", initData?.part || ""); form.setValue("companyID", initData?.company || ""); + form.setValue("personID", initData?.person || ""); form.setValue("expiryStarts", initData?.expiryStarts || ""); form.setValue("expiryEnds", initData?.expiryEnds || ""); + setUserTypeID(initData?.userType || ""); setPartID(initData?.part || ""); setCompanyID(initData?.company || ""); setPersonID(initData?.person || ""); + } + }, [initData]) + useEffect(() => { + form.setValue("userTypeID", userTypeID || ""); form.setValue("partID", partID || ""); form.setValue("companyID", companyID || ""); form.setValue("personID", personID || ""); + }, [userTypeID, partID, companyID, personID, form]); + + const mutation = useUpdateLivingSpaceMutation(); + function onSubmit(values: FormValues) { mutation.mutate({ data: values, uuid: uuid || "", buildID: buildIDFromUrl || "" }) } + const [isUserTypeEnabled, setIsUserTypeEnabled] = useState(true); + const [isPartsEnabled, setIsPartsEnabled] = useState(false); + const [isHandleCompanyAndPersonEnable, setIsHandleCompanyAndPersonEnable] = useState(true); + + useEffect(() => { if (!!isProperty) { setIsPartsEnabled(true) } else { setPartID(null); setIsPartsEnabled(false) } }, [isProperty]) + useEffect(() => { if (isPartInit) { setIsPartsEnabled(true) } else { setIsPartsEnabled(false) } }, [isPartInit]) + + if (!uuid) { return backToBuildAddress }; if (!initData) { return backToBuildAddress } + const tabsClassName = "border border-gray-300 rounded-sm h-10" + + return <> +
+ + + + {isUserTypeEnabled && User Type} + {isPartsEnabled && Parts} + {isHandleCompanyAndPersonEnable && Company} + {isHandleCompanyAndPersonEnable && Person} + +
+ {isUserTypeEnabled && + + } + {isPartsEnabled && buildIDFromUrl && + + } + {isHandleCompanyAndPersonEnable && } + {isHandleCompanyAndPersonEnable && } +
+
+
+
+ +} + +export { PageLivingSpaceUpdate }; \ No newline at end of file diff --git a/frontend/pages/living-space/update/queries.tsx b/frontend/pages/living-space/update/queries.tsx new file mode 100644 index 0000000..233ddb5 --- /dev/null +++ b/frontend/pages/living-space/update/queries.tsx @@ -0,0 +1,36 @@ +'use client' +import { z } from 'zod' +import { useMutation } from '@tanstack/react-query' +import { toISOIfNotZ } from '@/lib/utils' + +export const formSchema = z.object({ + 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 fetchGraphQlLivingSpaceUpdate = async (record: schemaType, uuid: string, buildID: 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-space/update?uuid=${uuid || ''}&buildID=${buildID || ''}`, { 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 useUpdateLivingSpaceMutation() { + return useMutation({ + mutationFn: ({ data, uuid, buildID }: { data: schemaType, uuid: string, buildID: string }) => fetchGraphQlLivingSpaceUpdate(data, uuid, buildID), + onSuccess: () => { console.log("Living Space updated successfully") }, + onError: (error) => { console.error("Update Living Space update failed:", error) }, + }) +} diff --git a/frontend/pages/living-space/update/schema.ts b/frontend/pages/living-space/update/schema.ts new file mode 100644 index 0000000..ef4e4bf --- /dev/null +++ b/frontend/pages/living-space/update/schema.ts @@ -0,0 +1,29 @@ +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({ + 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({ userTypeID, partID, companyID, personID }: FormProps) { + return useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + userTypeID: userTypeID || "", + partID: partID || "", + companyID: companyID || "", + personID: personID || "", + expiryStarts: "", + expiryEnds: "" + } + }); +} diff --git a/frontend/pages/living-space/update/types.ts b/frontend/pages/living-space/update/types.ts new file mode 100644 index 0000000..9abb4ff --- /dev/null +++ b/frontend/pages/living-space/update/types.ts @@ -0,0 +1,9 @@ +interface FormProps { + userTypeID: string | null; + partID: string | null; + companyID: string | null; + personID: string | null; +} + + +export type { FormProps }; \ No newline at end of file