diff --git a/ApiControllers/abstracts/event_clusters.py b/ApiControllers/abstracts/event_clusters.py index 7cb9d20..eea0009 100644 --- a/ApiControllers/abstracts/event_clusters.py +++ b/ApiControllers/abstracts/event_clusters.py @@ -49,21 +49,27 @@ class EventCluster: endpoint_uu_id=str(to_save_endpoint.uu_id), is_confirmed=True, db=db_session, - include_args=[ - Events.function_code, - Events.function_class, - Events.endpoint_code, - Events.endpoint_uu_id, - ] ) - event_to_save_database = Events.find_or_create(**event_dict_to_save) - 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}") + event_found = Events.filter_one( + Events.function_code == event_dict_to_save["function_code"], + db=db_session, + ).data + if event_found: + event_found.update(**event_dict_to_save) + event_found.save(db=db_session) 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 = Events.find_or_create( + **event_dict_to_save, + include_args=[ + Events.function_code, + Events.function_class, + Events.endpoint_code, + Events.endpoint_uu_id, + ] + ) + 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}") + event_to_save_database.save(db=db_session) def match_event(self, event_key: str) -> "Event": """ diff --git a/ApiControllers/initializer/create_route.py b/ApiControllers/initializer/create_route.py index dc73794..4325ca7 100644 --- a/ApiControllers/initializer/create_route.py +++ b/ApiControllers/initializer/create_route.py @@ -23,17 +23,24 @@ class RouteRegisterController: for route_method in [ method.lower() for method in getattr(route, "methods") ]: - restriction = EndpointRestriction.find_or_create( + add_or_update_dict = dict( endpoint_method=route_method, endpoint_name=route_path, endpoint_desc=route_summary.replace("_", " "), endpoint_function=route_summary, - operation_uu_id=operation_id, # UUID of the endpoint + operation_uu_id=operation_id, is_confirmed=True, - db=db_session, ) - if restriction.meta_data.created: - restriction.save(db=db_session) + endpoint_restriction_found = EndpointRestriction.filter_one_system( + EndpointRestriction.operation_uu_id == operation_id, db=db_session, + ).data + if endpoint_restriction_found: + endpoint_restriction_found.update(**add_or_update_dict, db=db_session) + endpoint_restriction_found.save(db=db_session) + else: + restriction = EndpointRestriction.find_or_create(**add_or_update_dict, db=db_session) + if restriction.meta_data.created: + restriction.save(db=db_session) def register_routes(self): for router in self.router_list: diff --git a/ApiServices/ManagementService/Endpoints/event_endpoints/route.py b/ApiServices/ManagementService/Endpoints/event_endpoints/route.py index 8cb30bc..4de5734 100644 --- a/ApiServices/ManagementService/Endpoints/event_endpoints/route.py +++ b/ApiServices/ManagementService/Endpoints/event_endpoints/route.py @@ -5,18 +5,39 @@ from ApiControllers.abstracts.default_validations import CommonHeaders from ApiControllers.providers.token_provider import TokenProvider from Controllers.Postgres.pagination import PaginateOnly, Pagination, PaginationResult from Controllers.Postgres.response import EndpointResponse -from Validations.service_endpoints.validations import Event2Employee, Event2Occupant +from Validations.service_endpoints.validations import Event2Employee, Event2Occupant, AddRemoveService +from Events.event_endpoints.cluster import EventsEndpointRouterCluster # Create API router event_endpoint_route = APIRouter(prefix="/events", tags=["Event Actions"]) @event_endpoint_route.post( - path="/list", - description="List events endpoint", + path="/list/available", + description="List available events endpoint", operation_id="0659d5e4-671f-466c-a84f-47a1290a6f0d", ) -def event_list_route( +def event_list_available_route( + data: PaginateOnly, + headers: CommonHeaders = Depends(CommonHeaders.as_dependency), +): + """ + List available events with pagination and filtering options + """ + 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 = EventsEndpointRouterCluster.get_event_cluster("EventsListAvailable") + event_cluster_matched = FoundCluster.match_event(event_key=event_key) + return event_cluster_matched.event_callable(list_options=data) + + +@event_endpoint_route.post( + path="/list/appended", + description="List appended events endpoint", + operation_id="4d563973-cdcd-44e1-94e0-4262ffb456a1", +) +def event_list_appended_route( data: PaginateOnly, headers: CommonHeaders = Depends(CommonHeaders.as_dependency), ): @@ -26,10 +47,10 @@ def event_list_route( 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 = EventsEndpointRouterCluster.get_event_cluster("EventsList") + FoundCluster = EventsEndpointRouterCluster.get_event_cluster("EventsListAppended") event_cluster_matched = FoundCluster.match_event(event_key=event_key) - return event_cluster_matched.event_callable(data=data) - + return event_cluster_matched.event_callable(list_options=data) + @event_endpoint_route.post( path="/register/service", @@ -37,7 +58,7 @@ def event_list_route( operation_id="c89a2150-db4d-4a8f-b6ec-9e0f09625f76", ) def event_register_service_route( - data: Any, + data: AddRemoveService, headers: CommonHeaders = Depends(CommonHeaders.as_dependency), ): """ @@ -46,7 +67,27 @@ def event_register_service_route( 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 = EventEndpointRouterCluster.get_event_cluster("EventRegisterService") + FoundCluster = EventsEndpointRouterCluster.get_event_cluster("EventRegisterService") + event_cluster_matched = FoundCluster.match_event(event_key=event_key) + return event_cluster_matched.event_callable(data=data) + + +@event_endpoint_route.post( + path="/unregister/service", + description="Unregister event from service endpoint", + operation_id="2f16dc9e-de02-449d-9c3f-1a21f87e8794", +) +def event_unregister_service_route( + data: AddRemoveService, + headers: CommonHeaders = Depends(CommonHeaders.as_dependency), +): + """ + Unregister event from service + """ + 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 = EventsEndpointRouterCluster.get_event_cluster("EventUnregisterService") event_cluster_matched = FoundCluster.match_event(event_key=event_key) return event_cluster_matched.event_callable(data=data) @@ -66,7 +107,7 @@ def event_bind_employee_extra_route( 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 = EventEndpointRouterCluster.get_event_cluster("EventBindEmployeeExtra") + FoundCluster = EventsEndpointRouterCluster.get_event_cluster("EventBindEmployeeExtra") event_cluster_matched = FoundCluster.match_event(event_key=event_key) return event_cluster_matched.event_callable(data=data) @@ -86,6 +127,6 @@ def event_bind_occupant_extra_route( 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 = EventEndpointRouterCluster.get_event_cluster("EventBindOccupantExtra") + FoundCluster = EventsEndpointRouterCluster.get_event_cluster("EventBindOccupantExtra") event_cluster_matched = FoundCluster.match_event(event_key=event_key) return event_cluster_matched.event_callable(data=data) diff --git a/ApiServices/ManagementService/Events/event_endpoints/cluster.py b/ApiServices/ManagementService/Events/event_endpoints/cluster.py index b025ad1..d978369 100644 --- a/ApiServices/ManagementService/Events/event_endpoints/cluster.py +++ b/ApiServices/ManagementService/Events/event_endpoints/cluster.py @@ -1,23 +1,35 @@ from ApiControllers.abstracts.event_clusters import EventCluster, RouterCluster from .supers_events import ( - EventsListEvent, + EventsListAvailableEvent, + EventsListAppendedEvent, EventRegisterServiceEvent, + EventUnRegisterServiceEvent, EventBindEmployeeExtraEvent, EventBindOccupantExtraEvent, ) EventsEndpointRouterCluster = RouterCluster(name="EventsEndpointRouterCluster") -EventsEndpointEventClusterList = EventCluster( - name="EventsList", endpoint_uu_id="0659d5e4-671f-466c-a84f-47a1290a6f0d" +EventsEndpointEventClusterListAvailable = EventCluster( + name="EventsListAvailable", endpoint_uu_id="0659d5e4-671f-466c-a84f-47a1290a6f0d" ) -EventsEndpointEventClusterList.add_event(EventsListEvent) +EventsEndpointEventClusterListAvailable.add_event(EventsListAvailableEvent) + +EventsEndpointEventClusterListAppended = EventCluster( + name="EventsListAppended", endpoint_uu_id="4d563973-cdcd-44e1-94e0-4262ffb456a1" +) +EventsEndpointEventClusterListAppended.add_event(EventsListAppendedEvent) EventsEndpointEventClusterRegisterService = EventCluster( name="EventRegisterService", endpoint_uu_id="c89a2150-db4d-4a8f-b6ec-9e0f09625f76" ) EventsEndpointEventClusterRegisterService.add_event(EventRegisterServiceEvent) +EventsEndpointEventClusterUnregisterService = EventCluster( + name="EventUnregisterService", endpoint_uu_id="2f16dc9e-de02-449d-9c3f-1a21f87e8794" +) +EventsEndpointEventClusterUnregisterService.add_event(EventUnRegisterServiceEvent) + EventsEndpointEventClusterBindEmployeeExtra = EventCluster( name="EventBindEmployeeExtra", endpoint_uu_id="58ef3640-04ec-43f9-8f3e-f86be3ce4a24" ) @@ -28,8 +40,9 @@ EventsEndpointEventClusterBindOccupantExtra = EventCluster( ) EventsEndpointEventClusterBindOccupantExtra.add_event(EventBindOccupantExtraEvent) -EventsEndpointRouterCluster.set_event_cluster(EventsEndpointEventClusterList) +EventsEndpointRouterCluster.set_event_cluster(EventsEndpointEventClusterListAvailable) +EventsEndpointRouterCluster.set_event_cluster(EventsEndpointEventClusterListAppended) EventsEndpointRouterCluster.set_event_cluster(EventsEndpointEventClusterRegisterService) +EventsEndpointRouterCluster.set_event_cluster(EventsEndpointEventClusterUnregisterService) EventsEndpointRouterCluster.set_event_cluster(EventsEndpointEventClusterBindEmployeeExtra) EventsEndpointRouterCluster.set_event_cluster(EventsEndpointEventClusterBindOccupantExtra) - \ No newline at end of file diff --git a/ApiServices/ManagementService/Events/event_endpoints/supers_events.py b/ApiServices/ManagementService/Events/event_endpoints/supers_events.py index d303917..5cce2d1 100644 --- a/ApiServices/ManagementService/Events/event_endpoints/supers_events.py +++ b/ApiServices/ManagementService/Events/event_endpoints/supers_events.py @@ -9,28 +9,45 @@ from Schemas import ( Event2EmployeeExtra, Event2OccupantExtra, Service2Events, + Services, ) - -# List endpoint -EventsListEvent = Event( - name="service_endpoint_list", - key="0a08c64b-ce20-4791-b1e9-014db6b75ea7", +# List available events endpoint +EventsListAvailableEvent = Event( + name="event_endpoint_list_available", + key="d39af512-ec71-4c0f-9b35-e53b0d06d3a4", request_validator=None, # TODO: Add request validator response_validator=None, # TODO: Add response validator - description="Super Users List services endpoint", + description="Super Users List available events endpoint", ) +# List appended events endpoint +EventsListAppendedEvent = Event( + name="event_endpoint_list_appended", + key="bea77d6a-d99f-468b-9002-b3bda6bb6ad0", + request_validator=None, # TODO: Add request validator + response_validator=None, # TODO: Add response validator + description="Super Users List appended events endpoint", +) # Event Register endpoint EventRegisterServiceEvent = Event( - name="service_endpoint_register_service", + name="event_endpoint_register_service", key="e18e7f89-5708-4a15-9258-99b0903ed43d", request_validator=None, # TODO: Add request validator response_validator=None, # TODO: Add response validator description="Super Users Register service endpoint", ) +# Event Unregister endpoint +EventUnRegisterServiceEvent = Event( + name="service_endpoint_unregister_service", + key="4d693774-4857-435b-a63c-c39baebfe916", + request_validator=None, # TODO: Add request validator + response_validator=None, # TODO: Add response validator + description="Super Users Unregister service endpoint", +) + # Bind employee extra endpoint EventBindEmployeeExtraEvent = Event( name="service_endpoint_bind_employee_extra", @@ -50,58 +67,196 @@ EventBindOccupantExtraEvent = Event( ) -def events_list_callable(list_options: PaginateOnly): +def events_list_available_callable(list_options: PaginateOnly): """ - Example callable method + List available events with pagination and filtering options """ list_options = PaginateOnly(**list_options.model_dump()) - with Services.new_session() as db_session: + service_uu_id = list_options.query.get('service_uu_id__ilike', None) + if not service_uu_id: + return { + "message": "MSG0003-PARAM-MISSING", + "data": list_options.query, + "completed": False, + } + with Events.new_session() as db_session: + service2events = Service2Events.filter_all(*Service2Events.convert(list_options.query), db=db_session) + already_events = [service_to_event.event_id for service_to_event in service2events.data] + list_options.query.pop('service_uu_id__ilike', None) + list_options.query.pop('service_uu_id', None) if list_options.query: - services_list = Services.filter_all( - *Services.convert(list_options.query), db=db_session - ) + events_list = Events.filter_all(*Events.convert(list_options.query), Events.id.not_in(already_events), db=db_session) else: - services_list = Services.filter_all(db=db_session) - pagination = Pagination(data=services_list) + events_list = Events.filter_all(Events.id.not_in(already_events), db=db_session) + pagination = Pagination(data=events_list) pagination.change(**list_options.model_dump()) - pagination_result = PaginationResult( - data=services_list, - pagination=pagination, - # response_model="", - ).pagination.as_dict + pagination_result = PaginationResult(data=events_list, pagination=pagination) return EndpointResponse( message="MSG0003-LIST", pagination_result=pagination_result, ).response -EventsListEvent.event_callable = events_list_callable + +EventsListAvailableEvent.event_callable = events_list_available_callable + + +def events_list_appended_callable(list_options: PaginateOnly): + """ + List appended events with pagination and filtering options + """ + list_options = PaginateOnly(**list_options.model_dump()) + service_uu_id = list_options.query.get('service_uu_id__ilike', None) + if not service_uu_id: + return { + "message": "MSG0003-PARAM-MISSING", + "data": list_options.query, + "completed": False, + } + with Events.new_session() as db_session: + service2events = Service2Events.filter_all(*Service2Events.convert(list_options.query), db=db_session) + already_events = [service_to_event.event_id for service_to_event in service2events.data] + list_options.query.pop('service_uu_id__ilike', None) + list_options.query.pop('service_uu_id', None) + if list_options.query: + events_list = Events.filter_all(*Events.convert(list_options.query), Events.id.in_(already_events), db=db_session) + else: + events_list = Events.filter_all(Events.id.in_(already_events), db=db_session) + pagination = Pagination(data=events_list) + pagination.change(**list_options.model_dump()) + pagination_result = PaginationResult(data=events_list, pagination=pagination) + return EndpointResponse( + message="MSG0003-LIST", + pagination_result=pagination_result, + ).response + + +EventsListAppendedEvent.event_callable = events_list_appended_callable + def event_register_service_callable(data: Any): """ - Example callable method + Register event to service """ - return EndpointResponse( - message="MSG0003-REGISTER", - ).response + with Events.new_session() as db_session: + event = Events.filter_one_system( + Events.uu_id == data.event_uu_id, + db=db_session + ) + print('event', event.data) + if not event.data: + return { + "message": "MSG0003-NOT-FOUND", + "data": data.model_dump(), + "completed": False, + } + service = Services.filter_one_system( + Services.uu_id == data.service_uu_id, + db=db_session + ) + print('service', service.data) + if not service.data: + return { + "message": "MSG0003-NOT-FOUND", + "data": data.model_dump(), + "completed": False, + } + service_to_event = Service2Events.find_or_create( + db=db_session, + include_args=[ + Service2Events.service_uu_id, + Service2Events.event_uu_id, + ], + service_id=service.data.id, + event_id=event.data.id, + is_confirmed=True, + service_uu_id=str(service.data.uu_id), + event_uu_id=str(event.data.uu_id), + ) + print('service_to_event', service_to_event) + if not service_to_event.meta_data.created: + return { + "message": "MSG0003-ALREADY-FOUND", + "data": data.model_dump(), + "completed": False, + } + service_to_event.save(db=db_session) + return { + "message": "MSG0003-REGISTER", + "data": data.model_dump(), + "completed": True, + } + EventRegisterServiceEvent.event_callable = event_register_service_callable + +def event_unregister_service_callable(data: Any): + """ + Unregister event from service + """ + with Events.new_session() as db_session: + event = Events.filter_one_system( + Events.uu_id == data.event_uu_id, db=db_session + ) + if not event.data: + return { + "message": "MSG0003-NOT-FOUND", + "data": data.model_dump(), + "completed": False, + } + service = Services.filter_one_system( + Services.uu_id == data.service_uu_id, db=db_session + ) + if not service.data: + return { + "message": "MSG0003-NOT-FOUND", + "data": data.model_dump(), + "completed": False, + } + service_to_event = Service2Events.filter_one_system( + Service2Events.service_id==service.data.id, + Service2Events.event_id==event.data.id, + db=db_session, + ) + if not service_to_event.data: + return { + "message": "MSG0003-NOT-FOUND", + "data": data.model_dump(), + "completed": False, + } + service_to_event.query.delete() + db_session.commit() + return { + "message": "MSG0003-UNREGISTER", + "data": data.model_dump(), + "completed": True, + } + + +EventUnRegisterServiceEvent.event_callable = event_unregister_service_callable + + def event_bind_employee_extra_callable(data: Any): """ - Example callable method + Bind event to employee extra """ - return EndpointResponse( - message="MSG0003-BIND", - ).response + return { + "message": "MSG0003-BIND", + "data": data.model_dump(), + "completed": True, + } + EventBindEmployeeExtraEvent.event_callable = event_bind_employee_extra_callable + def event_bind_occupant_extra_callable(data: Any): """ - Example callable method + Bind event to occupant extra """ return EndpointResponse( message="MSG0003-BIND", ).response + EventBindOccupantExtraEvent.event_callable = event_bind_occupant_extra_callable diff --git a/ApiServices/ManagementService/Events/service_endpoints/cluster.py b/ApiServices/ManagementService/Events/service_endpoints/cluster.py index 933083c..fc36468 100644 --- a/ApiServices/ManagementService/Events/service_endpoints/cluster.py +++ b/ApiServices/ManagementService/Events/service_endpoints/cluster.py @@ -16,3 +16,4 @@ ServiceEndpointEventClusterToService = EventCluster( ServiceEndpointEventClusterToService.add_event(ServiceEndpointToEventsEvent) ServiceEndpointRouterCluster.set_event_cluster(ServiceEndpointEventClusterList) +ServiceEndpointRouterCluster.set_event_cluster(ServiceEndpointEventClusterToService) diff --git a/ApiServices/ManagementService/Validations/service_endpoints/validations.py b/ApiServices/ManagementService/Validations/service_endpoints/validations.py index bc3a90e..0a15f28 100644 --- a/ApiServices/ManagementService/Validations/service_endpoints/validations.py +++ b/ApiServices/ManagementService/Validations/service_endpoints/validations.py @@ -1,8 +1,13 @@ +from pydantic import BaseModel - -class Event2Employee: +class Event2Employee(BaseModel): pass -class Event2Occupant: +class Event2Occupant(BaseModel): pass + + +class AddRemoveService(BaseModel): + event_uu_id: str + service_uu_id: str diff --git a/Controllers/Postgres/crud.py b/Controllers/Postgres/crud.py index 686e4f0..1b54c78 100644 --- a/Controllers/Postgres/crud.py +++ b/Controllers/Postgres/crud.py @@ -270,6 +270,7 @@ class CRUDModel: except Exception as e: db.rollback() + print('e', e) cls.raise_exception( f"Failed to find or create record: {str(e)}", status_code=500 ) diff --git a/WebServices/management-frontend/src/apicalls/api-fetcher.ts b/WebServices/management-frontend/src/apicalls/api-fetcher.ts index 576fcbb..4173d09 100644 --- a/WebServices/management-frontend/src/apicalls/api-fetcher.ts +++ b/WebServices/management-frontend/src/apicalls/api-fetcher.ts @@ -78,18 +78,13 @@ async function coreFetch( 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, @@ -99,7 +94,7 @@ async function coreFetch( if (process.env.NODE_ENV !== "production") { console.log("Fetching:", url, fetchOptions); - console.log("Response:", responseJson); + // console.log("Response:", responseJson); } return prepareResponse(responseJson, response.status); diff --git a/WebServices/management-frontend/src/apicalls/application/endpoints.tsx b/WebServices/management-frontend/src/apicalls/application/endpoints.tsx index 27e1383..1a2e8ea 100644 --- a/WebServices/management-frontend/src/apicalls/application/endpoints.tsx +++ b/WebServices/management-frontend/src/apicalls/application/endpoints.tsx @@ -26,7 +26,6 @@ async function listApplications(payload: PaginationParams): Promise> { + if (!payload.query.service_uu_id__ilike) { + console.warn('Missing service_uu_id in query parameters'); + return { + data: [], + pagination: defaultPaginationResponse, + }; + } -async function listEvents(payload: PaginationParams): Promise> { try { + const requestBody = { + page: payload.page, + size: payload.size, + order_field: payload.orderField, + order_type: payload.orderType, + query: payload.query, + }; + + console.log('Sending request to backend with service_uu_id:', payload.query.service_uu_id); + console.log('Full request body:', JSON.stringify(requestBody, null, 2)); + const response = await fetchDataWithToken( - eventsListEndpoint, - { - page: payload.page, - size: payload.size, - order_field: payload.orderField, - order_type: payload.orderType, - query: payload.query, - }, + eventsListAvailableEndpoint, + requestBody, "POST", false ); if (response?.status === 200 || response?.status === 202) { const responseData = response.data as PaginatedApiResponse; + console.log('list_events_available responseData:', JSON.stringify(responseData, null, 2)); 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 - } + pagination: collectPaginationFromApiResponse(responseData) }; } return { data: [], - pagination: { - page: 1, - size: 10, - totalCount: 0, - totalItems: 0, - totalPages: 0, - pageCount: 0, - orderField: ['name'], - orderType: ['asc'], - query: {}, - next: false, - back: false - } + pagination: defaultPaginationResponse, }; } catch (error) { console.error("Error fetching events list:", error); - // 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 - } + pagination: defaultPaginationResponse, }; } } +async function list_events_appended(payload: PaginationParams): Promise> { + + if (!payload.query.service_uu_id__ilike) { + console.warn('Missing service_uu_id in query parameters for list_events_appended'); + return { + data: [], + pagination: defaultPaginationResponse, + }; + } + + try { + const requestBody = { + page: payload.page, + size: payload.size, + order_field: payload.orderField, + order_type: payload.orderType, + query: payload.query, + }; + + console.log('Sending request to backend with service_uu_id:', payload.query.service_uu_id); + console.log('Full request body:', JSON.stringify(requestBody, null, 2)); + + const response = await fetchDataWithToken( + eventsListAppendedEndpoint, + requestBody, + "POST", + false + ); + + if (response?.status === 200 || response?.status === 202) { + const responseData = response.data as PaginatedApiResponse; + console.log('list_events_appended responseData:', JSON.stringify(responseData, null, 2)); + return { + data: responseData.data || [], + pagination: collectPaginationFromApiResponse(responseData) + }; + } + return { + data: [], + pagination: defaultPaginationResponse, + }; + } catch (error) { + console.error("Error fetching events list:", error); + return { + data: [], + pagination: defaultPaginationResponse, + }; + } +} + +async function appendEventToService(payload: AppendEventToService) { + try { + const response = await fetchDataWithToken( + appendEventToServiceEndpoint, + payload, + "POST", + false + ); + return response?.status === 200 || response?.status === 202 ? response.data : null; + } catch (error) { + console.error("Error appending event to service:", error); + } +} + +async function removeEventFromService(payload: RemoveEventFromService) { + try { + const response = await fetchDataWithToken( + removeEventFromServiceEndpoint, + payload, + "POST", + false + ); + return response?.status === 200 || response?.status === 202 ? response.data : null; + } catch (error) { + console.error("Error removing event from service:", error); + } +} + export { - listEvents, + list_events_available, + list_events_appended, + appendEventToService, + removeEventFromService, }; diff --git a/WebServices/management-frontend/src/app/api/appenders/create/route.ts b/WebServices/management-frontend/src/app/api/appenders/create/route.ts new file mode 100644 index 0000000..8494f37 --- /dev/null +++ b/WebServices/management-frontend/src/app/api/appenders/create/route.ts @@ -0,0 +1,22 @@ +import { appendEventToService } from "@/apicalls/events/endpoints"; +import { NextRequest } from "next/server"; +import { withErrorHandling } from "@/app/api/utils/requestHandlers"; +import { + successResponse, + errorResponse, +} from "@/app/api/utils/responseHandlers"; + +export const POST = withErrorHandling( + async (request: NextRequest, body: any) => { + const payload = { + event_uu_id: body.event_uu_id, + service_uu_id: body.service_uu_id, + }; + const result = await appendEventToService(payload); + if (!result) { + return errorResponse("Error appending event to service"); + } else { + return successResponse(result); + } + } +); diff --git a/WebServices/management-frontend/src/app/api/appenders/delete/route.ts b/WebServices/management-frontend/src/app/api/appenders/delete/route.ts new file mode 100644 index 0000000..f2a67ad --- /dev/null +++ b/WebServices/management-frontend/src/app/api/appenders/delete/route.ts @@ -0,0 +1,22 @@ +import { removeEventFromService } from "@/apicalls/events/endpoints"; +import { NextRequest } from "next/server"; +import { withErrorHandling } from "@/app/api/utils/requestHandlers"; +import { + successResponse, + errorResponse, +} from "@/app/api/utils/responseHandlers"; + +export const POST = withErrorHandling( + async (request: NextRequest, body: any) => { + const payload = { + event_uu_id: body.event_uu_id, + service_uu_id: body.service_uu_id, + }; + const result = await removeEventFromService(payload); + if (!result) { + return errorResponse("Error removing event from service"); + } else { + return successResponse(result); + } + } +); diff --git a/WebServices/management-frontend/src/app/api/appenders/list/route.ts b/WebServices/management-frontend/src/app/api/appenders/list/route.ts index d70207c..c23506e 100644 --- a/WebServices/management-frontend/src/app/api/appenders/list/route.ts +++ b/WebServices/management-frontend/src/app/api/appenders/list/route.ts @@ -1,4 +1,4 @@ -import { listEventsToService } from "@/apicalls/services/endpoints"; -import { createListHandler } from "@/app/api/utils"; +import { list_events_appended } from "@/apicalls/events/endpoints"; +import { createListHandler } from "@/app/api/utils/apiOperations"; -export const POST = createListHandler(listEventsToService); +export const POST = createListHandler(list_events_appended); diff --git a/WebServices/management-frontend/src/app/api/events/list/route.ts b/WebServices/management-frontend/src/app/api/events/list/route.ts index 16c81a6..6e0bd68 100644 --- a/WebServices/management-frontend/src/app/api/events/list/route.ts +++ b/WebServices/management-frontend/src/app/api/events/list/route.ts @@ -1,4 +1,4 @@ -import { listEvents } from "@/apicalls/events/endpoints"; -import { createListHandler } from "@/app/api/utils"; +import { list_events_available } from "@/apicalls/events/endpoints"; +import { createListHandler } from "@/app/api/utils/apiOperations"; -export const POST = createListHandler(listEvents); +export const POST = createListHandler(list_events_available); diff --git a/WebServices/management-frontend/src/app/api/utils/apiOperations.ts b/WebServices/management-frontend/src/app/api/utils/apiOperations.ts index e141f49..dcfcafa 100644 --- a/WebServices/management-frontend/src/app/api/utils/apiOperations.ts +++ b/WebServices/management-frontend/src/app/api/utils/apiOperations.ts @@ -28,18 +28,11 @@ export async function handleListOperation( body: any, listFunction: ListFunction ) { - // Extract pagination parameters with defaults const page = body.page || 1; const size = body.size || 10; - - // Extract sorting parameters with defaults - const orderField = body.orderField || ["name"]; + const orderField = body.orderField || ["uu_id"]; const orderType = body.orderType || ["asc"]; - - // Extract query filters const query = body.query || {}; - - // Call the actual API function for listing const response = await listFunction({ page, size, @@ -47,8 +40,6 @@ export async function handleListOperation( orderType, query, } as PaginationParams); - - // Return the list response return paginationResponse(response.data, response.pagination); } @@ -64,7 +55,6 @@ export async function handleCreateOperation( createFunction?: CreateFunction, requiredFields: string[] = [] ) { - // Validate required fields if specified if (requiredFields.length > 0) { const validation = validateRequiredFields(body, requiredFields); if (!validation.valid) { @@ -72,13 +62,11 @@ export async function handleCreateOperation( } } - // If a create function is provided, call it if (createFunction) { const result = await createFunction(body); return createResponse(result); } - // Otherwise return a mock response return createResponse({ id: Math.floor(Math.random() * 1000), ...body, @@ -96,26 +84,15 @@ export async function handleUpdateOperation( body: any, updateFunction?: UpdateFunction ) { - // Skip validation as it's handled at the page level with Yup - // and IDs are extracted from URL paths for update/delete operations - - // 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 (updateFunction) { console.log("Body:", body); const result = await updateFunction(body, uuid); return updateResponse(result); } - - // Otherwise return a mock response return updateResponse(body); } @@ -128,14 +105,7 @@ export async function handleDeleteOperation( request: NextRequest, deleteFunction?: DeleteFunction ) { - // Skip ID validation as it's handled at the page level - // and IDs are typically extracted from URL paths - - // 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); } @@ -143,8 +113,6 @@ export async function handleDeleteOperation( if (deleteFunction) { await deleteFunction(uuid); } - - // Return a success response return deleteResponse(); } diff --git a/WebServices/management-frontend/src/app/api/utils/types.ts b/WebServices/management-frontend/src/app/api/utils/types.ts index 7e82555..9f6a914 100644 --- a/WebServices/management-frontend/src/app/api/utils/types.ts +++ b/WebServices/management-frontend/src/app/api/utils/types.ts @@ -40,6 +40,20 @@ export interface PaginationResponse { back: boolean; } +export const defaultPaginationResponse: PaginationResponse = { + page: 1, + size: 10, + totalCount: 0, + totalItems: 0, + totalPages: 0, + pageCount: 0, + orderField: ["uu_id"], + orderType: ["asc"], + query: {}, + next: false, + back: false, +}; + /** * API response interface */ @@ -57,6 +71,24 @@ export interface PaginatedApiResponse { pagination: PaginationResponse; } +export const collectPaginationFromApiResponse = ( + response: PaginatedApiResponse +): PaginationResponse => { + return { + page: response.pagination?.page || 1, + size: response.pagination?.size || 10, + totalCount: response.pagination?.totalCount || 0, + totalItems: response.pagination?.totalItems || 0, + totalPages: response.pagination?.totalPages || 0, + pageCount: response.pagination?.pageCount || 0, + orderField: response.pagination?.orderField || ["uu_id"], + orderType: response.pagination?.orderType || ["asc"], + query: response.pagination?.query || {}, + next: response.pagination?.next || false, + back: response.pagination?.back || false, + }; +}; + /** * API handler function type */ @@ -65,7 +97,9 @@ export type ApiHandler = (request: NextRequest, body: any) => Promise; /** * List function type */ -export type ListFunction = (params: PaginationParams) => Promise>; +export type ListFunction = ( + params: PaginationParams +) => Promise>; /** * Create function type diff --git a/WebServices/management-frontend/src/components/common/CardDisplay/CardDisplay.tsx b/WebServices/management-frontend/src/components/common/CardDisplay/CardDisplay.tsx index 9e7f898..2dc2a49 100644 --- a/WebServices/management-frontend/src/components/common/CardDisplay/CardDisplay.tsx +++ b/WebServices/management-frontend/src/components/common/CardDisplay/CardDisplay.tsx @@ -21,6 +21,7 @@ export function CardDisplay({ showUpdateIcon = false, onViewClick, onUpdateClick, + size = "lg", }: CardDisplayProps) { if (error) { return ( @@ -44,7 +45,7 @@ export function CardDisplay({ /> )) ) : data.length === 0 ? ( -
+
{translations[lang].noData || "No data found"}
) : ( @@ -64,6 +65,7 @@ export function CardDisplay({ onViewClick={onViewClick} onUpdateClick={onUpdateClick} getFieldValue={getFieldValue} + size={size} /> )) )} diff --git a/WebServices/management-frontend/src/components/common/CardDisplay/CardItem.tsx b/WebServices/management-frontend/src/components/common/CardDisplay/CardItem.tsx index 0cb4848..d28e89e 100644 --- a/WebServices/management-frontend/src/components/common/CardDisplay/CardItem.tsx +++ b/WebServices/management-frontend/src/components/common/CardDisplay/CardItem.tsx @@ -1,10 +1,6 @@ "use client"; import React from "react"; -import { - Card, - CardContent, - CardHeader, -} from "@/components/ui/card"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Eye, Edit } from "lucide-react"; import { CardItemProps, CardActionsProps, CardFieldProps } from "./schema"; @@ -23,17 +19,108 @@ export function CardItem({ onViewClick, onUpdateClick, getFieldValue, + size = "lg", }: CardItemProps) { - return ( + const getCardHeight = () => { + switch (size) { + case "xs": return "h-16 max-h-16"; + case "sm": return "h-20 max-h-20"; + case "md": return "h-24 max-h-24"; + case "lg": + default: return "h-full"; + } + }; + const getCardStyle = () => { + switch (size) { + case "xs": return "!py-0 !gap-0 !flex !flex-col"; + case "sm": return "!py-1 !gap-1 !flex !flex-col"; + case "md": return "!py-2 !gap-2 !flex !flex-col"; + case "lg": + default: return ""; + } + }; + + const getTitleSize = () => { + switch (size) { + case "xs": return "text-xs"; + case "sm": return "text-sm"; + case "md": return "text-base"; + case "lg": + default: return "text-lg"; + } + }; + + const getContentPadding = () => { + switch (size) { + case "xs": return "p-1 py-1"; + case "sm": return "p-1 py-1"; + case "md": return "p-2 py-1"; + case "lg": + default: return "p-3"; + } + }; + + if (size === "xs" || size === "sm") { + return ( +
+
onCardClick(item) : undefined} + > + {showViewIcon && ( + + )} + {showUpdateIcon && ( + + )} +
+

{getFieldValue(item, titleField)}

+
+ {showFields.map((field) => ( + + ))} +
+
+
+
+ ); + } + + return (
onCardClick(item) : undefined} > - -

{getFieldValue(item, titleField)}

+ +

{getFieldValue(item, titleField)}

({ onUpdateClick={onUpdateClick} />
- +
{showFields.map((field) => ( ({ translations={translations} renderCustomField={renderCustomField} getFieldValue={getFieldValue} + size={size} /> ))}
@@ -62,8 +150,6 @@ export function CardItem({ ); } -// Interface moved to schema.ts - function CardActions({ item, showViewIcon, @@ -105,8 +191,6 @@ function CardActions({ ); } -// Interface moved to schema.ts - function CardField({ item, field, @@ -114,17 +198,48 @@ function CardField({ translations, renderCustomField, getFieldValue, + size = "lg", }: CardFieldProps) { + const getTextSize = () => { + switch (size) { + case "xs": return "text-xs"; + case "sm": return "text-xs"; + case "md": return "text-sm"; + case "lg": + default: return "text-base"; + } + }; + + const getLabelWidth = () => { + switch (size) { + case "xs": return "w-16"; + case "sm": return "w-20"; + case "md": return "w-24"; + case "lg": + default: return "w-32"; + } + }; + + if (renderCustomField) { + return renderCustomField(item, field); + } + + const label = translations?.[field]?.[lang] || field; + const value = getFieldValue(item, field); + + if (size === "xs" || size === "sm") { + return ( +
+ {label}: + {value} +
+ ); + } + return ( -
- - {translations[field]?.[lang] || field}: - - - {renderCustomField - ? renderCustomField(item, field) - : getFieldValue(item, field)} - +
+ {label}: + {value}
); } diff --git a/WebServices/management-frontend/src/components/common/CardDisplay/schema.ts b/WebServices/management-frontend/src/components/common/CardDisplay/schema.ts index 0954724..5ff2294 100644 --- a/WebServices/management-frontend/src/components/common/CardDisplay/schema.ts +++ b/WebServices/management-frontend/src/components/common/CardDisplay/schema.ts @@ -1,5 +1,7 @@ import { GridSize } from "../HeaderSelections/GridSelectionComponent"; +export type CardSize = "xs" | "sm" | "md" | "lg"; + export interface CardDisplayProps { showFields: string[]; data: T[]; @@ -15,6 +17,7 @@ export interface CardDisplayProps { showUpdateIcon?: boolean; onViewClick?: (item: T) => void; onUpdateClick?: (item: T) => void; + size?: CardSize; } export interface CardItemProps { @@ -31,6 +34,7 @@ export interface CardItemProps { onViewClick?: (item: T) => void; onUpdateClick?: (item: T) => void; getFieldValue: (item: any, field: string) => any; + size?: CardSize; } export interface CardActionsProps { @@ -47,6 +51,7 @@ export interface CardFieldProps { translations: Record; renderCustomField?: (item: T, field: string) => React.ReactNode; getFieldValue: (item: any, field: string) => any; + size?: CardSize; } export interface CardSkeletonProps { diff --git a/WebServices/management-frontend/src/components/common/FormDisplay/types.ts b/WebServices/management-frontend/src/components/common/FormDisplay/types.ts index 6dbfd44..af32e71 100644 --- a/WebServices/management-frontend/src/components/common/FormDisplay/types.ts +++ b/WebServices/management-frontend/src/components/common/FormDisplay/types.ts @@ -40,7 +40,7 @@ export interface ViewComponentProps extends BaseFormProps { export interface FormDisplayProps { mode: FormMode | FormModeView; initialData?: T; - refetch?: () => void; + refetch?: (additionalParams?: Record) => void; setMode: React.Dispatch>; setSelectedItem: React.Dispatch>; onCancel: () => void; diff --git a/WebServices/management-frontend/src/components/common/hooks/useApiData.ts b/WebServices/management-frontend/src/components/common/hooks/useApiData.ts index de3a70e..b1d29b2 100644 --- a/WebServices/management-frontend/src/components/common/hooks/useApiData.ts +++ b/WebServices/management-frontend/src/components/common/hooks/useApiData.ts @@ -40,7 +40,6 @@ export function useApiData( } catch (error) { console.error("Error fetching data from API:", error); - // Return empty data with pagination info on error return { data: [], pagination: { @@ -60,6 +59,5 @@ export function useApiData( } }; - // Use the generic data fetching hook with our API-specific fetch function return useDataFetching(fetchFromApi, initialParams); } diff --git a/WebServices/management-frontend/src/components/common/hooks/useDataFetching.ts b/WebServices/management-frontend/src/components/common/hooks/useDataFetching.ts index 372b8bf..0fc0539 100644 --- a/WebServices/management-frontend/src/components/common/hooks/useDataFetching.ts +++ b/WebServices/management-frontend/src/components/common/hooks/useDataFetching.ts @@ -24,7 +24,7 @@ export function useDataFetching( const [requestParams, setRequestParams] = useState({ page: initialParams.page || 1, size: initialParams.size || 10, - orderField: initialParams.orderField || ["name"], + orderField: initialParams.orderField || ["uu_id"], orderType: initialParams.orderType || ["asc"], query: initialParams.query || {}, }); @@ -85,14 +85,10 @@ export function useDataFetching( requestParams.query, ]); - // Track if this is the initial mount const initialMountRef = useRef(true); - - // Track previous request params to avoid unnecessary fetches const prevRequestParamsRef = useRef(requestParams); useEffect(() => { - // Only fetch on mount or when request params actually change const paramsChanged = JSON.stringify(prevRequestParamsRef.current) !== JSON.stringify(requestParams); @@ -102,19 +98,17 @@ export function useDataFetching( fetchDataFromApi(); initialMountRef.current = false; prevRequestParamsRef.current = { ...requestParams }; - }, 300); // Debounce + }, 300); return () => clearTimeout(timer); } }, [fetchDataFromApi, requestParams]); const updatePagination = useCallback((updates: Partial) => { - // Transform query parameters to use __ilike with %value% format if (updates.query) { const transformedQuery: Record = {}; Object.entries(updates.query).forEach(([key, value]) => { - // Only transform string values that aren't already using a special operator if ( typeof value === "string" && !key.includes("__") && @@ -127,13 +121,9 @@ export function useDataFetching( }); updates.query = transformedQuery; - - // Always reset to page 1 when search query changes if (!updates.hasOwnProperty("page")) { updates.page = 1; } - - // Reset response metadata when search changes to avoid stale pagination data setResponseMetadata({ totalCount: 0, totalItems: 0, @@ -153,7 +143,6 @@ export function useDataFetching( // Create a combined refetch function const refetch = useCallback(() => { - // Reset pagination to page 1 when manually refetching setRequestParams((prev) => ({ ...prev, page: 1, diff --git a/WebServices/management-frontend/src/eventRouters/appendersService/listComponent.tsx b/WebServices/management-frontend/src/eventRouters/appendersService/listComponent.tsx index 6e03d6e..e818411 100644 --- a/WebServices/management-frontend/src/eventRouters/appendersService/listComponent.tsx +++ b/WebServices/management-frontend/src/eventRouters/appendersService/listComponent.tsx @@ -6,9 +6,9 @@ import { Card, CardContent } from '@/components/ui/card'; import { TextQueryModifier } from '@/components/common/QueryModifiers/TextQueryModifier'; import { PaginationToolsComponent } from '@/components/common/PaginationModifiers/PaginationToolsComponent'; import { CardDisplay } from '@/components/common/CardDisplay/CardDisplay'; -import { ListComponentApplicationProps, ListComponentServiceProps } from './type'; +import { ListComponentEventsProps, ListComponentServicesProps } from './type'; -const ListComponentServices: React.FC = ({ +const ListComponentServices: React.FC = ({ lang, loading, error, @@ -17,6 +17,7 @@ const ListComponentServices: React.FC = ({ showFields, gridCols, titleField, + size, handleQueryChange, updatePagination, handleCardClick, @@ -75,6 +76,7 @@ const ListComponentServices: React.FC = ({ titleField={titleField} onCardClick={handleCardClick} gridCols={gridCols} + size={size} showViewIcon={true} onViewClick={handleViewClick} /> @@ -83,7 +85,7 @@ const ListComponentServices: React.FC = ({ ); }; -const ListComponentEvents: React.FC = ({ +const ListComponentEvents: React.FC = ({ lang, loading, error, @@ -91,6 +93,7 @@ const ListComponentEvents: React.FC = ({ pagination, showFields, gridCols, + size, handleQueryChange, updatePagination, handleCardClick, @@ -151,6 +154,57 @@ const ListComponentEvents: React.FC = ({ gridCols={gridCols} showViewIcon={true} onViewClick={handleViewClick} + size={size} + /> +
+ + ); +}; + +const ListComponentAppenders: React.FC = ({ + lang, + loading, + error, + data, + pagination, + showFields, + gridCols, + size, + handleQueryChange, + updatePagination, + handleCardClick, + handleViewClick, +}) => { + return ( + <> + {/* Pagination Tools Component */} + + + + + + + {/* Card Display Component */} +
+
@@ -160,4 +214,5 @@ const ListComponentEvents: React.FC = ({ export { ListComponentEvents, ListComponentServices, + ListComponentAppenders, }; \ No newline at end of file diff --git a/WebServices/management-frontend/src/eventRouters/appendersService/page.tsx b/WebServices/management-frontend/src/eventRouters/appendersService/page.tsx index 96aae8b..52ad06f 100644 --- a/WebServices/management-frontend/src/eventRouters/appendersService/page.tsx +++ b/WebServices/management-frontend/src/eventRouters/appendersService/page.tsx @@ -7,9 +7,15 @@ import { serviceViewFieldDefinitions, ServiceFieldDefinitionsType, ServiceSchema, + serviceFieldsByMode, } from "./schemaList/services"; -import * as schema from "./schemaList/schema"; - +import { + EventData, + eventFlatFieldDefinitions, + EventFieldDefinitionsType, + EventSchema, + eventFieldsByMode +} from "./schemaList/events"; import { ListComponentEvents, ListComponentServices } from "./listComponent"; import { translations, translationsServices, translationsAppenders, translationsEvents } from "./language"; @@ -21,6 +27,7 @@ import { useApiData } from "@/components/common"; import { Language } from "@/components/common/schemas"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; +import { CardSize } from "@/components/common/CardDisplay/schema"; const AppendersServicePage: React.FC = ({ lang }: { lang: Language }) => { @@ -39,7 +46,7 @@ const AppendersServicePage: React.FC = ({ lang }: { lang: Language }) error: errorEvents, updatePagination: updatePaginationEvents, refetch: refetchEvents - } = useApiData('/api/events'); + } = useApiData('/api/events'); const { data: dataAppenders, pagination: paginationAppenders, @@ -47,18 +54,18 @@ const AppendersServicePage: React.FC = ({ lang }: { lang: Language }) error: errorAppenders, updatePagination: updatePaginationAppenders, refetch: refetchAppenders - } = useApiData('/api/appenders'); + } = useApiData('/api/appenders'); const [mode, setMode] = useState("list"); const [gridCols, setGridCols] = useState(3); const [selectedItemServices, setSelectedItemServices] = useState(null); - const [selectedItemEvents, setSelectedItemEvents] = useState(null); - const [selectedItemAppenders, setSelectedItemAppenders] = useState(null); + const [selectedItemEvents, setSelectedItemEvents] = useState(null); + const [selectedItemAppenders, setSelectedItemAppenders] = useState(null); const [fieldDefinitionsServices, setFieldDefinitionsServices] = useState(null); - const [fieldDefinitionsEvents, setFieldDefinitionsEvents] = useState(null); - const [fieldDefinitionsAppenders, setFieldDefinitionsAppenders] = useState(null); + const [fieldDefinitionsEvents, setFieldDefinitionsEvents] = useState(null); + const [fieldDefinitionsAppenders, setFieldDefinitionsAppenders] = useState(null); const [validationSchemaServices, setValidationSchemaServices] = useState(null); const [validationSchemaEvents, setValidationSchemaEvents] = useState(null); @@ -69,23 +76,127 @@ const AppendersServicePage: React.FC = ({ lang }: { lang: Language }) useEffect(() => { setFieldDefinitionsServices(serviceViewFieldDefinitions); setValidationSchemaServices(ServiceSchema); - setFieldDefinitionsEvents(schema.viewFieldDefinitions); setValidationSchemaEvents(schema.ViewApplicationSchema); - setFieldDefinitionsAppenders(schema.viewFieldDefinitions); setValidationSchemaAppenders(schema.ViewApplicationSchema); + setFieldDefinitionsEvents(eventFlatFieldDefinitions); setValidationSchemaEvents(EventSchema); + setFieldDefinitionsAppenders(eventFlatFieldDefinitions); setValidationSchemaAppenders(EventSchema); }, [lang]); - const handleQueryChange = (key: string, value: string | null) => { + useEffect(() => { + if (selectedItemServices && selectedItemServices?.uu_id) { + updatePaginationAppenders({ + page: paginationAppenders.page, + size: paginationAppenders.size, + orderField: paginationAppenders.orderField, + orderType: paginationAppenders.orderType, + query: { + ...paginationAppenders.query, + service_uu_id: selectedItemServices.uu_id + } + }); + + updatePaginationEvents({ + page: paginationEvents.page, + size: paginationEvents.size, + orderField: paginationEvents.orderField, + orderType: paginationEvents.orderType, + query: { + ...paginationEvents.query, + service_uu_id: selectedItemServices.uu_id + } + }); + } + }, [selectedItemServices]); + + const handleQueryChangeServices = (key: string, value: string | null) => { const newQuery = { ...paginationServices.query }; if (value === null) { delete newQuery[key] } else if (value.trim() === "") { delete newQuery[key] } else { newQuery[key] = value } updatePaginationServices({ page: 1, query: newQuery }) }; + + const handleQueryChangeEvents = (key: string, value: string | null) => { + const newQuery = { ...paginationEvents.query }; + if (value === null) { delete newQuery[key] } else if (value.trim() === "") { delete newQuery[key] } else { newQuery[key] = value } + + if (selectedItemServices?.uu_id) { + newQuery.service_uu_id = selectedItemServices.uu_id; + } + updatePaginationEvents({ page: 1, query: newQuery }); + }; + + const handleQueryChangeAppenders = (key: string, value: string | null) => { + const newQuery = { ...paginationAppenders.query }; + if (value === null) { delete newQuery[key] } else if (value.trim() === "") { delete newQuery[key] } else { newQuery[key] = value } + if (selectedItemServices?.uu_id) { + newQuery.service_uu_id = selectedItemServices.uu_id; + } + updatePaginationAppenders({ page: 1, query: newQuery }); + }; + + const appendServiceEvent = async (event: EventData) => { + if (!selectedItemServices?.uu_id) { + throw new Error("Service not selected"); + } + const payload = { event_uu_id: event.uu_id, service_uu_id: selectedItemServices.uu_id } + fetch('/api/appenders/create', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }).then((res) => { + if (res.ok) { + updatePaginationAppenders({ + ...paginationAppenders, + query: { + ...paginationAppenders.query, + service_uu_id: selectedItemServices.uu_id + } + }); + updatePaginationEvents({ + ...paginationEvents, + query: { + ...paginationEvents.query, + service_uu_id: selectedItemServices.uu_id + } + }); + } + }) + } + + const removeServiceEvent = async (event: EventData) => { + if (!selectedItemServices?.uu_id) { + throw new Error("Service not selected"); + } + const payload = { event_uu_id: event.uu_id, service_uu_id: selectedItemServices.uu_id } + fetch('/api/appenders/delete', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }).then((res) => { + if (res.ok) { + updatePaginationAppenders({ + ...paginationAppenders, + query: { + ...paginationAppenders.query, + service_uu_id: selectedItemServices.uu_id + } + }); + updatePaginationEvents({ + ...paginationEvents, + query: { + ...paginationEvents.query, + service_uu_id: selectedItemServices.uu_id + } + }); + } + }) + } + const handleServicesCardClick = (item: ServiceData) => { setSelectedItemServices(item); setMode("list"); }; const handleServicesViewClick = (item: ServiceData) => { setSelectedItemServices(item); setMode("view"); }; - const handleEventsCardClick = (item: schema.ApplicationData) => { console.log("Events Card clicked:", item) }; - const handleEventsViewClick = (item: schema.ApplicationData) => { setSelectedItemEvents(item); setMode("view"); }; + const handleEventsCardClick = (item: EventData) => { appendServiceEvent(item); }; + const handleEventsViewClick = (item: EventData) => { setSelectedItemEvents(item); setMode("view"); }; - const handleAppendersCardClick = (item: schema.ApplicationData) => { console.log("Appenders Card clicked:", item) }; - const handleAppendersViewClick = (item: schema.ApplicationData) => { setSelectedItemAppenders(item); setMode("view"); }; + const handleAppendersCardClick = (item: EventData) => { removeServiceEvent(item); }; + const handleAppendersViewClick = (item: EventData) => { setSelectedItemAppenders(item); setMode("view"); }; const cancelAllSelections = () => { setMode("list"); @@ -103,7 +214,8 @@ const AppendersServicePage: React.FC = ({ lang }: { lang: Language }) showFields: showFieldsServices, gridCols: gridCols, titleField: "service_name", - handleQueryChange: handleQueryChange, + size: "lg" as CardSize, + handleQueryChange: handleQueryChangeServices, updatePagination: updatePaginationServices, handleCardClick: handleServicesCardClick, handleViewClick: handleServicesViewClick @@ -118,7 +230,8 @@ const AppendersServicePage: React.FC = ({ lang }: { lang: Language }) showFields: showFieldsEvents, gridCols: 1, titleField: "description", - handleQueryChange: handleQueryChange, + size: "sm" as CardSize, + handleQueryChange: handleQueryChangeEvents, updatePagination: updatePaginationEvents, handleCardClick: handleEventsCardClick, handleViewClick: handleEventsViewClick @@ -133,7 +246,8 @@ const AppendersServicePage: React.FC = ({ lang }: { lang: Language }) showFields: showFieldsEvents, gridCols: 1, titleField: "description", - handleQueryChange: handleQueryChange, + size: "sm" as CardSize, + handleQueryChange: handleQueryChangeAppenders, updatePagination: updatePaginationAppenders, handleCardClick: handleAppendersCardClick, handleViewClick: handleAppendersViewClick @@ -152,7 +266,7 @@ const AppendersServicePage: React.FC = ({ lang }: { lang: Language }) formProps: { fieldDefinitions: fieldDefinitionsServices, validationSchema: validationSchemaServices, - fieldsByMode: schema.fieldsByMode + fieldsByMode: serviceFieldsByMode } }; @@ -169,14 +283,10 @@ const AppendersServicePage: React.FC = ({ lang }: { lang: Language }) formProps: { fieldDefinitions: fieldDefinitionsEvents, validationSchema: validationSchemaEvents, - fieldsByMode: schema.fieldsByMode + fieldsByMode: eventFieldsByMode, } }; - const removeAppendersFromEvents = (events: schema.ApplicationData[], appenders: schema.ApplicationData[]) => { - - } - const appendersFormProps = { initialData: selectedItemAppenders || undefined, mode: mode, @@ -190,7 +300,7 @@ const AppendersServicePage: React.FC = ({ lang }: { lang: Language }) formProps: { fieldDefinitions: fieldDefinitionsAppenders, validationSchema: validationSchemaAppenders, - fieldsByMode: schema.fieldsByMode + fieldsByMode: eventFieldsByMode, } }; @@ -202,25 +312,26 @@ const AppendersServicePage: React.FC = ({ lang }: { lang: Language })
{mode === "list" ? (
- {!selectedItemServices ?
: -
- - - {translations[lang].serviceSelectedTitle} - {selectedItemServices?.service_name}{" "}{translations[lang].serviceSelectedContent} - -
-
-
+ { + !selectedItemServices ?
: +
+ + + {translations[lang].serviceSelectedTitle} + {selectedItemServices.uu_id}{" "}{selectedItemServices?.service_name}{" "}{translations[lang].serviceSelectedContent} + +
+
+
+
-
}
) : (
{selectedItemServices && {...serviceFormProps} />} - {selectedItemEvents && {...eventsFormProps} />} - {selectedItemAppenders && {...appendersFormProps} />} + {selectedItemEvents && {...eventsFormProps} />} + {selectedItemAppenders && {...appendersFormProps} />}
)}
diff --git a/WebServices/management-frontend/src/eventRouters/appendersService/schemaList/appenders.ts b/WebServices/management-frontend/src/eventRouters/appendersService/schemaList/appenders.ts index b343b68..0d6c11e 100644 --- a/WebServices/management-frontend/src/eventRouters/appendersService/schemaList/appenders.ts +++ b/WebServices/management-frontend/src/eventRouters/appendersService/schemaList/appenders.ts @@ -161,6 +161,9 @@ const appenderFlatFieldDefinitions = flattenFieldDefinitions( serviceBaseFieldDefinitions ); type AppenderFieldDefinitionsType = typeof appenderFlatFieldDefinitions; +const appenderFieldsByMode = { + view: Object.keys(serviceBaseFieldDefinitions), +}; export type { AppendersData, AppenderFieldDefinitionsType }; export { @@ -168,4 +171,5 @@ export { appenderFlatFieldDefinitions, AppenderBaseTranslationEn, AppenderBaseTranslationTr, + appenderFieldsByMode, }; diff --git a/WebServices/management-frontend/src/eventRouters/appendersService/schemaList/events.ts b/WebServices/management-frontend/src/eventRouters/appendersService/schemaList/events.ts index e69de29..b18f50b 100644 --- a/WebServices/management-frontend/src/eventRouters/appendersService/schemaList/events.ts +++ b/WebServices/management-frontend/src/eventRouters/appendersService/schemaList/events.ts @@ -0,0 +1,270 @@ +import { z } from "zod"; +import { flattenFieldDefinitions } from "@/eventRouters/schemas/zodSchemas"; + +interface EventData { + uu_id: string; + function_code: string; + function_class: string; + description?: string; + property_description?: string; + marketing_layer?: string; + cost?: number; + unit_price?: number; + endpoint_code: string; + endpoint_uu_id: string; + is_confirmed: boolean; + active: boolean; + deleted?: boolean; + created_at?: string; + updated_at?: string; +} + +const errorMessages = { + en: { + function_code: "Function code is required", + function_class: "Function class is required", + endpoint_code: "Endpoint code is required", + endpoint_uu_id: "Endpoint UUID is required", + }, + tr: { + function_code: "Fonksiyon kodu gereklidir", + function_class: "Fonksiyon sınıfı gereklidir", + endpoint_code: "Endpoint kodu gereklidir", + endpoint_uu_id: "Endpoint UUID gereklidir", + }, +}; + +const getEventBaseSchema = (lang: "en" | "tr" = "en") => + z.object({ + uu_id: z.string().optional(), + function_code: z.string().min(1, errorMessages[lang].function_code), + function_class: z.string().min(1, errorMessages[lang].function_class), + description: z.string().optional(), + property_description: z.string().optional(), + marketing_layer: z.string().optional(), + cost: z.number().optional(), + unit_price: z.number().optional(), + endpoint_code: z.string().min(1, errorMessages[lang].endpoint_code), + endpoint_uu_id: z.string().min(1, errorMessages[lang].endpoint_uu_id), + is_confirmed: z.boolean().default(false), + active: z.boolean().default(true), + deleted: z.boolean().default(false), + created_at: z.string().optional(), + updated_at: z.string().optional(), + }); + +const EventBaseSchema = getEventBaseSchema("en"); + +const EventBaseTranslationTr = { + uu_id: "UUID", + function_code: "Fonksiyon kodu", + function_class: "Fonksiyon sınıfı", + description: "Açıklama", + property_description: "Özellik açıklama", + marketing_layer: "Pazarlama katmanı", + cost: "Maliyet", + unit_price: "Birim fiyatı", + endpoint_code: "Endpoint kodu", + endpoint_uu_id: "Endpoint UUID", + is_confirmed: "Onaylandı", + active: "Active", + deleted: "Deleted", + created_at: "Created At", + updated_at: "Updated At", +}; + +const EventBaseTranslationEn = { + uu_id: "UUID", + function_code: "Function code", + function_class: "Function class", + description: "Description", + property_description: "Property description", + marketing_layer: "Marketing layer", + cost: "Cost", + unit_price: "Unit price", + endpoint_code: "Endpoint code", + endpoint_uu_id: "Endpoint UUID", + is_confirmed: "Confirmed", + active: "Active", + deleted: "Deleted", + created_at: "Created At", + updated_at: "Updated At", +}; + +const eventBaseFieldDefinitions = { + identificationInfo: { + title: "Event Information", + order: 1, + fields: { + uu_id: { + type: "text", + label: { + tr: EventBaseTranslationTr.uu_id, + en: EventBaseTranslationEn.uu_id, + }, + readOnly: true, + required: false, + }, + function_code: { + type: "text", + label: { + tr: EventBaseTranslationTr.function_code, + en: EventBaseTranslationEn.function_code, + }, + readOnly: false, + required: true, + }, + function_class: { + type: "text", + label: { + tr: EventBaseTranslationTr.function_class, + en: EventBaseTranslationEn.function_class, + }, + readOnly: false, + required: true, + }, + description: { + type: "text", + label: { + tr: EventBaseTranslationTr.description, + en: EventBaseTranslationEn.description, + }, + readOnly: false, + required: false, + }, + property_description: { + type: "text", + label: { + tr: EventBaseTranslationTr.property_description, + en: EventBaseTranslationEn.property_description, + }, + readOnly: false, + required: false, + }, + marketing_layer: { + type: "text", + label: { + tr: EventBaseTranslationTr.marketing_layer, + en: EventBaseTranslationEn.marketing_layer, + }, + readOnly: false, + required: false, + }, + cost: { + type: "number", + label: { + tr: EventBaseTranslationTr.cost, + en: EventBaseTranslationEn.cost, + }, + readOnly: false, + required: false, + }, + unit_price: { + type: "number", + label: { + tr: EventBaseTranslationTr.unit_price, + en: EventBaseTranslationEn.unit_price, + }, + readOnly: false, + required: false, + }, + endpoint_code: { + type: "text", + label: { + tr: EventBaseTranslationTr.endpoint_code, + en: EventBaseTranslationEn.endpoint_code, + }, + readOnly: false, + required: true, + }, + endpoint_uu_id: { + type: "text", + label: { + tr: EventBaseTranslationTr.endpoint_uu_id, + en: EventBaseTranslationEn.endpoint_uu_id, + }, + readOnly: false, + required: true, + }, + }, + }, + + statusInfo: { + title: "Status Information", + order: 3, + fields: { + active: { + type: "checkbox", + label: { + tr: EventBaseTranslationTr.active, + en: EventBaseTranslationEn.active, + }, + readOnly: false, + required: false, + defaultValue: true, + }, + deleted: { + type: "checkbox", + label: { + tr: EventBaseTranslationTr.deleted, + en: EventBaseTranslationEn.deleted, + }, + readOnly: true, + required: false, + defaultValue: false, + }, + is_confirmed: { + type: "checkbox", + label: { + tr: EventBaseTranslationTr.is_confirmed, + en: EventBaseTranslationEn.is_confirmed, + }, + readOnly: false, + required: true, + }, + }, + }, + + systemInfo: { + title: "System Information", + order: 4, + fields: { + created_at: { + type: "date", + label: { + tr: EventBaseTranslationTr.created_at, + en: EventBaseTranslationEn.created_at, + }, + readOnly: true, + required: false, + }, + updated_at: { + type: "date", + label: { + tr: EventBaseTranslationTr.updated_at, + en: EventBaseTranslationEn.updated_at, + }, + readOnly: true, + required: false, + }, + }, + }, +}; +const ViewEventSchema = EventBaseSchema; +const EventSchema = EventBaseSchema; +const eventFlatFieldDefinitions = flattenFieldDefinitions( + eventBaseFieldDefinitions +); +type EventFieldDefinitionsType = typeof eventFlatFieldDefinitions; +const eventFieldsByMode = { + view: Object.keys(eventBaseFieldDefinitions), +}; + +export type { EventData, EventFieldDefinitionsType }; +export { + EventSchema, + eventFlatFieldDefinitions, + EventBaseTranslationEn, + EventBaseTranslationTr, + eventFieldsByMode, +}; diff --git a/WebServices/management-frontend/src/eventRouters/appendersService/schemaList/services.ts b/WebServices/management-frontend/src/eventRouters/appendersService/schemaList/services.ts index fc67f7d..7de98c8 100644 --- a/WebServices/management-frontend/src/eventRouters/appendersService/schemaList/services.ts +++ b/WebServices/management-frontend/src/eventRouters/appendersService/schemaList/services.ts @@ -291,4 +291,5 @@ export { serviceViewFieldDefinitions, ServiceBaseTranslationEn, ServiceBaseTranslationTr, + serviceFieldsByMode, }; diff --git a/WebServices/management-frontend/src/eventRouters/appendersService/type.ts b/WebServices/management-frontend/src/eventRouters/appendersService/type.ts index 7abb743..943139d 100644 --- a/WebServices/management-frontend/src/eventRouters/appendersService/type.ts +++ b/WebServices/management-frontend/src/eventRouters/appendersService/type.ts @@ -1,24 +1,10 @@ import { Language } from "@/components/common/schemas"; import { GridSize } from "@/components/common/HeaderSelections/GridSelectionComponent"; -import * as schema from "./schemaList/schema"; +import { CardSize } from "@/components/common/CardDisplay/schema"; import * as schemaServices from "./schemaList/services"; +import * as schemaEvents from "./schemaList/events"; -export interface ListComponentApplicationProps { - lang: Language; - loading: boolean; - error: any; - data: schema.ApplicationData[]; - pagination: any; - showFields: string[]; - gridCols: GridSize | number; - titleField: string; - handleQueryChange: (key: string, value: string | null) => void; - updatePagination: (pagination: any) => void; - handleCardClick: (item: schema.ApplicationData) => void; - handleViewClick: (item: schema.ApplicationData) => void; -} - -export interface ListComponentServiceProps { +export interface ListComponentServicesProps { lang: Language; loading: boolean; error: any; @@ -27,8 +13,25 @@ export interface ListComponentServiceProps { showFields: string[]; gridCols: GridSize | number; titleField: string; + size: CardSize; handleQueryChange: (key: string, value: string | null) => void; updatePagination: (pagination: any) => void; handleCardClick: (item: schemaServices.ServiceData) => void; handleViewClick: (item: schemaServices.ServiceData) => void; } + +export interface ListComponentEventsProps { + lang: Language; + loading: boolean; + error: any; + data: schemaEvents.EventData[]; + pagination: any; + showFields: string[]; + gridCols: GridSize | number; + titleField: string; + size: CardSize; + handleQueryChange: (key: string, value: string | null) => void; + updatePagination: (pagination: any) => void; + handleCardClick: (item: schemaEvents.EventData) => void; + handleViewClick: (item: schemaEvents.EventData) => void; +} diff --git a/docker-compose.yml b/docker-compose.yml index 12f5699..65a2b62 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -152,17 +152,17 @@ services: # mem_limit: 512m # cpus: 0.5 - # dealer_service: - # container_name: dealer_service - # build: - # context: . - # dockerfile: ApiServices/DealerService/Dockerfile - # networks: - # - wag-services - # env_file: - # - api_env.env - # mem_limit: 512m - # cpus: 0.5 + dealer_service: + container_name: dealer_service + build: + context: . + dockerfile: ApiServices/DealerService/Dockerfile + networks: + - wag-services + env_file: + - api_env.env + mem_limit: 512m + cpus: 0.5 networks: wag-services: