import { Component } from '@angular/core';

@Component({
  selector: 'main-app',
  templateUrl: './app/app.template.html'
})
export class MainComponent {
  
   counter: number = 0;
   
   onClick() {
      this.counter++;
   }
}
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { MainComponent } from './app.component';
import { ChartComponent } from './chart.component';
import { TreeTableComponent } from './treetable.component';

// Import PrimeNG modules
import {AccordionModule} from 'primeng/primeng';
import {AutoCompleteModule} from 'primeng/primeng';
import {BreadcrumbModule} from 'primeng/primeng';
import {ButtonModule} from 'primeng/primeng';
import {CalendarModule} from 'primeng/primeng';
import {CarouselModule} from 'primeng/primeng';
import {ChartModule} from 'primeng/primeng';
import {CheckboxModule} from 'primeng/primeng';
import {ChipsModule} from 'primeng/primeng';
import {CodeHighlighterModule} from 'primeng/primeng';
import {ConfirmDialogModule} from 'primeng/primeng';
import {SharedModule} from 'primeng/primeng';
import {ContextMenuModule} from 'primeng/primeng';
import {DataGridModule} from 'primeng/primeng';
import {DataListModule} from 'primeng/primeng';
import {DataScrollerModule} from 'primeng/primeng';
import {DataTableModule} from 'primeng/primeng';
import {DialogModule} from 'primeng/primeng';
import {DragDropModule} from 'primeng/primeng';
import {DropdownModule} from 'primeng/primeng';
import {EditorModule} from 'primeng/primeng';
import {FieldsetModule} from 'primeng/primeng';
import {FileUploadModule} from 'primeng/primeng';
import {GalleriaModule} from 'primeng/primeng';
import {GMapModule} from 'primeng/primeng';
import {GrowlModule} from 'primeng/primeng';
import {InputMaskModule} from 'primeng/primeng';
import {InputSwitchModule} from 'primeng/primeng';
import {InputTextModule} from 'primeng/primeng';
import {InputTextareaModule} from 'primeng/primeng';
import {LightboxModule} from 'primeng/primeng';
import {ListboxModule} from 'primeng/primeng';
import {MegaMenuModule} from 'primeng/primeng';
import {MenuModule} from 'primeng/primeng';
import {MenubarModule} from 'primeng/primeng';
import {MessagesModule} from 'primeng/primeng';
import {MultiSelectModule} from 'primeng/primeng';
import {OrderListModule} from 'primeng/primeng';
import {OverlayPanelModule} from 'primeng/primeng';
import {PaginatorModule} from 'primeng/primeng';
import {PanelModule} from 'primeng/primeng';
import {PanelMenuModule} from 'primeng/primeng';
import {PasswordModule} from 'primeng/primeng';
import {PickListModule} from 'primeng/primeng';
import {ProgressBarModule} from 'primeng/primeng';
import {RadioButtonModule} from 'primeng/primeng';
import {RatingModule} from 'primeng/primeng';
import {ScheduleModule} from 'primeng/primeng';
import {SelectButtonModule} from 'primeng/primeng';
import {SlideMenuModule} from 'primeng/primeng';
import {SliderModule} from 'primeng/primeng';
import {SpinnerModule} from 'primeng/primeng';
import {SplitButtonModule} from 'primeng/primeng';
import {StepsModule} from 'primeng/primeng';
import {TabMenuModule} from 'primeng/primeng';
import {TabViewModule} from 'primeng/primeng';
import {TerminalModule} from 'primeng/primeng';
import {TieredMenuModule} from 'primeng/primeng';
import {ToggleButtonModule} from 'primeng/primeng';
import {ToolbarModule} from 'primeng/primeng';
import {TooltipModule} from 'primeng/primeng';
import {TreeModule} from 'primeng/primeng';
import {TreeTableModule} from 'primeng/primeng';
import {TriStateCheckboxModule} from 'primeng/primeng';

@NgModule({
  imports: [ 
        BrowserModule, 
        FormsModule,
//        AccordionModule,
        AutoCompleteModule,
//        BreadcrumbModule,
        ButtonModule,
//        CalendarModule,
//        CarouselModule,
        ChartModule,
        CheckboxModule,
//        ChipsModule,
//        CodeHighlighterModule,
        ConfirmDialogModule,
        SharedModule,
        ContextMenuModule, 
        DataGridModule,
        DataListModule,
        DataScrollerModule,
        DataTableModule,
        DialogModule,
        DragDropModule,
        DropdownModule,
//        EditorModule,
//        FieldsetModule,
//        FileUploadModule,
//        GalleriaModule,
//        GMapModule,
        GrowlModule,
        InputMaskModule,
        InputSwitchModule,
        InputTextModule,
        InputTextareaModule,
//        LightboxModule,
//        ListboxModule,
//        MegaMenuModule,
        MenuModule,
        MenubarModule,
        MessagesModule,
        MultiSelectModule,
//        OrderListModule,
        OverlayPanelModule,
        PaginatorModule,
        PanelModule,
        PanelMenuModule,
        PasswordModule,
//        PickListModule,
        ProgressBarModule,
        RadioButtonModule,
        RatingModule,
//        ScheduleModule,
        SelectButtonModule,
        SlideMenuModule,
        SliderModule,
        SpinnerModule,
        SplitButtonModule,
        StepsModule,
        TabMenuModule,
        TabViewModule,
//        TerminalModule,
//        TieredMenuModule,
        ToggleButtonModule,
        ToolbarModule,
        TooltipModule,
        TreeModule,
        TreeTableModule,
        TriStateCheckboxModule
    ],
  declarations: [ MainComponent, ChartComponent, TreeTableComponent ],
  bootstrap:    [ MainComponent, ChartComponent, TreeTableComponent ]
})

export class AppModule { }
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app.module';

const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);
<!DOCTYPE html>
<html>

<head>
  <title>Angular QuickStart</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!--<link rel="stylesheet" href="styles.css">-->

  <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
  
  <!-- Bootstrap dependencies -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous"></script>


  <!-- PrimeNG style dependencies -->
  <link rel="stylesheet" href="https://unpkg.com/primeng@4.0.1/resources/themes/omega/theme.css" />
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" />
  <link rel="stylesheet" href="https://unpkg.com/primeng@4.0.1/resources/primeng.min.css" />

  <!-- Polyfill for older browsers -->
  <script src="https://unpkg.com/core-js/client/shim.min.js"></script>
  
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.6/Chart.bundle.js"></script>

  <script src="https://unpkg.com/zone.js@0.8.4?main=browser"></script>
  <script src="https://unpkg.com/reflect-metadata@0.1.8"></script>
  <script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>

  <!-- 2. Configure SystemJS -->
  <script src="systemjs.config.js"></script>

  <style>
    body {
      padding: 1em;
      font-family: Arial, Helvetica, sans-serif;
    }
    .vote-treetable td,
    .ui-treetable tbody .ui-treetable-row td {
      position: relative;
    }
    .ui-widget-header .ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-state-highlight
    {
      background: #117ec315;
    }
  </style>
</head>

<body>
  <main-app>Loading my-app...</main-app>
  <br/>
</body>

</html>
/**
 * WEB ANGULAR VERSION
 * (based on systemjs.config.js in angular.io)
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
  System.config({
    // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
    transpiler: 'ts',
    typescriptOptions: {
      // Copy of compiler options in standard tsconfig.json
      "target": "es5",
      "module": "commonjs",
      "moduleResolution": "node",
      "sourceMap": true,
      "emitDecoratorMetadata": true,
      "experimentalDecorators": true,
      "noImplicitAny": true,
      "suppressImplicitAnyIndexErrors": true
    },
    meta: {
      'typescript': {
        "exports": "ts"
      }
    },
    paths: {
      // paths serve as alias
      'npm:': 'https://unpkg.com/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      app: 'app',

     // angular bundles
      '@angular/animations': 'npm:@angular/animations@4.0.0/bundles/animations.umd.js',
      '@angular/core': 'npm:@angular/core@4.0.0/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common@4.0.0/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler@4.0.0/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser@4.0.0/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic@4.0.0/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http@4.0.0/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router@4.0.0/bundles/router.umd.js',
      '@angular/forms': 'npm:@angular/forms@4.0.0/bundles/forms.umd.js',
      '@angular/upgrade': 'npm:@angular/upgrade@4.0.0/bundles/upgrade.umd.js',
      '@angular/upgrade/static': 'npm:@angular/upgrade@4.0.0/bundles/upgrade-static.umd.js',

      // other libraries
      'rxjs':                      'npm:rxjs',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',
      'ts':                        'npm:plugin-typescript@4.0.10/lib/plugin.js',
      'typescript':                'npm:typescript@2.0.3/lib/typescript.js',
      'primeng':                   'npm:primeng@4.0.1'

    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      app: {
        main: './main.ts',
        defaultExtension: 'ts'
      },
      rxjs: {
        defaultExtension: 'js'
      },
      primeng: {
        defaultExtension: 'js'
      }
    }
  });

  if (!global.noBootstrap) { bootstrap(); }

  // Bootstrap the `AppModule`(skip the `app/main.ts` that normally does this)
  function bootstrap() {
    console.log('Auto-bootstrapping');

    // Stub out `app/main.ts` so System.import('app') doesn't fail if called in the index.html
    System.set(System.normalizeSync('app/main.ts'), System.newModule({ }));

    // bootstrap and launch the app (equivalent to standard main.ts)
    Promise.all([
      System.import('@angular/platform-browser-dynamic'),
      getAppModule()
    ])
    .then(function (imports) {
      var platform = imports[0];
      var app      = imports[1];
      platform.platformBrowserDynamic().bootstrapModule(app.AppModule);
    })
    .catch(function(err){ console.error(err); });
  }

  // Import AppModule or make the default AppModule if there isn't one
  // returns a promise for the AppModule
  function getAppModule() {
    if (global.noAppModule) {
      return makeAppModule();
    }
    return System.import('app/app.module').catch(makeAppModule)
  }

  function makeAppModule() {
    console.log('No AppModule; making a bare-bones, default AppModule');

    return Promise.all([
      System.import('@angular/core'),
      System.import('@angular/platform-browser'),
      System.import('app/app.component')
    ])
    .then(function (imports) {

      var core    = imports[0];
      var browser = imports[1];
      var appComp = imports[2].AppComponent;

      var AppModule = function() {}

      AppModule.annotations = [
        new core.NgModule({
          imports:      [ browser.BrowserModule ],
          declarations: [ appComp ],
          bootstrap:    [ appComp ]
        })
      ]
      return {AppModule: AppModule};
    })
  }
})(this);
<button pButton label="Increment" (click)="onClick($event)"></button>
{{counter}}


<p-tabView>
    <p-tabPanel header="Votes">
        <treetable-app>Loading votes...</treetable-app>
    </p-tabPanel>
    <p-tabPanel header="Line Chart">
        <chart-app type="line">Loading Line Chart...</chart-app>
    </p-tabPanel>
    <p-tabPanel header="Bar Chart">
        <chart-app type="bar">Loading Bar Chart...</chart-app>
    </p-tabPanel>
    <p-tabPanel header="Doughnut Chart">
        <chart-app type="doughnut">Loading Doughnut Chart...</chart-app>
    </p-tabPanel>
</p-tabView>



import { Component, OnInit, ViewChild, Input } from '@angular/core';

@Component({
    selector: 'chart-app',
    templateUrl: './app/chart.template.html'
})
export class ChartComponent implements OnInit {
    @Input()
    type: string = 'line';

    @ViewChild('monthlyChart')
    monthlyChart;

    @ViewChild('weeklyChart')
    weeklyChart;
    
    public msgs: Message[];

    constructor() {
        this.weeklyDataOptions = {
            title: {
                display: true,
                text: 'Realtime Data',
                fontSize: 16
            },
            legend: {
                display: false,
                position: 'right'
            }
        }

        this.bgColors = {
            backgroundColor: [
                "#FF6384",
                "#36A2EB",
                "#FFCE56",
                "#63FF84",
                "#A236EB",
                "#CEFF56",
                "#EBA236"
            ],
            hoverBackgroundColor: [
                "#FF83A4",
                "#56C2FB",
                "#FFEE76",
                "#83FFA4",
                "#C256FB",
                "#EEFF76",
                "#FBC256"
            ]
        };
    }
  
    ngOnInit() {
        let options = this.type === 'line'
            ? {}
            : this.bgColors;

        this.monthlyData = {
            labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
            datasets: [
                {
                    label: 'First Dataset',
                    borderColor: '#4bc0c0',
                },
                {
                    label: 'Second Dataset',
                    borderColor: '#565656',
                }
            ]
        }
        
        this.weeklyData = {
            labels: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
            datasets: [{ label: 'Weekly' }],
        };

        this.UpdateChart(this.monthlyChart, this.monthlyData.datasets, Object.assign(options, { fill: false }));
        this.UpdateChart(this.weeklyChart, this.weeklyData.datasets, Object.assign(options, { fill: true }));
        setInterval(() =>
            this.UpdateChart(this.weeklyChart, this.weeklyData.datasets)
        , 5000);
    }

    public UpdateChart(chart: ChartComponent, datasets: Array<Object>, options: Object) {
        //console.log('updating');
        datasets.map(ds => Object.assign(ds, { data: this.getRandomValues(7, 10) }, options));
        chart.refresh(); 
    }

    selectData(event) {
        let msg = this.monthlyData.datasets[event.element._datasetIndex].data[event.element._index];
        this.msgs = [];
        this.msgs.push({
          severity: 'info', 
          summary: 'Data Selected', 
          detail: msg
        });
    }
    
    public getRandomValues(rowCount: number, maxValue: number): Array<number> {
        return new Array(rowCount).fill(0).map(() => Math.round(Math.random() * maxValue));
    }
}


{{monthlyData.labels[0]}}

<p-growl [(value)]="msgs"></p-growl>

<div style="max-width: 400px">

    <p-chart #monthlyChart [type]="type" [data]="monthlyData" (onDataSelect)="selectData($event)"></p-chart>

    <p-chart #weeklyChart [type]="type" [data]="weeklyData" [options]="weeklyDataOptions"></p-chart>
    
</div>
<p-growl [value]="msgs"></p-growl>

<p-treeTable [value]="data" selectionMode="multiple" [(selection)]="selectedReasons" (onNodeSelect)="reasonSelect($event)" (onNodeUnselect)="reasonUnselect($event)" (onNodeExpand)="reasonExpand($event)" [contextMenu]="cm" reorderableColumns="true" class="vote-treetable">
  <p-header>v0.1a</p-header>
  <!--<p-column *ngFor="let col of cols" [field]="col.field" [header]="col.header"></p-column>-->
  <p-column field="Title" header="Title">
    <ng-template let-node="rowData" pTemplate="body">
      <input [(ngModel)]="node.data.Title" style="background: transparent; border: none;">
    </ng-template>
  </p-column>
  <!--
  <p-column field="Priority" header="Priority">
    <ng-template let-node="rowData" pTemplate="body">
        <p-rating [(ngModel)]="node.data.Priority" [cancel]="false"
            (onRate)="handleRate($event)" (onCancel)="handleCancel($event)"></p-rating> 
        <span *ngIf="msg" style="margin-left:10px">{{msg}}</span>
    </ng-template>
  </p-column>-->

  <p-column field="Weight" header="Weight">
    <ng-template let-node="rowData" pTemplate="body">
        <!--<p-triStateCheckbox [(ngModel)]="node.data.Weight"></p-triStateCheckbox>-->
        <p-slider [(ngModel)]="node.data.Weight" min="-3" max="3" (onChange)="voteChange($event)" animate="true"></p-slider>
    </ng-template>
  </p-column>
  
  <p-column *ngFor="let c of choices;let j = index;" [field]="node.data.VoteGroups[j].ReasonId" [header]="c.Title">
    <ng-template let-node="rowData" pTemplate="body">
      <!--<p-slider [(ngModel)]="node.data.Weight" min="-3" max="3" (onChange)="voteChange($event)" animate="true"></p-slider>-->
      <p-spinner [(ngModel)]="node.data.VoteGroups[j].ReasonId" class="vote-spinner" min="-3" max="3" size="2">
      </p-spinner>
      <!--<input type="text" pInputText [(ngModel)]="node.data.Weight"/> (onChange)="voteChange($event)"-->
    </ng-template>
  </p-column>
</p-treeTable>

<style>
  .vote-treetable td,
  .ui-treetable tbody .ui-treetable-row td {
    position: relative !important;
  }
  
  .vote-spinner {
    position: absolute;
    right: 2px;
    top: 50%;
    transform: translateY(-50%);
    opacity: 0.9;
  }
  
  .vote-spinner > input {
    width: 100%;
  }
</style>

<p>Selected Nodes: <span *ngFor="let r of selectedReasons">{{r.data.Title}}<br/></span></p>

<p-contextMenu #cm [model]="menuItems"></p-contextMenu>
import { Component, OnInit, ViewChild, Input } from '@angular/core';
import {Header} from 'primeng/primeng';
import {Footer} from 'primeng/primeng';
import {TriStateCheckboxModule} from 'primeng/primeng';
import {RatingModule} from 'primeng/primeng';

import {Reason} from '../reason.ts';
import {Choice} from '../choice.ts';
import {Vote} from '../vote.ts';
import {VoteGroup} from '../votegroup.ts';
import {Data} from '../data.ts';

import Utils from '../utils.ts';

@Component({
    selector: 'treetable-app',
    templateUrl: './app/treetable.template.html',
    //styleUrls: ['./app/treetable.component.css']
})
export class TreeTableComponent implements OnInit {

    menuItems: MenuItem[] = [];    
    choices: Choice[] = [];
    reasons: TreeNode[] = [];
    votes: Vote[] = [];
    
    selectedReason: TreeNode;
    selectedReasons: TreeNode[] = [];
    
    constructor() {}
    
    ngOnInit() {
      this.choices = <Choice[]> [
        new Choice(1, 1, 'C1', 0), 
        new Choice(2, 1, 'C2', 1), 
        new Choice(3, 1, 'C3', 2)];
      
      this.reasons = <Reason[]> [ // id, topic, parent, title, position, weight, choiceCount
        new Reason(1, 1, null, 'R1', 0, 1), 
        new Reason(2, 1, 1,    'R2', 1, undefined), 
        new Reason(3, 1, null, 'R3', 2, 1)];

      this.votes = <Vote[]> [ // id, reason, choice, value, user
          new Vote(1, 1, 1, 2),
          new Vote(2, 2, 1, -1),
          new Vote(3, 3, 2, -2)];


      this.data = <TreeNode[]>[];
      
      for(let reason of this.reasons) {
        let rVotes = this.votes.filter(v => v.ReasonId == reason.Id) || [];
        for(let choice in this.choices) {
          let rcVotes = rVotes.filter(v => v.ChoiceId == choice.Id) || [];
          reason.VoteGroups.push(new VoteGroup(reason.Id, choice.Id, rcVotes))
        }
        this.data.push(this.getObject(reason));
      }
      console.log(JSON.stringify(this.data)); 

      this.data = Utils.arrayToTree(this.reasons);
      //alert(JSON.stringify(this.data));


      this.menuItems = [
          {label: 'View', icon: 'fa-search', command: (event) => this.viewReason(this.selectedReasons)},
          {label: 'Delete', icon: 'fa-close', command: (event) => this.deleteReason(this.selectedReasons)}
      ];
    }
    
    getObject(data: any) {
      return { data: data };
    }
    
    reasonSelect(event) {
      //this.msgs = [];
      //this.msgs.push({severity: 'info', summary: 'Reason Selected', detail: event.node.data.Title});
    }
    
    reasonUnselect(event) {
      //this.msgs = [];
      //this.msgs.push({severity: 'info', summary: 'Reason Unselected', detail: event.node.data.Title});
    }
    
    reasonExpand(event) {
      //if(event.node)
          //this.nodeService.getLazyFilesystem().then(nodes => event.node.children = nodes);
    }
    
    viewReason(nodes: TreeNode[]) {
      for(let node of nodes) {
        this.msgs = [];
        this.msgs.push({severity: 'info', summary: 'View Reason', detail: node.data.Title});
      }
    }

    deleteReason(nodes: TreeNode[]) {
      for(let node of nodes) {
        node.parent.children = node.parent.children.filter(n => n.data !== node.data);
        this.msgs = [];
        this.msgs.push({severity: 'info', summary: 'Delete Reason', detail: node.data.Title});
      }
    }
    
    handleRate(event) {
        //alert("You have rated " + event.value);
    }

    handleCancel(event) {
      alert("Rating Cancelled");
    }
    
    voteChange(event) {
      if(event) {
        this.msgs = [];
        this.msgs.push({severity: 'info', summary: 'Vote', detail: event.value});
      }
    }
}



import {Choice} from './choice.ts';
import {Vote} from './vote.ts';

export class Reason {
    Id: number;
    ParentId: number|null;
    TopicId: number;
    Title: string;
    Position: number;
    Priority: number;
    Weight: number = 0.00;
    UserId: number;
    Choices: Choice[] = [];
    Votes: Vote[] = [];
    VoteGroups: VoteGroup[] = [];
    ChildReasons: Reason[] = [];    
    
    constructor(id: number, topicId: number, parentId: number|null, title: string, position: number, weight: number, description: string) {
      this.Id = id;
      this.topicId = topicId;
      this.ParentId = parentId;
      this.Title = title;
      this.Position = position;
      this.Weight = weight;
      this.Description = description;
      this.Priority = 0;
    }
    
    setChildReasons(reasons: Reason[]) {
      
    }
}
import {Reason} from './reason.ts';
import {Vote} from './vote.ts';

export class Choice {
    Id: number;
    TopicId: number;
    Title: string;
    Position: number;
    UserId: number;
    Votes: Vote[] = [];
    Reasons: Reason[] = [];
    
    constructor(id: number, topicId: number, title: string, position: number) {
      this.Id = id;
      this.TopicId = topicId;
      this.Title = title;
      this.Position = position;
    }
}
import {Choice} from './choice.ts';
import {Reason} from './reason.ts';

export class Vote {
  Choice: Choice;
  Reason: Reason;
  Id: number;
  //TopicId: number;
  ReasonId: number;
  ChoiceId: number;
  UserId: number;
  Value: number;
  Min: number = -3;
  Max: number = 3;
  
  constructor(id: number, reasonId: number, choiceId: number, value: number, userId:number) {
    this.Id = id;
    //this.TopicId = topicId;
    this.ReasonId = reasonId;
    this.ChoiceId = choiceId;
    this.Value = value;
    this.UserId = userId;
  }

  public getUsername(): string {
    return 'SomeName' + this.Id;
  }

  public voteUp(): void {
    this.Value += 1;
  }

  public voteDown(): void {
    this.Value += -1;
  }
}

import {Choice} from './choice.ts';
import {Reason} from './reason.ts';
import {Vote} from './vote.ts';

export class Data {
  constructor() { }
  
}
export interface Item {
    Id: number;
    ParentId: number | null;
    [key: string]: any;
}
export interface TreeItem {
    data: Item | null;
    children: TreeItem[];
}
export interface ArrayToTreeConfig {
    id: string;
    parentId: string;
}


export default class Utils {
  /* Unflattens an array to a tree with runtime O(n) */
  static arrayToTree (items: Item[], config: ArrayToTreeConfig = { id: 'Id', parentId: 'ParentId' }): TreeItem[] {
    // the resulting unflattened tree
    const rootItems: TreeItem[] = []

    // stores all already processed items with ther ids as key so we can easily look them up
    const lookup: { [id: string]: TreeItem } = {}

    // idea of this loop:
    // whenever an item has a parent, but the parent is not yet in the lookup object, we store a preliminary parent
    // in the lookup object and fill it with the data of the parent later
    // if an item has no parentId, add it as a root element to rootItems
    for (const item of items) {
        const itemId = item[config.id]
        const parentId = item[config.parentId]

        // look whether item already exists in the lookup table
        if (!Object.prototype.hasOwnProperty.call(lookup, itemId)) {
          // item is not yet there, so add a preliminary item (its data will be added later)
          lookup[itemId] = { data: null, children: [] }
        }

        // add the current item's data to the item in the lookup table
        lookup[itemId].data = item

        const TreeItem = lookup[itemId]

        if (parentId === null) {
          // is a root item
          rootItems.push(TreeItem)
        } else {
          // has a parent
          // look whether the parent already exists in the lookup table
          if (!Object.prototype.hasOwnProperty.call(lookup, parentId)) {
              // parent is not yet there, so add a preliminary parent (its data will be added later)
              lookup[parentId] = { data: null, children: [] }
          }
  
          // add the current item to the parent
          lookup[parentId].children.push(TreeItem)
        }
    }

    return rootItems
  }
}
//import {Choice} from './choice.ts';
//import {Reason} from './reason.ts';
import {Vote} from './vote.ts';

export class VoteGroup {
  ReasonId: number;
  ChoiceId: number;
  Votes: Vote[] = [];
  AverageValue: number;
  UserValue: number;
  PctValue: number;
  
  Usernames: string;
  
  constructor(reasonId: number, choiceId: number, votes: Vote[]) {
    this.ReasonId = reasonId;
    this.ChoiceId = choiceId;
    this.Votes = votes || [];
    this.AverageValue = this.getAverageValue();
    this.PctValue = this.getPercentage();
  }
  
  public getPercentage(): number {
    return 100 * this.getTotalValue();// / (this.Max - this.Min)
  } 
  
  public getTotalValue(): number {
    return this.Votes.reduce(function(sum: number, vote: Vote) {
      return sum + vote.Value;
    }, 0);
  }
  
  public getVoteCount(): number {
    return this.Votes.length;
  }
  
  public getAverageValue(): number {
    return this.Votes.length == 0
       ? 0
       : this.getTotalValue() / this.Votes.length;
  }
  
  public getUsernames(users: any): string {
    let userIds = this.Votes.map(function(vote: Vote) {
      return vote.getUsername();
    });
    return userIds.join(', ');
  }
  
  public addVote(c: Vote): void {
      this.Votes.push(c);
  }

  public removeVote(i: number): void {
      if (this.Votes.length <= i) 
          throw new Error("index out of bound!");
      
      this.Votes.splice(i, 1);
  }

  public VoteUp(): void {
    this.Value += 1;
  }

  public VoteDown(): void {
    this.Value += -1;
  }
}