From b920e01156a75c9f20b6ebd4b296122948dd369c Mon Sep 17 00:00:00 2001 From: Ismael Dosil Date: Thu, 22 Jan 2026 21:02:07 -0300 Subject: [PATCH 1/4] refactor(all-users): split Actions into separate Edit and Archive columns Closes CHALK-071 --- src/components/UsersComponents/AllUsersTable.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/UsersComponents/AllUsersTable.tsx b/src/components/UsersComponents/AllUsersTable.tsx index b6561db7b..077f9acc4 100644 --- a/src/components/UsersComponents/AllUsersTable.tsx +++ b/src/components/UsersComponents/AllUsersTable.tsx @@ -175,12 +175,13 @@ class AllUsersTable extends React.Component { - Actions + Edit + Archive {paginated.length === 0 ? ( - No users found + No users found ) : paginated.map(user => ( {user.lastName} @@ -190,12 +191,14 @@ class AllUsersTable extends React.Component { {user.school} {user.archived ? 'Archived' : 'Active'} {this.formatDate(user.lastLogin)} - e.stopPropagation()} style={{ whiteSpace: 'nowrap' }}> + e.stopPropagation()} style={{ textAlign: 'center' }}> this.props.onUserClick?.(user)}> + + e.stopPropagation()} style={{ textAlign: 'center' }}> Date: Mon, 26 Jan 2026 14:47:55 -0300 Subject: [PATCH 2/4] feat(users): relocate All Users to Users tab, restrict to admin only - Remove All Users link from BurgerMenu navigation - Add All Users tab in UsersPage (visible only for admin role) - Add /LeadersAllUsers route restricted to admin - Update /AllUsers route to admin only Closes CHALK-082 Closes CHALK-083 --- src/App.tsx | 11 +++- src/components/BurgerMenu.tsx | 17 ------- src/views/protected/UsersViews/UsersPage.tsx | 53 +++++++++++++++++++- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index d227c0468..509abec3e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -639,7 +639,7 @@ class App extends React.Component { /> { render={(props: { history: H.History }) : React.ReactElement=> } + /> + } /> { /> } - {(role == Role.ADMIN || role == Role.PROGRAMLEADER || role == Role.SITELEADER) && <> - this.props.handleNavigation( (): void => { - this.setState({ menu: 16, chalkOpen: false }); - this.props.history.push("/AllUsers"); - })} - > - - - - - - } {(role == Role.ADMIN || role == Role.PROGRAMLEADER || role == Role.SITELEADER) && <> sendToSites: Array + allUsers: Types.User[] + allUsersLoading: boolean } function checkCurrent(item: string) { @@ -136,7 +139,9 @@ class UsersPage extends React.Component { archivedTeachers: [], archivedCoaches: [], propFilter: [], - sendToSites: [] + sendToSites: [], + allUsers: [], + allUsersLoading: true } } @@ -546,6 +551,29 @@ class UsersPage extends React.Component { this.setState({programData: data}) } + loadAllUsers = async () => { + if (this.props.userRole !== 'admin') return + this.setState({ allUsersLoading: true }) + try { + const users = await this.context.getAllUsers() + this.setState({ allUsers: users, allUsersLoading: false }) + } catch (e) { + console.error('Error loading all users:', e) + this.setState({ allUsersLoading: false }) + } + } + + handleAllUserClick = (user: Types.User) => { + // Could open edit dialog - for now just log + console.log('User clicked:', user) + } + + handleAllUserArchive = async (user: Types.User) => { + await this.context.db.collection('users').doc(user.id).update({ archived: !user.archived }) + this.setState(s => ({ + allUsers: s.allUsers.map(u => u.id === user.id ? { ...u, archived: !user.archived } : u) + })) + } static propTypes = { classes: PropTypes.exact({ @@ -589,6 +617,16 @@ class UsersPage extends React.Component { ) })} + {userRole === 'admin' && ( +
  • + { + this.loadAllUsers() + this.props.history.push('/LeadersAllUsers') + }}> + All Users + +
  • + )}
    • @@ -604,6 +642,19 @@ class UsersPage extends React.Component { + + userRole === 'admin' ? ( +
      + +
      + ) : ( +
      You must be an admin to access this page.
      + ) + } /> this.changePage(pageName)} From ed6d879f1dc06cf3d7ae30333e57b8115e37c0e6 Mon Sep 17 00:00:00 2001 From: Ismael Dosil Date: Mon, 26 Jan 2026 14:48:07 -0300 Subject: [PATCH 3/4] feat(all-users): show program name, move archive toggle, UI improvements - Change School column to display Program name with data lookup - Move archive toggle next to status field - Update AllUsersTable styling and layout Closes CHALK-080 Closes CHALK-081 --- src/components/Firebase/Firebase.tsx | 17 ++++++- .../UsersComponents/AllUsersTable.tsx | 45 ++++++++++--------- src/constants/Types.tsx | 1 + .../protected/AdminViews/AllUsersPage.tsx | 2 +- src/views/protected/UsersViews/UsersPage.tsx | 6 ++- 5 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/components/Firebase/Firebase.tsx b/src/components/Firebase/Firebase.tsx index 220aa7fdd..923c7c5fc 100644 --- a/src/components/Firebase/Firebase.tsx +++ b/src/components/Firebase/Firebase.tsx @@ -4744,22 +4744,35 @@ class Firebase { lastName: string email: string role: string - school: string + program: string archived: boolean lastLogin: Date | null }> = [] + // Fetch all programs to build a lookup map + const programsSnapshot = await this.db.collection('programs').get() + const programsMap = new Map() + programsSnapshot.docs.forEach(doc => { + programsMap.set(doc.id, doc.data().name || '') + }) + const usersSnapshot = await this.db.collection('users').get() usersSnapshot.docs.forEach(doc => { const data = doc.data() + // Get first program name from user's programs array + let programName = '' + if (data.programs && Array.isArray(data.programs) && data.programs.length > 0) { + const firstProgramId = data.programs[0].id || data.programs[0] + programName = programsMap.get(firstProgramId) || '' + } result.push({ id: doc.id, firstName: data.firstName || '', lastName: data.lastName || '', email: data.email || '', role: data.role || '', - school: data.school || '', + program: programName, archived: data.archived || false, lastLogin: data.lastLogin ? data.lastLogin.toDate() : null, }) diff --git a/src/components/UsersComponents/AllUsersTable.tsx b/src/components/UsersComponents/AllUsersTable.tsx index 077f9acc4..c85d59c63 100644 --- a/src/components/UsersComponents/AllUsersTable.tsx +++ b/src/components/UsersComponents/AllUsersTable.tsx @@ -20,13 +20,17 @@ import * as Types from '../../constants/Types' const TableRow = styled.tr` background-color: white; - :nth-child(odd) { background-color: #eaeaea; } + :nth-child(odd) { background-color: rgb(234, 234, 234); } &:hover { background-color: rgba(9, 136, 236, 0.4); cursor: pointer; } ` const TableCell = styled.td` - padding: 10px 8px; + padding: 4px 8px; text-align: left; + font-size: 1.25rem; + font-family: "Roboto", "Helvetica", "Arial", sans-serif; + font-weight: 400; + line-height: 1.6; ` const StatusBadge = styled.span<{ archived: boolean }>` @@ -78,7 +82,7 @@ class AllUsersTable extends React.Component { if (search) { const s = search.toLowerCase() users = users.filter(u => - `${u.firstName} ${u.lastName} ${u.email} ${u.role} ${u.school}`.toLowerCase().includes(s) + `${u.firstName} ${u.lastName} ${u.email} ${u.role} ${u.program}`.toLowerCase().includes(s) ) } if (roleFilter) users = users.filter(u => u.role === roleFilter) @@ -106,11 +110,11 @@ class AllUsersTable extends React.Component { handleExport = () => { const users = this.getFilteredUsers() - const headers = ['Last Name', 'First Name', 'Email', 'Role', 'School', 'Status', 'Last Login'] + const headers = ['Last Name', 'First Name', 'Email', 'Role', 'Program', 'Status', 'Last Login'] const escape = (val: string) => `"${(val || '').replace(/"/g, '""')}"` const rows = users.map(u => [ escape(u.lastName), escape(u.firstName), escape(u.email), - escape(this.formatRole(u.role)), escape(u.school || ''), + escape(this.formatRole(u.role)), escape(u.program || ''), escape(u.archived ? 'Archived' : 'Active'), escape(this.formatDate(u.lastLogin)) ].join(',')) @@ -129,7 +133,7 @@ class AllUsersTable extends React.Component { const roles = [...new Set(this.props.users.map(u => u.role))].sort() const SortHeader = ({ field, label }: { field: string; label: string }) => ( - this.handleSort(field)}> + this.handleSort(field)}> {label} @@ -172,33 +176,24 @@ class AllUsersTable extends React.Component { - + - Edit - Archive + Edit {paginated.length === 0 ? ( - No users found + No users found ) : paginated.map(user => ( {user.lastName} {user.firstName} {user.email} {this.formatRole(user.role)} - {user.school} - {user.archived ? 'Archived' : 'Active'} - {this.formatDate(user.lastLogin)} - e.stopPropagation()} style={{ textAlign: 'center' }}> - - this.props.onUserClick?.(user)}> - - - - - e.stopPropagation()} style={{ textAlign: 'center' }}> + {user.program} + e.stopPropagation()} style={{ display: 'flex', alignItems: 'center', gap: 8 }}> + {user.archived ? 'Archived' : 'Active'} { /> + {this.formatDate(user.lastLogin)} + e.stopPropagation()} style={{ textAlign: 'center' }}> + + this.props.onUserClick?.(user)}> + + + + ))} diff --git a/src/constants/Types.tsx b/src/constants/Types.tsx index 1624e2238..64e162e8f 100644 --- a/src/constants/Types.tsx +++ b/src/constants/Types.tsx @@ -235,5 +235,6 @@ export interface User { lastLogin?: Date, email?: string, school?: string, + program?: string, archived?: boolean } diff --git a/src/views/protected/AdminViews/AllUsersPage.tsx b/src/views/protected/AdminViews/AllUsersPage.tsx index 5d62eb418..54615d1d6 100644 --- a/src/views/protected/AdminViews/AllUsersPage.tsx +++ b/src/views/protected/AdminViews/AllUsersPage.tsx @@ -142,5 +142,5 @@ class AllUsersPage extends React.Component { } export default connect(state => ({ - isAdmin: [Role.ADMIN, Role.PROGRAMLEADER, Role.SITELEADER].includes(state.coachState.role), + isAdmin: state.coachState.role === Role.ADMIN, }))(AllUsersPage) diff --git a/src/views/protected/UsersViews/UsersPage.tsx b/src/views/protected/UsersViews/UsersPage.tsx index e15ca3818..35476993c 100644 --- a/src/views/protected/UsersViews/UsersPage.tsx +++ b/src/views/protected/UsersViews/UsersPage.tsx @@ -515,6 +515,10 @@ class UsersPage extends React.Component { let sendToSites = this.buildSiteData(coachData, siteData, programData, filter ? filter : []); this.setState({sendToSites: sendToSites}); + // Load All Users if on that route and user is admin + if (this.props.userRole === 'admin' && location.pathname === '/LeadersAllUsers') { + this.loadAllUsers() + } } @@ -644,7 +648,7 @@ class UsersPage extends React.Component { userRole === 'admin' ? ( -
      +
      Date: Mon, 26 Jan 2026 15:09:59 -0300 Subject: [PATCH 4/4] fix(deps): remove package-lock.json from gitignore Package-lock.json should be tracked to ensure consistent dependency installations Closes CHALK-084 --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index b1d06b516..37474144b 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,3 @@ public/precache-manifest.* # Project management (internal) .project/ -package-lock.json