From 60e9c2d9fe8061cbf7523849a6ac8087dff7fb88 Mon Sep 17 00:00:00 2001 From: greg-adams Date: Tue, 22 Oct 2024 14:56:25 -0700 Subject: [PATCH 1/4] add upload series table --- api/src/graphql/uploads.sdl.ts | 1 + api/src/services/uploads/uploads.ts | 19 ++++++ .../ReportingPeriods/ReportingPeriods.tsx | 5 +- .../ReportingPeriods/columns.tsx | 6 +- .../SubrecipientTableUploadLinksDisplay.tsx | 10 +-- .../Subrecipients/Subrecipients.tsx | 4 +- .../Subrecipient/Subrecipients/columns.tsx | 9 ++- .../components/TableBuilder/TableBuilder.tsx | 17 ++++- web/src/components/TableBuilder/TableRow.tsx | 19 ++++-- web/src/components/Upload/Upload/Upload.tsx | 5 +- .../Upload/UploadCell/UploadCell.tsx | 17 +++++ .../UploadSeriesTable/UploadSeriesTable.tsx | 67 +++++++++++++++++++ .../Upload/UploadSeriesTable/columns.tsx | 39 +++++++++++ web/src/components/Upload/Uploads/Uploads.tsx | 2 +- web/src/components/Upload/Uploads/columns.tsx | 10 +-- 15 files changed, 204 insertions(+), 26 deletions(-) create mode 100644 web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.tsx create mode 100644 web/src/components/Upload/UploadSeriesTable/columns.tsx diff --git a/api/src/graphql/uploads.sdl.ts b/api/src/graphql/uploads.sdl.ts index 12f56e4b..ff8d06ee 100644 --- a/api/src/graphql/uploads.sdl.ts +++ b/api/src/graphql/uploads.sdl.ts @@ -16,6 +16,7 @@ export const schema = gql` signedUrl: String latestValidation: UploadValidation subrecipientUploads: [SubrecipientUpload!] + seriesUploads: [Upload] } type Query { diff --git a/api/src/services/uploads/uploads.ts b/api/src/services/uploads/uploads.ts index 6125a98f..8b173c28 100644 --- a/api/src/services/uploads/uploads.ts +++ b/api/src/services/uploads/uploads.ts @@ -3,6 +3,7 @@ import type { Organization, ReportingPeriod } from '@prisma/client' import type { QueryResolvers, MutationResolvers, + ResolversTypes, UploadRelationResolvers, } from 'types/graphql' import { v4 as uuidv4 } from 'uuid' @@ -127,6 +128,7 @@ export const downloadUploadFile: MutationResolvers['downloadUploadFile'] = return signedUrl } +// eslint-disable-next-line @typescript-eslint/no-unused-vars export const Upload: UploadRelationResolvers = { uploadedBy: (_obj, { root }) => { return db.upload.findUnique({ where: { id: root?.id } }).uploadedBy() @@ -154,6 +156,23 @@ export const Upload: UploadRelationResolvers = { }) return latestValidation }, + seriesUploads: async ( + _obj, + { root } + ): Promise => { + return db.upload.findMany({ + where: { + AND: { + agencyId: root?.agencyId, + expenditureCategoryId: root?.expenditureCategoryId, + reportingPeriodId: root?.reportingPeriodId, + }, + }, + orderBy: { + createdAt: 'desc', + }, + }) + }, } type UploadsWithValidationsAndExpenditureCategory = Prisma.UploadGetPayload<{ include: { validations: true; expenditureCategory: true } diff --git a/web/src/components/ReportingPeriod/ReportingPeriods/ReportingPeriods.tsx b/web/src/components/ReportingPeriod/ReportingPeriods/ReportingPeriods.tsx index 34f6b768..71c482ec 100644 --- a/web/src/components/ReportingPeriod/ReportingPeriods/ReportingPeriods.tsx +++ b/web/src/components/ReportingPeriod/ReportingPeriods/ReportingPeriods.tsx @@ -2,6 +2,7 @@ import { useState, useMemo, useCallback } from 'react' import Button from 'react-bootstrap/Button' import Modal from 'react-bootstrap/Modal' +import type { FindReportingPeriodsWithCertification } from 'types/graphql' import { useAuth } from 'web/src/auth' import { useMutation } from '@redwoodjs/web' @@ -136,7 +137,9 @@ const ReportingPeriodsList = ({ - data={reportingPeriods} columns={columns} filterableInputs={['name']} diff --git a/web/src/components/ReportingPeriod/ReportingPeriods/columns.tsx b/web/src/components/ReportingPeriod/ReportingPeriods/columns.tsx index accd1660..bfb4ebc6 100644 --- a/web/src/components/ReportingPeriod/ReportingPeriods/columns.tsx +++ b/web/src/components/ReportingPeriod/ReportingPeriods/columns.tsx @@ -1,10 +1,14 @@ import { createColumnHelper } from '@tanstack/react-table' +import type { FindReportingPeriodsWithCertification } from 'types/graphql' import { Link, routes } from '@redwoodjs/router' import { formatDateString } from 'src/utils' -const columnHelper = createColumnHelper() +const columnHelper = + createColumnHelper< + FindReportingPeriodsWithCertification['reportingPeriodsWithCertification'] + >() export const columnDefs = ({ certificationDisplay, canEdit, isUSDRAdmin }) => { const columns = [ diff --git a/web/src/components/Subrecipient/SubrecipientTableUploadLinksDisplay/SubrecipientTableUploadLinksDisplay.tsx b/web/src/components/Subrecipient/SubrecipientTableUploadLinksDisplay/SubrecipientTableUploadLinksDisplay.tsx index 4cfca5a7..1a4ed876 100644 --- a/web/src/components/Subrecipient/SubrecipientTableUploadLinksDisplay/SubrecipientTableUploadLinksDisplay.tsx +++ b/web/src/components/Subrecipient/SubrecipientTableUploadLinksDisplay/SubrecipientTableUploadLinksDisplay.tsx @@ -1,14 +1,14 @@ -import { Subrecipient } from 'types/graphql' +import { FindSubrecipients } from 'types/graphql' import { Link, routes } from '@redwoodjs/router' type SubrecipientUpload = - | Subrecipient['validSubrecipientUploads'][number] - | Subrecipient['invalidSubrecipientUploads'][number] + | FindSubrecipients['subrecipients'][number]['validSubrecipientUploads'][number] + | FindSubrecipients['subrecipients'][number]['invalidSubrecipientUploads'][number] interface SubrecipientTableUploadLinksDisplayProps { - validSubrecipientUploads: Subrecipient['validSubrecipientUploads'] - invalidSubrecipientUploads: Subrecipient['invalidSubrecipientUploads'] + validSubrecipientUploads: FindSubrecipients['subrecipients'][number]['validSubrecipientUploads'] + invalidSubrecipientUploads: FindSubrecipients['subrecipients'][number]['invalidSubrecipientUploads'] } const SubrecipientTableUploadLinksDisplay = ({ diff --git a/web/src/components/Subrecipient/Subrecipients/Subrecipients.tsx b/web/src/components/Subrecipient/Subrecipients/Subrecipients.tsx index ea71a02c..42c6377b 100644 --- a/web/src/components/Subrecipient/Subrecipients/Subrecipients.tsx +++ b/web/src/components/Subrecipient/Subrecipients/Subrecipients.tsx @@ -1,3 +1,5 @@ +import type { FindSubrecipients } from 'types/graphql' + import TableBuilder from 'src/components/TableBuilder/TableBuilder' import { columnDefs } from './columns' @@ -6,7 +8,7 @@ const Subrecipients = ({ subrecipients }) => { const filterableInputs = ['uei', 'tin'] return ( - data={subrecipients} columns={columnDefs} filterableInputs={filterableInputs} diff --git a/web/src/components/Subrecipient/Subrecipients/columns.tsx b/web/src/components/Subrecipient/Subrecipients/columns.tsx index d1c4e233..57bea181 100644 --- a/web/src/components/Subrecipient/Subrecipients/columns.tsx +++ b/web/src/components/Subrecipient/Subrecipients/columns.tsx @@ -1,10 +1,11 @@ import { createColumnHelper, ColumnDef } from '@tanstack/react-table' -import { ParsedSubrecipient, Subrecipient } from 'types/graphql' +import { ParsedSubrecipient, FindSubrecipients } from 'types/graphql' import SubrecipientTableUploadLinksDisplay from 'src/components/Subrecipient/SubrecipientTableUploadLinksDisplay/SubrecipientTableUploadLinksDisplay' import { formatDateString, formatPhoneNumber } from 'src/utils' -const columnHelper = createColumnHelper() +const columnHelper = + createColumnHelper() function formatDetails( parsedSubrecipient: ParsedSubrecipient | null | undefined @@ -46,7 +47,9 @@ const getUEI = (ueiTinCombo: string) => ueiTinCombo.split('_')[0] const getTIN = (ueiTinCombo: string) => ueiTinCombo.split('_')[1] -export const columnDefs: ColumnDef[] = [ +export const columnDefs: ColumnDef< + FindSubrecipients['subrecipients'][number] +>[] = [ columnHelper.accessor((row) => getUEI(row.ueiTinCombo), { id: 'uei', cell: (info) => info.getValue() ?? '', diff --git a/web/src/components/TableBuilder/TableBuilder.tsx b/web/src/components/TableBuilder/TableBuilder.tsx index 267f1dd6..f8011a81 100644 --- a/web/src/components/TableBuilder/TableBuilder.tsx +++ b/web/src/components/TableBuilder/TableBuilder.tsx @@ -6,6 +6,7 @@ import { getSortedRowModel, getFilteredRowModel, ColumnFiltersState, + ColumnDef, } from '@tanstack/react-table' import { Button } from 'react-bootstrap' import Table from 'react-bootstrap/Table' @@ -18,11 +19,21 @@ import TableRow from './TableRow' and sorting functionality. For documentation, visit: https://tanstack.com/table/v8/docs/guide/introduction */ -function TableBuilder({ data, columns, filterableInputs = [] }) { +interface TableBuilderProps { + data: T[] + columns: ColumnDef[] + filterableInputs?: string[] +} + +function TableBuilder({ + data, + columns, + filterableInputs = [], +}: TableBuilderProps) { const [columnFilters, setColumnFilters] = useState([]) const [sorting, setSorting] = useState([]) - const table = useReactTable({ + const table = useReactTable({ data, columns: columns, state: { @@ -41,7 +52,7 @@ function TableBuilder({ data, columns, filterableInputs = [] }) { } return ( -
+
{!!filterableInputs.length && ( diff --git a/web/src/components/TableBuilder/TableRow.tsx b/web/src/components/TableBuilder/TableRow.tsx index f5093ab8..c4e13585 100644 --- a/web/src/components/TableBuilder/TableRow.tsx +++ b/web/src/components/TableBuilder/TableRow.tsx @@ -3,11 +3,20 @@ import { flexRender } from '@tanstack/react-table' const TableRow = ({ row }) => { return ( - {row.getVisibleCells().map((cell) => ( - - ))} + {row.getVisibleCells().map((cell) => { + const cellProps = + typeof cell.column.columnDef.meta?.getCellProps === 'function' + ? cell.column.columnDef.meta.getCellProps(cell) + : {} + + cellProps.className = `${cellProps.className || '' + } border border-slate-700` + return ( + + ) + })} ) } diff --git a/web/src/components/Upload/Upload/Upload.tsx b/web/src/components/Upload/Upload/Upload.tsx index 517b725b..0c8844a9 100644 --- a/web/src/components/Upload/Upload/Upload.tsx +++ b/web/src/components/Upload/Upload/Upload.tsx @@ -7,6 +7,7 @@ import { toast } from '@redwoodjs/web/toast' import { timeTag } from 'src/lib/formatters' +import UploadSeriesTable from '../UploadSeriesTable/UploadSeriesTable' import UploadValidationButtonGroup from '../UploadValidationButtonGroup/UploadValidationButtonGroup' import UploadValidationResultsTable, { Severity, @@ -125,7 +126,7 @@ const Upload = ({ upload, queryResult }) => { )}

Upload {upload.id} details

-
+
  • @@ -169,6 +170,8 @@ const Upload = ({ upload, queryResult }) => {
+ + ) } diff --git a/web/src/components/Upload/UploadCell/UploadCell.tsx b/web/src/components/Upload/UploadCell/UploadCell.tsx index e90f5d07..f109e530 100644 --- a/web/src/components/Upload/UploadCell/UploadCell.tsx +++ b/web/src/components/Upload/UploadCell/UploadCell.tsx @@ -32,6 +32,23 @@ export const QUERY = gql` name } } + seriesUploads { + id + createdAt + expenditureCategory { + code + } + reportingPeriod { + name + } + latestValidation { + id + passed + isManual + results + createdAt + } + } createdAt updatedAt } diff --git a/web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.tsx b/web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.tsx new file mode 100644 index 00000000..d566c60a --- /dev/null +++ b/web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.tsx @@ -0,0 +1,67 @@ +import type { FindUploadById } from 'types/graphql' + +import TableBuilder from 'src/components/TableBuilder/TableBuilder' +import { columnDefs } from 'src/components/Upload/UploadSeriesTable/columns' + +export type UploadSeriesRow = + FindUploadById['upload']['seriesUploads'][number] & { + _isCurrentUpload: boolean + _isFirstValid: boolean + } + +interface UploadSeriesTableProps { + upload: FindUploadById['upload'] + seriesUploads: FindUploadById['upload']['seriesUploads'] +} + +const UploadSeriesTable = ({ + upload, + seriesUploads, +}: UploadSeriesTableProps) => { + const ecCode = upload.expenditureCategory?.code || 'Not set' + const firstValid = seriesUploads.find( + (upload) => upload.latestValidation?.passed + ) + const formattedRows: UploadSeriesRow[] = seriesUploads.map((seriesUpload) => { + return { + ...seriesUpload, + _isCurrentUpload: seriesUpload.id === upload.id, + _isFirstValid: seriesUpload.id === firstValid?.id, + } + }) + + return ( +
+

+ Upload Series from{' '} + {upload.agency.code}, EC Code{' '} + {ecCode} in period{' '} + {upload.reportingPeriod?.name} +

+
+ {firstValid ? ( + 'The upload highlighted in green will be used for Treasury Reporting.' + ) : ( + + Agency {upload.agency.code}{' '} + does not have a valid upload with code{' '} + {ecCode} to use in Treasury + reporting for{' '} + {upload.reportingPeriod?.name} + + )} +
+ +
+
+ + data={formattedRows} + columns={columnDefs} + /> +
+
+
+ ) +} + +export default UploadSeriesTable diff --git a/web/src/components/Upload/UploadSeriesTable/columns.tsx b/web/src/components/Upload/UploadSeriesTable/columns.tsx new file mode 100644 index 00000000..98cce9cd --- /dev/null +++ b/web/src/components/Upload/UploadSeriesTable/columns.tsx @@ -0,0 +1,39 @@ +import { createColumnHelper } from '@tanstack/react-table' + +import { valueAsLink, validationDisplay } from '../Uploads/columns' + +import { UploadSeriesRow } from './UploadSeriesTable' + +const columnHelper = createColumnHelper() +const getCellProps = (info) => { + return { + className: info.row.original._isFirstValid ? 'table-success' : '', + } +} + +export const columnDefs = [ + columnHelper.accessor('id', { + cell: (info) => { + const row = info.row.original + + return row._isCurrentUpload + ? `${info.getValue()} (this upload)` + : valueAsLink(info) + }, + header: 'ID', + enableSorting: false, + meta: { + getCellProps, + }, + }), + { + accessorFn: validationDisplay, + cell: (cell) => cell.getValue(), + id: 'createdAt', + header: 'Validated?', + enableSorting: false, + meta: { + getCellProps, + }, + }, +] diff --git a/web/src/components/Upload/Uploads/Uploads.tsx b/web/src/components/Upload/Uploads/Uploads.tsx index 89235955..cf2e7618 100644 --- a/web/src/components/Upload/Uploads/Uploads.tsx +++ b/web/src/components/Upload/Uploads/Uploads.tsx @@ -14,7 +14,7 @@ const UploadsList = ({ uploads }: FindUploads) => { ] return ( - data={uploads} columns={columnDefs} filterableInputs={filterableInputs} diff --git a/web/src/components/Upload/Uploads/columns.tsx b/web/src/components/Upload/Uploads/columns.tsx index 7b6a3ec1..990714a1 100644 --- a/web/src/components/Upload/Uploads/columns.tsx +++ b/web/src/components/Upload/Uploads/columns.tsx @@ -1,13 +1,13 @@ import { createColumnHelper } from '@tanstack/react-table' -import type { Upload } from 'types/graphql' +import type { FindUploads } from 'types/graphql' import { Link, routes } from '@redwoodjs/router' import { formatDateString } from 'src/utils' -const columnHelper = createColumnHelper() +const columnHelper = createColumnHelper() -function valueAsLink(cell): JSX.Element { +export function valueAsLink(cell): JSX.Element { const value = cell.getValue() return ( @@ -21,7 +21,7 @@ function valueAsLink(cell): JSX.Element { ) } -function validationDisplay(row) { +export function validationDisplay(row) { const { latestValidation } = row const { results, passed, isManual, createdAt } = latestValidation @@ -51,7 +51,7 @@ export const columnDefs = [ cell: (info) => info.getValue(), header: 'Agency', }), - columnHelper.accessor((row: Upload) => row.expenditureCategory?.code, { + columnHelper.accessor((row) => row.expenditureCategory?.code, { cell: (info) => info.getValue() ?? 'Not set', header: 'EC Code', }), From 349f1945814deedada3c3c3e2025de7f2bb56086 Mon Sep 17 00:00:00 2001 From: greg-adams Date: Tue, 22 Oct 2024 14:56:46 -0700 Subject: [PATCH 2/4] add tests --- api/src/services/uploads/uploads.test.ts | 112 +++++++++++------- .../UploadSeriesTable.mock.ts | 79 ++++++++++++ .../UploadSeriesTable.test.tsx | 23 ++++ 3 files changed, 169 insertions(+), 45 deletions(-) create mode 100644 web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.mock.ts create mode 100644 web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.test.tsx diff --git a/api/src/services/uploads/uploads.test.ts b/api/src/services/uploads/uploads.test.ts index d64a68af..e5ece1ce 100644 --- a/api/src/services/uploads/uploads.test.ts +++ b/api/src/services/uploads/uploads.test.ts @@ -1,5 +1,3 @@ -import { Upload } from '@prisma/client' - import { deleteUploadFile, s3UploadFilePutSignedUrl, @@ -104,7 +102,7 @@ describe('uploads', () => { 'returns all uploads in their agency for organization staff', async (scenario: StandardScenario) => { mockCurrentUser(scenario.user.three) - const result: Upload[] = await uploads() + const result = await uploads() const uploadsBelongToOrg = await uploadsBelongToOrganization( result, @@ -151,7 +149,7 @@ describe('uploads', () => { }) scenario('updates an upload', async (scenario: StandardScenario) => { - const original = (await upload({ id: scenario.upload.one.id })) as Upload + const original = await upload({ id: scenario.upload.one.id }) const result = await updateUpload({ id: original.id, input: { filename: 'String2' }, @@ -168,55 +166,79 @@ describe('uploads', () => { expect(deleteUploadFile).not.toHaveBeenCalled() }) - scenario( - 'returns the latest validation for an upload when there are multiple validations', - async (scenario: StandardScenario) => { - const uploadIdOne = scenario.upload.one.id - const latestValidationOfScenarioOne = - await UploadRelationResolver.latestValidation( - {}, - { - root: { - id: uploadIdOne, - agencyId: scenario.upload.one.agencyId, - createdAt: scenario.upload.one.createdAt, - filename: scenario.upload.one.filename, - reportingPeriodId: scenario.upload.one.reportingPeriodId, - updatedAt: scenario.upload.one.updatedAt, - uploadedById: scenario.upload.one.uploadedById, - }, - context: undefined, - info: undefined, - } + describe('Uploads resolver', () => { + scenario( + 'latest validations: returns the latest validation for an upload when there are multiple validations', + async (scenario: StandardScenario) => { + const uploadIdOne = scenario.upload.one.id + const latestValidationOfScenarioOne = + await UploadRelationResolver.latestValidation( + {}, + { + root: { + id: uploadIdOne, + agencyId: scenario.upload.one.agencyId, + createdAt: scenario.upload.one.createdAt, + filename: scenario.upload.one.filename, + reportingPeriodId: scenario.upload.one.reportingPeriodId, + updatedAt: scenario.upload.one.updatedAt, + uploadedById: scenario.upload.one.uploadedById, + }, + context: undefined, + info: undefined, + } + ) + + const uploadIdTwo = scenario.upload.two.id + const latestValidationOfScenarioTwo = + await UploadRelationResolver.latestValidation( + {}, + { + root: { + id: uploadIdTwo, + agencyId: scenario.upload.two.agencyId, + createdAt: scenario.upload.two.createdAt, + filename: scenario.upload.two.filename, + reportingPeriodId: scenario.upload.two.reportingPeriodId, + updatedAt: scenario.upload.two.updatedAt, + uploadedById: scenario.upload.two.uploadedById, + }, + context: undefined, + info: undefined, + } + ) + + expect(latestValidationOfScenarioOne?.createdAt).toEqual( + new Date('2024-01-27T10:32:00.000Z') + ) + expect(latestValidationOfScenarioTwo?.createdAt).toEqual( + new Date('2024-01-29T18:13:25.000Z') ) + } + ) - const uploadIdTwo = scenario.upload.two.id - const latestValidationOfScenarioTwo = - await UploadRelationResolver.latestValidation( + scenario( + 'uploads resolver: correctly returns series uploads', + async (scenario: StandardScenario) => { + const results = await UploadRelationResolver.seriesUploads( {}, { root: { - id: uploadIdTwo, - agencyId: scenario.upload.two.agencyId, - createdAt: scenario.upload.two.createdAt, - filename: scenario.upload.two.filename, - reportingPeriodId: scenario.upload.two.reportingPeriodId, - updatedAt: scenario.upload.two.updatedAt, - uploadedById: scenario.upload.two.uploadedById, + ...scenario.upload.one, }, context: undefined, info: undefined, } ) - expect(latestValidationOfScenarioOne?.createdAt).toEqual( - new Date('2024-01-27T10:32:00.000Z') - ) - expect(latestValidationOfScenarioTwo?.createdAt).toEqual( - new Date('2024-01-29T18:13:25.000Z') - ) - } - ) + expect(results).toHaveLength(2) + expect(results.map((seriesUpload) => seriesUpload.id)).toEqual([ + scenario.upload.two.id, + scenario.upload.one.id, + ]) + } + ) + }) }) describe('downloads', () => { @@ -330,7 +352,7 @@ describe('getValidUploadsInCurrentPeriod', () => { const uploads = await getValidUploadsInCurrentPeriod( scenario.organization.one, - upload.reportingPeriodId + scenario.reportingPeriod.one ) expect(uploads.map((upload) => upload.id)).toContain(upload.id) @@ -347,7 +369,7 @@ describe('getValidUploadsInCurrentPeriod', () => { const uploads = await getValidUploadsInCurrentPeriod( scenario.organization.two, - upload.reportingPeriodId + scenario.reportingPeriod.one ) expect(uploads.map((upload) => upload.id)).toContain(upload.id) @@ -363,7 +385,7 @@ describe('getValidUploadsInCurrentPeriod', () => { const uploads = await getValidUploadsInCurrentPeriod( scenario.organization.two, - upload.reportingPeriodId + scenario.reportingPeriod.one ) expect(uploads.map((upload) => upload.id)).not.toContain(upload.id) diff --git a/web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.mock.ts b/web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.mock.ts new file mode 100644 index 00000000..6b0e5d19 --- /dev/null +++ b/web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.mock.ts @@ -0,0 +1,79 @@ +import type { FindUploadById } from 'types/graphql' + +export const standardUpload = (): FindUploadById['upload'] => ({ + id: 2, + filename: 'CPF Input Template v20240524.xlsm', + createdAt: '2024-09-05T22:02:35.736Z', + updatedAt: '2024-09-05T22:02:35.736Z', + __typename: 'Upload', + agency: { + code: 'MAUSDR', + __typename: 'Agency', + }, + expenditureCategory: null, + reportingPeriod: { + name: 'First Reporting Period', + __typename: 'ReportingPeriod', + }, + uploadedBy: { + id: 1, + name: 'USDR Admin', + __typename: 'User', + }, + latestValidation: { + id: 28, + passed: true, + isManual: false, + results: { + errors: [], + }, + createdAt: '2024-10-22T00:03:57.027Z', + __typename: 'UploadValidation', + initiatedBy: { + name: 'USDR Admin', + __typename: 'User', + }, + }, +}) + +export const standardSeriesUploads = + (): FindUploadById['upload']['seriesUploads'] => [ + { + id: 10, + createdAt: '2024-10-21T23:56:49.260Z', + __typename: 'Upload', + latestValidation: { + id: 26, + passed: false, + isManual: false, + results: null, + createdAt: '2024-10-21T23:56:49.310Z', + __typename: 'UploadValidation', + }, + expenditureCategory: null, + reportingPeriod: { + name: 'First Reporting Period', + __typename: 'ReportingPeriod', + }, + }, + { + id: 2, + createdAt: '2024-09-05T22:02:35.736Z', + __typename: 'Upload', + latestValidation: { + id: 28, + passed: true, + isManual: false, + results: { + errors: [], + }, + createdAt: '2024-10-22T00:03:57.027Z', + __typename: 'UploadValidation', + }, + expenditureCategory: null, + reportingPeriod: { + name: 'First Reporting Period', + __typename: 'ReportingPeriod', + }, + }, + ] diff --git a/web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.test.tsx b/web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.test.tsx new file mode 100644 index 00000000..1dfa2964 --- /dev/null +++ b/web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.test.tsx @@ -0,0 +1,23 @@ +import { render } from '@redwoodjs/testing/web' + +import UploadSeriesTable from './UploadSeriesTable' +import { standardUpload, standardSeriesUploads } from './UploadSeriesTable.mock' + +describe('UploadSeriesTable', () => { + it('renders successfully when uploads are empty', () => { + expect(() => { + render() + }).not.toThrow() + }) + + it('renders successfully when given multiple results', () => { + expect(() => { + render( + + ) + }).not.toThrow() + }) +}) From fe1575e7091d42ee8d4b0298dc623c0213d9606d Mon Sep 17 00:00:00 2001 From: greg-adams Date: Tue, 22 Oct 2024 15:04:56 -0700 Subject: [PATCH 3/4] remove unused --- web/src/components/Upload/UploadCell/UploadCell.tsx | 6 ------ .../Upload/UploadSeriesTable/UploadSeriesTable.mock.ts | 10 ---------- 2 files changed, 16 deletions(-) diff --git a/web/src/components/Upload/UploadCell/UploadCell.tsx b/web/src/components/Upload/UploadCell/UploadCell.tsx index f109e530..6b250418 100644 --- a/web/src/components/Upload/UploadCell/UploadCell.tsx +++ b/web/src/components/Upload/UploadCell/UploadCell.tsx @@ -35,12 +35,6 @@ export const QUERY = gql` seriesUploads { id createdAt - expenditureCategory { - code - } - reportingPeriod { - name - } latestValidation { id passed diff --git a/web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.mock.ts b/web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.mock.ts index 6b0e5d19..51073a3a 100644 --- a/web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.mock.ts +++ b/web/src/components/Upload/UploadSeriesTable/UploadSeriesTable.mock.ts @@ -50,11 +50,6 @@ export const standardSeriesUploads = createdAt: '2024-10-21T23:56:49.310Z', __typename: 'UploadValidation', }, - expenditureCategory: null, - reportingPeriod: { - name: 'First Reporting Period', - __typename: 'ReportingPeriod', - }, }, { id: 2, @@ -70,10 +65,5 @@ export const standardSeriesUploads = createdAt: '2024-10-22T00:03:57.027Z', __typename: 'UploadValidation', }, - expenditureCategory: null, - reportingPeriod: { - name: 'First Reporting Period', - __typename: 'ReportingPeriod', - }, }, ] From 024d8748d446f33724fe08d6dad350e6a0af7a15 Mon Sep 17 00:00:00 2001 From: Sushil Rajeeva Bhandary Date: Tue, 10 Dec 2024 15:33:05 -0500 Subject: [PATCH 4/4] merge conflict resolution --- .../components/TableBuilder/TableBuilder.tsx | 74 ++++++++++++++----- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/web/src/components/TableBuilder/TableBuilder.tsx b/web/src/components/TableBuilder/TableBuilder.tsx index f8011a81..bdacf374 100644 --- a/web/src/components/TableBuilder/TableBuilder.tsx +++ b/web/src/components/TableBuilder/TableBuilder.tsx @@ -8,7 +8,7 @@ import { ColumnFiltersState, ColumnDef, } from '@tanstack/react-table' -import { Button } from 'react-bootstrap' +import { Button, Form } from 'react-bootstrap' import Table from 'react-bootstrap/Table' import TableHeader from './TableHeader' @@ -17,18 +17,28 @@ import TableRow from './TableRow' /* This component uses TanStack Table to add filtering and sorting functionality. - For documentation, visit: https://tanstack.com/table/v8/docs/guide/introduction + For documentation, visit: https://tanstack.com/table/v8/docs/introduction */ + +interface GlobalFilter { + label: string + checked: boolean + loading?: boolean + onChange: () => void +} + interface TableBuilderProps { data: T[] columns: ColumnDef[] filterableInputs?: string[] + globalFilter?: GlobalFilter } function TableBuilder({ data, columns, filterableInputs = [], + globalFilter, }: TableBuilderProps) { const [columnFilters, setColumnFilters] = useState([]) const [sorting, setSorting] = useState([]) @@ -47,25 +57,57 @@ function TableBuilder({ getFilteredRowModel: getFilteredRowModel(), }) - const resetColumnFilters = () => { + const resetFilters = () => { table.resetColumnFilters() + if (globalFilter) { + globalFilter.onChange() + } + } + + const renderTableBody = () => { + const rows = table.getRowModel().rows + + if (!rows.length) { + return ( +
+ + + ) + } + + return rows.map((row) => ) } return (
- {flexRender(cell.column.columnDef.cell, cell.getContext())} - + {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+ No results found +
- {!!filterableInputs.length && ( + {(!!filterableInputs.length || globalFilter) && ( - + )} {table.getHeaderGroups().map((headerGroup) => ( @@ -76,11 +118,7 @@ function TableBuilder({ /> ))} - - {table.getRowModel().rows.map((row) => ( - - ))} - + {renderTableBody()}
- - +
+ {globalFilter && ( + + )} + +
+
)