import type { IssueResponse, ProjectResponse, SprintRecord, UserRecord } from "@issue/shared"; import { Check, Link, Trash, X } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { toast } from "sonner"; import { useSession } from "@/components/session-provider"; import SmallUserDisplay from "@/components/small-user-display"; import { StatusSelect } from "@/components/status-select"; import StatusTag from "@/components/status-tag"; import { TimerDisplay } from "@/components/timer-display"; import { TimerModal } from "@/components/timer-modal"; import { Button } from "@/components/ui/button"; import { ConfirmDialog } from "@/components/ui/confirm-dialog"; import { SelectTrigger } from "@/components/ui/select"; import { UserSelect } from "@/components/user-select"; import { issue } from "@/lib/server"; import { issueID } from "@/lib/utils"; import SmallSprintDisplay from "./small-sprint-display"; import { SprintSelect } from "./sprint-select"; export function IssueDetailPane({ project, sprints, issueData, members, statuses, close, onIssueUpdate, onIssueDelete, }: { project: ProjectResponse; sprints: SprintRecord[]; issueData: IssueResponse; members: UserRecord[]; statuses: Record; close: () => void; onIssueUpdate?: () => void; onIssueDelete?: (issueId: number) => void | Promise; }) { const { user } = useSession(); const [assigneeId, setAssigneeId] = useState( issueData.Issue.assigneeId?.toString() ?? "unassigned", ); const [sprintId, setSprintId] = useState(issueData.Issue.sprintId?.toString() ?? "unassigned"); const [status, setStatus] = useState(issueData.Issue.status); const [deleteOpen, setDeleteOpen] = useState(false); const [linkCopied, setLinkCopied] = useState(false); const copyTimeoutRef = useRef(null); useEffect(() => { setSprintId(issueData.Issue.sprintId?.toString() ?? "unassigned"); setAssigneeId(issueData.Issue.assigneeId?.toString() ?? "unassigned"); setStatus(issueData.Issue.status); }, [issueData.Issue.sprintId, issueData.Issue.assigneeId, issueData.Issue.status]); useEffect(() => { return () => { if (copyTimeoutRef.current) { window.clearTimeout(copyTimeoutRef.current); } }; }, []); const handleSprintChange = async (value: string) => { setSprintId(value); const newSprintId = value === "unassigned" ? null : Number(value); await issue.update({ issueId: issueData.Issue.id, sprintId: newSprintId, onSuccess: () => { onIssueUpdate?.(); toast.success( <> Successfully updated sprint to{" "} {value === "unassigned" ? ( "Unassigned" ) : ( s.id === newSprintId)} /> )}{" "} for {issueID(project.Project.key, issueData.Issue.number)} , { dismissible: false, }, ); }, onError: (error) => { console.error("error updating sprint:", error); setSprintId(issueData.Issue.sprintId?.toString() ?? "unassigned"); toast.error( <> Error updating sprint to{" "} {value === "unassigned" ? ( "Unassigned" ) : ( s.id === newSprintId)} /> )}{" "} for {issueID(project.Project.key, issueData.Issue.number)} , { dismissible: false, }, ); }, }); }; const handleAssigneeChange = async (value: string) => { setAssigneeId(value); const newAssigneeId = value === "unassigned" ? null : Number(value); await issue.update({ issueId: issueData.Issue.id, assigneeId: newAssigneeId, onSuccess: () => { const user = members.find((member) => member.id === newAssigneeId); toast.success(
Assigned {user ? : "unknown"}{" "} to {issueID(project.Project.key, issueData.Issue.number)}
, { dismissible: false, }, ); onIssueUpdate?.(); }, onError: (error) => { console.error("error updating assignee:", error); setAssigneeId(issueData.Issue.assigneeId?.toString() ?? "unassigned"); toast.error(`Error updating assignee: ${error}`, { dismissible: false, }); }, }); }; const handleStatusChange = async (value: string) => { setStatus(value); await issue.update({ issueId: issueData.Issue.id, status: value, onSuccess: () => { toast.success( <> {issueID(project.Project.key, issueData.Issue.number)}'s status updated to{" "} , { dismissible: false }, ); onIssueUpdate?.(); }, onError: (error) => { console.error("error updating status:", error); setStatus(issueData.Issue.status); toast.error(`Error updating status: ${error}`, { dismissible: false, }); }, }); }; const handleDelete = () => { setDeleteOpen(true); }; const handleCopyLink = async () => { try { await navigator.clipboard.writeText(window.location.href); setLinkCopied(true); if (copyTimeoutRef.current) { window.clearTimeout(copyTimeoutRef.current); } copyTimeoutRef.current = window.setTimeout(() => { setLinkCopied(false); copyTimeoutRef.current = null; }, 1500); } catch (error) { console.error("error copying issue link:", error); } }; const handleConfirmDelete = async () => { await issue.delete({ issueId: issueData.Issue.id, onSuccess: async () => { await onIssueDelete?.(issueData.Issue.id); toast.success(`Deleted issue ${issueID(project.Project.key, issueData.Issue.number)}`, { dismissible: false, }); }, onError: (error) => { console.error( `error deleting issue ${issueID(project.Project.key, issueData.Issue.number)}`, error, ); toast.error( `Error deleting issue ${issueID(project.Project.key, issueData.Issue.number)}: ${error}`, { dismissible: false, }, ); }, }); setDeleteOpen(false); }; return (

{issueID(project.Project.key, issueData.Issue.number)}

( )} />
{issueData.Issue.title}
{issueData.Issue.description !== "" && (

{issueData.Issue.description}

)}
Sprint:
Assignee:
Created by:
{user?.id === Number(assigneeId) && }
); }