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 index import endpoints_index
|
||||
|
|
@ -7,11 +7,27 @@ from events.building_parts.cluster import PartsRouterCluster
|
|||
from Validations.defaults.validations import CommonHeaders
|
||||
from Validations.response.pagination import PaginateOnly
|
||||
from Extensions.Middlewares.token_provider import TokenProvider
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
parts_endpoint_route = APIRouter(prefix="/parts", tags=["Parts Cluster"])
|
||||
|
||||
|
||||
class PartsListRequest(BaseModel):
|
||||
address_gov_code: str
|
||||
build_uu_id: str = None
|
||||
part_no: int
|
||||
part_level: int
|
||||
part_code: str
|
||||
part_gross_size: int
|
||||
part_net_size: int
|
||||
default_accessory: str
|
||||
human_livable: bool
|
||||
due_part_key: Optional[str] = None
|
||||
part_direction_uu_id: Optional[str] = None
|
||||
part_type_uu_id: Optional[str] = None
|
||||
|
||||
|
||||
parts_list = "PartsList"
|
||||
@parts_endpoint_route.post(
|
||||
path="/list",
|
||||
|
|
@ -22,7 +38,7 @@ def parts_list_route(data: PaginateOnly, headers: CommonHeaders = Depends(Common
|
|||
token_object = TokenProvider.get_dict_from_redis(token=headers.token)
|
||||
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
||||
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
|
||||
FoundCluster = PartsRouterCluster.get_event_cluster(parts_list)
|
||||
FoundCluster = PartsRouterCluster.get_event_cluster("PartsListEventCluster")
|
||||
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
|
||||
return event_cluster_matched.event_callable(list_options=data, headers=headers)
|
||||
|
||||
|
|
@ -33,11 +49,11 @@ parts_create = "PartsCreate"
|
|||
description="Create part endpoint",
|
||||
operation_id=endpoints_index[parts_create],
|
||||
)
|
||||
def parts_create_route(data, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)):
|
||||
def parts_create_route(data: PartsListRequest, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)):
|
||||
token_object = TokenProvider.get_dict_from_redis(token=headers.token)
|
||||
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
||||
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
|
||||
FoundCluster = PartsRouterCluster.get_event_cluster(parts_create)
|
||||
FoundCluster = PartsRouterCluster.get_event_cluster("PartsCreateEventCluster")
|
||||
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
|
||||
return event_cluster_matched.event_callable(data=data, headers=headers)
|
||||
|
||||
|
|
@ -48,11 +64,11 @@ parts_update = "PartsUpdate"
|
|||
description="Update part endpoint",
|
||||
operation_id=endpoints_index[parts_update],
|
||||
)
|
||||
def parts_update_route(uu_id: str, data, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)):
|
||||
def parts_update_route(uu_id: str, data: PartsListRequest, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)):
|
||||
token_object = TokenProvider.get_dict_from_redis(token=headers.token)
|
||||
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
||||
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
|
||||
FoundCluster = PartsRouterCluster.get_event_cluster(parts_update)
|
||||
FoundCluster = PartsRouterCluster.get_event_cluster("PartsUpdateEventCluster")
|
||||
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
|
||||
return event_cluster_matched.event_callable(uu_id=uu_id, data=data, headers=headers)
|
||||
|
||||
|
|
@ -67,6 +83,6 @@ def parts_delete_route(uu_id: str, headers: CommonHeaders = Depends(CommonHeader
|
|||
token_object = TokenProvider.get_dict_from_redis(token=headers.token)
|
||||
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
||||
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
|
||||
FoundCluster = PartsRouterCluster.get_event_cluster(parts_delete)
|
||||
FoundCluster = PartsRouterCluster.get_event_cluster("PartsDeleteEventCluster")
|
||||
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
|
||||
return event_cluster_matched.event_callable(uu_id=uu_id, headers=headers)
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel
|
||||
|
||||
from index import endpoints_index
|
||||
from events.builds.cluster import BuildRouterCluster
|
||||
|
|
@ -9,6 +10,31 @@ from Validations.response.pagination import PaginateOnly
|
|||
from Extensions.Middlewares.token_provider import TokenProvider
|
||||
|
||||
|
||||
# Pydantic model for build update validation
|
||||
class BuildFormModel(BaseModel):
|
||||
gov_address_code: str
|
||||
build_name: str
|
||||
build_no: str
|
||||
max_floor: int
|
||||
underground_floor: int
|
||||
build_date: str
|
||||
decision_period_date: str
|
||||
tax_no: str
|
||||
lift_count: int
|
||||
heating_system: bool
|
||||
cooling_system: bool
|
||||
hot_water_system: bool
|
||||
block_service_man_count: int
|
||||
security_service_man_count: int
|
||||
garage_count: int
|
||||
site_uu_id: Optional[str] = None
|
||||
address_uu_id: str
|
||||
build_types_uu_id: str
|
||||
|
||||
class Config:
|
||||
extra = "allow"
|
||||
|
||||
|
||||
build_endpoint_route = APIRouter(prefix="/builds", tags=["Builds Cluster"])
|
||||
|
||||
build_list = "BuildList"
|
||||
|
|
@ -21,7 +47,7 @@ def build_list_route(data: PaginateOnly, headers: CommonHeaders = Depends(Common
|
|||
token_object = TokenProvider.get_dict_from_redis(token=headers.token)
|
||||
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
||||
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
|
||||
FoundCluster = BuildRouterCluster.get_event_cluster(build_list)
|
||||
FoundCluster = BuildRouterCluster.get_event_cluster("BuildListEventCluster")
|
||||
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
|
||||
return event_cluster_matched.event_callable(list_options=data, headers=headers)
|
||||
|
||||
|
|
@ -32,11 +58,11 @@ build_create = "BuildCreate"
|
|||
description="Create build endpoint",
|
||||
operation_id=endpoints_index[build_create],
|
||||
)
|
||||
def build_create_route(data, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)):
|
||||
def build_create_route(data: BuildFormModel, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)):
|
||||
token_object = TokenProvider.get_dict_from_redis(token=headers.token)
|
||||
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
||||
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
|
||||
FoundCluster = BuildRouterCluster.get_event_cluster(build_create)
|
||||
FoundCluster = BuildRouterCluster.get_event_cluster("BuildCreateEventCluster")
|
||||
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
|
||||
return event_cluster_matched.event_callable(data=data, headers=headers)
|
||||
|
||||
|
|
@ -47,11 +73,11 @@ build_update = "BuildUpdate"
|
|||
description="Update build endpoint",
|
||||
operation_id=endpoints_index[build_update],
|
||||
)
|
||||
def build_update_route(uu_id: str, data, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)):
|
||||
def build_update_route(uu_id: str, data: BuildFormModel, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)):
|
||||
token_object = TokenProvider.get_dict_from_redis(token=headers.token)
|
||||
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
|
||||
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
|
||||
FoundCluster = BuildRouterCluster.get_event_cluster(build_update)
|
||||
FoundCluster = BuildRouterCluster.get_event_cluster("BuildUpdateEventCluster")
|
||||
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
|
||||
return event_cluster_matched.event_callable(uu_id=uu_id, data=data, headers=headers)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ from Validations.defaults.validations import CommonHeaders
|
|||
from Schemas import (
|
||||
Build,
|
||||
BuildParts,
|
||||
ApiEnumDropdown,
|
||||
BuildTypes,
|
||||
BuildParts,
|
||||
AccountRecords,
|
||||
)
|
||||
|
||||
|
|
@ -55,17 +58,43 @@ SuperPartsDeleteEvent = Event(
|
|||
|
||||
|
||||
def super_parts_list_callable(list_options: PaginateOnly, headers: CommonHeaders):
|
||||
return {
|
||||
"message": "MSG0003-LIST",
|
||||
"data": None,
|
||||
"completed": True,
|
||||
}
|
||||
list_options = PaginateOnly(**list_options.model_dump())
|
||||
# TODO: Pydantic Model must be implemnted for list_options.query
|
||||
with Build.new_session() as db_session:
|
||||
BuildParts.set_session(db_session)
|
||||
base_query = BuildParts.query.filter()
|
||||
build_parts_records_query = base_query
|
||||
if list_options.query:
|
||||
build_parts_records_query = BuildParts.query.filter(*BuildParts.convert(list_options.query))
|
||||
pagination = Pagination(data=build_parts_records_query, base_query=base_query)
|
||||
pagination.change(**list_options.model_dump())
|
||||
pagination_result = PaginationResult(data=build_parts_records_query, pagination=pagination, response_model=None)
|
||||
return EndpointResponse(message="MSG0003-LIST", pagination_result=pagination_result).response
|
||||
|
||||
|
||||
SuperPartsListEvent.event_callable = super_parts_list_callable
|
||||
|
||||
|
||||
def super_parts_create_callable(data, headers: CommonHeaders):
|
||||
|
||||
with Build.new_session() as db_session:
|
||||
|
||||
Build.set_session(db_session)
|
||||
BuildParts.set_session(db_session)
|
||||
BuildTypes.set_session(db_session)
|
||||
ApiEnumDropdown.set_session(db_session)
|
||||
|
||||
build = Build.query.filter(Build.uu_id == data.build_uu_id).first()
|
||||
part_direction = ApiEnumDropdown.query.filter(ApiEnumDropdown.uu_id == data.part_direction_uu_id).first()
|
||||
part_type = BuildTypes.query.filter(BuildTypes.uu_id == data.part_type_uu_id).first()
|
||||
|
||||
build_parts_created = BuildParts.create(
|
||||
**data.model_dump(),
|
||||
build_id=getattr(build, "id", None),
|
||||
part_direction_id=getattr(part_direction, "id", None),
|
||||
part_type_id=getattr(part_type, "id", None)
|
||||
)
|
||||
build_parts_created.save()
|
||||
return {
|
||||
"message": "MSG0001-INSERT",
|
||||
"data": None,
|
||||
|
|
@ -77,6 +106,25 @@ SuperPartsCreateEvent.event_callable = super_parts_create_callable
|
|||
|
||||
|
||||
def super_parts_update_callable(data, headers: CommonHeaders):
|
||||
with Build.new_session() as db_session:
|
||||
|
||||
Build.set_session(db_session)
|
||||
BuildParts.set_session(db_session)
|
||||
BuildTypes.set_session(db_session)
|
||||
ApiEnumDropdown.set_session(db_session)
|
||||
|
||||
build = Build.query.filter(Build.uu_id == data.build_uu_id).first()
|
||||
part_direction = ApiEnumDropdown.query.filter(ApiEnumDropdown.uu_id == data.part_direction_uu_id).first()
|
||||
part_type = BuildTypes.query.filter(BuildTypes.uu_id == data.part_type_uu_id).first()
|
||||
|
||||
build_parts_updated = BuildParts.query.filter(BuildParts.uu_id == data.uu_id).first()
|
||||
build_parts_updated.update(
|
||||
**data.model_dump(exclude_unset=True, exclude_none=True),
|
||||
build_id=getattr(build, "id", None),
|
||||
part_direction_id=getattr(part_direction, "id", None),
|
||||
part_type_id=getattr(part_type, "id", None)
|
||||
)
|
||||
build_parts_updated.save()
|
||||
return {
|
||||
"message": "MSG0002-UPDATE",
|
||||
"data": None,
|
||||
|
|
|
|||
|
|
@ -11,9 +11,13 @@ from Validations.response import (
|
|||
)
|
||||
from Validations.defaults.validations import CommonHeaders
|
||||
from Schemas import (
|
||||
Addresses,
|
||||
BuildTypes,
|
||||
Build,
|
||||
BuildSites,
|
||||
BuildParts,
|
||||
AccountRecords,
|
||||
Companies,
|
||||
# AccountRecords,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -53,32 +57,16 @@ SuperBuildDeleteEvent = Event(
|
|||
|
||||
def super_build_list_callable(list_options: PaginateOnly, headers: CommonHeaders):
|
||||
list_options = PaginateOnly(**list_options.model_dump())
|
||||
if token.is_employee:
|
||||
raise Exception("Forbidden for employees")
|
||||
|
||||
# TODO: Pydantic Model must be implemnted for list_options.query
|
||||
with AccountRecords.new_session() as db_session:
|
||||
AccountRecords.set_session(db_session)
|
||||
list_of_fields = [
|
||||
AccountRecords.iban,
|
||||
AccountRecords.bank_date,
|
||||
AccountRecords.currency,
|
||||
AccountRecords.currency_value,
|
||||
AccountRecords.process_comment,
|
||||
AccountRecords.add_comment_note,
|
||||
AccountRecords.receive_debit,
|
||||
AccountRecords.is_email_send,
|
||||
AccountRecords.is_notification_send,
|
||||
]
|
||||
account_records_query = db_session.query(*list_of_fields
|
||||
).join(BuildParts, BuildParts.id == AccountRecords.build_parts_id
|
||||
).filter(BuildParts.id == token.selected_occupant.build_part_id)
|
||||
with Build.new_session() as db_session:
|
||||
Build.set_session(db_session)
|
||||
base_query = Build.query.filter()
|
||||
build_records_query = base_query
|
||||
if list_options.query:
|
||||
account_records_query = account_records_query.filter(*AccountRecords.convert(list_options.query))
|
||||
|
||||
pagination = Pagination(data=account_records_query)
|
||||
build_records_query = Build.query.filter(*Build.convert(list_options.query))
|
||||
pagination = Pagination(data=build_records_query, base_query=base_query)
|
||||
pagination.change(**list_options.model_dump())
|
||||
pagination_result = PaginationResult(data=account_records_query, pagination=pagination)
|
||||
pagination_result = PaginationResult(data=build_records_query, pagination=pagination, response_model=None)
|
||||
return EndpointResponse(message="MSG0003-LIST", pagination_result=pagination_result).response
|
||||
|
||||
|
||||
|
|
@ -86,6 +74,19 @@ SuperBuildListEvent.event_callable = super_build_list_callable
|
|||
|
||||
|
||||
def super_build_create_callable(data, headers: CommonHeaders):
|
||||
|
||||
with Build.new_session() as db_session:
|
||||
|
||||
Build.set_session(db_session)
|
||||
Addresses.set_session(db_session)
|
||||
BuildTypes.set_session(db_session)
|
||||
BuildSites.set_session(db_session)
|
||||
|
||||
address_id = Addresses.query.filter(Addresses.uu_id == data.address_uu_id).first()
|
||||
build_types_id = BuildTypes.query.filter(BuildTypes.uu_id == data.build_types_uu_id).first()
|
||||
sites_id = BuildSites.query.filter(BuildSites.uu_id == data.site_uu_id).first()
|
||||
build = Build.create(**data.model_dump(), address_id=getattr(address_id, "id", None), build_types_id=getattr(build_types_id, "id", None), site_id=getattr(sites_id, "id", None))
|
||||
build.save()
|
||||
return {
|
||||
"message": "MSG0001-INSERT",
|
||||
"data": None,
|
||||
|
|
@ -96,7 +97,23 @@ def super_build_create_callable(data, headers: CommonHeaders):
|
|||
SuperBuildCreateEvent.event_callable = super_build_create_callable
|
||||
|
||||
|
||||
def super_build_update_callable(data, headers: CommonHeaders):
|
||||
def super_build_update_callable(uu_id: str, data, headers: CommonHeaders):
|
||||
data_dict = data.model_dump(exclude_unset=True, exclude_none=True)
|
||||
data_dict.pop("uu_id", None)
|
||||
data_dict.pop("uuid", None)
|
||||
with Build.new_session() as db_session:
|
||||
Build.set_session(db_session)
|
||||
Addresses.set_session(db_session)
|
||||
BuildTypes.set_session(db_session)
|
||||
BuildSites.set_session(db_session)
|
||||
|
||||
address_id = Addresses.query.filter(Addresses.uu_id == data.address_uu_id).first()
|
||||
build_types_id = BuildTypes.query.filter(BuildTypes.uu_id == data.build_types_uu_id).first()
|
||||
sites_id = BuildSites.query.filter(BuildSites.uu_id == data.site_uu_id).first()
|
||||
|
||||
build = Build.query.filter(Build.uu_id == uu_id).first()
|
||||
build.update(**data_dict, address_id=getattr(address_id, "id", None), build_types_id=getattr(build_types_id, "id", None), site_id=getattr(sites_id, "id", None))
|
||||
build.save()
|
||||
return {
|
||||
"message": "MSG0002-UPDATE",
|
||||
"data": None,
|
||||
|
|
@ -108,6 +125,10 @@ SuperBuildUpdateEvent.event_callable = super_build_update_callable
|
|||
|
||||
|
||||
def super_build_delete_callable(uu_id: str, headers: CommonHeaders):
|
||||
with Build.new_session() as db_session:
|
||||
Build.set_session(db_session)
|
||||
build = Build.query.filter(Build.uu_id == uu_id).first()
|
||||
build.delete()
|
||||
return {
|
||||
"message": "MSG0003-DELETE",
|
||||
"data": None,
|
||||
|
|
|
|||
|
|
@ -89,10 +89,15 @@ class TokenProvider:
|
|||
"""
|
||||
Retrieve event code from the token object or list of token objects.
|
||||
"""
|
||||
if isinstance(token, EmployeeTokenObject):
|
||||
if event_codes := token.selected_company.reachable_event_codes.get(endpoint_code, None):
|
||||
return event_codes
|
||||
elif isinstance(token, OccupantTokenObject):
|
||||
if event_codes := token.selected_occupant.reachable_event_codes.get(endpoint_code, None):
|
||||
return event_codes
|
||||
if token.is_employee and token.selected_company:
|
||||
employee_uu_id = token.selected_company.get("uu_id", None)
|
||||
print("endpoint_code", endpoint_code)
|
||||
print("employee_uu_id", employee_uu_id)
|
||||
if reachable_event_codes_dict := token.reachable_event_codes:
|
||||
print("reachable_event_codes_dict", reachable_event_codes_dict.get(employee_uu_id, {}))
|
||||
return reachable_event_codes_dict.get(employee_uu_id, {}).get(endpoint_code, None)
|
||||
elif token.is_occupant and token.selected_occupant:
|
||||
occupant_uu_id = token.selected_occupant.get("build_living_space_uu_id", None)
|
||||
if reachable_event_codes_dict := token.reachable_event_codes:
|
||||
return reachable_event_codes_dict.get(occupant_uu_id, {}).get(endpoint_code, None)
|
||||
raise ValueError("Invalid token type or no event code found.")
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
console.log(
|
||||
`GET /api/test/list - Returning ${paginatedData.length} items (page ${page}/${meta.totalPages})`
|
||||
);
|
||||
|
||||
// Return paginated response with the structure that useTableData expects
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
|
|
@ -76,10 +72,6 @@ export async function POST(request: Request) {
|
|||
|
||||
const { paginatedData, meta } = getPaginatedData(page, size);
|
||||
|
||||
console.log(
|
||||
`POST /api/test/list - Returning ${paginatedData.length} items (page ${page}/${meta.totalPages})`
|
||||
);
|
||||
|
||||
// Return paginated response with the structure that useTableData expects
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
|
|
|
|||
|
|
@ -32,9 +32,6 @@ export async function PATCH(request: Request) {
|
|||
};
|
||||
|
||||
globalData[itemIndex] = updatedItem;
|
||||
|
||||
console.log(`PATCH /api/test/update - Updated item with id: ${updatedItem.id}`);
|
||||
|
||||
return NextResponse.json({
|
||||
status: 200,
|
||||
data: updatedItem,
|
||||
|
|
|
|||
|
|
@ -126,7 +126,6 @@ const DataTable: React.FC<DataTableProps> = React.memo(({
|
|||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
console.log('Rendering header:', header.id);
|
||||
return (
|
||||
<th
|
||||
key={header.id}
|
||||
|
|
@ -156,7 +155,6 @@ const DataTable: React.FC<DataTableProps> = React.memo(({
|
|||
table.getRowModel().rows.map((row) => (
|
||||
<tr key={row.id} className="hover:bg-gray-50">
|
||||
{row.getVisibleCells().map((cell) => {
|
||||
console.log('Rendering cell:', cell.column.id);
|
||||
return (
|
||||
<td key={cell.id} className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
|
|
@ -371,14 +369,12 @@ const TableCardComponentImproved: React.FC<DashboardPageProps> = React.memo((pro
|
|||
renderPageOptions,
|
||||
} = useTableData({ apiUrl: `${API_BASE_URL}/test/list` });
|
||||
const activePageUrl = props.activePageUrl || '';
|
||||
console.log("activePageUrl", activePageUrl);
|
||||
|
||||
// Function to handle editing a row
|
||||
const handleEditRow = async (row: TableDataItem) => {
|
||||
try {
|
||||
// Store the row data in the cache
|
||||
await setCacheData(`${activePageUrl}/update`, row);
|
||||
console.log('Row data stored in cache:', row);
|
||||
|
||||
// Navigate to the update form
|
||||
router.push(`/panel/${activePageUrl}/update`);
|
||||
|
|
@ -387,30 +383,28 @@ const TableCardComponentImproved: React.FC<DashboardPageProps> = React.memo((pro
|
|||
}
|
||||
};
|
||||
|
||||
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<TableDataItem>();
|
||||
// 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
|
||||
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>
|
||||
);
|
||||
}
|
||||
cell: (info) => { return (actionButtonGroup(info)) }
|
||||
}),
|
||||
columnHelper.accessor('name', {
|
||||
cell: info => info.getValue(),
|
||||
|
|
@ -465,12 +459,8 @@ const TableCardComponentImproved: React.FC<DashboardPageProps> = React.memo((pro
|
|||
getPaginationRowModel: getPaginationRowModel(),
|
||||
manualPagination: true,
|
||||
pageCount: apiPagination.totalPages || 1,
|
||||
debugTable: true,
|
||||
});
|
||||
|
||||
// Check if actions column is included
|
||||
const actionColumnExists = table.getVisibleLeafColumns().some(col => col.id === 'actions');
|
||||
|
||||
return (
|
||||
isLoading ? <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">
|
||||
|
|
|
|||
|
|
@ -72,16 +72,9 @@ function CreateFromComponentBase({
|
|||
const fetchCacheDirectly = async () => {
|
||||
if (activePageUrl && !cacheLoaded) {
|
||||
try {
|
||||
console.log("Directly fetching cache for URL:", activePageUrl);
|
||||
|
||||
// Directly fetch cache data using the imported function
|
||||
const cachedData = await getCacheData(activePageUrl);
|
||||
console.log("Directly fetched cache data:", cachedData);
|
||||
|
||||
if (cachedData) {
|
||||
const formValues = form.getValues();
|
||||
|
||||
// Create a merged data object with proper typing
|
||||
const mergedData: FormValues = {
|
||||
name: cachedData.name || formValues.name || "",
|
||||
email: cachedData.email || formValues.email || "",
|
||||
|
|
@ -92,20 +85,12 @@ function CreateFromComponentBase({
|
|||
terms: cachedData.terms ?? formValues.terms ?? false,
|
||||
attachments: cachedData.attachments || formValues.attachments || ""
|
||||
};
|
||||
|
||||
console.log("Setting form with direct cache data:", mergedData);
|
||||
form.reset(mergedData);
|
||||
}
|
||||
|
||||
setCacheLoaded(true);
|
||||
|
||||
// Also call the context refresh if available (for state consistency)
|
||||
if (refreshCache) {
|
||||
refreshCache(activePageUrl);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching cache directly:", error);
|
||||
}
|
||||
if (refreshCache) { refreshCache(activePageUrl) }
|
||||
} catch (error) { console.error("Error fetching cache directly:", error) }
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -117,24 +102,13 @@ function CreateFromComponentBase({
|
|||
// Only update if the value is not empty
|
||||
if (value && activePageUrl) {
|
||||
try {
|
||||
// Get current form values
|
||||
const currentValues = form.getValues();
|
||||
|
||||
// Prepare updated data
|
||||
const updatedData = {
|
||||
...currentValues,
|
||||
[fieldName]: value
|
||||
};
|
||||
|
||||
console.log("Directly updating cache with:", updatedData);
|
||||
|
||||
// Directly update cache using imported function
|
||||
await setCacheData(activePageUrl, updatedData);
|
||||
|
||||
// Also use the context method if available (for state consistency)
|
||||
if (updateCache) {
|
||||
updateCache(activePageUrl, updatedData);
|
||||
}
|
||||
if (updateCache) { updateCache(activePageUrl, updatedData) }
|
||||
} catch (error) {
|
||||
console.error("Error updating cache:", error);
|
||||
}
|
||||
|
|
@ -143,14 +117,8 @@ function CreateFromComponentBase({
|
|||
|
||||
// Type-safe submit handler
|
||||
const onSubmit = async (data: FormValues) => {
|
||||
console.log("Form submitted with data:", data);
|
||||
|
||||
try {
|
||||
// Submit form data to API
|
||||
const response = await apiPostFetcher<any>({ url: "/tst/create", isNoCache: true, body: data });
|
||||
console.log("API response:", response);
|
||||
|
||||
// Clear cache on successful submission
|
||||
if (activePageUrl) {
|
||||
try {
|
||||
console.log("Directly clearing cache for URL:", activePageUrl);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const zodSchema = z.object({
|
|||
// Define the form type
|
||||
type FormValues = z.infer<typeof zodSchema>;
|
||||
|
||||
function UpdateFromComponentBase({
|
||||
function UpdateFromBuildComponentBase({
|
||||
cacheData,
|
||||
cacheLoading,
|
||||
cacheError,
|
||||
|
|
@ -72,11 +72,9 @@ function UpdateFromComponentBase({
|
|||
const fetchCacheDirectly = async () => {
|
||||
if (activePageUrl && !cacheLoaded) {
|
||||
try {
|
||||
console.log("Directly fetching cache for URL:", activePageUrl);
|
||||
|
||||
// Directly fetch cache data using the imported function
|
||||
const cachedData = await getCacheData(activePageUrl);
|
||||
console.log("Directly fetched cache data:", cachedData);
|
||||
|
||||
if (cachedData) {
|
||||
const formValues = form.getValues();
|
||||
|
|
@ -90,12 +88,6 @@ function UpdateFromComponentBase({
|
|||
const termsValue = typeof cachedData.terms === 'string'
|
||||
? cachedData.terms === 'true'
|
||||
: Boolean(cachedData.terms);
|
||||
|
||||
console.log("Raw notification value from cache:", cachedData.notifications, "type:", typeof cachedData.notifications);
|
||||
console.log("Raw terms value from cache:", cachedData.terms, "type:", typeof cachedData.terms);
|
||||
console.log("Converted notification value:", notificationsValue);
|
||||
console.log("Converted terms value:", termsValue);
|
||||
|
||||
const mergedData: FormValues = {
|
||||
name: cachedData.name || formValues.name || "",
|
||||
email: cachedData.email || formValues.email || "",
|
||||
|
|
@ -107,13 +99,9 @@ function UpdateFromComponentBase({
|
|||
attachments: cachedData.attachments || formValues.attachments || ""
|
||||
};
|
||||
|
||||
console.log("Setting form with direct cache data:", mergedData);
|
||||
form.reset(mergedData);
|
||||
}
|
||||
|
||||
setCacheLoaded(true);
|
||||
|
||||
// Also call the context refresh if available (for state consistency)
|
||||
if (refreshCache) {
|
||||
refreshCache(activePageUrl);
|
||||
}
|
||||
|
|
@ -419,7 +407,7 @@ function UpdateFromComponentBase({
|
|||
);
|
||||
}
|
||||
|
||||
export const UpdateFromComponent = withCache(UpdateFromComponentBase);
|
||||
export const UpdateFromBuildComponent = withCache(UpdateFromBuildComponentBase);
|
||||
|
||||
// Add default export to maintain compatibility with existing imports
|
||||
export default withCache(UpdateFromComponentBase);
|
||||
export default withCache(UpdateFromBuildComponentBase);
|
||||
|
|
|
|||
|
|
@ -75,14 +75,9 @@ const useContextCache = createContextHook<CacheData>({
|
|||
contextName: "cache",
|
||||
enablePeriodicRefresh: false,
|
||||
customFetch: async () => {
|
||||
// For initial load, we don't have a specific URL to fetch
|
||||
// Return an empty object as the initial state
|
||||
console.log("Initial cache fetch");
|
||||
return {};
|
||||
},
|
||||
customUpdate: async (newData: CacheData) => {
|
||||
// This won't be used directly, as we'll provide custom methods
|
||||
console.log("Cache update with:", newData);
|
||||
return true;
|
||||
},
|
||||
defaultValue: {}
|
||||
|
|
@ -112,22 +107,14 @@ export function useCache(): UseCacheResult {
|
|||
|
||||
// Normalize URL to handle encoding issues
|
||||
const normalizedUrl = url.startsWith('/') ? url : `/${url}`;
|
||||
console.log("Fetching cache for normalized URL:", normalizedUrl);
|
||||
|
||||
try {
|
||||
const urlData = await getCacheData(normalizedUrl);
|
||||
console.log("Received cache data:", urlData);
|
||||
|
||||
// Update the local state with the fetched data
|
||||
const updatedData = {
|
||||
...data,
|
||||
[normalizedUrl]: urlData
|
||||
};
|
||||
|
||||
console.log("Updating cache state with:", updatedData);
|
||||
await update(updatedData);
|
||||
|
||||
// Force a refresh to ensure the component gets the updated data
|
||||
await refresh();
|
||||
} catch (error) {
|
||||
console.error("Error refreshing cache:", error);
|
||||
|
|
@ -138,14 +125,9 @@ export function useCache(): UseCacheResult {
|
|||
const updateCache = async (url: string, urlData: any): Promise<void> => {
|
||||
// Normalize URL to handle encoding issues
|
||||
const normalizedUrl = url.startsWith('/') ? url : `/${url}`;
|
||||
console.log("Updating cache for normalized URL:", normalizedUrl);
|
||||
|
||||
try {
|
||||
const success = await setCacheData(normalizedUrl, urlData);
|
||||
console.log("Cache update success:", success);
|
||||
|
||||
if (success && data) {
|
||||
// Update local state
|
||||
await update({
|
||||
...data,
|
||||
[normalizedUrl]: urlData
|
||||
|
|
@ -162,12 +144,8 @@ export function useCache(): UseCacheResult {
|
|||
const clearCache = async (url: string): Promise<void> => {
|
||||
// Normalize URL to handle encoding issues
|
||||
const normalizedUrl = url.startsWith('/') ? url : `/${url}`;
|
||||
console.log("Clearing cache for normalized URL:", normalizedUrl);
|
||||
|
||||
try {
|
||||
const success = await clearCacheData(normalizedUrl);
|
||||
console.log("Cache clear success:", success);
|
||||
|
||||
if (success && data) {
|
||||
// Update local state
|
||||
const newData = { ...data };
|
||||
|
|
|
|||
|
|
@ -99,17 +99,11 @@ async function setContextPageOnline(
|
|||
body: JSON.stringify(setOnline),
|
||||
signal: controller.signal,
|
||||
});
|
||||
console.log("result", await result.json());
|
||||
|
||||
// Clear the timeout
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error(`HTTP error! Status: ${result.status}`);
|
||||
}
|
||||
|
||||
const data = await result.json();
|
||||
|
||||
if (data.status === 200 && data.data) {
|
||||
return data.data;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -66,14 +66,12 @@ export const OnlineProvider: FC<OnlineProviderProps> = ({ children }) => {
|
|||
const fetchOnline = useCallback(async (force = false) => {
|
||||
// Don't fetch if we already have data and it's not forced
|
||||
if (online && !force && !error) {
|
||||
console.log("Using existing online state:", online);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't retry too frequently
|
||||
const now = Date.now();
|
||||
if (!force && now - lastRetryTime < MIN_RETRY_INTERVAL) {
|
||||
console.log("Retry attempted too soon, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -82,35 +80,29 @@ export const OnlineProvider: FC<OnlineProviderProps> = ({ children }) => {
|
|||
setLastRetryTime(now);
|
||||
|
||||
try {
|
||||
console.log("Fetching online state...");
|
||||
const data = await checkContextPageOnline();
|
||||
|
||||
if (data) {
|
||||
console.log("Successfully fetched online state:", data);
|
||||
setOnline(data);
|
||||
setRetryCount(0); // Reset retry count on success
|
||||
} else {
|
||||
console.warn("No online state returned, using default");
|
||||
setOnline(DEFAULT_ONLINE_STATE);
|
||||
setError("Could not retrieve online state, using default values");
|
||||
|
||||
// Auto-retry if under the limit
|
||||
if (retryCount < MAX_AUTO_RETRIES) {
|
||||
setRetryCount(prev => prev + 1);
|
||||
console.log(`Scheduling retry ${retryCount + 1}/${MAX_AUTO_RETRIES}...`);
|
||||
setTimeout(() => fetchOnline(true), MIN_RETRY_INTERVAL);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
console.error("Error fetching online state:", errorMessage);
|
||||
setError(`Failed to fetch online state: ${errorMessage}`);
|
||||
setOnline(DEFAULT_ONLINE_STATE); // Use default as fallback
|
||||
|
||||
// Auto-retry if under the limit
|
||||
if (retryCount < MAX_AUTO_RETRIES) {
|
||||
setRetryCount(prev => prev + 1);
|
||||
console.log(`Scheduling retry ${retryCount + 1}/${MAX_AUTO_RETRIES}...`);
|
||||
setTimeout(() => fetchOnline(true), MIN_RETRY_INTERVAL);
|
||||
}
|
||||
} finally {
|
||||
|
|
@ -120,26 +112,20 @@ export const OnlineProvider: FC<OnlineProviderProps> = ({ children }) => {
|
|||
|
||||
// Manual retry function that can be called from components
|
||||
const retryFetch = useCallback(async () => {
|
||||
console.log("Manual retry requested");
|
||||
setRetryCount(0); // Reset retry count for manual retry
|
||||
await fetchOnline(true);
|
||||
}, [fetchOnline]);
|
||||
|
||||
// Fetch online state on component mount
|
||||
useEffect(() => {
|
||||
console.log("OnlineProvider mounted, fetching initial data");
|
||||
|
||||
// Always fetch data on mount
|
||||
fetchOnline();
|
||||
|
||||
// Set up periodic refresh (every 5 minutes)
|
||||
const refreshInterval = setInterval(() => {
|
||||
console.log("Performing periodic refresh of online state");
|
||||
fetchOnline(true);
|
||||
}, 5 * 60 * 1000);
|
||||
|
||||
return () => {
|
||||
console.log("OnlineProvider unmounted, clearing interval");
|
||||
clearInterval(refreshInterval);
|
||||
};
|
||||
}, [fetchOnline]);
|
||||
|
|
@ -150,42 +136,17 @@ export const OnlineProvider: FC<OnlineProviderProps> = ({ children }) => {
|
|||
setError(null);
|
||||
|
||||
try {
|
||||
console.log("Updating online state:", newOnline);
|
||||
// Update Redis first
|
||||
console.log('Updating Redis...');
|
||||
await setOnlineToRedis(newOnline);
|
||||
|
||||
// Then update context API
|
||||
console.log('Updating context API...');
|
||||
await setContextPageOnline(newOnline);
|
||||
|
||||
// Finally update local state to trigger re-renders
|
||||
console.log('Updating local state...');
|
||||
setOnline(newOnline);
|
||||
|
||||
console.log('Online state updated successfully');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error updating online state:', error);
|
||||
|
||||
// Still update local state to maintain UI consistency
|
||||
// even if the backend updates failed
|
||||
setOnline(newOnline);
|
||||
|
||||
return false;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Add debug logging for provider state
|
||||
useEffect(() => {
|
||||
console.log('OnlineProvider state updated:', {
|
||||
online: online ? 'present' : 'not present',
|
||||
isLoading
|
||||
});
|
||||
}, [online, isLoading]);
|
||||
|
||||
}
|
||||
return (
|
||||
<OnlineContext.Provider value={{ online, updateOnline, isLoading, error, retryFetch }}>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -34,14 +34,9 @@ const DEFAULT_USER_STATE: ClientUser = {
|
|||
// Client-side fetch function that uses the server-side implementation
|
||||
async function checkContextDashUserInfo(): Promise<ClientUser> {
|
||||
try {
|
||||
console.log("Fetching user data using server-side function");
|
||||
|
||||
// First try to use the server-side implementation
|
||||
try {
|
||||
const serverData = await getUserFromServer();
|
||||
// If we got valid data from the server, return it
|
||||
if (serverData && serverData.uuid) {
|
||||
// Check if we have a real user (not the default)
|
||||
if (
|
||||
serverData.uuid !== "default-user-id" ||
|
||||
serverData.email ||
|
||||
|
|
@ -49,30 +44,12 @@ async function checkContextDashUserInfo(): Promise<ClientUser> {
|
|||
(serverData.person.firstname !== "Guest" ||
|
||||
serverData.person.surname !== "User"))
|
||||
) {
|
||||
console.log("Valid user data found from server");
|
||||
return serverData;
|
||||
} else {
|
||||
console.log(
|
||||
"Default user data returned from server, falling back to client-side"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.warn("Invalid user data structure from server");
|
||||
}
|
||||
} catch (serverError) {
|
||||
console.warn(
|
||||
"Error using server-side user data fetch, falling back to client-side:",
|
||||
serverError
|
||||
);
|
||||
// Continue to client-side implementation
|
||||
}
|
||||
} catch (serverError) {}
|
||||
|
||||
// Fall back to client-side implementation
|
||||
console.log(
|
||||
`Falling back to client-side fetch: ${API_BASE_URL}/context/dash/user`
|
||||
);
|
||||
|
||||
// Create an AbortController to handle timeouts
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
|
||||
|
||||
|
|
@ -94,22 +71,14 @@ async function checkContextDashUserInfo(): Promise<ClientUser> {
|
|||
}
|
||||
|
||||
const data = await result.json();
|
||||
console.log("User data API response:", data);
|
||||
|
||||
// Handle different response formats
|
||||
if (data.status === 200 && data.data) {
|
||||
// Standard API response format
|
||||
return data.data;
|
||||
} else if (data.user) {
|
||||
// Direct Redis object format
|
||||
console.log("Found user data in Redis format");
|
||||
return data.user;
|
||||
} else if (data.uuid) {
|
||||
// Direct user object format
|
||||
console.log("Found direct user data format");
|
||||
return data;
|
||||
} else {
|
||||
console.warn("Invalid response format from user API");
|
||||
return DEFAULT_USER_STATE;
|
||||
}
|
||||
} catch (fetchError) {
|
||||
|
|
@ -129,11 +98,6 @@ async function checkContextDashUserInfo(): Promise<ClientUser> {
|
|||
throw fetchError;
|
||||
}
|
||||
} catch (error) {
|
||||
// Handle all other errors
|
||||
console.error(
|
||||
"Error fetching user data:",
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
);
|
||||
return DEFAULT_USER_STATE;
|
||||
}
|
||||
}
|
||||
|
|
@ -144,29 +108,12 @@ async function setContextDashUserInfo({
|
|||
userSet: ClientUser;
|
||||
}): Promise<boolean> {
|
||||
try {
|
||||
console.log("Setting user data using server-side function");
|
||||
|
||||
// First try to use the server-side implementation
|
||||
try {
|
||||
const success = await setUserFromServer(userSet);
|
||||
if (success) {
|
||||
console.log(
|
||||
"Successfully updated user data using server-side function"
|
||||
);
|
||||
return true;
|
||||
}
|
||||
} catch (serverError) {
|
||||
console.warn(
|
||||
"Error using server-side user data update, falling back to client-side:",
|
||||
serverError
|
||||
);
|
||||
// Continue to client-side implementation
|
||||
}
|
||||
|
||||
// Fall back to client-side implementation
|
||||
console.log(
|
||||
`Falling back to client-side update: ${API_BASE_URL}/context/dash/user`
|
||||
);
|
||||
} catch (serverError) {}
|
||||
|
||||
// Create an AbortController to handle timeouts
|
||||
const controller = new AbortController();
|
||||
|
|
@ -182,28 +129,18 @@ async function setContextDashUserInfo({
|
|||
body: JSON.stringify(userSet),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
// Clear the timeout
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error(`HTTP error! Status: ${result.status}`);
|
||||
}
|
||||
|
||||
const data = await result.json();
|
||||
console.log("Update user data API response:", data);
|
||||
|
||||
return data.status === 200;
|
||||
} catch (fetchError) {
|
||||
// Clear the timeout if it hasn't fired yet
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
// Check if this is an abort error (timeout)
|
||||
if (
|
||||
fetchError instanceof DOMException &&
|
||||
fetchError.name === "AbortError"
|
||||
) {
|
||||
console.warn("Request timed out or was aborted");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -211,10 +148,6 @@ async function setContextDashUserInfo({
|
|||
throw fetchError;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Error setting user data:",
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
// Log provider initialization for debugging
|
||||
React.useEffect(() => {
|
||||
console.log('ClientProviders initialized');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<OnlineProvider>
|
||||
<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 urlBuildingsList = `${baseUrlBuilding}/builds/list`;
|
||||
const urlBuildingsCreate = `${baseUrlBuilding}/builds/create`;
|
||||
const urlBuildingsUpdate = `${baseUrlBuilding}/builds/update`;
|
||||
|
||||
const urlPartsList = `${baseUrlBuilding}/parts/list`;
|
||||
const urlPartsCreate = `${baseUrlBuilding}/parts/create`;
|
||||
const urlPartsUpdate = `${baseUrlBuilding}/parts/update`;
|
||||
|
||||
export {
|
||||
urlCheckToken,
|
||||
urlPageValid,
|
||||
|
|
@ -38,6 +46,12 @@ export {
|
|||
urlLoginEndpoint,
|
||||
urlLoginSelectEndpoint,
|
||||
urlLogoutEndpoint,
|
||||
urlBuildingsList,
|
||||
urlBuildingsCreate,
|
||||
urlBuildingsUpdate,
|
||||
urlPartsList,
|
||||
urlPartsCreate,
|
||||
urlPartsUpdate,
|
||||
// For test use only
|
||||
urlTesterList,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,4 +21,12 @@ interface CookieObject {
|
|||
priority: string;
|
||||
}
|
||||
|
||||
export type { HttpMethod, ApiResponse, FetchOptions, CookieObject };
|
||||
interface Pagination {
|
||||
page?: number;
|
||||
size?: number;
|
||||
orderField?: string[];
|
||||
orderType?: string[];
|
||||
query?: Record<string, any>;
|
||||
}
|
||||
|
||||
export type { HttpMethod, ApiResponse, FetchOptions, CookieObject, Pagination };
|
||||
|
|
|
|||
|
|
@ -116,7 +116,6 @@ export function useTableData({
|
|||
next: false,
|
||||
back: false,
|
||||
});
|
||||
console.log("apiPagination", apiPagination);
|
||||
|
||||
// Watch for form value changes
|
||||
const page = form.watch(pageField as Path<TableFormValues>);
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ const menuTranslationEn = {
|
|||
"/build": [
|
||||
{ value: "Build", key: "build" },
|
||||
{ value: "Build", key: "build" },
|
||||
{ value: "Build", key: "build" },
|
||||
{ value: "Buildings", key: "build" },
|
||||
],
|
||||
"/build/parts": [
|
||||
{ value: "Build", key: "build" },
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ const menuTranslationTr = {
|
|||
"/build": [
|
||||
{ value: "Bina", key: "build" },
|
||||
{ value: "Bina", key: "build" },
|
||||
{ value: "Bina", key: "build" },
|
||||
{ value: "Binalar", key: "build" },
|
||||
],
|
||||
"/build/parts": [
|
||||
{ value: "Bina", key: "build" },
|
||||
|
|
|
|||
|
|
@ -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 { DPage } from "@/components/custom/content/DPage";
|
||||
import TableCardComponentImproved from "@/components/custom/content/TableCardComponentImproved";
|
||||
import CreateFromComponent from "@/components/custom/content/createFromComponent";
|
||||
import UpdateFromComponent from "@/components/custom/content/updateFromComponent";
|
||||
import BuildListPage from "./builds/superuser/ListPage";
|
||||
import CreateFromBuildComponent from "./builds/superuser/CreatePage";
|
||||
import UpdateFromBuildComponent from "./builds/superuser/UpdatePage";
|
||||
import BuildPartsListPage from "./buildParts/superuser/ListPage";
|
||||
import CreateFromBuildPartsComponent from "./buildParts/superuser/CreatePage";
|
||||
import UpdateFromBuildPartsComponent from "./buildParts/superuser/UpdatePage";
|
||||
|
||||
const pageIndexMulti: Record<string, Record<string, React.FC<any>>> = {
|
||||
"/dashboard": { DashboardPage: TableCardComponentImproved },
|
||||
"/build": { DashboardPage: TableCardComponentImproved },
|
||||
"/build/create": { DashboardPage: CreateFromComponent },
|
||||
"/build/update": { DashboardPage: UpdateFromComponent },
|
||||
"/build": { DashboardPage: BuildListPage },
|
||||
"/build/create": { DashboardPage: CreateFromBuildComponent },
|
||||
"/build/update": { DashboardPage: UpdateFromBuildComponent },
|
||||
"/build/parts": { DashboardPage: BuildPartsListPage },
|
||||
"/build/parts/create": { DashboardPage: CreateFromBuildPartsComponent },
|
||||
"/build/parts/update": { DashboardPage: UpdateFromBuildPartsComponent },
|
||||
"/people": { DashboardPage: DPage },
|
||||
"/people/create": { DashboardPage: DPage },
|
||||
"/people/update": { DashboardPage: DPage },
|
||||
|
|
|
|||
|
|
@ -1,71 +1,81 @@
|
|||
import { z } from 'zod';
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* Zod schema for BuildTypes
|
||||
* Corresponds to the BuildTypes class in Python
|
||||
*/
|
||||
export const buildTypesSchema = z.object({
|
||||
function_code: z.string().max(12).default('').describe('Function Code'),
|
||||
type_code: z.string().max(12).default('').describe('Structure Type Code'),
|
||||
lang: z.string().max(4).default('TR').describe('Language'),
|
||||
type_name: z.string().max(48).default('').describe('Type Name'),
|
||||
function_code: z.string().max(12).default("").describe("Function Code"),
|
||||
type_code: z.string().max(12).default("").describe("Structure Type Code"),
|
||||
lang: z.string().max(4).default("TR").describe("Language"),
|
||||
type_name: z.string().max(48).default("").describe("Type Name"),
|
||||
});
|
||||
|
||||
export type BuildTypes = z.infer<typeof buildTypesSchema>;
|
||||
|
||||
/**
|
||||
* Zod schema for Part2Employee
|
||||
* Corresponds to the Part2Employee class in Python
|
||||
*/
|
||||
export const part2EmployeeSchema = z.object({
|
||||
build_id: z.number().int().describe('Building ID'),
|
||||
part_id: z.number().int().describe('Part ID'),
|
||||
employee_id: z.number().int().describe('Employee ID'),
|
||||
});
|
||||
// /**
|
||||
// * Zod schema for Part2Employee
|
||||
// * Corresponds to the Part2Employee class in Python
|
||||
// */
|
||||
// export const part2EmployeeSchema = z.object({
|
||||
// build_uu_id: z.string().describe("Building UUID"),
|
||||
// part_uu_id: z.string().describe("Part UUID"),
|
||||
// employee_uu_id: z.string().describe("Employee UUID"),
|
||||
// });
|
||||
|
||||
export type Part2Employee = z.infer<typeof part2EmployeeSchema>;
|
||||
// export type Part2Employee = z.infer<typeof part2EmployeeSchema>;
|
||||
|
||||
/**
|
||||
* Zod schema for RelationshipEmployee2Build
|
||||
* Corresponds to the RelationshipEmployee2Build class in Python
|
||||
*/
|
||||
export const relationshipEmployee2BuildSchema = z.object({
|
||||
company_id: z.number().int(),
|
||||
employee_id: z.number().int(),
|
||||
member_id: z.number().int(),
|
||||
relationship_type: z.string().default('Employee'),
|
||||
show_only: z.boolean().default(false),
|
||||
});
|
||||
// /**
|
||||
// * Zod schema for RelationshipEmployee2Build
|
||||
// * Corresponds to the RelationshipEmployee2Build class in Python
|
||||
// */
|
||||
// export const relationshipEmployee2BuildSchema = z.object({
|
||||
// company_id: z.number().int(),
|
||||
// employee_id: z.number().int(),
|
||||
// member_id: z.number().int(),
|
||||
// relationship_type: z.string().default("Employee"),
|
||||
// show_only: z.boolean().default(false),
|
||||
// });
|
||||
|
||||
export type RelationshipEmployee2Build = z.infer<typeof relationshipEmployee2BuildSchema>;
|
||||
// export type RelationshipEmployee2Build = z.infer<
|
||||
// typeof relationshipEmployee2BuildSchema
|
||||
// >;
|
||||
|
||||
/**
|
||||
* Zod schema for Build
|
||||
* Corresponds to the Build class in Python
|
||||
*/
|
||||
export const buildSchema = z.object({
|
||||
gov_address_code: z.string().default(''),
|
||||
build_name: z.string().describe('Building Name'),
|
||||
build_no: z.string().max(8).describe('Building Number'),
|
||||
max_floor: z.number().int().default(1).describe('Max Floor'),
|
||||
underground_floor: z.number().int().default(0).describe('Underground Floor'),
|
||||
build_date: z.string().datetime().default('1900-01-01T00:00:00Z'),
|
||||
decision_period_date: z.string().datetime().default('1900-01-01T00:00:00Z').describe('Building annual ordinary meeting period'),
|
||||
tax_no: z.string().max(24).default(''),
|
||||
gov_address_code: z.string().default(""),
|
||||
build_name: z.string().describe("Building Name"),
|
||||
build_no: z.string().max(8).describe("Building Number"),
|
||||
max_floor: z.number().int().default(1).describe("Max Floor"),
|
||||
underground_floor: z.number().int().default(0).describe("Underground Floor"),
|
||||
build_date: z.string().datetime().default("1900-01-01T00:00:00Z"),
|
||||
decision_period_date: z
|
||||
.string()
|
||||
.datetime()
|
||||
.default("1900-01-01T00:00:00Z")
|
||||
.describe("Building annual ordinary meeting period"),
|
||||
tax_no: z.string().max(24).default(""),
|
||||
lift_count: z.number().int().default(0),
|
||||
heating_system: z.boolean().default(true),
|
||||
cooling_system: z.boolean().default(false),
|
||||
hot_water_system: z.boolean().default(false),
|
||||
block_service_man_count: z.number().int().default(0),
|
||||
security_service_man_count: z.number().int().default(0),
|
||||
garage_count: z.number().int().default(0).describe('Garage Count'),
|
||||
management_room_id: z.number().int().nullable().describe('Management Room ID'),
|
||||
garage_count: z.number().int().default(0).describe("Garage Count"),
|
||||
management_room_id: z
|
||||
.number()
|
||||
.int()
|
||||
.nullable()
|
||||
.describe("Management Room ID"),
|
||||
site_id: z.number().int().nullable(),
|
||||
site_uu_id: z.string().nullable().describe('Site UUID'),
|
||||
site_uu_id: z.string().nullable().describe("Site UUID"),
|
||||
address_id: z.number().int(),
|
||||
address_uu_id: z.string().describe('Address UUID'),
|
||||
build_types_id: z.number().int().describe('Building Type'),
|
||||
build_types_uu_id: z.string().describe('Building Type UUID'),
|
||||
address_uu_id: z.string().describe("Address UUID"),
|
||||
build_types_id: z.number().int().describe("Building Type"),
|
||||
build_types_uu_id: z.string().describe("Building Type UUID"),
|
||||
});
|
||||
|
||||
export type Build = z.infer<typeof buildSchema>;
|
||||
|
|
@ -75,21 +85,18 @@ export type Build = z.infer<typeof buildSchema>;
|
|||
* Corresponds to the BuildParts class in Python
|
||||
*/
|
||||
export const buildPartsSchema = z.object({
|
||||
address_gov_code: z.string().describe('Goverment Door Code'),
|
||||
part_no: z.number().int().default(0).describe('Part Number'),
|
||||
part_level: z.number().int().default(0).describe('Building Part Level'),
|
||||
part_code: z.string().default('').describe('Part Code'),
|
||||
part_gross_size: z.number().int().default(0).describe('Part Gross Size'),
|
||||
part_net_size: z.number().int().default(0).describe('Part Net Size'),
|
||||
default_accessory: z.string().default('0').describe('Default Accessory'),
|
||||
human_livable: z.boolean().default(true).describe('Human Livable'),
|
||||
due_part_key: z.string().default('').describe('Constant Payment Group'),
|
||||
build_id: z.number().int().describe('Building ID'),
|
||||
build_uu_id: z.string().describe('Building UUID'),
|
||||
part_direction_id: z.number().int().nullable(),
|
||||
part_direction_uu_id: z.string().nullable().describe('Part Direction UUID'),
|
||||
part_type_id: z.number().int().describe('Building Part Type'),
|
||||
part_type_uu_id: z.string().describe('Building Part Type UUID'),
|
||||
address_gov_code: z.string().describe("Goverment Door Code"),
|
||||
part_no: z.number().int().default(0).describe("Part Number"),
|
||||
part_level: z.number().int().default(0).describe("Building Part Level"),
|
||||
part_code: z.string().default("").describe("Part Code"),
|
||||
part_gross_size: z.number().int().default(0).describe("Part Gross Size"),
|
||||
part_net_size: z.number().int().default(0).describe("Part Net Size"),
|
||||
default_accessory: z.string().default("0").describe("Default Accessory"),
|
||||
human_livable: z.boolean().default(true).describe("Human Livable"),
|
||||
due_part_key: z.string().default("").describe("Constant Payment Group"),
|
||||
build_uu_id: z.string().describe("Building UUID"),
|
||||
part_direction_uu_id: z.string().nullable().describe("Part Direction UUID"),
|
||||
part_type_uu_id: z.string().describe("Building Part Type UUID"),
|
||||
});
|
||||
|
||||
export type BuildParts = z.infer<typeof buildPartsSchema>;
|
||||
|
|
@ -99,17 +106,20 @@ export type BuildParts = z.infer<typeof buildPartsSchema>;
|
|||
* Corresponds to the BuildLivingSpace class in Python
|
||||
*/
|
||||
export const buildLivingSpaceSchema = z.object({
|
||||
fix_value: z.number().default(0).describe('Fixed value is deducted from debit.'),
|
||||
fix_percent: z.number().default(0).describe('Fixed percent is deducted from debit.'),
|
||||
agreement_no: z.string().default('').describe('Agreement No'),
|
||||
fix_value: z
|
||||
.number()
|
||||
.default(0)
|
||||
.describe("Fixed value is deducted from debit."),
|
||||
fix_percent: z
|
||||
.number()
|
||||
.default(0)
|
||||
.describe("Fixed percent is deducted from debit."),
|
||||
agreement_no: z.string().default("").describe("Agreement No"),
|
||||
marketing_process: z.boolean().default(false),
|
||||
marketing_layer: z.number().int().default(0),
|
||||
build_parts_id: z.number().int().describe('Build Part ID'),
|
||||
build_parts_uu_id: z.string().describe('Build Part UUID'),
|
||||
person_id: z.number().int().describe('Responsible People ID'),
|
||||
person_uu_id: z.string().describe('Responsible People UUID'),
|
||||
occupant_type_id: z.number().int().describe('Occupant Type'),
|
||||
occupant_type_uu_id: z.string().describe('Occupant Type UUID'),
|
||||
build_parts_uu_id: z.string().describe("Build Part UUID"),
|
||||
person_uu_id: z.string().describe("Responsible People UUID"),
|
||||
occupant_type_uu_id: z.string().describe("Occupant Type UUID"),
|
||||
});
|
||||
|
||||
export type BuildLivingSpace = z.infer<typeof buildLivingSpaceSchema>;
|
||||
|
|
@ -122,12 +132,10 @@ export const buildManagementSchema = z.object({
|
|||
discounted_percentage: z.number().default(0),
|
||||
discounted_price: z.number().default(0),
|
||||
calculated_price: z.number().default(0),
|
||||
occupant_type: z.number().int().describe('Occupant Type'),
|
||||
occupant_type_uu_id: z.string().describe('Occupant Type UUID'),
|
||||
build_id: z.number().int().describe('Building ID'),
|
||||
build_uu_id: z.string().describe('Building UUID'),
|
||||
build_parts_id: z.number().int().describe('Build Part ID'),
|
||||
build_parts_uu_id: z.string().describe('Build Part UUID'),
|
||||
// occupant_type: z.number().int().describe("Occupant Type"),
|
||||
occupant_type_uu_id: z.string().describe("Occupant Type UUID"),
|
||||
build_uu_id: z.string().describe("Building UUID"),
|
||||
build_parts_uu_id: z.string().describe("Build Part UUID"),
|
||||
});
|
||||
|
||||
export type BuildManagement = z.infer<typeof buildManagementSchema>;
|
||||
|
|
@ -137,18 +145,16 @@ export type BuildManagement = z.infer<typeof buildManagementSchema>;
|
|||
* Corresponds to the BuildArea class in Python
|
||||
*/
|
||||
export const buildAreaSchema = z.object({
|
||||
area_name: z.string().default(''),
|
||||
area_code: z.string().default(''),
|
||||
area_type: z.string().default('GREEN'),
|
||||
area_direction: z.string().max(2).default('NN'),
|
||||
area_name: z.string().default(""),
|
||||
area_code: z.string().default(""),
|
||||
area_type: z.string().default("GREEN"),
|
||||
area_direction: z.string().max(2).default("NN"),
|
||||
area_gross_size: z.number().default(0),
|
||||
area_net_size: z.number().default(0),
|
||||
width: z.number().int().default(0),
|
||||
size: z.number().int().default(0),
|
||||
build_id: z.number().int(),
|
||||
build_uu_id: z.string().describe('Building UUID'),
|
||||
part_type_id: z.number().int().nullable().describe('Building Part Type'),
|
||||
part_type_uu_id: z.string().nullable().describe('Building Part Type UUID'),
|
||||
build_uu_id: z.string().describe("Building UUID"),
|
||||
part_type_uu_id: z.string().nullable().describe("Building Part Type UUID"),
|
||||
});
|
||||
|
||||
export type BuildArea = z.infer<typeof buildAreaSchema>;
|
||||
|
|
@ -160,8 +166,7 @@ export type BuildArea = z.infer<typeof buildAreaSchema>;
|
|||
export const buildSitesSchema = z.object({
|
||||
site_name: z.string().max(24),
|
||||
site_no: z.string().max(8),
|
||||
address_id: z.number().int(),
|
||||
address_uu_id: z.string().describe('Address UUID'),
|
||||
address_uu_id: z.string().describe("Address UUID"),
|
||||
});
|
||||
|
||||
export type BuildSites = z.infer<typeof buildSitesSchema>;
|
||||
|
|
@ -171,28 +176,23 @@ export type BuildSites = z.infer<typeof buildSitesSchema>;
|
|||
* Corresponds to the BuildCompaniesProviding class in Python
|
||||
*/
|
||||
export const buildCompaniesProvidingSchema = z.object({
|
||||
build_id: z.number().int().describe('Building ID'),
|
||||
build_uu_id: z.string().nullable().describe('Providing UUID'),
|
||||
company_id: z.number().int(),
|
||||
company_uu_id: z.string().nullable().describe('Providing UUID'),
|
||||
provide_id: z.number().int().nullable(),
|
||||
provide_uu_id: z.string().nullable().describe('Providing UUID'),
|
||||
contract_id: z.number().int().nullable(),
|
||||
build_uu_id: z.string().nullable().describe("Providing UUID"),
|
||||
company_uu_id: z.string().nullable().describe("Providing UUID"),
|
||||
provide_uu_id: z.string().nullable().describe("Providing UUID"),
|
||||
});
|
||||
|
||||
export type BuildCompaniesProviding = z.infer<typeof buildCompaniesProvidingSchema>;
|
||||
export type BuildCompaniesProviding = z.infer<
|
||||
typeof buildCompaniesProvidingSchema
|
||||
>;
|
||||
|
||||
/**
|
||||
* Zod schema for BuildPersonProviding
|
||||
* Corresponds to the BuildPersonProviding class in Python
|
||||
*/
|
||||
export const buildPersonProvidingSchema = z.object({
|
||||
build_id: z.number().int().describe('Building ID'),
|
||||
build_uu_id: z.string().nullable().describe('Providing UUID'),
|
||||
people_id: z.number().int(),
|
||||
people_uu_id: z.string().nullable().describe('People UUID'),
|
||||
provide_id: z.number().int().nullable(),
|
||||
provide_uu_id: z.string().nullable().describe('Providing UUID'),
|
||||
build_uu_id: z.string().nullable().describe("Providing UUID"),
|
||||
people_uu_id: z.string().nullable().describe("People UUID"),
|
||||
provide_uu_id: z.string().nullable().describe("Providing UUID"),
|
||||
contract_id: z.number().int().nullable(),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
selectPage: string;
|
||||
selectSize: string;
|
||||
actionButtonGroup: string;
|
||||
}
|
||||
|
||||
interface TableHeaderProps {
|
||||
|
|
|
|||
Loading…
Reference in New Issue