Browse Source

add comments

EUGENIO SOUZA CARVALHO 5 years ago
parent
commit
2b9958f3e9

+ 41 - 0
src/app/app.component.html

@@ -1,3 +1,44 @@
+<h3>Desafio Dito Front-end</h3>
+<p>
+    A solução foi desenvolvida utilizando Angular6 + SCSS. O código fonte está disponível no repositório git.
+    <a href="https://git.eugeniocarvalho.dev/eugeniucarvalho/dito-front">https://git.eugeniocarvalho.dev/eugeniucarvalho/dito-front</a>
+</p>
 
+<h4>Recursos Utilizados</h4>
+<ul>
+    <li>Foi utilizada a fonte <a href="https://fonts.googleapis.com/css?family=Roboto">Roboto</a></li>
+    <li>Os icones foram convertidos para uma font utilizando o serviço <a href="http://fontastic.me/">fontastic.me</a></li>
+    <li>As datas foram tratadas com o framework <a href="https://momentjs.com/">moment.js</a></li>
+    <li>Os valores foram formatados utilizando o pipe de currency do angular.</li>
+</ul>
 
+
+<h4>Caracteristicas da Solução</h4>
+
+<p>
+    Foi criado um component <b>TimelineComponent</b> que se comunica com um serviço <b>ApiEventsService</b>.
+    O component realiza uma chamada ao serviço que devolve uma estrutura organizada que facilita a renderização por
+    parte do component.
+</p>
+<p>
+    O serviço consome os dados do endpoint <a href="https://storage.googleapis.com/dito-questions/events.json">https://storage.googleapis.com/dito-questions/events.json</a>.
+</p>
+
+<blockquote>
+    * Não foi possivel realizar a chamada ajax para o endpoint devido a uma restrição de cross-origin não
+    especificada no arquivo armazenado no storage do google.
+    O serviço possui o código que representa a requisição ajax. Para demonstrar o funcionamento do component o
+    conteudo do arquivo foi inserido manualmente como entrada da chamada.
+    Detalhes sobre o cors no google cloud storage podem ser obtidos <a href="https://cloud.google.com/storage/docs/configuring-cors">aqui</a>.
+</blockquote>
+
+<h4>Tratamento da Entrada</h4>
+<p>
+    O conteudo da entrada passa por um processamento onde os dados são selecionados e reorganizados.
+    O serviço gera uma lista de transações. Cada transação possui uma lista de itens e um header.
+    Uma representação da classe encontra-se abaixo.
+</p>
+<pre [innerHTML]=classe></pre>
+
+<h4>Component em execução</h4>
 <dito-timeline></dito-timeline>

+ 6 - 0
src/app/app.component.ts

@@ -7,4 +7,10 @@ import { Component } from '@angular/core';
 })
 export class AppComponent {
   title = 'ditofront';
+
+  classe = `export class Transaction {
+    header: { value: string, icon: string }[] = [];
+    itens : { name: string, value: string }[] = [];
+    time  : moment.Moment;
+}`
 }

+ 0 - 2
src/app/timeline/timeline.component.html

@@ -1,8 +1,6 @@
 <ul class="timeline">
   <li class="event" *ngFor="let transaction of transactions">
-    <!-- <img class="" src="assets/icons/check.svg" /> -->
     <div class="event-circle icon icon-check"></div>
-
     <ul class="event-header">
       <li *ngFor="let item of transaction.header">
         <i class="icon {{item.icon}}"></i>

+ 2 - 13
src/app/timeline/timeline.component.scss

@@ -2,9 +2,10 @@
     background: #eff1fd;
     display: flex;
     padding: 32px 19px 0px 32px;
+    width: 500px;
+    margin: 0 auto;
 
     ul {
-        // border: 1px solid #000;
         display: flex;
         list-style: none;
         margin: 0px;
@@ -131,17 +132,5 @@
             position: absolute;
             border-radius: 1px;
         }
-
-        // content: "";
-        // position: absolute;
-        // transform: rotate(45deg);
-        // height: 13px;
-        // width: 13px;
-        // display: flex;
-        // left: -6px;
-        // background: #f8f8f8;
-        // box-shadow: -2px 2px 5px 0px #cdced9;
-        // z-index: 0;
-        // top: 15px;
     }
 }

+ 1 - 0
src/app/timeline/timeline.component.ts

@@ -13,6 +13,7 @@ export class TimelineComponent {
   constructor(
     protected $tls: ApiEventsService
   ) {
+    // Requisita as transações do serviço
     this.$tls.transactions().subscribe(transactions => {
       this.transactions = transactions;
     })

+ 86 - 61
src/app/timeline/timeline.service.ts

@@ -5,9 +5,8 @@ import * as moment from 'moment';
 import { CurrencyPipe } from '@angular/common';
 
 export class Transaction {
-  itens = [];
-  custom_data = {}
   header: { value: string, icon: string }[] = [];
+  itens: { name: string, value: string }[] = [];
   time: moment.Moment;
 };
 
@@ -16,9 +15,16 @@ export class Transaction {
 })
 export class ApiEventsService {
 
+  // Registro de todos os tratadores de eventos
+  protected eventHandler = new Map<string, (trans, event) => void>([
+    ['comprou-produto', (trans, event) => this.comprouProdutoHandler(trans, event)],
+    ['comprou', (trans, event) => this.comprouHandler(trans, event)],
+  ]);
+
   // public apiURL = 'https://storage.googleapis.com/dito-questions/events.json';
   public apiURL = 'https://dito-questions.storage.googleapis.com/events.json';
   public _events: any;
+  protected currencyPipe = new CurrencyPipe('pt-BR');
 
   constructor(
     protected $http: HttpClient
@@ -40,96 +46,115 @@ export class ApiEventsService {
         "event": "comprou", "timestamp": "2016-09-22T13:57:31.2311892-03:00", "revenue": 250, "custom_data": [{ "key": "store_name", "value": "Patio Savassi" }, { "key": "transaction_id", "value": "3029384" }]
       }
     ]
-    console.log(this._events)
-
   }
 
   public transactions() {
 
     return new Observable<any>(ob => {
+
+      // Realiza uma requisição para o endpoint determinado
       // this.$http.get(this.apiURL).subscribe(results => {
-      // const transations = this.parseEventResults(results);
+      //   ob.next(this.parseEventResults(results))
+      // })
+
+      // Simula a requisição ajax
       setTimeout(() => {
         ob.next(this.parseEventResults(this._events))
       })
-      //   console.log(results)
-      // })
     })
   }
-  protected parseEventResults(events) {
-    let currencyPipe = new CurrencyPipe('pt-BR')
-      , transactions = []
+  // Retona o id da transação do evento
+  protected findTransactionId(event): string {
+    let id;
+    event && event.custom_data.find(data => {
+      if (data.key === 'transaction_id') {
+        id = data.value;
+        return true;
+      }
+    })
+    return id;
+  }
+
+  // Converte a lista de eventos em um lista de transações
+  protected parseEventResults(events): Transaction[] {
+    let transactions = []
       , transactionsMap = new Map<string, Transaction>();
 
+    // Percorre a lista de eventos e realiza os agrupamentos
     events.map(event => {
-      let id;
-      const found = event.custom_data.find(data => {
-        if (data.key === 'transaction_id') {
-          id = data.value;
-          return true;
-        }
-      })
+      // Busca o id da transação contido no evento
+      let id = this.findTransactionId(event);
 
-      if (!found) {
+      if (!id) {
+        // Não tratado por falta de definição
         return;
       }
 
+      // Cria uma nova instancia para o id caso ainda não exista
       if (!transactionsMap.has(id)) {
         transactionsMap.set(id, new Transaction());
       }
-
+      // Recupera a instancia da transação correspondente ao id
       let trans = transactionsMap.get(id);
 
-      switch (event.event) {
-        case "comprou-produto":
-          let add = { name: "", value: "" }
-          // Atribui os valores do nome do produto e o preço para o objeto da listagem
-          event.custom_data.find(item => {
-            if (item.key === "product_name") {
-              add.name = item.value;
-            } else if (item.key === "product_price") {
-              // Formata o valor com a moeda brasileira
-              add.value = currencyPipe.transform(item.value, 'BRL', true, '1.2-2');
-            }
-          })
-          trans.itens.push(add)
-          break;
-        case "comprou":
-          // Converte o timestamp para um moment object
-          trans.time = moment(event.timestamp);
-          let place = "";
-
-          trans.custom_data = event;
-          // Busca o nome do estabelecimento
-          event.custom_data.find(item => {
-            if (item.key === "store_name") {
-              place = item.value;
-              return true;
-            }
-          })
-          // Cria  um array contendo os elementos do header de cada evento
-          // Cada elemento é mapeado para um item do array
-          trans.header = [
-            { icon: "icon-calendar", value: trans.time.format("DD/MM/YYYY") },
-            { icon: "icon-clock", value: trans.time.format("HH:mm") },
-            { icon: "icon-place", value: place },
-            { icon: "icon-money", value: currencyPipe.transform(event.revenue, 'BRL', true, '1.2-2') },
-          ];
-
-          break;
-      }
+      // Verifica o tipo de evento para tratamento
+      this.eventHandler.get(event.event)(trans, event)
     })
-    // Converte o map 
+    // Converte o map em lista
     transactionsMap.forEach(t => transactions.push(t))
 
     // Ordena as transações pela data
     transactions.sort((t1, t2) => {
-      // if (t1) return 1;
-      // if ( ) return -1;
       return (t1.time - t2.time);
     });
-    console.log('transactions', transactions)
+
     return transactions;
   }
 
+  // Realiza o tratamento do evento comprou
+  // * Extra os dados do cabeçalho
+  // * Seta o atributo header da transação
+
+  protected comprouHandler(trans, event) {
+    let place = "";
+    // Converte o timestamp para um moment object
+    trans.time = moment(event.timestamp);
+    // Busca o nome do estabelecimento
+    event.custom_data.find(item => {
+      if (item.key === "store_name") {
+        place = item.value;
+        return true;
+      }
+    })
+    // Cria  um array contendo os elementos do header de cada evento
+    // Cada elemento é mapeado para um item do array
+    trans.header = [
+      { icon: "icon-calendar", value: trans.time.format("DD/MM/YYYY") },
+      { icon: "icon-clock", value: trans.time.format("HH:mm") },
+      { icon: "icon-place", value: place },
+      { icon: "icon-money", value: this.currencyPipe.transform(event.revenue, 'BRL', true, '1.2-2') },
+    ];
+  }
+  // Realiza o tratamento do evento comprou-produto
+  // * Extra os nomes e os preços dos itens comprados
+  // * Adiciona o novo item na lista de itens da transação
+  protected comprouProdutoHandler(trans, event) {
+    let add = { name: "", value: "" }, control = 0;
+    // Atribui os valores do nome do produto e o preço para o objeto da listagem
+    event.custom_data.find(item => {
+      if (item.key === "product_name") {
+        add.name = item.value;
+        control++;
+      } else if (item.key === "product_price") {
+        // Formata o valor com a moeda brasileira
+        add.value = this.currencyPipe.transform(item.value, 'BRL', true, '1.2-2');
+        control++;
+      }
+      // Se ja encontrou os dois dados evita de percorrer o array todo.
+      if (control > 1) {
+        return true;
+      }
+    })
+    trans.itens.push(add)
+  }
 }

+ 36 - 0
src/styles.scss

@@ -1 +1,37 @@
 /* You can add global styles to this file, and also import other style files */
+body {
+    font-family: 'Roboto', sans-serif !important;
+    display: flex;
+    justify-content: center;
+    color: #000000b8;
+
+    a {
+        text-decoration: none;
+        color: #000c;
+        font-style: italic;
+        background: #0000000d;
+        border-radius: 5px;
+        padding: 2px 6px;
+        box-sizing: border-box;
+        font-size: 14px;
+
+        &:hover {
+            background: #0000001c;
+        }
+    }
+
+    blockquote {
+        font-size: 15px;
+        font-style: italic;
+        border-left: 2px solid #00000042;
+        margin: 0px;
+        padding: 0px 30px;
+        text-align: justify;
+    }
+
+    app-root {
+        width: 550px;
+        max-width: 550px;
+        margin: 20px 0px 40px;
+    }
+}