diff --git a/package-lock.json b/package-lock.json index 9792f28..522bc14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "temp-react-app", "version": "0.1.0", "dependencies": { + "@tanstack/react-table": "^8.21.3", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", @@ -3550,6 +3551,39 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", diff --git a/package.json b/package.json index 8795cdb..b2dfcd7 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@tanstack/react-table": "^8.21.3", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", diff --git a/src/components/StoreWatchPage.js b/src/components/StoreWatchPage.js index e6eaf73..4b5c9c5 100644 --- a/src/components/StoreWatchPage.js +++ b/src/components/StoreWatchPage.js @@ -1,4 +1,49 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; +import { + createColumnHelper, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getSortedRowModel, + useReactTable +} from '@tanstack/react-table'; + +const columnHelper = createColumnHelper(); + +const ColumnTextFilter = ({ column, placeholder }) => { + if (!column.getCanFilter()) { + return null; + } + return ( + column.setFilterValue(event.target.value)} + placeholder={placeholder} + className="mt-1 w-full rounded border px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-blue-500" + /> + ); +}; + +const ColumnSelectFilter = ({ column, options }) => { + if (!column.getCanFilter()) { + return null; + } + return ( + + ); +}; const StoreWatchPage = ({ authorizedFetch, knownStores = [] }) => { const [regions, setRegions] = useState([]); @@ -12,8 +57,8 @@ const StoreWatchPage = ({ authorizedFetch, knownStores = [] }) => { const [error, setError] = useState(''); const [dirty, setDirty] = useState(false); const [saving, setSaving] = useState(false); - const [filterText, setFilterText] = useState(''); - const [sortBy, setSortBy] = useState('name'); + const [sorting, setSorting] = useState([]); + const [columnFilters, setColumnFilters] = useState([]); const watchedIds = useMemo( () => new Set(watchList.map((entry) => String(entry.storeId))), @@ -48,47 +93,186 @@ const StoreWatchPage = ({ authorizedFetch, knownStores = [] }) => { return map; }, [knownStores]); - const filteredStores = useMemo(() => { - const search = filterText.trim().toLowerCase(); - const data = !search - ? [...eligibleStores] - : eligibleStores.filter((store) => { - const haystack = [ - store.name, - store.city, - store.street, - store.zipCode, - store.id - ] - .filter(Boolean) - .map((value) => String(value).toLowerCase()); - return haystack.some((value) => value.includes(search)); - }); - const compareString = (a = '', b = '') => a.localeCompare(b, 'de', { sensitivity: 'base' }); - data.sort((a, b) => { - switch (sortBy) { - case 'city': - return compareString(a.city || '', b.city || '') || compareString(a.name || '', b.name || ''); - case 'created-desc': { - const timeA = a.createdAt ? new Date(a.createdAt).getTime() : 0; - const timeB = b.createdAt ? new Date(b.createdAt).getTime() : 0; - if (timeA === timeB) { - return compareString(a.name || '', b.name || ''); - } - return timeB - timeA; - } - case 'membership': + const tableData = useMemo( + () => + eligibleStores.map((store) => ({ + ...store, + membership: membershipMap.has(String(store.id)) + })), + [eligibleStores, membershipMap] + ); + + const columns = useMemo( + () => [ + columnHelper.accessor('name', { + header: ({ column }) => ( +
{row.original.name}
+#{row.original.id}
+{row.original.city || 'unbekannt'}
+{row.original.street || ''}
+Bitte zuerst eine Region auswählen.
)} - {!storesLoading && selectedRegionId && filteredStores.length === 0 && ( + {!storesLoading && selectedRegionId && table.getRowModel().rows.length === 0 && (Keine Betriebe gefunden. Prüfe Filter oder sortiere anders.
)} - {!storesLoading && filteredStores.length > 0 && ( + {!storesLoading && table.getRowModel().rows.length > 0 && (| Betrieb | -Ort | -Kooperation seit | -Mitglied | -Überwachen | -
|---|---|---|---|---|
| + {flexRender(header.column.columnDef.header, header.getContext())} + | + ))} +||||
|
- {store.name} -#{store.id} + {table.getRowModel().rows.map((row) => ( + | ||||
| + {flexRender(cell.column.columnDef.cell, cell.getContext())} | -
- {store.city || 'unbekannt'} -{store.street || ''} - |
- {sinceLabel} | -- - {isMember ? 'Ja' : 'Nein'} - - | -- handleToggleStore(store, event.target.checked)} - /> - | -