From 139f84a934bc39c394e053fdc3d41c1e2e19fc50 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner <73236783+michelroegl-brunner@users.noreply.github.com> Date: Wed, 29 Jan 2025 19:07:31 +0100 Subject: [PATCH] [Frontend] Add /data to show API results (#1841) * [Frontend] Add /data to show API results * [Frontend] Add /data to show API results * update page.tsx * update page.tsx * update page.tsx * update page.tsx --- frontend/package-lock.json | 53 +++++++- frontend/package.json | 1 + frontend/src/app/data/page.tsx | 236 +++++++++++++++++++++++++++++++++ 3 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 frontend/src/app/data/page.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 024cb74b..af124282 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -38,6 +38,7 @@ "prettier-plugin-organize-imports": "^4.1.0", "react": "19.0.0-rc-02c0e824-20241028", "react-code-blocks": "^0.1.6", + "react-datepicker": "^7.6.0", "react-day-picker": "8.10.1", "react-dom": "19.0.0-rc-02c0e824-20241028", "react-icons": "^5.1.0", @@ -1083,9 +1084,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", - "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", "license": "MIT" }, "node_modules/@humanfs/core": { @@ -8017,6 +8018,46 @@ "react": ">=16" } }, + "node_modules/react-datepicker": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-7.6.0.tgz", + "integrity": "sha512-9cQH6Z/qa4LrGhzdc3XoHbhrxNcMi9MKjZmYgF/1MNNaJwvdSjv3Xd+jjvrEEbKEf71ZgCA3n7fQbdwd70qCRw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.0", + "clsx": "^2.1.1", + "date-fns": "^3.6.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/react-datepicker/node_modules/@floating-ui/react": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.3.tgz", + "integrity": "sha512-CLHnes3ixIFFKVQDdICjel8muhFLOBdQH7fgtHNPY8UbCNqbeKZ262G7K66lGQOUQWWnYocf7ZbUsLJgGfsLHg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.9", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/react-datepicker/node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/react-day-picker": { "version": "8.10.1", "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz", @@ -9055,6 +9096,12 @@ "dev": true, "license": "MIT" }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, "node_modules/tailwind-merge": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index 2309f43c..e689ba4c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -49,6 +49,7 @@ "prettier-plugin-organize-imports": "^4.1.0", "react": "19.0.0-rc-02c0e824-20241028", "react-code-blocks": "^0.1.6", + "react-datepicker": "^7.6.0", "react-day-picker": "8.10.1", "react-dom": "19.0.0-rc-02c0e824-20241028", "react-icons": "^5.1.0", diff --git a/frontend/src/app/data/page.tsx b/frontend/src/app/data/page.tsx new file mode 100644 index 00000000..5fc44bc1 --- /dev/null +++ b/frontend/src/app/data/page.tsx @@ -0,0 +1,236 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import DatePicker from 'react-datepicker'; +import 'react-datepicker/dist/react-datepicker.css'; +import { string } from "zod"; + + +interface DataModel { + id: number; + ct_type: number; + disk_size: number; + core_count: number; + ram_size: number; + verbose: string; + os_type: string; + os_version: string; + hn: string; + disableip6: string; + ssh: string; + tags: string; + nsapp: string; + created_at: string; + method: string; + pve_version: string; +} + + +const DataFetcher: React.FC = () => { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [searchQuery, setSearchQuery] = useState(''); + const [startDate, setStartDate] = useState(null); + const [endDate, setEndDate] = useState(null); + const [sortConfig, setSortConfig] = useState<{ key: keyof DataModel | null, direction: 'ascending' | 'descending' }>({ key: 'id', direction: 'descending' }); + const [itemsPerPage, setItemsPerPage] = useState(5); + const [currentPage, setCurrentPage] = useState(1); + + useEffect(() => { + const fetchData = async () => { + try { + const response = await fetch("http://api.htl-braunau.at/data/json"); + if (!response.ok) throw new Error("Failed to fetch data: ${response.statusText}"); + const result: DataModel[] = await response.json(); + setData(result); + } catch (err) { + setError((err as Error).message); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, []); + + + const filteredData = data.filter(item => { + const matchesSearchQuery = Object.values(item).some(value => + value.toString().toLowerCase().includes(searchQuery.toLowerCase()) + ); + const itemDate = new Date(item.created_at); + const matchesDateRange = (!startDate || itemDate >= startDate) && (!endDate || itemDate <= endDate); + return matchesSearchQuery && matchesDateRange; + }); + + const sortedData = React.useMemo(() => { + let sortableData = [...filteredData]; + if (sortConfig.key !== null) { + sortableData.sort((a, b) => { + if (sortConfig.key !== null && a[sortConfig.key] < b[sortConfig.key]) { + return sortConfig.direction === 'ascending' ? -1 : 1; + } + if (sortConfig.key !== null && a[sortConfig.key] > b[sortConfig.key]) { + return sortConfig.direction === 'ascending' ? 1 : -1; + } + return 0; + }); + } + return sortableData; + }, [filteredData, sortConfig]); + + const requestSort = (key: keyof DataModel | null) => { + let direction: 'ascending' | 'descending' = 'ascending'; + if (sortConfig.key === key && sortConfig.direction === 'ascending') { + direction = 'descending'; + } else if (sortConfig.key === key && sortConfig.direction === 'descending') { + direction = 'ascending'; + } else { + direction = 'descending'; + } + setSortConfig({ key, direction }); + }; + + interface SortConfig { + key: keyof DataModel | null; + direction: 'ascending' | 'descending'; + } + + const formatDate = (dateString: string): string => { + const date = new Date(dateString); + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const timezoneOffset = dateString.slice(-6); + return `${day}.${month}.${year} ${hours}:${minutes} ${timezoneOffset} GMT`; + }; + + const handleItemsPerPageChange = (event: React.ChangeEvent) => { + setItemsPerPage(Number(event.target.value)); + setCurrentPage(1); + }; + + const paginatedData = sortedData.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage); + + if (loading) return

Loading...

; + if (error) return

Error: {error}

; + + + return ( +
+

Created LXCs

+
+
+ setSearchQuery(e.target.value)} + className="p-2 border" + /> + +
+
+ setStartDate(date)} + selectsStart + startDate={startDate} + endDate={endDate} + placeholderText="Start date" + className="p-2 border" + /> + +
+ +
+ setEndDate(date)} + selectsEnd + startDate={startDate} + endDate={endDate} + placeholderText="End date" + className="p-2 border" + /> + +
+
+
+

{filteredData.length} results found

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + {paginatedData.map((item, index) => ( + + + + + + + + + + + + + + + + ))} + +
requestSort('nsapp')}>Application requestSort('os_type')}>OS requestSort('os_version')}>OS Version requestSort('disk_size')}>Disk Size requestSort('core_count')}>Core Count requestSort('ram_size')}>RAM Size requestSort('hn')}>Hostname requestSort('ssh')}>SSH requestSort('verbose')}>Verb requestSort('tags')}>Tags requestSort('method')}>Method requestSort('pve_version')}>PVE Version requestSort('created_at')}>Created At
{item.nsapp}{item.os_type}{item.os_version}{item.disk_size}{item.core_count}{item.ram_size}{item.hn}{item.ssh}{item.verbose}{item.tags.replace(/;/g, ' ')}{item.method}{item.pve_version}{formatDate(item.created_at)}
+
+
+
+ + Page {currentPage} + +
+
+ ); +}; + + + +export default DataFetcher;