Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Table Actions pattern and default columns #998

Merged
merged 23 commits into from
Jan 6, 2025

Conversation

martha
Copy link
Contributor

@martha martha commented Dec 16, 2024

Description

GH issue: https://github.com/open-path/Green-River/issues/6761

I opened this PR against #989, it should be retargeted to the base branch when that is merged. Both PRs should ideally go out in the same release.

Summary of changes:

  • Update all in-scope tables (see sheet) to use the new Table Actions pattern and to have the correct columns displayed. Remove any row-click behavior for these tables.
  • (Attempt to) Standardize common columns and language
    • For example, always display an enrollment Entry Date column as an exact date with a relative date tooltip, and the reverse for Last Updated. This involved adding a new component DateWithRelativeTooltip, kind of like the inverse of RelativeDateDisplay.
    • Always use "View Client" language (as opposed to "Client Profile")
  • Consolidate commonly used table row action definitions
  • Make some changes to the TableRowActions api to enable the Bulk Services use case.
    • Enable the primary action to be a React Node
    • Add the loading prop passed from the generic table

Other todos:

Depends on hmis-warehouse PR: (to do)

Type of change

  • Bug fix
  • New feature (adds functionality)
  • Code clean-up / housekeeping
  • Dependency update

Checklist before requesting review

  • I have performed a self-review of my code
  • I have run the code that is being changed under ideal conditions, and it doesn't fail
  • My code includes comments and/or descriptive variable names to help other engineers understand the intent (or not applicable)
  • My code follows the style guidelines of this project (eslint)
  • I have updated the documentation (or not applicable)
  • If it's not obvious how to test this change, I have provided testing instructions in this PR or the related issue

src/components/elements/table/GenericTable.tsx Outdated Show resolved Hide resolved
@@ -11,11 +12,10 @@ import {
} from '@/types/gqlTypes';
import { generateSafePath } from '@/utils/pathEncoding';

const columns: ColumnDef<ServiceTypeConfigFieldsFragment>[] = [
const COLUMNS: ColumnDef<ServiceTypeConfigFieldsFragment>[] = [
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We had briefly discussed storing all column definitions centrally, in a code-version of the beautiful column spreadsheet. In the end I didn't decide to do that in this PR, because I was worried it would be too much code churn for now. But I'm still somewhat interested in the idea -- you can see in this PR that we have a mix of approaches, some columns defined inline, some defined and exported and used by other components, and some defined in a util file.

I did try to standardize around only using useMemo/useCallback for columns and table actions when they have dependencies in the component props/state, and otherwise defining them in the same file but outside the component, like here. Let me know if you think this is a good approach.

src/modules/hmis/components/RelativeDateDisplay.tsx Outdated Show resolved Hide resolved
record.enrollment.client.id,
record.enrollment.id
),
state: { backToLabel: project.projectName },
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moving this into the table action enables removal of the rowLinkState prop on GenericTable

[]
);

const getTableRowActions = useCallback(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changing this table is out of scope of the ticket (not in the spreadsheet), but it was an especially easy example to update to the new pattern since it already used an Action Button column, so I pulled it in.

header: 'Name',
key: 'name',
render: (client) => <ClientName client={asClient(client)} linkToProfile />,
},
linkedNameNewTab: {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed those that weren't used anymore, but there are still a few here (like linkedNameNewTab) that are used in out-of-scope table that I didn't update.

@@ -6,24 +6,31 @@ import { parseAndFormatDate, serviceDetails } from '@/modules/hmis/hmisUtil';
import {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe this file should be renamed to util.ts?

@martha martha requested a review from gigxz December 16, 2024 19:06
@@ -65,18 +65,6 @@ export const generateAssessmentPath = (
});
};

export const assessmentRowLinkTo = (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed this in favor of keeping the assessment link consistent. I think this means that now, any table column that links to an assessment will open it in a household context if applicable. I'm not 100% confident in this change, maybe there was a good reason for the Client assessments page to link to the individual assessment view? I'm torn about whether it seems better to be consistent or maintain the previous behavior, let me know what you think.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This was a design decision so lets review with design before reverting it

Copy link
Contributor Author

@martha martha Jan 2, 2025

Choose a reason for hiding this comment

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

I see, thanks. Let's keep the behavior. I think we can still simplify the code by using generateAssessmentPath's individualViewOnly. Existing behavior that this PR replicates per 004ea99:

  • Household Assessments (qa example -> Household toggle) opens the assessment in household context if applicable
  • Enrollment Assessments (qa example) opens the assessment in household context if applicable
  • Client Assessments (qa example) always opens the assessment in individual context
  • Project Assessments (qa example) always opens the assessment in individual context

secondaryActions?.map((s) => {
return {
...s,
ariaLabel: s.ariaLabel || `${s.title}, ${recordName}`,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to support multiple ways of specifying the aria label? I wonder if we should just make it required on the primary/secondary action definitions, to simplify the logic. (Instead of inferring them based on recordName and title)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm yeah I agree that it's not worth the extra code complexity. But I also don't necessarily think we should make ariaLabel required on CommonMenuItem. In general, adding additional aria labels that express something beyond what the button text itself says should be optional.

I'm proposing this middle ground where the primary action's default aria label is set using title and recordName, if not provided in props, while secondary actions can "fend for themselves" in terms of aria labels. I think it's an ok balance. Let me know what you think, though, I'm happy to reconfigure it.

@gigxz

This comment was marked as outdated.

@@ -247,7 +243,12 @@ const GenericTable = <T extends { id: string }>({
width: def.width,
}}
>
<strong>{def.header}</strong>
{def.header ? (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Continuing the thread from here:

{def.key || def.someOtherFieldThatsRequiredIfHeaderIsMissing}

If you agree, I was thinking that based on the comment here,

unique key for element. if not provided, header is used

We probably don't really need to add an extra someOtherFieldThatsRequiredIfHeaderIsMissing, because key should serve exactly that purpose.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice -- sounds good, I see that with the union types you achieved that required-ness of header or key. Looks good!

@@ -54,7 +56,7 @@ const RelativeDateDisplay = ({
<Tooltip
title={
<Typography component='span' variant='inherit'>
{formattedDate}
{formattedDate} {tooltipSuffixText}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved over from other PR #989 (comment)

secondaryActionConfigs={[
{
...getViewEnrollmentMenuItem(row.enrollment, {
id: clientId,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the use case for #989 (comment)

) => {
return {
title: 'View Enrollment',
key: 'enrollment',
ariaLabel: `View Enrollment, ${clientBriefName(client)} ${entryExitRange(enrollment)}`,
ariaLabel: `View Enrollment, ${client.hasOwnProperty('firstName') ? clientBriefName(client as ClientNameFragment) : ''} ${entryExitRange(enrollment)}`,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Type cast shouldn't be needed / I don't think Pick<ClientNameFragment, 'id'> should be permitted -- I think this is same as other comment on the household PR?

Copy link
Contributor Author

@martha martha Jan 3, 2025

Choose a reason for hiding this comment

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

Hm, yes it's the same comment; I moved this change over from the other PR, where it wasn't relevant. But I agree with you that it's a bit messy and could be improved. It fixes the typescript issue arising in ClientServicesPage, where we don't have a client fragment to pass, only client ID - see #998 (comment). The Client Services Page overrides the aria label that calls for the client ID anyhow, so it's purely a typescript issue.

A couple alternative ideas:

  • Just avoid getViewEnrollmentMenuItem on ClientServicesPage - ok, but leads to some duplication
  • Use client from dashboard context - seems like a bit of a hassle to keep Typescript happy, but maybe ok?
  • edit: or the getViewEnrollmentMenuItem function could accept both clientId (required, for url) and client (optional; if provided, then used in aria label)? Or it could require one or the other?

What do you prefer?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah I see, thanks. I didn't realize there really was a called that only passed id. I think the approach you added with eee782f is fine but we should not need the cast as ClientNameFragment

@martha
Copy link
Contributor Author

martha commented Jan 6, 2025

Thanks @gigxz :)

I think this is ready -- what's our typical process for deploy branches? Shall I merge this PR into #989 and then treat that branch as the deploy branch? Should I rename (or merge that branch into a new branch with a clearer name) like deploy/6761-table-actions or similar?

@gigxz
Copy link
Collaborator

gigxz commented Jan 6, 2025

@martha either way is fine (using existing branch name or new branch name)! Lets just make sure that we update the backend test branch greenriver/hmis-warehouse#5012 to have the same branch name, and that shouldn't get merged until the frontend branch is merged.

@martha martha merged commit 7821056 into martha/6825-hh-member-row Jan 6, 2025
1 check passed
@martha martha deleted the martha/6761-tables branch January 6, 2025 21:55
martha added a commit that referenced this pull request Jan 6, 2025
* Update Project Enrollment tables

* fix a11y

* More accessibility fixes

* Simplify and resolve todos

* Add storybook story

* Remove errant comment

* Add comments

* Update api of TableRowActions

* Fix issue with fulColSpan

* Right-align table row actions

Co-authored-by: Gig <[email protected]>

* Remove comment

* Replace overrideTableBody with more understandable API

* Implement PR suggestion of moving table row to column def

* Cleanup and add comments

* Port over relevant changes from other PR

* Refactor to use renderCellContents

* Revert out-of-scope change

* Add todo

* Remove circular dependency

* Clean up

* Centralize action column base attrs

* PR feedback

* Fix typescript problems

* Implement Table Actions pattern and default columns (#998)

* Implement Table Actions pattern and default columns

* Add random comment

* Add storybook for table actions and fix cde story

* Implement PR suggestion of moving table row to column def

* add todo

* Finish refactor to use column def instead of getTableAction

* Fix bulk services loading

* Provide a visually hidden header when header isnt provided

* Keep existing behavior of individual/household assessment viewing

* Fix typescript

* Add accessible text for RelativeDateDisplay

* implement one possible approach for client ID typescript

* Remove unnecessary cast

---------

Co-authored-by: Gig Ashton <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants