fix (ui): tables

This commit is contained in:
null 2026-05-20 03:09:22 -05:00
parent 3fb0216d9b
commit 9ff87b7e20
3 changed files with 57 additions and 66 deletions

View File

@ -43,7 +43,10 @@ async def list_issues(
) -> ForgejoIssueListResponse:
"""List cached issues with optional filters."""
# Build query with filters
statement = select(ForgejoIssue).where(ForgejoIssue.organization_id == ctx.organization.id)
statement = select(ForgejoIssue).where(
ForgejoIssue.organization_id == ctx.organization.id,
ForgejoIssue.is_pull_request.is_(False),
)
if repository_id:
try:
@ -75,7 +78,10 @@ async def list_issues(
)
# Count total
total_statement = select(func.count()).select_from(ForgejoIssue).where(ForgejoIssue.organization_id == ctx.organization.id)
total_statement = select(func.count()).select_from(ForgejoIssue).where(
ForgejoIssue.organization_id == ctx.organization.id,
ForgejoIssue.is_pull_request.is_(False),
)
if repository_id:
try:
repo_uuid = UUID(repository_id)

View File

@ -260,6 +260,7 @@ export default function GitIssuesPage() {
<ForgejoIssuesTable
issues={visibleIssues}
repositories={repos}
isLoading={isLoadingIssues}
onRefresh={handleRefresh}
/>

View File

@ -1,19 +1,18 @@
"use client";
import { useCallback, useMemo, useState } from "react";
import { useMemo } from "react";
import {
type ColumnDef,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
import { CircleDot, ExternalLink, XCircle } from "lucide-react";
import { CircleDot, ExternalLink } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { DataTable } from "@/components/tables/DataTable";
import { CloseForgejoIssueDialog } from "@/components/git/CloseForgejoIssueDialog";
import type { ForgejoIssue, ForgejoIssueLabel } from "@/lib/api-forgejo";
import type { ForgejoRepository } from "@/lib/api-forgejo";
/** Normalize a Forgejo label color to a valid 6-char hex string or null. */
function normalizeLabelColor(raw: string | null | undefined): string | null {
@ -54,29 +53,42 @@ function LabelChip({ label }: { label: ForgejoIssueLabel }) {
export type ForgejoIssuesTableProps = {
issues: ForgejoIssue[];
repositories: ForgejoRepository[];
isLoading?: boolean;
onRefresh: () => void;
};
export function ForgejoIssuesTable({
issues,
repositories,
isLoading = false,
onRefresh,
onRefresh: _onRefresh,
}: ForgejoIssuesTableProps) {
const [closeIssueDialogOpen, setCloseIssueDialogOpen] = useState(false);
const [issueToClose, setIssueToClose] = useState<ForgejoIssue | null>(null);
const handleCloseClick = useCallback((issue: ForgejoIssue) => {
setIssueToClose(issue);
setCloseIssueDialogOpen(true);
}, []);
const handleCloseSuccess = () => {
onRefresh();
};
const repositoryNameById = useMemo(() => {
const map = new Map<string, string>();
for (const repository of repositories) {
map.set(
repository.id,
repository.display_name || `${repository.owner}/${repository.repo}`,
);
}
return map;
}, [repositories]);
const columns: ColumnDef<ForgejoIssue>[] = useMemo(
() => [
{
id: "repository",
header: "Repository",
cell: ({ row }) => {
const fallback = row.original.repository_id;
return (
<span className="block max-w-[220px] truncate text-muted">
{repositoryNameById.get(row.original.repository_id) ?? fallback}
</span>
);
},
},
{
accessorKey: "forgejo_issue_number",
header: "#",
@ -104,20 +116,6 @@ export function ForgejoIssuesTable({
</div>
),
},
{
accessorKey: "body_preview",
header: "Description",
cell: ({ row }) => {
const body = row.original.body_preview;
if (!body) return null;
const truncated = body.length > 120 ? body.slice(0, 120) + "…" : body;
return (
<div className="max-w-xs truncate text-sm text-muted" title={body}>
{truncated}
</div>
);
},
},
{
accessorKey: "state",
header: "State",
@ -153,13 +151,24 @@ export function ForgejoIssuesTable({
},
},
{
accessorKey: "author",
header: "Author",
cell: ({ row }) => (
<span className="block max-w-[160px] truncate text-muted">
{row.original.author || "Unknown"}
</span>
),
id: "assignee",
header: "Assignee",
cell: ({ row }) => {
const assignee = row.original.assignees?.[0];
const login =
assignee &&
typeof assignee === "object" &&
"login" in assignee &&
typeof assignee.login === "string"
? assignee.login
: null;
return (
<span className="block max-w-[160px] truncate text-muted">
{login || "Unassigned"}
</span>
);
},
},
{
accessorKey: "forgejo_updated_at",
@ -180,27 +189,8 @@ export function ForgejoIssuesTable({
}
},
},
{
id: "actions",
header: "Actions",
cell: ({ row }) => {
const issue = row.original;
if (issue.state !== "open" || issue.is_pull_request) return null;
return (
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0 text-[color:var(--danger)] hover:bg-[color:rgba(248,113,113,0.08)]"
onClick={() => handleCloseClick(issue)}
title="Close issue"
>
<XCircle className="h-4 w-4" />
</Button>
);
},
},
],
[handleCloseClick],
[repositoryNameById],
);
const table = useReactTable({
data: issues,
@ -215,7 +205,7 @@ export function ForgejoIssuesTable({
table={table}
isLoading={isLoading}
loadingLabel="Loading Git Project issues…"
tableClassName="min-w-[960px] w-full text-left text-sm"
tableClassName="min-w-[980px] w-full text-left text-sm"
emptyState={{
icon: <CircleDot className="h-12 w-12" />,
title: "No Git Project issues found",
@ -224,12 +214,6 @@ export function ForgejoIssuesTable({
}}
/>
</div>
<CloseForgejoIssueDialog
issue={issueToClose}
open={closeIssueDialogOpen}
onOpenChange={setCloseIssueDialogOpen}
onCloseSuccess={handleCloseSuccess}
/>
</>
);
}