EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
TabPrograms.jsx
Go to the documentation of this file.
1// TabPrograms.jsx - Installed Programs Tab
2import React, { useState, useEffect } from "react";
3import { apiFetch } from "../../lib/api";
4
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");
12
13 useEffect(() => {
14 if (!agentId) return;
15 fetchPrograms();
16 }, [agentId]);
17
18 const fetchPrograms = async () => {
19 setLoading(true);
20 setError(null);
21 try {
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 : []);
26 } catch (err) {
27 setError(err.message);
28 } finally {
29 setLoading(false);
30 }
31 };
32
33 const handleSort = (field) => {
34 if (sortField === field) {
35 setSortDir(sortDir === "asc" ? "desc" : "asc");
36 } else {
37 setSortField(field);
38 setSortDir("asc");
39 }
40 };
41
42 const filteredPrograms = programs
43 .filter((p) =>
44 (p.name || "").toLowerCase().includes(search.toLowerCase()) ||
45 (p.publisher || "").toLowerCase().includes(search.toLowerCase()) ||
46 (p.version || "").toLowerCase().includes(search.toLowerCase())
47 )
48 .sort((a, b) => {
49 const aVal = a[sortField] || "";
50 const bVal = b[sortField] || "";
51 const cmp = aVal.localeCompare(bVal);
52 return sortDir === "asc" ? cmp : -cmp;
53 });
54
55 if (loading) return <div className="card">Loading installed programs...</div>;
56 if (error) return <div className="card error">{error}</div>;
57
58 return (
59 <div className="tab-container">
60 <div className="tab-header">
61 <h2>Installed Programs ({programs.length})</h2>
62 <button className="btn" onClick={fetchPrograms}>
63 Refresh
64 </button>
65 </div>
66
67 <div className="card">
68 <input
69 type="text"
70 placeholder="Search programs..."
71 value={search}
72 onChange={(e) => setSearch(e.target.value)}
73 className="search-input"
74 style={{ width: "100%", padding: "8px", marginBottom: "16px" }}
75 />
76
77 <div className="table-responsive">
78 <table className="data-table">
79 <thead>
80 <tr>
81 <th onClick={() => handleSort("name")} style={{ cursor: "pointer" }}>
82 Name {sortField === "name" && (sortDir === "asc" ? "▲" : "▼")}
83 </th>
84 <th onClick={() => handleSort("version")} style={{ cursor: "pointer" }}>
85 Version {sortField === "version" && (sortDir === "asc" ? "▲" : "▼")}
86 </th>
87 <th onClick={() => handleSort("publisher")} style={{ cursor: "pointer" }}>
88 Publisher {sortField === "publisher" && (sortDir === "asc" ? "▲" : "▼")}
89 </th>
90 <th onClick={() => handleSort("installDate")} style={{ cursor: "pointer" }}>
91 Install Date {sortField === "installDate" && (sortDir === "asc" ? "▲" : "▼")}
92 </th>
93 <th>Size</th>
94 </tr>
95 </thead>
96 <tbody>
97 {filteredPrograms.length === 0 ? (
98 <tr>
99 <td colSpan="5" style={{ textAlign: "center", padding: "24px" }}>
100 {search ? "No programs match your search" : "No programs found"}
101 </td>
102 </tr>
103 ) : (
104 filteredPrograms.map((prog, idx) => (
105 <tr key={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>
111 </tr>
112 ))
113 )}
114 </tbody>
115 </table>
116 </div>
117 </div>
118 </div>
119 );
120}