updated Api Defaults

This commit is contained in:
Berkay 2025-05-02 20:46:04 +03:00
parent 1920c2a25d
commit 1ce28ec5f0
51 changed files with 986 additions and 1235 deletions

View File

@ -40,7 +40,7 @@ class EventCluster:
db=db_session, db=db_session,
).data: ).data:
for event in self.events: for event in self.events:
event_to_save_database = Events.find_or_create( event_dict_to_save = dict(
function_code=event.key, function_code=event.key,
function_class=event.name, function_class=event.name,
description=event.description, description=event.description,
@ -54,13 +54,16 @@ class EventCluster:
Events.function_class, Events.function_class,
Events.endpoint_code, Events.endpoint_code,
Events.endpoint_uu_id, Events.endpoint_uu_id,
], ]
) )
event_to_save_database = Events.find_or_create(**event_dict_to_save)
if event_to_save_database.meta_data.created: if event_to_save_database.meta_data.created:
print(f"UUID: {event_to_save_database.uu_id} event is saved to {to_save_endpoint.uu_id}")
else:
event_to_save_database.update(**event_dict_to_save)
if event_to_save_database.meta_data.updated:
print(f"UUID: {event_to_save_database.uu_id} event is updated to {to_save_endpoint.uu_id}")
event_to_save_database.save(db=db_session) event_to_save_database.save(db=db_session)
print(
f"UUID: {event_to_save_database.uu_id} event is saved to {to_save_endpoint.uu_id}"
)
def match_event(self, event_key: str) -> "Event": def match_event(self, event_key: str) -> "Event":
""" """

View File

@ -17,7 +17,7 @@ SupersPeopleCreateEvent = Event(
key="ec4c2404-a61b-46c7-bbdf-ce3357e6cf41", key="ec4c2404-a61b-46c7-bbdf-ce3357e6cf41",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="Create events of people endpoint", description="Super Users Create events of people endpoint",
) )
# Update endpoint # Update endpoint
@ -26,7 +26,7 @@ SupersPeopleUpdateEvent = Event(
key="91e77de4-9f29-4309-b121-4aad256d440c", key="91e77de4-9f29-4309-b121-4aad256d440c",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="Update events of people endpoint", description="Super Users Update events of people endpoint",
) )
# List endpoint # List endpoint
@ -35,7 +35,7 @@ SupersPeopleListEvent = Event(
key="6828d280-e587-400d-a622-c318277386c3", key="6828d280-e587-400d-a622-c318277386c3",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="List events of people endpoint", description="Super Users List events of people endpoint",
) )

View File

@ -10,7 +10,7 @@ SuperUsersListEvent = Event(
key="341b394f-9f11-4abb-99e7-4b27fa6bf012", key="341b394f-9f11-4abb-99e7-4b27fa6bf012",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="List events of users endpoint", description="Super User List events of users endpoint",
) )
# Create endpoint # Create endpoint
@ -19,7 +19,7 @@ SuperUsersCreateEvent = Event(
key="4e7e189e-e015-4ff8-902d-60138cbc77a6", key="4e7e189e-e015-4ff8-902d-60138cbc77a6",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="Create events of users endpoint", description="Super User Create events of users endpoint",
) )
# Update endpoint # Update endpoint
@ -28,7 +28,7 @@ SuperUsersUpdateEvent = Event(
key="efa4aa4a-d414-4391-91ee-97eb617b7755", key="efa4aa4a-d414-4391-91ee-97eb617b7755",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="Update events of users endpoint", description="Super User Update events of users endpoint",
) )

View File

@ -5,7 +5,7 @@ from ApiControllers.abstracts.default_validations import CommonHeaders
from ApiControllers.providers.token_provider import TokenProvider from ApiControllers.providers.token_provider import TokenProvider
from Controllers.Postgres.pagination import PaginateOnly, Pagination, PaginationResult from Controllers.Postgres.pagination import PaginateOnly, Pagination, PaginationResult
from Controllers.Postgres.response import EndpointResponse from Controllers.Postgres.response import EndpointResponse, CreateEndpointResponse
from Schemas import Applications from Schemas import Applications
from Validations.application.validations import ( from Validations.application.validations import (
RequestApplication, RequestApplication,
@ -66,18 +66,18 @@ def application_create_route(
created_application = Applications.find_or_create( created_application = Applications.find_or_create(
db=db_session, db=db_session,
include_args=[ include_args=[
Applications.application_for == data.application_for, Applications.application_for,
Applications.application_code == data.application_code, Applications.application_code,
Applications.site_url == data.site_url, Applications.site_url,
] ],
** created_application_dict, **created_application_dict,
) )
if created_application.meta_data.created: if created_application.meta_data.created:
return EndpointResponse( return CreateEndpointResponse(
message="MSG0001-INSERT", message="MSG0001-INSERT",
data=created_application, data=created_application,
).response ).response
return EndpointResponse( return CreateEndpointResponse(
message="MSG0002-FOUND", message="MSG0002-FOUND",
data=created_application, data=created_application,
).response ).response
@ -98,23 +98,23 @@ def application_update_route(
""" """
token_object = TokenProvider.get_dict_from_redis(token=headers.token) token_object = TokenProvider.get_dict_from_redis(token=headers.token)
with Applications.new_session() as db_session: with Applications.new_session() as db_session:
updated_application_dict = data.model_dump() updated_application_dict = data.model_dump(exclude_unset=True, exclude_none=True)
found_application = Applications.filter_one( found_application = Applications.filter_one(
Applications.uu_id == application_uuid, db=db_session Applications.uu_id == application_uuid, db=db_session
).data ).data
if not found_application: if not found_application:
return EndpointResponse( return CreateEndpointResponse(
message="MSG0002-FOUND", message="MSG0002-FOUND",
data=found_application, data=found_application,
).response ).response
updated_application = found_application.update(**updated_application_dict) updated_application = found_application.update(db=db_session,**updated_application_dict)
updated_application.save(db_session) updated_application.save(db_session)
if updated_application.meta_data.updated: if updated_application.meta_data.updated:
return EndpointResponse( return CreateEndpointResponse(
message="MSG0003-UPDATE", message="MSG0003-UPDATE",
data=updated_application, data=updated_application,
).response ).response
return EndpointResponse( return CreateEndpointResponse(
message="MSG0003-UPDATE", message="MSG0003-UPDATE",
data=updated_application, data=updated_application,
).response ).response

View File

@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends
from typing import Any from typing import Any
from fastapi import APIRouter, Depends
from ApiControllers.abstracts.default_validations import CommonHeaders from ApiControllers.abstracts.default_validations import CommonHeaders
from ApiControllers.providers.token_provider import TokenProvider from ApiControllers.providers.token_provider import TokenProvider

View File

@ -14,7 +14,7 @@ ApplicationListEvent = Event(
key="b4efda1e-bde7-4659-ab1a-ef74c0fd88b6", key="b4efda1e-bde7-4659-ab1a-ef74c0fd88b6",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="List events of users endpoint", description="Super Users List applications endpoint",
) )
# Create endpoint # Create endpoint
@ -23,7 +23,7 @@ ApplicationCreateEvent = Event(
key="f53ca9aa-5536-4d77-9129-78d67e61db4a", key="f53ca9aa-5536-4d77-9129-78d67e61db4a",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="Create events of users endpoint", description="Super Users Create applications endpoint",
) )
# Update endpoint # Update endpoint
@ -32,7 +32,7 @@ ApplicationUpdateEvent = Event(
key="0e9a855e-4e69-44b5-8ac2-825daa32840c", key="0e9a855e-4e69-44b5-8ac2-825daa32840c",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="Update events of users endpoint", description="Super Users Update applications endpoint",
) )
#Bind Application to employee #Bind Application to employee
@ -41,7 +41,7 @@ ApplicationBindEmployeeEvent = Event(
key="26a96c2d-bca8-41cb-8ac1-f3ca8124434b", key="26a96c2d-bca8-41cb-8ac1-f3ca8124434b",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="Bind events of users endpoint", description="Super Users Application Bind employee endpoint",
) )
#Bind Application to occupant #Bind Application to occupant
@ -50,7 +50,7 @@ ApplicationBindOccupantEvent = Event(
key="4eaf2bb0-2a42-4d21-ae65-a9259ebee189", key="4eaf2bb0-2a42-4d21-ae65-a9259ebee189",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="Bind events of users endpoint", description="Super Users Application Bind occupant endpoint",
) )

View File

@ -18,7 +18,7 @@ EventsListEvent = Event(
key="0a08c64b-ce20-4791-b1e9-014db6b75ea7", key="0a08c64b-ce20-4791-b1e9-014db6b75ea7",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="List services endpoint", description="Super Users List services endpoint",
) )
@ -28,7 +28,7 @@ EventRegisterServiceEvent = Event(
key="e18e7f89-5708-4a15-9258-99b0903ed43d", key="e18e7f89-5708-4a15-9258-99b0903ed43d",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="Register service endpoint", description="Super Users Register service endpoint",
) )
# Bind employee extra endpoint # Bind employee extra endpoint
@ -37,7 +37,7 @@ EventBindEmployeeExtraEvent = Event(
key="cd452928-4256-4fb4-b81e-0ca41d723616", key="cd452928-4256-4fb4-b81e-0ca41d723616",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="Bind service to employee extra endpoint", description="Super Users Bind service to employee extra endpoint",
) )
# Bind occupant extra endpoint # Bind occupant extra endpoint
@ -46,7 +46,7 @@ EventBindOccupantExtraEvent = Event(
key="cb11a150-8049-45c9-8cf3-d5290ffd2e4a", key="cb11a150-8049-45c9-8cf3-d5290ffd2e4a",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="Bind service to occupant extra endpoint", description="Super Users Bind service to occupant extra endpoint",
) )

View File

@ -10,7 +10,7 @@ ServiceEndpointListEvent = Event(
key="7da6ceac-925a-4faa-9cc5-3f34396b5684", key="7da6ceac-925a-4faa-9cc5-3f34396b5684",
request_validator=None, # TODO: Add request validator request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator response_validator=None, # TODO: Add response validator
description="List services endpoint", description="Super Users List services endpoint",
) )

View File

@ -1,29 +0,0 @@
FROM python:3.12-slim
WORKDIR /
# Install system dependencies and Poetry
RUN apt-get update && apt-get install -y --no-install-recommends gcc && rm -rf /var/lib/apt/lists/* && pip install --no-cache-dir poetry
# Copy Poetry configuration
COPY /pyproject.toml ./pyproject.toml
# Configure Poetry and install dependencies with optimizations
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi --no-root --only main \
&& pip cache purge && rm -rf ~/.cache/pypoetry
# Copy application code
COPY /ApiControllers /ApiControllers
COPY /ApiDefaults /ApiDefaults
COPY /Controllers /Controllers
COPY /Schemas /Schemas
COPY /ApiServices/IdentityService/Endpoints /ApiDefaults/Endpoints
COPY /ApiServices/IdentityService/Events /ApiDefaults/Events
COPY /ApiServices/IdentityService/Validations /ApiDefaults/Validations
# Set Python path to include app directory
ENV PYTHONPATH=/ PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
# Run the application using the configured uvicorn server
CMD ["poetry", "run", "python", "ApiDefaults/app.py"]

View File

@ -1,67 +0,0 @@
from fastapi import APIRouter, Depends
from ApiControllers.abstracts.default_validations import CommonHeaders
from ApiControllers.providers.token_provider import TokenProvider
from Controllers.Postgres.pagination import PaginateOnly
from Events.people.cluster import PeopleRouterCluster
people_route = APIRouter(prefix="/people", tags=["People"])
@people_route.post(
path="/list",
description="List people endpoint",
operation_id="f102db46-031a-43e4-966a-dae6896f985b",
)
def people_route_list(
data: PaginateOnly,
headers: CommonHeaders = Depends(CommonHeaders.as_dependency),
):
"""
List people endpoint
"""
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 = PeopleRouterCluster.get_event_cluster("PeopleList")
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
return event_cluster_matched.event_callable(data=data)
@people_route.post(
path="/create",
description="Create people endpoint",
operation_id="eb465fde-337f-4b81-94cf-28c6d4f2b1b6",
)
def people_route_create(
headers: CommonHeaders = Depends(CommonHeaders.as_dependency),
):
"""
Create people endpoint
"""
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 = PeopleRouterCluster.get_event_cluster("PeopleCreate")
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
return event_cluster_matched.event_callable()
@people_route.post(
path="/update",
description="Update people endpoint",
operation_id="c9e5ba69-6915-43f5-8f9c-a5c2aa865b89",
)
def people_route_update(
headers: CommonHeaders = Depends(CommonHeaders.as_dependency),
):
"""
Update people endpoint
"""
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 = PeopleRouterCluster.get_event_cluster("PeopleUpdate")
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
return event_cluster_matched.event_callable()

View File

@ -1,18 +0,0 @@
from fastapi import APIRouter
def get_routes() -> list[APIRouter]:
from .people.route import people_route
from .user.route import user_route
return [user_route, people_route]
def get_safe_endpoint_urls() -> list[tuple[str, str]]:
return [
("/", "GET"),
("/docs", "GET"),
("/redoc", "GET"),
("/openapi.json", "GET"),
("/metrics", "GET"),
]

View File

@ -1,72 +0,0 @@
import uuid
from fastapi import APIRouter, Header
from typing import Any
from ApiDefaults.config import api_config
from Events.user.cluster import UserRouterCluster
from ApiControllers.providers.token_provider import TokenProvider
from ApiControllers.abstracts.default_validations import CommonHeaders
from Controllers.Postgres.pagination import PaginateOnly
user_route = APIRouter(prefix="/user", tags=["User"])
@user_route.post(
path="/list",
description="List users endpoint",
operation_id="1aca3094-fe80-4e0f-a460-1a506419082a",
)
def user_list_route(
data: PaginateOnly,
headers: CommonHeaders,
):
"""
List users endpoint
"""
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 = UserRouterCluster.get_event_cluster("UserList")
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
return event_cluster_matched.event_callable(data=data)
@user_route.post(
path="/create",
description="Create users endpoint",
operation_id="9686211f-4260-485d-8076-186c22c053aa",
)
def user_create_route(
data: Any,
headers: CommonHeaders,
):
"""
Create users endpoint
"""
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 = UserRouterCluster.get_event_cluster("UserCreate")
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
return event_cluster_matched.event_callable(data=data)
@user_route.post(
path="/update",
description="Update users endpoint",
operation_id="268e887b-5ff5-4f99-b1be-7e127e28a198",
)
def user_update_route(
data: Any,
headers: CommonHeaders,
):
"""
Update users endpoint
"""
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 = UserRouterCluster.get_event_cluster("UserUpdate")
event_cluster_matched = FoundCluster.match_event(event_key=event_key)
return event_cluster_matched.event_callable(data=data)

View File

@ -1,8 +0,0 @@
from .people.cluster import PeopleRouterCluster
from .user.cluster import UserRouterCluster
__all__ = [
"PeopleRouterCluster",
"UserRouterCluster",
]

View File

@ -1,25 +0,0 @@
from ApiControllers.abstracts.event_clusters import EventCluster, RouterCluster
from .supers_events import (
SupersPeopleCreateEvent,
SupersPeopleUpdateEvent,
SupersPeopleListEvent,
)
PeopleRouterCluster = RouterCluster(name="PeopleRouterCluster")
PeopleEventClusterList = EventCluster(
name="PeopleList", endpoint_uu_id="f102db46-031a-43e4-966a-dae6896f985b"
)
PeopleEventClusterList.add_event(SupersPeopleListEvent)
PeopleEventClusterCreate = EventCluster(
name="PeopleCreate", endpoint_uu_id="eb465fde-337f-4b81-94cf-28c6d4f2b1b6"
)
PeopleEventClusterCreate.add_event(SupersPeopleCreateEvent)
PeopleEventClusterUpdate = EventCluster(
name="PeopleUpdate", endpoint_uu_id="c9e5ba69-6915-43f5-8f9c-a5c2aa865b89"
)
PeopleEventClusterUpdate.add_event(SupersPeopleUpdateEvent)
PeopleRouterCluster.set_event_cluster(PeopleEventClusterList)
PeopleRouterCluster.set_event_cluster(PeopleEventClusterCreate)
PeopleRouterCluster.set_event_cluster(PeopleEventClusterUpdate)

View File

@ -1,103 +0,0 @@
from ApiControllers.abstracts.event_clusters import Event
from Validations.people.validations import (
REQUESTAWMXNTKMGPPOJWRCTZUBADNFLQDBDYVQAORFAVCSXUUHEBQHCEPCSKFBADBODFDBPYKOVINV,
)
from Controllers.Postgres.pagination import (
ListOptions,
Pagination,
PaginationResult,
PaginateOnly,
)
from Controllers.Postgres.response import EndpointResponse
from Schemas import People
SupersPeopleCreateEvent = Event(
name="supers_people_create",
key="ec4c2404-a61b-46c7-bbdf-ce3357e6cf41",
request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator
description="Create events of people endpoint",
)
# Update endpoint
SupersPeopleUpdateEvent = Event(
name="supers_people_update",
key="91e77de4-9f29-4309-b121-4aad256d440c",
request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator
description="Update events of people endpoint",
)
# List endpoint
SupersPeopleListEvent = Event(
name="supers_people_list",
key="6828d280-e587-400d-a622-c318277386c3",
request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator
description="List events of people endpoint",
)
def supers_people_create_callable():
"""
Example callable method
"""
return {
"completed": True,
"message": "Example callable method 2",
"info": {
"host": "example_host",
"user_agent": "example_user_agent",
},
}
SupersPeopleCreateEvent.event_callable = supers_people_create_callable
def supers_people_update_callable():
"""
Example callable method
"""
return {
"completed": True,
"message": "Example callable method 2",
"info": {
"host": "example_host",
"user_agent": "example_user_agent",
},
}
SupersPeopleUpdateEvent.event_callable = supers_people_update_callable
def supers_people_list_callable(data: PaginateOnly):
"""
Example callable method
"""
list_options = PaginateOnly(**data.model_dump())
with People.new_session() as db_session:
if list_options.query:
people_list = People.filter_all(
*People.convert(list_options.query), db=db_session
)
else:
people_list = People.filter_all(db=db_session)
pagination = Pagination(data=people_list)
pagination.change(**list_options.model_dump())
pagination_result = PaginationResult(
data=people_list,
pagination=pagination,
response_model=REQUESTAWMXNTKMGPPOJWRCTZUBADNFLQDBDYVQAORFAVCSXUUHEBQHCEPCSKFBADBODFDBPYKOVINV,
)
return EndpointResponse(
message="MSG0002-LIST",
pagination_result=pagination_result,
).response
SupersPeopleListEvent.event_callable = supers_people_list_callable

View File

@ -1,27 +0,0 @@
from ApiControllers.abstracts.event_clusters import EventCluster, RouterCluster
from .supers_events import (
SuperUsersListEvent,
SuperUsersCreateEvent,
SuperUsersUpdateEvent,
)
UserRouterCluster = RouterCluster(name="UserRouterCluster")
UserEventClusterList = EventCluster(
name="UserList", endpoint_uu_id="1aca3094-fe80-4e0f-a460-1a506419082a"
)
UserEventClusterList.add_event(SuperUsersListEvent)
UserEventClusterCreate = EventCluster(
name="UserCreate", endpoint_uu_id="9686211f-4260-485d-8076-186c22c053aa"
)
UserEventClusterCreate.add_event(SuperUsersCreateEvent)
UserEventClusterUpdate = EventCluster(
name="UserUpdate", endpoint_uu_id="268e887b-5ff5-4f99-b1be-7e127e28a198"
)
UserEventClusterUpdate.add_event(SuperUsersUpdateEvent)
UserRouterCluster.set_event_cluster(UserEventClusterList)
UserRouterCluster.set_event_cluster(UserEventClusterCreate)
UserRouterCluster.set_event_cluster(UserEventClusterUpdate)

View File

@ -1,94 +0,0 @@
from ApiControllers.abstracts.event_clusters import Event
from Controllers.Postgres.pagination import Pagination, PaginationResult, PaginateOnly
from Controllers.Postgres.response import EndpointResponse
from Schemas import Users
# List endpoint
SuperUsersListEvent = Event(
name="supers_users_list",
key="341b394f-9f11-4abb-99e7-4b27fa6bf012",
request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator
description="List events of users endpoint",
)
# Create endpoint
SuperUsersCreateEvent = Event(
name="supers_users_create",
key="4e7e189e-e015-4ff8-902d-60138cbc77a6",
request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator
description="Create events of users endpoint",
)
# Update endpoint
SuperUsersUpdateEvent = Event(
name="supers_users_update",
key="efa4aa4a-d414-4391-91ee-97eb617b7755",
request_validator=None, # TODO: Add request validator
response_validator=None, # TODO: Add response validator
description="Update events of users endpoint",
)
def supers_users_list_callable(list_options: PaginateOnly):
"""
Example callable method
"""
list_options = PaginateOnly(**list_options.model_dump())
with Users.new_session() as db_session:
if list_options.query:
users_list = Users.filter_all(
*Users.convert(list_options.query), db=db_session
)
else:
users_list = Users.filter_all(db=db_session)
pagination = Pagination(data=users_list)
pagination.change(**list_options.model_dump())
pagination_result = PaginationResult(
data=users_list,
pagination=pagination,
# response_model="",
).pagination.as_dict
return EndpointResponse(
message="MSG0003-LIST",
pagination_result=pagination_result,
).response
SuperUsersListEvent.event_callable = supers_users_list_callable
def supers_users_create_callable():
"""
Example callable method
"""
return {
"completed": True,
"message": "Example callable method 2",
"info": {
"host": "example_host",
"user_agent": "example_user_agent",
},
}
SuperUsersCreateEvent.event_callable = supers_users_create_callable
def supers_users_update_callable():
"""
Example callable method
"""
return {
"completed": True,
"message": "Example callable method 2",
"info": {
"host": "example_host",
"user_agent": "example_user_agent",
},
}
SuperUsersUpdateEvent.event_callable = supers_users_update_callable

View File

@ -1,26 +0,0 @@
from pydantic import BaseModel
from typing import Optional
class ListOptions(BaseModel):
"""
Query for list option abilities
"""
page: Optional[int] = 1
size: Optional[int] = 10
order_field: Optional[str] = "id"
order_type: Optional[str] = "asc"
# include_joins: Optional[list] = None
query: Optional[dict] = None
class PaginateOnly(BaseModel):
"""
Query for list option abilities
"""
page: Optional[int] = 1
size: Optional[int] = 10
order_field: Optional[str] = "id"
order_type: Optional[str] = "asc"

View File

@ -1,24 +0,0 @@
from pydantic import BaseModel
class REQUESTAWMXNTKMGPPOJWRCTZUBADNFLQDBDYVQAORFAVCSXUUHEBQHCEPCSKFBADBODFDBPYKOVINV(
BaseModel
):
uu_id: str
created_at: str
updated_at: str
person_tag: str
expiry_starts: str
expiry_ends: str
firstname: str
middle_name: str
surname: str
birth_date: str
birth_place: str
sex_code: str
country_code: str
tax_no: str
active: bool
deleted: bool
is_confirmed: bool
is_notification_send: bool

View File

@ -1,33 +0,0 @@
from pydantic import BaseModel
class FF7C859A068EB4583A47AB5924EC8C19A033881823FBA42EAAD089BF7AC8059CE(BaseModel):
"""
a13143ade48954c2ba3f86869e027de5b28c8a9b619bf4ef28264a8e375371601
"""
pass
class F1B82565925FF4F5DAD2A36370788305A0A362E031EAC4A9E8BDFF4F35E265A6C(BaseModel):
"""
aa487ab3bfd9e4e6abc2db714ac6197f60bbc9068ac6541e7a815d5b1e969796b
"""
pass
class F3117E7D66FE6471C8452B97AB504EF0C29822B6395CA4D65A18FDD563F0EC8D7(BaseModel):
"""
a1bf55a214b684438a97a47e4f097ac7ae27b0dff03c4475cbd4301e24a032aac
"""
pass
class F33B4DE316B8A456480DD6ED5B5E2D35A2E6FCAF74BAC40D7A2970D318B153F85(BaseModel):
"""
F33B4DE316B8A456480DD6ED5B5E2D35A2E6FCAF74BAC40D7A2970D318B153F85
"""
pass

View File

@ -134,3 +134,21 @@ class EndpointResponse(BaseModel):
"data": resutl_data, "data": resutl_data,
"pagination": pagination_dict, "pagination": pagination_dict,
} }
class CreateEndpointResponse(BaseModel):
"""Create endpoint response model."""
completed: bool = True
message: str = "Success"
data: Any
@property
def response(self):
"""Convert response to dictionary format."""
return {
"completed": self.completed,
"message": self.message,
"data": self.data,
}

View File

@ -0,0 +1,177 @@
"use server";
import { retrieveAccessToken } from "@/apicalls/cookies/token";
import {
DEFAULT_RESPONSE,
defaultHeaders,
FetchOptions,
HttpMethod,
ApiResponse,
DEFAULT_TIMEOUT,
} from "./basics";
/**
* Creates a promise that rejects after a specified timeout
* @param ms Timeout in milliseconds
* @param controller AbortController to abort the fetch request
* @returns A promise that rejects after the timeout
*/
const createTimeoutPromise = (
ms: number,
controller: AbortController
): Promise<never> => {
return new Promise((_, reject) => {
setTimeout(() => {
controller.abort();
reject(new Error(`Request timed out after ${ms}ms`));
}, ms);
});
};
/**
* Prepares a standardized API response
* @param response The response data
* @param statusCode HTTP status code
* @returns Standardized API response
*/
const prepareResponse = <T>(
response: T,
statusCode: number
): ApiResponse<T> => {
try {
return {
status: statusCode,
data: response || ({} as T),
};
} catch (error) {
console.error("Error preparing response:", error);
return {
...DEFAULT_RESPONSE,
error: "Response parsing error",
} as ApiResponse<T>;
}
};
/**
* Core fetch function with timeout and error handling
* @param url The URL to fetch
* @param options Fetch options
* @param headers Request headers
* @param payload Request payload
* @returns API response
*/
async function coreFetch<T>(
url: string,
options: FetchOptions = {},
headers: Record<string, string> = defaultHeaders,
payload?: any
): Promise<ApiResponse<T>> {
const { method = "POST", cache = false, timeout = DEFAULT_TIMEOUT } = options;
try {
const controller = new AbortController();
const signal = controller.signal;
const fetchOptions: RequestInit = {
method,
headers,
cache: cache ? "force-cache" : "no-cache",
signal,
};
// Add body if needed
if (method !== "GET" && payload) {
fetchOptions.body = JSON.stringify(
// Handle special case for updateDataWithToken
payload.payload ? payload.payload : payload
);
}
// Create timeout promise
const timeoutPromise = createTimeoutPromise(timeout, controller);
// Race between fetch and timeout
const response = (await Promise.race([
fetch(url, fetchOptions),
timeoutPromise,
])) as Response;
const responseJson = await response.json();
if (process.env.NODE_ENV !== "production") {
console.log("Fetching:", url, fetchOptions);
console.log("Response:", responseJson);
}
return prepareResponse(responseJson, response.status);
} catch (error) {
console.error(`Fetch error (${url}):`, error);
return {
...DEFAULT_RESPONSE,
error: error instanceof Error ? error.message : "Network error",
} as ApiResponse<T>;
}
}
/**
* Fetch data without authentication
*/
async function fetchData<T>(
endpoint: string,
payload?: any,
method: HttpMethod = "POST",
cache: boolean = false,
timeout: number = DEFAULT_TIMEOUT
): Promise<ApiResponse<T>> {
return coreFetch<T>(
endpoint,
{ method, cache, timeout },
defaultHeaders,
payload
);
}
/**
* Fetch data with authentication token
*/
async function fetchDataWithToken<T>(
endpoint: string,
payload?: any,
method: HttpMethod = "POST",
cache: boolean = false,
timeout: number = DEFAULT_TIMEOUT
): Promise<ApiResponse<T>> {
const accessToken = (await retrieveAccessToken()) || "";
const headers = {
...defaultHeaders,
"eys-acs-tkn": accessToken,
};
return coreFetch<T>(endpoint, { method, cache, timeout }, headers, payload);
}
/**
* Update data with authentication token and UUID
*/
async function updateDataWithToken<T>(
endpoint: string,
uuid: string,
payload?: any,
method: HttpMethod = "POST",
cache: boolean = false,
timeout: number = DEFAULT_TIMEOUT
): Promise<ApiResponse<T>> {
const accessToken = (await retrieveAccessToken()) || "";
const headers = {
...defaultHeaders,
"eys-acs-tkn": accessToken,
};
return coreFetch<T>(
`${endpoint}/${uuid}`,
{ method, cache, timeout },
headers,
payload
);
}
export { fetchData, fetchDataWithToken, updateDataWithToken };

View File

@ -1,144 +0,0 @@
"use server";
import { retrieveAccessToken } from "@/apicalls/cookies/token";
const defaultHeaders = {
accept: "application/json",
language: "tr",
domain: "evyos.com.tr",
tz: "GMT+3",
"Content-type": "application/json",
};
const DefaultResponse = {
error: "Hata tipi belirtilmedi",
status: "500",
data: {},
};
const cacheList = ["no-cache", "no-store", "force-cache", "only-if-cached"];
const prepareResponse = (response: any, statusCode: number) => {
try {
return {
status: statusCode,
data: response || {},
};
} catch (error) {
console.error("Error preparing response:", error);
return {
...DefaultResponse,
error: "Response parsing error",
};
}
};
const fetchData = async (
endpoint: string,
payload: any,
method: string = "POST",
cache: boolean = false
) => {
try {
const headers = {
...defaultHeaders,
};
const fetchOptions: RequestInit = {
method,
headers,
cache: cache ? "force-cache" : "no-cache",
};
if (method === "POST" && payload) {
fetchOptions.body = JSON.stringify(payload);
}
const response = await fetch(endpoint, fetchOptions);
const responseJson = await response.json();
console.log("Fetching:", endpoint, fetchOptions);
console.log("Response:", responseJson);
return prepareResponse(responseJson, response.status);
} catch (error) {
console.error("Fetch error:", error);
return {
...DefaultResponse,
error: error instanceof Error ? error.message : "Network error",
};
}
};
const updateDataWithToken = async (
endpoint: string,
uuid: string,
payload: any,
method: string = "POST",
cache: boolean = false
) => {
const accessToken = (await retrieveAccessToken()) || "";
const headers = {
...defaultHeaders,
"eys-acs-tkn": accessToken,
};
try {
const fetchOptions: RequestInit = {
method,
headers,
cache: cache ? "force-cache" : "no-cache",
};
if (method !== "GET" && payload) {
fetchOptions.body = JSON.stringify(payload.payload);
}
const response = await fetch(`${endpoint}/${uuid}`, fetchOptions);
const responseJson = await response.json();
console.log("Fetching:", `${endpoint}/${uuid}`, fetchOptions);
console.log("Response:", responseJson);
return prepareResponse(responseJson, response.status);
} catch (error) {
console.error("Update error:", error);
return {
...DefaultResponse,
error: error instanceof Error ? error.message : "Network error",
};
}
};
const fetchDataWithToken = async (
endpoint: string,
payload: any,
method: string = "POST",
cache: boolean = false
) => {
const accessToken = (await retrieveAccessToken()) || "";
const headers = {
...defaultHeaders,
"eys-acs-tkn": accessToken,
};
try {
const fetchOptions: RequestInit = {
method,
headers,
cache: cache ? "force-cache" : "no-cache",
};
if (method === "POST" && payload) {
fetchOptions.body = JSON.stringify(payload);
}
const response = await fetch(endpoint, fetchOptions);
const responseJson = await response.json();
console.log("Fetching:", endpoint, fetchOptions);
console.log("Response:", responseJson);
return prepareResponse(responseJson, response.status);
} catch (error) {
console.error("Fetch with token error:", error);
return {
...DefaultResponse,
error: error instanceof Error ? error.message : "Network error",
};
}
};
export { fetchData, fetchDataWithToken, updateDataWithToken };

View File

@ -3,18 +3,34 @@ const formatServiceUrl = (url: string) => {
return url.startsWith("http") ? url : `http://${url}`; return url.startsWith("http") ? url : `http://${url}`;
}; };
export const baseUrlAuth = formatServiceUrl( const baseUrlAuth = formatServiceUrl(
process.env.NEXT_PUBLIC_AUTH_SERVICE_URL || "auth_service:8001" process.env.NEXT_PUBLIC_AUTH_SERVICE_URL || "auth_service:8001"
); );
export const baseUrlPeople = formatServiceUrl( const baseUrlPeople = formatServiceUrl(
process.env.NEXT_PUBLIC_VALIDATION_SERVICE_URL || "identity_service:8002" process.env.NEXT_PUBLIC_VALIDATION_SERVICE_URL || "identity_service:8002"
); );
// export const baseUrlEvent = formatServiceUrl( const baseUrlApplication = formatServiceUrl(
// process.env.NEXT_PUBLIC_EVENT_SERVICE_URL || "eventservice:8888" process.env.NEXT_PUBLIC_MANAGEMENT_SERVICE_URL || "management_service:8004"
// ); );
export const tokenSecret = process.env.TOKENSECRET_90 || "";
export const cookieObject: any = { // Types for better type safety
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
interface ApiResponse<T = any> {
status: number;
data: T;
error?: string;
}
interface FetchOptions {
method?: HttpMethod;
cache?: boolean;
timeout?: number;
}
const tokenSecret = process.env.TOKENSECRET_90 || "";
const cookieObject: any = {
httpOnly: true, httpOnly: true,
path: "/", path: "/",
sameSite: "none", sameSite: "none",
@ -23,53 +39,29 @@ export const cookieObject: any = {
priority: "high", priority: "high",
}; };
interface FilterListInterface { // Constants
page?: number | null | undefined; const DEFAULT_TIMEOUT = 10000; // 10 seconds
size?: number | null | undefined; const defaultHeaders = {
orderField?: string | null | undefined; accept: "application/json",
orderType?: string | null | undefined; language: "tr",
includeJoins?: any[] | null | undefined; domain: "management.com.tr",
query?: any | null | undefined; tz: "GMT+3",
} "Content-type": "application/json",
};
const DEFAULT_RESPONSE: ApiResponse = {
error: "Hata tipi belirtilmedi",
status: 500,
data: {},
};
class FilterList { export type { HttpMethod, ApiResponse, FetchOptions };
page: number; export {
size: number; DEFAULT_TIMEOUT,
orderField: string; DEFAULT_RESPONSE,
orderType: string; defaultHeaders,
includeJoins: any[]; baseUrlAuth,
query: any; baseUrlPeople,
baseUrlApplication,
constructor({ tokenSecret,
page = 1, cookieObject,
size = 5, };
orderField = "id",
orderType = "asc",
includeJoins = [],
query = {},
}: FilterListInterface = {}) {
this.page = page ?? 1;
this.size = size ?? 5;
this.orderField = orderField ?? "uu_id";
this.orderType = orderType ?? "asc";
this.orderType = this.orderType.startsWith("a") ? "asc" : "desc";
this.includeJoins = includeJoins ?? [];
this.query = query ?? {};
}
filter() {
return {
page: this.page,
size: this.size,
orderField: this.orderField,
orderType: this.orderType,
includeJoins: this.includeJoins,
query: this.query,
};
}
}
const defaultFilterList = new FilterList({});
export { FilterList, defaultFilterList };
export type { FilterListInterface };

View File

@ -2,29 +2,17 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { CreateComponentProps } from "./types"; import { CreateComponentProps, FieldDefinition } from "./types";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form"; import { useForm, SubmitHandler } from "react-hook-form";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { AlertCircle } from "lucide-react"; import { AlertCircle } from "lucide-react";
// Import field definitions type
interface FieldDefinition {
type: string;
group: string;
label: string;
options?: string[];
readOnly?: boolean;
required?: boolean;
defaultValue?: any;
name?: string;
}
export function CreateComponent<T>({ export function CreateComponent<T>({
refetch, refetch,
setMode, setMode,
@ -33,6 +21,7 @@ export function CreateComponent<T>({
lang, lang,
translations, translations,
formProps = {}, formProps = {},
apiUrl,
}: CreateComponentProps<T>) { }: CreateComponentProps<T>) {
const t = translations[lang as keyof typeof translations] || {}; const t = translations[lang as keyof typeof translations] || {};
@ -75,7 +64,7 @@ export function CreateComponent<T>({
formState: { errors }, formState: { errors },
setValue, setValue,
watch, watch,
} = useForm({ } = useForm<Record<string, any>>({
defaultValues, defaultValues,
resolver: validationSchema ? zodResolver(validationSchema) : undefined, resolver: validationSchema ? zodResolver(validationSchema) : undefined,
}); });
@ -83,14 +72,26 @@ export function CreateComponent<T>({
const formValues = watch(); const formValues = watch();
// Handle form submission // Handle form submission
const onSubmit = async (data: Record<string, any>) => { const onSubmit: SubmitHandler<Record<string, any>> = async (data) => {
try { try {
console.log("Form data to save:", data); if (apiUrl) {
const createUrl = `${apiUrl}/create`;
const response = await fetch(createUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
console.log("Response:", response.ok);
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
// Here you would make an API call to save the data const createdItem = await response.json();
// For example: await createApplication(data); console.log("Created item:", createdItem);
}
// Mock API call success
if (refetch) refetch(); if (refetch) refetch();
setMode("list"); setMode("list");
setSelectedItem(null); setSelectedItem(null);
@ -140,13 +141,13 @@ export function CreateComponent<T>({
return ( return (
<div className="space-y-2" key={fieldName}> <div className="space-y-2" key={fieldName}>
<Label htmlFor={fieldName}> <Label htmlFor={fieldName}>
{t[fieldName] || field.label} {t[fieldName] || field.label[lang as "en" | "tr"]}
{field.required && <span className="text-red-500 ml-1">*</span>} {field.required && <span className="text-red-500 ml-1">*</span>}
</Label> </Label>
<Input <Input
id={fieldName} id={fieldName}
{...register(fieldName)} {...register(fieldName)}
placeholder={t[fieldName] || field.label} placeholder={t[fieldName] || field.label[lang as "en" | "tr"]}
disabled={field.readOnly} disabled={field.readOnly}
/> />
{errorMessage && ( {errorMessage && (
@ -159,13 +160,13 @@ export function CreateComponent<T>({
return ( return (
<div className="space-y-2" key={fieldName}> <div className="space-y-2" key={fieldName}>
<Label htmlFor={fieldName}> <Label htmlFor={fieldName}>
{t[fieldName] || field.label} {t[fieldName] || field.label[lang as "en" | "tr"]}
{field.required && <span className="text-red-500 ml-1">*</span>} {field.required && <span className="text-red-500 ml-1">*</span>}
</Label> </Label>
<Textarea <Textarea
id={fieldName} id={fieldName}
{...register(fieldName)} {...register(fieldName)}
placeholder={t[fieldName] || field.label} placeholder={t[fieldName] || field.label[lang as "en" | "tr"]}
rows={3} rows={3}
disabled={field.readOnly} disabled={field.readOnly}
/> />
@ -179,7 +180,7 @@ export function CreateComponent<T>({
return ( return (
<div className="space-y-2" key={fieldName}> <div className="space-y-2" key={fieldName}>
<Label htmlFor={fieldName}> <Label htmlFor={fieldName}>
{t[fieldName] || field.label} {t[fieldName] || field.label[lang as "en" | "tr"]}
{field.required && <span className="text-red-500 ml-1">*</span>} {field.required && <span className="text-red-500 ml-1">*</span>}
</Label> </Label>
<Select <Select
@ -188,7 +189,7 @@ export function CreateComponent<T>({
disabled={field.readOnly} disabled={field.readOnly}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder={t[fieldName] || field.label} /> <SelectValue placeholder={t[fieldName] || field.label[lang as "en" | "tr"]} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{field.options?.map((option) => ( {field.options?.map((option) => (
@ -216,7 +217,7 @@ export function CreateComponent<T>({
disabled={field.readOnly} disabled={field.readOnly}
/> />
<Label htmlFor={fieldName}> <Label htmlFor={fieldName}>
{t[fieldName] || field.label} {t[fieldName] || field.label[lang as "en" | "tr"]}
{field.required && <span className="text-red-500 ml-1">*</span>} {field.required && <span className="text-red-500 ml-1">*</span>}
</Label> </Label>
{errorMessage && ( {errorMessage && (
@ -231,7 +232,7 @@ export function CreateComponent<T>({
}; };
return ( return (
<form onSubmit={handleSubmit(() => console.log("Form data to save:"))}> <form onSubmit={handleSubmit(onSubmit)}>
<Card className="w-full mb-6"> <Card className="w-full mb-6">
<CardHeader> <CardHeader>
<CardTitle>{t.create || "Create"}</CardTitle> <CardTitle>{t.create || "Create"}</CardTitle>

View File

@ -15,6 +15,7 @@ export function FormDisplay<T>({
lang, lang,
translations, translations,
formProps = {}, formProps = {},
apiUrl,
}: FormDisplayProps<T>) { }: FormDisplayProps<T>) {
const [enhancedFormProps, setEnhancedFormProps] = useState(formProps); const [enhancedFormProps, setEnhancedFormProps] = useState(formProps);
@ -84,6 +85,7 @@ export function FormDisplay<T>({
lang={lang} lang={lang}
translations={translations} translations={translations}
formProps={enhancedFormProps} formProps={enhancedFormProps}
apiUrl={apiUrl}
/> />
); );
case "update": case "update":
@ -98,6 +100,7 @@ export function FormDisplay<T>({
lang={lang} lang={lang}
translations={translations} translations={translations}
formProps={enhancedFormProps} formProps={enhancedFormProps}
apiUrl={apiUrl}
/> />
) : null; ) : null;
case "view": case "view":

View File

@ -2,7 +2,7 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { UpdateComponentProps } from "./types"; import { UpdateComponentProps, FieldDefinition } from "./types";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
@ -13,18 +13,6 @@ import { useForm } from "react-hook-form";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { AlertCircle } from "lucide-react"; import { AlertCircle } from "lucide-react";
// Import field definitions type
interface FieldDefinition {
type: string;
group: string;
label: string;
options?: string[];
readOnly?: boolean;
required?: boolean;
defaultValue?: any;
name?: string; // Add name property for TypeScript compatibility
}
export function UpdateComponent<T>({ export function UpdateComponent<T>({
initialData, initialData,
refetch, refetch,
@ -33,23 +21,20 @@ export function UpdateComponent<T>({
onCancel, onCancel,
lang, lang,
translations, translations,
apiUrl,
formProps = {}, formProps = {},
}: UpdateComponentProps<T>) { }: UpdateComponentProps<T>) {
const t = translations[lang as keyof typeof translations] || {}; const t = translations[lang as keyof typeof translations] || {};
// Get field definitions from formProps if available
const fieldDefinitions = formProps.fieldDefinitions || {}; const fieldDefinitions = formProps.fieldDefinitions || {};
const validationSchema = formProps.validationSchema; const validationSchema = formProps.validationSchema;
// Group fields by their group property
const [groupedFields, setGroupedFields] = useState<Record<string, FieldDefinition[]>>({}); const [groupedFields, setGroupedFields] = useState<Record<string, FieldDefinition[]>>({});
// Process field definitions to group them
useEffect(() => { useEffect(() => {
if (Object.keys(fieldDefinitions).length > 0) { if (Object.keys(fieldDefinitions).length > 0) {
const groups: Record<string, FieldDefinition[]> = {}; const groups: Record<string, FieldDefinition[]> = {};
// Group fields by their group property
Object.entries(fieldDefinitions).forEach(([fieldName, definition]) => { Object.entries(fieldDefinitions).forEach(([fieldName, definition]) => {
const def = definition as FieldDefinition; const def = definition as FieldDefinition;
if (!groups[def.group]) { if (!groups[def.group]) {
@ -62,32 +47,36 @@ export function UpdateComponent<T>({
} }
}, [fieldDefinitions]); }, [fieldDefinitions]);
// Initialize form with default values from field definitions and initialData
const defaultValues: Record<string, any> = {}; const defaultValues: Record<string, any> = {};
Object.entries(fieldDefinitions).forEach(([key, def]) => { Object.entries(fieldDefinitions).forEach(([key, def]) => {
const fieldDef = def as FieldDefinition; const fieldDef = def as FieldDefinition;
defaultValues[key] = fieldDef.defaultValue !== undefined ? fieldDef.defaultValue : ""; defaultValues[key] = fieldDef.defaultValue !== undefined ? fieldDef.defaultValue : "";
}); });
// Merge initialData with default values
if (initialData) { if (initialData) {
Object.assign(defaultValues, initialData as Record<string, any>); Object.assign(defaultValues, initialData as Record<string, any>);
} }
// Setup form with validation schema if available
const { const {
register, register,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors, isSubmitting, isValid },
setValue, setValue,
watch, watch,
reset, reset,
trigger,
} = useForm({ } = useForm({
defaultValues, defaultValues,
resolver: validationSchema ? zodResolver(validationSchema) : undefined, resolver: validationSchema ? zodResolver(validationSchema) : undefined,
mode: "onChange",
}); });
// Reset form when initialData changes useEffect(() => {
if (Object.keys(errors).length > 0) {
console.log("Form errors:", errors);
}
}, [errors]);
useEffect(() => { useEffect(() => {
if (initialData) { if (initialData) {
reset({ ...initialData as Record<string, any> }); reset({ ...initialData as Record<string, any> });
@ -99,17 +88,52 @@ export function UpdateComponent<T>({
// Handle form submission // Handle form submission
const onSubmit = async (data: any) => { const onSubmit = async (data: any) => {
try { try {
console.log("Form data to update:", data);
// Here you would make an API call to update the data const isFormValid = await trigger();
// For example: await updateApplication(data); if (!isFormValid) {
console.error("Form validation failed - stopping submission");
return; // Stop submission if validation fails
}
if (!apiUrl) {
console.error("API URL is missing or undefined");
return;
}
const uuid = initialData ? (initialData as any).uuid || (initialData as any).uu_id : null;
if (!uuid) {
console.error("UUID not found in initialData");
throw new Error("UUID is required for update operations");
}
const dataToSend = { ...data };
Object.entries(fieldDefinitions).forEach(([key, def]) => {
const fieldDef = def as FieldDefinition;
if (fieldDef.readOnly) {
delete dataToSend[key];
}
});
// Mock API call success const updateUrl = `${apiUrl}/update?uuid=${uuid}`;
try {
let response = await fetch(updateUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(dataToSend),
});
if (!response.ok) {
const errorText = await response.text();
console.error("API error response:", errorText);
throw new Error(`API error: ${response.status}`);
}
if (refetch) refetch(); if (refetch) refetch();
setMode("list"); setMode("list");
setSelectedItem(null); setSelectedItem(null);
} catch (fetchError) {
console.error("Error during fetch:", fetchError);
}
} catch (error) { } catch (error) {
console.error("Error updating form:", error); console.error("Error details:", error);
} }
}; };
@ -125,43 +149,53 @@ export function UpdateComponent<T>({
// Translate group names for display dynamically // Translate group names for display dynamically
const getGroupTitle = (groupName: string) => { const getGroupTitle = (groupName: string) => {
// First check if there's a translation for the exact group key
if (t[groupName]) { if (t[groupName]) {
return t[groupName]; return t[groupName];
} }
// Try to format the group name in a more readable way if no translation exists
// Convert camelCase or snake_case to Title Case with spaces
const formattedName = groupName const formattedName = groupName
// Insert space before capital letters and uppercase the first letter
.replace(/([A-Z])/g, ' $1') .replace(/([A-Z])/g, ' $1')
// Replace underscores with spaces
.replace(/_/g, ' ') .replace(/_/g, ' ')
// Capitalize first letter
.replace(/^./, (str) => str.toUpperCase()) .replace(/^./, (str) => str.toUpperCase())
// Capitalize each word
.replace(/\b\w/g, (c) => c.toUpperCase()); .replace(/\b\w/g, (c) => c.toUpperCase());
return formattedName; return formattedName;
}; };
// Render a field based on its type
const renderField = (fieldName: string, field: FieldDefinition) => { const renderField = (fieldName: string, field: FieldDefinition) => {
const errorMessage = errors[fieldName]?.message as string; const errorMessage = errors[fieldName]?.message as string;
const fieldValue = formValues[fieldName];
const renderLabel = () => (
<Label htmlFor={fieldName}>
{t[fieldName] || field.label[lang as "en" | "tr"]}
{field.required && <span className="text-red-500 ml-1">*</span>}
</Label>
);
if (field.readOnly) {
return (
<div className="space-y-2" key={fieldName}>
{renderLabel()}
<div className="p-2 bg-gray-50 rounded border border-gray-200">
{field.type === "checkbox" ?
(fieldValue ? "Yes" : "No") :
(fieldValue || "-")}
</div>
</div>
);
}
// For editable fields, render the appropriate input type
switch (field.type) { switch (field.type) {
case "text": case "text":
return ( return (
<div className="space-y-2" key={fieldName}> <div className="space-y-2" key={fieldName}>
<Label htmlFor={fieldName}> {renderLabel()}
{t[fieldName] || field.label}
{field.required && <span className="text-red-500 ml-1">*</span>}
</Label>
<Input <Input
id={fieldName} id={fieldName}
{...register(fieldName)} {...register(fieldName)}
placeholder={t[fieldName] || field.label} placeholder={t[fieldName] || field.label[lang as "en" | "tr"]}
disabled={field.readOnly}
/> />
{errorMessage && ( {errorMessage && (
<p className="text-sm text-red-500">{errorMessage}</p> <p className="text-sm text-red-500">{errorMessage}</p>
@ -172,16 +206,12 @@ export function UpdateComponent<T>({
case "textarea": case "textarea":
return ( return (
<div className="space-y-2" key={fieldName}> <div className="space-y-2" key={fieldName}>
<Label htmlFor={fieldName}> {renderLabel()}
{t[fieldName] || field.label}
{field.required && <span className="text-red-500 ml-1">*</span>}
</Label>
<Textarea <Textarea
id={fieldName} id={fieldName}
{...register(fieldName)} {...register(fieldName)}
placeholder={t[fieldName] || field.label} placeholder={t[fieldName] || field.label[lang as "en" | "tr"]}
rows={3} rows={3}
disabled={field.readOnly}
/> />
{errorMessage && ( {errorMessage && (
<p className="text-sm text-red-500">{errorMessage}</p> <p className="text-sm text-red-500">{errorMessage}</p>
@ -192,17 +222,13 @@ export function UpdateComponent<T>({
case "select": case "select":
return ( return (
<div className="space-y-2" key={fieldName}> <div className="space-y-2" key={fieldName}>
<Label htmlFor={fieldName}> {renderLabel()}
{t[fieldName] || field.label}
{field.required && <span className="text-red-500 ml-1">*</span>}
</Label>
<Select <Select
value={formValues[fieldName]} value={fieldValue}
onValueChange={(value) => handleSelectChange(fieldName, value)} onValueChange={(value) => handleSelectChange(fieldName, value)}
disabled={field.readOnly}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder={t[fieldName] || field.label} /> <SelectValue placeholder={t[fieldName] || field.label[lang as "en" | "tr"]} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{field.options?.map((option) => ( {field.options?.map((option) => (
@ -223,16 +249,12 @@ export function UpdateComponent<T>({
<div className="flex items-center space-x-2" key={fieldName}> <div className="flex items-center space-x-2" key={fieldName}>
<Checkbox <Checkbox
id={fieldName} id={fieldName}
checked={formValues[fieldName]} checked={fieldValue}
onCheckedChange={(checked) => onCheckedChange={(checked) =>
handleCheckboxChange(fieldName, checked as boolean) handleCheckboxChange(fieldName, checked as boolean)
} }
disabled={field.readOnly}
/> />
<Label htmlFor={fieldName}> {renderLabel()}
{t[fieldName] || field.label}
{field.required && <span className="text-red-500 ml-1">*</span>}
</Label>
{errorMessage && ( {errorMessage && (
<p className="text-sm text-red-500">{errorMessage}</p> <p className="text-sm text-red-500">{errorMessage}</p>
)} )}
@ -242,15 +264,11 @@ export function UpdateComponent<T>({
case "date": case "date":
return ( return (
<div className="space-y-2" key={fieldName}> <div className="space-y-2" key={fieldName}>
<Label htmlFor={fieldName}> {renderLabel()}
{t[fieldName] || field.label}
{field.required && <span className="text-red-500 ml-1">*</span>}
</Label>
<Input <Input
id={fieldName} id={fieldName}
type="date" type="date"
{...register(fieldName)} {...register(fieldName)}
disabled={field.readOnly}
/> />
{errorMessage && ( {errorMessage && (
<p className="text-sm text-red-500">{errorMessage}</p> <p className="text-sm text-red-500">{errorMessage}</p>
@ -277,6 +295,13 @@ export function UpdateComponent<T>({
<AlertCircle className="h-4 w-4" /> <AlertCircle className="h-4 w-4" />
<AlertDescription> <AlertDescription>
{t.formErrors || "Please correct the errors in the form"} {t.formErrors || "Please correct the errors in the form"}
<ul className="mt-2 list-disc pl-5">
{Object.entries(errors).map(([field, error]) => (
<li key={field}>
{t[field] || field}: {(error as any)?.message || 'Invalid value'}
</li>
))}
</ul>
</AlertDescription> </AlertDescription>
</Alert> </Alert>
)} )}

View File

@ -2,21 +2,10 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { ViewComponentProps } from "./types"; import { ViewComponentProps, FieldDefinition } from "./types";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { z } from "zod"; import { z } from "zod";
// Import field definitions type
export interface FieldDefinition {
type: string;
group: string;
label: string;
options?: string[];
readOnly?: boolean;
required?: boolean;
defaultValue?: any;
name?: string; // Add name property for TypeScript compatibility
}
// Utility function to format field label // Utility function to format field label
const formatFieldLabel = (fieldName: string) => const formatFieldLabel = (fieldName: string) =>
@ -103,7 +92,7 @@ const ViewFieldGroup: React.FC<{
key={fieldName} key={fieldName}
fieldName={fieldName} fieldName={fieldName}
value={value} value={value}
label={field.label} label={field.label[lang as "en" | "tr"]}
lang={lang} lang={lang}
translations={translations} translations={translations}
hasError={hasError} hasError={hasError}

View File

@ -1,5 +1,17 @@
"use client"; "use client";
// Import field definitions type
export interface FieldDefinition {
type: string;
group: string;
label: { tr: string; en: string };
options?: string[];
readOnly?: boolean;
required?: boolean;
defaultValue?: any;
name?: string;
}
// Define the FormMode type to ensure consistency // Define the FormMode type to ensure consistency
export type FormMode = "list" | "create" | "update" | "view"; export type FormMode = "list" | "create" | "update" | "view";
@ -12,12 +24,14 @@ export interface BaseFormProps<T> {
lang: string; lang: string;
translations: Record<string, Record<string, string>>; translations: Record<string, Record<string, string>>;
formProps?: Record<string, any>; formProps?: Record<string, any>;
apiUrl?: string;
} }
export interface CreateComponentProps<T> extends BaseFormProps<T> {} export interface CreateComponentProps<T> extends BaseFormProps<T> {}
export interface UpdateComponentProps<T> extends BaseFormProps<T> { export interface UpdateComponentProps<T> extends BaseFormProps<T> {
initialData: T; // Required for update initialData: T; // Required for update
apiUrl: string;
} }
export interface ViewComponentProps<T> extends BaseFormProps<T> { export interface ViewComponentProps<T> extends BaseFormProps<T> {
@ -34,4 +48,5 @@ export interface FormDisplayProps<T> {
lang: string; lang: string;
translations: Record<string, Record<string, string>>; translations: Record<string, Record<string, string>>;
formProps?: Record<string, any>; formProps?: Record<string, any>;
apiUrl: string;
} }

View File

@ -16,9 +16,8 @@ export function useApiData<T>(
params: RequestParams params: RequestParams
): Promise<ApiResponse<T>> => { ): Promise<ApiResponse<T>> => {
try { try {
// Prepare the request body with action and all params // Prepare the request body with pagination parameters
const requestBody = { const requestBody = {
action: "list",
page: params.page, page: params.page,
size: params.size, size: params.size,
orderField: params.orderField, orderField: params.orderField,
@ -26,8 +25,11 @@ export function useApiData<T>(
query: params.query, query: params.query,
}; };
// Construct the list endpoint URL
const listEndpoint = `${endpoint}/list`;
// Make the API request using POST // Make the API request using POST
const response = await fetch(endpoint, { const response = await fetch(listEndpoint, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@ -1,10 +1,10 @@
import { retrievePagebyUrl, retrievePageList } from "@/apicalls/cookies/token"; import { retrievePageByUrl } from "@/eventRouters/pageRetriever";
import { retrievePageByUrlAndPageId } from "@/components/navigator/retriever"; import { PageProps } from "@/validations/translations/translation";
import React from "react"; import React, { ReactElement } from "react";
export interface DashboardPageParams { export interface DashboardPageParams {
/** /**
* The active page path, e.g., "/individual", "/dashboard" * The active page path, e.g., "/application", "/dashboard"
*/ */
pageUrl: string; pageUrl: string;
@ -33,20 +33,15 @@ export interface DashboardPageResult {
/** /**
* The page component to render * The page component to render
*/ */
PageComponent: any; PageComponent: React.FC<PageProps>;
/**
* The list of site URLs for the menu
*/
siteUrlsList: string[];
} }
/** /**
* Hook to retrieve and prepare dashboard page data for client-frontend application * Hook to retrieve and prepare dashboard page data
* Handles retrieving site URLs, page components, and other necessary data * Throws errors for Next.js error boundary to catch
* *
* @param params The dashboard page parameters * @param params The dashboard page parameters
* @returns The processed dashboard page data including site URLs * @returns The processed dashboard page data
* @throws Error if page URL is invalid or page component is not found * @throws Error if page URL is invalid or page component is not found
*/ */
export async function useDashboardPage({ export async function useDashboardPage({
@ -55,7 +50,6 @@ export async function useDashboardPage({
}: DashboardPageParams): Promise<DashboardPageResult> { }: DashboardPageParams): Promise<DashboardPageResult> {
let searchParamsInstance: { [key: string]: string | undefined } = {}; let searchParamsInstance: { [key: string]: string | undefined } = {};
const defaultLang = "en"; const defaultLang = "en";
// Validate pageUrl // Validate pageUrl
if (!pageUrl || typeof pageUrl !== "string") { if (!pageUrl || typeof pageUrl !== "string") {
throw new Error(`Invalid page URL: ${pageUrl}`); throw new Error(`Invalid page URL: ${pageUrl}`);
@ -80,25 +74,8 @@ export async function useDashboardPage({
); );
} }
// Get site URLs list
let siteUrlsList: string[] = [];
try {
siteUrlsList = (await retrievePageList()) || [];
} catch (err) {
console.error("Error retrieving site URLs:", err);
throw new Error(`Failed to retrieve site URLs: ${err}`);
}
// Get page component // Get page component
let pageToDirect; const PageComponent = retrievePageByUrl(pageUrl);
let PageComponent;
try {
pageToDirect = await retrievePagebyUrl(pageUrl);
PageComponent = retrievePageByUrlAndPageId(pageToDirect, pageUrl);
} catch (err) {
console.error("Error retrieving page component:", err);
throw new Error(`Page component not found for URL: ${pageUrl}`);
}
// Check if page component exists // Check if page component exists
if (!PageComponent) { if (!PageComponent) {
@ -110,7 +87,6 @@ export async function useDashboardPage({
searchParamsInstance, searchParamsInstance,
lang, lang,
PageComponent, PageComponent,
siteUrlsList,
}; };
} }

View File

@ -1,24 +1,17 @@
"use server"; "use server";
import { fetchData, fetchDataWithToken } from "../api-fetcher"; import { fetchDataWithToken } from "../api-fetcher";
import { baseUrlApplication } from "../basics"; import { baseUrlApplication } from "../basics";
import { PageListOptions, PaginateOnly } from "../schemas/list"; import { PaginationParams } from "../schemas/list";
import type { PaginatedApiResponse } from "@/app/api/utils/types";
const applicationListEndpoint = `${baseUrlApplication}/application/list`; const applicationListEndpoint = `${baseUrlApplication}/application/list`;
const applicationUpdateEndpoint = `${baseUrlApplication}/application/update`; const applicationUpdateEndpoint = `${baseUrlApplication}/application/update`;
const applicationCreateEndpoint = `${baseUrlApplication}/application/create`; const applicationCreateEndpoint = `${baseUrlApplication}/application/create`;
const applicationDeleteEndpoint = `${baseUrlApplication}/application/delete`; const applicationDeleteEndpoint = `${baseUrlApplication}/application/delete`;
interface RequestApplication {
name: string;
application_code: string;
site_url: string;
application_type: string;
application_for: string;
description?: string;
}
async function listApplications(payload: PageListOptions) { async function listApplications(payload: PaginationParams): Promise<PaginatedApiResponse<any>> {
try { try {
const response = await fetchDataWithToken( const response = await fetchDataWithToken(
applicationListEndpoint, applicationListEndpoint,
@ -32,12 +25,62 @@ async function listApplications(payload: PageListOptions) {
"POST", "POST",
false false
); );
return response?.status === 200 || response?.status === 202
? response.data // Format the response to match the expected PaginatedApiResponse format
: null; if (response?.status === 200 || response?.status === 202) {
const responseData = response.data as any;
return {
data: responseData.data || [],
pagination: {
page: responseData.pagination?.page || 1,
size: responseData.pagination?.size || 10,
totalCount: responseData.pagination?.totalCount || 0,
totalItems: responseData.pagination?.totalItems || 0,
totalPages: responseData.pagination?.totalPages || 0,
pageCount: responseData.pagination?.pageCount || 0,
orderField: responseData.pagination?.orderField || ['name'],
orderType: responseData.pagination?.orderType || ['asc'],
query: responseData.pagination?.query || {},
next: responseData.pagination?.next || false,
back: responseData.pagination?.back || false
}
};
}
return {
data: [],
pagination: {
page: 1,
size: 10,
totalCount: 0,
totalItems: 0,
totalPages: 0,
pageCount: 0,
orderField: ['name'],
orderType: ['asc'],
query: {},
next: false,
back: false
}
};
} catch (error) { } catch (error) {
console.error("Error fetching application list:", error); console.error("Error fetching application list:", error);
return null; // Return a default empty response instead of null to match the expected return type
return {
data: [],
pagination: {
page: 1,
size: 10,
totalCount: 0,
totalItems: 0,
totalPages: 0,
pageCount: 0,
orderField: ['name'],
orderType: ['asc'],
query: {},
next: false,
back: false
}
};
} }
} }

View File

@ -39,54 +39,6 @@ const cookieObject: any = {
priority: "high", priority: "high",
}; };
interface FilterListInterface {
page?: number | null | undefined;
size?: number | null | undefined;
orderField?: string | null | undefined;
orderType?: string | null | undefined;
includeJoins?: any[] | null | undefined;
query?: any | null | undefined;
}
class FilterList {
page: number;
size: number;
orderField: string;
orderType: string;
includeJoins: any[];
query: any;
constructor({
page = 1,
size = 5,
orderField = "id",
orderType = "asc",
includeJoins = [],
query = {},
}: FilterListInterface = {}) {
this.page = page ?? 1;
this.size = size ?? 5;
this.orderField = orderField ?? "uu_id";
this.orderType = orderType ?? "asc";
this.orderType = this.orderType.startsWith("a") ? "asc" : "desc";
this.includeJoins = includeJoins ?? [];
this.query = query ?? {};
}
filter() {
return {
page: this.page,
size: this.size,
orderField: this.orderField,
orderType: this.orderType,
includeJoins: this.includeJoins,
query: this.query,
};
}
}
const defaultFilterList = new FilterList({});
// Constants // Constants
const DEFAULT_TIMEOUT = 10000; // 10 seconds const DEFAULT_TIMEOUT = 10000; // 10 seconds
const defaultHeaders = { const defaultHeaders = {
@ -102,18 +54,11 @@ const DEFAULT_RESPONSE: ApiResponse = {
data: {}, data: {},
}; };
export type { export type { HttpMethod, ApiResponse, FetchOptions };
FilterList,
FilterListInterface,
HttpMethod,
ApiResponse,
FetchOptions,
};
export { export {
DEFAULT_TIMEOUT, DEFAULT_TIMEOUT,
DEFAULT_RESPONSE, DEFAULT_RESPONSE,
defaultHeaders, defaultHeaders,
defaultFilterList,
baseUrlAuth, baseUrlAuth,
baseUrlPeople, baseUrlPeople,
baseUrlApplication, baseUrlApplication,

View File

@ -5,7 +5,7 @@ export interface PaginateOnly {
orderType?: string[]; orderType?: string[];
} }
export interface PageListOptions { export interface PaginationParams {
page?: number; page?: number;
size?: number; size?: number;
orderField?: string[]; orderField?: string[];

View File

@ -18,7 +18,7 @@ export default async function DashLayout({
} }
return ( return (
<div className="h-screen w-full"> <div className="h-screen w-full">
<div className="h-full w-full overflow-y-auto">{children}</div> <div className="h-full w-full">{children}</div>
</div> </div>
); );
} }

View File

@ -1,9 +1,4 @@
import { NextRequest } from "next/server"; import { createCreateHandler } from "@/app/api/utils";
// Import the createApplication function when it's available import { createApplication } from "@/apicalls/application/application";
// import { createApplication } from "@/apicalls/application/application";
import { createCreateHandler } from "../../utils";
// Create a handler for creating applications using our utility function export const POST = createCreateHandler(createApplication);
// When createApplication is available, pass it as the first argument
// No need for field validation as it's already handled by Zod at the page level
export const POST = createCreateHandler();

View File

@ -1,6 +1,4 @@
import { NextRequest } from "next/server";
import { listApplications } from "@/apicalls/application/application"; import { listApplications } from "@/apicalls/application/application";
import { createListHandler } from "../../utils"; import { createListHandler } from "@/app/api/utils";
// Create a handler for listing applications using our utility function
export const POST = createListHandler(listApplications); export const POST = createListHandler(listApplications);

View File

@ -1,10 +1,4 @@
import { NextRequest } from "next/server";
// Import the updateApplication function when it's available
// import { updateApplication } from "@/apicalls/application/application";
import { createUpdateHandler } from "../../utils"; import { createUpdateHandler } from "../../utils";
import { updateApplication } from "@/apicalls/application/application";
// Create a handler for updating applications using our utility function export const POST = createUpdateHandler(updateApplication);
// When updateApplication is available, pass it as the first argument
// No need for field validation as it's already handled by Zod at the page level
// We only need to ensure 'id' is present for updates
export const POST = createUpdateHandler();

View File

@ -5,7 +5,7 @@ import {
paginationResponse, paginationResponse,
createResponse, createResponse,
updateResponse, updateResponse,
deleteResponse deleteResponse,
} from "./responseHandlers"; } from "./responseHandlers";
import { withErrorHandling, validateRequiredFields } from "./requestHandlers"; import { withErrorHandling, validateRequiredFields } from "./requestHandlers";
import { import {
@ -14,7 +14,7 @@ import {
ListFunction, ListFunction,
CreateFunction, CreateFunction,
UpdateFunction, UpdateFunction,
DeleteFunction DeleteFunction,
} from "./types"; } from "./types";
/** /**
@ -23,7 +23,7 @@ import {
* @param body Request body * @param body Request body
* @param listFunction The function to call to get the list data * @param listFunction The function to call to get the list data
*/ */
export async function handleListOperation<T>( export async function handleListOperation(
request: NextRequest, request: NextRequest,
body: any, body: any,
listFunction: ListFunction listFunction: ListFunction
@ -59,8 +59,7 @@ export async function handleListOperation<T>(
* @param createFunction The function to call to create the item * @param createFunction The function to call to create the item
* @param requiredFields Array of required field names * @param requiredFields Array of required field names
*/ */
export async function handleCreateOperation<T>( export async function handleCreateOperation(
request: NextRequest,
body: any, body: any,
createFunction?: CreateFunction, createFunction?: CreateFunction,
requiredFields: string[] = [] requiredFields: string[] = []
@ -91,23 +90,28 @@ export async function handleCreateOperation<T>(
* @param request NextRequest object * @param request NextRequest object
* @param body Request body * @param body Request body
* @param updateFunction The function to call to update the item * @param updateFunction The function to call to update the item
* @param requiredFields Array of required field names
*/ */
export async function handleUpdateOperation<T>( export async function handleUpdateOperation(
request: NextRequest, request: NextRequest,
body: any, body: any,
updateFunction?: UpdateFunction, updateFunction?: UpdateFunction
requiredFields: string[] = ['id']
) { ) {
// Validate required fields // Skip validation as it's handled at the page level with Yup
const validation = validateRequiredFields(body, requiredFields); // and IDs are extracted from URL paths for update/delete operations
if (!validation.valid) {
return errorResponse(validation.error as string, 400); // Get UUID from query string. This is the value of the "uuid" query
// string parameter, e.g. if the URL is "?uuid=SOMEUUID", this will
// be "SOMEUUID".
const uuid = request.nextUrl.searchParams.get("uuid");
console.log("UUID:", uuid);
if (!uuid) {
return errorResponse("UUID not found", 400);
} }
// If an update function is provided, call it
if (updateFunction) { if (updateFunction) {
const result = await updateFunction(body.id, body); console.log("Body:", body);
const result = await updateFunction(body, uuid);
return updateResponse(result); return updateResponse(result);
} }
@ -118,23 +122,26 @@ export async function handleUpdateOperation<T>(
/** /**
* Generic delete operation handler * Generic delete operation handler
* @param request NextRequest object * @param request NextRequest object
* @param body Request body
* @param deleteFunction The function to call to delete the item * @param deleteFunction The function to call to delete the item
*/ */
export async function handleDeleteOperation( export async function handleDeleteOperation(
request: NextRequest, request: NextRequest,
body: any,
deleteFunction?: DeleteFunction deleteFunction?: DeleteFunction
) { ) {
// Validate that ID is provided // Skip ID validation as it's handled at the page level
const validation = validateRequiredFields(body, ['id']); // and IDs are typically extracted from URL paths
if (!validation.valid) {
return errorResponse(validation.error as string, 400); // Get UUID from query string. This is the value of the "uuid" query
// string parameter, e.g. if the URL is "?uuid=SOMEUUID", this will
// be "SOMEUUID".
const uuid = request.nextUrl.searchParams.get("uuid");
if (!uuid) {
return errorResponse("UUID not found", 400);
} }
// If a delete function is provided, call it
if (deleteFunction) { if (deleteFunction) {
await deleteFunction(body.id); await deleteFunction(uuid);
} }
// Return a success response // Return a success response
@ -146,7 +153,7 @@ export async function handleDeleteOperation(
* @param listFunction The function to call to get the list data * @param listFunction The function to call to get the list data
*/ */
export function createListHandler(listFunction: ListFunction) { export function createListHandler(listFunction: ListFunction) {
return withErrorHandling((request, body) => return withErrorHandling((request: NextRequest, body: any) =>
handleListOperation(request, body, listFunction) handleListOperation(request, body, listFunction)
); );
} }
@ -160,22 +167,19 @@ export function createCreateHandler(
createFunction?: CreateFunction, createFunction?: CreateFunction,
requiredFields: string[] = [] requiredFields: string[] = []
) { ) {
return withErrorHandling((request, body) => console.log("Required fields:", requiredFields);
handleCreateOperation(request, body, createFunction, requiredFields) return withErrorHandling((body: any) =>
handleCreateOperation(body, createFunction, requiredFields)
); );
} }
/** /**
* Create a wrapped update handler with error handling * Create a wrapped update handler with error handling
* @param updateFunction The function to call to update the item * @param updateFunction The function to call to update the item
* @param requiredFields Array of required field names
*/ */
export function createUpdateHandler( export function createUpdateHandler(updateFunction?: UpdateFunction) {
updateFunction?: UpdateFunction, return withErrorHandling((request: NextRequest, body: any) =>
requiredFields: string[] = ['id'] handleUpdateOperation(request, body, updateFunction)
) {
return withErrorHandling((request, body) =>
handleUpdateOperation(request, body, updateFunction, requiredFields)
); );
} }
@ -183,10 +187,8 @@ export function createUpdateHandler(
* Create a wrapped delete handler with error handling * Create a wrapped delete handler with error handling
* @param deleteFunction The function to call to delete the item * @param deleteFunction The function to call to delete the item
*/ */
export function createDeleteHandler( export function createDeleteHandler(deleteFunction?: DeleteFunction) {
deleteFunction?: DeleteFunction return withErrorHandling((request: NextRequest) =>
) { handleDeleteOperation(request, deleteFunction)
return withErrorHandling((request, body) =>
handleDeleteOperation(request, body, deleteFunction)
); );
} }

View File

@ -64,6 +64,7 @@ export function CreateComponent<T>({
formState: { errors }, formState: { errors },
setValue, setValue,
watch, watch,
reset,
} = useForm<Record<string, any>>({ } = useForm<Record<string, any>>({
defaultValues, defaultValues,
resolver: validationSchema ? zodResolver(validationSchema) : undefined, resolver: validationSchema ? zodResolver(validationSchema) : undefined,
@ -71,14 +72,37 @@ export function CreateComponent<T>({
const formValues = watch(); const formValues = watch();
// Get language-specific validation schema if available
useEffect(() => {
if (formProps.schemaPath) {
const loadLanguageValidationSchema = async () => {
try {
// Dynamic import of the schema module
const schemaModule = await import(formProps.schemaPath);
// Check if language-specific schema functions are available
if (schemaModule.getCreateApplicationSchema) {
const langValidationSchema = schemaModule.getCreateApplicationSchema(lang as "en" | "tr");
// Reset the form with the current values
reset(defaultValues);
// Update the validation schema in formProps for future validations
formProps.validationSchema = langValidationSchema;
}
} catch (error) {
console.error("Error loading language-specific validation schema:", error);
}
};
loadLanguageValidationSchema();
}
}, [lang, formProps.schemaPath, reset, defaultValues]);
// Handle form submission // Handle form submission
const onSubmit: SubmitHandler<Record<string, any>> = async (data) => { const onSubmit: SubmitHandler<Record<string, any>> = async (data) => {
try { try {
console.log("Form data to save:", data);
// Make an API call to save the data if apiUrl is provided
if (apiUrl) { if (apiUrl) {
// Use the create endpoint by appending '/create' to the apiUrl
const createUrl = `${apiUrl}/create`; const createUrl = `${apiUrl}/create`;
const response = await fetch(createUrl, { const response = await fetch(createUrl, {
method: 'POST', method: 'POST',
@ -87,13 +111,13 @@ export function CreateComponent<T>({
}, },
body: JSON.stringify(data), body: JSON.stringify(data),
}); });
console.log("Response:", response.ok);
if (!response.ok) { if (!response.ok) {
throw new Error(`API error: ${response.status}`); throw new Error(`API error: ${response.status}`);
} }
// Optional: get the created item from response const createdItem = await response.json();
// const createdItem = await response.json(); console.log("Created item:", createdItem);
} }
if (refetch) refetch(); if (refetch) refetch();
@ -116,23 +140,17 @@ export function CreateComponent<T>({
// Translate group names for display dynamically // Translate group names for display dynamically
const getGroupTitle = (groupName: string) => { const getGroupTitle = (groupName: string) => {
// First check if there's a translation for the exact group key // Check if we have a translation for this group name
if (t[groupName]) { if (t[groupName]) {
return t[groupName]; return t[groupName];
} }
// Try to format the group name in a more readable way if no translation exists // If no translation is found, just format the name as a fallback
// Convert camelCase or snake_case to Title Case with spaces
const formattedName = groupName const formattedName = groupName
// Insert space before capital letters and uppercase the first letter
.replace(/([A-Z])/g, ' $1') .replace(/([A-Z])/g, ' $1')
// Replace underscores with spaces
.replace(/_/g, ' ') .replace(/_/g, ' ')
// Capitalize first letter
.replace(/^./, (str) => str.toUpperCase()) .replace(/^./, (str) => str.toUpperCase())
// Capitalize each word
.replace(/\b\w/g, (c) => c.toUpperCase()); .replace(/\b\w/g, (c) => c.toUpperCase());
return formattedName; return formattedName;
}; };
@ -237,6 +255,14 @@ export function CreateComponent<T>({
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="grid grid-cols-2 gap-4 pt-6 my-4">
<Button type="button" variant="outline" onClick={onCancel} className="w-full">
{t.cancel || "Cancel"}
</Button>
<Button type="submit" className="w-full">
{t.save || "Save"}
</Button>
</div>
<Card className="w-full mb-6"> <Card className="w-full mb-6">
<CardHeader> <CardHeader>
<CardTitle>{t.create || "Create"}</CardTitle> <CardTitle>{t.create || "Create"}</CardTitle>
@ -269,14 +295,7 @@ export function CreateComponent<T>({
))} ))}
</div> </div>
<div className="flex justify-end space-x-2 pt-6">
<Button type="button" variant="outline" onClick={onCancel}>
{t.cancel || "Cancel"}
</Button>
<Button type="submit">
{t.save || "Save"}
</Button>
</div>
</CardContent> </CardContent>
</Card> </Card>
</form> </form>

View File

@ -19,9 +19,9 @@ export function FormDisplay<T>({
}: FormDisplayProps<T>) { }: FormDisplayProps<T>) {
const [enhancedFormProps, setEnhancedFormProps] = useState(formProps); const [enhancedFormProps, setEnhancedFormProps] = useState(formProps);
// Dynamically import schema definitions if provided in formProps // Update form props when language or mode changes
useEffect(() => { useEffect(() => {
const loadSchemaDefinitions = async () => { const updateFormProps = async () => {
try { try {
// Check if schemaPath is provided in formProps // Check if schemaPath is provided in formProps
if (formProps.schemaPath) { if (formProps.schemaPath) {
@ -40,12 +40,14 @@ export function FormDisplay<T>({
fieldDefs = schemaModule.viewFieldDefinitions; fieldDefs = schemaModule.viewFieldDefinitions;
} }
// Get the appropriate validation schema based on mode // Get the appropriate validation schema based on mode and language
let validationSchema; let validationSchema;
if (mode === "create" && schemaModule.CreateApplicationSchema) { if (mode === "create" && schemaModule.getCreateApplicationSchema) {
validationSchema = schemaModule.CreateApplicationSchema; // Use language-aware schema factory function
} else if (mode === "update" && schemaModule.UpdateApplicationSchema) { validationSchema = schemaModule.getCreateApplicationSchema(lang as "en" | "tr");
validationSchema = schemaModule.UpdateApplicationSchema; } else if (mode === "update" && schemaModule.getUpdateApplicationSchema) {
// Use language-aware schema factory function
validationSchema = schemaModule.getUpdateApplicationSchema(lang as "en" | "tr");
} else if (mode === "view" && schemaModule.ViewApplicationSchema) { } else if (mode === "view" && schemaModule.ViewApplicationSchema) {
validationSchema = schemaModule.ViewApplicationSchema; validationSchema = schemaModule.ViewApplicationSchema;
} else if (schemaModule.ApplicationSchema) { } else if (schemaModule.ApplicationSchema) {
@ -55,22 +57,40 @@ export function FormDisplay<T>({
// Get the grouped field definitions structure if available // Get the grouped field definitions structure if available
const groupedFieldDefs = schemaModule.baseFieldDefinitions || {}; const groupedFieldDefs = schemaModule.baseFieldDefinitions || {};
// Update form props with schema information // Update form props with schema information and current language
setEnhancedFormProps({ setEnhancedFormProps({
...formProps, ...formProps,
fieldDefinitions: fieldDefs || {}, fieldDefinitions: fieldDefs || {},
validationSchema, validationSchema,
fieldsByMode: schemaModule.fieldsByMode || {}, fieldsByMode: schemaModule.fieldsByMode || {},
groupedFieldDefinitions: groupedFieldDefs, groupedFieldDefinitions: groupedFieldDefs,
// Add current language to force child components to recognize changes
currentLang: lang,
// Add schema path for dynamic imports in child components
schemaPath: formProps.schemaPath
});
} else {
// If no schema path, just update with current language
setEnhancedFormProps({
...formProps,
currentLang: lang
}); });
} }
} catch (error) { } catch (error) {
console.error("Error loading schema definitions:", error); console.error("Error loading schema definitions:", error);
// Even on error, update the language
setEnhancedFormProps({
...formProps,
currentLang: lang
});
} }
}; };
loadSchemaDefinitions(); updateFormProps();
}, [formProps, mode, lang]); // Added lang as a dependency to ensure re-fetch when language changes }, [formProps, mode, lang]); // Lang dependency ensures re-fetch when language changes
// Debug the props received by FormDisplay
// FormDisplay component renders different form modes based on the mode prop
// Render the appropriate component based on the mode // Render the appropriate component based on the mode
switch (mode) { switch (mode) {
@ -89,9 +109,12 @@ export function FormDisplay<T>({
/> />
); );
case "update": case "update":
// Create a stable key for the component to ensure proper re-rendering
const updateKey = `update-${lang}-${(initialData as any)?.uu_id || 'new'}`;
return initialData ? ( return initialData ? (
<UpdateComponent<T> <UpdateComponent<T>
key={`update-${lang}`} // Add key with lang to force re-render on language change key={updateKey} // Add key with lang and item ID to force re-render
initialData={initialData} initialData={initialData}
refetch={refetch} refetch={refetch}
setMode={setMode} setMode={setMode}
@ -100,6 +123,7 @@ export function FormDisplay<T>({
lang={lang} lang={lang}
translations={translations} translations={translations}
formProps={enhancedFormProps} formProps={enhancedFormProps}
apiUrl={apiUrl}
/> />
) : null; ) : null;
case "view": case "view":

View File

@ -1,5 +1,5 @@
"use client"; "use client";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useMemo } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { UpdateComponentProps, FieldDefinition } from "./types"; import { UpdateComponentProps, FieldDefinition } from "./types";
@ -21,6 +21,7 @@ export function UpdateComponent<T>({
onCancel, onCancel,
lang, lang,
translations, translations,
apiUrl,
formProps = {}, formProps = {},
}: UpdateComponentProps<T>) { }: UpdateComponentProps<T>) {
const t = translations[lang as keyof typeof translations] || {}; const t = translations[lang as keyof typeof translations] || {};
@ -29,75 +30,179 @@ export function UpdateComponent<T>({
const fieldDefinitions = formProps.fieldDefinitions || {}; const fieldDefinitions = formProps.fieldDefinitions || {};
const validationSchema = formProps.validationSchema; const validationSchema = formProps.validationSchema;
// Group fields by their group property // Ensure field definitions are processed only once
const processedFieldDefinitions = useMemo(() => {
const processed = { ...fieldDefinitions };
// Make all fields editable except system fields
Object.entries(processed).forEach(([fieldName, definition]) => {
if (fieldName !== 'uu_id' && fieldName !== 'created_at' && fieldName !== 'updated_at') {
(processed[fieldName] as FieldDefinition).readOnly = false;
}
});
return processed;
}, [fieldDefinitions]);
const [groupedFields, setGroupedFields] = useState<Record<string, FieldDefinition[]>>({}); const [groupedFields, setGroupedFields] = useState<Record<string, FieldDefinition[]>>({});
// Process field definitions to group them
useEffect(() => { useEffect(() => {
if (Object.keys(fieldDefinitions).length > 0) { if (Object.keys(processedFieldDefinitions).length > 0) {
const groups: Record<string, FieldDefinition[]> = {}; const groups: Record<string, FieldDefinition[]> = {};
// Group fields by their group property // Group the processed field definitions
Object.entries(fieldDefinitions).forEach(([fieldName, definition]) => { Object.entries(processedFieldDefinitions).forEach(([fieldName, definition]) => {
// Convert to FieldDefinition type
const def = definition as FieldDefinition; const def = definition as FieldDefinition;
// Add the field name to the definition
const fieldDef = { ...def, name: fieldName };
// Add to the appropriate group
if (!groups[def.group]) { if (!groups[def.group]) {
groups[def.group] = []; groups[def.group] = [];
} }
groups[def.group].push({ ...def, name: fieldName }); groups[def.group].push(fieldDef);
}); });
setGroupedFields(groups); setGroupedFields(groups);
} }
}, [fieldDefinitions]); }, [processedFieldDefinitions]);
// Initialize form with default values from field definitions and initialData
const defaultValues: Record<string, any> = {}; const defaultValues: Record<string, any> = {};
Object.entries(fieldDefinitions).forEach(([key, def]) => { Object.entries(processedFieldDefinitions).forEach(([key, def]) => {
const fieldDef = def as FieldDefinition; const fieldDef = def as FieldDefinition;
defaultValues[key] = fieldDef.defaultValue !== undefined ? fieldDef.defaultValue : ""; defaultValues[key] = fieldDef.defaultValue !== undefined ? fieldDef.defaultValue : "";
}); });
// Merge initialData with default values
if (initialData) { if (initialData) {
Object.assign(defaultValues, initialData as Record<string, any>); Object.assign(defaultValues, initialData as Record<string, any>);
} }
// Setup form with validation schema if available // Track the current language to detect changes
const [currentLang, setCurrentLang] = useState(lang);
const { const {
register, register,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors, isSubmitting, isValid },
setValue, setValue,
watch, watch,
reset, reset,
trigger,
} = useForm({ } = useForm({
defaultValues, defaultValues,
resolver: validationSchema ? zodResolver(validationSchema) : undefined, resolver: validationSchema ? zodResolver(validationSchema) : undefined,
mode: "onChange",
}); });
// Reset form when initialData changes useEffect(() => {
if (Object.keys(errors).length > 0) {
console.log("Form errors:", errors);
}
}, [errors]);
useEffect(() => { useEffect(() => {
if (initialData) { if (initialData) {
reset({ ...initialData as Record<string, any> }); reset({ ...initialData as Record<string, any> });
} }
}, [initialData, reset]); }, [initialData, reset]);
// Detect language changes and update validation schema
useEffect(() => {
// If language has changed, update the form
if (currentLang !== lang || formProps.currentLang !== lang) {
const updateValidationForLanguage = async () => {
try {
// If we have a schema path, dynamically load the schema for the current language
if (formProps.schemaPath) {
// Dynamic import of the schema module
const schemaModule = await import(formProps.schemaPath);
// Check if language-specific schema functions are available
if (schemaModule.getUpdateApplicationSchema) {
// Get the schema for the current language
const langValidationSchema = schemaModule.getUpdateApplicationSchema(lang as "en" | "tr");
// Save current form values
const formValues = watch();
// Reset the form with current values but clear errors
reset(formValues, {
keepDirty: true,
keepValues: true,
keepErrors: false
});
// Manually trigger validation after reset
setTimeout(() => {
// Trigger validation for all fields to show updated error messages
Object.keys(formValues).forEach(fieldName => {
trigger(fieldName);
});
}, 0);
// Update our tracked language
setCurrentLang(lang);
}
}
} catch (error) {
console.error("Error updating validation schema for language:", error);
}
};
updateValidationForLanguage();
}
}, [lang, formProps.currentLang, currentLang, formProps.schemaPath, reset, watch, trigger]);
const formValues = watch(); const formValues = watch();
// Handle form submission // Handle form submission
const onSubmit = async (data: any) => { const onSubmit = async (data: any) => {
try { try {
console.log("Form data to update:", data);
// Here you would make an API call to update the data const isFormValid = await trigger();
// For example: await updateApplication(data); if (!isFormValid) {
console.error("Form validation failed - stopping submission");
return; // Stop submission if validation fails
}
if (!apiUrl) {
console.error("API URL is missing or undefined");
return;
}
const uuid = initialData ? (initialData as any).uuid || (initialData as any).uu_id : null;
if (!uuid) {
console.error("UUID not found in initialData");
throw new Error("UUID is required for update operations");
}
const dataToSend = { ...data };
Object.entries(fieldDefinitions).forEach(([key, def]) => {
const fieldDef = def as FieldDefinition;
if (fieldDef.readOnly) {
delete dataToSend[key];
}
});
// Mock API call success const updateUrl = `${apiUrl}/update?uuid=${uuid}`;
try {
let response = await fetch(updateUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(dataToSend),
});
if (!response.ok) {
const errorText = await response.text();
console.error("API error response:", errorText);
throw new Error(`API error: ${response.status}`);
}
if (refetch) refetch(); if (refetch) refetch();
setMode("list"); setMode("list");
setSelectedItem(null); setSelectedItem(null);
} catch (fetchError) {
console.error("Error during fetch:", fetchError);
}
} catch (error) { } catch (error) {
console.error("Error updating form:", error); console.error("Error details:", error);
} }
}; };
@ -113,43 +218,55 @@ export function UpdateComponent<T>({
// Translate group names for display dynamically // Translate group names for display dynamically
const getGroupTitle = (groupName: string) => { const getGroupTitle = (groupName: string) => {
// First check if there's a translation for the exact group key // Check if we have a translation for this group name
if (t[groupName]) { if (t[groupName]) {
return t[groupName]; return t[groupName];
} }
// Try to format the group name in a more readable way if no translation exists // If no translation is found, just format the name as a fallback
// Convert camelCase or snake_case to Title Case with spaces
const formattedName = groupName const formattedName = groupName
// Insert space before capital letters and uppercase the first letter
.replace(/([A-Z])/g, ' $1') .replace(/([A-Z])/g, ' $1')
// Replace underscores with spaces
.replace(/_/g, ' ') .replace(/_/g, ' ')
// Capitalize first letter
.replace(/^./, (str) => str.toUpperCase()) .replace(/^./, (str) => str.toUpperCase())
// Capitalize each word
.replace(/\b\w/g, (c) => c.toUpperCase()); .replace(/\b\w/g, (c) => c.toUpperCase());
return formattedName; return formattedName;
}; };
// Render a field based on its type
const renderField = (fieldName: string, field: FieldDefinition) => { const renderField = (fieldName: string, field: FieldDefinition) => {
const errorMessage = errors[fieldName]?.message as string; const errorMessage = errors[fieldName]?.message as string;
const fieldValue = formValues[fieldName];
switch (field.type) { const renderLabel = () => (
case "text":
return (
<div className="space-y-2" key={fieldName}>
<Label htmlFor={fieldName}> <Label htmlFor={fieldName}>
{t[fieldName] || field.label[lang as "en" | "tr"]} {t[fieldName] || field.label[lang as "en" | "tr"]}
{field.required && <span className="text-red-500 ml-1">*</span>} {field.required && <span className="text-red-500 ml-1">*</span>}
</Label> </Label>
);
if (field.readOnly) {
return (
<div className="space-y-2" key={fieldName}>
{renderLabel()}
<div className="p-2 bg-gray-50 rounded border border-gray-200">
{field.type === "checkbox" ?
(fieldValue ? "Yes" : "No") :
(fieldValue || "-")}
</div>
</div>
);
}
// For editable fields, render the appropriate input type
switch (field.type) {
case "text":
return (
<div className="space-y-2" key={fieldName}>
{renderLabel()}
<Input <Input
id={fieldName} id={fieldName}
{...register(fieldName)} {...register(fieldName)}
placeholder={t[fieldName] || field.label[lang as "en" | "tr"]} placeholder={t[fieldName] || field.label[lang as "en" | "tr"]}
disabled={field.readOnly}
/> />
{errorMessage && ( {errorMessage && (
<p className="text-sm text-red-500">{errorMessage}</p> <p className="text-sm text-red-500">{errorMessage}</p>
@ -160,16 +277,12 @@ export function UpdateComponent<T>({
case "textarea": case "textarea":
return ( return (
<div className="space-y-2" key={fieldName}> <div className="space-y-2" key={fieldName}>
<Label htmlFor={fieldName}> {renderLabel()}
{t[fieldName] || field.label[lang as "en" | "tr"]}
{field.required && <span className="text-red-500 ml-1">*</span>}
</Label>
<Textarea <Textarea
id={fieldName} id={fieldName}
{...register(fieldName)} {...register(fieldName)}
placeholder={t[fieldName] || field.label[lang as "en" | "tr"]} placeholder={t[fieldName] || field.label[lang as "en" | "tr"]}
rows={3} rows={3}
disabled={field.readOnly}
/> />
{errorMessage && ( {errorMessage && (
<p className="text-sm text-red-500">{errorMessage}</p> <p className="text-sm text-red-500">{errorMessage}</p>
@ -180,14 +293,10 @@ export function UpdateComponent<T>({
case "select": case "select":
return ( return (
<div className="space-y-2" key={fieldName}> <div className="space-y-2" key={fieldName}>
<Label htmlFor={fieldName}> {renderLabel()}
{t[fieldName] || field.label[lang as "en" | "tr"]}
{field.required && <span className="text-red-500 ml-1">*</span>}
</Label>
<Select <Select
value={formValues[fieldName]} value={fieldValue}
onValueChange={(value) => handleSelectChange(fieldName, value)} onValueChange={(value) => handleSelectChange(fieldName, value)}
disabled={field.readOnly}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder={t[fieldName] || field.label[lang as "en" | "tr"]} /> <SelectValue placeholder={t[fieldName] || field.label[lang as "en" | "tr"]} />
@ -211,16 +320,12 @@ export function UpdateComponent<T>({
<div className="flex items-center space-x-2" key={fieldName}> <div className="flex items-center space-x-2" key={fieldName}>
<Checkbox <Checkbox
id={fieldName} id={fieldName}
checked={formValues[fieldName]} checked={fieldValue}
onCheckedChange={(checked) => onCheckedChange={(checked) =>
handleCheckboxChange(fieldName, checked as boolean) handleCheckboxChange(fieldName, checked as boolean)
} }
disabled={field.readOnly}
/> />
<Label htmlFor={fieldName}> {renderLabel()}
{t[fieldName] || field.label[lang as "en" | "tr"]}
{field.required && <span className="text-red-500 ml-1">*</span>}
</Label>
{errorMessage && ( {errorMessage && (
<p className="text-sm text-red-500">{errorMessage}</p> <p className="text-sm text-red-500">{errorMessage}</p>
)} )}
@ -230,15 +335,11 @@ export function UpdateComponent<T>({
case "date": case "date":
return ( return (
<div className="space-y-2" key={fieldName}> <div className="space-y-2" key={fieldName}>
<Label htmlFor={fieldName}> {renderLabel()}
{t[fieldName] || field.label[lang as "en" | "tr"]}
{field.required && <span className="text-red-500 ml-1">*</span>}
</Label>
<Input <Input
id={fieldName} id={fieldName}
type="date" type="date"
{...register(fieldName)} {...register(fieldName)}
disabled={field.readOnly}
/> />
{errorMessage && ( {errorMessage && (
<p className="text-sm text-red-500">{errorMessage}</p> <p className="text-sm text-red-500">{errorMessage}</p>
@ -259,16 +360,33 @@ export function UpdateComponent<T>({
<CardDescription>{t.updateDescription || "Update existing item"}</CardDescription> <CardDescription>{t.updateDescription || "Update existing item"}</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{/* Display validation errors summary if any */} {/* Display validation errors summary if any */}
{Object.keys(errors).length > 0 && ( {Object.keys(errors).length > 0 && (
<Alert variant="destructive" className="mb-4"> <Alert variant="destructive" className="mb-4">
<AlertCircle className="h-4 w-4" /> <AlertCircle className="h-4 w-4" />
<AlertDescription> <AlertDescription>
{t.formErrors || "Please correct the errors in the form"} {t.formErrors || "Please correct the errors in the form"}
<ul className="mt-2 list-disc pl-5">
{Object.entries(errors).map(([field, error]) => (
<li key={field}>
{t[field] || field}: {(error as any)?.message || 'Invalid value'}
</li>
))}
</ul>
</AlertDescription> </AlertDescription>
</Alert> </Alert>
)} )}
<div className="grid grid-cols-2 gap-4 pt-6 my-4">
<Button type="button" variant="outline" onClick={onCancel} className="w-full">
{t.cancel || "Cancel"}
</Button>
<Button type="submit" className="w-full">
{t.save || "Save"}
</Button>
</div>
{/* Render fields grouped by their group property */} {/* Render fields grouped by their group property */}
<div className="space-y-6"> <div className="space-y-6">
{Object.entries(groupedFields).map(([groupName, fields]) => ( {Object.entries(groupedFields).map(([groupName, fields]) => (
@ -285,14 +403,7 @@ export function UpdateComponent<T>({
))} ))}
</div> </div>
<div className="flex justify-end space-x-2 pt-6">
<Button type="button" variant="outline" onClick={onCancel}>
{t.cancel || "Cancel"}
</Button>
<Button type="submit">
{t.save || "Save"}
</Button>
</div>
</CardContent> </CardContent>
</Card> </Card>
</form> </form>

View File

@ -31,6 +31,7 @@ export interface CreateComponentProps<T> extends BaseFormProps<T> {}
export interface UpdateComponentProps<T> extends BaseFormProps<T> { export interface UpdateComponentProps<T> extends BaseFormProps<T> {
initialData: T; // Required for update initialData: T; // Required for update
apiUrl: string;
} }
export interface ViewComponentProps<T> extends BaseFormProps<T> { export interface ViewComponentProps<T> extends BaseFormProps<T> {
@ -47,5 +48,5 @@ export interface FormDisplayProps<T> {
lang: string; lang: string;
translations: Record<string, Record<string, string>>; translations: Record<string, Record<string, string>>;
formProps?: Record<string, any>; formProps?: Record<string, any>;
apiUrl?: string; apiUrl: string;
} }

View File

@ -1,16 +1,7 @@
export type LanguageKey = "en" | "tr"; import {
LanguageKey,
export interface TranslationSet { TranslationSet,
showing: string; } from "@/validations/translations/translation";
of: string;
items: string;
total: string;
filtered: string;
page: string;
previous: string;
next: string;
itemsPerPage: string;
}
export const fieldLanguageTranslation = { export const fieldLanguageTranslation = {
tr: { tr: {
@ -53,6 +44,8 @@ export const translations = {
update: "Update", update: "Update",
delete: "Delete", delete: "Delete",
view: "View", view: "View",
save: "Save",
cancel: "Cancel",
// Search and filters // Search and filters
search: "Search", search: "Search",
@ -104,6 +97,14 @@ export const translations = {
availableApplications: "Available Applications", availableApplications: "Available Applications",
code: "Code", code: "Code",
sortBy: "Sort by:", sortBy: "Sort by:",
formErrors: "Please correct the errors in the form",
// Form group titles
identificationInfo: "Identification Information",
applicationDetails: "Application Details",
statusInfo: "Status Information",
systemInfo: "System Information",
createDescription: "Create Application",
}, },
tr: { tr: {
// Page title // Page title
@ -115,7 +116,8 @@ export const translations = {
update: "Güncelle", update: "Güncelle",
delete: "Sil", delete: "Sil",
view: "Görüntüle", view: "Görüntüle",
save: "Kaydet",
cancel: "İptal",
// Search and filters // Search and filters
search: "Ara", search: "Ara",
typeSelection: "Tür Seçimi", typeSelection: "Tür Seçimi",
@ -166,6 +168,14 @@ export const translations = {
availableApplications: "Mevcut Uygulamalar", availableApplications: "Mevcut Uygulamalar",
code: "Kod", code: "Kod",
sortBy: "Sırala:", sortBy: "Sırala:",
formErrors: "Lütfen formdaki hataları düzeltiniz",
// Form group titles
identificationInfo: "Kimlik Bilgileri",
applicationDetails: "Uygulama Detayları",
statusInfo: "Durum Bilgileri",
systemInfo: "Sistem Bilgileri",
createDescription: "Yeni bir uygulama oluşturun",
}, },
}; };

View File

@ -1,20 +1,22 @@
"use client"; "use client";
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import * as schema from "./schema"; import * as schema from "./schema";
import { translations } from "./language";
import type { FormMode } from "@/components/common/FormDisplay/types";
import { PageProps } from "@/validations/translations/translation";
import { useApiData } from "@/components/common/hooks/useApiData";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Building, Filter, User } from "lucide-react"; import { Building, Filter, User } from "lucide-react";
import { TextQueryModifier, SelectQueryModifier, TypeQueryModifier } from "@/components/common/QueryModifiers"; import { TypeQueryModifier } from "@/components/common/QueryModifiers/TypeQueryModifier";
import { TextQueryModifier } from "@/components/common/QueryModifiers/TextQueryModifier";
import { SelectQueryModifier } from "@/components/common/QueryModifiers/SelectQueryModifier";
import { CreateButton } from "@/components/common/ActionButtonsDisplay/CreateButton"; import { CreateButton } from "@/components/common/ActionButtonsDisplay/CreateButton";
import { PaginationToolsComponent } from "@/components/common/PaginationModifiers/PaginationToolsComponent"; import { PaginationToolsComponent } from "@/components/common/PaginationModifiers/PaginationToolsComponent";
import { CardDisplay } from "@/components/common/CardDisplay"; import { CardDisplay } from "@/components/common/CardDisplay/CardDisplay";
import { FormMode } from "@/components/common/FormDisplay/types";
import { FormDisplay } from "@/components/common/FormDisplay/FormDisplay"; import { FormDisplay } from "@/components/common/FormDisplay/FormDisplay";
import { GridSelectionComponent, GridSize } from "@/components/common/HeaderSelections/GridSelectionComponent"; import { GridSelectionComponent, GridSize } from "@/components/common/HeaderSelections/GridSelectionComponent";
import { LanguageSelectionComponent, Language } from "@/components/common/HeaderSelections/LanguageSelectionComponent"; import { LanguageSelectionComponent, Language } from "@/components/common/HeaderSelections/LanguageSelectionComponent";
import { getCreateApplicationSchema, getUpdateApplicationSchema } from "./schema";
import { translations } from "./language";
import { PageProps } from "@/validations/translations/translation";
import { useApiData } from "@/components/common";
const ApplicationPage: React.FC<PageProps> = ({ lang: initialLang = "en" }) => { const ApplicationPage: React.FC<PageProps> = ({ lang: initialLang = "en" }) => {
// Add local state for language to ensure it persists when changed // Add local state for language to ensure it persists when changed
@ -35,6 +37,39 @@ const ApplicationPage: React.FC<PageProps> = ({ lang: initialLang = "en" }) => {
const [selectedItem, setSelectedItem] = useState<schema.ApplicationData | null>(null); const [selectedItem, setSelectedItem] = useState<schema.ApplicationData | null>(null);
const [gridCols, setGridCols] = useState<GridSize>(3); const [gridCols, setGridCols] = useState<GridSize>(3);
// Use state to store the field definitions and validation schema
const [fieldDefinitions, setFieldDefinitions] = useState(() =>
mode === 'create' ? schema.createFieldDefinitions :
mode === 'update' ? schema.updateFieldDefinitions :
schema.viewFieldDefinitions
);
const [validationSchema, setValidationSchema] = useState(() =>
mode === 'create' ? getCreateApplicationSchema(lang as "en" | "tr") :
mode === 'update' ? getUpdateApplicationSchema(lang as "en" | "tr") :
schema.ViewApplicationSchema
);
// Update field definitions when mode changes
useEffect(() => {
// Select the appropriate field definitions based on the current mode
const newFieldDefs = mode === 'create' ? schema.createFieldDefinitions :
mode === 'update' ? schema.updateFieldDefinitions :
schema.viewFieldDefinitions;
setFieldDefinitions(newFieldDefs);
}, [mode]);
// Update validation schema when mode or language changes
useEffect(() => {
setValidationSchema(
mode === 'create' ? getCreateApplicationSchema(lang as "en" | "tr") :
mode === 'update' ? getUpdateApplicationSchema(lang as "en" | "tr") :
schema.ViewApplicationSchema
);
}, [mode, lang]);
// Fields to display in the cards // Fields to display in the cards
const showFields = ["application_code", "site_url", "application_type"]; const showFields = ["application_code", "site_url", "application_type"];
@ -95,13 +130,6 @@ const ApplicationPage: React.FC<PageProps> = ({ lang: initialLang = "en" }) => {
}); });
}; };
// Reset all filters
const handleResetAllFilters = () => {
updatePagination({
page: 1,
query: {},
});
};
// Handle card actions // Handle card actions
const handleCardClick = (item: schema.ApplicationData) => { const handleCardClick = (item: schema.ApplicationData) => {
@ -115,7 +143,9 @@ const ApplicationPage: React.FC<PageProps> = ({ lang: initialLang = "en" }) => {
const handleUpdateClick = (item: schema.ApplicationData) => { const handleUpdateClick = (item: schema.ApplicationData) => {
setSelectedItem(item); setSelectedItem(item);
setTimeout(() => {
setMode("update"); setMode("update");
}, 0);
}; };
// Handle create button click // Handle create button click
@ -282,12 +312,8 @@ const ApplicationPage: React.FC<PageProps> = ({ lang: initialLang = "en" }) => {
translations={translations} translations={translations}
apiUrl='/api/applications' apiUrl='/api/applications'
formProps={{ formProps={{
fieldDefinitions: mode === 'create' ? schema.createFieldDefinitions : fieldDefinitions,
mode === 'update' ? schema.updateFieldDefinitions : validationSchema,
schema.viewFieldDefinitions,
validationSchema: mode === 'create' ? schema.CreateApplicationSchema :
mode === 'update' ? schema.UpdateApplicationSchema :
schema.ViewApplicationSchema,
fieldsByMode: schema.fieldsByMode fieldsByMode: schema.fieldsByMode
}} }}
/> />

View File

@ -15,16 +15,32 @@ export interface ApplicationData {
updated_at?: string; updated_at?: string;
} }
// Base schema with all possible fields // Validation error messages by language
const ApplicationBaseSchema = z.object({ const errorMessages = {
en: {
nameRequired: "Name is required",
applicationCodeRequired: "Application code is required",
siteUrlRequired: "Site URL is required",
applicationTypeRequired: "Application type is required"
},
tr: {
nameRequired: "İsim gereklidir",
applicationCodeRequired: "Uygulama kodu gereklidir",
siteUrlRequired: "Site URL'si gereklidir",
applicationTypeRequired: "Uygulama tipi gereklidir"
}
};
// Function to get schema with language-specific validation messages
const getApplicationBaseSchema = (lang: "en" | "tr" = "en") => z.object({
// Identification fields // Identification fields
uu_id: z.number().optional(), uu_id: z.string().optional(),
name: z.string().min(1, "Name is required"), name: z.string().min(1, errorMessages[lang].nameRequired),
application_code: z.string().min(1, "Application code is required"), application_code: z.string().min(1, errorMessages[lang].applicationCodeRequired),
// Application details // Application details
site_url: z.string().min(1, "Site URL is required"), site_url: z.string().min(1, errorMessages[lang].siteUrlRequired),
application_type: z.string().min(1, "Application type is required"), application_type: z.string().min(1, errorMessages[lang].applicationTypeRequired),
application_for: z.string().optional(), application_for: z.string().optional(),
description: z.string().optional(), description: z.string().optional(),
@ -37,6 +53,9 @@ const ApplicationBaseSchema = z.object({
updated_at: z.string().optional(), updated_at: z.string().optional(),
}); });
// For backward compatibility
const ApplicationBaseSchema = getApplicationBaseSchema("en");
const ApplicationBaseTranslationTr = { const ApplicationBaseTranslationTr = {
uu_id: "UUID", uu_id: "UUID",
name: "Name", name: "Name",
@ -65,28 +84,46 @@ const ApplicationBaseTranslationEn = {
updated_at: "Updated At", updated_at: "Updated At",
}; };
// Schema for creating a new application // Create schema for creating new applications with language support
export const CreateApplicationSchema = ApplicationBaseSchema.omit({ const getCreateApplicationSchema = (lang: "en" | "tr" = "en") =>
getApplicationBaseSchema(lang).omit({
uu_id: true, uu_id: true,
created_at: true, created_at: true,
updated_at: true, updated_at: true,
deleted: true, deleted: true,
}); });
// Schema for updating an existing application // Update schema for updating existing applications with language support
export const UpdateApplicationSchema = ApplicationBaseSchema.omit({ const getUpdateApplicationSchema = (lang: "en" | "tr" = "en") =>
getApplicationBaseSchema(lang).omit({
created_at: true, created_at: true,
updated_at: true, updated_at: true,
deleted: true, deleted: true,
}).required({ }).required({
uu_id: true, uu_id: true,
}); });
// For backward compatibility
const CreateApplicationSchema = getCreateApplicationSchema("en");
const UpdateApplicationSchema = getUpdateApplicationSchema("en");
// Schema for viewing an application (all fields) // Schema for viewing an application (all fields)
export const ViewApplicationSchema = ApplicationBaseSchema; const ViewApplicationSchema = ApplicationBaseSchema;
// Default schema (used for validation) // Default schema (used for validation)
export const ApplicationSchema = ApplicationBaseSchema; const ApplicationSchema = ApplicationBaseSchema;
// Export all schemas and schema generators
export {
ApplicationBaseSchema,
ApplicationSchema,
CreateApplicationSchema,
UpdateApplicationSchema,
ViewApplicationSchema,
getApplicationBaseSchema,
getCreateApplicationSchema,
getUpdateApplicationSchema,
};
export type ApplicationFormData = z.infer<typeof ApplicationSchema>; export type ApplicationFormData = z.infer<typeof ApplicationSchema>;
export type CreateApplicationFormData = z.infer<typeof CreateApplicationSchema>; export type CreateApplicationFormData = z.infer<typeof CreateApplicationSchema>;

View File

@ -48,6 +48,21 @@ interface PageProps {
type PageComponent = React.ComponentType<PageProps>; type PageComponent = React.ComponentType<PageProps>;
export type LanguageKey = "en" | "tr";
export interface TranslationSet {
showing: string;
of: string;
items: string;
total: string;
filtered: string;
page: string;
previous: string;
next: string;
itemsPerPage: string;
}
export type { export type {
PageComponent, PageComponent,
PageProps, PageProps,

View File

@ -1,17 +1,17 @@
services: services:
client_frontend: # client_frontend:
container_name: client_frontend # container_name: client_frontend
build: # build:
context: . # context: .
dockerfile: WebServices/client-frontend/Dockerfile # dockerfile: WebServices/client-frontend/Dockerfile
networks: # networks:
- wag-services # - wag-services
ports: # ports:
- "3000:3000" # - "3000:3000"
environment: # environment:
- NODE_ENV=development # - NODE_ENV=development
cpus: 1 # cpus: 1
mem_limit: 2048m # mem_limit: 2048m
# volumes: # volumes:
# - client-frontend:/WebServices/client-frontend # - client-frontend:/WebServices/client-frontend