,
+ getForgejoIssues({ board_id: boardId, state: "open", limit: 50 }),
]);
if (cancelled) return;
const links = Array.isArray(linksResult)
@@ -70,7 +71,7 @@ export function BoardForgejoIssuesPanel({
board_id: boardId,
state: "open",
limit: 50,
- }) as ForgejoIssueListResponse;
+ });
setIssues(issuesResult.items);
} catch (err) {
setError(
@@ -124,6 +125,7 @@ export function BoardForgejoIssuesPanel({
issues={issues}
repositories={repositories}
isLoading={isLoading}
+ canClose={canClose}
onRefresh={handleRefresh}
/>
)}
diff --git a/frontend/src/components/git/CloseForgejoIssueDialog.tsx b/frontend/src/components/git/CloseForgejoIssueDialog.tsx
index ac77264..ffea045 100644
--- a/frontend/src/components/git/CloseForgejoIssueDialog.tsx
+++ b/frontend/src/components/git/CloseForgejoIssueDialog.tsx
@@ -16,6 +16,7 @@ import { closeForgejoIssue } from "@/lib/api-forgejo";
type CloseForgejoIssueDialogProps = {
issue: ForgejoIssue | null;
+ repositoryName: string;
open: boolean;
onOpenChange: (open: boolean) => void;
onCloseSuccess: () => void;
@@ -23,6 +24,7 @@ type CloseForgejoIssueDialogProps = {
export function CloseForgejoIssueDialog({
issue,
+ repositoryName,
open,
onOpenChange,
onCloseSuccess,
@@ -54,12 +56,12 @@ export function CloseForgejoIssueDialog({
Close Git Project issue
- Pipeline will mark issue{" "}
+ Confirm closing{" "}
- #{issue.forgejo_issue_number}
- {" "}
- as closed in the connected Git provider and refresh the local issue
- cache.
+ {repositoryName}#{issue.forgejo_issue_number}
+
+ . Pipeline will close it in the connected Git provider and refresh
+ the local issue cache.
diff --git a/frontend/src/components/git/ForgejoIssuesTable.tsx b/frontend/src/components/git/ForgejoIssuesTable.tsx
index 96c2357..5fdd22c 100644
--- a/frontend/src/components/git/ForgejoIssuesTable.tsx
+++ b/frontend/src/components/git/ForgejoIssuesTable.tsx
@@ -1,18 +1,20 @@
"use client";
-import { useMemo } from "react";
+import { useMemo, useState } from "react";
import {
type ColumnDef,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
-import { CircleDot, ExternalLink } from "lucide-react";
+import { CircleDot, ExternalLink, XCircle } from "lucide-react";
+import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { DataTable } from "@/components/tables/DataTable";
import type { ForgejoIssue, ForgejoIssueLabel } from "@/lib/api-forgejo";
import type { ForgejoRepository } from "@/lib/api-forgejo";
+import { CloseForgejoIssueDialog } from "@/components/git/CloseForgejoIssueDialog";
/** Normalize a Forgejo label color to a valid 6-char hex string or null. */
function normalizeLabelColor(raw: string | null | undefined): string | null {
@@ -55,6 +57,7 @@ export type ForgejoIssuesTableProps = {
issues: ForgejoIssue[];
repositories: ForgejoRepository[];
isLoading?: boolean;
+ canClose?: boolean;
onRefresh: () => void;
};
@@ -62,8 +65,12 @@ export function ForgejoIssuesTable({
issues,
repositories,
isLoading = false,
- onRefresh: _onRefresh,
+ canClose = false,
+ onRefresh,
}: ForgejoIssuesTableProps) {
+ const [issueToClose, setIssueToClose] = useState(null);
+ const [isCloseDialogOpen, setIsCloseDialogOpen] = useState(false);
+
const repositoryNameById = useMemo(() => {
const map = new Map();
for (const repository of repositories) {
@@ -74,7 +81,6 @@ export function ForgejoIssuesTable({
}
return map;
}, [repositories]);
-
const columns: ColumnDef[] = useMemo(
() => [
{
@@ -189,8 +195,37 @@ export function ForgejoIssuesTable({
}
},
},
+ {
+ id: "actions",
+ header: "Actions",
+ cell: ({ row }) => {
+ const issue = row.original;
+ const canShowClose =
+ canClose && issue.state === "open" && !issue.is_pull_request;
+ if (!canShowClose) {
+ return null;
+ }
+ const repositoryName =
+ repositoryNameById.get(issue.repository_id) ?? issue.repository_id;
+ return (
+
+ );
+ },
+ },
],
- [repositoryNameById],
+ [canClose, repositoryNameById],
);
const table = useReactTable({
data: issues,
@@ -214,6 +249,23 @@ export function ForgejoIssuesTable({
}}
/>
+ {
+ setIsCloseDialogOpen(nextOpen);
+ if (!nextOpen) {
+ setIssueToClose(null);
+ }
+ }}
+ onCloseSuccess={onRefresh}
+ />
>
);
}