Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/tasks/tasks.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { TApiMethodsRecord } from '../common/common.types'
import {
AddTaskToWatchListDto,
CrateTaskReqDto,
DeferTaskReqDto,
GetTaskReqDto,
GetTasksDto,
GetWatchListTaskDto,
Expand Down Expand Up @@ -53,6 +54,13 @@ export const TasksApi = {
},
},

deferTask: {
key: ['tasksApi.deferredTask'],
fn: async ({ taskId, ...payload }: DeferTaskReqDto): Promise<TTask> => {
const { data } = await apiClient.patch<TTask>(`/v1/tasks/${taskId}?action=defer`, payload)
return data
},
},
toggleTaskWatchListStatus: {
key: ['tasksApi.toggleTaskWatchListStatus'],
fn: async ({ taskId, isActive }: ToggleWatchListStatusDto): Promise<void> => {
Expand Down
1 change: 1 addition & 0 deletions api/tasks/tasks.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export enum TASK_STATUS_ENUM {
TODO = 'TODO',
IN_PROGRESS = 'IN_PROGRESS',
DONE = 'DONE',
DEFERRED = 'DEFERRED',
}

export enum TASK_PRIORITY_ENUM {
Expand Down
12 changes: 12 additions & 0 deletions api/tasks/tasks.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type TTask = {
tags?: string[]
dueAt?: string
in_watchlist?: boolean | null
deferredDetails?: TDeferredDetails
}

export type TEditTask = {
Expand Down Expand Up @@ -111,3 +112,14 @@ export type ReassignTaskReqDto = {
task_id: string
executor_id: string
}

export type TDeferredDetails = {
deferredTill: string
deferredBy: TMinimalUser
deferredAt: string
}

export type DeferTaskReqDto = {
taskId: string
deferredTill: string
}
23 changes: 22 additions & 1 deletion components/create-edit-todo-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ import { SelectLabels } from '@/modules/dashboard/components/select-labels'
import { zodResolver } from '@hookform/resolvers/zod'
import { useQuery } from '@tanstack/react-query'
import { CalendarIcon, CircleDotIcon, LucideIcon, PlayIcon, TagIcon } from 'lucide-react'
import { useState } from 'react'
import { Controller, useForm, UseFormWatch } from 'react-hook-form'
import { z } from 'zod'
import { DatePickerSelect } from './date-picker-select'
import { DeferredTaskButton } from './deferred-task-button'
import { UserAndTeamSearch } from './user-and-team-search'

const todoFormSchema = z.object({
Expand Down Expand Up @@ -138,6 +140,7 @@ export const CreateEditTodoForm = ({

const buttonText = mode === 'create' ? 'Create' : 'Save'
const buttonLoadingText = mode === 'create' ? 'Creating...' : 'Saving...'
const [deferModalOpen, setDeferModalOpen] = useState(false)

const handleFormSubmit = (data: TTodoFormData) => {
onSubmit(data)
Expand Down Expand Up @@ -272,7 +275,14 @@ export const CreateEditTodoForm = ({
>
<Select
value={field.value}
onValueChange={(value) => field.onChange(value as TASK_STATUS_ENUM)}
onValueChange={(value) => {
const isAlreadyDeferred = field.value === TASK_STATUS_ENUM.DEFERRED
if (value === TASK_STATUS_ENUM.DEFERRED && !isAlreadyDeferred) {
setDeferModalOpen(true)
} else {
field.onChange(value as TASK_STATUS_ENUM)
}
}}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select task status" />
Expand All @@ -282,8 +292,19 @@ export const CreateEditTodoForm = ({
<SelectItem value={TASK_STATUS_ENUM.TODO}>Todo</SelectItem>
<SelectItem value={TASK_STATUS_ENUM.IN_PROGRESS}>In Progress</SelectItem>
<SelectItem value={TASK_STATUS_ENUM.DONE}>Done</SelectItem>

{initialData && (
<SelectItem value={TASK_STATUS_ENUM.DEFERRED}>Defer</SelectItem>
)}
</SelectContent>
</Select>
{initialData && (
<DeferredTaskButton
todo={initialData}
open={deferModalOpen}
setOpen={setDeferModalOpen}
/>
)}
</FormInput>
)}
/>
Expand Down
94 changes: 94 additions & 0 deletions components/deferred-task-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
'use client'

import { TasksApi } from '@/api/tasks/tasks.api'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { Label } from '@/components/ui/label'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useState } from 'react'
import { toast } from 'sonner'
import { TTodoFormData } from './create-edit-todo-form'
import { DatePickerSelect } from './date-picker-select'

type DeferredTaskButtonProps = {
todo: Partial<TTodoFormData>
open: boolean
setOpen: (open: boolean) => void
}

export const DeferredTaskButton = ({ todo, open, setOpen }: DeferredTaskButtonProps) => {
const [deferredTill, setDeferredTill] = useState<Date>()
const queryClient = useQueryClient()

const deferTaskMutation = useMutation({
mutationFn: TasksApi.deferTask.fn,
onSuccess: () => {
toast.success('Task deferred successfully')
void queryClient.invalidateQueries({ queryKey: TasksApi.getTasks.key() })
void queryClient.invalidateQueries({
queryKey: TasksApi.getTasks.key({ status: 'DEFERRED' }),
})
setOpen(false)
setDeferredTill(undefined)
},
onError: () => {
toast.error('Failed to defer task, please try again')
},
})

const handleDeferTask = () => {
if (!deferredTill) {
toast.error('Please select a defer date')
return
}

deferTaskMutation.mutate({
taskId: todo.taskId ?? '',
deferredTill: deferredTill.toISOString(),
})
}

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Defer Todo</DialogTitle>
<DialogDescription>
Defer &quot;{todo.title}&quot; to a later date. The todo will be moved to your deferred
todos.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="deferredTill" className="text-right">
Defer Until
</Label>
<div className="col-span-3">
<DatePickerSelect
value={deferredTill}
onChange={setDeferredTill}
isDateDisabled={(date) => date <= new Date()}
/>
</div>
</div>
</div>
<DialogFooter>
<Button
type="submit"
onClick={handleDeferTask}
disabled={!deferredTill || deferTaskMutation.isPending}
>
{deferTaskMutation.isPending ? 'Deferring...' : 'Defer Todo'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
3 changes: 3 additions & 0 deletions components/edit-task-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export const EditTodoButton = ({ todo }: EditTodoButtonProps) => {
onSuccess: (res) => {
void queryClient.invalidateQueries({ queryKey: TasksApi.getTasks.key() })
void queryClient.invalidateQueries({ queryKey: TasksApi.getWatchListTasks.key })
void queryClient.invalidateQueries({
queryKey: TasksApi.getTasks.key({ status: 'DEFERRED' }),
})

if (res.assignee?.user_type === USER_TYPE_ENUM.TEAM) {
void queryClient.invalidateQueries({
Expand Down
20 changes: 14 additions & 6 deletions components/todo-list-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ const QUERY_PARAMS_KEYS = {

type TodoListTableHeaderProps = {
showActions?: boolean
isDeferred?: boolean
}

export const TodoListTableHeader = ({ showActions }: TodoListTableHeaderProps) => {
export const TodoListTableHeader = ({ showActions, isDeferred }: TodoListTableHeaderProps) => {
return (
<TableHeader>
<TableRow>
Expand All @@ -33,7 +34,7 @@ export const TodoListTableHeader = ({ showActions }: TodoListTableHeaderProps) =
<TableHead className="text-black">Label</TableHead>
<TableHead className="text-black">Priority</TableHead>
<TableHead className="text-black">Assignee</TableHead>
<TableHead className="text-black">Due date</TableHead>
<TableHead className="text-black">{isDeferred ? 'Deferred Until' : 'Due Date'}</TableHead>
{showActions && <TableHead className="text-black">Actions</TableHead>}
</TableRow>
</TableHeader>
Expand All @@ -43,9 +44,11 @@ export const TodoListTableHeader = ({ showActions }: TodoListTableHeaderProps) =
type TodoListTableRowProps = {
todo: TTask
showActions?: boolean
isDeferred?: boolean
}

const TodoListTableRow = ({ todo, showActions }: TodoListTableRowProps) => {
const TodoListTableRow = ({ todo, showActions, isDeferred }: TodoListTableRowProps) => {
const date = isDeferred ? todo.deferredDetails?.deferredTill : todo.dueAt
return (
<TableRow>
<TableCell className="whitespace-nowrap">{todo.title}</TableCell>
Expand All @@ -65,7 +68,7 @@ const TodoListTableRow = ({ todo, showActions }: TodoListTableRowProps) => {
<TableCell className="whitespace-nowrap">{todo.assignee?.assignee_name ?? '--'}</TableCell>

<TableCell className="whitespace-nowrap">
{todo.dueAt ? new DateUtil(todo.dueAt).format(DateFormats.D_MMM_YYYY) : '--'}
{date ? new DateUtil(date).format(DateFormats.D_MMM_YYYY) : '--'}
</TableCell>

<TableCell>
Expand All @@ -87,13 +90,15 @@ type TodoListTableBodyProps = {
isLoading?: boolean
isPlaceholderData?: boolean
showActions?: boolean
isDeferred?: boolean
}

const TodoListTableBody = ({
tasks,
isLoading,
isPlaceholderData,
showActions,
isDeferred,
}: TodoListTableBodyProps) => {
if (isLoading || isPlaceholderData) {
return (
Expand Down Expand Up @@ -124,6 +129,7 @@ const TodoListTableBody = ({
key={task.id}
todo={task}
showActions={showActions && task.assignee?.user_type !== USER_TYPE_ENUM.TEAM}
isDeferred={isDeferred}
/>
))}
</TableBody>
Expand Down Expand Up @@ -163,6 +169,7 @@ export const TodoListTable = ({

const search = searchParams.get(QUERY_PARAMS_KEYS.search) ?? ''
const currentTab = searchParams.get('tab')
const isDeferred = currentTab === DashboardTasksTableTabs.Deferred

const filteredTasks = !search
? tasks
Expand Down Expand Up @@ -196,7 +203,7 @@ export const TodoListTable = ({
containerClassName="w-full lg:max-w-xs"
onChange={(e) => handleSearch(e.target.value)}
/>
{currentTab !== DashboardTasksTableTabs.WatchList && (
{currentTab == DashboardTasksTableTabs.All && (
<div className="flex px-4">
<Switch
id="includeDoneTasks"
Expand All @@ -212,12 +219,13 @@ export const TodoListTable = ({

<div className="overflow-hidden rounded-md border">
<Table>
<TodoListTableHeader showActions={showActions} />
<TodoListTableHeader showActions={showActions} isDeferred={isDeferred} />
<TodoListTableBody
tasks={filteredTasks}
isLoading={isLoading}
isPlaceholderData={isPlaceholderData}
showActions={showActions}
isDeferred={isDeferred}
/>
</Table>
</div>
Expand Down
5 changes: 4 additions & 1 deletion components/todo-status-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const TASK_STATUS_TO_TEXT_MAP: Record<TASK_STATUS_ENUM, string> = {
[TASK_STATUS_ENUM.TODO]: 'Todo',
[TASK_STATUS_ENUM.IN_PROGRESS]: 'In Progress',
[TASK_STATUS_ENUM.DONE]: 'Done',
[TASK_STATUS_ENUM.DEFERRED]: 'Deferred',
}

type TodoStatusTableProps = {
Expand All @@ -20,7 +21,9 @@ export const TodoStatusTable = ({ status }: TodoStatusTableProps) => {
? 'bg-gray-100 text-gray-700'
: status === TASK_STATUS_ENUM.IN_PROGRESS
? 'bg-yellow-100 text-yellow-700'
: 'bg-green-100 text-green-700',
: status === TASK_STATUS_ENUM.DEFERRED
? 'bg-purple-100 text-purple-700'
: 'bg-green-100 text-green-700',
)}
>
{TASK_STATUS_TO_TEXT_MAP[status]}
Expand Down
1 change: 1 addition & 0 deletions lib/todo-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class TodoUtil {

static getDefaultTodoFormData = (todo: TTask): Partial<TTodoFormData> => {
return {
taskId: todo.id,
title: todo.title,
status: todo.status,
priority: todo.priority,
Expand Down
30 changes: 30 additions & 0 deletions modules/dashboard/components/dashboard-deferred-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client'

import { TasksApi } from '@/api/tasks/tasks.api'
import { GetTaskReqDto } from '@/api/tasks/tasks.types'
import { CommonPageError } from '@/components/common-page-error'
import { TodoListTable } from '@/components/todo-list-table'
import { useQuery } from '@tanstack/react-query'
import { useSearchParams } from 'next/navigation'
import { DashboardTasksTableTabs as TabsConstants } from '../constants'

export const DashboardDeferredTable = () => {
const searchParams = useSearchParams()
const tab = searchParams.get('tab')
const status = searchParams.get('status')
const isInvalidCombination = tab === TabsConstants.Deferred && status === 'Done'

const queryParams: GetTaskReqDto = { status: 'DEFERRED' }

const { data, isLoading, isError } = useQuery({
queryKey: TasksApi.getTasks.key(queryParams),
queryFn: () => TasksApi.getTasks.fn(queryParams),
select: (data) => data.tasks,
})

if (isError || isInvalidCombination) {
return <CommonPageError />
}

return <TodoListTable showActions isLoading={isLoading} tasks={data} />
}
Loading