Skip to content

Commit

Permalink
feat(datatrakWeb): RN-1329: Task details view (#5783)
Browse files Browse the repository at this point in the history
* Create button

* Move modal to ui-components

* Move country selector to features folder

* Update country selector exports/imports

* Update tupaia-pin.svg

* Country selector on modal

* Move survey selector to features

* Move types

* Update survey list component to take care of fetching

* Survey selector

* Move entity selector to features

* Fix types

* Entity selector

* Styling entity selector

* Due date

* WIP

* WIP

* assignee input

* Add loading state and save user id

* Styling repeat scheduler

* Comments placholder

* Styling

* WIP

* Create task route

* Create task workflow

* Clear form when modal is reopened

* Update schemas.ts

* remove unused import

* Handle reset

* Fix datatrak tests

* Fix central server tests

* Move modal to ui-components

* Remove unused import

* Remove duplicate file

* Fix build

* Fix tests

* Update packages/central-server/src/apiV2/utilities/constructNewRecordValidationRules.js

Co-authored-by: Tom Caiger <caigertom@gmail.com>

* Fix error messages

* Handle search term in the BE

* WIP

* WIP

* Assignee Id modal

* Working assignee

* remove unused property

* Fix timezone issue

* Fix date formatting of filter

* remove unused variable

* Remove unused variable

* Fix casing

* Default to showing countries if no primary entity question

* Update AssigneeInput.tsx

* Show loader when loading project and countries

* Fix copy

* Exclude internal users

* Fix types

* Change colour of icon in entity list

* Link to details page

* Fix modal button types

* Fix types

* Update handling of columns

* get task route

* WIP

* Link to survey for incomplete tasks

* Handle unloaded task

* Task metadata section

* Fix merge issue

* WIP

* Update schemas.ts

* feat(datatrakWeb): RN-1358: Assign tasks from dashboard (#5770)

* Create button

* Move modal to ui-components

* Move country selector to features folder

* Update country selector exports/imports

* Update tupaia-pin.svg

* Country selector on modal

* Move survey selector to features

* Move types

* Update survey list component to take care of fetching

* Survey selector

* Move entity selector to features

* Fix types

* Entity selector

* Styling entity selector

* Due date

* WIP

* WIP

* assignee input

* Add loading state and save user id

* Styling repeat scheduler

* Comments placholder

* Styling

* WIP

* Create task route

* Create task workflow

* Clear form when modal is reopened

* Update schemas.ts

* remove unused import

* Handle reset

* Fix datatrak tests

* Fix central server tests

* Move modal to ui-components

* Remove unused import

* Remove duplicate file

* Fix build

* Fix tests

* Update packages/central-server/src/apiV2/utilities/constructNewRecordValidationRules.js

Co-authored-by: Tom Caiger <caigertom@gmail.com>

* Fix error messages

* Handle search term in the BE

* WIP

* WIP

* Assignee Id modal

* Working assignee

* remove unused property

* Fix timezone issue

* Fix date formatting of filter

* remove unused variable

* Remove unused variable

* Fix casing

* Default to showing countries if no primary entity question

* Update AssigneeInput.tsx

* Show loader when loading project and countries

* Fix copy

* Exclude internal users

* Fix types

* Change colour of icon in entity list

* Fix modal button types

* Fix types

---------

Co-authored-by: Tom Caiger <caigertom@gmail.com>

* Fix merge error

* Fix styles and types

* Styling

* Fix multi re-render

* Comments placeholder

* Add buttons

* Submit changes

* Clear changes

* Fix merge issues

* Disable inputs when task is completed or cancelled

* responsive styling

* Add cancel menu

* Generate types

* Fix loading styling

* Remove unused var

* Handle onSuccess of edit task

* PR fixes

* PR fixes

* Remove form provider

---------

Co-authored-by: Tom Caiger <caigertom@gmail.com>
  • Loading branch information
alexd-bes and tcaiger authored Jul 16, 2024
1 parent 5a8793f commit d708a95
Show file tree
Hide file tree
Showing 45 changed files with 815 additions and 417 deletions.
6 changes: 6 additions & 0 deletions packages/datatrak-web-server/src/app/createApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
ActivityFeedRoute,
CreateTaskRequest,
CreateTaskRoute,
EditTaskRequest,
EditTaskRoute,
EntitiesRequest,
EntitiesRoute,
EntityDescendantsRequest,
Expand Down Expand Up @@ -47,6 +49,8 @@ import {
SurveysRoute,
SurveyUsersRequest,
SurveyUsersRoute,
TaskRequest,
TaskRoute,
TasksRequest,
TasksRoute,
UserRequest,
Expand Down Expand Up @@ -81,10 +85,12 @@ export async function createApp() {
.get<RecentSurveysRequest>('recentSurveys', handleWith(RecentSurveysRoute))
.get<ActivityFeedRequest>('activityFeed', handleWith(ActivityFeedRoute))
.get<TasksRequest>('tasks', handleWith(TasksRoute))
.get<TaskRequest>('tasks/:taskId', handleWith(TaskRoute))
.get<SingleSurveyResponseRequest>('surveyResponse/:id', handleWith(SingleSurveyResponseRoute))
.get<SurveyUsersRequest>('users/:surveyCode/:countryCode', handleWith(SurveyUsersRoute))
// Post Routes
.post<CreateTaskRequest>('tasks', handleWith(CreateTaskRoute))
.put<EditTaskRequest>('tasks/:taskId', handleWith(EditTaskRoute))
.post<SubmitSurveyResponseRequest>(
'submitSurveyResponse',
handleWith(SubmitSurveyResponseRoute),
Expand Down
49 changes: 11 additions & 38 deletions packages/datatrak-web-server/src/routes/CreateTaskRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,34 @@

import { Request } from 'express';
import { Route } from '@tupaia/server-boilerplate';
import { DatatrakWebCreateTaskRequest, TaskStatus } from '@tupaia/types';
import { stripTimezoneFromDate } from '@tupaia/utils';
import { DatatrakWebTaskChangeRequest, TaskStatus } from '@tupaia/types';
import { formatTaskChanges } from '../utils';

export type CreateTaskRequest = Request<
DatatrakWebCreateTaskRequest.Params,
DatatrakWebCreateTaskRequest.ResBody,
DatatrakWebCreateTaskRequest.ReqBody,
DatatrakWebCreateTaskRequest.ReqQuery
DatatrakWebTaskChangeRequest.Params,
DatatrakWebTaskChangeRequest.ResBody,
DatatrakWebTaskChangeRequest.ReqBody,
DatatrakWebTaskChangeRequest.ReqQuery
>;

export class CreateTaskRoute extends Route<CreateTaskRequest> {
public async buildResponse() {
const { models, body, ctx } = this.req;

const { surveyCode, entityId, assigneeId, dueDate, repeatSchedule } = body;
const { surveyCode, entityId } = body;

const survey = await models.survey.findOne({ code: surveyCode });
if (!survey) {
throw new Error('Survey not found');
}

const taskDetails: {
survey_id: string;
entity_id: string;
assignee_id?: string;
due_date?: string | null;
repeat_schedule?: string;
status?: TaskStatus;
} = {
const taskDetails = formatTaskChanges({
...body,
survey_id: survey.id,
entity_id: entityId,
assignee_id: assigneeId,
};

if (repeatSchedule) {
// if task is repeating, clear due date
taskDetails.repeat_schedule = JSON.stringify({
// TODO: format this correctly when recurring tasks are implemented
frequency: repeatSchedule,
});
taskDetails.due_date = null;
} else {
if (!dueDate) {
throw new Error('Due date is required for non-repeating tasks');
}

// apply status and due date only if not a repeating task
// set due date to end of day
const endOfDay = new Date(new Date(dueDate).setHours(23, 59, 59, 999));

// strip timezone from date so that the returned date is always in the user's timezone
const withoutTimezone = stripTimezoneFromDate(endOfDay);

taskDetails.due_date = withoutTimezone;
});

if (taskDetails.due_date) {
taskDetails.status = TaskStatus.to_do;
}

Expand Down
32 changes: 32 additions & 0 deletions packages/datatrak-web-server/src/routes/EditTaskRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Tupaia
* Copyright (c) 2017 - 2024 Beyond Essential Systems Pty Ltd
*/
/**
* Tupaia
* Copyright (c) 2017 - 2024 Beyond Essential Systems Pty Ltd
*/

import { Request } from 'express';
import { Route } from '@tupaia/server-boilerplate';
import { formatTaskChanges } from '../utils';
import { DatatrakWebTaskChangeRequest } from '@tupaia/types';

export type EditTaskRequest = Request<
{ taskId: string },
Record<string, never>,
Partial<DatatrakWebTaskChangeRequest.ReqBody>,
Record<string, never>
>;

export class EditTaskRoute extends Route<EditTaskRequest> {
public async buildResponse() {
const { body, ctx, params } = this.req;

const { taskId } = params;

const taskDetails = formatTaskChanges(body);

return ctx.services.central.updateResource(`tasks/${taskId}`, {}, taskDetails);
}
}
47 changes: 47 additions & 0 deletions packages/datatrak-web-server/src/routes/TaskRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Tupaia
* Copyright (c) 2017 - 2024 Beyond Essential Systems Pty Ltd
*/

import { Request } from 'express';
import { Route } from '@tupaia/server-boilerplate';
import { DatatrakWebTaskRequest } from '@tupaia/types';
import { TaskT, formatTaskResponse } from '../utils';

export type TaskRequest = Request<
DatatrakWebTaskRequest.Params,
DatatrakWebTaskRequest.ResBody,
DatatrakWebTaskRequest.ReqBody,
DatatrakWebTaskRequest.ReqQuery
>;

const FIELDS = [
'id',
'survey.name',
'survey.code',
'entity.country_code',
'entity.name',
'assignee_name',
'assignee_id',
'task_status',
'due_date',
'repeat_schedule',
'survey_id',
'entity_id',
];

export class TaskRoute extends Route<TaskRequest> {
public async buildResponse() {
const { ctx, params } = this.req;
const { taskId } = params;

const task: TaskT = await ctx.services.central.fetchResources(`tasks/${taskId}`, {
columns: FIELDS,
});
if (!task) {
throw new Error(`Task with id ${taskId} not found`);
}

return formatTaskResponse(task);
}
}
29 changes: 3 additions & 26 deletions packages/datatrak-web-server/src/routes/TasksRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
* Copyright (c) 2017 - 2024 Beyond Essential Systems Pty Ltd
*/
import { Request } from 'express';
import camelcaseKeys from 'camelcase-keys';
import { Route } from '@tupaia/server-boilerplate';
import { parse } from 'cookie';
import { DatatrakWebTasksRequest, Task, TaskStatus } from '@tupaia/types';
import { RECORDS } from '@tupaia/database';
import { TaskT, formatTaskResponse } from '../utils';

export type TasksRequest = Request<
DatatrakWebTasksRequest.Params,
Expand Down Expand Up @@ -162,37 +162,14 @@ export class TasksRoute extends Route<TasksRequest> {
page,
});

const formattedTasks = tasks.map((task: SingleTask) => {
const {
entity_id: entityId,
'entity.name': entityName,
'entity.country_code': entityCountryCode,
'survey.code': surveyCode,
survey_id: surveyId,
'survey.name': surveyName,
...rest
} = task;
return {
...rest,
entity: {
id: entityId,
name: entityName,
countryCode: entityCountryCode,
},
survey: {
id: surveyId,
name: surveyName,
code: surveyCode,
},
};
});
const formattedTasks = tasks.map((task: TaskT) => formatTaskResponse(task));

// Get all task ids for pagination
const count = await this.queryForCount();
const numberOfPages = Math.ceil(count / pageSize);

return {
tasks: camelcaseKeys(formattedTasks, { deep: true }),
tasks: formattedTasks,
count,
numberOfPages,
};
Expand Down
2 changes: 2 additions & 0 deletions packages/datatrak-web-server/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,7 @@ export { ActivityFeedRequest, ActivityFeedRoute } from './ActivityFeedRoute';
export { EntitiesRequest, EntitiesRoute } from './EntitiesRoute';
export { GenerateLoginTokenRequest, GenerateLoginTokenRoute } from './GenerateLoginTokenRoute';
export { TasksRequest, TasksRoute } from './TasksRoute';
export { TaskRequest, TaskRoute } from './TaskRoute';
export { SurveyUsersRequest, SurveyUsersRoute } from './SurveyUsersRoute';
export { CreateTaskRequest, CreateTaskRoute } from './CreateTaskRoute';
export { EditTaskRequest, EditTaskRoute } from './EditTaskRoute';
42 changes: 42 additions & 0 deletions packages/datatrak-web-server/src/utils/formatTaskChanges.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Tupaia
* Copyright (c) 2017 - 2024 Beyond Essential Systems Pty Ltd
*/

import { DatatrakWebTaskChangeRequest, Task } from '@tupaia/types';
import { stripTimezoneFromDate } from '@tupaia/utils';

type Input = Partial<DatatrakWebTaskChangeRequest.ReqBody> &
Partial<Pick<Task, 'entity_id' | 'survey_id' | 'status'>>;

type Output = Partial<Omit<Task, 'due_date'>> & {
due_date?: string | null;
};

export const formatTaskChanges = (task: Input) => {
const { dueDate, repeatSchedule, assigneeId, ...restOfTask } = task;

const taskDetails: Output & {
due_date?: string | null;
} = { assignee_id: assigneeId, ...restOfTask };

if (repeatSchedule) {
// if task is repeating, clear due date
taskDetails.repeat_schedule = JSON.stringify({
// TODO: format this correctly when recurring tasks are implemented
frequency: repeatSchedule,
});
taskDetails.due_date = null;
} else if (dueDate) {
// apply status and due date only if not a repeating task
// set due date to end of day
const endOfDay = new Date(new Date(dueDate).setHours(23, 59, 59, 999));

// strip timezone from date so that the returned date is always in the user's timezone
const withoutTimezone = stripTimezoneFromDate(endOfDay);

taskDetails.due_date = withoutTimezone;
}

return taskDetails;
};
45 changes: 45 additions & 0 deletions packages/datatrak-web-server/src/utils/formatTaskResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Tupaia
* Copyright (c) 2017 - 2024 Beyond Essential Systems Pty Ltd
*/

import { DatatrakWebTaskRequest, Entity, Survey, Task } from '@tupaia/types';
import camelcaseKeys from 'camelcase-keys';

export type TaskT = Omit<Task, 'created_at'> & {
'entity.name': Entity['name'];
'entity.country_code': string;
'survey.code': Survey['code'];
'survey.name': Survey['name'];
task_status: DatatrakWebTaskRequest.ResBody['taskStatus'];
};

export const formatTaskResponse = (task: TaskT): DatatrakWebTaskRequest.ResBody => {
const {
entity_id: entityId,
'entity.name': entityName,
'entity.country_code': entityCountryCode,
'survey.code': surveyCode,
survey_id: surveyId,
'survey.name': surveyName,
task_status: taskStatus,
...rest
} = task;

const formattedTask = {
...rest,
taskStatus,
entity: {
id: entityId,
name: entityName,
countryCode: entityCountryCode,
},
survey: {
id: surveyId,
name: surveyName,
code: surveyCode,
},
};

return camelcaseKeys(formattedTask) as DatatrakWebTaskRequest.ResBody;
};
2 changes: 2 additions & 0 deletions packages/datatrak-web-server/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@

export { sortSearchResults } from './sortSearchResults';
export { addRecentEntities } from './addRecentEntities';
export * from './formatTaskResponse';
export * from './formatTaskChanges';
6 changes: 3 additions & 3 deletions packages/datatrak-web/src/api/mutations/useCreateTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
*/

import { useMutation, useQueryClient } from 'react-query';
import { DatatrakWebCreateTaskRequest } from '@tupaia/types';
import { DatatrakWebTaskChangeRequest } from '@tupaia/types';
import { post } from '../api';

export const useCreateTask = (onSuccess?: () => void) => {
const queryClient = useQueryClient();
return useMutation<any, Error, DatatrakWebCreateTaskRequest.ReqBody, unknown>(
(data: DatatrakWebCreateTaskRequest.ReqBody) => {
return useMutation<any, Error, DatatrakWebTaskChangeRequest.ReqBody, unknown>(
(data: DatatrakWebTaskChangeRequest.ReqBody) => {
return post('tasks', {
data,
});
Expand Down
5 changes: 3 additions & 2 deletions packages/datatrak-web/src/api/mutations/useEditTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { put } from '../api';

type PartialTask = Partial<Task>;

export const useEditTask = (taskId: Task['id'], onSuccess?: () => void) => {
export const useEditTask = (taskId?: Task['id'], onSuccess?: () => void) => {
const queryClient = useQueryClient();
return useMutation<any, Error, PartialTask, unknown>(
(task: PartialTask) => {
Expand All @@ -19,8 +19,9 @@ export const useEditTask = (taskId: Task['id'], onSuccess?: () => void) => {
},
{
onSuccess: () => {
if (onSuccess) onSuccess();
queryClient.invalidateQueries('tasks');
queryClient.invalidateQueries(['tasks', taskId]);
if (onSuccess) onSuccess();
},
},
);
Expand Down
1 change: 1 addition & 0 deletions packages/datatrak-web/src/api/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ export { useActivityFeed, useCurrentProjectActivityFeed } from './useActivityFee
export { useProjectSurveys } from './useProjectSurveys';
export { useEntities } from './useEntities';
export { useTasks } from './useTasks';
export { useTask } from './useTask';
export { useSurveyUsers } from './useSurveyUsers';
Loading

0 comments on commit d708a95

Please sign in to comment.