Skip to content

Commit

Permalink
feat: task info messages
Browse files Browse the repository at this point in the history
  • Loading branch information
stepan662 committed Mar 5, 2025
1 parent c531344 commit 7de47fe
Show file tree
Hide file tree
Showing 18 changed files with 419 additions and 126 deletions.
1 change: 1 addition & 0 deletions e2e/cypress/support/dataCyType.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ declare namespace DataCy {
"task-detail" |
"task-detail-author" |
"task-detail-characters" |
"task-detail-close" |
"task-detail-closed-at" |
"task-detail-created-at" |
"task-detail-download-report" |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ interface TaskRepository : JpaRepository<Task, Long> {
@Query(
nativeQuery = true,
value = """
select distinct on (l.id, tt.key_id)
select distinct on (l.id, tt.key_id, taskAssigned)
tt.key_id as keyId,
l.id as languageId,
l.tag as languageTag,
Expand All @@ -146,7 +146,7 @@ interface TaskRepository : JpaRepository<Task, Long> {
tt.key_id in :keyIds
and l.deleted_at is null
and (t.state = 'IN_PROGRESS' or t.state = 'NEW')
order by l.id, tt.key_id, t.type desc, t.id desc
order by l.id, tt.key_id, taskAssigned, t.type desc, t.id desc
""",
)
fun getByKeyId(
Expand Down
6 changes: 1 addition & 5 deletions webapp/src/ee/task/components/PrefilterTask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,7 @@ export const PrefilterTask = ({ taskNumber }: PrefilterTaskProps) => {
<T keyName="task_filter_indicator_blocking_warning" />{' '}
{blockingTasksLoadable.data.map((taskNumber, i) => (
<React.Fragment key={taskNumber}>
<TaskTooltip
taskNumber={taskNumber}
project={project}
newTaskActions={true}
>
<TaskTooltip taskNumber={taskNumber} project={project}>
<StyledTaskId>#{taskNumber}</StyledTaskId>
</TaskTooltip>
{i !== blockingTasksLoadable.data.length - 1 && ', '}
Expand Down
77 changes: 38 additions & 39 deletions webapp/src/ee/task/components/TaskDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useState } from 'react';
import { Formik } from 'formik';
import { T, useTranslate } from '@tolgee/react';
import {
Expand All @@ -10,7 +9,7 @@ import {
Typography,
} from '@mui/material';
import { Link } from 'react-router-dom';
import { DotsVertical } from '@untitled-ui/icons-react';
import { X } from '@untitled-ui/icons-react';

import { useApiMutation, useApiQuery } from 'tg.service/http/useQueryApi';
import { TextField } from 'tg.component/common/form/fields/TextField';
Expand All @@ -20,17 +19,16 @@ import { messageService } from 'tg.service/MessageService';
import { UserAccount } from 'tg.component/UserAccount';
import { ProjectWithAvatar } from 'tg.component/ProjectWithAvatar';
import { useDateFormatter } from 'tg.hooks/useLocale';
import { stopAndPrevent } from 'tg.fixtures/eventHandler';
import { components } from 'tg.service/apiSchema.generated';

import { TaskDatePicker } from './TaskDatePicker';
import { AssigneeSearchSelect } from './assigneeSelect/AssigneeSearchSelect';
import { TaskLabel } from './TaskLabel';
import { TaskInfoItem } from './TaskInfoItem';
import { TaskScope } from './TaskScope';
import { TaskMenu } from './TaskMenu';
import { BoxLoading } from 'tg.component/common/BoxLoading';
import { getTaskUrl } from 'tg.constants/links';
import { TaskDetailActions } from './TaskDetailActions';

type TaskModel = components['schemas']['TaskModel'];

Expand Down Expand Up @@ -77,9 +75,16 @@ const StyledTopPart = styled('div')`

const StyledActions = styled('div')`
display: flex;
gap: 8px;
gap: 32px;
padding-top: 24px;
justify-content: end;
justify-content: space-between;
`;

const StyledActionGroup = styled('div')`
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: end;
`;

type Props = {
Expand All @@ -92,7 +97,6 @@ type Props = {
export const TaskDetail = ({ onClose, projectId, taskNumber, task }: Props) => {
const { t } = useTranslate();
const formatDate = useDateFormatter();
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);

const taskLoadable = useApiQuery({
url: '/v2/projects/{projectId}/tasks/{taskNumber}',
Expand Down Expand Up @@ -127,10 +131,6 @@ export const TaskDetail = ({ onClose, projectId, taskNumber, task }: Props) => {

const canEditTask = scopes.includes('tasks.edit');

const handleClose = () => {
setAnchorEl(null);
};

const data = taskLoadable.data ?? task;

if (!data && taskLoadable.isLoading) {
Expand All @@ -154,21 +154,12 @@ export const TaskDetail = ({ onClose, projectId, taskNumber, task }: Props) => {
</StyledSubtitle>
<StyledMenu>
<IconButton
size="small"
onClick={stopAndPrevent((e) => setAnchorEl(e.currentTarget))}
data-cy="task-item-menu"
onClick={onClose}
data-cy="task-detail-close"
size="medium"
>
<DotsVertical />
<X width={26} height={26} />
</IconButton>
<TaskMenu
task={data}
anchorEl={anchorEl}
onClose={handleClose}
project={projectLoadable.data}
projectScopes={projectLoadable.data.computedPermission.scopes}
newTaskActions={false}
hideTaskDetail={true}
/>
</StyledMenu>
</>
)}
Expand Down Expand Up @@ -301,22 +292,30 @@ export const TaskDetail = ({ onClose, projectId, taskNumber, task }: Props) => {
data-cy="task-detail-project"
/>
</Box>

<StyledActions>
<Button onClick={onClose}>{t('global_close_button')}</Button>
{canEditTask && (
<LoadingButton
color="primary"
variant="contained"
loading={isSubmitting}
disabled={!dirty}
type="submit"
data-cy="task-detail-submit"
onClick={() => submitForm()}
>
{t('task_detail_submit_button')}
</LoadingButton>
)}
<StyledActionGroup>
<TaskDetailActions
task={data}
project={project!}
projectScopes={project!.computedPermission.scopes}
/>
</StyledActionGroup>
<StyledActionGroup>
{canEditTask && (
<LoadingButton
size="small"
color="primary"
variant="contained"
loading={isSubmitting}
disabled={!dirty}
type="submit"
data-cy="task-detail-submit"
onClick={() => submitForm()}
>
{t('task_detail_submit_button')}
</LoadingButton>
)}
</StyledActionGroup>
</StyledActions>
</StyledContainer>
)}
Expand Down
140 changes: 140 additions & 0 deletions webapp/src/ee/task/components/TaskDetailActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { Button } from '@mui/material';
import { T, useTranslate } from '@tolgee/react';

import { confirmation } from 'tg.hooks/confirmation';
import { components } from 'tg.service/apiSchema.generated';
import { Scope } from 'tg.fixtures/permissions';
import { messageService } from 'tg.service/MessageService';
import { useApiMutation } from 'tg.service/http/useQueryApi';

import { useUser } from 'tg.globalContext/helpers';
import { TASK_ACTIVE_STATES } from 'tg.component/task/taskActiveStates';

type TaskModel = components['schemas']['TaskModel'];
type SimpleProjectModel = components['schemas']['SimpleProjectModel'];

type Props = {
task: TaskModel;
project: SimpleProjectModel;
projectScopes?: Scope[];
};

export const TaskDetailActions = ({ task, project, projectScopes }: Props) => {
const user = useUser();
const cancelMutation = useApiMutation({
url: '/v2/projects/{projectId}/tasks/{taskNumber}/close',
method: 'put',
invalidatePrefix: [
'/v2/projects/{projectId}/translations',
'/v2/projects/{projectId}/tasks',
'/v2/user-tasks',
],
});

const reopenMutation = useApiMutation({
url: '/v2/projects/{projectId}/tasks/{taskNumber}/reopen',
method: 'put',
invalidatePrefix: [
'/v2/projects/{projectId}/translations',
'/v2/projects/{projectId}/tasks',
'/v2/user-tasks',
],
});

const finishMutation = useApiMutation({
url: '/v2/projects/{projectId}/tasks/{taskNumber}/finish',
method: 'put',
invalidatePrefix: [
'/v2/projects/{projectId}/translations',
'/v2/projects/{projectId}/tasks',
'/v2/user-tasks',
],
});

const canEditTask = projectScopes?.includes('tasks.edit');
const canMarkAsDone =
projectScopes?.includes('tasks.edit') ||
Boolean(task.assignees.find((u) => u.id === user?.id));

function handleClose() {
confirmation({
title: <T keyName="task_menu_cancel_confirmation_title" />,
onConfirm() {
cancelMutation.mutate(
{
path: { projectId: project.id, taskNumber: task.number },
},
{
onSuccess() {
messageService.success(<T keyName="task_menu_cancel_success" />);
},
}
);
},
});
}

function handleReopen() {
reopenMutation.mutate(
{
path: { projectId: project.id, taskNumber: task.number },
},
{
onSuccess() {
messageService.success(<T keyName="task_menu_reopen_success" />);
},
}
);
}

function handleMarkAsDone() {
finishMutation.mutate(
{
path: { projectId: project.id, taskNumber: task.number },
},
{
onSuccess() {
messageService.success(<T keyName="task_menu_finish_success" />);
},
}
);
}

const { t } = useTranslate();
return (
<>
{TASK_ACTIVE_STATES.includes(task.state) && (
<Button
color="error"
variant="outlined"
size="small"
disabled={!canEditTask}
onClick={handleClose}
>
{t('task_menu_cancel_task')}
</Button>
)}

{TASK_ACTIVE_STATES.includes(task.state) ? (
<Button
variant="outlined"
color="success"
size="small"
onClick={handleMarkAsDone}
disabled={task.doneItems !== task.totalItems || !canMarkAsDone}
>
{t('task_menu_mark_as_done')}
</Button>
) : (
<Button
variant="outlined"
size="small"
onClick={handleReopen}
disabled={!canEditTask}
>
{t('task_menu_mark_as_in_progress')}
</Button>
)}
</>
);
};
Loading

0 comments on commit 7de47fe

Please sign in to comment.