diff --git a/src/ops-console/components/campaignSetup/useCampaignReach.ts b/src/ops-console/components/campaignSetup/useCampaignReach.ts index 069be7b6c2..ac0eec59cb 100644 --- a/src/ops-console/components/campaignSetup/useCampaignReach.ts +++ b/src/ops-console/components/campaignSetup/useCampaignReach.ts @@ -21,7 +21,7 @@ export const useCampaignReach = (selectedSchoolIds: string[]) => { if ( selectedSchoolIds.length === 0 || !api.getParentWhatsappClassesBySchoolId || - !api.getParentWhatsappParentPhonesByClassId + !api.getCampaignParentsInGroupBySchoolIds ) { setCampaignReach(emptyReach); return; @@ -29,39 +29,21 @@ export const useCampaignReach = (selectedSchoolIds: string[]) => { setLoadingReach(true); try { - const schoolClasses = await Promise.all( - selectedSchoolIds.map((schoolId) => - api.getParentWhatsappClassesBySchoolId!(schoolId), - ), - ); - - const groupedClasses = schoolClasses - .flat() - .filter( - (classRow) => - classRow.group_id && String(classRow.group_id).trim() !== '', - ); + const schoolClasses = + await api.getParentWhatsappClassesBySchoolId(selectedSchoolIds); - const memberLists = await Promise.all( - groupedClasses.map(async (classRow) => { - try { - const phones = await api.getParentWhatsappParentPhonesByClassId!( - classRow.id, - ); - return Array.from(new Set(phones)); - } catch { - return []; - } - }), + const groupedClasses = schoolClasses.filter( + (classRow) => + classRow.group_id && String(classRow.group_id).trim() !== '', ); + const memberCount = + await api.getCampaignParentsInGroupBySchoolIds(selectedSchoolIds); + if (!mounted) return; setCampaignReach({ groupCount: groupedClasses.length, - memberCount: memberLists.reduce( - (total, members) => total + members.length, - 0, - ), + memberCount, }); } catch (error) { logger.error('Failed to load campaign reach:', error); diff --git a/src/ops-console/pages/Parentwhatsappinvite/ParentWhatsappInvitationPageService.ts b/src/ops-console/pages/Parentwhatsappinvite/ParentWhatsappInvitationPageService.ts index cf73f58d41..f141f818ea 100644 --- a/src/ops-console/pages/Parentwhatsappinvite/ParentWhatsappInvitationPageService.ts +++ b/src/ops-console/pages/Parentwhatsappinvite/ParentWhatsappInvitationPageService.ts @@ -464,7 +464,7 @@ const fetchClassesForSchool = async ( api: ApiHandler, schoolId: string, ): Promise => { - return await api.getParentWhatsappClassesBySchoolId(schoolId); + return await api.getParentWhatsappClassesBySchoolId([schoolId]); }; // Fetches WhatsApp group details via parent WhatsApp dedicated RPC path. diff --git a/src/services/api/ApiHandler.ts b/src/services/api/ApiHandler.ts index 3c21d9786b..2ea7981b21 100644 --- a/src/services/api/ApiHandler.ts +++ b/src/services/api/ApiHandler.ts @@ -1914,7 +1914,7 @@ export class ApiHandler implements ServiceApi { return await this.s.getClassesBySchoolId(schoolId); } - public async getParentWhatsappClassesBySchoolId(schoolId: string): Promise< + public async getParentWhatsappClassesBySchoolId(schoolIds: string[]): Promise< { id: string; name: string; @@ -1927,7 +1927,7 @@ export class ApiHandler implements ServiceApi { 'Parent WhatsApp class lookup is not implemented in current API service.', ); } - return await this.s.getParentWhatsappClassesBySchoolId(schoolId); + return await this.s.getParentWhatsappClassesBySchoolId(schoolIds); } public async getParentWhatsappParentPhonesByClassId( @@ -1940,6 +1940,18 @@ export class ApiHandler implements ServiceApi { } return await this.s.getParentWhatsappParentPhonesByClassId(classId); } + + public async getCampaignParentsInGroupBySchoolIds( + schoolIds: string[], + ): Promise { + if (!this.s.getCampaignParentsInGroupBySchoolIds) { + throw new Error( + 'Campaign parents-in-group metrics lookup is not implemented in current API service.', + ); + } + return await this.s.getCampaignParentsInGroupBySchoolIds(schoolIds); + } + public async createAutoProfile( languageDocId: string | undefined, tcVersion: number, diff --git a/src/services/api/ServiceApi.ts b/src/services/api/ServiceApi.ts index 6823cebdd0..fc0fc5aca0 100644 --- a/src/services/api/ServiceApi.ts +++ b/src/services/api/ServiceApi.ts @@ -2855,8 +2855,8 @@ export interface ServiceApi { getClassesBySchoolId(schoolId: string): Promise[]>; - // Parent WhatsApp Invitation: lightweight class lookup for invite workflow. - getParentWhatsappClassesBySchoolId?: (schoolId: string) => Promise< + // Parent WhatsApp Invitation: lightweight class lookup for selected schools. + getParentWhatsappClassesBySchoolId?: (schoolIds: string[]) => Promise< { id: string; name: string; @@ -2870,6 +2870,13 @@ export interface ServiceApi { classId: string, ) => Promise; + /** + * Returns the seven-day parents-in-group total for selected campaign schools. + */ + getCampaignParentsInGroupBySchoolIds?: ( + schoolIds: string[], + ) => Promise; + /** * Creates a auto student profile for a parent and returns the student object * @param {string} languageDocId - languageDocId is `Language` doc id diff --git a/src/services/api/SqliteApi.ts b/src/services/api/SqliteApi.ts index 5c9cf2cea8..fcbb860766 100644 --- a/src/services/api/SqliteApi.ts +++ b/src/services/api/SqliteApi.ts @@ -7316,7 +7316,7 @@ order by } // Parent WhatsApp Invitation: class lookup with group/invite fields. - async getParentWhatsappClassesBySchoolId(schoolId: string): Promise< + async getParentWhatsappClassesBySchoolId(schoolIds: string[]): Promise< { id: string; name: string; @@ -7329,7 +7329,7 @@ order by 'Parent WhatsApp class lookup is not implemented in Supabase API.', ); } - return await this._serverApi.getParentWhatsappClassesBySchoolId(schoolId); + return await this._serverApi.getParentWhatsappClassesBySchoolId(schoolIds); } // Parent WhatsApp Invitation: parent phones from class_user -> user join. diff --git a/src/services/api/SupabaseApi.ts b/src/services/api/SupabaseApi.ts index ed9876016f..a6b726265a 100644 --- a/src/services/api/SupabaseApi.ts +++ b/src/services/api/SupabaseApi.ts @@ -390,6 +390,8 @@ const CAMPAIGN_LISTING_NATIVE_SORT_COLUMNS: Partial< endDate: 'end_date', }; +const CAMPAIGN_REACH_METRIC_WINDOW = '7d'; + const isCampaignListingRelationSort = ( orderBy: NonNullable, ) => orderBy === 'manager' || orderBy === 'programName'; @@ -4378,7 +4380,7 @@ export class SupabaseApi implements ServiceApi { } // Parent WhatsApp Invitation: class lookup with group and invite fields. - async getParentWhatsappClassesBySchoolId(schoolId: string): Promise< + async getParentWhatsappClassesBySchoolId(schoolIds: string[]): Promise< { id: string; name: string; @@ -4386,17 +4388,18 @@ export class SupabaseApi implements ServiceApi { whatsapp_invite_link?: string | null; }[] > { - if (!this.supabase) return []; + if (!this.supabase || schoolIds.length === 0) return []; + const uniqueSchoolIds = Array.from(new Set(schoolIds)); const { data, error } = await this.supabase .from(TABLES.Class) .select('id, name, group_id, whatsapp_invite_link') - .eq('school_id', schoolId) + .in('school_id', uniqueSchoolIds) .eq('is_deleted', false); if (error) { logger.error( - 'Error in parent WhatsApp class lookup by school ID:', + 'Error in parent WhatsApp class lookup by school IDs:', error, ); throw error; @@ -4442,6 +4445,33 @@ export class SupabaseApi implements ServiceApi { return Array.from(phoneSet); } + async getCampaignParentsInGroupBySchoolIds( + schoolIds: string[], + ): Promise { + if (!this.supabase || schoolIds.length === 0) return 0; + + const uniqueSchoolIds = Array.from(new Set(schoolIds)); + const { data, error } = await this.supabase + .from(TABLES.SchoolMetrics) + .select('school_id, parents_in_group') + .in('school_id', uniqueSchoolIds) + .eq('metric_window', CAMPAIGN_REACH_METRIC_WINDOW) + .eq('is_deleted', false); + + if (error) { + logger.error( + 'Error fetching campaign parents-in-group school metrics:', + error, + ); + throw error; + } + + return (data ?? []).reduce( + (total, row) => total + (row.parents_in_group ?? 0), + 0, + ); + } + async getUsersByIds(userIds: string[]): Promise[]> { if (!this.supabase || userIds.length === 0) return []; diff --git a/src/services/database.ts b/src/services/database.ts index 8cf3acb1f6..d3b9a4d4c2 100644 --- a/src/services/database.ts +++ b/src/services/database.ts @@ -4259,6 +4259,7 @@ export type Database = { metric_window: string | null; onboarded_students: number | null; partners: string[] | null; + parents_in_group: number | null; parents_reached: number | null; program_id: string | null; program_managers: string[] | null; @@ -4291,6 +4292,7 @@ export type Database = { metric_window?: string | null; onboarded_students?: number | null; partners?: string[] | null; + parents_in_group?: number | null; parents_reached?: number | null; program_id?: string | null; program_managers?: string[] | null; @@ -4323,6 +4325,7 @@ export type Database = { metric_window?: string | null; onboarded_students?: number | null; partners?: string[] | null; + parents_in_group?: number | null; parents_reached?: number | null; program_id?: string | null; program_managers?: string[] | null;