From cbff737083cc49ca77572e9f183d8654c44d75b1 Mon Sep 17 00:00:00 2001 From: Misha Rautela Date: Tue, 12 May 2026 22:26:36 -0700 Subject: [PATCH 1/4] fix(dashboards): standardize no-data messaging across ED drawers Unify the no-data state across all 8 ED marketing drawers to show a consistent informational card guiding users to contact marketing ops. Add hasNoData guards to engaged-community and flywheel-conversion drawers to prevent false "Performing Well" sections when data is stale. LFXV2-1644 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Misha Rautela --- .../brand-health-drawer.component.html | 4 +- .../email-ctr-drawer.component.html | 4 +- .../engaged-community-drawer.component.html | 115 ++++++++++-------- .../engaged-community-drawer.component.ts | 4 + .../flywheel-conversion-drawer.component.html | 115 ++++++++++-------- .../flywheel-conversion-drawer.component.ts | 4 + .../member-acquisition-drawer.component.html | 2 +- .../member-retention-drawer.component.html | 4 +- .../social-media-drawer.component.html | 2 +- .../website-visits-drawer.component.html | 4 +- 10 files changed, 148 insertions(+), 110 deletions(-) diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/brand-health-drawer/brand-health-drawer.component.html b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/brand-health-drawer/brand-health-drawer.component.html index 2cddf827c..e62ba57c9 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/brand-health-drawer/brand-health-drawer.component.html +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/brand-health-drawer/brand-health-drawer.component.html @@ -152,8 +152,8 @@

Performing Well

- No brand mention data available -

No brand mentions are currently available for this foundation

+ No brand mention activity detected +

Engage with marketing ops to activate campaigns and start tracking results.

diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/email-ctr-drawer/email-ctr-drawer.component.html b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/email-ctr-drawer/email-ctr-drawer.component.html index 5a0e3b59a..46cf4664b 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/email-ctr-drawer/email-ctr-drawer.component.html +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/email-ctr-drawer/email-ctr-drawer.component.html @@ -107,8 +107,8 @@

Performing Well

-

No active campaigns detected

-

No email, paid, or attribution activity is currently available for this foundation

+

No email or paid activity detected

+

Engage with marketing ops to activate campaigns and start tracking results.

diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.html b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.html index b0ea02394..4418a3518 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.html +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.html @@ -53,65 +53,80 @@

-
- -

Needs Your Attention

+ class="flex flex-col rounded-lg border border-gray-200 border-l-4 border-l-blue-600 bg-white overflow-hidden" + role="status" + data-testid="engaged-community-drawer-no-data"> +
+ +
+

No community engagement activity detected

+

Engage with marketing ops to activate campaigns and start tracking results.

+
- - @for (action of attentionActions(); track action.title) { -
-
- {{ action.title }} -

{{ action.description }}

-
- @if (action.priority === 'high') { - - } @else { - - } +
+ } @else { + + @if (attentionActions().length > 0 || attentionInsights().length > 0) { +
+
+ +

Needs Your Attention

- } - @for (insight of attentionInsights(); track insight.text) { -
- - {{ insight.text }} -
- } -
- } + @for (action of attentionActions(); track action.title) { +
+
+ {{ action.title }} +

{{ action.description }}

+
+ @if (action.priority === 'high') { + + } @else { + + } +
+ } - - @if (performingActions().length > 0 || performingInsights().length > 0) { -
-
- -

Performing Well

+ @for (insight of attentionInsights(); track insight.text) { +
+ + {{ insight.text }} +
+ }
+ } - @for (action of performingActions(); track action.title) { -
-
- {{ action.title }} -

{{ action.description }}

-
+ + @if (performingActions().length > 0 || performingInsights().length > 0) { +
+
+ +

Performing Well

- } - @for (insight of performingInsights(); track insight.text) { -
- - {{ insight.text }} -
- } -
+ @for (action of performingActions(); track action.title) { +
+
+ {{ action.title }} +

{{ action.description }}

+
+
+ } + + @for (insight of performingInsights(); track insight.text) { +
+ + {{ insight.text }} +
+ } +
+ } } diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts index 7ccd27f4b..42211b122 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts @@ -53,6 +53,10 @@ export class EngagedCommunityDrawerComponent { weeklyTrend: [], }); // === Computed Signals === + protected readonly hasNoData: Signal = computed(() => { + const { totalMembers, changePercentage, monthlyData } = this.data(); + return totalMembers === 0 || (changePercentage === 0 && monthlyData.length === 0); + }); protected readonly formattedTotalMembers: Signal = computed(() => formatNumber(this.data().totalMembers)); protected readonly recommendedActions: Signal = this.initRecommendedActions(); protected readonly keyInsights: Signal = this.initKeyInsights(); diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.html b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.html index dbc3a315e..8507e9a48 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.html +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.html @@ -61,65 +61,80 @@

-
- -

Needs Your Attention

+ class="flex flex-col rounded-lg border border-gray-200 border-l-4 border-l-blue-600 bg-white overflow-hidden" + role="status" + data-testid="flywheel-conversion-drawer-no-data"> +
+ +
+

No flywheel conversion activity detected

+

Engage with marketing ops to activate campaigns and start tracking results.

+
- - @for (action of attentionActions(); track action.title) { -
-
- {{ action.title }} -

{{ action.description }}

-
- @if (action.priority === 'high') { - - } @else { - - } +
+ } @else { + + @if (attentionActions().length > 0 || attentionInsights().length > 0) { +
+
+ +

Needs Your Attention

- } - @for (insight of attentionInsights(); track insight.text) { -
- - {{ insight.text }} -
- } -
- } + @for (action of attentionActions(); track action.title) { +
+
+ {{ action.title }} +

{{ action.description }}

+
+ @if (action.priority === 'high') { + + } @else { + + } +
+ } - - @if (performingActions().length > 0 || performingInsights().length > 0) { -
-
- -

Performing Well

+ @for (insight of attentionInsights(); track insight.text) { +
+ + {{ insight.text }} +
+ }
+ } - @for (action of performingActions(); track action.title) { -
-
- {{ action.title }} -

{{ action.description }}

-
+ + @if (performingActions().length > 0 || performingInsights().length > 0) { +
+
+ +

Performing Well

- } - @for (insight of performingInsights(); track insight.text) { -
- - {{ insight.text }} -
- } -
+ @for (action of performingActions(); track action.title) { +
+
+ {{ action.title }} +

{{ action.description }}

+
+
+ } + + @for (insight of performingInsights(); track insight.text) { +
+ + {{ insight.text }} +
+ } +
+ } } diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.ts b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.ts index a294da2b2..6da5c0595 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.ts +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.ts @@ -64,6 +64,10 @@ export class FlywheelConversionDrawerComponent { // === Computed Signals === protected readonly formattedEventAttendees: Signal = computed(() => formatNumber(this.data().funnel.eventAttendees)); protected readonly reengagement: Signal> = computed(() => getFlywheelReengagement(this.data())); + protected readonly hasNoData: Signal = computed(() => { + const { conversionRate, funnel, monthlyData } = this.data(); + return conversionRate === 0 && funnel.eventAttendees === 0 && this.reengagement().reengagementRate === 0 && monthlyData.length === 0; + }); protected readonly reengagementRate: Signal = computed(() => `${this.reengagement().reengagementRate.toFixed(1)}%`); protected readonly recommendedActions: Signal = computed(() => buildFlywheelRecommendedActions(this.data())); protected readonly keyInsights: Signal = computed(() => buildFlywheelKeyInsights(this.data())); diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/member-acquisition-drawer/member-acquisition-drawer.component.html b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/member-acquisition-drawer/member-acquisition-drawer.component.html index 93d2895d8..ea039ba5e 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/member-acquisition-drawer/member-acquisition-drawer.component.html +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/member-acquisition-drawer/member-acquisition-drawer.component.html @@ -153,7 +153,7 @@

Performing Well

No membership activity detected

-

No member acquisition data is currently available for this foundation

+

Engage with marketing ops to activate campaigns and start tracking results.

diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/member-retention-drawer/member-retention-drawer.component.html b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/member-retention-drawer/member-retention-drawer.component.html index f840e1c75..9b6c5ac7f 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/member-retention-drawer/member-retention-drawer.component.html +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/member-retention-drawer/member-retention-drawer.component.html @@ -128,8 +128,8 @@

Performing Well

-

No retention data available

-

No member retention data is currently available for this foundation

+

No retention activity detected

+

Engage with marketing ops to activate campaigns and start tracking results.

diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.html b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.html index a5ad57355..a74efe540 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.html +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.html @@ -140,7 +140,7 @@

Performing Well

No social media activity detected

-

No follower or engagement data is currently available for this foundation

+

Engage with marketing ops to activate campaigns and start tracking results.

diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/website-visits-drawer/website-visits-drawer.component.html b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/website-visits-drawer/website-visits-drawer.component.html index 59465e9d4..c23c9cf26 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/website-visits-drawer/website-visits-drawer.component.html +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/website-visits-drawer/website-visits-drawer.component.html @@ -124,8 +124,8 @@

Performing Well

-

No website traffic data available

-

No web session data is currently available for this foundation

+

No brand reach activity detected

+

Engage with marketing ops to activate campaigns and start tracking results.

From 8d3d5a6db28437c065a7d06aed85b54cbd1d27cb Mon Sep 17 00:00:00 2001 From: Misha Rautela Date: Tue, 12 May 2026 22:32:47 -0700 Subject: [PATCH 2/4] fix(dashboards): address review feedback on no-data guards Align hasNoData conditions with shared rule-engine guards: - website-visits: rename "brand reach" to "website traffic" in no-data title - flywheel: drop reengagementRate check to match buildFlywheelRecommendedActions - engaged-community: require both totalMembers=0 AND monthlyData empty before hiding insights, matching initRecommendedActions guard LFXV2-1644 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Misha Rautela --- .../engaged-community-drawer.component.ts | 2 +- .../flywheel-conversion-drawer.component.ts | 2 +- .../website-visits-drawer/website-visits-drawer.component.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts index 42211b122..06c3c50b5 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts @@ -55,7 +55,7 @@ export class EngagedCommunityDrawerComponent { // === Computed Signals === protected readonly hasNoData: Signal = computed(() => { const { totalMembers, changePercentage, monthlyData } = this.data(); - return totalMembers === 0 || (changePercentage === 0 && monthlyData.length === 0); + return (totalMembers === 0 && monthlyData.length === 0) || (changePercentage === 0 && monthlyData.length === 0); }); protected readonly formattedTotalMembers: Signal = computed(() => formatNumber(this.data().totalMembers)); protected readonly recommendedActions: Signal = this.initRecommendedActions(); diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.ts b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.ts index 6da5c0595..51032dec2 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.ts +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.ts @@ -66,7 +66,7 @@ export class FlywheelConversionDrawerComponent { protected readonly reengagement: Signal> = computed(() => getFlywheelReengagement(this.data())); protected readonly hasNoData: Signal = computed(() => { const { conversionRate, funnel, monthlyData } = this.data(); - return conversionRate === 0 && funnel.eventAttendees === 0 && this.reengagement().reengagementRate === 0 && monthlyData.length === 0; + return conversionRate === 0 && funnel.eventAttendees === 0 && monthlyData.length === 0; }); protected readonly reengagementRate: Signal = computed(() => `${this.reengagement().reengagementRate.toFixed(1)}%`); protected readonly recommendedActions: Signal = computed(() => buildFlywheelRecommendedActions(this.data())); diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/website-visits-drawer/website-visits-drawer.component.html b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/website-visits-drawer/website-visits-drawer.component.html index c23c9cf26..c4c60d890 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/website-visits-drawer/website-visits-drawer.component.html +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/website-visits-drawer/website-visits-drawer.component.html @@ -124,7 +124,7 @@

Performing Well

-

No brand reach activity detected

+

No website traffic detected

Engage with marketing ops to activate campaigns and start tracking results.

From 8d04d6bba5f1df5a3b82d7f470f6b7dd895b7df1 Mon Sep 17 00:00:00 2001 From: Misha Rautela Date: Wed, 13 May 2026 07:28:16 -0700 Subject: [PATCH 3/4] refactor(dashboards): extract hasNoData to private initHasNoData() Move multi-line inline computed() for hasNoData in engaged-community and flywheel-conversion drawers to private initHasNoData() methods, matching the component organization convention used by sibling signals. LFXV2-1644 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Misha Rautela --- .../engaged-community-drawer.component.ts | 12 ++++++++---- .../flywheel-conversion-drawer.component.ts | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts index 06c3c50b5..14aae0d51 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts @@ -53,10 +53,7 @@ export class EngagedCommunityDrawerComponent { weeklyTrend: [], }); // === Computed Signals === - protected readonly hasNoData: Signal = computed(() => { - const { totalMembers, changePercentage, monthlyData } = this.data(); - return (totalMembers === 0 && monthlyData.length === 0) || (changePercentage === 0 && monthlyData.length === 0); - }); + protected readonly hasNoData: Signal = this.initHasNoData(); protected readonly formattedTotalMembers: Signal = computed(() => formatNumber(this.data().totalMembers)); protected readonly recommendedActions: Signal = this.initRecommendedActions(); protected readonly keyInsights: Signal = this.initKeyInsights(); @@ -193,6 +190,13 @@ export class EngagedCommunityDrawerComponent { } // === Private Initializers === + private initHasNoData(): Signal { + return computed(() => { + const { totalMembers, changePercentage, monthlyData } = this.data(); + return (totalMembers === 0 && monthlyData.length === 0) || (changePercentage === 0 && monthlyData.length === 0); + }); + } + private initRecommendedActions(): Signal { return computed(() => { const { totalMembers, changePercentage, breakdown, monthlyData } = this.data(); diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.ts b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.ts index 51032dec2..6af7efc3f 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.ts +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/flywheel-conversion-drawer/flywheel-conversion-drawer.component.ts @@ -64,10 +64,7 @@ export class FlywheelConversionDrawerComponent { // === Computed Signals === protected readonly formattedEventAttendees: Signal = computed(() => formatNumber(this.data().funnel.eventAttendees)); protected readonly reengagement: Signal> = computed(() => getFlywheelReengagement(this.data())); - protected readonly hasNoData: Signal = computed(() => { - const { conversionRate, funnel, monthlyData } = this.data(); - return conversionRate === 0 && funnel.eventAttendees === 0 && monthlyData.length === 0; - }); + protected readonly hasNoData: Signal = this.initHasNoData(); protected readonly reengagementRate: Signal = computed(() => `${this.reengagement().reengagementRate.toFixed(1)}%`); protected readonly recommendedActions: Signal = computed(() => buildFlywheelRecommendedActions(this.data())); protected readonly keyInsights: Signal = computed(() => buildFlywheelKeyInsights(this.data())); @@ -155,6 +152,13 @@ export class FlywheelConversionDrawerComponent { } // === Private Initializers === + private initHasNoData(): Signal { + return computed(() => { + const { conversionRate, funnel, monthlyData } = this.data(); + return conversionRate === 0 && funnel.eventAttendees === 0 && monthlyData.length === 0; + }); + } + private initTrendChartData(): Signal> { return computed(() => { const { monthlyData } = this.data(); From a4abb6c6bf874246c83d072346d5b0a8a53a2b2b Mon Sep 17 00:00:00 2001 From: Misha Rautela Date: Wed, 13 May 2026 07:43:03 -0700 Subject: [PATCH 4/4] fix(dashboards): tighten engaged-community hasNoData guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove stale-data clause (changePercentage === 0) from hasNoData so it only fires when totalMembers === 0 && monthlyData is empty — matching initRecommendedActions exactly. Prevents contradictory "no activity" card when Total Engaged is non-zero. LFXV2-1644 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Misha Rautela --- .../engaged-community-drawer.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts index 14aae0d51..428b2da04 100644 --- a/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts +++ b/apps/lfx-one/src/app/modules/dashboards/executive-director/components/engaged-community-drawer/engaged-community-drawer.component.ts @@ -192,8 +192,8 @@ export class EngagedCommunityDrawerComponent { // === Private Initializers === private initHasNoData(): Signal { return computed(() => { - const { totalMembers, changePercentage, monthlyData } = this.data(); - return (totalMembers === 0 && monthlyData.length === 0) || (changePercentage === 0 && monthlyData.length === 0); + const { totalMembers, monthlyData } = this.data(); + return totalMembers === 0 && monthlyData.length === 0; }); }