From a9655c5f489cd76ca552bf30a56319f24d8f408a Mon Sep 17 00:00:00 2001 From: Berkay Date: Tue, 24 Jun 2025 12:41:06 +0300 Subject: [PATCH] 2 chained application designed and new stage inited --- .../endpoints/building_parts/router.py | 30 +- .../Building/endpoints/builds/router.py | 38 +- .../events/building_parts/supers_events.py | 58 +- .../Building/events/builds/supers_events.py | 71 ++- .../Extensions/Middlewares/token_provider.py | 17 +- .../src/app/api/builds/create/route.ts | 23 + .../customer/src/app/api/builds/list/route.ts | 22 + .../src/app/api/builds/update/[uuid]/route.ts | 30 + .../customer/src/app/api/test/list/route.ts | 8 - .../customer/src/app/api/test/update/route.ts | 3 - .../content/TableCardComponentImproved.tsx | 44 +- .../custom/content/createFromComponent.tsx | 38 +- .../custom/content/updateFromComponent.tsx | 18 +- .../mutual/context/cache/context.tsx | 28 +- .../mutual/context/online/context.ts | 6 - .../mutual/context/online/provider.tsx | 41 +- .../components/mutual/context/user/context.ts | 71 +-- .../mutual/formInputs/CheckBoxInput.tsx | 50 ++ .../mutual/formInputs/DatetimeInput.tsx | 68 +++ .../mutual/formInputs/NumberInput.tsx | 58 ++ .../mutual/formInputs/StringInput.tsx | 53 ++ .../mutual/providers/client-providers.tsx | 5 - .../custom/backend}/a.txt | 0 .../fetchers/custom/backend/builds/fetch.tsx | 30 + ServicesWeb/customer/src/fetchers/index.ts | 14 + ServicesWeb/customer/src/fetchers/types.ts | 10 +- .../customer/src/hooks/useTableData.ts | 1 - .../src/languages/mutual/menu/english.ts | 2 +- .../src/languages/mutual/menu/turkish.ts | 2 +- .../{builds/listPage.tsx => buildParts/a.txt} | 0 .../multi/buildParts/superuser/CreatePage.tsx | 221 +++++++ .../multi/buildParts/superuser/ListPage.tsx | 542 +++++++++++++++++ .../multi/buildParts/superuser/UpdatePage.tsx | 234 +++++++ .../multi/buildParts/superuser/schema.ts | 56 ++ .../buildParts/superuser/translations.ts | 45 ++ .../customer/src/pages/multi/builds/a.txt | 0 .../multi/builds/superuser/CreatePage.tsx | 307 ++++++++++ .../pages/multi/builds/superuser/ListPage.tsx | 573 ++++++++++++++++++ .../multi/builds/superuser/UpdatePage.tsx | 335 ++++++++++ .../pages/multi/builds/superuser/schema.ts | 57 ++ .../multi/builds/superuser/translations.ts | 48 ++ ServicesWeb/customer/src/pages/multi/index.ts | 17 +- .../src/schemas/custom/building/build.ts | 190 +++--- ServicesWeb/customer/src/utils/zodTypes.ts | 11 + .../validations/mutual/table/validations.ts | 1 + 45 files changed, 3090 insertions(+), 386 deletions(-) create mode 100644 ServicesWeb/customer/src/app/api/builds/create/route.ts create mode 100644 ServicesWeb/customer/src/app/api/builds/list/route.ts create mode 100644 ServicesWeb/customer/src/app/api/builds/update/[uuid]/route.ts create mode 100644 ServicesWeb/customer/src/components/mutual/formInputs/CheckBoxInput.tsx create mode 100644 ServicesWeb/customer/src/components/mutual/formInputs/DatetimeInput.tsx create mode 100644 ServicesWeb/customer/src/components/mutual/formInputs/NumberInput.tsx create mode 100644 ServicesWeb/customer/src/components/mutual/formInputs/StringInput.tsx rename ServicesWeb/customer/src/{layouts => fetchers/custom/backend}/a.txt (100%) create mode 100644 ServicesWeb/customer/src/fetchers/custom/backend/builds/fetch.tsx rename ServicesWeb/customer/src/pages/multi/{builds/listPage.tsx => buildParts/a.txt} (100%) create mode 100644 ServicesWeb/customer/src/pages/multi/buildParts/superuser/CreatePage.tsx create mode 100644 ServicesWeb/customer/src/pages/multi/buildParts/superuser/ListPage.tsx create mode 100644 ServicesWeb/customer/src/pages/multi/buildParts/superuser/UpdatePage.tsx create mode 100644 ServicesWeb/customer/src/pages/multi/buildParts/superuser/schema.ts create mode 100644 ServicesWeb/customer/src/pages/multi/buildParts/superuser/translations.ts create mode 100644 ServicesWeb/customer/src/pages/multi/builds/a.txt create mode 100644 ServicesWeb/customer/src/pages/multi/builds/superuser/CreatePage.tsx create mode 100644 ServicesWeb/customer/src/pages/multi/builds/superuser/ListPage.tsx create mode 100644 ServicesWeb/customer/src/pages/multi/builds/superuser/UpdatePage.tsx create mode 100644 ServicesWeb/customer/src/pages/multi/builds/superuser/schema.ts create mode 100644 ServicesWeb/customer/src/pages/multi/builds/superuser/translations.ts create mode 100644 ServicesWeb/customer/src/utils/zodTypes.ts diff --git a/ServicesApi/Builds/Building/endpoints/building_parts/router.py b/ServicesApi/Builds/Building/endpoints/building_parts/router.py index ef4b946..357d152 100644 --- a/ServicesApi/Builds/Building/endpoints/building_parts/router.py +++ b/ServicesApi/Builds/Building/endpoints/building_parts/router.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Optional, Any from fastapi import APIRouter, Depends from index import endpoints_index @@ -7,11 +7,27 @@ from events.building_parts.cluster import PartsRouterCluster from Validations.defaults.validations import CommonHeaders from Validations.response.pagination import PaginateOnly from Extensions.Middlewares.token_provider import TokenProvider +from pydantic import BaseModel parts_endpoint_route = APIRouter(prefix="/parts", tags=["Parts Cluster"]) +class PartsListRequest(BaseModel): + address_gov_code: str + build_uu_id: str = None + part_no: int + part_level: int + part_code: str + part_gross_size: int + part_net_size: int + default_accessory: str + human_livable: bool + due_part_key: Optional[str] = None + part_direction_uu_id: Optional[str] = None + part_type_uu_id: Optional[str] = None + + parts_list = "PartsList" @parts_endpoint_route.post( path="/list", @@ -22,7 +38,7 @@ def parts_list_route(data: PaginateOnly, headers: CommonHeaders = Depends(Common token_object = TokenProvider.get_dict_from_redis(token=headers.token) event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object) event_key = TokenProvider.retrieve_event_codes(**event_founder_dict) - FoundCluster = PartsRouterCluster.get_event_cluster(parts_list) + FoundCluster = PartsRouterCluster.get_event_cluster("PartsListEventCluster") event_cluster_matched = FoundCluster.match_event(event_key=event_key) return event_cluster_matched.event_callable(list_options=data, headers=headers) @@ -33,11 +49,11 @@ parts_create = "PartsCreate" description="Create part endpoint", operation_id=endpoints_index[parts_create], ) -def parts_create_route(data, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)): +def parts_create_route(data: PartsListRequest, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)): token_object = TokenProvider.get_dict_from_redis(token=headers.token) event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object) event_key = TokenProvider.retrieve_event_codes(**event_founder_dict) - FoundCluster = PartsRouterCluster.get_event_cluster(parts_create) + FoundCluster = PartsRouterCluster.get_event_cluster("PartsCreateEventCluster") event_cluster_matched = FoundCluster.match_event(event_key=event_key) return event_cluster_matched.event_callable(data=data, headers=headers) @@ -48,11 +64,11 @@ parts_update = "PartsUpdate" description="Update part endpoint", operation_id=endpoints_index[parts_update], ) -def parts_update_route(uu_id: str, data, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)): +def parts_update_route(uu_id: str, data: PartsListRequest, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)): token_object = TokenProvider.get_dict_from_redis(token=headers.token) event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object) event_key = TokenProvider.retrieve_event_codes(**event_founder_dict) - FoundCluster = PartsRouterCluster.get_event_cluster(parts_update) + FoundCluster = PartsRouterCluster.get_event_cluster("PartsUpdateEventCluster") event_cluster_matched = FoundCluster.match_event(event_key=event_key) return event_cluster_matched.event_callable(uu_id=uu_id, data=data, headers=headers) @@ -67,6 +83,6 @@ def parts_delete_route(uu_id: str, headers: CommonHeaders = Depends(CommonHeader token_object = TokenProvider.get_dict_from_redis(token=headers.token) event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object) event_key = TokenProvider.retrieve_event_codes(**event_founder_dict) - FoundCluster = PartsRouterCluster.get_event_cluster(parts_delete) + FoundCluster = PartsRouterCluster.get_event_cluster("PartsDeleteEventCluster") event_cluster_matched = FoundCluster.match_event(event_key=event_key) return event_cluster_matched.event_callable(uu_id=uu_id, headers=headers) \ No newline at end of file diff --git a/ServicesApi/Builds/Building/endpoints/builds/router.py b/ServicesApi/Builds/Building/endpoints/builds/router.py index 4b9a684..4014fe7 100644 --- a/ServicesApi/Builds/Building/endpoints/builds/router.py +++ b/ServicesApi/Builds/Building/endpoints/builds/router.py @@ -1,5 +1,6 @@ -from typing import Any +from typing import Any, Optional from fastapi import APIRouter, Depends +from pydantic import BaseModel from index import endpoints_index from events.builds.cluster import BuildRouterCluster @@ -9,6 +10,31 @@ from Validations.response.pagination import PaginateOnly from Extensions.Middlewares.token_provider import TokenProvider +# Pydantic model for build update validation +class BuildFormModel(BaseModel): + gov_address_code: str + build_name: str + build_no: str + max_floor: int + underground_floor: int + build_date: str + decision_period_date: str + tax_no: str + lift_count: int + heating_system: bool + cooling_system: bool + hot_water_system: bool + block_service_man_count: int + security_service_man_count: int + garage_count: int + site_uu_id: Optional[str] = None + address_uu_id: str + build_types_uu_id: str + + class Config: + extra = "allow" + + build_endpoint_route = APIRouter(prefix="/builds", tags=["Builds Cluster"]) build_list = "BuildList" @@ -21,7 +47,7 @@ def build_list_route(data: PaginateOnly, headers: CommonHeaders = Depends(Common token_object = TokenProvider.get_dict_from_redis(token=headers.token) event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object) event_key = TokenProvider.retrieve_event_codes(**event_founder_dict) - FoundCluster = BuildRouterCluster.get_event_cluster(build_list) + FoundCluster = BuildRouterCluster.get_event_cluster("BuildListEventCluster") event_cluster_matched = FoundCluster.match_event(event_key=event_key) return event_cluster_matched.event_callable(list_options=data, headers=headers) @@ -32,11 +58,11 @@ build_create = "BuildCreate" description="Create build endpoint", operation_id=endpoints_index[build_create], ) -def build_create_route(data, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)): +def build_create_route(data: BuildFormModel, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)): token_object = TokenProvider.get_dict_from_redis(token=headers.token) event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object) event_key = TokenProvider.retrieve_event_codes(**event_founder_dict) - FoundCluster = BuildRouterCluster.get_event_cluster(build_create) + FoundCluster = BuildRouterCluster.get_event_cluster("BuildCreateEventCluster") event_cluster_matched = FoundCluster.match_event(event_key=event_key) return event_cluster_matched.event_callable(data=data, headers=headers) @@ -47,11 +73,11 @@ build_update = "BuildUpdate" description="Update build endpoint", operation_id=endpoints_index[build_update], ) -def build_update_route(uu_id: str, data, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)): +def build_update_route(uu_id: str, data: BuildFormModel, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)): token_object = TokenProvider.get_dict_from_redis(token=headers.token) event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object) event_key = TokenProvider.retrieve_event_codes(**event_founder_dict) - FoundCluster = BuildRouterCluster.get_event_cluster(build_update) + FoundCluster = BuildRouterCluster.get_event_cluster("BuildUpdateEventCluster") event_cluster_matched = FoundCluster.match_event(event_key=event_key) return event_cluster_matched.event_callable(uu_id=uu_id, data=data, headers=headers) diff --git a/ServicesApi/Builds/Building/events/building_parts/supers_events.py b/ServicesApi/Builds/Building/events/building_parts/supers_events.py index 14fdfd3..1914851 100644 --- a/ServicesApi/Builds/Building/events/building_parts/supers_events.py +++ b/ServicesApi/Builds/Building/events/building_parts/supers_events.py @@ -13,6 +13,9 @@ from Validations.defaults.validations import CommonHeaders from Schemas import ( Build, BuildParts, + ApiEnumDropdown, + BuildTypes, + BuildParts, AccountRecords, ) @@ -55,17 +58,43 @@ SuperPartsDeleteEvent = Event( def super_parts_list_callable(list_options: PaginateOnly, headers: CommonHeaders): - return { - "message": "MSG0003-LIST", - "data": None, - "completed": True, - } + list_options = PaginateOnly(**list_options.model_dump()) + # TODO: Pydantic Model must be implemnted for list_options.query + with Build.new_session() as db_session: + BuildParts.set_session(db_session) + base_query = BuildParts.query.filter() + build_parts_records_query = base_query + if list_options.query: + build_parts_records_query = BuildParts.query.filter(*BuildParts.convert(list_options.query)) + pagination = Pagination(data=build_parts_records_query, base_query=base_query) + pagination.change(**list_options.model_dump()) + pagination_result = PaginationResult(data=build_parts_records_query, pagination=pagination, response_model=None) + return EndpointResponse(message="MSG0003-LIST", pagination_result=pagination_result).response SuperPartsListEvent.event_callable = super_parts_list_callable def super_parts_create_callable(data, headers: CommonHeaders): + + with Build.new_session() as db_session: + + Build.set_session(db_session) + BuildParts.set_session(db_session) + BuildTypes.set_session(db_session) + ApiEnumDropdown.set_session(db_session) + + build = Build.query.filter(Build.uu_id == data.build_uu_id).first() + part_direction = ApiEnumDropdown.query.filter(ApiEnumDropdown.uu_id == data.part_direction_uu_id).first() + part_type = BuildTypes.query.filter(BuildTypes.uu_id == data.part_type_uu_id).first() + + build_parts_created = BuildParts.create( + **data.model_dump(), + build_id=getattr(build, "id", None), + part_direction_id=getattr(part_direction, "id", None), + part_type_id=getattr(part_type, "id", None) + ) + build_parts_created.save() return { "message": "MSG0001-INSERT", "data": None, @@ -77,6 +106,25 @@ SuperPartsCreateEvent.event_callable = super_parts_create_callable def super_parts_update_callable(data, headers: CommonHeaders): + with Build.new_session() as db_session: + + Build.set_session(db_session) + BuildParts.set_session(db_session) + BuildTypes.set_session(db_session) + ApiEnumDropdown.set_session(db_session) + + build = Build.query.filter(Build.uu_id == data.build_uu_id).first() + part_direction = ApiEnumDropdown.query.filter(ApiEnumDropdown.uu_id == data.part_direction_uu_id).first() + part_type = BuildTypes.query.filter(BuildTypes.uu_id == data.part_type_uu_id).first() + + build_parts_updated = BuildParts.query.filter(BuildParts.uu_id == data.uu_id).first() + build_parts_updated.update( + **data.model_dump(exclude_unset=True, exclude_none=True), + build_id=getattr(build, "id", None), + part_direction_id=getattr(part_direction, "id", None), + part_type_id=getattr(part_type, "id", None) + ) + build_parts_updated.save() return { "message": "MSG0002-UPDATE", "data": None, diff --git a/ServicesApi/Builds/Building/events/builds/supers_events.py b/ServicesApi/Builds/Building/events/builds/supers_events.py index d6829dc..d5bb436 100644 --- a/ServicesApi/Builds/Building/events/builds/supers_events.py +++ b/ServicesApi/Builds/Building/events/builds/supers_events.py @@ -11,9 +11,13 @@ from Validations.response import ( ) from Validations.defaults.validations import CommonHeaders from Schemas import ( + Addresses, + BuildTypes, Build, + BuildSites, BuildParts, - AccountRecords, + Companies, + # AccountRecords, ) @@ -53,32 +57,16 @@ SuperBuildDeleteEvent = Event( def super_build_list_callable(list_options: PaginateOnly, headers: CommonHeaders): list_options = PaginateOnly(**list_options.model_dump()) - if token.is_employee: - raise Exception("Forbidden for employees") - # TODO: Pydantic Model must be implemnted for list_options.query - with AccountRecords.new_session() as db_session: - AccountRecords.set_session(db_session) - list_of_fields = [ - AccountRecords.iban, - AccountRecords.bank_date, - AccountRecords.currency, - AccountRecords.currency_value, - AccountRecords.process_comment, - AccountRecords.add_comment_note, - AccountRecords.receive_debit, - AccountRecords.is_email_send, - AccountRecords.is_notification_send, - ] - account_records_query = db_session.query(*list_of_fields - ).join(BuildParts, BuildParts.id == AccountRecords.build_parts_id - ).filter(BuildParts.id == token.selected_occupant.build_part_id) + with Build.new_session() as db_session: + Build.set_session(db_session) + base_query = Build.query.filter() + build_records_query = base_query if list_options.query: - account_records_query = account_records_query.filter(*AccountRecords.convert(list_options.query)) - - pagination = Pagination(data=account_records_query) + build_records_query = Build.query.filter(*Build.convert(list_options.query)) + pagination = Pagination(data=build_records_query, base_query=base_query) pagination.change(**list_options.model_dump()) - pagination_result = PaginationResult(data=account_records_query, pagination=pagination) + pagination_result = PaginationResult(data=build_records_query, pagination=pagination, response_model=None) return EndpointResponse(message="MSG0003-LIST", pagination_result=pagination_result).response @@ -86,6 +74,19 @@ SuperBuildListEvent.event_callable = super_build_list_callable def super_build_create_callable(data, headers: CommonHeaders): + + with Build.new_session() as db_session: + + Build.set_session(db_session) + Addresses.set_session(db_session) + BuildTypes.set_session(db_session) + BuildSites.set_session(db_session) + + address_id = Addresses.query.filter(Addresses.uu_id == data.address_uu_id).first() + build_types_id = BuildTypes.query.filter(BuildTypes.uu_id == data.build_types_uu_id).first() + sites_id = BuildSites.query.filter(BuildSites.uu_id == data.site_uu_id).first() + build = Build.create(**data.model_dump(), address_id=getattr(address_id, "id", None), build_types_id=getattr(build_types_id, "id", None), site_id=getattr(sites_id, "id", None)) + build.save() return { "message": "MSG0001-INSERT", "data": None, @@ -96,7 +97,23 @@ def super_build_create_callable(data, headers: CommonHeaders): SuperBuildCreateEvent.event_callable = super_build_create_callable -def super_build_update_callable(data, headers: CommonHeaders): +def super_build_update_callable(uu_id: str, data, headers: CommonHeaders): + data_dict = data.model_dump(exclude_unset=True, exclude_none=True) + data_dict.pop("uu_id", None) + data_dict.pop("uuid", None) + with Build.new_session() as db_session: + Build.set_session(db_session) + Addresses.set_session(db_session) + BuildTypes.set_session(db_session) + BuildSites.set_session(db_session) + + address_id = Addresses.query.filter(Addresses.uu_id == data.address_uu_id).first() + build_types_id = BuildTypes.query.filter(BuildTypes.uu_id == data.build_types_uu_id).first() + sites_id = BuildSites.query.filter(BuildSites.uu_id == data.site_uu_id).first() + + build = Build.query.filter(Build.uu_id == uu_id).first() + build.update(**data_dict, address_id=getattr(address_id, "id", None), build_types_id=getattr(build_types_id, "id", None), site_id=getattr(sites_id, "id", None)) + build.save() return { "message": "MSG0002-UPDATE", "data": None, @@ -108,6 +125,10 @@ SuperBuildUpdateEvent.event_callable = super_build_update_callable def super_build_delete_callable(uu_id: str, headers: CommonHeaders): + with Build.new_session() as db_session: + Build.set_session(db_session) + build = Build.query.filter(Build.uu_id == uu_id).first() + build.delete() return { "message": "MSG0003-DELETE", "data": None, diff --git a/ServicesApi/Extensions/Middlewares/token_provider.py b/ServicesApi/Extensions/Middlewares/token_provider.py index 85c1dc7..93e32ab 100644 --- a/ServicesApi/Extensions/Middlewares/token_provider.py +++ b/ServicesApi/Extensions/Middlewares/token_provider.py @@ -89,10 +89,15 @@ class TokenProvider: """ Retrieve event code from the token object or list of token objects. """ - if isinstance(token, EmployeeTokenObject): - if event_codes := token.selected_company.reachable_event_codes.get(endpoint_code, None): - return event_codes - elif isinstance(token, OccupantTokenObject): - if event_codes := token.selected_occupant.reachable_event_codes.get(endpoint_code, None): - return event_codes + if token.is_employee and token.selected_company: + employee_uu_id = token.selected_company.get("uu_id", None) + print("endpoint_code", endpoint_code) + print("employee_uu_id", employee_uu_id) + if reachable_event_codes_dict := token.reachable_event_codes: + print("reachable_event_codes_dict", reachable_event_codes_dict.get(employee_uu_id, {})) + return reachable_event_codes_dict.get(employee_uu_id, {}).get(endpoint_code, None) + elif token.is_occupant and token.selected_occupant: + occupant_uu_id = token.selected_occupant.get("build_living_space_uu_id", None) + if reachable_event_codes_dict := token.reachable_event_codes: + return reachable_event_codes_dict.get(occupant_uu_id, {}).get(endpoint_code, None) raise ValueError("Invalid token type or no event code found.") diff --git a/ServicesWeb/customer/src/app/api/builds/create/route.ts b/ServicesWeb/customer/src/app/api/builds/create/route.ts new file mode 100644 index 0000000..dd49b4a --- /dev/null +++ b/ServicesWeb/customer/src/app/api/builds/create/route.ts @@ -0,0 +1,23 @@ +import { NextResponse } from "next/server"; +import { createBuilding } from "@/fetchers/custom/backend/builds/fetch"; + +export async function POST(req: Request): Promise { + try { + const body = await req.json(); + console.log("POST CREATE BUILD", { + body, + }); + const buildingCreated = await createBuilding(body); + + return NextResponse.json({ + status: 200, + data: buildingCreated, + }); + } catch (error) { + console.error("Error creating building:", error); + return NextResponse.json( + { error: "Error creating building" }, + { status: 500 } + ); + } +} diff --git a/ServicesWeb/customer/src/app/api/builds/list/route.ts b/ServicesWeb/customer/src/app/api/builds/list/route.ts new file mode 100644 index 0000000..4ead45a --- /dev/null +++ b/ServicesWeb/customer/src/app/api/builds/list/route.ts @@ -0,0 +1,22 @@ +import { NextResponse } from "next/server"; +import { getBuildingsList } from "@/fetchers/custom/backend/builds/fetch"; + +export async function POST(req: Request): Promise { + const body = await req.json(); + const pagination = { + page: body.page || 1, + size: body.size || 10, + orderField: body.orderField || ["uu_id"], + orderType: body.orderType || ["asc"], + query: body.query || {}, + }; + try { + const buildingsList = await getBuildingsList(pagination); + return NextResponse.json({ data: buildingsList }); + } catch (error) { + return NextResponse.json( + { error: "Error fetching buildings list" }, + { status: 500 } + ); + } +} diff --git a/ServicesWeb/customer/src/app/api/builds/update/[uuid]/route.ts b/ServicesWeb/customer/src/app/api/builds/update/[uuid]/route.ts new file mode 100644 index 0000000..0810bca --- /dev/null +++ b/ServicesWeb/customer/src/app/api/builds/update/[uuid]/route.ts @@ -0,0 +1,30 @@ +import { NextResponse } from "next/server"; +import { updateBuilding } from "@/fetchers/custom/backend/builds/fetch"; + +// Define the route handler with proper typing for Next.js App Router +export async function POST( + req: Request, + { params }: { params: { uuid: string } } +): Promise { + try { + const body = await req.json(); + const uuid = params.uuid; + + console.log("POST UPDATE BUILD", { + body, + uuid, + }); + + const buildingCreated = await updateBuilding(uuid, body); + return NextResponse.json({ + status: 200, + data: buildingCreated, + }); + } catch (error) { + console.error("Error updating building:", error); + return NextResponse.json( + { error: "Error updating building" }, + { status: 500 } + ); + } +} diff --git a/ServicesWeb/customer/src/app/api/test/list/route.ts b/ServicesWeb/customer/src/app/api/test/list/route.ts index 342bfc1..e94dc51 100644 --- a/ServicesWeb/customer/src/app/api/test/list/route.ts +++ b/ServicesWeb/customer/src/app/api/test/list/route.ts @@ -34,10 +34,6 @@ export async function GET(request: Request) { const { paginatedData, meta } = getPaginatedData(page, size); - console.log( - `GET /api/test/list - Returning ${paginatedData.length} items (page ${page}/${meta.totalPages})` - ); - // Return paginated response with the structure that useTableData expects return NextResponse.json({ success: true, @@ -76,10 +72,6 @@ export async function POST(request: Request) { const { paginatedData, meta } = getPaginatedData(page, size); - console.log( - `POST /api/test/list - Returning ${paginatedData.length} items (page ${page}/${meta.totalPages})` - ); - // Return paginated response with the structure that useTableData expects return NextResponse.json({ success: true, diff --git a/ServicesWeb/customer/src/app/api/test/update/route.ts b/ServicesWeb/customer/src/app/api/test/update/route.ts index c86d788..19321fe 100644 --- a/ServicesWeb/customer/src/app/api/test/update/route.ts +++ b/ServicesWeb/customer/src/app/api/test/update/route.ts @@ -32,9 +32,6 @@ export async function PATCH(request: Request) { }; globalData[itemIndex] = updatedItem; - - console.log(`PATCH /api/test/update - Updated item with id: ${updatedItem.id}`); - return NextResponse.json({ status: 200, data: updatedItem, diff --git a/ServicesWeb/customer/src/components/custom/content/TableCardComponentImproved.tsx b/ServicesWeb/customer/src/components/custom/content/TableCardComponentImproved.tsx index 7a80cce..fda64f2 100644 --- a/ServicesWeb/customer/src/components/custom/content/TableCardComponentImproved.tsx +++ b/ServicesWeb/customer/src/components/custom/content/TableCardComponentImproved.tsx @@ -126,7 +126,6 @@ const DataTable: React.FC = React.memo(({ {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { - console.log('Rendering header:', header.id); return ( = React.memo(({ table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => { - console.log('Rendering cell:', cell.column.id); return ( {flexRender(cell.column.columnDef.cell, cell.getContext())} @@ -371,14 +369,12 @@ const TableCardComponentImproved: React.FC = React.memo((pro renderPageOptions, } = useTableData({ apiUrl: `${API_BASE_URL}/test/list` }); const activePageUrl = props.activePageUrl || ''; - console.log("activePageUrl", activePageUrl); // Function to handle editing a row const handleEditRow = async (row: TableDataItem) => { try { // Store the row data in the cache await setCacheData(`${activePageUrl}/update`, row); - console.log('Row data stored in cache:', row); // Navigate to the update form router.push(`/panel/${activePageUrl}/update`); @@ -387,30 +383,28 @@ const TableCardComponentImproved: React.FC = React.memo((pro } }; + const actionButtonGroup = (info: any) => ( + + ); + const columnHelper = createColumnHelper(); - // Make sure columns are properly defined with the actions column const columns = React.useMemo(() => [ columnHelper.display({ id: 'actions', header: () => Actions, - cell: (info) => { - console.log('Rendering action cell for row:', info.row.id); - return ( - - ); - } + cell: (info) => { return (actionButtonGroup(info)) } }), columnHelper.accessor('name', { cell: info => info.getValue(), @@ -465,12 +459,8 @@ const TableCardComponentImproved: React.FC = React.memo((pro getPaginationRowModel: getPaginationRowModel(), manualPagination: true, pageCount: apiPagination.totalPages || 1, - debugTable: true, }); - // Check if actions column is included - const actionColumnExists = table.getVisibleLeafColumns().some(col => col.id === 'actions'); - return ( isLoading ? :
diff --git a/ServicesWeb/customer/src/components/custom/content/createFromComponent.tsx b/ServicesWeb/customer/src/components/custom/content/createFromComponent.tsx index 6ccfd14..e28ae4e 100644 --- a/ServicesWeb/customer/src/components/custom/content/createFromComponent.tsx +++ b/ServicesWeb/customer/src/components/custom/content/createFromComponent.tsx @@ -72,16 +72,9 @@ function CreateFromComponentBase({ const fetchCacheDirectly = async () => { if (activePageUrl && !cacheLoaded) { try { - console.log("Directly fetching cache for URL:", activePageUrl); - - // Directly fetch cache data using the imported function const cachedData = await getCacheData(activePageUrl); - console.log("Directly fetched cache data:", cachedData); - if (cachedData) { const formValues = form.getValues(); - - // Create a merged data object with proper typing const mergedData: FormValues = { name: cachedData.name || formValues.name || "", email: cachedData.email || formValues.email || "", @@ -92,20 +85,12 @@ function CreateFromComponentBase({ terms: cachedData.terms ?? formValues.terms ?? false, attachments: cachedData.attachments || formValues.attachments || "" }; - - console.log("Setting form with direct cache data:", mergedData); form.reset(mergedData); } setCacheLoaded(true); - - // Also call the context refresh if available (for state consistency) - if (refreshCache) { - refreshCache(activePageUrl); - } - } catch (error) { - console.error("Error fetching cache directly:", error); - } + if (refreshCache) { refreshCache(activePageUrl) } + } catch (error) { console.error("Error fetching cache directly:", error) } } }; @@ -117,24 +102,13 @@ function CreateFromComponentBase({ // Only update if the value is not empty if (value && activePageUrl) { try { - // Get current form values const currentValues = form.getValues(); - - // Prepare updated data const updatedData = { ...currentValues, [fieldName]: value }; - - console.log("Directly updating cache with:", updatedData); - - // Directly update cache using imported function await setCacheData(activePageUrl, updatedData); - - // Also use the context method if available (for state consistency) - if (updateCache) { - updateCache(activePageUrl, updatedData); - } + if (updateCache) { updateCache(activePageUrl, updatedData) } } catch (error) { console.error("Error updating cache:", error); } @@ -143,14 +117,8 @@ function CreateFromComponentBase({ // Type-safe submit handler const onSubmit = async (data: FormValues) => { - console.log("Form submitted with data:", data); - try { - // Submit form data to API const response = await apiPostFetcher({ url: "/tst/create", isNoCache: true, body: data }); - console.log("API response:", response); - - // Clear cache on successful submission if (activePageUrl) { try { console.log("Directly clearing cache for URL:", activePageUrl); diff --git a/ServicesWeb/customer/src/components/custom/content/updateFromComponent.tsx b/ServicesWeb/customer/src/components/custom/content/updateFromComponent.tsx index dcd2852..c45f95b 100644 --- a/ServicesWeb/customer/src/components/custom/content/updateFromComponent.tsx +++ b/ServicesWeb/customer/src/components/custom/content/updateFromComponent.tsx @@ -31,7 +31,7 @@ const zodSchema = z.object({ // Define the form type type FormValues = z.infer; -function UpdateFromComponentBase({ +function UpdateFromBuildComponentBase({ cacheData, cacheLoading, cacheError, @@ -72,11 +72,9 @@ function UpdateFromComponentBase({ const fetchCacheDirectly = async () => { if (activePageUrl && !cacheLoaded) { try { - console.log("Directly fetching cache for URL:", activePageUrl); // Directly fetch cache data using the imported function const cachedData = await getCacheData(activePageUrl); - console.log("Directly fetched cache data:", cachedData); if (cachedData) { const formValues = form.getValues(); @@ -90,12 +88,6 @@ function UpdateFromComponentBase({ const termsValue = typeof cachedData.terms === 'string' ? cachedData.terms === 'true' : Boolean(cachedData.terms); - - console.log("Raw notification value from cache:", cachedData.notifications, "type:", typeof cachedData.notifications); - console.log("Raw terms value from cache:", cachedData.terms, "type:", typeof cachedData.terms); - console.log("Converted notification value:", notificationsValue); - console.log("Converted terms value:", termsValue); - const mergedData: FormValues = { name: cachedData.name || formValues.name || "", email: cachedData.email || formValues.email || "", @@ -107,13 +99,9 @@ function UpdateFromComponentBase({ attachments: cachedData.attachments || formValues.attachments || "" }; - console.log("Setting form with direct cache data:", mergedData); form.reset(mergedData); } - setCacheLoaded(true); - - // Also call the context refresh if available (for state consistency) if (refreshCache) { refreshCache(activePageUrl); } @@ -419,7 +407,7 @@ function UpdateFromComponentBase({ ); } -export const UpdateFromComponent = withCache(UpdateFromComponentBase); +export const UpdateFromBuildComponent = withCache(UpdateFromBuildComponentBase); // Add default export to maintain compatibility with existing imports -export default withCache(UpdateFromComponentBase); +export default withCache(UpdateFromBuildComponentBase); diff --git a/ServicesWeb/customer/src/components/mutual/context/cache/context.tsx b/ServicesWeb/customer/src/components/mutual/context/cache/context.tsx index 1078c56..9cd8812 100644 --- a/ServicesWeb/customer/src/components/mutual/context/cache/context.tsx +++ b/ServicesWeb/customer/src/components/mutual/context/cache/context.tsx @@ -75,14 +75,9 @@ const useContextCache = createContextHook({ contextName: "cache", enablePeriodicRefresh: false, customFetch: async () => { - // For initial load, we don't have a specific URL to fetch - // Return an empty object as the initial state - console.log("Initial cache fetch"); return {}; }, customUpdate: async (newData: CacheData) => { - // This won't be used directly, as we'll provide custom methods - console.log("Cache update with:", newData); return true; }, defaultValue: {} @@ -107,27 +102,19 @@ export function useCache(): UseCacheResult { if (!url && typeof window !== 'undefined') { url = window.location.pathname; } - + if (!url) return; - + // Normalize URL to handle encoding issues const normalizedUrl = url.startsWith('/') ? url : `/${url}`; - console.log("Fetching cache for normalized URL:", normalizedUrl); - + try { const urlData = await getCacheData(normalizedUrl); - console.log("Received cache data:", urlData); - - // Update the local state with the fetched data const updatedData = { ...data, [normalizedUrl]: urlData }; - - console.log("Updating cache state with:", updatedData); await update(updatedData); - - // Force a refresh to ensure the component gets the updated data await refresh(); } catch (error) { console.error("Error refreshing cache:", error); @@ -138,14 +125,9 @@ export function useCache(): UseCacheResult { const updateCache = async (url: string, urlData: any): Promise => { // Normalize URL to handle encoding issues const normalizedUrl = url.startsWith('/') ? url : `/${url}`; - console.log("Updating cache for normalized URL:", normalizedUrl); - try { const success = await setCacheData(normalizedUrl, urlData); - console.log("Cache update success:", success); - if (success && data) { - // Update local state await update({ ...data, [normalizedUrl]: urlData @@ -162,12 +144,8 @@ export function useCache(): UseCacheResult { const clearCache = async (url: string): Promise => { // Normalize URL to handle encoding issues const normalizedUrl = url.startsWith('/') ? url : `/${url}`; - console.log("Clearing cache for normalized URL:", normalizedUrl); - try { const success = await clearCacheData(normalizedUrl); - console.log("Cache clear success:", success); - if (success && data) { // Update local state const newData = { ...data }; diff --git a/ServicesWeb/customer/src/components/mutual/context/online/context.ts b/ServicesWeb/customer/src/components/mutual/context/online/context.ts index 57ab813..12b3142 100644 --- a/ServicesWeb/customer/src/components/mutual/context/online/context.ts +++ b/ServicesWeb/customer/src/components/mutual/context/online/context.ts @@ -99,17 +99,11 @@ async function setContextPageOnline( body: JSON.stringify(setOnline), signal: controller.signal, }); - console.log("result", await result.json()); - - // Clear the timeout clearTimeout(timeoutId); - if (!result.ok) { throw new Error(`HTTP error! Status: ${result.status}`); } - const data = await result.json(); - if (data.status === 200 && data.data) { return data.data; } else { diff --git a/ServicesWeb/customer/src/components/mutual/context/online/provider.tsx b/ServicesWeb/customer/src/components/mutual/context/online/provider.tsx index 40e405b..db54d35 100644 --- a/ServicesWeb/customer/src/components/mutual/context/online/provider.tsx +++ b/ServicesWeb/customer/src/components/mutual/context/online/provider.tsx @@ -66,14 +66,12 @@ export const OnlineProvider: FC = ({ children }) => { const fetchOnline = useCallback(async (force = false) => { // Don't fetch if we already have data and it's not forced if (online && !force && !error) { - console.log("Using existing online state:", online); return; } // Don't retry too frequently const now = Date.now(); if (!force && now - lastRetryTime < MIN_RETRY_INTERVAL) { - console.log("Retry attempted too soon, skipping"); return; } @@ -82,35 +80,29 @@ export const OnlineProvider: FC = ({ children }) => { setLastRetryTime(now); try { - console.log("Fetching online state..."); const data = await checkContextPageOnline(); if (data) { - console.log("Successfully fetched online state:", data); setOnline(data); setRetryCount(0); // Reset retry count on success } else { - console.warn("No online state returned, using default"); setOnline(DEFAULT_ONLINE_STATE); setError("Could not retrieve online state, using default values"); // Auto-retry if under the limit if (retryCount < MAX_AUTO_RETRIES) { setRetryCount(prev => prev + 1); - console.log(`Scheduling retry ${retryCount + 1}/${MAX_AUTO_RETRIES}...`); setTimeout(() => fetchOnline(true), MIN_RETRY_INTERVAL); } } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - console.error("Error fetching online state:", errorMessage); setError(`Failed to fetch online state: ${errorMessage}`); setOnline(DEFAULT_ONLINE_STATE); // Use default as fallback // Auto-retry if under the limit if (retryCount < MAX_AUTO_RETRIES) { setRetryCount(prev => prev + 1); - console.log(`Scheduling retry ${retryCount + 1}/${MAX_AUTO_RETRIES}...`); setTimeout(() => fetchOnline(true), MIN_RETRY_INTERVAL); } } finally { @@ -120,26 +112,20 @@ export const OnlineProvider: FC = ({ children }) => { // Manual retry function that can be called from components const retryFetch = useCallback(async () => { - console.log("Manual retry requested"); setRetryCount(0); // Reset retry count for manual retry await fetchOnline(true); }, [fetchOnline]); // Fetch online state on component mount useEffect(() => { - console.log("OnlineProvider mounted, fetching initial data"); - - // Always fetch data on mount fetchOnline(); // Set up periodic refresh (every 5 minutes) const refreshInterval = setInterval(() => { - console.log("Performing periodic refresh of online state"); fetchOnline(true); }, 5 * 60 * 1000); return () => { - console.log("OnlineProvider unmounted, clearing interval"); clearInterval(refreshInterval); }; }, [fetchOnline]); @@ -150,42 +136,17 @@ export const OnlineProvider: FC = ({ children }) => { setError(null); try { - console.log("Updating online state:", newOnline); - // Update Redis first - console.log('Updating Redis...'); await setOnlineToRedis(newOnline); - - // Then update context API - console.log('Updating context API...'); await setContextPageOnline(newOnline); - - // Finally update local state to trigger re-renders - console.log('Updating local state...'); setOnline(newOnline); - - console.log('Online state updated successfully'); return true; } catch (error) { - console.error('Error updating online state:', error); - - // Still update local state to maintain UI consistency - // even if the backend updates failed setOnline(newOnline); - return false; } finally { setIsLoading(false); } - }; - - // Add debug logging for provider state - useEffect(() => { - console.log('OnlineProvider state updated:', { - online: online ? 'present' : 'not present', - isLoading - }); - }, [online, isLoading]); - + } return ( {children} diff --git a/ServicesWeb/customer/src/components/mutual/context/user/context.ts b/ServicesWeb/customer/src/components/mutual/context/user/context.ts index 264d8f4..cc8a4d4 100644 --- a/ServicesWeb/customer/src/components/mutual/context/user/context.ts +++ b/ServicesWeb/customer/src/components/mutual/context/user/context.ts @@ -34,14 +34,9 @@ const DEFAULT_USER_STATE: ClientUser = { // Client-side fetch function that uses the server-side implementation async function checkContextDashUserInfo(): Promise { try { - console.log("Fetching user data using server-side function"); - - // First try to use the server-side implementation try { const serverData = await getUserFromServer(); - // If we got valid data from the server, return it if (serverData && serverData.uuid) { - // Check if we have a real user (not the default) if ( serverData.uuid !== "default-user-id" || serverData.email || @@ -49,30 +44,12 @@ async function checkContextDashUserInfo(): Promise { (serverData.person.firstname !== "Guest" || serverData.person.surname !== "User")) ) { - console.log("Valid user data found from server"); return serverData; - } else { - console.log( - "Default user data returned from server, falling back to client-side" - ); } } else { - console.warn("Invalid user data structure from server"); } - } catch (serverError) { - console.warn( - "Error using server-side user data fetch, falling back to client-side:", - serverError - ); - // Continue to client-side implementation - } + } catch (serverError) {} - // Fall back to client-side implementation - console.log( - `Falling back to client-side fetch: ${API_BASE_URL}/context/dash/user` - ); - - // Create an AbortController to handle timeouts const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT); @@ -94,22 +71,14 @@ async function checkContextDashUserInfo(): Promise { } const data = await result.json(); - console.log("User data API response:", data); - // Handle different response formats if (data.status === 200 && data.data) { - // Standard API response format return data.data; } else if (data.user) { - // Direct Redis object format - console.log("Found user data in Redis format"); return data.user; } else if (data.uuid) { - // Direct user object format - console.log("Found direct user data format"); return data; } else { - console.warn("Invalid response format from user API"); return DEFAULT_USER_STATE; } } catch (fetchError) { @@ -129,11 +98,6 @@ async function checkContextDashUserInfo(): Promise { throw fetchError; } } catch (error) { - // Handle all other errors - console.error( - "Error fetching user data:", - error instanceof Error ? error.message : "Unknown error" - ); return DEFAULT_USER_STATE; } } @@ -144,29 +108,12 @@ async function setContextDashUserInfo({ userSet: ClientUser; }): Promise { try { - console.log("Setting user data using server-side function"); - - // First try to use the server-side implementation try { const success = await setUserFromServer(userSet); if (success) { - console.log( - "Successfully updated user data using server-side function" - ); return true; } - } catch (serverError) { - console.warn( - "Error using server-side user data update, falling back to client-side:", - serverError - ); - // Continue to client-side implementation - } - - // Fall back to client-side implementation - console.log( - `Falling back to client-side update: ${API_BASE_URL}/context/dash/user` - ); + } catch (serverError) {} // Create an AbortController to handle timeouts const controller = new AbortController(); @@ -182,28 +129,18 @@ async function setContextDashUserInfo({ body: JSON.stringify(userSet), signal: controller.signal, }); - - // Clear the timeout clearTimeout(timeoutId); - if (!result.ok) { throw new Error(`HTTP error! Status: ${result.status}`); } - const data = await result.json(); - console.log("Update user data API response:", data); - return data.status === 200; } catch (fetchError) { - // Clear the timeout if it hasn't fired yet clearTimeout(timeoutId); - - // Check if this is an abort error (timeout) if ( fetchError instanceof DOMException && fetchError.name === "AbortError" ) { - console.warn("Request timed out or was aborted"); return false; } @@ -211,10 +148,6 @@ async function setContextDashUserInfo({ throw fetchError; } } catch (error) { - console.error( - "Error setting user data:", - error instanceof Error ? error.message : "Unknown error" - ); return false; } } diff --git a/ServicesWeb/customer/src/components/mutual/formInputs/CheckBoxInput.tsx b/ServicesWeb/customer/src/components/mutual/formInputs/CheckBoxInput.tsx new file mode 100644 index 0000000..184bca9 --- /dev/null +++ b/ServicesWeb/customer/src/components/mutual/formInputs/CheckBoxInput.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import { Checkbox } from "@/components/mutual/ui/checkbox"; +import { + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/mutual/ui/form"; +import { Control } from "react-hook-form"; + +interface CheckBoxInputProps { + control: Control; + name: string; + label: string; + onBlurCallback?: (fieldName: any, value: any) => void; +} + +export function CheckBoxInput({ + control, + name, + label, + onBlurCallback, +}: CheckBoxInputProps) { + return ( + ( + + + { + field.onChange(checked); + if (onBlurCallback) { + onBlurCallback(name, checked); + } + }} + /> + +
+ {label} +
+ +
+ )} + /> + ); +} \ No newline at end of file diff --git a/ServicesWeb/customer/src/components/mutual/formInputs/DatetimeInput.tsx b/ServicesWeb/customer/src/components/mutual/formInputs/DatetimeInput.tsx new file mode 100644 index 0000000..51ec68f --- /dev/null +++ b/ServicesWeb/customer/src/components/mutual/formInputs/DatetimeInput.tsx @@ -0,0 +1,68 @@ +{/* String Input Component */ } +import React from "react"; +import { Input } from "@/components/mutual/ui/input"; +import { + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/mutual/ui/form"; +import { Control } from "react-hook-form"; + + +interface DatetimeInputProps { + control: Control; + name: string; + label: string; + placeholder?: string; + onBlurCallback?: (fieldName: any, value: any) => void; +} + +export function DatetimeInput({ + control, + name, + label, + placeholder, + onBlurCallback, +}: DatetimeInputProps) { + return ( + ( + + {label} + + { + // Get the date value from the input (will be in YYYY-MM-DD format) + const dateValue = e.target.value; + + // Store the date value directly in YYYY-MM-DD format + // This matches our custom schema validation + if (dateValue) { + field.onChange(dateValue); + } else { + field.onChange(""); + } + }} + onBlur={(e) => { + field.onBlur(); + if (onBlurCallback) { + onBlurCallback(name, e.target.value); + } + }} + /> + + + + )} + /> + ); +} + diff --git a/ServicesWeb/customer/src/components/mutual/formInputs/NumberInput.tsx b/ServicesWeb/customer/src/components/mutual/formInputs/NumberInput.tsx new file mode 100644 index 0000000..94f5b3b --- /dev/null +++ b/ServicesWeb/customer/src/components/mutual/formInputs/NumberInput.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { Input } from "@/components/mutual/ui/input"; +import { + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/mutual/ui/form"; +import { Control } from "react-hook-form"; + +interface NumberInputProps { + control: Control; + name: string; + label: string; + placeholder?: string; + onBlurCallback?: (fieldName: any, value: any) => void; +} + +export function NumberInput({ + control, + name, + label, + placeholder, + onBlurCallback, +}: NumberInputProps) { + return ( + ( + + {label} + + { + const value = e.target.value === "" ? 0 : Number(e.target.value); + field.onChange(value); + }} + onBlur={(e) => { + field.onBlur(); + if (onBlurCallback) { + const value = e.target.value === "" ? 0 : Number(e.target.value); + onBlurCallback(name, value); + } + }} + /> + + + + )} + /> + ); +} diff --git a/ServicesWeb/customer/src/components/mutual/formInputs/StringInput.tsx b/ServicesWeb/customer/src/components/mutual/formInputs/StringInput.tsx new file mode 100644 index 0000000..5c1585b --- /dev/null +++ b/ServicesWeb/customer/src/components/mutual/formInputs/StringInput.tsx @@ -0,0 +1,53 @@ +{/* String Input Component */ } +import React from "react"; +import { Input } from "@/components/mutual/ui/input"; +import { + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/mutual/ui/form"; +import { Control } from "react-hook-form"; + +interface StringInputProps { + control: Control; + name: string; + label: string; + placeholder?: string; + onBlurCallback?: (fieldName: any, value: any) => void; +} + +export function StringInput({ + control, + name, + label, + placeholder, + onBlurCallback, +}: StringInputProps) { + return ( + ( + + {label} + + { + field.onBlur(); + if (onBlurCallback) { + onBlurCallback(name, e.target.value); + } + }} + /> + + + + )} + /> + ); +} diff --git a/ServicesWeb/customer/src/components/mutual/providers/client-providers.tsx b/ServicesWeb/customer/src/components/mutual/providers/client-providers.tsx index d4b324f..e8712af 100644 --- a/ServicesWeb/customer/src/components/mutual/providers/client-providers.tsx +++ b/ServicesWeb/customer/src/components/mutual/providers/client-providers.tsx @@ -8,11 +8,6 @@ interface ClientProvidersProps { } export function ClientProviders({ children }: ClientProvidersProps) { - // Log provider initialization for debugging - React.useEffect(() => { - console.log('ClientProviders initialized'); - }, []); - return ( diff --git a/ServicesWeb/customer/src/layouts/a.txt b/ServicesWeb/customer/src/fetchers/custom/backend/a.txt similarity index 100% rename from ServicesWeb/customer/src/layouts/a.txt rename to ServicesWeb/customer/src/fetchers/custom/backend/a.txt diff --git a/ServicesWeb/customer/src/fetchers/custom/backend/builds/fetch.tsx b/ServicesWeb/customer/src/fetchers/custom/backend/builds/fetch.tsx new file mode 100644 index 0000000..3e9925b --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/custom/backend/builds/fetch.tsx @@ -0,0 +1,30 @@ +import { fetchDataWithToken } from "@/fetchers/fecther"; +import { urlBuildingsList, urlBuildingsUpdate, urlBuildingsCreate } from "@/fetchers/index"; +import { Pagination } from "@/fetchers/types"; +import { fetchResponseStatus } from "@/fetchers/utils"; + +async function getBuildingsList(pagination: Pagination) { + const response = await fetchDataWithToken(urlBuildingsList, pagination, "POST", false); + if (fetchResponseStatus(response)) { return response.data } + else { throw new Error(response.error || "Bir hata oluştu") } +} + +async function updateBuilding(uuid: string, data: any) { + const newUrlOfUpdate = `${urlBuildingsUpdate}/${uuid}`; + const response = await fetchDataWithToken(newUrlOfUpdate, data, "POST", false); + if (fetchResponseStatus(response)) { return response.data } + else { throw new Error(response.error || "Bir hata oluştu") } +} + +async function createBuilding(data: any) { + const newUrl = `${urlBuildingsCreate}`; + const response = await fetchDataWithToken(newUrl, data, "POST", false); + if (fetchResponseStatus(response)) { return response.data } + else { throw new Error(response.error || "Bir hata oluştu") } +} + +export { + getBuildingsList, + updateBuilding, + createBuilding +} diff --git a/ServicesWeb/customer/src/fetchers/index.ts b/ServicesWeb/customer/src/fetchers/index.ts index e291ba7..749662f 100644 --- a/ServicesWeb/customer/src/fetchers/index.ts +++ b/ServicesWeb/customer/src/fetchers/index.ts @@ -31,6 +31,14 @@ const urlLogoutEndpoint = `${baseUrlAuth}/authentication/logout`; const urlTesterList = `${baseUrlTester}/tester/list`; +const urlBuildingsList = `${baseUrlBuilding}/builds/list`; +const urlBuildingsCreate = `${baseUrlBuilding}/builds/create`; +const urlBuildingsUpdate = `${baseUrlBuilding}/builds/update`; + +const urlPartsList = `${baseUrlBuilding}/parts/list`; +const urlPartsCreate = `${baseUrlBuilding}/parts/create`; +const urlPartsUpdate = `${baseUrlBuilding}/parts/update`; + export { urlCheckToken, urlPageValid, @@ -38,6 +46,12 @@ export { urlLoginEndpoint, urlLoginSelectEndpoint, urlLogoutEndpoint, + urlBuildingsList, + urlBuildingsCreate, + urlBuildingsUpdate, + urlPartsList, + urlPartsCreate, + urlPartsUpdate, // For test use only urlTesterList, }; diff --git a/ServicesWeb/customer/src/fetchers/types.ts b/ServicesWeb/customer/src/fetchers/types.ts index 8d547ab..27ed5e3 100644 --- a/ServicesWeb/customer/src/fetchers/types.ts +++ b/ServicesWeb/customer/src/fetchers/types.ts @@ -21,4 +21,12 @@ interface CookieObject { priority: string; } -export type { HttpMethod, ApiResponse, FetchOptions, CookieObject }; +interface Pagination { + page?: number; + size?: number; + orderField?: string[]; + orderType?: string[]; + query?: Record; +} + +export type { HttpMethod, ApiResponse, FetchOptions, CookieObject, Pagination }; diff --git a/ServicesWeb/customer/src/hooks/useTableData.ts b/ServicesWeb/customer/src/hooks/useTableData.ts index 721ea3a..a4427f7 100644 --- a/ServicesWeb/customer/src/hooks/useTableData.ts +++ b/ServicesWeb/customer/src/hooks/useTableData.ts @@ -116,7 +116,6 @@ export function useTableData({ next: false, back: false, }); - console.log("apiPagination", apiPagination); // Watch for form value changes const page = form.watch(pageField as Path); diff --git a/ServicesWeb/customer/src/languages/mutual/menu/english.ts b/ServicesWeb/customer/src/languages/mutual/menu/english.ts index 677625e..cf02116 100644 --- a/ServicesWeb/customer/src/languages/mutual/menu/english.ts +++ b/ServicesWeb/customer/src/languages/mutual/menu/english.ts @@ -37,7 +37,7 @@ const menuTranslationEn = { "/build": [ { value: "Build", key: "build" }, { value: "Build", key: "build" }, - { value: "Build", key: "build" }, + { value: "Buildings", key: "build" }, ], "/build/parts": [ { value: "Build", key: "build" }, diff --git a/ServicesWeb/customer/src/languages/mutual/menu/turkish.ts b/ServicesWeb/customer/src/languages/mutual/menu/turkish.ts index 9a2d114..fd24178 100644 --- a/ServicesWeb/customer/src/languages/mutual/menu/turkish.ts +++ b/ServicesWeb/customer/src/languages/mutual/menu/turkish.ts @@ -37,7 +37,7 @@ const menuTranslationTr = { "/build": [ { value: "Bina", key: "build" }, { value: "Bina", key: "build" }, - { value: "Bina", key: "build" }, + { value: "Binalar", key: "build" }, ], "/build/parts": [ { value: "Bina", key: "build" }, diff --git a/ServicesWeb/customer/src/pages/multi/builds/listPage.tsx b/ServicesWeb/customer/src/pages/multi/buildParts/a.txt similarity index 100% rename from ServicesWeb/customer/src/pages/multi/builds/listPage.tsx rename to ServicesWeb/customer/src/pages/multi/buildParts/a.txt diff --git a/ServicesWeb/customer/src/pages/multi/buildParts/superuser/CreatePage.tsx b/ServicesWeb/customer/src/pages/multi/buildParts/superuser/CreatePage.tsx new file mode 100644 index 0000000..2768a7b --- /dev/null +++ b/ServicesWeb/customer/src/pages/multi/buildParts/superuser/CreatePage.tsx @@ -0,0 +1,221 @@ +import React, { useState, useEffect } from "react"; +import { apiPostFetcher } from "@/lib/fetcher"; +import { useRouter } from "next/navigation"; + +import { withCache } from "@/components/mutual/context/cache/withCache"; +import { Button } from "@/components/mutual/ui/button"; +import { Form } from "@/components/mutual/ui/form"; +import { getCacheData, setCacheData, clearCacheData } from "@/components/mutual/context/cache/context"; + +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { buildPartsSchemaCreate, BuildPartsCreateInterface, buildPartsSchema } from "./schema"; +import { buildPartsTranslations, translationsOfPage } from "./translations"; +import { LanguageTypes } from "@/validations/mutual/language/validations"; + +import { NumberInput } from "@/components/mutual/formInputs/NumberInput"; +import { StringInput } from "@/components/mutual/formInputs/StringInput"; +import { CheckBoxInput } from "@/components/mutual/formInputs/CheckBoxInput"; + +const emptyValues: BuildPartsCreateInterface = { + build_uu_id: null, + address_gov_code: "", + part_no: 0, + part_level: 0, + part_code: "", + part_gross_size: 0, + part_net_size: 0, + default_accessory: "0", + human_livable: true, + due_part_key: "", + part_direction_uu_id: null, +}; + +function CreateFromComponentBase({ + onlineData, + cacheData, + cacheLoading, + cacheError, + refreshCache, + updateCache, + clearCache, + activePageUrl +}: { + onlineData?: any; + cacheData?: { [url: string]: any } | null; + cacheLoading?: boolean; + cacheError?: string | null; + refreshCache?: (url?: string) => Promise; + updateCache?: (url: string, data: any) => Promise; + clearCache?: (url: string) => Promise; + activePageUrl: string; +}) { + const language: LanguageTypes = onlineData?.lang || 'en'; + const router = useRouter(); + const listUrl = `/panel/${activePageUrl?.replace("/create", "")}`; + const [cacheLoaded, setCacheLoaded] = useState(false); + const form = useForm({ + resolver: zodResolver(buildPartsSchemaCreate), + defaultValues: emptyValues + }); + + useEffect(() => { + const fetchCacheDirectly = async () => { + if (!cacheLoaded) { + try { + console.log("Directly fetching cache for URL:", activePageUrl); + const cachedData = await getCacheData(activePageUrl); + if (cachedData) { + const formValues = form.getValues(); + const mergedData = { + build_uu_id: cachedData.build_uu_id || formValues.build_uu_id || null, + address_gov_code: cachedData.address_gov_code || formValues.address_gov_code || "", + part_no: cachedData.part_no || formValues.part_no || 0, + part_level: cachedData.part_level || formValues.part_level || 0, + part_code: cachedData.part_code || formValues.part_code || "", + part_gross_size: cachedData.part_gross_size || formValues.part_gross_size || 0, + part_net_size: cachedData.part_net_size || formValues.part_net_size || 0, + default_accessory: cachedData.default_accessory || formValues.default_accessory || "0", + human_livable: cachedData.human_livable || formValues.human_livable || true, + due_part_key: cachedData.due_part_key || formValues.due_part_key || "", + part_direction_uu_id: cachedData.part_direction_uu_id || formValues.part_direction_uu_id || null, + }; + form.reset(mergedData); + } + setCacheLoaded(true); + if (refreshCache) { refreshCache(activePageUrl) } + } catch (error) { console.error("Error fetching cache directly:", error) } + } + }; + fetchCacheDirectly(); + }, []); + + const handleFieldBlur = async (fieldName: string, value: any) => { + if (value) { + try { + const currentValues = form.getValues(); + const updatedData = { ...currentValues, [fieldName]: value }; + await setCacheData(activePageUrl, updatedData); + if (updateCache) { updateCache(activePageUrl, updatedData) } + } catch (error) { console.error("Error updating cache:", error) } + } + }; + + const onSubmit = async (data: any) => { + console.log("Form submitted with data:", data); + + try { + const response = await apiPostFetcher({ url: `/api/parts/create`, isNoCache: true, body: data }); + await clearCacheData(activePageUrl); + if (clearCache) { clearCache(activePageUrl) } + if (response.success) { form.reset(emptyValues); router.push(listUrl) } + } catch (error) { console.error("Error submitting form:", error) } + } + + return ( +
+ {/* back to list button */} +
+ +
+

{translationsOfPage[language].title}

+
+ + {/* Address Government Code */} + + + {/* Part Number */} + + + {/* Part Level */} + + + {/* Part Code */} + + + {/* Part Gross Size */} + + + {/* Part Net Size */} + + + {/* Default Accessory */} + + + {/* Human Livable */} + + + {/* Due Part Key */} + + + {/* Part Direction UUID */} + + + + + +
+ ); +} + +export const CreateFromBuildComponent = withCache(CreateFromComponentBase); +export default withCache(CreateFromBuildComponent); diff --git a/ServicesWeb/customer/src/pages/multi/buildParts/superuser/ListPage.tsx b/ServicesWeb/customer/src/pages/multi/buildParts/superuser/ListPage.tsx new file mode 100644 index 0000000..02a8585 --- /dev/null +++ b/ServicesWeb/customer/src/pages/multi/buildParts/superuser/ListPage.tsx @@ -0,0 +1,542 @@ +'use client'; +import React from "react"; +import { + useReactTable, + getCoreRowModel, + getSortedRowModel, + getPaginationRowModel, + flexRender, + createColumnHelper, + ColumnDef, +} from "@tanstack/react-table"; +import { buildPartsTranslations } from './translations'; +import { UseFormReturn } from "react-hook-form"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage +} from "@/components/mutual/ui/form"; +import { Button } from "@/components/mutual/ui/button"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/mutual/ui/select"; +import { API_BASE_URL } from "@/config/config"; +import { useTableData } from "@/hooks/useTableData"; +import { LanguageTypes } from "@/validations/mutual/language/validations"; +import { + Translations, + TableHeaderProps, + LoadingSpinnerProps, + ErrorDisplayProps, + MobilePaginationControlsProps, + DashboardPageProps, +} from "@/validations/mutual/table/validations"; +import LoadingContent from "@/components/mutual/loader/component"; +import { Pencil } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { setCacheData } from "@/components/mutual/context/cache/context"; +import { BuildPartsSchemaInterface } from "./schema"; + +interface DataTableProps { + table: ReturnType; + tableData: BuildPartsSchemaInterface[]; + isLoading: boolean; + handleSortingChange: (columnId: string) => void; + getSortingIcon: (columnId: string) => React.ReactNode; + flexRender: typeof flexRender; + t: Translations; +} + +interface TableFormProps { + form: UseFormReturn; + handleFormSubmit: (e: React.FormEvent) => void; + handleSelectChange: (value: string, field: { onChange: (value: number) => void }) => void; + renderPageOptions: () => { key: string | number; value: string; label: string }[]; + pageSizeOptions: number[]; + apiPagination: { + size: number; + page: number; + totalCount: number; + totalPages: number; + pageCount: number; + }; + handleFirstPage: () => void; + handlePreviousPage: () => void; + handleNextPage: () => void; + isPreviousDisabled: () => boolean; + isNextDisabled: () => boolean; + t: Translations; +} + +const translations: Record = { + en: { + dataTable: 'Data Table', + tableWithApiData: 'Table with API Data', + loading: 'Loading...', + noDataAvailable: 'No data available', + page: 'Page', + size: 'Size', + total: 'Total', + items: 'items', + first: 'First', + previous: 'Previous', + next: 'Next', + selectPage: 'Select page', + selectSize: 'Select size', + actionButtonGroup: 'Actions', + }, + tr: { + dataTable: 'Veri Tablosu', + tableWithApiData: 'API Verili Tablo', + loading: 'Yükleniyor...', + noDataAvailable: 'Veri bulunamadı', + page: 'Sayfa', + size: 'Boyut', + total: 'Toplam', + items: 'öğe', + first: 'İlk', + previous: 'Önceki', + next: 'Sonraki', + selectPage: 'Sayfa seç', + selectSize: 'Boyut seç', + actionButtonGroup: 'Eylemler', + } +}; + +const DataTable: React.FC = React.memo(({ + table, + tableData, + isLoading, + handleSortingChange, + getSortingIcon, + flexRender, + t, +}) => { + return ( +
+ {/* Semi-transparent loading overlay that preserves interactivity */} + {isLoading && ( +
+ {/* We don't put anything here as we already have the loading indicator in the header */} +
+ )} + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ); + })} + + ))} + + + {tableData.length === 0 && !isLoading ? ( + + + + ) : ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => { + return ( + + ); + })} + + )) + )} + +
{t.dataTable}
handleSortingChange(header.column.id)} + aria-sort={header.column.getIsSorted() ? (header.column.getIsSorted() === 'desc' ? 'descending' : 'ascending') : undefined} + scope="col" + > +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {getSortingIcon(header.column.id)} +
+
+ {t.noDataAvailable} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
+ ); +}); + +const TableForm: React.FC = ({ + form, + handleFormSubmit, + handleSelectChange, + renderPageOptions, + pageSizeOptions, + apiPagination, + handleFirstPage, + handlePreviousPage, + handleNextPage, + isPreviousDisabled, + isNextDisabled, + t +}) => { + return ( +
+
+ +
+ ( + + {t.page} + + + + )} + /> +
+
+ ( + + {t.size} + + + + )} + /> +
+
+

+ {t.page}: {apiPagination.page}{" / "} {apiPagination.totalPages} · + {t.size}: {apiPagination.pageCount}{" / "} {apiPagination.size} · + {t.total}: {apiPagination.totalCount} {t.items} +

+
+
+ + + + {/* */} +
+
+ +
+ ); +}; + +const MobilePaginationControls: React.FC = ({ + handlePreviousPage, + handleNextPage, + isPreviousDisabled, + isNextDisabled, + t +}) => { + return ( +
+
+ + +
+
+ ); +}; + +const TableHeader: React.FC = ({ title, description, isLoading, error, t }) => { + return ( + +
+
+
+

{title}

+

{description}

+
+ {/* {isLoading && } */} + {error && } +
+
+ ); +}; + +const ErrorDisplay: React.FC = ({ message }) => { + return
{message}
; +}; + +const BuildListPage: React.FC = React.memo((props) => { + // Initialize translation with English as default + const language = props.onlineData?.lang as LanguageTypes || 'en'; + const t = translations[language]; + const searchParams = props.searchParams; + console.log('searchParams', searchParams); + const router = useRouter(); + + const { + form, + tableData, + sorting, + isLoading, + error, + pagination, + apiPagination, + setSorting, + handleSortingChange, + handleSelectChange, + handlePageChange, + handleFirstPage, + handlePreviousPage, + handleNextPage, + getSortingIcon, + handleFormSubmit, + // Disabled states + isPreviousDisabled, + isNextDisabled, + pageSizeOptions, + renderPageOptions, + } = useTableData({ apiUrl: `${API_BASE_URL}/parts/list` }); + + const activePageUrl = props.activePageUrl || ''; + const handleEditRow = async (row: BuildPartsSchemaInterface) => { + try { + // Store the row data in the cache + await setCacheData(`${activePageUrl}/update`, row); + console.log('Row data stored in cache:', row); + + // Navigate to the update form + router.push(`/panel/${activePageUrl}/update`); + } catch (error) { + console.error('Error storing row data in cache:', error); + } + }; + + const actionButtonGroup = (info: any) => ( + + ); + + const columnHelper = createColumnHelper(); + const columns = React.useMemo(() => [ + columnHelper.display({ + id: 'actions', + header: () => {t.actionButtonGroup}, + cell: (info) => { return (actionButtonGroup(info)) } + }), + // columnHelper.accessor('uu_id', { + // cell: info => info.getValue(), + // header: () => {buildPartsTranslations[language].uu_id}, + // footer: info => info.column.id + // }), + // columnHelper.accessor('build_uu_id', { + // cell: info => info.getValue(), + // header: () => {buildPartsTranslations[language].build_uu_id}, + // footer: info => info.column.id + // }), + columnHelper.accessor('address_gov_code', { + cell: info => info.getValue(), + header: () => {buildPartsTranslations[language].address_gov_code}, + footer: info => info.column.id + }), + columnHelper.accessor('part_no', { + cell: info => String(info.getValue()), + header: () => {buildPartsTranslations[language].part_no}, + footer: info => info.column.id + }), + columnHelper.accessor('part_level', { + cell: info => String(info.getValue()), + header: () => {buildPartsTranslations[language].part_level}, + footer: info => info.column.id + }), + columnHelper.accessor('part_code', { + cell: info => info.getValue(), + header: () => {buildPartsTranslations[language].part_code}, + footer: info => info.column.id + }), + columnHelper.accessor('part_gross_size', { + cell: info => String(info.getValue()), + header: () => {buildPartsTranslations[language].part_gross_size}, + footer: info => info.column.id + }), + columnHelper.accessor('part_net_size', { + cell: info => String(info.getValue()), + header: () => {buildPartsTranslations[language].part_net_size}, + footer: info => info.column.id + }), + columnHelper.accessor('default_accessory', { + cell: info => info.getValue(), + header: () => {buildPartsTranslations[language].default_accessory}, + footer: info => info.column.id + }), + columnHelper.accessor('human_livable', { + cell: info => info.getValue() ? 'Yes' : 'No', + header: () => {buildPartsTranslations[language].human_livable}, + footer: info => info.column.id + }), + columnHelper.accessor('due_part_key', { + cell: info => info.getValue(), + header: () => {buildPartsTranslations[language].due_part_key}, + footer: info => info.column.id + }), + columnHelper.accessor('part_direction_uu_id', { + cell: info => info.getValue() || '', + header: () => {buildPartsTranslations[language].part_direction_uu_id}, + footer: info => info.column.id + }), + ], [columnHelper]) as ColumnDef[]; + + const table = useReactTable({ + data: tableData, + columns, + state: { sorting, pagination }, + onSortingChange: setSorting, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + manualPagination: true, + pageCount: apiPagination.totalPages || 1, + }) + + return ( + isLoading ? : +
+ +
+ + +
+ + + + {/* Mobile pagination controls - only visible on small screens */} + + + +
+ ); +}); + +export default BuildListPage; diff --git a/ServicesWeb/customer/src/pages/multi/buildParts/superuser/UpdatePage.tsx b/ServicesWeb/customer/src/pages/multi/buildParts/superuser/UpdatePage.tsx new file mode 100644 index 0000000..98cb01f --- /dev/null +++ b/ServicesWeb/customer/src/pages/multi/buildParts/superuser/UpdatePage.tsx @@ -0,0 +1,234 @@ +import React, { useState, useEffect } from "react"; +import { apiPostFetcher } from "@/lib/fetcher"; +import { useRouter } from "next/navigation"; + +import { withCache } from "@/components/mutual/context/cache/withCache"; +import { Button } from "@/components/mutual/ui/button"; +import { Form } from "@/components/mutual/ui/form"; +import { getCacheData, setCacheData, clearCacheData } from "@/components/mutual/context/cache/context"; + +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { LanguageTypes } from "@/validations/mutual/language/validations"; + +import { buildPartsSchema, BuildPartsInterface } from "./schema"; +import { buildPartsTranslations, translationsOfPage } from "./translations"; + +import { CheckBoxInput } from "@/components/mutual/formInputs/CheckBoxInput"; +import { NumberInput } from "@/components/mutual/formInputs/NumberInput"; +import { StringInput } from "@/components/mutual/formInputs/StringInput"; + +const emptyValues: BuildPartsInterface = { + uu_id: "", + build_uu_id: null, + address_gov_code: "", + part_no: 0, + part_level: 0, + part_code: "", + part_gross_size: 0, + part_net_size: 0, + default_accessory: "0", + human_livable: true, + due_part_key: "", + part_direction_uu_id: null +}; + +function UpdateFromComponentBase({ + onlineData, + cacheData, + cacheLoading, + cacheError, + refreshCache, + updateCache, + clearCache, + activePageUrl +}: { + onlineData?: any; + cacheData?: { [url: string]: any } | null; + cacheLoading?: boolean; + cacheError?: string | null; + refreshCache?: (url?: string) => Promise; + updateCache?: (url: string, data: any) => Promise; + clearCache?: (url: string) => Promise; + activePageUrl: string; +}) { + const language: LanguageTypes = onlineData?.lang || 'en'; + const [cacheLoaded, setCacheLoaded] = useState(false); + const [partUUID, setPartUUID] = useState(""); const router = useRouter(); + const listUrl = `/panel/${activePageUrl?.replace("/update", "")}`; + + const form = useForm({ + resolver: zodResolver(buildPartsSchema), + mode: "onSubmit", + defaultValues: emptyValues + }); + + useEffect(() => { + const fetchData = async () => { + if (!cacheLoaded) { + try { + const cachedData = await getCacheData(activePageUrl); + if (cachedData) { + const formValues = form.getValues(); + const mergedData = { + uu_id: "", + address_gov_code: cachedData.address_gov_code || formValues.address_gov_code || "", + part_no: cachedData.part_no ?? formValues.part_no ?? 0, + part_level: cachedData.part_level ?? formValues.part_level ?? 0, + part_code: cachedData.part_code || formValues.part_code || "", + part_gross_size: cachedData.part_gross_size ?? formValues.part_gross_size ?? 0, + part_net_size: cachedData.part_net_size ?? formValues.part_net_size ?? 0, + default_accessory: cachedData.default_accessory || formValues.default_accessory || "0", + human_livable: cachedData.human_livable ?? formValues.human_livable ?? true, + due_part_key: cachedData.due_part_key || formValues.due_part_key || "", + part_direction_uu_id: cachedData.part_direction_uu_id ?? formValues.part_direction_uu_id ?? null + }; + form.reset(mergedData); + setPartUUID(cachedData.uu_id); + } + setCacheLoaded(true); + if (refreshCache) { refreshCache(activePageUrl); } + } catch (error) { console.error("Error fetching cache:", error); } + } + }; + fetchData(); + }, []); + + const handleFieldBlur = async (fieldName: string, value: any) => { + if (value !== undefined) { + try { + const currentValues = form.getValues(); + const updatedData = { ...currentValues, [fieldName]: value }; + await setCacheData(activePageUrl, updatedData); + if (updateCache) { updateCache(activePageUrl, updatedData); } + } catch (error) { console.error("Error updating cache:", error); } + } + }; + + const onSubmit = async (data: any) => { + try { + console.log('Form data received:', data); + const formattedData = { + ...data, + uuid: partUUID + }; + const response = await apiPostFetcher({ + url: `/api/parts/update/${partUUID}`, + isNoCache: true, + body: formattedData, + }); + await clearCacheData(activePageUrl); + if (clearCache) { clearCache(activePageUrl); } + if (response.success) { form.reset(emptyValues); router.push(listUrl) } + } catch (error) { console.error("Error submitting form:", error); } + } + + return ( +
+ {/* back to list button */} +
+ +
+

{translationsOfPage[language].title}

+

UUID: {partUUID}

+ +
+ + {/* Address Government Code */} + + + {/* Part Number */} + + + {/* Part Level */} + + + {/* Part Code */} + + + {/* Part Gross Size */} + + + {/* Part Net Size */} + + + {/* Default Accessory */} + + + {/* Human Livable */} + + + {/* Due Part Key */} + + + {/* Part Direction UUID */} + + + + + +
+ ); +} + +export const UpdateFromComponent = withCache(UpdateFromComponentBase); +export default withCache(UpdateFromComponentBase); diff --git a/ServicesWeb/customer/src/pages/multi/buildParts/superuser/schema.ts b/ServicesWeb/customer/src/pages/multi/buildParts/superuser/schema.ts new file mode 100644 index 0000000..8973e33 --- /dev/null +++ b/ServicesWeb/customer/src/pages/multi/buildParts/superuser/schema.ts @@ -0,0 +1,56 @@ +import * as z from "zod"; + +/** + * Zod schema for BuildParts + * Corresponds to the BuildParts class in Python + */ +const buildPartsSchema = z.object({ + uu_id: z.string(), + build_uu_id: z.string().nullable(), + address_gov_code: z.string().describe("Goverment Door Code"), + part_no: z.number().int().describe("Part Number"), + part_level: z.number().int().describe("Building Part Level"), + part_code: z.string().describe("Part Code"), + part_gross_size: z.number().int().describe("Part Gross Size"), + part_net_size: z.number().int().describe("Part Net Size"), + default_accessory: z.string().describe("Default Accessory"), + human_livable: z.boolean().describe("Human Livable"), + due_part_key: z.string().describe("Constant Payment Group"), + part_direction_uu_id: z.string().nullable().describe("Part Direction UUID"), +}); + +const buildPartsSchemaCreate = buildPartsSchema.omit({ uu_id: true }).extend({ + part_no: z.number().int().default(0), + part_level: z.number().int().default(0), + part_code: z.string().default(""), + part_gross_size: z.number().int().default(0), + part_net_size: z.number().int().default(0), + default_accessory: z.string().default("0"), + human_livable: z.boolean().default(true), + due_part_key: z.string().default(""), +}); + +type BuildPartsInterface = z.infer; +type BuildPartsCreateInterface = z.infer; + +interface BuildPartsSchemaInterface { + uu_id: string; + build_uu_id: string | null; + address_gov_code: string; + part_no: number; + part_level: number; + part_code: string; + part_gross_size: number; + part_net_size: number; + default_accessory: string; + human_livable: boolean; + due_part_key: string; + part_direction_uu_id: string | null; +} + +export type { + BuildPartsInterface, + BuildPartsSchemaInterface, + BuildPartsCreateInterface, +}; +export { buildPartsSchema, buildPartsSchemaCreate }; diff --git a/ServicesWeb/customer/src/pages/multi/buildParts/superuser/translations.ts b/ServicesWeb/customer/src/pages/multi/buildParts/superuser/translations.ts new file mode 100644 index 0000000..9b178c5 --- /dev/null +++ b/ServicesWeb/customer/src/pages/multi/buildParts/superuser/translations.ts @@ -0,0 +1,45 @@ +export const buildPartsTranslations = { + tr: { + uu_id: "UUID", + build_uu_id: "Bina UUID", + address_gov_code: "Devlet Adres Kodu", + part_no: "Daire Numarası", + part_level: "Daire Katı", + part_code: "Daire Kodu", + part_gross_size: "Daire Brüt Boyutu", + part_net_size: "Daire Net Boyutu", + default_accessory: "Varsayılan Aksesuar", + human_livable: "İnsan Yaşanabilir", + due_part_key: "Sabit Ödeme Grubu", + part_direction_uu_id: "Daire Yön UUID", + }, + en: { + uu_id: "UUID", + build_uu_id: "Building UUID", + address_gov_code: "Government Door Code", + part_no: "Part Number", + part_level: "Building Part Level", + part_code: "Part Code", + part_gross_size: "Part Gross Size", + part_net_size: "Part Net Size", + default_accessory: "Default Accessory", + human_livable: "Human Livable", + due_part_key: "Constant Payment Group", + part_direction_uu_id: "Part Direction UUID", + }, +}; + +export const translationsOfPage = { + tr: { + title: "Bina Bölümü Oluştur", + updateTitle: "Bina Bölümü Güncelle", + back2List: "Listeye Geri Dön", + }, + en: { + title: "Create Building Part", + updateTitle: "Update Building Part", + back2List: "Back to List", + }, +}; + +export default buildPartsTranslations; diff --git a/ServicesWeb/customer/src/pages/multi/builds/a.txt b/ServicesWeb/customer/src/pages/multi/builds/a.txt new file mode 100644 index 0000000..e69de29 diff --git a/ServicesWeb/customer/src/pages/multi/builds/superuser/CreatePage.tsx b/ServicesWeb/customer/src/pages/multi/builds/superuser/CreatePage.tsx new file mode 100644 index 0000000..938a676 --- /dev/null +++ b/ServicesWeb/customer/src/pages/multi/builds/superuser/CreatePage.tsx @@ -0,0 +1,307 @@ +import React, { useState, useEffect } from "react"; +import { apiPostFetcher } from "@/lib/fetcher"; +import { useRouter } from "next/navigation"; + +import { withCache } from "@/components/mutual/context/cache/withCache"; +import { Button } from "@/components/mutual/ui/button"; +import { Form } from "@/components/mutual/ui/form"; +import { getCacheData, setCacheData, clearCacheData } from "@/components/mutual/context/cache/context"; + +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { buildSchemaCreate, BuildCreateInterface } from "./schema"; +import { buildTranslations } from "./translations"; +import { LanguageTypes } from "@/validations/mutual/language/validations"; + +import { NumberInput } from "@/components/mutual/formInputs/NumberInput"; +import { StringInput } from "@/components/mutual/formInputs/StringInput"; +import { DatetimeInput } from "@/components/mutual/formInputs/DatetimeInput"; +import { CheckBoxInput } from "@/components/mutual/formInputs/CheckBoxInput"; + +const emptyValues: BuildCreateInterface = { + gov_address_code: "", + build_name: "", + build_no: "", + max_floor: 0, + underground_floor: 0, + build_date: "", + decision_period_date: "", + tax_no: "", + lift_count: 0, + heating_system: false, + cooling_system: false, + hot_water_system: false, + block_service_man_count: 0, + security_service_man_count: 0, + garage_count: 0, + site_uu_id: null, + address_uu_id: "", + build_types_uu_id: "" +}; + +const translationsOfPage = { + tr: { + title: "Bina Oluştur", + back2List: "Listeye Geri Dön" + }, + en: { + title: "Create Building", + back2List: "Back to List" + } +} + +function CreateFromComponentBase({ + onlineData, + cacheData, + cacheLoading, + cacheError, + refreshCache, + updateCache, + clearCache, + activePageUrl +}: { + onlineData?: any; + cacheData?: { [url: string]: any } | null; + cacheLoading?: boolean; + cacheError?: string | null; + refreshCache?: (url?: string) => Promise; + updateCache?: (url: string, data: any) => Promise; + clearCache?: (url: string) => Promise; + activePageUrl: string; +}) { + const language: LanguageTypes = onlineData?.lang || 'en'; + const router = useRouter(); + const listUrl = `/panel/${activePageUrl?.replace("/create", "")}`; + const [cacheLoaded, setCacheLoaded] = useState(false); + const form = useForm({ resolver: zodResolver(buildSchemaCreate), defaultValues: emptyValues }); + + useEffect(() => { + const fetchCacheDirectly = async () => { + if (!cacheLoaded) { + try { + console.log("Directly fetching cache for URL:", activePageUrl); + const cachedData = await getCacheData(activePageUrl); + if (cachedData) { + const formValues = form.getValues(); + const mergedData: BuildCreateInterface = { + gov_address_code: cachedData.gov_address_code || formValues.gov_address_code || "", + build_name: cachedData.build_name || formValues.build_name || "", + build_no: cachedData.build_no || formValues.build_no || "", + max_floor: cachedData.max_floor ?? formValues.max_floor ?? 0, + underground_floor: cachedData.underground_floor ?? formValues.underground_floor ?? 0, + build_date: cachedData.build_date || formValues.build_date || "", + decision_period_date: cachedData.decision_period_date || formValues.decision_period_date || "", + tax_no: cachedData.tax_no || formValues.tax_no || "", + lift_count: cachedData.lift_count ?? formValues.lift_count ?? 0, + heating_system: cachedData.heating_system ?? formValues.heating_system ?? false, + cooling_system: cachedData.cooling_system ?? formValues.cooling_system ?? false, + hot_water_system: cachedData.hot_water_system ?? formValues.hot_water_system ?? false, + block_service_man_count: cachedData.block_service_man_count ?? formValues.block_service_man_count ?? 0, + security_service_man_count: cachedData.security_service_man_count ?? formValues.security_service_man_count ?? 0, + garage_count: cachedData.garage_count ?? formValues.garage_count ?? 0, + site_uu_id: cachedData.site_uu_id ?? formValues.site_uu_id ?? null, + address_uu_id: cachedData.address_uu_id || formValues.address_uu_id || "", + build_types_uu_id: cachedData.build_types_uu_id || formValues.build_types_uu_id || "" + }; + form.reset(mergedData); + } + setCacheLoaded(true); + if (refreshCache) { refreshCache(activePageUrl) } + } catch (error) { console.error("Error fetching cache directly:", error) } + } + }; + fetchCacheDirectly(); + }, []); + + const handleFieldBlur = async (fieldName: keyof BuildCreateInterface, value: any) => { + if (value) { + try { + const currentValues = form.getValues(); + const updatedData = { ...currentValues, [fieldName]: value }; + await setCacheData(activePageUrl, updatedData); + if (updateCache) { updateCache(activePageUrl, updatedData) } + } catch (error) { console.error("Error updating cache:", error) } + } + }; + + const onSubmit = async (data: BuildCreateInterface) => { + console.log("Form submitted with data:", data); + + try { + const response = await apiPostFetcher({ url: `/api/builds/create`, isNoCache: true, body: data }); + await clearCacheData(activePageUrl); + if (clearCache) { clearCache(activePageUrl) } + if (response.success) { form.reset(emptyValues); router.push(listUrl) } + } catch (error) { console.error("Error submitting form:", error) } + } + + return ( +
+ {/* back to list button */} +
+ +
+

{translationsOfPage[language].title}

+
+ + {/* Government Address Code */} + + + {/* Building Name */} + + + {/* Building Number */} + + + {/* Max Floor */} + + + {/* Underground Floor */} + + + {/* Build Date */} + + + {/* Decision Period Date */} + + + {/* Tax Number */} + + + {/* Lift Count */} + + + {/* Heating System */} + + + {/* Cooling System */} + + + {/* Hot Water System */} + + + {/* Block Service Man Count */} + + + {/* Security Service Man Count */} + + + {/* Garage Count */} + + + {/* Site UUID */} + + + {/* Address UUID */} + + + {/* Build Types UUID */} + + + + +
+ ); +} + +export const CreateFromBuildComponent = withCache(CreateFromComponentBase); +export default withCache(CreateFromBuildComponent); diff --git a/ServicesWeb/customer/src/pages/multi/builds/superuser/ListPage.tsx b/ServicesWeb/customer/src/pages/multi/builds/superuser/ListPage.tsx new file mode 100644 index 0000000..386a939 --- /dev/null +++ b/ServicesWeb/customer/src/pages/multi/builds/superuser/ListPage.tsx @@ -0,0 +1,573 @@ +'use client'; +import React from "react"; +import { + useReactTable, + getCoreRowModel, + getSortedRowModel, + getPaginationRowModel, + flexRender, + createColumnHelper, + ColumnDef, +} from "@tanstack/react-table"; +import { buildTranslations } from './translations'; +import { UseFormReturn } from "react-hook-form"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage +} from "@/components/mutual/ui/form"; +import { Button } from "@/components/mutual/ui/button"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/mutual/ui/select"; +import { API_BASE_URL } from "@/config/config"; +import { useTableData } from "@/hooks/useTableData"; +import { LanguageTypes } from "@/validations/mutual/language/validations"; +import { + Translations, + TableHeaderProps, + LoadingSpinnerProps, + ErrorDisplayProps, + MobilePaginationControlsProps, + DashboardPageProps, +} from "@/validations/mutual/table/validations"; +import LoadingContent from "@/components/mutual/loader/component"; +import { Pencil } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { setCacheData } from "@/components/mutual/context/cache/context"; +import { BuildSchemaInterface } from "./schema"; + +interface DataTableProps { + table: ReturnType; + tableData: BuildSchemaInterface[]; + isLoading: boolean; + handleSortingChange: (columnId: string) => void; + getSortingIcon: (columnId: string) => React.ReactNode; + flexRender: typeof flexRender; + t: Translations; +} + +interface TableFormProps { + form: UseFormReturn; + handleFormSubmit: (e: React.FormEvent) => void; + handleSelectChange: (value: string, field: { onChange: (value: number) => void }) => void; + renderPageOptions: () => { key: string | number; value: string; label: string }[]; + pageSizeOptions: number[]; + apiPagination: { + size: number; + page: number; + totalCount: number; + totalPages: number; + pageCount: number; + }; + handleFirstPage: () => void; + handlePreviousPage: () => void; + handleNextPage: () => void; + isPreviousDisabled: () => boolean; + isNextDisabled: () => boolean; + t: Translations; +} + +const translations: Record = { + en: { + dataTable: 'Data Table', + tableWithApiData: 'Table with API Data', + loading: 'Loading...', + noDataAvailable: 'No data available', + page: 'Page', + size: 'Size', + total: 'Total', + items: 'items', + first: 'First', + previous: 'Previous', + next: 'Next', + selectPage: 'Select page', + selectSize: 'Select size', + actionButtonGroup: 'Actions', + }, + tr: { + dataTable: 'Veri Tablosu', + tableWithApiData: 'API Verili Tablo', + loading: 'Yükleniyor...', + noDataAvailable: 'Veri bulunamadı', + page: 'Sayfa', + size: 'Boyut', + total: 'Toplam', + items: 'öğe', + first: 'İlk', + previous: 'Önceki', + next: 'Sonraki', + selectPage: 'Sayfa seç', + selectSize: 'Boyut seç', + actionButtonGroup: 'Eylemler', + } +}; + +const DataTable: React.FC = React.memo(({ + table, + tableData, + isLoading, + handleSortingChange, + getSortingIcon, + flexRender, + t, +}) => { + return ( +
+ {/* Semi-transparent loading overlay that preserves interactivity */} + {isLoading && ( +
+ {/* We don't put anything here as we already have the loading indicator in the header */} +
+ )} + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ); + })} + + ))} + + + {tableData.length === 0 && !isLoading ? ( + + + + ) : ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => { + return ( + + ); + })} + + )) + )} + +
{t.dataTable}
handleSortingChange(header.column.id)} + aria-sort={header.column.getIsSorted() ? (header.column.getIsSorted() === 'desc' ? 'descending' : 'ascending') : undefined} + scope="col" + > +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {getSortingIcon(header.column.id)} +
+
+ {t.noDataAvailable} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
+ ); +}); + +const TableForm: React.FC = ({ + form, + handleFormSubmit, + handleSelectChange, + renderPageOptions, + pageSizeOptions, + apiPagination, + handleFirstPage, + handlePreviousPage, + handleNextPage, + isPreviousDisabled, + isNextDisabled, + t +}) => { + return ( +
+
+ +
+ ( + + {t.page} + + + + )} + /> +
+
+ ( + + {t.size} + + + + )} + /> +
+
+

+ {t.page}: {apiPagination.page}{" / "} {apiPagination.totalPages} · + {t.size}: {apiPagination.pageCount}{" / "} {apiPagination.size} · + {t.total}: {apiPagination.totalCount} {t.items} +

+
+
+ + + + {/* */} +
+
+ +
+ ); +}; + +const MobilePaginationControls: React.FC = ({ + handlePreviousPage, + handleNextPage, + isPreviousDisabled, + isNextDisabled, + t +}) => { + return ( +
+
+ + +
+
+ ); +}; + +const TableHeader: React.FC = ({ title, description, isLoading, error, t }) => { + return ( + +
+
+
+

{title}

+

{description}

+
+ {/* {isLoading && } */} + {error && } +
+
+ ); +}; + +const ErrorDisplay: React.FC = ({ message }) => { + return
{message}
; +}; + +const BuildListPage: React.FC = React.memo((props) => { + // Initialize translation with English as default + const language = props.onlineData?.lang as LanguageTypes || 'en'; + + const t = translations[language]; + const router = useRouter(); + + const { + form, + tableData, + sorting, + isLoading, + error, + pagination, + apiPagination, + setSorting, + handleSortingChange, + handleSelectChange, + handlePageChange, + handleFirstPage, + handlePreviousPage, + handleNextPage, + getSortingIcon, + handleFormSubmit, + // Disabled states + isPreviousDisabled, + isNextDisabled, + pageSizeOptions, + renderPageOptions, + } = useTableData({ apiUrl: `${API_BASE_URL}/builds/list` }); + const activePageUrl = props.activePageUrl || ''; + const handleEditRow = async (row: BuildSchemaInterface) => { + try { + await setCacheData(`${activePageUrl}/update`, row); + router.push(`/panel/${activePageUrl}/update`); + } catch (error) { + console.error('Error storing row data in cache:', error); + } + }; + + const actionButtonGroup = (info: any) => ( + + ); + + const columnHelper = createColumnHelper(); + const columns = React.useMemo(() => [ + columnHelper.display({ + id: 'actions', + header: () => {t.actionButtonGroup}, + cell: (info) => { return (actionButtonGroup(info)) } + }), + columnHelper.accessor('uu_id', { + cell: info => info.getValue(), + header: () => {buildTranslations[language].uu_id}, + footer: info => info.column.id + }), + columnHelper.accessor('gov_address_code', { + cell: info => info.getValue(), + header: () => {buildTranslations[language].gov_address_code}, + footer: info => info.column.id + }), + columnHelper.accessor('build_name', { + cell: info => info.getValue(), + header: () => {buildTranslations[language].build_name}, + footer: info => info.column.id + }), + columnHelper.accessor('build_no', { + cell: info => info.getValue(), + header: () => {buildTranslations[language].build_no}, + footer: info => info.column.id + }), + columnHelper.accessor('max_floor', { + cell: info => String(info.getValue()), + header: () => {buildTranslations[language].max_floor}, + footer: info => info.column.id + }), + columnHelper.accessor('underground_floor', { + cell: info => String(info.getValue()), + header: () => {buildTranslations[language].underground_floor}, + footer: info => info.column.id + }), + columnHelper.accessor('build_date', { + cell: info => info.getValue(), + header: () => {buildTranslations[language].build_date}, + footer: info => info.column.id + }), + columnHelper.accessor('decision_period_date', { + cell: info => info.getValue(), + header: () => {buildTranslations[language].decision_period_date}, + footer: info => info.column.id + }), + columnHelper.accessor('tax_no', { + cell: info => info.getValue(), + header: () => {buildTranslations[language].tax_no}, + footer: info => info.column.id + }), + columnHelper.accessor('lift_count', { + cell: info => String(info.getValue()), + header: () => {buildTranslations[language].lift_count}, + footer: info => info.column.id + }), + columnHelper.accessor('heating_system', { + cell: info => info.getValue() ? 'Yes' : 'No', + header: () => {buildTranslations[language].heating_system}, + footer: info => info.column.id + }), + columnHelper.accessor('cooling_system', { + cell: info => info.getValue() ? 'Yes' : 'No', + header: () => {buildTranslations[language].cooling_system}, + footer: info => info.column.id + }), + columnHelper.accessor('hot_water_system', { + cell: info => info.getValue() ? 'Yes' : 'No', + header: () => {buildTranslations[language].hot_water_system}, + footer: info => info.column.id + }), + columnHelper.accessor('block_service_man_count', { + cell: info => String(info.getValue()), + header: () => {buildTranslations[language].block_service_man_count}, + footer: info => info.column.id + }), + columnHelper.accessor('security_service_man_count', { + cell: info => String(info.getValue()), + header: () => {buildTranslations[language].security_service_man_count}, + footer: info => info.column.id + }), + columnHelper.accessor('garage_count', { + cell: info => String(info.getValue()), + header: () => {buildTranslations[language].garage_count}, + footer: info => info.column.id + }), + columnHelper.accessor('site_uu_id', { + cell: info => info.getValue(), + header: () => {buildTranslations[language].site_uu_id}, + footer: info => info.column.id + }), + columnHelper.accessor('address_uu_id', { + cell: info => info.getValue(), + header: () => {buildTranslations[language].address_uu_id}, + footer: info => info.column.id + }), + columnHelper.accessor('build_types_uu_id', { + cell: info => info.getValue(), + header: () => {buildTranslations[language].build_types_uu_id}, + footer: info => info.column.id + }), + ], [columnHelper]) as ColumnDef[]; + + const table = useReactTable({ + data: tableData, + columns, + state: { sorting, pagination }, + onSortingChange: setSorting, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + manualPagination: true, + pageCount: apiPagination.totalPages || 1, + }) + + return ( + isLoading ? : +
+ +
+ + +
+ + + + {/* Mobile pagination controls - only visible on small screens */} + + + +
+ ); +}); + +export default BuildListPage; diff --git a/ServicesWeb/customer/src/pages/multi/builds/superuser/UpdatePage.tsx b/ServicesWeb/customer/src/pages/multi/builds/superuser/UpdatePage.tsx new file mode 100644 index 0000000..ba804f5 --- /dev/null +++ b/ServicesWeb/customer/src/pages/multi/builds/superuser/UpdatePage.tsx @@ -0,0 +1,335 @@ +import React, { useState, useEffect } from "react"; +import { apiPostFetcher } from "@/lib/fetcher"; +import { useRouter } from "next/navigation"; + +import { withCache } from "@/components/mutual/context/cache/withCache"; +import { Button } from "@/components/mutual/ui/button"; +import { Form } from "@/components/mutual/ui/form"; +import { getCacheData, setCacheData, clearCacheData } from "@/components/mutual/context/cache/context"; + +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { LanguageTypes } from "@/validations/mutual/language/validations"; + +import { buildSchema, BuildInterface } from "./schema"; +import { buildTranslations } from "./translations"; + +import { CheckBoxInput } from "@/components/mutual/formInputs/CheckBoxInput"; +import { NumberInput } from "@/components/mutual/formInputs/NumberInput"; +import { StringInput } from "@/components/mutual/formInputs/StringInput"; +import { DatetimeInput } from "@/components/mutual/formInputs/DatetimeInput"; + +const emptyValues: BuildInterface = { + uu_id: "", + gov_address_code: "", + build_name: "", + build_no: "", + max_floor: 0, + underground_floor: 0, + build_date: "", + decision_period_date: "", + tax_no: "", + lift_count: 0, + heating_system: false, + cooling_system: false, + hot_water_system: false, + block_service_man_count: 0, + security_service_man_count: 0, + garage_count: 0, + site_uu_id: null, + address_uu_id: "", + build_types_uu_id: "" +}; + +const translationsOfPage = { + tr: { + title: "Bina Güncelle", + back2List: "Listeye Geri Dön" + }, + en: { + title: "Update Building", + back2List: "Back to List" + } +} + +function UpdateFromComponentBase({ + onlineData, + cacheData, + cacheLoading, + cacheError, + refreshCache, + updateCache, + clearCache, + activePageUrl +}: { + onlineData?: any; + cacheData?: { [url: string]: any } | null; + cacheLoading?: boolean; + cacheError?: string | null; + refreshCache?: (url?: string) => Promise; + updateCache?: (url: string, data: any) => Promise; + clearCache?: (url: string) => Promise; + activePageUrl: string; +}) { + const language: LanguageTypes = onlineData?.lang || 'en'; + const [cacheLoaded, setCacheLoaded] = useState(false); + const [buildUUID, setBuildUUID] = useState(""); + const router = useRouter(); + const listUrl = `/panel/${activePageUrl?.replace("/update", "")}`; + const form = useForm({ + resolver: zodResolver(buildSchema), + mode: "onSubmit", + defaultValues: emptyValues + }); + + useEffect(() => { + const fetchData = async () => { + if (!cacheLoaded) { + try { + const cachedData = await getCacheData(activePageUrl); + if (cachedData) { + const formValues = form.getValues(); + const mergedData: BuildInterface = { + uu_id: "", + gov_address_code: cachedData.gov_address_code || formValues.gov_address_code || "", + build_name: cachedData.build_name || formValues.build_name || "", + build_no: cachedData.build_no || formValues.build_no || "", + max_floor: cachedData.max_floor ?? formValues.max_floor ?? 0, + underground_floor: cachedData.underground_floor ?? formValues.underground_floor ?? 0, + build_date: cachedData.build_date || formValues.build_date || "", + decision_period_date: cachedData.decision_period_date || formValues.decision_period_date || "", + tax_no: cachedData.tax_no || formValues.tax_no || "", + lift_count: cachedData.lift_count ?? formValues.lift_count ?? 0, + heating_system: cachedData.heating_system ?? formValues.heating_system ?? false, + cooling_system: cachedData.cooling_system ?? formValues.cooling_system ?? false, + hot_water_system: cachedData.hot_water_system ?? formValues.hot_water_system ?? false, + block_service_man_count: cachedData.block_service_man_count ?? formValues.block_service_man_count ?? 0, + security_service_man_count: cachedData.security_service_man_count ?? formValues.security_service_man_count ?? 0, + garage_count: cachedData.garage_count ?? formValues.garage_count ?? 0, + site_uu_id: cachedData.site_uu_id ?? formValues.site_uu_id ?? null, + address_uu_id: cachedData.address_uu_id || formValues.address_uu_id || "", + build_types_uu_id: cachedData.build_types_uu_id || formValues.build_types_uu_id || "" + }; + form.reset(mergedData); + setBuildUUID(cachedData.uu_id); + } + setCacheLoaded(true); + if (refreshCache) { refreshCache(activePageUrl); } + } catch (error) { console.error("Error fetching cache:", error); } + } + }; + fetchData(); + }, []); + + const handleFieldBlur = async (fieldName: keyof BuildInterface, value: any) => { + if (value) { + try { + const currentValues = form.getValues(); + const updatedData = { ...currentValues, [fieldName]: value }; + await setCacheData(activePageUrl, updatedData); + if (updateCache) { updateCache(activePageUrl, updatedData); } + } catch (error) { console.error("Error updating cache:", error); } + } + }; + + const onSubmit = async (data: BuildInterface) => { + try { + console.log('Form data received:', data); + const formattedData = { + ...data, + build_date: data.build_date || "", + decision_period_date: data.decision_period_date || "", + uuid: buildUUID + }; + const response = await apiPostFetcher({ + url: `/api/builds/update/${buildUUID}`, + isNoCache: true, + body: formattedData, + }); + await clearCacheData(activePageUrl); + if (clearCache) { clearCache(activePageUrl); } + if (response.success) { form.reset(emptyValues); router.push(listUrl) } + } catch (error) { console.error("Error submitting form:", error); } + } + + return ( +
+ {/* back to list button */} +
+ +
+

{translationsOfPage[language].title}

+

UUID: {buildUUID}

+ +
+ { + console.error('Form validation errors:', errors); + })} className="space-y-6"> + {/* Government Address Code */} + + + {/* Building Name */} + + + {/* Building Number */} + + + {/* Max Floor */} + + + {/* Underground Floor */} + + + {/* Build Date */} + + + {/* Decision Period Date */} + + + {/* Tax Number */} + + + {/* Lift Count */} + + + {/* Heating System */} + + + {/* Cooling System */} + + + {/* Hot Water System */} + + + {/* Block Service Man Count */} + + + {/* Security Service Man Count */} + + + {/* Garage Count */} + + + {/* Site UUID */} + + + {/* Address UUID */} + + + {/* Build Types UUID */} + + + + + +
+ ); +} + +export const UpdateFromComponent = withCache(UpdateFromComponentBase); +export default withCache(UpdateFromComponentBase); diff --git a/ServicesWeb/customer/src/pages/multi/builds/superuser/schema.ts b/ServicesWeb/customer/src/pages/multi/builds/superuser/schema.ts new file mode 100644 index 0000000..090ec15 --- /dev/null +++ b/ServicesWeb/customer/src/pages/multi/builds/superuser/schema.ts @@ -0,0 +1,57 @@ +import * as z from "zod"; +import { dateStringSchema } from "@/utils/zodTypes"; + +const buildSchema = z.object({ + uu_id: z.string(), + gov_address_code: z.string(), + build_name: z.string().describe("Building Name"), + build_no: z.string().max(8).describe("Building Number"), + max_floor: z.number().int().describe("Max Floor"), + underground_floor: z.number().int().describe("Underground Floor"), + build_date: dateStringSchema, + decision_period_date: dateStringSchema.describe( + "Building annual ordinary meeting period" + ), + tax_no: z.string().max(24), + lift_count: z.number().int(), + heating_system: z.boolean(), + cooling_system: z.boolean(), + hot_water_system: z.boolean(), + block_service_man_count: z.number().int(), + security_service_man_count: z.number().int(), + garage_count: z.number().int().describe("Garage Count"), + + site_uu_id: z.string().nullable().describe("Site UUID"), + address_uu_id: z.string().describe("Address UUID"), + build_types_uu_id: z.string().describe("Building Type UUID"), +}); + +const buildSchemaCreate = buildSchema.omit({ uu_id: true }); + +type BuildInterface = z.infer; +type BuildCreateInterface = z.infer; + +interface BuildSchemaInterface { + uu_id: string; + gov_address_code: string; + build_name: string; + build_no: string; + max_floor: number; + underground_floor: number; + build_date: string; + decision_period_date: string; + tax_no: string; + lift_count: number; + heating_system: boolean; + cooling_system: boolean; + hot_water_system: boolean; + block_service_man_count: number; + security_service_man_count: number; + garage_count: number; + site_uu_id: string | null; + address_uu_id: string; + build_types_uu_id: string; +} + +export type { BuildInterface, BuildSchemaInterface, BuildCreateInterface }; +export { buildSchema, buildSchemaCreate }; diff --git a/ServicesWeb/customer/src/pages/multi/builds/superuser/translations.ts b/ServicesWeb/customer/src/pages/multi/builds/superuser/translations.ts new file mode 100644 index 0000000..1942105 --- /dev/null +++ b/ServicesWeb/customer/src/pages/multi/builds/superuser/translations.ts @@ -0,0 +1,48 @@ +export const buildTranslations = { + tr: { + uu_id: "UUID", + gov_address_code: "Devlet Adres Kodu", + build_name: "Bina Adı", + build_no: "Bina No", + max_floor: "En Fazla Kat", + underground_floor: "Asfaltı Kat", + build_date: "Bina Tarihi", + decision_period_date: "Karar Periyodu", + tax_no: "Vergi No", + lift_count: "Kalkınma Sayısı", + heating_system: "Isıltma Sistemi", + cooling_system: "Soğutma Sistemi", + hot_water_system: "Sıcak Su Sistemi", + block_service_man_count: "Blok Hizmet Müdürü Sayısı", + security_service_man_count: "Güvenlik Hizmet Müdürü Sayısı", + garage_count: "Garaj Sayısı", + management_room_id: "Yönetim Oda ID", + site_uu_id: "Site UUID", + address_uu_id: "Adres UUID", + build_types_uu_id: "Bina Türü UUID", + }, + en: { + uu_id: "UUID", + gov_address_code: "Gov Address Code", + build_name: "Build Name", + build_no: "Build No", + max_floor: "Max Floor", + underground_floor: "Underground Floor", + build_date: "Build Date", + decision_period_date: "Decision Period Date", + tax_no: "Tax No", + lift_count: "Lift Count", + heating_system: "Heating System", + cooling_system: "Cooling System", + hot_water_system: "Hot Water System", + block_service_man_count: "Block Service Man Count", + security_service_man_count: "Security Service Man Count", + garage_count: "Garage Count", + management_room_id: "Management Room ID", + site_uu_id: "Site UUID", + address_uu_id: "Address UUID", + build_types_uu_id: "Build Types UUID", + }, +}; + +export default buildTranslations; diff --git a/ServicesWeb/customer/src/pages/multi/index.ts b/ServicesWeb/customer/src/pages/multi/index.ts index 7afc68d..7a1dee6 100644 --- a/ServicesWeb/customer/src/pages/multi/index.ts +++ b/ServicesWeb/customer/src/pages/multi/index.ts @@ -1,14 +1,21 @@ // import { DashboardPage } from "@/components/custom/content/DashboardPage"; import { DPage } from "@/components/custom/content/DPage"; import TableCardComponentImproved from "@/components/custom/content/TableCardComponentImproved"; -import CreateFromComponent from "@/components/custom/content/createFromComponent"; -import UpdateFromComponent from "@/components/custom/content/updateFromComponent"; +import BuildListPage from "./builds/superuser/ListPage"; +import CreateFromBuildComponent from "./builds/superuser/CreatePage"; +import UpdateFromBuildComponent from "./builds/superuser/UpdatePage"; +import BuildPartsListPage from "./buildParts/superuser/ListPage"; +import CreateFromBuildPartsComponent from "./buildParts/superuser/CreatePage"; +import UpdateFromBuildPartsComponent from "./buildParts/superuser/UpdatePage"; const pageIndexMulti: Record>> = { "/dashboard": { DashboardPage: TableCardComponentImproved }, - "/build": { DashboardPage: TableCardComponentImproved }, - "/build/create": { DashboardPage: CreateFromComponent }, - "/build/update": { DashboardPage: UpdateFromComponent }, + "/build": { DashboardPage: BuildListPage }, + "/build/create": { DashboardPage: CreateFromBuildComponent }, + "/build/update": { DashboardPage: UpdateFromBuildComponent }, + "/build/parts": { DashboardPage: BuildPartsListPage }, + "/build/parts/create": { DashboardPage: CreateFromBuildPartsComponent }, + "/build/parts/update": { DashboardPage: UpdateFromBuildPartsComponent }, "/people": { DashboardPage: DPage }, "/people/create": { DashboardPage: DPage }, "/people/update": { DashboardPage: DPage }, diff --git a/ServicesWeb/customer/src/schemas/custom/building/build.ts b/ServicesWeb/customer/src/schemas/custom/building/build.ts index 511c115..714f184 100644 --- a/ServicesWeb/customer/src/schemas/custom/building/build.ts +++ b/ServicesWeb/customer/src/schemas/custom/building/build.ts @@ -1,71 +1,81 @@ -import { z } from 'zod'; +import { z } from "zod"; /** * Zod schema for BuildTypes * Corresponds to the BuildTypes class in Python */ export const buildTypesSchema = z.object({ - function_code: z.string().max(12).default('').describe('Function Code'), - type_code: z.string().max(12).default('').describe('Structure Type Code'), - lang: z.string().max(4).default('TR').describe('Language'), - type_name: z.string().max(48).default('').describe('Type Name'), + function_code: z.string().max(12).default("").describe("Function Code"), + type_code: z.string().max(12).default("").describe("Structure Type Code"), + lang: z.string().max(4).default("TR").describe("Language"), + type_name: z.string().max(48).default("").describe("Type Name"), }); export type BuildTypes = z.infer; -/** - * Zod schema for Part2Employee - * Corresponds to the Part2Employee class in Python - */ -export const part2EmployeeSchema = z.object({ - build_id: z.number().int().describe('Building ID'), - part_id: z.number().int().describe('Part ID'), - employee_id: z.number().int().describe('Employee ID'), -}); +// /** +// * Zod schema for Part2Employee +// * Corresponds to the Part2Employee class in Python +// */ +// export const part2EmployeeSchema = z.object({ +// build_uu_id: z.string().describe("Building UUID"), +// part_uu_id: z.string().describe("Part UUID"), +// employee_uu_id: z.string().describe("Employee UUID"), +// }); -export type Part2Employee = z.infer; +// export type Part2Employee = z.infer; -/** - * Zod schema for RelationshipEmployee2Build - * Corresponds to the RelationshipEmployee2Build class in Python - */ -export const relationshipEmployee2BuildSchema = z.object({ - company_id: z.number().int(), - employee_id: z.number().int(), - member_id: z.number().int(), - relationship_type: z.string().default('Employee'), - show_only: z.boolean().default(false), -}); +// /** +// * Zod schema for RelationshipEmployee2Build +// * Corresponds to the RelationshipEmployee2Build class in Python +// */ +// export const relationshipEmployee2BuildSchema = z.object({ +// company_id: z.number().int(), +// employee_id: z.number().int(), +// member_id: z.number().int(), +// relationship_type: z.string().default("Employee"), +// show_only: z.boolean().default(false), +// }); -export type RelationshipEmployee2Build = z.infer; +// export type RelationshipEmployee2Build = z.infer< +// typeof relationshipEmployee2BuildSchema +// >; /** * Zod schema for Build * Corresponds to the Build class in Python */ export const buildSchema = z.object({ - gov_address_code: z.string().default(''), - build_name: z.string().describe('Building Name'), - build_no: z.string().max(8).describe('Building Number'), - max_floor: z.number().int().default(1).describe('Max Floor'), - underground_floor: z.number().int().default(0).describe('Underground Floor'), - build_date: z.string().datetime().default('1900-01-01T00:00:00Z'), - decision_period_date: z.string().datetime().default('1900-01-01T00:00:00Z').describe('Building annual ordinary meeting period'), - tax_no: z.string().max(24).default(''), + gov_address_code: z.string().default(""), + build_name: z.string().describe("Building Name"), + build_no: z.string().max(8).describe("Building Number"), + max_floor: z.number().int().default(1).describe("Max Floor"), + underground_floor: z.number().int().default(0).describe("Underground Floor"), + build_date: z.string().datetime().default("1900-01-01T00:00:00Z"), + decision_period_date: z + .string() + .datetime() + .default("1900-01-01T00:00:00Z") + .describe("Building annual ordinary meeting period"), + tax_no: z.string().max(24).default(""), lift_count: z.number().int().default(0), heating_system: z.boolean().default(true), cooling_system: z.boolean().default(false), hot_water_system: z.boolean().default(false), block_service_man_count: z.number().int().default(0), security_service_man_count: z.number().int().default(0), - garage_count: z.number().int().default(0).describe('Garage Count'), - management_room_id: z.number().int().nullable().describe('Management Room ID'), + garage_count: z.number().int().default(0).describe("Garage Count"), + management_room_id: z + .number() + .int() + .nullable() + .describe("Management Room ID"), site_id: z.number().int().nullable(), - site_uu_id: z.string().nullable().describe('Site UUID'), + site_uu_id: z.string().nullable().describe("Site UUID"), address_id: z.number().int(), - address_uu_id: z.string().describe('Address UUID'), - build_types_id: z.number().int().describe('Building Type'), - build_types_uu_id: z.string().describe('Building Type UUID'), + address_uu_id: z.string().describe("Address UUID"), + build_types_id: z.number().int().describe("Building Type"), + build_types_uu_id: z.string().describe("Building Type UUID"), }); export type Build = z.infer; @@ -75,21 +85,18 @@ export type Build = z.infer; * Corresponds to the BuildParts class in Python */ export const buildPartsSchema = z.object({ - address_gov_code: z.string().describe('Goverment Door Code'), - part_no: z.number().int().default(0).describe('Part Number'), - part_level: z.number().int().default(0).describe('Building Part Level'), - part_code: z.string().default('').describe('Part Code'), - part_gross_size: z.number().int().default(0).describe('Part Gross Size'), - part_net_size: z.number().int().default(0).describe('Part Net Size'), - default_accessory: z.string().default('0').describe('Default Accessory'), - human_livable: z.boolean().default(true).describe('Human Livable'), - due_part_key: z.string().default('').describe('Constant Payment Group'), - build_id: z.number().int().describe('Building ID'), - build_uu_id: z.string().describe('Building UUID'), - part_direction_id: z.number().int().nullable(), - part_direction_uu_id: z.string().nullable().describe('Part Direction UUID'), - part_type_id: z.number().int().describe('Building Part Type'), - part_type_uu_id: z.string().describe('Building Part Type UUID'), + address_gov_code: z.string().describe("Goverment Door Code"), + part_no: z.number().int().default(0).describe("Part Number"), + part_level: z.number().int().default(0).describe("Building Part Level"), + part_code: z.string().default("").describe("Part Code"), + part_gross_size: z.number().int().default(0).describe("Part Gross Size"), + part_net_size: z.number().int().default(0).describe("Part Net Size"), + default_accessory: z.string().default("0").describe("Default Accessory"), + human_livable: z.boolean().default(true).describe("Human Livable"), + due_part_key: z.string().default("").describe("Constant Payment Group"), + build_uu_id: z.string().describe("Building UUID"), + part_direction_uu_id: z.string().nullable().describe("Part Direction UUID"), + part_type_uu_id: z.string().describe("Building Part Type UUID"), }); export type BuildParts = z.infer; @@ -99,17 +106,20 @@ export type BuildParts = z.infer; * Corresponds to the BuildLivingSpace class in Python */ export const buildLivingSpaceSchema = z.object({ - fix_value: z.number().default(0).describe('Fixed value is deducted from debit.'), - fix_percent: z.number().default(0).describe('Fixed percent is deducted from debit.'), - agreement_no: z.string().default('').describe('Agreement No'), + fix_value: z + .number() + .default(0) + .describe("Fixed value is deducted from debit."), + fix_percent: z + .number() + .default(0) + .describe("Fixed percent is deducted from debit."), + agreement_no: z.string().default("").describe("Agreement No"), marketing_process: z.boolean().default(false), marketing_layer: z.number().int().default(0), - build_parts_id: z.number().int().describe('Build Part ID'), - build_parts_uu_id: z.string().describe('Build Part UUID'), - person_id: z.number().int().describe('Responsible People ID'), - person_uu_id: z.string().describe('Responsible People UUID'), - occupant_type_id: z.number().int().describe('Occupant Type'), - occupant_type_uu_id: z.string().describe('Occupant Type UUID'), + build_parts_uu_id: z.string().describe("Build Part UUID"), + person_uu_id: z.string().describe("Responsible People UUID"), + occupant_type_uu_id: z.string().describe("Occupant Type UUID"), }); export type BuildLivingSpace = z.infer; @@ -122,12 +132,10 @@ export const buildManagementSchema = z.object({ discounted_percentage: z.number().default(0), discounted_price: z.number().default(0), calculated_price: z.number().default(0), - occupant_type: z.number().int().describe('Occupant Type'), - occupant_type_uu_id: z.string().describe('Occupant Type UUID'), - build_id: z.number().int().describe('Building ID'), - build_uu_id: z.string().describe('Building UUID'), - build_parts_id: z.number().int().describe('Build Part ID'), - build_parts_uu_id: z.string().describe('Build Part UUID'), + // occupant_type: z.number().int().describe("Occupant Type"), + occupant_type_uu_id: z.string().describe("Occupant Type UUID"), + build_uu_id: z.string().describe("Building UUID"), + build_parts_uu_id: z.string().describe("Build Part UUID"), }); export type BuildManagement = z.infer; @@ -137,18 +145,16 @@ export type BuildManagement = z.infer; * Corresponds to the BuildArea class in Python */ export const buildAreaSchema = z.object({ - area_name: z.string().default(''), - area_code: z.string().default(''), - area_type: z.string().default('GREEN'), - area_direction: z.string().max(2).default('NN'), + area_name: z.string().default(""), + area_code: z.string().default(""), + area_type: z.string().default("GREEN"), + area_direction: z.string().max(2).default("NN"), area_gross_size: z.number().default(0), area_net_size: z.number().default(0), width: z.number().int().default(0), size: z.number().int().default(0), - build_id: z.number().int(), - build_uu_id: z.string().describe('Building UUID'), - part_type_id: z.number().int().nullable().describe('Building Part Type'), - part_type_uu_id: z.string().nullable().describe('Building Part Type UUID'), + build_uu_id: z.string().describe("Building UUID"), + part_type_uu_id: z.string().nullable().describe("Building Part Type UUID"), }); export type BuildArea = z.infer; @@ -160,8 +166,7 @@ export type BuildArea = z.infer; export const buildSitesSchema = z.object({ site_name: z.string().max(24), site_no: z.string().max(8), - address_id: z.number().int(), - address_uu_id: z.string().describe('Address UUID'), + address_uu_id: z.string().describe("Address UUID"), }); export type BuildSites = z.infer; @@ -171,28 +176,23 @@ export type BuildSites = z.infer; * Corresponds to the BuildCompaniesProviding class in Python */ export const buildCompaniesProvidingSchema = z.object({ - build_id: z.number().int().describe('Building ID'), - build_uu_id: z.string().nullable().describe('Providing UUID'), - company_id: z.number().int(), - company_uu_id: z.string().nullable().describe('Providing UUID'), - provide_id: z.number().int().nullable(), - provide_uu_id: z.string().nullable().describe('Providing UUID'), - contract_id: z.number().int().nullable(), + build_uu_id: z.string().nullable().describe("Providing UUID"), + company_uu_id: z.string().nullable().describe("Providing UUID"), + provide_uu_id: z.string().nullable().describe("Providing UUID"), }); -export type BuildCompaniesProviding = z.infer; +export type BuildCompaniesProviding = z.infer< + typeof buildCompaniesProvidingSchema +>; /** * Zod schema for BuildPersonProviding * Corresponds to the BuildPersonProviding class in Python */ export const buildPersonProvidingSchema = z.object({ - build_id: z.number().int().describe('Building ID'), - build_uu_id: z.string().nullable().describe('Providing UUID'), - people_id: z.number().int(), - people_uu_id: z.string().nullable().describe('People UUID'), - provide_id: z.number().int().nullable(), - provide_uu_id: z.string().nullable().describe('Providing UUID'), + build_uu_id: z.string().nullable().describe("Providing UUID"), + people_uu_id: z.string().nullable().describe("People UUID"), + provide_uu_id: z.string().nullable().describe("Providing UUID"), contract_id: z.number().int().nullable(), }); diff --git a/ServicesWeb/customer/src/utils/zodTypes.ts b/ServicesWeb/customer/src/utils/zodTypes.ts new file mode 100644 index 0000000..32d1f0a --- /dev/null +++ b/ServicesWeb/customer/src/utils/zodTypes.ts @@ -0,0 +1,11 @@ +import * as z from "zod"; + +// Custom date validator that accepts YYYY-MM-DD format or empty string +export const dateStringSchema = z.string().refine( + (val) => { + if (!val) return true; // Allow empty strings + // Check if it's a valid date in YYYY-MM-DD format + return /^\d{4}-\d{2}-\d{2}$/.test(val) || !isNaN(Date.parse(val)); + }, + { message: "Invalid date format. Use YYYY-MM-DD" } +); diff --git a/ServicesWeb/customer/src/validations/mutual/table/validations.ts b/ServicesWeb/customer/src/validations/mutual/table/validations.ts index 93f28ec..1486ead 100644 --- a/ServicesWeb/customer/src/validations/mutual/table/validations.ts +++ b/ServicesWeb/customer/src/validations/mutual/table/validations.ts @@ -12,6 +12,7 @@ interface Translations { next: string; selectPage: string; selectSize: string; + actionButtonGroup: string; } interface TableHeaderProps {