From 5246d310dd022ea087e57430d9bb923dfe7f7095 Mon Sep 17 00:00:00 2001 From: Ismael Dosil Date: Tue, 27 Jan 2026 20:36:04 -0300 Subject: [PATCH] feat(all-users): show action type in Last Action column - Display action type (Observation, Training, Conference Plan, etc.) alongside date - Add lastActionType field to User interface - Update CSV export with Action Type column - Fix potential undefined email issue in Edit dialog Closes CHALK-090 --- src/components/Firebase/Firebase.tsx | 39 ++++++++++--------- .../UsersComponents/AllUsersTable.tsx | 13 +++++-- src/constants/Types.tsx | 1 + .../protected/AdminViews/AllUsersPage.tsx | 2 +- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/components/Firebase/Firebase.tsx b/src/components/Firebase/Firebase.tsx index e2cceacad..8850767d3 100644 --- a/src/components/Firebase/Firebase.tsx +++ b/src/components/Firebase/Firebase.tsx @@ -4738,18 +4738,18 @@ class Firebase { * @returns {Array} Array of user objects with id, name, email, role, status, and lastLogin */ /** - * Get last action date for all users by querying activity collections. + * Get last action date and type for all users by querying activity collections. * Uses optimized approach: 5 total queries instead of per-user queries. - * @returns Map - Last action date per user + * @returns Map - Last action info per user */ - getUsersLastAction = async (): Promise> => { - const lastActionMap = new Map() + getUsersLastAction = async (): Promise> => { + const lastActionMap = new Map() - const updateIfNewer = (userId: string, date: Date | null) => { + const updateIfNewer = (userId: string, date: Date | null, type: string) => { if (!date || !userId) return const current = lastActionMap.get(userId) - if (!current || date > current) { - lastActionMap.set(userId, date) + if (!current || date > current.date) { + lastActionMap.set(userId, { date, type }) } } @@ -4773,7 +4773,7 @@ class Firebase { const data = doc.data() const userId = extractUserId(data.teacher) const endDate = data.end?.toDate?.() || null - updateIfNewer(userId, endDate) + updateIfNewer(userId, endDate, 'Observation') }) // 2. Knowledge Checks (6K+) @@ -4781,7 +4781,7 @@ class Firebase { const data = doc.data() const userId = data.answeredBy const timestamp = data.timestamp?.toDate?.() || null - updateIfNewer(userId, timestamp) + updateIfNewer(userId, timestamp, 'Training') }) // 3. Conference Plans @@ -4790,8 +4790,8 @@ class Firebase { const userId = data.teacher const created = data.dateCreated?.toDate?.() || null const modified = data.dateModified?.toDate?.() || null - updateIfNewer(userId, created) - updateIfNewer(userId, modified) + updateIfNewer(userId, created, 'Conference Plan') + updateIfNewer(userId, modified, 'Conference Plan') }) // 4. Action Plans @@ -4800,8 +4800,8 @@ class Firebase { const userId = data.teacher const created = data.dateCreated?.toDate?.() || null const modified = data.dateModified?.toDate?.() || null - updateIfNewer(userId, created) - updateIfNewer(userId, modified) + updateIfNewer(userId, created, 'Action Plan') + updateIfNewer(userId, modified, 'Action Plan') }) // 5. Emails (check both sender and recipient) @@ -4811,10 +4811,10 @@ class Firebase { const recipientId = data.recipientId const created = data.dateCreated?.toDate?.() || null const modified = data.dateModified?.toDate?.() || null - updateIfNewer(senderId, created) - updateIfNewer(senderId, modified) - updateIfNewer(recipientId, created) - updateIfNewer(recipientId, modified) + updateIfNewer(senderId, created, 'Email') + updateIfNewer(senderId, modified, 'Email') + updateIfNewer(recipientId, created, 'Email') + updateIfNewer(recipientId, modified, 'Email') }) return lastActionMap @@ -4831,6 +4831,7 @@ class Firebase { archived: boolean lastLogin: Date | null lastAction: Date | null + lastActionType: string }> = [] // Fetch programs, users, and last action data in parallel @@ -4879,6 +4880,7 @@ class Firebase { } } } + const lastActionData = lastActionMap.get(doc.id) result.push({ id: doc.id, firstName: data.firstName || '', @@ -4888,7 +4890,8 @@ class Firebase { program: programName, archived: data.archived || false, lastLogin: data.lastLogin ? data.lastLogin.toDate() : null, - lastAction: lastActionMap.get(doc.id) || null, + lastAction: lastActionData?.date || null, + lastActionType: lastActionData?.type || '', }) }) diff --git a/src/components/UsersComponents/AllUsersTable.tsx b/src/components/UsersComponents/AllUsersTable.tsx index 8785e8414..641ff2336 100644 --- a/src/components/UsersComponents/AllUsersTable.tsx +++ b/src/components/UsersComponents/AllUsersTable.tsx @@ -109,16 +109,23 @@ class AllUsersTable extends React.Component { formatDate = (d: Date | null) => d ? d.toLocaleString([], { dateStyle: 'short', timeStyle: 'short' }) : 'Never' + formatLastAction = (user: Types.User) => { + if (!user.lastAction) return 'Never' + const date = user.lastAction.toLocaleString([], { dateStyle: 'short', timeStyle: 'short' }) + return user.lastActionType ? `${date} (${user.lastActionType})` : date + } + handleExport = () => { const users = this.getFilteredUsers() - const headers = ['Last Name', 'First Name', 'Email', 'Role', 'Program', 'Status', 'Last Login', 'Last Action'] + const headers = ['Last Name', 'First Name', 'Email', 'Role', 'Program', 'Status', 'Last Login', 'Last Action', 'Action Type'] 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.program || ''), escape(u.archived ? 'Archived' : 'Active'), escape(this.formatDate(u.lastLogin)), - escape(this.formatDate(u.lastAction)) + escape(this.formatDate(u.lastAction)), + escape(u.lastActionType || '') ].join(',')) const csv = [headers.join(','), ...rows].join('\n') const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8;' }) @@ -207,7 +214,7 @@ class AllUsersTable extends React.Component { {this.formatDate(user.lastLogin)} - {this.formatDate(user.lastAction)} + {this.formatLastAction(user)} e.stopPropagation()} style={{ textAlign: 'center' }}> this.props.onUserClick?.(user)}> diff --git a/src/constants/Types.tsx b/src/constants/Types.tsx index 4ef5b9db9..6afa57414 100644 --- a/src/constants/Types.tsx +++ b/src/constants/Types.tsx @@ -234,6 +234,7 @@ export interface User { }>, lastLogin?: Date, lastAction?: Date, + lastActionType?: string, email?: string, school?: string, program?: string, diff --git a/src/views/protected/AdminViews/AllUsersPage.tsx b/src/views/protected/AdminViews/AllUsersPage.tsx index 54615d1d6..6eff6ef9b 100644 --- a/src/views/protected/AdminViews/AllUsersPage.tsx +++ b/src/views/protected/AdminViews/AllUsersPage.tsx @@ -38,7 +38,7 @@ class AllUsersPage extends React.Component { handleUserClick = (user: Types.User) => { this.setState({ selected: user, firstName: user.firstName, lastName: user.lastName, - email: user.email, editOpen: true, + email: user.email || '', editOpen: true, }) }