diff --git a/packages/web/src/common/utils/i18n/locales/en/tasks.json b/packages/web/src/common/utils/i18n/locales/en/tasks.json index ce7f0a8b..2b4e7f88 100644 --- a/packages/web/src/common/utils/i18n/locales/en/tasks.json +++ b/packages/web/src/common/utils/i18n/locales/en/tasks.json @@ -1,15 +1,25 @@ { - "addTask": "Add a new task", - "editTask": "Edit task", + "add-task": "Add a new task", + "create-task": "Create task", + "created-task": "Task created.", + "delete-task": "Delete task", + "edit-task": "Edit task", + "errors": { + "not-found": "Not found", + "unable-to-find-task": "We're unable to find the task you requested." + }, + "go-back-to-task-list": "Go back to task list", "label": { - "title": "Title", - "completed": "Is complete" + "assignee": "Assignee", + "completed": "Is complete", + "status": "Status", + "title": "Title" }, - "createdTask": "Task created.", - "markComplete": "Mark complete", - "markIncomplete": "Mark incomplete", - "markedComplete": "Marked task complete", - "markedIncomplete": "Marked task incomplete", + "list-tasks": "List tasks", + "mark-complete": "Mark complete", + "mark-incomplete": "Mark incomplete", + "marked-complete": "Marked task complete", + "marked-incomplete": "Marked task incomplete", "marking-complete": "Marking task complete...", "marking-incomplete": "Marking task incomplete...", "status": { @@ -20,6 +30,7 @@ "task": "Task", "tasks": "Tasks", "tasks-empty": "Nothing to do. Create a task to get started.", - "updatedTask": "Task updated.", - "update-failed": "Failed to update task. Detail: {{message}}" + "updated-task": "Task updated.", + "update-failed": "Failed to update task. Detail: {{message}}", + "view-task": "View task" } diff --git a/packages/web/src/common/utils/i18n/locales/es/tasks.json b/packages/web/src/common/utils/i18n/locales/es/tasks.json index 0142bece..6e9f928a 100644 --- a/packages/web/src/common/utils/i18n/locales/es/tasks.json +++ b/packages/web/src/common/utils/i18n/locales/es/tasks.json @@ -1,15 +1,25 @@ { - "addTask": "Crear una nueva tarea", - "editTask": "Editar tarea", + "add-task": "Crear una nueva tarea", + "create-task": "Crear tarea", + "created-task": "Tarea creada.", + "delete-task": "Eliminar tarea", + "edit-task": "Editar tarea", + "errors": { + "not-found": "No encontrado", + "unable-to-find-task": "No podemos encontrar la tarea que solicitaste." + }, + "go-back-to-task-list": "Volver a la lista de tareas", "label": { - "title": "Título", - "completed": "Está completo" + "assignee": "Asignado a", + "completed": "Está completo", + "status": "Estado", + "title": "Título" }, - "createdTask": "Tarea creada.", - "markComplete": "Márcalo completo", - "markIncomplete": "Márcalo como incompleto", - "markedComplete": "Tarea marcada completada", - "markedIncomplete": "Tarea marcada como incompleta", + "list-tasks": "Listar tareas", + "mark-complete": "Márcalo completo", + "mark-incomplete": "Márcalo como incompleto", + "marked-complete": "Tarea marcada completada", + "marked-incomplete": "Tarea marcada como incompleta", "marking-complete": "Marcando la tarea como completada...", "marking-incomplete": "Marcando la tarea como incompleta...", "status": { @@ -20,6 +30,7 @@ "task": "Tarea", "tasks": "Tareas", "tasks-empty": "No hay nada que hacer. Haz una tarea para empezar.", - "updatedTask": "Tarea actualizada.", - "update-failed": "Error al actualizar la tarea. Detalle: {{message}}" + "updated-task": "Tarea actualizada.", + "update-failed": "Error al actualizar la tarea. Detalle: {{message}}", + "view-task": "Ver tarea" } diff --git a/packages/web/src/common/utils/i18n/locales/fr/tasks.json b/packages/web/src/common/utils/i18n/locales/fr/tasks.json index 1c0bc80f..9fc5c546 100644 --- a/packages/web/src/common/utils/i18n/locales/fr/tasks.json +++ b/packages/web/src/common/utils/i18n/locales/fr/tasks.json @@ -1,15 +1,25 @@ { - "addTask": "Créer une nouvelle tâche", - "editTask": "Modifier la tâche", + "add-task": "Créer une nouvelle tâche", + "create-task": "Créer la tâche", + "created-task": "Tâche créée.", + "delete-task": "Supprimer la tâche", + "edit-task": "Modifier la tâche", + "errors": { + "not-found": "Non trouvé", + "unable-to-find-task": "Nous ne pouvons pas trouver la tâche que vous avez demandée." + }, + "go-back-to-task-list": "Retour à la liste des tâches", "label": { - "title": "Titre", - "completed": "Est complet" + "assignee": "Assigné à", + "completed": "Est complet", + "status": "Statut", + "title": "Titre" }, - "createdTask": "Tâche créée.", - "markComplete": "Marquez-le comme terminé", - "markIncomplete": "Marquez-le comme incomplet", - "markedComplete": "Tâche marquée terminée", - "markedIncomplete": "Tâche marquée incomplète", + "list-tasks": "Lister les tâches", + "mark-complete": "Marquez-le comme terminé", + "mark-incomplete": "Marquez-le comme incomplet", + "marked-complete": "Tâche marquée terminée", + "marked-incomplete": "Tâche marquée incomplète", "marking-complete": "Marquage de la tâche comme terminée...", "marking-incomplete": "Marquage de la tâche comme incomplète...", "status": { @@ -20,6 +30,7 @@ "task": "Tâche", "tasks": "Tâches", "tasks-empty": "Rien à faire. Créez une tâche pour commencer.", - "updatedTask": "Tâche mise à jour.", - "update-failed": "Échec de la mise à jour de la tâche. Détail : {{message}}" + "updated-task": "Tâche mise à jour.", + "update-failed": "Échec de la mise à jour de la tâche. Détail : {{message}}", + "view-task": "Voir la tâche" } diff --git a/packages/web/src/pages/Settings/SettingsPage.tsx b/packages/web/src/pages/Settings/SettingsPage.tsx index 49557645..06e338ed 100644 --- a/packages/web/src/pages/Settings/SettingsPage.tsx +++ b/packages/web/src/pages/Settings/SettingsPage.tsx @@ -1,4 +1,4 @@ -import { Link, Outlet } from 'react-router-dom'; +import { NavLink, Outlet } from 'react-router-dom'; import { Palette } from 'lucide-react'; import { Page } from '@react-starter/shared/components/Content/Page'; @@ -8,7 +8,7 @@ import { Skeleton } from '@react-starter/shared/components/shadcn/skeleton'; import { Avatar, AvatarFallback } from '@react-starter/shared/components/shadcn/avatar'; import { useGetCurrentUser } from '@/common/api/useGetCurrentUser'; -import SettingsPageBreadcrumbs from '@/pages/Settings/components/SettingsPageBreadcrumbs'; +import { SettingsPageBreadcrumbs } from '@/pages/Settings/components/SettingsPageBreadcrumbs'; import { Button } from '@react-starter/shared/components/shadcn/button'; /** @@ -44,10 +44,10 @@ const SettingsPage = () => { diff --git a/packages/web/src/pages/Settings/components/SettingsPageBreadcrumbs.test.tsx b/packages/web/src/pages/Settings/components/SettingsPageBreadcrumbs.test.tsx index 22251202..4e4eb303 100644 --- a/packages/web/src/pages/Settings/components/SettingsPageBreadcrumbs.test.tsx +++ b/packages/web/src/pages/Settings/components/SettingsPageBreadcrumbs.test.tsx @@ -3,7 +3,7 @@ import { Navigate, Route, Routes } from 'react-router-dom'; import { render, screen } from '@/test/test-utils'; -import SettingsPageBreadcrumbs from './SettingsPageBreadcrumbs'; +import { SettingsPageBreadcrumbs } from './SettingsPageBreadcrumbs'; describe('SettingsPageBreadcrumbs', () => { it('should render successfully', async () => { diff --git a/packages/web/src/pages/Settings/components/SettingsPageBreadcrumbs.tsx b/packages/web/src/pages/Settings/components/SettingsPageBreadcrumbs.tsx index d1d4f842..d58d77cf 100644 --- a/packages/web/src/pages/Settings/components/SettingsPageBreadcrumbs.tsx +++ b/packages/web/src/pages/Settings/components/SettingsPageBreadcrumbs.tsx @@ -32,7 +32,10 @@ const SettingsPageBreadcrumbs = ({ className, testId = 'page-settings-breadcrumb <> - + {pathElements[3].replace('-', ' ')} @@ -43,4 +46,4 @@ const SettingsPageBreadcrumbs = ({ className, testId = 'page-settings-breadcrumb ); }; -export default SettingsPageBreadcrumbs; +export { SettingsPageBreadcrumbs }; diff --git a/packages/web/src/pages/Tasks/TasksPage.tsx b/packages/web/src/pages/Tasks/TasksPage.tsx index da9ff57f..038a0006 100644 --- a/packages/web/src/pages/Tasks/TasksPage.tsx +++ b/packages/web/src/pages/Tasks/TasksPage.tsx @@ -5,7 +5,7 @@ import { Page } from '@react-starter/shared/components/Content/Page'; import { Container } from '@react-starter/shared/components/Content/Container'; import { Heading } from '@react-starter/shared/components/Text/Heading'; -import TasksPageBreadcrumbs from '@/pages/Tasks/components/TasksPageBreadcrumbs'; +import { TasksPageBreadcrumbs } from '@/pages/Tasks/components/TasksPageBreadcrumbs'; import { AddTaskButton } from '@/pages/Tasks/components/Add/AddTaskButton'; /** diff --git a/packages/web/src/pages/Tasks/components/Add/AddTaskButton.tsx b/packages/web/src/pages/Tasks/components/Add/AddTaskButton.tsx index 6de9f438..c0278489 100644 --- a/packages/web/src/pages/Tasks/components/Add/AddTaskButton.tsx +++ b/packages/web/src/pages/Tasks/components/Add/AddTaskButton.tsx @@ -16,13 +16,13 @@ const AddTaskButton = ({ ...props }: React.ComponentProps) => { return ( - - {t('addTask', { ns: 'tasks' })} + {t('add-task', { ns: 'tasks' })} ); }; diff --git a/packages/web/src/pages/Tasks/components/Add/TaskAdd.tsx b/packages/web/src/pages/Tasks/components/Add/TaskAdd.tsx index be8617f9..21e558c0 100644 --- a/packages/web/src/pages/Tasks/components/Add/TaskAdd.tsx +++ b/packages/web/src/pages/Tasks/components/Add/TaskAdd.tsx @@ -41,7 +41,7 @@ const TaskAdd = ({ className, testId = 'task-add' }: BaseComponentProps) => { { task: data }, { onSuccess: () => { - toast(t('createdTask', { ns: 'tasks' })); + toast(t('created-task', { ns: 'tasks' })); navigate(-1); }, onError: (err) => { @@ -59,7 +59,7 @@ const TaskAdd = ({ className, testId = 'task-add' }: BaseComponentProps) => {
{/* heading */} - {t('addTask', { ns: 'tasks' })} + {t('add-task', { ns: 'tasks' })} {/* error state */} diff --git a/packages/web/src/pages/Tasks/components/Delete/TaskDeleteDialog.tsx b/packages/web/src/pages/Tasks/components/Delete/TaskDeleteDialog.tsx index 24e7797a..ef5ab032 100644 --- a/packages/web/src/pages/Tasks/components/Delete/TaskDeleteDialog.tsx +++ b/packages/web/src/pages/Tasks/components/Delete/TaskDeleteDialog.tsx @@ -10,6 +10,7 @@ import { DialogDescription, DialogFooter, } from '@react-starter/shared/components/shadcn/dialog'; +import { Tooltip, TooltipTrigger, TooltipContent } from '@react-starter/shared/components/shadcn/tooltip'; import { toast } from '@react-starter/shared/components/shadcn/sonner'; import { ErrorAlert } from '@react-starter/shared/components/Alert/ErrorAlert'; @@ -67,9 +68,14 @@ const TaskDeleteDialog = ({ onSuccess, task, trigger, ...props }: TaskDeleteDial return ( {trigger && ( - - {trigger} - + + + + {trigger} + + + {t('delete-task', { ns: 'tasks' })} + )} diff --git a/packages/web/src/pages/Tasks/components/Edit/TaskCompleteToggle.tsx b/packages/web/src/pages/Tasks/components/Edit/TaskCompleteToggle.tsx index 930b7879..85bc6905 100644 --- a/packages/web/src/pages/Tasks/components/Edit/TaskCompleteToggle.tsx +++ b/packages/web/src/pages/Tasks/components/Edit/TaskCompleteToggle.tsx @@ -22,11 +22,11 @@ interface TaskCompleteToggleProps extends BaseComponentProps { * to toggle the value of the Task `complete` attribute. * @param {TaskCompleteToggleProps} props - Component properties. */ -export const TaskCompleteToggle = ({ className, task, testId = 'toggle-task-complete' }: TaskCompleteToggleProps) => { +const TaskCompleteToggle = ({ className, task, testId = 'toggle-task-complete' }: TaskCompleteToggleProps) => { const { t } = useTranslation(); const { mutate: updateTask, isPending } = useUpdateTask(); - const buttonTitle = task.completed ? t('markIncomplete', { ns: 'tasks' }) : t('markComplete', { ns: 'tasks' }); + const buttonTitle = task.completed ? t('mark-incomplete', { ns: 'tasks' }) : t('mark-complete', { ns: 'tasks' }); /** * Actions to perform when the task complete toggle button is clicked. @@ -41,7 +41,7 @@ export const TaskCompleteToggle = ({ className, task, testId = 'toggle-task-comp }, { onSuccess: (data) => { - toast(data.completed ? t('markedComplete', { ns: 'tasks' }) : t('markedIncomplete', { ns: 'tasks' })); + toast(data.completed ? t('marked-complete', { ns: 'tasks' }) : t('marked-incomplete', { ns: 'tasks' })); }, }, ); @@ -61,3 +61,5 @@ export const TaskCompleteToggle = ({ className, task, testId = 'toggle-task-comp ); }; + +export { TaskCompleteToggle }; diff --git a/packages/web/src/pages/Tasks/components/Edit/TaskEdit.tsx b/packages/web/src/pages/Tasks/components/Edit/TaskEdit.tsx index d5962f5d..575c0714 100644 --- a/packages/web/src/pages/Tasks/components/Edit/TaskEdit.tsx +++ b/packages/web/src/pages/Tasks/components/Edit/TaskEdit.tsx @@ -47,7 +47,7 @@ const TaskEdit = ({ className, testId = 'task-edit' }: BaseComponentProps) => { { task: updatedTask }, { onSuccess: () => { - toast(t('updatedTask', { ns: 'tasks' })); + toast(t('updated-task', { ns: 'tasks' })); navigate(-1); }, onError: (err) => { @@ -65,7 +65,7 @@ const TaskEdit = ({ className, testId = 'task-edit' }: BaseComponentProps) => {
{/* heading */} - {t('editTask', { ns: 'tasks' })} + {t('edit-task', { ns: 'tasks' })} {/* loading state */} diff --git a/packages/web/src/pages/Tasks/components/List/TaskListItem.tsx b/packages/web/src/pages/Tasks/components/List/TaskListItem.tsx index 574c1c86..4d4f5883 100644 --- a/packages/web/src/pages/Tasks/components/List/TaskListItem.tsx +++ b/packages/web/src/pages/Tasks/components/List/TaskListItem.tsx @@ -44,7 +44,7 @@ const TaskListItem = ({ task, ...props }: TaskListItemProps) => { const updateTaskPromise = updateTaskAsync({ task: taskData }); toast.promise(updateTaskPromise, { loading: isComplete ? t('marking-complete', { ns: 'tasks' }) : t('marking-incomplete', { ns: 'tasks' }), - success: isComplete ? t('markedComplete', { ns: 'tasks' }) : t('markedIncomplete', { ns: 'tasks' }), + success: isComplete ? t('marked-complete', { ns: 'tasks' }) : t('marked-incomplete', { ns: 'tasks' }), error: (error) => t('update-failed', { ns: 'tasks', message: error.message }), }); }; @@ -53,7 +53,9 @@ const TaskListItem = ({ task, ...props }: TaskListItemProps) => { {task.completed ? : } - {task.title} + + {task.title} + diff --git a/packages/web/src/pages/Tasks/components/TaskDetailLayout.test.tsx b/packages/web/src/pages/Tasks/components/TaskDetailLayout.test.tsx index 9a1d2c7e..eaaf72ac 100644 --- a/packages/web/src/pages/Tasks/components/TaskDetailLayout.test.tsx +++ b/packages/web/src/pages/Tasks/components/TaskDetailLayout.test.tsx @@ -37,11 +37,11 @@ describe('TaskDetailLayout', () => { it('should display a task', async () => { // ARRANGE - render(); - await screen.findByTestId('component-task-view'); + render(); + await screen.findByTestId('layout-task-detail'); // ASSERT - expect(screen.getByTestId('component-task-view')).toBeDefined(); + expect(screen.getByTestId('task-view')).toBeDefined(); }); it('should display task error', async () => { @@ -52,11 +52,11 @@ describe('TaskDetailLayout', () => { error: new Error(), isLoading: false, } as unknown as UseQueryResult); - render(); - await screen.findByTestId('component-error-task'); + render(); + await screen.findByTestId('layout-task-detail-error'); // ASSERT - expect(screen.getByTestId('component-error-task')).toBeDefined(); + expect(screen.getByTestId('layout-task-detail-error')).toBeDefined(); }); it('should render loading state', async () => { @@ -67,21 +67,21 @@ describe('TaskDetailLayout', () => { error: undefined, isLoading: true, } as unknown as UseQueryResult); - render(); - await screen.findByTestId('component-loading'); + render(); + await screen.findByTestId('layout-task-detail-loading'); // ASSERT - expect(screen.getByTestId('component-loading')).toBeDefined(); + expect(screen.getByTestId('layout-task-detail-loading')).toBeDefined(); }); it('should navigate back using close button', async () => { // ARRANGE const user = userEvent.setup(); - render(); - await screen.findByTestId('component-button-close'); + render(); + const closeButton = await screen.findByTestId('layout-task-detail-button-close'); // ACT - await user.click(screen.getByTestId('component-button-close')); + await user.click(closeButton); // ASSERT expect(mockNavigate).toHaveBeenCalledTimes(1); @@ -91,11 +91,11 @@ describe('TaskDetailLayout', () => { it('should navigate to edit', async () => { // ARRANGE const user = userEvent.setup(); - render(); - await screen.findByTestId('component-button-edit'); + render(); + await screen.findByTestId('layout-task-detail-button-edit'); // ACT - await user.click(screen.getByTestId('component-button-edit')); + await user.click(screen.getByTestId('layout-task-detail-button-edit')); // ASSERT expect(mockNavigate).toHaveBeenCalledTimes(1); diff --git a/packages/web/src/pages/Tasks/components/TaskDetailLayout.tsx b/packages/web/src/pages/Tasks/components/TaskDetailLayout.tsx index 33b097a7..d6d7eb26 100644 --- a/packages/web/src/pages/Tasks/components/TaskDetailLayout.tsx +++ b/packages/web/src/pages/Tasks/components/TaskDetailLayout.tsx @@ -1,11 +1,21 @@ -import { useNavigate, useParams } from 'react-router-dom'; -import { Check, Pencil, Trash, X } from 'lucide-react'; +import { Link, useNavigate, useParams } from 'react-router-dom'; +import { Pencil, SearchSlash, Trash, X } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import type { BaseComponentProps } from '@react-starter/shared/types/components'; import { Skeleton } from '@react-starter/shared/components/shadcn/skeleton'; +import { Heading } from '@react-starter/shared/components/Text/Heading'; +import { Tooltip, TooltipTrigger, TooltipContent } from '@react-starter/shared/components/shadcn/tooltip'; import { Button } from '@react-starter/shared/components/shadcn/button'; import { ErrorAlert } from '@react-starter/shared/components/Alert/ErrorAlert'; +import { + Empty, + EmptyContent, + EmptyDescription, + EmptyHeader, + EmptyMedia, + EmptyTitle, +} from '@react-starter/shared/components/shadcn/empty'; +import { ButtonGroup, ButtonGroupSeparator } from '@react-starter/shared/components/shadcn/button-group'; import { useGetTask } from '@/pages/Tasks/api/useGetTask'; import { TaskDeleteDialog } from '@/pages/Tasks/components/Delete/TaskDeleteDialog'; @@ -14,96 +24,137 @@ import { TaskView } from '@/pages/Tasks/components/View/TaskView'; /** * The `TaskDetailLayout` component renders a layout for viewing and maintaining * a single `Task`. Provides buttons and navigation to perform actions on the Task. - * @param {BaseComponentProps} props - Component properties. */ -const TaskDetailLayout = ({ className, testId = 'layout-task-detail' }: BaseComponentProps) => { +const TaskDetailLayout = ({ ...props }: React.ComponentProps<'div'>) => { const { t } = useTranslation(); const navigate = useNavigate(); const { taskId } = useParams(); - const { data: task, error: taskError, isLoading: isLoadingTask } = useGetTask({ taskId: Number(taskId) }); + const { data: task, error, isLoading } = useGetTask({ taskId: Number(taskId) }); - return ( -
- {/* Heading */} -
- {!!task && ( -
- -

- {t('task', { ns: 'tasks' })}: {task.title} -

+ // Show loading skeleton while the task data is being fetched. + if (isLoading) + return ( +
+
+ +
+ + +
- )} - {isLoadingTask && } - - {/* Menu */} -
- {task && ( - - )} - {task && ( - - - - } - onSuccess={() => navigate(-1)} - /> - )} -
-
- - {taskError && ( - - )} - - {isLoadingTask && ( -
-
- - -
-
+
+
-
+
- )} +
+ ); + + // Show error alert if there was an error fetching the task. + if (error) { + return ( + + ); + } - {task && ( -
- + // Show not found if the task does not exist. + if (!task) + return ( + + + + + + {t('errors.not-found', { ns: 'tasks' })} + {t('errors.unable-to-find-task', { ns: 'tasks' })} + + + + + + + ); + + // Render the task detail layout once the task data is available. + return ( +
+ {/* Heading */} +
+
+ {task.title}
- )} + + {/* Menu */} + + + + + + {t('edit-task', { ns: 'tasks' })} + + + + + + + + } + onSuccess={() => navigate(-1)} + /> + + + + + + + + {t('go-back-to-task-list', { ns: 'tasks' })} + + +
+ +
+ +
); }; diff --git a/packages/web/src/pages/Tasks/components/TasksPageBreadcrumbs.test.tsx b/packages/web/src/pages/Tasks/components/TasksPageBreadcrumbs.test.tsx index 64d7c8da..bba6ebb2 100644 --- a/packages/web/src/pages/Tasks/components/TasksPageBreadcrumbs.test.tsx +++ b/packages/web/src/pages/Tasks/components/TasksPageBreadcrumbs.test.tsx @@ -3,7 +3,7 @@ import { Navigate, Route, Routes } from 'react-router-dom'; import { render, screen } from '@/test/test-utils'; -import TasksPageBreadcrumbs from './TasksPageBreadcrumbs'; +import { TasksPageBreadcrumbs } from './TasksPageBreadcrumbs'; describe('TasksPageBreadcrumbs', () => { it('should render successfully', async () => { diff --git a/packages/web/src/pages/Tasks/components/TasksPageBreadcrumbs.tsx b/packages/web/src/pages/Tasks/components/TasksPageBreadcrumbs.tsx index 5c6ed945..378d8042 100644 --- a/packages/web/src/pages/Tasks/components/TasksPageBreadcrumbs.tsx +++ b/packages/web/src/pages/Tasks/components/TasksPageBreadcrumbs.tsx @@ -61,7 +61,7 @@ const TasksPageBreadcrumbs = ({ className, testId = 'page-tasks-breadcrumbs' }: {!!task && ( - + {task.title} @@ -83,4 +83,4 @@ const TasksPageBreadcrumbs = ({ className, testId = 'page-tasks-breadcrumbs' }: ); }; -export default TasksPageBreadcrumbs; +export { TasksPageBreadcrumbs }; diff --git a/packages/web/src/pages/Tasks/components/View/TaskView.test.tsx b/packages/web/src/pages/Tasks/components/View/TaskView.test.tsx index 1a6f3f13..cc4d2bcc 100644 --- a/packages/web/src/pages/Tasks/components/View/TaskView.test.tsx +++ b/packages/web/src/pages/Tasks/components/View/TaskView.test.tsx @@ -26,11 +26,11 @@ describe('TaskView', () => { isLoading: true, isError: false, } as unknown as UseQueryResult); - render(); - await screen.findByTestId('component-user-loading'); + render(); + const loading = await screen.findByTestId('task-view-user-loading'); // ASSERT - expect(screen.getByTestId('component-user-loading')).toBeDefined(); + expect(loading).toBeDefined(); }); it('should show user error state', async () => { @@ -41,11 +41,11 @@ describe('TaskView', () => { isLoading: false, isError: true, } as unknown as UseQueryResult); - render(); - await screen.findByTestId('component-user-error'); + render(); + const error = await screen.findByTestId('task-view-user-error'); // ASSERT - expect(screen.getByTestId('component-user-error')).toBeDefined(); + expect(error).toBeDefined(); }); it('should show user content', async () => { @@ -56,11 +56,11 @@ describe('TaskView', () => { isLoading: false, isError: false, } as unknown as UseQueryResult); - render(); - await screen.findByTestId('component-user-name'); + render(); + const userName = await screen.findByTestId('task-view-user-name'); // ASSERT - expect(screen.getByTestId('component-user-name')).toHaveTextContent(userFixture1.name); + expect(userName).toHaveTextContent(userFixture1.name); }); it('should show complete task', async () => { @@ -71,11 +71,11 @@ describe('TaskView', () => { isLoading: false, isError: false, } as unknown as UseQueryResult); - render(); - await screen.findByTestId('component-status'); + render(); + const status = await screen.findByTestId('task-view-status'); // ASSERT - expect(screen.getByTestId('component-status')).toHaveTextContent(/^COMPLETE$/); + expect(status).toHaveTextContent(/^COMPLETE$/i); }); it('should show incomplete task', async () => { @@ -86,10 +86,10 @@ describe('TaskView', () => { isLoading: false, isError: false, } as unknown as UseQueryResult); - render(); - await screen.findByTestId('component-status'); + render(); + const status = await screen.findByTestId('task-view-status'); // ASSERT - expect(screen.getByTestId('component-status')).toHaveTextContent(/^INCOMPLETE$/); + expect(status).toHaveTextContent(/^INCOMPLETE$/i); }); }); diff --git a/packages/web/src/pages/Tasks/components/View/TaskView.tsx b/packages/web/src/pages/Tasks/components/View/TaskView.tsx index 3664254d..560e2dd6 100644 --- a/packages/web/src/pages/Tasks/components/View/TaskView.tsx +++ b/packages/web/src/pages/Tasks/components/View/TaskView.tsx @@ -1,7 +1,6 @@ import { useTranslation } from 'react-i18next'; +import { cn } from 'cn'; -import { cn } from '@react-starter/shared/utils/css'; -import { BaseComponentProps } from '@react-starter/shared/types/components'; import { Badge } from '@react-starter/shared/components/shadcn/badge'; import { Skeleton } from '@react-starter/shared/components/shadcn/skeleton'; @@ -11,9 +10,8 @@ import { useGetUser } from '@/common/api/useGetUser'; /** * Properties for the `TaskView` component. * @param task - A `Task` object. - * @see {@link BaseComponentProps} */ -interface TaskViewProps extends BaseComponentProps { +interface TaskViewProps extends React.ComponentProps<'div'> { task: Task; } @@ -22,34 +20,37 @@ interface TaskViewProps extends BaseComponentProps { * This component is for the read-only display of a single Task. * @param {TaskViewProps} props - Component properties. */ -export const TaskView = ({ className, task, testId = 'task-view' }: TaskViewProps) => { +const TaskView = ({ className, task, ...props }: TaskViewProps) => { const { t } = useTranslation(); const { data: user, isLoading: isLoadingUser, isError: isErrorUser } = useGetUser({ userId: task.userId }); return ( -
-
-
Title
-
- {task.title} -
-
- -
-
Assignee
+
+
+
{t('label.assignee', { ns: 'tasks' })}
- {isLoadingUser && } - {isErrorUser && {t('unable-to-find-short')}} - {user && {user.name}} + {isLoadingUser && } + {isErrorUser && ( + + {t('errors.not-found', { ns: 'tasks' })} + + )} + {user && {user.name}}
-
-
Status
- - {task.completed ? 'COMPLETE' : 'INCOMPLETE'} +
+
{t('label.status', { ns: 'tasks' })}
+ + {task.completed ? t('complete', { ns: 'tasks' }) : t('incomplete', { ns: 'tasks' })}
); }; + +export { TaskView };