Package updates and DataTable migration
Some checks failed
Deploy to production / build (push) Failing after 1m0s
Some checks failed
Deploy to production / build (push) Failing after 1m0s
This commit is contained in:
@@ -118,4 +118,11 @@ sudo pm2 start server.cjs
|
|||||||
- Mobile improvements
|
- Mobile improvements
|
||||||
- New Carosel on weather description
|
- New Carosel on weather description
|
||||||
- Updates to weather modal
|
- Updates to weather modal
|
||||||
- Reivew Mantine v9 for changes
|
- Add mantine-led for monitoring status (https://gfazioli.github.io/mantine-led)
|
||||||
|
- Update Theme playlists
|
||||||
|
- Include mantine-reflection (https://gfazioli.github.io/mantine-reflection#example-complex-components)
|
||||||
|
- Include mantine-parallax (https://gfazioli.github.io/mantine-parallax#example-presentation)
|
||||||
|
- Include mantine-depth-select (https://gfazioli.github.io/mantine-depth-select/)
|
||||||
|
- Explore https://gfazioli.github.io/mantine-scene/
|
||||||
|
- Explore https://gfazioli.github.io/mantine-window/
|
||||||
|
- Upgrade spinners/loaders (https://gfazioli.github.io/mantine-spinner#example-random)
|
||||||
1520
package-lock.json
generated
1520
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
49
package.json
49
package.json
@@ -10,39 +10,40 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gfazioli/mantine-border-animate": "^2.0.9",
|
"@gfazioli/mantine-border-animate": "^3.0.2",
|
||||||
"@gfazioli/mantine-text-animate": "^4.0.8",
|
"@gfazioli/mantine-text-animate": "^4.0.10",
|
||||||
"@hello-pangea/dnd": "^18.0.1",
|
"@hello-pangea/dnd": "^18.0.1",
|
||||||
"@mantine/core": "^9.5.0",
|
"@mantine/core": "^9.6.2",
|
||||||
"@mantine/dates": "^9.5.0",
|
"@mantine/dates": "^9.6.2",
|
||||||
"@mantine/form": "^9.5.0",
|
"@mantine/form": "^9.6.2",
|
||||||
"@mantine/hooks": "^9.5.0",
|
"@mantine/hooks": "^9.6.2",
|
||||||
"@mantine/notifications": "^9.5.0",
|
"@mantine/notifications": "^9.6.2",
|
||||||
"@tanstack/react-query": "^5.101.4",
|
"@tanstack/react-query": "^5.103.2",
|
||||||
"@tanstack/react-query-devtools": "^5.101.4",
|
"@tanstack/react-query-devtools": "^5.103.2",
|
||||||
"@tanstack/react-query-persist-client": "^5.101.4",
|
"@tanstack/react-query-persist-client": "^5.103.2",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^9.2.4",
|
||||||
|
"@tanstack/react-virtual": "^3.14.13",
|
||||||
"idb-keyval": "^6.3.0",
|
"idb-keyval": "^6.3.0",
|
||||||
"jotai": "^2.20.2",
|
"jotai": "^3.0.0",
|
||||||
"openmeteo": "^1.2.3",
|
"openmeteo": "^1.2.3",
|
||||||
"react": "^19.2.8",
|
"react": "^19.3.0",
|
||||||
"react-dom": "^19.2.8",
|
"react-dom": "^19.3.0",
|
||||||
"react-fontpicker-ts": "^1.3.0",
|
"react-fontpicker-ts": "^1.3.0",
|
||||||
"react-icons": "^5.7.0",
|
"react-icons": "^5.7.0",
|
||||||
"react-router-dom": "^7.18.2",
|
"react-router-dom": "^7.18.4",
|
||||||
"recharts": "^3.10.1"
|
"recharts": "^3.10.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^26.1.2",
|
"@types/node": "^26.6.2",
|
||||||
"@types/react": "^19.2.18",
|
"@types/react": "^19.3.0",
|
||||||
"@types/react-dom": "^19.2.4",
|
"@types/react-dom": "^19.3.0",
|
||||||
"@vitejs/plugin-react": "^6.0.5",
|
"@vitejs/plugin-react": "^6.1.1",
|
||||||
"@vitest/coverage-v8": "^4.1.10",
|
"@vitest/coverage-v8": "^5.0.2",
|
||||||
"@vitest/ui": "^4.1.10",
|
"@vitest/ui": "^5.0.2",
|
||||||
"typescript": "^7.0.2",
|
"typescript": "^7.0.2",
|
||||||
"vite": "^8.2.0",
|
"vite": "^8.3.1",
|
||||||
"vite-plugin-dts": "~5.0.3",
|
"vite-plugin-dts": "~5.1.1",
|
||||||
"vite-tsconfig-paths": "^6.1.1",
|
"vite-tsconfig-paths": "^6.1.1",
|
||||||
"vitest": "^4.1.10"
|
"vitest": "^5.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
import { ActionIcon, Box, Group, Text } from '@mantine/core';
|
import { ActionIcon, Box, Group, Text } from '@mantine/core';
|
||||||
import { createColumnHelper } from '@tanstack/react-table';
|
import {
|
||||||
|
columnFilteringFeature,
|
||||||
|
createColumnHelper,
|
||||||
|
filterFn_includesString,
|
||||||
|
rowSortingFeature,
|
||||||
|
tableFeatures,
|
||||||
|
} from '@tanstack/react-table';
|
||||||
import { FaFilter, FaTrash } from 'react-icons/fa';
|
import { FaFilter, FaTrash } from 'react-icons/fa';
|
||||||
import type { SearchHistoryItem } from 'types';
|
import type { SearchHistoryItem } from 'types';
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<SearchHistoryItem>();
|
const features = tableFeatures({
|
||||||
|
rowSortingFeature,
|
||||||
|
columnFilteringFeature,
|
||||||
|
filterFns: { includesString: filterFn_includesString },
|
||||||
|
});
|
||||||
|
const columnHelper = createColumnHelper<typeof features, SearchHistoryItem>();
|
||||||
export const SearchHistoryColumns = (
|
export const SearchHistoryColumns = (
|
||||||
setSelectedItem: React.Dispatch<React.SetStateAction<SearchHistoryItem | undefined>>,
|
setSelectedItem: React.Dispatch<React.SetStateAction<SearchHistoryItem | undefined>>,
|
||||||
deleteItemOpen: () => void,
|
deleteItemOpen: () => void,
|
||||||
@@ -26,7 +37,7 @@ export const SearchHistoryColumns = (
|
|||||||
</Group>
|
</Group>
|
||||||
),
|
),
|
||||||
header: () => (
|
header: () => (
|
||||||
<Group gap={0} justify='center'>
|
<Group gap={0} justify='center' align='center'>
|
||||||
<Box
|
<Box
|
||||||
w={'1.75rem'}
|
w={'1.75rem'}
|
||||||
h={'1.75rem'}
|
h={'1.75rem'}
|
||||||
@@ -48,9 +59,8 @@ export const SearchHistoryColumns = (
|
|||||||
),
|
),
|
||||||
meta: {
|
meta: {
|
||||||
filterVariant: 'counter',
|
filterVariant: 'counter',
|
||||||
|
width: '32px',
|
||||||
},
|
},
|
||||||
size: 32,
|
|
||||||
maxSize: 32,
|
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('prefix', {
|
columnHelper.accessor('prefix', {
|
||||||
@@ -63,27 +73,28 @@ export const SearchHistoryColumns = (
|
|||||||
header: () => <Text fw={600}>Prefix</Text>,
|
header: () => <Text fw={600}>Prefix</Text>,
|
||||||
meta: {
|
meta: {
|
||||||
filterVariant: 'mantineSelect',
|
filterVariant: 'mantineSelect',
|
||||||
|
width: '72px',
|
||||||
},
|
},
|
||||||
filterFn: (row, id, value) => {
|
filterFn: (row, id, value) => {
|
||||||
if (value === null) return true;
|
if (value === null) return true;
|
||||||
return row.getValue(id) === value;
|
return row.getValue(id) === value;
|
||||||
},
|
},
|
||||||
size: 32,
|
|
||||||
maxSize: 32,
|
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('provider', {
|
columnHelper.accessor('provider', {
|
||||||
id: 'provider',
|
id: 'provider',
|
||||||
cell: ({ cell }: any) => (
|
cell: ({ cell }: any) => (
|
||||||
<Text mx={8} style={{ textAlign: 'center' }}>
|
<Text
|
||||||
|
mx={8}
|
||||||
|
style={{ textAlign: 'center', overflow: 'ellipsis', whiteSpace: 'nowrap' }}
|
||||||
|
>
|
||||||
{cell.getValue()}
|
{cell.getValue()}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
header: () => <Text fw={600}>Provider</Text>,
|
header: () => <Text fw={600}>Provider</Text>,
|
||||||
meta: {
|
meta: {
|
||||||
filterVariant: 'mantineSelect',
|
filterVariant: 'mantineSelect',
|
||||||
|
width: '128px',
|
||||||
},
|
},
|
||||||
size: 128,
|
|
||||||
maxSize: 128,
|
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('searchTerm', {
|
columnHelper.accessor('searchTerm', {
|
||||||
id: 'searchTerm',
|
id: 'searchTerm',
|
||||||
@@ -93,9 +104,18 @@ export const SearchHistoryColumns = (
|
|||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
|
display: 'block',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text w={'100%'} mx={8} style={{ justifySelf: 'center' }}>
|
<Text
|
||||||
|
mx={8}
|
||||||
|
style={{
|
||||||
|
justifySelf: 'center',
|
||||||
|
overflow: 'hidden',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{cell.getValue()}
|
{cell.getValue()}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -103,8 +123,9 @@ export const SearchHistoryColumns = (
|
|||||||
header: () => <Text fw={600}>Search String</Text>,
|
header: () => <Text fw={600}>Search String</Text>,
|
||||||
meta: {
|
meta: {
|
||||||
filterVariant: 'text',
|
filterVariant: 'text',
|
||||||
|
width: 'calc(100% - 375px)',
|
||||||
},
|
},
|
||||||
maxSize: 1920,
|
filterFn: 'includesString',
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('dateTimeStamp', {
|
columnHelper.accessor('dateTimeStamp', {
|
||||||
id: 'dateTimeStamp',
|
id: 'dateTimeStamp',
|
||||||
@@ -126,6 +147,7 @@ export const SearchHistoryColumns = (
|
|||||||
header: () => <Text fw={600}>Time Stamp</Text>,
|
header: () => <Text fw={600}>Time Stamp</Text>,
|
||||||
meta: {
|
meta: {
|
||||||
filterVariant: 'range',
|
filterVariant: 'range',
|
||||||
|
width: '148px',
|
||||||
},
|
},
|
||||||
filterFn: (row, id, value) => {
|
filterFn: (row, id, value) => {
|
||||||
const startDate = new Date(value[0]);
|
const startDate = new Date(value[0]);
|
||||||
@@ -136,8 +158,5 @@ export const SearchHistoryColumns = (
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
size: 132,
|
|
||||||
maxSize: 132,
|
|
||||||
minSize: 132,
|
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,40 +1,41 @@
|
|||||||
import { Button, Group, ScrollArea, Text } from '@mantine/core';
|
import { Button, Group, Table, Text } from '@mantine/core';
|
||||||
import {
|
import { flexRender, type SortingState, useTable } from '@tanstack/react-table';
|
||||||
type ColumnFiltersState,
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
flexRender,
|
import { type Dispatch, type SetStateAction, useState } from 'react';
|
||||||
getCoreRowModel,
|
|
||||||
getExpandedRowModel,
|
|
||||||
getFacetedMinMaxValues,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
type SortingState,
|
|
||||||
useReactTable,
|
|
||||||
} from '@tanstack/react-table';
|
|
||||||
import { type Dispatch, type SetStateAction, useEffect, useState } from 'react';
|
|
||||||
import { scrollAreaClasses } from 'styles';
|
|
||||||
import { Filter } from './components';
|
import { Filter } from './components';
|
||||||
|
|
||||||
export function DataTable({
|
export function DataTable({
|
||||||
data,
|
data,
|
||||||
columns,
|
columns,
|
||||||
|
features,
|
||||||
sorting,
|
sorting,
|
||||||
setSorting,
|
setSorting,
|
||||||
filtersEnabled,
|
filtersEnabled,
|
||||||
globalFilter,
|
globalFilter,
|
||||||
setGlobalFilter,
|
|
||||||
isRefreshing,
|
isRefreshing,
|
||||||
}: {
|
}: {
|
||||||
data: any[];
|
data: any;
|
||||||
columns: any[];
|
columns: any[];
|
||||||
|
features: any;
|
||||||
sorting: SortingState;
|
sorting: SortingState;
|
||||||
setSorting: Dispatch<SetStateAction<SortingState>>;
|
setSorting: Dispatch<SetStateAction<SortingState>>;
|
||||||
filtersEnabled: boolean;
|
filtersEnabled: boolean;
|
||||||
globalFilter: string | number;
|
globalFilter: string | number;
|
||||||
setGlobalFilter: Dispatch<SetStateAction<string | number>>;
|
|
||||||
isRefreshing?: boolean;
|
isRefreshing?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
const [scrollParent, setScrollParent] = useState<HTMLDivElement | null>(null);
|
||||||
const table = useReactTable({
|
|
||||||
|
const virtualizer = useVirtualizer({
|
||||||
|
count: data.length,
|
||||||
|
getScrollElement: () => scrollParent,
|
||||||
|
estimateSize: () => 30,
|
||||||
|
overscan: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
const virtualItems = virtualizer.getVirtualItems();
|
||||||
|
|
||||||
|
const table = useTable({
|
||||||
|
features,
|
||||||
data: data,
|
data: data,
|
||||||
columns,
|
columns,
|
||||||
initialState: {
|
initialState: {
|
||||||
@@ -42,48 +43,24 @@ export function DataTable({
|
|||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
sorting,
|
sorting,
|
||||||
columnFilters,
|
|
||||||
globalFilter: globalFilter,
|
globalFilter: globalFilter,
|
||||||
},
|
},
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getExpandedRowModel: getExpandedRowModel(),
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
getFacetedRowModel: getFilteredRowModel(),
|
|
||||||
getFacetedMinMaxValues: getFacetedMinMaxValues(),
|
|
||||||
onColumnFiltersChange: setColumnFilters,
|
|
||||||
enableSorting: true,
|
|
||||||
enableFilters: true,
|
|
||||||
enableGlobalFilter: true,
|
|
||||||
globalFilterFn: 'includesString',
|
|
||||||
onGlobalFilterChange: setGlobalFilter,
|
|
||||||
});
|
});
|
||||||
|
const { rows } = table.getRowModel();
|
||||||
useEffect(() => {
|
|
||||||
if (!filtersEnabled) {
|
|
||||||
setColumnFilters([]);
|
|
||||||
}
|
|
||||||
}, [filtersEnabled]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea
|
<Table.ScrollContainer
|
||||||
className={scrollAreaClasses.default}
|
minWidth={'100%'}
|
||||||
classNames={scrollAreaClasses}
|
scrollAreaProps={{ viewportRef: setScrollParent }}
|
||||||
styles={{
|
|
||||||
scrollbar: {
|
|
||||||
paddingTop: '2.5rem',
|
|
||||||
paddingBottom: '2rem',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<table
|
<Table
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
borderSpacing: '2px 0px',
|
borderSpacing: '2px 0px',
|
||||||
backgroundColor: 'var(--mantine-color-accent-0)',
|
backgroundColor: 'var(--mantine-color-accent-0)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<thead
|
<Table.Thead
|
||||||
style={{
|
style={{
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: 0,
|
top: 0,
|
||||||
@@ -92,133 +69,159 @@ export function DataTable({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<tr key={headerGroup.id}>
|
<Table.Tr key={headerGroup.id}>
|
||||||
{headerGroup.headers.map((header) => (
|
{headerGroup.headers.map((header: any) => {
|
||||||
<th
|
return (
|
||||||
key={header.id}
|
<Table.Th
|
||||||
style={{
|
key={header.id}
|
||||||
width: header.getSize(),
|
style={{
|
||||||
padding: 0,
|
padding: 0,
|
||||||
}}
|
}}
|
||||||
>
|
w={`${header.column.columnDef.meta?.width}`}
|
||||||
{header.isPlaceholder ? null : (
|
>
|
||||||
<>
|
{header.isPlaceholder ? null : (
|
||||||
<Button
|
<>
|
||||||
{...{
|
<Button
|
||||||
style: {
|
{...{
|
||||||
cursor: 'pointer',
|
style: {
|
||||||
borderRadius: '0px',
|
cursor: 'pointer',
|
||||||
backgroundColor: 'transparent',
|
borderRadius: '0px',
|
||||||
},
|
backgroundColor: 'transparent',
|
||||||
px: 0,
|
},
|
||||||
onClick: () => {
|
px: 0,
|
||||||
if (header.column.getCanSort()) {
|
w: '100%',
|
||||||
if (sorting[0].id === header.id) {
|
onClick: () => {
|
||||||
setSorting([
|
if (header.column.getCanSort()) {
|
||||||
{
|
if (sorting[0].id === header.id) {
|
||||||
id: header.id,
|
setSorting([
|
||||||
desc: !sorting[0].desc,
|
{
|
||||||
},
|
id: header.id,
|
||||||
]);
|
desc: !sorting[0].desc,
|
||||||
} else {
|
},
|
||||||
setSorting([
|
]);
|
||||||
{
|
} else {
|
||||||
id: header.id,
|
setSorting([
|
||||||
desc: false,
|
{
|
||||||
},
|
id: header.id,
|
||||||
]);
|
desc: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<Group gap={4} align='center' justify='center'>
|
||||||
<Group align='center' justify='center'>
|
{flexRender(
|
||||||
{flexRender(
|
header.column.columnDef.header,
|
||||||
header.column.columnDef.header,
|
header.getContext(),
|
||||||
header.getContext(),
|
)}
|
||||||
)}
|
{{
|
||||||
{{
|
asc: (
|
||||||
asc: (
|
<Group gap={0}>
|
||||||
<Group gap={0}>
|
<Text c={'var(--mantine-color-accent-0)'}>🠗</Text>
|
||||||
<Text c={'var(--mantine-color-accent-0)'}>🠗</Text>
|
<Text>🠕</Text>
|
||||||
<Text>🠕</Text>
|
</Group>
|
||||||
</Group>
|
),
|
||||||
),
|
desc: (
|
||||||
desc: (
|
<Group gap={0}>
|
||||||
<Group gap={0}>
|
<Text>🠗</Text>
|
||||||
<Text>🠗</Text>
|
<Text c={'var(--mantine-color-accent-0)'}>🠕</Text>
|
||||||
<Text c={'var(--mantine-color-accent-0)'}>🠕</Text>
|
</Group>
|
||||||
</Group>
|
),
|
||||||
),
|
}[header.column.getIsSorted() as string] ?? null}
|
||||||
}[header.column.getIsSorted() as string] ?? null}
|
</Group>
|
||||||
</Group>
|
</Button>
|
||||||
</Button>
|
{filtersEnabled ? (
|
||||||
{filtersEnabled ? (
|
<Filter
|
||||||
<Filter
|
column={header.column}
|
||||||
column={header.column}
|
options={[
|
||||||
options={[...new Set(data.map((item: any) => item[header.id]))]}
|
...new Set(data.map((item: any) => item[header.id])),
|
||||||
resultCount={table.getFilteredRowModel().rows.length}
|
]}
|
||||||
/>
|
resultCount={table.getFilteredRowModel().rows.length}
|
||||||
) : null}
|
/>
|
||||||
</>
|
) : null}
|
||||||
)}
|
</>
|
||||||
</th>
|
)}
|
||||||
))}
|
</Table.Th>
|
||||||
</tr>
|
);
|
||||||
|
})}
|
||||||
|
</Table.Tr>
|
||||||
))}
|
))}
|
||||||
</thead>
|
</Table.Thead>
|
||||||
<tbody>
|
<Table.Tbody>
|
||||||
{table.getRowModel().rows.map((row, index) => (
|
{virtualItems.length > 0 && (
|
||||||
<tr
|
<tr aria-hidden>
|
||||||
key={row.id}
|
<td
|
||||||
style={{
|
aria-hidden
|
||||||
color: 'var(--mantine-color-primary-0)',
|
colSpan={5}
|
||||||
backgroundColor:
|
style={{ height: virtualItems[0].start, padding: 0, border: 'none' }}
|
||||||
index % 2 === 0
|
/>
|
||||||
? 'color-mix(in srgb, var(--mantine-color-background-0), var(--mantine-color-primary-0) 10%)'
|
|
||||||
: 'color-mix(in srgb, var(--mantine-color-background-0), var(--mantine-color-primary-0) 30%)',
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
row.toggleExpanded();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<td
|
|
||||||
key={cell.id}
|
|
||||||
style={{
|
|
||||||
width: cell.column.getSize(),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
)}
|
||||||
</tbody>
|
{virtualItems.map((virtualItem) => {
|
||||||
<tfoot
|
const row = rows[virtualItem.index];
|
||||||
|
return (
|
||||||
|
<Table.Tr
|
||||||
|
key={row?.id}
|
||||||
|
style={{
|
||||||
|
color: 'var(--mantine-color-primary-0)',
|
||||||
|
backgroundColor:
|
||||||
|
virtualItem.index % 2 === 0
|
||||||
|
? 'color-mix(in srgb, var(--mantine-color-background-0), var(--mantine-color-primary-0) 10%)'
|
||||||
|
: 'color-mix(in srgb, var(--mantine-color-background-0), var(--mantine-color-primary-0) 30%)',
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
row?.toggleExpanded();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{row?.getAllCells().map((cell: any) => {
|
||||||
|
return (
|
||||||
|
<Table.Td
|
||||||
|
key={cell.id}
|
||||||
|
style={{
|
||||||
|
maxWidth: `${cell.column.columnDef.meta?.width}`,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</Table.Td>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Table.Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{virtualItems.length > 0 && (
|
||||||
|
<tr aria-hidden>
|
||||||
|
<td
|
||||||
|
aria-hidden
|
||||||
|
colSpan={5}
|
||||||
|
style={{
|
||||||
|
height:
|
||||||
|
virtualizer.getTotalSize() -
|
||||||
|
virtualItems[virtualItems.length - 1].end,
|
||||||
|
padding: 0,
|
||||||
|
border: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</Table.Tbody>
|
||||||
|
<Table.Tfoot
|
||||||
style={{
|
style={{
|
||||||
transition: 'all 1s ease',
|
transition: 'all 1s ease',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{table.getFooterGroups().map((footerGroup) => (
|
<Table.Tr
|
||||||
<tr key={footerGroup.id}>
|
|
||||||
{footerGroup.headers.map((header) => (
|
|
||||||
<th key={header.id}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(header.column.columnDef.footer, header.getContext())}
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
<tr
|
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'var(--mantine-color-primary-0)',
|
backgroundColor: 'var(--mantine-color-primary-0)',
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<td colSpan={5}>
|
<Table.Td colSpan={5}>
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
color: 'var(--mantine-color-background-0)',
|
color: 'var(--mantine-color-background-0)',
|
||||||
@@ -228,20 +231,24 @@ export function DataTable({
|
|||||||
>
|
>
|
||||||
Total Entries: {table.getCoreRowModel().rows.length}
|
Total Entries: {table.getCoreRowModel().rows.length}
|
||||||
</Text>
|
</Text>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr
|
<Table.Tr
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
backgroundColor: 'var(--mantine-color-accent-0)',
|
backgroundColor: isRefreshing
|
||||||
|
? 'var(--mantine-color-accent-0)'
|
||||||
|
: 'transparent',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: isRefreshing ? 0 : -30,
|
bottom: isRefreshing ? 0 : -40,
|
||||||
|
paddingBottom: 6,
|
||||||
|
paddingTop: 6,
|
||||||
transition: 'all 1.5s ease-in-out',
|
transition: 'all 1.5s ease-in-out',
|
||||||
placeContent: 'center',
|
placeContent: 'center',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<td colSpan={5} width={'100%'} style={{}}>
|
<Table.Td colSpan={5} width={'100%'} style={{}}>
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
color: 'var(--mantine-color-background-0)',
|
color: 'var(--mantine-color-background-0)',
|
||||||
@@ -251,10 +258,10 @@ export function DataTable({
|
|||||||
>
|
>
|
||||||
Updating
|
Updating
|
||||||
</Text>
|
</Text>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
</tfoot>
|
</Table.Tfoot>
|
||||||
</table>
|
</Table>
|
||||||
</ScrollArea>
|
</Table.ScrollContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
55
src/components/DataTable/components/Features.ts
Normal file
55
src/components/DataTable/components/Features.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import {
|
||||||
|
columnFacetingFeature,
|
||||||
|
columnFilteringFeature,
|
||||||
|
createExpandedRowModel,
|
||||||
|
createFacetedMinMaxValues,
|
||||||
|
createFacetedRowModel,
|
||||||
|
createFilteredRowModel,
|
||||||
|
createSortedRowModel,
|
||||||
|
filterFn_equalsString,
|
||||||
|
filterFn_includesString,
|
||||||
|
filterFn_inDateRange,
|
||||||
|
globalFilteringFeature,
|
||||||
|
metaHelper,
|
||||||
|
rowExpandingFeature,
|
||||||
|
rowSortingFeature,
|
||||||
|
sortFn_alphanumeric,
|
||||||
|
sortFn_text,
|
||||||
|
tableFeatures,
|
||||||
|
} from '@tanstack/react-table';
|
||||||
|
|
||||||
|
interface ColumnMeta {
|
||||||
|
width?: number | string;
|
||||||
|
filterVariant?:
|
||||||
|
| 'text'
|
||||||
|
| 'range'
|
||||||
|
| 'select'
|
||||||
|
| 'mantineSelect'
|
||||||
|
| 'multiSelect'
|
||||||
|
| 'slider'
|
||||||
|
| 'checkbox'
|
||||||
|
| 'counter'
|
||||||
|
| 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
const features = tableFeatures({
|
||||||
|
filterFns: {
|
||||||
|
equalsString: filterFn_equalsString,
|
||||||
|
includesString: filterFn_includesString,
|
||||||
|
inDateRange: filterFn_inDateRange,
|
||||||
|
},
|
||||||
|
sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text },
|
||||||
|
filteredRowModel: createFilteredRowModel(),
|
||||||
|
expandedRowModel: createExpandedRowModel(),
|
||||||
|
sortedRowModel: createSortedRowModel(),
|
||||||
|
facetedRowModel: createFacetedRowModel(),
|
||||||
|
facetedMinMaxValues: createFacetedMinMaxValues(),
|
||||||
|
rowSortingFeature,
|
||||||
|
rowExpandingFeature,
|
||||||
|
columnFacetingFeature,
|
||||||
|
columnFilteringFeature,
|
||||||
|
globalFilteringFeature,
|
||||||
|
columnMeta: metaHelper<ColumnMeta>(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default features;
|
||||||
@@ -11,32 +11,15 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { DatePickerInput } from '@mantine/dates';
|
import { DatePickerInput } from '@mantine/dates';
|
||||||
import type { Column, RowData } from '@tanstack/react-table';
|
|
||||||
import { DebouncedInput } from 'components';
|
import { DebouncedInput } from 'components';
|
||||||
import { datePickerInputClasses, selectClasses } from 'styles';
|
import { datePickerInputClasses, selectClasses } from 'styles';
|
||||||
|
|
||||||
declare module '@tanstack/react-table' {
|
|
||||||
//allows us to define custom properties for our columns
|
|
||||||
interface ColumnMeta<_TData extends RowData, _TValue> {
|
|
||||||
filterVariant?:
|
|
||||||
| 'text'
|
|
||||||
| 'range'
|
|
||||||
| 'select'
|
|
||||||
| 'mantineSelect'
|
|
||||||
| 'multiSelect'
|
|
||||||
| 'slider'
|
|
||||||
| 'checkbox'
|
|
||||||
| 'counter'
|
|
||||||
| 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Filter({
|
export function Filter({
|
||||||
column,
|
column,
|
||||||
options,
|
options,
|
||||||
resultCount,
|
resultCount,
|
||||||
}: {
|
}: {
|
||||||
column: Column<unknown>;
|
column: any;
|
||||||
options: any;
|
options: any;
|
||||||
resultCount?: number;
|
resultCount?: number;
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -61,7 +61,9 @@ export default function CurrentWeather({
|
|||||||
<Text>Humidity</Text>
|
<Text>Humidity</Text>
|
||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
rightSide={<Text>{current.current.relative_humidity_2m?.toFixed(0)}%</Text>}
|
rightSide={
|
||||||
|
<Text>{current.current.relative_humidity_2m?.toFixed(0)}%</Text>
|
||||||
|
}
|
||||||
highlight='right'
|
highlight='right'
|
||||||
/>
|
/>
|
||||||
<WeatherFeaturePill
|
<WeatherFeaturePill
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useCommonHooks, useQueryAPI } from 'hooks';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { textClasses } from 'styles';
|
import { textClasses } from 'styles';
|
||||||
import type { SearchHistoryItem } from 'types';
|
import type { SearchHistoryItem } from 'types';
|
||||||
|
import features from '../../../../components/DataTable/components/Features';
|
||||||
|
|
||||||
export function SearchHistory() {
|
export function SearchHistory() {
|
||||||
const searchEndpoint = 'api/searchHistory';
|
const searchEndpoint = 'api/searchHistory';
|
||||||
@@ -101,11 +102,11 @@ export function SearchHistory() {
|
|||||||
filtersEnabled,
|
filtersEnabled,
|
||||||
setFiltersEnabled,
|
setFiltersEnabled,
|
||||||
)}
|
)}
|
||||||
|
features={features}
|
||||||
sorting={sorting}
|
sorting={sorting}
|
||||||
setSorting={setSorting}
|
setSorting={setSorting}
|
||||||
filtersEnabled={filtersEnabled}
|
filtersEnabled={filtersEnabled}
|
||||||
globalFilter={globalFilter}
|
globalFilter={globalFilter}
|
||||||
setGlobalFilter={setGlobalFilter}
|
|
||||||
isRefreshing={historyDataIsFetching}
|
isRefreshing={historyDataIsFetching}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
Reference in New Issue
Block a user