diff --git a/platform/lib/translations.ts b/platform/lib/translations.ts index a969fc6..0dcb52b 100644 --- a/platform/lib/translations.ts +++ b/platform/lib/translations.ts @@ -452,6 +452,9 @@ export const translations = { repoList: { empty: "No repositories yet.", searchPlaceholder: "Search repositories...", + human: "Human", + ai: "AI", + sortByAi: "Most AI first", }, }, investHere: { @@ -1868,6 +1871,9 @@ export const translations = { repoList: { empty: "Nenhum repositório ainda.", searchPlaceholder: "Buscar repositórios...", + human: "Humano", + ai: "IA", + sortByAi: "Mais IA primeiro", }, }, investHere: { diff --git a/platform/src/app/[tenant]/dashboard/repo-list.tsx b/platform/src/app/[tenant]/dashboard/repo-list.tsx index 7d53c68..e91f682 100644 --- a/platform/src/app/[tenant]/dashboard/repo-list.tsx +++ b/platform/src/app/[tenant]/dashboard/repo-list.tsx @@ -1,19 +1,17 @@ -'use client'; +"use client"; -import { useState } from 'react'; +import { useState } from "react"; -import Link from 'next/link'; +import Link from "next/link"; -import { ArrowUp, ArrowDown, Search, Trash2 } from 'lucide-react'; - -import { Sparkline } from '@/components/charts/Sparkline'; -import { DeleteRepositoryDialog } from '@/components/repos/DeleteRepositoryDialog'; -import { Button } from '@/components/ui/button'; -import { useTranslation } from '@/hooks/useTranslation'; -import { cn } from '@/lib/utils'; -import type { RepoSummary } from '@/types/temporal'; -import { healthIndicator } from '@/types/temporal'; +import { ArrowDownWideNarrow, Search, Trash2 } from "lucide-react"; +import { DeleteRepositoryDialog } from "@/components/repos/DeleteRepositoryDialog"; +import { Button } from "@/components/ui/button"; +import { useTranslation } from "@/hooks/useTranslation"; +import { cn } from "@/lib/utils"; +import type { RepoSummary } from "@/types/temporal"; +import { healthIndicator } from "@/types/temporal"; interface RepoListProps { repos: RepoSummary[]; @@ -24,10 +22,10 @@ interface RepoListProps { } const healthColors: Record = { - green: 'bg-signal-purple', - yellow: 'bg-signal-yellow', - red: 'bg-signal-red', - gray: 'bg-signal-gray', + green: "bg-signal-purple", + yellow: "bg-signal-yellow", + red: "bg-signal-red", + gray: "bg-signal-gray", }; export function RepoList({ @@ -38,15 +36,18 @@ export function RepoList({ showSearch = false, }: RepoListProps) { const { t } = useTranslation(); - const [query, setQuery] = useState(''); + const [query, setQuery] = useState(""); + const [sortByAi, setSortByAi] = useState(false); const showDeleteColumn = canDelete && !!organizationId; if (repos.length === 0) { return (
-

{t('dashboard.repoList.empty')}

+

{t("dashboard.repoList.empty")}

); @@ -56,25 +57,45 @@ export function RepoList({ ? repos.filter((r) => r.name.toLowerCase().includes(query.toLowerCase())) : repos; + // Repos without AI data sort to the end regardless of direction. + const sorted = sortByAi + ? [...filtered].sort( + (a, b) => + (b.ai_detection_coverage_pct ?? -1) - + (a.ai_detection_coverage_pct ?? -1), + ) + : filtered; + return (
{showSearch && repos.length > 5 && ( -
- - setQuery(e.target.value)} - className="w-full rounded-md border border-border bg-card py-2 pl-9 pr-3 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring" - /> +
+
+ + setQuery(e.target.value)} + className="w-full rounded-md border border-border bg-card py-2 pl-9 pr-3 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring" + /> +
+
)} - {filtered.map((repo) => { + {sorted.map((repo) => { const color = healthIndicator(repo.health); - const hasDelta = - repo.stabilization_delta !== null && repo.stabilization_delta !== 0; - const deltaUp = hasDelta && repo.stabilization_delta! > 0; + const aiPct = repo.ai_detection_coverage_pct; + const humanPct = aiPct != null ? 100 - aiPct : null; return (
{/* Health dot */}
-

{repo.name}

+

+ {repo.name} +

{repo.runs_count} runs {repo.last_run_at && @@ -107,42 +130,20 @@ export function RepoList({

-
- {/* Sparkline — hidden on very narrow screens to prevent crowding */} -
- -
- - {/* AI adoption badge — fixed width to keep alignment */} - - {repo.ai_detection_coverage_pct != null && repo.ai_detection_coverage_pct > 0 - ? `AI ${repo.ai_detection_coverage_pct < 10 - ? repo.ai_detection_coverage_pct.toFixed(1) - : repo.ai_detection_coverage_pct.toFixed(0)}%` - : ''} - - - {/* Stabilization value + delta */} -
- - {repo.stabilization_ratio !== null - ? `${(repo.stabilization_ratio * 100).toFixed(0)}%` - : '\u2014'} - - - {deltaUp ? ( - - ) : ( - - )} - -
+ {/* Commit mix — share of commits by origin */} +
+ {humanPct !== null && aiPct !== null ? ( + <> + + {t("dashboard.repoList.human")} {humanPct.toFixed(0)}% + + + {t("dashboard.repoList.ai")} {aiPct.toFixed(0)}% + + + ) : ( + {"—"} + )}
@@ -157,7 +158,7 @@ export function RepoList({ variant="ghost" size="icon" className="size-8 text-muted-foreground hover:text-destructive" - aria-label={t('repos.deleteButton')} + aria-label={t("repos.deleteButton")} >