Skip to content

[JSX] Add RecordTable and move candidate_list onto it - #11277

Draft
HenriRabalais wants to merge 15 commits into
aces:mainfrom
HenriRabalais:2026-10-28_record-table
Draft

HenriRabalais wants to merge 15 commits into
aces:mainfrom
HenriRabalais:2026-10-28_record-table

Conversation

@HenriRabalais

Copy link
Copy Markdown
Collaborator

Stacked on #11194, which is stacked on #11193. Until both merge, this diff also shows their commits.

Description

DataTable takes each row as an array matched to fields by position, and a module formats cells through getFormattedCell, which only sees that row's values. As suggested on #11251, this adds a new component rather than changing DataTable. RecordTable takes the records themselves, and each column is one object with value, display and format, so a column is defined in one place and the filter, the CSV and the screen all read from it.

It also reworks the toolbar and footer from #11249. Filters sit in a drawer on the left, a clicked record opens in a panel on the right, and one toolbar holds the table's name, its saved views, search, layout, grouping, columns, the CSV download and the table's own actions. Create and edit forms open in the same right-hand panel.

candidate_list is the first module on it. new_profile's form is split from its page so the candidate list can open it from the table. The other modules stay on DataTable and move over one PR each.

Changes

  • Added RecordTable in jsx/table/, with columns defined by value, display and format.
  • Added the filter drawer, saved views kept in the browser's local storage, sorting on several columns, grouping, a card layout, showing, hiding and reordering columns, a selection bar for actions on several rows, and a record panel.
  • Moved DataTable's filter matching into jsx/table/filterMatch.ts so both tables share it.
  • Moved candidate_list onto RecordTable and updated its integration test.
  • Split new_profile's form into NewProfileForm.js so it can open from the candidate list.
  • Added the new strings to the translation templates.

Testing Instructions

  1. Go to Access Profile.
  2. Filter by Site and DoB in the drawer, and check that the URL keeps both and that /candidate_list/?project=Pumpernickel still filters.
  3. Sort by a column, then shift-click a second one.
  4. Group by Site, then switch to Cards.
  5. Click a row and check that the record opens on the right. Move between records with the arrow keys and close with Esc.
  6. Click New Profile and check that the form opens in the panel and creates a candidate.
  7. Select two rows and download them as CSV.
  8. Save a view, reload the page, and check that it is still there.

Related Issues

Part of #11249. Supersedes #11252.

@github-actions github-actions Bot added Language: PHP PR or issue that update PHP code Language: Javascript PR or issue that update Javascript code Module: candidate_list PR or issue related to candidate_list module Module: configuration PR or issue related to configuration module Module: imaging_browser PR or issue related to imaging_browser module Module: new_profile PR or issue related to new_profile module Module: user_accounts PR or issue related to the user_accounts module labels Nov 2, 2026
@HenriRabalais HenriRabalais added Category: Feature PR or issue that aims to introduce a new feature Area: UI PR or issue related to the user interface State: Discussion required PR or issue that requires the resolution of a discussion with the relevant parties to proceed labels Nov 2, 2026
@HenriRabalais HenriRabalais self-assigned this Nov 2, 2026
Comment thread jsx/table/RecordTable.tsx
Comment on lines +25 to +37
export type Column<R> = {
name: string,
label: string,
value: (record: R) => any,
display?: (value: any, record: R) => string,
format?: (value: any, record: R) => ReactNode,
type?: ColumnType,
filter?: ColumnFilter,
hidden?: boolean,
groupable?: boolean,
sticky?: boolean,
group?: string,
};

@MaximeBICMTL MaximeBICMTL Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a little annoyed by the any type here, as it means type checking will not be able to check if values are used correctly within each column, and is exactly one of the bugs you encountered in one of your recent bug fix PRs.

I made this small experiment that shows how we can actually have type checking for value types within each column. The main issues are that the implementation is a little complex (we must encode existential types using a continuation passing style since Typescript does not have them as a first-class feature) and that the user must call col(/* column definition */) on each column, but it works without any cast, without any, and any mistake in a column definition or manipulation would result in a type error. Tell me if you have any thought.

https://www.typescriptlang.org/play/?#code/PTAEBUAsFNQJwPYHdQBcCeAHaAaUTIBLAY0lABNoBnYuQgI2rRlCsgENtQEAzZ2cu1TsAdACgMXAErJQAXlABtAHYBXALaM4eKqjrKA5jr2FDAXTFiQEFoOEAuZnGixESKvkKoy5Qjx7QzsqoaFjU4sQIyroUQuyOMkiKZvJKYqAZSgBMABx4AOQAggA2JND5BQDK6l6Q+WY46ZmKAIxZBQAyLhWg+QDCMMr1jZlKACwAnAUACoSBzj35AOLsBspRw2IWVgBUOzaw68oAtIHsVNDkoJHFGsqh2HgEJGQA1uvuoOz0CKohP95+PBoJE4FdJLAvKBCB5KHQAG6XUA8RDqL7KK5eKjpfbcJD3eHsW6wCEiUA7YASMKgPoIW7qZQAHikeAAagA+VIAbyaGWU7HU0Ecun0Rl5oEJxIAFG4EgBKRyskaZHgIODqIRSyWqIWgVkK1gmQyNAC+lmAewO1yiqFMqiEhCix0w5yopgMhvQxVgZwuVxudweuHwRFIoHeyA8BCEwNBmJCMIogUIiKuKIQ6hxGXof1A6wTqA8yAJRJ1QbJFKpXAAonBzpdafSmVJOQpGeB2VLIsFHIyOVKAPqORt3Zls9ly+Sc8CTuTT82W2nKRFwELsPNO31IgMMtAIdGgLdXKLQCuUniqZTEW1Ra3FMd6zs75TDumjlmPg21+vkEcM5mcjyGTOKgqhwPc3YhHO1rBF2b4MnKYhmrs+xQIcm51n6d4aFQTwwM4QLaiS1K8Ie7Bhs+0IeCB7CmJcZ5iN2MTPlQjiKH+zbIHgPx0tA7DKOyeAccyXGGqK7IpAoijikBoyZPygqOPk7DkKoxSoBU4qjERjgysgs6cm4igAAwpOyCgtHkWkqmqGqoLpREGRKpawAA-L04BSAAqtW+SgEpABihQdJUvnKhkJrhaAslyXmAq6vkF7FMUxwKeUUXaS5uluE5AAGAAkXJGS0ZgmqAhVGVkpW5RlNnqkIDkuU5RFRWa2wWqhLBHthDK4SGgQkiwpQxBCFAIEw+bhh8Xw-Lm3jEVwpF8RR8ElsSDFMSER4caxoDfn6wmJOyySpNJow3HBTZUCZZhylFF0sa0t2NO1loALKcLEwh7vwhBwD1a06n1piUNgGLQMExToNwfDzYeAAe5EhJREJUAxF5Xje9wGNAqCsi5VAAVKdjxKAUjJHgLGOPtDarQByQGpeEb4skJ2ySBYH3CTIgapgUp6UgTksTznD85BBnipBYurULq0iERAtyohGRK0hC77B9mBfeuqD7vNf3IrZQioNuq3OcSwPg2DlCQ9DpFw9AiPXgDQZo+S56XtejrY7jAVG6gJvkPjFtEyTCQUwDu007+dMtgzwpGgYrMpOzuOc9rIt84rU6R5n0vBBL502tLTay02IiqvVqCXXc8suYrqsq4hyHWH0RLFEC3wIIiyKe1jaNiDjeME8TcR4Ntq1UIhQ9+1XgfB0Do-COPmG01diFAA

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaximeBICMTL

thanks for putting the playground together, it makes the problem really clear.

I like the idea of checking each column against its own value type. I tried a lighter version of it in the playground: if Column<R, V> declares value and format as methods, a small col<R, V>(column): Column<R, unknown> helper that just returns its argument is enough. Each column is checked on its own (a format that doesn't match its value is a type error, and V is inferred), and the columns still sit in one array without a cast or any continuation passing. Do you see something that approach misses?

I'm leaning towards doing that, which would turn the any in RecordTable.tsx, filterMatch.ts and tableState.ts into unknown. The modules are plain JS today, so it would only help TypeScript callers for now.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of checking each column against its own value type. I tried a lighter version of it in the playground: if Column<R, V> declares value and format as methods, a small col<R, V>(column): Column<R, unknown> helper that just returns its argument is enough. Each column is checked on its own (a format that doesn't match its value is a type error, and V is inferred), and the columns still sit in one array without a cast or any continuation passing. Do you see something that approach misses?

Hard to say without seeing an implementation, but from the description that would solve the issue. 👍

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: UI PR or issue related to the user interface Category: Feature PR or issue that aims to introduce a new feature Language: Javascript PR or issue that update Javascript code Language: PHP PR or issue that update PHP code Module: candidate_list PR or issue related to candidate_list module Module: configuration PR or issue related to configuration module Module: imaging_browser PR or issue related to imaging_browser module Module: new_profile PR or issue related to new_profile module Module: user_accounts PR or issue related to the user_accounts module State: Discussion required PR or issue that requires the resolution of a discussion with the relevant parties to proceed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants