fix (ui): tables
This commit is contained in:
parent
3fb0216d9b
commit
9ff87b7e20
|
|
@ -43,7 +43,10 @@ async def list_issues(
|
||||||
) -> ForgejoIssueListResponse:
|
) -> ForgejoIssueListResponse:
|
||||||
"""List cached issues with optional filters."""
|
"""List cached issues with optional filters."""
|
||||||
# Build query with 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:
|
if repository_id:
|
||||||
try:
|
try:
|
||||||
|
|
@ -75,7 +78,10 @@ async def list_issues(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Count total
|
# 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:
|
if repository_id:
|
||||||
try:
|
try:
|
||||||
repo_uuid = UUID(repository_id)
|
repo_uuid = UUID(repository_id)
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,7 @@ export default function GitIssuesPage() {
|
||||||
|
|
||||||
<ForgejoIssuesTable
|
<ForgejoIssuesTable
|
||||||
issues={visibleIssues}
|
issues={visibleIssues}
|
||||||
|
repositories={repos}
|
||||||
isLoading={isLoadingIssues}
|
isLoading={isLoadingIssues}
|
||||||
onRefresh={handleRefresh}
|
onRefresh={handleRefresh}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useMemo, useState } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type ColumnDef,
|
type ColumnDef,
|
||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
useReactTable,
|
useReactTable,
|
||||||
} from "@tanstack/react-table";
|
} 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 { Badge } from "@/components/ui/badge";
|
||||||
import { DataTable } from "@/components/tables/DataTable";
|
import { DataTable } from "@/components/tables/DataTable";
|
||||||
import { CloseForgejoIssueDialog } from "@/components/git/CloseForgejoIssueDialog";
|
|
||||||
import type { ForgejoIssue, ForgejoIssueLabel } from "@/lib/api-forgejo";
|
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. */
|
/** Normalize a Forgejo label color to a valid 6-char hex string or null. */
|
||||||
function normalizeLabelColor(raw: string | null | undefined): string | null {
|
function normalizeLabelColor(raw: string | null | undefined): string | null {
|
||||||
|
|
@ -54,29 +53,42 @@ function LabelChip({ label }: { label: ForgejoIssueLabel }) {
|
||||||
|
|
||||||
export type ForgejoIssuesTableProps = {
|
export type ForgejoIssuesTableProps = {
|
||||||
issues: ForgejoIssue[];
|
issues: ForgejoIssue[];
|
||||||
|
repositories: ForgejoRepository[];
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
onRefresh: () => void;
|
onRefresh: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ForgejoIssuesTable({
|
export function ForgejoIssuesTable({
|
||||||
issues,
|
issues,
|
||||||
|
repositories,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
onRefresh,
|
onRefresh: _onRefresh,
|
||||||
}: ForgejoIssuesTableProps) {
|
}: ForgejoIssuesTableProps) {
|
||||||
const [closeIssueDialogOpen, setCloseIssueDialogOpen] = useState(false);
|
const repositoryNameById = useMemo(() => {
|
||||||
const [issueToClose, setIssueToClose] = useState<ForgejoIssue | null>(null);
|
const map = new Map<string, string>();
|
||||||
|
for (const repository of repositories) {
|
||||||
const handleCloseClick = useCallback((issue: ForgejoIssue) => {
|
map.set(
|
||||||
setIssueToClose(issue);
|
repository.id,
|
||||||
setCloseIssueDialogOpen(true);
|
repository.display_name || `${repository.owner}/${repository.repo}`,
|
||||||
}, []);
|
);
|
||||||
|
}
|
||||||
const handleCloseSuccess = () => {
|
return map;
|
||||||
onRefresh();
|
}, [repositories]);
|
||||||
};
|
|
||||||
|
|
||||||
const columns: ColumnDef<ForgejoIssue>[] = useMemo(
|
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",
|
accessorKey: "forgejo_issue_number",
|
||||||
header: "#",
|
header: "#",
|
||||||
|
|
@ -104,20 +116,6 @@ export function ForgejoIssuesTable({
|
||||||
</div>
|
</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",
|
accessorKey: "state",
|
||||||
header: "State",
|
header: "State",
|
||||||
|
|
@ -153,13 +151,24 @@ export function ForgejoIssuesTable({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "author",
|
id: "assignee",
|
||||||
header: "Author",
|
header: "Assignee",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => {
|
||||||
<span className="block max-w-[160px] truncate text-muted">
|
const assignee = row.original.assignees?.[0];
|
||||||
{row.original.author || "Unknown"}
|
const login =
|
||||||
</span>
|
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",
|
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({
|
const table = useReactTable({
|
||||||
data: issues,
|
data: issues,
|
||||||
|
|
@ -215,7 +205,7 @@ export function ForgejoIssuesTable({
|
||||||
table={table}
|
table={table}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
loadingLabel="Loading Git Project issues…"
|
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={{
|
emptyState={{
|
||||||
icon: <CircleDot className="h-12 w-12" />,
|
icon: <CircleDot className="h-12 w-12" />,
|
||||||
title: "No Git Project issues found",
|
title: "No Git Project issues found",
|
||||||
|
|
@ -224,12 +214,6 @@ export function ForgejoIssuesTable({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<CloseForgejoIssueDialog
|
|
||||||
issue={issueToClose}
|
|
||||||
open={closeIssueDialogOpen}
|
|
||||||
onOpenChange={setCloseIssueDialogOpen}
|
|
||||||
onCloseSuccess={handleCloseSuccess}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue