From 020f179b8254e64dc94e62ca8b6eba02bd56085d Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Fri, 7 Feb 2025 17:16:53 +0100 Subject: [PATCH] fixes --- frontend/src/app/data/page.tsx | 181 ++++++++++++-------- frontend/src/components/FilterComponent.tsx | 15 +- 2 files changed, 116 insertions(+), 80 deletions(-) diff --git a/frontend/src/app/data/page.tsx b/frontend/src/app/data/page.tsx index 4ff80984..5bbaea81 100644 --- a/frontend/src/app/data/page.tsx +++ b/frontend/src/app/data/page.tsx @@ -5,6 +5,7 @@ import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; import ApplicationChart from "../../components/ApplicationChart"; import FilterComponent from "../../components/FilterComponent"; +import { AlertCircle } from "lucide-react"; // Modernes Icon für Fehler interface DataModel { id: number; @@ -45,6 +46,25 @@ const DataFetcher: React.FC = () => { setItemsPerPage((prev) => prev + 25); }; + const handleShowError = (event: React.MouseEvent, errorMessage: string) => { + setErrorPopup({ + open: true, + message: errorMessage, + x: event.clientX, + y: event.clientY, + }); + }; + + const handleCloseError = () => { + setErrorPopup({ open: false, message: "", x: 0, y: 0 }); + }; + const [errorPopup, setErrorPopup] = useState<{ open: boolean; message: string; x: number; y: number }>({ + open: false, + message: "", + x: 0, + y: 0, + }); + const formatDate = (dateString: string): string => { const date = new Date(dateString); const year = date.getFullYear(); @@ -60,7 +80,7 @@ const DataFetcher: React.FC = () => { 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) { @@ -252,46 +272,42 @@ const DataFetcher: React.FC = () => {

- {/* Status Legend */}

Status Legend: 🔄 installing {filteredCounts.installing} | ✔️ completed {filteredCounts.done} | ❌ failed {filteredCounts.failed} | ❓ unknown {filteredCounts.unknown}

-

-
- {/* Table */} - + +
+
- + {columns.map(({ key, type, label }) => ( - ))} - - {/* Filters Row - Displays below headers */} - - + + {/* Aktive Filter-Zeile */} + {columns.map(({ key }) => ( ))} - + + {filteredData.length > 0 ? ( filteredData.slice(0, itemsPerPage).map((item, index) => ( - - + {/* Status mit Icon */} + - + {/* Dynamische Spaltenbreiten */} + + + {/* Kleinere Spalten für HDD, Cores, RAM */} + + + - - - - - - - - - + {/* Weitere Spalten mit Overflow-Fix */} + + + + {/* Fehler mit Tooltip & Icon */} + + + {/* Created At mit Formatierung */} + )) ) : ( - )}
-
- {sortColumn === key && renderSortIcon(key)} - toggleSort(key)} - > - {label} - +
+
+ {/* Filter-Icon links */} {key !== "created_at" && ( - +
+ col.key === key)?.type || "text"} + activeFilters={filters[key] || []} + onApplyFilter={applyFilters} + onRemoveFilter={removeFilter} + allData={data} + /> +
)} + toggleSort(key)}> + {label} {sortColumn === key && renderSortIcon(key)} +
{filters[key] && filters[key].length > 0 ? ( @@ -308,80 +324,99 @@ const DataFetcher: React.FC = () => { ))} ) : ( - + )}
+
- {item.status === "done" ? ( - ✔️ - ) : item.status === "failed" ? ( - - ) : item.status === "installing" ? ( - 🔄 - ) : ( - {item.status} - )} - - {item.status} - + {item.status === "done" ? "✔️" : + item.status === "failed" ? "❌" : + item.status === "installing" ? "🔄" : item.status}
+ {/* Type mit Icon */} +
- {item.type === "lxc" ? ( - 📦 - ) : item.type === "vm" ? ( - 🖥️ - ) : ( - {item.type} - )} - - {item.type} - + {item.type === "lxc" ? "📦" : + item.type === "vm" ? "🖥️" : item.type}
{item.nsapp}{item.os_type}{item.disk_size}{item.core_count}{item.ram_size}{item.nsapp}{item.os_type}{item.disk_size}{item.core_count}{item.ram_size}{item.method}{item.pve_version}{item.error}{formatDate(item.created_at)}{item.method}{item.pve_version} + {item.error && item.error !== "none" ? ( + + ) : ( + + )} + {formatDate(item.created_at)}
+ No results found
- {itemsPerPage < filteredData.length && ( -
- -
- )}
+ + {/* Fehler-Popup jetzt direkt neben dem Icon */} + {errorPopup.open && ( +
+

{errorPopup.message}

+ +
+ )} + + {/* Load More Button */} + {itemsPerPage < filteredData.length && ( +
+ +
+ )} ); -}; +} export default DataFetcher; diff --git a/frontend/src/components/FilterComponent.tsx b/frontend/src/components/FilterComponent.tsx index 60325659..fe6a83b9 100644 --- a/frontend/src/components/FilterComponent.tsx +++ b/frontend/src/components/FilterComponent.tsx @@ -34,19 +34,19 @@ const FilterComponent: React.FC = ({ column, type, activeFilters, o setFilters((prevFilters) => { const updatedFilters = [...prevFilters]; updatedFilters[index][key] = newValue; - + if (key === "value" && type === "text") { handleAutocomplete(newValue as string); } - + return updatedFilters; }); - + if (key === "value") { setTimeout(() => setShowSuggestions(false), 100); // Vorschläge ausblenden, sobald Wert gesetzt wird } }; - + const handleAutocomplete = (input: string) => { let filteredSuggestions: string[] = []; @@ -119,12 +119,13 @@ const FilterComponent: React.FC = ({ column, type, activeFilters, o
updateFilter(index, "value", e.target.value)} className="w-full mt-2 p-1 border rounded" - onFocus={() => handleAutocomplete("")} - onBlur={() => setTimeout(() => setShowSuggestions(false), 200)} // Delay hiding to allow clicking + onFocus={() => handleAutocomplete("")} // Zeige Vorschläge an + onBlur={() => setTimeout(() => setShowSuggestions(false), 200)} // Verhindert sofortiges Schließen /> + {type === "text" && (