fix (ui): tables
This commit is contained in:
parent
3fb0216d9b
commit
9ff87b7e20
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -260,6 +260,7 @@ export default function GitIssuesPage() {
|
|||
|
||||
<ForgejoIssuesTable
|
||||
issues={visibleIssues}
|
||||
repositories={repos}
|
||||
isLoading={isLoadingIssues}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue