2 chained application designed and new stage inited
This commit is contained in:
parent
311736ce06
commit
a9655c5f48
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Any
|
from typing import Optional, Any
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
|
|
||||||
from index import endpoints_index
|
from index import endpoints_index
|
||||||
|
|
@ -7,11 +7,27 @@ from events.building_parts.cluster import PartsRouterCluster
|
||||||
from Validations.defaults.validations import CommonHeaders
|
from Validations.defaults.validations import CommonHeaders
|
||||||
from Validations.response.pagination import PaginateOnly
|
from Validations.response.pagination import PaginateOnly
|
||||||
from Extensions.Middlewares.token_provider import TokenProvider
|
from Extensions.Middlewares.token_provider import TokenProvider
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
parts_endpoint_route = APIRouter(prefix="/parts", tags=["Parts Cluster"])
|
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_list = "PartsList"
|
||||||
@parts_endpoint_route.post(
|
@parts_endpoint_route.post(
|
||||||
path="/list",
|
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)
|
token_object = TokenProvider.get_dict_from_redis(token=headers.token)
|
||||||
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
||||||
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
|
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)
|
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
|
||||||
return event_cluster_matched.event_callable(list_options=data, headers=headers)
|
return event_cluster_matched.event_callable(list_options=data, headers=headers)
|
||||||
|
|
||||||
|
|
@ -33,11 +49,11 @@ parts_create = "PartsCreate"
|
||||||
description="Create part endpoint",
|
description="Create part endpoint",
|
||||||
operation_id=endpoints_index[parts_create],
|
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)
|
token_object = TokenProvider.get_dict_from_redis(token=headers.token)
|
||||||
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
||||||
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
|
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)
|
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
|
||||||
return event_cluster_matched.event_callable(data=data, headers=headers)
|
return event_cluster_matched.event_callable(data=data, headers=headers)
|
||||||
|
|
||||||
|
|
@ -48,11 +64,11 @@ parts_update = "PartsUpdate"
|
||||||
description="Update part endpoint",
|
description="Update part endpoint",
|
||||||
operation_id=endpoints_index[parts_update],
|
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)
|
token_object = TokenProvider.get_dict_from_redis(token=headers.token)
|
||||||
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
||||||
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
|
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)
|
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
|
||||||
return event_cluster_matched.event_callable(uu_id=uu_id, data=data, headers=headers)
|
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)
|
token_object = TokenProvider.get_dict_from_redis(token=headers.token)
|
||||||
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
||||||
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
|
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)
|
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
|
||||||
return event_cluster_matched.event_callable(uu_id=uu_id, headers=headers)
|
return event_cluster_matched.event_callable(uu_id=uu_id, headers=headers)
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from typing import Any
|
from typing import Any, Optional
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from index import endpoints_index
|
from index import endpoints_index
|
||||||
from events.builds.cluster import BuildRouterCluster
|
from events.builds.cluster import BuildRouterCluster
|
||||||
|
|
@ -9,6 +10,31 @@ from Validations.response.pagination import PaginateOnly
|
||||||
from Extensions.Middlewares.token_provider import TokenProvider
|
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_endpoint_route = APIRouter(prefix="/builds", tags=["Builds Cluster"])
|
||||||
|
|
||||||
build_list = "BuildList"
|
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)
|
token_object = TokenProvider.get_dict_from_redis(token=headers.token)
|
||||||
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
||||||
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
|
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)
|
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
|
||||||
return event_cluster_matched.event_callable(list_options=data, headers=headers)
|
return event_cluster_matched.event_callable(list_options=data, headers=headers)
|
||||||
|
|
||||||
|
|
@ -32,11 +58,11 @@ build_create = "BuildCreate"
|
||||||
description="Create build endpoint",
|
description="Create build endpoint",
|
||||||
operation_id=endpoints_index[build_create],
|
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)
|
token_object = TokenProvider.get_dict_from_redis(token=headers.token)
|
||||||
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
||||||
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
|
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)
|
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
|
||||||
return event_cluster_matched.event_callable(data=data, headers=headers)
|
return event_cluster_matched.event_callable(data=data, headers=headers)
|
||||||
|
|
||||||
|
|
@ -47,11 +73,11 @@ build_update = "BuildUpdate"
|
||||||
description="Update build endpoint",
|
description="Update build endpoint",
|
||||||
operation_id=endpoints_index[build_update],
|
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)
|
token_object = TokenProvider.get_dict_from_redis(token=headers.token)
|
||||||
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
||||||
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
|
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)
|
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
|
||||||
return event_cluster_matched.event_callable(uu_id=uu_id, data=data, headers=headers)
|
return event_cluster_matched.event_callable(uu_id=uu_id, data=data, headers=headers)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ from Validations.defaults.validations import CommonHeaders
|
||||||
from Schemas import (
|
from Schemas import (
|
||||||
Build,
|
Build,
|
||||||
BuildParts,
|
BuildParts,
|
||||||
|
ApiEnumDropdown,
|
||||||
|
BuildTypes,
|
||||||
|
BuildParts,
|
||||||
AccountRecords,
|
AccountRecords,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -55,17 +58,43 @@ SuperPartsDeleteEvent = Event(
|
||||||
|
|
||||||
|
|
||||||
def super_parts_list_callable(list_options: PaginateOnly, headers: CommonHeaders):
|
def super_parts_list_callable(list_options: PaginateOnly, headers: CommonHeaders):
|
||||||
return {
|
list_options = PaginateOnly(**list_options.model_dump())
|
||||||
"message": "MSG0003-LIST",
|
# TODO: Pydantic Model must be implemnted for list_options.query
|
||||||
"data": None,
|
with Build.new_session() as db_session:
|
||||||
"completed": True,
|
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
|
SuperPartsListEvent.event_callable = super_parts_list_callable
|
||||||
|
|
||||||
|
|
||||||
def super_parts_create_callable(data, headers: CommonHeaders):
|
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 {
|
return {
|
||||||
"message": "MSG0001-INSERT",
|
"message": "MSG0001-INSERT",
|
||||||
"data": None,
|
"data": None,
|
||||||
|
|
@ -77,6 +106,25 @@ SuperPartsCreateEvent.event_callable = super_parts_create_callable
|
||||||
|
|
||||||
|
|
||||||
def super_parts_update_callable(data, headers: CommonHeaders):
|
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 {
|
return {
|
||||||
"message": "MSG0002-UPDATE",
|
"message": "MSG0002-UPDATE",
|
||||||
"data": None,
|
"data": None,
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,13 @@ from Validations.response import (
|
||||||
)
|
)
|
||||||
from Validations.defaults.validations import CommonHeaders
|
from Validations.defaults.validations import CommonHeaders
|
||||||
from Schemas import (
|
from Schemas import (
|
||||||
|
Addresses,
|
||||||
|
BuildTypes,
|
||||||
Build,
|
Build,
|
||||||
|
BuildSites,
|
||||||
BuildParts,
|
BuildParts,
|
||||||
AccountRecords,
|
Companies,
|
||||||
|
# AccountRecords,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -53,32 +57,16 @@ SuperBuildDeleteEvent = Event(
|
||||||
|
|
||||||
def super_build_list_callable(list_options: PaginateOnly, headers: CommonHeaders):
|
def super_build_list_callable(list_options: PaginateOnly, headers: CommonHeaders):
|
||||||
list_options = PaginateOnly(**list_options.model_dump())
|
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
|
# TODO: Pydantic Model must be implemnted for list_options.query
|
||||||
with AccountRecords.new_session() as db_session:
|
with Build.new_session() as db_session:
|
||||||
AccountRecords.set_session(db_session)
|
Build.set_session(db_session)
|
||||||
list_of_fields = [
|
base_query = Build.query.filter()
|
||||||
AccountRecords.iban,
|
build_records_query = base_query
|
||||||
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)
|
|
||||||
if list_options.query:
|
if list_options.query:
|
||||||
account_records_query = account_records_query.filter(*AccountRecords.convert(list_options.query))
|
build_records_query = Build.query.filter(*Build.convert(list_options.query))
|
||||||
|
pagination = Pagination(data=build_records_query, base_query=base_query)
|
||||||
pagination = Pagination(data=account_records_query)
|
|
||||||
pagination.change(**list_options.model_dump())
|
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
|
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):
|
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 {
|
return {
|
||||||
"message": "MSG0001-INSERT",
|
"message": "MSG0001-INSERT",
|
||||||
"data": None,
|
"data": None,
|
||||||
|
|
@ -96,7 +97,23 @@ def super_build_create_callable(data, headers: CommonHeaders):
|
||||||
SuperBuildCreateEvent.event_callable = super_build_create_callable
|
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 {
|
return {
|
||||||
"message": "MSG0002-UPDATE",
|
"message": "MSG0002-UPDATE",
|
||||||
"data": None,
|
"data": None,
|
||||||
|
|
@ -108,6 +125,10 @@ SuperBuildUpdateEvent.event_callable = super_build_update_callable
|
||||||
|
|
||||||
|
|
||||||
def super_build_delete_callable(uu_id: str, headers: CommonHeaders):
|
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 {
|
return {
|
||||||
"message": "MSG0003-DELETE",
|
"message": "MSG0003-DELETE",
|
||||||
"data": None,
|
"data": None,
|
||||||
|
|
|
||||||
|
|
@ -89,10 +89,15 @@ class TokenProvider:
|
||||||
"""
|
"""
|
||||||
Retrieve event code from the token object or list of token objects.
|
Retrieve event code from the token object or list of token objects.
|
||||||
"""
|
"""
|
||||||
if isinstance(token, EmployeeTokenObject):
|
if token.is_employee and token.selected_company:
|
||||||
if event_codes := token.selected_company.reachable_event_codes.get(endpoint_code, None):
|
employee_uu_id = token.selected_company.get("uu_id", None)
|
||||||
return event_codes
|
print("endpoint_code", endpoint_code)
|
||||||
elif isinstance(token, OccupantTokenObject):
|
print("employee_uu_id", employee_uu_id)
|
||||||
if event_codes := token.selected_occupant.reachable_event_codes.get(endpoint_code, None):
|
if reachable_event_codes_dict := token.reachable_event_codes:
|
||||||
return 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.")
|
raise ValueError("Invalid token type or no event code found.")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { createBuilding } from "@/fetchers/custom/backend/builds/fetch";
|
||||||
|
|
||||||
|
export async function POST(req: Request): Promise<NextResponse> {
|
||||||
|
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 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { getBuildingsList } from "@/fetchers/custom/backend/builds/fetch";
|
||||||
|
|
||||||
|
export async function POST(req: Request): Promise<NextResponse> {
|
||||||
|
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 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<NextResponse> {
|
||||||
|
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 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -34,10 +34,6 @@ export async function GET(request: Request) {
|
||||||
|
|
||||||
const { paginatedData, meta } = getPaginatedData(page, size);
|
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 paginated response with the structure that useTableData expects
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -76,10 +72,6 @@ export async function POST(request: Request) {
|
||||||
|
|
||||||
const { paginatedData, meta } = getPaginatedData(page, size);
|
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 paginated response with the structure that useTableData expects
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,6 @@ export async function PATCH(request: Request) {
|
||||||
};
|
};
|
||||||
|
|
||||||
globalData[itemIndex] = updatedItem;
|
globalData[itemIndex] = updatedItem;
|
||||||
|
|
||||||
console.log(`PATCH /api/test/update - Updated item with id: ${updatedItem.id}`);
|
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
status: 200,
|
status: 200,
|
||||||
data: updatedItem,
|
data: updatedItem,
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,6 @@ const DataTable: React.FC<DataTableProps> = React.memo(({
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<tr key={headerGroup.id}>
|
<tr key={headerGroup.id}>
|
||||||
{headerGroup.headers.map((header) => {
|
{headerGroup.headers.map((header) => {
|
||||||
console.log('Rendering header:', header.id);
|
|
||||||
return (
|
return (
|
||||||
<th
|
<th
|
||||||
key={header.id}
|
key={header.id}
|
||||||
|
|
@ -156,7 +155,6 @@ const DataTable: React.FC<DataTableProps> = React.memo(({
|
||||||
table.getRowModel().rows.map((row) => (
|
table.getRowModel().rows.map((row) => (
|
||||||
<tr key={row.id} className="hover:bg-gray-50">
|
<tr key={row.id} className="hover:bg-gray-50">
|
||||||
{row.getVisibleCells().map((cell) => {
|
{row.getVisibleCells().map((cell) => {
|
||||||
console.log('Rendering cell:', cell.column.id);
|
|
||||||
return (
|
return (
|
||||||
<td key={cell.id} className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
<td key={cell.id} className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
|
@ -371,14 +369,12 @@ const TableCardComponentImproved: React.FC<DashboardPageProps> = React.memo((pro
|
||||||
renderPageOptions,
|
renderPageOptions,
|
||||||
} = useTableData({ apiUrl: `${API_BASE_URL}/test/list` });
|
} = useTableData({ apiUrl: `${API_BASE_URL}/test/list` });
|
||||||
const activePageUrl = props.activePageUrl || '';
|
const activePageUrl = props.activePageUrl || '';
|
||||||
console.log("activePageUrl", activePageUrl);
|
|
||||||
|
|
||||||
// Function to handle editing a row
|
// Function to handle editing a row
|
||||||
const handleEditRow = async (row: TableDataItem) => {
|
const handleEditRow = async (row: TableDataItem) => {
|
||||||
try {
|
try {
|
||||||
// Store the row data in the cache
|
// Store the row data in the cache
|
||||||
await setCacheData(`${activePageUrl}/update`, row);
|
await setCacheData(`${activePageUrl}/update`, row);
|
||||||
console.log('Row data stored in cache:', row);
|
|
||||||
|
|
||||||
// Navigate to the update form
|
// Navigate to the update form
|
||||||
router.push(`/panel/${activePageUrl}/update`);
|
router.push(`/panel/${activePageUrl}/update`);
|
||||||
|
|
@ -387,15 +383,7 @@ const TableCardComponentImproved: React.FC<DashboardPageProps> = React.memo((pro
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<TableDataItem>();
|
const actionButtonGroup = (info: any) => (
|
||||||
// Make sure columns are properly defined with the actions column
|
|
||||||
const columns = React.useMemo(() => [
|
|
||||||
columnHelper.display({
|
|
||||||
id: 'actions',
|
|
||||||
header: () => <span>Actions</span>,
|
|
||||||
cell: (info) => {
|
|
||||||
console.log('Rendering action cell for row:', info.row.id);
|
|
||||||
return (
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|
@ -410,7 +398,13 @@ const TableCardComponentImproved: React.FC<DashboardPageProps> = React.memo((pro
|
||||||
<span className="sr-only">Edit</span>
|
<span className="sr-only">Edit</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
const columnHelper = createColumnHelper<TableDataItem>();
|
||||||
|
const columns = React.useMemo(() => [
|
||||||
|
columnHelper.display({
|
||||||
|
id: 'actions',
|
||||||
|
header: () => <span>Actions</span>,
|
||||||
|
cell: (info) => { return (actionButtonGroup(info)) }
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('name', {
|
columnHelper.accessor('name', {
|
||||||
cell: info => info.getValue(),
|
cell: info => info.getValue(),
|
||||||
|
|
@ -465,12 +459,8 @@ const TableCardComponentImproved: React.FC<DashboardPageProps> = React.memo((pro
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
manualPagination: true,
|
manualPagination: true,
|
||||||
pageCount: apiPagination.totalPages || 1,
|
pageCount: apiPagination.totalPages || 1,
|
||||||
debugTable: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if actions column is included
|
|
||||||
const actionColumnExists = table.getVisibleLeafColumns().some(col => col.id === 'actions');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isLoading ? <LoadingContent height="h-48" size="w-36 h-36" plane="h-full w-full" /> :
|
isLoading ? <LoadingContent height="h-48" size="w-36 h-36" plane="h-full w-full" /> :
|
||||||
<div className="bg-white rounded-lg shadow-md overflow-hidden mb-20">
|
<div className="bg-white rounded-lg shadow-md overflow-hidden mb-20">
|
||||||
|
|
|
||||||
|
|
@ -72,16 +72,9 @@ function CreateFromComponentBase({
|
||||||
const fetchCacheDirectly = async () => {
|
const fetchCacheDirectly = async () => {
|
||||||
if (activePageUrl && !cacheLoaded) {
|
if (activePageUrl && !cacheLoaded) {
|
||||||
try {
|
try {
|
||||||
console.log("Directly fetching cache for URL:", activePageUrl);
|
|
||||||
|
|
||||||
// Directly fetch cache data using the imported function
|
|
||||||
const cachedData = await getCacheData(activePageUrl);
|
const cachedData = await getCacheData(activePageUrl);
|
||||||
console.log("Directly fetched cache data:", cachedData);
|
|
||||||
|
|
||||||
if (cachedData) {
|
if (cachedData) {
|
||||||
const formValues = form.getValues();
|
const formValues = form.getValues();
|
||||||
|
|
||||||
// Create a merged data object with proper typing
|
|
||||||
const mergedData: FormValues = {
|
const mergedData: FormValues = {
|
||||||
name: cachedData.name || formValues.name || "",
|
name: cachedData.name || formValues.name || "",
|
||||||
email: cachedData.email || formValues.email || "",
|
email: cachedData.email || formValues.email || "",
|
||||||
|
|
@ -92,20 +85,12 @@ function CreateFromComponentBase({
|
||||||
terms: cachedData.terms ?? formValues.terms ?? false,
|
terms: cachedData.terms ?? formValues.terms ?? false,
|
||||||
attachments: cachedData.attachments || formValues.attachments || ""
|
attachments: cachedData.attachments || formValues.attachments || ""
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("Setting form with direct cache data:", mergedData);
|
|
||||||
form.reset(mergedData);
|
form.reset(mergedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
setCacheLoaded(true);
|
setCacheLoaded(true);
|
||||||
|
if (refreshCache) { refreshCache(activePageUrl) }
|
||||||
// Also call the context refresh if available (for state consistency)
|
} 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
|
// Only update if the value is not empty
|
||||||
if (value && activePageUrl) {
|
if (value && activePageUrl) {
|
||||||
try {
|
try {
|
||||||
// Get current form values
|
|
||||||
const currentValues = form.getValues();
|
const currentValues = form.getValues();
|
||||||
|
|
||||||
// Prepare updated data
|
|
||||||
const updatedData = {
|
const updatedData = {
|
||||||
...currentValues,
|
...currentValues,
|
||||||
[fieldName]: value
|
[fieldName]: value
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("Directly updating cache with:", updatedData);
|
|
||||||
|
|
||||||
// Directly update cache using imported function
|
|
||||||
await setCacheData(activePageUrl, updatedData);
|
await setCacheData(activePageUrl, updatedData);
|
||||||
|
if (updateCache) { updateCache(activePageUrl, updatedData) }
|
||||||
// Also use the context method if available (for state consistency)
|
|
||||||
if (updateCache) {
|
|
||||||
updateCache(activePageUrl, updatedData);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating cache:", error);
|
console.error("Error updating cache:", error);
|
||||||
}
|
}
|
||||||
|
|
@ -143,14 +117,8 @@ function CreateFromComponentBase({
|
||||||
|
|
||||||
// Type-safe submit handler
|
// Type-safe submit handler
|
||||||
const onSubmit = async (data: FormValues) => {
|
const onSubmit = async (data: FormValues) => {
|
||||||
console.log("Form submitted with data:", data);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Submit form data to API
|
|
||||||
const response = await apiPostFetcher<any>({ url: "/tst/create", isNoCache: true, body: data });
|
const response = await apiPostFetcher<any>({ url: "/tst/create", isNoCache: true, body: data });
|
||||||
console.log("API response:", response);
|
|
||||||
|
|
||||||
// Clear cache on successful submission
|
|
||||||
if (activePageUrl) {
|
if (activePageUrl) {
|
||||||
try {
|
try {
|
||||||
console.log("Directly clearing cache for URL:", activePageUrl);
|
console.log("Directly clearing cache for URL:", activePageUrl);
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ const zodSchema = z.object({
|
||||||
// Define the form type
|
// Define the form type
|
||||||
type FormValues = z.infer<typeof zodSchema>;
|
type FormValues = z.infer<typeof zodSchema>;
|
||||||
|
|
||||||
function UpdateFromComponentBase({
|
function UpdateFromBuildComponentBase({
|
||||||
cacheData,
|
cacheData,
|
||||||
cacheLoading,
|
cacheLoading,
|
||||||
cacheError,
|
cacheError,
|
||||||
|
|
@ -72,11 +72,9 @@ function UpdateFromComponentBase({
|
||||||
const fetchCacheDirectly = async () => {
|
const fetchCacheDirectly = async () => {
|
||||||
if (activePageUrl && !cacheLoaded) {
|
if (activePageUrl && !cacheLoaded) {
|
||||||
try {
|
try {
|
||||||
console.log("Directly fetching cache for URL:", activePageUrl);
|
|
||||||
|
|
||||||
// Directly fetch cache data using the imported function
|
// Directly fetch cache data using the imported function
|
||||||
const cachedData = await getCacheData(activePageUrl);
|
const cachedData = await getCacheData(activePageUrl);
|
||||||
console.log("Directly fetched cache data:", cachedData);
|
|
||||||
|
|
||||||
if (cachedData) {
|
if (cachedData) {
|
||||||
const formValues = form.getValues();
|
const formValues = form.getValues();
|
||||||
|
|
@ -90,12 +88,6 @@ function UpdateFromComponentBase({
|
||||||
const termsValue = typeof cachedData.terms === 'string'
|
const termsValue = typeof cachedData.terms === 'string'
|
||||||
? cachedData.terms === 'true'
|
? cachedData.terms === 'true'
|
||||||
: Boolean(cachedData.terms);
|
: 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 = {
|
const mergedData: FormValues = {
|
||||||
name: cachedData.name || formValues.name || "",
|
name: cachedData.name || formValues.name || "",
|
||||||
email: cachedData.email || formValues.email || "",
|
email: cachedData.email || formValues.email || "",
|
||||||
|
|
@ -107,13 +99,9 @@ function UpdateFromComponentBase({
|
||||||
attachments: cachedData.attachments || formValues.attachments || ""
|
attachments: cachedData.attachments || formValues.attachments || ""
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("Setting form with direct cache data:", mergedData);
|
|
||||||
form.reset(mergedData);
|
form.reset(mergedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
setCacheLoaded(true);
|
setCacheLoaded(true);
|
||||||
|
|
||||||
// Also call the context refresh if available (for state consistency)
|
|
||||||
if (refreshCache) {
|
if (refreshCache) {
|
||||||
refreshCache(activePageUrl);
|
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
|
// Add default export to maintain compatibility with existing imports
|
||||||
export default withCache(UpdateFromComponentBase);
|
export default withCache(UpdateFromBuildComponentBase);
|
||||||
|
|
|
||||||
|
|
@ -75,14 +75,9 @@ const useContextCache = createContextHook<CacheData>({
|
||||||
contextName: "cache",
|
contextName: "cache",
|
||||||
enablePeriodicRefresh: false,
|
enablePeriodicRefresh: false,
|
||||||
customFetch: async () => {
|
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 {};
|
return {};
|
||||||
},
|
},
|
||||||
customUpdate: async (newData: CacheData) => {
|
customUpdate: async (newData: CacheData) => {
|
||||||
// This won't be used directly, as we'll provide custom methods
|
|
||||||
console.log("Cache update with:", newData);
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
defaultValue: {}
|
defaultValue: {}
|
||||||
|
|
@ -112,22 +107,14 @@ export function useCache(): UseCacheResult {
|
||||||
|
|
||||||
// Normalize URL to handle encoding issues
|
// Normalize URL to handle encoding issues
|
||||||
const normalizedUrl = url.startsWith('/') ? url : `/${url}`;
|
const normalizedUrl = url.startsWith('/') ? url : `/${url}`;
|
||||||
console.log("Fetching cache for normalized URL:", normalizedUrl);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const urlData = await getCacheData(normalizedUrl);
|
const urlData = await getCacheData(normalizedUrl);
|
||||||
console.log("Received cache data:", urlData);
|
|
||||||
|
|
||||||
// Update the local state with the fetched data
|
|
||||||
const updatedData = {
|
const updatedData = {
|
||||||
...data,
|
...data,
|
||||||
[normalizedUrl]: urlData
|
[normalizedUrl]: urlData
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("Updating cache state with:", updatedData);
|
|
||||||
await update(updatedData);
|
await update(updatedData);
|
||||||
|
|
||||||
// Force a refresh to ensure the component gets the updated data
|
|
||||||
await refresh();
|
await refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error refreshing cache:", error);
|
console.error("Error refreshing cache:", error);
|
||||||
|
|
@ -138,14 +125,9 @@ export function useCache(): UseCacheResult {
|
||||||
const updateCache = async (url: string, urlData: any): Promise<void> => {
|
const updateCache = async (url: string, urlData: any): Promise<void> => {
|
||||||
// Normalize URL to handle encoding issues
|
// Normalize URL to handle encoding issues
|
||||||
const normalizedUrl = url.startsWith('/') ? url : `/${url}`;
|
const normalizedUrl = url.startsWith('/') ? url : `/${url}`;
|
||||||
console.log("Updating cache for normalized URL:", normalizedUrl);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const success = await setCacheData(normalizedUrl, urlData);
|
const success = await setCacheData(normalizedUrl, urlData);
|
||||||
console.log("Cache update success:", success);
|
|
||||||
|
|
||||||
if (success && data) {
|
if (success && data) {
|
||||||
// Update local state
|
|
||||||
await update({
|
await update({
|
||||||
...data,
|
...data,
|
||||||
[normalizedUrl]: urlData
|
[normalizedUrl]: urlData
|
||||||
|
|
@ -162,12 +144,8 @@ export function useCache(): UseCacheResult {
|
||||||
const clearCache = async (url: string): Promise<void> => {
|
const clearCache = async (url: string): Promise<void> => {
|
||||||
// Normalize URL to handle encoding issues
|
// Normalize URL to handle encoding issues
|
||||||
const normalizedUrl = url.startsWith('/') ? url : `/${url}`;
|
const normalizedUrl = url.startsWith('/') ? url : `/${url}`;
|
||||||
console.log("Clearing cache for normalized URL:", normalizedUrl);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const success = await clearCacheData(normalizedUrl);
|
const success = await clearCacheData(normalizedUrl);
|
||||||
console.log("Cache clear success:", success);
|
|
||||||
|
|
||||||
if (success && data) {
|
if (success && data) {
|
||||||
// Update local state
|
// Update local state
|
||||||
const newData = { ...data };
|
const newData = { ...data };
|
||||||
|
|
|
||||||
|
|
@ -99,17 +99,11 @@ async function setContextPageOnline(
|
||||||
body: JSON.stringify(setOnline),
|
body: JSON.stringify(setOnline),
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
console.log("result", await result.json());
|
|
||||||
|
|
||||||
// Clear the timeout
|
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
throw new Error(`HTTP error! Status: ${result.status}`);
|
throw new Error(`HTTP error! Status: ${result.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await result.json();
|
const data = await result.json();
|
||||||
|
|
||||||
if (data.status === 200 && data.data) {
|
if (data.status === 200 && data.data) {
|
||||||
return data.data;
|
return data.data;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -66,14 +66,12 @@ export const OnlineProvider: FC<OnlineProviderProps> = ({ children }) => {
|
||||||
const fetchOnline = useCallback(async (force = false) => {
|
const fetchOnline = useCallback(async (force = false) => {
|
||||||
// Don't fetch if we already have data and it's not forced
|
// Don't fetch if we already have data and it's not forced
|
||||||
if (online && !force && !error) {
|
if (online && !force && !error) {
|
||||||
console.log("Using existing online state:", online);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't retry too frequently
|
// Don't retry too frequently
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (!force && now - lastRetryTime < MIN_RETRY_INTERVAL) {
|
if (!force && now - lastRetryTime < MIN_RETRY_INTERVAL) {
|
||||||
console.log("Retry attempted too soon, skipping");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,35 +80,29 @@ export const OnlineProvider: FC<OnlineProviderProps> = ({ children }) => {
|
||||||
setLastRetryTime(now);
|
setLastRetryTime(now);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log("Fetching online state...");
|
|
||||||
const data = await checkContextPageOnline();
|
const data = await checkContextPageOnline();
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
console.log("Successfully fetched online state:", data);
|
|
||||||
setOnline(data);
|
setOnline(data);
|
||||||
setRetryCount(0); // Reset retry count on success
|
setRetryCount(0); // Reset retry count on success
|
||||||
} else {
|
} else {
|
||||||
console.warn("No online state returned, using default");
|
|
||||||
setOnline(DEFAULT_ONLINE_STATE);
|
setOnline(DEFAULT_ONLINE_STATE);
|
||||||
setError("Could not retrieve online state, using default values");
|
setError("Could not retrieve online state, using default values");
|
||||||
|
|
||||||
// Auto-retry if under the limit
|
// Auto-retry if under the limit
|
||||||
if (retryCount < MAX_AUTO_RETRIES) {
|
if (retryCount < MAX_AUTO_RETRIES) {
|
||||||
setRetryCount(prev => prev + 1);
|
setRetryCount(prev => prev + 1);
|
||||||
console.log(`Scheduling retry ${retryCount + 1}/${MAX_AUTO_RETRIES}...`);
|
|
||||||
setTimeout(() => fetchOnline(true), MIN_RETRY_INTERVAL);
|
setTimeout(() => fetchOnline(true), MIN_RETRY_INTERVAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||||
console.error("Error fetching online state:", errorMessage);
|
|
||||||
setError(`Failed to fetch online state: ${errorMessage}`);
|
setError(`Failed to fetch online state: ${errorMessage}`);
|
||||||
setOnline(DEFAULT_ONLINE_STATE); // Use default as fallback
|
setOnline(DEFAULT_ONLINE_STATE); // Use default as fallback
|
||||||
|
|
||||||
// Auto-retry if under the limit
|
// Auto-retry if under the limit
|
||||||
if (retryCount < MAX_AUTO_RETRIES) {
|
if (retryCount < MAX_AUTO_RETRIES) {
|
||||||
setRetryCount(prev => prev + 1);
|
setRetryCount(prev => prev + 1);
|
||||||
console.log(`Scheduling retry ${retryCount + 1}/${MAX_AUTO_RETRIES}...`);
|
|
||||||
setTimeout(() => fetchOnline(true), MIN_RETRY_INTERVAL);
|
setTimeout(() => fetchOnline(true), MIN_RETRY_INTERVAL);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -120,26 +112,20 @@ export const OnlineProvider: FC<OnlineProviderProps> = ({ children }) => {
|
||||||
|
|
||||||
// Manual retry function that can be called from components
|
// Manual retry function that can be called from components
|
||||||
const retryFetch = useCallback(async () => {
|
const retryFetch = useCallback(async () => {
|
||||||
console.log("Manual retry requested");
|
|
||||||
setRetryCount(0); // Reset retry count for manual retry
|
setRetryCount(0); // Reset retry count for manual retry
|
||||||
await fetchOnline(true);
|
await fetchOnline(true);
|
||||||
}, [fetchOnline]);
|
}, [fetchOnline]);
|
||||||
|
|
||||||
// Fetch online state on component mount
|
// Fetch online state on component mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("OnlineProvider mounted, fetching initial data");
|
|
||||||
|
|
||||||
// Always fetch data on mount
|
|
||||||
fetchOnline();
|
fetchOnline();
|
||||||
|
|
||||||
// Set up periodic refresh (every 5 minutes)
|
// Set up periodic refresh (every 5 minutes)
|
||||||
const refreshInterval = setInterval(() => {
|
const refreshInterval = setInterval(() => {
|
||||||
console.log("Performing periodic refresh of online state");
|
|
||||||
fetchOnline(true);
|
fetchOnline(true);
|
||||||
}, 5 * 60 * 1000);
|
}, 5 * 60 * 1000);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
console.log("OnlineProvider unmounted, clearing interval");
|
|
||||||
clearInterval(refreshInterval);
|
clearInterval(refreshInterval);
|
||||||
};
|
};
|
||||||
}, [fetchOnline]);
|
}, [fetchOnline]);
|
||||||
|
|
@ -150,42 +136,17 @@ export const OnlineProvider: FC<OnlineProviderProps> = ({ children }) => {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log("Updating online state:", newOnline);
|
|
||||||
// Update Redis first
|
|
||||||
console.log('Updating Redis...');
|
|
||||||
await setOnlineToRedis(newOnline);
|
await setOnlineToRedis(newOnline);
|
||||||
|
|
||||||
// Then update context API
|
|
||||||
console.log('Updating context API...');
|
|
||||||
await setContextPageOnline(newOnline);
|
await setContextPageOnline(newOnline);
|
||||||
|
|
||||||
// Finally update local state to trigger re-renders
|
|
||||||
console.log('Updating local state...');
|
|
||||||
setOnline(newOnline);
|
setOnline(newOnline);
|
||||||
|
|
||||||
console.log('Online state updated successfully');
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} 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);
|
setOnline(newOnline);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// Add debug logging for provider state
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('OnlineProvider state updated:', {
|
|
||||||
online: online ? 'present' : 'not present',
|
|
||||||
isLoading
|
|
||||||
});
|
|
||||||
}, [online, isLoading]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OnlineContext.Provider value={{ online, updateOnline, isLoading, error, retryFetch }}>
|
<OnlineContext.Provider value={{ online, updateOnline, isLoading, error, retryFetch }}>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -34,14 +34,9 @@ const DEFAULT_USER_STATE: ClientUser = {
|
||||||
// Client-side fetch function that uses the server-side implementation
|
// Client-side fetch function that uses the server-side implementation
|
||||||
async function checkContextDashUserInfo(): Promise<ClientUser> {
|
async function checkContextDashUserInfo(): Promise<ClientUser> {
|
||||||
try {
|
try {
|
||||||
console.log("Fetching user data using server-side function");
|
|
||||||
|
|
||||||
// First try to use the server-side implementation
|
|
||||||
try {
|
try {
|
||||||
const serverData = await getUserFromServer();
|
const serverData = await getUserFromServer();
|
||||||
// If we got valid data from the server, return it
|
|
||||||
if (serverData && serverData.uuid) {
|
if (serverData && serverData.uuid) {
|
||||||
// Check if we have a real user (not the default)
|
|
||||||
if (
|
if (
|
||||||
serverData.uuid !== "default-user-id" ||
|
serverData.uuid !== "default-user-id" ||
|
||||||
serverData.email ||
|
serverData.email ||
|
||||||
|
|
@ -49,30 +44,12 @@ async function checkContextDashUserInfo(): Promise<ClientUser> {
|
||||||
(serverData.person.firstname !== "Guest" ||
|
(serverData.person.firstname !== "Guest" ||
|
||||||
serverData.person.surname !== "User"))
|
serverData.person.surname !== "User"))
|
||||||
) {
|
) {
|
||||||
console.log("Valid user data found from server");
|
|
||||||
return serverData;
|
return serverData;
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
"Default user data returned from server, falling back to client-side"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} 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 controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
|
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
|
||||||
|
|
||||||
|
|
@ -94,22 +71,14 @@ async function checkContextDashUserInfo(): Promise<ClientUser> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await result.json();
|
const data = await result.json();
|
||||||
console.log("User data API response:", data);
|
|
||||||
|
|
||||||
// Handle different response formats
|
|
||||||
if (data.status === 200 && data.data) {
|
if (data.status === 200 && data.data) {
|
||||||
// Standard API response format
|
|
||||||
return data.data;
|
return data.data;
|
||||||
} else if (data.user) {
|
} else if (data.user) {
|
||||||
// Direct Redis object format
|
|
||||||
console.log("Found user data in Redis format");
|
|
||||||
return data.user;
|
return data.user;
|
||||||
} else if (data.uuid) {
|
} else if (data.uuid) {
|
||||||
// Direct user object format
|
|
||||||
console.log("Found direct user data format");
|
|
||||||
return data;
|
return data;
|
||||||
} else {
|
} else {
|
||||||
console.warn("Invalid response format from user API");
|
|
||||||
return DEFAULT_USER_STATE;
|
return DEFAULT_USER_STATE;
|
||||||
}
|
}
|
||||||
} catch (fetchError) {
|
} catch (fetchError) {
|
||||||
|
|
@ -129,11 +98,6 @@ async function checkContextDashUserInfo(): Promise<ClientUser> {
|
||||||
throw fetchError;
|
throw fetchError;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Handle all other errors
|
|
||||||
console.error(
|
|
||||||
"Error fetching user data:",
|
|
||||||
error instanceof Error ? error.message : "Unknown error"
|
|
||||||
);
|
|
||||||
return DEFAULT_USER_STATE;
|
return DEFAULT_USER_STATE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -144,29 +108,12 @@ async function setContextDashUserInfo({
|
||||||
userSet: ClientUser;
|
userSet: ClientUser;
|
||||||
}): Promise<boolean> {
|
}): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
console.log("Setting user data using server-side function");
|
|
||||||
|
|
||||||
// First try to use the server-side implementation
|
|
||||||
try {
|
try {
|
||||||
const success = await setUserFromServer(userSet);
|
const success = await setUserFromServer(userSet);
|
||||||
if (success) {
|
if (success) {
|
||||||
console.log(
|
|
||||||
"Successfully updated user data using server-side function"
|
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (serverError) {
|
} 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`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create an AbortController to handle timeouts
|
// Create an AbortController to handle timeouts
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|
@ -182,28 +129,18 @@ async function setContextDashUserInfo({
|
||||||
body: JSON.stringify(userSet),
|
body: JSON.stringify(userSet),
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear the timeout
|
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
throw new Error(`HTTP error! Status: ${result.status}`);
|
throw new Error(`HTTP error! Status: ${result.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await result.json();
|
const data = await result.json();
|
||||||
console.log("Update user data API response:", data);
|
|
||||||
|
|
||||||
return data.status === 200;
|
return data.status === 200;
|
||||||
} catch (fetchError) {
|
} catch (fetchError) {
|
||||||
// Clear the timeout if it hasn't fired yet
|
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
// Check if this is an abort error (timeout)
|
|
||||||
if (
|
if (
|
||||||
fetchError instanceof DOMException &&
|
fetchError instanceof DOMException &&
|
||||||
fetchError.name === "AbortError"
|
fetchError.name === "AbortError"
|
||||||
) {
|
) {
|
||||||
console.warn("Request timed out or was aborted");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,10 +148,6 @@ async function setContextDashUserInfo({
|
||||||
throw fetchError;
|
throw fetchError;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
|
||||||
"Error setting user data:",
|
|
||||||
error instanceof Error ? error.message : "Unknown error"
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<any>;
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
onBlurCallback?: (fieldName: any, value: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CheckBoxInput({
|
||||||
|
control,
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
onBlurCallback,
|
||||||
|
}: CheckBoxInputProps) {
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
control={control}
|
||||||
|
name={name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md p-4 border">
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
field.onChange(checked);
|
||||||
|
if (onBlurCallback) {
|
||||||
|
onBlurCallback(name, checked);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="space-y-1 leading-none">
|
||||||
|
<FormLabel>{label}</FormLabel>
|
||||||
|
</div>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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<any>;
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
placeholder?: string;
|
||||||
|
onBlurCallback?: (fieldName: any, value: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DatetimeInput({
|
||||||
|
control,
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
onBlurCallback,
|
||||||
|
}: DatetimeInputProps) {
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
control={control}
|
||||||
|
name={name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{label}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
placeholder={placeholder || label}
|
||||||
|
{...field}
|
||||||
|
value={field.value ? field.value.substring(0, 10) : ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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<any>;
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
placeholder?: string;
|
||||||
|
onBlurCallback?: (fieldName: any, value: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NumberInput({
|
||||||
|
control,
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
onBlurCallback,
|
||||||
|
}: NumberInputProps) {
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
control={control}
|
||||||
|
name={name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{label}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder={placeholder || label}
|
||||||
|
{...field}
|
||||||
|
value={field.value === 0 || field.value === null ? "" : field.value}
|
||||||
|
onChange={(e) => {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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<any>;
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
placeholder?: string;
|
||||||
|
onBlurCallback?: (fieldName: any, value: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StringInput({
|
||||||
|
control,
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
onBlurCallback,
|
||||||
|
}: StringInputProps) {
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
control={control}
|
||||||
|
name={name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{label}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder={placeholder || label}
|
||||||
|
{...field}
|
||||||
|
value={field.value ?? ""}
|
||||||
|
onBlur={(e) => {
|
||||||
|
field.onBlur();
|
||||||
|
if (onBlurCallback) {
|
||||||
|
onBlurCallback(name, e.target.value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -8,11 +8,6 @@ interface ClientProvidersProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ClientProviders({ children }: ClientProvidersProps) {
|
export function ClientProviders({ children }: ClientProvidersProps) {
|
||||||
// Log provider initialization for debugging
|
|
||||||
React.useEffect(() => {
|
|
||||||
console.log('ClientProviders initialized');
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OnlineProvider>
|
<OnlineProvider>
|
||||||
<CacheProvider>
|
<CacheProvider>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -31,6 +31,14 @@ const urlLogoutEndpoint = `${baseUrlAuth}/authentication/logout`;
|
||||||
|
|
||||||
const urlTesterList = `${baseUrlTester}/tester/list`;
|
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 {
|
export {
|
||||||
urlCheckToken,
|
urlCheckToken,
|
||||||
urlPageValid,
|
urlPageValid,
|
||||||
|
|
@ -38,6 +46,12 @@ export {
|
||||||
urlLoginEndpoint,
|
urlLoginEndpoint,
|
||||||
urlLoginSelectEndpoint,
|
urlLoginSelectEndpoint,
|
||||||
urlLogoutEndpoint,
|
urlLogoutEndpoint,
|
||||||
|
urlBuildingsList,
|
||||||
|
urlBuildingsCreate,
|
||||||
|
urlBuildingsUpdate,
|
||||||
|
urlPartsList,
|
||||||
|
urlPartsCreate,
|
||||||
|
urlPartsUpdate,
|
||||||
// For test use only
|
// For test use only
|
||||||
urlTesterList,
|
urlTesterList,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,12 @@ interface CookieObject {
|
||||||
priority: string;
|
priority: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { HttpMethod, ApiResponse, FetchOptions, CookieObject };
|
interface Pagination {
|
||||||
|
page?: number;
|
||||||
|
size?: number;
|
||||||
|
orderField?: string[];
|
||||||
|
orderType?: string[];
|
||||||
|
query?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { HttpMethod, ApiResponse, FetchOptions, CookieObject, Pagination };
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,6 @@ export function useTableData({
|
||||||
next: false,
|
next: false,
|
||||||
back: false,
|
back: false,
|
||||||
});
|
});
|
||||||
console.log("apiPagination", apiPagination);
|
|
||||||
|
|
||||||
// Watch for form value changes
|
// Watch for form value changes
|
||||||
const page = form.watch(pageField as Path<TableFormValues>);
|
const page = form.watch(pageField as Path<TableFormValues>);
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ const menuTranslationEn = {
|
||||||
"/build": [
|
"/build": [
|
||||||
{ value: "Build", key: "build" },
|
{ value: "Build", key: "build" },
|
||||||
{ value: "Build", key: "build" },
|
{ value: "Build", key: "build" },
|
||||||
{ value: "Build", key: "build" },
|
{ value: "Buildings", key: "build" },
|
||||||
],
|
],
|
||||||
"/build/parts": [
|
"/build/parts": [
|
||||||
{ value: "Build", key: "build" },
|
{ value: "Build", key: "build" },
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ const menuTranslationTr = {
|
||||||
"/build": [
|
"/build": [
|
||||||
{ value: "Bina", key: "build" },
|
{ value: "Bina", key: "build" },
|
||||||
{ value: "Bina", key: "build" },
|
{ value: "Bina", key: "build" },
|
||||||
{ value: "Bina", key: "build" },
|
{ value: "Binalar", key: "build" },
|
||||||
],
|
],
|
||||||
"/build/parts": [
|
"/build/parts": [
|
||||||
{ value: "Bina", key: "build" },
|
{ value: "Bina", key: "build" },
|
||||||
|
|
|
||||||
|
|
@ -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<void>;
|
||||||
|
updateCache?: (url: string, data: any) => Promise<void>;
|
||||||
|
clearCache?: (url: string) => Promise<void>;
|
||||||
|
activePageUrl: string;
|
||||||
|
}) {
|
||||||
|
const language: LanguageTypes = onlineData?.lang || 'en';
|
||||||
|
const router = useRouter();
|
||||||
|
const listUrl = `/panel/${activePageUrl?.replace("/create", "")}`;
|
||||||
|
const [cacheLoaded, setCacheLoaded] = useState<boolean>(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<any>({ 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 (
|
||||||
|
<div className="w-full max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-md">
|
||||||
|
{/* back to list button */}
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<Button onClick={() => router.push(listUrl)}>{translationsOfPage[language].back2List}</Button>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl font-bold mb-6">{translationsOfPage[language].title}</h2>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||||
|
{/* Address Government Code */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="address_gov_code"
|
||||||
|
label={buildPartsTranslations[language].address_gov_code}
|
||||||
|
placeholder={buildPartsTranslations[language].address_gov_code}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Part Number */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="part_no"
|
||||||
|
label={buildPartsTranslations[language].part_no}
|
||||||
|
placeholder={buildPartsTranslations[language].part_no}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Part Level */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="part_level"
|
||||||
|
label={buildPartsTranslations[language].part_level}
|
||||||
|
placeholder={buildPartsTranslations[language].part_level}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Part Code */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="part_code"
|
||||||
|
label={buildPartsTranslations[language].part_code}
|
||||||
|
placeholder={buildPartsTranslations[language].part_code}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Part Gross Size */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="part_gross_size"
|
||||||
|
label={buildPartsTranslations[language].part_gross_size}
|
||||||
|
placeholder={buildPartsTranslations[language].part_gross_size}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Part Net Size */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="part_net_size"
|
||||||
|
label={buildPartsTranslations[language].part_net_size}
|
||||||
|
placeholder={buildPartsTranslations[language].part_net_size}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Default Accessory */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="default_accessory"
|
||||||
|
label={buildPartsTranslations[language].default_accessory}
|
||||||
|
placeholder={buildPartsTranslations[language].default_accessory}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Human Livable */}
|
||||||
|
<CheckBoxInput
|
||||||
|
control={form.control}
|
||||||
|
name="human_livable"
|
||||||
|
label={buildPartsTranslations[language].human_livable}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Due Part Key */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="due_part_key"
|
||||||
|
label={buildPartsTranslations[language].due_part_key}
|
||||||
|
placeholder={buildPartsTranslations[language].due_part_key}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Part Direction UUID */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="part_direction_uu_id"
|
||||||
|
label={buildPartsTranslations[language].part_direction_uu_id}
|
||||||
|
placeholder={buildPartsTranslations[language].part_direction_uu_id}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button type="submit" className="w-full">{language === 'en' ? translationsOfPage.en.title : translationsOfPage.tr.title}</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CreateFromBuildComponent = withCache(CreateFromComponentBase);
|
||||||
|
export default withCache(CreateFromBuildComponent);
|
||||||
|
|
@ -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<typeof useReactTable>;
|
||||||
|
tableData: BuildPartsSchemaInterface[];
|
||||||
|
isLoading: boolean;
|
||||||
|
handleSortingChange: (columnId: string) => void;
|
||||||
|
getSortingIcon: (columnId: string) => React.ReactNode;
|
||||||
|
flexRender: typeof flexRender;
|
||||||
|
t: Translations;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TableFormProps {
|
||||||
|
form: UseFormReturn<any>;
|
||||||
|
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<LanguageTypes, Translations> = {
|
||||||
|
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<DataTableProps> = React.memo(({
|
||||||
|
table,
|
||||||
|
tableData,
|
||||||
|
isLoading,
|
||||||
|
handleSortingChange,
|
||||||
|
getSortingIcon,
|
||||||
|
flexRender,
|
||||||
|
t,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="overflow-x-auto relative">
|
||||||
|
{/* Semi-transparent loading overlay that preserves interactivity */}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="absolute inset-0 bg-white bg-opacity-60 flex items-center justify-center z-10">
|
||||||
|
{/* We don't put anything here as we already have the loading indicator in the header */}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
|
<caption className="sr-only">{t.dataTable}</caption>
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<tr key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header) => {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={header.id}
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
|
||||||
|
onClick={() => handleSortingChange(header.column.id)}
|
||||||
|
aria-sort={header.column.getIsSorted() ? (header.column.getIsSorted() === 'desc' ? 'descending' : 'ascending') : undefined}
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-1">
|
||||||
|
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
|
<span>{getSortingIcon(header.column.id)}</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
|
{tableData.length === 0 && !isLoading ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={table.getAllColumns().length} className="px-6 py-4 text-center text-gray-500">
|
||||||
|
{t.noDataAvailable}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<tr key={row.id} className="hover:bg-gray-50">
|
||||||
|
{row.getVisibleCells().map((cell) => {
|
||||||
|
return (
|
||||||
|
<td key={cell.id} className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const TableForm: React.FC<TableFormProps> = ({
|
||||||
|
form,
|
||||||
|
handleFormSubmit,
|
||||||
|
handleSelectChange,
|
||||||
|
renderPageOptions,
|
||||||
|
pageSizeOptions,
|
||||||
|
apiPagination,
|
||||||
|
handleFirstPage,
|
||||||
|
handlePreviousPage,
|
||||||
|
handleNextPage,
|
||||||
|
isPreviousDisabled,
|
||||||
|
isNextDisabled,
|
||||||
|
t
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="p-4 border-b border-gray-200">
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={handleFormSubmit} className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
|
<div className="md:col-span-1">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="page"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t.page}</FormLabel>
|
||||||
|
<Select
|
||||||
|
value={field.value.toString()}
|
||||||
|
onValueChange={(value: string) => handleSelectChange(value, field)}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t.selectPage} />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{renderPageOptions().length > 0 ? (
|
||||||
|
renderPageOptions().map((option: { key: string | number; value: string; label: string }) => (
|
||||||
|
<SelectItem key={option.key} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem key="1" value="1">1</SelectItem>
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="md:col-span-1">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="size"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t.size}</FormLabel>
|
||||||
|
<Select
|
||||||
|
value={field.value.toString()}
|
||||||
|
onValueChange={(value: string) => handleSelectChange(value, field)}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t.selectSize} />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{pageSizeOptions.map((size: number) => (
|
||||||
|
<SelectItem key={size} value={size.toString()}>
|
||||||
|
{size}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="md:col-span-1 flex items-end">
|
||||||
|
<p className="text-sm text-gray-700">
|
||||||
|
{t.page}: <span className="font-medium">{apiPagination.page}</span><span>{" / "}</span> <span className="font-medium">{apiPagination.totalPages}</span> ·
|
||||||
|
{t.size}: <span className="font-medium">{apiPagination.pageCount}</span><span>{" / "}</span> <span className="font-medium">{apiPagination.size}</span> ·
|
||||||
|
{t.total}: <span className="font-medium">{apiPagination.totalCount}</span> {t.items}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="md:col-span-1 flex items-end justify-end space-x-2">
|
||||||
|
<Button
|
||||||
|
onClick={handleFirstPage}
|
||||||
|
disabled={isPreviousDisabled()}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
aria-label="Go to first page"
|
||||||
|
>{t.first}</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handlePreviousPage}
|
||||||
|
disabled={isPreviousDisabled()}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
aria-label="Go to previous page"
|
||||||
|
>{t.previous}</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleNextPage}
|
||||||
|
disabled={isNextDisabled()}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
aria-label="Go to next page"
|
||||||
|
>{t.next}</Button>
|
||||||
|
{/* <Button type="submit" disabled={isLoading} size="sm">Fetch</Button> */}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MobilePaginationControls: React.FC<MobilePaginationControlsProps> = ({
|
||||||
|
handlePreviousPage,
|
||||||
|
handleNextPage,
|
||||||
|
isPreviousDisabled,
|
||||||
|
isNextDisabled,
|
||||||
|
t
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:hidden">
|
||||||
|
<div className="flex-1 flex justify-between">
|
||||||
|
<Button
|
||||||
|
onClick={handlePreviousPage}
|
||||||
|
disabled={isPreviousDisabled()}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
aria-label="Go to previous page"
|
||||||
|
>{t.previous}</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleNextPage}
|
||||||
|
disabled={isNextDisabled()}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="ml-2"
|
||||||
|
aria-label="Go to next page"
|
||||||
|
>{t.next}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TableHeader: React.FC<TableHeaderProps> = ({ title, description, isLoading, error, t }) => {
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div className="p-4 border-b border-gray-200">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold text-gray-800">{title}</h2>
|
||||||
|
<p className="text-sm text-gray-500">{description}</p>
|
||||||
|
</div>
|
||||||
|
{/* {isLoading && <LoadingContent height="h-16" size="w-36 h-48" plane="h-full w-full" />} */}
|
||||||
|
{error && <ErrorDisplay message={error} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ message }) => {
|
||||||
|
return <div className="text-red-500">{message}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BuildListPage: React.FC<DashboardPageProps> = 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) => (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log('Edit button clicked');
|
||||||
|
handleEditRow && handleEditRow(info.row.original);
|
||||||
|
}}
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
>
|
||||||
|
<Pencil className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Edit</span>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<BuildPartsSchemaInterface>();
|
||||||
|
const columns = React.useMemo(() => [
|
||||||
|
columnHelper.display({
|
||||||
|
id: 'actions',
|
||||||
|
header: () => <span>{t.actionButtonGroup}</span>,
|
||||||
|
cell: (info) => { return (actionButtonGroup(info)) }
|
||||||
|
}),
|
||||||
|
// columnHelper.accessor('uu_id', {
|
||||||
|
// cell: info => info.getValue(),
|
||||||
|
// header: () => <span>{buildPartsTranslations[language].uu_id}</span>,
|
||||||
|
// footer: info => info.column.id
|
||||||
|
// }),
|
||||||
|
// columnHelper.accessor('build_uu_id', {
|
||||||
|
// cell: info => info.getValue(),
|
||||||
|
// header: () => <span>{buildPartsTranslations[language].build_uu_id}</span>,
|
||||||
|
// footer: info => info.column.id
|
||||||
|
// }),
|
||||||
|
columnHelper.accessor('address_gov_code', {
|
||||||
|
cell: info => info.getValue(),
|
||||||
|
header: () => <span>{buildPartsTranslations[language].address_gov_code}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('part_no', {
|
||||||
|
cell: info => String(info.getValue()),
|
||||||
|
header: () => <span>{buildPartsTranslations[language].part_no}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('part_level', {
|
||||||
|
cell: info => String(info.getValue()),
|
||||||
|
header: () => <span>{buildPartsTranslations[language].part_level}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('part_code', {
|
||||||
|
cell: info => info.getValue(),
|
||||||
|
header: () => <span>{buildPartsTranslations[language].part_code}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('part_gross_size', {
|
||||||
|
cell: info => String(info.getValue()),
|
||||||
|
header: () => <span>{buildPartsTranslations[language].part_gross_size}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('part_net_size', {
|
||||||
|
cell: info => String(info.getValue()),
|
||||||
|
header: () => <span>{buildPartsTranslations[language].part_net_size}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('default_accessory', {
|
||||||
|
cell: info => info.getValue(),
|
||||||
|
header: () => <span>{buildPartsTranslations[language].default_accessory}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('human_livable', {
|
||||||
|
cell: info => info.getValue() ? 'Yes' : 'No',
|
||||||
|
header: () => <span>{buildPartsTranslations[language].human_livable}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('due_part_key', {
|
||||||
|
cell: info => info.getValue(),
|
||||||
|
header: () => <span>{buildPartsTranslations[language].due_part_key}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('part_direction_uu_id', {
|
||||||
|
cell: info => info.getValue() || '',
|
||||||
|
header: () => <span>{buildPartsTranslations[language].part_direction_uu_id}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
], [columnHelper]) as ColumnDef<BuildPartsSchemaInterface>[];
|
||||||
|
|
||||||
|
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 ? <LoadingContent height="h-48" size="w-36 h-36" plane="h-full w-full" /> :
|
||||||
|
<div className="bg-white rounded-lg shadow-md overflow-hidden mb-20">
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center p-4">
|
||||||
|
<TableHeader
|
||||||
|
title={t.dataTable}
|
||||||
|
description={t.tableWithApiData}
|
||||||
|
isLoading={isLoading}
|
||||||
|
error={error}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() => router.push(`/panel/${activePageUrl}/create`)}
|
||||||
|
className="bg-primary text-white hover:bg-primary/90"
|
||||||
|
>
|
||||||
|
<span className="mr-2">Create New</span>
|
||||||
|
<span className="sr-only">Create new item</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TableForm
|
||||||
|
form={form}
|
||||||
|
handleFormSubmit={handleFormSubmit}
|
||||||
|
handleSelectChange={handleSelectChange}
|
||||||
|
renderPageOptions={renderPageOptions}
|
||||||
|
pageSizeOptions={pageSizeOptions}
|
||||||
|
apiPagination={apiPagination}
|
||||||
|
handleFirstPage={handleFirstPage}
|
||||||
|
handlePreviousPage={handlePreviousPage}
|
||||||
|
handleNextPage={handleNextPage}
|
||||||
|
isPreviousDisabled={isPreviousDisabled}
|
||||||
|
isNextDisabled={isNextDisabled}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Mobile pagination controls - only visible on small screens */}
|
||||||
|
<MobilePaginationControls
|
||||||
|
handlePreviousPage={handlePreviousPage}
|
||||||
|
handleNextPage={handleNextPage}
|
||||||
|
isPreviousDisabled={isPreviousDisabled}
|
||||||
|
isNextDisabled={isNextDisabled}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
table={table}
|
||||||
|
tableData={tableData}
|
||||||
|
isLoading={isLoading}
|
||||||
|
handleSortingChange={handleSortingChange}
|
||||||
|
getSortingIcon={getSortingIcon}
|
||||||
|
flexRender={flexRender}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default BuildListPage;
|
||||||
|
|
@ -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<void>;
|
||||||
|
updateCache?: (url: string, data: any) => Promise<void>;
|
||||||
|
clearCache?: (url: string) => Promise<void>;
|
||||||
|
activePageUrl: string;
|
||||||
|
}) {
|
||||||
|
const language: LanguageTypes = onlineData?.lang || 'en';
|
||||||
|
const [cacheLoaded, setCacheLoaded] = useState<boolean>(false);
|
||||||
|
const [partUUID, setPartUUID] = useState<string>(""); 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<any>({
|
||||||
|
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 (
|
||||||
|
<div className="w-full max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-md">
|
||||||
|
{/* back to list button */}
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<Button onClick={() => router.push(listUrl)}>{translationsOfPage[language].back2List}</Button>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl font-bold mb-3">{translationsOfPage[language].title}</h2>
|
||||||
|
<h4 className="text-sm text-gray-500 mb-6">UUID: {partUUID}</h4>
|
||||||
|
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||||
|
{/* Address Government Code */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="address_gov_code"
|
||||||
|
label={buildPartsTranslations[language].address_gov_code}
|
||||||
|
placeholder={buildPartsTranslations[language].address_gov_code}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Part Number */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="part_no"
|
||||||
|
label={buildPartsTranslations[language].part_no}
|
||||||
|
placeholder={buildPartsTranslations[language].part_no}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Part Level */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="part_level"
|
||||||
|
label={buildPartsTranslations[language].part_level}
|
||||||
|
placeholder={buildPartsTranslations[language].part_level}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Part Code */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="part_code"
|
||||||
|
label={buildPartsTranslations[language].part_code}
|
||||||
|
placeholder={buildPartsTranslations[language].part_code}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Part Gross Size */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="part_gross_size"
|
||||||
|
label={buildPartsTranslations[language].part_gross_size}
|
||||||
|
placeholder={buildPartsTranslations[language].part_gross_size}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Part Net Size */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="part_net_size"
|
||||||
|
label={buildPartsTranslations[language].part_net_size}
|
||||||
|
placeholder={buildPartsTranslations[language].part_net_size}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Default Accessory */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="default_accessory"
|
||||||
|
label={buildPartsTranslations[language].default_accessory}
|
||||||
|
placeholder={buildPartsTranslations[language].default_accessory}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Human Livable */}
|
||||||
|
<CheckBoxInput
|
||||||
|
control={form.control}
|
||||||
|
name="human_livable"
|
||||||
|
label={buildPartsTranslations[language].human_livable}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Due Part Key */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="due_part_key"
|
||||||
|
label={buildPartsTranslations[language].due_part_key}
|
||||||
|
placeholder={buildPartsTranslations[language].due_part_key}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Part Direction UUID */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="part_direction_uu_id"
|
||||||
|
label={buildPartsTranslations[language].part_direction_uu_id}
|
||||||
|
placeholder={buildPartsTranslations[language].part_direction_uu_id}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button type="submit" className="w-full">{language === 'en' ? translationsOfPage.en.title : translationsOfPage.tr.title}</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UpdateFromComponent = withCache(UpdateFromComponentBase);
|
||||||
|
export default withCache(UpdateFromComponentBase);
|
||||||
|
|
@ -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<typeof buildPartsSchema>;
|
||||||
|
type BuildPartsCreateInterface = z.infer<typeof buildPartsSchemaCreate>;
|
||||||
|
|
||||||
|
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 };
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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<void>;
|
||||||
|
updateCache?: (url: string, data: any) => Promise<void>;
|
||||||
|
clearCache?: (url: string) => Promise<void>;
|
||||||
|
activePageUrl: string;
|
||||||
|
}) {
|
||||||
|
const language: LanguageTypes = onlineData?.lang || 'en';
|
||||||
|
const router = useRouter();
|
||||||
|
const listUrl = `/panel/${activePageUrl?.replace("/create", "")}`;
|
||||||
|
const [cacheLoaded, setCacheLoaded] = useState<boolean>(false);
|
||||||
|
const form = useForm<BuildCreateInterface>({ 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<any>({ 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 (
|
||||||
|
<div className="w-full max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-md">
|
||||||
|
{/* back to list button */}
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<Button onClick={() => router.push(listUrl)}>{translationsOfPage[language].back2List}</Button>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl font-bold mb-6">{translationsOfPage[language].title}</h2>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||||
|
{/* Government Address Code */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="gov_address_code"
|
||||||
|
label={buildTranslations[language].gov_address_code}
|
||||||
|
placeholder={buildTranslations[language].gov_address_code}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Building Name */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="build_name"
|
||||||
|
label={buildTranslations[language].build_name}
|
||||||
|
placeholder={buildTranslations[language].build_name}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Building Number */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="build_no"
|
||||||
|
label={buildTranslations[language].build_no}
|
||||||
|
placeholder={buildTranslations[language].build_no}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Max Floor */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="max_floor"
|
||||||
|
label={buildTranslations[language].max_floor}
|
||||||
|
placeholder={buildTranslations[language].max_floor}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Underground Floor */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="underground_floor"
|
||||||
|
label={buildTranslations[language].underground_floor}
|
||||||
|
placeholder={buildTranslations[language].underground_floor}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Build Date */}
|
||||||
|
<DatetimeInput
|
||||||
|
control={form.control}
|
||||||
|
name="build_date"
|
||||||
|
label={buildTranslations[language].build_date}
|
||||||
|
placeholder={buildTranslations[language].build_date}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Decision Period Date */}
|
||||||
|
<DatetimeInput
|
||||||
|
control={form.control}
|
||||||
|
name="decision_period_date"
|
||||||
|
label={buildTranslations[language].decision_period_date}
|
||||||
|
placeholder={buildTranslations[language].decision_period_date}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Tax Number */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="tax_no"
|
||||||
|
label={buildTranslations[language].tax_no}
|
||||||
|
placeholder={buildTranslations[language].tax_no}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Lift Count */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="lift_count"
|
||||||
|
label={buildTranslations[language].lift_count}
|
||||||
|
placeholder={buildTranslations[language].lift_count}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Heating System */}
|
||||||
|
<CheckBoxInput
|
||||||
|
control={form.control}
|
||||||
|
name="heating_system"
|
||||||
|
label={buildTranslations[language].heating_system}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Cooling System */}
|
||||||
|
<CheckBoxInput
|
||||||
|
control={form.control}
|
||||||
|
name="cooling_system"
|
||||||
|
label={buildTranslations[language].cooling_system}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Hot Water System */}
|
||||||
|
<CheckBoxInput
|
||||||
|
control={form.control}
|
||||||
|
name="hot_water_system"
|
||||||
|
label={buildTranslations[language].hot_water_system}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Block Service Man Count */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="block_service_man_count"
|
||||||
|
label={buildTranslations[language].block_service_man_count}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Security Service Man Count */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="security_service_man_count"
|
||||||
|
label={buildTranslations[language].security_service_man_count}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Garage Count */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="garage_count"
|
||||||
|
label={buildTranslations[language].garage_count}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Site UUID */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="site_uu_id"
|
||||||
|
label={buildTranslations[language].site_uu_id}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Address UUID */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="address_uu_id"
|
||||||
|
label={buildTranslations[language].address_uu_id}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Build Types UUID */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="build_types_uu_id"
|
||||||
|
label={buildTranslations[language].build_types_uu_id}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
<Button type="submit" className="w-full">{language === 'en' ? 'Create Building' : 'Bina Oluştur'}</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CreateFromBuildComponent = withCache(CreateFromComponentBase);
|
||||||
|
export default withCache(CreateFromBuildComponent);
|
||||||
|
|
@ -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<typeof useReactTable>;
|
||||||
|
tableData: BuildSchemaInterface[];
|
||||||
|
isLoading: boolean;
|
||||||
|
handleSortingChange: (columnId: string) => void;
|
||||||
|
getSortingIcon: (columnId: string) => React.ReactNode;
|
||||||
|
flexRender: typeof flexRender;
|
||||||
|
t: Translations;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TableFormProps {
|
||||||
|
form: UseFormReturn<any>;
|
||||||
|
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<LanguageTypes, Translations> = {
|
||||||
|
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<DataTableProps> = React.memo(({
|
||||||
|
table,
|
||||||
|
tableData,
|
||||||
|
isLoading,
|
||||||
|
handleSortingChange,
|
||||||
|
getSortingIcon,
|
||||||
|
flexRender,
|
||||||
|
t,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="overflow-x-auto relative">
|
||||||
|
{/* Semi-transparent loading overlay that preserves interactivity */}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="absolute inset-0 bg-white bg-opacity-60 flex items-center justify-center z-10">
|
||||||
|
{/* We don't put anything here as we already have the loading indicator in the header */}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
|
<caption className="sr-only">{t.dataTable}</caption>
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<tr key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header) => {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={header.id}
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
|
||||||
|
onClick={() => handleSortingChange(header.column.id)}
|
||||||
|
aria-sort={header.column.getIsSorted() ? (header.column.getIsSorted() === 'desc' ? 'descending' : 'ascending') : undefined}
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-1">
|
||||||
|
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
|
<span>{getSortingIcon(header.column.id)}</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
|
{tableData.length === 0 && !isLoading ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={table.getAllColumns().length} className="px-6 py-4 text-center text-gray-500">
|
||||||
|
{t.noDataAvailable}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<tr key={row.id} className="hover:bg-gray-50">
|
||||||
|
{row.getVisibleCells().map((cell) => {
|
||||||
|
return (
|
||||||
|
<td key={cell.id} className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const TableForm: React.FC<TableFormProps> = ({
|
||||||
|
form,
|
||||||
|
handleFormSubmit,
|
||||||
|
handleSelectChange,
|
||||||
|
renderPageOptions,
|
||||||
|
pageSizeOptions,
|
||||||
|
apiPagination,
|
||||||
|
handleFirstPage,
|
||||||
|
handlePreviousPage,
|
||||||
|
handleNextPage,
|
||||||
|
isPreviousDisabled,
|
||||||
|
isNextDisabled,
|
||||||
|
t
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="p-4 border-b border-gray-200">
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={handleFormSubmit} className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
|
<div className="md:col-span-1">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="page"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t.page}</FormLabel>
|
||||||
|
<Select
|
||||||
|
value={field.value.toString()}
|
||||||
|
onValueChange={(value: string) => handleSelectChange(value, field)}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t.selectPage} />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{renderPageOptions().length > 0 ? (
|
||||||
|
renderPageOptions().map((option: { key: string | number; value: string; label: string }) => (
|
||||||
|
<SelectItem key={option.key} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem key="1" value="1">
|
||||||
|
1
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="md:col-span-1">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="size"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t.size}</FormLabel>
|
||||||
|
<Select
|
||||||
|
value={field.value.toString()}
|
||||||
|
onValueChange={(value: string) => handleSelectChange(value, field)}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t.selectSize} />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{pageSizeOptions.map((size: number) => (
|
||||||
|
<SelectItem key={size} value={size.toString()}>
|
||||||
|
{size}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="md:col-span-1 flex items-end">
|
||||||
|
<p className="text-sm text-gray-700">
|
||||||
|
{t.page}: <span className="font-medium">{apiPagination.page}</span><span>{" / "}</span> <span className="font-medium">{apiPagination.totalPages}</span> ·
|
||||||
|
{t.size}: <span className="font-medium">{apiPagination.pageCount}</span><span>{" / "}</span> <span className="font-medium">{apiPagination.size}</span> ·
|
||||||
|
{t.total}: <span className="font-medium">{apiPagination.totalCount}</span> {t.items}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="md:col-span-1 flex items-end justify-end space-x-2">
|
||||||
|
<Button
|
||||||
|
onClick={handleFirstPage}
|
||||||
|
disabled={isPreviousDisabled()}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
aria-label="Go to first page"
|
||||||
|
>{t.first}</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handlePreviousPage}
|
||||||
|
disabled={isPreviousDisabled()}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
aria-label="Go to previous page"
|
||||||
|
>{t.previous}</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleNextPage}
|
||||||
|
disabled={isNextDisabled()}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
aria-label="Go to next page"
|
||||||
|
>{t.next}</Button>
|
||||||
|
{/* <Button type="submit" disabled={isLoading} size="sm">Fetch</Button> */}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MobilePaginationControls: React.FC<MobilePaginationControlsProps> = ({
|
||||||
|
handlePreviousPage,
|
||||||
|
handleNextPage,
|
||||||
|
isPreviousDisabled,
|
||||||
|
isNextDisabled,
|
||||||
|
t
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:hidden">
|
||||||
|
<div className="flex-1 flex justify-between">
|
||||||
|
<Button
|
||||||
|
onClick={handlePreviousPage}
|
||||||
|
disabled={isPreviousDisabled()}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
aria-label="Go to previous page"
|
||||||
|
>{t.previous}</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleNextPage}
|
||||||
|
disabled={isNextDisabled()}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="ml-2"
|
||||||
|
aria-label="Go to next page"
|
||||||
|
>{t.next}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TableHeader: React.FC<TableHeaderProps> = ({ title, description, isLoading, error, t }) => {
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div className="p-4 border-b border-gray-200">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold text-gray-800">{title}</h2>
|
||||||
|
<p className="text-sm text-gray-500">{description}</p>
|
||||||
|
</div>
|
||||||
|
{/* {isLoading && <LoadingContent height="h-16" size="w-36 h-48" plane="h-full w-full" />} */}
|
||||||
|
{error && <ErrorDisplay message={error} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ message }) => {
|
||||||
|
return <div className="text-red-500">{message}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BuildListPage: React.FC<DashboardPageProps> = 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) => (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log('Edit button clicked');
|
||||||
|
handleEditRow && handleEditRow(info.row.original);
|
||||||
|
}}
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
>
|
||||||
|
<Pencil className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Edit</span>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<BuildSchemaInterface>();
|
||||||
|
const columns = React.useMemo(() => [
|
||||||
|
columnHelper.display({
|
||||||
|
id: 'actions',
|
||||||
|
header: () => <span>{t.actionButtonGroup}</span>,
|
||||||
|
cell: (info) => { return (actionButtonGroup(info)) }
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('uu_id', {
|
||||||
|
cell: info => info.getValue(),
|
||||||
|
header: () => <span>{buildTranslations[language].uu_id}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('gov_address_code', {
|
||||||
|
cell: info => info.getValue(),
|
||||||
|
header: () => <span>{buildTranslations[language].gov_address_code}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('build_name', {
|
||||||
|
cell: info => info.getValue(),
|
||||||
|
header: () => <span>{buildTranslations[language].build_name}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('build_no', {
|
||||||
|
cell: info => info.getValue(),
|
||||||
|
header: () => <span>{buildTranslations[language].build_no}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('max_floor', {
|
||||||
|
cell: info => String(info.getValue()),
|
||||||
|
header: () => <span>{buildTranslations[language].max_floor}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('underground_floor', {
|
||||||
|
cell: info => String(info.getValue()),
|
||||||
|
header: () => <span>{buildTranslations[language].underground_floor}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('build_date', {
|
||||||
|
cell: info => info.getValue(),
|
||||||
|
header: () => <span>{buildTranslations[language].build_date}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('decision_period_date', {
|
||||||
|
cell: info => info.getValue(),
|
||||||
|
header: () => <span>{buildTranslations[language].decision_period_date}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('tax_no', {
|
||||||
|
cell: info => info.getValue(),
|
||||||
|
header: () => <span>{buildTranslations[language].tax_no}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('lift_count', {
|
||||||
|
cell: info => String(info.getValue()),
|
||||||
|
header: () => <span>{buildTranslations[language].lift_count}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('heating_system', {
|
||||||
|
cell: info => info.getValue() ? 'Yes' : 'No',
|
||||||
|
header: () => <span>{buildTranslations[language].heating_system}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('cooling_system', {
|
||||||
|
cell: info => info.getValue() ? 'Yes' : 'No',
|
||||||
|
header: () => <span>{buildTranslations[language].cooling_system}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('hot_water_system', {
|
||||||
|
cell: info => info.getValue() ? 'Yes' : 'No',
|
||||||
|
header: () => <span>{buildTranslations[language].hot_water_system}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('block_service_man_count', {
|
||||||
|
cell: info => String(info.getValue()),
|
||||||
|
header: () => <span>{buildTranslations[language].block_service_man_count}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('security_service_man_count', {
|
||||||
|
cell: info => String(info.getValue()),
|
||||||
|
header: () => <span>{buildTranslations[language].security_service_man_count}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('garage_count', {
|
||||||
|
cell: info => String(info.getValue()),
|
||||||
|
header: () => <span>{buildTranslations[language].garage_count}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('site_uu_id', {
|
||||||
|
cell: info => info.getValue(),
|
||||||
|
header: () => <span>{buildTranslations[language].site_uu_id}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('address_uu_id', {
|
||||||
|
cell: info => info.getValue(),
|
||||||
|
header: () => <span>{buildTranslations[language].address_uu_id}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('build_types_uu_id', {
|
||||||
|
cell: info => info.getValue(),
|
||||||
|
header: () => <span>{buildTranslations[language].build_types_uu_id}</span>,
|
||||||
|
footer: info => info.column.id
|
||||||
|
}),
|
||||||
|
], [columnHelper]) as ColumnDef<BuildSchemaInterface>[];
|
||||||
|
|
||||||
|
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 ? <LoadingContent height="h-48" size="w-36 h-36" plane="h-full w-full" /> :
|
||||||
|
<div className="bg-white rounded-lg shadow-md overflow-hidden mb-20">
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center p-4">
|
||||||
|
<TableHeader
|
||||||
|
title={t.dataTable}
|
||||||
|
description={t.tableWithApiData}
|
||||||
|
isLoading={isLoading}
|
||||||
|
error={error}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() => router.push(`/panel/${activePageUrl}/create`)}
|
||||||
|
className="bg-primary text-white hover:bg-primary/90"
|
||||||
|
>
|
||||||
|
<span className="mr-2">Create New</span>
|
||||||
|
<span className="sr-only">Create new item</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TableForm
|
||||||
|
form={form}
|
||||||
|
handleFormSubmit={handleFormSubmit}
|
||||||
|
handleSelectChange={handleSelectChange}
|
||||||
|
renderPageOptions={renderPageOptions}
|
||||||
|
pageSizeOptions={pageSizeOptions}
|
||||||
|
apiPagination={apiPagination}
|
||||||
|
handleFirstPage={handleFirstPage}
|
||||||
|
handlePreviousPage={handlePreviousPage}
|
||||||
|
handleNextPage={handleNextPage}
|
||||||
|
isPreviousDisabled={isPreviousDisabled}
|
||||||
|
isNextDisabled={isNextDisabled}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Mobile pagination controls - only visible on small screens */}
|
||||||
|
<MobilePaginationControls
|
||||||
|
handlePreviousPage={handlePreviousPage}
|
||||||
|
handleNextPage={handleNextPage}
|
||||||
|
isPreviousDisabled={isPreviousDisabled}
|
||||||
|
isNextDisabled={isNextDisabled}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
table={table}
|
||||||
|
tableData={tableData}
|
||||||
|
isLoading={isLoading}
|
||||||
|
handleSortingChange={handleSortingChange}
|
||||||
|
getSortingIcon={getSortingIcon}
|
||||||
|
flexRender={flexRender}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default BuildListPage;
|
||||||
|
|
@ -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<void>;
|
||||||
|
updateCache?: (url: string, data: any) => Promise<void>;
|
||||||
|
clearCache?: (url: string) => Promise<void>;
|
||||||
|
activePageUrl: string;
|
||||||
|
}) {
|
||||||
|
const language: LanguageTypes = onlineData?.lang || 'en';
|
||||||
|
const [cacheLoaded, setCacheLoaded] = useState<boolean>(false);
|
||||||
|
const [buildUUID, setBuildUUID] = useState<string>("");
|
||||||
|
const router = useRouter();
|
||||||
|
const listUrl = `/panel/${activePageUrl?.replace("/update", "")}`;
|
||||||
|
const form = useForm<BuildInterface>({
|
||||||
|
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<any>({
|
||||||
|
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 (
|
||||||
|
<div className="w-full max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-md">
|
||||||
|
{/* back to list button */}
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<Button onClick={() => router.push(listUrl)}>{translationsOfPage[language].back2List}</Button>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl font-bold mb-3">{translationsOfPage[language].title}</h2>
|
||||||
|
<h4 className="text-sm text-gray-500 mb-6">UUID: {buildUUID}</h4>
|
||||||
|
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit, (errors) => {
|
||||||
|
console.error('Form validation errors:', errors);
|
||||||
|
})} className="space-y-6">
|
||||||
|
{/* Government Address Code */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="gov_address_code"
|
||||||
|
label={buildTranslations[language].gov_address_code}
|
||||||
|
placeholder={buildTranslations[language].gov_address_code}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Building Name */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="build_name"
|
||||||
|
label={buildTranslations[language].build_name}
|
||||||
|
placeholder={buildTranslations[language].build_name}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Building Number */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="build_no"
|
||||||
|
label={buildTranslations[language].build_no}
|
||||||
|
placeholder={buildTranslations[language].build_no}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Max Floor */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="max_floor"
|
||||||
|
label={buildTranslations[language].max_floor}
|
||||||
|
placeholder={buildTranslations[language].max_floor}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Underground Floor */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="underground_floor"
|
||||||
|
label={buildTranslations[language].underground_floor}
|
||||||
|
placeholder={buildTranslations[language].underground_floor}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Build Date */}
|
||||||
|
<DatetimeInput
|
||||||
|
control={form.control}
|
||||||
|
name="build_date"
|
||||||
|
label={buildTranslations[language].build_date}
|
||||||
|
placeholder={buildTranslations[language].build_date}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Decision Period Date */}
|
||||||
|
<DatetimeInput
|
||||||
|
control={form.control}
|
||||||
|
name="decision_period_date"
|
||||||
|
label={buildTranslations[language].decision_period_date}
|
||||||
|
placeholder={buildTranslations[language].decision_period_date}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Tax Number */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="tax_no"
|
||||||
|
label={buildTranslations[language].tax_no}
|
||||||
|
placeholder={buildTranslations[language].tax_no}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Lift Count */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="lift_count"
|
||||||
|
label={buildTranslations[language].lift_count}
|
||||||
|
placeholder={buildTranslations[language].lift_count}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Heating System */}
|
||||||
|
<CheckBoxInput
|
||||||
|
control={form.control}
|
||||||
|
name="heating_system"
|
||||||
|
label={buildTranslations[language].heating_system}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Cooling System */}
|
||||||
|
<CheckBoxInput
|
||||||
|
control={form.control}
|
||||||
|
name="cooling_system"
|
||||||
|
label={buildTranslations[language].cooling_system}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Hot Water System */}
|
||||||
|
<CheckBoxInput
|
||||||
|
control={form.control}
|
||||||
|
name="hot_water_system"
|
||||||
|
label={buildTranslations[language].hot_water_system}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Block Service Man Count */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="block_service_man_count"
|
||||||
|
label={buildTranslations[language].block_service_man_count}
|
||||||
|
placeholder={buildTranslations[language].block_service_man_count}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Security Service Man Count */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="security_service_man_count"
|
||||||
|
label={buildTranslations[language].security_service_man_count}
|
||||||
|
placeholder={buildTranslations[language].security_service_man_count}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Garage Count */}
|
||||||
|
<NumberInput
|
||||||
|
control={form.control}
|
||||||
|
name="garage_count"
|
||||||
|
label={buildTranslations[language].garage_count}
|
||||||
|
placeholder={buildTranslations[language].garage_count}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Site UUID */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="site_uu_id"
|
||||||
|
label={buildTranslations[language].site_uu_id}
|
||||||
|
placeholder={buildTranslations[language].site_uu_id}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Address UUID */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="address_uu_id"
|
||||||
|
label={buildTranslations[language].address_uu_id}
|
||||||
|
placeholder={buildTranslations[language].address_uu_id}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Build Types UUID */}
|
||||||
|
<StringInput
|
||||||
|
control={form.control}
|
||||||
|
name="build_types_uu_id"
|
||||||
|
label={buildTranslations[language].build_types_uu_id}
|
||||||
|
placeholder={buildTranslations[language].build_types_uu_id}
|
||||||
|
onBlurCallback={handleFieldBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button type="submit" className="w-full">{language === 'en' ? 'Update Building' : 'Bina Güncelle'}</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UpdateFromComponent = withCache(UpdateFromComponentBase);
|
||||||
|
export default withCache(UpdateFromComponentBase);
|
||||||
|
|
@ -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<typeof buildSchema>;
|
||||||
|
type BuildCreateInterface = z.infer<typeof buildSchemaCreate>;
|
||||||
|
|
||||||
|
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 };
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -1,14 +1,21 @@
|
||||||
// import { DashboardPage } from "@/components/custom/content/DashboardPage";
|
// import { DashboardPage } from "@/components/custom/content/DashboardPage";
|
||||||
import { DPage } from "@/components/custom/content/DPage";
|
import { DPage } from "@/components/custom/content/DPage";
|
||||||
import TableCardComponentImproved from "@/components/custom/content/TableCardComponentImproved";
|
import TableCardComponentImproved from "@/components/custom/content/TableCardComponentImproved";
|
||||||
import CreateFromComponent from "@/components/custom/content/createFromComponent";
|
import BuildListPage from "./builds/superuser/ListPage";
|
||||||
import UpdateFromComponent from "@/components/custom/content/updateFromComponent";
|
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<string, Record<string, React.FC<any>>> = {
|
const pageIndexMulti: Record<string, Record<string, React.FC<any>>> = {
|
||||||
"/dashboard": { DashboardPage: TableCardComponentImproved },
|
"/dashboard": { DashboardPage: TableCardComponentImproved },
|
||||||
"/build": { DashboardPage: TableCardComponentImproved },
|
"/build": { DashboardPage: BuildListPage },
|
||||||
"/build/create": { DashboardPage: CreateFromComponent },
|
"/build/create": { DashboardPage: CreateFromBuildComponent },
|
||||||
"/build/update": { DashboardPage: UpdateFromComponent },
|
"/build/update": { DashboardPage: UpdateFromBuildComponent },
|
||||||
|
"/build/parts": { DashboardPage: BuildPartsListPage },
|
||||||
|
"/build/parts/create": { DashboardPage: CreateFromBuildPartsComponent },
|
||||||
|
"/build/parts/update": { DashboardPage: UpdateFromBuildPartsComponent },
|
||||||
"/people": { DashboardPage: DPage },
|
"/people": { DashboardPage: DPage },
|
||||||
"/people/create": { DashboardPage: DPage },
|
"/people/create": { DashboardPage: DPage },
|
||||||
"/people/update": { DashboardPage: DPage },
|
"/people/update": { DashboardPage: DPage },
|
||||||
|
|
|
||||||
|
|
@ -1,71 +1,81 @@
|
||||||
import { z } from 'zod';
|
import { z } from "zod";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zod schema for BuildTypes
|
* Zod schema for BuildTypes
|
||||||
* Corresponds to the BuildTypes class in Python
|
* Corresponds to the BuildTypes class in Python
|
||||||
*/
|
*/
|
||||||
export const buildTypesSchema = z.object({
|
export const buildTypesSchema = z.object({
|
||||||
function_code: z.string().max(12).default('').describe('Function Code'),
|
function_code: z.string().max(12).default("").describe("Function Code"),
|
||||||
type_code: z.string().max(12).default('').describe('Structure Type Code'),
|
type_code: z.string().max(12).default("").describe("Structure Type Code"),
|
||||||
lang: z.string().max(4).default('TR').describe('Language'),
|
lang: z.string().max(4).default("TR").describe("Language"),
|
||||||
type_name: z.string().max(48).default('').describe('Type Name'),
|
type_name: z.string().max(48).default("").describe("Type Name"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type BuildTypes = z.infer<typeof buildTypesSchema>;
|
export type BuildTypes = z.infer<typeof buildTypesSchema>;
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Zod schema for Part2Employee
|
// * Zod schema for Part2Employee
|
||||||
* Corresponds to the Part2Employee class in Python
|
// * Corresponds to the Part2Employee class in Python
|
||||||
*/
|
// */
|
||||||
export const part2EmployeeSchema = z.object({
|
// export const part2EmployeeSchema = z.object({
|
||||||
build_id: z.number().int().describe('Building ID'),
|
// build_uu_id: z.string().describe("Building UUID"),
|
||||||
part_id: z.number().int().describe('Part ID'),
|
// part_uu_id: z.string().describe("Part UUID"),
|
||||||
employee_id: z.number().int().describe('Employee ID'),
|
// employee_uu_id: z.string().describe("Employee UUID"),
|
||||||
});
|
// });
|
||||||
|
|
||||||
export type Part2Employee = z.infer<typeof part2EmployeeSchema>;
|
// export type Part2Employee = z.infer<typeof part2EmployeeSchema>;
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Zod schema for RelationshipEmployee2Build
|
// * Zod schema for RelationshipEmployee2Build
|
||||||
* Corresponds to the RelationshipEmployee2Build class in Python
|
// * Corresponds to the RelationshipEmployee2Build class in Python
|
||||||
*/
|
// */
|
||||||
export const relationshipEmployee2BuildSchema = z.object({
|
// export const relationshipEmployee2BuildSchema = z.object({
|
||||||
company_id: z.number().int(),
|
// company_id: z.number().int(),
|
||||||
employee_id: z.number().int(),
|
// employee_id: z.number().int(),
|
||||||
member_id: z.number().int(),
|
// member_id: z.number().int(),
|
||||||
relationship_type: z.string().default('Employee'),
|
// relationship_type: z.string().default("Employee"),
|
||||||
show_only: z.boolean().default(false),
|
// show_only: z.boolean().default(false),
|
||||||
});
|
// });
|
||||||
|
|
||||||
export type RelationshipEmployee2Build = z.infer<typeof relationshipEmployee2BuildSchema>;
|
// export type RelationshipEmployee2Build = z.infer<
|
||||||
|
// typeof relationshipEmployee2BuildSchema
|
||||||
|
// >;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zod schema for Build
|
* Zod schema for Build
|
||||||
* Corresponds to the Build class in Python
|
* Corresponds to the Build class in Python
|
||||||
*/
|
*/
|
||||||
export const buildSchema = z.object({
|
export const buildSchema = z.object({
|
||||||
gov_address_code: z.string().default(''),
|
gov_address_code: z.string().default(""),
|
||||||
build_name: z.string().describe('Building Name'),
|
build_name: z.string().describe("Building Name"),
|
||||||
build_no: z.string().max(8).describe('Building Number'),
|
build_no: z.string().max(8).describe("Building Number"),
|
||||||
max_floor: z.number().int().default(1).describe('Max Floor'),
|
max_floor: z.number().int().default(1).describe("Max Floor"),
|
||||||
underground_floor: z.number().int().default(0).describe('Underground Floor'),
|
underground_floor: z.number().int().default(0).describe("Underground Floor"),
|
||||||
build_date: z.string().datetime().default('1900-01-01T00:00:00Z'),
|
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'),
|
decision_period_date: z
|
||||||
tax_no: z.string().max(24).default(''),
|
.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),
|
lift_count: z.number().int().default(0),
|
||||||
heating_system: z.boolean().default(true),
|
heating_system: z.boolean().default(true),
|
||||||
cooling_system: z.boolean().default(false),
|
cooling_system: z.boolean().default(false),
|
||||||
hot_water_system: z.boolean().default(false),
|
hot_water_system: z.boolean().default(false),
|
||||||
block_service_man_count: z.number().int().default(0),
|
block_service_man_count: z.number().int().default(0),
|
||||||
security_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'),
|
garage_count: z.number().int().default(0).describe("Garage Count"),
|
||||||
management_room_id: z.number().int().nullable().describe('Management Room ID'),
|
management_room_id: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.nullable()
|
||||||
|
.describe("Management Room ID"),
|
||||||
site_id: z.number().int().nullable(),
|
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_id: z.number().int(),
|
||||||
address_uu_id: z.string().describe('Address UUID'),
|
address_uu_id: z.string().describe("Address UUID"),
|
||||||
build_types_id: z.number().int().describe('Building Type'),
|
build_types_id: z.number().int().describe("Building Type"),
|
||||||
build_types_uu_id: z.string().describe('Building Type UUID'),
|
build_types_uu_id: z.string().describe("Building Type UUID"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Build = z.infer<typeof buildSchema>;
|
export type Build = z.infer<typeof buildSchema>;
|
||||||
|
|
@ -75,21 +85,18 @@ export type Build = z.infer<typeof buildSchema>;
|
||||||
* Corresponds to the BuildParts class in Python
|
* Corresponds to the BuildParts class in Python
|
||||||
*/
|
*/
|
||||||
export const buildPartsSchema = z.object({
|
export const buildPartsSchema = z.object({
|
||||||
address_gov_code: z.string().describe('Goverment Door Code'),
|
address_gov_code: z.string().describe("Goverment Door Code"),
|
||||||
part_no: z.number().int().default(0).describe('Part Number'),
|
part_no: z.number().int().default(0).describe("Part Number"),
|
||||||
part_level: z.number().int().default(0).describe('Building Part Level'),
|
part_level: z.number().int().default(0).describe("Building Part Level"),
|
||||||
part_code: z.string().default('').describe('Part Code'),
|
part_code: z.string().default("").describe("Part Code"),
|
||||||
part_gross_size: z.number().int().default(0).describe('Part Gross Size'),
|
part_gross_size: z.number().int().default(0).describe("Part Gross Size"),
|
||||||
part_net_size: z.number().int().default(0).describe('Part Net Size'),
|
part_net_size: z.number().int().default(0).describe("Part Net Size"),
|
||||||
default_accessory: z.string().default('0').describe('Default Accessory'),
|
default_accessory: z.string().default("0").describe("Default Accessory"),
|
||||||
human_livable: z.boolean().default(true).describe('Human Livable'),
|
human_livable: z.boolean().default(true).describe("Human Livable"),
|
||||||
due_part_key: z.string().default('').describe('Constant Payment Group'),
|
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"),
|
||||||
build_uu_id: z.string().describe('Building UUID'),
|
part_direction_uu_id: z.string().nullable().describe("Part Direction UUID"),
|
||||||
part_direction_id: z.number().int().nullable(),
|
part_type_uu_id: z.string().describe("Building Part Type UUID"),
|
||||||
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'),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type BuildParts = z.infer<typeof buildPartsSchema>;
|
export type BuildParts = z.infer<typeof buildPartsSchema>;
|
||||||
|
|
@ -99,17 +106,20 @@ export type BuildParts = z.infer<typeof buildPartsSchema>;
|
||||||
* Corresponds to the BuildLivingSpace class in Python
|
* Corresponds to the BuildLivingSpace class in Python
|
||||||
*/
|
*/
|
||||||
export const buildLivingSpaceSchema = z.object({
|
export const buildLivingSpaceSchema = z.object({
|
||||||
fix_value: z.number().default(0).describe('Fixed value is deducted from debit.'),
|
fix_value: z
|
||||||
fix_percent: z.number().default(0).describe('Fixed percent is deducted from debit.'),
|
.number()
|
||||||
agreement_no: z.string().default('').describe('Agreement No'),
|
.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_process: z.boolean().default(false),
|
||||||
marketing_layer: z.number().int().default(0),
|
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"),
|
||||||
build_parts_uu_id: z.string().describe('Build Part UUID'),
|
person_uu_id: z.string().describe("Responsible People UUID"),
|
||||||
person_id: z.number().int().describe('Responsible People ID'),
|
occupant_type_uu_id: z.string().describe("Occupant Type UUID"),
|
||||||
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'),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type BuildLivingSpace = z.infer<typeof buildLivingSpaceSchema>;
|
export type BuildLivingSpace = z.infer<typeof buildLivingSpaceSchema>;
|
||||||
|
|
@ -122,12 +132,10 @@ export const buildManagementSchema = z.object({
|
||||||
discounted_percentage: z.number().default(0),
|
discounted_percentage: z.number().default(0),
|
||||||
discounted_price: z.number().default(0),
|
discounted_price: z.number().default(0),
|
||||||
calculated_price: z.number().default(0),
|
calculated_price: z.number().default(0),
|
||||||
occupant_type: z.number().int().describe('Occupant Type'),
|
// occupant_type: z.number().int().describe("Occupant Type"),
|
||||||
occupant_type_uu_id: z.string().describe('Occupant Type UUID'),
|
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_uu_id: z.string().describe('Building UUID'),
|
build_parts_uu_id: z.string().describe("Build Part UUID"),
|
||||||
build_parts_id: z.number().int().describe('Build Part ID'),
|
|
||||||
build_parts_uu_id: z.string().describe('Build Part UUID'),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type BuildManagement = z.infer<typeof buildManagementSchema>;
|
export type BuildManagement = z.infer<typeof buildManagementSchema>;
|
||||||
|
|
@ -137,18 +145,16 @@ export type BuildManagement = z.infer<typeof buildManagementSchema>;
|
||||||
* Corresponds to the BuildArea class in Python
|
* Corresponds to the BuildArea class in Python
|
||||||
*/
|
*/
|
||||||
export const buildAreaSchema = z.object({
|
export const buildAreaSchema = z.object({
|
||||||
area_name: z.string().default(''),
|
area_name: z.string().default(""),
|
||||||
area_code: z.string().default(''),
|
area_code: z.string().default(""),
|
||||||
area_type: z.string().default('GREEN'),
|
area_type: z.string().default("GREEN"),
|
||||||
area_direction: z.string().max(2).default('NN'),
|
area_direction: z.string().max(2).default("NN"),
|
||||||
area_gross_size: z.number().default(0),
|
area_gross_size: z.number().default(0),
|
||||||
area_net_size: z.number().default(0),
|
area_net_size: z.number().default(0),
|
||||||
width: z.number().int().default(0),
|
width: z.number().int().default(0),
|
||||||
size: z.number().int().default(0),
|
size: z.number().int().default(0),
|
||||||
build_id: z.number().int(),
|
build_uu_id: z.string().describe("Building UUID"),
|
||||||
build_uu_id: z.string().describe('Building UUID'),
|
part_type_uu_id: z.string().nullable().describe("Building Part Type UUID"),
|
||||||
part_type_id: z.number().int().nullable().describe('Building Part Type'),
|
|
||||||
part_type_uu_id: z.string().nullable().describe('Building Part Type UUID'),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type BuildArea = z.infer<typeof buildAreaSchema>;
|
export type BuildArea = z.infer<typeof buildAreaSchema>;
|
||||||
|
|
@ -160,8 +166,7 @@ export type BuildArea = z.infer<typeof buildAreaSchema>;
|
||||||
export const buildSitesSchema = z.object({
|
export const buildSitesSchema = z.object({
|
||||||
site_name: z.string().max(24),
|
site_name: z.string().max(24),
|
||||||
site_no: z.string().max(8),
|
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<typeof buildSitesSchema>;
|
export type BuildSites = z.infer<typeof buildSitesSchema>;
|
||||||
|
|
@ -171,28 +176,23 @@ export type BuildSites = z.infer<typeof buildSitesSchema>;
|
||||||
* Corresponds to the BuildCompaniesProviding class in Python
|
* Corresponds to the BuildCompaniesProviding class in Python
|
||||||
*/
|
*/
|
||||||
export const buildCompaniesProvidingSchema = z.object({
|
export const buildCompaniesProvidingSchema = z.object({
|
||||||
build_id: z.number().int().describe('Building ID'),
|
build_uu_id: z.string().nullable().describe("Providing UUID"),
|
||||||
build_uu_id: z.string().nullable().describe('Providing UUID'),
|
company_uu_id: z.string().nullable().describe("Providing UUID"),
|
||||||
company_id: z.number().int(),
|
provide_uu_id: z.string().nullable().describe("Providing UUID"),
|
||||||
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(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type BuildCompaniesProviding = z.infer<typeof buildCompaniesProvidingSchema>;
|
export type BuildCompaniesProviding = z.infer<
|
||||||
|
typeof buildCompaniesProvidingSchema
|
||||||
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zod schema for BuildPersonProviding
|
* Zod schema for BuildPersonProviding
|
||||||
* Corresponds to the BuildPersonProviding class in Python
|
* Corresponds to the BuildPersonProviding class in Python
|
||||||
*/
|
*/
|
||||||
export const buildPersonProvidingSchema = z.object({
|
export const buildPersonProvidingSchema = z.object({
|
||||||
build_id: z.number().int().describe('Building ID'),
|
build_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"),
|
||||||
people_id: z.number().int(),
|
provide_uu_id: z.string().nullable().describe("Providing UUID"),
|
||||||
people_uu_id: z.string().nullable().describe('People UUID'),
|
|
||||||
provide_id: z.number().int().nullable(),
|
|
||||||
provide_uu_id: z.string().nullable().describe('Providing UUID'),
|
|
||||||
contract_id: z.number().int().nullable(),
|
contract_id: z.number().int().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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" }
|
||||||
|
);
|
||||||
|
|
@ -12,6 +12,7 @@ interface Translations {
|
||||||
next: string;
|
next: string;
|
||||||
selectPage: string;
|
selectPage: string;
|
||||||
selectSize: string;
|
selectSize: string;
|
||||||
|
actionButtonGroup: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TableHeaderProps {
|
interface TableHeaderProps {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue