2 chained application designed and new stage inited

This commit is contained in:
Berkay 2025-06-24 12:41:06 +03:00
parent 311736ce06
commit a9655c5f48
45 changed files with 3090 additions and 386 deletions

View File

@ -1,4 +1,4 @@
from typing import Any from typing import Optional, Any
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from index import endpoints_index from index import endpoints_index
@ -7,11 +7,27 @@ from events.building_parts.cluster import PartsRouterCluster
from Validations.defaults.validations import CommonHeaders from Validations.defaults.validations import CommonHeaders
from Validations.response.pagination import PaginateOnly from Validations.response.pagination import PaginateOnly
from Extensions.Middlewares.token_provider import TokenProvider from Extensions.Middlewares.token_provider import TokenProvider
from pydantic import BaseModel
parts_endpoint_route = APIRouter(prefix="/parts", tags=["Parts Cluster"]) parts_endpoint_route = APIRouter(prefix="/parts", tags=["Parts Cluster"])
class PartsListRequest(BaseModel):
address_gov_code: str
build_uu_id: str = None
part_no: int
part_level: int
part_code: str
part_gross_size: int
part_net_size: int
default_accessory: str
human_livable: bool
due_part_key: Optional[str] = None
part_direction_uu_id: Optional[str] = None
part_type_uu_id: Optional[str] = None
parts_list = "PartsList" parts_list = "PartsList"
@parts_endpoint_route.post( @parts_endpoint_route.post(
path="/list", path="/list",
@ -22,7 +38,7 @@ def parts_list_route(data: PaginateOnly, headers: CommonHeaders = Depends(Common
token_object = TokenProvider.get_dict_from_redis(token=headers.token) token_object = TokenProvider.get_dict_from_redis(token=headers.token)
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object) event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict) event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
FoundCluster = PartsRouterCluster.get_event_cluster(parts_list) FoundCluster = PartsRouterCluster.get_event_cluster("PartsListEventCluster")
event_cluster_matched = FoundCluster.match_event(event_key=event_key) event_cluster_matched = FoundCluster.match_event(event_key=event_key)
return event_cluster_matched.event_callable(list_options=data, headers=headers) return event_cluster_matched.event_callable(list_options=data, headers=headers)
@ -33,11 +49,11 @@ parts_create = "PartsCreate"
description="Create part endpoint", description="Create part endpoint",
operation_id=endpoints_index[parts_create], operation_id=endpoints_index[parts_create],
) )
def parts_create_route(data, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)): def parts_create_route(data: PartsListRequest, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)):
token_object = TokenProvider.get_dict_from_redis(token=headers.token) token_object = TokenProvider.get_dict_from_redis(token=headers.token)
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object) event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict) event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
FoundCluster = PartsRouterCluster.get_event_cluster(parts_create) FoundCluster = PartsRouterCluster.get_event_cluster("PartsCreateEventCluster")
event_cluster_matched = FoundCluster.match_event(event_key=event_key) event_cluster_matched = FoundCluster.match_event(event_key=event_key)
return event_cluster_matched.event_callable(data=data, headers=headers) return event_cluster_matched.event_callable(data=data, headers=headers)
@ -48,11 +64,11 @@ parts_update = "PartsUpdate"
description="Update part endpoint", description="Update part endpoint",
operation_id=endpoints_index[parts_update], operation_id=endpoints_index[parts_update],
) )
def parts_update_route(uu_id: str, data, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)): def parts_update_route(uu_id: str, data: PartsListRequest, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)):
token_object = TokenProvider.get_dict_from_redis(token=headers.token) token_object = TokenProvider.get_dict_from_redis(token=headers.token)
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object) event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict) event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
FoundCluster = PartsRouterCluster.get_event_cluster(parts_update) FoundCluster = PartsRouterCluster.get_event_cluster("PartsUpdateEventCluster")
event_cluster_matched = FoundCluster.match_event(event_key=event_key) event_cluster_matched = FoundCluster.match_event(event_key=event_key)
return event_cluster_matched.event_callable(uu_id=uu_id, data=data, headers=headers) return event_cluster_matched.event_callable(uu_id=uu_id, data=data, headers=headers)
@ -67,6 +83,6 @@ def parts_delete_route(uu_id: str, headers: CommonHeaders = Depends(CommonHeader
token_object = TokenProvider.get_dict_from_redis(token=headers.token) token_object = TokenProvider.get_dict_from_redis(token=headers.token)
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object) event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict) event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
FoundCluster = PartsRouterCluster.get_event_cluster(parts_delete) FoundCluster = PartsRouterCluster.get_event_cluster("PartsDeleteEventCluster")
event_cluster_matched = FoundCluster.match_event(event_key=event_key) event_cluster_matched = FoundCluster.match_event(event_key=event_key)
return event_cluster_matched.event_callable(uu_id=uu_id, headers=headers) return event_cluster_matched.event_callable(uu_id=uu_id, headers=headers)

View File

@ -1,5 +1,6 @@
from typing import Any from typing import Any, Optional
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from pydantic import BaseModel
from index import endpoints_index from index import endpoints_index
from events.builds.cluster import BuildRouterCluster from events.builds.cluster import BuildRouterCluster
@ -9,6 +10,31 @@ from Validations.response.pagination import PaginateOnly
from Extensions.Middlewares.token_provider import TokenProvider from Extensions.Middlewares.token_provider import TokenProvider
# Pydantic model for build update validation
class BuildFormModel(BaseModel):
gov_address_code: str
build_name: str
build_no: str
max_floor: int
underground_floor: int
build_date: str
decision_period_date: str
tax_no: str
lift_count: int
heating_system: bool
cooling_system: bool
hot_water_system: bool
block_service_man_count: int
security_service_man_count: int
garage_count: int
site_uu_id: Optional[str] = None
address_uu_id: str
build_types_uu_id: str
class Config:
extra = "allow"
build_endpoint_route = APIRouter(prefix="/builds", tags=["Builds Cluster"]) build_endpoint_route = APIRouter(prefix="/builds", tags=["Builds Cluster"])
build_list = "BuildList" build_list = "BuildList"
@ -21,7 +47,7 @@ def build_list_route(data: PaginateOnly, headers: CommonHeaders = Depends(Common
token_object = TokenProvider.get_dict_from_redis(token=headers.token) token_object = TokenProvider.get_dict_from_redis(token=headers.token)
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object) event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict) event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
FoundCluster = BuildRouterCluster.get_event_cluster(build_list) FoundCluster = BuildRouterCluster.get_event_cluster("BuildListEventCluster")
event_cluster_matched = FoundCluster.match_event(event_key=event_key) event_cluster_matched = FoundCluster.match_event(event_key=event_key)
return event_cluster_matched.event_callable(list_options=data, headers=headers) return event_cluster_matched.event_callable(list_options=data, headers=headers)
@ -32,11 +58,11 @@ build_create = "BuildCreate"
description="Create build endpoint", description="Create build endpoint",
operation_id=endpoints_index[build_create], operation_id=endpoints_index[build_create],
) )
def build_create_route(data, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)): def build_create_route(data: BuildFormModel, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)):
token_object = TokenProvider.get_dict_from_redis(token=headers.token) token_object = TokenProvider.get_dict_from_redis(token=headers.token)
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object) event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict) event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
FoundCluster = BuildRouterCluster.get_event_cluster(build_create) FoundCluster = BuildRouterCluster.get_event_cluster("BuildCreateEventCluster")
event_cluster_matched = FoundCluster.match_event(event_key=event_key) event_cluster_matched = FoundCluster.match_event(event_key=event_key)
return event_cluster_matched.event_callable(data=data, headers=headers) return event_cluster_matched.event_callable(data=data, headers=headers)
@ -47,11 +73,11 @@ build_update = "BuildUpdate"
description="Update build endpoint", description="Update build endpoint",
operation_id=endpoints_index[build_update], operation_id=endpoints_index[build_update],
) )
def build_update_route(uu_id: str, data, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)): def build_update_route(uu_id: str, data: BuildFormModel, headers: CommonHeaders = Depends(CommonHeaders.as_dependency)):
token_object = TokenProvider.get_dict_from_redis(token=headers.token) token_object = TokenProvider.get_dict_from_redis(token=headers.token)
event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object) event_founder_dict = dict(endpoint_code=headers.operation_id, token=token_object)
event_key = TokenProvider.retrieve_event_codes(**event_founder_dict) event_key = TokenProvider.retrieve_event_codes(**event_founder_dict)
FoundCluster = BuildRouterCluster.get_event_cluster(build_update) FoundCluster = BuildRouterCluster.get_event_cluster("BuildUpdateEventCluster")
event_cluster_matched = FoundCluster.match_event(event_key=event_key) event_cluster_matched = FoundCluster.match_event(event_key=event_key)
return event_cluster_matched.event_callable(uu_id=uu_id, data=data, headers=headers) return event_cluster_matched.event_callable(uu_id=uu_id, data=data, headers=headers)

View File

@ -13,6 +13,9 @@ from Validations.defaults.validations import CommonHeaders
from Schemas import ( from Schemas import (
Build, Build,
BuildParts, BuildParts,
ApiEnumDropdown,
BuildTypes,
BuildParts,
AccountRecords, AccountRecords,
) )
@ -55,17 +58,43 @@ SuperPartsDeleteEvent = Event(
def super_parts_list_callable(list_options: PaginateOnly, headers: CommonHeaders): def super_parts_list_callable(list_options: PaginateOnly, headers: CommonHeaders):
return { list_options = PaginateOnly(**list_options.model_dump())
"message": "MSG0003-LIST", # TODO: Pydantic Model must be implemnted for list_options.query
"data": None, with Build.new_session() as db_session:
"completed": True, BuildParts.set_session(db_session)
} base_query = BuildParts.query.filter()
build_parts_records_query = base_query
if list_options.query:
build_parts_records_query = BuildParts.query.filter(*BuildParts.convert(list_options.query))
pagination = Pagination(data=build_parts_records_query, base_query=base_query)
pagination.change(**list_options.model_dump())
pagination_result = PaginationResult(data=build_parts_records_query, pagination=pagination, response_model=None)
return EndpointResponse(message="MSG0003-LIST", pagination_result=pagination_result).response
SuperPartsListEvent.event_callable = super_parts_list_callable SuperPartsListEvent.event_callable = super_parts_list_callable
def super_parts_create_callable(data, headers: CommonHeaders): def super_parts_create_callable(data, headers: CommonHeaders):
with Build.new_session() as db_session:
Build.set_session(db_session)
BuildParts.set_session(db_session)
BuildTypes.set_session(db_session)
ApiEnumDropdown.set_session(db_session)
build = Build.query.filter(Build.uu_id == data.build_uu_id).first()
part_direction = ApiEnumDropdown.query.filter(ApiEnumDropdown.uu_id == data.part_direction_uu_id).first()
part_type = BuildTypes.query.filter(BuildTypes.uu_id == data.part_type_uu_id).first()
build_parts_created = BuildParts.create(
**data.model_dump(),
build_id=getattr(build, "id", None),
part_direction_id=getattr(part_direction, "id", None),
part_type_id=getattr(part_type, "id", None)
)
build_parts_created.save()
return { return {
"message": "MSG0001-INSERT", "message": "MSG0001-INSERT",
"data": None, "data": None,
@ -77,6 +106,25 @@ SuperPartsCreateEvent.event_callable = super_parts_create_callable
def super_parts_update_callable(data, headers: CommonHeaders): def super_parts_update_callable(data, headers: CommonHeaders):
with Build.new_session() as db_session:
Build.set_session(db_session)
BuildParts.set_session(db_session)
BuildTypes.set_session(db_session)
ApiEnumDropdown.set_session(db_session)
build = Build.query.filter(Build.uu_id == data.build_uu_id).first()
part_direction = ApiEnumDropdown.query.filter(ApiEnumDropdown.uu_id == data.part_direction_uu_id).first()
part_type = BuildTypes.query.filter(BuildTypes.uu_id == data.part_type_uu_id).first()
build_parts_updated = BuildParts.query.filter(BuildParts.uu_id == data.uu_id).first()
build_parts_updated.update(
**data.model_dump(exclude_unset=True, exclude_none=True),
build_id=getattr(build, "id", None),
part_direction_id=getattr(part_direction, "id", None),
part_type_id=getattr(part_type, "id", None)
)
build_parts_updated.save()
return { return {
"message": "MSG0002-UPDATE", "message": "MSG0002-UPDATE",
"data": None, "data": None,

View File

@ -11,9 +11,13 @@ from Validations.response import (
) )
from Validations.defaults.validations import CommonHeaders from Validations.defaults.validations import CommonHeaders
from Schemas import ( from Schemas import (
Addresses,
BuildTypes,
Build, Build,
BuildSites,
BuildParts, BuildParts,
AccountRecords, Companies,
# AccountRecords,
) )
@ -53,32 +57,16 @@ SuperBuildDeleteEvent = Event(
def super_build_list_callable(list_options: PaginateOnly, headers: CommonHeaders): def super_build_list_callable(list_options: PaginateOnly, headers: CommonHeaders):
list_options = PaginateOnly(**list_options.model_dump()) list_options = PaginateOnly(**list_options.model_dump())
if token.is_employee:
raise Exception("Forbidden for employees")
# TODO: Pydantic Model must be implemnted for list_options.query # TODO: Pydantic Model must be implemnted for list_options.query
with AccountRecords.new_session() as db_session: with Build.new_session() as db_session:
AccountRecords.set_session(db_session) Build.set_session(db_session)
list_of_fields = [ base_query = Build.query.filter()
AccountRecords.iban, build_records_query = base_query
AccountRecords.bank_date,
AccountRecords.currency,
AccountRecords.currency_value,
AccountRecords.process_comment,
AccountRecords.add_comment_note,
AccountRecords.receive_debit,
AccountRecords.is_email_send,
AccountRecords.is_notification_send,
]
account_records_query = db_session.query(*list_of_fields
).join(BuildParts, BuildParts.id == AccountRecords.build_parts_id
).filter(BuildParts.id == token.selected_occupant.build_part_id)
if list_options.query: if list_options.query:
account_records_query = account_records_query.filter(*AccountRecords.convert(list_options.query)) build_records_query = Build.query.filter(*Build.convert(list_options.query))
pagination = Pagination(data=build_records_query, base_query=base_query)
pagination = Pagination(data=account_records_query)
pagination.change(**list_options.model_dump()) pagination.change(**list_options.model_dump())
pagination_result = PaginationResult(data=account_records_query, pagination=pagination) pagination_result = PaginationResult(data=build_records_query, pagination=pagination, response_model=None)
return EndpointResponse(message="MSG0003-LIST", pagination_result=pagination_result).response return EndpointResponse(message="MSG0003-LIST", pagination_result=pagination_result).response
@ -86,6 +74,19 @@ SuperBuildListEvent.event_callable = super_build_list_callable
def super_build_create_callable(data, headers: CommonHeaders): def super_build_create_callable(data, headers: CommonHeaders):
with Build.new_session() as db_session:
Build.set_session(db_session)
Addresses.set_session(db_session)
BuildTypes.set_session(db_session)
BuildSites.set_session(db_session)
address_id = Addresses.query.filter(Addresses.uu_id == data.address_uu_id).first()
build_types_id = BuildTypes.query.filter(BuildTypes.uu_id == data.build_types_uu_id).first()
sites_id = BuildSites.query.filter(BuildSites.uu_id == data.site_uu_id).first()
build = Build.create(**data.model_dump(), address_id=getattr(address_id, "id", None), build_types_id=getattr(build_types_id, "id", None), site_id=getattr(sites_id, "id", None))
build.save()
return { return {
"message": "MSG0001-INSERT", "message": "MSG0001-INSERT",
"data": None, "data": None,
@ -96,7 +97,23 @@ def super_build_create_callable(data, headers: CommonHeaders):
SuperBuildCreateEvent.event_callable = super_build_create_callable SuperBuildCreateEvent.event_callable = super_build_create_callable
def super_build_update_callable(data, headers: CommonHeaders): def super_build_update_callable(uu_id: str, data, headers: CommonHeaders):
data_dict = data.model_dump(exclude_unset=True, exclude_none=True)
data_dict.pop("uu_id", None)
data_dict.pop("uuid", None)
with Build.new_session() as db_session:
Build.set_session(db_session)
Addresses.set_session(db_session)
BuildTypes.set_session(db_session)
BuildSites.set_session(db_session)
address_id = Addresses.query.filter(Addresses.uu_id == data.address_uu_id).first()
build_types_id = BuildTypes.query.filter(BuildTypes.uu_id == data.build_types_uu_id).first()
sites_id = BuildSites.query.filter(BuildSites.uu_id == data.site_uu_id).first()
build = Build.query.filter(Build.uu_id == uu_id).first()
build.update(**data_dict, address_id=getattr(address_id, "id", None), build_types_id=getattr(build_types_id, "id", None), site_id=getattr(sites_id, "id", None))
build.save()
return { return {
"message": "MSG0002-UPDATE", "message": "MSG0002-UPDATE",
"data": None, "data": None,
@ -108,6 +125,10 @@ SuperBuildUpdateEvent.event_callable = super_build_update_callable
def super_build_delete_callable(uu_id: str, headers: CommonHeaders): def super_build_delete_callable(uu_id: str, headers: CommonHeaders):
with Build.new_session() as db_session:
Build.set_session(db_session)
build = Build.query.filter(Build.uu_id == uu_id).first()
build.delete()
return { return {
"message": "MSG0003-DELETE", "message": "MSG0003-DELETE",
"data": None, "data": None,

View File

@ -89,10 +89,15 @@ class TokenProvider:
""" """
Retrieve event code from the token object or list of token objects. Retrieve event code from the token object or list of token objects.
""" """
if isinstance(token, EmployeeTokenObject): if token.is_employee and token.selected_company:
if event_codes := token.selected_company.reachable_event_codes.get(endpoint_code, None): employee_uu_id = token.selected_company.get("uu_id", None)
return event_codes print("endpoint_code", endpoint_code)
elif isinstance(token, OccupantTokenObject): print("employee_uu_id", employee_uu_id)
if event_codes := token.selected_occupant.reachable_event_codes.get(endpoint_code, None): if reachable_event_codes_dict := token.reachable_event_codes:
return event_codes print("reachable_event_codes_dict", reachable_event_codes_dict.get(employee_uu_id, {}))
return reachable_event_codes_dict.get(employee_uu_id, {}).get(endpoint_code, None)
elif token.is_occupant and token.selected_occupant:
occupant_uu_id = token.selected_occupant.get("build_living_space_uu_id", None)
if reachable_event_codes_dict := token.reachable_event_codes:
return reachable_event_codes_dict.get(occupant_uu_id, {}).get(endpoint_code, None)
raise ValueError("Invalid token type or no event code found.") raise ValueError("Invalid token type or no event code found.")

View File

@ -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 }
);
}
}

View File

@ -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 }
);
}
}

View File

@ -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 }
);
}
}

View File

@ -34,10 +34,6 @@ export async function GET(request: Request) {
const { paginatedData, meta } = getPaginatedData(page, size); const { paginatedData, meta } = getPaginatedData(page, size);
console.log(
`GET /api/test/list - Returning ${paginatedData.length} items (page ${page}/${meta.totalPages})`
);
// Return paginated response with the structure that useTableData expects // Return paginated response with the structure that useTableData expects
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
@ -76,10 +72,6 @@ export async function POST(request: Request) {
const { paginatedData, meta } = getPaginatedData(page, size); const { paginatedData, meta } = getPaginatedData(page, size);
console.log(
`POST /api/test/list - Returning ${paginatedData.length} items (page ${page}/${meta.totalPages})`
);
// Return paginated response with the structure that useTableData expects // Return paginated response with the structure that useTableData expects
return NextResponse.json({ return NextResponse.json({
success: true, success: true,

View File

@ -32,9 +32,6 @@ export async function PATCH(request: Request) {
}; };
globalData[itemIndex] = updatedItem; globalData[itemIndex] = updatedItem;
console.log(`PATCH /api/test/update - Updated item with id: ${updatedItem.id}`);
return NextResponse.json({ return NextResponse.json({
status: 200, status: 200,
data: updatedItem, data: updatedItem,

View File

@ -126,7 +126,6 @@ const DataTable: React.FC<DataTableProps> = React.memo(({
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}> <tr key={headerGroup.id}>
{headerGroup.headers.map((header) => { {headerGroup.headers.map((header) => {
console.log('Rendering header:', header.id);
return ( return (
<th <th
key={header.id} key={header.id}
@ -156,7 +155,6 @@ const DataTable: React.FC<DataTableProps> = React.memo(({
table.getRowModel().rows.map((row) => ( table.getRowModel().rows.map((row) => (
<tr key={row.id} className="hover:bg-gray-50"> <tr key={row.id} className="hover:bg-gray-50">
{row.getVisibleCells().map((cell) => { {row.getVisibleCells().map((cell) => {
console.log('Rendering cell:', cell.column.id);
return ( return (
<td key={cell.id} className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td key={cell.id} className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{flexRender(cell.column.columnDef.cell, cell.getContext())} {flexRender(cell.column.columnDef.cell, cell.getContext())}
@ -371,14 +369,12 @@ const TableCardComponentImproved: React.FC<DashboardPageProps> = React.memo((pro
renderPageOptions, renderPageOptions,
} = useTableData({ apiUrl: `${API_BASE_URL}/test/list` }); } = useTableData({ apiUrl: `${API_BASE_URL}/test/list` });
const activePageUrl = props.activePageUrl || ''; const activePageUrl = props.activePageUrl || '';
console.log("activePageUrl", activePageUrl);
// Function to handle editing a row // Function to handle editing a row
const handleEditRow = async (row: TableDataItem) => { const handleEditRow = async (row: TableDataItem) => {
try { try {
// Store the row data in the cache // Store the row data in the cache
await setCacheData(`${activePageUrl}/update`, row); await setCacheData(`${activePageUrl}/update`, row);
console.log('Row data stored in cache:', row);
// Navigate to the update form // Navigate to the update form
router.push(`/panel/${activePageUrl}/update`); router.push(`/panel/${activePageUrl}/update`);
@ -387,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>(); const columnHelper = createColumnHelper<TableDataItem>();
// Make sure columns are properly defined with the actions column
const columns = React.useMemo(() => [ const columns = React.useMemo(() => [
columnHelper.display({ columnHelper.display({
id: 'actions', id: 'actions',
header: () => <span>Actions</span>, header: () => <span>Actions</span>,
cell: (info) => { cell: (info) => { return (actionButtonGroup(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>
);
}
}), }),
columnHelper.accessor('name', { columnHelper.accessor('name', {
cell: info => info.getValue(), cell: info => info.getValue(),
@ -465,12 +459,8 @@ const TableCardComponentImproved: React.FC<DashboardPageProps> = React.memo((pro
getPaginationRowModel: getPaginationRowModel(), getPaginationRowModel: getPaginationRowModel(),
manualPagination: true, manualPagination: true,
pageCount: apiPagination.totalPages || 1, pageCount: apiPagination.totalPages || 1,
debugTable: true,
}); });
// Check if actions column is included
const actionColumnExists = table.getVisibleLeafColumns().some(col => col.id === 'actions');
return ( return (
isLoading ? <LoadingContent height="h-48" size="w-36 h-36" plane="h-full w-full" /> : isLoading ? <LoadingContent height="h-48" size="w-36 h-36" plane="h-full w-full" /> :
<div className="bg-white rounded-lg shadow-md overflow-hidden mb-20"> <div className="bg-white rounded-lg shadow-md overflow-hidden mb-20">

View File

@ -72,16 +72,9 @@ function CreateFromComponentBase({
const fetchCacheDirectly = async () => { const fetchCacheDirectly = async () => {
if (activePageUrl && !cacheLoaded) { if (activePageUrl && !cacheLoaded) {
try { try {
console.log("Directly fetching cache for URL:", activePageUrl);
// Directly fetch cache data using the imported function
const cachedData = await getCacheData(activePageUrl); const cachedData = await getCacheData(activePageUrl);
console.log("Directly fetched cache data:", cachedData);
if (cachedData) { if (cachedData) {
const formValues = form.getValues(); const formValues = form.getValues();
// Create a merged data object with proper typing
const mergedData: FormValues = { const mergedData: FormValues = {
name: cachedData.name || formValues.name || "", name: cachedData.name || formValues.name || "",
email: cachedData.email || formValues.email || "", email: cachedData.email || formValues.email || "",
@ -92,20 +85,12 @@ function CreateFromComponentBase({
terms: cachedData.terms ?? formValues.terms ?? false, terms: cachedData.terms ?? formValues.terms ?? false,
attachments: cachedData.attachments || formValues.attachments || "" attachments: cachedData.attachments || formValues.attachments || ""
}; };
console.log("Setting form with direct cache data:", mergedData);
form.reset(mergedData); form.reset(mergedData);
} }
setCacheLoaded(true); setCacheLoaded(true);
if (refreshCache) { refreshCache(activePageUrl) }
// Also call the context refresh if available (for state consistency) } catch (error) { console.error("Error fetching cache directly:", error) }
if (refreshCache) {
refreshCache(activePageUrl);
}
} catch (error) {
console.error("Error fetching cache directly:", error);
}
} }
}; };
@ -117,24 +102,13 @@ function CreateFromComponentBase({
// Only update if the value is not empty // Only update if the value is not empty
if (value && activePageUrl) { if (value && activePageUrl) {
try { try {
// Get current form values
const currentValues = form.getValues(); const currentValues = form.getValues();
// Prepare updated data
const updatedData = { const updatedData = {
...currentValues, ...currentValues,
[fieldName]: value [fieldName]: value
}; };
console.log("Directly updating cache with:", updatedData);
// Directly update cache using imported function
await setCacheData(activePageUrl, updatedData); await setCacheData(activePageUrl, updatedData);
if (updateCache) { updateCache(activePageUrl, updatedData) }
// Also use the context method if available (for state consistency)
if (updateCache) {
updateCache(activePageUrl, updatedData);
}
} catch (error) { } catch (error) {
console.error("Error updating cache:", error); console.error("Error updating cache:", error);
} }
@ -143,14 +117,8 @@ function CreateFromComponentBase({
// Type-safe submit handler // Type-safe submit handler
const onSubmit = async (data: FormValues) => { const onSubmit = async (data: FormValues) => {
console.log("Form submitted with data:", data);
try { try {
// Submit form data to API
const response = await apiPostFetcher<any>({ url: "/tst/create", isNoCache: true, body: data }); const response = await apiPostFetcher<any>({ url: "/tst/create", isNoCache: true, body: data });
console.log("API response:", response);
// Clear cache on successful submission
if (activePageUrl) { if (activePageUrl) {
try { try {
console.log("Directly clearing cache for URL:", activePageUrl); console.log("Directly clearing cache for URL:", activePageUrl);

View File

@ -31,7 +31,7 @@ const zodSchema = z.object({
// Define the form type // Define the form type
type FormValues = z.infer<typeof zodSchema>; type FormValues = z.infer<typeof zodSchema>;
function UpdateFromComponentBase({ function UpdateFromBuildComponentBase({
cacheData, cacheData,
cacheLoading, cacheLoading,
cacheError, cacheError,
@ -72,11 +72,9 @@ function UpdateFromComponentBase({
const fetchCacheDirectly = async () => { const fetchCacheDirectly = async () => {
if (activePageUrl && !cacheLoaded) { if (activePageUrl && !cacheLoaded) {
try { try {
console.log("Directly fetching cache for URL:", activePageUrl);
// Directly fetch cache data using the imported function // Directly fetch cache data using the imported function
const cachedData = await getCacheData(activePageUrl); const cachedData = await getCacheData(activePageUrl);
console.log("Directly fetched cache data:", cachedData);
if (cachedData) { if (cachedData) {
const formValues = form.getValues(); const formValues = form.getValues();
@ -90,12 +88,6 @@ function UpdateFromComponentBase({
const termsValue = typeof cachedData.terms === 'string' const termsValue = typeof cachedData.terms === 'string'
? cachedData.terms === 'true' ? cachedData.terms === 'true'
: Boolean(cachedData.terms); : Boolean(cachedData.terms);
console.log("Raw notification value from cache:", cachedData.notifications, "type:", typeof cachedData.notifications);
console.log("Raw terms value from cache:", cachedData.terms, "type:", typeof cachedData.terms);
console.log("Converted notification value:", notificationsValue);
console.log("Converted terms value:", termsValue);
const mergedData: FormValues = { const mergedData: FormValues = {
name: cachedData.name || formValues.name || "", name: cachedData.name || formValues.name || "",
email: cachedData.email || formValues.email || "", email: cachedData.email || formValues.email || "",
@ -107,13 +99,9 @@ function UpdateFromComponentBase({
attachments: cachedData.attachments || formValues.attachments || "" attachments: cachedData.attachments || formValues.attachments || ""
}; };
console.log("Setting form with direct cache data:", mergedData);
form.reset(mergedData); form.reset(mergedData);
} }
setCacheLoaded(true); setCacheLoaded(true);
// Also call the context refresh if available (for state consistency)
if (refreshCache) { if (refreshCache) {
refreshCache(activePageUrl); refreshCache(activePageUrl);
} }
@ -419,7 +407,7 @@ function UpdateFromComponentBase({
); );
} }
export const UpdateFromComponent = withCache(UpdateFromComponentBase); export const UpdateFromBuildComponent = withCache(UpdateFromBuildComponentBase);
// Add default export to maintain compatibility with existing imports // Add default export to maintain compatibility with existing imports
export default withCache(UpdateFromComponentBase); export default withCache(UpdateFromBuildComponentBase);

View File

@ -75,14 +75,9 @@ const useContextCache = createContextHook<CacheData>({
contextName: "cache", contextName: "cache",
enablePeriodicRefresh: false, enablePeriodicRefresh: false,
customFetch: async () => { customFetch: async () => {
// For initial load, we don't have a specific URL to fetch
// Return an empty object as the initial state
console.log("Initial cache fetch");
return {}; return {};
}, },
customUpdate: async (newData: CacheData) => { customUpdate: async (newData: CacheData) => {
// This won't be used directly, as we'll provide custom methods
console.log("Cache update with:", newData);
return true; return true;
}, },
defaultValue: {} defaultValue: {}
@ -112,22 +107,14 @@ export function useCache(): UseCacheResult {
// Normalize URL to handle encoding issues // Normalize URL to handle encoding issues
const normalizedUrl = url.startsWith('/') ? url : `/${url}`; const normalizedUrl = url.startsWith('/') ? url : `/${url}`;
console.log("Fetching cache for normalized URL:", normalizedUrl);
try { try {
const urlData = await getCacheData(normalizedUrl); const urlData = await getCacheData(normalizedUrl);
console.log("Received cache data:", urlData);
// Update the local state with the fetched data
const updatedData = { const updatedData = {
...data, ...data,
[normalizedUrl]: urlData [normalizedUrl]: urlData
}; };
console.log("Updating cache state with:", updatedData);
await update(updatedData); await update(updatedData);
// Force a refresh to ensure the component gets the updated data
await refresh(); await refresh();
} catch (error) { } catch (error) {
console.error("Error refreshing cache:", error); console.error("Error refreshing cache:", error);
@ -138,14 +125,9 @@ export function useCache(): UseCacheResult {
const updateCache = async (url: string, urlData: any): Promise<void> => { const updateCache = async (url: string, urlData: any): Promise<void> => {
// Normalize URL to handle encoding issues // Normalize URL to handle encoding issues
const normalizedUrl = url.startsWith('/') ? url : `/${url}`; const normalizedUrl = url.startsWith('/') ? url : `/${url}`;
console.log("Updating cache for normalized URL:", normalizedUrl);
try { try {
const success = await setCacheData(normalizedUrl, urlData); const success = await setCacheData(normalizedUrl, urlData);
console.log("Cache update success:", success);
if (success && data) { if (success && data) {
// Update local state
await update({ await update({
...data, ...data,
[normalizedUrl]: urlData [normalizedUrl]: urlData
@ -162,12 +144,8 @@ export function useCache(): UseCacheResult {
const clearCache = async (url: string): Promise<void> => { const clearCache = async (url: string): Promise<void> => {
// Normalize URL to handle encoding issues // Normalize URL to handle encoding issues
const normalizedUrl = url.startsWith('/') ? url : `/${url}`; const normalizedUrl = url.startsWith('/') ? url : `/${url}`;
console.log("Clearing cache for normalized URL:", normalizedUrl);
try { try {
const success = await clearCacheData(normalizedUrl); const success = await clearCacheData(normalizedUrl);
console.log("Cache clear success:", success);
if (success && data) { if (success && data) {
// Update local state // Update local state
const newData = { ...data }; const newData = { ...data };

View File

@ -99,17 +99,11 @@ async function setContextPageOnline(
body: JSON.stringify(setOnline), body: JSON.stringify(setOnline),
signal: controller.signal, signal: controller.signal,
}); });
console.log("result", await result.json());
// Clear the timeout
clearTimeout(timeoutId); clearTimeout(timeoutId);
if (!result.ok) { if (!result.ok) {
throw new Error(`HTTP error! Status: ${result.status}`); throw new Error(`HTTP error! Status: ${result.status}`);
} }
const data = await result.json(); const data = await result.json();
if (data.status === 200 && data.data) { if (data.status === 200 && data.data) {
return data.data; return data.data;
} else { } else {

View File

@ -66,14 +66,12 @@ export const OnlineProvider: FC<OnlineProviderProps> = ({ children }) => {
const fetchOnline = useCallback(async (force = false) => { const fetchOnline = useCallback(async (force = false) => {
// Don't fetch if we already have data and it's not forced // Don't fetch if we already have data and it's not forced
if (online && !force && !error) { if (online && !force && !error) {
console.log("Using existing online state:", online);
return; return;
} }
// Don't retry too frequently // Don't retry too frequently
const now = Date.now(); const now = Date.now();
if (!force && now - lastRetryTime < MIN_RETRY_INTERVAL) { if (!force && now - lastRetryTime < MIN_RETRY_INTERVAL) {
console.log("Retry attempted too soon, skipping");
return; return;
} }
@ -82,35 +80,29 @@ export const OnlineProvider: FC<OnlineProviderProps> = ({ children }) => {
setLastRetryTime(now); setLastRetryTime(now);
try { try {
console.log("Fetching online state...");
const data = await checkContextPageOnline(); const data = await checkContextPageOnline();
if (data) { if (data) {
console.log("Successfully fetched online state:", data);
setOnline(data); setOnline(data);
setRetryCount(0); // Reset retry count on success setRetryCount(0); // Reset retry count on success
} else { } else {
console.warn("No online state returned, using default");
setOnline(DEFAULT_ONLINE_STATE); setOnline(DEFAULT_ONLINE_STATE);
setError("Could not retrieve online state, using default values"); setError("Could not retrieve online state, using default values");
// Auto-retry if under the limit // Auto-retry if under the limit
if (retryCount < MAX_AUTO_RETRIES) { if (retryCount < MAX_AUTO_RETRIES) {
setRetryCount(prev => prev + 1); setRetryCount(prev => prev + 1);
console.log(`Scheduling retry ${retryCount + 1}/${MAX_AUTO_RETRIES}...`);
setTimeout(() => fetchOnline(true), MIN_RETRY_INTERVAL); setTimeout(() => fetchOnline(true), MIN_RETRY_INTERVAL);
} }
} }
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error'; const errorMessage = error instanceof Error ? error.message : 'Unknown error';
console.error("Error fetching online state:", errorMessage);
setError(`Failed to fetch online state: ${errorMessage}`); setError(`Failed to fetch online state: ${errorMessage}`);
setOnline(DEFAULT_ONLINE_STATE); // Use default as fallback setOnline(DEFAULT_ONLINE_STATE); // Use default as fallback
// Auto-retry if under the limit // Auto-retry if under the limit
if (retryCount < MAX_AUTO_RETRIES) { if (retryCount < MAX_AUTO_RETRIES) {
setRetryCount(prev => prev + 1); setRetryCount(prev => prev + 1);
console.log(`Scheduling retry ${retryCount + 1}/${MAX_AUTO_RETRIES}...`);
setTimeout(() => fetchOnline(true), MIN_RETRY_INTERVAL); setTimeout(() => fetchOnline(true), MIN_RETRY_INTERVAL);
} }
} finally { } finally {
@ -120,26 +112,20 @@ export const OnlineProvider: FC<OnlineProviderProps> = ({ children }) => {
// Manual retry function that can be called from components // Manual retry function that can be called from components
const retryFetch = useCallback(async () => { const retryFetch = useCallback(async () => {
console.log("Manual retry requested");
setRetryCount(0); // Reset retry count for manual retry setRetryCount(0); // Reset retry count for manual retry
await fetchOnline(true); await fetchOnline(true);
}, [fetchOnline]); }, [fetchOnline]);
// Fetch online state on component mount // Fetch online state on component mount
useEffect(() => { useEffect(() => {
console.log("OnlineProvider mounted, fetching initial data");
// Always fetch data on mount
fetchOnline(); fetchOnline();
// Set up periodic refresh (every 5 minutes) // Set up periodic refresh (every 5 minutes)
const refreshInterval = setInterval(() => { const refreshInterval = setInterval(() => {
console.log("Performing periodic refresh of online state");
fetchOnline(true); fetchOnline(true);
}, 5 * 60 * 1000); }, 5 * 60 * 1000);
return () => { return () => {
console.log("OnlineProvider unmounted, clearing interval");
clearInterval(refreshInterval); clearInterval(refreshInterval);
}; };
}, [fetchOnline]); }, [fetchOnline]);
@ -150,42 +136,17 @@ export const OnlineProvider: FC<OnlineProviderProps> = ({ children }) => {
setError(null); setError(null);
try { try {
console.log("Updating online state:", newOnline);
// Update Redis first
console.log('Updating Redis...');
await setOnlineToRedis(newOnline); await setOnlineToRedis(newOnline);
// Then update context API
console.log('Updating context API...');
await setContextPageOnline(newOnline); await setContextPageOnline(newOnline);
// Finally update local state to trigger re-renders
console.log('Updating local state...');
setOnline(newOnline); setOnline(newOnline);
console.log('Online state updated successfully');
return true; return true;
} catch (error) { } catch (error) {
console.error('Error updating online state:', error);
// Still update local state to maintain UI consistency
// even if the backend updates failed
setOnline(newOnline); setOnline(newOnline);
return false; return false;
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}; }
// Add debug logging for provider state
useEffect(() => {
console.log('OnlineProvider state updated:', {
online: online ? 'present' : 'not present',
isLoading
});
}, [online, isLoading]);
return ( return (
<OnlineContext.Provider value={{ online, updateOnline, isLoading, error, retryFetch }}> <OnlineContext.Provider value={{ online, updateOnline, isLoading, error, retryFetch }}>
{children} {children}

View File

@ -34,14 +34,9 @@ const DEFAULT_USER_STATE: ClientUser = {
// Client-side fetch function that uses the server-side implementation // Client-side fetch function that uses the server-side implementation
async function checkContextDashUserInfo(): Promise<ClientUser> { async function checkContextDashUserInfo(): Promise<ClientUser> {
try { try {
console.log("Fetching user data using server-side function");
// First try to use the server-side implementation
try { try {
const serverData = await getUserFromServer(); const serverData = await getUserFromServer();
// If we got valid data from the server, return it
if (serverData && serverData.uuid) { if (serverData && serverData.uuid) {
// Check if we have a real user (not the default)
if ( if (
serverData.uuid !== "default-user-id" || serverData.uuid !== "default-user-id" ||
serverData.email || serverData.email ||
@ -49,30 +44,12 @@ async function checkContextDashUserInfo(): Promise<ClientUser> {
(serverData.person.firstname !== "Guest" || (serverData.person.firstname !== "Guest" ||
serverData.person.surname !== "User")) serverData.person.surname !== "User"))
) { ) {
console.log("Valid user data found from server");
return serverData; return serverData;
} else {
console.log(
"Default user data returned from server, falling back to client-side"
);
} }
} else { } else {
console.warn("Invalid user data structure from server");
} }
} catch (serverError) { } catch (serverError) {}
console.warn(
"Error using server-side user data fetch, falling back to client-side:",
serverError
);
// Continue to client-side implementation
}
// Fall back to client-side implementation
console.log(
`Falling back to client-side fetch: ${API_BASE_URL}/context/dash/user`
);
// Create an AbortController to handle timeouts
const controller = new AbortController(); const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT); const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
@ -94,22 +71,14 @@ async function checkContextDashUserInfo(): Promise<ClientUser> {
} }
const data = await result.json(); const data = await result.json();
console.log("User data API response:", data);
// Handle different response formats
if (data.status === 200 && data.data) { if (data.status === 200 && data.data) {
// Standard API response format
return data.data; return data.data;
} else if (data.user) { } else if (data.user) {
// Direct Redis object format
console.log("Found user data in Redis format");
return data.user; return data.user;
} else if (data.uuid) { } else if (data.uuid) {
// Direct user object format
console.log("Found direct user data format");
return data; return data;
} else { } else {
console.warn("Invalid response format from user API");
return DEFAULT_USER_STATE; return DEFAULT_USER_STATE;
} }
} catch (fetchError) { } catch (fetchError) {
@ -129,11 +98,6 @@ async function checkContextDashUserInfo(): Promise<ClientUser> {
throw fetchError; throw fetchError;
} }
} catch (error) { } catch (error) {
// Handle all other errors
console.error(
"Error fetching user data:",
error instanceof Error ? error.message : "Unknown error"
);
return DEFAULT_USER_STATE; return DEFAULT_USER_STATE;
} }
} }
@ -144,29 +108,12 @@ async function setContextDashUserInfo({
userSet: ClientUser; userSet: ClientUser;
}): Promise<boolean> { }): Promise<boolean> {
try { try {
console.log("Setting user data using server-side function");
// First try to use the server-side implementation
try { try {
const success = await setUserFromServer(userSet); const success = await setUserFromServer(userSet);
if (success) { if (success) {
console.log(
"Successfully updated user data using server-side function"
);
return true; return true;
} }
} catch (serverError) { } catch (serverError) {}
console.warn(
"Error using server-side user data update, falling back to client-side:",
serverError
);
// Continue to client-side implementation
}
// Fall back to client-side implementation
console.log(
`Falling back to client-side update: ${API_BASE_URL}/context/dash/user`
);
// Create an AbortController to handle timeouts // Create an AbortController to handle timeouts
const controller = new AbortController(); const controller = new AbortController();
@ -182,28 +129,18 @@ async function setContextDashUserInfo({
body: JSON.stringify(userSet), body: JSON.stringify(userSet),
signal: controller.signal, signal: controller.signal,
}); });
// Clear the timeout
clearTimeout(timeoutId); clearTimeout(timeoutId);
if (!result.ok) { if (!result.ok) {
throw new Error(`HTTP error! Status: ${result.status}`); throw new Error(`HTTP error! Status: ${result.status}`);
} }
const data = await result.json(); const data = await result.json();
console.log("Update user data API response:", data);
return data.status === 200; return data.status === 200;
} catch (fetchError) { } catch (fetchError) {
// Clear the timeout if it hasn't fired yet
clearTimeout(timeoutId); clearTimeout(timeoutId);
// Check if this is an abort error (timeout)
if ( if (
fetchError instanceof DOMException && fetchError instanceof DOMException &&
fetchError.name === "AbortError" fetchError.name === "AbortError"
) { ) {
console.warn("Request timed out or was aborted");
return false; return false;
} }
@ -211,10 +148,6 @@ async function setContextDashUserInfo({
throw fetchError; throw fetchError;
} }
} catch (error) { } catch (error) {
console.error(
"Error setting user data:",
error instanceof Error ? error.message : "Unknown error"
);
return false; return false;
} }
} }

View File

@ -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>
)}
/>
);
}

View File

@ -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>
)}
/>
);
}

View File

@ -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>
)}
/>
);
}

View File

@ -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>
)}
/>
);
}

View File

@ -8,11 +8,6 @@ interface ClientProvidersProps {
} }
export function ClientProviders({ children }: ClientProvidersProps) { export function ClientProviders({ children }: ClientProvidersProps) {
// Log provider initialization for debugging
React.useEffect(() => {
console.log('ClientProviders initialized');
}, []);
return ( return (
<OnlineProvider> <OnlineProvider>
<CacheProvider> <CacheProvider>

View File

@ -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
}

View File

@ -31,6 +31,14 @@ const urlLogoutEndpoint = `${baseUrlAuth}/authentication/logout`;
const urlTesterList = `${baseUrlTester}/tester/list`; const urlTesterList = `${baseUrlTester}/tester/list`;
const urlBuildingsList = `${baseUrlBuilding}/builds/list`;
const urlBuildingsCreate = `${baseUrlBuilding}/builds/create`;
const urlBuildingsUpdate = `${baseUrlBuilding}/builds/update`;
const urlPartsList = `${baseUrlBuilding}/parts/list`;
const urlPartsCreate = `${baseUrlBuilding}/parts/create`;
const urlPartsUpdate = `${baseUrlBuilding}/parts/update`;
export { export {
urlCheckToken, urlCheckToken,
urlPageValid, urlPageValid,
@ -38,6 +46,12 @@ export {
urlLoginEndpoint, urlLoginEndpoint,
urlLoginSelectEndpoint, urlLoginSelectEndpoint,
urlLogoutEndpoint, urlLogoutEndpoint,
urlBuildingsList,
urlBuildingsCreate,
urlBuildingsUpdate,
urlPartsList,
urlPartsCreate,
urlPartsUpdate,
// For test use only // For test use only
urlTesterList, urlTesterList,
}; };

View File

@ -21,4 +21,12 @@ interface CookieObject {
priority: string; priority: string;
} }
export type { HttpMethod, ApiResponse, FetchOptions, CookieObject }; interface Pagination {
page?: number;
size?: number;
orderField?: string[];
orderType?: string[];
query?: Record<string, any>;
}
export type { HttpMethod, ApiResponse, FetchOptions, CookieObject, Pagination };

View File

@ -116,7 +116,6 @@ export function useTableData({
next: false, next: false,
back: false, back: false,
}); });
console.log("apiPagination", apiPagination);
// Watch for form value changes // Watch for form value changes
const page = form.watch(pageField as Path<TableFormValues>); const page = form.watch(pageField as Path<TableFormValues>);

View File

@ -37,7 +37,7 @@ const menuTranslationEn = {
"/build": [ "/build": [
{ value: "Build", key: "build" }, { value: "Build", key: "build" },
{ value: "Build", key: "build" }, { value: "Build", key: "build" },
{ value: "Build", key: "build" }, { value: "Buildings", key: "build" },
], ],
"/build/parts": [ "/build/parts": [
{ value: "Build", key: "build" }, { value: "Build", key: "build" },

View File

@ -37,7 +37,7 @@ const menuTranslationTr = {
"/build": [ "/build": [
{ value: "Bina", key: "build" }, { value: "Bina", key: "build" },
{ value: "Bina", key: "build" }, { value: "Bina", key: "build" },
{ value: "Bina", key: "build" }, { value: "Binalar", key: "build" },
], ],
"/build/parts": [ "/build/parts": [
{ value: "Bina", key: "build" }, { value: "Bina", key: "build" },

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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 };

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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 };

View File

@ -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;

View File

@ -1,14 +1,21 @@
// import { DashboardPage } from "@/components/custom/content/DashboardPage"; // import { DashboardPage } from "@/components/custom/content/DashboardPage";
import { DPage } from "@/components/custom/content/DPage"; import { DPage } from "@/components/custom/content/DPage";
import TableCardComponentImproved from "@/components/custom/content/TableCardComponentImproved"; import TableCardComponentImproved from "@/components/custom/content/TableCardComponentImproved";
import CreateFromComponent from "@/components/custom/content/createFromComponent"; import BuildListPage from "./builds/superuser/ListPage";
import UpdateFromComponent from "@/components/custom/content/updateFromComponent"; import CreateFromBuildComponent from "./builds/superuser/CreatePage";
import UpdateFromBuildComponent from "./builds/superuser/UpdatePage";
import BuildPartsListPage from "./buildParts/superuser/ListPage";
import CreateFromBuildPartsComponent from "./buildParts/superuser/CreatePage";
import UpdateFromBuildPartsComponent from "./buildParts/superuser/UpdatePage";
const pageIndexMulti: Record<string, Record<string, React.FC<any>>> = { const pageIndexMulti: Record<string, Record<string, React.FC<any>>> = {
"/dashboard": { DashboardPage: TableCardComponentImproved }, "/dashboard": { DashboardPage: TableCardComponentImproved },
"/build": { DashboardPage: TableCardComponentImproved }, "/build": { DashboardPage: BuildListPage },
"/build/create": { DashboardPage: CreateFromComponent }, "/build/create": { DashboardPage: CreateFromBuildComponent },
"/build/update": { DashboardPage: UpdateFromComponent }, "/build/update": { DashboardPage: UpdateFromBuildComponent },
"/build/parts": { DashboardPage: BuildPartsListPage },
"/build/parts/create": { DashboardPage: CreateFromBuildPartsComponent },
"/build/parts/update": { DashboardPage: UpdateFromBuildPartsComponent },
"/people": { DashboardPage: DPage }, "/people": { DashboardPage: DPage },
"/people/create": { DashboardPage: DPage }, "/people/create": { DashboardPage: DPage },
"/people/update": { DashboardPage: DPage }, "/people/update": { DashboardPage: DPage },

View File

@ -1,71 +1,81 @@
import { z } from 'zod'; import { z } from "zod";
/** /**
* Zod schema for BuildTypes * Zod schema for BuildTypes
* Corresponds to the BuildTypes class in Python * Corresponds to the BuildTypes class in Python
*/ */
export const buildTypesSchema = z.object({ export const buildTypesSchema = z.object({
function_code: z.string().max(12).default('').describe('Function Code'), function_code: z.string().max(12).default("").describe("Function Code"),
type_code: z.string().max(12).default('').describe('Structure Type Code'), type_code: z.string().max(12).default("").describe("Structure Type Code"),
lang: z.string().max(4).default('TR').describe('Language'), lang: z.string().max(4).default("TR").describe("Language"),
type_name: z.string().max(48).default('').describe('Type Name'), type_name: z.string().max(48).default("").describe("Type Name"),
}); });
export type BuildTypes = z.infer<typeof buildTypesSchema>; export type BuildTypes = z.infer<typeof buildTypesSchema>;
/** // /**
* Zod schema for Part2Employee // * Zod schema for Part2Employee
* Corresponds to the Part2Employee class in Python // * Corresponds to the Part2Employee class in Python
*/ // */
export const part2EmployeeSchema = z.object({ // export const part2EmployeeSchema = z.object({
build_id: z.number().int().describe('Building ID'), // build_uu_id: z.string().describe("Building UUID"),
part_id: z.number().int().describe('Part ID'), // part_uu_id: z.string().describe("Part UUID"),
employee_id: z.number().int().describe('Employee ID'), // employee_uu_id: z.string().describe("Employee UUID"),
}); // });
export type Part2Employee = z.infer<typeof part2EmployeeSchema>; // export type Part2Employee = z.infer<typeof part2EmployeeSchema>;
/** // /**
* Zod schema for RelationshipEmployee2Build // * Zod schema for RelationshipEmployee2Build
* Corresponds to the RelationshipEmployee2Build class in Python // * Corresponds to the RelationshipEmployee2Build class in Python
*/ // */
export const relationshipEmployee2BuildSchema = z.object({ // export const relationshipEmployee2BuildSchema = z.object({
company_id: z.number().int(), // company_id: z.number().int(),
employee_id: z.number().int(), // employee_id: z.number().int(),
member_id: z.number().int(), // member_id: z.number().int(),
relationship_type: z.string().default('Employee'), // relationship_type: z.string().default("Employee"),
show_only: z.boolean().default(false), // show_only: z.boolean().default(false),
}); // });
export type RelationshipEmployee2Build = z.infer<typeof relationshipEmployee2BuildSchema>; // export type RelationshipEmployee2Build = z.infer<
// typeof relationshipEmployee2BuildSchema
// >;
/** /**
* Zod schema for Build * Zod schema for Build
* Corresponds to the Build class in Python * Corresponds to the Build class in Python
*/ */
export const buildSchema = z.object({ export const buildSchema = z.object({
gov_address_code: z.string().default(''), gov_address_code: z.string().default(""),
build_name: z.string().describe('Building Name'), build_name: z.string().describe("Building Name"),
build_no: z.string().max(8).describe('Building Number'), build_no: z.string().max(8).describe("Building Number"),
max_floor: z.number().int().default(1).describe('Max Floor'), max_floor: z.number().int().default(1).describe("Max Floor"),
underground_floor: z.number().int().default(0).describe('Underground Floor'), underground_floor: z.number().int().default(0).describe("Underground Floor"),
build_date: z.string().datetime().default('1900-01-01T00:00:00Z'), build_date: z.string().datetime().default("1900-01-01T00:00:00Z"),
decision_period_date: z.string().datetime().default('1900-01-01T00:00:00Z').describe('Building annual ordinary meeting period'), decision_period_date: z
tax_no: z.string().max(24).default(''), .string()
.datetime()
.default("1900-01-01T00:00:00Z")
.describe("Building annual ordinary meeting period"),
tax_no: z.string().max(24).default(""),
lift_count: z.number().int().default(0), lift_count: z.number().int().default(0),
heating_system: z.boolean().default(true), heating_system: z.boolean().default(true),
cooling_system: z.boolean().default(false), cooling_system: z.boolean().default(false),
hot_water_system: z.boolean().default(false), hot_water_system: z.boolean().default(false),
block_service_man_count: z.number().int().default(0), block_service_man_count: z.number().int().default(0),
security_service_man_count: z.number().int().default(0), security_service_man_count: z.number().int().default(0),
garage_count: z.number().int().default(0).describe('Garage Count'), garage_count: z.number().int().default(0).describe("Garage Count"),
management_room_id: z.number().int().nullable().describe('Management Room ID'), management_room_id: z
.number()
.int()
.nullable()
.describe("Management Room ID"),
site_id: z.number().int().nullable(), site_id: z.number().int().nullable(),
site_uu_id: z.string().nullable().describe('Site UUID'), site_uu_id: z.string().nullable().describe("Site UUID"),
address_id: z.number().int(), address_id: z.number().int(),
address_uu_id: z.string().describe('Address UUID'), address_uu_id: z.string().describe("Address UUID"),
build_types_id: z.number().int().describe('Building Type'), build_types_id: z.number().int().describe("Building Type"),
build_types_uu_id: z.string().describe('Building Type UUID'), build_types_uu_id: z.string().describe("Building Type UUID"),
}); });
export type Build = z.infer<typeof buildSchema>; export type Build = z.infer<typeof buildSchema>;
@ -75,21 +85,18 @@ export type Build = z.infer<typeof buildSchema>;
* Corresponds to the BuildParts class in Python * Corresponds to the BuildParts class in Python
*/ */
export const buildPartsSchema = z.object({ export const buildPartsSchema = z.object({
address_gov_code: z.string().describe('Goverment Door Code'), address_gov_code: z.string().describe("Goverment Door Code"),
part_no: z.number().int().default(0).describe('Part Number'), part_no: z.number().int().default(0).describe("Part Number"),
part_level: z.number().int().default(0).describe('Building Part Level'), part_level: z.number().int().default(0).describe("Building Part Level"),
part_code: z.string().default('').describe('Part Code'), part_code: z.string().default("").describe("Part Code"),
part_gross_size: z.number().int().default(0).describe('Part Gross Size'), part_gross_size: z.number().int().default(0).describe("Part Gross Size"),
part_net_size: z.number().int().default(0).describe('Part Net Size'), part_net_size: z.number().int().default(0).describe("Part Net Size"),
default_accessory: z.string().default('0').describe('Default Accessory'), default_accessory: z.string().default("0").describe("Default Accessory"),
human_livable: z.boolean().default(true).describe('Human Livable'), human_livable: z.boolean().default(true).describe("Human Livable"),
due_part_key: z.string().default('').describe('Constant Payment Group'), due_part_key: z.string().default("").describe("Constant Payment Group"),
build_id: z.number().int().describe('Building ID'), build_uu_id: z.string().describe("Building UUID"),
build_uu_id: z.string().describe('Building UUID'), part_direction_uu_id: z.string().nullable().describe("Part Direction UUID"),
part_direction_id: z.number().int().nullable(), part_type_uu_id: z.string().describe("Building Part Type UUID"),
part_direction_uu_id: z.string().nullable().describe('Part Direction UUID'),
part_type_id: z.number().int().describe('Building Part Type'),
part_type_uu_id: z.string().describe('Building Part Type UUID'),
}); });
export type BuildParts = z.infer<typeof buildPartsSchema>; export type BuildParts = z.infer<typeof buildPartsSchema>;
@ -99,17 +106,20 @@ export type BuildParts = z.infer<typeof buildPartsSchema>;
* Corresponds to the BuildLivingSpace class in Python * Corresponds to the BuildLivingSpace class in Python
*/ */
export const buildLivingSpaceSchema = z.object({ export const buildLivingSpaceSchema = z.object({
fix_value: z.number().default(0).describe('Fixed value is deducted from debit.'), fix_value: z
fix_percent: z.number().default(0).describe('Fixed percent is deducted from debit.'), .number()
agreement_no: z.string().default('').describe('Agreement No'), .default(0)
.describe("Fixed value is deducted from debit."),
fix_percent: z
.number()
.default(0)
.describe("Fixed percent is deducted from debit."),
agreement_no: z.string().default("").describe("Agreement No"),
marketing_process: z.boolean().default(false), marketing_process: z.boolean().default(false),
marketing_layer: z.number().int().default(0), marketing_layer: z.number().int().default(0),
build_parts_id: z.number().int().describe('Build Part ID'), build_parts_uu_id: z.string().describe("Build Part UUID"),
build_parts_uu_id: z.string().describe('Build Part UUID'), person_uu_id: z.string().describe("Responsible People UUID"),
person_id: z.number().int().describe('Responsible People ID'), occupant_type_uu_id: z.string().describe("Occupant Type UUID"),
person_uu_id: z.string().describe('Responsible People UUID'),
occupant_type_id: z.number().int().describe('Occupant Type'),
occupant_type_uu_id: z.string().describe('Occupant Type UUID'),
}); });
export type BuildLivingSpace = z.infer<typeof buildLivingSpaceSchema>; export type BuildLivingSpace = z.infer<typeof buildLivingSpaceSchema>;
@ -122,12 +132,10 @@ export const buildManagementSchema = z.object({
discounted_percentage: z.number().default(0), discounted_percentage: z.number().default(0),
discounted_price: z.number().default(0), discounted_price: z.number().default(0),
calculated_price: z.number().default(0), calculated_price: z.number().default(0),
occupant_type: z.number().int().describe('Occupant Type'), // occupant_type: z.number().int().describe("Occupant Type"),
occupant_type_uu_id: z.string().describe('Occupant Type UUID'), occupant_type_uu_id: z.string().describe("Occupant Type UUID"),
build_id: z.number().int().describe('Building ID'), build_uu_id: z.string().describe("Building UUID"),
build_uu_id: z.string().describe('Building UUID'), build_parts_uu_id: z.string().describe("Build Part UUID"),
build_parts_id: z.number().int().describe('Build Part ID'),
build_parts_uu_id: z.string().describe('Build Part UUID'),
}); });
export type BuildManagement = z.infer<typeof buildManagementSchema>; export type BuildManagement = z.infer<typeof buildManagementSchema>;
@ -137,18 +145,16 @@ export type BuildManagement = z.infer<typeof buildManagementSchema>;
* Corresponds to the BuildArea class in Python * Corresponds to the BuildArea class in Python
*/ */
export const buildAreaSchema = z.object({ export const buildAreaSchema = z.object({
area_name: z.string().default(''), area_name: z.string().default(""),
area_code: z.string().default(''), area_code: z.string().default(""),
area_type: z.string().default('GREEN'), area_type: z.string().default("GREEN"),
area_direction: z.string().max(2).default('NN'), area_direction: z.string().max(2).default("NN"),
area_gross_size: z.number().default(0), area_gross_size: z.number().default(0),
area_net_size: z.number().default(0), area_net_size: z.number().default(0),
width: z.number().int().default(0), width: z.number().int().default(0),
size: z.number().int().default(0), size: z.number().int().default(0),
build_id: z.number().int(), build_uu_id: z.string().describe("Building UUID"),
build_uu_id: z.string().describe('Building UUID'), part_type_uu_id: z.string().nullable().describe("Building Part Type UUID"),
part_type_id: z.number().int().nullable().describe('Building Part Type'),
part_type_uu_id: z.string().nullable().describe('Building Part Type UUID'),
}); });
export type BuildArea = z.infer<typeof buildAreaSchema>; export type BuildArea = z.infer<typeof buildAreaSchema>;
@ -160,8 +166,7 @@ export type BuildArea = z.infer<typeof buildAreaSchema>;
export const buildSitesSchema = z.object({ export const buildSitesSchema = z.object({
site_name: z.string().max(24), site_name: z.string().max(24),
site_no: z.string().max(8), site_no: z.string().max(8),
address_id: z.number().int(), address_uu_id: z.string().describe("Address UUID"),
address_uu_id: z.string().describe('Address UUID'),
}); });
export type BuildSites = z.infer<typeof buildSitesSchema>; export type BuildSites = z.infer<typeof buildSitesSchema>;
@ -171,28 +176,23 @@ export type BuildSites = z.infer<typeof buildSitesSchema>;
* Corresponds to the BuildCompaniesProviding class in Python * Corresponds to the BuildCompaniesProviding class in Python
*/ */
export const buildCompaniesProvidingSchema = z.object({ export const buildCompaniesProvidingSchema = z.object({
build_id: z.number().int().describe('Building ID'), build_uu_id: z.string().nullable().describe("Providing UUID"),
build_uu_id: z.string().nullable().describe('Providing UUID'), company_uu_id: z.string().nullable().describe("Providing UUID"),
company_id: z.number().int(), provide_uu_id: z.string().nullable().describe("Providing UUID"),
company_uu_id: z.string().nullable().describe('Providing UUID'),
provide_id: z.number().int().nullable(),
provide_uu_id: z.string().nullable().describe('Providing UUID'),
contract_id: z.number().int().nullable(),
}); });
export type BuildCompaniesProviding = z.infer<typeof buildCompaniesProvidingSchema>; export type BuildCompaniesProviding = z.infer<
typeof buildCompaniesProvidingSchema
>;
/** /**
* Zod schema for BuildPersonProviding * Zod schema for BuildPersonProviding
* Corresponds to the BuildPersonProviding class in Python * Corresponds to the BuildPersonProviding class in Python
*/ */
export const buildPersonProvidingSchema = z.object({ export const buildPersonProvidingSchema = z.object({
build_id: z.number().int().describe('Building ID'), build_uu_id: z.string().nullable().describe("Providing UUID"),
build_uu_id: z.string().nullable().describe('Providing UUID'), people_uu_id: z.string().nullable().describe("People UUID"),
people_id: z.number().int(), provide_uu_id: z.string().nullable().describe("Providing UUID"),
people_uu_id: z.string().nullable().describe('People UUID'),
provide_id: z.number().int().nullable(),
provide_uu_id: z.string().nullable().describe('Providing UUID'),
contract_id: z.number().int().nullable(), contract_id: z.number().int().nullable(),
}); });

View File

@ -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" }
);

View File

@ -12,6 +12,7 @@ interface Translations {
next: string; next: string;
selectPage: string; selectPage: string;
selectSize: string; selectSize: string;
actionButtonGroup: string;
} }
interface TableHeaderProps { interface TableHeaderProps {