mirror of https://github.com/ghostfolio/ghostfolio
				
				
			
							committed by
							
								
								Thomas
							
						
					
				
				 10 changed files with 258 additions and 2 deletions
			
			
		@ -0,0 +1,6 @@ | 
				
			|||
describe('ui', () => { | 
				
			|||
  beforeEach(() => cy.visit('/iframe.html?id=valuecomponent--loading')); | 
				
			|||
  it('should render the component', () => { | 
				
			|||
    cy.get('gf-value').should('exist'); | 
				
			|||
  }); | 
				
			|||
}); | 
				
			|||
@ -1,7 +1,15 @@ | 
				
			|||
import { NgModule } from '@angular/core'; | 
				
			|||
import { CommonModule } from '@angular/common'; | 
				
			|||
import { ValueComponent } from './value/value.component'; | 
				
			|||
// import { GfValueModule } from './value/value.module';
 | 
				
			|||
 | 
				
			|||
@NgModule({ | 
				
			|||
  imports: [CommonModule] | 
				
			|||
  imports: [CommonModule/*, GfValueModule*/], | 
				
			|||
  declarations: [ | 
				
			|||
    ValueComponent | 
				
			|||
  ], | 
				
			|||
  exports: [ | 
				
			|||
    ValueComponent | 
				
			|||
  ] | 
				
			|||
}) | 
				
			|||
export class UiModule {} | 
				
			|||
 | 
				
			|||
@ -0,0 +1,46 @@ | 
				
			|||
<ng-container *ngIf="value || value === 0 || value === null"> | 
				
			|||
    <div | 
				
			|||
      class="d-flex" | 
				
			|||
      [ngClass]="position === 'end' ? 'justify-content-end' : ''" | 
				
			|||
    > | 
				
			|||
      <ng-container *ngIf="isNumber || value === null"> | 
				
			|||
        <div *ngIf="colorizeSign && value > 0" class="mr-1 text-success">+</div> | 
				
			|||
        <div *ngIf="colorizeSign && value < 0" class="mr-1 text-danger">-</div> | 
				
			|||
        <div *ngIf="isPercent" [ngClass]="size === 'medium' ? 'h4 mb-0' : ''"> | 
				
			|||
          {{ formattedValue }}% | 
				
			|||
        </div> | 
				
			|||
        <div *ngIf="!isPercent" [ngClass]="size === 'medium' ? 'h4 mb-0' : ''"> | 
				
			|||
          <ng-container *ngIf="value === null"> | 
				
			|||
            <span class="text-monospace text-muted">***</span> | 
				
			|||
          </ng-container> | 
				
			|||
          <ng-container *ngIf="value !== null"> | 
				
			|||
            {{ formattedValue }} | 
				
			|||
          </ng-container> | 
				
			|||
        </div> | 
				
			|||
        <small *ngIf="currency && size === 'medium'" class="ml-1"> | 
				
			|||
          {{ currency }} | 
				
			|||
        </small> | 
				
			|||
        <div *ngIf="currency && size !== 'medium'" class="ml-1"> | 
				
			|||
          {{ currency }} | 
				
			|||
        </div> | 
				
			|||
      </ng-container> | 
				
			|||
      <ng-container *ngIf="isDate"> | 
				
			|||
        <div [ngClass]="size === 'medium' ? 'h4 mb-0' : ''"> | 
				
			|||
          {{ formattedDate }} | 
				
			|||
        </div> | 
				
			|||
      </ng-container> | 
				
			|||
    </div> | 
				
			|||
    <small *ngIf="label"> | 
				
			|||
      {{ label }} | 
				
			|||
    </small> | 
				
			|||
  </ng-container> | 
				
			|||
   | 
				
			|||
  <ngx-skeleton-loader | 
				
			|||
    *ngIf="value === undefined" | 
				
			|||
    animation="pulse" | 
				
			|||
    [theme]="{ | 
				
			|||
      height: '1.5rem', | 
				
			|||
      width: '5rem' | 
				
			|||
    }" | 
				
			|||
  ></ngx-skeleton-loader> | 
				
			|||
   | 
				
			|||
@ -0,0 +1,5 @@ | 
				
			|||
:host { | 
				
			|||
  display: flex; | 
				
			|||
  flex-direction: column; | 
				
			|||
  font-variant-numeric: tabular-nums; | 
				
			|||
} | 
				
			|||
@ -0,0 +1,25 @@ | 
				
			|||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | 
				
			|||
 | 
				
			|||
import { ValueComponent } from './value.component'; | 
				
			|||
 | 
				
			|||
describe('ValueComponent', () => { | 
				
			|||
  let component: ValueComponent; | 
				
			|||
  let fixture: ComponentFixture<ValueComponent>; | 
				
			|||
 | 
				
			|||
  beforeEach(async () => { | 
				
			|||
    await TestBed.configureTestingModule({ | 
				
			|||
      declarations: [ ValueComponent ] | 
				
			|||
    }) | 
				
			|||
    .compileComponents(); | 
				
			|||
  }); | 
				
			|||
 | 
				
			|||
  beforeEach(() => { | 
				
			|||
    fixture = TestBed.createComponent(ValueComponent); | 
				
			|||
    component = fixture.componentInstance; | 
				
			|||
    fixture.detectChanges(); | 
				
			|||
  }); | 
				
			|||
 | 
				
			|||
  it('should create', () => { | 
				
			|||
    expect(component).toBeTruthy(); | 
				
			|||
  }); | 
				
			|||
}); | 
				
			|||
@ -0,0 +1,38 @@ | 
				
			|||
import { moduleMetadata, Story, Meta } from '@storybook/angular'; | 
				
			|||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; | 
				
			|||
import { ValueComponent } from './value.component'; | 
				
			|||
 | 
				
			|||
export default { | 
				
			|||
  title: 'Value', | 
				
			|||
  component: ValueComponent, | 
				
			|||
  decorators: [ | 
				
			|||
    moduleMetadata({ | 
				
			|||
      imports: [NgxSkeletonLoaderModule], | 
				
			|||
    }) | 
				
			|||
  ], | 
				
			|||
} as Meta<ValueComponent>; | 
				
			|||
 | 
				
			|||
const Template: Story<ValueComponent> = (args: ValueComponent) => ({ | 
				
			|||
  props: args, | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
export const Loading = Template.bind({}); | 
				
			|||
Loading.args = { | 
				
			|||
  value: undefined | 
				
			|||
} | 
				
			|||
 | 
				
			|||
export const Integer = Template.bind({}); | 
				
			|||
Integer.args = { | 
				
			|||
  isInteger: true, | 
				
			|||
  locale: 'en', | 
				
			|||
  value: 7 | 
				
			|||
} | 
				
			|||
 | 
				
			|||
export const Currency = Template.bind({}); | 
				
			|||
Currency.args = { | 
				
			|||
  currency: 'USD', | 
				
			|||
  isInteger: true, | 
				
			|||
  label: 'Label', | 
				
			|||
  locale: 'en', | 
				
			|||
  value: 7 | 
				
			|||
} | 
				
			|||
@ -0,0 +1,108 @@ | 
				
			|||
import { | 
				
			|||
  ChangeDetectionStrategy, | 
				
			|||
  Component, | 
				
			|||
  Input, | 
				
			|||
  OnChanges, | 
				
			|||
} from '@angular/core'; | 
				
			|||
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config'; | 
				
			|||
import { format, isDate } from 'date-fns'; | 
				
			|||
import { isNumber } from 'lodash'; | 
				
			|||
 | 
				
			|||
@Component({ | 
				
			|||
  selector: 'gf-value', | 
				
			|||
  changeDetection: ChangeDetectionStrategy.OnPush, | 
				
			|||
  templateUrl: './value.component.html', | 
				
			|||
  styleUrls: ['./value.component.scss'] | 
				
			|||
}) | 
				
			|||
export class ValueComponent implements OnChanges { | 
				
			|||
  @Input() colorizeSign = false; | 
				
			|||
  @Input() currency = ''; | 
				
			|||
  @Input() isCurrency = false; | 
				
			|||
  @Input() isInteger = false; | 
				
			|||
  @Input() isPercent = false; | 
				
			|||
  @Input() label = ''; | 
				
			|||
  @Input() locale = ''; | 
				
			|||
  @Input() position = ''; | 
				
			|||
  @Input() size = ''; | 
				
			|||
  @Input() value: number | string = ''; | 
				
			|||
 | 
				
			|||
  public absoluteValue = 0; | 
				
			|||
  public formattedDate = ''; | 
				
			|||
  public formattedValue = ''; | 
				
			|||
  public isDate = false; | 
				
			|||
  public isNumber = false; | 
				
			|||
  public useAbsoluteValue = false; | 
				
			|||
 | 
				
			|||
  public constructor() {} | 
				
			|||
 | 
				
			|||
  public ngOnChanges() { | 
				
			|||
    if (this.value || this.value === 0) { | 
				
			|||
      if (isNumber(this.value)) { | 
				
			|||
        this.isDate = false; | 
				
			|||
        this.isNumber = true; | 
				
			|||
        this.absoluteValue = Math.abs(<number>this.value); | 
				
			|||
 | 
				
			|||
        if (this.colorizeSign) { | 
				
			|||
          this.useAbsoluteValue = true; | 
				
			|||
          if (this.currency || this.isCurrency) { | 
				
			|||
            try { | 
				
			|||
              this.formattedValue = this.absoluteValue.toLocaleString( | 
				
			|||
                this.locale, | 
				
			|||
                { | 
				
			|||
                  maximumFractionDigits: 2, | 
				
			|||
                  minimumFractionDigits: 2 | 
				
			|||
                } | 
				
			|||
              ); | 
				
			|||
            } catch {} | 
				
			|||
          } else if (this.isPercent) { | 
				
			|||
            try { | 
				
			|||
              this.formattedValue = (this.absoluteValue * 100).toLocaleString( | 
				
			|||
                this.locale, | 
				
			|||
                { | 
				
			|||
                  maximumFractionDigits: 2, | 
				
			|||
                  minimumFractionDigits: 2 | 
				
			|||
                } | 
				
			|||
              ); | 
				
			|||
            } catch {} | 
				
			|||
          } | 
				
			|||
        } else if (this.isPercent) { | 
				
			|||
          try { | 
				
			|||
            this.formattedValue = (this.value * 100).toLocaleString( | 
				
			|||
              this.locale, | 
				
			|||
              { | 
				
			|||
                maximumFractionDigits: 2, | 
				
			|||
                minimumFractionDigits: 2 | 
				
			|||
              } | 
				
			|||
            ); | 
				
			|||
          } catch {} | 
				
			|||
        } else if (this.currency || this.isCurrency) { | 
				
			|||
          try { | 
				
			|||
            this.formattedValue = this.value?.toLocaleString(this.locale, { | 
				
			|||
              maximumFractionDigits: 2, | 
				
			|||
              minimumFractionDigits: 2 | 
				
			|||
            }); | 
				
			|||
          } catch {} | 
				
			|||
        } else if (this.isInteger) { | 
				
			|||
          try { | 
				
			|||
            this.formattedValue = this.value?.toLocaleString(this.locale, { | 
				
			|||
              maximumFractionDigits: 0, | 
				
			|||
              minimumFractionDigits: 0 | 
				
			|||
            }); | 
				
			|||
          } catch {} | 
				
			|||
        } | 
				
			|||
      } else { | 
				
			|||
        try { | 
				
			|||
          if (isDate(new Date(this.value))) { | 
				
			|||
            this.isDate = true; | 
				
			|||
            this.isNumber = false; | 
				
			|||
 | 
				
			|||
            this.formattedDate = format( | 
				
			|||
              new Date(<string>this.value), | 
				
			|||
              DEFAULT_DATE_FORMAT | 
				
			|||
            ); | 
				
			|||
          } | 
				
			|||
        } catch {} | 
				
			|||
      } | 
				
			|||
    } | 
				
			|||
  } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,13 @@ | 
				
			|||
import { CommonModule } from '@angular/common'; | 
				
			|||
import { NgModule } from '@angular/core'; | 
				
			|||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; | 
				
			|||
 | 
				
			|||
import { ValueComponent } from './value.component'; | 
				
			|||
 | 
				
			|||
@NgModule({ | 
				
			|||
  declarations: [ValueComponent], | 
				
			|||
  exports: [ValueComponent], | 
				
			|||
  imports: [CommonModule, NgxSkeletonLoaderModule], | 
				
			|||
  providers: [] | 
				
			|||
}) | 
				
			|||
export class GfValueModule {} | 
				
			|||
					Loading…
					
					
				
		Reference in new issue