Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 14 additions & 24 deletions src/components/Teams/Team.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable react/destructuring-assignment */
import styles from './Team.module.css';
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { connect, useSelector, useDispatch } from 'react-redux';
import { Button } from 'reactstrap';
Expand Down Expand Up @@ -77,40 +77,31 @@ export function Team({
// string key for cache/DOM ids
const teamIdKey = String(props.teamId ?? '');

// localMembers is only populated after the user opens the modal or hovers.
// On mount we do NOT pre-fetch — that was firing GET /api/team/:id/users
// for every single row on page load (thousands of requests = 30s load time).
// Member counts are computed from props.team.members which already comes
// from the getAllTeams aggregation, so no extra request is needed.
const [localMembers, setLocalMembers] = useState(() => getCachedTeamMembers(teamIdKey) || null);
const [loading, setLoading] = useState(false);

useEffect(() => {
const cached = getCachedTeamMembers(teamIdKey);
if (cached && !localMembers) setLocalMembers(cached);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [teamIdKey]);

useEffect(() => {
if (!getCachedTeamMembers(teamIdKey) && teamIdKey) {
fetchTeamMembersCached(dispatch, getTeamMembers, teamIdKey)
.then(setLocalMembers)
.catch(() => {});
}
}, [dispatch, teamIdKey]);

const members = localMembers ?? props.team?.members ?? [];
const { total, active: activeCount, inactive } = computeCounts(members, loading, localMembers);
const { total, active: activeCount, inactive } = computeCounts(members, false, localMembers);

// Fire callback immediately (keeps tests & UX snappy), then refresh members
// Fire callback immediately (keeps tests & UX snappy), then refresh members.
// We do NOT reset localMembers to null or set loading=true here — doing so
// caused the count to flash back to '…' on every click. Since props.team.members
// is always available as a fallback, the count stays stable while the fresh
// fetch completes in the background.
const handleOpenMembers = () => {
clearCachedTeamMembers(teamIdKey);
setLocalMembers(null); // reset stale local state
if (typeof props.onMembersClick === 'function') {
props.onMembersClick(teamIdRaw, props.name, props.teamCode); // don't pass localMembers
}
setLoading(true);
fetchTeamMembersCached(dispatch, getTeamMembers, teamIdKey)
.then(fresh => {
setLocalMembers(fresh);
})
.catch(() => {})
.finally(() => setLoading(false));
.catch(() => {});
};

return (
Expand Down Expand Up @@ -165,9 +156,8 @@ export function Team({
onClick={handleOpenMembers}
data-testid="members-btn"
aria-label="Users"
disabled={loading}
>
{loading ? <i className="fa fa-spinner fa-spin" /> : <i className="fa fa-users" />}
<i className="fa fa-users" />
</button>
</td>

Expand Down
23 changes: 20 additions & 3 deletions src/components/Teams/TeamMembersPopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,29 @@ export const TeamMembersPopup = React.memo(props => {
const map = {};
if (Array.isArray(teamsData) && teamsData.length > 0) {
for (const m of teamsData[0]?.members || []) {
map[m.userId] = m.visible;
// Backend's getAllTeams aggregation returns each member object
// keyed by `_id` (the user's id), not `userId`. Using the wrong
// key here meant every lookup below resolved to `undefined`,
// which made the "See All" toggle appear to reset itself.
map[m._id] = m.visible;
}
}
return map;
}, [props.teamData]);

// Only render toggle rows once teamData AND its members are populated so that
// `choice` is never undefined on first mount — prevents the brief ON flash.
// props.teamData can exist with members: undefined on the first Redux update,
// so we must check members is a non-empty array too.
const isMemberVisibilityReady = useMemo(() => {
return (
Array.isArray(props.teamData) &&
props.teamData.length > 0 &&
Array.isArray(props.teamData[0]?.members) &&
props.teamData[0].members.length > 0
);
}, [props.teamData]);

useEffect(() => {
setIsValidUser(true);
setDuplicateUserAlert(false);
Expand Down Expand Up @@ -303,7 +320,7 @@ export const TeamMembersPopup = React.memo(props => {
};

const renderBody = () => {
if (showTableSpinner) {
if (showTableSpinner || !isMemberVisibilityReady) {
return (
<tr>
<td align="center" colSpan={canAssignTeamToUsers ? 6 : 5}>
Expand Down Expand Up @@ -505,7 +522,7 @@ TeamMembersPopup.propTypes = {
PropTypes.shape({
members: PropTypes.arrayOf(
PropTypes.shape({
userId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
visible: PropTypes.bool,
}),
),
Expand Down
7 changes: 6 additions & 1 deletion src/components/Teams/Teams.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ class Teams extends React.PureComponent {
await this.props.updateTeamMemeberVisibility(this.state.selectedTeamId, userId, visibility);
const freshMembers = await this.props.getTeamMembers(this.state.selectedTeamId);
this.setState({ selectedTeamMembers: freshMembers || [] });
await this.props.getAllUserTeams();
};

// NOTE: Team component calls (id, name, code) and we open immediately
Expand All @@ -402,10 +403,14 @@ class Teams extends React.PureComponent {

onTeamMembersPopupClose = () => {
this.props.clearTeamMembers();
// Do NOT clear selectedTeamId here — setting it to undefined causes
// getTeamMembers(undefined) on the next toggle, which fires a 400 error
// and briefly wipes selectedTeamData, resetting all toggles to ON.
// selectedTeamId gets overwritten correctly when the next team is opened.
this.setState({
selectedTeamId: undefined,
selectedTeam: '',
teamMembersPopupOpen: false,
selectedTeamMembers: [],
});
};

Expand Down
7 changes: 6 additions & 1 deletion src/components/Teams/ToggleSwitch/ToggleSwitch.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import style from './ToggleSwitch.module.scss';

const DEFAULT_VISIBILITY = true;

function ToggleSwitch({ switchType, UpdateTeamMembersVisibility, userId, choice }) {
const [visibility, setVisibility] = useState(choice !== undefined ? choice : DEFAULT_VISIBILITY);

useEffect(() => {
console.log('ToggleSwitch choice prop:', userId, choice);
setVisibility(choice !== undefined ? choice : DEFAULT_VISIBILITY);
}, [choice]);

const toggleVisibility = () => {
const isChecked = !visibility;
setVisibility(isChecked);
Expand Down
Loading