kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #1195 from tinsever/fix/1175-renaming-column-ignored

fix(web): renaming column ignored

authored by

Andrej and committed by
GitHub
19fcc700 2b528247

+122 -23
+42 -6
apps/web/src/components/task/subtask-assignee-popover.tsx
··· 15 15 import { toast } from "@/lib/toast"; 16 16 import type Task from "@/types/task"; 17 17 18 + const INITIAL_VISIBLE_USERS = 40; 19 + const VISIBLE_USERS_STEP = 40; 20 + 18 21 type SubtaskAssigneePopoverProps = { 19 22 tasks: Task[]; 20 23 workspaceId: string; ··· 28 31 }: SubtaskAssigneePopoverProps) { 29 32 const { t } = useTranslation(); 30 33 const [open, setOpen] = useState(false); 34 + const [visibleUsersCount, setVisibleUsersCount] = useState( 35 + INITIAL_VISIBLE_USERS, 36 + ); 31 37 const { mutateAsync: updateTaskAssignee } = useUpdateTaskAssignee(); 32 38 const { data: workspaceUsers } = useGetActiveWorkspaceUsers(workspaceId); 33 39 ··· 75 81 return [unassignedOption, ...userOptions]; 76 82 }, [usersOptions, handleAssigneeChange]); 77 83 84 + const visibleUsersOptions = useMemo(() => { 85 + return usersOptions?.slice(0, visibleUsersCount) ?? []; 86 + }, [usersOptions, visibleUsersCount]); 87 + 88 + const handleOpenChange = useCallback((nextOpen: boolean) => { 89 + setOpen(nextOpen); 90 + if (nextOpen) { 91 + setVisibleUsersCount(INITIAL_VISIBLE_USERS); 92 + } 93 + }, []); 94 + 95 + const handleListScroll = useCallback( 96 + (event: React.UIEvent<HTMLDivElement>) => { 97 + const target = event.currentTarget; 98 + const nearBottom = 99 + target.scrollHeight - target.scrollTop - target.clientHeight < 48; 100 + 101 + if (!nearBottom) return; 102 + 103 + setVisibleUsersCount((current) => { 104 + const totalUsers = usersOptions?.length ?? current; 105 + return Math.min(current + VISIBLE_USERS_STEP, totalUsers); 106 + }); 107 + }, 108 + [usersOptions?.length], 109 + ); 110 + 78 111 useNumberedShortcuts(open, shortcutOptions); 79 112 80 113 return ( 81 - <Popover open={open} onOpenChange={setOpen} modal={false}> 114 + <Popover open={open} onOpenChange={handleOpenChange} modal={false}> 82 115 <PopoverTrigger asChild>{children}</PopoverTrigger> 83 - <PopoverContent className="w-48 p-0" align="start"> 84 - <div className="space-y-1 p-1"> 116 + <PopoverContent className="w-56 p-0" align="start"> 117 + <div 118 + className="max-h-80 space-y-1 overflow-y-auto p-1" 119 + onScroll={handleListScroll} 120 + > 85 121 <Button 86 122 variant="ghost" 87 123 size="sm" ··· 105 141 <ShortcutNumber number={1} /> 106 142 )} 107 143 </Button> 108 - {usersOptions?.slice(0, 8).map((user, index) => ( 144 + {visibleUsersOptions.map((user, index) => ( 109 145 <Button 110 146 key={user.value} 111 147 variant="ghost" ··· 122 158 <span className="text-sm truncate">{user.label}</span> 123 159 {currentAssignee === user.value ? ( 124 160 <Check className="ml-auto h-4 w-4 shrink-0" /> 125 - ) : ( 161 + ) : index < 8 ? ( 126 162 <ShortcutNumber number={index + 2} /> 127 - )} 163 + ) : null} 128 164 </Button> 129 165 ))} 130 166 </div>
+2 -2
apps/web/src/components/task/subtask-status-popover.tsx
··· 12 12 import { useGetColumns } from "@/hooks/queries/column/use-get-columns"; 13 13 import { useNumberedShortcuts } from "@/hooks/use-numbered-shortcuts"; 14 14 import { getColumnIcon } from "@/lib/column"; 15 - import { getStatusLabel } from "@/lib/i18n/domain"; 15 + import { getStatusDisplayLabel } from "@/lib/i18n/domain"; 16 16 import { toast } from "@/lib/toast"; 17 17 import type Task from "@/types/task"; 18 18 ··· 89 89 > 90 90 {getColumnIcon(status.value, status.isFinal)} 91 91 <span className="text-sm"> 92 - {getStatusLabel(status.value) || status.label} 92 + {getStatusDisplayLabel(status.value, status.label)} 93 93 </span> 94 94 {currentStatus === status.value ? ( 95 95 <Check className="ml-auto h-4 w-4" />
+42 -6
apps/web/src/components/task/task-assignee-popover.tsx
··· 15 15 import { toast } from "@/lib/toast"; 16 16 import type Task from "@/types/task"; 17 17 18 + const INITIAL_VISIBLE_USERS = 40; 19 + const VISIBLE_USERS_STEP = 40; 20 + 18 21 type TaskAssigneePopoverProps = { 19 22 task: Task; 20 23 workspaceId: string; ··· 28 31 }: TaskAssigneePopoverProps) { 29 32 const { t } = useTranslation(); 30 33 const [open, setOpen] = useState(false); 34 + const [visibleUsersCount, setVisibleUsersCount] = useState( 35 + INITIAL_VISIBLE_USERS, 36 + ); 31 37 const { mutateAsync: updateTaskAssignee } = useUpdateTaskAssignee(); 32 38 const { data: workspaceUsers } = useGetActiveWorkspaceUsers(workspaceId); 33 39 ··· 67 73 return [unassignedOption, ...userOptions]; 68 74 }, [usersOptions, handleAssigneeChange]); 69 75 76 + const visibleUsersOptions = useMemo(() => { 77 + return usersOptions?.slice(0, visibleUsersCount) ?? []; 78 + }, [usersOptions, visibleUsersCount]); 79 + 80 + const handleOpenChange = useCallback((nextOpen: boolean) => { 81 + setOpen(nextOpen); 82 + if (nextOpen) { 83 + setVisibleUsersCount(INITIAL_VISIBLE_USERS); 84 + } 85 + }, []); 86 + 87 + const handleListScroll = useCallback( 88 + (event: React.UIEvent<HTMLDivElement>) => { 89 + const target = event.currentTarget; 90 + const nearBottom = 91 + target.scrollHeight - target.scrollTop - target.clientHeight < 48; 92 + 93 + if (!nearBottom) return; 94 + 95 + setVisibleUsersCount((current) => { 96 + const totalUsers = usersOptions?.length ?? current; 97 + return Math.min(current + VISIBLE_USERS_STEP, totalUsers); 98 + }); 99 + }, 100 + [usersOptions?.length], 101 + ); 102 + 70 103 useNumberedShortcuts(open, shortcutOptions); 71 104 72 105 return ( 73 - <Popover open={open} onOpenChange={setOpen}> 106 + <Popover open={open} onOpenChange={handleOpenChange}> 74 107 <PopoverTrigger asChild>{children}</PopoverTrigger> 75 - <PopoverContent className="w-48 p-0" align="start"> 76 - <div className="space-y-1 p-1"> 108 + <PopoverContent className="w-56 p-0" align="start"> 109 + <div 110 + className="max-h-80 space-y-1 overflow-y-auto p-1" 111 + onScroll={handleListScroll} 112 + > 77 113 <Button 78 114 variant="ghost" 79 115 size="sm" ··· 97 133 <ShortcutNumber number={1} /> 98 134 )} 99 135 </Button> 100 - {usersOptions?.slice(0, 8).map((user, index) => ( 136 + {visibleUsersOptions.map((user, index) => ( 101 137 <Button 102 138 key={user.value} 103 139 variant="ghost" ··· 114 150 <span className="text-sm truncate">{user.label}</span> 115 151 {task.userId === user.value ? ( 116 152 <Check className="ml-auto h-4 w-4 shrink-0" /> 117 - ) : ( 153 + ) : index < 8 ? ( 118 154 <ShortcutNumber number={index + 2} /> 119 - )} 155 + ) : null} 120 156 </Button> 121 157 ))} 122 158 </div>
+17 -7
apps/web/src/components/task/task-properties-sidebar.tsx
··· 19 19 TooltipTrigger, 20 20 } from "@/components/ui/tooltip"; 21 21 import labelColors from "@/constants/label-colors"; 22 + import { useGetColumns } from "@/hooks/queries/column/use-get-columns"; 22 23 import useGetGiteaIntegration from "@/hooks/queries/gitea-integration/use-get-gitea-integration"; 23 24 import useGetGithubIntegration from "@/hooks/queries/github-integration/use-get-github-integration"; 24 25 import useGetLabelsByTask from "@/hooks/queries/label/use-get-labels-by-task"; ··· 30 31 import { getColumnIcon } from "@/lib/column"; 31 32 import { dueDateStatusColors, getDueDateStatus } from "@/lib/due-date-status"; 32 33 import { formatDateShort } from "@/lib/format"; 33 - import { getPriorityLabel, getStatusLabel } from "@/lib/i18n/domain"; 34 + import { getPriorityLabel, getStatusDisplayLabel } from "@/lib/i18n/domain"; 34 35 import { getPriorityIcon } from "@/lib/priority"; 35 36 import { toast } from "@/lib/toast"; 36 37 import TaskAssigneePopover from "./task-assignee-popover"; ··· 81 82 const { t } = useTranslation(); 82 83 const { data: task } = useGetTask(taskId ?? ""); 83 84 const { data: project } = useGetProject({ id: projectId, workspaceId }); 85 + const { data: columns = [] } = useGetColumns(projectId); 84 86 const { data: workspaceUsers } = useGetActiveWorkspaceUsers(workspaceId); 85 87 const { data: taskLabels = [] } = useGetLabelsByTask(taskId ?? ""); 86 88 const { data: githubIntegration } = useGetGithubIntegration(projectId); ··· 88 90 const { data: workspaceProjects = [] } = useGetProjects({ workspaceId }); 89 91 const canMoveTask = 90 92 Boolean(task) && workspaceProjects.some((p) => p.id !== task?.projectId); 93 + const statusColumn = columns.find( 94 + (column) => column.slug === task?.status || column.id === task?.status, 95 + ); 96 + const statusLabel = getStatusDisplayLabel( 97 + task?.status ?? "", 98 + statusColumn?.name, 99 + ); 100 + const statusIsFinal = statusColumn?.isFinal ?? false; 91 101 92 102 const projectSlug = project?.slug; 93 103 const taskNumber = task?.number; ··· 188 198 size="sm" 189 199 className="justify-start h-7 px-1.5 gap-1.5" 190 200 > 191 - {getColumnIcon(task.status ?? "", false)} 201 + {getColumnIcon(task.status ?? "", statusIsFinal)} 192 202 <span className="text-xs font-semibold truncate"> 193 - {getStatusLabel(task.status ?? "")} 203 + {statusLabel} 194 204 </span> 195 205 </Button> 196 206 </TaskStatusPopover> ··· 372 382 size="sm" 373 383 className="justify-start h-7 px-1.5 gap-1.5" 374 384 > 375 - {getColumnIcon(task.status ?? "", false)} 385 + {getColumnIcon(task.status ?? "", statusIsFinal)} 376 386 <span className="text-xs font-semibold truncate"> 377 - {getStatusLabel(task.status ?? "")} 387 + {statusLabel} 378 388 </span> 379 389 </Button> 380 390 </TaskStatusPopover> ··· 559 569 size="sm" 560 570 className="justify-start h-7 px-1.5 gap-1.5 w-full" 561 571 > 562 - {getColumnIcon(task.status ?? "", false)} 572 + {getColumnIcon(task.status ?? "", statusIsFinal)} 563 573 <span className="text-xs font-semibold truncate"> 564 - {getStatusLabel(task.status ?? "")} 574 + {statusLabel} 565 575 </span> 566 576 </Button> 567 577 </TaskStatusPopover>
+2 -2
apps/web/src/components/task/task-status-popover.tsx
··· 11 11 import { useUpdateTaskStatus } from "@/hooks/mutations/task/use-update-task-status"; 12 12 import { useNumberedShortcuts } from "@/hooks/use-numbered-shortcuts"; 13 13 import { getColumnIcon } from "@/lib/column"; 14 - import { getStatusLabel } from "@/lib/i18n/domain"; 14 + import { getStatusDisplayLabel } from "@/lib/i18n/domain"; 15 15 import { toast } from "@/lib/toast"; 16 16 import useProjectStore from "@/store/project"; 17 17 import type Task from "@/types/task"; ··· 80 80 > 81 81 {getColumnIcon(status.value, status.isFinal)} 82 82 <span className="text-sm"> 83 - {getStatusLabel(status.value) || status.label} 83 + {getStatusDisplayLabel(status.value, status.label)} 84 84 </span> 85 85 {task.status === status.value ? ( 86 86 <Check className="ml-auto h-4 w-4" />
+17
apps/web/src/lib/i18n/domain.ts
··· 1 1 import i18n from "i18next"; 2 + import { DEFAULT_COLUMNS } from "@/constants/columns"; 2 3 3 4 export function getStatusLabel(status: string) { 4 5 return i18n.t(`tasks:status.${status}`, { 5 6 defaultValue: toDisplayCase(status), 6 7 }); 8 + } 9 + 10 + export function getStatusDisplayLabel(status: string, columnName?: string) { 11 + const defaultName = DEFAULT_COLUMNS.find( 12 + (column) => column.id === status, 13 + )?.name; 14 + 15 + if (columnName) { 16 + if (defaultName && columnName === defaultName) { 17 + return getStatusLabel(status); 18 + } 19 + 20 + return columnName; 21 + } 22 + 23 + return getStatusLabel(status); 7 24 } 8 25 9 26 export function getPriorityLabel(priority: string) {