feat(tasks): offene Git-Issues als Aufgaben uebernehmen
Ein Klick im Git-Panel macht aus den offenen Issues Aufgaben - mit Titel, Labels und einem Link zurueck ins Git-System. Grundlage ist der zuletzt geholte Snapshot. Hier wird bewusst nichts nachgeladen: ein Klick soll nicht ungefragt fremde APIs ansprechen und womoeglich in ein Rate-Limit laufen. Wer den neuesten Stand will, gleicht vorher ab; ohne Snapshot sagt der Server das statt still nichts zu tun. Die Issue-Nummer bleibt an der Aufgabe und ist je Projekt eindeutig. Ein zweiter Klick legt deshalb keine Dubletten an, auch wenn die Aufgabe inzwischen verschoben oder umbenannt wurde. NULL gilt in Postgres als verschieden, von Hand angelegte Aufgaben stoeren die Eindeutigkeit also nicht. Nachgewiesen an vercel/swr: 15 offene Issues uebernommen, beim zweiten Aufruf 0 angelegt und 15 uebersprungen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
GitMerge,
|
||||
GitPullRequest,
|
||||
HardDrive,
|
||||
ListPlus,
|
||||
Loader2,
|
||||
Lock,
|
||||
Package,
|
||||
@@ -48,8 +49,36 @@ export function GitPanel({ project }: { project: ProjectDetail }) {
|
||||
const router = useRouter();
|
||||
const [tab, setTab] = useState<TabKey>("pulls");
|
||||
const [syncing, setSyncing] = useState(false);
|
||||
const [importing, setImporting] = useState(false);
|
||||
const [message, setMessage] = useState<{ ok: boolean; text: string } | null>(null);
|
||||
|
||||
/** Offene Issues als Aufgaben anlegen – ohne erneuten Abruf beim Provider. */
|
||||
async function importIssues() {
|
||||
setImporting(true);
|
||||
setMessage(null);
|
||||
try {
|
||||
const response = await fetch(`/api/projects/${project.id}/tasks/from-issues`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: "{}",
|
||||
});
|
||||
const body = await response.json().catch(() => ({}));
|
||||
if (!response.ok) throw new Error(body.error ?? "Uebernahme fehlgeschlagen.");
|
||||
setMessage({
|
||||
ok: true,
|
||||
text:
|
||||
body.created === 0
|
||||
? `Nichts Neues – alle ${body.skipped} Issues sind bereits als Aufgabe vorhanden.`
|
||||
: `${body.created} Aufgabe(n) angelegt${body.skipped ? `, ${body.skipped} uebersprungen` : ""}.`,
|
||||
});
|
||||
router.refresh();
|
||||
} catch (caught) {
|
||||
setMessage({ ok: false, text: caught instanceof Error ? caught.message : String(caught) });
|
||||
} finally {
|
||||
setImporting(false);
|
||||
}
|
||||
}
|
||||
|
||||
const snapshot = project.snapshot;
|
||||
const branches = asArray<BranchInfo>(snapshot?.branches);
|
||||
const tags = asArray<TagInfo>(snapshot?.tags);
|
||||
@@ -329,7 +358,30 @@ export function GitPanel({ project }: { project: ProjectDetail }) {
|
||||
/>
|
||||
) : null}
|
||||
{tab === "issues" ? (
|
||||
<TicketList tickets={issues} empty="Keine offenen Issues." icon={CircleDot} />
|
||||
<>
|
||||
{issues.length > 0 ? (
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-white/6 bg-white/2 px-4 py-2">
|
||||
<p className="text-[11px] text-slate-500">
|
||||
Aus offenen Issues Aufgaben machen. Bereits uebernommene werden
|
||||
uebersprungen.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost px-2.5 py-1 text-[11px]"
|
||||
onClick={importIssues}
|
||||
disabled={importing}
|
||||
>
|
||||
{importing ? (
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
) : (
|
||||
<ListPlus className="h-3 w-3" />
|
||||
)}
|
||||
Als Aufgaben uebernehmen
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
<TicketList tickets={issues} empty="Keine offenen Issues." icon={CircleDot} />
|
||||
</>
|
||||
) : null}
|
||||
{tab === "branches" ? <BranchList branches={branches} /> : null}
|
||||
{tab === "tags" ? <TagList tags={tags} /> : null}
|
||||
|
||||
Reference in New Issue
Block a user