From c92157eaa0040e6b79179bc2e3b751e5bd558f3b Mon Sep 17 00:00:00 2001 From: Felix Evers Date: Sun, 28 Dec 2025 04:38:31 +0100 Subject: [PATCH 1/9] feat: add task priority and estimated time - Add priority field (P1-P4) with color coding similar to Todoist - Add estimated_time field (in minutes) for task duration estimation - Both fields are optional - Update backend: model, migration, GraphQL types, resolvers - Update frontend: UI components, queries, display with color coding - Priority colors: P1 (red), P2 (orange), P3 (yellow), P4 (blue) Implements #30 and #31 --- backend/api/inputs.py | 12 ++++ backend/api/resolvers/task.py | 8 +++ backend/api/types/task.py | 2 + .../add_task_priority_and_estimated_time.py | 33 ++++++++++ backend/database/models/task.py | 4 +- web/api/gql/generated.ts | 33 ++++++++-- web/api/graphql/GetMyTasks.graphql | 2 + web/api/graphql/GetPatient.graphql | 2 + web/api/graphql/GetPatients.graphql | 2 + web/api/graphql/GetTask.graphql | 2 + web/api/graphql/GetTasks.graphql | 2 + web/components/tasks/TaskCardView.tsx | 49 +++++++++++---- web/components/tasks/TaskDetailView.tsx | 50 +++++++++++++++- web/components/tasks/TaskList.tsx | 60 ++++++++++++++++--- web/pages/location/[id].tsx | 2 + web/pages/tasks/index.tsx | 2 + web/pages/teams/[id].tsx | 2 + web/pages/wards/[id].tsx | 2 + 18 files changed, 240 insertions(+), 29 deletions(-) create mode 100644 backend/database/migrations/versions/add_task_priority_and_estimated_time.py diff --git a/backend/api/inputs.py b/backend/api/inputs.py index 4936f82..1f7e80a 100644 --- a/backend/api/inputs.py +++ b/backend/api/inputs.py @@ -49,6 +49,14 @@ class PatientState(Enum): DEAD = "DEAD" +@strawberry.enum +class TaskPriority(Enum): + P1 = "P1" + P2 = "P2" + P3 = "P3" + P4 = "P4" + + @strawberry.input class PropertyValueInput: definition_id: strawberry.ID @@ -104,6 +112,8 @@ class CreateTaskInput: assignee_id: strawberry.ID | None = None previous_task_ids: list[strawberry.ID] | None = None properties: list[PropertyValueInput] | None = None + priority: TaskPriority | None = None + estimated_time: int | None = None @strawberry.input @@ -116,6 +126,8 @@ class UpdateTaskInput: previous_task_ids: list[strawberry.ID] | None = None properties: list[PropertyValueInput] | None = None checksum: str | None = None + priority: TaskPriority | None = None + estimated_time: int | None = None @strawberry.input diff --git a/backend/api/resolvers/task.py b/backend/api/resolvers/task.py index fef46d2..b32ba53 100644 --- a/backend/api/resolvers/task.py +++ b/backend/api/resolvers/task.py @@ -224,6 +224,8 @@ async def create_task(self, info: Info, data: CreateTaskInput) -> TaskType: patient_id=data.patient_id, assignee_id=data.assignee_id, due_date=normalize_datetime_to_utc(data.due_date), + priority=data.priority.value if data.priority else None, + estimated_time=data.estimated_time, ) if data.properties is not None: @@ -284,6 +286,12 @@ async def update_task( else None ) + if data.priority is not strawberry.UNSET: + task.priority = data.priority.value if data.priority else None + + if data.estimated_time is not strawberry.UNSET: + task.estimated_time = data.estimated_time + if data.properties is not None: property_service = TaskMutation._get_property_service(db) await property_service.process_properties( diff --git a/backend/api/types/task.py b/backend/api/types/task.py index 6f57304..f7c445d 100644 --- a/backend/api/types/task.py +++ b/backend/api/types/task.py @@ -25,6 +25,8 @@ class TaskType: update_date: datetime | None assignee_id: strawberry.ID | None patient_id: strawberry.ID + priority: str | None + estimated_time: int | None @strawberry.field async def assignee( diff --git a/backend/database/migrations/versions/add_task_priority_and_estimated_time.py b/backend/database/migrations/versions/add_task_priority_and_estimated_time.py new file mode 100644 index 0000000..cc9d965 --- /dev/null +++ b/backend/database/migrations/versions/add_task_priority_and_estimated_time.py @@ -0,0 +1,33 @@ +"""Add priority and estimated_time to tasks. + +Revision ID: add_task_priority_time +Revises: 0de3078888ba +Create Date: 2025-12-19 00:00:00.000000 +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +revision: str = "add_task_priority_time" +down_revision: Union[str, Sequence[str], None] = "0de3078888ba" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column( + "tasks", + sa.Column("priority", sa.String(), nullable=True), + ) + op.add_column( + "tasks", + sa.Column("estimated_time", sa.Integer(), nullable=True), + ) + + +def downgrade() -> None: + op.drop_column("tasks", "estimated_time") + op.drop_column("tasks", "priority") diff --git a/backend/database/models/task.py b/backend/database/models/task.py index 8625e1e..79d8e64 100644 --- a/backend/database/models/task.py +++ b/backend/database/models/task.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING from database.models.base import Base -from sqlalchemy import Boolean, Column, ForeignKey, String, Table +from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, Table from sqlalchemy.orm import Mapped, mapped_column, relationship if TYPE_CHECKING: @@ -44,6 +44,8 @@ class Task(Base): nullable=True, ) patient_id: Mapped[str] = mapped_column(ForeignKey("patients.id")) + priority: Mapped[str | None] = mapped_column(String, nullable=True) + estimated_time: Mapped[int | None] = mapped_column(Integer, nullable=True) assignee: Mapped[User | None] = relationship( "User", diff --git a/web/api/gql/generated.ts b/web/api/gql/generated.ts index f6d0c5f..1660388 100644 --- a/web/api/gql/generated.ts +++ b/web/api/gql/generated.ts @@ -51,8 +51,10 @@ export type CreateTaskInput = { assigneeId?: InputMaybe; description?: InputMaybe; dueDate?: InputMaybe; + estimatedTime?: InputMaybe; patientId: Scalars['ID']['input']; previousTaskIds?: InputMaybe>; + priority?: InputMaybe; properties?: InputMaybe>; title: Scalars['String']['input']; }; @@ -403,6 +405,13 @@ export type SubscriptionTaskUpdatedArgs = { taskId?: InputMaybe; }; +export enum TaskPriority { + P1 = 'P1', + P2 = 'P2', + P3 = 'P3', + P4 = 'P4' +} + export type TaskType = { __typename?: 'TaskType'; assignee?: Maybe; @@ -412,9 +421,11 @@ export type TaskType = { description?: Maybe; done: Scalars['Boolean']['output']; dueDate?: Maybe; + estimatedTime?: Maybe; id: Scalars['ID']['output']; patient: PatientType; patientId: Scalars['ID']['output']; + priority?: Maybe; properties: Array; title: Scalars['String']['output']; updateDate?: Maybe; @@ -454,7 +465,9 @@ export type UpdateTaskInput = { description?: InputMaybe; done?: InputMaybe; dueDate?: InputMaybe; + estimatedTime?: InputMaybe; previousTaskIds?: InputMaybe>; + priority?: InputMaybe; properties?: InputMaybe>; title?: InputMaybe; }; @@ -489,7 +502,7 @@ export type GetLocationsQuery = { __typename?: 'Query', locationNodes: Array<{ _ export type GetMyTasksQueryVariables = Exact<{ [key: string]: never; }>; -export type GetMyTasksQuery = { __typename?: 'Query', me?: { __typename?: 'UserType', id: string, tasks: Array<{ __typename?: 'TaskType', id: string, title: string, description?: string | null, done: boolean, dueDate?: any | null, creationDate: any, updateDate?: any | null, patient: { __typename?: 'PatientType', id: string, name: string, assignedLocation?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null, assignedLocations: Array<{ __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null }> }, assignee?: { __typename?: 'UserType', id: string, name: string, avatarUrl?: string | null } | null }> } | null }; +export type GetMyTasksQuery = { __typename?: 'Query', me?: { __typename?: 'UserType', id: string, tasks: Array<{ __typename?: 'TaskType', id: string, title: string, description?: string | null, done: boolean, dueDate?: any | null, priority?: string | null, estimatedTime?: number | null, creationDate: any, updateDate?: any | null, patient: { __typename?: 'PatientType', id: string, name: string, assignedLocation?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null, assignedLocations: Array<{ __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null }> }, assignee?: { __typename?: 'UserType', id: string, name: string, avatarUrl?: string | null } | null }> } | null }; export type GetOverviewDataQueryVariables = Exact<{ [key: string]: never; }>; @@ -501,7 +514,7 @@ export type GetPatientQueryVariables = Exact<{ }>; -export type GetPatientQuery = { __typename?: 'Query', patient?: { __typename?: 'PatientType', id: string, firstname: string, lastname: string, birthdate: any, sex: Sex, state: PatientState, checksum: string, assignedLocation?: { __typename?: 'LocationNodeType', id: string, title: string } | null, assignedLocations: Array<{ __typename?: 'LocationNodeType', id: string, title: string }>, clinic: { __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null } | null } | null }, position?: { __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null } | null } | null } | null, teams: Array<{ __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null } | null } | null }>, tasks: Array<{ __typename?: 'TaskType', id: string, title: string, description?: string | null, done: boolean, dueDate?: any | null, updateDate?: any | null, assignee?: { __typename?: 'UserType', id: string, name: string, avatarUrl?: string | null } | null }>, properties: Array<{ __typename?: 'PropertyValueType', textValue?: string | null, numberValue?: number | null, booleanValue?: boolean | null, dateValue?: any | null, dateTimeValue?: any | null, selectValue?: string | null, multiSelectValues?: Array | null, definition: { __typename?: 'PropertyDefinitionType', id: string, name: string, description?: string | null, fieldType: FieldType, isActive: boolean, allowedEntities: Array, options: Array } }> } | null }; +export type GetPatientQuery = { __typename?: 'Query', patient?: { __typename?: 'PatientType', id: string, firstname: string, lastname: string, birthdate: any, sex: Sex, state: PatientState, checksum: string, assignedLocation?: { __typename?: 'LocationNodeType', id: string, title: string } | null, assignedLocations: Array<{ __typename?: 'LocationNodeType', id: string, title: string }>, clinic: { __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null } | null } | null }, position?: { __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null } | null } | null } | null, teams: Array<{ __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null } | null } | null }>, tasks: Array<{ __typename?: 'TaskType', id: string, title: string, description?: string | null, done: boolean, dueDate?: any | null, priority?: string | null, estimatedTime?: number | null, updateDate?: any | null, assignee?: { __typename?: 'UserType', id: string, name: string, avatarUrl?: string | null } | null }>, properties: Array<{ __typename?: 'PropertyValueType', textValue?: string | null, numberValue?: number | null, booleanValue?: boolean | null, dateValue?: any | null, dateTimeValue?: any | null, selectValue?: string | null, multiSelectValues?: Array | null, definition: { __typename?: 'PropertyDefinitionType', id: string, name: string, description?: string | null, fieldType: FieldType, isActive: boolean, allowedEntities: Array, options: Array } }> } | null }; export type GetPatientsQueryVariables = Exact<{ locationId?: InputMaybe; @@ -510,14 +523,14 @@ export type GetPatientsQueryVariables = Exact<{ }>; -export type GetPatientsQuery = { __typename?: 'Query', patients: Array<{ __typename?: 'PatientType', id: string, name: string, firstname: string, lastname: string, birthdate: any, sex: Sex, state: PatientState, assignedLocation?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null, assignedLocations: Array<{ __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null } | null }>, clinic: { __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null } | null } | null }, position?: { __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null } | null } | null } | null, teams: Array<{ __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null } | null } | null }>, tasks: Array<{ __typename?: 'TaskType', id: string, title: string, description?: string | null, done: boolean, dueDate?: any | null, creationDate: any, updateDate?: any | null, assignee?: { __typename?: 'UserType', id: string, name: string, avatarUrl?: string | null } | null }>, properties: Array<{ __typename?: 'PropertyValueType', textValue?: string | null, definition: { __typename?: 'PropertyDefinitionType', name: string } }> }> }; +export type GetPatientsQuery = { __typename?: 'Query', patients: Array<{ __typename?: 'PatientType', id: string, name: string, firstname: string, lastname: string, birthdate: any, sex: Sex, state: PatientState, assignedLocation?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null, assignedLocations: Array<{ __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null } | null }>, clinic: { __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null } | null } | null }, position?: { __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null } | null } | null } | null, teams: Array<{ __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null } | null } | null }>, tasks: Array<{ __typename?: 'TaskType', id: string, title: string, description?: string | null, done: boolean, dueDate?: any | null, priority?: string | null, estimatedTime?: number | null, creationDate: any, updateDate?: any | null, assignee?: { __typename?: 'UserType', id: string, name: string, avatarUrl?: string | null } | null }>, properties: Array<{ __typename?: 'PropertyValueType', textValue?: string | null, definition: { __typename?: 'PropertyDefinitionType', name: string } }> }> }; export type GetTaskQueryVariables = Exact<{ id: Scalars['ID']['input']; }>; -export type GetTaskQuery = { __typename?: 'Query', task?: { __typename?: 'TaskType', id: string, title: string, description?: string | null, done: boolean, dueDate?: any | null, checksum: string, patient: { __typename?: 'PatientType', id: string, name: string }, assignee?: { __typename?: 'UserType', id: string, name: string } | null, properties: Array<{ __typename?: 'PropertyValueType', textValue?: string | null, numberValue?: number | null, booleanValue?: boolean | null, dateValue?: any | null, dateTimeValue?: any | null, selectValue?: string | null, multiSelectValues?: Array | null, definition: { __typename?: 'PropertyDefinitionType', id: string, name: string, description?: string | null, fieldType: FieldType, isActive: boolean, allowedEntities: Array, options: Array } }> } | null }; +export type GetTaskQuery = { __typename?: 'Query', task?: { __typename?: 'TaskType', id: string, title: string, description?: string | null, done: boolean, dueDate?: any | null, priority?: string | null, estimatedTime?: number | null, checksum: string, patient: { __typename?: 'PatientType', id: string, name: string }, assignee?: { __typename?: 'UserType', id: string, name: string } | null, properties: Array<{ __typename?: 'PropertyValueType', textValue?: string | null, numberValue?: number | null, booleanValue?: boolean | null, dateValue?: any | null, dateTimeValue?: any | null, selectValue?: string | null, multiSelectValues?: Array | null, definition: { __typename?: 'PropertyDefinitionType', id: string, name: string, description?: string | null, fieldType: FieldType, isActive: boolean, allowedEntities: Array, options: Array } }> } | null }; export type GetTasksQueryVariables = Exact<{ rootLocationIds?: InputMaybe | Scalars['ID']['input']>; @@ -525,7 +538,7 @@ export type GetTasksQueryVariables = Exact<{ }>; -export type GetTasksQuery = { __typename?: 'Query', tasks: Array<{ __typename?: 'TaskType', id: string, title: string, description?: string | null, done: boolean, dueDate?: any | null, creationDate: any, updateDate?: any | null, patient: { __typename?: 'PatientType', id: string, name: string, assignedLocation?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null, assignedLocations: Array<{ __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null }> }, assignee?: { __typename?: 'UserType', id: string, name: string, avatarUrl?: string | null } | null }> }; +export type GetTasksQuery = { __typename?: 'Query', tasks: Array<{ __typename?: 'TaskType', id: string, title: string, description?: string | null, done: boolean, dueDate?: any | null, priority?: string | null, estimatedTime?: number | null, creationDate: any, updateDate?: any | null, patient: { __typename?: 'PatientType', id: string, name: string, assignedLocation?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null, assignedLocations: Array<{ __typename?: 'LocationNodeType', id: string, title: string, kind: LocationType, parent?: { __typename?: 'LocationNodeType', id: string, title: string, parent?: { __typename?: 'LocationNodeType', id: string, title: string } | null } | null }> }, assignee?: { __typename?: 'UserType', id: string, name: string, avatarUrl?: string | null } | null }> }; export type GetUsersQueryVariables = Exact<{ [key: string]: never; }>; @@ -811,6 +824,8 @@ export const GetMyTasksDocument = ` description done dueDate + priority + estimatedTime creationDate updateDate patient { @@ -1016,6 +1031,8 @@ export const GetPatientDocument = ` description done dueDate + priority + estimatedTime updateDate assignee { id @@ -1169,6 +1186,8 @@ export const GetPatientsDocument = ` description done dueDate + priority + estimatedTime creationDate updateDate assignee { @@ -1211,6 +1230,8 @@ export const GetTaskDocument = ` description done dueDate + priority + estimatedTime checksum patient { id @@ -1266,6 +1287,8 @@ export const GetTasksDocument = ` description done dueDate + priority + estimatedTime creationDate updateDate patient { diff --git a/web/api/graphql/GetMyTasks.graphql b/web/api/graphql/GetMyTasks.graphql index 8ea0c6d..2342e52 100644 --- a/web/api/graphql/GetMyTasks.graphql +++ b/web/api/graphql/GetMyTasks.graphql @@ -7,6 +7,8 @@ query GetMyTasks { description done dueDate + priority + estimatedTime creationDate updateDate patient { diff --git a/web/api/graphql/GetPatient.graphql b/web/api/graphql/GetPatient.graphql index f05cf6d..258e2da 100644 --- a/web/api/graphql/GetPatient.graphql +++ b/web/api/graphql/GetPatient.graphql @@ -84,6 +84,8 @@ query GetPatient($id: ID!) { description done dueDate + priority + estimatedTime updateDate assignee { id diff --git a/web/api/graphql/GetPatients.graphql b/web/api/graphql/GetPatients.graphql index 17bc1a0..1fbff92 100644 --- a/web/api/graphql/GetPatients.graphql +++ b/web/api/graphql/GetPatients.graphql @@ -101,6 +101,8 @@ query GetPatients($locationId: ID, $rootLocationIds: [ID!], $states: [PatientSta description done dueDate + priority + estimatedTime creationDate updateDate assignee { diff --git a/web/api/graphql/GetTask.graphql b/web/api/graphql/GetTask.graphql index 3c8c2d3..12c14aa 100644 --- a/web/api/graphql/GetTask.graphql +++ b/web/api/graphql/GetTask.graphql @@ -5,6 +5,8 @@ query GetTask($id: ID!) { description done dueDate + priority + estimatedTime checksum patient { id diff --git a/web/api/graphql/GetTasks.graphql b/web/api/graphql/GetTasks.graphql index ec6f7b8..ffcff1c 100644 --- a/web/api/graphql/GetTasks.graphql +++ b/web/api/graphql/GetTasks.graphql @@ -5,6 +5,8 @@ query GetTasks($rootLocationIds: [ID!], $assigneeId: ID) { description done dueDate + priority + estimatedTime creationDate updateDate patient { diff --git a/web/components/tasks/TaskCardView.tsx b/web/components/tasks/TaskCardView.tsx index 1f7a44a..b923429 100644 --- a/web/components/tasks/TaskCardView.tsx +++ b/web/components/tasks/TaskCardView.tsx @@ -14,6 +14,8 @@ type FlexibleTask = { description?: string | null, done: boolean, dueDate?: Date | string | null, + priority?: string | null, + estimatedTime?: number | null, updateDate?: Date | string | null, patient?: { id: string, @@ -111,13 +113,24 @@ export const TaskCardView = ({ task, onToggleDone: _onToggleDone, onClick, showA
-
+ {(task as FlexibleTask).priority && ( +
)} - > - {taskName} +
+ {taskName} +
{task.assignee && (
@@ -156,12 +169,24 @@ export const TaskCardView = ({ task, onToggleDone: _onToggleDone, onClick, showA )}
)} - {dueDate && ( -
- - -
- )} +
+ {(task as FlexibleTask).estimatedTime && ( +
+ + + {(task as FlexibleTask).estimatedTime! < 60 + ? `${(task as FlexibleTask).estimatedTime}m` + : `${Math.floor((task as FlexibleTask).estimatedTime! / 60)}h ${(task as FlexibleTask).estimatedTime! % 60}m`} + +
+ )} + {dueDate && ( +
+ + +
+ )} +
) } diff --git a/web/components/tasks/TaskDetailView.tsx b/web/components/tasks/TaskDetailView.tsx index 16ebd68..9711fe5 100644 --- a/web/components/tasks/TaskDetailView.tsx +++ b/web/components/tasks/TaskDetailView.tsx @@ -1,6 +1,6 @@ import { useEffect, useState, useMemo } from 'react' import { useTasksTranslation } from '@/i18n/useTasksTranslation' -import type { CreateTaskInput, UpdateTaskInput } from '@/api/gql/generated' +import type { CreateTaskInput, UpdateTaskInput, TaskPriority } from '@/api/gql/generated' import { useAssignTaskMutation, useCreateTaskMutation, @@ -122,6 +122,8 @@ export const TaskDetailView = ({ taskId, onClose, onSuccess, initialPatientId }: patientId: initialPatientId || '', assigneeId: null, dueDate: null, + priority: null, + estimatedTime: null, done: false, }) @@ -133,6 +135,8 @@ export const TaskDetailView = ({ taskId, onClose, onSuccess, initialPatientId }: patientId: taskData.task.patient?.id || '', assigneeId: taskData.task.assignee?.id || null, dueDate: taskData.task.dueDate ? new Date(taskData.task.dueDate) : null, + priority: (taskData.task.priority as TaskPriority | null) || null, + estimatedTime: taskData.task.estimatedTime ?? null, done: taskData.task.done || false }) } else if (initialPatientId && !taskId) { @@ -209,8 +213,10 @@ export const TaskDetailView = ({ taskId, onClose, onSuccess, initialPatientId }: description: formData.description, assigneeId: formData.assigneeId, dueDate: formData.dueDate, + priority: (formData.priority as TaskPriority | null) || undefined, + estimatedTime: formData.estimatedTime, properties: formData.properties - } as CreateTaskInput + } as CreateTaskInput & { priority?: TaskPriority | null, estimatedTime?: number | null } }) } @@ -354,6 +360,46 @@ export const TaskDetailView = ({ taskId, onClose, onSuccess, initialPatientId }: )} + + {({ isShowingError: _1, setIsShowingError: _2, ...bag }) => ( + + )} + + + + {({ isShowingError: _1, setIsShowingError: _2, ...bag }) => ( + { + const value = e.target.value === '' ? null : parseInt(e.target.value, 10) + updateLocalState({ estimatedTime: isNaN(value as number) ? null : value }) + }} + onBlur={() => { + persistChanges({ estimatedTime: formData.estimatedTime }) + }} + /> + )} + + {({ isShowingError: _1, setIsShowingError: _2, ...bag }) => (