@eric-emg/symphiq-components 1.2.200 → 1.2.201

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -56430,26 +56430,49 @@ class InitialTargetSettingComponent {
56430
56430
  }, ...(ngDevMode ? [{ debugName: "percentageIncrease" }] : []));
56431
56431
  this.displayedMetricCalculations = computed(() => {
56432
56432
  const response = this.storedResponse();
56433
- if (!response || !response.relatedMetricTargets) {
56433
+ if (!response) {
56434
56434
  return [];
56435
56435
  }
56436
- return response.relatedMetricTargets.map((metricValue) => {
56437
- const metric = metricValue.metric;
56438
- const funnelMetric = this.funnelMetrics().find(fm => fm.relatedMetric === metric);
56439
- const currentValue = sumMetricFromUiData(this.mainUiData(), metric, 'priorYear');
56440
- const targetValue = parseFloat(metricValue.value || '0');
56441
- return {
56442
- metric,
56443
- funnelMetric: funnelMetric?.funnelMetric,
56444
- currentValue,
56445
- targetValue,
56446
- percentageIncrease: response.equalRelatedMetricIncreasePercent || 0,
56447
- isFunnelStage: funnelMetric?.funnelMetric === metric,
56448
- funnelInd: funnelMetric?.funnelInd,
56449
- relatedInd: funnelMetric?.relatedInd,
56450
- description: funnelMetric?.relatedMetricDescription
56451
- };
56452
- });
56436
+ const results = [];
56437
+ if (response.funnelMetricValues) {
56438
+ response.funnelMetricValues.forEach((metricValue) => {
56439
+ const metric = metricValue.metric;
56440
+ const funnelMetric = this.funnelMetrics().find(fm => fm.funnelMetric === metric && fm.funnelMetric === fm.relatedMetric);
56441
+ const currentValue = sumMetricFromUiData(this.mainUiData(), metric, 'priorYear');
56442
+ const { targetValue, percentageIncrease } = this.parseMetricValue(metricValue.value);
56443
+ results.push({
56444
+ metric,
56445
+ funnelMetric: metric,
56446
+ currentValue,
56447
+ targetValue,
56448
+ percentageIncrease,
56449
+ isFunnelStage: true,
56450
+ funnelInd: funnelMetric?.funnelInd,
56451
+ relatedInd: 0,
56452
+ description: funnelMetric?.funnelMetricDescription
56453
+ });
56454
+ });
56455
+ }
56456
+ if (response.relatedMetricTargets) {
56457
+ response.relatedMetricTargets.forEach((metricValue) => {
56458
+ const metric = metricValue.metric;
56459
+ const funnelMetric = this.funnelMetrics().find(fm => fm.relatedMetric === metric);
56460
+ const currentValue = sumMetricFromUiData(this.mainUiData(), metric, 'priorYear');
56461
+ const { targetValue, percentageIncrease } = this.parseMetricValue(metricValue.value);
56462
+ results.push({
56463
+ metric,
56464
+ funnelMetric: funnelMetric?.funnelMetric,
56465
+ currentValue,
56466
+ targetValue,
56467
+ percentageIncrease: percentageIncrease || response.equalRelatedMetricIncreasePercent || 0,
56468
+ isFunnelStage: false,
56469
+ funnelInd: funnelMetric?.funnelInd,
56470
+ relatedInd: funnelMetric?.relatedInd,
56471
+ description: funnelMetric?.relatedMetricDescription
56472
+ });
56473
+ });
56474
+ }
56475
+ return results;
56453
56476
  }, ...(ngDevMode ? [{ debugName: "displayedMetricCalculations" }] : []));
56454
56477
  this.displayedTargetRevenue = computed(() => {
56455
56478
  const response = this.storedResponse();
@@ -56523,6 +56546,28 @@ class InitialTargetSettingComponent {
56523
56546
  }
56524
56547
  }, { allowSignalWrites: true });
56525
56548
  }
56549
+ parseMetricValue(value) {
56550
+ if (!value)
56551
+ return { targetValue: 0, percentageIncrease: 0 };
56552
+ let targetValue = 0;
56553
+ let percentageIncrease = 0;
56554
+ const arrowMatch = value.match(/\([\d.,]+\s*->\s*([\d.,]+)\)/);
56555
+ if (arrowMatch) {
56556
+ targetValue = parseFloat(arrowMatch[1].replace(/,/g, ''));
56557
+ }
56558
+ const targetMatch = value.match(/target:\s*([\d.,]+)/);
56559
+ if (targetMatch) {
56560
+ targetValue = parseFloat(targetMatch[1].replace(/,/g, ''));
56561
+ }
56562
+ const percentMatch = value.match(/([-\d.]+)%\s*(increase|change|decrease)?/);
56563
+ if (percentMatch) {
56564
+ percentageIncrease = parseFloat(percentMatch[1]);
56565
+ }
56566
+ if (!arrowMatch && !targetMatch) {
56567
+ targetValue = parseFloat(value.replace(/,/g, '')) || 0;
56568
+ }
56569
+ return { targetValue, percentageIncrease };
56570
+ }
56526
56571
  ngAfterViewInit() {
56527
56572
  setTimeout(() => {
56528
56573
  this.absoluteInputRef?.nativeElement?.focus();
@@ -56802,214 +56847,214 @@ class InitialTargetSettingComponent {
56802
56847
  AreaChartComponent
56803
56848
  ],
56804
56849
  changeDetection: ChangeDetectionStrategy.OnPush,
56805
- template: `
56806
- <div class="space-y-8 pb-32">
56807
- <div [ngClass]="sectionCardClasses()" class="rounded-2xl border shadow-lg p-8">
56808
- <h2 [ngClass]="sectionTitleClasses()" class="text-2xl font-bold mb-6">
56809
- Calculate Your Revenue Target
56810
- </h2>
56811
-
56812
- <div class="grid lg:grid-cols-2 gap-8">
56813
- <div class="space-y-6">
56814
- <div>
56815
- <label [ngClass]="labelClasses()" class="block text-sm font-semibold mb-2">
56816
- {{ currentYear() }} Revenue
56817
- </label>
56818
- <div class="space-y-1 mb-4">
56819
- <p [ngClass]="priorYearLabelClasses()" class="text-xs">
56820
- {{ priorYear() }} Revenue: {{ formatCurrency(priorYearRevenue()) }}
56821
- </p>
56822
- @if (currentPaceProjection() > 0) {
56823
- <p [ngClass]="priorYearLabelClasses()" class="text-xs">
56824
- Current Pace Projection: {{ formatCurrency(currentPaceProjection()) }}
56825
- </p>
56826
- }
56827
- </div>
56828
-
56829
- <div class="flex gap-2 mb-4">
56830
- <button
56831
- (click)="setInputMode('absolute')"
56832
- [ngClass]="inputModeButtonClasses('absolute')"
56833
- class="flex-1 py-2 px-4 rounded-lg text-sm font-semibold transition-all">
56834
- Absolute Amount
56835
- </button>
56836
- <button
56837
- (click)="setInputMode('percentage')"
56838
- [ngClass]="inputModeButtonClasses('percentage')"
56839
- class="flex-1 py-2 px-4 rounded-lg text-sm font-semibold transition-all">
56840
- % Increase
56841
- </button>
56842
- </div>
56843
-
56844
- @if (inputMode() === 'absolute') {
56845
- <div class="relative">
56846
- <span [ngClass]="inputPrefixClasses()" class="absolute left-4 top-1/2 -translate-y-1/2 text-xl font-bold">
56847
- $
56848
- </span>
56849
- <input
56850
- #absoluteInputRef
56851
- type="number"
56852
- [(ngModel)]="absoluteInput"
56853
- (ngModelChange)="onAbsoluteInputChange()"
56854
- [ngClass]="inputClasses()"
56855
- class="w-full pl-10 pr-4 py-4 rounded-xl text-2xl font-bold border-2 transition-all"
56856
- placeholder="0"
56857
- min="0"
56858
- step="1000">
56859
- </div>
56860
- } @else {
56861
- <div class="relative">
56862
- <input
56863
- #percentageInputRef
56864
- type="number"
56865
- [(ngModel)]="percentageInput"
56866
- (ngModelChange)="onPercentageInputChange()"
56867
- [ngClass]="inputClasses()"
56868
- class="w-full pr-10 pl-4 py-4 rounded-xl text-2xl font-bold border-2 transition-all"
56869
- placeholder="0"
56870
- min="0"
56871
- max="1000"
56872
- step="0.1">
56873
- <span [ngClass]="inputSuffixClasses()" class="absolute right-4 top-1/2 -translate-y-1/2 text-xl font-bold">
56874
- %
56875
- </span>
56876
- </div>
56877
- }
56878
- </div>
56879
-
56880
- @if (calculatedRevenue() > 0) {
56881
- <div [ngClass]="calculatedValuesCardClasses()" class="p-6 rounded-xl border-2">
56882
- <div class="space-y-4">
56883
- <div>
56884
- <p [ngClass]="calculatedLabelClasses()" class="text-xs font-medium uppercase tracking-wider mb-1">
56885
- {{ currentYear() }} Revenue Target
56886
- </p>
56887
- <p [ngClass]="calculatedValueClasses()" class="text-3xl font-bold">
56888
- {{ formatCurrency(calculatedRevenue()) }}
56889
- </p>
56890
- </div>
56891
- <div class="grid grid-cols-2 gap-4 pt-4" [ngClass]="calculatedDividerClasses()">
56892
- <div>
56893
- <p [ngClass]="calculatedLabelClasses()" class="text-xs font-medium uppercase tracking-wider mb-1">
56894
- Increase Amount
56895
- </p>
56896
- <p [ngClass]="calculatedSecondaryClasses()" class="text-xl font-bold">
56897
- {{ formatCurrency(calculatedRevenue() - priorYearRevenue()) }}
56898
- </p>
56899
- </div>
56900
- <div>
56901
- <p [ngClass]="calculatedLabelClasses()" class="text-xs font-medium uppercase tracking-wider mb-1">
56902
- % Growth
56903
- </p>
56904
- <p [ngClass]="calculatedSecondaryClasses()" class="text-xl font-bold">
56905
- +{{ formatPercentage(percentageIncrease(), 1) }}
56906
- </p>
56907
- </div>
56908
- </div>
56909
- @if (currentPaceProjection() > 0 && gapToClose().amount !== 0) {
56910
- <div class="grid grid-cols-2 gap-4 pt-4" [ngClass]="calculatedDividerClasses()">
56911
- <div>
56912
- <p [ngClass]="calculatedLabelClasses()" class="text-xs font-medium uppercase tracking-wider mb-1">
56913
- Gap to Close
56914
- </p>
56915
- <p [ngClass]="calculatedSecondaryClasses()" class="text-xl font-bold">
56916
- {{ formatCurrency(absValue(gapToClose().amount)) }}
56917
- </p>
56918
- </div>
56919
- <div>
56920
- <p [ngClass]="calculatedLabelClasses()" class="text-xs font-medium uppercase tracking-wider mb-1">
56921
- Additional Growth Needed
56922
- </p>
56923
- <p [ngClass]="calculatedSecondaryClasses()" class="text-xl font-bold">
56924
- {{ gapToClose().amount > 0 ? '+' : '' }}{{ formatPercentage(gapToClose().percentage, 1) }}
56925
- </p>
56926
- </div>
56927
- </div>
56928
- }
56929
- </div>
56930
- </div>
56931
- }
56932
- </div>
56933
-
56934
- <div>
56935
- <p [ngClass]="chartTitleClasses()" class="text-sm font-semibold mb-3">
56936
- Year-over-Year Revenue Trend
56937
- </p>
56938
- <div [ngClass]="chartContainerClasses()" class="rounded-xl border p-4">
56939
- @if (revenueChartData()) {
56940
- <symphiq-area-chart
56941
- [chart]="revenueChartData()!"
56942
- [showAxisLabels]="true"
56943
- [viewMode]="viewMode()"
56944
- [currencySymbol]="'$'"
56945
- [height]="'320px'"
56946
- />
56947
- } @else {
56948
- <div class="h-64 flex items-center justify-center">
56949
- <p [ngClass]="noDataClasses()" class="text-sm">
56950
- No revenue data available
56951
- </p>
56952
- </div>
56953
- }
56954
- </div>
56955
- </div>
56956
- </div>
56957
- </div>
56958
-
56959
- @if (showMetricsVisualization()) {
56960
- <div [ngClass]="sectionCardClasses()" class="rounded-2xl border shadow-lg p-8">
56961
- <div class="mb-6">
56962
- <h2 [ngClass]="sectionTitleClasses()" class="text-2xl font-bold mb-2">
56963
- Contributing Metrics
56964
- </h2>
56965
- <p [ngClass]="sectionDescriptionClasses()" class="text-sm">
56966
- To achieve your revenue target of {{ formatCurrency(displayedTargetRevenue()) }}, the following metrics need to improve by these amounts. These improvements compound through your funnel to drive revenue growth.
56967
- </p>
56968
- </div>
56969
-
56970
- <symphiq-funnel-metrics-visualization
56971
- [viewMode]="viewMode()"
56972
- [calculations]="displayedMetricCalculations()"
56973
- [pacingMetrics]="pacingMetrics()"
56974
- />
56975
- </div>
56976
- }
56977
-
56978
- <symphiq-sticky-submit-bar
56979
- [viewMode]="viewMode()"
56980
- [isValid]="isValid()"
56981
- [isSubmitting]="isCalculating()"
56982
- [validationMessage]="validationMessage()"
56983
- [buttonText]="submitButtonText()"
56984
- (submitClick)="handleSubmitClick()"
56985
- />
56986
-
56987
- @if (calculationState() === 'results') {
56988
- <div class="fixed bottom-24 left-0 right-0 z-40 pb-4 px-4">
56989
- <div class="max-w-7xl mx-auto flex gap-4 justify-center">
56990
- <button
56991
- (click)="handleAdjustTarget()"
56992
- [ngClass]="secondaryButtonClasses()"
56993
- class="px-6 py-3 rounded-xl font-semibold transition-all shadow-lg">
56994
- Adjust Revenue Target
56995
- </button>
56996
- </div>
56997
- </div>
56998
- }
56999
-
57000
- @if (calculationState() === 'input' && hasStoredResponse()) {
57001
- <div class="fixed bottom-32 left-0 right-0 z-40 pb-4 px-4">
57002
- <div class="max-w-7xl mx-auto flex gap-4 justify-center">
57003
- <button
57004
- (click)="handleCancel()"
57005
- [ngClass]="cancelButtonClasses()"
57006
- class="px-6 py-3 rounded-xl font-semibold transition-all shadow-lg">
57007
- Cancel
57008
- </button>
57009
- </div>
57010
- </div>
57011
- }
57012
- </div>
56850
+ template: `
56851
+ <div class="space-y-8 pb-32">
56852
+ <div [ngClass]="sectionCardClasses()" class="rounded-2xl border shadow-lg p-8">
56853
+ <h2 [ngClass]="sectionTitleClasses()" class="text-2xl font-bold mb-6">
56854
+ Calculate Your Revenue Target
56855
+ </h2>
56856
+
56857
+ <div class="grid lg:grid-cols-2 gap-8">
56858
+ <div class="space-y-6">
56859
+ <div>
56860
+ <label [ngClass]="labelClasses()" class="block text-sm font-semibold mb-2">
56861
+ {{ currentYear() }} Revenue
56862
+ </label>
56863
+ <div class="space-y-1 mb-4">
56864
+ <p [ngClass]="priorYearLabelClasses()" class="text-xs">
56865
+ {{ priorYear() }} Revenue: {{ formatCurrency(priorYearRevenue()) }}
56866
+ </p>
56867
+ @if (currentPaceProjection() > 0) {
56868
+ <p [ngClass]="priorYearLabelClasses()" class="text-xs">
56869
+ Current Pace Projection: {{ formatCurrency(currentPaceProjection()) }}
56870
+ </p>
56871
+ }
56872
+ </div>
56873
+
56874
+ <div class="flex gap-2 mb-4">
56875
+ <button
56876
+ (click)="setInputMode('absolute')"
56877
+ [ngClass]="inputModeButtonClasses('absolute')"
56878
+ class="flex-1 py-2 px-4 rounded-lg text-sm font-semibold transition-all">
56879
+ Absolute Amount
56880
+ </button>
56881
+ <button
56882
+ (click)="setInputMode('percentage')"
56883
+ [ngClass]="inputModeButtonClasses('percentage')"
56884
+ class="flex-1 py-2 px-4 rounded-lg text-sm font-semibold transition-all">
56885
+ % Increase
56886
+ </button>
56887
+ </div>
56888
+
56889
+ @if (inputMode() === 'absolute') {
56890
+ <div class="relative">
56891
+ <span [ngClass]="inputPrefixClasses()" class="absolute left-4 top-1/2 -translate-y-1/2 text-xl font-bold">
56892
+ $
56893
+ </span>
56894
+ <input
56895
+ #absoluteInputRef
56896
+ type="number"
56897
+ [(ngModel)]="absoluteInput"
56898
+ (ngModelChange)="onAbsoluteInputChange()"
56899
+ [ngClass]="inputClasses()"
56900
+ class="w-full pl-10 pr-4 py-4 rounded-xl text-2xl font-bold border-2 transition-all"
56901
+ placeholder="0"
56902
+ min="0"
56903
+ step="1000">
56904
+ </div>
56905
+ } @else {
56906
+ <div class="relative">
56907
+ <input
56908
+ #percentageInputRef
56909
+ type="number"
56910
+ [(ngModel)]="percentageInput"
56911
+ (ngModelChange)="onPercentageInputChange()"
56912
+ [ngClass]="inputClasses()"
56913
+ class="w-full pr-10 pl-4 py-4 rounded-xl text-2xl font-bold border-2 transition-all"
56914
+ placeholder="0"
56915
+ min="0"
56916
+ max="1000"
56917
+ step="0.1">
56918
+ <span [ngClass]="inputSuffixClasses()" class="absolute right-4 top-1/2 -translate-y-1/2 text-xl font-bold">
56919
+ %
56920
+ </span>
56921
+ </div>
56922
+ }
56923
+ </div>
56924
+
56925
+ @if (calculatedRevenue() > 0) {
56926
+ <div [ngClass]="calculatedValuesCardClasses()" class="p-6 rounded-xl border-2">
56927
+ <div class="space-y-4">
56928
+ <div>
56929
+ <p [ngClass]="calculatedLabelClasses()" class="text-xs font-medium uppercase tracking-wider mb-1">
56930
+ {{ currentYear() }} Revenue Target
56931
+ </p>
56932
+ <p [ngClass]="calculatedValueClasses()" class="text-3xl font-bold">
56933
+ {{ formatCurrency(calculatedRevenue()) }}
56934
+ </p>
56935
+ </div>
56936
+ <div class="grid grid-cols-2 gap-4 pt-4" [ngClass]="calculatedDividerClasses()">
56937
+ <div>
56938
+ <p [ngClass]="calculatedLabelClasses()" class="text-xs font-medium uppercase tracking-wider mb-1">
56939
+ Increase Amount
56940
+ </p>
56941
+ <p [ngClass]="calculatedSecondaryClasses()" class="text-xl font-bold">
56942
+ {{ formatCurrency(calculatedRevenue() - priorYearRevenue()) }}
56943
+ </p>
56944
+ </div>
56945
+ <div>
56946
+ <p [ngClass]="calculatedLabelClasses()" class="text-xs font-medium uppercase tracking-wider mb-1">
56947
+ % Growth
56948
+ </p>
56949
+ <p [ngClass]="calculatedSecondaryClasses()" class="text-xl font-bold">
56950
+ +{{ formatPercentage(percentageIncrease(), 1) }}
56951
+ </p>
56952
+ </div>
56953
+ </div>
56954
+ @if (currentPaceProjection() > 0 && gapToClose().amount !== 0) {
56955
+ <div class="grid grid-cols-2 gap-4 pt-4" [ngClass]="calculatedDividerClasses()">
56956
+ <div>
56957
+ <p [ngClass]="calculatedLabelClasses()" class="text-xs font-medium uppercase tracking-wider mb-1">
56958
+ Gap to Close
56959
+ </p>
56960
+ <p [ngClass]="calculatedSecondaryClasses()" class="text-xl font-bold">
56961
+ {{ formatCurrency(absValue(gapToClose().amount)) }}
56962
+ </p>
56963
+ </div>
56964
+ <div>
56965
+ <p [ngClass]="calculatedLabelClasses()" class="text-xs font-medium uppercase tracking-wider mb-1">
56966
+ Additional Growth Needed
56967
+ </p>
56968
+ <p [ngClass]="calculatedSecondaryClasses()" class="text-xl font-bold">
56969
+ {{ gapToClose().amount > 0 ? '+' : '' }}{{ formatPercentage(gapToClose().percentage, 1) }}
56970
+ </p>
56971
+ </div>
56972
+ </div>
56973
+ }
56974
+ </div>
56975
+ </div>
56976
+ }
56977
+ </div>
56978
+
56979
+ <div>
56980
+ <p [ngClass]="chartTitleClasses()" class="text-sm font-semibold mb-3">
56981
+ Year-over-Year Revenue Trend
56982
+ </p>
56983
+ <div [ngClass]="chartContainerClasses()" class="rounded-xl border p-4">
56984
+ @if (revenueChartData()) {
56985
+ <symphiq-area-chart
56986
+ [chart]="revenueChartData()!"
56987
+ [showAxisLabels]="true"
56988
+ [viewMode]="viewMode()"
56989
+ [currencySymbol]="'$'"
56990
+ [height]="'320px'"
56991
+ />
56992
+ } @else {
56993
+ <div class="h-64 flex items-center justify-center">
56994
+ <p [ngClass]="noDataClasses()" class="text-sm">
56995
+ No revenue data available
56996
+ </p>
56997
+ </div>
56998
+ }
56999
+ </div>
57000
+ </div>
57001
+ </div>
57002
+ </div>
57003
+
57004
+ @if (showMetricsVisualization()) {
57005
+ <div [ngClass]="sectionCardClasses()" class="rounded-2xl border shadow-lg p-8">
57006
+ <div class="mb-6">
57007
+ <h2 [ngClass]="sectionTitleClasses()" class="text-2xl font-bold mb-2">
57008
+ Contributing Metrics
57009
+ </h2>
57010
+ <p [ngClass]="sectionDescriptionClasses()" class="text-sm">
57011
+ To achieve your revenue target of {{ formatCurrency(displayedTargetRevenue()) }}, the following metrics need to improve by these amounts. These improvements compound through your funnel to drive revenue growth.
57012
+ </p>
57013
+ </div>
57014
+
57015
+ <symphiq-funnel-metrics-visualization
57016
+ [viewMode]="viewMode()"
57017
+ [calculations]="displayedMetricCalculations()"
57018
+ [pacingMetrics]="pacingMetrics()"
57019
+ />
57020
+ </div>
57021
+ }
57022
+
57023
+ <symphiq-sticky-submit-bar
57024
+ [viewMode]="viewMode()"
57025
+ [isValid]="isValid()"
57026
+ [isSubmitting]="isCalculating()"
57027
+ [validationMessage]="validationMessage()"
57028
+ [buttonText]="submitButtonText()"
57029
+ (submitClick)="handleSubmitClick()"
57030
+ />
57031
+
57032
+ @if (calculationState() === 'results') {
57033
+ <div class="fixed bottom-24 left-0 right-0 z-40 pb-4 px-4">
57034
+ <div class="max-w-7xl mx-auto flex gap-4 justify-center">
57035
+ <button
57036
+ (click)="handleAdjustTarget()"
57037
+ [ngClass]="secondaryButtonClasses()"
57038
+ class="px-6 py-3 rounded-xl font-semibold transition-all shadow-lg">
57039
+ Adjust Revenue Target
57040
+ </button>
57041
+ </div>
57042
+ </div>
57043
+ }
57044
+
57045
+ @if (calculationState() === 'input' && hasStoredResponse()) {
57046
+ <div class="fixed bottom-32 left-0 right-0 z-40 pb-4 px-4">
57047
+ <div class="max-w-7xl mx-auto flex gap-4 justify-center">
57048
+ <button
57049
+ (click)="handleCancel()"
57050
+ [ngClass]="cancelButtonClasses()"
57051
+ class="px-6 py-3 rounded-xl font-semibold transition-all shadow-lg">
57052
+ Cancel
57053
+ </button>
57054
+ </div>
57055
+ </div>
57056
+ }
57057
+ </div>
57013
57058
  `
57014
57059
  }]
57015
57060
  }], () => [], { absoluteInputRef: [{