Table updates

This commit is contained in:
Sean Edmunds
2025-11-10 13:32:31 -06:00
parent 3e59cccfd5
commit 0397c0e1b0
7 changed files with 271 additions and 179 deletions

69
package-lock.json generated
View File

@@ -10,11 +10,11 @@
"dependencies": { "dependencies": {
"@gfazioli/mantine-text-animate": "^2.3.3", "@gfazioli/mantine-text-animate": "^2.3.3",
"@hello-pangea/dnd": "^18.0.1", "@hello-pangea/dnd": "^18.0.1",
"@mantine/core": "^8.3.6", "@mantine/core": "^8.3.7",
"@mantine/dates": "^8.3.6", "@mantine/dates": "^8.3.7",
"@mantine/form": "^8.3.6", "@mantine/form": "^8.3.7",
"@mantine/hooks": "^8.3.6", "@mantine/hooks": "^8.3.7",
"@mantine/notifications": "^8.3.6", "@mantine/notifications": "^8.3.7",
"@tanstack/react-query": "^5.90.7", "@tanstack/react-query": "^5.90.7",
"@tanstack/react-query-devtools": "^5.90.2", "@tanstack/react-query-devtools": "^5.90.2",
"@tanstack/react-query-persist-client": "^5.90.9", "@tanstack/react-query-persist-client": "^5.90.9",
@@ -25,7 +25,7 @@
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-router-dom": "^7.9.5", "react-router-dom": "^7.9.5",
"recharts": "^3.3.0" "recharts": "^3.4.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.10.0", "@types/node": "^24.10.0",
@@ -898,9 +898,9 @@
} }
}, },
"node_modules/@mantine/core": { "node_modules/@mantine/core": {
"version": "8.3.6", "version": "8.3.7",
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.6.tgz", "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.7.tgz",
"integrity": "sha512-paTl+0x+O/QtgMtqVJaG8maD8sfiOdgPmLOyG485FmeGZ1L3KMdEkhxZtmdGlDFsLXhmMGQ57ducT90bvhXX5A==", "integrity": "sha512-7gUTiUrD0pukpkSNvScGi/e+pBuBWYJP5yFHT4TFf1fmaQAA7PbNoAzUeEkPbVromDtK0VdEcpRlYpDnJhpz2g==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@@ -912,31 +912,31 @@
"type-fest": "^4.41.0" "type-fest": "^4.41.0"
}, },
"peerDependencies": { "peerDependencies": {
"@mantine/hooks": "8.3.6", "@mantine/hooks": "8.3.7",
"react": "^18.x || ^19.x", "react": "^18.x || ^19.x",
"react-dom": "^18.x || ^19.x" "react-dom": "^18.x || ^19.x"
} }
}, },
"node_modules/@mantine/dates": { "node_modules/@mantine/dates": {
"version": "8.3.6", "version": "8.3.7",
"resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-8.3.6.tgz", "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-8.3.7.tgz",
"integrity": "sha512-lSi1zvyL86SKeePH0J3vOjAR7ZIVNOrZm6ja7jAH6IBdcpQOKH8TXbrcAi5okEStvmvkne7pVaGu0VkdE8KnAw==", "integrity": "sha512-dyCvkn0aYvvZ8/bUm6EGl9epJ4pq398pzJ/wexzFPHuFdLq/kdIe5zurp/puNcn4CHraglYUflDqj0j8d9aadA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"clsx": "^2.1.1" "clsx": "^2.1.1"
}, },
"peerDependencies": { "peerDependencies": {
"@mantine/core": "8.3.6", "@mantine/core": "8.3.7",
"@mantine/hooks": "8.3.6", "@mantine/hooks": "8.3.7",
"dayjs": ">=1.0.0", "dayjs": ">=1.0.0",
"react": "^18.x || ^19.x", "react": "^18.x || ^19.x",
"react-dom": "^18.x || ^19.x" "react-dom": "^18.x || ^19.x"
} }
}, },
"node_modules/@mantine/form": { "node_modules/@mantine/form": {
"version": "8.3.6", "version": "8.3.7",
"resolved": "https://registry.npmjs.org/@mantine/form/-/form-8.3.6.tgz", "resolved": "https://registry.npmjs.org/@mantine/form/-/form-8.3.7.tgz",
"integrity": "sha512-hIu0KdP1e1Vu7KUQ+cIDpor9UE9vO7iXR3dOMu6GPF3MlHFbwnCjakW9nxSCjP1PRTMwA3m43s4GIt22XfK9tg==", "integrity": "sha512-LWC1hlVAfgWtrbCNSoGmo+c8IIXAsLSR2KS25QaIfvRVeMeG1I+RheHK/6VmqKEWChpQzjc346gi4LNApbWRZg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@@ -947,9 +947,9 @@
} }
}, },
"node_modules/@mantine/hooks": { "node_modules/@mantine/hooks": {
"version": "8.3.6", "version": "8.3.7",
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.6.tgz", "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.7.tgz",
"integrity": "sha512-liHfaWXHAkLjJy+Bkr29UsCwAoDQ/a64WrM67lksx8F0qqyjR5RQH8zVlhuOjdpQnwtlUkE/YiTvbJiPcoI0bw==", "integrity": "sha512-2xnKS8C6fTzMoq1dtOJGrh2bIBuWnVrijL/DsDk60UaUgbABh8zScwU5ndX+p02ds2bebo34LovscP9fH38uPg==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"peerDependencies": { "peerDependencies": {
@@ -957,25 +957,25 @@
} }
}, },
"node_modules/@mantine/notifications": { "node_modules/@mantine/notifications": {
"version": "8.3.6", "version": "8.3.7",
"resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-8.3.6.tgz", "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-8.3.7.tgz",
"integrity": "sha512-d3A96lyrFOVXtrwASEXALfzooKnnA60T2LclMXFF/4k27Ay5Hwza4D+ylqgxf0RkPfF9J6LhBXk72OjL5RH5Kg==", "integrity": "sha512-NvG9nVfsdoAGbyFxmb/2cCED91N6QVCUsasdcFhpBb5KNMjQbNjwXLVKgvdAA3Fhnjzcc6SF2CdxgTN625kFRA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@mantine/store": "8.3.6", "@mantine/store": "8.3.7",
"react-transition-group": "4.4.5" "react-transition-group": "4.4.5"
}, },
"peerDependencies": { "peerDependencies": {
"@mantine/core": "8.3.6", "@mantine/core": "8.3.7",
"@mantine/hooks": "8.3.6", "@mantine/hooks": "8.3.7",
"react": "^18.x || ^19.x", "react": "^18.x || ^19.x",
"react-dom": "^18.x || ^19.x" "react-dom": "^18.x || ^19.x"
} }
}, },
"node_modules/@mantine/store": { "node_modules/@mantine/store": {
"version": "8.3.6", "version": "8.3.7",
"resolved": "https://registry.npmjs.org/@mantine/store/-/store-8.3.6.tgz", "resolved": "https://registry.npmjs.org/@mantine/store/-/store-8.3.7.tgz",
"integrity": "sha512-fo86wF6nL8RPukY8cseAFQKk+bRVv3Ga/WmHJMYRsCbNleZOEZMXXUf/OVhmr1D3t+xzCzAlJe/sQ8MIS+c+pA==", "integrity": "sha512-YOK+LW7ZVHv1c/bOnStraJbyh7+uRSeo1DG2eH9M+EllXJfNG8bVhOfrnnraYZ1rbbvD/66/hSZkAZJbZeTOAw==",
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"react": "^18.x || ^19.x" "react": "^18.x || ^19.x"
@@ -3601,10 +3601,13 @@
} }
}, },
"node_modules/recharts": { "node_modules/recharts": {
"version": "3.3.0", "version": "3.4.1",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.3.0.tgz", "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.4.1.tgz",
"integrity": "sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==", "integrity": "sha512-35kYg6JoOgwq8sE4rhYkVWwa6aAIgOtT+Ob0gitnShjwUwZmhrmy7Jco/5kJNF4PnLXgt9Hwq+geEMS+WrjU1g==",
"license": "MIT", "license": "MIT",
"workspaces": [
"www"
],
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "1.x.x || 2.x.x", "@reduxjs/toolkit": "1.x.x || 2.x.x",
"clsx": "^2.1.1", "clsx": "^2.1.1",

View File

@@ -12,11 +12,11 @@
"dependencies": { "dependencies": {
"@gfazioli/mantine-text-animate": "^2.3.3", "@gfazioli/mantine-text-animate": "^2.3.3",
"@hello-pangea/dnd": "^18.0.1", "@hello-pangea/dnd": "^18.0.1",
"@mantine/core": "^8.3.6", "@mantine/core": "^8.3.7",
"@mantine/dates": "^8.3.6", "@mantine/dates": "^8.3.7",
"@mantine/form": "^8.3.6", "@mantine/form": "^8.3.7",
"@mantine/hooks": "^8.3.6", "@mantine/hooks": "^8.3.7",
"@mantine/notifications": "^8.3.6", "@mantine/notifications": "^8.3.7",
"@tanstack/react-query": "^5.90.7", "@tanstack/react-query": "^5.90.7",
"@tanstack/react-query-devtools": "^5.90.2", "@tanstack/react-query-devtools": "^5.90.2",
"@tanstack/react-query-persist-client": "^5.90.9", "@tanstack/react-query-persist-client": "^5.90.9",
@@ -27,7 +27,7 @@
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-router-dom": "^7.9.5", "react-router-dom": "^7.9.5",
"recharts": "^3.3.0" "recharts": "^3.4.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.10.0", "@types/node": "^24.10.0",

View File

@@ -1,4 +1,4 @@
import { Button, Group, Text } from '@mantine/core'; import { Button, Group, ScrollArea, Text } from '@mantine/core';
import { import {
type ColumnFiltersState, type ColumnFiltersState,
flexRender, flexRender,
@@ -11,6 +11,7 @@ import {
useReactTable, useReactTable,
} from '@tanstack/react-table'; } from '@tanstack/react-table';
import { type Dispatch, type SetStateAction, useEffect, useState } from 'react'; 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({
@@ -62,144 +63,168 @@ export function DataTable({
} }
}, [filtersEnabled]); }, [filtersEnabled]);
return ( return (
<table <ScrollArea
style={{ className={scrollAreaClasses.default}
borderSpacing: '2px 0px', classNames={scrollAreaClasses}
backgroundColor: 'var(--mantine-color-accent-0)', styles={{
scrollbar: {
paddingTop: '2.5rem',
paddingBottom: '2rem',
},
}} }}
> >
<thead <table
style={{ style={{
position: 'sticky', width: '100%',
top: 0, borderSpacing: '2px 0px',
zIndex: 1, backgroundColor: 'var(--mantine-color-accent-0)',
backgroundColor: 'var(--mantine-color-primary-0)',
}} }}
> >
{table.getHeaderGroups().map((headerGroup) => ( <thead
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th
key={header.id}
style={{
width: header.getSize(),
padding: 0,
}}
>
{header.isPlaceholder ? null : (
<>
<Button
{...{
style: {
cursor: 'pointer',
borderRadius: '0px',
backgroundColor: 'transparent',
},
px: 0,
onClick: () => {
if (header.column.getCanSort()) {
if (sorting[0].id === header.id) {
setSorting([
{
id: header.id,
desc: !sorting[0].desc,
},
]);
} else {
setSorting([
{
id: header.id,
desc: false,
},
]);
}
}
},
}}
>
<Group align='center' justify='center'>
{flexRender(header.column.columnDef.header, header.getContext())}
{{
asc: ' 🔼',
desc: ' 🔽',
}[header.column.getIsSorted() as string] ?? null}
</Group>
</Button>
{filtersEnabled ? (
<Filter
column={header.column}
options={[...new Set(data.map((item: any) => item[header.id]))]}
resultCount={table.getFilteredRowModel().rows.length}
/>
) : null}
</>
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row, index) => (
<tr
key={row.id}
style={{
color: 'var(--mantine-color-primary-0)',
backgroundColor:
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>
))}
</tbody>
<tfoot>
{table.getFooterGroups().map((footerGroup) => (
<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={{
width: '100%',
backgroundColor: 'var(--mantine-color-primary-0)',
position: 'sticky', position: 'sticky',
bottom: 0, top: 0,
zIndex: 1,
backgroundColor: 'var(--mantine-color-primary-0)',
}} }}
> >
<td colSpan={5}> {table.getHeaderGroups().map((headerGroup) => (
<Text <tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th
key={header.id}
style={{
width: header.getSize(),
padding: 0,
}}
>
{header.isPlaceholder ? null : (
<>
<Button
{...{
style: {
cursor: 'pointer',
borderRadius: '0px',
backgroundColor: 'transparent',
},
px: 0,
onClick: () => {
if (header.column.getCanSort()) {
if (sorting[0].id === header.id) {
setSorting([
{
id: header.id,
desc: !sorting[0].desc,
},
]);
} else {
setSorting([
{
id: header.id,
desc: false,
},
]);
}
}
},
}}
>
<Group align='center' justify='center'>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{{
asc: (
<Group gap={0}>
<Text c={'var(--mantine-color-accent-0)'}>🠗</Text>
<Text>🠕</Text>
</Group>
),
desc: (
<Group gap={0}>
<Text>🠗</Text>
<Text c={'var(--mantine-color-accent-0)'}>🠕</Text>
</Group>
),
}[header.column.getIsSorted() as string] ?? null}
</Group>
</Button>
{filtersEnabled ? (
<Filter
column={header.column}
options={[...new Set(data.map((item: any) => item[header.id]))]}
resultCount={table.getFilteredRowModel().rows.length}
/>
) : null}
</>
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row, index) => (
<tr
key={row.id}
style={{ style={{
color: 'var(--mantine-color-background-0)', color: 'var(--mantine-color-primary-0)',
fontWeight: 'bold', backgroundColor:
textAlign: 'center', 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();
}} }}
> >
Total Entries: {table.getRowCount()} {row.getVisibleCells().map((cell) => (
</Text> <td
</td> key={cell.id}
</tr> style={{
</tfoot> width: cell.column.getSize(),
</table> }}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
<tfoot>
{table.getFooterGroups().map((footerGroup) => (
<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={{
backgroundColor: 'var(--mantine-color-primary-0)',
position: 'sticky',
bottom: 0,
}}
>
<td colSpan={5}>
<Text
style={{
color: 'var(--mantine-color-background-0)',
fontWeight: 'bold',
textAlign: 'center',
}}
>
Total Entries: {table.getCoreRowModel().rows.length}
</Text>
</td>
</tr>
</tfoot>
</table>
</ScrollArea>
); );
} }

View File

@@ -13,7 +13,7 @@ import {
import { DatePickerInput } from '@mantine/dates'; import { DatePickerInput } from '@mantine/dates';
import type { Column, RowData } from '@tanstack/react-table'; import type { Column, RowData } from '@tanstack/react-table';
import { DebouncedInput } from 'components'; import { DebouncedInput } from 'components';
import { selectClasses } from 'styles'; import { datePickerInputClasses, selectClasses } from 'styles';
declare module '@tanstack/react-table' { declare module '@tanstack/react-table' {
//allows us to define custom properties for our columns //allows us to define custom properties for our columns
@@ -57,16 +57,7 @@ export function Filter({
<DatePickerInput <DatePickerInput
type='range' type='range'
clearable clearable
style={{ classNames={datePickerInputClasses}
width: '100%',
}}
styles={{
input: {
borderRadius: 0,
border: 'none',
color: 'var(--mantine-color-primary-0)',
},
}}
value={columnFilterValue as [Date, Date]} value={columnFilterValue as [Date, Date]}
onChange={(value) => column.setFilterValue(value)} onChange={(value) => column.setFilterValue(value)}
/> />
@@ -99,6 +90,12 @@ export function Filter({
options: selectClasses.menuSelectOptions, options: selectClasses.menuSelectOptions,
option: selectClasses.menuSelectOption, option: selectClasses.menuSelectOption,
}} }}
styles={{
input: {
border: 'none',
borderRadius: 0,
},
}}
data={options data={options
.map((option: ComboboxItem) => { .map((option: ComboboxItem) => {
return { value: option, label: option }; return { value: option, label: option };

View File

@@ -1,6 +1,7 @@
import { ActionIcon, TextInput } from '@mantine/core'; import { ActionIcon, TextInput } from '@mantine/core';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FiX } from 'react-icons/fi'; import { FiX } from 'react-icons/fi';
import { textInputClasses } from 'styles';
export function DebouncedInput({ export function DebouncedInput({
value: initialValue, value: initialValue,
@@ -34,11 +35,20 @@ export function DebouncedInput({
p={0} p={0}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onChange={(e) => setValue(e.target.value)} onChange={(e) => setValue(e.target.value)}
classNames={textInputClasses}
styles={{ styles={{
input: { input: {
borderRadius: 0, borderRadius: 0,
border: 'none', borderStyle: 'none',
color: 'var(--mantine-color-primary-0)', color: 'var(--mantine-color-primary-0)',
'&::placeholder': {
color: 'red !important',
},
},
wrapper: {
'&::placeholder': {
color: 'red !important',
},
}, },
}} }}
rightSection={ rightSection={

View File

@@ -0,0 +1,55 @@
.input {
border-radius: 0 !important;
border: none !important;
color: var(--mantine-color-primary-0) !important;
}
.levelsGroup {
background-color: var(--mantine-color-background-0) !important;
}
.day {
color: var(--mantine-color-primary-0) !important;
&:hover {
color: var(--mantine-color-background-0) !important;
background-color: var(--mantine-color-primary-0) !important;
}
&[data-weekend="true"] {
color: var(--mantine-color-accent-0) !important;
}
}
.monthsListControl {
color: var(--mantine-color-primary-0) !important;
&:hover {
color: var(--mantine-color-background-0) !important;
background-color: var(--mantine-color-primary-0) !important;
}
&[data-weekend="true"] {
color: var(--mantine-color-accent-0) !important;
}
}
.yearsListControl {
color: var(--mantine-color-primary-0) !important;
&:hover {
color: var(--mantine-color-background-0) !important;
background-color: var(--mantine-color-primary-0) !important;
}
&[data-weekend="true"] {
color: var(--mantine-color-accent-0) !important;
}
}
.weekday {
color: var(--mantine-color-border-0) !important;
}
.calendarHeaderControl {
color: var(--mantine-color-primary-0) !important;
&:hover {
color: var(--mantine-color-background-0) !important;
background-color: var(--mantine-color-primary-0) !important;
}
}
.calendarHeaderLevel {
color: var(--mantine-color-primary-0) !important;
&:hover {
color: var(--mantine-color-background-0) !important;
background-color: var(--mantine-color-primary-0) !important;
}
}

View File

@@ -3,6 +3,7 @@ import boxClasses from './Box.module.css';
import buttonClasses from './Button.module.css'; import buttonClasses from './Button.module.css';
import cardClasses from './Card.module.css'; import cardClasses from './Card.module.css';
import colorSwatchClasses from './ColorSwatch.module.css'; import colorSwatchClasses from './ColorSwatch.module.css';
import datePickerInputClasses from './DatePickerInput.module.css';
import menuClasses from './Menu.module.css'; import menuClasses from './Menu.module.css';
import modalClasses from './Modal.module.css'; import modalClasses from './Modal.module.css';
import multiSelectClasses from './MultiSelect.module.css'; import multiSelectClasses from './MultiSelect.module.css';
@@ -23,6 +24,7 @@ export {
buttonClasses, buttonClasses,
cardClasses, cardClasses,
colorSwatchClasses, colorSwatchClasses,
datePickerInputClasses,
menuClasses, menuClasses,
modalClasses, modalClasses,
multiSelectClasses, multiSelectClasses,