1// TabPrograms.jsx - Installed Programs Tab
2import React, { useState, useEffect } from "react";
3import { apiFetch } from "../../lib/api";
5export default function TabPrograms({ agentId }) {
6 const [programs, setPrograms] = useState([]);
7 const [loading, setLoading] = useState(true);
8 const [error, setError] = useState(null);
9 const [search, setSearch] = useState("");
10 const [sortField, setSortField] = useState("name");
11 const [sortDir, setSortDir] = useState("asc");
18 const fetchPrograms = async () => {
22 const res = await apiFetch(`/agent/${agentId}/programs`);
23 if (!res.ok) throw new Error("Failed to fetch programs");
24 const data = await res.json();
25 setPrograms(Array.isArray(data) ? data : []);
27 setError(err.message);
33 const handleSort = (field) => {
34 if (sortField === field) {
35 setSortDir(sortDir === "asc" ? "desc" : "asc");
42 const filteredPrograms = programs
44 (p.name || "").toLowerCase().includes(search.toLowerCase()) ||
45 (p.publisher || "").toLowerCase().includes(search.toLowerCase()) ||
46 (p.version || "").toLowerCase().includes(search.toLowerCase())
49 const aVal = a[sortField] || "";
50 const bVal = b[sortField] || "";
51 const cmp = aVal.localeCompare(bVal);
52 return sortDir === "asc" ? cmp : -cmp;
55 if (loading) return <div className="card">Loading installed programs...</div>;
56 if (error) return <div className="card error">{error}</div>;
59 <div className="tab-container">
60 <div className="tab-header">
61 <h2>Installed Programs ({programs.length})</h2>
62 <button className="btn" onClick={fetchPrograms}>
67 <div className="card">
70 placeholder="Search programs..."
72 onChange={(e) => setSearch(e.target.value)}
73 className="search-input"
74 style={{ width: "100%", padding: "8px", marginBottom: "16px" }}
77 <div className="table-responsive">
78 <table className="data-table">
81 <th onClick={() => handleSort("name")} style={{ cursor: "pointer" }}>
82 Name {sortField === "name" && (sortDir === "asc" ? "▲" : "▼")}
84 <th onClick={() => handleSort("version")} style={{ cursor: "pointer" }}>
85 Version {sortField === "version" && (sortDir === "asc" ? "▲" : "▼")}
87 <th onClick={() => handleSort("publisher")} style={{ cursor: "pointer" }}>
88 Publisher {sortField === "publisher" && (sortDir === "asc" ? "▲" : "▼")}
90 <th onClick={() => handleSort("installDate")} style={{ cursor: "pointer" }}>
91 Install Date {sortField === "installDate" && (sortDir === "asc" ? "▲" : "▼")}
97 {filteredPrograms.length === 0 ? (
99 <td colSpan="5" style={{ textAlign: "center", padding: "24px" }}>
100 {search ? "No programs match your search" : "No programs found"}
104 filteredPrograms.map((prog, idx) => (
106 <td>{prog.name || "—"}</td>
107 <td>{prog.version || "—"}</td>
108 <td>{prog.publisher || "—"}</td>
109 <td>{prog.installDate ? new Date(prog.installDate).toLocaleDateString() : "—"}</td>
110 <td>{prog.size || "—"}</td>