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 @@ -6,6 +6,7 @@ import {
GetTaskReqDto,
GetTasksDto,
GetWatchListTaskDto,
ReassignTaskReqDto,
ToggleWatchListStatusDto,
TTask,
TWatchListTask,
Expand Down Expand Up @@ -57,4 +58,11 @@ export const TasksApi = {
await apiClient.patch(`/v1/watchlist/tasks/${taskId}`, { isActive })
},
},

reassignTask: {
key: ['TasksApi.reassignTask'],
fn: async ({ task_id, executor_id }: ReassignTaskReqDto): Promise<void> => {
await apiClient.patch(`/v1/task-assignments/${task_id}`, { executor_id })
},
},
} satisfies TApiMethodsRecord
5 changes: 5 additions & 0 deletions api/tasks/tasks.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,8 @@ export type AssignTaskToUserReqDto = {
task_id: string
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent naming convention usage category Readability

Tell me more
What is the issue?

The codebase uses both camelCase (taskId) and snake_case (task_id) naming conventions for properties.

Why this matters

Mixed naming conventions across the codebase reduce readability and make it harder to predict property names. TypeScript conventionally uses camelCase.

Suggested change ∙ Feature Preview

Standardize on camelCase throughout:

export type ReassignTaskReqDto = {
  taskId: string
  assigneeId: string
}

export type AssignTaskToUserReqDto = {
  taskId: string
  assigneeId: string
}
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

assignee_id: string
}

export type ReassignTaskReqDto = {
task_id: string
executor_id: string
}
Comment on lines +109 to +112
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing User Type Validation Field category Functionality

Tell me more
What is the issue?

The ReassignTaskReqDto is missing the user_type field which is present in other task assignment-related DTOs and is required for proper user role validation.

Why this matters

Without the user_type field, the API cannot validate if the target user has the appropriate role for the task, potentially allowing assignments to users with incorrect permissions.

Suggested change ∙ Feature Preview

Add the user_type field to the DTO:

export type ReassignTaskReqDto = {
  task_id: string
  assignee_id: string
  user_type: USER_TYPE_ENUM
}
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

6 changes: 1 addition & 5 deletions api/teams/teams.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ export const TeamsApi = {
},
},
getTeamById: {
key: ({ teamId, member: members }: GetTeamByIdReqDto) => [
'TeamsApi.getTeamById',
teamId,
members,
],
key: ({ teamId, member }: GetTeamByIdReqDto) => ['TeamsApi.getTeamById', teamId, member],
fn: async ({ teamId, ...params }: GetTeamByIdReqDto): Promise<GetTeamByIdResponseDto> => {
const { data } = await apiClient.get<GetTeamByIdResponseDto>(`/v1/teams/${teamId}`, {
params,
Expand Down
117 changes: 117 additions & 0 deletions components/reassign-user.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { TasksApi } from '@/api/tasks/tasks.api'
import { TUser } from '@/api/users/users.types'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { UserSearchIcon } from 'lucide-react'
import { ReactNode, useState } from 'react'
import { toast } from 'sonner'
import { TeamUserSearchDropdown } from './team-user-search-dropdown'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from './ui/alert-dialog'
import { Button } from './ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'

type ReassignUserModalProps = {
taskId: string
teamId: string
open: boolean
children: ReactNode
onOpenChange: (open: boolean) => void
}

const ReassignUserModal = ({
open,
taskId,
teamId,
children,
onOpenChange,
}: ReassignUserModalProps) => {
const queryClient = useQueryClient()
const [selectedUser, setSelectedUser] = useState<TUser | null>(null)

const reassignTaskMutation = useMutation({
mutationFn: TasksApi.reassignTask.fn,
onSuccess: () => {
toast.success(`Task assigned to ${selectedUser?.name}`)
void queryClient.invalidateQueries({ queryKey: TasksApi.getTasks.key(teamId) })
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overly Broad Cache Invalidation category Performance

Tell me more
What is the issue?

Invalidating the entire task query cache when only a single task is modified is inefficient.

Why this matters

This causes unnecessary refetching of all tasks when only one task's assignment has changed, potentially causing performance issues with large task lists.

Suggested change ∙ Feature Preview

Use more specific cache updates by either invalidating just the specific task or using cache mutation:

queryClient.setQueryData([TasksApi.getTasks.key(teamId)], (old: any) => {
  // Update only the specific task in the cache
  return old.map((task: any) => 
    task.id === taskId ? { ...task, executor_id: selectedUser.id } : task
  );
});
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

onOpenChange(false)
setSelectedUser(null)
},
onError: () => {
toast.error('Failed to reassign task, please try again')
},
Comment on lines +47 to +49
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loss of Error Context in Mutation Handler category Error Handling

Tell me more
What is the issue?

The error handler for the reassignTask mutation loses the actual error information by only showing a generic message.

Why this matters

Without the actual error details, debugging production issues becomes more difficult as developers won't know the root cause of the failure.

Suggested change ∙ Feature Preview

Include the error information in the error handler:

onError: (error) => {
      console.error('Task reassignment failed:', error);
      toast.error(`Failed to reassign task: ${error.message || 'Please try again'}`);
    }
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

})

const handleReassign = () => {
if (!selectedUser) return

debugger
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove debugger statement before merging.

Debugger statements should not be committed to production code.

Apply this diff to remove the debugger statement:

-    debugger
     reassignTaskMutation.mutate({
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
debugger
reassignTaskMutation.mutate({
🧰 Tools
🪛 Biome (1.9.4)

[error] 55-55: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)

🤖 Prompt for AI Agents
In components/reassign-user.tsx at line 55, there is a debugger statement that
should be removed before merging. Delete the debugger statement to ensure no
debugging code remains in the production codebase.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debugger Statement in Production Code category Functionality

Tell me more
What is the issue?

There is a debugger statement left in the production code in the handleReassign function.

Why this matters

This will cause the application to pause execution when the developer tools are open, disrupting the normal flow of the application in production environments.

Suggested change ∙ Feature Preview

Remove the debugger statement:

const handleReassign = () => {
  if (!selectedUser) return

  reassignTaskMutation.mutate({
    task_id: taskId,
    executor_id: selectedUser.id,
  })
}
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

reassignTaskMutation.mutate({
task_id: taskId,
executor_id: selectedUser.id,
})
}
Comment on lines +52 to +60
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Development Artifact in Production Code category Design

Tell me more
What is the issue?

The handleReassign function contains a debugger statement which should not be in production code.

Why this matters

Leaving debugger statements in production code can cause unexpected pauses in execution when developer tools are open and makes the code less professional.

Suggested change ∙ Feature Preview

Remove the debugger statement:

const handleReassign = () => {
  if (!selectedUser) return
  reassignTaskMutation.mutate({
    task_id: taskId,
    executor_id: selectedUser.id,
  })
}
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.


return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>

<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Reassign user</AlertDialogTitle>
</AlertDialogHeader>

<TeamUserSearchDropdown
teamId={teamId}
value={selectedUser?.id}
placeholder="Search user"
onUserSelect={setSelectedUser}
/>

<AlertDialogFooter>
<AlertDialogCancel disabled={reassignTaskMutation.isPending}>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={handleReassign}
disabled={!selectedUser || reassignTaskMutation.isPending}
>
{reassignTaskMutation.isPending ? 'Reassigning...' : 'Reassign'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}

type ReassignUserProps = {
taskId: string
teamId: string
}

export const ReassignUser = ({ taskId, teamId }: ReassignUserProps) => {
const [showModal, setShowModal] = useState(false)

return (
<ReassignUserModal open={showModal} onOpenChange={setShowModal} taskId={taskId} teamId={teamId}>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="icon"
variant="ghost"
onClick={() => setShowModal(true)}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline Handler Recreation category Performance

Tell me more
What is the issue?

The click handler is recreated on every render due to the inline arrow function.

Why this matters

Creating new function references on each render causes unnecessary memory allocations and potential re-renders of child components that rely on prop referential equality.

Suggested change ∙ Feature Preview

Extract the click handler to a memoized function using useCallback:

const handleClick = useCallback(() => setShowModal(true), []);
// Then use it as:
<Button onClick={handleClick} ... >
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

className="cursor-pointer hover:bg-gray-200 hover:text-gray-800 active:bg-gray-300 active:text-gray-900"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long inline Tailwind classes category Readability

Tell me more
What is the issue?

Long inline Tailwind CSS classes reduce code readability.

Why this matters

Multiple inline Tailwind classes make the component markup harder to scan and understand. These styles could be abstracted into a reusable class or component style.

Suggested change ∙ Feature Preview

Extract the styles into a dedicated CSS class or component style:

const buttonStyles = "cursor-pointer hover:bg-gray-200 hover:text-gray-800 active:bg-gray-300 active:text-gray-900"

<Button className={buttonStyles} ... />
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

>
<UserSearchIcon className="h-5 w-5" />
</Button>
</TooltipTrigger>
<TooltipContent>Reassign user</TooltipContent>
</Tooltip>
</ReassignUserModal>
)
}
96 changes: 96 additions & 0 deletions components/team-user-search-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { TeamsApi } from '@/api/teams/teams.api'
import { TUser } from '@/api/users/users.types'
import { cn } from '@/lib/utils'
import { useQuery } from '@tanstack/react-query'
import { ChevronDown, User } from 'lucide-react'
import { useState } from 'react'
import { Button } from './ui/button'
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from './ui/command'
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover'

type TeamUserSearchDropdownProps = {
teamId: string
value?: string
placeholder?: string
onUserSelect: (user: TUser) => void
}

export const TeamUserSearchDropdown = ({
teamId,
value,
placeholder = 'Search user',
onUserSelect,
}: TeamUserSearchDropdownProps) => {
const [open, setOpen] = useState(false)
const [search, setSearch] = useState('')

const { data: usersList, isLoading } = useQuery({
queryKey: TeamsApi.getTeamById.key({ teamId, member: true }),
queryFn: () => TeamsApi.getTeamById.fn({ teamId, member: true }),
select: (res) => res.users,
})

const selectedUser = usersList?.find((user) => user.id === value)

const handleSelect = (user: TUser) => {
onUserSelect(user)
setOpen(false)
setSearch('')
}

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className={cn(
'w-full justify-between font-normal',
!selectedUser && 'text-muted-foreground',
)}
>
{selectedUser?.name ?? placeholder}
<ChevronDown className="opacity-50" />
</Button>
</PopoverTrigger>

<PopoverContent
align="start"
className="p-0"
style={{ width: 'var(--radix-popover-trigger-width)' }}
>
<Command>
<CommandInput placeholder={placeholder} value={search} onValueChange={setSearch} />

<CommandList>
<CommandEmpty>{isLoading ? 'Searching users...' : 'No users found.'}</CommandEmpty>

<CommandGroup>
{usersList?.map((user) => (
<CommandItem
key={user.id}
value={user.name}
onSelect={() => handleSelect(user)}
className="flex items-center gap-2"
>
<User className="text-muted-foreground h-4 w-4" />
<div className="flex-1">
<div className="font-medium">{user.name}</div>
</div>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}
21 changes: 14 additions & 7 deletions components/todo-list-table.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { USER_TYPE_ENUM } from '@/api/common/common-enum'
import { TTask } from '@/api/tasks/tasks.types'
import { DateFormats, DateUtil } from '@/lib/date-util'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
Expand All @@ -20,7 +21,7 @@ type TodoListTableHeaderProps = {
showActions?: boolean
}

const TodoListTableHeader = ({ showActions }: TodoListTableHeaderProps) => {
export const TodoListTableHeader = ({ showActions }: TodoListTableHeaderProps) => {
return (
<TableHeader>
<TableRow>
Expand Down Expand Up @@ -64,14 +65,16 @@ const TodoListTableRow = ({ todo, showActions }: TodoListTableRowProps) => {
{todo.dueAt ? new DateUtil(todo.dueAt).format(DateFormats.D_MMM_YYYY) : '--'}
</TableCell>

{showActions && (
<TableCell>
<TableCell>
{showActions ? (
<div className="flex items-center gap-0.5">
<EditTodoButton todo={todo} />
<WatchListButton taskId={todo.id} isInWatchlist={todo.in_watchlist} />
</div>
</TableCell>
)}
) : (
<div className="px-2">--</div>
)}
</TableCell>
</TableRow>
)
}
Expand Down Expand Up @@ -108,13 +111,17 @@ const TodoListTableBody = ({ tasks, isLoading, showActions }: TodoListTableBodyP
return (
<TableBody>
{tasks?.map((task) => (
<TodoListTableRow key={task.id} todo={task} showActions={showActions} />
<TodoListTableRow
key={task.id}
todo={task}
showActions={showActions && task.assignee?.user_type !== USER_TYPE_ENUM.TEAM}
/>
))}
</TableBody>
)
}

const TodoListTableRowShimmer = ({ showActions = true }: { showActions?: boolean }) => {
export const TodoListTableRowShimmer = ({ showActions = true }: { showActions?: boolean }) => {
return (
<TableRow>
<TableCell colSpan={showActions ? 7 : 6}>
Expand Down
1 change: 1 addition & 0 deletions modules/dashboard/components/create-todo-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const CreateTodoButton = () => {
const createTaskMutation = useMutation({
mutationFn: TasksApi.createTask.fn,
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: TasksApi.getTasks.key() })
void queryClient.invalidateQueries({ queryKey: TasksApi.getTasks.key() })
Comment on lines +16 to 17
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove duplicate query invalidation call.

There are two identical calls to queryClient.invalidateQueries with the same query key. This duplication serves no purpose and should be removed.

Apply this diff to remove the duplicate:

       void queryClient.invalidateQueries({ queryKey: TasksApi.getTasks.key() })
-      void queryClient.invalidateQueries({ queryKey: TasksApi.getTasks.key() })
       toast.success('Todo created successfully')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void queryClient.invalidateQueries({ queryKey: TasksApi.getTasks.key() })
void queryClient.invalidateQueries({ queryKey: TasksApi.getTasks.key() })
void queryClient.invalidateQueries({ queryKey: TasksApi.getTasks.key() })
toast.success('Todo created successfully')
🤖 Prompt for AI Agents
In modules/dashboard/components/create-todo-button.tsx around lines 16 to 17,
there are two identical calls to queryClient.invalidateQueries with the same
query key. Remove one of these duplicate calls to avoid redundant query
invalidation.

toast.success('Todo created successfully')
setShowCreateTaskForm(false)
Expand Down
2 changes: 1 addition & 1 deletion modules/teams/components/teams-layout-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export const TeamsLayoutHeader = ({ teamId }: TeamsLayoutHeaderProps) => {

<Button size="sm" onClick={() => setIsAddMembersModalOpen(true)}>
<UserRoundPlusIcon />
Add a member
Add members
</Button>

<AlertDialog open={isAddMembersModalOpen} onOpenChange={handleCloseModal}>
Expand Down
Loading