This commit is contained in:
CanbiZ 2025-02-07 16:43:44 +01:00
parent 3cc7a8ceea
commit f8c308679e
2 changed files with 164 additions and 121 deletions

View File

@ -33,8 +33,8 @@ const DataFetcher: React.FC = () => {
const [searchQuery, setSearchQuery] = useState("");
const [startDate, setStartDate] = useState<Date | null>(null);
const [endDate, setEndDate] = useState<Date | null>(null);
const [sortColumn, setSortColumn] = useState<string | null>(null);
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc");
const [sortColumn, setSortColumn] = useState<string | null>("created_at");
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("desc");
const [installingCounts, setInstallingCounts] = useState(0);
const [failedCounts, setFailedCounts] = useState(0);
const [doneCounts, setDoneCounts] = useState(0);
@ -54,6 +54,13 @@ const DataFetcher: React.FC = () => {
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${day}.${month}.${year} ${hours}:${minutes}`;
};
const filteredCounts = {
installing: filteredData.filter(item => item.status === "installing").length,
done: filteredData.filter(item => item.status === "done").length,
failed: filteredData.filter(item => item.status === "failed").length,
unknown: filteredData.filter(item => !["installing", "done", "failed"].includes(item.status)).length,
};
const toggleSort = (column: string) => {
if (sortColumn === column) {
@ -67,27 +74,11 @@ const DataFetcher: React.FC = () => {
const renderSortIcon = (column: string) => {
if (sortColumn !== column) return <span className="text-gray-400"></span>;
return sortDirection === "asc" ? (
<span className="text-red-500"></span>
<span className="text-gray-400"></span>
) : (
<span className="text-red-500"></span>
<span className="text-gray-400"></span>
);
};
const sortData = (data: DataModel[]) => {
if (!sortColumn) return data;
return [...data].sort((a, b) => {
const valA = a[sortColumn as keyof DataModel];
const valB = b[sortColumn as keyof DataModel];
if (typeof valA === "number" && typeof valB === "number") {
return sortDirection === "asc" ? valA - valB : valB - valA;
}
if (typeof valA === "string" && typeof valB === "string") {
return sortDirection === "asc" ? valA.localeCompare(valB) : valB.localeCompare(valA);
}
return 0;
});
};
useEffect(() => {
const fetchData = async () => {
@ -201,6 +192,12 @@ const DataFetcher: React.FC = () => {
});
});
});
const filteredCounts = {
installing: filteredData.filter(item => item.status === "installing").length,
done: filteredData.filter(item => item.status === "done").length,
failed: filteredData.filter(item => item.status === "failed").length,
unknown: filteredData.filter(item => !["installing", "done", "failed"].includes(item.status)).length,
};
// Sortieren
if (sortColumn) {
@ -218,9 +215,10 @@ const DataFetcher: React.FC = () => {
});
}
// Nur die Anzahl der angezeigten Einträge limitieren
setFilteredData(sortedAndFilteredData.slice(0, itemsPerPage));
}, [filters, data, searchQuery, startDate, endDate, sortColumn, sortDirection, itemsPerPage]);
// Daten sofort aktualisieren
setFilteredData(sortedAndFilteredData);
}, [filters, data, searchQuery, startDate, endDate, sortColumn, sortDirection]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
@ -228,48 +226,55 @@ const DataFetcher: React.FC = () => {
const columns = [
{ key: "status", type: "text", label: "Status" },
{ key: "type", type: "text", label: "Type" },
{ key: "nsapp", type: "text", label: "Application" },
{ key: "nsapp", type: "text", label: "App" },
{ key: "os_type", type: "text", label: "OS" },
{ key: "disk_size", type: "number", label: "HDD Size" },
{ key: "disk_size", type: "number", label: "HDD" },
{ key: "core_count", type: "number", label: "Cores" },
{ key: "ram_size", type: "number", label: "RAM" },
{ key: "method", type: "text", label: "Method" },
{ key: "pve_version", type: "text", label: "PVE Version" },
{ key: "pve_version", type: "text", label: "Version" },
{ key: "error", type: "text", label: "Error" },
{ key: "created_at", type: "text", label: "Created At" }
{ key: "created_at", type: "text", label: "Created" }
];
return (
<div className="p-6 mt-20">
<h1 className="text-2xl font-bold mb-4 text-center">Created LXCs</h1>
<div className="p-6 mt-20 max-w-full">
<h1 className="text-2xl font-bold mb-4 text-center">Created LXCs - {filteredData.length} Installations</h1>
<ApplicationChart data={filteredData} />
<br></br>
{/* Search & Date Filters */}
<div className="mb-4 flex space-x-4">
<div className="mb-4 flex space-x-4 w-full">
<input type="text" placeholder="Search..." value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} className="p-2 border" />
<DatePicker selected={startDate} onChange={setStartDate} placeholderText="Start date" className="p-2 border" />
<DatePicker selected={endDate} onChange={setEndDate} placeholderText="End date" className="p-2 border" />
</div>
<ApplicationChart data={filteredData} />
<br></br>
{/* Status Legend */}
<div className="mb-4 flex justify-between items-center">
<p className="text-lg font-bold">{filteredData.length} results found</p>
<p className="text-lg font">
Status Legend: 🔄 installing {installingCounts} | completed {doneCounts} | failed {failedCounts} | unknown {unknownCounts}
Status Legend: 🔄 installing {filteredCounts.installing} | completed {filteredCounts.done} | failed {filteredCounts.failed} | unknown {filteredCounts.unknown}
</p>
<br></br>
</div>
<div className="max-w-screen-2xl mx-auto overflow-visible">
{/* Table */}
<table className="min-w-full table-auto border-collapse">
<table className="table-fixed border-collapse w-full">
<thead>
<tr>
{columns.map(({ key, type, label }) => (
<th key={key} onClick={() => toggleSort(key)} className="cursor-pointer px-4 py-2 border-b text-left">
<th key={key}>
<div className="flex items-center space-x-1 flex-row-reverse">
{sortColumn === key && renderSortIcon(key)}
<span className="font-semibold">{label}</span>
<span
className="font-semibold cursor-pointer"
onClick={() => toggleSort(key)}
>
{label}
</span>
{key !== "created_at" && (
<FilterComponent
column={key}
type={type}
@ -278,9 +283,9 @@ const DataFetcher: React.FC = () => {
onRemoveFilter={removeFilter}
allData={data}
/>
)}
</div>
</th>
))}
</tr>
</thead>
@ -288,7 +293,7 @@ const DataFetcher: React.FC = () => {
<thead>
<tr>
{columns.map(({ key }) => (
<th key={key} className="px-4 py-2 border-b text-left">
<th key={key} className="px-4 py-3 border-b text-left">
{filters[key] && filters[key].length > 0 ? (
<div className="flex flex-wrap gap-1">
{filters[key].map((filter: { operator: string; value: any }, index: number) => (
@ -303,7 +308,7 @@ const DataFetcher: React.FC = () => {
))}
</div>
) : (
<span className="text-gray-500"></span>
<span className="px-10 py-2 text-right text-gray-500"></span>
)}
</th>
))}
@ -313,17 +318,49 @@ const DataFetcher: React.FC = () => {
{filteredData.length > 0 ? (
filteredData.slice(0, itemsPerPage).map((item, index) => (
<tr key={index}>
<td className="px-4 py-2 border-b">{item.status}</td>
<td className="px-4 py-2 border-b">{item.type}</td>
<td className="px-4 py-2 border-b">{item.nsapp}</td>
<td className="px-4 py-2 border-b">{item.os_type}</td>
<td className="px-4 py-2 border-b">{item.disk_size}</td>
<td className="px-4 py-2 border-b">{item.core_count}</td>
<td className="px-4 py-2 border-b">{item.ram_size}</td>
<td className="px-4 py-2 border-b">{item.method}</td>
<td className="px-4 py-2 border-b">{item.pve_version}</td>
<td className="px-4 py-2 border-b">{item.error}</td>
<td className="px-4 py-2 border-b">{formatDate(item.created_at)}</td>
<td className="px-4 py-2 border-b text-center">
<div className="relative group">
{item.status === "done" ? (
<span className="group-hover:tooltip"></span>
) : item.status === "failed" ? (
<span className="group-hover:tooltip"></span>
) : item.status === "installing" ? (
<span className="group-hover:tooltip">🔄</span>
) : (
<span>{item.status}</span>
)}
<span className="absolute hidden group-hover:block bg-gray-900 text-white text-xs px-2 py-1 rounded">
{item.status}
</span>
</div>
</td>
<td className="px-4 py-2 border-b text-center">
<div className="relative group">
{item.type === "lxc" ? (
<span className="group-hover:tooltip">📦</span>
) : item.type === "vm" ? (
<span className="group-hover:tooltip">🖥</span>
) : (
<span>{item.type}</span>
)}
<span className="absolute hidden group-hover:block bg-gray-900 text-white text-xs px-2 py-1 rounded -top-6 left-1/2 transform -translate-x-1/2">
{item.type}
</span>
</div>
</td>
<td className="px-4 py-3 border-b overflow-hidden text-ellipsis">{item.nsapp}</td>
<td className="px-4 py-3 border-b overflow-hidden text-ellipsis">{item.os_type}</td>
<td className="px-4 py-3 border-b overflow-hidden text-ellipsis">{item.disk_size}</td>
<td className="px-4 py-3 border-b overflow-hidden text-ellipsis">{item.core_count}</td>
<td className="px-4 py-3 border-b overflow-hidden text-ellipsis">{item.ram_size}</td>
<td className="px-4 py-3 border-b overflow-hidden text-ellipsis">{item.method}</td>
<td className="px-4 py-3 border-b overflow-hidden text-ellipsis">{item.pve_version}</td>
<td className="px-4 py-3 border-b overflow-hidden text-ellipsis">{item.error}</td>
<td className="px-4 py-3 border-b overflow-hidden text-ellipsis">{formatDate(item.created_at)}</td>
</tr>
))
) : (
@ -342,7 +379,7 @@ const DataFetcher: React.FC = () => {
</button>
</div>
)}
</div>
</div>
);
};

View File

@ -41,6 +41,10 @@ const FilterComponent: React.FC<FilterProps> = ({ column, type, activeFilters, o
return updatedFilters;
});
if (key === "value") {
setTimeout(() => setShowSuggestions(false), 100); // Vorschläge ausblenden, sobald Wert gesetzt wird
}
};
const handleAutocomplete = (input: string) => {
@ -138,14 +142,16 @@ const FilterComponent: React.FC<FilterProps> = ({ column, type, activeFilters, o
key={i}
className="p-2 hover:bg-gray-200 dark:hover:bg-gray-700 cursor-pointer"
onMouseDown={(e) => {
e.preventDefault(); // Damit das Blur-Event das Schließen nicht überschreibt
e.preventDefault(); // Verhindert, dass das Input-Feld sofort das Blur-Event auslöst
updateFilter(index, "value", suggestion);
setSuggestions([]);
setSuggestions([]); // Vorschläge ausblenden
setShowSuggestions(false);
}}
onClick={() => setFilters([{ operator: filters[index].operator, value: suggestion }])} // Setzt den Wert im Input zurück
>
{suggestion}
</li>
))}
</ul>
)}