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.

at main 135 lines 4.9 kB view raw
1import { useNavigate } from "@tanstack/react-router"; 2import { ArrowUpRight } from "lucide-react"; 3import { useTranslation } from "react-i18next"; 4import Activity from "@/components/activity"; 5import CommentInput from "@/components/activity/comment-input"; 6import { isCommentActivity } from "@/components/activity/utils"; 7import { ExternalLinksAccordion } from "@/components/external-links/external-links-accordion"; 8import useAuth from "@/components/providers/auth-provider/hooks/use-auth"; 9import { Timeline } from "@/components/ui/timeline"; 10import useGetActivitiesByTaskId from "@/hooks/queries/activity/use-get-activities-by-task-id"; 11import useExternalLinks from "@/hooks/queries/external-link/use-external-links"; 12import useGetProject from "@/hooks/queries/project/use-get-project"; 13import useGetTask from "@/hooks/queries/task/use-get-task"; 14import useGetTaskRelations from "@/hooks/queries/task-relation/use-get-task-relations"; 15import type { ExternalLink } from "@/types/external-link"; 16import TaskDescription from "./task-description"; 17import TaskRelations from "./task-relations"; 18import TaskSubtasks from "./task-subtasks"; 19import TaskTitle from "./task-title"; 20 21type TaskDetailsContentProps = { 22 taskId: string | undefined; 23 projectId: string; 24 workspaceId: string; 25 className?: string; 26}; 27 28export default function TaskDetailsContent({ 29 taskId, 30 projectId, 31 workspaceId, 32 className, 33}: TaskDetailsContentProps) { 34 const { t } = useTranslation(); 35 const navigate = useNavigate(); 36 const { data: task } = useGetTask(taskId ?? ""); 37 const { data: project } = useGetProject({ id: projectId, workspaceId }); 38 const { data: activities = [] } = useGetActivitiesByTaskId(taskId ?? ""); 39 const { data: externalLinks = [], isLoading: isLoadingExternalLinks } = 40 useExternalLinks(taskId ?? ""); 41 const { data: relations = [] } = useGetTaskRelations(taskId ?? ""); 42 const { user } = useAuth(); 43 44 const parentRelation = relations.find( 45 (rel) => rel.relationType === "subtask" && rel.targetTaskId === taskId, 46 ); 47 const parentTask = parentRelation?.sourceTask; 48 49 if (!taskId) return null; 50 51 return ( 52 <div className={`${className} gap-4`}> 53 <div className="flex flex-col gap-2.5"> 54 {parentTask && ( 55 <button 56 type="button" 57 className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors w-fit" 58 onClick={() => 59 navigate({ 60 to: "/dashboard/workspace/$workspaceId/project/$projectId/task/$taskId", 61 params: { 62 workspaceId, 63 projectId, 64 taskId: parentTask.id, 65 }, 66 }) 67 } 68 > 69 <ArrowUpRight className="size-3" /> 70 <span> 71 {t("tasks:detail.subtaskOf")}{" "} 72 <span className="font-medium">{parentTask.title}</span> 73 </span> 74 </button> 75 )} 76 <p className="text-xs font-semibold text-foreground/70"> 77 {project?.slug}-{task?.number} 78 </p> 79 <TaskTitle taskId={taskId} /> 80 <TaskDescription taskId={taskId} /> 81 </div> 82 {!isLoadingExternalLinks && externalLinks.length > 0 && ( 83 <div className="mt-4"> 84 <ExternalLinksAccordion 85 externalLinks={externalLinks as ExternalLink[]} 86 isLoading={isLoadingExternalLinks} 87 /> 88 </div> 89 )} 90 <div className="mt-4"> 91 <TaskSubtasks 92 taskId={taskId} 93 projectId={projectId} 94 workspaceId={workspaceId} 95 /> 96 </div> 97 <div className="mt-2"> 98 <TaskRelations 99 taskId={taskId} 100 projectId={projectId} 101 workspaceId={workspaceId} 102 /> 103 </div> 104 <span className="text-sm font-medium text-muted-foreground h-[1px] bg-border w-full block shrink-0" /> 105 <div className="flex flex-col gap-4"> 106 <h1 className="text-md font-semibold">{t("tasks:detail.activity")}</h1> 107 {user?.id && taskId && <CommentInput taskId={taskId} />} 108 {activities.length > 0 ? ( 109 <Timeline> 110 {activities.map((activity, index) => { 111 const nextActivity = activities[index + 1]; 112 const showConnector = 113 !isCommentActivity(activity) && 114 Boolean(nextActivity) && 115 !isCommentActivity(nextActivity); 116 117 return ( 118 <Activity 119 key={activity.id} 120 activity={activity} 121 step={activities.length - index} 122 showConnector={showConnector} 123 /> 124 ); 125 })} 126 </Timeline> 127 ) : ( 128 <p className="text-sm font-medium text-muted-foreground"> 129 {t("tasks:detail.noActivity")} 130 </p> 131 )} 132 </div> 133 </div> 134 ); 135}