diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml index 8788a47f..aaab527f 100644 --- a/.github/workflows/frontend-tests.yml +++ b/.github/workflows/frontend-tests.yml @@ -27,5 +27,8 @@ jobs: - name: Run linter working-directory: web run: npm run lint + - name: Check translation keys + working-directory: web + run: npm run check-translations diff --git a/backend/api/decorators/filter_sort.py b/backend/api/decorators/filter_sort.py index a004e645..7545f8ad 100644 --- a/backend/api/decorators/filter_sort.py +++ b/backend/api/decorators/filter_sort.py @@ -15,11 +15,43 @@ from database import models from database.models.base import Base from sqlalchemy import Select, and_, func, or_, select +from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import aliased T = TypeVar("T") +async def get_property_field_types( + db: AsyncSession, + filtering: list[FilterInput] | None, + sorting: list[SortInput] | None, +) -> dict[str, str]: + property_def_ids: set[str] = set() + if filtering: + for f in filtering: + if ( + f.column_type == ColumnType.PROPERTY + and f.property_definition_id + ): + property_def_ids.add(f.property_definition_id) + if sorting: + for s in sorting: + if ( + s.column_type == ColumnType.PROPERTY + and s.property_definition_id + ): + property_def_ids.add(s.property_definition_id) + if not property_def_ids: + return {} + result = await db.execute( + select(models.PropertyDefinition).where( + models.PropertyDefinition.id.in_(property_def_ids) + ) + ) + prop_defs = result.scalars().all() + return {str(p.id): p.field_type for p in prop_defs} + + def detect_entity_type(model_class: type[Base]) -> str | None: if model_class == models.Patient: return "patient" diff --git a/backend/api/errors.py b/backend/api/errors.py new file mode 100644 index 00000000..51fb4712 --- /dev/null +++ b/backend/api/errors.py @@ -0,0 +1,13 @@ +from graphql import GraphQLError + +FORBIDDEN_MESSAGE = ( + "Insufficient permission. Please contact an administrator " + "if you believe this is an error." +) + + +def raise_forbidden(message: str | None = None) -> None: + raise GraphQLError( + message or FORBIDDEN_MESSAGE, + extensions={"code": "FORBIDDEN"}, + ) diff --git a/backend/api/resolvers/location.py b/backend/api/resolvers/location.py index 14894d3e..da246621 100644 --- a/backend/api/resolvers/location.py +++ b/backend/api/resolvers/location.py @@ -4,6 +4,7 @@ from api.audit import audit_log from api.context import Info from api.decorators.pagination import apply_pagination +from api.errors import raise_forbidden from api.inputs import CreateLocationNodeInput, LocationType, UpdateLocationNodeInput from api.resolvers.base import BaseMutationResolver, BaseSubscriptionResolver from api.services.authorization import AuthorizationService @@ -52,10 +53,7 @@ async def location_node( info.context.user, info.context ) if location.id not in accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() return location @@ -83,10 +81,7 @@ async def location_nodes( if recursive and parent_id: if parent_id not in accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() cte = ( select(models.LocationNode) @@ -106,10 +101,7 @@ async def location_nodes( ) if parent_id: if parent_id not in accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() query = query.where(models.LocationNode.parent_id == parent_id) if kind: @@ -143,16 +135,10 @@ async def create_location_node( ) if not accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() if data.parent_id and data.parent_id not in accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() location = models.LocationNode( title=data.title, @@ -180,10 +166,7 @@ async def update_location_node( ) if not accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() result = await db.execute( select(models.LocationNode).where(models.LocationNode.id == id) @@ -197,16 +180,10 @@ async def update_location_node( ) if location.id not in accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() if data.parent_id is not None and data.parent_id not in accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() if data.title is not None: location.title = data.title @@ -230,10 +207,7 @@ async def delete_location_node(self, info: Info, id: strawberry.ID) -> bool: ) if not accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() result = await db.execute( select(models.LocationNode).where(models.LocationNode.id == id) @@ -247,10 +221,7 @@ async def delete_location_node(self, info: Info, id: strawberry.ID) -> bool: ) if location.id not in accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() await BaseMutationResolver.delete_entity( info, location, models.LocationNode, "location_node" diff --git a/backend/api/resolvers/patient.py b/backend/api/resolvers/patient.py index cb018e20..c808c31b 100644 --- a/backend/api/resolvers/patient.py +++ b/backend/api/resolvers/patient.py @@ -7,13 +7,13 @@ apply_filtering, apply_sorting, filtered_and_sorted_query, + get_property_field_types, ) from api.decorators.full_text_search import ( apply_full_text_search, full_text_search_query, ) from api.inputs import ( - ColumnType, FilterInput, FullTextSearchInput, PaginationInput, @@ -27,8 +27,8 @@ from api.services.notifications import notify_entity_deleted from api.services.property import PropertyService from api.types.patient import PatientType +from api.errors import raise_forbidden from database import models -from graphql import GraphQLError from sqlalchemy import func, select from sqlalchemy.orm import aliased, selectinload from sqlalchemy.sql import Select @@ -73,13 +73,7 @@ async def _build_patients_base_query( filter_cte = None if location_node_id: if location_node_id not in accessible_location_ids: - raise GraphQLError( - ( - "Insufficient permission. Please contact an administrator " - "if you believe this is an error." - ), - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() filter_cte = ( select(models.LocationNode.id) .where(models.LocationNode.id == location_node_id) @@ -172,13 +166,7 @@ async def patient( if not await auth_service.can_access_patient( info.context.user, patient, info.context ): - raise GraphQLError( - ( - "Insufficient permission. Please contact an administrator " - "if you believe this is an error." - ), - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() return patient @strawberry.field @@ -218,54 +206,17 @@ async def patientsTotal( if search and search is not strawberry.UNSET: query = apply_full_text_search(query, search, models.Patient) + property_field_types = await get_property_field_types( + info.context.db, filtering, sorting + ) if filtering: - property_field_types: dict[str, str] = {} - property_def_ids = set() - for f in filtering: - if ( - f.column_type == ColumnType.PROPERTY - and f.property_definition_id - ): - property_def_ids.add(f.property_definition_id) - - if property_def_ids: - prop_defs_result = await info.context.db.execute( - select(models.PropertyDefinition).where( - models.PropertyDefinition.id.in_(property_def_ids) - ) - ) - prop_defs = prop_defs_result.scalars().all() - property_field_types = { - str(prop_def.id): prop_def.field_type - for prop_def in prop_defs - } - query = apply_filtering( query, filtering, models.Patient, property_field_types ) - if sorting: - property_field_types: dict[str, str] = {} - property_def_ids = set() - for s in sorting: - if ( - s.column_type == ColumnType.PROPERTY - and s.property_definition_id - ): - property_def_ids.add(s.property_definition_id) - - if property_def_ids: - prop_defs_result = await info.context.db.execute( - select(models.PropertyDefinition).where( - models.PropertyDefinition.id.in_(property_def_ids) - ) - ) - prop_defs = prop_defs_result.scalars().all() - property_field_types = { - str(prop_def.id): prop_def.field_type for prop_def in prop_defs - } - - query = apply_sorting(query, sorting, models.Patient, property_field_types) + query = apply_sorting( + query, sorting, models.Patient, property_field_types + ) subquery = query.subquery() count_query = select(func.count(func.distinct(subquery.c.id))) @@ -363,54 +314,17 @@ async def recentPatientsTotal( if search and search is not strawberry.UNSET: query = apply_full_text_search(query, search, models.Patient) + property_field_types = await get_property_field_types( + info.context.db, filtering, sorting + ) if filtering: - property_field_types: dict[str, str] = {} - property_def_ids = set() - for f in filtering: - if ( - f.column_type == ColumnType.PROPERTY - and f.property_definition_id - ): - property_def_ids.add(f.property_definition_id) - - if property_def_ids: - prop_defs_result = await info.context.db.execute( - select(models.PropertyDefinition).where( - models.PropertyDefinition.id.in_(property_def_ids) - ) - ) - prop_defs = prop_defs_result.scalars().all() - property_field_types = { - str(prop_def.id): prop_def.field_type - for prop_def in prop_defs - } - query = apply_filtering( query, filtering, models.Patient, property_field_types ) - if sorting: - property_field_types: dict[str, str] = {} - property_def_ids = set() - for s in sorting: - if ( - s.column_type == ColumnType.PROPERTY - and s.property_definition_id - ): - property_def_ids.add(s.property_definition_id) - - if property_def_ids: - prop_defs_result = await info.context.db.execute( - select(models.PropertyDefinition).where( - models.PropertyDefinition.id.in_(property_def_ids) - ) - ) - prop_defs = prop_defs_result.scalars().all() - property_field_types = { - str(prop_def.id): prop_def.field_type for prop_def in prop_defs - } - - query = apply_sorting(query, sorting, models.Patient, property_field_types) + query = apply_sorting( + query, sorting, models.Patient, property_field_types + ) subquery = query.subquery() count_query = select(func.count(func.distinct(subquery.c.id))) @@ -449,44 +363,23 @@ async def create_patient( ) if not accessible_location_ids: - raise GraphQLError( - ( - "Insufficient permission. Please contact an " - "administrator if you believe this is an error." - ), - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() if data.clinic_id not in accessible_location_ids: - raise GraphQLError( - ( - "Insufficient permission. Please contact an administrator " - "if you believe this is an error." - ), - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() await location_service.validate_and_get_clinic(data.clinic_id) if data.position_id: if data.position_id not in accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() await location_service.validate_and_get_position(data.position_id) teams = [] if data.team_ids: for team_id in data.team_ids: if team_id not in accessible_location_ids: - raise GraphQLError( - ( - "Insufficient permission. Please contact an " - "administrator if you believe this is an error." - ), - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() teams = await location_service.validate_and_get_teams( data.team_ids ) @@ -509,10 +402,7 @@ async def create_patient( if data.assigned_location_ids: for loc_id in data.assigned_location_ids: if loc_id not in accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() locations = await location_service.get_locations_by_ids( data.assigned_location_ids ) @@ -522,13 +412,7 @@ async def create_patient( data.assigned_location_id not in accessible_location_ids ): - raise GraphQLError( - ( - "Insufficient permission. Please contact an " - "administrator if you believe this is an error." - ), - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() location = await location_service.get_location_by_id( data.assigned_location_id ) @@ -573,10 +457,7 @@ async def update_patient( if not await auth_service.can_access_patient( info.context.user, patient, info.context ): - raise GraphQLError( - "Forbidden: You do not have access to this patient", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() if data.checksum: validate_checksum(patient, data.checksum, "Patient") @@ -601,10 +482,7 @@ async def update_patient( if data.clinic_id is not None: if data.clinic_id not in accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() await location_service.validate_and_get_clinic(data.clinic_id) patient.clinic_id = data.clinic_id @@ -613,13 +491,7 @@ async def update_patient( patient.position_id = None else: if data.position_id not in accessible_location_ids: - raise GraphQLError( - ( - "Insufficient permission. Please contact an " - "administrator if you believe this is an error." - ), - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() await location_service.validate_and_get_position( data.position_id ) @@ -631,10 +503,7 @@ async def update_patient( else: for team_id in data.team_ids: if team_id not in accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() patient.teams = await location_service.validate_and_get_teams( data.team_ids ) @@ -642,10 +511,7 @@ async def update_patient( if data.assigned_location_ids is not None: for loc_id in data.assigned_location_ids: if loc_id not in accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() locations = await location_service.get_locations_by_ids( data.assigned_location_ids ) @@ -655,13 +521,7 @@ async def update_patient( data.assigned_location_id not in accessible_location_ids ): - raise GraphQLError( - ( - "Insufficient permission. Please contact an " - "administrator if you believe this is an error." - ), - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() location = await location_service.get_location_by_id( data.assigned_location_id ) @@ -700,10 +560,7 @@ async def delete_patient(self, info: Info, id: strawberry.ID) -> bool: if not await auth_service.can_access_patient( info.context.user, patient, info.context ): - raise GraphQLError( - "Forbidden: You do not have access to this patient", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() patient.deleted = True await BaseMutationResolver.update_and_notify( @@ -736,10 +593,7 @@ async def _update_patient_state( if not await auth_service.can_access_patient( info.context.user, patient, info.context ): - raise GraphQLError( - "Forbidden: You do not have access to this patient", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() patient.state = state.value await BaseMutationResolver.update_and_notify( @@ -788,53 +642,23 @@ async def patient_created( info: Info, root_location_ids: list[strawberry.ID] | None = None, ) -> AsyncGenerator[strawberry.ID, None]: - import logging - from api.services.subscription import ( patient_belongs_to_root_locations, + subscribe_with_location_filter, ) - logger = logging.getLogger(__name__) - root_location_ids_str = ( [str(lid) for lid in root_location_ids] if root_location_ids else None ) - - logger.info( - f"[SUBSCRIPTION] Initializing patient_created subscription: " - f"root_location_ids={root_location_ids_str}" - ) - - async for patient_id in BaseSubscriptionResolver.entity_created( - info, "patient" + base = BaseSubscriptionResolver.entity_created(info, "patient") + async for patient_id in subscribe_with_location_filter( + base, + info.context.db, + root_location_ids_str, + patient_belongs_to_root_locations, ): - logger.info( - f"[SUBSCRIPTION] PatientSubscription received patient_created event: " - f"patient_id={patient_id}, root_location_ids={root_location_ids_str}" - ) - if root_location_ids_str: - belongs = await patient_belongs_to_root_locations( - info.context.db, - str(patient_id), - root_location_ids_str, - ) - if not belongs: - logger.debug( - f"[SUBSCRIPTION] PatientSubscription filtered out patient_created event " - f"(location mismatch): patient_id={patient_id}, " - f"root_location_ids={root_location_ids_str}" - ) - continue - logger.debug( - f"[SUBSCRIPTION] PatientSubscription passed location filter: " - f"patient_id={patient_id}, root_location_ids={root_location_ids_str}" - ) - logger.info( - f"[SUBSCRIPTION] PatientSubscription yielding patient_created event: " - f"patient_id={patient_id}" - ) yield patient_id @strawberry.subscription @@ -844,54 +668,25 @@ async def patient_updated( patient_id: strawberry.ID | None = None, root_location_ids: list[strawberry.ID] | None = None, ) -> AsyncGenerator[strawberry.ID, None]: - import logging - from api.services.subscription import ( patient_belongs_to_root_locations, + subscribe_with_location_filter, ) - logger = logging.getLogger(__name__) - root_location_ids_str = ( [str(lid) for lid in root_location_ids] if root_location_ids else None ) - - logger.info( - f"[SUBSCRIPTION] Initializing patient_updated subscription: " - f"patient_id={patient_id}, root_location_ids={root_location_ids_str}" - ) - - async for updated_id in BaseSubscriptionResolver.entity_updated( + base = BaseSubscriptionResolver.entity_updated( info, "patient", patient_id + ) + async for updated_id in subscribe_with_location_filter( + base, + info.context.db, + root_location_ids_str, + patient_belongs_to_root_locations, ): - logger.info( - f"[SUBSCRIPTION] PatientSubscription received patient_updated event: " - f"updated_id={updated_id}, filter_patient_id={patient_id}, " - f"root_location_ids={root_location_ids_str}" - ) - if root_location_ids_str: - belongs = await patient_belongs_to_root_locations( - info.context.db, - str(updated_id), - root_location_ids_str, - ) - if not belongs: - logger.debug( - f"[SUBSCRIPTION] PatientSubscription filtered out patient_updated event " - f"(location mismatch): updated_id={updated_id}, " - f"root_location_ids={root_location_ids_str}" - ) - continue - logger.debug( - f"[SUBSCRIPTION] PatientSubscription passed location filter: " - f"updated_id={updated_id}, root_location_ids={root_location_ids_str}" - ) - logger.info( - f"[SUBSCRIPTION] PatientSubscription yielding patient_updated event: " - f"updated_id={updated_id}" - ) yield updated_id @strawberry.subscription @@ -901,56 +696,27 @@ async def patient_state_changed( patient_id: strawberry.ID | None = None, root_location_ids: list[strawberry.ID] | None = None, ) -> AsyncGenerator[strawberry.ID, None]: - import logging - from api.services.subscription import ( create_redis_subscription, patient_belongs_to_root_locations, + subscribe_with_location_filter, ) - logger = logging.getLogger(__name__) - root_location_ids_str = ( [str(lid) for lid in root_location_ids] if root_location_ids else None ) - - logger.info( - f"[SUBSCRIPTION] Initializing patient_state_changed subscription: " - f"patient_id={patient_id}, root_location_ids={root_location_ids_str}" - ) - - async for updated_id in create_redis_subscription( + base = create_redis_subscription( "patient_state_changed", str(patient_id) if patient_id else None, + ) + async for updated_id in subscribe_with_location_filter( + base, + info.context.db, + root_location_ids_str, + patient_belongs_to_root_locations, ): - logger.info( - f"[SUBSCRIPTION] PatientSubscription received patient_state_changed event: " - f"updated_id={updated_id}, filter_patient_id={patient_id}, " - f"root_location_ids={root_location_ids_str}" - ) - if root_location_ids_str: - belongs = await patient_belongs_to_root_locations( - info.context.db, - str(updated_id), - root_location_ids_str, - ) - if not belongs: - logger.debug( - f"[SUBSCRIPTION] PatientSubscription filtered out patient_state_changed event " - f"(location mismatch): updated_id={updated_id}, " - f"root_location_ids={root_location_ids_str}" - ) - continue - logger.debug( - f"[SUBSCRIPTION] PatientSubscription passed location filter: " - f"updated_id={updated_id}, root_location_ids={root_location_ids_str}" - ) - logger.info( - f"[SUBSCRIPTION] PatientSubscription yielding patient_state_changed event: " - f"updated_id={updated_id}" - ) yield updated_id @strawberry.subscription @@ -959,51 +725,21 @@ async def patient_deleted( info: Info, root_location_ids: list[strawberry.ID] | None = None, ) -> AsyncGenerator[strawberry.ID, None]: - import logging - from api.services.subscription import ( patient_belongs_to_root_locations, + subscribe_with_location_filter, ) - logger = logging.getLogger(__name__) - root_location_ids_str = ( [str(lid) for lid in root_location_ids] if root_location_ids else None ) - - logger.info( - f"[SUBSCRIPTION] Initializing patient_deleted subscription: " - f"root_location_ids={root_location_ids_str}" - ) - - async for patient_id in BaseSubscriptionResolver.entity_deleted( - info, "patient" + base = BaseSubscriptionResolver.entity_deleted(info, "patient") + async for patient_id in subscribe_with_location_filter( + base, + info.context.db, + root_location_ids_str, + patient_belongs_to_root_locations, ): - logger.info( - f"[SUBSCRIPTION] PatientSubscription received patient_deleted event: " - f"patient_id={patient_id}, root_location_ids={root_location_ids_str}" - ) - if root_location_ids_str: - belongs = await patient_belongs_to_root_locations( - info.context.db, - str(patient_id), - root_location_ids_str, - ) - if not belongs: - logger.debug( - f"[SUBSCRIPTION] PatientSubscription filtered out patient_deleted event " - f"(location mismatch): patient_id={patient_id}, " - f"root_location_ids={root_location_ids_str}" - ) - continue - logger.debug( - f"[SUBSCRIPTION] PatientSubscription passed location filter: " - f"patient_id={patient_id}, root_location_ids={root_location_ids_str}" - ) - logger.info( - f"[SUBSCRIPTION] PatientSubscription yielding patient_deleted event: " - f"patient_id={patient_id}" - ) yield patient_id diff --git a/backend/api/resolvers/task.py b/backend/api/resolvers/task.py index b2bb58f1..7de16120 100644 --- a/backend/api/resolvers/task.py +++ b/backend/api/resolvers/task.py @@ -3,17 +3,18 @@ import strawberry from api.audit import audit_log from api.context import Info +from api.errors import raise_forbidden from api.decorators.filter_sort import ( apply_filtering, apply_sorting, filtered_and_sorted_query, + get_property_field_types, ) from api.decorators.full_text_search import ( apply_full_text_search, full_text_search_query, ) from api.inputs import ( - ColumnType, FilterInput, FullTextSearchInput, PaginationInput, @@ -45,10 +46,7 @@ async def task(self, info: Info, id: strawberry.ID) -> TaskType | None: if task and task.patient: auth_service = AuthorizationService(info.context.db) if not await auth_service.can_access_patient(info.context.user, task.patient, info.context): - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() return task @strawberry.field @@ -70,10 +68,7 @@ async def tasks( if patient_id: if not await auth_service.can_access_patient_id(info.context.user, patient_id, info.context): - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() query = select(models.Task).options( selectinload(models.Task.patient).selectinload(models.Patient.assigned_locations) @@ -110,10 +105,7 @@ async def tasks( if root_location_ids: invalid_ids = [lid for lid in root_location_ids if lid not in accessible_location_ids] if invalid_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() root_cte = ( select(models.LocationNode.id) .where(models.LocationNode.id.in_(root_location_ids)) @@ -129,10 +121,7 @@ async def tasks( team_location_cte = None if assignee_team_id: if assignee_team_id not in accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() team_location_cte = ( select(models.LocationNode.id) .where(models.LocationNode.id == assignee_team_id) @@ -198,10 +187,7 @@ async def tasksTotal( if patient_id: if not await auth_service.can_access_patient_id(info.context.user, patient_id, info.context): - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() query = select(models.Task).where(models.Task.patient_id == patient_id) @@ -234,10 +220,7 @@ async def tasksTotal( if root_location_ids: invalid_ids = [lid for lid in root_location_ids if lid not in accessible_location_ids] if invalid_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() root_cte = ( select(models.LocationNode.id) .where(models.LocationNode.id.in_(root_location_ids)) @@ -253,10 +236,7 @@ async def tasksTotal( team_location_cte = None if assignee_team_id: if assignee_team_id not in accessible_location_ids: - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() team_location_cte = ( select(models.LocationNode.id) .where(models.LocationNode.id == assignee_team_id) @@ -304,45 +284,17 @@ async def tasksTotal( if search and search is not strawberry.UNSET: query = apply_full_text_search(query, search, models.Task) + property_field_types = await get_property_field_types( + info.context.db, filtering, sorting + ) if filtering: - property_field_types: dict[str, str] = {} - property_def_ids = set() - for f in filtering: - if f.column_type == ColumnType.PROPERTY and f.property_definition_id: - property_def_ids.add(f.property_definition_id) - - if property_def_ids: - prop_defs_result = await info.context.db.execute( - select(models.PropertyDefinition).where( - models.PropertyDefinition.id.in_(property_def_ids) - ) - ) - prop_defs = prop_defs_result.scalars().all() - property_field_types = { - str(prop_def.id): prop_def.field_type for prop_def in prop_defs - } - - query = apply_filtering(query, filtering, models.Task, property_field_types) - + query = apply_filtering( + query, filtering, models.Task, property_field_types + ) if sorting: - property_field_types: dict[str, str] = {} - property_def_ids = set() - for s in sorting: - if s.column_type == ColumnType.PROPERTY and s.property_definition_id: - property_def_ids.add(s.property_definition_id) - - if property_def_ids: - prop_defs_result = await info.context.db.execute( - select(models.PropertyDefinition).where( - models.PropertyDefinition.id.in_(property_def_ids) - ) - ) - prop_defs = prop_defs_result.scalars().all() - property_field_types = { - str(prop_def.id): prop_def.field_type for prop_def in prop_defs - } - - query = apply_sorting(query, sorting, models.Task, property_field_types) + query = apply_sorting( + query, sorting, models.Task, property_field_types + ) subquery = query.subquery() count_query = select(func.count(func.distinct(subquery.c.id))) @@ -478,45 +430,17 @@ async def recentTasksTotal( if search and search is not strawberry.UNSET: query = apply_full_text_search(query, search, models.Task) + property_field_types = await get_property_field_types( + info.context.db, filtering, sorting + ) if filtering: - property_field_types: dict[str, str] = {} - property_def_ids = set() - for f in filtering: - if f.column_type == ColumnType.PROPERTY and f.property_definition_id: - property_def_ids.add(f.property_definition_id) - - if property_def_ids: - prop_defs_result = await info.context.db.execute( - select(models.PropertyDefinition).where( - models.PropertyDefinition.id.in_(property_def_ids) - ) - ) - prop_defs = prop_defs_result.scalars().all() - property_field_types = { - str(prop_def.id): prop_def.field_type for prop_def in prop_defs - } - - query = apply_filtering(query, filtering, models.Task, property_field_types) - + query = apply_filtering( + query, filtering, models.Task, property_field_types + ) if sorting: - property_field_types: dict[str, str] = {} - property_def_ids = set() - for s in sorting: - if s.column_type == ColumnType.PROPERTY and s.property_definition_id: - property_def_ids.add(s.property_definition_id) - - if property_def_ids: - prop_defs_result = await info.context.db.execute( - select(models.PropertyDefinition).where( - models.PropertyDefinition.id.in_(property_def_ids) - ) - ) - prop_defs = prop_defs_result.scalars().all() - property_field_types = { - str(prop_def.id): prop_def.field_type for prop_def in prop_defs - } - - query = apply_sorting(query, sorting, models.Task, property_field_types) + query = apply_sorting( + query, sorting, models.Task, property_field_types + ) subquery = query.subquery() count_query = select(func.count(func.distinct(subquery.c.id))) @@ -535,10 +459,7 @@ def _get_property_service(db) -> PropertyService: async def create_task(self, info: Info, data: CreateTaskInput) -> TaskType: auth_service = AuthorizationService(info.context.db) if not await auth_service.can_access_patient_id(info.context.user, data.patient_id, info.context): - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() if data.assignee_id and data.assignee_team_id: raise GraphQLError( @@ -602,10 +523,7 @@ async def update_task( if task.patient: auth_service = AuthorizationService(db) if not await auth_service.can_access_patient(info.context.user, task.patient, info.context): - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() if data.checksum: validate_checksum(task, data.checksum, "Task") @@ -681,10 +599,7 @@ async def _update_task_field( if task.patient: auth_service = AuthorizationService(db) if not await auth_service.can_access_patient(info.context.user, task.patient, info.context): - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() field_updater(task) await BaseMutationResolver.update_and_notify( @@ -793,10 +708,7 @@ async def delete_task(self, info: Info, id: strawberry.ID) -> bool: if task.patient: auth_service = AuthorizationService(db) if not await auth_service.can_access_patient(info.context.user, task.patient, info.context): - raise GraphQLError( - "Insufficient permission. Please contact an administrator if you believe this is an error.", - extensions={"code": "FORBIDDEN"}, - ) + raise_forbidden() patient_id = task.patient_id await BaseMutationResolver.delete_entity( @@ -813,53 +725,23 @@ async def task_created( info: Info, root_location_ids: list[strawberry.ID] | None = None, ) -> AsyncGenerator[strawberry.ID, None]: - import logging - from api.services.subscription import ( + subscribe_with_location_filter, task_belongs_to_root_locations, ) - logger = logging.getLogger(__name__) - root_location_ids_str = ( [str(lid) for lid in root_location_ids] if root_location_ids else None ) - - logger.info( - f"[SUBSCRIPTION] Initializing task_created subscription: " - f"root_location_ids={root_location_ids_str}" - ) - - async for task_id in BaseSubscriptionResolver.entity_created( - info, "task" + base = BaseSubscriptionResolver.entity_created(info, "task") + async for task_id in subscribe_with_location_filter( + base, + info.context.db, + root_location_ids_str, + task_belongs_to_root_locations, ): - logger.info( - f"[SUBSCRIPTION] TaskSubscription received task_created event: " - f"task_id={task_id}, root_location_ids={root_location_ids_str}" - ) - if root_location_ids_str: - belongs = await task_belongs_to_root_locations( - info.context.db, - str(task_id), - root_location_ids_str, - ) - if not belongs: - logger.debug( - f"[SUBSCRIPTION] TaskSubscription filtered out task_created event " - f"(location mismatch): task_id={task_id}, " - f"root_location_ids={root_location_ids_str}" - ) - continue - logger.debug( - f"[SUBSCRIPTION] TaskSubscription passed location filter: " - f"task_id={task_id}, root_location_ids={root_location_ids_str}" - ) - logger.info( - f"[SUBSCRIPTION] TaskSubscription yielding task_created event: " - f"task_id={task_id}" - ) yield task_id @strawberry.subscription @@ -869,54 +751,25 @@ async def task_updated( task_id: strawberry.ID | None = None, root_location_ids: list[strawberry.ID] | None = None, ) -> AsyncGenerator[strawberry.ID, None]: - import logging - from api.services.subscription import ( + subscribe_with_location_filter, task_belongs_to_root_locations, ) - logger = logging.getLogger(__name__) - root_location_ids_str = ( [str(lid) for lid in root_location_ids] if root_location_ids else None ) - - logger.info( - f"[SUBSCRIPTION] Initializing task_updated subscription: " - f"task_id={task_id}, root_location_ids={root_location_ids_str}" - ) - - async for updated_id in BaseSubscriptionResolver.entity_updated( + base = BaseSubscriptionResolver.entity_updated( info, "task", task_id + ) + async for updated_id in subscribe_with_location_filter( + base, + info.context.db, + root_location_ids_str, + task_belongs_to_root_locations, ): - logger.info( - f"[SUBSCRIPTION] TaskSubscription received task_updated event: " - f"updated_id={updated_id}, filter_task_id={task_id}, " - f"root_location_ids={root_location_ids_str}" - ) - if root_location_ids_str: - belongs = await task_belongs_to_root_locations( - info.context.db, - str(updated_id), - root_location_ids_str, - ) - if not belongs: - logger.debug( - f"[SUBSCRIPTION] TaskSubscription filtered out task_updated event " - f"(location mismatch): updated_id={updated_id}, " - f"root_location_ids={root_location_ids_str}" - ) - continue - logger.debug( - f"[SUBSCRIPTION] TaskSubscription passed location filter: " - f"updated_id={updated_id}, root_location_ids={root_location_ids_str}" - ) - logger.info( - f"[SUBSCRIPTION] TaskSubscription yielding task_updated event: " - f"updated_id={updated_id}" - ) yield updated_id @strawberry.subscription @@ -925,51 +778,21 @@ async def task_deleted( info: Info, root_location_ids: list[strawberry.ID] | None = None, ) -> AsyncGenerator[strawberry.ID, None]: - import logging - from api.services.subscription import ( + subscribe_with_location_filter, task_belongs_to_root_locations, ) - logger = logging.getLogger(__name__) - root_location_ids_str = ( [str(lid) for lid in root_location_ids] if root_location_ids else None ) - - logger.info( - f"[SUBSCRIPTION] Initializing task_deleted subscription: " - f"root_location_ids={root_location_ids_str}" - ) - - async for task_id in BaseSubscriptionResolver.entity_deleted( - info, "task" + base = BaseSubscriptionResolver.entity_deleted(info, "task") + async for task_id in subscribe_with_location_filter( + base, + info.context.db, + root_location_ids_str, + task_belongs_to_root_locations, ): - logger.info( - f"[SUBSCRIPTION] TaskSubscription received task_deleted event: " - f"task_id={task_id}, root_location_ids={root_location_ids_str}" - ) - if root_location_ids_str: - belongs = await task_belongs_to_root_locations( - info.context.db, - str(task_id), - root_location_ids_str, - ) - if not belongs: - logger.debug( - f"[SUBSCRIPTION] TaskSubscription filtered out task_deleted event " - f"(location mismatch): task_id={task_id}, " - f"root_location_ids={root_location_ids_str}" - ) - continue - logger.debug( - f"[SUBSCRIPTION] TaskSubscription passed location filter: " - f"task_id={task_id}, root_location_ids={root_location_ids_str}" - ) - logger.info( - f"[SUBSCRIPTION] TaskSubscription yielding task_deleted event: " - f"task_id={task_id}" - ) yield task_id diff --git a/backend/api/services/subscription.py b/backend/api/services/subscription.py index 087b2bde..4a44757e 100644 --- a/backend/api/services/subscription.py +++ b/backend/api/services/subscription.py @@ -1,5 +1,5 @@ import logging -from collections.abc import AsyncGenerator +from collections.abc import AsyncGenerator, Awaitable, Callable from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession @@ -142,3 +142,20 @@ async def task_belongs_to_root_locations( return await patient_belongs_to_root_locations( db, task.patient.id, root_location_ids ) + + +async def subscribe_with_location_filter( + entity_id_iterator: AsyncGenerator[str, None], + db: AsyncSession, + root_location_ids: list[str] | None, + belongs_check: Callable[ + [AsyncSession, str, list[str]], Awaitable[bool] + ], +) -> AsyncGenerator[str, None]: + if not root_location_ids: + async for entity_id in entity_id_iterator: + yield entity_id + return + async for entity_id in entity_id_iterator: + if await belongs_check(db, str(entity_id), root_location_ids): + yield entity_id diff --git a/docs/dev-notes.md b/docs/dev-notes.md new file mode 100644 index 00000000..14913ee3 --- /dev/null +++ b/docs/dev-notes.md @@ -0,0 +1,46 @@ +# Developer notes + +Conventions, patterns, and how to extend. + +## Conventions + +- **No comments**: Code should be self-explanatory; avoid self-explanatory comments. +- **Clean code**: Clear naming, explicit typing, modern syntax, consistent spacing. +- **Atomic components**: Small, reusable pieces; parameters sliced so they can be reused. +- **Utils**: Shared logic lives in `web/utils/` (e.g. `date`, `dueDate`, `priority`, `propertyColumn`); add new helpers there or in a new util file as needed. + +## Patterns + +- **Composition over inheritance**: Prefer composing small hooks and components rather than deep class hierarchies. +- **Shared hooks**: Table state and property column visibility are centralized in `useTableState` and `usePropertyColumnVisibility`; paginated lists use `usePaginatedEntityQuery`. +- **Backend helpers**: `get_property_field_types` for Total resolvers; `subscribe_with_location_filter` for location-scoped subscriptions; `raise_forbidden()` for 403 responses. + +## Commands + +After changing GraphQL or translations, run: + +- **GraphQL**: `npm run generate-graphql` (in `web/`) after editing `*.graphql` files. +- **Translations**: `npm run build-intl` (in `web/`) after editing `*.arb` files. + +## How to extend + +### New entity (e.g. “Resource”) + +1. **Backend**: Add model in `database/models/`, migration, type in `api/types/`, inputs in `api/inputs.py`, resolvers in `api/resolvers/`. Use `BaseMutationResolver` and notification helpers; add `get_property_field_types` in any Total resolver that filters/sorts by property columns. +2. **Web**: Add GraphQL operations in `web/api/`, run `generate-graphql`. Add data hooks (e.g. `useResources`, `useResourcesPaginated` if needed) and any table/detail components. Use `useTableState` and `usePropertyColumnVisibility` for tables with property columns. + +### New table with filters/sort/pagination + +1. Use `useTableState(prefix)` for persisted state. +2. Use `usePropertyColumnVisibility` if the table has dynamic property columns. +3. Use `getPropertyColumnsForEntity` for property column definitions. +4. For server-paginated data, use `usePaginatedEntityQuery` or a wrapper like `usePatientsPaginated`. + +### New subscription with location filter + +1. Backend: Expose a subscription that gets a base iterator (e.g. `BaseSubscriptionResolver.entity_updated(info, "entity_name", entity_id)`) and passes it to `subscribe_with_location_filter(..., info.context.db, root_location_ids_str, entity_belongs_to_root_locations)`. +2. Ensure an `entity_belongs_to_root_locations`-style function exists for the entity (or reuse patient/task logic if the entity is tied to them). + +### Adding queries and mutations (web) + +See [web/docs/adding-queries-and-mutations.md](../web/docs/adding-queries-and-mutations.md) for the workflow (GraphQL doc, codegen, hooks). diff --git a/docs/how-it-works.md b/docs/how-it-works.md new file mode 100644 index 00000000..621384bd --- /dev/null +++ b/docs/how-it-works.md @@ -0,0 +1,37 @@ +# How it works + +Core flows and main abstractions. + +## Auth + +- Frontend redirects to Keycloak for login; receives JWT. +- Apollo link sends `Authorization: Bearer ` on every request. +- Backend `auth.py` validates the token and attaches the user to the request context. +- Resolvers use `info.context.user` and `AuthorizationService` to enforce access. + +## Patient and task CRUD + +- **Mutations** load the entity, check location access via `AuthorizationService.can_access_patient` (or equivalent), then create/update/delete and call `BaseMutationResolver.create_and_notify` / `update_and_notify` / `delete_entity`. +- **Notifications** publish entity IDs to Redis channels (`patient_created`, `task_updated`, etc.); the web app subscribes and refetches or merges into the cache. +- **Location filtering**: List resolvers (e.g. `patients`, `tasks`) call `get_user_accessible_location_ids` and restrict by clinic/position/assigned locations/teams via CTEs and joins. + +## Filter, sort, pagination + +- List fields use decorators `@filtered_and_sorted_query()` and `@full_text_search_query()`; they apply `apply_filtering`, `apply_sorting`, and optional full-text search and pagination. +- **Total** resolvers (`patientsTotal`, `tasksTotal`, `recentPatientsTotal`, `recentTasksTotal`) use a shared helper `get_property_field_types(db, filtering, sorting)` so property columns used in filter/sort have the correct field types when joining `PropertyValue`; then they call `apply_filtering` and `apply_sorting` and return a count. + +## Subscriptions + +- Base subscriptions (`entity_created`, `entity_updated`, `entity_deleted`) subscribe to Redis channels and yield entity IDs. +- **Location filter**: `subscribe_with_location_filter(base_iterator, db, root_location_ids_str, belongs_check)` wraps a base iterator and yields only IDs for which the entity belongs to the given root locations (via `patient_belongs_to_root_locations` or `task_belongs_to_root_locations`). Patient and task subscription resolvers use this so the UI only gets events for the current location scope. + +## Frontend: table state and property columns + +- **useTableState(storageKeyPrefix)** persists pagination, sorting, filters, and column visibility in `localStorage` with keys `{prefix}-column-*`. Tables (PatientList, TaskList, RecentPatientsTable, RecentTasksTable) use it with prefixes like `patient-list`, `task-list`, `recent-patients`, `recent-tasks`. +- **usePropertyColumnVisibility(propertyDefinitionsData, entity, columnVisibility, setColumnVisibility)** runs a single effect that, when property columns exist but none are in visibility, sets all property columns to hidden so new property definitions don’t clutter the table by default. +- **getPropertyColumnsForEntity(propertyDefinitionsData, entity)** returns column definitions for that entity’s active property definitions; tables use it with `createPropertyColumn`-style columns. + +## Frontend: paginated lists + +- **usePaginatedEntityQuery** is the generic hook: it takes a GraphQL document, variables, page size, optional `getPageDataKey`, and extractors for items and total count. It keeps a page cache and flattens results for infinite-style pagination. +- **usePatientsPaginated** and **useTasksPaginated** are thin wrappers that pass the right document, variables, extractors, and (for patients) `getPageDataKey` for cache deduplication. diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 00000000..fe3a4a24 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,47 @@ +# Project overview + +**helpwave tasks** is a task and ward-management platform for healthcare. It brings structure to clinical workflows: patients, tasks, locations, and custom properties. + +## What it does + +- **Patients**: Admit, discharge, wait, mark deceased; attach to locations (clinic, ward, room, bed) and teams. +- **Tasks**: Create, assign (user or team), complete, reopen; filter by location and assignee. +- **Locations**: Hierarchy (hospital → clinic → ward → room → bed) and teams; access control is location-based. +- **Properties**: Custom field definitions for patients and tasks (text, number, date, select, user, etc.). +- **Audit**: Activity logged to InfluxDB for compliance and debugging. + +## Main components + +| Component | Role | +|-----------|------| +| **backend** | Python (FastAPI + Strawberry GraphQL), PostgreSQL, Redis, InfluxDB. Auth via Keycloak. | +| **web** | Next.js (React), Apollo Client, GraphQL. PWA-ready. | +| **simulator** | Scripts to simulate clinic traffic (patients, tasks) for development. | +| **proxy** | Nginx reverse proxy for production (TLS, routing). | +| **keycloak** | Realm and client config for SSO. | + +## High-level architecture + +```mermaid +flowchart LR + Client[Client] + Proxy[Proxy] + Web[Web] + Backend[Backend] + DB[(PostgreSQL)] + Redis[Redis] + Influx[InfluxDB] + Keycloak[Keycloak] + Client --> Proxy + Proxy --> Web + Proxy --> Backend + Backend --> DB + Backend --> Redis + Backend --> Influx + Backend --> Keycloak + Web --> Backend +``` + +- **Web** serves the UI and talks to the backend GraphQL API (HTTP + WebSocket for subscriptions). +- **Backend** resolves queries/mutations/subscriptions, enforces location-based access, and publishes entity events to Redis for real-time updates. +- **Keycloak** issues JWT; the backend validates tokens and passes user/locations into resolvers. diff --git a/docs/structure.md b/docs/structure.md new file mode 100644 index 00000000..afa7003b --- /dev/null +++ b/docs/structure.md @@ -0,0 +1,57 @@ +# Project structure + +Key folders and their responsibilities. + +## Root + +| Folder | Responsibility | +|--------|----------------| +| **backend/** | GraphQL API, database, auth, business logic. | +| **web/** | Next.js app: pages, components, data layer, i18n. | +| **simulator/** | Dev tool: creates patients/tasks via GraphQL. | +| **proxy/** | Nginx config for production. | +| **keycloak/** | Realm JSON and docs. | +| **scaffold/** | Seed data for locations/structures. | +| **tests/** | E2E (Playwright), shared test config. | +| **docs/** | Project documentation (this folder). | + +## Backend (`backend/`) + +| Path | Responsibility | +|------|----------------| +| **api/** | Resolvers, services, decorators, types, inputs. | +| **api/resolvers/** | GraphQL queries/mutations/subscriptions (patient, task, location, property, user, audit). | +| **api/services/** | Authorization, location, property, notifications, subscription (Redis), validation, datetime, checksum. | +| **api/decorators/** | Filter/sort/pagination, full-text search, `get_property_field_types` for Total resolvers. | +| **api/inputs.py** | Strawberry input types. | +| **api/types/** | Strawberry types for DB models. | +| **api/errors.py** | Shared `raise_forbidden()`. | +| **database/** | SQLAlchemy models, migrations (Alembic), session. | +| **auth.py** | JWT validation, user from token. | +| **config.py** | Env-based config. | +| **main.py** | FastAPI app, GraphQL router. | + +## Web (`web/`) + +| Path | Responsibility | +|------|----------------| +| **components/** | UI: tables (PatientList, TaskList, Recent*), patients, tasks, properties, layout, dialogs. | +| **data/** | Apollo client, cache, hooks (queries/mutations), subscriptions, persistence. | +| **data/hooks/** | `usePatientsPaginated`, `useTasksPaginated`, `usePaginatedEntityQuery`, queryHelpers. | +| **pages/** | Next.js routes and page components. | +| **hooks/** | `useTableState`, `usePropertyColumnVisibility`, auth, pagination, conflict resolution. | +| **utils/** | Date, due-date, priority, property columns, location, table config, GraphQL helpers. | +| **i18n/** | Translation wiring and usage. | +| **locales/** | ARB translation files. | +| **providers/** | Apollo, conflict, subscriptions. | +| **api/** | GraphQL documents and generated types. | + +## Simulator (`simulator/`) + +| Path | Responsibility | +|------|----------------| +| **main.py** | Entrypoint. | +| **simulator.py** | Orchestration. | +| **patient_manager.py**, **task_manager.py** | Create/update entities via GraphQL. | +| **location_manager.py** | Location tree and assignment. | +| **graphql_client.py** | HTTP GraphQL client. | diff --git a/web/components/AvatarComponent.tsx b/web/components/AvatarComponent.tsx deleted file mode 100644 index 0a07a112..00000000 --- a/web/components/AvatarComponent.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react' -import type { AvatarSize } from '@helpwave/hightide' -import { Avatar, type AvatarProps } from '@helpwave/hightide' -import clsx from 'clsx' - -interface AvatarStatusComponentProps extends AvatarProps { - isOnline?: boolean | null, -} - -export const AvatarStatusComponent: React.FC = ({ - isOnline, - className, - ...avatarProps -}) => { - const size = avatarProps.size || 'sm' - const dotSizeClasses: Record, string> = { - xs: 'w-3 h-3', - sm: 'w-3.5 h-3.5', - md: 'w-4 h-4', - lg: 'w-5 h-5', - } - - const dotPositionClasses: Record, string> = { - xs: 'bottom-0 right-0', - sm: 'bottom-0 right-0', - md: 'bottom-0 right-0', - lg: 'bottom-0 right-0', - } - - const dotBorderClasses: Record, string> = { - xs: 'border-[1.5px]', - sm: 'border-2', - md: 'border-2', - lg: 'border-2', - } - - const showOnline = isOnline === true - - return ( -
- -
-
- ) -} - diff --git a/web/components/AvatarWithStatus.tsx b/web/components/AvatarWithStatus.tsx deleted file mode 100644 index bba076d5..00000000 --- a/web/components/AvatarWithStatus.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react' -import { Avatar, type AvatarProps } from '@helpwave/hightide' -import { OnlineStatusIndicator } from './OnlineStatusIndicator' -import clsx from 'clsx' - -interface AvatarWithStatusProps extends AvatarProps { - isOnline?: boolean | null, - showStatus?: boolean, -} - -export const AvatarWithStatus: React.FC = ({ - isOnline, - showStatus = true, - className, - ...avatarProps -}) => { - const size = avatarProps.size || 'md' - const indicatorSize = size === 'sm' ? 'sm' : size === 'lg' ? 'lg' : 'md' - const positionOffset = size === 'sm' ? 'bottom-0 right-0' : size === 'lg' ? 'bottom-0.5 right-0.5' : 'bottom-0 right-0' - - return ( -
- - {showStatus && ( -
- -
- )} -
- ) -} - diff --git a/web/components/ConnectionStatusIndicator.tsx b/web/components/ConnectionStatusIndicator.tsx deleted file mode 100644 index 5aae90bf..00000000 --- a/web/components/ConnectionStatusIndicator.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import type { ReactElement } from 'react' -import { Wifi, WifiOff, Loader2 } from 'lucide-react' -import { Tooltip, Button } from '@helpwave/hightide' -import { useConnectionStatus } from '@/hooks/useConnectionStatus' -import { useTasksTranslation } from '@/i18n/useTasksTranslation' - -export function ConnectionStatusIndicator(): ReactElement { - const status = useConnectionStatus() - const translation = useTasksTranslation() - - const tooltip = - status === 'connected' - ? translation('connectionConnected') ?? 'Connected' - : status === 'connecting' - ? translation('connectionConnecting') ?? 'Connecting…' - : translation('connectionDisconnected') ?? 'Disconnected' - - return ( - - - - ) -} diff --git a/web/components/OnlineStatusIndicator.tsx b/web/components/OnlineStatusIndicator.tsx deleted file mode 100644 index b7a0b088..00000000 --- a/web/components/OnlineStatusIndicator.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react' -import clsx from 'clsx' - -interface OnlineStatusIndicatorProps { - isOnline: boolean | null | undefined, - size?: 'sm' | 'md' | 'lg', - className?: string, -} - -export const OnlineStatusIndicator: React.FC = ({ - isOnline, - size = 'md', - className, -}) => { - const sizeClasses = { - sm: 'w-2 h-2', - md: 'w-2.5 h-2.5', - lg: 'w-3 h-3', - } - - const borderClasses = { - sm: 'border-[1.5px]', - md: 'border-2', - lg: 'border-2', - } - - if (isOnline === null || isOnline === undefined) { - return null - } - - return ( -
- ) -} - diff --git a/web/components/patients/LocationChips.tsx b/web/components/patients/LocationChips.tsx index 893bcdaa..cc9842e3 100644 --- a/web/components/patients/LocationChips.tsx +++ b/web/components/patients/LocationChips.tsx @@ -22,13 +22,13 @@ interface LocationChipsProps extends HTMLAttributes { } const getKindStyles = (kind: LocationType | undefined) => { - if (kind === 'HOSPITAL') return 'not-print:location-hospital not-print:coloring-solid' - if (kind === 'PRACTICE') return 'not-print:location-practice not-print:coloring-solid' - if (kind === 'CLINIC') return 'not-print:location-clinic not-print:coloring-solid' - if (kind === 'TEAM') return 'not-print:location-team not-print:coloring-solid' - if (kind === 'WARD') return 'not-print:location-ward not-print:coloring-solid' - if (kind === 'ROOM') return 'not-print:location-room not-print:coloring-solid' - if (kind === 'BED') return 'not-print:location-bed not-print:coloring-solid' + if (kind === 'HOSPITAL') return 'location-hospital coloring-solid' + if (kind === 'PRACTICE') return 'location-practice coloring-solid' + if (kind === 'CLINIC') return 'location-clinic coloring-solid' + if (kind === 'TEAM') return 'location-team coloring-solid' + if (kind === 'WARD') return 'location-ward coloring-solid' + if (kind === 'ROOM') return 'location-room coloring-solid' + if (kind === 'BED') return 'location-bed coloring-solid' return '' } @@ -58,7 +58,7 @@ export const LocationChips = ({ locations, disableLink = false, small = false, p className={clsx('cursor-pointer hover:opacity-80 transition-opacity max-w-full', small && 'text-xs')} >
- + {displayTitle} {linkTarget?.kind && ( diff --git a/web/components/tables/PatientList.tsx b/web/components/tables/PatientList.tsx index c74b53e9..4635a282 100644 --- a/web/components/tables/PatientList.tsx +++ b/web/components/tables/PatientList.tsx @@ -11,9 +11,10 @@ import { PatientStateChip } from '@/components/patients/PatientStateChip' import { getLocationNodesByKind, type LocationKindColumn } from '@/utils/location' import { useTasksTranslation } from '@/i18n/useTasksTranslation' import { useTasksContext } from '@/hooks/useTasksContext' -import type { ColumnDef, Row, ColumnFiltersState, PaginationState, SortingState, TableState, VisibilityState } from '@tanstack/table-core' -import { createPropertyColumn } from '@/utils/propertyColumn' -import { useStateWithLocalStorage } from '@/hooks/useStateWithLocalStorage' +import type { ColumnDef, Row, TableState } from '@tanstack/table-core' +import { getPropertyColumnsForEntity } from '@/utils/propertyColumn' +import { useTableState } from '@/hooks/useTableState' +import { usePropertyColumnVisibility } from '@/hooks/usePropertyColumnVisibility' import { TABLE_PAGE_SIZE } from '@/utils/tableConfig' export type PatientViewModel = { @@ -31,11 +32,6 @@ export type PatientViewModel = { properties?: GetPatientsQuery['patients'][0]['properties'], } -const STORAGE_KEY_COLUMN_VISIBILITY = 'patient-list-column-visibility' -const STORAGE_KEY_COLUMN_FILTERS = 'patient-list-column-filters' -const STORAGE_KEY_COLUMN_SORTING = 'patient-list-column-sorting' -const STORAGE_KEY_COLUMN_PAGINATION = 'patient-list-column-pagination' - const LOCATION_KIND_HEADERS: Record = { CLINIC: 'locationClinic', WARD: 'locationWard', @@ -60,31 +56,30 @@ export const PatientList = forwardRef(({ initi const translation = useTasksTranslation() const { selectedRootLocationIds } = useTasksContext() const { refreshingPatientIds } = useRefreshingEntityIds() + const { data: propertyDefinitionsData } = usePropertyDefinitions() const effectiveRootLocationIds = rootLocationIds ?? selectedRootLocationIds const [isPanelOpen, setIsPanelOpen] = useState(false) const [selectedPatient, setSelectedPatient] = useState(undefined) const [searchQuery, setSearchQuery] = useState('') const [openedPatientId, setOpenedPatientId] = useState(null) - const [pagination, setPagination] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_PAGINATION, - defaultValue: { - pageSize: 10, - pageIndex: 0 - } - }) - const [sorting, setSorting] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_SORTING, - defaultValue: [] - }) - const [filters, setFilters] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_FILTERS, - defaultValue: [] - }) - const [columnVisibility, setColumnVisibility] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_VISIBILITY, - defaultValue: {} - }) + const { + pagination, + setPagination, + sorting, + setSorting, + filters, + setFilters, + columnVisibility, + setColumnVisibility, + } = useTableState('patient-list') + + usePropertyColumnVisibility( + propertyDefinitionsData, + PropertyEntity.Patient, + columnVisibility, + setColumnVisibility + ) const allPatientStates: PatientState[] = useMemo(() => [ PatientState.Admitted, @@ -111,26 +106,6 @@ export const PatientList = forwardRef(({ initi { pageSize: TABLE_PAGE_SIZE } ) - const { data: propertyDefinitionsData } = usePropertyDefinitions() - - useEffect(() => { - if (propertyDefinitionsData?.propertyDefinitions) { - const patientProperties = propertyDefinitionsData.propertyDefinitions.filter( - def => def.isActive && def.allowedEntities.includes(PropertyEntity.Patient) - ) - const propertyColumnIds = patientProperties.map(prop => `property_${prop.id}`) - const hasPropertyColumnsInVisibility = propertyColumnIds.some(id => id in columnVisibility) - - if (!hasPropertyColumnsInVisibility && propertyColumnIds.length > 0) { - const initialVisibility: VisibilityState = { ...columnVisibility } - propertyColumnIds.forEach(id => { - initialVisibility[id] = false - }) - setColumnVisibility(initialVisibility) - } - } - }, [propertyDefinitionsData, columnVisibility, setColumnVisibility]) - const patients: PatientViewModel[] = useMemo(() => { if (!patientsData || patientsData.length === 0) return [] @@ -187,16 +162,10 @@ export const PatientList = forwardRef(({ initi setOpenedPatientId(null) } - const patientPropertyColumns = useMemo[]>(() => { - if (!propertyDefinitionsData?.propertyDefinitions) return [] - - const patientProperties = propertyDefinitionsData.propertyDefinitions.filter( - def => def.isActive && def.allowedEntities.includes(PropertyEntity.Patient) - ) - - return patientProperties.map(prop => - createPropertyColumn(prop)) - }, [propertyDefinitionsData]) + const patientPropertyColumns = useMemo[]>( + () => getPropertyColumnsForEntity(propertyDefinitionsData, PropertyEntity.Patient), + [propertyDefinitionsData] + ) const rowLoadingCell = useMemo(() => , []) @@ -412,9 +381,7 @@ export const PatientList = forwardRef(({ initi
)} - +
, 'table'> { patients: PatientViewModel[], onSelectPatient: (id: string) => void, @@ -31,54 +27,28 @@ export const RecentPatientsTable = ({ const translation = useTasksTranslation() const { data: propertyDefinitionsData } = usePropertyDefinitions() - const [pagination, setPagination] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_PAGINATION, - defaultValue: { - pageSize: 10, - pageIndex: 0 - } - }) - const [sorting, setSorting] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_SORTING, - defaultValue: [] - }) - const [filters, setFilters] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_FILTERS, - defaultValue: [] - }) - const [columnVisibility, setColumnVisibility] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_VISIBILITY, - defaultValue: {} - }) - - useEffect(() => { - if (propertyDefinitionsData?.propertyDefinitions) { - const patientProperties = propertyDefinitionsData.propertyDefinitions.filter( - def => def.isActive && def.allowedEntities.includes(PropertyEntity.Patient) - ) - const propertyColumnIds = patientProperties.map(prop => `property_${prop.id}`) - const hasPropertyColumnsInVisibility = propertyColumnIds.some(id => id in columnVisibility) - - if (!hasPropertyColumnsInVisibility && propertyColumnIds.length > 0) { - const initialVisibility: VisibilityState = { ...columnVisibility } - propertyColumnIds.forEach(id => { - initialVisibility[id] = false - }) - setColumnVisibility(initialVisibility) - } - } - }, [propertyDefinitionsData, columnVisibility, setColumnVisibility]) - - const patientPropertyColumns = useMemo[]>(() => { - if (!propertyDefinitionsData?.propertyDefinitions) return [] + const { + pagination, + setPagination, + sorting, + setSorting, + filters, + setFilters, + columnVisibility, + setColumnVisibility, + } = useTableState('recent-patients') - const patientProperties = propertyDefinitionsData.propertyDefinitions.filter( - def => def.isActive && def.allowedEntities.includes(PropertyEntity.Patient) - ) + usePropertyColumnVisibility( + propertyDefinitionsData, + PropertyEntity.Patient, + columnVisibility, + setColumnVisibility + ) - return patientProperties.map(prop => - createPropertyColumn(prop)) - }, [propertyDefinitionsData]) + const patientPropertyColumns = useMemo[]>( + () => getPropertyColumnsForEntity(propertyDefinitionsData, PropertyEntity.Patient), + [propertyDefinitionsData] + ) const patientColumns = useMemo[]>(() => [ { @@ -143,7 +113,7 @@ export const RecentPatientsTable = ({ const fixedPagination = useMemo(() => ({ ...pagination, - pageSize: 10 + pageSize: 5 }), [pagination]) return ( @@ -155,7 +125,7 @@ export const RecentPatientsTable = ({ onRowClick={onRowClick} initialState={{ pagination: { - pageSize: 10, + pageSize: 5, }, }} state={{ @@ -170,12 +140,14 @@ export const RecentPatientsTable = ({ onColumnFiltersChange={setFilters} enableMultiSort={true} > -
+
{translation('recentPatients')} {translation('patientsUpdatedRecently')}
- +
+ +
diff --git a/web/components/tables/RecentTasksTable.tsx b/web/components/tables/RecentTasksTable.tsx index 29545641..39b08615 100644 --- a/web/components/tables/RecentTasksTable.tsx +++ b/web/components/tables/RecentTasksTable.tsx @@ -1,7 +1,7 @@ import { useTasksTranslation } from '@/i18n/useTasksTranslation' -import type { ColumnDef, Row, ColumnFiltersState, PaginationState, SortingState, TableState, VisibilityState } from '@tanstack/react-table' +import type { ColumnDef, Row, TableState } from '@tanstack/react-table' import type { GetOverviewDataQuery, TaskPriority } from '@/api/gql/generated' -import { useCallback, useMemo, useEffect } from 'react' +import { useCallback, useMemo } from 'react' import clsx from 'clsx' import type { TableProps } from '@helpwave/hightide' import { Button, Checkbox, FillerCell, TableDisplay, TableProvider, Tooltip } from '@helpwave/hightide' @@ -11,16 +11,12 @@ import { DueDateUtils } from '@/utils/dueDate' import { PriorityUtils } from '@/utils/priority' import { PropertyEntity } from '@/api/gql/generated' import { usePropertyDefinitions } from '@/data' -import { createPropertyColumn } from '@/utils/propertyColumn' -import { useStateWithLocalStorage } from '@/hooks/useStateWithLocalStorage' +import { getPropertyColumnsForEntity } from '@/utils/propertyColumn' +import { useTableState } from '@/hooks/useTableState' +import { usePropertyColumnVisibility } from '@/hooks/usePropertyColumnVisibility' type TaskViewModel = GetOverviewDataQuery['recentTasks'][0] -const STORAGE_KEY_COLUMN_VISIBILITY = 'recent-tasks-column-visibility' -const STORAGE_KEY_COLUMN_FILTERS = 'recent-tasks-column-filters' -const STORAGE_KEY_COLUMN_SORTING = 'recent-tasks-column-sorting' -const STORAGE_KEY_COLUMN_PAGINATION = 'recent-tasks-column-pagination' - export interface RecentTasksTableProps extends Omit, 'table'> { tasks: TaskViewModel[], completeTask: (id: string) => void, @@ -41,54 +37,28 @@ export const RecentTasksTable = ({ const translation = useTasksTranslation() const { data: propertyDefinitionsData } = usePropertyDefinitions() - const [pagination, setPagination] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_PAGINATION, - defaultValue: { - pageSize: 10, - pageIndex: 0 - } - }) - const [sorting, setSorting] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_SORTING, - defaultValue: [] - }) - const [filters, setFilters] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_FILTERS, - defaultValue: [] - }) - const [columnVisibility, setColumnVisibility] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_VISIBILITY, - defaultValue: {} - }) - - useEffect(() => { - if (propertyDefinitionsData?.propertyDefinitions) { - const taskProperties = propertyDefinitionsData.propertyDefinitions.filter( - def => def.isActive && def.allowedEntities.includes(PropertyEntity.Task) - ) - const propertyColumnIds = taskProperties.map(prop => `property_${prop.id}`) - const hasPropertyColumnsInVisibility = propertyColumnIds.some(id => id in columnVisibility) - - if (!hasPropertyColumnsInVisibility && propertyColumnIds.length > 0) { - const initialVisibility: VisibilityState = { ...columnVisibility } - propertyColumnIds.forEach(id => { - initialVisibility[id] = false - }) - setColumnVisibility(initialVisibility) - } - } - }, [propertyDefinitionsData, columnVisibility, setColumnVisibility]) - - const taskPropertyColumns = useMemo[]>(() => { - if (!propertyDefinitionsData?.propertyDefinitions) return [] - - const taskProperties = propertyDefinitionsData.propertyDefinitions.filter( - def => def.isActive && def.allowedEntities.includes(PropertyEntity.Task) - ) + const { + pagination, + setPagination, + sorting, + setSorting, + filters, + setFilters, + columnVisibility, + setColumnVisibility, + } = useTableState('recent-tasks') + + usePropertyColumnVisibility( + propertyDefinitionsData, + PropertyEntity.Task, + columnVisibility, + setColumnVisibility + ) - return taskProperties.map(prop => - createPropertyColumn(prop)) - }, [propertyDefinitionsData]) + const taskPropertyColumns = useMemo[]>( + () => getPropertyColumnsForEntity(propertyDefinitionsData, PropertyEntity.Task), + [propertyDefinitionsData] + ) const taskColumns = useMemo[]>(() => [ { @@ -217,7 +187,7 @@ export const RecentTasksTable = ({ const fixedPagination = useMemo(() => ({ ...pagination, - pageSize: 10 + pageSize: 5 }), [pagination]) return ( @@ -229,7 +199,7 @@ export const RecentTasksTable = ({ onRowClick={onRowClick} initialState={{ pagination: { - pageSize: 10, + pageSize: 5, }, }} state={{ @@ -245,12 +215,14 @@ export const RecentTasksTable = ({ enableMultiSort={true} isUsingFillerRows={true} > -
+
{translation('recentTasks')} {translation('tasksUpdatedRecently')}
- +
+ +
diff --git a/web/components/tables/TaskList.tsx b/web/components/tables/TaskList.tsx index c8a122ca..7b111b50 100644 --- a/web/components/tables/TaskList.tsx +++ b/web/components/tables/TaskList.tsx @@ -15,10 +15,12 @@ import { PatientDetailView } from '@/components/patients/PatientDetailView' import { useTasksTranslation } from '@/i18n/useTasksTranslation' import { useTasksContext } from '@/hooks/useTasksContext' import { UserInfoPopup } from '@/components/UserInfoPopup' -import type { ColumnDef, ColumnFiltersState, PaginationState, SortingState, TableState, VisibilityState } from '@tanstack/table-core' +import type { ColumnDef, ColumnFiltersState, TableState } from '@tanstack/table-core' +import { DueDateUtils } from '@/utils/dueDate' import { PriorityUtils } from '@/utils/priority' -import { createPropertyColumn } from '@/utils/propertyColumn' -import { useStateWithLocalStorage } from '@/hooks/useStateWithLocalStorage' +import { getPropertyColumnsForEntity } from '@/utils/propertyColumn' +import { useTableState } from '@/hooks/useTableState' +import { usePropertyColumnVisibility } from '@/hooks/usePropertyColumnVisibility' export type TaskViewModel = { id: string, @@ -65,47 +67,33 @@ type TaskListProps = { showAllTasksMode?: boolean, } -const isOverdue = (dueDate: Date | undefined, done: boolean): boolean => { - if (!dueDate || done) return false - return dueDate.getTime() < Date.now() -} - -const isCloseToDueDate = (dueDate: Date | undefined, done: boolean): boolean => { - if (!dueDate || done) return false - const now = Date.now() - const dueTime = dueDate.getTime() - const oneHour = 60 * 60 * 1000 - return dueTime > now && dueTime - now <= oneHour -} - - - -const STORAGE_KEY_COLUMN_VISIBILITY = 'task-list-column-visibility' -const STORAGE_KEY_COLUMN_FILTERS = 'task-list-column-filters' -const STORAGE_KEY_COLUMN_SORTING = 'task-list-column-sorting' -const STORAGE_KEY_COLUMN_PAGINATION = 'task-list-column-pagination' - export const TaskList = forwardRef(({ tasks: initialTasks, onRefetch, showAssignee = false, initialTaskId, onInitialTaskOpened, headerActions, totalCount, loading = false, showAllTasksMode = false }, ref) => { const translation = useTasksTranslation() + const { data: propertyDefinitionsData } = usePropertyDefinitions() - const [pagination, setPagination] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_PAGINATION, - defaultValue: { - pageSize: 10, - pageIndex: 0 - } - }) - const [sorting, setSorting] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_SORTING, - defaultValue: [ + const { + pagination, + setPagination, + sorting, + setSorting, + filters, + setFilters, + columnVisibility, + setColumnVisibility, + } = useTableState('task-list', { + defaultSorting: [ { id: 'done', desc: false }, { id: 'dueDate', desc: false }, - ] - }) - const [filters, setFilters] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_FILTERS, - defaultValue: [] + ], }) + + usePropertyColumnVisibility( + propertyDefinitionsData, + PropertyEntity.Task, + columnVisibility, + setColumnVisibility + ) + const normalizeDoneFilterValue = useCallback((value: unknown): boolean | undefined => { if (value === true || value === 'true' || value === 'done') return true if (value === false || value === 'false' || value === 'undone') return false @@ -136,34 +124,11 @@ export const TaskList = forwardRef(({ tasks: initial }) }) }, [setFilters, normalizeDoneFilterValue]) - const [columnVisibility, setColumnVisibility] = useStateWithLocalStorage({ - key: STORAGE_KEY_COLUMN_VISIBILITY, - defaultValue: {} - }) const queryClient = useQueryClient() const { totalPatientsCount, user } = useTasksContext() const { refreshingTaskIds } = useRefreshingEntityIds() const [optimisticUpdates, setOptimisticUpdates] = useState>(new Map()) - const { data: propertyDefinitionsData } = usePropertyDefinitions() - - useEffect(() => { - if (propertyDefinitionsData?.propertyDefinitions) { - const taskProperties = propertyDefinitionsData.propertyDefinitions.filter( - def => def.isActive && def.allowedEntities.includes(PropertyEntity.Task) - ) - const propertyColumnIds = taskProperties.map(prop => `property_${prop.id}`) - const hasPropertyColumnsInVisibility = propertyColumnIds.some(id => id in columnVisibility) - - if (!hasPropertyColumnsInVisibility && propertyColumnIds.length > 0) { - const initialVisibility: VisibilityState = { ...columnVisibility } - propertyColumnIds.forEach(id => { - initialVisibility[id] = false - }) - setColumnVisibility(initialVisibility) - } - } - }, [propertyDefinitionsData, columnVisibility, setColumnVisibility]) const [completeTask] = useCompleteTask() const [reopenTask] = useReopenTask() const [assignTask] = useAssignTask() @@ -343,14 +308,10 @@ export const TaskList = forwardRef(({ tasks: initial setIsHandoverDialogOpen(false) } - const taskProperties = useMemo(() => { - return propertyDefinitionsData?.propertyDefinitions - .filter(def => def.isActive && def.allowedEntities.includes(PropertyEntity.Task)) ?? [] - }, [propertyDefinitionsData]) - - const taskPropertyColumns = useMemo[]>(() => { - return taskProperties.map(prop => createPropertyColumn(prop)) - }, [taskProperties]) + const taskPropertyColumns = useMemo[]>( + () => getPropertyColumnsForEntity(propertyDefinitionsData, PropertyEntity.Task), + [propertyDefinitionsData] + ) const rowLoadingCell = useMemo(() => , []) @@ -431,8 +392,8 @@ export const TaskList = forwardRef(({ tasks: initial cell: ({ row }) => { if (refreshingTaskIds.has(row.original.id)) return rowLoadingCell if (!row.original.dueDate) return - - const overdue = isOverdue(row.original.dueDate, row.original.done) - const closeToDue = isCloseToDueDate(row.original.dueDate, row.original.done) + const overdue = DueDateUtils.isOverdue(row.original.dueDate, row.original.done) + const closeToDue = DueDateUtils.isCloseToDueDate(row.original.dueDate, row.original.done) let colorClass = '' if (overdue) { colorClass = '!text-red-500' @@ -468,22 +429,17 @@ export const TaskList = forwardRef(({ tasks: initial ) } return ( - <> -
- -
- {data.patient?.name} - + ) }, sortingFn: 'text', @@ -645,7 +601,7 @@ export const TaskList = forwardRef(({ tasks: initial display: none !important; } `} - +
= { + pageSize: number, + getPageDataKey?: (data: TQueryData | undefined) => string, +} + +export type UsePaginatedEntityQueryResult = { + data: TItem[], + loading: boolean, + error: Error | undefined, + fetchNextPage: () => void, + hasNextPage: boolean, + totalCount: number | undefined, + refetch: () => void, +} + +export function usePaginatedEntityQuery< + TQueryData, + TVariables extends Record, + TItem +>( + document: Parameters>[0], + variables: TVariables | undefined, + options: UsePaginatedEntityQueryOptions, + extractItems: (data: TQueryData | undefined) => TItem[], + extractTotal: (data: TQueryData | undefined) => number | undefined +): UsePaginatedEntityQueryResult { + const { pageSize, getPageDataKey } = options + const [pageIndex, setPageIndex] = useState(0) + const [pages, setPages] = useState<(TQueryData | undefined)[]>([]) + const variablesWithPagination = useMemo(() => ({ + ...(variables ?? {}), + pagination: { pageIndex, pageSize } + }), [variables, pageIndex, pageSize]) + const variablesWithPaginationTyped = variablesWithPagination as TVariables & { pagination: { pageIndex: number, pageSize: number } } + const variablesKey = useMemo( + () => JSON.stringify(variablesWithPagination), + [variablesWithPagination] + ) + const result = useQueryWhenReady( + document, + variablesWithPaginationTyped, + { fetchPolicy: 'cache-and-network' } + ) + const totalCount = extractTotal(result.data) + const prevDataKeyRef = useRef('') + const variablesKeyRef = useRef('') + + useEffect(() => { + if (variablesKey !== variablesKeyRef.current) { + variablesKeyRef.current = variablesKey + prevDataKeyRef.current = '' + } + if (result.loading || result.data === undefined) return + if (getPageDataKey) { + const dataKey = getPageDataKey(result.data) + if (prevDataKeyRef.current === dataKey) return + prevDataKeyRef.current = dataKey + } + setPages((prev) => { + const next = [...prev] + next[pageIndex] = result.data + return next + }) + }, [result.loading, result.data, pageIndex, variablesKey, getPageDataKey]) + + const flattened = useMemo(() => { + const currentFromCache = !result.loading ? result.data : undefined + const before = pages.slice(0, pageIndex).flatMap((p) => extractItems(p)) + const current = currentFromCache !== undefined ? extractItems(currentFromCache) : extractItems(pages[pageIndex]) + const after = pages.slice(pageIndex + 1).flatMap((p) => extractItems(p)) + return [...before, ...current, ...after] + }, [pageIndex, pages, result.data, result.loading, extractItems]) + + const hasNextPage = + (totalCount !== undefined && flattened.length < totalCount) ?? false + + const fetchNextPage = useCallback(() => { + if (hasNextPage && !result.loading) { + setPageIndex((i) => i + 1) + } + }, [hasNextPage, result.loading]) + + const refetch = useCallback(() => { + result.refetch() + }, [result]) + + return { + data: flattened, + loading: result.loading, + error: result.error, + fetchNextPage, + hasNextPage, + totalCount, + refetch, + } +} diff --git a/web/data/hooks/usePatientsPaginated.ts b/web/data/hooks/usePatientsPaginated.ts index 547fdc08..644bcade 100644 --- a/web/data/hooks/usePatientsPaginated.ts +++ b/web/data/hooks/usePatientsPaginated.ts @@ -1,10 +1,9 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { GetPatientsDocument, type GetPatientsQuery, type GetPatientsQueryVariables } from '@/api/gql/generated' -import { useQueryWhenReady } from './queryHelpers' +import { usePaginatedEntityQuery } from './usePaginatedEntityQuery' export type UsePatientsPaginatedOptions = { pageSize: number, @@ -36,70 +35,15 @@ export function usePatientsPaginated( variables: GetPatientsQueryVariables | undefined, options: UsePatientsPaginatedOptions ): UsePatientsPaginatedResult { - const { pageSize } = options - const [pageIndex, setPageIndex] = useState(0) - const [pages, setPages] = useState<(GetPatientsQuery | undefined)[]>([]) - const variablesWithPagination: GetPatientsQueryVariables = useMemo(() => ({ - ...(variables ?? {}), - pagination: { pageIndex, pageSize }, - }), [variables, pageIndex, pageSize]) - const variablesKey = useMemo( - () => JSON.stringify(variablesWithPagination), - [variablesWithPagination] - ) - const result = useQueryWhenReady( + return usePaginatedEntityQuery< + GetPatientsQuery, + GetPatientsQueryVariables, + GetPatientsQuery['patients'][0] + >( GetPatientsDocument, - variablesWithPagination, - { fetchPolicy: 'cache-and-network' } + variables, + { pageSize: options.pageSize, getPageDataKey }, + (data) => data?.patients ?? [], + (data) => data?.patientsTotal ) - const totalCount = result.data?.patientsTotal - const prevDataKeyRef = useRef('') - const variablesKeyRef = useRef('') - - useEffect(() => { - if (variablesKey !== variablesKeyRef.current) { - variablesKeyRef.current = variablesKey - prevDataKeyRef.current = '' - } - if (result.loading || result.data === undefined) return - const dataKey = getPageDataKey(result.data) - if (prevDataKeyRef.current === dataKey) return - prevDataKeyRef.current = dataKey - setPages((prev) => { - const next = [...prev] - next[pageIndex] = result.data - return next - }) - }, [result.loading, result.data, pageIndex, variablesKey]) - - const flattenedPatients = useMemo(() => { - const currentFromCache = !result.loading ? result.data?.patients : undefined - const before = pages.slice(0, pageIndex).flatMap((p) => p?.patients ?? []) - const current = currentFromCache ?? pages[pageIndex]?.patients ?? [] - const after = pages.slice(pageIndex + 1).flatMap((p) => p?.patients ?? []) - return [...before, ...current, ...after] - }, [pageIndex, pages, result.data?.patients, result.loading]) - - const hasNextPage = - (totalCount !== undefined && flattenedPatients.length < totalCount) ?? false - - const fetchNextPage = useCallback(() => { - if (hasNextPage && !result.loading) { - setPageIndex((i) => i + 1) - } - }, [hasNextPage, result.loading]) - - const refetch = useCallback(() => { - result.refetch() - }, [result]) - - return { - data: flattenedPatients, - loading: result.loading, - error: result.error, - fetchNextPage, - hasNextPage, - totalCount, - refetch, - } } diff --git a/web/data/hooks/useTasksPaginated.ts b/web/data/hooks/useTasksPaginated.ts index ed553d6d..624f20e7 100644 --- a/web/data/hooks/useTasksPaginated.ts +++ b/web/data/hooks/useTasksPaginated.ts @@ -1,10 +1,9 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' import { GetTasksDocument, type GetTasksQuery, type GetTasksQueryVariables } from '@/api/gql/generated' -import { useQueryWhenReady } from './queryHelpers' +import { usePaginatedEntityQuery } from './usePaginatedEntityQuery' export type UseTasksPaginatedOptions = { pageSize: number, @@ -24,58 +23,15 @@ export function useTasksPaginated( variables: GetTasksQueryVariables | undefined, options: UseTasksPaginatedOptions ): UseTasksPaginatedResult { - const { pageSize } = options - const [pageIndex, setPageIndex] = useState(0) - const [pages, setPages] = useState<(GetTasksQuery | undefined)[]>([]) - const variablesWithPagination: GetTasksQueryVariables = { - ...(variables ?? {}), - pagination: { pageIndex, pageSize }, - } - const result = useQueryWhenReady( + return usePaginatedEntityQuery< + GetTasksQuery, + GetTasksQueryVariables, + GetTasksQuery['tasks'][0] + >( GetTasksDocument, - variablesWithPagination, - { fetchPolicy: 'cache-and-network' } + variables, + { pageSize: options.pageSize }, + (data) => data?.tasks ?? [], + (data) => data?.tasksTotal ) - const totalCount = result.data?.tasksTotal - - useEffect(() => { - if (!result.loading && result.data !== undefined) { - setPages((prev) => { - const next = [...prev] - next[pageIndex] = result.data - return next - }) - } - }, [result.loading, result.data, pageIndex]) - - const flattenedTasks = useMemo(() => { - const currentFromCache = !result.loading ? result.data?.tasks : undefined - const before = pages.slice(0, pageIndex).flatMap((p) => p?.tasks ?? []) - const current = currentFromCache ?? pages[pageIndex]?.tasks ?? [] - const after = pages.slice(pageIndex + 1).flatMap((p) => p?.tasks ?? []) - return [...before, ...current, ...after] - }, [pageIndex, pages, result.data?.tasks, result.loading]) - - const hasNextPage = - (totalCount !== undefined && flattenedTasks.length < totalCount) ?? false - - const fetchNextPage = useCallback(() => { - if (hasNextPage && !result.loading) { - setPageIndex((i) => i + 1) - } - }, [hasNextPage, result.loading]) - - const refetch = useCallback(() => { - result.refetch() - }, [result]) - - return { - data: flattenedTasks, - loading: result.loading, - error: result.error, - fetchNextPage, - hasNextPage, - totalCount, - refetch, - } } diff --git a/web/hooks/useAuth.tsx b/web/hooks/useAuth.tsx index 640740b5..5ce4e96e 100644 --- a/web/hooks/useAuth.tsx +++ b/web/hooks/useAuth.tsx @@ -2,12 +2,11 @@ import type { ComponentType, PropsWithChildren, ReactNode } from 'react' import { createContext, useCallback, useContext, useEffect, useState } from 'react' -import { LoadingAnimation } from '@helpwave/hightide' +import { HelpwaveLogo } from '@helpwave/hightide' import { login, logout, onTokenExpiringCallback, removeUser, renewToken, restoreSession } from '@/api/auth/authService' import type { User } from 'oidc-client-ts' import { getConfig } from '@/utils/config' import { usePathname } from 'next/navigation' -import { LoginPage } from '@/components/pages/login' const config = getConfig() @@ -124,27 +123,28 @@ export const AuthProvider = ({ .catch(() => {}) }, []) + useEffect(() => { + if (isIgnored || isUnprotected || identity || isLoading) return + login( + config.auth.redirect_uri + + `?redirect_uri=${encodeURIComponent(window.location.href)}` + ).catch(() => {}) + }, [identity, isLoading, isIgnored, isUnprotected]) + + const authLoadingContent = ( +
+ +
+ ) + let content: ReactNode = children if (!isUnprotected && !isIgnored && !identity) { - if (isLoading) { - content = ( -
- -
- ) - } else { - content = ( - { - await login( - config.auth.redirect_uri + - `?redirect_uri=${encodeURIComponent(window.location.href)}` - ) - return true - }} - /> - ) - } + content = authLoadingContent } return ( diff --git a/web/hooks/usePaginatedQuery.ts b/web/hooks/usePaginatedQuery.ts deleted file mode 100644 index 5f5283a7..00000000 --- a/web/hooks/usePaginatedQuery.ts +++ /dev/null @@ -1,279 +0,0 @@ -import React from 'react' -import { useInfiniteQuery, useQuery } from '@tanstack/react-query' -import type { QueryKey } from '@tanstack/react-query' -import { fetcher } from '@/api/gql/fetcher' - -export type PaginationMode = 'infinite' | 'numbered' - -export interface PaginatedQueryOptions { - queryKey: QueryKey, - queryFn: (page: number, variables: TVariables) => Promise, - variables: TVariables, - pageSize: number, - mode?: PaginationMode, - enabled?: boolean, - refetchInterval?: number | false, - refetchOnWindowFocus?: boolean, - refetchOnMount?: boolean, -} - -export interface PaginatedGraphQLQueryOptions> { - queryKey: QueryKey, - document: string, - baseVariables: TVariables, - pageSize: number, - extractItems: (queryResult: TQueryResult) => TItem[], - extractTotalCount?: (queryResult: TQueryResult) => number | undefined, - mode?: PaginationMode, - enabled?: boolean, - refetchInterval?: number | false, - refetchOnWindowFocus?: boolean, - refetchOnMount?: boolean, -} - -export interface PaginatedQueryResult { - data: TData[], - isLoading: boolean, - isError: boolean, - error: Error | null, - fetchNextPage: () => void, - hasNextPage: boolean, - isFetchingNextPage: boolean, - refetch: () => void, - totalPages?: number, - currentPage?: number, - goToPage?: (page: number) => void, - totalCount?: number, -} - -function useInfinitePaginatedQuery>({ - queryKey, - queryFn, - variables, - pageSize, - enabled, - refetchInterval, - refetchOnWindowFocus, - refetchOnMount, -}: Omit, 'mode'>): PaginatedQueryResult { - const { - data, - isLoading, - isError, - error, - fetchNextPage, - hasNextPage, - isFetchingNextPage, - refetch, - } = useInfiniteQuery({ - queryKey: [...queryKey, variables], - queryFn: ({ pageParam }) => queryFn(pageParam as number, variables), - initialPageParam: 0, - getNextPageParam: (lastPage, allPages) => { - const lastPageData = Array.isArray(lastPage) ? lastPage : [] - if (lastPageData.length < pageSize) { - return undefined - } - return allPages.length - }, - enabled, - refetchInterval, - refetchOnWindowFocus, - refetchOnMount, - }) - - const flattenedData = data?.pages?.flatMap((page) => { - return Array.isArray(page) ? page : [] - }) ?? [] - - return { - data: flattenedData as TData[], - isLoading, - isError, - error: error as Error | null, - fetchNextPage, - hasNextPage: hasNextPage ?? false, - isFetchingNextPage, - refetch: () => { - refetch() - }, - } -} - -function useNumberedPaginatedQuery>({ - queryKey, - queryFn, - variables, - pageSize, - enabled, - refetchInterval, - refetchOnWindowFocus, - refetchOnMount, -}: Omit, 'mode'>): PaginatedQueryResult { - const [currentPage, setCurrentPage] = React.useState(0) - const [allPages, setAllPages] = React.useState([]) - const prevVariablesRef = React.useRef('') - - const variablesKey = JSON.stringify(variables) - - React.useEffect(() => { - if (prevVariablesRef.current !== variablesKey) { - setCurrentPage(0) - setAllPages([]) - prevVariablesRef.current = variablesKey - } - }, [variablesKey]) - - const { - data, - isLoading, - isError, - error, - refetch, - } = useQuery({ - queryKey: [...queryKey, variables, currentPage], - queryFn: () => queryFn(currentPage, variables), - enabled, - refetchInterval, - refetchOnWindowFocus, - refetchOnMount, - }) - - React.useEffect(() => { - if (data !== undefined) { - const pageData = Array.isArray(data) ? data : [] - setAllPages((prev) => { - const newPages = [...prev] - newPages[currentPage] = pageData - return newPages - }) - } - }, [data, currentPage]) - - const flattenedData = allPages.flat() - const lastPageData = allPages[currentPage] ?? [] - const hasNextPage = lastPageData.length >= pageSize - - const fetchNextPage = React.useCallback(() => { - if (hasNextPage && !isLoading) { - setCurrentPage((prev) => prev + 1) - } - }, [hasNextPage, isLoading]) - - const goToPage = React.useCallback((page: number) => { - if (page >= 0 && page !== currentPage) { - setCurrentPage(page) - } - }, [currentPage]) - - return { - data: flattenedData, - isLoading, - isError, - error: error as Error | null, - fetchNextPage, - hasNextPage, - isFetchingNextPage: false, - refetch: () => { - setCurrentPage(0) - setAllPages([]) - refetch() - }, - currentPage, - goToPage, - } -} - -export function usePaginatedQuery>({ - queryKey, - queryFn, - variables, - pageSize, - mode = 'infinite', - enabled = true, - refetchInterval, - refetchOnWindowFocus = true, - refetchOnMount = true, -}: PaginatedQueryOptions): PaginatedQueryResult { - const infiniteResult = useInfinitePaginatedQuery({ - queryKey, - queryFn, - variables, - pageSize, - enabled: mode === 'infinite' && enabled, - refetchInterval, - refetchOnWindowFocus, - refetchOnMount, - }) - - const numberedResult = useNumberedPaginatedQuery({ - queryKey, - queryFn, - variables, - pageSize, - enabled: mode === 'numbered' && enabled, - refetchInterval, - refetchOnWindowFocus, - refetchOnMount, - }) - - return mode === 'infinite' ? infiniteResult : numberedResult -} - -export function usePaginatedGraphQLQuery>({ - queryKey, - document, - baseVariables, - pageSize, - extractItems, - extractTotalCount, - mode = 'infinite', - enabled = true, - refetchInterval, - refetchOnWindowFocus = true, - refetchOnMount = true, -}: PaginatedGraphQLQueryOptions): PaginatedQueryResult { - const [totalCount, setTotalCount] = React.useState(undefined) - - const queryFn = React.useCallback( - async (page: number, variables: TVariables): Promise => { - const paginatedVariables = { - ...variables, - pagination: { - pageIndex: page, - pageSize: pageSize, - }, - } - const result = await fetcher(document, paginatedVariables)() - - if (extractTotalCount) { - const count = extractTotalCount(result) - if (count !== undefined) { - setTotalCount(count) - } - } - - return extractItems(result) - }, - [document, pageSize, extractItems, extractTotalCount] - ) - - const result = usePaginatedQuery({ - queryKey, - queryFn, - variables: baseVariables, - pageSize, - mode, - enabled, - refetchInterval, - refetchOnWindowFocus, - refetchOnMount, - }) - - return { - ...result, - data: result.data as TItem[], - totalCount, - } -} - diff --git a/web/hooks/usePropertyColumnVisibility.ts b/web/hooks/usePropertyColumnVisibility.ts new file mode 100644 index 00000000..09d80f5f --- /dev/null +++ b/web/hooks/usePropertyColumnVisibility.ts @@ -0,0 +1,40 @@ +import { useEffect } from 'react' +import type { Dispatch, SetStateAction } from 'react' +import type { VisibilityState } from '@tanstack/react-table' +import type { PropertyEntity } from '@/api/gql/generated' + +type PropertyDefinitionsData = { + propertyDefinitions?: Array<{ + id: string, + isActive: boolean, + allowedEntities: string[], + }>, +} | null | undefined + +export function usePropertyColumnVisibility( + propertyDefinitionsData: PropertyDefinitionsData, + entity: PropertyEntity, + columnVisibility: VisibilityState, + setColumnVisibility: Dispatch> +): void { + useEffect(() => { + if (!propertyDefinitionsData?.propertyDefinitions) return + + const entityValue = entity as string + const properties = propertyDefinitionsData.propertyDefinitions.filter( + def => def.isActive && def.allowedEntities.includes(entityValue) + ) + const propertyColumnIds = properties.map(prop => `property_${prop.id}`) + const hasPropertyColumnsInVisibility = propertyColumnIds.some( + id => id in columnVisibility + ) + + if (!hasPropertyColumnsInVisibility && propertyColumnIds.length > 0) { + const initialVisibility: VisibilityState = { ...columnVisibility } + propertyColumnIds.forEach(id => { + initialVisibility[id] = false + }) + setColumnVisibility(initialVisibility) + } + }, [propertyDefinitionsData, entity, columnVisibility, setColumnVisibility]) +} diff --git a/web/hooks/useTableState.ts b/web/hooks/useTableState.ts new file mode 100644 index 00000000..882a9b1a --- /dev/null +++ b/web/hooks/useTableState.ts @@ -0,0 +1,76 @@ +import type { + ColumnFiltersState, + PaginationState, + SortingState, + VisibilityState +} from '@tanstack/react-table' +import type { Dispatch, SetStateAction } from 'react' +import { useStateWithLocalStorage } from '@/hooks/useStateWithLocalStorage' + +const defaultPagination: PaginationState = { + pageSize: 10, + pageIndex: 0, +} + +const defaultSorting: SortingState = [] +const defaultFilters: ColumnFiltersState = [] +const defaultColumnVisibility: VisibilityState = {} + +export type UseTableStateOptions = { + defaultSorting?: SortingState, + defaultPagination?: PaginationState, + defaultFilters?: ColumnFiltersState, + defaultColumnVisibility?: VisibilityState, +} + +export type UseTableStateResult = { + pagination: PaginationState, + setPagination: Dispatch>, + sorting: SortingState, + setSorting: Dispatch>, + filters: ColumnFiltersState, + setFilters: Dispatch>, + columnVisibility: VisibilityState, + setColumnVisibility: Dispatch>, +} + +export function useTableState( + storageKeyPrefix: string, + options: UseTableStateOptions = {} +): UseTableStateResult { + const { + defaultSorting: initialSorting = defaultSorting, + defaultPagination: initialPagination = defaultPagination, + defaultFilters: initialFilters = defaultFilters, + defaultColumnVisibility: initialColumnVisibility = defaultColumnVisibility, + } = options + + const [pagination, setPagination] = useStateWithLocalStorage({ + key: `${storageKeyPrefix}-column-pagination`, + defaultValue: initialPagination, + }) + const [sorting, setSorting] = useStateWithLocalStorage({ + key: `${storageKeyPrefix}-column-sorting`, + defaultValue: initialSorting, + }) + const [filters, setFilters] = useStateWithLocalStorage({ + key: `${storageKeyPrefix}-column-filters`, + defaultValue: initialFilters, + }) + const [columnVisibility, setColumnVisibility] = + useStateWithLocalStorage({ + key: `${storageKeyPrefix}-column-visibility`, + defaultValue: initialColumnVisibility, + }) + + return { + pagination, + setPagination, + sorting, + setSorting, + filters, + setFilters, + columnVisibility, + setColumnVisibility, + } +} diff --git a/web/hooks/useViewToggle.ts b/web/hooks/useViewToggle.ts deleted file mode 100644 index d0c3bcc2..00000000 --- a/web/hooks/useViewToggle.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { useState, useEffect } from 'react' - -type ViewType = 'table' | 'card' - -const STORAGE_KEY_PATIENT = 'patient-view-type' -const STORAGE_KEY_TASK = 'task-view-type' - -export const usePatientViewToggle = () => { - const [viewType, setViewType] = useState('table') - - useEffect(() => { - if (typeof window !== 'undefined') { - const stored = localStorage.getItem(STORAGE_KEY_PATIENT) as ViewType | null - if (stored === 'table' || stored === 'card') { - setViewType(stored) - } - } - }, []) - - const toggleView = (newViewType: ViewType) => { - setViewType(newViewType) - if (typeof window !== 'undefined') { - localStorage.setItem(STORAGE_KEY_PATIENT, newViewType) - } - } - - return { viewType, toggleView } -} - -export const useTaskViewToggle = () => { - const [viewType, setViewType] = useState('table') - - useEffect(() => { - if (typeof window !== 'undefined') { - const stored = localStorage.getItem(STORAGE_KEY_TASK) as ViewType | null - if (stored === 'table' || stored === 'card') { - setViewType(stored) - } - } - }, []) - - const toggleView = (newViewType: ViewType) => { - setViewType(newViewType) - if (typeof window !== 'undefined') { - localStorage.setItem(STORAGE_KEY_TASK, newViewType) - } - } - - return { viewType, toggleView } -} - diff --git a/web/i18n/translations.ts b/web/i18n/translations.ts index 55f38e06..58e5064d 100644 --- a/web/i18n/translations.ts +++ b/web/i18n/translations.ts @@ -5,7 +5,7 @@ import type { Translation } from '@helpwave/internationalization' import { TranslationGen } from '@helpwave/internationalization' -export const tasksTranslationLocales = ['de-DE', 'en-US'] as const +export const tasksTranslationLocales = ['de-DE', 'en-US', 'es-ES', 'fr-FR', 'nl-NL', 'pt-BR'] as const export type TasksTranslationLocales = typeof tasksTranslationLocales[number] @@ -16,22 +16,16 @@ export type TasksTranslationEntries = { 'addProperty': string, 'addTask': string, 'admitPatient': string, - 'age': string, 'anonymous': string, 'anonymousSubmission': string, 'archivedPropertyDescription': string, 'archiveProperty': string, - 'assignedLocation': string, 'assignedTo': string, - 'assignee': string, 'authenticationFailed': string, 'birthdate': string, 'cancel': string, - 'cardView': string, - 'chooseLanguage': string, - 'chooseTheme': string, + 'clear': string, 'clearCache': string, - 'clickToAdd': string, 'clinic': string, 'clinics': string, 'closedTasks': string, @@ -46,12 +40,9 @@ export type TasksTranslationEntries = { 'connectionConnecting': string, 'connectionDisconnected': string, 'create': string, - 'createdAt': string, 'createTask': string, - 'currentPatient': string, 'currentTime': string, 'dashboard': string, - 'dashboardWelcome': (values: { name: string }) => string, 'dashboardWelcomeAfternoon': (values: { name: string }) => string, 'dashboardWelcomeDescription': string, 'dashboardWelcomeEvening': (values: { name: string }) => string, @@ -61,7 +52,6 @@ export type TasksTranslationEntries = { 'delete': string, 'deletePatient': string, 'deletePatientConfirmation': string, - 'deleteTaskConfirmation': string, 'description': string, 'descriptionPlaceholder': string, 'deselectAll': string, @@ -76,7 +66,6 @@ export type TasksTranslationEntries = { 'editPatient': string, 'editTask': string, 'enterFeedback': string, - 'enterName': string, 'error': string, 'errorOccurred': string, 'estimatedTime': string, @@ -94,17 +83,12 @@ export type TasksTranslationEntries = { 'install': string, 'installApp': string, 'installAppDescription': string, - 'itsYou': string, + 'language': string, 'lastName': string, - 'lastUpdate': string, - 'loading': string, 'location': string, 'locationBed': string, - 'locationChipsShowFullPath': string, 'locationClinic': string, 'locationRoom': string, - 'locations': string, - 'locationTeam': string, 'locationType': (values: { type: string }) => string, 'locationWard': string, 'login': string, @@ -118,27 +102,17 @@ export type TasksTranslationEntries = { 'myOpenTasks': string, 'myTasks': string, 'name': string, - 'nBed': (values: { count: number }) => string, - 'nCurrentlyPatients': (values: { count: number }) => string, - 'newestAdmissions': string, - 'newPatient': string, + 'no': string, 'noClosedTasks': string, 'noLocationsFound': string, 'noNotifications': string, 'noOpenTasks': string, 'noPatient': string, 'noResultsFound': string, - 'nOrganization': (values: { count: number }) => string, 'notAssigned': string, - 'notes': string, 'notifications': string, 'nPatient': (values: { count: number }) => string, - 'nProperties': (values: { count: number }) => string, - 'nRoom': (values: { count: number }) => string, 'nTask': (values: { count: number }) => string, - 'nTeam': (values: { count: number }) => string, - 'nWard': (values: { count: number }) => string, - 'nYear': (values: { count: number }) => string, 'occupancy': string, 'ok': string, 'openSurvey': string, @@ -146,7 +120,6 @@ export type TasksTranslationEntries = { 'option': string, 'organizations': string, 'overview': string, - 'overwrite': string, 'pages.404.notFound': string, 'pages.404.notFoundDescription1': string, 'pages.404.notFoundDescription2': string, @@ -162,23 +135,16 @@ export type TasksTranslationEntries = { 'pickPositionDescription': string, 'pickTeams': string, 'pickTeamsDescription': string, - 'place': string, 'position': string, 'preferences': string, - 'print': string, - 'printOnlyAvailableInTableMode': string, 'priority': (values: { priority: string }) => string, 'priorityLabel': string, 'priorityNone': string, 'privacy': string, - 'private': string, - 'profile': string, 'properties': string, 'property': string, - 'public': string, - 'publish': string, + 'pThemes': (values: { count: number }) => string, 'rAdd': (values: { name: string }) => string, - 'rClickToAdd': (values: { name: string }) => string, 'recentPatients': string, 'recentTasks': string, 'rEdit': (values: { name: string }) => string, @@ -186,10 +152,8 @@ export type TasksTranslationEntries = { 'removeProperty': string, 'removePropertyConfirmation': string, 'retakeSurvey': string, - 'returnHome': string, - 'rooms': string, 'rShow': (values: { name: string }) => string, - 'save': string, + 'search': string, 'searchLocations': string, 'searchUserOrTeam': string, 'searchUsersOrTeams': string, @@ -200,9 +164,6 @@ export type TasksTranslationEntries = { 'selectLocation': string, 'selectLocationDescription': string, 'selectOptions': string, - 'selectOrganization': string, - 'selectOrganizations': string, - 'selectPatient': string, 'selectPosition': string, 'selectRootLocation': string, 'selectRootLocationDescription': string, @@ -211,10 +172,7 @@ export type TasksTranslationEntries = { 'settingsDescription': string, 'sex': string, 'shiftHandover': string, - 'shiftHandoverDescription': string, - 'showAllPatients': string, 'showAllTasks': string, - 'showDone': string, 'showTeamTasks': string, 'sPropertySubjectType': (values: { subject: string }) => string, 'sPropertyType': (values: { type: string }) => string, @@ -225,36 +183,27 @@ export type TasksTranslationEntries = { 'subjectType': string, 'submissionDetails': string, 'submit': string, - 'submitAnonymously': string, 'submittingAs': (values: { name: string }) => string, 'surveyDescription': string, 'surveyTitle': string, 'system': string, - 'tableView': string, 'task': string, 'tasks': string, - 'taskStatus': (values: { status: string }) => string, 'tasksUpdatedRecently': string, 'taskTitlePlaceholder': string, 'teams': string, 'themeMode': (values: { theme: string }) => string, - 'time.today': string, 'title': string, 'totalPatients': string, 'type': string, - 'unassigned': string, 'updated': string, - 'updateLocation': string, - 'updateLocationConfirmation': string, 'url': string, 'userInformation': string, - 'username': string, 'users': string, - 'visibility': string, 'waitingForPatient': string, 'waitPatient': string, 'wards': string, - 'withName': string, + 'yes': string, } export const tasksTranslation: Translation> = { @@ -265,22 +214,16 @@ export const tasksTranslation: Translation { - return `Guten Morgen, ${name}!` - }, 'dashboardWelcomeAfternoon': ({ name }): string => { return `Guten Nachmittag, ${name}!` }, @@ -331,7 +269,6 @@ export const tasksTranslation: Translation { return TranslationGen.resolveSelect(type, { 'CLINIC': `Klinik`, @@ -397,37 +328,14 @@ export const tasksTranslation: Translation { - return TranslationGen.resolvePlural(count, { - '=1': `${count} Bett`, - 'other': `${count} Betten`, - }) - }, - 'nCurrentlyPatients': ({ count }): string => { - let _out: string = '' - _out += `Aktuell ` - _out += TranslationGen.resolvePlural(count, { - '=1': `${count} Patient`, - 'other': `${count} Patienten`, - }) - return _out - }, - 'newestAdmissions': `Neueste Aufnahmen`, - 'newPatient': `Neuer Patient`, + 'no': `Nein`, 'noClosedTasks': `Keine erledigten Aufgaben`, 'noLocationsFound': `Keine Standorte gefunden`, 'noNotifications': `Keine aktuellen Updates`, 'noOpenTasks': `Keine offenen Aufgaben`, 'noPatient': `Kein Patient`, 'noResultsFound': `Keine Ergebnisse gefunden`, - 'nOrganization': ({ count }): string => { - return TranslationGen.resolvePlural(count, { - '=1': `${count} Organisation`, - 'other': `${count} Organisationen`, - }) - }, 'notAssigned': `Nicht zugewiesen`, - 'notes': `Notizen`, 'notifications': `Benachrichtigungen`, 'nPatient': ({ count }): string => { return TranslationGen.resolvePlural(count, { @@ -435,42 +343,12 @@ export const tasksTranslation: Translation { - return TranslationGen.resolvePlural(count, { - '=1': `${count} Eigenschaft`, - 'other': `${count} Eigenschaften`, - }) - }, - 'nRoom': ({ count }): string => { - return TranslationGen.resolvePlural(count, { - '=1': `${count} Zimmer`, - 'other': `${count} Zimmer`, - }) - }, 'nTask': ({ count }): string => { return TranslationGen.resolvePlural(count, { '=1': `${count} Aufgabe`, 'other': `${count} Aufgaben`, }) }, - 'nTeam': ({ count }): string => { - return TranslationGen.resolvePlural(count, { - '=1': `${count} Team`, - 'other': `${count} Teams`, - }) - }, - 'nWard': ({ count }): string => { - return TranslationGen.resolvePlural(count, { - '=1': `${count} Station`, - 'other': `${count} Stationen`, - }) - }, - 'nYear': ({ count }): string => { - return TranslationGen.resolvePlural(count, { - '=1': `${count} Jahr alt`, - 'other': `${count} Jahre alt`, - }) - }, 'occupancy': `Belegung`, 'ok': `OK`, 'openSurvey': `Umfrage öffnen`, @@ -478,7 +356,6 @@ export const tasksTranslation: Translation { return TranslationGen.resolveSelect(priority, { 'P1': `Normal`, @@ -519,18 +393,17 @@ export const tasksTranslation: Translation { + return TranslationGen.resolvePlural(count, { + '=1': `Design`, + 'other': `Designs`, + }) + }, 'rAdd': ({ name }): string => { return `${name} hinzufügen` }, - 'rClickToAdd': ({ name }): string => { - return `Klicken um ${name} hinzuzufügen` - }, 'recentPatients': `Deine kürzlichen Patienten`, 'recentTasks': `Deine kürzlichen Aufgaben`, 'rEdit': ({ name }): string => { @@ -540,12 +413,10 @@ export const tasksTranslation: Translation { return `${name} anzeigen` }, - 'save': `Speichern`, + 'search': `Suchen`, 'searchLocations': `Standorte suchen...`, 'searchUserOrTeam': `Nach Benutzer (oder Team) suchen...`, 'searchUsersOrTeams': `Benutzer oder Teams suchen...`, @@ -556,9 +427,6 @@ export const tasksTranslation: Translation { return TranslationGen.resolveSelect(subject, { @@ -599,24 +464,14 @@ export const tasksTranslation: Translation { return `Übermitteln als ${name}` }, 'surveyDescription': `Ihr Feedback ist wertvoll für uns. Bitte nehmen Sie sich einen Moment Zeit, um unsere Umfrage auszufüllen.`, 'surveyTitle': `Helfen Sie uns, helpwave tasks zu verbessern`, 'system': `System`, - 'tableView': `Tabellenansicht`, 'task': `Aufgabe`, 'tasks': `Aufgaben`, - 'taskStatus': ({ status }): string => { - return TranslationGen.resolveSelect(status, { - 'overdue': `Überfällig`, - 'upcoming': `Anstehend`, - 'done': `Fertig`, - 'other': `-`, - }) - }, 'tasksUpdatedRecently': `Kürzlich aktualisierte Aufgaben`, 'taskTitlePlaceholder': `Was muss erledigt werden?`, 'teams': `Teams`, @@ -628,23 +483,17 @@ export const tasksTranslation: Translation { - return `Good Morning, ${name}!` - }, 'dashboardWelcomeAfternoon': ({ name }): string => { return `Good Afternoon, ${name}!` }, @@ -719,7 +557,6 @@ export const tasksTranslation: Translation { return TranslationGen.resolveSelect(type, { 'CLINIC': `Clinic`, @@ -785,37 +616,14 @@ export const tasksTranslation: Translation { - return TranslationGen.resolvePlural(count, { - '=1': `${count} Bed`, - 'other': `${count} Beds`, - }) - }, - 'nCurrentlyPatients': ({ count }): string => { - let _out: string = '' - _out += `Currently ` - _out += TranslationGen.resolvePlural(count, { - '=1': `${count} Patient`, - 'other': `${count} Patients`, - }) - return _out - }, - 'newestAdmissions': `Newest admissions`, - 'newPatient': `New Patient`, + 'no': `No`, 'noClosedTasks': `No closed tasks`, 'noLocationsFound': `No locations found`, 'noNotifications': `No recent updates`, 'noOpenTasks': `No open tasks`, 'noPatient': `No Patient`, 'noResultsFound': `No results found`, - 'nOrganization': ({ count }): string => { - return TranslationGen.resolvePlural(count, { - '=1': `${count} Organization`, - 'other': `${count} Organizations`, - }) - }, 'notAssigned': `Not assigned`, - 'notes': `notes`, 'notifications': `Notifications`, 'nPatient': ({ count }): string => { return TranslationGen.resolvePlural(count, { @@ -823,42 +631,12 @@ export const tasksTranslation: Translation { - return TranslationGen.resolvePlural(count, { - '=1': `${count} Property`, - 'other': `${count} Properties`, - }) - }, - 'nRoom': ({ count }): string => { - return TranslationGen.resolvePlural(count, { - '=1': `${count} Room`, - 'other': `${count} Rooms`, - }) - }, 'nTask': ({ count }): string => { return TranslationGen.resolvePlural(count, { '=1': `${count} Task`, 'other': `${count} Tasks`, }) }, - 'nTeam': ({ count }): string => { - return TranslationGen.resolvePlural(count, { - '=1': `${count} Team`, - 'other': `${count} Teams`, - }) - }, - 'nWard': ({ count }): string => { - return TranslationGen.resolvePlural(count, { - '=1': `${count} Ward`, - 'other': `${count} Wards`, - }) - }, - 'nYear': ({ count }): string => { - return TranslationGen.resolvePlural(count, { - '=1': `${count} year old`, - 'other': `${count} years old`, - }) - }, 'occupancy': `Occupancy`, 'ok': `OK`, 'openSurvey': `Open Survey`, @@ -866,7 +644,6 @@ export const tasksTranslation: Translation { return TranslationGen.resolveSelect(priority, { 'P1': `Normal`, @@ -907,17 +681,17 @@ export const tasksTranslation: Translation { + return TranslationGen.resolvePlural(count, { + '=1': `Theme`, + 'other': `Themes`, + }) + }, 'rAdd': ({ name }): string => { return `Add ${name}` }, - 'rClickToAdd': ({ name }): string => { - return `Click to add ${name}!` - }, 'recentPatients': `Your Recent Patients`, 'recentTasks': `Your Recent Tasks`, 'rEdit': ({ name }): string => { @@ -927,12 +701,10 @@ export const tasksTranslation: Translation { return `Show ${name}` }, - 'save': `Save`, + 'search': `Search`, 'searchLocations': `Search locations...`, 'searchUserOrTeam': `Search for user (or team)...`, 'searchUsersOrTeams': `Search users or teams...`, @@ -943,9 +715,6 @@ export const tasksTranslation: Translation { return TranslationGen.resolveSelect(subject, { @@ -986,24 +752,14 @@ export const tasksTranslation: Translation { return `Submitting as ${name}` }, 'surveyDescription': `Your feedback is valuable to us. Please take a moment to complete our survey.`, 'surveyTitle': `Help us to improve helpwave tasks`, 'system': `System`, - 'tableView': `Table View`, 'task': `Task`, 'tasks': `Tasks`, - 'taskStatus': ({ status }): string => { - return TranslationGen.resolveSelect(status, { - 'overdue': `Overdue`, - 'upcoming': `Upcoming`, - 'done': `Done`, - 'other': `-`, - }) - }, 'tasksUpdatedRecently': `Tasks updated recently`, 'taskTitlePlaceholder': `What needs to be done?`, 'teams': `Teams`, @@ -1015,23 +771,1172 @@ export const tasksTranslation: Translation { + let _out: string = '' + _out += `¿Está seguro de que desea transferir ` + _out += TranslationGen.resolvePlural(taskCount, { + '=1': `${taskCount} tarea abierta`, + 'other': `${taskCount} tareas abiertas`, + }) + _out += ` a ${name}?` + return _out + }, + 'conflictDetected': `Conflicto detectado`, + 'connectionConnected': `Conectado`, + 'connectionConnecting': `Conectando…`, + 'connectionDisconnected': `Desconectado`, + 'create': `Crear`, + 'createTask': `Crear tarea`, + 'currentTime': `Hora actual`, + 'dashboard': `Panel`, + 'dashboardWelcomeAfternoon': ({ name }): string => { + return `Buenas tardes, ${name}!` + }, + 'dashboardWelcomeDescription': `Esto es lo que ocurre hoy.`, + 'dashboardWelcomeEvening': ({ name }): string => { + return `Buenas noches, ${name}!` + }, + 'dashboardWelcomeMorning': ({ name }): string => { + return `Buenos días, ${name}!` + }, + 'dashboardWelcomeNight': ({ name }): string => { + return `Buenas noches, ${name}!` + }, + 'dashboardWelcomeNoon': ({ name }): string => { + return `Buenas tardes, ${name}!` + }, + 'delete': `Eliminar`, + 'deletePatient': `Eliminar paciente`, + 'deletePatientConfirmation': `¿Está seguro de que desea eliminar este paciente? Esta acción no se puede deshacer.`, + 'description': `Descripción`, + 'descriptionPlaceholder': `Añadir más detalles...`, + 'deselectAll': `Deseleccionar todo`, + 'developmentAndPreviewInstance': `Instancia de desarrollo y vista previa`, + 'dischargePatient': `Dar de alta al paciente`, + 'dischargePatientConfirmation': `¿Está seguro de que desea dar de alta a este paciente? Esta acción cambiará el estado del paciente.`, + 'dismiss': `Cerrar`, + 'dismissAll': `Cerrar todo`, + 'diverse': `Diverso`, + 'done': `Hecho`, + 'dueDate': `Fecha de vencimiento`, + 'editPatient': `Editar paciente`, + 'editTask': `Editar tarea`, + 'enterFeedback': `Escriba aquí sus comentarios...`, + 'error': `Error`, + 'errorOccurred': `Se ha producido un error`, + 'estimatedTime': `Tiempo estimado (minutos)`, + 'expandAll': `Expandir todo`, + 'feedback': `Comentarios`, + 'feedbackDescription': `Comparta sus comentarios, reporte errores o sugiera mejoras.`, + 'female': `Femenino`, + 'filterAll': `Todos`, + 'filterUndone': `Pendientes`, + 'firstName': `Nombre`, + 'freeBeds': `Camas libres`, + 'homePage': `Página principal`, + 'imprint': `Aviso legal`, + 'inactive': `Inactivo`, + 'install': `Instalar`, + 'installApp': `Instalar aplicación`, + 'installAppDescription': `Instale helpwave tasks para una mejor experiencia con soporte offline y acceso más rápido.`, + 'language': `Idioma`, + 'lastName': `Apellido`, + 'location': `Ubicación`, + 'locationBed': `Cama`, + 'locationClinic': `Clínica`, + 'locationRoom': `Habitación`, + 'locationType': ({ type }): string => { + return TranslationGen.resolveSelect(type, { + 'CLINIC': `Clínica`, + 'WARD': `Planta`, + 'TEAM': `Equipo`, + 'ROOM': `Habitación`, + 'BED': `Cama`, + 'other': `Otro`, + }) + }, + 'locationWard': `Planta`, + 'login': `Iniciar sesión`, + 'loginRequired': `Inicio de sesión requerido`, + 'loginRequiredDescription': `Para usar este sitio debe iniciar sesión.`, + 'logout': `Cerrar sesión`, + 'male': `Masculino`, + 'markPatientDead': `Marcar paciente como fallecido`, + 'markPatientDeadConfirmation': `¿Está seguro de que desea marcar este paciente como fallecido?`, + 'myFavorites': `Mis favoritos`, + 'myOpenTasks': `Mis tareas abiertas`, + 'myTasks': `Mis tareas`, + 'name': `Nombre`, + 'no': `No`, + 'noClosedTasks': `No hay tareas cerradas`, + 'noLocationsFound': `No se encontraron ubicaciones`, + 'noNotifications': `Sin actualizaciones recientes`, + 'noOpenTasks': `No hay tareas abiertas`, + 'noPatient': `Sin paciente`, + 'noResultsFound': `No se encontraron resultados`, + 'notAssigned': `No asignado`, + 'notifications': `Notificaciones`, + 'nPatient': ({ count }): string => { + return TranslationGen.resolvePlural(count, { + '=1': `${count} Paciente`, + 'other': `${count} Pacientes`, + }) + }, + 'nTask': ({ count }): string => { + return TranslationGen.resolvePlural(count, { + '=1': `${count} Tarea`, + 'other': `${count} Tareas`, + }) + }, + 'occupancy': `Ocupación`, + 'ok': `OK`, + 'openSurvey': `Abrir encuesta`, + 'openTasks': `Tareas abiertas`, + 'option': `Opción`, + 'organizations': `Organizaciones`, + 'overview': `Resumen`, + 'pages.404.notFound': `404 - Página no encontrada`, + 'pages.404.notFoundDescription1': `Esta no es la página que busca`, + 'pages.404.notFoundDescription2': `Volver a la`, + 'patient': `Paciente`, + 'patientActions': `Acciones del paciente`, + 'patientData': `Datos`, + 'patients': `Pacientes`, + 'patientState': ({ state }): string => { + return TranslationGen.resolveSelect(state, { + 'WAIT': `En espera`, + 'ADMITTED': `Ingresado`, + 'DISCHARGED': `Alta`, + 'DEAD': `Fallecido`, + 'other': `Desconocido`, + }) + }, + 'patientsUpdatedRecently': `Pacientes actualizados recientemente`, + 'pickClinic': `Elegir la clínica`, + 'pickClinicDescription': `Seleccione la clínica para este paciente. Solo se pueden seleccionar ubicaciones de tipo clínica.`, + 'pickPosition': `Elegir la ubicación`, + 'pickPositionDescription': `Seleccione la ubicación para este paciente. Puede elegir hospital, consulta, clínica, planta, cama o habitación.`, + 'pickTeams': `Elegir equipos`, + 'pickTeamsDescription': `Seleccione uno o más equipos para este paciente. Puede elegir clínica, equipo, consulta u hospital.`, + 'position': `Ubicación`, + 'preferences': `Preferencias`, + 'priority': ({ priority }): string => { + return TranslationGen.resolveSelect(priority, { + 'P1': `Normal`, + 'P2': `Media`, + 'P3': `Alta`, + 'P4': `Crítica`, + 'other': `-`, + }) + }, + 'priorityLabel': `Prioridad`, + 'priorityNone': `Ninguna`, + 'privacy': `Privacidad`, + 'properties': `Propiedades`, + 'property': `Propiedad`, + 'pThemes': ({ count }): string => { + return TranslationGen.resolvePlural(count, { + '=1': `Tema`, + 'other': `Temas`, + }) + }, + 'rAdd': ({ name }): string => { + return `Añadir ${name}` + }, + 'recentPatients': `Tus pacientes recientes`, + 'recentTasks': `Tus tareas recientes`, + 'rEdit': ({ name }): string => { + return `Actualizar ${name}!` + }, + 'refreshing': `Actualizando…`, + 'removeProperty': `Eliminar propiedad`, + 'removePropertyConfirmation': `¿Está seguro de que desea eliminar esta propiedad? Esta acción no se puede deshacer.`, + 'retakeSurvey': `Repetir encuesta`, + 'rShow': ({ name }): string => { + return `Mostrar ${name}` + }, + 'search': `Buscar`, + 'searchLocations': `Buscar ubicaciones...`, + 'searchUserOrTeam': `Buscar usuario (o equipo)...`, + 'searchUsersOrTeams': `Buscar usuarios o equipos...`, + 'security': `Seguridad`, + 'selectAll': `Seleccionar todo`, + 'selectAssignee': `Asignar a...`, + 'selectClinic': `Seleccionar clínica`, + 'selectLocation': `Seleccionar ubicación`, + 'selectLocationDescription': `Seleccione la ubicación asignada para el paciente.`, + 'selectOptions': `Opciones`, + 'selectPosition': `Seleccionar ubicación`, + 'selectRootLocation': `Seleccionar ubicación raíz`, + 'selectRootLocationDescription': `Seleccione las ubicaciones raíz que definen su ámbito. Solo se pueden seleccionar hospitales, consultas, clínicas y equipos. Al seleccionar una ubicación padre se incluyen todas las hijas.`, + 'selectTeams': `Seleccionar equipos`, + 'settings': `Ajustes`, + 'settingsDescription': `Aquí puede cambiar la configuración de la aplicación.`, + 'sex': `Sexo`, + 'shiftHandover': `Traspaso de turno`, + 'showAllTasks': `Mostrar todas las tareas`, + 'showTeamTasks': `Mostrar tareas del equipo`, + 'sPropertySubjectType': ({ subject }): string => { + return TranslationGen.resolveSelect(subject, { + 'patient': `Paciente`, + 'task': `Tarea`, + 'other': `Tipo de sujeto no definido`, + }) + }, + 'sPropertyType': ({ type }): string => { + return TranslationGen.resolveSelect(type, { + 'multiSelect': `Selección múltiple`, + 'singleSelect': `Selección única`, + 'number': `Número`, + 'text': `Texto`, + 'date': `Fecha`, + 'dateTime': `Punto temporal`, + 'checkbox': `Casilla`, + 'user': `Usuario`, + 'other': `Tipo no definido`, + }) + }, + 'stagingModalDisclaimerMarkdown': `Esta instancia pública de helpwave tasks es para \\b{desarrollo y vista previa}. Asegúrese de \\b{solo} introducir \\b{datos de prueba no confidenciales}. Esta instancia puede \\negative{\\b{eliminarse en cualquier momento}}`, + 'startRecording': `Iniciar grabación`, + 'status': `Estado`, + 'stopRecording': `Detener grabación`, + 'subjectType': `Tipo de sujeto`, + 'submissionDetails': `Detalles del envío`, + 'submit': `Enviar`, + 'submittingAs': ({ name }): string => { + return `Enviando como ${name}` + }, + 'surveyDescription': `Su opinión es valiosa. Dedique un momento a completar nuestra encuesta.`, + 'surveyTitle': `Ayúdenos a mejorar helpwave tasks`, + 'system': `Sistema`, + 'task': `Tarea`, + 'tasks': `Tareas`, + 'tasksUpdatedRecently': `Tareas actualizadas recientemente`, + 'taskTitlePlaceholder': `¿Qué hay que hacer?`, + 'teams': `Equipos`, + 'themeMode': ({ theme }): string => { + return TranslationGen.resolveSelect(theme, { + 'dark': `Oscuro`, + 'light': `Claro`, + 'system': `Sistema`, + 'other': `Sistema`, + }) + }, + 'title': `Título`, + 'totalPatients': `Total de pacientes`, + 'type': `Tipo`, + 'updated': `Actualizado`, + 'url': `URL`, + 'userInformation': `Información del usuario`, + 'users': `Usuarios`, + 'waitingForPatient': `En espera de paciente`, + 'waitPatient': `Poner paciente en espera`, + 'wards': `Plantas`, + 'yes': `Sí` + }, + 'fr-FR': { + 'account': `Compte`, + 'active': `Actif`, + 'addPatient': `Ajouter un patient`, + 'addProperty': `Ajouter une propriété`, + 'addTask': `Ajouter une tâche`, + 'admitPatient': `Admettre le patient`, + 'anonymous': `Anonyme`, + 'anonymousSubmission': `Envoi anonyme`, + 'archivedPropertyDescription': `Les propriétés archivées ne peuvent plus être assignées aux objets.`, + 'archiveProperty': `Archiver la propriété`, + 'assignedTo': `Assigné à`, + 'authenticationFailed': `Échec de l'authentification`, + 'birthdate': `Date de naissance`, + 'cancel': `Annuler`, + 'clear': `Effacer`, + 'clearCache': `Vider le cache`, + 'clinic': `Clinique`, + 'clinics': `Cliniques`, + 'closedTasks': `Tâches terminées`, + 'collapseAll': `Tout réduire`, + 'confirm': `Confirmer`, + 'confirmSelection': `Confirmer la sélection`, + 'confirmShiftHandover': `Confirmer la passation`, + 'confirmShiftHandoverDescription': `Êtes-vous sûr de vouloir transférer toutes les tâches ouvertes à l'utilisateur sélectionné ?`, + 'confirmShiftHandoverDescriptionWithName': ({ taskCount, name }): string => { + let _out: string = '' + _out += `Êtes-vous sûr de vouloir transférer ` + _out += TranslationGen.resolvePlural(taskCount, { + '=1': `${taskCount} tâche ouverte`, + 'other': `${taskCount} tâches ouvertes`, + }) + _out += ` à ${name} ?` + return _out + }, + 'conflictDetected': `Conflit détecté`, + 'connectionConnected': `Connecté`, + 'connectionConnecting': `Connexion…`, + 'connectionDisconnected': `Déconnecté`, + 'create': `Créer`, + 'createTask': `Créer une tâche`, + 'currentTime': `Heure actuelle`, + 'dashboard': `Tableau de bord`, + 'dashboardWelcomeAfternoon': ({ name }): string => { + return `Bon après-midi, ${name} !` + }, + 'dashboardWelcomeDescription': `Voici ce qui se passe aujourd'hui.`, + 'dashboardWelcomeEvening': ({ name }): string => { + return `Bonsoir, ${name} !` + }, + 'dashboardWelcomeMorning': ({ name }): string => { + return `Bonjour, ${name} !` + }, + 'dashboardWelcomeNight': ({ name }): string => { + return `Bonne nuit, ${name} !` + }, + 'dashboardWelcomeNoon': ({ name }): string => { + return `Bon après-midi, ${name} !` + }, + 'delete': `Supprimer`, + 'deletePatient': `Supprimer le patient`, + 'deletePatientConfirmation': `Êtes-vous sûr de vouloir supprimer ce patient ? Cette action est irréversible.`, + 'description': `Description`, + 'descriptionPlaceholder': `Ajouter plus de détails...`, + 'deselectAll': `Tout désélectionner`, + 'developmentAndPreviewInstance': `Instance de développement et d'aperçu`, + 'dischargePatient': `Sortir le patient`, + 'dischargePatientConfirmation': `Êtes-vous sûr de vouloir sortir ce patient ? Cette action modifiera son état.`, + 'dismiss': `Fermer`, + 'dismissAll': `Tout fermer`, + 'diverse': `Divers`, + 'done': `Terminé`, + 'dueDate': `Date d'échéance`, + 'editPatient': `Modifier le patient`, + 'editTask': `Modifier la tâche`, + 'enterFeedback': `Entrez vos commentaires ici...`, + 'error': `Erreur`, + 'errorOccurred': `Une erreur s'est produite`, + 'estimatedTime': `Temps estimé (minutes)`, + 'expandAll': `Tout développer`, + 'feedback': `Commentaires`, + 'feedbackDescription': `Partagez vos commentaires, signalez des bugs ou suggérez des améliorations.`, + 'female': `Féminin`, + 'filterAll': `Tous`, + 'filterUndone': `En attente`, + 'firstName': `Prénom`, + 'freeBeds': `Lits disponibles`, + 'homePage': `Page d'accueil`, + 'imprint': `Mentions légales`, + 'inactive': `Inactif`, + 'install': `Installer`, + 'installApp': `Installer l'application`, + 'installAppDescription': `Installez helpwave tasks pour une meilleure expérience avec prise en charge hors ligne et accès plus rapide.`, + 'language': `Langue`, + 'lastName': `Nom de famille`, + 'location': `Emplacement`, + 'locationBed': `Lit`, + 'locationClinic': `Clinique`, + 'locationRoom': `Chambre`, + 'locationType': ({ type }): string => { + return TranslationGen.resolveSelect(type, { + 'CLINIC': `Clinique`, + 'WARD': `Service`, + 'TEAM': `Équipe`, + 'ROOM': `Chambre`, + 'BED': `Lit`, + 'other': `Autre`, + }) + }, + 'locationWard': `Service`, + 'login': `Connexion`, + 'loginRequired': `Connexion requise`, + 'loginRequiredDescription': `Pour utiliser ce site, vous devez être connecté.`, + 'logout': `Déconnexion`, + 'male': `Masculin`, + 'markPatientDead': `Déclarer le patient décédé`, + 'markPatientDeadConfirmation': `Êtes-vous sûr de vouloir déclarer ce patient décédé ?`, + 'myFavorites': `Mes favoris`, + 'myOpenTasks': `Mes tâches ouvertes`, + 'myTasks': `Mes tâches`, + 'name': `Nom`, + 'no': `Non`, + 'noClosedTasks': `Aucune tâche terminée`, + 'noLocationsFound': `Aucun emplacement trouvé`, + 'noNotifications': `Aucune mise à jour récente`, + 'noOpenTasks': `Aucune tâche ouverte`, + 'noPatient': `Aucun patient`, + 'noResultsFound': `Aucun résultat trouvé`, + 'notAssigned': `Non assigné`, + 'notifications': `Notifications`, + 'nPatient': ({ count }): string => { + return TranslationGen.resolvePlural(count, { + '=1': `${count} Patient`, + 'other': `${count} Patients`, + }) + }, + 'nTask': ({ count }): string => { + return TranslationGen.resolvePlural(count, { + '=1': `${count} Tâche`, + 'other': `${count} Tâches`, + }) + }, + 'occupancy': `Occupation`, + 'ok': `OK`, + 'openSurvey': `Ouvrir l'enquête`, + 'openTasks': `Tâches ouvertes`, + 'option': `Option`, + 'organizations': `Organisations`, + 'overview': `Aperçu`, + 'pages.404.notFound': `404 - Page non trouvée`, + 'pages.404.notFoundDescription1': `Ce n'est définitivement pas la page que vous cherchez`, + 'pages.404.notFoundDescription2': `Retour à la`, + 'patient': `Patient`, + 'patientActions': `Actions patient`, + 'patientData': `Données`, + 'patients': `Patients`, + 'patientState': ({ state }): string => { + return TranslationGen.resolveSelect(state, { + 'WAIT': `En attente`, + 'ADMITTED': `Admis`, + 'DISCHARGED': `Sorti`, + 'DEAD': `Décédé`, + 'other': `Inconnu`, + }) + }, + 'patientsUpdatedRecently': `Patients mis à jour récemment`, + 'pickClinic': `Choisir la clinique`, + 'pickClinicDescription': `Sélectionnez la clinique pour ce patient. Seules les emplacements de type clinique peuvent être sélectionnés.`, + 'pickPosition': `Choisir l'emplacement`, + 'pickPositionDescription': `Sélectionnez l'emplacement pour ce patient. Vous pouvez choisir hôpital, cabinet, clinique, service, lit ou chambre.`, + 'pickTeams': `Choisir les équipes`, + 'pickTeamsDescription': `Sélectionnez une ou plusieurs équipes pour ce patient. Vous pouvez choisir clinique, équipe, cabinet ou hôpital.`, + 'position': `Emplacement`, + 'preferences': `Préférences`, + 'priority': ({ priority }): string => { + return TranslationGen.resolveSelect(priority, { + 'P1': `Normal`, + 'P2': `Moyenne`, + 'P3': `Haute`, + 'P4': `Critique`, + 'other': `-`, + }) + }, + 'priorityLabel': `Priorité`, + 'priorityNone': `Aucune`, + 'privacy': `Confidentialité`, + 'properties': `Propriétés`, + 'property': `Propriété`, + 'pThemes': ({ count }): string => { + return TranslationGen.resolvePlural(count, { + '=1': `Thème`, + 'other': `Thèmes`, + }) + }, + 'rAdd': ({ name }): string => { + return `Ajouter ${name}` + }, + 'recentPatients': `Vos patients récents`, + 'recentTasks': `Vos tâches récentes`, + 'rEdit': ({ name }): string => { + return `Mettre à jour ${name} !` + }, + 'refreshing': `Actualisation…`, + 'removeProperty': `Supprimer la propriété`, + 'removePropertyConfirmation': `Êtes-vous sûr de vouloir supprimer cette propriété ? Cette action est irréversible.`, + 'retakeSurvey': `Refaire l'enquête`, + 'rShow': ({ name }): string => { + return `Afficher ${name}` + }, + 'search': `Rechercher`, + 'searchLocations': `Rechercher des emplacements...`, + 'searchUserOrTeam': `Rechercher un utilisateur (ou une équipe)...`, + 'searchUsersOrTeams': `Rechercher des utilisateurs ou des équipes...`, + 'security': `Sécurité`, + 'selectAll': `Tout sélectionner`, + 'selectAssignee': `Assigner à...`, + 'selectClinic': `Sélectionner la clinique`, + 'selectLocation': `Sélectionner l'emplacement`, + 'selectLocationDescription': `Veuillez sélectionner l'emplacement assigné au patient.`, + 'selectOptions': `Options`, + 'selectPosition': `Sélectionner l'emplacement`, + 'selectRootLocation': `Sélectionner l'emplacement racine`, + 'selectRootLocationDescription': `Sélectionnez les emplacements racine qui définissent votre périmètre. Seuls les hôpitaux, cabinets, cliniques et équipes peuvent être sélectionnés. La sélection d'un emplacement parent inclut tous ses enfants.`, + 'selectTeams': `Sélectionner les équipes`, + 'settings': `Paramètres`, + 'settingsDescription': `Ici vous pouvez modifier la configuration de l'application.`, + 'sex': `Sexe`, + 'shiftHandover': `Passation`, + 'showAllTasks': `Afficher toutes les tâches`, + 'showTeamTasks': `Afficher les tâches d'équipe`, + 'sPropertySubjectType': ({ subject }): string => { + return TranslationGen.resolveSelect(subject, { + 'patient': `Patient`, + 'task': `Tâche`, + 'other': `Type de sujet non défini`, + }) + }, + 'sPropertyType': ({ type }): string => { + return TranslationGen.resolveSelect(type, { + 'multiSelect': `Sélection multiple`, + 'singleSelect': `Sélection unique`, + 'number': `Nombre`, + 'text': `Texte`, + 'date': `Date`, + 'dateTime': `Date et heure`, + 'checkbox': `Case à cocher`, + 'user': `Utilisateur`, + 'other': `Type non défini`, + }) + }, + 'stagingModalDisclaimerMarkdown': `Cette instance publique de helpwave tasks est destinée au \\b{développement et à laperçu}. Veuillez \\b{ne} saisir \\b{que des données de test non confidentielles}. Cette instance peut \\negative{\\b{être supprimée à tout moment}}`, + 'startRecording': `Démarrer l'enregistrement`, + 'status': `Statut`, + 'stopRecording': `Arrêter l'enregistrement`, + 'subjectType': `Type de sujet`, + 'submissionDetails': `Détails de l'envoi`, + 'submit': `Envoyer`, + 'submittingAs': ({ name }): string => { + return `Envoi en tant que ${name}` + }, + 'surveyDescription': `Vos commentaires sont précieux. Veuillez prendre un moment pour remplir notre enquête.`, + 'surveyTitle': `Aidez-nous à améliorer helpwave tasks`, + 'system': `Système`, + 'task': `Tâche`, + 'tasks': `Tâches`, + 'tasksUpdatedRecently': `Tâches mises à jour récemment`, + 'taskTitlePlaceholder': `Que faut-il faire ?`, + 'teams': `Équipes`, + 'themeMode': ({ theme }): string => { + return TranslationGen.resolveSelect(theme, { + 'dark': `Sombre`, + 'light': `Clair`, + 'system': `Système`, + 'other': `Système`, + }) + }, + 'title': `Titre`, + 'totalPatients': `Total des patients`, + 'type': `Type`, + 'updated': `Mis à jour`, + 'url': `URL`, + 'userInformation': `Informations utilisateur`, + 'users': `Utilisateurs`, + 'waitingForPatient': `En attente du patient`, + 'waitPatient': `Mettre le patient en attente`, + 'wards': `Services`, + 'yes': `Oui` + }, + 'nl-NL': { + 'account': `Account`, + 'active': `Actief`, + 'addPatient': `Patiënt toevoegen`, + 'addProperty': `Eigenschap toevoegen`, + 'addTask': `Taak toevoegen`, + 'admitPatient': `Patiënt opnemen`, + 'anonymous': `Anoniem`, + 'anonymousSubmission': `Anonieme inzending`, + 'archivedPropertyDescription': `Gearchiveerde eigenschappen kunnen niet meer aan objecten worden toegewezen.`, + 'archiveProperty': `Eigenschap archiveren`, + 'assignedTo': `Toegewezen aan`, + 'authenticationFailed': `Authenticatie mislukt`, + 'birthdate': `Geboortedatum`, + 'cancel': `Annuleren`, + 'clear': `Wissen`, + 'clearCache': `Cache legen`, + 'clinic': `Kliniek`, + 'clinics': `Klinieken`, + 'closedTasks': `Afgeronde taken`, + 'collapseAll': `Alles invouwen`, + 'confirm': `Bevestigen`, + 'confirmSelection': `Selectie bevestigen`, + 'confirmShiftHandover': `Dienstwissel bevestigen`, + 'confirmShiftHandoverDescription': `Weet u zeker dat u alle open taken wilt overdragen aan de geselecteerde gebruiker?`, + 'confirmShiftHandoverDescriptionWithName': ({ taskCount, name }): string => { + let _out: string = '' + _out += `Weet u zeker dat u ` + _out += TranslationGen.resolvePlural(taskCount, { + '=1': `${taskCount} open taak`, + 'other': `${taskCount} open taken`, + }) + _out += ` wilt overdragen aan ${name}?` + return _out + }, + 'conflictDetected': `Conflict gedetecteerd`, + 'connectionConnected': `Verbonden`, + 'connectionConnecting': `Verbinden…`, + 'connectionDisconnected': `Verbinding verbroken`, + 'create': `Aanmaken`, + 'createTask': `Taak aanmaken`, + 'currentTime': `Huidige tijd`, + 'dashboard': `Dashboard`, + 'dashboardWelcomeAfternoon': ({ name }): string => { + return `Goedemiddag, ${name}!` + }, + 'dashboardWelcomeDescription': `Dit is wat er vandaag gebeurt.`, + 'dashboardWelcomeEvening': ({ name }): string => { + return `Goedenavond, ${name}!` + }, + 'dashboardWelcomeMorning': ({ name }): string => { + return `Goedemorgen, ${name}!` + }, + 'dashboardWelcomeNight': ({ name }): string => { + return `Welterusten, ${name}!` + }, + 'dashboardWelcomeNoon': ({ name }): string => { + return `Goedemiddag, ${name}!` + }, + 'delete': `Verwijderen`, + 'deletePatient': `Patiënt verwijderen`, + 'deletePatientConfirmation': `Weet u zeker dat u deze patiënt wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.`, + 'description': `Beschrijving`, + 'descriptionPlaceholder': `Meer details toevoegen...`, + 'deselectAll': `Alles deselecteren`, + 'developmentAndPreviewInstance': `Ontwikkel- en voorbeeldinstantie`, + 'dischargePatient': `Patiënt ontslaan`, + 'dischargePatientConfirmation': `Weet u zeker dat u deze patiënt wilt ontslaan? Deze actie wijzigt de status van de patiënt.`, + 'dismiss': `Sluiten`, + 'dismissAll': `Alles sluiten`, + 'diverse': `Divers`, + 'done': `Klaar`, + 'dueDate': `Vervaldatum`, + 'editPatient': `Patiënt bewerken`, + 'editTask': `Taak bewerken`, + 'enterFeedback': `Voer hier uw feedback in...`, + 'error': `Fout`, + 'errorOccurred': `Er is een fout opgetreden`, + 'estimatedTime': `Geschatte tijd (minuten)`, + 'expandAll': `Alles uitvouwen`, + 'feedback': `Feedback`, + 'feedbackDescription': `Deel uw feedback, meld bugs of stel verbeteringen voor.`, + 'female': `Vrouw`, + 'filterAll': `Alle`, + 'filterUndone': `Open`, + 'firstName': `Voornaam`, + 'freeBeds': `Vrije bedden`, + 'homePage': `Startpagina`, + 'imprint': `Impressum`, + 'inactive': `Inactief`, + 'install': `Installeren`, + 'installApp': `App installeren`, + 'installAppDescription': `Installeer helpwave tasks voor een betere ervaring met offline ondersteuning en snellere toegang.`, + 'language': `Taal`, + 'lastName': `Achternaam`, + 'location': `Locatie`, + 'locationBed': `Bed`, + 'locationClinic': `Kliniek`, + 'locationRoom': `Kamer`, + 'locationType': ({ type }): string => { + return TranslationGen.resolveSelect(type, { + 'CLINIC': `Kliniek`, + 'WARD': `Afdeling`, + 'TEAM': `Team`, + 'ROOM': `Kamer`, + 'BED': `Bed`, + 'other': `Overig`, + }) + }, + 'locationWard': `Afdeling`, + 'login': `Inloggen`, + 'loginRequired': `Inloggen vereist`, + 'loginRequiredDescription': `Om deze site te gebruiken moet u zijn ingelogd.`, + 'logout': `Uitloggen`, + 'male': `Man`, + 'markPatientDead': `Patiënt als overleden markeren`, + 'markPatientDeadConfirmation': `Weet u zeker dat u deze patiënt als overleden wilt markeren?`, + 'myFavorites': `Mijn favorieten`, + 'myOpenTasks': `Mijn open taken`, + 'myTasks': `Mijn taken`, + 'name': `Naam`, + 'no': `Nee`, + 'noClosedTasks': `Geen afgeronde taken`, + 'noLocationsFound': `Geen locaties gevonden`, + 'noNotifications': `Geen recente updates`, + 'noOpenTasks': `Geen open taken`, + 'noPatient': `Geen patiënt`, + 'noResultsFound': `Geen resultaten gevonden`, + 'notAssigned': `Niet toegewezen`, + 'notifications': `Meldingen`, + 'nPatient': ({ count }): string => { + return TranslationGen.resolvePlural(count, { + '=1': `${count} Patiënt`, + 'other': `${count} Patiënten`, + }) + }, + 'nTask': ({ count }): string => { + return TranslationGen.resolvePlural(count, { + '=1': `${count} Taak`, + 'other': `${count} Taken`, + }) + }, + 'occupancy': `Bezetting`, + 'ok': `OK`, + 'openSurvey': `Enquête openen`, + 'openTasks': `Open taken`, + 'option': `Optie`, + 'organizations': `Organisaties`, + 'overview': `Overzicht`, + 'pages.404.notFound': `404 - Pagina niet gevonden`, + 'pages.404.notFoundDescription1': `Dit is zeker niet de pagina die u zoekt`, + 'pages.404.notFoundDescription2': `Terug naar de`, + 'patient': `Patiënt`, + 'patientActions': `Patiëntacties`, + 'patientData': `Gegevens`, + 'patients': `Patiënten`, + 'patientState': ({ state }): string => { + return TranslationGen.resolveSelect(state, { + 'WAIT': `Wachtend`, + 'ADMITTED': `Opgenomen`, + 'DISCHARGED': `Ontslagen`, + 'DEAD': `Overleden`, + 'other': `Onbekend`, + }) + }, + 'patientsUpdatedRecently': `Recent bijgewerkte patiënten`, + 'pickClinic': `Kies de kliniek`, + 'pickClinicDescription': `Selecteer de kliniek voor deze patiënt. Alleen klinieklocaties kunnen worden geselecteerd.`, + 'pickPosition': `Kies de locatie`, + 'pickPositionDescription': `Selecteer de locatie voor deze patiënt. U kunt ziekenhuis, praktijk, kliniek, afdeling, bed of kamer selecteren.`, + 'pickTeams': `Kies teams`, + 'pickTeamsDescription': `Selecteer een of meer teams voor deze patiënt. U kunt kliniek, team, praktijk of ziekenhuis selecteren.`, + 'position': `Locatie`, + 'preferences': `Voorkeuren`, + 'priority': ({ priority }): string => { + return TranslationGen.resolveSelect(priority, { + 'P1': `Normaal`, + 'P2': `Medium`, + 'P3': `Hoog`, + 'P4': `Kritiek`, + 'other': `-`, + }) + }, + 'priorityLabel': `Prioriteit`, + 'priorityNone': `Geen`, + 'privacy': `Privacy`, + 'properties': `Eigenschappen`, + 'property': `Eigenschap`, + 'pThemes': ({ count }): string => { + let _out: string = '' + _out += `'` + _out += TranslationGen.resolvePlural(count, { + '=1': `Thema`, + 'other': `Themas`, + }) + return _out + }, + 'rAdd': ({ name }): string => { + return `${name} toevoegen` + }, + 'recentPatients': `Uw recente patiënten`, + 'recentTasks': `Uw recente taken`, + 'rEdit': ({ name }): string => { + return `${name} bijwerken!` + }, + 'refreshing': `Bijwerken…`, + 'removeProperty': `Eigenschap verwijderen`, + 'removePropertyConfirmation': `Weet u zeker dat u deze eigenschap wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.`, + 'retakeSurvey': `Enquête opnieuw doen`, + 'rShow': ({ name }): string => { + return `${name} tonen` + }, + 'search': `Zoeken`, + 'searchLocations': `Locaties zoeken...`, + 'searchUserOrTeam': `Zoek gebruiker (of team)...`, + 'searchUsersOrTeams': `Zoek gebruikers of teams...`, + 'security': `Beveiliging`, + 'selectAll': `Alles selecteren`, + 'selectAssignee': `Toewijzen aan...`, + 'selectClinic': `Kliniek selecteren`, + 'selectLocation': `Locatie selecteren`, + 'selectLocationDescription': `Selecteer de toegewezen locatie voor de patiënt.`, + 'selectOptions': `Opties`, + 'selectPosition': `Locatie selecteren`, + 'selectRootLocation': `Hooflocatie selecteren`, + 'selectRootLocationDescription': `Selecteer de hoofdlocaties die uw bereik bepalen. Alleen ziekenhuizen, praktijken, klinieken en teams kunnen worden geselecteerd. Het selecteren van een bovenliggende locatie omvat alle onderliggende.`, + 'selectTeams': `Teams selecteren`, + 'settings': `Instellingen`, + 'settingsDescription': `Hier kunt u de app-configuratie wijzigen.`, + 'sex': `Geslacht`, + 'shiftHandover': `Dienstwissel`, + 'showAllTasks': `Alle taken tonen`, + 'showTeamTasks': `Teamtaken tonen`, + 'sPropertySubjectType': ({ subject }): string => { + return TranslationGen.resolveSelect(subject, { + 'patient': `Patiënt`, + 'task': `Taak`, + 'other': `Ongedefinieerd onderwerptype`, + }) + }, + 'sPropertyType': ({ type }): string => { + return TranslationGen.resolveSelect(type, { + 'multiSelect': `Meerdere selectie`, + 'singleSelect': `Enkele selectie`, + 'number': `Getal`, + 'text': `Tekst`, + 'date': `Datum`, + 'dateTime': `Tijdstip`, + 'checkbox': `Selectievakje`, + 'user': `Gebruiker`, + 'other': `Ongedefinieerd type`, + }) + }, + 'stagingModalDisclaimerMarkdown': `Deze openbare instantie van helpwave tasks is voor \\b{ontwikkeling en voorbeeld}. Zorg ervoor dat u \\b{alleen} \\b{niet-vertrouwelijke testgegevens} invoert. Deze instantie kan \\negative{\\b{op elk moment worden verwijderd}}`, + 'startRecording': `Opname starten`, + 'status': `Status`, + 'stopRecording': `Opname stoppen`, + 'subjectType': `Onderwerptype`, + 'submissionDetails': `Inzendingsgegevens`, + 'submit': `Verzenden`, + 'submittingAs': ({ name }): string => { + return `Verzenden als ${name}` + }, + 'surveyDescription': `Uw feedback is waardevol. Neem een moment om onze enquête in te vullen.`, + 'surveyTitle': `Help ons helpwave tasks te verbeteren`, + 'system': `Systeem`, + 'task': `Taak`, + 'tasks': `Taken`, + 'tasksUpdatedRecently': `Recent bijgewerkte taken`, + 'taskTitlePlaceholder': `Wat moet er gedaan worden?`, + 'teams': `Teams`, + 'themeMode': ({ theme }): string => { + return TranslationGen.resolveSelect(theme, { + 'dark': `Donker`, + 'light': `Licht`, + 'system': `Systeem`, + 'other': `Systeem`, + }) + }, + 'title': `Titel`, + 'totalPatients': `Totaal patiënten`, + 'type': `Type`, + 'updated': `Bijgewerkt`, + 'url': `URL`, + 'userInformation': `Gebruikersinformatie`, + 'users': `Gebruikers`, + 'waitingForPatient': `Wachten op patiënt`, + 'waitPatient': `Patiënt in wachtrij zetten`, + 'wards': `Afdelingen`, + 'yes': `Ja` + }, + 'pt-BR': { + 'account': `Conta`, + 'active': `Ativo`, + 'addPatient': `Adicionar paciente`, + 'addProperty': `Adicionar propriedade`, + 'addTask': `Adicionar tarefa`, + 'admitPatient': `Internar paciente`, + 'anonymous': `Anônimo`, + 'anonymousSubmission': `Envio anônimo`, + 'archivedPropertyDescription': `Propriedades arquivadas não podem mais ser atribuídas a objetos.`, + 'archiveProperty': `Arquivar propriedade`, + 'assignedTo': `Atribuído a`, + 'authenticationFailed': `Falha na autenticação`, + 'birthdate': `Data de nascimento`, + 'cancel': `Cancelar`, + 'clear': `Limpar`, + 'clearCache': `Limpar cache`, + 'clinic': `Clínica`, + 'clinics': `Clínicas`, + 'closedTasks': `Tarefas concluídas`, + 'collapseAll': `Recolher tudo`, + 'confirm': `Confirmar`, + 'confirmSelection': `Confirmar seleção`, + 'confirmShiftHandover': `Confirmar passagem de turno`, + 'confirmShiftHandoverDescription': `Tem certeza de que deseja transferir todas as tarefas abertas para o usuário selecionado?`, + 'confirmShiftHandoverDescriptionWithName': ({ taskCount, name }): string => { + let _out: string = '' + _out += `Tem certeza de que deseja transferir ` + _out += TranslationGen.resolvePlural(taskCount, { + '=1': `${taskCount} tarefa aberta`, + 'other': `${taskCount} tarefas abertas`, + }) + _out += ` para ${name}?` + return _out + }, + 'conflictDetected': `Conflito detectado`, + 'connectionConnected': `Conectado`, + 'connectionConnecting': `Conectando…`, + 'connectionDisconnected': `Desconectado`, + 'create': `Criar`, + 'createTask': `Criar tarefa`, + 'currentTime': `Hora atual`, + 'dashboard': `Painel`, + 'dashboardWelcomeAfternoon': ({ name }): string => { + return `Boa tarde, ${name}!` + }, + 'dashboardWelcomeDescription': `Eis o que está acontecendo hoje.`, + 'dashboardWelcomeEvening': ({ name }): string => { + return `Boa noite, ${name}!` + }, + 'dashboardWelcomeMorning': ({ name }): string => { + return `Bom dia, ${name}!` + }, + 'dashboardWelcomeNight': ({ name }): string => { + return `Boa noite, ${name}!` + }, + 'dashboardWelcomeNoon': ({ name }): string => { + return `Boa tarde, ${name}!` + }, + 'delete': `Excluir`, + 'deletePatient': `Excluir paciente`, + 'deletePatientConfirmation': `Tem certeza de que deseja excluir este paciente? Esta ação não pode ser desfeita.`, + 'description': `Descrição`, + 'descriptionPlaceholder': `Adicionar mais detalhes...`, + 'deselectAll': `Desmarcar todos`, + 'developmentAndPreviewInstance': `Instância de desenvolvimento e pré-visualização`, + 'dischargePatient': `Dar alta ao paciente`, + 'dischargePatientConfirmation': `Tem certeza de que deseja dar alta a este paciente? Esta ação alterará o estado do paciente.`, + 'dismiss': `Fechar`, + 'dismissAll': `Descartar todas`, + 'diverse': `Diverso`, + 'done': `Concluído`, + 'dueDate': `Data de vencimento`, + 'editPatient': `Editar paciente`, + 'editTask': `Editar tarefa`, + 'enterFeedback': `Digite seu feedback aqui...`, + 'error': `Erro`, + 'errorOccurred': `Ocorreu um erro`, + 'estimatedTime': `Tempo estimado (minutos)`, + 'expandAll': `Expandir tudo`, + 'feedback': `Feedback`, + 'feedbackDescription': `Compartilhe seu feedback, reporte bugs ou sugira melhorias.`, + 'female': `Feminino`, + 'filterAll': `Todos`, + 'filterUndone': `Pendentes`, + 'firstName': `Nome`, + 'freeBeds': `Leitos livres`, + 'homePage': `Página inicial`, + 'imprint': `Impresso`, + 'inactive': `Inativo`, + 'install': `Instalar`, + 'installApp': `Instalar aplicativo`, + 'installAppDescription': `Instale o helpwave tasks para uma melhor experiência com suporte offline e acesso mais rápido.`, + 'language': `Idioma`, + 'lastName': `Sobrenome`, + 'location': `Localização`, + 'locationBed': `Leito`, + 'locationClinic': `Clínica`, + 'locationRoom': `Quarto`, + 'locationType': ({ type }): string => { + return TranslationGen.resolveSelect(type, { + 'CLINIC': `Clínica`, + 'WARD': `Enfermaria`, + 'TEAM': `Equipe`, + 'ROOM': `Quarto`, + 'BED': `Leito`, + 'other': `Outro`, + }) + }, + 'locationWard': `Enfermaria`, + 'login': `Entrar`, + 'loginRequired': `Login necessário`, + 'loginRequiredDescription': `Para usar este site você precisa estar conectado.`, + 'logout': `Sair`, + 'male': `Masculino`, + 'markPatientDead': `Marcar paciente como óbito`, + 'markPatientDeadConfirmation': `Tem certeza de que deseja marcar este paciente como óbito?`, + 'myFavorites': `Meus favoritos`, + 'myOpenTasks': `Minhas tarefas abertas`, + 'myTasks': `Minhas tarefas`, + 'name': `Nome`, + 'no': `Não`, + 'noClosedTasks': `Nenhuma tarefa concluída`, + 'noLocationsFound': `Nenhuma localização encontrada`, + 'noNotifications': `Nenhuma atualização recente`, + 'noOpenTasks': `Nenhuma tarefa aberta`, + 'noPatient': `Sem paciente`, + 'noResultsFound': `Nenhum resultado encontrado`, + 'notAssigned': `Não atribuído`, + 'notifications': `Notificações`, + 'nPatient': ({ count }): string => { + return TranslationGen.resolvePlural(count, { + '=1': `${count} Paciente`, + 'other': `${count} Pacientes`, + }) + }, + 'nTask': ({ count }): string => { + return TranslationGen.resolvePlural(count, { + '=1': `${count} Tarefa`, + 'other': `${count} Tarefas`, + }) + }, + 'occupancy': `Ocupação`, + 'ok': `OK`, + 'openSurvey': `Abrir pesquisa`, + 'openTasks': `Tarefas abertas`, + 'option': `Opção`, + 'organizations': `Organizações`, + 'overview': `Visão geral`, + 'pages.404.notFound': `404 - Página não encontrada`, + 'pages.404.notFoundDescription1': `Esta não é a página que você procura`, + 'pages.404.notFoundDescription2': `Voltar para a`, + 'patient': `Paciente`, + 'patientActions': `Ações do paciente`, + 'patientData': `Dados`, + 'patients': `Pacientes`, + 'patientState': ({ state }): string => { + return TranslationGen.resolveSelect(state, { + 'WAIT': `Aguardando`, + 'ADMITTED': `Internado`, + 'DISCHARGED': `Alta`, + 'DEAD': `Óbito`, + 'other': `Desconhecido`, + }) + }, + 'patientsUpdatedRecently': `Pacientes atualizados recentemente`, + 'pickClinic': `Escolher a clínica`, + 'pickClinicDescription': `Selecione a clínica para este paciente. Apenas localizações do tipo clínica podem ser selecionadas.`, + 'pickPosition': `Escolher a localização`, + 'pickPositionDescription': `Selecione a localização para este paciente. Você pode escolher hospital, consultório, clínica, enfermaria, leito ou quarto.`, + 'pickTeams': `Escolher equipes`, + 'pickTeamsDescription': `Selecione uma ou mais equipes para este paciente. Você pode escolher clínica, equipe, consultório ou hospital.`, + 'position': `Localização`, + 'preferences': `Preferências`, + 'priority': ({ priority }): string => { + return TranslationGen.resolveSelect(priority, { + 'P1': `Normal`, + 'P2': `Média`, + 'P3': `Alta`, + 'P4': `Crítica`, + 'other': `-`, + }) + }, + 'priorityLabel': `Prioridade`, + 'priorityNone': `Nenhuma`, + 'privacy': `Privacidade`, + 'properties': `Propriedades`, + 'property': `Propriedade`, + 'pThemes': ({ count }): string => { + return TranslationGen.resolvePlural(count, { + '=1': `Tema`, + 'other': `Temas`, + }) + }, + 'rAdd': ({ name }): string => { + return `Adicionar ${name}` + }, + 'recentPatients': `Seus pacientes recentes`, + 'recentTasks': `Suas tarefas recentes`, + 'rEdit': ({ name }): string => { + return `Atualizar ${name}!` + }, + 'refreshing': `Atualizando…`, + 'removeProperty': `Remover propriedade`, + 'removePropertyConfirmation': `Tem certeza de que deseja remover esta propriedade? Esta ação não pode ser desfeita.`, + 'retakeSurvey': `Refazer pesquisa`, + 'rShow': ({ name }): string => { + return `Mostrar ${name}` + }, + 'search': `Pesquisar`, + 'searchLocations': `Pesquisar localizações...`, + 'searchUserOrTeam': `Pesquisar usuário (ou equipe)...`, + 'searchUsersOrTeams': `Pesquisar usuários ou equipes...`, + 'security': `Segurança`, + 'selectAll': `Selecionar todos`, + 'selectAssignee': `Atribuir a...`, + 'selectClinic': `Selecionar clínica`, + 'selectLocation': `Selecionar localização`, + 'selectLocationDescription': `Selecione a localização atribuída ao paciente.`, + 'selectOptions': `Opções`, + 'selectPosition': `Selecionar localização`, + 'selectRootLocation': `Selecionar localização raiz`, + 'selectRootLocationDescription': `Selecione as localizações raiz que definem seu escopo. Apenas hospitais, consultórios, clínicas e equipes podem ser selecionados. Selecionar uma localização pai inclui todas as filhas.`, + 'selectTeams': `Selecionar equipes`, + 'settings': `Configurações`, + 'settingsDescription': `Aqui você pode alterar a configuração do aplicativo.`, + 'sex': `Sexo`, + 'shiftHandover': `Passagem de turno`, + 'showAllTasks': `Mostrar todas as tarefas`, + 'showTeamTasks': `Mostrar tarefas da equipe`, + 'sPropertySubjectType': ({ subject }): string => { + return TranslationGen.resolveSelect(subject, { + 'patient': `Paciente`, + 'task': `Tarefa`, + 'other': `Tipo de sujeito indefinido`, + }) + }, + 'sPropertyType': ({ type }): string => { + return TranslationGen.resolveSelect(type, { + 'multiSelect': `Seleção múltipla`, + 'singleSelect': `Seleção única`, + 'number': `Número`, + 'text': `Texto`, + 'date': `Data`, + 'dateTime': `Data/hora`, + 'checkbox': `Caixa de seleção`, + 'user': `Usuário`, + 'other': `Tipo indefinido`, + }) + }, + 'stagingModalDisclaimerMarkdown': `Esta instância pública do helpwave tasks é para \\b{desenvolvimento e pré-visualização}. Certifique-se de \\b{apenas} inserir \\b{dados de teste não confidenciais}. Esta instância pode \\negative{\\b{ser excluída a qualquer momento}}`, + 'startRecording': `Iniciar gravação`, + 'status': `Status`, + 'stopRecording': `Parar gravação`, + 'subjectType': `Tipo de sujeito`, + 'submissionDetails': `Detalhes do envio`, + 'submit': `Enviar`, + 'submittingAs': ({ name }): string => { + return `Enviando como ${name}` + }, + 'surveyDescription': `Seu feedback é valioso. Por favor, dedique um momento para responder nossa pesquisa.`, + 'surveyTitle': `Ajude-nos a melhorar o helpwave tasks`, + 'system': `Sistema`, + 'task': `Tarefa`, + 'tasks': `Tarefas`, + 'tasksUpdatedRecently': `Tarefas atualizadas recentemente`, + 'taskTitlePlaceholder': `O que precisa ser feito?`, + 'teams': `Equipes`, + 'themeMode': ({ theme }): string => { + return TranslationGen.resolveSelect(theme, { + 'dark': `Escuro`, + 'light': `Claro`, + 'system': `Sistema`, + 'other': `Sistema`, + }) + }, + 'title': `Título`, + 'totalPatients': `Total de pacientes`, + 'type': `Tipo`, + 'updated': `Atualizado`, + 'url': `URL`, + 'userInformation': `Informações do usuário`, + 'users': `Usuários`, + 'waitingForPatient': `Aguardando paciente`, + 'waitPatient': `Colocar paciente em espera`, + 'wards': `Enfermarias`, + 'yes': `Sim` } } diff --git a/web/locales/de-DE.arb b/web/locales/de-DE.arb index c980135c..2e336a19 100644 --- a/web/locales/de-DE.arb +++ b/web/locales/de-DE.arb @@ -1,85 +1,39 @@ { "@@locale": "de-DE", - "nOrganization": "{count, plural, =1{# Organisation} other{# Organisationen}}", - "@nOrganization": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nWard": "{count, plural, =1{# Station} other{# Stationen}}", - "@nWard": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nRoom": "{count, plural, =1{# Zimmer} other{# Zimmer}}", - "@nRoom": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nBed": "{count, plural, =1{# Bett} other{# Betten}}", - "@nBed": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nPatient": "{count, plural, =1{# Patient} other{# Patienten}}", - "@nPatient": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nTask": "{count, plural, =1{# Aufgabe} other{# Aufgaben}}", - "@nTask": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nTeam": "{count, plural, =1{# Team} other{# Teams}}", - "@nTeam": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nYear": "{count, plural, =1{# Jahr alt} other{# Jahre alt}}", - "@nYear": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nProperties": "{count, plural, =1{# Eigenschaft} other{# Eigenschaften}}", - "@nProperties": { + "account": "Konto", + "active": "Aktiv", + "addPatient": "Patient hinzufügen", + "addProperty": "Eigenschaften hinzufügen", + "addTask": "Aufgabe hinzufügen", + "archivedPropertyDescription": "Archivierte Eigenschaften können nicht mehr neu Objekten hinzugeügt werden.", + "archiveProperty": "Eigenschaft Archivieren", + "assignedTo": "Zugewiesen an", + "authenticationFailed": "Authentifizierung fehlgeschlagen", + "birthdate": "Geburtsdatum", + "cancel": "Abbrechen", + "clearCache": "Cache leeren", + "clinics": "Kliniken", + "closedTasks": "Erledigte Aufgaben", + "conflictDetected": "Konflikt erkannt", + "confirm": "Bestätigen", + "confirmShiftHandover": "Schichtübergabe bestätigen", + "confirmShiftHandoverDescription": "Sind Sie sicher, dass Sie alle offenen Aufgaben an den ausgewählten Benutzer übertragen möchten?", + "confirmShiftHandoverDescriptionWithName": "Sind Sie sicher, dass Sie {taskCount, plural, =1{# offene Aufgabe} other{# offene Aufgaben}} an {name} übertragen möchten?", + "@confirmShiftHandoverDescriptionWithName": { "placeholders": { - "count": { + "taskCount": { "type": "number" - } - } - }, - "dashboardWelcome": "Guten Morgen, {name}!", - "@dashboardWelcome": { - "placeholders": { + }, "name": { "type": "string" } } }, + "create": "Erstellen", + "createTask": "Aufgabe erstellen", + "currentTime": "Aktuelle Zeit", + "dashboard": "Dashboard", + "dashboardWelcomeDescription": "Hier ist, was heute passiert.", "dashboardWelcomeMorning": "Guten Morgen, {name}!", "@dashboardWelcomeMorning": { "placeholders": { @@ -120,150 +74,160 @@ } } }, - "taskStatus": "{status, select, overdue{Überfällig} upcoming{Anstehend} done{Fertig} other{-}}", - "@taskStatus": { - "placeholders": { - "status": {} - } - }, - "age": "Alter", - "assignedTo": "Zugewiesen an", - "assignee": "Verantwortlich", - "birthdate": "Geburtsdatum", - "confirm": "Bestätigen", - "createdAt": "Erstellt am", - "currentTime": "Aktuelle Zeit", - "dashboard": "Dashboard", - "dashboardWelcomeDescription": "Hier ist, was heute passiert.", + "delete": "Löschen", "description": "Beschreibung", "descriptionPlaceholder": "Weitere Details hinzufügen...", "developmentAndPreviewInstance": "Entwicklungs- und Vorschauinstanz", "dismiss": "Schließen", + "diverse": "Divers", + "done": "Fertig", "dueDate": "Fälligkeitsdatum", + "editPatient": "Patient bearbeiten", + "editTask": "Aufgabe bearbeiten", + "errorOccurred": "Ein Fehler ist aufgetreten", + "estimatedTime": "Geschätzte Zeit (Minuten)", + "feedback": "Feedback", + "female": "Weiblich", + "filterAll": "Alle", + "filterUndone": "Offen", + "firstName": "Vorname", + "freeBeds": "Freie Betten", "homePage": "Startseite", "imprint": "Impressum", - "itsYou": "Du bist es", + "inactive": "Inaktiv", + "language": "Sprache", + "lastName": "Nachname", "location": "Ort", + "locationBed": "Bett", + "locationClinic": "Klinik", + "locationRoom": "Zimmer", + "locationType": "{type, select, CLINIC{Klinik} WARD{Station} TEAM{Team} ROOM{Zimmer} BED{Bett} other{Sonstiges}}", + "@locationType": { + "placeholders": { + "type": { + "type": "string" + } + } + }, + "locationWard": "Station", "login": "Login", - "loginRequired": "Login benötigt", - "loginRequiredDescription": "Um diese Seite benutzen zu können musst du eingeloggt sein.", "logout": "Abmelden", + "male": "Männlich", + "myFavorites": "Meine Favoriten", "myOpenTasks": "Meine offenen Aufgaben", "myTasks": "Meine Aufgaben", "name": "Name", - "newestAdmissions": "Neueste Aufnahmen", - "patientsUpdatedRecently": "Kürzlich aktualisierte Patienten", + "no": "Nein", + "noClosedTasks": "Keine erledigten Aufgaben", + "noOpenTasks": "Keine offenen Aufgaben", "noPatient": "Kein Patient", + "noResultsFound": "Keine Ergebnisse gefunden", "notAssigned": "Nicht zugewiesen", - "notes": "Notizen", + "nPatient": "{count, plural, =1{# Patient} other{# Patienten}}", + "@nPatient": { + "placeholders": { + "count": { + "type": "number" + } + } + }, + "nTask": "{count, plural, =1{# Aufgabe} other{# Aufgaben}}", + "@nTask": { + "placeholders": { + "count": { + "type": "number" + } + } + }, + "occupancy": "Belegung", + "openSurvey": "Umfrage öffnen", + "openTasks": "Offene Aufgaben", + "option": "Option", + "organizations": "Organisationen", + "overview": "Übersicht", "pages.404.notFound": "404 - Seite nicht gefunden", "pages.404.notFoundDescription1": "Das ist definitiv nicht die Seite nach der Sie suchen", "pages.404.notFoundDescription2": "Zurück zur", "patient": "Patient", + "patientData": "Daten", "patients": "Patienten", - "place": "Ort", - "preferences": "Präferenzen", - "privacy": "Datenschutz", - "private": "Privat", - "public": "Öffentlich", - "publish": "Veröffentlichen", - "recentPatients": "Deine kürzlichen Patienten", - "recentTasks": "Deine kürzlichen Aufgaben", - "rooms": "Zimmer", - "settings": "Einstellungen", - "settingsDescription": "Hier kannst du die App Konfiguration ändern.", - "sex": "Geschlecht", - "stagingModalDisclaimerMarkdown": "Diese öffentliche Instanz von helpwave tasks ist für '\\b{Entwicklungs- und Vorschauzwecke}' gedacht. Bitte stellen Sie sicher, dass Sie '\\b{ausschließlich nicht-vertrauliche Testdaten}' eingeben. Diese Instanz kann '\\negative{\\b{jederzeit gelöscht}}' werden.", - "status": "Status", - "task": "Aufgabe", - "tasks": "Aufgaben", - "tasksUpdatedRecently": "Kürzlich aktualisierte Aufgaben", - "teams": "Teams", - "clinics": "Kliniken", - "clinic": "Klinik", - "position": "Standort", - "selectClinic": "Klinik auswählen", - "selectPosition": "Standort auswählen", - "selectTeams": "Teams auswählen", - "pickClinic": "Klinik auswählen", - "pickClinicDescription": "Wählen Sie die Klinik für diesen Patienten aus. Es können nur Klinik-Standorte ausgewählt werden.", - "pickPosition": "Standort auswählen", - "pickPositionDescription": "Wählen Sie den Standort für diesen Patienten aus. Sie können Krankenhaus-, Praxis-, Klinik-, Station-, Bett- oder Zimmer-Standorte auswählen.", - "pickTeams": "Teams auswählen", - "pickTeamsDescription": "Wählen Sie ein oder mehrere Teams für diesen Patienten aus. Sie können Klinik-, Team-, Praxis- oder Krankenhaus-Standorte auswählen.", - "totalPatients": "Gesamtpatienten", - "updated": "Aktualisiert", - "visibility": "Sichtbarkeit", - "lastUpdate": "Letzte Änderung", - "wards": "Stationen", - "overview": "Übersicht", - "nCurrentlyPatients": "Aktuell {count, plural, =1{# Patient} other{# Patienten}}", - "@nCurrentlyPatients": { + "patientState": "{state, select, WAIT{Wartend} ADMITTED{Aufgenommen} DISCHARGED{Entlassen} DEAD{Verstorben} other{Unbekannt}}", + "@patientState": { + "placeholders": { + "state": { + "type": "string" + } + } + }, + "patientsUpdatedRecently": "Kürzlich aktualisierte Patienten", + "pThemes": "{count, plural, =1{Design} other{Designs}}", + "@pThemes": { "placeholders": { "count": { "type": "number" } } }, - "authenticationFailed": "Authentifizierung fehlgeschlagen", - "returnHome": "Zur Homepage", - "addPatient": "Patient hinzufügen", - "addTask": "Aufgabe hinzufügen", - "createTask": "Aufgabe erstellen", - "editPatient": "Patient bearbeiten", - "editTask": "Aufgabe bearbeiten", - "openTasks": "Offene Aufgaben", - "closedTasks": "Erledigte Aufgaben", - "noOpenTasks": "Keine offenen Aufgaben", - "noClosedTasks": "Keine erledigten Aufgaben", - "save": "Speichern", - "cancel": "Abbrechen", - "create": "Erstellen", - "delete": "Löschen", - "firstName": "Vorname", - "lastName": "Nachname", - "male": "Männlich", - "female": "Weiblich", - "diverse": "Divers", - "time.today": "Heute", - "myFavorites": "Meine Favoriten", - "freeBeds": "Freie Betten", - "cardView": "Kachelansicht", - "occupancy": "Belegung", + "preferences": "Präferenzen", + "priority": "{priority, select, P1{Normal} P2{Mittel} P3{Hoch} P4{Kritisch} other{-}}", + "@priority": { + "placeholders": { + "priority": {} + } + }, + "priorityLabel": "Priorität", + "priorityNone": "Keine", + "privacy": "Datenschutz", "properties": "Eigenschaften", - "addProperty": "Eigenschaften hinzufügen", - "type": "Typ", - "tableView": "Tabellenansicht", - "subjectType": "Subjekt Type", - "active": "Aktiv", - "inactive": "Inaktiv", - "rEdit": "{name} ändern", - "@rEdit": { + "property": "Eigenschaft", + "rAdd": "{name} hinzufügen", + "@rAdd": { "placeholders": { "name": { "type": "string" } } }, - "rShow": "{name} anzeigen", - "@rShow": { + "rEdit": "{name} ändern", + "@rEdit": { "placeholders": { "name": { "type": "string" } } }, - "rAdd": "{name} hinzufügen", - "@rAdd": { + "recentPatients": "Deine kürzlichen Patienten", + "recentTasks": "Deine kürzlichen Aufgaben", + "refreshing": "Aktualisiere…", + "removeProperty": "Eigenschaft entfernen", + "removePropertyConfirmation": "Sind Sie sicher, dass Sie diese Eigenschaft entfernen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "retakeSurvey": "Umfrage erneut durchführen", + "rShow": "{name} anzeigen", + "@rShow": { "placeholders": { "name": { "type": "string" } } }, - "property": "Eigenschaft", + "search": "Suchen", + "searchUserOrTeam": "Nach Benutzer (oder Team) suchen...", + "searchUsersOrTeams": "Benutzer oder Teams suchen...", + "security": "Sicherheit", + "selectAssignee": "Zuweisen an...", + "selectLocation": "Standort auswählen", "selectOptions": "Auswahl Optionen", - "option": "Option", + "settings": "Einstellungen", + "settingsDescription": "Hier kannst du die App Konfiguration ändern.", + "sex": "Geschlecht", + "shiftHandover": "Schichtübergabe", + "showAllTasks": "Alle Aufgaben anzeigen", + "showTeamTasks": "Team-Aufgaben anzeigen", + "stagingModalDisclaimerMarkdown": "Diese öffentliche Instanz von helpwave tasks ist für '\\b{Entwicklungs- und Vorschauzwecke}' gedacht. Bitte stellen Sie sicher, dass Sie '\\b{ausschließlich nicht-vertrauliche Testdaten}' eingeben. Diese Instanz kann '\\negative{\\b{jederzeit gelöscht}}' werden.", + "status": "Status", + "subjectType": "Subjekt Type", + "surveyDescription": "Ihr Feedback ist wertvoll für uns. Bitte nehmen Sie sich einen Moment Zeit, um unsere Umfrage auszufüllen.", + "surveyTitle": "Helfen Sie uns, helpwave tasks zu verbessern", "sPropertySubjectType": "{subject, select, patient{Patient} task{Aufgabe} other{Undefinierter Subjekt Typ}}", "@sPropertySubjectType": { "placeholders": { @@ -280,38 +244,12 @@ } } }, - "archiveProperty": "Eigenschaft Archivieren", - "archivedPropertyDescription": "Archivierte Eigenschaften können nicht mehr neu Objekten hinzugeügt werden.", - "title": "Titel", - "taskTitlePlaceholder": "Was muss erledigt werden?", - "selectPatient": "Patient auswählen", - "currentPatient": "Aktueller Patient", - "selectAssignee": "Zuweisen an...", - "unassigned": "Nicht zugewiesen", - "newPatient": "Neuer Patient", - "selectLocationDescription": "Bitte wählen Sie den zugewiesenen Standort für den Patienten aus.", - "searchLocations": "Standorte suchen...", - "searchUsersOrTeams": "Benutzer oder Teams suchen...", - "searchUserOrTeam": "Nach Benutzer (oder Team) suchen...", - "selectAll": "Alle auswählen", - "deselectAll": "Auswahl aufheben", - "loading": "Laden...", - "noLocationsFound": "Keine Standorte gefunden", - "noResultsFound": "Keine Ergebnisse gefunden", - "confirmSelection": "Auswahl bestätigen", - "expandAll": "Alle ausklappen", - "collapseAll": "Alle einklappen", - "assignedLocation": "Zugewiesener Standort", - "selectLocation": "Standort auswählen", - "selectRootLocation": "Stammstandort auswählen", - "selectRootLocationDescription": "Wählen Sie die Stammstandorte aus, die Ihren zugänglichen Bereich definieren. Es können nur Krankenhäuser, Praxen, Kliniken und Teams ausgewählt werden. Die Auswahl eines übergeordneten Standorts umfasst alle untergeordneten Standorte.", - "locations": "Standorte", - "organizations": "Organisationen", - "selectOrganization": "Organisation auswählen", - "selectOrganizations": "Organisationen auswählen", - "clearCache": "Cache leeren", - "account": "Konto", "system": "System", + "task": "Aufgabe", + "tasks": "Aufgaben", + "tasksUpdatedRecently": "Kürzlich aktualisierte Aufgaben", + "taskTitlePlaceholder": "Was muss erledigt werden?", + "teams": "Teams", "themeMode": "{theme, select, dark{Dunkel} light{Hell} system{System} other{System}}", "@themeMode": { "placeholders": { @@ -320,116 +258,35 @@ } } }, - "chooseLanguage": "Sprache wählen", - "chooseTheme": "Design wählen", - "done": "Fertig", - "filterAll": "Alle", - "filterUndone": "Offen", - "showDone": "Erledigte anzeigen", - "showAllPatients": "Alle Patienten anzeigen", - "profile": "Profil", - "patientData": "Daten", - "clickToAdd": "Klicken um hinzuzufügen", - "rClickToAdd": "Klicken um {name} hinzuzufügen", - "@rClickToAdd": { - "placeholders": { - "name": { - "type": "string" - } - } - }, - "locationChipsShowFullPath": "Vollständigen Standortpfad in Chips anzeigen", - "locationClinic": "Klinik", - "locationWard": "Station", - "locationTeam": "Team", - "locationRoom": "Zimmer", - "locationBed": "Bett", - "locationType": "{type, select, CLINIC{Klinik} WARD{Station} TEAM{Team} ROOM{Zimmer} BED{Bett} other{Sonstiges}}", - "@locationType": { - "placeholders": { - "type": { - "type": "string" - } - } - }, - "waitingForPatient": "Warten auf Patient", - "patientActions": "Patientenaktionen", - "admitPatient": "Patient aufnehmen", - "dischargePatient": "Patient entlassen", - "markPatientDead": "Patient als verstorben markieren", - "waitPatient": "Patient auf Warteliste setzen", - "patientState": "{state, select, WAIT{Wartend} ADMITTED{Aufgenommen} DISCHARGED{Entlassen} DEAD{Verstorben} other{Unbekannt}}", - "@patientState": { - "placeholders": { - "state": { - "type": "string" - } - } - }, - "deleteTaskConfirmation": "Sind Sie sicher, dass Sie diese Aufgabe löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", - "markPatientDeadConfirmation": "Sind Sie sicher, dass Sie diesen Patienten als verstorben markieren möchten?", - "dischargePatientConfirmation": "Sind Sie sicher, dass Sie diesen Patienten entlassen möchten? Diese Aktion ändert den Status des Patienten.", - "updateLocation": "Standort aktualisieren", - "updateLocationConfirmation": "Sind Sie sicher, dass Sie den Standort des Patienten aktualisieren möchten?", - "removeProperty": "Eigenschaft entfernen", - "removePropertyConfirmation": "Sind Sie sicher, dass Sie diese Eigenschaft entfernen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", - "openSurvey": "Umfrage öffnen", - "retakeSurvey": "Umfrage erneut durchführen", - "feedback": "Feedback", - "surveyTitle": "Helfen Sie uns, helpwave tasks zu verbessern", - "surveyDescription": "Ihr Feedback ist wertvoll für uns. Bitte nehmen Sie sich einen Moment Zeit, um unsere Umfrage auszufüllen.", - "installApp": "App installieren", - "installAppDescription": "Installieren Sie helpwave tasks für eine bessere Erfahrung mit Offline-Unterstützung und schnellerem Zugriff.", - "install": "Installieren", - "noNotifications": "Keine aktuellen Updates", - "notifications": "Benachrichtigungen", - "dismissAll": "Alle verwerfen", - "security": "Sicherheit", - "shiftHandover": "Schichtübergabe", - "shiftHandoverDescription": "Wählen Sie einen Benutzer aus, um alle Ihnen zugewiesenen offenen Aufgaben zu übertragen.", - "confirmShiftHandover": "Schichtübergabe bestätigen", - "confirmShiftHandoverDescription": "Sind Sie sicher, dass Sie alle offenen Aufgaben an den ausgewählten Benutzer übertragen möchten?", - "confirmShiftHandoverDescriptionWithName": "Sind Sie sicher, dass Sie {taskCount, plural, =1{# offene Aufgabe} other{# offene Aufgaben}} an {name} übertragen möchten?", - "@confirmShiftHandoverDescriptionWithName": { - "placeholders": { - "taskCount": { - "type": "number" - }, - "name": { - "type": "string" - } - } - }, - "priority": "{priority, select, P1{Normal} P2{Mittel} P3{Hoch} P4{Kritisch} other{-}}", - "@priority": { - "placeholders": { - "priority": {} - } - }, - "priorityLabel": "Priorität", - "priorityNone": "Keine", - "print": "Drucken", - "printOnlyAvailableInTableMode": "Drucken ist nur im Tabellenmodus verfügbar", - "deletePatient": "Patient löschen", - "deletePatientConfirmation": "Sind Sie sicher, dass Sie diesen Patienten löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", - "feedbackDescription": "Teilen Sie Ihr Feedback, melden Sie Fehler oder schlagen Sie Verbesserungen vor.", - "url": "URL", + "title": "Titel", + "totalPatients": "Gesamtpatienten", + "type": "Typ", + "updated": "Aktualisiert", + "users": "Benutzer", + "wards": "Stationen", + "yes": "Ja", + "anonymous": "Anonym", + "anonymousSubmission": "Anonyme Übermittlung", + "collapseAll": "Alle einklappen", + "confirmSelection": "Auswahl bestätigen", + "deselectAll": "Auswahl aufheben", "enterFeedback": "Geben Sie hier Ihr Feedback ein...", + "expandAll": "Alle ausklappen", + "feedbackDescription": "Teilen Sie Ihr Feedback, melden Sie Fehler oder schlagen Sie Verbesserungen vor.", + "noLocationsFound": "Keine Standorte gefunden", + "pickClinic": "Klinik auswählen", + "pickClinicDescription": "Wählen Sie die Klinik für diesen Patienten aus. Es können nur Klinik-Standorte ausgewählt werden.", + "pickPosition": "Standort auswählen", + "pickPositionDescription": "Wählen Sie den Standort für diesen Patienten aus. Sie können Krankenhaus-, Praxis-, Klinik-, Station-, Bett- oder Zimmer-Standorte auswählen.", + "pickTeams": "Teams auswählen", + "pickTeamsDescription": "Wählen Sie ein oder mehrere Teams für diesen Patienten aus. Sie können Klinik-, Team-, Praxis- oder Krankenhaus-Standorte auswählen.", + "searchLocations": "Standorte suchen...", + "selectAll": "Alle auswählen", + "selectLocationDescription": "Bitte wählen Sie den zugewiesenen Standort für den Patienten aus.", + "selectRootLocation": "Stammstandort auswählen", + "selectRootLocationDescription": "Wählen Sie die Stammstandorte aus, die Ihren zugänglichen Bereich definieren. Es können nur Krankenhäuser, Praxen, Kliniken und Teams ausgewählt werden. Die Auswahl eines übergeordneten Standorts umfasst alle untergeordneten Standorte.", "startRecording": "Aufnahme starten", "stopRecording": "Aufnahme stoppen", - "submit": "Absenden", - "username": "Benutzername", - "anonymous": "Anonym", - "withName": "Mit Namen (für Nachfragen)", - "enterName": "Geben Sie Ihren Namen ein...", - "users": "Benutzer", - "showTeamTasks": "Team-Aufgaben anzeigen", - "showAllTasks": "Alle Aufgaben anzeigen", - "error": "Fehler", - "errorOccurred": "Ein Fehler ist aufgetreten", - "estimatedTime": "Geschätzte Zeit (Minuten)", - "ok": "OK", - "submitAnonymously": "Anonym absenden", "submissionDetails": "Übermittlungsdetails", "submittingAs": "Übermitteln als {name}", "@submittingAs": { @@ -439,12 +296,36 @@ } } }, - "anonymousSubmission": "Anonyme Übermittlung", - "conflictDetected": "Konflikt erkannt", - "overwrite": "Überschreiben", - "refreshing": "Aktualisiere…", + "submit": "Absenden", + "url": "URL", + "userInformation": "Benutzerinformationen", + "admitPatient": "Patient aufnehmen", + "clear": "Löschen", + "clinic": "Klinik", "connectionConnected": "Verbunden", "connectionConnecting": "Verbinde…", "connectionDisconnected": "Getrennt", - "userInformation": "Benutzerinformationen" + "deletePatient": "Patient löschen", + "deletePatientConfirmation": "Sind Sie sicher, dass Sie diesen Patienten löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "dischargePatient": "Patient entlassen", + "dischargePatientConfirmation": "Sind Sie sicher, dass Sie diesen Patienten entlassen möchten? Diese Aktion ändert den Status des Patienten.", + "dismissAll": "Alle verwerfen", + "error": "Fehler", + "install": "Installieren", + "installApp": "App installieren", + "installAppDescription": "Installieren Sie helpwave tasks für eine bessere Erfahrung mit Offline-Unterstützung und schnellerem Zugriff.", + "loginRequired": "Login benötigt", + "loginRequiredDescription": "Um diese Seite benutzen zu können musst du eingeloggt sein.", + "markPatientDead": "Patient als verstorben markieren", + "markPatientDeadConfirmation": "Sind Sie sicher, dass Sie diesen Patienten als verstorben markieren möchten?", + "noNotifications": "Keine aktuellen Updates", + "notifications": "Benachrichtigungen", + "ok": "OK", + "patientActions": "Patientenaktionen", + "position": "Standort", + "selectClinic": "Klinik auswählen", + "selectPosition": "Standort auswählen", + "selectTeams": "Teams auswählen", + "waitPatient": "Patient auf Warteliste setzen", + "waitingForPatient": "Warten auf Patient" } diff --git a/web/locales/en-US.arb b/web/locales/en-US.arb index 1f7661f3..cba3ce4f 100644 --- a/web/locales/en-US.arb +++ b/web/locales/en-US.arb @@ -1,85 +1,39 @@ { "@@locale": "en-US", - "nOrganization": "{count, plural, =1{# Organization} other{# Organizations}}", - "@nOrganization": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nWard": "{count, plural, =1{# Ward} other{# Wards}}", - "@nWard": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nRoom": "{count, plural, =1{# Room} other{# Rooms}}", - "@nRoom": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nBed": "{count, plural, =1{# Bed} other{# Beds}}", - "@nBed": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nPatient": "{count, plural, =1{# Patient} other{# Patients}}", - "@nPatient": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nTask": "{count, plural, =1{# Task} other{# Tasks}}", - "@nTask": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nTeam": "{count, plural, =1{# Team} other{# Teams}}", - "@nTeam": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nYear": "{count, plural, =1{# year old} other{# years old}}", - "@nYear": { - "placeholders": { - "count": { - "type": "number" - } - } - }, - "nProperties": "{count, plural, =1{# Property} other{# Properties}}", - "@nProperties": { + "account": "Account", + "active": "Active", + "addPatient": "Add Patient", + "addProperty": "Add Property", + "addTask": "Add Task", + "archivedPropertyDescription": "Archived Properties can no longer be assigned to objects.", + "archiveProperty": "Archive Property", + "assignedTo": "Assigned to", + "authenticationFailed": "Authentication Failed", + "birthdate": "Birthdate", + "cancel": "Cancel", + "clearCache": "Clear Cache", + "clinics": "Clinics", + "closedTasks": "Closed Tasks", + "conflictDetected": "Conflict Detected", + "confirm": "Confirm", + "confirmShiftHandover": "Confirm Shift Handover", + "confirmShiftHandoverDescription": "Are you sure you want to transfer all open tasks to the selected user?", + "confirmShiftHandoverDescriptionWithName": "Are you sure you want to transfer {taskCount, plural, =1{# open task} other{# open tasks}} to {name}?", + "@confirmShiftHandoverDescriptionWithName": { "placeholders": { - "count": { + "taskCount": { "type": "number" - } - } - }, - "dashboardWelcome": "Good Morning, {name}!", - "@dashboardWelcome": { - "placeholders": { + }, "name": { "type": "string" } } }, + "create": "Create", + "createTask": "Create Task", + "currentTime": "Current Time", + "dashboard": "Dashboard", + "dashboardWelcomeDescription": "Here is what is happening today.", "dashboardWelcomeMorning": "Good Morning, {name}!", "@dashboardWelcomeMorning": { "placeholders": { @@ -120,150 +74,160 @@ } } }, - "taskStatus": "{status, select, overdue{Overdue} upcoming{Upcoming} done{Done} other{-}}", - "@taskStatus": { - "placeholders": { - "status": {} - } - }, - "age": "Age", - "assignedTo": "Assigned to", - "assignee": "Assignee", - "birthdate": "Birthdate", - "confirm": "Confirm", - "createdAt": "Created at", - "currentTime": "Current Time", - "dashboard": "Dashboard", - "dashboardWelcomeDescription": "Here is what is happening today.", + "delete": "Delete", "description": "Description", + "diverse": "Diverse", "descriptionPlaceholder": "Add more details...", "developmentAndPreviewInstance": "Development and preview instance", "dismiss": "Dismiss", + "done": "Done", "dueDate": "Due Date", + "editPatient": "Edit Patient", + "editTask": "Edit Task", + "errorOccurred": "An error occurred", + "estimatedTime": "Estimated Time (minutes)", + "feedback": "Feedback", + "female": "Female", + "filterAll": "All", + "filterUndone": "Undone", + "firstName": "First Name", + "freeBeds": "Free Beds", "homePage": "Home Page", "imprint": "Imprint", - "itsYou": "You", + "inactive": "Inactive", + "language": "Language", + "lastName": "Last Name", "location": "Location", + "locationBed": "Bed", + "locationClinic": "Clinic", + "locationRoom": "Room", + "locationType": "{type, select, CLINIC{Clinic} WARD{Ward} TEAM{Team} ROOM{Room} BED{Bed} other{Other}}", + "@locationType": { + "placeholders": { + "type": { + "type": "string" + } + } + }, + "locationWard": "Ward", "login": "Login", - "loginRequired": "Login required", - "loginRequiredDescription": "To use this site you need to be logged in.", "logout": "Logout", + "male": "Male", + "myFavorites": "My Favorites", "myOpenTasks": "My Open Tasks", "myTasks": "My tasks", "name": "Name", - "newestAdmissions": "Newest admissions", - "patientsUpdatedRecently": "Patients updated recently", + "no": "No", + "noClosedTasks": "No closed tasks", + "noOpenTasks": "No open tasks", "noPatient": "No Patient", + "noResultsFound": "No results found", "notAssigned": "Not assigned", - "notes": "notes", + "nPatient": "{count, plural, =1{# Patient} other{# Patients}}", + "@nPatient": { + "placeholders": { + "count": { + "type": "number" + } + } + }, + "nTask": "{count, plural, =1{# Task} other{# Tasks}}", + "@nTask": { + "placeholders": { + "count": { + "type": "number" + } + } + }, + "occupancy": "Occupancy", + "openSurvey": "Open Survey", + "openTasks": "Open Tasks", + "option": "Option", + "organizations": "Organizations", + "overview": "Overview", "pages.404.notFound": "404 - Page not found", "pages.404.notFoundDescription1": "This is definitely not the page you're looking for", "pages.404.notFoundDescription2": "Let me take you to the", "patient": "Patient", + "patientData": "Data", "patients": "Patients", - "place": "Place", - "preferences": "Preferences", - "privacy": "Privacy", - "private": "private", - "public": "public", - "publish": "publish", - "recentPatients": "Your Recent Patients", - "recentTasks": "Your Recent Tasks", - "rooms": "Rooms", - "settings": "Settings", - "settingsDescription": "Here you can change the app configuration.", - "sex": "Sex", - "stagingModalDisclaimerMarkdown": "This public instance of helpwave tasks is for '\\b{development and preview purposes}'. Please make sure to '\\b{only}' enter '\\b{non-confidential testing data}'. This instance can be '\\negative{\\b{deleted at any time}}'", - "status": "Status", - "task": "Task", - "tasks": "Tasks", - "tasksUpdatedRecently": "Tasks updated recently", - "teams": "Teams", - "clinics": "Clinics", - "clinic": "Clinic", - "position": "Location", - "selectClinic": "Select Clinic", - "selectPosition": "Select Location", - "selectTeams": "Select Teams", - "pickClinic": "Pick the clinic", - "pickClinicDescription": "Select the clinic for this patient. Only clinic locations can be selected.", - "pickPosition": "Pick the location", - "pickPositionDescription": "Select the location for this patient. You can select hospital, practice, clinic, ward, bed, or room locations.", - "pickTeams": "Pick teams", - "pickTeamsDescription": "Select one or more teams for this patient. You can select clinic, team, practice, or hospital locations.", - "totalPatients": "Total Patients", - "updated": "Updated", - "visibility": "Visibility", - "lastUpdate": "Last Update", - "wards": "Wards", - "overview": "Overview", - "nCurrentlyPatients": "Currently {count, plural, =1{# Patient} other{# Patients}}", - "@nCurrentlyPatients": { + "patientState": "{state, select, WAIT{Waiting} ADMITTED{Admitted} DISCHARGED{Discharged} DEAD{Dead} other{Unknown}}", + "@patientState": { + "placeholders": { + "state": { + "type": "string" + } + } + }, + "patientsUpdatedRecently": "Patients updated recently", + "pThemes": "{count, plural, =1{Theme} other{Themes}}", + "@pThemes": { "placeholders": { "count": { "type": "number" } } }, - "authenticationFailed": "Authentication Failed", - "returnHome": "Return Home", - "addPatient": "Add Patient", - "addTask": "Add Task", - "createTask": "Create Task", - "editPatient": "Edit Patient", - "editTask": "Edit Task", - "openTasks": "Open Tasks", - "closedTasks": "Closed Tasks", - "noOpenTasks": "No open tasks", - "noClosedTasks": "No closed tasks", - "save": "Save", - "cancel": "Cancel", - "create": "Create", - "delete": "Delete", - "firstName": "First Name", - "lastName": "Last Name", - "male": "Male", - "female": "Female", - "diverse": "Diverse", - "time.today": "Today", - "myFavorites": "My Favorites", - "freeBeds": "Free Beds", - "cardView": "Card View", - "occupancy": "Occupancy", + "preferences": "Preferences", + "priority": "{priority, select, P1{Normal} P2{Medium} P3{High} P4{Critical} other{-}}", + "@priority": { + "placeholders": { + "priority": {} + } + }, + "priorityLabel": "Priority", + "priorityNone": "None", + "privacy": "Privacy", "properties": "Properties", - "addProperty": "Add Property", - "type": "Type", - "tableView": "Table View", - "subjectType": "Subject Type", - "active": "Active", - "inactive": "Inactive", - "rEdit": "Update {name}!", - "@rEdit": { + "property": "Property", + "rAdd": "Add {name}", + "@rAdd": { "placeholders": { "name": { "type": "string" } } }, - "rShow": "Show {name}", - "@rShow": { + "rEdit": "Update {name}!", + "@rEdit": { "placeholders": { "name": { "type": "string" } } }, - "rAdd": "Add {name}", - "@rAdd": { + "recentPatients": "Your Recent Patients", + "recentTasks": "Your Recent Tasks", + "refreshing": "Refreshing…", + "removeProperty": "Remove Property", + "removePropertyConfirmation": "Are you sure you want to remove this property? This action cannot be undone.", + "retakeSurvey": "Retake Survey", + "rShow": "Show {name}", + "@rShow": { "placeholders": { "name": { "type": "string" } } }, - "property": "Property", + "search": "Search", + "searchUserOrTeam": "Search for user (or team)...", + "searchUsersOrTeams": "Search users or teams...", + "security": "Security", + "selectAssignee": "Assign to...", + "selectLocation": "Select Location", "selectOptions": "Select Options", - "option": "Option", + "settings": "Settings", + "settingsDescription": "Here you can change the app configuration.", + "sex": "Sex", + "shiftHandover": "Shift Handover", + "showAllTasks": "Show All Tasks", + "showTeamTasks": "Show Team Tasks", + "stagingModalDisclaimerMarkdown": "This public instance of helpwave tasks is for '\\b{development and preview purposes}'. Please make sure to '\\b{only}' enter '\\b{non-confidential testing data}'. This instance can be '\\negative{\\b{deleted at any time}}'", + "status": "Status", + "subjectType": "Subject Type", + "surveyDescription": "Your feedback is valuable to us. Please take a moment to complete our survey.", + "surveyTitle": "Help us to improve helpwave tasks", "sPropertySubjectType": "{subject, select, patient{Patient} task{Task} other{Undefined Subject Type}}", "@sPropertySubjectType": { "placeholders": { @@ -280,38 +244,12 @@ } } }, - "archiveProperty": "Archive Property", - "archivedPropertyDescription": "Archived Properties can no longer be assigned to objects.", - "title": "Title", - "taskTitlePlaceholder": "What needs to be done?", - "selectPatient": "Select a patient", - "currentPatient": "Current Patient", - "selectAssignee": "Assign to...", - "unassigned": "Unassigned", - "newPatient": "New Patient", - "selectLocationDescription": "Please select the assigned location for the patient.", - "searchLocations": "Search locations...", - "searchUsersOrTeams": "Search users or teams...", - "searchUserOrTeam": "Search for user (or team)...", - "selectAll": "Select All", - "deselectAll": "Deselect All", - "loading": "Loading...", - "noLocationsFound": "No locations found", - "noResultsFound": "No results found", - "confirmSelection": "Confirm Selection", - "expandAll": "Expand All", - "collapseAll": "Collapse All", - "assignedLocation": "Assigned Location", - "selectLocation": "Select Location", - "selectRootLocation": "Select Root Location", - "selectRootLocationDescription": "Select the root locations that define your accessible scope. Only hospitals, practices, clinics, and teams can be selected. Selecting a parent location includes all its children.", - "locations": "locations", - "organizations": "Organizations", - "selectOrganization": "Select Organization", - "selectOrganizations": "Select Organizations", - "clearCache": "Clear Cache", - "account": "Account", "system": "System", + "task": "Task", + "tasks": "Tasks", + "tasksUpdatedRecently": "Tasks updated recently", + "taskTitlePlaceholder": "What needs to be done?", + "teams": "Teams", "themeMode": "{theme, select, dark{Dark} light{Light} system{System} other{System}}", "@themeMode": { "placeholders": { @@ -320,117 +258,35 @@ } } }, - "chooseLanguage": "Choose Language", - "chooseTheme": "Choose Theme", - "done": "Done", - "filterAll": "All", - "filterUndone": "Undone", - "showDone": "Show done", - "showAllPatients": "Show all patients", - "patientData": "Data", - "clickToAdd": "Click to add", - "rClickToAdd": "Click to add {name}!", - "@rClickToAdd": { - "placeholders": { - "name": { - "type": "string" - } - } - }, - "locationChipsShowFullPath": "Show full location path in chips", - "locationClinic": "Clinic", - "locationWard": "Ward", - "locationTeam": "Team", - "locationRoom": "Room", - "locationBed": "Bed", - "locationType": "{type, select, CLINIC{Clinic} WARD{Ward} TEAM{Team} ROOM{Room} BED{Bed} other{Other}}", - "@locationType": { - "placeholders": { - "type": { - "type": "string" - } - } - }, - "waitingForPatient": "Waiting for patient", - "patientActions": "Patient Actions", - "admitPatient": "Admit Patient", - "dischargePatient": "Discharge Patient", - "markPatientDead": "Mark Patient Dead", - "waitPatient": "Set Patient to Waiting", - "patientState": "{state, select, WAIT{Waiting} ADMITTED{Admitted} DISCHARGED{Discharged} DEAD{Dead} other{Unknown}}", - "@patientState": { - "placeholders": { - "state": { - "type": "string" - } - } - }, - "deleteTaskConfirmation": "Are you sure you want to delete this task? This action cannot be undone.", - "markPatientDeadConfirmation": "Are you sure you want to mark this patient as dead?", - "dischargePatientConfirmation": "Are you sure you want to discharge this patient? This action will change the patient state.", - "updateLocation": "Update Location", - "updateLocationConfirmation": "Are you sure you want to update the patient''s location?", - "removeProperty": "Remove Property", - "removePropertyConfirmation": "Are you sure you want to remove this property? This action cannot be undone.", - "openSurvey": "Open Survey", - "retakeSurvey": "Retake Survey", - "feedback": "Feedback", - "surveyTitle": "Help us to improve helpwave tasks", - "surveyDescription": "Your feedback is valuable to us. Please take a moment to complete our survey.", - "installApp": "Install App", - "installAppDescription": "Install helpwave tasks for a better experience with offline support and faster access.", - "install": "Install", - "conflictDetected": "Conflict Detected", - "overwrite": "Overwrite", - "noNotifications": "No recent updates", - "notifications": "Notifications", - "dismissAll": "Dismiss All", - "security": "Security", - "shiftHandover": "Shift Handover", - "shiftHandoverDescription": "Select a user to transfer all open tasks assigned to you.", - "confirmShiftHandover": "Confirm Shift Handover", - "confirmShiftHandoverDescription": "Are you sure you want to transfer all open tasks to the selected user?", - "confirmShiftHandoverDescriptionWithName": "Are you sure you want to transfer {taskCount, plural, =1{# open task} other{# open tasks}} to {name}?", - "@confirmShiftHandoverDescriptionWithName": { - "placeholders": { - "taskCount": { - "type": "number" - }, - "name": { - "type": "string" - } - } - }, - "priority": "{priority, select, P1{Normal} P2{Medium} P3{High} P4{Critical} other{-}}", - "@priority": { - "placeholders": { - "priority": {} - } - }, - "priorityLabel": "Priority", - "priorityNone": "None", - "print": "Print", - "printOnlyAvailableInTableMode": "Print is only available in table mode", - "deletePatient": "Delete Patient", - "deletePatientConfirmation": "Are you sure you want to delete this patient? This action cannot be undone.", - "feedbackDescription": "Share your feedback, report bugs, or suggest improvements.", - "url": "URL", + "title": "Title", + "totalPatients": "Total Patients", + "type": "Type", + "updated": "Updated", + "users": "Users", + "wards": "Wards", + "yes": "Yes", + "anonymous": "Anonymous", + "anonymousSubmission": "Anonymous Submission", + "collapseAll": "Collapse All", + "confirmSelection": "Confirm Selection", + "deselectAll": "Deselect All", "enterFeedback": "Enter your feedback here...", + "expandAll": "Expand All", + "feedbackDescription": "Share your feedback, report bugs, or suggest improvements.", + "noLocationsFound": "No locations found", + "pickClinic": "Pick the clinic", + "pickClinicDescription": "Select the clinic for this patient. Only clinic locations can be selected.", + "pickPosition": "Pick the location", + "pickPositionDescription": "Select the location for this patient. You can select hospital, practice, clinic, ward, bed, or room locations.", + "pickTeams": "Pick teams", + "pickTeamsDescription": "Select one or more teams for this patient. You can select clinic, team, practice, or hospital locations.", + "searchLocations": "Search locations...", + "selectAll": "Select All", + "selectLocationDescription": "Please select the assigned location for the patient.", + "selectRootLocation": "Select Root Location", + "selectRootLocationDescription": "Select the root locations that define your accessible scope. Only hospitals, practices, clinics, and teams can be selected. Selecting a parent location includes all its children.", "startRecording": "Start Recording", "stopRecording": "Stop Recording", - "submit": "Submit", - "username": "Username", - "anonymous": "Anonymous", - "withName": "With name (for follow-up questions)", - "enterName": "Enter your name...", - "users": "Users", - "showTeamTasks": "Show Team Tasks", - "showAllTasks": "Show All Tasks", - "error": "Error", - "errorOccurred": "An error occurred", - "estimatedTime": "Estimated Time (minutes)", - "ok": "OK", - "submitAnonymously": "Submit anonymously", "submissionDetails": "Submission Details", "submittingAs": "Submitting as {name}", "@submittingAs": { @@ -440,10 +296,36 @@ } } }, - "anonymousSubmission": "Anonymous Submission", + "submit": "Submit", + "url": "URL", + "userInformation": "User Information", + "admitPatient": "Admit Patient", + "clear": "Clear", + "clinic": "Clinic", "connectionConnected": "Connected", "connectionConnecting": "Connecting…", "connectionDisconnected": "Disconnected", - "refreshing": "Refreshing…", - "userInformation": "User Information" + "deletePatient": "Delete Patient", + "deletePatientConfirmation": "Are you sure you want to delete this patient? This action cannot be undone.", + "dischargePatient": "Discharge Patient", + "dischargePatientConfirmation": "Are you sure you want to discharge this patient? This action will change the patient state.", + "dismissAll": "Dismiss All", + "error": "Error", + "install": "Install", + "installApp": "Install App", + "installAppDescription": "Install helpwave tasks for a better experience with offline support and faster access.", + "loginRequired": "Login required", + "loginRequiredDescription": "To use this site you need to be logged in.", + "markPatientDead": "Mark Patient Dead", + "markPatientDeadConfirmation": "Are you sure you want to mark this patient as dead?", + "noNotifications": "No recent updates", + "notifications": "Notifications", + "ok": "OK", + "patientActions": "Patient Actions", + "position": "Location", + "selectClinic": "Select Clinic", + "selectPosition": "Select Location", + "selectTeams": "Select Teams", + "waitPatient": "Set Patient to Waiting", + "waitingForPatient": "Waiting for patient" } diff --git a/web/locales/es-ES.arb b/web/locales/es-ES.arb new file mode 100644 index 00000000..6968ba5f --- /dev/null +++ b/web/locales/es-ES.arb @@ -0,0 +1,221 @@ +{ + "@@locale": "es-ES", + "account": "Cuenta", + "active": "Activo", + "addPatient": "Añadir paciente", + "addProperty": "Añadir propiedad", + "addTask": "Añadir tarea", + "archivedPropertyDescription": "Las propiedades archivadas ya no pueden asignarse a objetos.", + "archiveProperty": "Archivar propiedad", + "assignedTo": "Asignado a", + "authenticationFailed": "Error de autenticación", + "birthdate": "Fecha de nacimiento", + "cancel": "Cancelar", + "clearCache": "Vaciar caché", + "clinics": "Clínicas", + "closedTasks": "Tareas cerradas", + "conflictDetected": "Conflicto detectado", + "confirm": "Confirmar", + "confirmShiftHandover": "Confirmar traspaso de turno", + "confirmShiftHandoverDescription": "¿Está seguro de que desea transferir todas las tareas abiertas al usuario seleccionado?", + "confirmShiftHandoverDescriptionWithName": "¿Está seguro de que desea transferir {taskCount, plural, =1{# tarea abierta} other{# tareas abiertas}} a {name}?", + "@confirmShiftHandoverDescriptionWithName": { + "placeholders": { + "taskCount": { "type": "number" }, + "name": { "type": "string" } + } + }, + "create": "Crear", + "createTask": "Crear tarea", + "currentTime": "Hora actual", + "dashboard": "Panel", + "dashboardWelcomeDescription": "Esto es lo que ocurre hoy.", + "dashboardWelcomeMorning": "Buenos días, {name}!", + "@dashboardWelcomeMorning": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeNoon": "Buenas tardes, {name}!", + "@dashboardWelcomeNoon": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeAfternoon": "Buenas tardes, {name}!", + "@dashboardWelcomeAfternoon": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeEvening": "Buenas noches, {name}!", + "@dashboardWelcomeEvening": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeNight": "Buenas noches, {name}!", + "@dashboardWelcomeNight": { "placeholders": { "name": { "type": "string" } } }, + "delete": "Eliminar", + "description": "Descripción", + "diverse": "Diverso", + "descriptionPlaceholder": "Añadir más detalles...", + "developmentAndPreviewInstance": "Instancia de desarrollo y vista previa", + "dismiss": "Cerrar", + "done": "Hecho", + "dueDate": "Fecha de vencimiento", + "editPatient": "Editar paciente", + "editTask": "Editar tarea", + "errorOccurred": "Se ha producido un error", + "estimatedTime": "Tiempo estimado (minutos)", + "feedback": "Comentarios", + "female": "Femenino", + "filterAll": "Todos", + "filterUndone": "Pendientes", + "firstName": "Nombre", + "freeBeds": "Camas libres", + "homePage": "Página principal", + "imprint": "Aviso legal", + "inactive": "Inactivo", + "language": "Idioma", + "lastName": "Apellido", + "location": "Ubicación", + "locationBed": "Cama", + "locationClinic": "Clínica", + "locationRoom": "Habitación", + "locationType": "{type, select, CLINIC{Clínica} WARD{Planta} TEAM{Equipo} ROOM{Habitación} BED{Cama} other{Otro}}", + "@locationType": { "placeholders": { "type": { "type": "string" } } }, + "locationWard": "Planta", + "login": "Iniciar sesión", + "logout": "Cerrar sesión", + "male": "Masculino", + "myFavorites": "Mis favoritos", + "myOpenTasks": "Mis tareas abiertas", + "myTasks": "Mis tareas", + "name": "Nombre", + "no": "No", + "noClosedTasks": "No hay tareas cerradas", + "noOpenTasks": "No hay tareas abiertas", + "noPatient": "Sin paciente", + "noResultsFound": "No se encontraron resultados", + "notAssigned": "No asignado", + "nPatient": "{count, plural, =1{# Paciente} other{# Pacientes}}", + "@nPatient": { "placeholders": { "count": { "type": "number" } } }, + "nTask": "{count, plural, =1{# Tarea} other{# Tareas}}", + "@nTask": { "placeholders": { "count": { "type": "number" } } }, + "occupancy": "Ocupación", + "openSurvey": "Abrir encuesta", + "openTasks": "Tareas abiertas", + "option": "Opción", + "organizations": "Organizaciones", + "overview": "Resumen", + "pages.404.notFound": "404 - Página no encontrada", + "pages.404.notFoundDescription1": "Esta no es la página que busca", + "pages.404.notFoundDescription2": "Volver a la", + "patient": "Paciente", + "patientData": "Datos", + "patients": "Pacientes", + "patientState": "{state, select, WAIT{En espera} ADMITTED{Ingresado} DISCHARGED{Alta} DEAD{Fallecido} other{Desconocido}}", + "@patientState": { "placeholders": { "state": { "type": "string" } } }, + "patientsUpdatedRecently": "Pacientes actualizados recientemente", + "pThemes": "{count, plural, =1{Tema} other{Temas}}", + "@pThemes": { "placeholders": { "count": { "type": "number" } } }, + "preferences": "Preferencias", + "priority": "{priority, select, P1{Normal} P2{Media} P3{Alta} P4{Crítica} other{-}}", + "@priority": { "placeholders": { "priority": {} } }, + "priorityLabel": "Prioridad", + "priorityNone": "Ninguna", + "privacy": "Privacidad", + "properties": "Propiedades", + "property": "Propiedad", + "rAdd": "Añadir {name}", + "@rAdd": { "placeholders": { "name": { "type": "string" } } }, + "rEdit": "Actualizar {name}!", + "@rEdit": { "placeholders": { "name": { "type": "string" } } }, + "recentPatients": "Tus pacientes recientes", + "recentTasks": "Tus tareas recientes", + "refreshing": "Actualizando…", + "removeProperty": "Eliminar propiedad", + "removePropertyConfirmation": "¿Está seguro de que desea eliminar esta propiedad? Esta acción no se puede deshacer.", + "retakeSurvey": "Repetir encuesta", + "rShow": "Mostrar {name}", + "@rShow": { "placeholders": { "name": { "type": "string" } } }, + "search": "Buscar", + "searchUserOrTeam": "Buscar usuario (o equipo)...", + "searchUsersOrTeams": "Buscar usuarios o equipos...", + "security": "Seguridad", + "selectAssignee": "Asignar a...", + "selectLocation": "Seleccionar ubicación", + "selectOptions": "Opciones", + "settings": "Ajustes", + "settingsDescription": "Aquí puede cambiar la configuración de la aplicación.", + "sex": "Sexo", + "shiftHandover": "Traspaso de turno", + "showAllTasks": "Mostrar todas las tareas", + "showTeamTasks": "Mostrar tareas del equipo", + "stagingModalDisclaimerMarkdown": "Esta instancia pública de helpwave tasks es para '\\b{desarrollo y vista previa}'. Asegúrese de '\\b{solo}' introducir '\\b{datos de prueba no confidenciales}'. Esta instancia puede '\\negative{\\b{eliminarse en cualquier momento}}'", + "status": "Estado", + "subjectType": "Tipo de sujeto", + "surveyDescription": "Su opinión es valiosa. Dedique un momento a completar nuestra encuesta.", + "surveyTitle": "Ayúdenos a mejorar helpwave tasks", + "sPropertySubjectType": "{subject, select, patient{Paciente} task{Tarea} other{Tipo de sujeto no definido}}", + "@sPropertySubjectType": { "placeholders": { "subject": { "type": "string" } } }, + "sPropertyType": "{type, select, multiSelect{Selección múltiple} singleSelect{Selección única} number{Número} text{Texto} date{Fecha} dateTime{Punto temporal} checkbox{Casilla} user{Usuario} other{Tipo no definido}}", + "@sPropertyType": { "placeholders": { "type": { "type": "string" } } }, + "system": "Sistema", + "task": "Tarea", + "tasks": "Tareas", + "tasksUpdatedRecently": "Tareas actualizadas recientemente", + "taskTitlePlaceholder": "¿Qué hay que hacer?", + "teams": "Equipos", + "themeMode": "{theme, select, dark{Oscuro} light{Claro} system{Sistema} other{Sistema}}", + "@themeMode": { "placeholders": { "theme": { "type": "string" } } }, + "title": "Título", + "totalPatients": "Total de pacientes", + "type": "Tipo", + "updated": "Actualizado", + "users": "Usuarios", + "wards": "Plantas", + "yes": "Sí", + "anonymous": "Anónimo", + "anonymousSubmission": "Envío anónimo", + "collapseAll": "Contraer todo", + "confirmSelection": "Confirmar selección", + "deselectAll": "Deseleccionar todo", + "enterFeedback": "Escriba aquí sus comentarios...", + "expandAll": "Expandir todo", + "feedbackDescription": "Comparta sus comentarios, reporte errores o sugiera mejoras.", + "noLocationsFound": "No se encontraron ubicaciones", + "pickClinic": "Elegir la clínica", + "pickClinicDescription": "Seleccione la clínica para este paciente. Solo se pueden seleccionar ubicaciones de tipo clínica.", + "pickPosition": "Elegir la ubicación", + "pickPositionDescription": "Seleccione la ubicación para este paciente. Puede elegir hospital, consulta, clínica, planta, cama o habitación.", + "pickTeams": "Elegir equipos", + "pickTeamsDescription": "Seleccione uno o más equipos para este paciente. Puede elegir clínica, equipo, consulta u hospital.", + "searchLocations": "Buscar ubicaciones...", + "selectAll": "Seleccionar todo", + "selectLocationDescription": "Seleccione la ubicación asignada para el paciente.", + "selectRootLocation": "Seleccionar ubicación raíz", + "selectRootLocationDescription": "Seleccione las ubicaciones raíz que definen su ámbito. Solo se pueden seleccionar hospitales, consultas, clínicas y equipos. Al seleccionar una ubicación padre se incluyen todas las hijas.", + "startRecording": "Iniciar grabación", + "stopRecording": "Detener grabación", + "submissionDetails": "Detalles del envío", + "submittingAs": "Enviando como {name}", + "@submittingAs": { "placeholders": { "name": { "type": "string" } } }, + "submit": "Enviar", + "url": "URL", + "userInformation": "Información del usuario", + "admitPatient": "Ingresar paciente", + "clear": "Borrar", + "clinic": "Clínica", + "connectionConnected": "Conectado", + "connectionConnecting": "Conectando…", + "connectionDisconnected": "Desconectado", + "deletePatient": "Eliminar paciente", + "deletePatientConfirmation": "¿Está seguro de que desea eliminar este paciente? Esta acción no se puede deshacer.", + "dischargePatient": "Dar de alta al paciente", + "dischargePatientConfirmation": "¿Está seguro de que desea dar de alta a este paciente? Esta acción cambiará el estado del paciente.", + "dismissAll": "Cerrar todo", + "error": "Error", + "install": "Instalar", + "installApp": "Instalar aplicación", + "installAppDescription": "Instale helpwave tasks para una mejor experiencia con soporte offline y acceso más rápido.", + "loginRequired": "Inicio de sesión requerido", + "loginRequiredDescription": "Para usar este sitio debe iniciar sesión.", + "markPatientDead": "Marcar paciente como fallecido", + "markPatientDeadConfirmation": "¿Está seguro de que desea marcar este paciente como fallecido?", + "noNotifications": "Sin actualizaciones recientes", + "notifications": "Notificaciones", + "ok": "OK", + "patientActions": "Acciones del paciente", + "position": "Ubicación", + "selectClinic": "Seleccionar clínica", + "selectPosition": "Seleccionar ubicación", + "selectTeams": "Seleccionar equipos", + "waitPatient": "Poner paciente en espera", + "waitingForPatient": "En espera de paciente" +} diff --git a/web/locales/fr-FR.arb b/web/locales/fr-FR.arb new file mode 100644 index 00000000..73373f83 --- /dev/null +++ b/web/locales/fr-FR.arb @@ -0,0 +1,221 @@ +{ + "@@locale": "fr-FR", + "account": "Compte", + "active": "Actif", + "addPatient": "Ajouter un patient", + "addProperty": "Ajouter une propriété", + "addTask": "Ajouter une tâche", + "archivedPropertyDescription": "Les propriétés archivées ne peuvent plus être assignées aux objets.", + "archiveProperty": "Archiver la propriété", + "assignedTo": "Assigné à", + "authenticationFailed": "Échec de l''authentification", + "birthdate": "Date de naissance", + "cancel": "Annuler", + "clearCache": "Vider le cache", + "clinics": "Cliniques", + "closedTasks": "Tâches terminées", + "conflictDetected": "Conflit détecté", + "confirm": "Confirmer", + "confirmShiftHandover": "Confirmer la passation", + "confirmShiftHandoverDescription": "Êtes-vous sûr de vouloir transférer toutes les tâches ouvertes à l''utilisateur sélectionné ?", + "confirmShiftHandoverDescriptionWithName": "Êtes-vous sûr de vouloir transférer {taskCount, plural, =1{# tâche ouverte} other{# tâches ouvertes}} à {name} ?", + "@confirmShiftHandoverDescriptionWithName": { + "placeholders": { + "taskCount": { "type": "number" }, + "name": { "type": "string" } + } + }, + "create": "Créer", + "createTask": "Créer une tâche", + "currentTime": "Heure actuelle", + "dashboard": "Tableau de bord", + "dashboardWelcomeDescription": "Voici ce qui se passe aujourd''hui.", + "dashboardWelcomeMorning": "Bonjour, {name} !", + "@dashboardWelcomeMorning": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeNoon": "Bon après-midi, {name} !", + "@dashboardWelcomeNoon": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeAfternoon": "Bon après-midi, {name} !", + "@dashboardWelcomeAfternoon": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeEvening": "Bonsoir, {name} !", + "@dashboardWelcomeEvening": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeNight": "Bonne nuit, {name} !", + "@dashboardWelcomeNight": { "placeholders": { "name": { "type": "string" } } }, + "delete": "Supprimer", + "description": "Description", + "diverse": "Divers", + "descriptionPlaceholder": "Ajouter plus de détails...", + "developmentAndPreviewInstance": "Instance de développement et d''aperçu", + "dismiss": "Fermer", + "done": "Terminé", + "dueDate": "Date d''échéance", + "editPatient": "Modifier le patient", + "editTask": "Modifier la tâche", + "errorOccurred": "Une erreur s''est produite", + "estimatedTime": "Temps estimé (minutes)", + "feedback": "Commentaires", + "female": "Féminin", + "filterAll": "Tous", + "filterUndone": "En attente", + "firstName": "Prénom", + "freeBeds": "Lits disponibles", + "homePage": "Page d''accueil", + "imprint": "Mentions légales", + "inactive": "Inactif", + "language": "Langue", + "lastName": "Nom de famille", + "location": "Emplacement", + "locationBed": "Lit", + "locationClinic": "Clinique", + "locationRoom": "Chambre", + "locationType": "{type, select, CLINIC{Clinique} WARD{Service} TEAM{Équipe} ROOM{Chambre} BED{Lit} other{Autre}}", + "@locationType": { "placeholders": { "type": { "type": "string" } } }, + "locationWard": "Service", + "login": "Connexion", + "logout": "Déconnexion", + "male": "Masculin", + "myFavorites": "Mes favoris", + "myOpenTasks": "Mes tâches ouvertes", + "myTasks": "Mes tâches", + "name": "Nom", + "no": "Non", + "noClosedTasks": "Aucune tâche terminée", + "noOpenTasks": "Aucune tâche ouverte", + "noPatient": "Aucun patient", + "noResultsFound": "Aucun résultat trouvé", + "notAssigned": "Non assigné", + "nPatient": "{count, plural, =1{# Patient} other{# Patients}}", + "@nPatient": { "placeholders": { "count": { "type": "number" } } }, + "nTask": "{count, plural, =1{# Tâche} other{# Tâches}}", + "@nTask": { "placeholders": { "count": { "type": "number" } } }, + "occupancy": "Occupation", + "openSurvey": "Ouvrir l''enquête", + "openTasks": "Tâches ouvertes", + "option": "Option", + "organizations": "Organisations", + "overview": "Aperçu", + "pages.404.notFound": "404 - Page non trouvée", + "pages.404.notFoundDescription1": "Ce n''est définitivement pas la page que vous cherchez", + "pages.404.notFoundDescription2": "Retour à la", + "patient": "Patient", + "patientData": "Données", + "patients": "Patients", + "patientState": "{state, select, WAIT{En attente} ADMITTED{Admis} DISCHARGED{Sorti} DEAD{Décédé} other{Inconnu}}", + "@patientState": { "placeholders": { "state": { "type": "string" } } }, + "patientsUpdatedRecently": "Patients mis à jour récemment", + "pThemes": "{count, plural, =1{Thème} other{Thèmes}}", + "@pThemes": { "placeholders": { "count": { "type": "number" } } }, + "preferences": "Préférences", + "priority": "{priority, select, P1{Normal} P2{Moyenne} P3{Haute} P4{Critique} other{-}}", + "@priority": { "placeholders": { "priority": {} } }, + "priorityLabel": "Priorité", + "priorityNone": "Aucune", + "privacy": "Confidentialité", + "properties": "Propriétés", + "property": "Propriété", + "rAdd": "Ajouter {name}", + "@rAdd": { "placeholders": { "name": { "type": "string" } } }, + "rEdit": "Mettre à jour {name} !", + "@rEdit": { "placeholders": { "name": { "type": "string" } } }, + "recentPatients": "Vos patients récents", + "recentTasks": "Vos tâches récentes", + "refreshing": "Actualisation…", + "removeProperty": "Supprimer la propriété", + "removePropertyConfirmation": "Êtes-vous sûr de vouloir supprimer cette propriété ? Cette action est irréversible.", + "retakeSurvey": "Refaire l''enquête", + "rShow": "Afficher {name}", + "@rShow": { "placeholders": { "name": { "type": "string" } } }, + "search": "Rechercher", + "searchUserOrTeam": "Rechercher un utilisateur (ou une équipe)...", + "searchUsersOrTeams": "Rechercher des utilisateurs ou des équipes...", + "security": "Sécurité", + "selectAssignee": "Assigner à...", + "selectLocation": "Sélectionner l''emplacement", + "selectOptions": "Options", + "settings": "Paramètres", + "settingsDescription": "Ici vous pouvez modifier la configuration de l''application.", + "sex": "Sexe", + "shiftHandover": "Passation", + "showAllTasks": "Afficher toutes les tâches", + "showTeamTasks": "Afficher les tâches d''équipe", + "stagingModalDisclaimerMarkdown": "Cette instance publique de helpwave tasks est destinée au '\\b{développement et à l''aperçu}'. Veuillez '\\b{ne}' saisir '\\b{que des données de test non confidentielles}'. Cette instance peut '\\negative{\\b{être supprimée à tout moment}}'", + "status": "Statut", + "subjectType": "Type de sujet", + "surveyDescription": "Vos commentaires sont précieux. Veuillez prendre un moment pour remplir notre enquête.", + "surveyTitle": "Aidez-nous à améliorer helpwave tasks", + "sPropertySubjectType": "{subject, select, patient{Patient} task{Tâche} other{Type de sujet non défini}}", + "@sPropertySubjectType": { "placeholders": { "subject": { "type": "string" } } }, + "sPropertyType": "{type, select, multiSelect{Sélection multiple} singleSelect{Sélection unique} number{Nombre} text{Texte} date{Date} dateTime{Date et heure} checkbox{Case à cocher} user{Utilisateur} other{Type non défini}}", + "@sPropertyType": { "placeholders": { "type": { "type": "string" } } }, + "system": "Système", + "task": "Tâche", + "tasks": "Tâches", + "tasksUpdatedRecently": "Tâches mises à jour récemment", + "taskTitlePlaceholder": "Que faut-il faire ?", + "teams": "Équipes", + "themeMode": "{theme, select, dark{Sombre} light{Clair} system{Système} other{Système}}", + "@themeMode": { "placeholders": { "theme": { "type": "string" } } }, + "title": "Titre", + "totalPatients": "Total des patients", + "type": "Type", + "updated": "Mis à jour", + "users": "Utilisateurs", + "wards": "Services", + "yes": "Oui", + "anonymous": "Anonyme", + "anonymousSubmission": "Envoi anonyme", + "collapseAll": "Tout réduire", + "confirmSelection": "Confirmer la sélection", + "deselectAll": "Tout désélectionner", + "enterFeedback": "Entrez vos commentaires ici...", + "expandAll": "Tout développer", + "feedbackDescription": "Partagez vos commentaires, signalez des bugs ou suggérez des améliorations.", + "noLocationsFound": "Aucun emplacement trouvé", + "pickClinic": "Choisir la clinique", + "pickClinicDescription": "Sélectionnez la clinique pour ce patient. Seules les emplacements de type clinique peuvent être sélectionnés.", + "pickPosition": "Choisir l''emplacement", + "pickPositionDescription": "Sélectionnez l''emplacement pour ce patient. Vous pouvez choisir hôpital, cabinet, clinique, service, lit ou chambre.", + "pickTeams": "Choisir les équipes", + "pickTeamsDescription": "Sélectionnez une ou plusieurs équipes pour ce patient. Vous pouvez choisir clinique, équipe, cabinet ou hôpital.", + "searchLocations": "Rechercher des emplacements...", + "selectAll": "Tout sélectionner", + "selectLocationDescription": "Veuillez sélectionner l''emplacement assigné au patient.", + "selectRootLocation": "Sélectionner l''emplacement racine", + "selectRootLocationDescription": "Sélectionnez les emplacements racine qui définissent votre périmètre. Seuls les hôpitaux, cabinets, cliniques et équipes peuvent être sélectionnés. La sélection d''un emplacement parent inclut tous ses enfants.", + "startRecording": "Démarrer l''enregistrement", + "stopRecording": "Arrêter l''enregistrement", + "submissionDetails": "Détails de l''envoi", + "submittingAs": "Envoi en tant que {name}", + "@submittingAs": { "placeholders": { "name": { "type": "string" } } }, + "submit": "Envoyer", + "url": "URL", + "userInformation": "Informations utilisateur", + "admitPatient": "Admettre le patient", + "clear": "Effacer", + "clinic": "Clinique", + "connectionConnected": "Connecté", + "connectionConnecting": "Connexion…", + "connectionDisconnected": "Déconnecté", + "deletePatient": "Supprimer le patient", + "deletePatientConfirmation": "Êtes-vous sûr de vouloir supprimer ce patient ? Cette action est irréversible.", + "dischargePatient": "Sortir le patient", + "dischargePatientConfirmation": "Êtes-vous sûr de vouloir sortir ce patient ? Cette action modifiera son état.", + "dismissAll": "Tout fermer", + "error": "Erreur", + "install": "Installer", + "installApp": "Installer l''application", + "installAppDescription": "Installez helpwave tasks pour une meilleure expérience avec prise en charge hors ligne et accès plus rapide.", + "loginRequired": "Connexion requise", + "loginRequiredDescription": "Pour utiliser ce site, vous devez être connecté.", + "markPatientDead": "Déclarer le patient décédé", + "markPatientDeadConfirmation": "Êtes-vous sûr de vouloir déclarer ce patient décédé ?", + "noNotifications": "Aucune mise à jour récente", + "notifications": "Notifications", + "ok": "OK", + "patientActions": "Actions patient", + "position": "Emplacement", + "selectClinic": "Sélectionner la clinique", + "selectPosition": "Sélectionner l''emplacement", + "selectTeams": "Sélectionner les équipes", + "waitPatient": "Mettre le patient en attente", + "waitingForPatient": "En attente du patient" +} diff --git a/web/locales/nl-NL.arb b/web/locales/nl-NL.arb new file mode 100644 index 00000000..02cf8e26 --- /dev/null +++ b/web/locales/nl-NL.arb @@ -0,0 +1,221 @@ +{ + "@@locale": "nl-NL", + "account": "Account", + "active": "Actief", + "addPatient": "Patiënt toevoegen", + "addProperty": "Eigenschap toevoegen", + "addTask": "Taak toevoegen", + "archivedPropertyDescription": "Gearchiveerde eigenschappen kunnen niet meer aan objecten worden toegewezen.", + "archiveProperty": "Eigenschap archiveren", + "assignedTo": "Toegewezen aan", + "authenticationFailed": "Authenticatie mislukt", + "birthdate": "Geboortedatum", + "cancel": "Annuleren", + "clearCache": "Cache legen", + "clinics": "Klinieken", + "closedTasks": "Afgeronde taken", + "conflictDetected": "Conflict gedetecteerd", + "confirm": "Bevestigen", + "confirmShiftHandover": "Dienstwissel bevestigen", + "confirmShiftHandoverDescription": "Weet u zeker dat u alle open taken wilt overdragen aan de geselecteerde gebruiker?", + "confirmShiftHandoverDescriptionWithName": "Weet u zeker dat u {taskCount, plural, =1{# open taak} other{# open taken}} wilt overdragen aan {name}?", + "@confirmShiftHandoverDescriptionWithName": { + "placeholders": { + "taskCount": { "type": "number" }, + "name": { "type": "string" } + } + }, + "create": "Aanmaken", + "createTask": "Taak aanmaken", + "currentTime": "Huidige tijd", + "dashboard": "Dashboard", + "dashboardWelcomeDescription": "Dit is wat er vandaag gebeurt.", + "dashboardWelcomeMorning": "Goedemorgen, {name}!", + "@dashboardWelcomeMorning": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeNoon": "Goedemiddag, {name}!", + "@dashboardWelcomeNoon": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeAfternoon": "Goedemiddag, {name}!", + "@dashboardWelcomeAfternoon": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeEvening": "Goedenavond, {name}!", + "@dashboardWelcomeEvening": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeNight": "Welterusten, {name}!", + "@dashboardWelcomeNight": { "placeholders": { "name": { "type": "string" } } }, + "delete": "Verwijderen", + "description": "Beschrijving", + "diverse": "Divers", + "descriptionPlaceholder": "Meer details toevoegen...", + "developmentAndPreviewInstance": "Ontwikkel- en voorbeeldinstantie", + "dismiss": "Sluiten", + "done": "Klaar", + "dueDate": "Vervaldatum", + "editPatient": "Patiënt bewerken", + "editTask": "Taak bewerken", + "errorOccurred": "Er is een fout opgetreden", + "estimatedTime": "Geschatte tijd (minuten)", + "feedback": "Feedback", + "female": "Vrouw", + "filterAll": "Alle", + "filterUndone": "Open", + "firstName": "Voornaam", + "freeBeds": "Vrije bedden", + "homePage": "Startpagina", + "imprint": "Impressum", + "inactive": "Inactief", + "language": "Taal", + "lastName": "Achternaam", + "location": "Locatie", + "locationBed": "Bed", + "locationClinic": "Kliniek", + "locationRoom": "Kamer", + "locationType": "{type, select, CLINIC{Kliniek} WARD{Afdeling} TEAM{Team} ROOM{Kamer} BED{Bed} other{Overig}}", + "@locationType": { "placeholders": { "type": { "type": "string" } } }, + "locationWard": "Afdeling", + "login": "Inloggen", + "logout": "Uitloggen", + "male": "Man", + "myFavorites": "Mijn favorieten", + "myOpenTasks": "Mijn open taken", + "myTasks": "Mijn taken", + "name": "Naam", + "no": "Nee", + "noClosedTasks": "Geen afgeronde taken", + "noOpenTasks": "Geen open taken", + "noPatient": "Geen patiënt", + "noResultsFound": "Geen resultaten gevonden", + "notAssigned": "Niet toegewezen", + "nPatient": "{count, plural, =1{# Patiënt} other{# Patiënten}}", + "@nPatient": { "placeholders": { "count": { "type": "number" } } }, + "nTask": "{count, plural, =1{# Taak} other{# Taken}}", + "@nTask": { "placeholders": { "count": { "type": "number" } } }, + "occupancy": "Bezetting", + "openSurvey": "Enquête openen", + "openTasks": "Open taken", + "option": "Optie", + "organizations": "Organisaties", + "overview": "Overzicht", + "pages.404.notFound": "404 - Pagina niet gevonden", + "pages.404.notFoundDescription1": "Dit is zeker niet de pagina die u zoekt", + "pages.404.notFoundDescription2": "Terug naar de", + "patient": "Patiënt", + "patientData": "Gegevens", + "patients": "Patiënten", + "patientState": "{state, select, WAIT{Wachtend} ADMITTED{Opgenomen} DISCHARGED{Ontslagen} DEAD{Overleden} other{Onbekend}}", + "@patientState": { "placeholders": { "state": { "type": "string" } } }, + "patientsUpdatedRecently": "Recent bijgewerkte patiënten", + "pThemes": "{count, plural, =1{Thema} other{Thema''s}}", + "@pThemes": { "placeholders": { "count": { "type": "number" } } }, + "preferences": "Voorkeuren", + "priority": "{priority, select, P1{Normaal} P2{Medium} P3{Hoog} P4{Kritiek} other{-}}", + "@priority": { "placeholders": { "priority": {} } }, + "priorityLabel": "Prioriteit", + "priorityNone": "Geen", + "privacy": "Privacy", + "properties": "Eigenschappen", + "property": "Eigenschap", + "rAdd": "{name} toevoegen", + "@rAdd": { "placeholders": { "name": { "type": "string" } } }, + "rEdit": "{name} bijwerken!", + "@rEdit": { "placeholders": { "name": { "type": "string" } } }, + "recentPatients": "Uw recente patiënten", + "recentTasks": "Uw recente taken", + "refreshing": "Bijwerken…", + "removeProperty": "Eigenschap verwijderen", + "removePropertyConfirmation": "Weet u zeker dat u deze eigenschap wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.", + "retakeSurvey": "Enquête opnieuw doen", + "rShow": "{name} tonen", + "@rShow": { "placeholders": { "name": { "type": "string" } } }, + "search": "Zoeken", + "searchUserOrTeam": "Zoek gebruiker (of team)...", + "searchUsersOrTeams": "Zoek gebruikers of teams...", + "security": "Beveiliging", + "selectAssignee": "Toewijzen aan...", + "selectLocation": "Locatie selecteren", + "selectOptions": "Opties", + "settings": "Instellingen", + "settingsDescription": "Hier kunt u de app-configuratie wijzigen.", + "sex": "Geslacht", + "shiftHandover": "Dienstwissel", + "showAllTasks": "Alle taken tonen", + "showTeamTasks": "Teamtaken tonen", + "stagingModalDisclaimerMarkdown": "Deze openbare instantie van helpwave tasks is voor '\\b{ontwikkeling en voorbeeld}'. Zorg ervoor dat u '\\b{alleen}' '\\b{niet-vertrouwelijke testgegevens}' invoert. Deze instantie kan '\\negative{\\b{op elk moment worden verwijderd}}'", + "status": "Status", + "subjectType": "Onderwerptype", + "surveyDescription": "Uw feedback is waardevol. Neem een moment om onze enquête in te vullen.", + "surveyTitle": "Help ons helpwave tasks te verbeteren", + "sPropertySubjectType": "{subject, select, patient{Patiënt} task{Taak} other{Ongedefinieerd onderwerptype}}", + "@sPropertySubjectType": { "placeholders": { "subject": { "type": "string" } } }, + "sPropertyType": "{type, select, multiSelect{Meerdere selectie} singleSelect{Enkele selectie} number{Getal} text{Tekst} date{Datum} dateTime{Tijdstip} checkbox{Selectievakje} user{Gebruiker} other{Ongedefinieerd type}}", + "@sPropertyType": { "placeholders": { "type": { "type": "string" } } }, + "system": "Systeem", + "task": "Taak", + "tasks": "Taken", + "tasksUpdatedRecently": "Recent bijgewerkte taken", + "taskTitlePlaceholder": "Wat moet er gedaan worden?", + "teams": "Teams", + "themeMode": "{theme, select, dark{Donker} light{Licht} system{Systeem} other{Systeem}}", + "@themeMode": { "placeholders": { "theme": { "type": "string" } } }, + "title": "Titel", + "totalPatients": "Totaal patiënten", + "type": "Type", + "updated": "Bijgewerkt", + "users": "Gebruikers", + "wards": "Afdelingen", + "yes": "Ja", + "anonymous": "Anoniem", + "anonymousSubmission": "Anonieme inzending", + "collapseAll": "Alles invouwen", + "confirmSelection": "Selectie bevestigen", + "deselectAll": "Alles deselecteren", + "enterFeedback": "Voer hier uw feedback in...", + "expandAll": "Alles uitvouwen", + "feedbackDescription": "Deel uw feedback, meld bugs of stel verbeteringen voor.", + "noLocationsFound": "Geen locaties gevonden", + "pickClinic": "Kies de kliniek", + "pickClinicDescription": "Selecteer de kliniek voor deze patiënt. Alleen klinieklocaties kunnen worden geselecteerd.", + "pickPosition": "Kies de locatie", + "pickPositionDescription": "Selecteer de locatie voor deze patiënt. U kunt ziekenhuis, praktijk, kliniek, afdeling, bed of kamer selecteren.", + "pickTeams": "Kies teams", + "pickTeamsDescription": "Selecteer een of meer teams voor deze patiënt. U kunt kliniek, team, praktijk of ziekenhuis selecteren.", + "searchLocations": "Locaties zoeken...", + "selectAll": "Alles selecteren", + "selectLocationDescription": "Selecteer de toegewezen locatie voor de patiënt.", + "selectRootLocation": "Hooflocatie selecteren", + "selectRootLocationDescription": "Selecteer de hoofdlocaties die uw bereik bepalen. Alleen ziekenhuizen, praktijken, klinieken en teams kunnen worden geselecteerd. Het selecteren van een bovenliggende locatie omvat alle onderliggende.", + "startRecording": "Opname starten", + "stopRecording": "Opname stoppen", + "submissionDetails": "Inzendingsgegevens", + "submittingAs": "Verzenden als {name}", + "@submittingAs": { "placeholders": { "name": { "type": "string" } } }, + "submit": "Verzenden", + "url": "URL", + "userInformation": "Gebruikersinformatie", + "admitPatient": "Patiënt opnemen", + "clear": "Wissen", + "clinic": "Kliniek", + "connectionConnected": "Verbonden", + "connectionConnecting": "Verbinden…", + "connectionDisconnected": "Verbinding verbroken", + "deletePatient": "Patiënt verwijderen", + "deletePatientConfirmation": "Weet u zeker dat u deze patiënt wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.", + "dischargePatient": "Patiënt ontslaan", + "dischargePatientConfirmation": "Weet u zeker dat u deze patiënt wilt ontslaan? Deze actie wijzigt de status van de patiënt.", + "dismissAll": "Alles sluiten", + "error": "Fout", + "install": "Installeren", + "installApp": "App installeren", + "installAppDescription": "Installeer helpwave tasks voor een betere ervaring met offline ondersteuning en snellere toegang.", + "loginRequired": "Inloggen vereist", + "loginRequiredDescription": "Om deze site te gebruiken moet u zijn ingelogd.", + "markPatientDead": "Patiënt als overleden markeren", + "markPatientDeadConfirmation": "Weet u zeker dat u deze patiënt als overleden wilt markeren?", + "noNotifications": "Geen recente updates", + "notifications": "Meldingen", + "ok": "OK", + "patientActions": "Patiëntacties", + "position": "Locatie", + "selectClinic": "Kliniek selecteren", + "selectPosition": "Locatie selecteren", + "selectTeams": "Teams selecteren", + "waitPatient": "Patiënt in wachtrij zetten", + "waitingForPatient": "Wachten op patiënt" +} diff --git a/web/locales/pt-BR.arb b/web/locales/pt-BR.arb new file mode 100644 index 00000000..bdde681c --- /dev/null +++ b/web/locales/pt-BR.arb @@ -0,0 +1,221 @@ +{ + "@@locale": "pt-BR", + "account": "Conta", + "active": "Ativo", + "addPatient": "Adicionar paciente", + "addProperty": "Adicionar propriedade", + "addTask": "Adicionar tarefa", + "archivedPropertyDescription": "Propriedades arquivadas não podem mais ser atribuídas a objetos.", + "archiveProperty": "Arquivar propriedade", + "assignedTo": "Atribuído a", + "authenticationFailed": "Falha na autenticação", + "birthdate": "Data de nascimento", + "cancel": "Cancelar", + "clearCache": "Limpar cache", + "clinics": "Clínicas", + "closedTasks": "Tarefas concluídas", + "conflictDetected": "Conflito detectado", + "confirm": "Confirmar", + "confirmShiftHandover": "Confirmar passagem de turno", + "confirmShiftHandoverDescription": "Tem certeza de que deseja transferir todas as tarefas abertas para o usuário selecionado?", + "confirmShiftHandoverDescriptionWithName": "Tem certeza de que deseja transferir {taskCount, plural, =1{# tarefa aberta} other{# tarefas abertas}} para {name}?", + "@confirmShiftHandoverDescriptionWithName": { + "placeholders": { + "taskCount": { "type": "number" }, + "name": { "type": "string" } + } + }, + "create": "Criar", + "createTask": "Criar tarefa", + "currentTime": "Hora atual", + "dashboard": "Painel", + "dashboardWelcomeDescription": "Eis o que está acontecendo hoje.", + "dashboardWelcomeMorning": "Bom dia, {name}!", + "@dashboardWelcomeMorning": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeNoon": "Boa tarde, {name}!", + "@dashboardWelcomeNoon": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeAfternoon": "Boa tarde, {name}!", + "@dashboardWelcomeAfternoon": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeEvening": "Boa noite, {name}!", + "@dashboardWelcomeEvening": { "placeholders": { "name": { "type": "string" } } }, + "dashboardWelcomeNight": "Boa noite, {name}!", + "@dashboardWelcomeNight": { "placeholders": { "name": { "type": "string" } } }, + "delete": "Excluir", + "description": "Descrição", + "diverse": "Diverso", + "descriptionPlaceholder": "Adicionar mais detalhes...", + "developmentAndPreviewInstance": "Instância de desenvolvimento e pré-visualização", + "dismiss": "Fechar", + "done": "Concluído", + "dueDate": "Data de vencimento", + "editPatient": "Editar paciente", + "editTask": "Editar tarefa", + "errorOccurred": "Ocorreu um erro", + "estimatedTime": "Tempo estimado (minutos)", + "feedback": "Feedback", + "female": "Feminino", + "filterAll": "Todos", + "filterUndone": "Pendentes", + "firstName": "Nome", + "freeBeds": "Leitos livres", + "homePage": "Página inicial", + "imprint": "Impresso", + "inactive": "Inativo", + "language": "Idioma", + "lastName": "Sobrenome", + "location": "Localização", + "locationBed": "Leito", + "locationClinic": "Clínica", + "locationRoom": "Quarto", + "locationType": "{type, select, CLINIC{Clínica} WARD{Enfermaria} TEAM{Equipe} ROOM{Quarto} BED{Leito} other{Outro}}", + "@locationType": { "placeholders": { "type": { "type": "string" } } }, + "locationWard": "Enfermaria", + "login": "Entrar", + "logout": "Sair", + "male": "Masculino", + "myFavorites": "Meus favoritos", + "myOpenTasks": "Minhas tarefas abertas", + "myTasks": "Minhas tarefas", + "name": "Nome", + "no": "Não", + "noClosedTasks": "Nenhuma tarefa concluída", + "noOpenTasks": "Nenhuma tarefa aberta", + "noPatient": "Sem paciente", + "noResultsFound": "Nenhum resultado encontrado", + "notAssigned": "Não atribuído", + "nPatient": "{count, plural, =1{# Paciente} other{# Pacientes}}", + "@nPatient": { "placeholders": { "count": { "type": "number" } } }, + "nTask": "{count, plural, =1{# Tarefa} other{# Tarefas}}", + "@nTask": { "placeholders": { "count": { "type": "number" } } }, + "occupancy": "Ocupação", + "openSurvey": "Abrir pesquisa", + "openTasks": "Tarefas abertas", + "option": "Opção", + "organizations": "Organizações", + "overview": "Visão geral", + "pages.404.notFound": "404 - Página não encontrada", + "pages.404.notFoundDescription1": "Esta não é a página que você procura", + "pages.404.notFoundDescription2": "Voltar para a", + "patient": "Paciente", + "patientData": "Dados", + "patients": "Pacientes", + "patientState": "{state, select, WAIT{Aguardando} ADMITTED{Internado} DISCHARGED{Alta} DEAD{Óbito} other{Desconhecido}}", + "@patientState": { "placeholders": { "state": { "type": "string" } } }, + "patientsUpdatedRecently": "Pacientes atualizados recentemente", + "pThemes": "{count, plural, =1{Tema} other{Temas}}", + "@pThemes": { "placeholders": { "count": { "type": "number" } } }, + "preferences": "Preferências", + "priority": "{priority, select, P1{Normal} P2{Média} P3{Alta} P4{Crítica} other{-}}", + "@priority": { "placeholders": { "priority": {} } }, + "priorityLabel": "Prioridade", + "priorityNone": "Nenhuma", + "privacy": "Privacidade", + "properties": "Propriedades", + "property": "Propriedade", + "rAdd": "Adicionar {name}", + "@rAdd": { "placeholders": { "name": { "type": "string" } } }, + "rEdit": "Atualizar {name}!", + "@rEdit": { "placeholders": { "name": { "type": "string" } } }, + "recentPatients": "Seus pacientes recentes", + "recentTasks": "Suas tarefas recentes", + "refreshing": "Atualizando…", + "removeProperty": "Remover propriedade", + "removePropertyConfirmation": "Tem certeza de que deseja remover esta propriedade? Esta ação não pode ser desfeita.", + "retakeSurvey": "Refazer pesquisa", + "rShow": "Mostrar {name}", + "@rShow": { "placeholders": { "name": { "type": "string" } } }, + "search": "Pesquisar", + "searchUserOrTeam": "Pesquisar usuário (ou equipe)...", + "searchUsersOrTeams": "Pesquisar usuários ou equipes...", + "security": "Segurança", + "selectAssignee": "Atribuir a...", + "selectLocation": "Selecionar localização", + "selectOptions": "Opções", + "settings": "Configurações", + "settingsDescription": "Aqui você pode alterar a configuração do aplicativo.", + "sex": "Sexo", + "shiftHandover": "Passagem de turno", + "showAllTasks": "Mostrar todas as tarefas", + "showTeamTasks": "Mostrar tarefas da equipe", + "stagingModalDisclaimerMarkdown": "Esta instância pública do helpwave tasks é para '\\b{desenvolvimento e pré-visualização}'. Certifique-se de '\\b{apenas}' inserir '\\b{dados de teste não confidenciais}'. Esta instância pode '\\negative{\\b{ser excluída a qualquer momento}}'", + "status": "Status", + "subjectType": "Tipo de sujeito", + "surveyDescription": "Seu feedback é valioso. Por favor, dedique um momento para responder nossa pesquisa.", + "surveyTitle": "Ajude-nos a melhorar o helpwave tasks", + "sPropertySubjectType": "{subject, select, patient{Paciente} task{Tarefa} other{Tipo de sujeito indefinido}}", + "@sPropertySubjectType": { "placeholders": { "subject": { "type": "string" } } }, + "sPropertyType": "{type, select, multiSelect{Seleção múltipla} singleSelect{Seleção única} number{Número} text{Texto} date{Data} dateTime{Data/hora} checkbox{Caixa de seleção} user{Usuário} other{Tipo indefinido}}", + "@sPropertyType": { "placeholders": { "type": { "type": "string" } } }, + "system": "Sistema", + "task": "Tarefa", + "tasks": "Tarefas", + "tasksUpdatedRecently": "Tarefas atualizadas recentemente", + "taskTitlePlaceholder": "O que precisa ser feito?", + "teams": "Equipes", + "themeMode": "{theme, select, dark{Escuro} light{Claro} system{Sistema} other{Sistema}}", + "@themeMode": { "placeholders": { "theme": { "type": "string" } } }, + "title": "Título", + "totalPatients": "Total de pacientes", + "type": "Tipo", + "updated": "Atualizado", + "users": "Usuários", + "wards": "Enfermarias", + "yes": "Sim", + "anonymous": "Anônimo", + "anonymousSubmission": "Envio anônimo", + "collapseAll": "Recolher tudo", + "confirmSelection": "Confirmar seleção", + "deselectAll": "Desmarcar todos", + "enterFeedback": "Digite seu feedback aqui...", + "expandAll": "Expandir tudo", + "feedbackDescription": "Compartilhe seu feedback, reporte bugs ou sugira melhorias.", + "noLocationsFound": "Nenhuma localização encontrada", + "pickClinic": "Escolher a clínica", + "pickClinicDescription": "Selecione a clínica para este paciente. Apenas localizações do tipo clínica podem ser selecionadas.", + "pickPosition": "Escolher a localização", + "pickPositionDescription": "Selecione a localização para este paciente. Você pode escolher hospital, consultório, clínica, enfermaria, leito ou quarto.", + "pickTeams": "Escolher equipes", + "pickTeamsDescription": "Selecione uma ou mais equipes para este paciente. Você pode escolher clínica, equipe, consultório ou hospital.", + "searchLocations": "Pesquisar localizações...", + "selectAll": "Selecionar todos", + "selectLocationDescription": "Selecione a localização atribuída ao paciente.", + "selectRootLocation": "Selecionar localização raiz", + "selectRootLocationDescription": "Selecione as localizações raiz que definem seu escopo. Apenas hospitais, consultórios, clínicas e equipes podem ser selecionados. Selecionar uma localização pai inclui todas as filhas.", + "startRecording": "Iniciar gravação", + "stopRecording": "Parar gravação", + "submissionDetails": "Detalhes do envio", + "submittingAs": "Enviando como {name}", + "@submittingAs": { "placeholders": { "name": { "type": "string" } } }, + "submit": "Enviar", + "url": "URL", + "userInformation": "Informações do usuário", + "admitPatient": "Internar paciente", + "clear": "Limpar", + "clinic": "Clínica", + "connectionConnected": "Conectado", + "connectionConnecting": "Conectando…", + "connectionDisconnected": "Desconectado", + "deletePatient": "Excluir paciente", + "deletePatientConfirmation": "Tem certeza de que deseja excluir este paciente? Esta ação não pode ser desfeita.", + "dischargePatient": "Dar alta ao paciente", + "dischargePatientConfirmation": "Tem certeza de que deseja dar alta a este paciente? Esta ação alterará o estado do paciente.", + "dismissAll": "Descartar todas", + "error": "Erro", + "install": "Instalar", + "installApp": "Instalar aplicativo", + "installAppDescription": "Instale o helpwave tasks para uma melhor experiência com suporte offline e acesso mais rápido.", + "loginRequired": "Login necessário", + "loginRequiredDescription": "Para usar este site você precisa estar conectado.", + "markPatientDead": "Marcar paciente como óbito", + "markPatientDeadConfirmation": "Tem certeza de que deseja marcar este paciente como óbito?", + "noNotifications": "Nenhuma atualização recente", + "notifications": "Notificações", + "ok": "OK", + "patientActions": "Ações do paciente", + "position": "Localização", + "selectClinic": "Selecionar clínica", + "selectPosition": "Selecionar localização", + "selectTeams": "Selecionar equipes", + "waitPatient": "Colocar paciente em espera", + "waitingForPatient": "Aguardando paciente" +} diff --git a/web/package.json b/web/package.json index 55cca4b4..d415f2d0 100644 --- a/web/package.json +++ b/web/package.json @@ -9,7 +9,8 @@ "build": "npm run build-intl & next build", "start": "next start", "lint": "tsc --noEmit && eslint .", - "generate-graphql": "graphql-codegen" + "generate-graphql": "graphql-codegen", + "check-translations": "node scripts/check-translation-keys.mjs" }, "dependencies": { "@apollo/client": "4.0.11", diff --git a/web/pages/index.tsx b/web/pages/index.tsx index 4aafd1c4..e9159080 100644 --- a/web/pages/index.tsx +++ b/web/pages/index.tsx @@ -74,7 +74,11 @@ const StatCard = ({ label, value, icon, iconWrapperClassName, className }: StatC const Dashboard: NextPage = () => { const translation = useTasksTranslation() const { user, myTasksCount, totalPatientsCount } = useTasksContext() - const { data, refetch } = useOverviewData() + const overviewVariables = useMemo(() => ({ + recentTasksPagination: { pageSize: 5, pageIndex: 0 }, + recentPatientsPagination: { pageSize: 5, pageIndex: 0 }, + }), []) + const { data, refetch } = useOverviewData(overviewVariables) const [completeTask] = useCompleteTask() const [reopenTask] = useReopenTask() @@ -119,20 +123,20 @@ const Dashboard: NextPage = () => { -
+
completeTask({ variables: { id }, onCompleted: () => refetch() }), [completeTask, refetch])} reopenTask={useCallback((id) => reopenTask({ variables: { id }, onCompleted: () => refetch() }), [reopenTask, refetch])} onSelectPatient={setSelectedPatientId} onSelectTask={setSelectedTaskId} - className="w-full 2xl:min-w-150 flex-1" + className="w-full min-w-0" />
diff --git a/web/pages/location/[id].tsx b/web/pages/location/[id].tsx index 96c01543..b72e9606 100644 --- a/web/pages/location/[id].tsx +++ b/web/pages/location/[id].tsx @@ -170,6 +170,8 @@ const LocationPage: NextPage = () => { {translation('locationType', { type: locationKind })} )} +
+ {parentChain.length > 0 && (
{parentChain.map((parent, index) => (
@@ -178,7 +180,7 @@ const LocationPage: NextPage = () => {
))}
- + )} ) } diff --git a/web/scripts/check-translation-keys.mjs b/web/scripts/check-translation-keys.mjs new file mode 100644 index 00000000..fff6f6e4 --- /dev/null +++ b/web/scripts/check-translation-keys.mjs @@ -0,0 +1,108 @@ +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const webRoot = path.resolve(__dirname, '..') +const localesDir = path.join(webRoot, 'locales') + +const KNOWN_DYNAMIC_KEYS = [ + 'dashboardWelcomeMorning', + 'dashboardWelcomeNoon', + 'dashboardWelcomeAfternoon', + 'dashboardWelcomeEvening', + 'dashboardWelcomeNight', +] + +const TRANSLATION_CALL_RE = /translation\s*\(\s*['"]([^'"]+)['"]/g + +function loadArbKeys(filePath) { + const raw = fs.readFileSync(filePath, 'utf8') + const json = JSON.parse(raw) + return new Set( + Object.keys(json).filter( + (k) => !k.startsWith('@') && k !== '@@locale' + ) + ) +} + +function allArbFiles() { + const names = fs.readdirSync(localesDir) + return names + .filter((n) => n.endsWith('.arb') && !n.includes('/')) + .map((n) => path.join(localesDir, n)) +} + +const SCAN_DIRS = ['components', 'pages', 'hooks', 'utils', 'providers', 'data'] +const SKIP_DIRS = new Set(['node_modules', '.next', 'scripts', '__tests__', 'cache', 'link', 'mutations', 'storage', 'subscriptions']) + +function collectKeysFromSource(dir, keys = new Set()) { + const entries = fs.readdirSync(dir, { withFileTypes: true }) + for (const e of entries) { + const full = path.join(dir, e.name) + if (SKIP_DIRS.has(e.name)) continue + if (e.isDirectory()) { + collectKeysFromSource(full, keys) + continue + } + if (!/\.(tsx?|jsx?|mjs|cjs)$/.test(e.name)) continue + if (path.basename(full) === 'translations.ts') continue + const content = fs.readFileSync(full, 'utf8') + let m + TRANSLATION_CALL_RE.lastIndex = 0 + while ((m = TRANSLATION_CALL_RE.exec(content)) !== null) { + keys.add(m[1]) + } + } + return keys +} + +function main() { + const usedKeys = new Set() + for (const subdir of SCAN_DIRS) { + const dir = path.join(webRoot, subdir) + if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) { + collectKeysFromSource(dir, usedKeys) + } + } + KNOWN_DYNAMIC_KEYS.forEach((k) => usedKeys.add(k)) + + const arbPaths = allArbFiles() + if (arbPaths.length === 0) { + console.error('No ARB files found in', localesDir) + process.exit(1) + } + + const localeKeys = new Map() + for (const p of arbPaths) { + const locale = path.basename(p, '.arb') + localeKeys.set(locale, loadArbKeys(p)) + } + + const allLocales = [...localeKeys.keys()] + const missing = [] + for (const key of usedKeys) { + for (const locale of allLocales) { + if (!localeKeys.get(locale).has(key)) { + missing.push({ key, locale }) + } + } + } + + if (missing.length > 0) { + console.error('Missing translation keys (used in code but not in ARB):\n') + const byKey = new Map() + for (const { key, locale } of missing) { + if (!byKey.has(key)) byKey.set(key, []) + byKey.get(key).push(locale) + } + for (const [key, locales] of byKey) { + console.error(` [${key}] missing in: ${locales.join(', ')}`) + } + process.exit(1) + } + + console.log('All translation keys used in code exist in every locale.') +} + +main() diff --git a/web/style/index.css b/web/style/index.css index 900d0d09..df895453 100644 --- a/web/style/index.css +++ b/web/style/index.css @@ -1,2 +1 @@ -@import "./colors.css"; -@import "./table-printing.css"; \ No newline at end of file +@import "./colors.css"; \ No newline at end of file diff --git a/web/style/table-printing.css b/web/style/table-printing.css deleted file mode 100644 index e0b916e2..00000000 --- a/web/style/table-printing.css +++ /dev/null @@ -1,161 +0,0 @@ -@media print { - @page { - size: A4 landscape; - margin: 0; - } - - * { - -webkit-print-color-adjust: exact !important; - print-color-adjust: exact !important; - } - - * { - min-width: auto !important; - text-decoration: none !important; - } - - html, - body { - width: 100% !important; - height: auto !important; - margin: 0 !important; - padding: 0 !important; - overflow: hidden !important; - overflow-x: hidden !important; - overflow-y: hidden !important; - max-height: none !important; - max-width: 100% !important; - background: white !important; - color: black !important; - } - - body * { - visibility: hidden; - } - - .print-content, - .print-content * { - visibility: visible; - } - - .print-content { - position: absolute; - width: 100% !important; - max-width: 100% !important; - left: 0 !important; - top: 0 !important; - margin: 0 !important; - padding: 0 !important; - height: auto !important; - max-height: none !important; - } - - .print-content table { - width: 100% !important; - max-width: 100% !important; - - height: auto !important; - max-height: none !important; - - margin: 0 !important; - padding: 0 !important; - - font-size: 0.55rem; - page-break-inside: auto; - - table-layout: fixed; - border-collapse: collapse; - border: 1px solid #000; - - overflow: hidden !important; - overflow-x: hidden !important; - overflow-y: hidden !important; - } - - .print-content thead tr { - page-break-after: avoid; - break-after: avoid; - } - - /* TODO consider this, if all columns should have the same width - .print-content colgroup col { - width: auto !important; - } - */ - - .print-content thead tr th { - position: sticky; - top: 0; - z-index: 1; - width: auto !important; - max-width: 100% !important; - - font-size: 0.6rem !important; - text-align: left; - font-weight: bold; - - padding: 0.25rem 0.3rem !important; - border: 1px solid #000 !important; - border-radius: 0 !important; - - color: black !important; - background-color: #f0f0f0 !important; - } - - - .print-content tbody tr { - page-break-inside: avoid; - break-inside: avoid; - } - - .print-content tbody tr td { - max-width: 100% !important; - - font-size: 0.55rem !important; - text-align: left; - word-wrap: break-word; - overflow-wrap: break-word; - hyphens: auto; - - padding: 0.2rem 0.3rem !important; - border: 1px solid #000 !important; - border-radius: 0 !important; - - background: white !important; - color: black !important; - } - - .print-content tbody tr:nth-child(even) td { - background-color: #f9f9f9 !important; - } - - .print-content tbody tr.table-body-filler-row, - .print-content tbody tr:empty, - .print-content tbody tr:has(td:only-child) { - display: none !important; - } - - .print-button, - button { - display: none !important; - } - - a[href]:after { - content: ""; - } - - img { - max-width: 100% !important; - height: auto !important; - } - - .print-content [data-name="chip"] { - font-size: 0.55rem !important; - padding: 0.1rem 0.25rem !important; - - border: 1px solid #000 !important; - - background: white !important; - color: black !important; - } -} \ No newline at end of file diff --git a/web/utils/propertyColumn.tsx b/web/utils/propertyColumn.tsx index 40a473c6..e8cce1b7 100644 --- a/web/utils/propertyColumn.tsx +++ b/web/utils/propertyColumn.tsx @@ -1,5 +1,5 @@ import type { ColumnDef } from '@tanstack/table-core' -import { ColumnType, FieldType, type LocationType, type PropertyDefinitionType, type PropertyValueType } from '@/api/gql/generated' +import { ColumnType, FieldType, type LocationType, type PropertyDefinitionType, type PropertyValueType, type PropertyEntity } from '@/api/gql/generated' import { getPropertyFilterFn } from './propertyFilterMapping' import { PropertyCell } from '@/components/properties/PropertyCell' @@ -82,3 +82,18 @@ export function createPropertyColumn( filterFn, } as ColumnDef } + +type PropertyDefinitionsData = { + propertyDefinitions?: PropertyDefinitionType[], +} | null | undefined + +export function getPropertyColumnsForEntity( + propertyDefinitionsData: PropertyDefinitionsData, + entity: PropertyEntity +): ColumnDef[] { + if (!propertyDefinitionsData?.propertyDefinitions) return [] + const properties = propertyDefinitionsData.propertyDefinitions.filter( + def => def.isActive && def.allowedEntities.includes(entity) + ) + return properties.map(prop => createPropertyColumn(prop)) +} diff --git a/web/utils/tableToGraphQL.ts b/web/utils/tableToGraphQL.ts deleted file mode 100644 index 77a5243f..00000000 --- a/web/utils/tableToGraphQL.ts +++ /dev/null @@ -1,132 +0,0 @@ -import type { ColumnDef, TableState } from '@tanstack/table-core' -import type { - FilterInput, - SortInput, - PaginationInput, - FullTextSearchInput, - FilterParameter -} from '@/api/gql/generated' -import { ColumnType, SortDirection, FilterOperator } from '@/api/gql/generated' - -export function mapTableStateToGraphQL( - tableState: TableState | undefined, - columns: ColumnDef[], - searchText: string | undefined -): { - filtering: FilterInput[] | undefined, - sorting: SortInput[] | undefined, - pagination: PaginationInput | undefined, - search: FullTextSearchInput | undefined, -} { - const filtering: FilterInput[] = [] - const sorting: SortInput[] = [] - - if (tableState?.columnFilters) { - for (const filter of tableState.columnFilters) { - const column = columns.find(col => col.id === filter.id) - if (!column) continue - - const meta = column.meta as { - columnType?: ColumnType, - propertyDefinitionId?: string, - fieldType?: string, - } | undefined - const columnType = meta?.columnType ?? ColumnType.DirectAttribute - - if (columnType === ColumnType.Property && !meta?.propertyDefinitionId) { - continue - } - - const filterValue = filter.value - if (filterValue === undefined || filterValue === null || filterValue === '') { - continue - } - - let operator: FilterOperator - let parameter: FilterParameter - - if (Array.isArray(filterValue)) { - if ( - columnType === ColumnType.Property && - meta?.fieldType === 'FIELD_TYPE_MULTI_SELECT' - ) { - operator = FilterOperator.TagsContains - } else { - operator = FilterOperator.TagsSingleContains - } - parameter = { - searchTags: filterValue, - } - } else if (typeof filterValue === 'boolean') { - operator = filterValue - ? FilterOperator.BooleanIsTrue - : FilterOperator.BooleanIsFalse - parameter = {} - } else if (typeof filterValue === 'string') { - operator = FilterOperator.TextContains - parameter = { - searchText: filterValue, - isCaseSensitive: false, - } - } else { - continue - } - - filtering.push({ - column: filter.id, - operator, - parameter, - columnType: columnType, - propertyDefinitionId: columnType === ColumnType.Property ? meta?.propertyDefinitionId : undefined, - }) - } - } - - if (tableState?.sorting) { - for (const sort of tableState.sorting) { - const column = columns.find(col => col.id === sort.id) - if (!column) continue - - const meta = column.meta as { - columnType?: ColumnType, - propertyDefinitionId?: string, - } | undefined - const columnType = meta?.columnType ?? ColumnType.DirectAttribute - - if (columnType === ColumnType.Property && !meta?.propertyDefinitionId) { - continue - } - - sorting.push({ - column: sort.id, - direction: sort.desc ? SortDirection.Desc : SortDirection.Asc, - columnType: columnType, - propertyDefinitionId: - columnType === ColumnType.Property - ? meta?.propertyDefinitionId - : undefined, - }) - } - } - - const pagination: PaginationInput | undefined = tableState?.pagination - ? { - pageIndex: tableState.pagination.pageIndex, - pageSize: tableState.pagination.pageSize ?? undefined, - } - : undefined - - const search: FullTextSearchInput | undefined = searchText - ? { - searchText: searchText, - includeProperties: true, - } - : undefined - - return { - filtering: filtering.length > 0 ? filtering : undefined, - sorting: sorting.length > 0 ? sorting : undefined, - pagination, - search, - } -}