diff --git a/packages/backend/src/db/queries/issues.ts b/packages/backend/src/db/queries/issues.ts index 36c31cb..36f2806 100644 --- a/packages/backend/src/db/queries/issues.ts +++ b/packages/backend/src/db/queries/issues.ts @@ -41,7 +41,10 @@ export async function deleteIssue(id: number) { return await db.delete(Issue).where(eq(Issue.id, id)); } -export async function updateIssue(id: number, updates: { title?: string; description?: string }) { +export async function updateIssue( + id: number, + updates: { title?: string; description?: string; assigneeId?: number | null }, +) { return await db.update(Issue).set(updates).where(eq(Issue.id, id)).returning(); } diff --git a/packages/backend/src/routes/issue/update.ts b/packages/backend/src/routes/issue/update.ts index 6893585..e4746da 100644 --- a/packages/backend/src/routes/issue/update.ts +++ b/packages/backend/src/routes/issue/update.ts @@ -1,7 +1,8 @@ import type { BunRequest } from "bun"; import { updateIssue } from "../../db/queries"; -// /issue/update?id=1&title=Testing&description=Description +// /issue/update?id=1&title=Testing&description=Description&assigneeId=2 +// assigneeId can be "null" to unassign export default async function issueUpdate(req: BunRequest) { const url = new URL(req.url); const id = url.searchParams.get("id"); @@ -11,13 +12,24 @@ export default async function issueUpdate(req: BunRequest) { const title = url.searchParams.get("title") || undefined; const description = url.searchParams.get("description") || undefined; - if (!title && !description) { + const assigneeIdParam = url.searchParams.get("assigneeId"); + + // Parse assigneeId: "null" means unassign, number means assign, undefined means no change + let assigneeId: number | null | undefined; + if (assigneeIdParam === "null") { + assigneeId = null; + } else if (assigneeIdParam) { + assigneeId = Number(assigneeIdParam); + } + + if (!title && !description && assigneeId === undefined) { return new Response("no updates provided", { status: 400 }); } const issue = await updateIssue(Number(id), { title, description, + assigneeId, }); return Response.json(issue); diff --git a/packages/frontend/src/Index.tsx b/packages/frontend/src/Index.tsx index e075435..0494174 100644 --- a/packages/frontend/src/Index.tsx +++ b/packages/frontend/src/Index.tsx @@ -278,7 +278,7 @@ function Index() { {/* issue detail pane */} - {selectedIssue && ( + {selectedIssue && selectedOrganisation && ( <> setSelectedIssue(null)} + onIssueUpdate={refetchIssues} /> diff --git a/packages/frontend/src/components/issue-detail-pane.tsx b/packages/frontend/src/components/issue-detail-pane.tsx index e390627..e85a641 100644 --- a/packages/frontend/src/components/issue-detail-pane.tsx +++ b/packages/frontend/src/components/issue-detail-pane.tsx @@ -1,18 +1,64 @@ -import type { IssueResponse, ProjectResponse } from "@issue/shared"; +import type { IssueResponse, OrganisationMemberResponse, ProjectResponse } from "@issue/shared"; import { X } from "lucide-react"; +import { useEffect, useState } from "react"; +import Avatar from "@/components/avatar"; import SmallUserDisplay from "@/components/small-user-display"; import { Button } from "@/components/ui/button"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { issue, organisation } from "@/lib/server"; import { issueID } from "@/lib/utils"; export function IssueDetailPane({ project, issueData, + organisationId, close, + onIssueUpdate, }: { project: ProjectResponse; issueData: IssueResponse; + organisationId: number; close: () => void; + onIssueUpdate?: () => void; }) { + const [members, setMembers] = useState([]); + const [assigneeId, setAssigneeId] = useState( + issueData.Issue.assigneeId?.toString() ?? "unassigned", + ); + + useEffect(() => { + setAssigneeId(issueData.Issue.assigneeId?.toString() ?? "unassigned"); + }, [issueData.Issue.assigneeId]); + + useEffect(() => { + organisation.members({ + organisationId, + onSuccess: (data) => { + setMembers(data); + }, + onError: (error) => { + console.error("error fetching members:", error); + }, + }); + }, [organisationId]); + + const handleAssigneeChange = async (value: string) => { + setAssigneeId(value); + const newAssigneeId = value === "unassigned" ? null : Number(value); + + await issue.update({ + issueId: issueData.Issue.id, + assigneeId: newAssigneeId, + onSuccess: () => { + onIssueUpdate?.(); + }, + onError: (error) => { + console.error("error updating assignee:", error); + setAssigneeId(issueData.Issue.assigneeId?.toString() ?? "unassigned"); + }, + }); + }; + return (
@@ -31,12 +77,67 @@ export function IssueDetailPane({

{issueData.Issue.title}

{issueData.Issue.description}

- {issueData.Assignee && ( -
- Assignee: - -
- )} +
+ Assignee: + +
diff --git a/packages/frontend/src/lib/server/issue/index.ts b/packages/frontend/src/lib/server/issue/index.ts index 4ce8fcc..67080ff 100644 --- a/packages/frontend/src/lib/server/issue/index.ts +++ b/packages/frontend/src/lib/server/issue/index.ts @@ -1,2 +1,3 @@ export { byProject } from "@/lib/server/issue/byProject"; export { create } from "@/lib/server/issue/create"; +export { update } from "@/lib/server/issue/update"; diff --git a/packages/frontend/src/lib/server/issue/update.ts b/packages/frontend/src/lib/server/issue/update.ts new file mode 100644 index 0000000..acc698e --- /dev/null +++ b/packages/frontend/src/lib/server/issue/update.ts @@ -0,0 +1,36 @@ +import { getAuthHeaders, getServerURL } from "@/lib/utils"; +import type { ServerQueryInput } from ".."; + +export async function update({ + issueId, + title, + description, + assigneeId, + onSuccess, + onError, +}: { + issueId: number; + title?: string; + description?: string; + assigneeId?: number | null; +} & ServerQueryInput) { + const url = new URL(`${getServerURL()}/issue/update`); + url.searchParams.set("id", `${issueId}`); + if (title !== undefined) url.searchParams.set("title", title); + if (description !== undefined) url.searchParams.set("description", description); + if (assigneeId !== undefined) { + url.searchParams.set("assigneeId", assigneeId === null ? "null" : `${assigneeId}`); + } + + const res = await fetch(url.toString(), { + headers: getAuthHeaders(), + }); + + if (!res.ok) { + const error = await res.text(); + onError?.(error || `failed to update issue (${res.status})`); + } else { + const data = await res.json(); + onSuccess?.(data, res); + } +} diff --git a/todo.md b/todo.md index 552a6bc..f41ff4f 100644 --- a/todo.md +++ b/todo.md @@ -1,8 +1,6 @@ - org settings - sprints - issues - - issue creator - - issue assignee - deadline - comments - status