feat: updated project dashboard with tracker list and recent tickets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
eb8908e434
commit
e98f7d59de
4 changed files with 69 additions and 8 deletions
|
|
@ -82,7 +82,7 @@ fn load_or_generate_key(path: &std::path::Path) -> Result<[u8; 32], Box<dyn std:
|
|||
} else {
|
||||
let mut key = [0u8; 32];
|
||||
rand::rngs::OsRng.fill_bytes(&mut key);
|
||||
std::fs::write(path, &key)?;
|
||||
std::fs::write(path, key)?;
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ fn from_row(row: &rusqlite::Row) -> rusqlite::Result<ProcessedTicket> {
|
|||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
const SELECT_ALL_COLS: &str = "SELECT id, tracker_id, artifact_id, artifact_title, artifact_data, \
|
||||
status, analyst_report, developer_report, worktree_path, branch_name, \
|
||||
detected_at, processed_at FROM processed_tickets";
|
||||
|
|
@ -91,6 +92,7 @@ impl ProcessedTicket {
|
|||
Ok(count > 0)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn list_by_tracker(conn: &Connection, tracker_id: &str) -> Result<Vec<ProcessedTicket>> {
|
||||
let sql = format!(
|
||||
"{} WHERE tracker_id = ?1 ORDER BY detected_at DESC",
|
||||
|
|
@ -115,6 +117,7 @@ impl ProcessedTicket {
|
|||
rows.collect()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_by_id(conn: &Connection, id: &str) -> Result<ProcessedTicket> {
|
||||
let sql = format!("{} WHERE id = ?1", SELECT_ALL_COLS);
|
||||
conn.query_row(&sql, params![id], from_row)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ impl TuleapClient {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn test_connection(&self) -> Result<(), String> {
|
||||
let url = format!("{}/api/projects?limit=1", self.base_url);
|
||||
let resp = self
|
||||
|
|
|
|||
|
|
@ -1,17 +1,30 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useParams, Link, useNavigate } from "react-router-dom";
|
||||
import { getProject, deleteProject } from "../../lib/api";
|
||||
import type { Project } from "../../lib/types";
|
||||
import { getProject, deleteProject, listTrackers, listProcessedTickets } from "../../lib/api";
|
||||
import type { Project, WatchedTracker, ProcessedTicket } from "../../lib/types";
|
||||
import TrackerList from "../trackers/TrackerList";
|
||||
|
||||
export default function ProjectDashboard() {
|
||||
const { projectId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [project, setProject] = useState<Project | null>(null);
|
||||
const [trackers, setTrackers] = useState<WatchedTracker[]>([]);
|
||||
const [tickets, setTickets] = useState<ProcessedTicket[]>([]);
|
||||
|
||||
async function loadData() {
|
||||
if (!projectId) return;
|
||||
const [proj, trks, tkts] = await Promise.all([
|
||||
getProject(projectId),
|
||||
listTrackers(projectId),
|
||||
listProcessedTickets(projectId),
|
||||
]);
|
||||
setProject(proj);
|
||||
setTrackers(trks);
|
||||
setTickets(tkts);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (projectId) {
|
||||
getProject(projectId).then(setProject);
|
||||
}
|
||||
loadData();
|
||||
}, [projectId]);
|
||||
|
||||
async function handleDelete() {
|
||||
|
|
@ -23,10 +36,25 @@ export default function ProjectDashboard() {
|
|||
navigate("/");
|
||||
}
|
||||
|
||||
function statusBadgeClass(status: string): string {
|
||||
switch (status) {
|
||||
case "Pending":
|
||||
return "bg-yellow-100 text-yellow-700";
|
||||
case "Done":
|
||||
return "bg-green-100 text-green-700";
|
||||
case "Error":
|
||||
return "bg-red-100 text-red-700";
|
||||
default:
|
||||
return "bg-blue-100 text-blue-700";
|
||||
}
|
||||
}
|
||||
|
||||
if (!project) {
|
||||
return <div className="p-8 text-gray-400">Loading...</div>;
|
||||
}
|
||||
|
||||
const recentTickets = tickets.slice(-10).reverse();
|
||||
|
||||
return (
|
||||
<div className="p-8">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
|
|
@ -68,8 +96,37 @@ export default function ProjectDashboard() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 text-gray-400 text-sm">
|
||||
Tracker surveillance and ticket processing will be available in the next update.
|
||||
<div className="mt-8">
|
||||
<h3 className="text-lg font-semibold mb-4">Watched Trackers</h3>
|
||||
<TrackerList trackers={trackers} projectId={project.id} onRefresh={loadData} />
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<h3 className="text-lg font-semibold mb-4">Recent Tickets</h3>
|
||||
{recentTickets.length === 0 ? (
|
||||
<div className="text-sm text-gray-400">No tickets processed yet.</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{recentTickets.map((ticket) => (
|
||||
<div
|
||||
key={ticket.id}
|
||||
className="bg-white rounded-lg border border-gray-200 p-4 flex items-center justify-between gap-4"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-gray-400 font-mono">#{ticket.artifact_id}</span>
|
||||
<span className="text-sm font-medium truncate">{ticket.artifact_title}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
className={`text-xs px-2 py-0.5 rounded-full font-medium shrink-0 ${statusBadgeClass(ticket.status)}`}
|
||||
>
|
||||
{ticket.status}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue