diff --git a/backend/Value-Modeling.png b/backend/Value-Modeling.png
new file mode 100644
index 0000000..3be7b53
Binary files /dev/null and b/backend/Value-Modeling.png differ
diff --git a/frontend/src/app/main/copilot/value-modeling/grid-object.model.ts b/frontend/src/app/main/copilot/value-modeling/grid-object.model.ts
new file mode 100644
index 0000000..939bcb0
--- /dev/null
+++ b/frontend/src/app/main/copilot/value-modeling/grid-object.model.ts
@@ -0,0 +1,46 @@
+export interface MetricState {
+ seats: number;
+ adoptedDevs: number;
+ monthlyDevsReportingTimeSavings: number;
+ percentSeatsReportingTimeSavings: number;
+ percentSeatsAdopted: number;
+ percentMaxAdopted: number;
+ dailySuggestions: number;
+ dailyChatTurns: number;
+ weeklyPRSummaries: number;
+ weeklyTimeSaved: number;
+ monthlyTimeSavings: number;
+ annualTimeSavingsDollars: number;
+ productivityBoost: number;
+ [key: string]: number; // Index signature
+}
+
+export interface GridObject {
+ current: MetricState;
+ target: MetricState;
+ max: MetricState;
+}
+
+export function initializeGridObject(): GridObject {
+ const defaultMetricState: MetricState = {
+ seats: 0,
+ adoptedDevs: 0,
+ monthlyDevsReportingTimeSavings: 0,
+ percentSeatsReportingTimeSavings: 0,
+ percentSeatsAdopted: 0,
+ percentMaxAdopted: 0,
+ dailySuggestions: 0,
+ dailyChatTurns: 0,
+ weeklyPRSummaries: 0,
+ weeklyTimeSaved: 0,
+ monthlyTimeSavings: 0,
+ annualTimeSavingsDollars: 0,
+ productivityBoost: 0
+ };
+
+ return {
+ current: { ...defaultMetricState },
+ target: { ...defaultMetricState },
+ max: { ...defaultMetricState }
+ };
+}
diff --git a/frontend/src/app/main/copilot/value-modeling/value-modeling.component.html b/frontend/src/app/main/copilot/value-modeling/value-modeling.component.html
index 589c824..3f407c7 100644
--- a/frontend/src/app/main/copilot/value-modeling/value-modeling.component.html
+++ b/frontend/src/app/main/copilot/value-modeling/value-modeling.component.html
@@ -1,13 +1,18 @@
- Adopted Users
+ Org-Level Metrics
@@ -21,7 +26,7 @@
Value Modeling and Targeting
- Activity Per Daily User
+ Per Daily User Metrics
@@ -33,98 +38,332 @@
Value Modeling and Targeting
+
+
+
+
-
-
-
\ No newline at end of file
diff --git a/frontend/src/app/main/copilot/value-modeling/value-modeling.component.scss b/frontend/src/app/main/copilot/value-modeling/value-modeling.component.scss
index 779f8d8..44ed6bd 100644
--- a/frontend/src/app/main/copilot/value-modeling/value-modeling.component.scss
+++ b/frontend/src/app/main/copilot/value-modeling/value-modeling.component.scss
@@ -5,12 +5,21 @@
}
.page-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
margin-bottom: 24px;
+
h1 {
margin: 0;
font-size: 24px;
font-weight: 500;
}
+
+ .button-group {
+ display: flex;
+ gap: 16px;
+ }
}
.charts-grid {
@@ -55,10 +64,58 @@
}
}
+.metrics-table {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 24px;
+
+ mat-card {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 16px;
+
+ table {
+ width: 100%;
+ border-collapse: collapse;
+
+ th, td {
+ padding: 8px;
+ text-align: right; /* Right justify all content */
+ width: 25%; /* Ensure consistent column widths */
+ }
+
+ th {
+ font-weight: 500;
+ }
+
+ td {
+ mat-form-field {
+ width: 100%;
+ }
+ }
+ }
+ }
+}
+
.example-right-align {
text-align: right;
}
+.invalid {
+ border-color: red;
+}
+
+mat-form-field[disabled] {
+ .mat-form-field-wrapper {
+ background-color: #f5f5f5;
+ box-shadow: none;
+ }
+
+ .mat-input-element {
+ color: #9e9e9e;
+ }
+}
+
@media (max-width: 768px) {
.charts-grid {
grid-template-columns: 1fr;
@@ -75,4 +132,8 @@
grid-template-columns: 1fr;
}
}
+
+ .metrics-table {
+ grid-template-columns: 1fr;
+ }
}
\ No newline at end of file
diff --git a/frontend/src/app/main/copilot/value-modeling/value-modeling.component.ts b/frontend/src/app/main/copilot/value-modeling/value-modeling.component.ts
index c8f850e..7e5eeaa 100644
--- a/frontend/src/app/main/copilot/value-modeling/value-modeling.component.ts
+++ b/frontend/src/app/main/copilot/value-modeling/value-modeling.component.ts
@@ -1,34 +1,23 @@
-import { Component, OnInit } from '@angular/core';
-import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
+import { Component, OnInit, AfterViewInit } from '@angular/core';
+import { FormControl, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { MaterialModule } from '../../../material.module';
+import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { CommonModule, DecimalPipe } from '@angular/common';
import { HighchartsChartModule } from 'highcharts-angular';
+import { SharedModule } from '../../../shared/shared.module';
import * as Highcharts from 'highcharts';
-
-interface MetricState {
- adoption: string;
- usage: {
- activity: string;
- timeSavings: string;
- };
- timeSavedDollars: string;
- downstreamProductivity: string;
-}
-
-interface Metrics {
- current: MetricState;
- target: MetricState;
- max: MetricState;
-}
+import { GridObject, MetricState, initializeGridObject } from './grid-object.model';
@Component({
selector: 'app-value-modeling',
standalone: true,
imports: [
MaterialModule,
+ MatSlideToggleModule,
CommonModule,
ReactiveFormsModule,
HighchartsChartModule,
+ SharedModule
],
providers: [
DecimalPipe
@@ -36,7 +25,7 @@ interface Metrics {
templateUrl: './value-modeling.component.html',
styleUrl: './value-modeling.component.scss'
})
-export class ValueModelingComponent implements OnInit {
+export class ValueModelingComponent implements OnInit, AfterViewInit {
Highcharts: typeof Highcharts = Highcharts;
// chartOptions: Highcharts.Options;
adoptionChartOption: Highcharts.Options = {
@@ -97,9 +86,9 @@ export class ValueModelingComponent implements OnInit {
series: [{
type: 'bar',
data: [
- this.calculateOverallImpact('current'),
- this.calculateOverallImpact('target'),
- this.calculateOverallImpact('max')
+ // this.calculateOverallImpact('current'),
+ // this.calculateOverallImpact('target'),
+ // this.calculateOverallImpact('max')
]
}],
xAxis: {
@@ -115,71 +104,244 @@ export class ValueModelingComponent implements OnInit {
//get 7 day moving average time savings (current time saved)
// set up the initial model values based on the above and the settings from the API
- model = {
- adoption: {
- current: 0, //
- target: 0,
- max: 0
- },
- activity: {
- current: 0,
- target: 0,
- max: 0
- },
- timeSavings: {
- current: 0,
- target: 0,
- max: 0
- },
- timeSavedDollars: {
- current: 0,
- target: 0,
- max: 0
- },
- downstreamProductivity: {
- current: 0,
- target: 0,
- max: 0
- }
- };
+
form = new FormGroup({
current: new FormGroup({
- adoption: new FormControl(20),
- activityLevel: new FormControl(30),
- timeSavings: new FormControl(15),
- timeSavedDollars: new FormControl(50000),
- downstreamProductivity: new FormControl(25)
+ seats: new FormControl('0', [Validators.required, Validators.min(0)]),
+ adoptedDevs: new FormControl('0', [Validators.required, Validators.min(0)]),
+ monthlyDevsReportingTimeSavings: new FormControl('0', [Validators.required, Validators.min(0)]),
+ percentSeatsReportingTimeSavings: new FormControl('0', [Validators.required, Validators.min(0), Validators.max(100)]),
+ percentSeatsAdopted: new FormControl('0', [Validators.required, Validators.min(0), Validators.max(100)]),
+ percentMaxAdopted: new FormControl('0', [Validators.required, Validators.min(0), Validators.max(100)]),
+ dailySuggestions: new FormControl('0', [Validators.required, Validators.min(0)]),
+ dailyChatTurns: new FormControl('0', [Validators.required, Validators.min(0)]),
+ weeklyPRSummaries: new FormControl('0', [Validators.required, Validators.min(0)]),
+ weeklyTimeSaved: new FormControl('0', [Validators.required, Validators.min(0)]),
+ monthlyTimeSavings: new FormControl('0', [Validators.required, Validators.min(0)]),
+ annualTimeSavingsDollars: new FormControl('0', [Validators.required, Validators.min(0)]),
+ productivityBoost: new FormControl('0', [Validators.required, Validators.min(0), Validators.max(100)])
}),
target: new FormGroup({
- adoption: new FormControl(60),
- activityLevel: new FormControl(70),
- timeSavings: new FormControl(45),
- timeSavedDollars: new FormControl(150000),
- downstreamProductivity: new FormControl(75)
+ seats: new FormControl('0', [Validators.required, Validators.min(0)]),
+ adoptedDevs: new FormControl('0', [Validators.required, Validators.min(0)]),
+ monthlyDevsReportingTimeSavings: new FormControl('0', [Validators.required, Validators.min(0)]),
+ percentSeatsReportingTimeSavings: new FormControl('0', [Validators.required, Validators.min(0), Validators.max(100)]),
+ percentSeatsAdopted: new FormControl('0', [Validators.required, Validators.min(0), Validators.max(100)]),
+ percentMaxAdopted: new FormControl('0', [Validators.required, Validators.min(0), Validators.max(100)]),
+ dailySuggestions: new FormControl('0', [Validators.required, Validators.min(0)]),
+ dailyChatTurns: new FormControl('0', [Validators.required, Validators.min(0)]),
+ weeklyPRSummaries: new FormControl('0', [Validators.required, Validators.min(0)]),
+ weeklyTimeSaved: new FormControl('0', [Validators.required, Validators.min(0)]),
+ monthlyTimeSavings: new FormControl('0', [Validators.required, Validators.min(0)]),
+ annualTimeSavingsDollars: new FormControl('0', [Validators.required, Validators.min(0)]),
+ productivityBoost: new FormControl('0', [Validators.required, Validators.min(0), Validators.max(100)])
}),
max: new FormGroup({
- adoption: new FormControl(99),
- activityLevel: new FormControl(99),
- timeSavings: new FormControl(99),
- timeSavedDollars: new FormControl(99),
- downstreamProductivity: new FormControl(99)
+ seats: new FormControl('0', [Validators.required, Validators.min(0)]),
+ adoptedDevs: new FormControl('0', [Validators.required, Validators.min(0)]),
+ monthlyDevsReportingTimeSavings: new FormControl('0', [Validators.required, Validators.min(0)]),
+ percentSeatsReportingTimeSavings: new FormControl('0', [Validators.required, Validators.min(0), Validators.max(100)]),
+ percentSeatsAdopted: new FormControl('0', [Validators.required, Validators.min(0), Validators.max(100)]),
+ percentMaxAdopted: new FormControl('0', [Validators.required, Validators.min(0), Validators.max(100)]),
+ dailySuggestions: new FormControl('0', [Validators.required, Validators.min(0)]),
+ dailyChatTurns: new FormControl('0', [Validators.required, Validators.min(0)]),
+ weeklyPRSummaries: new FormControl('0', [Validators.required, Validators.min(0)]),
+ weeklyTimeSaved: new FormControl('0', [Validators.required, Validators.min(0)]),
+ monthlyTimeSavings: new FormControl('0', [Validators.required, Validators.min(0)]),
+ annualTimeSavingsDollars: new FormControl('0', [Validators.required, Validators.min(0)]),
+ productivityBoost: new FormControl('0', [Validators.required, Validators.min(0), Validators.max(100)])
})
});
+ gridObject: GridObject = initializeGridObject();
+ gridObjectSaved: GridObject = initializeGridObject();
+ disableInputs = false;
+
constructor(
private decimalPipe: DecimalPipe
) {}
ngOnInit() {
+ console.log('Component loaded');
+ console.log('ngOnInit: Initializing component');
+ console.log('Initial gridObject:', this.gridObject);
// this.updateChartData();
// this.form.valueChanges.subscribe(() => this.updateChartData());
+ this.form.valueChanges.subscribe((values) => {
+ console.log('1*. Form value changed:', values);
+ // Example update logic for internal state
+ // this.model.adoption.current = values.current.adoption;
+ // this.model.adoption.target = values.target.adoption;
+ // this.model.adoption.max = values.max.adoption;
+ // ...existing code...
+ });
+ }
+
+ ngAfterViewInit() {
+ console.log('0. ngAfterViewInit: View initialized');
+ this.makeEventListenersPassive();
+ }
+
+ private makeEventListenersPassive() {
+ const elements = document.querySelectorAll('.highcharts-container');
+ elements.forEach(element => {
+ element.addEventListener('touchstart', () => {}, { passive: true });
+ });
+ console.log('0. makeEventListenersPassive: Event listeners set to passive');
+ }
+
+ onBlur(event: Event, level: 'current' | 'target' | 'max', field: keyof MetricState) {
+ const input = event.target as HTMLInputElement;
+ const value = parseFloat(input.value.replace(/,/g, '').replace(/[^0-9.-]+/g, ''));
+ this.gridObject[level][field] = isNaN(value) ? 0 : value;
+ console.log(`2. onBlur: Updated gridObject[${level}][${field}] to`, this.gridObject[level][field]);
+ this.modelCalc();
+ // print out gridObject for debugging
+ // console.log('Updated gridObject:', this.gridObject);
+ }
+
+ loadGridObject() {
+ // Stub: Load the gridObject from a data source
+ console.log('loadGridObject: Loading saved gridObject',this.gridObjectSaved);
+ this.gridObject = this.gridObjectSaved;
+ this.modelCalc();
+ //this.updateFormFromGridObject();
+ console.log('Loaded gridObject:', this.gridObject);
+ }
+
+ saveGridObject() {
+ // Stub: Save the gridObject to a data source
+
+ this.updateGridObjectFromForm();
+ this.modelCalc();
+ //This needs to be a deep copy. If we do a shallow copy, the gridObjectSaved will be updated whenever the gridObject is updated.
+ this.gridObjectSaved = JSON.parse(JSON.stringify(this.gridObject));
+ console.log('7. Saved gridObject:', this.gridObjectSaved);
+ }
+
+ private updateFormFromGridObject() {
+ //console.log('updateFormFromGridObject: Updating form from gridObject');
+ this.form.patchValue({
+ current: this.convertMetricStateToString(this.gridObject.current),
+ target: this.convertMetricStateToString(this.gridObject.target),
+ max: this.convertMetricStateToString(this.gridObject.max)
+ });
+ console.log('6. Updated form values:', this.form.value);
+ }
+
+ private updateGridObjectFromForm() {
+ //console.log('updateGridObjectFromForm: Updating gridObject from form');
+ const currentFormValue = this.form.get('current')?.value || {};
+ const targetFormValue = this.form.get('target')?.value || {};
+ const maxFormValue = this.form.get('max')?.value || {};
+
+ this.gridObject.current = this.convertMetricStateToNumber(currentFormValue);
+ this.gridObject.target = this.convertMetricStateToNumber(targetFormValue);
+ this.gridObject.max = this.convertMetricStateToNumber(maxFormValue);
+ // print out gridObject for debugging
+ console.log('6. Updated gridObject from form:', this.gridObject);
+ }
+
+ private convertMetricStateToString(metricState: MetricState): { [key: string]: string } {
+ const result: { [key: string]: string } = {};
+ for (const key in metricState) {
+ if (metricState.hasOwnProperty(key)) {
+ result[key] = this.decimalPipe.transform(metricState[key], '1.0-0') || '0';
+ console.log('called convertMetricStateToString:', key.toString);
+ }
+ }
+ return result;
+ }
+
+ private convertMetricStateToNumber(metricState: { [key: string]: string }): MetricState {
+ const result: MetricState = {
+ seats: 0,
+ adoptedDevs: 0,
+ monthlyDevsReportingTimeSavings: 0,
+ percentSeatsReportingTimeSavings: 0,
+ percentSeatsAdopted: 0,
+ percentMaxAdopted: 0,
+ dailySuggestions: 0,
+ dailyChatTurns: 0,
+ weeklyPRSummaries: 0,
+ weeklyTimeSaved: 0,
+ monthlyTimeSavings: 0,
+ annualTimeSavingsDollars: 0,
+ productivityBoost: 0
+ };
+ for (const key in metricState) {
+ if (metricState.hasOwnProperty(key)) {
+ const value = parseFloat(metricState[key].replace(/,/g, '').replace(/[^0-9.-]+/g, ''));
+ result[key as keyof MetricState] = isNaN(value) ? 0 : value;
+ }
+ }
+ return result;
}
- private calculateOverallImpact(level: 'current' | 'target' | 'max'): number {
- const values = this.form?.get(level)?.value;
- if (!values) return 0;
+ modelCalc() {
+ try {
+ console.log('3. modelCalc: Calculating model');
+ // 1. Calculate Max column percentages and then Impacts
+ this.gridObject.max.percentSeatsAdopted = this.calculatePercentage(this.gridObject.max.adoptedDevs, this.gridObject.max.seats);
+ this.gridObject.max.percentSeatsReportingTimeSavings = this.calculatePercentage(this.gridObject.max.monthlyDevsReportingTimeSavings, this.gridObject.max.seats);
+ this.gridObject.max.percentMaxAdopted = this.calculatePercentage(this.gridObject.max.adoptedDevs, this.gridObject.max.seats);
+ this.gridObject.max.annualTimeSavingsDollars = this.calculateAnnualTimeSavingsDollars(this.gridObject.max.weeklyTimeSaved, this.gridObject.max.adoptedDevs);
+ this.gridObject.max.monthlyTimeSavings = this.calculateMonthlyTimeSavings(this.gridObject.max.adoptedDevs, this.gridObject.max.weeklyTimeSaved);
+ this.gridObject.max.productivityBoost = this.calculateProductivityBoost(this.gridObject.max.dailySuggestions, this.gridObject.max.dailyChatTurns);
- const usageAvg = (Number(values.activityLevel) + Number(values.timeSavings)) / 2;
- return (Number(values.adoption) * usageAvg * Number(values.downstreamProductivity)) / 10000;
+ // 2. Calculate Current column percentages and then Impacts
+ this.gridObject.current.percentSeatsAdopted = this.calculatePercentage(this.gridObject.current.adoptedDevs, this.gridObject.current.seats);
+ this.gridObject.current.percentSeatsReportingTimeSavings = this.calculatePercentage(this.gridObject.current.monthlyDevsReportingTimeSavings, this.gridObject.current.seats);
+ this.gridObject.current.percentMaxAdopted = this.calculatePercentage(this.gridObject.current.adoptedDevs, this.gridObject.current.seats);
+ this.gridObject.current.annualTimeSavingsDollars = this.calculateAnnualTimeSavingsDollars(this.gridObject.current.weeklyTimeSaved, this.gridObject.current.adoptedDevs);
+ this.gridObject.current.monthlyTimeSavings = this.calculateMonthlyTimeSavings(this.gridObject.current.adoptedDevs, this.gridObject.current.weeklyTimeSaved);
+ this.gridObject.current.productivityBoost = this.calculateProductivityBoost(this.gridObject.current.dailySuggestions, this.gridObject.current.dailyChatTurns);
+
+ // 3. Calculate Target column values (percentages and then impacts)
+ this.gridObject.target.percentSeatsAdopted = this.calculatePercentage(this.gridObject.target.adoptedDevs, this.gridObject.target.seats);
+ this.gridObject.target.percentSeatsReportingTimeSavings = this.calculatePercentage(this.gridObject.target.monthlyDevsReportingTimeSavings, this.gridObject.target.seats);
+ this.gridObject.target.percentMaxAdopted = this.calculatePercentage(this.gridObject.target.adoptedDevs, this.gridObject.target.seats);
+ this.gridObject.target.annualTimeSavingsDollars = this.calculateAnnualTimeSavingsDollars(this.gridObject.target.weeklyTimeSaved, this.gridObject.target.adoptedDevs);
+ this.gridObject.target.monthlyTimeSavings = this.calculateMonthlyTimeSavings(this.gridObject.target.adoptedDevs, this.gridObject.target.weeklyTimeSaved);
+ this.gridObject.target.productivityBoost = this.calculateProductivityBoost(this.gridObject.target.dailySuggestions, this.gridObject.target.dailyChatTurns);
+// 4. Update the form values
+
+ console.log('4. modelCalc: Updated gridObject:', this.gridObject);
+ this.updateFormFromGridObject();
+ } catch (error) {
+ const errorMessage = (error instanceof Error) ? error.message : 'An unknown error occurred';
+ console.error(`5. Error in ModelCalc: ${errorMessage}`);
+ alert(`Error in ModelCalc: ${errorMessage}`);
+ }
}
+
+ private calculatePercentage(numerator: number, denominator: number): number {
+ if (denominator === 0) {
+ return 0;
+ }
+ return (numerator / denominator) * 100;
+ }
+
+ private calculateAnnualTimeSavingsDollars(weeklyTimeSaved: number, adoptedDevs: number): number {
+ const weeksInYear = 50; // TO DO: needs to come from settings
+ const hourlyRate = 50; // TO DO: needs to come from settings
+ return weeklyTimeSaved * weeksInYear * hourlyRate * adoptedDevs;
+ }
+
+ private calculateProductivityBoost(dailySuggestions: number, dailyChatTurns: number): number {
+ return dailySuggestions + dailyChatTurns; // Example calculation
+ }
+
+ private calculateMonthlyTimeSavings(adoptedDevs: number, weeklyTimeSaved: number): number {
+ return adoptedDevs * weeklyTimeSaved * 4;
+ }
+
+ toggleInputs(disable: boolean) {
+ if (disable) {
+ this.disableInputs = true;
+ } else {
+ this.disableInputs = false;
+ }
+ console.log('disableInputs:', this.disableInputs);
+}
}
\ No newline at end of file
diff --git a/frontend/src/app/shared/directives/thousand-separator.directive.ts b/frontend/src/app/shared/directives/thousand-separator.directive.ts
new file mode 100644
index 0000000..2e053ce
--- /dev/null
+++ b/frontend/src/app/shared/directives/thousand-separator.directive.ts
@@ -0,0 +1,26 @@
+import { Directive, ElementRef, HostListener } from '@angular/core';
+
+@Directive({
+ selector: '[matThousandSeparator]'
+})
+export class ThousandSeparatorDirective {
+ private regex: RegExp = new RegExp(/^\d+$/);
+
+ constructor(private el: ElementRef) {}
+
+ @HostListener('input', ['$event'])
+ onInputChange(event: Event) {
+ const input = event.target as HTMLInputElement;
+ let value = input.value.replace(/,/g, '');
+ if (this.regex.test(value)) {
+ value = this.formatNumber(value);
+ input.value = value;
+ } else {
+ input.value = input.value.slice(0, -1);
+ }
+ }
+
+ private formatNumber(value: string): string {
+ return value.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+ }
+}
diff --git a/frontend/src/app/shared/pipes/currency.pipe.ts b/frontend/src/app/shared/pipes/currency.pipe.ts
new file mode 100644
index 0000000..24d19ef
--- /dev/null
+++ b/frontend/src/app/shared/pipes/currency.pipe.ts
@@ -0,0 +1,13 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+ name: 'matCurrencyPipe'
+})
+export class CurrencyPipe implements PipeTransform {
+ transform(value: number | string): string {
+ if (typeof value === 'string') {
+ value = parseFloat(value.replace(/,/g, ''));
+ }
+ return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value);
+ }
+}
diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts
new file mode 100644
index 0000000..a7aed3f
--- /dev/null
+++ b/frontend/src/app/shared/shared.module.ts
@@ -0,0 +1,11 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ThousandSeparatorDirective } from './directives/thousand-separator.directive';
+import { CurrencyPipe } from './pipes/currency.pipe';
+
+@NgModule({
+ declarations: [ThousandSeparatorDirective, CurrencyPipe],
+ imports: [CommonModule],
+ exports: [ThousandSeparatorDirective, CurrencyPipe]
+})
+export class SharedModule {}