diff --git a/packages/api/router/organization/index.ts b/packages/api/router/organization/index.ts index 8b0fd04b2a..806ad37b75 100644 --- a/packages/api/router/organization/index.ts +++ b/packages/api/router/organization/index.ts @@ -24,6 +24,7 @@ type OrganizationHandlerCache = { slugRedirect: typeof import('./query.slugRedirect.handler').slugRedirect getIntlCrisis: typeof import('./query.getIntlCrisis.handler').getIntlCrisis getNatlCrisis: typeof import('./query.getNatlCrisis.handler').getNatlCrisis + forOrganizationTable: typeof import('./query.forOrganizationTable.handler').forOrganizationTable // #endregion // @@ -162,6 +163,17 @@ export const orgRouter = defineRouter({ if (!HandlerCache.getNatlCrisis) throw new Error('Failed to load handler') return HandlerCache.getNatlCrisis({ ctx, input }) }), + forOrganizationTable: publicProcedure + .input(schema.ZForOrganizationTableSchema) + .query(async ({ ctx, input }) => { + if (!HandlerCache.forOrganizationTable) + HandlerCache.forOrganizationTable = await import('./query.forOrganizationTable.handler').then( + (mod) => mod.forOrganizationTable + ) + + if (!HandlerCache.forOrganizationTable) throw new Error('Failed to load handler') + return HandlerCache.forOrganizationTable({ ctx, input }) + }), // #endregion diff --git a/packages/api/router/organization/query.forOrganizationTable.handler.ts b/packages/api/router/organization/query.forOrganizationTable.handler.ts new file mode 100644 index 0000000000..d83da841f7 --- /dev/null +++ b/packages/api/router/organization/query.forOrganizationTable.handler.ts @@ -0,0 +1,32 @@ +import { prisma } from '@weareinreach/db' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForOrganizationTableSchema } from './query.forOrganizationTable.schema' + +export const forOrganizationTable = async ({ input }: TRPCHandlerParams) => { + const results = await prisma.organization.findMany({ + where: input, + select: { + id: true, + name: true, + slug: true, + lastVerified: true, + updatedAt: true, + createdAt: true, + published: true, + deleted: true, + locations: { + select: { + id: true, + name: true, + updatedAt: true, + createdAt: true, + published: true, + deleted: true, + }, + }, + }, + orderBy: [{ deleted: 'desc' }, { name: 'asc' }], + }) + return results +} diff --git a/packages/api/router/organization/query.forOrganizationTable.schema.ts b/packages/api/router/organization/query.forOrganizationTable.schema.ts new file mode 100644 index 0000000000..8525ad6799 --- /dev/null +++ b/packages/api/router/organization/query.forOrganizationTable.schema.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' + +export const ZForOrganizationTableSchema = z + .object({ + published: z.boolean(), + deleted: z.boolean(), + }) + .partial() + .optional() +export type TForOrganizationTableSchema = z.infer diff --git a/packages/api/router/organization/schemas.ts b/packages/api/router/organization/schemas.ts index 738fc286ab..bdef2c7b0a 100644 --- a/packages/api/router/organization/schemas.ts +++ b/packages/api/router/organization/schemas.ts @@ -4,6 +4,7 @@ export * from './mutation.createNewQuick.schema' export * from './mutation.createNewSuggestion.schema' export * from './query.checkForExisting.schema' export * from './query.forLocationPage.schema' +export * from './query.forOrganizationTable.schema' export * from './query.forOrgPage.schema' export * from './query.generateSlug.schema' export * from './query.getById.schema' diff --git a/packages/ui/.storybook/decorators.tsx b/packages/ui/.storybook/decorators.tsx index 49050ef149..b1ed638562 100644 --- a/packages/ui/.storybook/decorators.tsx +++ b/packages/ui/.storybook/decorators.tsx @@ -110,7 +110,7 @@ export const Layouts = (Story: StoryFn, context: StoryContext) => { switch (layoutWrapper) { case 'centeredFullscreen': { return ( -
+
) diff --git a/packages/ui/.storybook/main.ts b/packages/ui/.storybook/main.ts index 34feb547b7..aab23ff809 100644 --- a/packages/ui/.storybook/main.ts +++ b/packages/ui/.storybook/main.ts @@ -38,7 +38,7 @@ const config: StorybookConfig = { options: { builder: { lazyCompilation: Boolean(process.env.SB_LAZY), - fsCache: Boolean(process.env.SB_CACHE), + fsCache: true, // Boolean(process.env.SB_CACHE), useSWC: true, }, nextConfigPath: path.resolve(__dirname, '../../../apps/app/next.config.mjs'), diff --git a/packages/ui/.storybook/preview.tsx b/packages/ui/.storybook/preview.tsx index e57ee3c5da..0624307684 100644 --- a/packages/ui/.storybook/preview.tsx +++ b/packages/ui/.storybook/preview.tsx @@ -1,6 +1,6 @@ import './wdyr' import { type BADGE } from '@geometricpanda/storybook-addon-badges' -import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport' +import { type INITIAL_VIEWPORTS } from '@storybook/addon-viewport' import { type Preview } from '@storybook/react' import { type WhyDidYouRenderOptions } from '@welldone-software/why-did-you-render' import { type RequestHandler, rest } from 'msw' @@ -67,7 +67,93 @@ const preview: Preview = { }, i18n, viewport: { - viewports: INITIAL_VIEWPORTS, + viewports: { + iphonex: { + name: 'iPhone X', + styles: { height: '812px', width: '375px' }, + type: 'mobile', + }, + iphonexsmax: { + name: 'iPhone XS Max', + styles: { height: '896px', width: '414px' }, + type: 'mobile', + }, + iphonese2: { + name: 'iPhone SE (2nd generation)', + styles: { height: '667px', width: '375px' }, + type: 'mobile', + }, + iphone12mini: { + name: 'iPhone 12 mini', + styles: { height: '812px', width: '375px' }, + type: 'mobile', + }, + iphone12: { + name: 'iPhone 12', + styles: { height: '844px', width: '390px' }, + type: 'mobile', + }, + iphone12promax: { + name: 'iPhone 12 Pro Max', + styles: { height: '926px', width: '428px' }, + type: 'mobile', + }, + galaxys9: { + name: 'Galaxy S9', + styles: { height: '740px', width: '360px' }, + type: 'mobile', + }, + nexus6p: { + name: 'Nexus 6P', + styles: { height: '732px', width: '412px' }, + type: 'mobile', + }, + pixel: { + name: 'Pixel', + styles: { height: '960px', width: '540px' }, + type: 'mobile', + }, + pixelxl: { + name: 'Pixel XL', + styles: { height: '1280px', width: '720px' }, + type: 'mobile', + }, + ipad: { + name: 'iPad', + styles: { height: '1024px', width: '768px' }, + type: 'tablet', + }, + ipad10p: { + name: 'iPad Pro 10.5-in', + styles: { height: '1112px', width: '834px' }, + type: 'tablet', + }, + ipad12p: { + name: 'iPad Pro 12.9-in', + styles: { height: '1366px', width: '1024px' }, + type: 'tablet', + }, + desktop1: { + name: 'Desktop - 1920x1080', + styles: { width: '1920px', height: '1080px' }, + type: 'desktop', + }, + desktop2: { + name: 'Desktop - 1366x768', + styles: { width: '1366px', height: '768px' }, + type: 'desktop', + }, + desktop3: { + name: 'Desktop - 1440x900', + styles: { width: '1440px', height: '900px' }, + type: 'desktop', + }, + desktop4: { + name: 'Desktop - 1280x720', + styles: { width: '1280px', height: '720px' }, + type: 'desktop', + }, + }, }, chromatic: { delay: 1000, @@ -132,7 +218,6 @@ declare module '@storybook/react' { layout?: 'centered' | 'fullscreen' | 'padded' layoutWrapper?: LayoutsDecorator disableStrictMode?: boolean - disableWhyDidYouRender?: boolean pseudo?: Partial> & { rootElement?: string } rqDevtools?: boolean searchContext?: SearchStateProviderProps['initState'] diff --git a/packages/ui/components/data-portal/OrganizationTable.stories.tsx b/packages/ui/components/data-portal/OrganizationTable.stories.tsx new file mode 100644 index 0000000000..51671e2583 --- /dev/null +++ b/packages/ui/components/data-portal/OrganizationTable.stories.tsx @@ -0,0 +1,19 @@ +import { type Meta, type StoryObj } from '@storybook/react' + +import { organization } from '~ui/mockData/organization' + +import { OrganizationTable } from './OrganizationTable' + +export default { + title: 'Data Portal/Tables/Organizations', + component: OrganizationTable, + parameters: { + layoutWrapper: 'centeredFullscreen', + msw: [organization.forOrganizationTable], + rqDevtools: true, + }, +} satisfies Meta + +type StoryDef = StoryObj + +export const Default = {} satisfies StoryDef diff --git a/packages/ui/components/data-portal/OrganizationTable.tsx b/packages/ui/components/data-portal/OrganizationTable.tsx new file mode 100644 index 0000000000..73790973a1 --- /dev/null +++ b/packages/ui/components/data-portal/OrganizationTable.tsx @@ -0,0 +1,391 @@ +import { ActionIcon, createStyles, Group, rem, Text, Tooltip, useMantineTheme } from '@mantine/core' +import { DateTime } from 'luxon' +import { + MantineReactTable, + type MRT_ColumnDef, + type MRT_ColumnFilterFnsState, + type MRT_ColumnFiltersState, + type MRT_SortingState, + type MRT_Virtualizer, + useMantineReactTable, +} from 'mantine-react-table' +import { useRouter } from 'next/router' +import { type Dispatch, type SetStateAction, useMemo, useRef, useState } from 'react' + +import { Icon } from '~ui/icon' +import { trpc as api } from '~ui/lib/trpcClient' + +const useStyles = createStyles((theme) => ({ + warning: { + color: theme.other.colors.tertiary.red, + }, + warningDim: { + color: theme.fn.lighten(theme.other.colors.tertiary.red, 0.3), + }, + bottomBar: { + paddingTop: rem(20), + }, +})) + +const ToolbarButtons = ({ columnFilters, setColumnFilters }: ToolbarButtonsProps) => { + const theme = useMantineTheme() + const toggle = (key: 'published' | 'deleted') => { + const current = columnFilters.find(({ id }) => key === id) + const options = key === 'published' ? [undefined, true, false] : [false, true, undefined] + const currentIdx = options.indexOf(current?.value as boolean | undefined) + const nextIdx = (currentIdx + 1) % options.length + setColumnFilters((prev) => + options[nextIdx] === undefined + ? prev.filter(({ id }) => id !== key) + : [...prev.filter(({ id }) => id !== key), { id: key, value: options[nextIdx] }] + ) + } + const publishedState = columnFilters.find(({ id }) => id === 'published')?.value as boolean | undefined + const deletedState = columnFilters.find(({ id }) => id === 'deleted')?.value as boolean | undefined + + return ( + + + toggle('published')}> + + + + + toggle('deleted')}> + + + + + + + + ) +} + +export const OrganizationTable = () => { + const { classes } = useStyles() + const router = useRouter() + const { data, isLoading, isError, isFetching } = api.organization.forOrganizationTable.useQuery(undefined, { + placeholderData: [], + select: (data) => data.map(({ locations, ...rest }) => ({ ...rest, subRows: locations })), + }) + + const columns = useMemo[number]>[]>( + () => [ + { + accessorKey: 'name', + header: 'Name', + columnFilterModeOptions: ['contains', 'fuzzy', 'startsWith', 'endsWith'], + filterVariant: 'autocomplete', + enableResizing: true, + minSize: 250, + }, + { + accessorKey: 'lastVerified', + header: 'Verified', + Cell: ({ cell, row }) => { + if (row.getParentRow()) return null + if (!cell.getValue()) + return ( + + + Never + + ) + const date = DateTime.fromJSDate(cell.getValue()) + return ( + + {date.toRelativeCalendar()} + + ) + }, + columnFilterModeOptions: ['betweenInclusive'], + filterVariant: 'date-range', + enableColumnFilterModes: false, + size: 150, + }, + { + accessorKey: 'updatedAt', + header: 'Updated', + Cell: ({ cell }) => { + if (!cell.getValue()) return null + const date = DateTime.fromJSDate(cell.getValue()) + return {date.toLocaleString(DateTime.DATETIME_SHORT)} + }, + columnFilterModeOptions: ['betweenInclusive'], + filterVariant: 'date-range', + enableColumnFilterModes: false, + size: 150, + }, + { + accessorKey: 'createdAt', + header: 'Created', + Cell: ({ cell }) => { + if (!cell.getValue()) return null + const date = DateTime.fromJSDate(cell.getValue()) + return {date.toLocaleString(DateTime.DATETIME_SHORT)} + }, + columnFilterModeOptions: ['betweenInclusive'], + filterVariant: 'date-range', + enableColumnFilterModes: false, + size: 150, + }, + { + accessorKey: 'published', + header: 'Published', + Cell: ({ cell }) => cell.getValue().toString(), + columnFilterModeOptions: ['equals'], + filterVariant: 'checkbox', + enableColumnFilterModes: false, + mantineFilterCheckboxProps: { label: 'Published?' }, + enableSorting: false, + enableColumnActions: false, + size: 110, + }, + { + accessorKey: 'deleted', + header: 'Deleted', + Cell: ({ cell }) => cell.getValue().toString(), + columnFilterModeOptions: ['equals'], + filterVariant: 'checkbox', + enableColumnFilterModes: false, + mantineFilterCheckboxProps: { label: 'Deleted?' }, + enableSorting: false, + enableColumnActions: false, + size: 100, + }, + ], + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) + + const [columnFilters, setColumnFilters] = useState([ + { id: 'deleted', value: false }, + ]) + const [columnFilterFns, setColumnFilterFns] = //filter modes + useState( + Object.fromEntries( + columns.map(({ accessorKey, columnFilterModeOptions }) => [ + accessorKey, + columnFilterModeOptions?.at(0) ?? 'equals', + ]) + ) + ) + const [globalFilter, setGlobalFilter] = useState('') + const [sorting, setSorting] = useState([ + { id: 'deleted', desc: false }, + { id: 'published', desc: true }, + { id: 'name', desc: false }, + ]) + const rowVirtualizerInstanceRef = useRef>(null) + + const getAlertBanner = () => { + switch (true) { + case isError: { + return { color: 'red', children: 'Error fetching data' } + } + case isFetching: + case isLoading: { + return { color: 'green', children: 'Loading data' } + } + default: { + return { color: 'white', children: null, sx: { backgroundColor: 'transparent' } } + } + } + } + + const table = useMantineReactTable({ + // #region Basic Props + columns, + data: data ?? [], + // #endregion + // #region Enable features / Table options + columnFilterDisplayMode: 'popover', + enableColumnFilterModes: true, + enableGlobalFilterModes: true, + enableColumnResizing: false, + enableFacetedValues: true, + enablePagination: false, + enablePinning: true, + enableRowActions: true, + enableRowNumbers: false, + enableRowVirtualization: true, + enableExpanding: true, + enableMultiRowSelection: true, + enableRowSelection: (row) => !row.getParentRow(), + enableHiding: true, + getRowId: (originalRow) => originalRow.id, + isMultiSortEvent: () => true, + positionGlobalFilter: 'left', + rowCount: data?.length ?? 0, + rowVirtualizerInstanceRef, + rowVirtualizerProps: { overscan: 5 }, + // #endregion + // #region State + initialState: { + columnPinning: { left: ['mrt-row-expand', 'mrt-row-select', 'mrt-row-actions', 'name'] }, + columnVisibility: { + published: false, + deleted: false, + }, + showColumnFilters: false, + showGlobalFilter: true, + }, + state: { + columnFilterFns, + columnFilters, + globalFilter, + isLoading, + showAlertBanner: getAlertBanner !== undefined, + showProgressBars: isFetching, + sorting, + density: 'xs', + }, + // #endregion + // #region Mantine component props to be passed down + mantinePaperProps: { miw: '85%' }, + mantineProgressProps: ({ isTopToolbar }) => ({ style: { display: isTopToolbar ? 'block' : 'none' } }), + mantineSelectCheckboxProps: ({ row }) => ({ style: { display: row.getCanSelect() ? 'block' : 'none' } }), + mantineTableBodyCellProps: ({ row }) => ({ + sx: (theme) => ({ + textDecoration: row.original.deleted ? 'line-through' : 'none', + color: row.original.published ? undefined : theme.other.colors.secondary.darkGray, + }), + }), + mantineToolbarAlertBannerProps: getAlertBanner(), + mantineTableProps: { striped: true }, + // #endregion + // #region Override sections + renderToolbarInternalActions: () => ( + + ), + renderBottomToolbar: ({ table }) => { + if (table.getPreFilteredRowModel().rows.length !== table.getFilteredRowModel().rows.length) { + return ( +
+ + Showing {table.getFilteredRowModel().rows.length} of{' '} + {table.getPreFilteredRowModel().rows.length} results + +
+ ) + } + return ( +
+ {table.getFilteredRowModel().rows.length} results +
+ ) + }, + renderRowActions: ({ row }) => { + const handleView = () => { + const parent = row.getParentRow() + if (parent) { + router.push({ + pathname: '/org/[slug]/[orgLocationId]', + query: { + slug: parent.original.slug, + orgLocationId: row.original.id, + }, + }) + } else { + router.push({ + pathname: '/org/[slug]', + query: { + slug: row.original.slug, + }, + }) + } + } + const handleEdit = () => { + const parent = row.getParentRow() + if (parent) { + router.push({ + pathname: '/org/[slug]/[orgLocationId]/edit', + query: { + slug: parent.original.slug, + orgLocationId: row.original.id, + }, + }) + } else { + router.push({ + pathname: '/org/[slug]/edit', + query: { + slug: row.original.slug, + }, + }) + } + } + + return ( + + + + + + + + + + + + + ) + }, + // #endregion + // #region Events + onColumnFilterFnsChange: setColumnFilterFns, + onColumnFiltersChange: setColumnFilters, + onGlobalFilterChange: setGlobalFilter, + onSortingChange: setSorting, + // #endregion + }) + + return +} + +interface ToolbarButtonsProps { + columnFilters: MRT_ColumnFiltersState + setColumnFilters: Dispatch> +} diff --git a/packages/ui/icon/index.tsx b/packages/ui/icon/index.tsx index e94c3cb0d9..6bd6212bc9 100644 --- a/packages/ui/icon/index.tsx +++ b/packages/ui/icon/index.tsx @@ -1,6 +1,6 @@ -import { Icon as Iconify, type IconifyIconProps } from '@iconify/react' +import { Icon as Iconify, type IconifyIconProps, type IconProps } from '@iconify/react' import { createStyles } from '@mantine/core' -import { type RefAttributes, type SVGProps } from 'react' +import { forwardRef, type SVGProps } from 'react' import { type LiteralUnion } from 'type-fest' import { iconList } from './iconList' @@ -19,10 +19,21 @@ const useStyles = createStyles((theme, { block }: IconStylesParams) => ({ }, })) -export const Icon = ({ icon, block, className, ...props }: CustomIconProps) => { - const { classes, cx } = useStyles({ block }) - return -} +export const Icon = forwardRef( + ({ icon, block, className, ...props }, ref) => { + const { classes, cx } = useStyles({ block }) + return ( + + ) + } +) +Icon.displayName = '@weareinreach/ui/icon' export type IconList = (typeof iconList)[number] interface IconStylesParams { /** Sets `display: 'block'` */ @@ -34,4 +45,4 @@ interface CustomIconifyIconProps extends IconifyIconProps, IconStylesParams { } type IconElementProps = SVGProps -type CustomIconProps = IconElementProps & CustomIconifyIconProps & { ref?: RefAttributes } +type CustomIconProps = IconElementProps & CustomIconifyIconProps //& { ref?: RefAttributes } diff --git a/packages/ui/mockData/organization.ts b/packages/ui/mockData/organization.ts index 33f1d4a551..2be8cca081 100644 --- a/packages/ui/mockData/organization.ts +++ b/packages/ui/mockData/organization.ts @@ -1,3 +1,5 @@ +import { faker } from '@faker-js/faker' + import { type ApiOutput } from '@weareinreach/api' import { getTRPCMock } from '~ui/lib/getTrpcMock' @@ -271,6 +273,50 @@ export const organization = { path: ['organization', 'getIdFromSlug'], response: organizationData.getIdFromSlug, }), + forOrganizationTable: getTRPCMock({ + path: ['organization', 'forOrganizationTable'], + response: () => { + const totalRecords = 1000 + faker.seed(1024) + const data: ApiOutput['organization']['forOrganizationTable'] = [] + + for (let index = 0; index < totalRecords; index++) { + const lastVerified = faker.date.past() + const updatedAt = faker.date.past({ refDate: lastVerified }) + const createdAt = faker.date.past({ refDate: updatedAt }) + const locations: NonNullable[number]['locations'] = + [] + + const totalLocations = faker.number.int({ min: 0, max: 7 }) + + for (let locIdx = 0; locIdx < totalLocations; locIdx++) { + const updatedAt = faker.date.past({ refDate: lastVerified }) + const createdAt = faker.date.past({ refDate: updatedAt }) + locations.push({ + id: `oloc_${faker.string.alphanumeric({ length: 26, casing: 'upper' })}`, + name: `${faker.location.street()} location`, + updatedAt, + createdAt, + published: faker.datatype.boolean(0.9), + deleted: faker.datatype.boolean(0.05), + }) + } + + data.push({ + id: `orgn_${faker.string.alphanumeric({ length: 26, casing: 'upper' })}`, + name: faker.company.name(), + slug: faker.lorem.slug(3), + lastVerified: faker.helpers.maybe(() => lastVerified, { probability: 0.9 }) ?? null, + updatedAt, + createdAt, + published: faker.datatype.boolean(0.9), + deleted: faker.datatype.boolean(0.05), + locations, + }) + } + return data + }, + }), } type Data = Partial<{ diff --git a/packages/ui/package.json b/packages/ui/package.json index 2ee18bc245..66b9320639 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -137,6 +137,7 @@ "i18next-http-backend": "2.2.1", "i18next-intervalplural-postprocessor": "3.0.0", "luxon": "3.4.0", + "mantine-react-table": "1.1.1", "merge-anything": "5.1.7", "msw": "1.2.3", "msw-storybook-addon": "1.8.0", @@ -205,6 +206,7 @@ "i18next-http-backend": "^2", "i18next-intervalplural-postprocessor": "^3", "luxon": "^3", + "mantine-react-table": "^1", "next": "^13", "next-auth": "^4", "next-i18next": "^14.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f9fbdeaa2..e6b81c4f88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1709,6 +1709,9 @@ importers: luxon: specifier: 3.4.0 version: 3.4.0 + mantine-react-table: + specifier: 1.1.1 + version: 1.1.1(@emotion/react@11.11.1)(@mantine/core@6.0.19)(@mantine/dates@6.0.19)(@mantine/hooks@6.0.19)(@tabler/icons-react@2.30.0)(react-dom@18.2.0)(react@18.2.0) merge-anything: specifier: 5.1.7 version: 5.1.7 @@ -10152,10 +10155,23 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + /@tanstack/react-virtual@3.0.0-beta.54(react@18.2.0): + resolution: {integrity: sha512-D1mDMf4UPbrtHRZZriCly5bXTBMhylslm4dhcHqTtDJ6brQcgGmk8YD9JdWBGWfGSWPKoh2x1H3e7eh+hgPXtQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 + dependencies: + '@tanstack/virtual-core': 3.0.0-beta.54 + react: 18.2.0 + dev: true + /@tanstack/table-core@8.9.3: resolution: {integrity: sha512-NpHZBoHTfqyJk0m/s/+CSuAiwtebhYK90mDuf5eylTvgViNOujiaOaxNDxJkQQAsVvHWZftUGAx1EfO1rkKtLg==} engines: {node: '>=12'} + /@tanstack/virtual-core@3.0.0-beta.54: + resolution: {integrity: sha512-jtkwqdP2rY2iCCDVAFuaNBH3fiEi29aTn2RhtIoky8DTTiCdc48plpHHreLwmv1PICJ4AJUUESaq3xa8fZH8+g==} + dev: true + /@terraformer/wkt@2.2.0: resolution: {integrity: sha512-i33rTSqPtmO4sRdeznI0IEc9gpIZZIXN5kGhZ4rTwVtDccDKL3h4uia9cmWdRJlJMlG4Febxatw5b9ylI5YYuA==} @@ -18712,6 +18728,30 @@ packages: tmpl: 1.0.5 dev: true + /mantine-react-table@1.1.1(@emotion/react@11.11.1)(@mantine/core@6.0.19)(@mantine/dates@6.0.19)(@mantine/hooks@6.0.19)(@tabler/icons-react@2.30.0)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-sKYZ+S7pS1HJZrKKmLUFUaC+9zYODgBoFwpkO12YUgAQ3e/vIVNwgaGHvbnfoZfW8BZ8Pc69KlWx6+EZYJU8Bg==} + engines: {node: '>=14'} + peerDependencies: + '@emotion/react': '>=11' + '@mantine/core': '>=6' + '@mantine/dates': '>=6' + '@mantine/hooks': '>=6' + '@tabler/icons-react': '>=2.23.0' + react: '>=18.0 || 18' + react-dom: '>=18.0 || 18' + dependencies: + '@emotion/react': 11.11.1(@types/react@18.2.20)(react@18.2.0) + '@mantine/core': 6.0.19(@emotion/react@11.11.1)(@mantine/hooks@6.0.19)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0) + '@mantine/dates': 6.0.19(@mantine/core@6.0.19)(@mantine/hooks@6.0.19)(dayjs@1.11.9)(react@18.2.0) + '@mantine/hooks': 6.0.19(react@18.2.0) + '@tabler/icons-react': 2.30.0(react@18.2.0) + '@tanstack/match-sorter-utils': 8.8.4 + '@tanstack/react-table': 8.9.3(react-dom@18.2.0)(react@18.2.0) + '@tanstack/react-virtual': 3.0.0-beta.54(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + /map-obj@1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'}