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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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) {
// Log provider initialization for debugging
React.useEffect(() => {
console.log('ClientProviders initialized');
}, []);
return (
<OnlineProvider>
<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 urlBuildingsList = `${baseUrlBuilding}/builds/list`;
const urlBuildingsCreate = `${baseUrlBuilding}/builds/create`;
const urlBuildingsUpdate = `${baseUrlBuilding}/builds/update`;
const urlPartsList = `${baseUrlBuilding}/parts/list`;
const urlPartsCreate = `${baseUrlBuilding}/parts/create`;
const urlPartsUpdate = `${baseUrlBuilding}/parts/update`;
export {
urlCheckToken,
urlPageValid,
@ -38,6 +46,12 @@ export {
urlLoginEndpoint,
urlLoginSelectEndpoint,
urlLogoutEndpoint,
urlBuildingsList,
urlBuildingsCreate,
urlBuildingsUpdate,
urlPartsList,
urlPartsCreate,
urlPartsUpdate,
// For test use only
urlTesterList,
};

View File

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

View File

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

View File

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

View File

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

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

View File

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

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;
selectPage: string;
selectSize: string;
actionButtonGroup: string;
}
interface TableHeaderProps {