Explorar o código

módulo de clientes parcialmente terminado

robert2206 %!s(int64=8) %!d(string=hai) anos
pai
achega
a79ef666bf

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

@@ -7,6 +7,8 @@ import { HallPage } from '../pages/hall/hall';
 import { HomePage } from '../pages/home/home';
 
 import { CustomerListPage } from '../pages/customer-list/customer-list';
+import { CustomerOptions } from "../pages/customer-list/customer-options";
+import { CustomerDetailsPage } from "../pages/customer-details/customer-details";
 import { BudgetListPage } from '../pages/budget-list/budget-list';
 import { ProductListPage } from '../pages/product-list/product-list';
 import { ProductOptions } from '../pages/product-list/product-options';
@@ -47,6 +49,8 @@ import { ChartModule } from 'ng2-chartjs2';
         HallPage,
         HomePage,
         CustomerListPage,
+        CustomerOptions,
+        CustomerDetailsPage,
         BudgetListPage,
         ProductListPage,
         ProductOptions,
@@ -75,6 +79,8 @@ import { ChartModule } from 'ng2-chartjs2';
         HallPage,
         HomePage,
         CustomerListPage,
+        CustomerOptions,
+        CustomerDetailsPage,
         BudgetListPage,
         ProductListPage,
         ProductOptions,

BIN=BIN
src/assets/images/customer.png


BIN=BIN
src/assets/images/product1.png


+ 1 - 1
src/decorators/document-type.ts → src/decorators/document.type.ts

@@ -3,6 +3,6 @@
  */
 export function Document(type: string) {
     return function (target: Function) {
-        Reflect.defineMetadata("document", type, target);        
+        // Reflect.defineMetadata("document", type, target);        
     }
 }

+ 1 - 1
src/decorators/entrypoint.ts

@@ -3,6 +3,6 @@
  */
 export function Entrypoint(name: string) {
     return function (target: Function) {
-        Reflect.defineMetadata("entrypoint", name, target);
+        // Reflect.defineMetadata("entrypoint", name, target);
     }
 }

+ 1 - 1
src/interfaces/navigable-interface.ts

@@ -1,3 +1,3 @@
 export interface INavigable {
-    
+    goToPage(page: any): void;
 }

+ 12 - 0
src/models/base.model.ts

@@ -0,0 +1,12 @@
+export class BaseModel {
+
+    public id: string;
+    public remote_id: number;
+    public doc_state: string;
+
+    constructor() {
+        this.id = null;
+        this.remote_id = 0;
+        this.doc_state = "created";
+    }
+}

+ 46 - 0
src/models/partner.ts

@@ -0,0 +1,46 @@
+import { BaseModel } from "./base.model";
+
+export class Partner extends BaseModel {
+
+    public name: string;
+    public phone: string;
+    public street: string;
+    public partner_latitude: number;
+    public employee: boolean;
+    public is_company: boolean;
+    public email: string;
+    public opportunity_count: number;
+    public city: string;
+    public street2: string;
+    public opportunity_ids: Array<any>;
+    public supplier: boolean;
+    public partner_longitude: number;
+    public customer: boolean;
+    public image_medium: string;
+    public mobile: string;
+    public image_small: string;
+    public commercial_partner_id: number;
+
+    constructor() {
+        super();
+
+        this.name = null;
+        this.phone = null;
+        this.street = null;
+        this.partner_latitude = 0;
+        this.employee = false;
+        this.is_company = false;
+        this.email = null;
+        this.opportunity_count = 0;
+        this.city = null;
+        this.street2 = null;
+        this.opportunity_ids = [];
+        this.supplier = false;
+        this.partner_longitude = 0;
+        this.customer = false;
+        this.image_medium = null;
+        this.mobile = null;
+        this.image_small = null;
+        this.commercial_partner_id = 0;
+    }
+}

+ 5 - 10
src/models/product.ts

@@ -1,9 +1,6 @@
-import { Document } from '../decorators/document-type';
-import { Entrypoint } from '../decorators/entrypoint';
-
-@Document("product")
-@Entrypoint("product_product")    
-export class Product {
+import { BaseModel } from "./base.model";
+ 
+export class Product extends BaseModel {
 
     public default_code: string;
     public description: string;
@@ -19,12 +16,12 @@ export class Product {
     public standard_price: number;
     public type: string;
     public website_published: false;
-    public remote_id: number;
-    public doc_state: string;
     public company_id: number;
     public attribute_line_ids: Array<any>;
 
     constructor() {
+        super();
+
         this.default_code = null;
         this.description = null;
         this.ean13 = null;
@@ -38,8 +35,6 @@ export class Product {
         this.standard_price = 1;
         this.type = "product";
         this.website_published = false;
-        this.remote_id = 0;
-        this.doc_state = "created";
         this.company_id = 0;
         this.attribute_line_ids = [];
     }

+ 9 - 1
src/pages/budget-list/budget-list.ts

@@ -1,11 +1,12 @@
 import { Component } from '@angular/core';
 import { NavController } from 'ionic-angular';
+import { INavigable } from "../../interfaces/navigable-interface";
 
 @Component({
     selector: 'page-budget-list',
     templateUrl: 'budget-list.html'
 })
-export class BudgetListPage {
+export class BudgetListPage implements INavigable {
 
   constructor(public navCtrl: NavController) {}
 
@@ -13,4 +14,11 @@ export class BudgetListPage {
     console.log('Hello BudgetListPage Page');
   }
 
+  /**
+   *
+   */
+  goToPage(page: any) {
+
+  }
+
 }

+ 67 - 0
src/pages/customer-details/customer-details.html

@@ -0,0 +1,67 @@
+
+<ion-header>
+
+  <ion-navbar color="primary">
+      <ion-title>Cliente</ion-title>
+
+      <ion-buttons end>
+          <button ion-button (click)="save()">
+              <ion-icon color="ligth" name="archive"></ion-icon>
+          </button>
+      </ion-buttons>
+  </ion-navbar>
+
+</ion-header>
+
+<ion-content>
+
+    <ion-card>
+        <div style="position: relative">
+            <img src="./assets/images/customer.png" *ngIf="!customer.image_medium"/>
+            <img [src]="customer.image_medium | sanitizeUrl" *ngIf="customer.image_medium"/>
+
+            <ion-fab left bottom>
+                <button ion-fab color="yellow" class="fab-picture" (click)="showPhotoOptions()">
+                    <ion-icon name="camera" color="light"></ion-icon>
+                </button>
+            </ion-fab>
+        </div>
+    </ion-card>
+
+    <ion-list>
+        <ion-item>
+            <ion-label stacked>Nombre</ion-label>
+            <ion-input type="text" [(ngModel)]="customer.name" required></ion-input>
+        </ion-item>
+
+        <ion-item>
+            <ion-label stacked>Ciudad</ion-label>
+            <ion-input type="text" [(ngModel)]="customer.city" required></ion-input>
+        </ion-item>
+
+        <ion-item>
+            <ion-label stacked>Dirección</ion-label>
+            <ion-input type="text" [(ngModel)]="customer.street" required></ion-input>
+        </ion-item>
+
+        <ion-item>
+            <ion-label stacked>Dirección 2</ion-label>
+            <ion-input type="text" [(ngModel)]="customer.street2" required></ion-input>
+        </ion-item>
+
+        <ion-item>
+            <ion-label stacked>Teléfono</ion-label>
+            <ion-input type="text" [(ngModel)]="customer.phone" required></ion-input>
+        </ion-item>
+
+        <ion-item>
+            <ion-label stacked>Celular</ion-label>
+            <ion-input type="text" [(ngModel)]="customer.mobile" required></ion-input>
+        </ion-item>
+
+        <ion-item>
+            <ion-label stacked>E-mail</ion-label>
+            <ion-input type="text" [(ngModel)]="customer.email" required></ion-input>
+        </ion-item>
+    </ion-list>
+</ion-content>

+ 20 - 0
src/pages/customer-details/customer-details.scss

@@ -0,0 +1,20 @@
+page-customer-details {
+    
+    ion-buttons button span ion-icon {
+        padding: 0 6px !important;
+        font-size: 1.4em !important;
+    }
+
+    ion-card img {
+        margin: 10px auto;
+        display: block;
+        border-radius: 10px;
+        height: 200px;
+        width: auto;
+    }
+
+    button[fab].fab-picture {
+        top: calc(100% - 35px);
+        z-index: 50;
+    }
+}

+ 152 - 0
src/pages/customer-details/customer-details.ts

@@ -0,0 +1,152 @@
+import { Component } from '@angular/core';
+import { NavController, NavParams, ActionSheetController, ToastController, AlertController } from 'ionic-angular';
+import { CustomerListPage } from "../customer-list/customer-list";
+import { Partner } from "../../models/partner";
+import { CameraProvider } from "../../providers/camera-provider";
+import { DataProvider } from "../../providers/data-provider";
+import { PhoneProvider } from "../../providers/phone-provider";
+
+@Component({
+    selector: 'page-customer-details',
+    templateUrl: 'customer-details.html',
+    providers: [ Partner, CameraProvider, PhoneProvider ]
+})
+export class CustomerDetailsPage {
+
+    constructor(
+        public navCtrl: NavController,
+        public navParams: NavParams,
+        public actionSheetCtrl: ActionSheetController,
+        public toastCtrl: ToastController,
+        public alertCtrl: AlertController,
+        public camera: CameraProvider,
+        public phone: PhoneProvider,
+        public customer: Partner,
+        public db: DataProvider
+    ) { 
+        if (!(this.navParams.data instanceof CustomerListPage)) {
+            this.customer = this.navParams.data;
+            this.customer.doc_state = "updated";
+        }
+    }
+
+    /**
+     *
+     */
+    ionViewDidLoad() {
+        this.phone.enableShakeWatcher().subscribe(() => { 
+            this.askIfGetCurrentPosition();
+        }, e => { 
+            console.log(e);
+        });
+    }
+
+    /**
+     *
+     */
+    ionViewDidLeave() {
+        this.phone.disableShakeWatcher();
+    }
+
+    /**
+     *
+     */
+    private askIfGetCurrentPosition() {
+        this.alertCtrl.create({
+            title: "Confirmar",
+            message: "Desea obtener la posición por GPS?",
+            buttons: [
+                {
+                    text: "Cancelar",
+                    handler: () => {
+                        console.log("Canceled");
+                    }
+                },
+                {
+                    text: "Aceptar",
+                    handler: () => {
+                        
+                    }
+                }
+            ]
+        }).present();
+    }
+
+    /**
+     *
+     */
+    private isValidForm(): boolean {
+        if (this.customer.name) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     *
+     */
+    showPhotoOptions(): void {
+        this.actionSheetCtrl.create({
+            title: "Tomar una foto",
+            buttons: [
+                {
+                    text: "Desde la cámara",
+                    icon: "camera",
+                    handler: () => {
+                        this.takePicture("camera");
+                    }
+                },
+                {
+                    text: "Desde la galería",
+                    icon: "images",
+                    handler: () => {
+                        this.takePicture("album");
+                    }
+                },
+                {
+                    text: "Cancelar",
+                    role: "cancel"
+                }
+            ]
+        }).present();
+    }
+
+    /**
+     *
+     */
+    private takePicture(source: string): void {
+        this.camera.getPicture(source).then(i => {
+            this.customer.image_medium = i;
+            this.customer.image_small = i;
+        }).catch(e => {
+            console.log(e);
+        });
+    }
+
+    /**
+     *
+     */
+    save(): void {
+        if (!this.isValidForm()) {
+            return;
+        }
+
+        this.customer.customer = true;
+
+        this.db.save(DataProvider.DOCS.PARTNER, this.customer).then(c => { 
+            if (this.navParams.data instanceof CustomerListPage) {
+                this.navParams.data.add(c);
+            }
+
+            this.navCtrl.pop(this);
+        }).catch(e => { 
+            console.log(e);
+            
+            this.toastCtrl.create({
+                message: "No se ha podido guardar los datos",
+                duration: 3000
+            }).present();
+        });
+    }
+}

+ 61 - 5
src/pages/customer-list/customer-list.html

@@ -16,19 +16,75 @@
 
 </ion-header>
 
-<ion-toolbar color="primary" *ngIf="search">
-    <ion-searchbar placeholder="Buscar" (ionInput)="filterNews($event)"></ion-searchbar>
+<ion-toolbar color="primary" *ngIf="isSearchMode()">
+    <ion-searchbar placeholder="Buscar" debounce="1000" (ionInput)="search($event)"></ion-searchbar>
     <ion-buttons end>
         <button ion-button (click)="toggleSearch()">
-            <ion-icon name="exit"></ion-icon>
+            <ion-icon name="arrow-forward"></ion-icon>
         </button>
     </ion-buttons>
 </ion-toolbar>
 
-<ion-content padding>
+<ion-content>
+    <ion-card *ngFor="let c of elements; trackBy:trackByElements">
+        <ion-item>
+            <ion-thumbnail item-left>
+                <img src="./assets/images/customer.png" *ngIf="!c.image_medium"/>
+                <img [src]="c.image_medium | sanitizeUrl" *ngIf="c.image_medium"/>
+            </ion-thumbnail>
 
+            <h2>{{ c.name }}</h2>
+
+            <p>
+                <strong>Ciudad:</strong>
+                {{ c.city }}
+            </p>
+
+            <p>
+                <strong>Dirección:</strong>
+                {{ c.street }}
+            </p>
+
+            <p>
+                <strong>Celular:</strong>
+                {{ c.mobile }}
+            </p>
+
+            <button ion-button primary clear item-right (click)="showOptions($event, c)">
+                <ion-icon name="more"></ion-icon>
+            </button>
+        </ion-item>
+
+        <ion-row>
+            <ion-col>
+                <button ion-button primary clear small>
+                    <ion-icon name="cash"></ion-icon>
+                    <div>{{ c.sales_order_count || 0}} Pedidos</div>
+                </button>
+            </ion-col>
+
+            <ion-col>
+                <button ion-button primary clear small>
+                    <ion-icon name="document"></ion-icon>
+                    <div>{{ c.invoice_count || 0}} Facturas</div>
+                </button>
+            </ion-col>
+
+            <ion-col>
+                <button ion-button primary clear small>
+                    <ion-icon name="flag"></ion-icon>
+                    <div>{{ c.opportunity_count || 0}} Iniciativas</div>
+                </button>
+            </ion-col>
+        </ion-row>
+    </ion-card>
+
+    <ion-infinite-scroll (ionInfinite)="seek($event)">
+        <ion-infinite-scroll-content></ion-infinite-scroll-content>
+    </ion-infinite-scroll>
+    
     <ion-fab right bottom>
-        <button ion-fab color="yellow">
+        <button ion-fab color="yellow" (click)="goToPage(null)">
             <ion-icon name="add" color="light"></ion-icon>
         </button>
     </ion-fab>

+ 1 - 4
src/pages/customer-list/customer-list.scss

@@ -1,6 +1,3 @@
 page-customer-list {
-    ion-buttons button span ion-icon {
-        padding: 0 6px !important;
-        font-size: 1.4em !important;
-    }
+
 }

+ 297 - 12
src/pages/customer-list/customer-list.ts

@@ -1,31 +1,316 @@
 import { Component } from '@angular/core';
-import { NavController } from 'ionic-angular';
+import { NavController, LoadingController, ToastController, PopoverController, AlertController } from 'ionic-angular'
+import { CallNumber } from "ionic-native";
+import { DefaultListable } from "../../defaults/default-listable";
+import { INavigable } from "../../interfaces/navigable-interface";
+import { Partner } from "../../models/partner";
+import { DataProvider } from "../../providers/data-provider";
+import { CustomerDetailsPage } from "../customer-details/customer-details";
+import { CustomerOptions } from "./customer-options";
+import { PhoneProvider } from "../../providers/phone-provider";
 
 @Component({
     selector: 'page-customer-list',
-    templateUrl: 'customer-list.html'
+    templateUrl: 'customer-list.html',
+    providers: [ PhoneProvider ]
 })
-export class CustomerListPage {
+export class CustomerListPage extends DefaultListable<Partner> implements INavigable {
 
-    search: boolean = false;
+    popover: any = null;
 
-    constructor(public navCtrl: NavController) {}
+    constructor(
+        public navCtrl: NavController,
+        public loadingCtrl: LoadingController,
+        public toastCtrl: ToastController,
+        public popoverCtrl: PopoverController,
+        public alertCtrl: AlertController,
+        public db: DataProvider,
+        public phone: PhoneProvider
+    ) {
+        super(loadingCtrl);
+    }
 
+    /**
+     *
+     */
     ionViewDidLoad() {
-        console.log('Hello CustomerList Page');
+        this.initialize();
+    }
+
+    /**
+     *
+     */
+    initialize(): void {
+        let loader = this.loadingCtrl.create({
+            content: "Cargando clientes, espere..."
+        });
+        loader.present();
+
+        this.db.getAll(DataProvider.DOCS.PARTNER).then(partners => { 
+            this.elements = partners.filter(item => {
+                return item.customer = true && item.doc_state !== "deleted"; 
+            });
+
+            loader.dismiss();
+        }).catch(e => {
+            console.log(e);
+
+            loader.dismiss();
+            
+
+            this.toastCtrl.create({
+                message: "No se ha podido cargar los clientes",
+                duration: 3000
+            }).present();
+        });
+    }
+    
+    /**
+     *
+     */
+    goToPage(page: any): void {
+        this.navCtrl.push(CustomerDetailsPage, this);
+    }
+
+    /**
+     *
+     */
+    showOptions(e, item) {
+        this.popover = this.popoverCtrl.create(CustomerOptions);
+
+        this.popover.present({
+            ev: e
+        });
+
+        this.popover.onDidDismiss(data => {
+            if (!data) {
+                return;
+            }
+
+            switch (data.role) {
+                case "open":
+                    this.openItem(item);
+                    break;
+                case "delete":
+                    this.askIfRemoveItem(item);
+                    break;
+                case "call":
+                    this.askIfCall(item);
+                    break;
+                case "navigate":
+                    this.askIfNavigate(item);
+                    break;
+                case "contact":
+                    this.askIfSaveContact(item);
+                    break;
+            
+                default:
+                    this.openItem(item);
+                    break;
+            }
+            
+        });
+    }
+
+    /**
+     *
+     */
+    openItem(item: any) {
+        this.navCtrl.push(CustomerDetailsPage, item, data => {
+            console.log(data);
+        });
+    }
+    
+     /**
+     *
+     */
+    askIfRemoveItem(item): void {
+        this.alertCtrl.create({
+            title: "Confirmar",
+            message: "Desea borrar este producto?",
+            buttons: [
+                {
+                    text: "Cancelar",
+                    handler: () => {
+                        console.log("Canceled");
+                        
+                    }
+                },
+                {
+                    text: "Aceptar",
+                    handler: () => {
+                        this.removeItem(item);
+                    }
+                }
+            ]
+        }).present();
     }
 
-    //
-    toggleSearch(): void {
-        this.search = !this.search;
+    /**
+     *
+     */
+    removeItem(item: any) {
+        item.doc_state = "deleted";
+        
+        this.db.delete(DataProvider.DOCS.PRODUCT_TEMPLATE, item).then(result => {
+            this.remove(item);
+        }).catch(e => {
+            console.log(e);
+            
+            this.toastCtrl.create({
+                message: "No se pudo eliminar el producto",
+                duration: 3000
+            }).present();
+        });
+    }
+
+        /**
+     *
+     */
+    askIfCall(item: Partner): void {
+        if (!item.mobile) {
+            this.toastCtrl.create({
+                message: "No hay contacto que llamar",
+                duration: 3000
+            }).present();
+
+            return;
+        }
+
+        this.alertCtrl.create({
+            title: "Confirmar",
+            message: "Desea llamar al cliente?",
+            buttons: [
+                {
+                    text: "Cancelar",
+                    handler: () => {
+                        console.log("Canceled");
+                    }
+                },
+                {
+                    text: "Aceptar",
+                    handler: () => {
+                        this.call(item);
+                    }
+                }
+            ]
+        }).present();
+    }
+
+    /**
+     *
+     */
+    call(item: Partner): void {
+        this.phone.call(item.mobile).subscribe(() => { 
+            console.log("Dial opened");
+        }, e => {
+            console.log(e);
+        });
+    }
 
-        if (!this.search) {
-            console.log('data load');
+    /**
+     *
+     */
+    askIfNavigate(item: Partner): void {
+        if (!item.partner_latitude || !item.partner_longitude) {
+            this.toastCtrl.create({
+                message: "No hay información para navegar",
+                duration: 3000
+            }).present();
+
+            return;
         }
+
+        this.alertCtrl.create({
+            title: "Confirmar",
+            message: "Desea navegar a la posición del cliente?",
+            buttons: [
+                {
+                    text: "Cancelar",
+                    handler: () => {
+                        console.log("Canceled");
+                    }
+                },
+                {
+                    text: "Aceptar",
+                    handler: () => {
+                        this.navigate(item);
+                    }
+                }
+            ]
+        }).present();
+    }
+
+    /**
+     *
+     */
+    navigate(item: Partner): void {
+        this.phone.navigate(item.partner_latitude, item.partner_longitude).subscribe(() => {
+            this.toastCtrl.create({
+                message: "Abriendo aplicación",
+                duration: 3000
+            }).present();
+        }, e => {
+            console.log(e);
+            
+            this.toastCtrl.create({
+                message: "No se pudo abrir la aplicación",
+                duration: 3000
+            }).present();
+        });
     }
 
-    filterNews(event: any): void {
+    /**
+     *
+     */
+    askIfSaveContact(item: Partner): void {
+        if (!item.phone && !item.phone && !item.mobile) {
+            this.toastCtrl.create({
+                message: "No hay información de contacto para guardar",
+                duration: 3000
+            }).present();
 
+            return;
+        }
+
+        this.alertCtrl.create({
+            title: "Confirmar",
+            message: "Desea guardar el contacto en el teléfono?",
+            buttons: [
+                {
+                    text: "Cancelar",
+                    handler: () => {
+                        console.log("Canceled");
+                    }
+                },
+                {
+                    text: "Aceptar",
+                    handler: () => {
+                        this.saveContact(item);
+                    }
+                }
+            ]
+        }).present();
     }
 
+    /**
+     *
+     */
+    saveContact(item: Partner): void {
+        console.log(item);
+        
+        this.phone.saveContact(item.name, [item.mobile]).subscribe(() => {
+            this.toastCtrl.create({
+                message: "Contacto guardado en el teléfono",
+                duration: 3000
+            }).present();
+        }, e => {
+            console.log(e);
+
+            this.toastCtrl.create({
+                message: "Contacto guardado en el teléfono",
+                duration: 3000
+            }).present();
+        });
+    }
 }

+ 26 - 0
src/pages/customer-list/customer-options.html

@@ -0,0 +1,26 @@
+<ion-list no-lines>
+    <button ion-item icon-left (click)="click('open')">
+        <ion-icon name="open"></ion-icon>
+        Abrir
+    </button>
+
+    <button ion-item icon-left (click)="click('delete')">
+        <ion-icon name="close"></ion-icon>
+        Eliminar
+    </button>
+
+    <button ion-item icon-left (click)="click('call')">
+        <ion-icon name="call"></ion-icon>
+        Llamar
+    </button>
+
+    <button ion-item icon-left (click)="click('navigate')">
+        <ion-icon name="navigate"></ion-icon>
+        Ir a ubicación
+    </button>
+
+    <button ion-item icon-left (click)="click('contact')">
+        <ion-icon name="contact"></ion-icon>
+        Guardar contacto
+    </button>
+</ion-list>

+ 22 - 0
src/pages/customer-list/customer-options.ts

@@ -0,0 +1,22 @@
+import { Component } from '@angular/core';
+import { NavParams, ViewController } from 'ionic-angular';
+
+@Component({
+    templateUrl: 'customer-options.html'
+})
+export class CustomerOptions {
+
+    constructor(
+        public nav: NavParams,
+        public viewCtrl: ViewController
+    ) { }
+
+    /**
+     *
+     */
+    click(role: string): void {
+        this.viewCtrl.dismiss({
+            role: role
+        });
+    }
+}

+ 1 - 0
src/pages/product-details/product-details.scss

@@ -1,4 +1,5 @@
 page-product-details {
+    
     ion-buttons button span ion-icon {
         padding: 0 6px !important;
         font-size: 1.4em !important;

+ 1 - 3
src/pages/product-details/product-details.ts

@@ -6,12 +6,10 @@ import { ProductListPage } from '../product-list/product-list';
 import { DataProvider } from '../../providers/data-provider'
 import { CameraProvider } from '../../providers/camera-provider';
 import { PreferencesProvider } from '../../providers/preferences-provider';
-// import 'reflect-metadata';
-
 
 @Component({
     selector: 'page-product-details',
-    providers: [Product, CameraProvider],
+    providers: [ Product, CameraProvider],
     templateUrl: 'product-details.html'
 })
 export class ProductDetailsPage {

+ 0 - 1
src/pages/product-list/product-list.html

@@ -65,7 +65,6 @@
         <ion-infinite-scroll-content></ion-infinite-scroll-content>
     </ion-infinite-scroll>
     
-
     <ion-fab right bottom>
         <button ion-fab color="yellow" (click)="goToPage(null)">
             <ion-icon name="add" color="light"></ion-icon>

+ 1 - 21
src/pages/product-list/product-list.scss

@@ -1,23 +1,3 @@
 page-product-list {
-    ion-buttons[start] {
-        order: 2;
-    }
-
-    ion-buttons button span ion-icon, ion-item div button span ion-icon {
-        padding: 0 6px !important;
-        font-size: 1.8em !important;
-    }
-
-    ion-label h2 {
-        word-break: keep-all;
-        white-space: normal;
-    }
-
-    ion-card ion-row ion-col button {
-        text-transform: none !important;
-    }
-
-    ion-card ion-row ion-col button ion-icon {
-        padding: 0 6px !important;
-    }
+    
 }

+ 5 - 2
src/pages/product-list/product-options.html

@@ -1,8 +1,11 @@
 <ion-list no-lines>
-    <button ion-item (click)="click('open')">
+    <button ion-item icon-left (click)="click('open')">
+        <ion-icon name="open"></ion-icon>
         Abrir
     </button>
-    <button ion-item (click)="click('delete')">
+
+    <button ion-item icon-left (click)="click('delete')">
+        <ion-icon name="close"></ion-icon>
         Eliminar
     </button>
 </ion-list>

+ 110 - 0
src/providers/phone-provider.ts

@@ -0,0 +1,110 @@
+import { Injectable } from "@angular/core";
+import { Observable } from "rxjs/Observable";
+import { Observer } from "rxjs/Observer";
+import { Subscription } from "rxjs/Subscription";
+import { Contact, Contacts, ContactName, ContactField, ContactAddress, CallNumber, Geolocation, LaunchNavigator, LaunchNavigatorOptions, Shake } from "ionic-native";
+
+@Injectable()
+export class PhoneProvider {
+
+    watcher: Subscription;
+
+    constructor() { }
+    
+    /**
+     *
+     */
+    saveContact(name: string, numbers: Array<any>, email?: string, addresses?: Array<any>): Observable<any> {
+        return Observable.create((observer: Observer<any>) => { 
+            let contact = Contacts.create();
+            contact.displayName.concat(name);
+
+            for (let i = 0; i < numbers.length; i++) {
+                contact.phoneNumbers.push(new ContactField('mobile', numbers[i]));
+            }
+            
+            if (email) {
+                contact.emails.push(new ContactField("email", email));
+            }
+            
+            if (addresses) {
+                for (let i = 0; i < addresses.length; i++) {
+                    contact.addresses.push(new ContactAddress());
+                }
+            }
+
+            console.log(contact);
+            
+            contact.save().then(() => { 
+                observer.complete();
+            }, e => {
+                observer.error(e);
+            });
+        });
+    }   
+
+    /**
+     *
+     */
+    call(numberToCall: string): Observable<any> {
+        return Observable.create((observer: Observer<any>) => { 
+            CallNumber.callNumber(numberToCall, true).then(() => { 
+                observer.complete();
+            }).catch(e => { 
+                observer.error(e);
+            });
+        });
+    }
+
+    /**
+     *
+     */
+    getCurrentPosition(): Observable<any> {
+        return Observable.create((observer: Observer<any>) => {
+            Geolocation.getCurrentPosition().then(position => {
+                observer.next(position);
+                observer.complete();
+            }).catch(e => { 
+                observer.error(e);
+            });
+        });
+    }
+
+    /**
+     *
+     */
+    navigate(latitude: number, longitude: number): Observable<any> {
+        return Observable.create((observer: Observer<any>) => {
+            LaunchNavigator.navigate([latitude, longitude]).then(s => { 
+                observer.next(s);
+                observer.complete();
+            }).catch(e => {
+                observer.error(e);
+            });
+        });
+    }
+
+    /**
+     *
+     */
+    enableShakeWatcher(): Observable<any> {
+        console.log("Watch");
+        
+        this.disableShakeWatcher();
+
+        return Observable.create((observer: Observer<any>) => {
+            this.watcher = Shake.startWatch(40).subscribe(() => { 
+                observer.next("ok");
+            });
+        });
+    }
+
+    /**
+     *
+     */
+    disableShakeWatcher(): void {
+        if (this.watcher && !this.watcher.closed) {
+            this.watcher.unsubscribe();
+        }
+    }
+}

+ 35 - 0
src/theme/odoo.scss

@@ -1,5 +1,40 @@
 $list-header-margin: 0px;
+$popover-width: 200px;
+$popover-border-radius: 5px;
+$button-header-size: 1.8em;
+$card-padding: 6px;
 
 ion-list-header {
     margin-bottom: $list-header-margin !important;
 }
+
+ion-buttons[start] {
+    order: 2;
+}
+
+ion-buttons button span ion-icon, ion-item div button span ion-icon {
+    padding: 0 6px !important;
+    font-size: $button-header-size !important;
+}
+
+ion-label h2 {
+    word-break: keep-all;
+    white-space: normal;
+}
+
+ion-card ion-row ion-col button {
+    text-transform: none !important;
+}
+
+ion-card ion-row ion-col button ion-icon {
+    padding: 0 $card-padding !important;
+}
+
+.popover-content {
+    width: $popover-width !important;
+    border-radius: $popover-border-radius !important;
+}
+
+.popover-viewport ion-list {
+    margin: -1px 0 5px 10px !important;
+}