Compare commits

..

3 Commits

Author SHA1 Message Date
c677fdcb59 feat: Reportes, chat de discusión y mejoras de navegación
- Tab "Reportados" en Contratos (cliente) y tab "Reportadas" en Postulaciones (proveedor) con skeleton loading e ionViewWillEnter
- Modal de chat para discusión de reportes: burbujas por rol (propio/otro/moderador), skeleton, input con cámara y envío
- Endpoints GET/POST contracts/reports/{id}/comments en ichamba.service
- userId guardado en AuthService desde auth/user para identificar mensajes propios
- Botón "Postularse" corregido con slot=end en ion-item
- Todas las secciones de tabs migradas de ngOnInit a ionViewWillEnter + ChangeDetectorRef.detectChanges()
- Android navigation bar reactiva al tema del sistema vía values/values-night styles.xml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 21:05:24 -06:00
aa8b0061c9 feat: Actualización general de la aplicación
- Contratos, tarjetas, postulaciones, categorías y reportes
- Servicios: auth, ichamba, env, language, firebase, interceptor
- Guards, modelos, componentes y páginas de verificación
- Configuración: angular.json, tsconfig, polyfills, environments
- Capacitor: capacitor.config.json y android settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 08:18:40 -06:00
db0d0001fa feat: Side menu dinámico, login social y traducciones hero
- app.component: menú lateral con roles de usuario, ChangeDetectorRef
  para re-render tras login, integración OneSignal al iniciar sesión
- auth/login: botones Google y Apple con estilos, navegación corregida
- auth/register y forgot: mejoras de UX y navegación
- landing: sombra en botones del footer
- i18n: claves hero (select, bank, bank_account, fee, required) en es/en

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 08:18:12 -06:00
94 changed files with 14699 additions and 9984 deletions

View File

@@ -9,7 +9,6 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies { dependencies {
implementation project(':capacitor-community-facebook-login')
implementation project(':capacitor-community-http') implementation project(':capacitor-community-http')
implementation project(':capacitor-app') implementation project(':capacitor-app')
implementation project(':capacitor-browser') implementation project(':capacitor-browser')
@@ -21,9 +20,7 @@ dependencies {
implementation project(':capacitor-preferences') implementation project(':capacitor-preferences')
implementation project(':capacitor-splash-screen') implementation project(':capacitor-splash-screen')
implementation project(':capacitor-status-bar') implementation project(':capacitor-status-bar')
implementation project(':codetrix-studio-capacitor-google-auth') implementation "com.onesignal:OneSignal:5.9.4"
implementation "com.onesignal:OneSignal:5.1.38"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10"
} }
apply from: "../../node_modules/onesignal-cordova-plugin/build-extras-onesignal.gradle" apply from: "../../node_modules/onesignal-cordova-plugin/build-extras-onesignal.gradle"

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:background">@null</item>
<item name="android:navigationBarColor">#000000</item>
<item name="android:windowLightNavigationBar">false</item>
</style>
</resources>

View File

@@ -13,6 +13,8 @@
<item name="windowActionBar">false</item> <item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item> <item name="windowNoTitle">true</item>
<item name="android:background">@null</item> <item name="android:background">@null</item>
<item name="android:navigationBarColor">#FFFFFF</item>
<item name="android:windowLightNavigationBar">true</item>
</style> </style>

View File

@@ -2,9 +2,6 @@
include ':capacitor-android' include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
include ':capacitor-community-facebook-login'
project(':capacitor-community-facebook-login').projectDir = new File('../node_modules/@capacitor-community/facebook-login/android')
include ':capacitor-community-http' include ':capacitor-community-http'
project(':capacitor-community-http').projectDir = new File('../node_modules/@capacitor-community/http/android') project(':capacitor-community-http').projectDir = new File('../node_modules/@capacitor-community/http/android')
@@ -37,6 +34,3 @@ project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capa
include ':capacitor-status-bar' include ':capacitor-status-bar'
project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android') project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')
include ':codetrix-studio-capacitor-google-auth'
project(':codetrix-studio-capacitor-google-auth').projectDir = new File('../node_modules/@codetrix-studio/capacitor-google-auth/android')

View File

@@ -177,7 +177,8 @@
} }
}, },
"cli": { "cli": {
"defaultCollection": "@ionic/angular-toolkit" "defaultCollection": "@ionic/angular-toolkit",
"analytics": false
}, },
"schematics": { "schematics": {
"@ionic/angular-toolkit:component": { "@ionic/angular-toolkit:component": {

1
capacitor.config.json Normal file
View File

@@ -0,0 +1 @@
{}

22092
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,15 +12,14 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "~18.2.14", "@angular/animations": "~22.0.1",
"@angular/common": "~18.2.14", "@angular/common": "~22.0.1",
"@angular/compiler": "~18.2.14", "@angular/compiler": "~22.0.1",
"@angular/core": "~18.2.14", "@angular/core": "~22.0.1",
"@angular/forms": "~18.2.14", "@angular/forms": "~22.0.1",
"@angular/platform-browser": "~18.2.14", "@angular/platform-browser": "~22.0.1",
"@angular/platform-browser-dynamic": "~18.2.14", "@angular/platform-browser-dynamic": "~22.0.1",
"@angular/router": "~18.2.14", "@angular/router": "~22.0.1",
"@capacitor-community/facebook-login": "^7.0.1",
"@capacitor-community/http": "^1.4.1", "@capacitor-community/http": "^1.4.1",
"@capacitor/android": "^6.0.0", "@capacitor/android": "^6.0.0",
"@capacitor/app": "^6.0.0", "@capacitor/app": "^6.0.0",
@@ -34,40 +33,40 @@
"@capacitor/preferences": "^6.0.0", "@capacitor/preferences": "^6.0.0",
"@capacitor/splash-screen": "^6.0.0", "@capacitor/splash-screen": "^6.0.0",
"@capacitor/status-bar": "^6.0.0", "@capacitor/status-bar": "^6.0.0",
"@codetrix-studio/capacitor-google-auth": "^3.4.0-rc.4", "@ionic/angular": "^8.8.10",
"@ionic/angular": "^8.7.17",
"@ngx-translate/core": "~14.0.0", "@ngx-translate/core": "~14.0.0",
"@ngx-translate/http-loader": "~7.0.0", "@ngx-translate/http-loader": "~7.0.0",
"onesignal-cordova-plugin": "^5.2.19", "firebase": "^12.14.0",
"onesignal-cordova-plugin": "^5.3.12",
"rxjs": "~7.8.1", "rxjs": "~7.8.1",
"swiper": "^11.2.10", "swiper": "^11.2.10",
"tslib": "^2.6.0", "tslib": "^2.6.0",
"zone.js": "~0.14.10" "zone.js": "~0.15.0"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~18.2.21", "@angular-devkit/build-angular": "~22.0.1",
"@angular-eslint/builder": "^21.1.0", "@angular-eslint/builder": "^22.0.0",
"@angular-eslint/eslint-plugin": "^21.1.0", "@angular-eslint/eslint-plugin": "^22.0.0",
"@angular-eslint/eslint-plugin-template": "^21.1.0", "@angular-eslint/eslint-plugin-template": "^22.0.0",
"@angular-eslint/schematics": "^21.1.0", "@angular-eslint/schematics": "^22.0.0",
"@angular-eslint/template-parser": "^21.1.0", "@angular-eslint/template-parser": "^22.0.0",
"@angular/cli": "~18.2.21", "@angular/cli": "~22.0.1",
"@angular/compiler-cli": "~18.2.14", "@angular/compiler-cli": "~22.0.1",
"@angular/language-service": "~18.2.14", "@angular/language-service": "~22.0.1",
"@capacitor/cli": "5.2.1", "@capacitor/cli": "5.2.1",
"@ionic/angular-toolkit": "^12.3.0", "@ionic/angular-toolkit": "^12.3.0",
"@types/jasmine": "~4.3.0", "@types/jasmine": "~4.3.0",
"@types/node": "~18.18.0", "@types/node": "^20.0.0",
"@typescript-eslint/eslint-plugin": "^8.53.0", "@typescript-eslint/eslint-plugin": "^8.61.0",
"@typescript-eslint/parser": "^8.53.0", "@typescript-eslint/parser": "^8.61.0",
"eslint": "^8.57.1", "eslint": "^10.0.0",
"jasmine-core": "~4.6.0", "jasmine-core": "~4.6.0",
"karma": "~6.4.0", "karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0", "karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0", "karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0", "karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0", "karma-jasmine-html-reporter": "~2.0.0",
"typescript": "~5.4.5" "typescript": "~6.0.3"
}, },
"description": "An Ionic project" "description": "An Ionic project"
} }

View File

@@ -27,7 +27,6 @@ const routes: Routes = [
{ path: 'start', loadChildren: () => import('./pages/contracts/start/start.module').then(m => m.StartPageModule), canActivate: [AuthGuard]}, { path: 'start', loadChildren: () => import('./pages/contracts/start/start.module').then(m => m.StartPageModule), canActivate: [AuthGuard]},
{ path: 'review/:contract_id', loadChildren: () => import('./pages/contracts/review/review.module').then(m => m.ReviewPageModule), canActivate: [AuthGuard] }, { path: 'review/:contract_id', loadChildren: () => import('./pages/contracts/review/review.module').then(m => m.ReviewPageModule), canActivate: [AuthGuard] },
{ path: 'report/:contract_id', loadChildren: () => import('./pages/contracts/report/report.module').then(m => m.ReportPageModule), canActivate: [AuthGuard] }, { path: 'report/:contract_id', loadChildren: () => import('./pages/contracts/report/report.module').then(m => m.ReportPageModule), canActivate: [AuthGuard] },
{ path: 'reports', loadChildren: () => import('./pages/reports/reports.module').then(m => m.ReportsPageModule), canActivate: [AuthGuard]},
{ path: 'nohome/:contract_id', loadChildren: () => import('./pages/contracts/nohome/nohome.module').then(m => m.NohomePageModule), canActivate: [AuthGuard] }, { path: 'nohome/:contract_id', loadChildren: () => import('./pages/contracts/nohome/nohome.module').then(m => m.NohomePageModule), canActivate: [AuthGuard] },
{ path: 'extra', loadChildren: () => import('./pages/contracts/extra/extra.module').then(m => m.ExtraPageModule), canActivate: [AuthGuard] }, { path: 'extra', loadChildren: () => import('./pages/contracts/extra/extra.module').then(m => m.ExtraPageModule), canActivate: [AuthGuard] },
{ path: 'ended', loadChildren: () => import('./pages/postulations/ended/ended.module').then(m => m.EndedPageModule), canActivate: [AuthGuard] }, { path: 'ended', loadChildren: () => import('./pages/postulations/ended/ended.module').then(m => m.EndedPageModule), canActivate: [AuthGuard] },

View File

@@ -8,12 +8,46 @@
</ion-header> </ion-header>
<ion-content> <ion-content>
<ion-list> <ion-list>
<ion-menu-toggle auto-hide="false" *ngFor="let p of appPages"> <ion-menu-toggle auto-hide="false" [class.ion-hide]="userRole === 0">
<ion-item [routerDirection]="'root'" [routerLink]="[p.url]"> <ion-item [routerDirection]="'root'" [routerLink]="['/dashboard']">
<ion-icon slot="start" [name]="p.icon"></ion-icon> <ion-icon slot="start" name="home"></ion-icon>
<ion-label> <ion-label>{{'menu.home' | translate}}</ion-label>
{{p.title}} </ion-item>
</ion-label> </ion-menu-toggle>
<ion-menu-toggle auto-hide="false" [class.ion-hide]="userRole === 0">
<ion-item [routerDirection]="'root'" [routerLink]="['/cards']">
<ion-icon slot="start" name="card"></ion-icon>
<ion-label>{{'menu.cards' | translate}}</ion-label>
</ion-item>
</ion-menu-toggle>
<ion-menu-toggle auto-hide="false" [class.ion-hide]="userRole < 2">
<ion-item [routerDirection]="'root'" [routerLink]="['/postulations']">
<ion-icon slot="start" name="hammer"></ion-icon>
<ion-label>{{'menu.postulations' | translate}}</ion-label>
</ion-item>
</ion-menu-toggle>
<ion-menu-toggle auto-hide="false" [class.ion-hide]="userRole === 0">
<ion-item [routerDirection]="'root'" [routerLink]="['/contracts']">
<ion-icon slot="start" name="file-tray-full"></ion-icon>
<ion-label>{{'menu.contracts' | translate}}</ion-label>
</ion-item>
</ion-menu-toggle>
<ion-menu-toggle auto-hide="false" [class.ion-hide]="userRole === 0">
<ion-item [routerDirection]="'root'" [routerLink]="['/faq']">
<ion-icon slot="start" name="information-circle"></ion-icon>
<ion-label>{{'menu.faq' | translate}}</ion-label>
</ion-item>
</ion-menu-toggle>
<ion-menu-toggle auto-hide="false" [class.ion-hide]="userRole < 2">
<ion-item [routerDirection]="'root'" [routerLink]="['/start']">
<ion-icon slot="start" name="send"></ion-icon>
<ion-label>{{'menu.start' | translate}}</ion-label>
</ion-item>
</ion-menu-toggle>
<ion-menu-toggle auto-hide="false" [class.ion-hide]="userRole !== 1">
<ion-item [routerDirection]="'root'" [routerLink]="['/hero']">
<ion-icon slot="start" name="ribbon"></ion-icon>
<ion-label>{{'menu.hero' | translate}}</ion-label>
</ion-item> </ion-item>
</ion-menu-toggle> </ion-menu-toggle>
<ion-item (click)="logout()"> <ion-item (click)="logout()">

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { Platform, NavController, LoadingController } from '@ionic/angular'; import { Platform, NavController, LoadingController } from '@ionic/angular';
import { EventService } from './services/event.service'; import { EventService } from './services/event.service';
import { StatusBar, Style } from '@capacitor/status-bar'; import { StatusBar, Style } from '@capacitor/status-bar';
@@ -13,17 +13,17 @@ import { OneSignalService } from './services/onesignal.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: 'app.component.html' templateUrl: 'app.component.html',
standalone: false
}) })
export class AppComponent { export class AppComponent implements OnInit {
private loading: any; private loading: any;
userRole: number = 0;
public appPages: any[] = [];
constructor( constructor(
private platform: Platform, private platform: Platform,
private authService: AuthService, public authService: AuthService,
private languageService: LanguageService, private languageService: LanguageService,
private translateService: TranslateService, private translateService: TranslateService,
private navCtrl: NavController, private navCtrl: NavController,
@@ -31,75 +31,20 @@ export class AppComponent {
private alertService: AlertService, private alertService: AlertService,
private loadingCtrl: LoadingController, private loadingCtrl: LoadingController,
private oneSignalService: OneSignalService, private oneSignalService: OneSignalService,
private cdr: ChangeDetectorRef,
) { ) {
this.initializeApp(); this.initializeApp();
this.events.subscribe('set_role', role => { this.events.subscribe('set_role', role => {
// Set OneSignal user tags when role is set (user logged in) this.userRole = role ?? 0;
this.cdr.detectChanges();
this.setupOneSignalUser(role); this.setupOneSignalUser(role);
if (role >= 2){
this.appPages = [
{
title: this.translateService.instant('menu.home'),
url: '/dashboard',
icon: 'home'
},
{
title: this.translateService.instant('menu.cards'),
url: '/cards',
icon: 'card'
},
{
title: this.translateService.instant('menu.postulations'),
url: '/postulations',
icon: 'hammer'
},
{
title: this.translateService.instant('menu.contracts'),
url: '/contracts',
icon: 'filing'
},
{
title: this.translateService.instant('menu.faq'),
url: '/faq',
icon: 'information-circle'
},
{
title: this.translateService.instant('menu.start'),
url: '/start',
icon: 'send'
}
];
} else {
this.appPages = [
{
title: this.translateService.instant('menu.home'),
url: '/dashboard',
icon: 'home'
},
{
title: this.translateService.instant('menu.cards'),
url: '/cards',
icon: 'card'
},
{
title: this.translateService.instant('menu.contracts'),
url: '/contracts',
icon: 'filing'
},
{
title: this.translateService.instant('menu.faq'),
url: '/faq',
icon: 'information-circle'
},
{
title: this.translateService.instant('menu.hero'),
url: '/hero',
icon: 'ribbon'
}
];
}
}); });
} }
ngOnInit() {
this.userRole = this.authService.userRole;
}
async initializeApp() { async initializeApp() {
await this.platform.ready(); await this.platform.ready();
@@ -114,7 +59,6 @@ export class AppComponent {
} }
this.languageService.getDefaultLanguage(); this.languageService.getDefaultLanguage();
this.authService.getToken();
} }
private async setupOneSignalUser(role: number) { private async setupOneSignalUser(role: number) {

View File

@@ -1,4 +1,5 @@
import { NgModule } from '@angular/core'; import { NgModule, APP_INITIALIZER } from '@angular/core';
import { AuthService } from './services/auth.service';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router'; import { RouteReuseStrategy } from '@angular/router';
@@ -6,7 +7,8 @@ import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { HttpClient, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; import { HttpClient, provideHttpClient, withInterceptorsFromDi, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptorService } from './services/auth-interceptor.service';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { TranslateHttpLoader } from '@ngx-translate/http-loader';
@@ -16,6 +18,10 @@ export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, 'assets/i18n/', '.json'); return new TranslateHttpLoader(http, 'assets/i18n/', '.json');
} }
function initAuth(authService: AuthService): () => Promise<void> {
return () => authService.initialize();
}
@NgModule({ @NgModule({
declarations: [AppComponent], declarations: [AppComponent],
bootstrap: [AppComponent], bootstrap: [AppComponent],
@@ -34,6 +40,8 @@ export function createTranslateLoader(http: HttpClient) {
], ],
providers: [ providers: [
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptorService, multi: true },
{ provide: APP_INITIALIZER, useFactory: initAuth, deps: [AuthService], multi: true },
provideHttpClient(withInterceptorsFromDi()) provideHttpClient(withInterceptorsFromDi())
] ]
}) })

View File

@@ -44,7 +44,8 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
useExisting: forwardRef(() => RatingComponent), useExisting: forwardRef(() => RatingComponent),
multi: true multi: true
} }
] ],
standalone: false
}) })
export class RatingComponent implements ControlValueAccessor { export class RatingComponent implements ControlValueAccessor {
@Input() readonly: string = 'false'; @Input() readonly: string = 'false';

View File

@@ -1,7 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { User } from 'src/app/models/user';
import { AuthService } from '../services/auth.service'; import { AuthService } from '../services/auth.service';
@Injectable({ @Injectable({
@@ -9,34 +7,24 @@ import { AuthService } from '../services/auth.service';
}) })
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
user: User;
constructor( constructor(
private router: Router, private router: Router,
private authService: AuthService private authService: AuthService
) {} ) {}
canActivate( canActivate(
next: ActivatedRouteSnapshot, next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { state: RouterStateSnapshot): boolean {
const currentUser = this.authService.isLoggedIn; if (this.authService.isReported) {
const userVerified = this.authService.isVerified;
const userReported = this.authService.isReported;
//if (userVerified == false) {
//this.router.navigate(['/verify']);
// return false;
//} else if (userReported == true) {
if (userReported == true) {
this.router.navigate(['/landing']); this.router.navigate(['/landing']);
return false; return false;
} else {
if (currentUser) {
// authorised so return true
return true;
}
} }
// not logged in so redirect to login page with the return url
if (this.authService.isLoggedIn) {
return true;
}
this.router.navigate(['/landing']); this.router.navigate(['/landing']);
return false; return false;
} }

View File

@@ -9,7 +9,7 @@ import { AuthService } from '../services/auth.service';
}) })
export class AuthoriginGuard implements CanActivate { export class AuthoriginGuard implements CanActivate {
user: User; user!: User;
constructor( constructor(
private router: Router, private router: Router,

View File

@@ -4,6 +4,7 @@ import { Component } from '@angular/core';
selector: 'app-home', selector: 'app-home',
templateUrl: 'home.page.html', templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'], styleUrls: ['home.page.scss'],
standalone: false
}) })
export class HomePage { export class HomePage {

View File

@@ -1,8 +1,8 @@
export class User { export class User {
id: number; id!: number;
name: string; name!: string;
email: string; email!: string;
phone: string; phone!: string;
phone_verified_at: string; phone_verified_at!: string;
reported: boolean; reported!: boolean;
} }

View File

@@ -8,8 +8,8 @@
<ion-title>{{'auth.forgot' | translate}}</ion-title> <ion-title>{{'auth.forgot' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<p text-wrap>{{'auth.forgot_instructions' | translate}}</p><br> <p class="ion-text-wrap">{{'auth.forgot_instructions' | translate}}</p><br>
<form #form="ngForm" (ngSubmit)="forgot(form)" method="post"> <form #form="ngForm" (ngSubmit)="forgot(form)" method="post">
<ion-item> <ion-item>
<ion-label position="floating">{{'auth.email' | translate}}</ion-label> <ion-label position="floating">{{'auth.email' | translate}}</ion-label>

View File

@@ -1,52 +1,52 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { NavController, LoadingController } from '@ionic/angular'; import { NavController, LoadingController } from '@ionic/angular';
import { NgForm } from '@angular/forms'; import { NgForm } from '@angular/forms';
import { AuthService } from 'src/app/services/auth.service';
import { AlertService } from 'src/app/services/alert.service'; import { AlertService } from 'src/app/services/alert.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { FirebaseAuthService } from 'src/app/services/firebase-auth.service';
@Component({ @Component({
selector: 'app-forgot', selector: 'app-forgot',
templateUrl: './forgot.page.html', templateUrl: './forgot.page.html',
styleUrls: ['./forgot.page.scss'], styleUrls: ['./forgot.page.scss'],
standalone: false
}) })
export class ForgotPage implements OnInit { export class ForgotPage implements OnInit {
private loading; private loading: HTMLIonLoadingElement | null = null;
constructor( constructor(
private authService: AuthService, private firebaseAuthService: FirebaseAuthService,
private navCtrl: NavController, private navCtrl: NavController,
private alertService: AlertService, private alertService: AlertService,
private translateService: TranslateService, private translateService: TranslateService,
private loadingCtrl: LoadingController private loadingCtrl: LoadingController
) { } ) { }
ngOnInit() {
} ngOnInit() { }
// Dismiss Forgot Modal
dismissForgot() { dismissForgot() {
this.navCtrl.back(); this.navCtrl.back();
} }
// On Register button tap, dismiss login modal and open register modal
forgot(form: NgForm) { async forgot(form: NgForm) {
this.loadingCtrl.create().then((overlay) => { this.loading = await this.loadingCtrl.create();
this.loading = overlay; await this.loading.present();
this.loading.present();
}); try {
this.authService.forgot(form.value.email).subscribe( await this.firebaseAuthService.sendPasswordReset(form.value.email);
data => { if (this.loading) this.loading.dismiss();
this.loading.dismiss(); this.alertService.presentToast(this.translateService.instant('alerts.reset_pass'));
this.dismissForgot();
} catch (e: any) {
if (this.loading) this.loading.dismiss();
if (e.code === 'auth/user-not-found') {
// Por seguridad mostramos el mismo mensaje aunque no exista el usuario
this.alertService.presentToast(this.translateService.instant('alerts.reset_pass')); this.alertService.presentToast(this.translateService.instant('alerts.reset_pass'));
},
error => {
this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['message']);
console.log(error);
},
() => {
this.dismissForgot(); this.dismissForgot();
} else {
this.alertService.presentToast(this.translateService.instant('alerts.error') + e.message);
} }
); }
} }
} }

View File

@@ -8,7 +8,7 @@
<ion-title>{{'auth.login' | translate}}</ion-title> <ion-title>{{'auth.login' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<form #form="ngForm" (ngSubmit)="login(form)" method="post"> <form #form="ngForm" (ngSubmit)="login(form)" method="post">
<ion-item> <ion-item>
<ion-label position="floating">{{'auth.email' | translate}}</ion-label> <ion-label position="floating">{{'auth.email' | translate}}</ion-label>
@@ -22,11 +22,27 @@
<br> <br>
<ion-button type="submit" expand="full" color="primary">{{'auth.login' | translate}}</ion-button> <ion-button type="submit" expand="full" color="primary">{{'auth.login' | translate}}</ion-button>
</form> </form>
<ion-text><p text-center (click)="forgotModal()"><u>{{'auth.forgot' | translate}}</u></p></ion-text> <ion-text><p class="ion-text-center" (click)="forgotModal()"><u>{{'auth.forgot' | translate}}</u></p></ion-text>
<br><br> <br><br>
<ion-button expand="full" color="facebook" (click)="loginFacebook()">{{'auth.fb_login' | translate}}</ion-button> <!-- Google Sign-In — branding oficial: fondo blanco, logo G coloreado, borde gris -->
<ion-button expand="full" color="danger" (click)="loginGoogle()">{{'auth.google_login' | translate}}</ion-button> <button class="btn-social btn-google" (click)="loginGoogle()">
<p text-center>{{'auth.signup_ad' | translate}}</p> <svg class="btn-social__icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/>
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z" fill="#FBBC05"/>
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
</svg>
<span class="btn-social__label">{{'auth.google_login' | translate}}</span>
</button>
<!-- Apple Sign-In — branding oficial: fondo negro, logo Apple blanco, SF font -->
<button class="btn-social btn-apple" (click)="loginApple()">
<svg class="btn-social__icon btn-social__icon--apple" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.7 9.05 7.4c1.42.07 2.4.78 3.23.8 1.22-.24 2.39-.93 3.68-.84 1.56.12 2.73.72 3.5 1.9-3.22 1.93-2.46 5.91.59 7.03-.69 1.55-1.6 3.08-3 4zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z" fill="#FFFFFF"/>
</svg>
<span class="btn-social__label">{{'auth.apple_login' | translate}}</span>
</button>
<p class="ion-text-center">{{'auth.signup_ad' | translate}}</p>
<ion-button expand="full" color="secondary" (click)="registerModal()">{{'auth.signup' | translate}}</ion-button> <ion-button expand="full" color="secondary" (click)="registerModal()">{{'auth.signup' | translate}}</ion-button>
<ion-text><p text-center>{{'auth.terms_1' | translate}}<u (click)="openTerms()">{{'auth.terms_2' | translate}}</u>{{'auth.terms_3' | translate}}</p></ion-text> <ion-text><p class="ion-text-center">{{'auth.terms_1' | translate}}<u (click)="openTerms()">{{'auth.terms_2' | translate}}</u>{{'auth.terms_3' | translate}}</p></ion-text>
</ion-content> </ion-content>

View File

@@ -0,0 +1,63 @@
.btn-social {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 44px;
border-radius: 4px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 500;
letter-spacing: 0.06em;
margin-bottom: 12px;
padding: 0 12px;
transition: box-shadow 0.2s ease;
&__icon {
width: 20px;
height: 20px;
margin-right: 12px;
flex-shrink: 0;
&--apple {
width: 18px;
height: 18px;
}
}
&__label {
flex: 1;
text-align: center;
margin-right: 32px; // compensa el ancho del icono para centrar visualmente
text-transform: uppercase;
}
&:active {
box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.15);
}
}
// Google: fondo blanco, borde gris, texto oscuro
.btn-google {
background-color: #ffffff;
border: 1px solid #DADCE0;
color: #3c4043;
font-family: 'Roboto', sans-serif;
&:hover {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
}
// Apple: fondo negro, texto blanco, font del sistema
.btn-apple {
background-color: #000000;
color: #ffffff;
font-family: -apple-system, 'SF Pro Display', BlinkMacSystemFont, sans-serif;
border-radius: 6px;
&:hover {
background-color: #1a1a1a;
}
}

View File

@@ -4,22 +4,24 @@ import { NgForm } from '@angular/forms';
import { AuthService } from 'src/app/services/auth.service'; import { AuthService } from 'src/app/services/auth.service';
import { AlertService } from 'src/app/services/alert.service'; import { AlertService } from 'src/app/services/alert.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { FacebookLogin, FacebookLoginResponse } from '@capacitor-community/facebook-login'; import { FirebaseAuthService } from 'src/app/services/firebase-auth.service';
import { GoogleAuth } from '@codetrix-studio/capacitor-google-auth';
import { Browser } from '@capacitor/browser'; import { Browser } from '@capacitor/browser';
import { firstValueFrom } from 'rxjs';
@Component({ @Component({
selector: 'app-login', selector: 'app-login',
templateUrl: './login.page.html', templateUrl: './login.page.html',
styleUrls: ['./login.page.scss'] styleUrls: ['./login.page.scss'],
standalone: false
}) })
export class LoginPage implements OnInit { export class LoginPage implements OnInit {
private loading: any; private loading: HTMLIonLoadingElement | null = null;
constructor( constructor(
private authService: AuthService, private authService: AuthService,
private firebaseAuthService: FirebaseAuthService,
private navCtrl: NavController, private navCtrl: NavController,
private alertService: AlertService, private alertService: AlertService,
private translateService: TranslateService, private translateService: TranslateService,
@@ -45,68 +47,73 @@ export class LoginPage implements OnInit {
this.loading = await this.loadingCtrl.create(); this.loading = await this.loadingCtrl.create();
await this.loading.present(); await this.loading.present();
this.authService.login(form.value.email, form.value.password).subscribe( try {
data => { const idToken = await this.firebaseAuthService.signInWithEmail(
if (this.loading) this.loading.dismiss(); form.value.email,
}, form.value.password
error => { );
if (this.loading) this.loading.dismiss();
if (JSON.stringify(error['status']) == '401') { this.authService.loginWithFirebase(idToken).subscribe(
this.alertService.presentToast(this.translateService.instant('alerts.login_error')); (data: any) => {},
} (error: any) => {
else { if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['message']); if (JSON.stringify(error['status']) == '401') {
console.log(error); this.alertService.presentToast(this.translateService.instant('alerts.login_error'));
} } else {
}, this.alertService.presentToast(this.translateService.instant('alerts.error') + error['message']);
() => { }
if (this.loading) this.loading.dismiss(); },
this.dismissLogin(); () => { this.navigateAfterLogin(); }
this.navCtrl.navigateRoot('/dashboard'); );
this.alertService.presentToast(this.translateService.instant('alerts.login')); } catch (e: any) {
} if (this.loading) this.loading.dismiss();
); const msg = this.getFirebaseErrorMessage(e.code);
this.alertService.presentToast(msg);
}
} }
async loginFacebook() { private async navigateAfterLogin() {
this.loading = await this.loadingCtrl.create(); try {
await this.loading.present(); await firstValueFrom(this.authService.user());
} catch { }
if (this.loading) this.loading.dismiss();
this.navCtrl.navigateRoot('/dashboard');
this.alertService.presentToast(this.translateService.instant('alerts.login'));
}
try { private getFirebaseErrorMessage(code: string): string {
// Login with permissions switch (code) {
const result = await FacebookLogin.login({ permissions: ['public_profile', 'email'] }); case 'auth/wrong-password':
case 'auth/user-not-found':
case 'auth/invalid-credential':
return this.translateService.instant('alerts.login_error');
case 'auth/too-many-requests':
return this.translateService.instant('alerts.too_many_requests');
default:
return this.translateService.instant('alerts.error') + code;
}
}
if (result.accessToken) { async loginApple() {
// Get user ID and Token this.loading = await this.loadingCtrl.create();
const fb_id = result.accessToken.userId; await this.loading.present();
const fb_token = result.accessToken.token;
console.log("ID: " + fb_id); try {
console.log("Token: " + fb_token); const idToken = await this.firebaseAuthService.signInWithApple();
this.authService.login_register_fb(fb_token, fb_id).subscribe( this.authService.loginWithFirebase(idToken).subscribe(
data => { (data: any) => {},
}, (error: any) => {
error => { if (this.loading) this.loading.dismiss();
this.loading.dismiss(); this.alertService.presentToast(this.translateService.instant('alerts.error') + error);
this.alertService.presentToast(this.translateService.instant('alerts.error') + error); console.log(error);
console.log(error); },
}, () => { this.navigateAfterLogin(); }
() => { );
this.loading.dismiss(); } catch (e) {
this.dismissLogin(); if (this.loading) this.loading.dismiss();
this.navCtrl.navigateRoot('/dashboard'); this.alertService.presentToast(this.translateService.instant('alerts.error') + e);
this.alertService.presentToast(this.translateService.instant('alerts.login')); }
}
);
} else {
this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.timeout_error'));
}
} catch (e) {
this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.error') + e);
}
} }
async loginGoogle() { async loginGoogle() {
@@ -114,32 +121,19 @@ export class LoginPage implements OnInit {
await this.loading.present(); await this.loading.present();
try { try {
const res = await GoogleAuth.signIn(); const idToken = await this.firebaseAuthService.signInWithGoogle();
console.log(res);
const google_id = res.id; this.authService.loginWithFirebase(idToken).subscribe(
const google_token = res.authentication.accessToken; (data: any) => {},
(error: any) => {
console.log("ID: " + google_id); if (this.loading) this.loading.dismiss();
console.log("Token: " + google_token);
this.authService.login_register_googlePlus(google_token, google_id).subscribe(
data => {
},
error => {
this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.error') + error); this.alertService.presentToast(this.translateService.instant('alerts.error') + error);
console.log(error); console.log(error);
}, },
() => { () => { this.navigateAfterLogin(); }
this.loading.dismiss();
this.dismissLogin();
this.navCtrl.navigateRoot('/dashboard');
this.alertService.presentToast(this.translateService.instant('alerts.login'));
}
); );
} catch (err) { } catch (err) {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.error') + err); this.alertService.presentToast(this.translateService.instant('alerts.error') + err);
console.log(err); console.log(err);
} }

View File

@@ -8,7 +8,7 @@
<ion-title>{{'auth.signup' | translate}}</ion-title> <ion-title>{{'auth.signup' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<form #form="ngForm" (ngSubmit)="register(form)" method="post"> <form #form="ngForm" (ngSubmit)="register(form)" method="post">
<ion-item> <ion-item>
<ion-label position="floating">{{'auth.name' | translate}}</ion-label> <ion-label position="floating">{{'auth.name' | translate}}</ion-label>
@@ -37,7 +37,7 @@
<br><br> <br><br>
<ion-row> <ion-row>
<ion-col size="9.5"> <ion-col size="9.5">
<p text-center style="margin-bottom:0;margin-top:0.25em;color:#4d4d4d">{{'auth.terms_1' | translate}}<u (click)="openTerms()">{{'auth.terms_2' | translate}}</u>{{'auth.terms_3' | translate}}</p> <p class="ion-text-center" style="margin-bottom:0;margin-top:0.25em;color:#4d4d4d">{{'auth.terms_1' | translate}}<u (click)="openTerms()">{{'auth.terms_2' | translate}}</u>{{'auth.terms_3' | translate}}</p>
</ion-col> </ion-col>
<ion-col size="2.5"> <ion-col size="2.5">
<ion-item lines="none"> <ion-item lines="none">
@@ -48,6 +48,6 @@
<br> <br>
<ion-button type="submit" expand="full" color="secondary">{{'auth.signup' | translate}}</ion-button> <ion-button type="submit" expand="full" color="secondary">{{'auth.signup' | translate}}</ion-button>
</form> </form>
<br><p text-center>{{'auth.login_ad' | translate}}</p> <br><p class="ion-text-center">{{'auth.login_ad' | translate}}</p>
<ion-button expand="full" color="primary" (click)="loginModal()">{{'auth.login' | translate}}</ion-button> <ion-button expand="full" color="primary" (click)="loginModal()">{{'auth.login' | translate}}</ion-button>
</ion-content> </ion-content>

View File

@@ -4,85 +4,93 @@ import { AuthService } from 'src/app/services/auth.service';
import { NgForm } from '@angular/forms'; import { NgForm } from '@angular/forms';
import { AlertService } from 'src/app/services/alert.service'; import { AlertService } from 'src/app/services/alert.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { FirebaseAuthService } from 'src/app/services/firebase-auth.service';
import { Browser } from '@capacitor/browser'; import { Browser } from '@capacitor/browser';
@Component({ @Component({
selector: 'app-register', selector: 'app-register',
templateUrl: './register.page.html', templateUrl: './register.page.html',
styleUrls: ['./register.page.scss'], styleUrls: ['./register.page.scss'],
standalone: false
}) })
export class RegisterPage implements OnInit { export class RegisterPage implements OnInit {
loading: any = null; loading: HTMLIonLoadingElement | null = null;
terms = false; terms = false;
constructor( constructor(
private authService: AuthService, private authService: AuthService,
private firebaseAuthService: FirebaseAuthService,
private translateService: TranslateService, private translateService: TranslateService,
private navCtrl: NavController, private navCtrl: NavController,
private loadingCtrl: LoadingController, private loadingCtrl: LoadingController,
private alertService: AlertService private alertService: AlertService
) { } ) { }
ngOnInit() {
} ngOnInit() { }
// Dismiss Register Modal
dismissRegister() { dismissRegister() {
this.navCtrl.navigateRoot('/landing'); this.navCtrl.navigateRoot('/landing');
} }
// On Login button tap, dismiss Register modal and open login Modal
loginModal() { loginModal() {
this.navCtrl.navigateForward('/login'); this.navCtrl.navigateForward('/login');
} }
updateChecked () { updateChecked() {
if (this.terms == true) { this.terms = !this.terms;
this.terms = false;
console.log(this.terms);
} else {
this.terms = true;
console.log(this.terms);
}
} }
register(form: NgForm) { async register(form: NgForm) {
if (!this.terms) {
if (this.terms == true) {
if (form.value.name && form.value.email && form.value.phone && form.value.password && form.value.password1) {
if (form.value.password == form.value.password1) {
this.loadingCtrl.create().then((overlay) => {
this.loading = overlay;
this.loading.present();
});
this.authService.register(form.value.name, form.value.email, form.value.phone, form.value.password).subscribe(
data => {
this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.signup'));
},
error => {
if (JSON.stringify(error['status']) == '422') {
this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.signup_error'));
}
else {
this.loading.dismiss();
console.log(error);
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['message']);
}
},
() => {
}
);
} else {
this.alertService.presentToast(this.translateService.instant('alerts.signup_error_1'));
}
} else {
this.alertService.presentToast(this.translateService.instant('alerts.signup_error_2'));
}
} else {
this.alertService.presentToast(this.translateService.instant('alerts.signup_error_3')); this.alertService.presentToast(this.translateService.instant('alerts.signup_error_3'));
return;
} }
const { name, email, phone, password, password1 } = form.value;
if (!name || !email || !phone || !password || !password1) {
this.alertService.presentToast(this.translateService.instant('alerts.signup_error_2'));
return;
}
if (password !== password1) {
this.alertService.presentToast(this.translateService.instant('alerts.signup_error_1'));
return;
}
this.loading = await this.loadingCtrl.create();
await this.loading.present();
try {
// Firebase crea la cuenta con email + password
const idToken = await this.firebaseAuthService.registerWithEmail(email, password);
// Backend crea el perfil y devuelve el token de la app
this.authService.registerWithFirebase(idToken, name, phone, email).subscribe(
(data: any) => {},
(error: any) => {
if (this.loading) this.loading.dismiss();
if (error['status'] === 422) {
this.alertService.presentToast(this.translateService.instant('alerts.signup_error'));
} else {
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['message']);
}
},
() => {
if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.signup'));
this.navCtrl.navigateRoot('/dashboard');
}
);
} catch (e: any) {
if (this.loading) this.loading.dismiss();
if (e.code === 'auth/email-already-in-use') {
this.alertService.presentToast(this.translateService.instant('alerts.signup_error'));
} else {
this.alertService.presentToast(this.translateService.instant('alerts.error') + e.message);
}
}
} }
async openTerms() { async openTerms() {

View File

@@ -6,7 +6,7 @@
<ion-title>{{'cards.add' | translate}}</ion-title> <ion-title>{{'cards.add' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<form #form="ngForm" id="card_form" (ngSubmit)="addcard(form)" method="post"> <form #form="ngForm" id="card_form" (ngSubmit)="addcard(form)" method="post">
<ion-item [class.ng-invalid]="!holder_check"> <ion-item [class.ng-invalid]="!holder_check">
@@ -52,10 +52,10 @@
</ion-row> </ion-row>
<ion-row> <ion-row>
<ion-col size="6"> <ion-col size="6">
<p text-center>{{'cards.openpay_2' | translate}}<p> <p class="ion-text-center">{{'cards.openpay_2' | translate}}<p>
</ion-col> </ion-col>
<ion-col size="6" style="border-left: 1px solid #d0d0d0"> <ion-col size="6" style="border-left: 1px solid #d0d0d0">
<p text-center>{{'cards.openpay_3' | translate}}<p> <p class="ion-text-center">{{'cards.openpay_3' | translate}}<p>
</ion-col> </ion-col>
</ion-row> </ion-row>
<ion-row> <ion-row>

View File

@@ -14,22 +14,23 @@ declare var OpenPay: any;
selector: 'app-addcard', selector: 'app-addcard',
templateUrl: './addcard.page.html', templateUrl: './addcard.page.html',
styleUrls: ['./addcard.page.scss'], styleUrls: ['./addcard.page.scss'],
standalone: false
}) })
export class AddcardPage implements OnInit { export class AddcardPage implements OnInit {
private loading; private loading: HTMLIonLoadingElement | null = null;
card_check = true; card_check = true;
date_check = true; date_check = true;
cvv_check = true; cvv_check = true;
holder_check = null; holder_check: boolean | null = null;
line1_check = null; line1_check: boolean | null = null;
line2_check = null; line2_check: boolean | null = null;
line3_check = null; line3_check: boolean | null = null;
postal_code_check = null; postal_code_check: boolean | null = null;
city_check = null; city_check: boolean | null = null;
state_check = null; state_check: boolean | null = null;
month_number = null; month_number: any = null;
year_number = null; year_number: any = null;
constructor( constructor(
private modalController: ModalController, private modalController: ModalController,
@@ -147,28 +148,28 @@ export class AddcardPage implements OnInit {
"country_code":"MX" "country_code":"MX"
}*/ }*/
}, },
onSuccess => { (onSuccess: any) => {
let token = onSuccess.data.id; let token = onSuccess.data.id;
let device_id = (<HTMLInputElement>document.getElementById('deviceDataId')).value; let device_id = (<HTMLInputElement>document.getElementById('deviceDataId')).value;
this.ichambaService.addCard(token, device_id).subscribe( this.ichambaService.addCard(token, device_id).subscribe(
data => { (data: any) => {
if (data['type'] == "error") { if (data['type'] == "error") {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(data['message']); this.alertService.presentToast(data['message']);
} else { } else {
this.events.publish('refreshcards', 'data'); this.events.publish('refreshcards', 'data');
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.dismissAddcard(); this.dismissAddcard();
this.alertService.presentToast(this.translateService.instant('alerts.added_card')); this.alertService.presentToast(this.translateService.instant('alerts.added_card'));
} }
}, error => { }, (error: any) => {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']); this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
}); });
//console.log(onSuccess.data.id); //console.log(onSuccess.data.id);
//console.log(document.getElementById('deviceDataId').value); //console.log(document.getElementById('deviceDataId').value);
}, onError => { }, (onError: any) => {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.geo_error') + onError['status']); this.alertService.presentToast(this.translateService.instant('alerts.geo_error') + onError['status']);
}); });
} }

View File

@@ -6,11 +6,11 @@
<ion-title>{{'cards.header' | translate}}</ion-title> <ion-title>{{'cards.header' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<ion-card *ngFor="let card of cards"> <ion-card *ngFor="let card of cards">
<ion-item> <ion-item>
<ion-label> <ion-label>
<h2 text-capitalize>{{ card.brand }}</h2> <h2 class="ion-text-capitalize">{{ card.brand }}</h2>
<p>{{card.card_number}}</p> <p>{{card.card_number}}</p>
</ion-label> </ion-label>
<ion-button color="danger" (click)="deletecard(card.id)"> <ion-button color="danger" (click)="deletecard(card.id)">

View File

@@ -12,10 +12,11 @@ import { AlertService } from 'src/app/services/alert.service';
selector: 'app-cards', selector: 'app-cards',
templateUrl: './cards.page.html', templateUrl: './cards.page.html',
styleUrls: ['./cards.page.scss'], styleUrls: ['./cards.page.scss'],
standalone: false
}) })
export class CardsPage implements OnInit { export class CardsPage implements OnInit {
cards = []; cards: any[] = [];
constructor( constructor(
private modalController: ModalController, private modalController: ModalController,

View File

@@ -13,6 +13,7 @@ declare var google: any;
selector: 'app-category', selector: 'app-category',
templateUrl: './category.page.html', templateUrl: './category.page.html',
styleUrls: ['./category.page.scss'], styleUrls: ['./category.page.scss'],
standalone: false
}) })
export class CategoryPage implements OnInit { export class CategoryPage implements OnInit {
@@ -22,20 +23,20 @@ export class CategoryPage implements OnInit {
//amount: number = null; //amount: number = null;
//amount_check = true; //amount_check = true;
minyear: string = (new Date().getFullYear() + '-' + ((new Date().getMonth() + 1) < 10 ? '0' : '') + (new Date().getMonth() + 1) + '-' + (new Date().getDate() < 10 ? '0' : '') + new Date().getDate()).toString(); minyear: string = (new Date().getFullYear() + '-' + ((new Date().getMonth() + 1) < 10 ? '0' : '') + (new Date().getMonth() + 1) + '-' + (new Date().getDate() < 10 ? '0' : '') + new Date().getDate()).toString();
maxyear: string = null; maxyear: string | null = null;
mintime: number; mintime: number = 0;
maxtime: number; maxtime: number = 0;
setHour: any; setHour: any;
setDate: any; setDate: any;
references: string; references: string = '';
details: string; details: string = '';
differenceDate: number; differenceDate: number = 0;
differenceHour: number; differenceHour: number = 0;
myPosition: any = {}; myPosition: any = {};
myAddress: string = null; myAddress: string | null = null;
myIntnumber: string = null; myIntnumber: string | null = null;
addressAutocomplete: string = ''; addressAutocomplete: string = '';
placesSearch: string = ''; placesSearch: any = '';
showif = true; showif = true;
constructor( constructor(
@@ -54,7 +55,7 @@ export class CategoryPage implements OnInit {
ngOnInit() { ngOnInit() {
this.category = this.activatedRoute.snapshot.paramMap.get('category'); this.category = this.activatedRoute.snapshot.paramMap.get('category');
this.ichambaService.getParameters() this.ichambaService.getParameters()
.subscribe( data => { .subscribe( (data: any) => {
this.mintime = data['min_time']; this.mintime = data['min_time'];
this.maxtime = data['max_time']; this.maxtime = data['max_time'];
}); });
@@ -135,7 +136,7 @@ export class CategoryPage implements OnInit {
this.placesSearch = null; this.placesSearch = null;
} }
console.log(this.addressAutocomplete) console.log(this.addressAutocomplete)
new google.maps.places.AutocompleteService().getPredictions({ input: this.addressAutocomplete }, predictions => { new google.maps.places.AutocompleteService().getPredictions({ input: this.addressAutocomplete }, (predictions: any) => {
this.ngZone.run(() => { this.ngZone.run(() => {
this.placesSearch = predictions; this.placesSearch = predictions;
console.log(predictions); console.log(predictions);
@@ -148,7 +149,7 @@ export class CategoryPage implements OnInit {
this.addressAutocomplete = place_description; // Sincronizar con el input this.addressAutocomplete = place_description; // Sincronizar con el input
console.log(place_id); console.log(place_id);
this.hidelist(); this.hidelist();
new google.maps.Geocoder().geocode({ placeId: place_id }, coordinates => { new google.maps.Geocoder().geocode({ placeId: place_id }, (coordinates: any) => {
this.ngZone.run(() => { this.ngZone.run(() => {
console.log(coordinates[0].geometry.location.lat() + ", " + coordinates[0].geometry.location.lng()); console.log(coordinates[0].geometry.location.lat() + ", " + coordinates[0].geometry.location.lng());
this.myPosition = { this.myPosition = {
@@ -194,20 +195,20 @@ export class CategoryPage implements OnInit {
this.loading.present(); this.loading.present();
}); });
if (!Number.isInteger(parseInt(this.myIntnumber))) { if (!Number.isInteger(parseInt(this.myIntnumber ?? ''))) {
this.myIntnumber = null; this.myIntnumber = null;
} }
this.ichambaService.addPostulation(this.category, this.myAddress, this.myIntnumber, this.references, this.myPosition.latitude, this.myPosition.longitude, this.setDate, this.setHour, this.details).subscribe( this.ichambaService.addPostulation(this.category, this.myAddress!, this.myIntnumber ?? '', this.references, this.myPosition.latitude, this.myPosition.longitude, this.setDate, this.setHour, this.details).subscribe(
data => { (data: any) => {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
if (data['message'] == 'No providers') { if (data['message'] == 'No providers') {
this.alertService.presentToast(this.translateService.instant('alerts.no_provider')); this.alertService.presentToast(this.translateService.instant('alerts.no_provider'));
} else { } else {
this.navCtrl.navigateRoot('/dashboard'); this.navCtrl.navigateRoot('/dashboard');
this.alertService.presentToast(this.translateService.instant('alerts.categories_success')); this.alertService.presentToast(this.translateService.instant('alerts.categories_success'));
} }
}, error => { }, (error: any) => {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']); this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
}); });
} else { } else {

View File

@@ -6,27 +6,46 @@
<ion-title>{{'contracts.header' | translate}}</ion-title> <ion-title>{{'contracts.header' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<h2 text-capitalize style="padding-bottom: 0.5em">{{'contracts.header_2' | translate}}</h2> <h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">{{'contracts.header_2' | translate}}</h2>
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)"> <ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
<ion-refresher-content></ion-refresher-content> <ion-refresher-content></ion-refresher-content>
</ion-refresher> </ion-refresher>
<ng-container *ngIf="loading">
<ion-card *ngFor="let _ of [1,2]">
<ion-item style="--border-color: #fff">
<ion-label>
<ion-skeleton-text [animated]="true" style="width: 55%; height: 18px; margin-bottom: 8px"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 85%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 70%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 50%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 40%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 35%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 65%; height: 16px; margin-top: 6px"></ion-skeleton-text>
</ion-label>
</ion-item>
</ion-card>
</ng-container>
<ng-container *ngIf="!loading">
<ng-container *ngFor="let ccontract of ccontracts; let i = index"> <ng-container *ngFor="let ccontract of ccontracts; let i = index">
<ion-card *ngIf="ccontract.status==1"> <ion-card *ngIf="ccontract.status==1">
<ion-item style="--border-color: #fff"> <ion-item style="--border-color: #fff">
<ion-label> <ion-label>
<h2 *ngIf="lang===true"text-capitalize>{{ ccontract.category }}</h2> <h2 *ngIf="lang===true" class="ion-text-capitalize">{{ ccontract.category }}</h2>
<h2 *ngIf="lang===false"text-capitalize>{{ ccontract.en_category }}</h2> <h2 *ngIf="lang===false" class="ion-text-capitalize">{{ ccontract.en_category }}</h2>
<p text-wrap>{{ ccontract.address }}</p> <p class="ion-text-wrap">{{ ccontract.address }}</p>
<p text-wrap text-capitalize>{{ ccontracts_dates[i] }}</p> <p class="ion-text-wrap ion-text-capitalize">{{ ccontracts_dates[i] }}</p>
<p text-wrap text-capitalize>{{'contracts.supplier' | translate}}: {{ ccontract.supplier }}</p> <p class="ion-text-wrap ion-text-capitalize">{{'contracts.supplier' | translate}}: {{ ccontract.supplier }}</p>
<p text-wrap>{{'contracts.phone' | translate}}: <a href="tel:{{ ccontract.phone }}">{{ ccontract.phone }}</a></p> <p class="ion-text-wrap">{{'contracts.phone' | translate}}: <a href="tel:{{ ccontract.phone }}">{{ ccontract.phone }}</a></p>
<p text-wrap text-capitalize>{{'contracts.amount' | translate}}: ${{ ccontract.amount }}</p> <p class="ion-text-wrap ion-text-capitalize">{{'contracts.amount' | translate}}: ${{ ccontract.amount }}</p>
<h2 text-wrap>{{'contracts.start_pin' | translate}}<b>{{ ccontract.code }}</b></h2> <h2 class="ion-text-wrap">{{'contracts.start_pin' | translate}}<b>{{ ccontract.code }}</b></h2>
<p text-wrap>{{'contracts.info_pin' | translate}}</p> <p class="ion-text-wrap">{{'contracts.info_pin' | translate}}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-button *ngIf="ccontract.past_due < 0" style="height: 4.0em; padding-right: 1.0em; padding-bottom:1.0em; float: right" color="danger" (click)="cancelContract(ccontract.id, ccontract.date)">{{'contracts.cancel_1.1' | translate}}<br>{{'contracts.cancel_1.2' | translate}}</ion-button> <ion-button *ngIf="ccontract.past_due < 0" style="height: 4.0em; padding-right: 1.0em; padding-bottom:1.0em; float: right" color="danger" (click)="cancelContract(ccontract.id, ccontract.date)">{{'contracts.cancel_1.1' | translate}}<br>{{'contracts.cancel_1.2' | translate}}</ion-button>
</ion-card> </ion-card>
</ng-container> </ng-container>
</ng-container>
</ion-content> </ion-content>

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, ChangeDetectorRef } from '@angular/core';
import { ModalController, MenuController, NavController } from '@ionic/angular'; import { ModalController, MenuController, NavController } from '@ionic/angular';
import { EventService } from '../../../services/event.service'; import { EventService } from '../../../services/event.service';
import { EnvService } from 'src/app/services/env.service'; import { EnvService } from 'src/app/services/env.service';
@@ -13,12 +13,14 @@ import { AlertController } from '@ionic/angular';
selector: 'app-contracted', selector: 'app-contracted',
templateUrl: './contracted.page.html', templateUrl: './contracted.page.html',
styleUrls: ['./contracted.page.scss'], styleUrls: ['./contracted.page.scss'],
standalone: false
}) })
export class ContractedPage implements OnInit { export class ContractedPage {
ccontracts = []; ccontracts: any[] = [];
ccontracts_dates = []; ccontracts_dates: any[] = [];
lang: boolean; lang: boolean = false;
loading = true;
constructor( constructor(
private modalController: ModalController, private modalController: ModalController,
@@ -32,32 +34,29 @@ export class ContractedPage implements OnInit {
private translateService: TranslateService, private translateService: TranslateService,
private languageService: LanguageService, private languageService: LanguageService,
private env: EnvService, private env: EnvService,
private cdr: ChangeDetectorRef,
) { ) {
this.events.subscribe('refreshccontracts', (data) => { this.events.subscribe('refreshccontracts', (data) => {
this.getccontracts(); this.getccontracts();
}); });
} }
ngOnInit() { ionViewWillEnter() {
this.lang = this.languageService.getDefaultLanguage() === 'es';
this.loading = true;
this.getccontracts(); this.getccontracts();
if (this.languageService.getDefaultLanguage() == 'es') {
this.lang = true;
} else {
this.lang = false
}
} }
refresh(event) { refresh(event: any) {
this.ichambaService.getCurrentcontracts().subscribe( this.ichambaService.getCurrentcontracts().subscribe(
data => { data => {
this.ccontracts = data; this.ccontracts = data;
this.ccontracts_dates = [];
for (var i of this.ccontracts) { for (var i of this.ccontracts) {
if (this.languageService.getDefaultLanguage() == 'es') { const locale = this.lang ? 'es-US' : 'en-US';
this.ccontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'})); this.ccontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace(',', '')).toLocaleDateString(locale, { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'long' }));
} else {
this.ccontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'}));
}
} }
this.cdr.detectChanges();
event.target.complete(); event.target.complete();
}, error => { }, error => {
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']); this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
@@ -69,14 +68,15 @@ export class ContractedPage implements OnInit {
this.ichambaService.getCurrentcontracts().subscribe( this.ichambaService.getCurrentcontracts().subscribe(
data => { data => {
this.ccontracts = data; this.ccontracts = data;
this.ccontracts_dates = [];
for (var i of this.ccontracts) { for (var i of this.ccontracts) {
if (this.languageService.getDefaultLanguage() == 'es') { const locale = this.lang ? 'es-US' : 'en-US';
this.ccontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'})); this.ccontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace(',', '')).toLocaleDateString(locale, { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'long' }));
} else {
this.ccontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'}));
}
} }
this.loading = false;
this.cdr.detectChanges();
}, error => { }, error => {
this.loading = false;
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']); this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
}); });
} }
@@ -90,10 +90,10 @@ export class ContractedPage implements OnInit {
this.presentAlert(id) this.presentAlert(id)
} else { */ } else { */
this.ichambaService.cancelContract(id).subscribe( this.ichambaService.cancelContract(id).subscribe(
data => { (data: any) => {
this.getccontracts(); this.getccontracts();
this.alertService.presentToast(data['message']); this.alertService.presentToast(data['message']);
}, error => { }, (error: any) => {
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']); this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
}); });
//} //}
@@ -114,10 +114,10 @@ export class ContractedPage implements OnInit {
text: 'Confirmar', text: 'Confirmar',
handler: () => { handler: () => {
this.ichambaService.cancelContract(id).subscribe( this.ichambaService.cancelContract(id).subscribe(
data => { (data: any) => {
this.getccontracts(); this.getccontracts();
this.alertService.presentToast(data['message']); this.alertService.presentToast(data['message']);
}, error => { }, (error: any) => {
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']); this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
}); });
} }

View File

@@ -33,6 +33,10 @@ const routes: Routes = [
{ {
path: 'finished', path: 'finished',
loadChildren: () => import('./finished/finished.module').then(m => m.FinishedPageModule) loadChildren: () => import('./finished/finished.module').then(m => m.FinishedPageModule)
},
{
path: 'reported',
loadChildren: () => import('../reports/reports.module').then(m => m.ReportsPageModule)
} }
] ]
} }

View File

@@ -24,5 +24,10 @@
<ion-label>{{'contracts.header_3' | translate}}</ion-label> <ion-label>{{'contracts.header_3' | translate}}</ion-label>
</ion-tab-button> </ion-tab-button>
<ion-tab-button tab="reported">
<ion-icon name="flag"></ion-icon>
<ion-label>{{'contracts.header_4' | translate}}</ion-label>
</ion-tab-button>
</ion-tab-bar> </ion-tab-bar>
</ion-tabs> </ion-tabs>

View File

@@ -10,6 +10,7 @@ import { AlertService } from 'src/app/services/alert.service';
selector: 'app-contracts', selector: 'app-contracts',
templateUrl: './contracts.page.html', templateUrl: './contracts.page.html',
styleUrls: ['./contracts.page.scss'], styleUrls: ['./contracts.page.scss'],
standalone: false
}) })
export class ContractsPage implements OnInit { export class ContractsPage implements OnInit {

View File

@@ -6,8 +6,8 @@
<ion-title>Añadir Fondos</ion-title> <ion-title>Añadir Fondos</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<p text-wrap>Está por añadir un fondo extra al servicio realizado por <b>{{ supplier }}</b>. <p class="ion-text-wrap">Está por añadir un fondo extra al servicio realizado por <b>{{ supplier }}</b>.
Para continuar, seleccione la tarjeta con la que desee pagar el servicio e ingrese el código Para continuar, seleccione la tarjeta con la que desee pagar el servicio e ingrese el código
de seguridad de la tarjeta.</p><br> de seguridad de la tarjeta.</p><br>
<ion-item [class.ng-invalid]="!amount_check"> <ion-item [class.ng-invalid]="!amount_check">

View File

@@ -15,18 +15,19 @@ declare var OpenPay: any;
selector: 'app-extra', selector: 'app-extra',
templateUrl: './extra.page.html', templateUrl: './extra.page.html',
styleUrls: ['./extra.page.scss'], styleUrls: ['./extra.page.scss'],
standalone: false
}) })
export class ExtraPage implements OnInit { export class ExtraPage implements OnInit {
private loading; private loading: HTMLIonLoadingElement | null = null;
amount: number = null; amount: number | null = null;
amount_check = true; amount_check = true;
code_check = true; code_check = true;
cards = []; cards: any[] = [];
card_id = null; card_id: any = null;
supplier = null; supplier: any = null;
cards_menu = null; cards_menu: any = null;
contract_id: number = null; contract_id: number | null = null;
constructor( constructor(
private modalController: ModalController, private modalController: ModalController,
@@ -104,16 +105,16 @@ export class ExtraPage implements OnInit {
this.loading = overlay; this.loading = overlay;
this.loading.present(); this.loading.present();
}); });
this.ichambaService.addExtra(this.contract_id, this.amount, this.card_id, code, device_id).subscribe( this.ichambaService.addExtra(this.contract_id!, this.amount!, this.card_id, code, device_id).subscribe(
data => { (data: any) => {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
if (data['message'] = 'extra_added') { if (data['message'] == 'extra_added') {
this.alertService.presentToast("Fondo extra añadido con éxito"); this.alertService.presentToast("Fondo extra añadido con éxito");
} }
this.events.publish('refreshpcontracts', 'data'); this.events.publish('refreshpcontracts', 'data');
this.navCtrl.navigateRoot('/contracts/contracted'); this.navCtrl.navigateRoot('/contracts/contracted');
}, error => { }, (error: any) => {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']); this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
}); });
//console.log(onSuccess.data.id); //console.log(onSuccess.data.id);

View File

@@ -3,25 +3,43 @@
<ion-buttons slot="start"> <ion-buttons slot="start">
<ion-menu-button></ion-menu-button> <ion-menu-button></ion-menu-button>
</ion-buttons> </ion-buttons>
<ion-title>{{'contract.header' | translate}}</ion-title> <ion-title>{{'contracts.header' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<h2 text-capitalize style="padding-bottom: 0.5em">{{'contracts.header_3' | translate}}</h2> <h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">{{'contracts.header_3' | translate}}</h2>
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)"> <ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
<ion-refresher-content></ion-refresher-content> <ion-refresher-content></ion-refresher-content>
</ion-refresher> </ion-refresher>
<ng-container *ngIf="loading">
<ion-card *ngFor="let _ of [1,2]">
<ion-item style="--border-color: #fff">
<ion-label>
<ion-skeleton-text [animated]="true" style="width: 55%; height: 18px; margin-bottom: 8px"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 85%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 70%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 50%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 40%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 35%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 65%; height: 16px; margin-top: 6px"></ion-skeleton-text>
</ion-label>
</ion-item>
</ion-card>
</ng-container>
<ng-container *ngIf="!loading">
<ng-container *ngFor="let fcontract of fcontracts; let i = index"> <ng-container *ngFor="let fcontract of fcontracts; let i = index">
<ion-card> <ion-card>
<ion-item style="--border-color: #fff"> <ion-item style="--border-color: #fff">
<ion-label> <ion-label>
<h2 text-capitalize *ngIf="lang===true">{{ fcontract.category }}</h2> <h2 class="ion-text-capitalize" *ngIf="lang===true">{{ fcontract.category }}</h2>
<h2 text-capitalize *ngIf="lang===false">{{ fcontract.en_category }}</h2> <h2 class="ion-text-capitalize" *ngIf="lang===false">{{ fcontract.en_category }}</h2>
<h2 text-capitalize *ngIf="fcontract.parent">{{'contracts.parent' | translate}}</h2> <h2 class="ion-text-capitalize" *ngIf="fcontract.parent">{{'contracts.parent' | translate}}</h2>
<p text-wrap>{{ fcontract.address }}</p> <p class="ion-text-wrap">{{ fcontract.address }}</p>
<p text-wrap text-capitalize>{{ fcontracts_dates[i] }}</p> <p class="ion-text-wrap ion-text-capitalize">{{ fcontracts_dates[i] }}</p>
<p text-wrap text-capitalize>{{'contracts.supplier' | translate}}: {{ fcontract.supplier }}</p> <p class="ion-text-wrap ion-text-capitalize">{{'contracts.supplier' | translate}}: {{ fcontract.supplier }}</p>
<p text-wrap text-capitalize>{{'contracts.amount' | translate}}: ${{ fcontract.amount }}</p> <p class="ion-text-wrap ion-text-capitalize">{{'contracts.amount' | translate}}: ${{ fcontract.amount }}</p>
<p>{{'contracts.status' | translate}}: {{ fcontract.status }}</p> <p>{{'contracts.status' | translate}}: {{ fcontract.status }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
@@ -35,4 +53,5 @@
</ion-row> </ion-row>
</ion-card> </ion-card>
</ng-container> </ng-container>
</ng-container>
</ion-content> </ion-content>

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, ChangeDetectorRef } from '@angular/core';
import { ReviewPage } from '../review/review.page'; import { ReviewPage } from '../review/review.page';
import { ModalController, MenuController, NavController } from '@ionic/angular'; import { ModalController, MenuController, NavController } from '@ionic/angular';
import { EventService } from '../../../services/event.service'; import { EventService } from '../../../services/event.service';
@@ -15,12 +15,14 @@ import { AlertController } from '@ionic/angular';
selector: 'app-finished', selector: 'app-finished',
templateUrl: './finished.page.html', templateUrl: './finished.page.html',
styleUrls: ['./finished.page.scss'], styleUrls: ['./finished.page.scss'],
standalone: false
}) })
export class FinishedPage implements OnInit { export class FinishedPage {
fcontracts = []; fcontracts: any[] = [];
fcontracts_dates = []; fcontracts_dates: any[] = [];
lang: boolean; lang: boolean = false;
loading = true;
constructor( constructor(
private modalController: ModalController, private modalController: ModalController,
@@ -36,32 +38,29 @@ export class FinishedPage implements OnInit {
private translateService: TranslateService, private translateService: TranslateService,
private languageService: LanguageService, private languageService: LanguageService,
private env: EnvService, private env: EnvService,
private cdr: ChangeDetectorRef,
) { ) {
this.events.subscribe('refreshccontracts', (data) => { this.events.subscribe('refreshccontracts', (data) => {
this.getfcontracts(); this.getfcontracts();
}); });
} }
ngOnInit() { ionViewWillEnter() {
this.lang = this.languageService.getDefaultLanguage() === 'es';
this.loading = true;
this.getfcontracts(); this.getfcontracts();
if (this.languageService.getDefaultLanguage() == 'es') {
this.lang = true;
} else {
this.lang = false
}
} }
refresh(event) { refresh(event: any) {
this.ichambaService.getFinishedcontracts().subscribe( this.ichambaService.getFinishedcontracts().subscribe(
data => { data => {
this.fcontracts = data; this.fcontracts = data;
this.fcontracts_dates = [];
for (var i of this.fcontracts) { for (var i of this.fcontracts) {
if (this.languageService.getDefaultLanguage() == 'es') { const locale = this.lang ? 'es-US' : 'en-US';
this.fcontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'})); this.fcontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace(',', '')).toLocaleDateString(locale, { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'long' }));
} else {
this.fcontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'}));
}
} }
this.cdr.detectChanges();
event.target.complete(); event.target.complete();
}, error => { }, error => {
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']); this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
@@ -73,14 +72,15 @@ export class FinishedPage implements OnInit {
this.ichambaService.getFinishedcontracts().subscribe( this.ichambaService.getFinishedcontracts().subscribe(
data => { data => {
this.fcontracts = data; this.fcontracts = data;
this.fcontracts_dates = [];
for (var i of this.fcontracts) { for (var i of this.fcontracts) {
if (this.languageService.getDefaultLanguage() == 'es') { const locale = this.lang ? 'es-US' : 'en-US';
this.fcontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'})); this.fcontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace(',', '')).toLocaleDateString(locale, { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'long' }));
} else {
this.fcontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'}));
}
} }
this.loading = false;
this.cdr.detectChanges();
}, error => { }, error => {
this.loading = false;
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']); this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
}); });
} }

View File

@@ -6,31 +6,31 @@
<ion-title>{{'contracts.hire' | translate}}</ion-title> <ion-title>{{'contracts.hire' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<p text-wrap>{{'contracts.hire_info_1' | translate}} <b>{{ supplier }}</b> {{'contracts.hire_info_2' | translate}}<br> <p class="ion-text-wrap">{{'contracts.hire_info_1' | translate}} <b>{{ supplier }}</b> {{'contracts.hire_info_2' | translate}}<br>
{{'contracts.hire_pay' | translate}}</p><br> {{'contracts.hire_pay' | translate}}</p><br>
<ion-row> <ion-row>
<ion-col size="9"> <ion-col size="9">
<p text-left style="margin:0">{{'contracts.fee_amount' | translate}}:</p> <p class="ion-text-start" style="margin:0">{{'contracts.fee_amount' | translate}}:</p>
</ion-col> </ion-col>
<ion-col size="3"> <ion-col size="3">
<p text-right style="margin:0">${{ fee | number: '1.2-2'}}</p> <p class="ion-text-end" style="margin:0">${{ fee | number: '1.2-2'}}</p>
</ion-col> </ion-col>
</ion-row> </ion-row>
<ion-row> <ion-row>
<ion-col size="9"> <ion-col size="9">
<p text-left style="margin:0">{{'contracts.discount_amount' | translate}}:</p> <p class="ion-text-start" style="margin:0">{{'contracts.discount_amount' | translate}}:</p>
</ion-col> </ion-col>
<ion-col size="3"> <ion-col size="3">
<p text-right style="margin:0">${{ discount | number: '1.2-2'}}</p> <p class="ion-text-end" style="margin:0">${{ discount | number: '1.2-2'}}</p>
</ion-col> </ion-col>
</ion-row> </ion-row>
<ion-row> <ion-row>
<ion-col size="9"> <ion-col size="9">
<p text-left style="margin:0"><b>{{'contracts.final_amount' | translate}}:</b></p> <p class="ion-text-start" style="margin:0"><b>{{'contracts.final_amount' | translate}}:</b></p>
</ion-col> </ion-col>
<ion-col size="3"> <ion-col size="3">
<p text-right style="margin:0"><b>${{ final_amount | number: '1.2-2'}}</b></p> <p class="ion-text-end" style="margin:0"><b>${{ final_amount | number: '1.2-2'}}</b></p>
</ion-col> </ion-col>
</ion-row> </ion-row>
<br> <br>
@@ -71,10 +71,10 @@
</ion-row> </ion-row>
<ion-row> <ion-row>
<ion-col size="6"> <ion-col size="6">
<p text-center>{{'cards.openpay_2' | translate}}<p> <p class="ion-text-center">{{'cards.openpay_2' | translate}}<p>
</ion-col> </ion-col>
<ion-col size="6" style="border-left: 1px solid #d0d0d0"> <ion-col size="6" style="border-left: 1px solid #d0d0d0">
<p text-center>{{'cards.openpay_3' | translate}}<p> <p class="ion-text-center">{{'cards.openpay_3' | translate}}<p>
</ion-col> </ion-col>
</ion-row> </ion-row>
<ion-row> <ion-row>

View File

@@ -16,23 +16,24 @@ declare var OpenPay: any;
selector: 'app-hire', selector: 'app-hire',
templateUrl: './hire.page.html', templateUrl: './hire.page.html',
styleUrls: ['./hire.page.scss'], styleUrls: ['./hire.page.scss'],
standalone: false
}) })
export class HirePage implements OnInit { export class HirePage implements OnInit {
private loading; private loading: HTMLIonLoadingElement | null = null;
code_check = true; code_check = true;
cards = []; cards: any[] = [];
code= null; code: any = null;
device_id = null; device_id: any = null;
card_id = null; card_id: any = null;
supplier = null; supplier: any = null;
supplier_id = null; supplier_id: any = null;
postulation_id = null; postulation_id: any = null;
fee = null; fee: any = null;
discount = 0; discount = 0;
final_amount = null; final_amount: any = null;
coupon = null; coupon: any = null;
cards_menu = null; cards_menu: any = null;
paymentBypass = false; paymentBypass = false;
constructor( constructor(
@@ -109,16 +110,16 @@ export class HirePage implements OnInit {
this.loading.present(); this.loading.present();
}); });
this.ichambaService.checkCoupon(this.postulation_id, this.supplier_id, this.coupon).subscribe( this.ichambaService.checkCoupon(this.postulation_id, this.supplier_id, this.coupon).subscribe(
data => { (data: any) => {
if (data) { if (data) {
if (data['name'] == "used") { if (data['name'] == "used") {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.coupon_used')); this.alertService.presentToast(this.translateService.instant('alerts.coupon_used'));
} else if (data['name'] == "expired") { } else if (data['name'] == "expired") {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.coupon_expired')); this.alertService.presentToast(this.translateService.instant('alerts.coupon_expired'));
} else if (data['name'] == this.coupon) { } else if (data['name'] == this.coupon) {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.discount = (data['amount'] + (this.fee * (data['percentage']/100))); this.discount = (data['amount'] + (this.fee * (data['percentage']/100)));
console.log(this.discount); console.log(this.discount);
if (this.discount > this.fee){ if (this.discount > this.fee){
@@ -128,17 +129,17 @@ export class HirePage implements OnInit {
console.log(this.final_amount); console.log(this.final_amount);
this.alertService.presentToast(this.translateService.instant('alerts.coupon_valid')); this.alertService.presentToast(this.translateService.instant('alerts.coupon_valid'));
} else if (data['name'] == "success") { } else if (data['name'] == "success") {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.events.publish('refreshpcontracts', 'data'); this.events.publish('refreshpcontracts', 'data');
this.navCtrl.navigateRoot('/contracts/contracted'); this.navCtrl.navigateRoot('/contracts/contracted');
this.alertService.presentToast(this.translateService.instant('alerts.hire')); this.alertService.presentToast(this.translateService.instant('alerts.hire'));
} }
} else { } else {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.nocoupon')); this.alertService.presentToast(this.translateService.instant('alerts.nocoupon'));
} }
}, error => { }, (error: any) => {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']); this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
}); });
} }
@@ -152,24 +153,24 @@ export class HirePage implements OnInit {
this.loading.present(); this.loading.present();
}); });
this.ichambaService.createContract(this.postulation_id, this.supplier_id, 'BYPASS', this.coupon, '000', 'BYPASS').subscribe( this.ichambaService.createContract(this.postulation_id, this.supplier_id, 'BYPASS', this.coupon, '000', 'BYPASS').subscribe(
data => { (data: any) => {
if (data['type'] == "error") { if (data['type'] == "error") {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(data['message']); this.alertService.presentToast(data['message']);
} else if (data['name'] == "used") { } else if (data['name'] == "used") {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('contracts.coupon_used')); this.alertService.presentToast(this.translateService.instant('contracts.coupon_used'));
} else if (data['name'] == "expired") { } else if (data['name'] == "expired") {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('contracts.coupon_expired')); this.alertService.presentToast(this.translateService.instant('contracts.coupon_expired'));
} else { } else {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.events.publish('refreshpcontracts', 'data'); this.events.publish('refreshpcontracts', 'data');
this.navCtrl.navigateRoot('/contracts/contracted'); this.navCtrl.navigateRoot('/contracts/contracted');
this.alertService.presentToast(this.translateService.instant('alerts.hire')); this.alertService.presentToast(this.translateService.instant('alerts.hire'));
} }
}, error => { }, (error: any) => {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']); this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
}); });
return; return;
@@ -186,24 +187,24 @@ export class HirePage implements OnInit {
this.loading.present(); this.loading.present();
}); });
this.ichambaService.createContract(this.postulation_id, this.supplier_id, this.card_id, this.coupon, code, device_id).subscribe( this.ichambaService.createContract(this.postulation_id, this.supplier_id, this.card_id, this.coupon, code, device_id).subscribe(
data => { (data: any) => {
if (data['type'] == "error") { if (data['type'] == "error") {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(data['message']); this.alertService.presentToast(data['message']);
} else if (data['name'] == "used") { } else if (data['name'] == "used") {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('contracts.coupon_used')); this.alertService.presentToast(this.translateService.instant('contracts.coupon_used'));
} else if (data['name'] == "expired") { } else if (data['name'] == "expired") {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('contracts.coupon_expired')); this.alertService.presentToast(this.translateService.instant('contracts.coupon_expired'));
} else { } else {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.events.publish('refreshpcontracts', 'data'); this.events.publish('refreshpcontracts', 'data');
this.navCtrl.navigateRoot('/contracts/contracted'); this.navCtrl.navigateRoot('/contracts/contracted');
this.alertService.presentToast(this.translateService.instant('alerts.hire')); this.alertService.presentToast(this.translateService.instant('alerts.hire'));
} }
}, error => { }, (error: any) => {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']); this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
}); });
//console.log(onSuccess.data.id); //console.log(onSuccess.data.id);

View File

@@ -12,6 +12,7 @@ import { DomSanitizer } from '@angular/platform-browser';
selector: 'app-nohome', selector: 'app-nohome',
templateUrl: './nohome.page.html', templateUrl: './nohome.page.html',
styleUrls: ['./nohome.page.scss'], styleUrls: ['./nohome.page.scss'],
standalone: false
}) })
export class NohomePage implements OnInit { export class NohomePage implements OnInit {
@@ -81,7 +82,7 @@ export class NohomePage implements OnInit {
send() { send() {
this.ichambaService.noHomeConfirm(this.contract_id, this.myPosition.latitude, this.myPosition.longitude, this.description).subscribe( this.ichambaService.noHomeConfirm(this.contract_id, this.myPosition.latitude, this.myPosition.longitude, this.description).subscribe(
data => { (data: any) => {
this.alertService.presentToast(data['message']); this.alertService.presentToast(data['message']);
this.navCtrl.navigateRoot('/dashboard'); this.navCtrl.navigateRoot('/dashboard');
}, error => { }, error => {

View File

@@ -6,20 +6,39 @@
<ion-title>{{'contracts.header' | translate}}</ion-title> <ion-title>{{'contracts.header' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<h2 text-capitalize style="padding-bottom: 0.5em">{{'contracts.header_1' | translate}}</h2> <h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">{{'contracts.header_1' | translate}}</h2>
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)"> <ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
<ion-refresher-content></ion-refresher-content> <ion-refresher-content></ion-refresher-content>
</ion-refresher> </ion-refresher>
<ng-container *ngIf="loading">
<ion-card *ngFor="let _ of [1,2]">
<ion-item style="--border-color: #fff">
<ion-label>
<ion-skeleton-text [animated]="true" style="width: 55%; height: 18px; margin-bottom: 8px"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 85%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 70%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 50%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 40%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 35%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 65%; height: 16px; margin-top: 6px"></ion-skeleton-text>
</ion-label>
</ion-item>
</ion-card>
</ng-container>
<ng-container *ngIf="!loading">
<ion-card *ngFor="let pcontract of pcontracts; let i = index"> <ion-card *ngFor="let pcontract of pcontracts; let i = index">
<ion-item> <ion-item>
<ion-label> <ion-label>
<h2 *ngIf="lang===true" text-capitalize>{{ pcontract.category }}</h2> <h2 *ngIf="lang===true" class="ion-text-capitalize">{{ pcontract.category }}</h2>
<h2 *ngIf="lang===false" text-capitalize>{{ pcontract.en_category }}</h2> <h2 *ngIf="lang===false" class="ion-text-capitalize">{{ pcontract.en_category }}</h2>
<p text-wrap>{{pcontract.address}}</p> <p class="ion-text-wrap">{{pcontract.address}}</p>
<p text-wrap text-capitalize>{{pcontracts_dates[i]}}</p> <p class="ion-text-wrap ion-text-capitalize">{{pcontracts_dates[i]}}</p>
</ion-label> </ion-label>
<ion-button style="height: 3em; padding-left: 0.5em;" color="secondary" (click)="viewsuppliers(pcontract.id)">{{'contracts.viewsuppliers_1.1' | translate}}<br>{{'contracts.viewsuppliers_1.2' | translate}}</ion-button> <ion-button style="height: 3em; padding-left: 0.5em;" color="secondary" (click)="viewsuppliers(pcontract.id)">{{'contracts.viewsuppliers_1.1' | translate}}<br>{{'contracts.viewsuppliers_1.2' | translate}}</ion-button>
</ion-item> </ion-item>
</ion-card> </ion-card>
</ng-container>
</ion-content> </ion-content>

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, ChangeDetectorRef } from '@angular/core';
import { ModalController, MenuController, NavController } from '@ionic/angular'; import { ModalController, MenuController, NavController } from '@ionic/angular';
import { EventService } from '../../../services/event.service'; import { EventService } from '../../../services/event.service';
import { EnvService } from 'src/app/services/env.service'; import { EnvService } from 'src/app/services/env.service';
@@ -12,12 +12,14 @@ import { AlertService } from 'src/app/services/alert.service';
selector: 'app-pending', selector: 'app-pending',
templateUrl: './pending.page.html', templateUrl: './pending.page.html',
styleUrls: ['./pending.page.scss'], styleUrls: ['./pending.page.scss'],
standalone: false
}) })
export class PendingPage implements OnInit { export class PendingPage {
pcontracts = []; pcontracts: any[] = [];
pcontracts_dates = []; pcontracts_dates: any[] = [];
lang: boolean; lang: boolean = false;
loading = true;
constructor( constructor(
private modalController: ModalController, private modalController: ModalController,
@@ -30,32 +32,29 @@ export class PendingPage implements OnInit {
private translateService: TranslateService, private translateService: TranslateService,
private languageService: LanguageService, private languageService: LanguageService,
private env: EnvService, private env: EnvService,
private cdr: ChangeDetectorRef,
) { ) {
this.events.subscribe('refreshpcontracts', (data) => { this.events.subscribe('refreshpcontracts', (data) => {
this.getpcontracts(); this.getpcontracts();
}); });
} }
ngOnInit() { ionViewWillEnter() {
this.lang = this.languageService.getDefaultLanguage() === 'es';
this.loading = true;
this.getpcontracts(); this.getpcontracts();
if (this.languageService.getDefaultLanguage() == 'es') {
this.lang = true;
} else {
this.lang = false
}
} }
refresh (event) { refresh(event: any) {
this.ichambaService.getPendingcontracts().subscribe( this.ichambaService.getPendingcontracts().subscribe(
data => { data => {
this.pcontracts = data; this.pcontracts = data;
this.pcontracts_dates = [];
for (var i of this.pcontracts) { for (var i of this.pcontracts) {
if (this.languageService.getDefaultLanguage() == 'es') { const locale = this.lang ? 'es-US' : 'en-US';
this.pcontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'})); this.pcontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace(',', '')).toLocaleDateString(locale, { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'long' }));
} else {
this.pcontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'}));
}
} }
this.cdr.detectChanges();
event.target.complete(); event.target.complete();
}, error => { }, error => {
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']); this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
@@ -67,14 +66,15 @@ export class PendingPage implements OnInit {
this.ichambaService.getPendingcontracts().subscribe( this.ichambaService.getPendingcontracts().subscribe(
data => { data => {
this.pcontracts = data; this.pcontracts = data;
this.pcontracts_dates = [];
for (var i of this.pcontracts) { for (var i of this.pcontracts) {
if (this.languageService.getDefaultLanguage() == 'es') { const locale = this.lang ? 'es-US' : 'en-US';
this.pcontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'})); this.pcontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace(',', '')).toLocaleDateString(locale, { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'long' }));
} else {
this.pcontracts_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'}));
}
} }
this.loading = false;
this.cdr.detectChanges();
}, error => { }, error => {
this.loading = false;
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']); this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
}); });
} }

View File

@@ -6,18 +6,18 @@
<ion-title>Contratos</ion-title> <ion-title>Contratos</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<h2 text-capitalize style="padding-bottom: 0.5em">Reagendados</h2> <h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">Reagendados</h2>
<ng-container *ngFor="let ccontract of ccontracts; let i = index"> <ng-container *ngFor="let ccontract of ccontracts; let i = index">
<ion-card *ngIf="ccontract.status==2"> <ion-card *ngIf="ccontract.status==2">
<ion-item> <ion-item>
<ion-label> <ion-label>
<h2 text-capitalize>{{ ccontract.category }}</h2> <h2 class="ion-text-capitalize">{{ ccontract.category }}</h2>
<p text-wrap>{{ ccontract.address }}</p> <p class="ion-text-wrap">{{ ccontract.address }}</p>
<p text-wrap text-capitalize>{{ ccontracts_dates[i] }}</p> <p class="ion-text-wrap ion-text-capitalize">{{ ccontracts_dates[i] }}</p>
<p text-wrap text-capitalize>Proveedor: {{ ccontract.supplier }}</p> <p class="ion-text-wrap ion-text-capitalize">Proveedor: {{ ccontract.supplier }}</p>
<p>Monto: ${{ ccontract.amount }}</p> <p>Monto: ${{ ccontract.amount }}</p>
<p *ngIf="ccontract.time_limit < 10" text-wrap>Código de inicio de servicio: ${{ ccontract.code }} (Por favor, dar este código <p *ngIf="ccontract.time_limit < 10" class="ion-text-wrap">Código de inicio de servicio: ${{ ccontract.code }} (Por favor, dar este código
al proveedor para iniciar el servicio)</p> al proveedor para iniciar el servicio)</p>
</ion-label> </ion-label>
<ion-button style="height: 3em; padding-left: 0.5em;" color="danger">Cancelar<br>contrato</ion-button> <ion-button style="height: 3em; padding-left: 0.5em;" color="danger">Cancelar<br>contrato</ion-button>

View File

@@ -10,11 +10,12 @@ import { AlertService } from 'src/app/services/alert.service';
selector: 'app-redated', selector: 'app-redated',
templateUrl: './redated.page.html', templateUrl: './redated.page.html',
styleUrls: ['./redated.page.scss'], styleUrls: ['./redated.page.scss'],
standalone: false
}) })
export class RedatedPage implements OnInit { export class RedatedPage implements OnInit {
ccontracts = []; ccontracts: any[] = [];
ccontracts_dates = []; ccontracts_dates: any[] = [];
constructor( constructor(
private modalController: ModalController, private modalController: ModalController,

View File

@@ -6,8 +6,8 @@
<ion-title>{{'contracts.report_header' | translate}}</ion-title> <ion-title>{{'contracts.report_header' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<h2 text-center>{{'contracts.report_description' | translate}}</h2> <h2 class="ion-text-center">{{'contracts.report_description' | translate}}</h2>
<ion-item> <ion-item>
<ion-textarea [(ngModel)]="comment">{{'contracts.report_commentaries' | translate}}</ion-textarea> <ion-textarea [(ngModel)]="comment">{{'contracts.report_commentaries' | translate}}</ion-textarea>
</ion-item> </ion-item>

View File

@@ -12,11 +12,12 @@ import { AlertService } from 'src/app/services/alert.service';
selector: 'app-report', selector: 'app-report',
templateUrl: './report.page.html', templateUrl: './report.page.html',
styleUrls: ['./report.page.scss'], styleUrls: ['./report.page.scss'],
standalone: false
}) })
export class ReportPage implements OnInit { export class ReportPage implements OnInit {
private loading; private loading: HTMLIonLoadingElement | null = null;
contract_id: number = null; contract_id: number | null = null;
comment: any; comment: any;
constructor( constructor(
@@ -40,13 +41,13 @@ export class ReportPage implements OnInit {
this.loading = overlay; this.loading = overlay;
this.loading.present(); this.loading.present();
}); });
this.ichambaService.reportContract(this.contract_id, this.comment).subscribe( this.ichambaService.reportContract(this.contract_id!, this.comment).subscribe(
data => { (data: any) => {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.report_send')); this.alertService.presentToast(this.translateService.instant('alerts.report_send'));
this.navCtrl.navigateRoot('/dashboard'); this.navCtrl.navigateRoot('/dashboard');
}, error => { }, (error: any) => {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']); this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
}); });
} else { } else {

View File

@@ -6,8 +6,8 @@
<ion-title>{{'contracts.review_header' | translate}}</ion-title> <ion-title>{{'contracts.review_header' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<h2 text-center>{{'contracts.review_description' | translate}}</h2> <h2 class="ion-text-center">{{'contracts.review_description' | translate}}</h2>
<div class="rate_div"> <div class="rate_div">
<rating [(ngModel)]="rate" <rating [(ngModel)]="rate"
readonly="false" readonly="false"

View File

@@ -11,12 +11,13 @@ import { AlertService } from 'src/app/services/alert.service';
selector: 'app-review', selector: 'app-review',
templateUrl: './review.page.html', templateUrl: './review.page.html',
styleUrls: ['./review.page.scss'], styleUrls: ['./review.page.scss'],
standalone: false
}) })
export class ReviewPage implements OnInit { export class ReviewPage implements OnInit {
private loading; private loading: HTMLIonLoadingElement | null = null;
rate: any; rate: any;
contract_id: number = null; contract_id: number | null = null;
comment: any; comment: any;
constructor( constructor(
@@ -33,7 +34,7 @@ export class ReviewPage implements OnInit {
this.contract_id = Number(this.activatedRoute.snapshot.paramMap.get('contract_id')); this.contract_id = Number(this.activatedRoute.snapshot.paramMap.get('contract_id'));
} }
onRateChange(rating){ onRateChange(rating: any){
console.log("changed rating: " + rating); console.log("changed rating: " + rating);
// do your stuff // do your stuff
} }
@@ -44,13 +45,13 @@ export class ReviewPage implements OnInit {
this.loading = overlay; this.loading = overlay;
this.loading.present(); this.loading.present();
}); });
this.ichambaService.reviewContract(this.contract_id, this.rate, this.comment).subscribe( this.ichambaService.reviewContract(this.contract_id!, this.rate, this.comment).subscribe(
data => { (data: any) => {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(data['message']); this.alertService.presentToast(data['message']);
this.navCtrl.navigateRoot('/dashboard'); this.navCtrl.navigateRoot('/dashboard');
}, error => { }, (error: any) => {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']); this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
}); });
} else { } else {

View File

@@ -18,10 +18,10 @@ ion-input{
<ion-title>{{'contracts.start_header' | translate}}</ion-title> <ion-title>{{'contracts.start_header' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<form #form="ngForm" id="start_form" (ngSubmit)="startservice(form)" method="post"> <form #form="ngForm" id="start_form" (ngSubmit)="startservice(form)" method="post">
<h1 text-center>{{'contracts.start_description_1.1' | translate}}</h1> <h1 class="ion-text-center">{{'contracts.start_description_1.1' | translate}}</h1>
<ion-row text-center> <ion-row class="ion-text-center">
<ion-col> <ion-col>
<ion-input ngModel name="otp1" #otp1 required maxLength="1" type="number" (keyup)="otpController($event,otp2,'')"> <ion-input ngModel name="otp1" #otp1 required maxLength="1" type="number" (keyup)="otpController($event,otp2,'')">
</ion-input> </ion-input>
@@ -37,7 +37,7 @@ ion-input{
</ion-input> </ion-input>
</ion-col> </ion-col>
</ion-row> </ion-row>
<p text-justify>{{'contracts.start_description_1.2' | translate}}</p> <p class="ion-text-justify">{{'contracts.start_description_1.2' | translate}}</p>
<br> <br>
<ion-button type="submit" expand="full" color="primary">{{'contracts.start_send' | translate}}</ion-button> <ion-button type="submit" expand="full" color="primary">{{'contracts.start_send' | translate}}</ion-button>
</form> </form>

View File

@@ -10,11 +10,12 @@ import { AlertService } from 'src/app/services/alert.service';
selector: 'app-start', selector: 'app-start',
templateUrl: './start.page.html', templateUrl: './start.page.html',
styleUrls: ['./start.page.scss'], styleUrls: ['./start.page.scss'],
standalone: false
}) })
export class StartPage implements OnInit { export class StartPage implements OnInit {
private loading; private loading: HTMLIonLoadingElement | null = null;
pin: number = null; pin: number | null = null;
constructor( constructor(
private authService: AuthService, private authService: AuthService,
@@ -28,7 +29,7 @@ export class StartPage implements OnInit {
ngOnInit() { ngOnInit() {
} }
otpController(event,next,prev){ otpController(event: any, next: any, prev: any){
if (event.target.value.length > 1){ if (event.target.value.length > 1){
event.target.value = event.target.value.slice(-1); event.target.value = event.target.value.slice(-1);
next.setFocus(); next.setFocus();
@@ -49,18 +50,18 @@ export class StartPage implements OnInit {
this.loading = overlay; this.loading = overlay;
this.loading.present(); this.loading.present();
}); });
this.ichambaService.startContract(this.pin).subscribe( this.ichambaService.startContract(this.pin!).subscribe(
data => { (data: any) => {
if (data['message'] == "No service") { if (data['message'] == "No service") {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast("No hay ningún servicio agendado a esta hora"); this.alertService.presentToast("No hay ningún servicio agendado a esta hora");
} else { } else {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast(data['message']); this.alertService.presentToast(data['message']);
this.navCtrl.navigateRoot('/dashboard'); this.navCtrl.navigateRoot('/dashboard');
} }
}, error => { }, (error: any) => {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']); this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
}); });
console.log(form.value.otp1 + form.value.otp2 + form.value.otp3 + form.value.otp4 + form.value.otp5 + form.value.otp6) console.log(form.value.otp1 + form.value.otp2 + form.value.otp3 + form.value.otp4 + form.value.otp5 + form.value.otp6)
@@ -72,19 +73,19 @@ export class StartPage implements OnInit {
this.loading.present(); this.loading.present();
}); });
this.ichambaService.noHomeCheck().subscribe( this.ichambaService.noHomeCheck().subscribe(
data => { (data: any) => {
if (data['message'] == "wait") { if (data['message'] == "wait") {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast("Por favor espere a los 10 minutos de tolerancia de la hora acordada"); this.alertService.presentToast("Por favor espere a los 10 minutos de tolerancia de la hora acordada");
} else if (data['message'] == "no-contract") { } else if (data['message'] == "no-contract") {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast("No hay contratos citados a esta hora"); this.alertService.presentToast("No hay contratos citados a esta hora");
} else { } else {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.navCtrl.navigateForward(['/nohome/', data['id']]); this.navCtrl.navigateForward(['/nohome/', data['id']]);
} }
}, error => { }, (error: any) => {
this.loading.dismiss(); if (this.loading) this.loading.dismiss();
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']); this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
} }
); );

View File

@@ -4,6 +4,7 @@ import { Component, OnInit } from '@angular/core';
selector: 'app-comments', selector: 'app-comments',
templateUrl: './comments.page.html', templateUrl: './comments.page.html',
styleUrls: ['./comments.page.scss'], styleUrls: ['./comments.page.scss'],
standalone: false
}) })
export class CommentsPage implements OnInit { export class CommentsPage implements OnInit {

View File

@@ -6,11 +6,11 @@
<ion-title>{{'contracts.viewsuppliers_header' | translate}}</ion-title> <ion-title>{{'contracts.viewsuppliers_header' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<h3 *ngIf="lang===true" text-capitalize>{{ pcontractssuppliers_category[0] }}</h3> <h3 *ngIf="lang===true" class="ion-text-capitalize">{{ pcontractssuppliers_category[0] }}</h3>
<h3 *ngIf="lang===false" text-capitalize>{{ pcontractssuppliers_en_category[0] }}</h3> <h3 *ngIf="lang===false" class="ion-text-capitalize">{{ pcontractssuppliers_en_category[0] }}</h3>
<p text-capitalize>{{ pcontractssuppliers_address[0] }}</p> <p class="ion-text-capitalize">{{ pcontractssuppliers_address[0] }}</p>
<p text-capitalize style="margin-top: -0.6em">{{ pcontractssuppliers_dates[0] }}</p> <p class="ion-text-capitalize" style="margin-top: -0.6em">{{ pcontractssuppliers_dates[0] }}</p>
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)"> <ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
<ion-refresher-content></ion-refresher-content> <ion-refresher-content></ion-refresher-content>
</ion-refresher> </ion-refresher>
@@ -20,11 +20,11 @@
</ion-card-header> </ion-card-header>
<ion-item *ngIf="pcontractsupplier.supplier != null"> <ion-item *ngIf="pcontractsupplier.supplier != null">
<ion-label> <ion-label>
<h2 text-wrap text-capitalize *ngIf="pcontractsupplier.supplier != null">{{ pcontractsupplier.supplier }}</h2> <h2 class="ion-text-wrap ion-text-capitalize" *ngIf="pcontractsupplier.supplier != null">{{ pcontractsupplier.supplier }}</h2>
<p text-wrap *ngIf="pcontractsupplier.supplier != null"><ion-icon name="checkmark-circle" color="secondary" *ngIf="pcontractsupplier.membership != false"></ion-icon> {{ pcontractssuppliers_certified[i] }}</p> <p class="ion-text-wrap" *ngIf="pcontractsupplier.supplier != null"><ion-icon name="checkmark-circle" color="secondary" *ngIf="pcontractsupplier.membership != false"></ion-icon> {{ pcontractssuppliers_certified[i] }}</p>
<p text-wrap *ngIf="pcontractsupplier.supplier != null">{{'contracts.viewsuppliers_tags' | translate}}: {{ pcontractsupplier.tags }}</p> <p class="ion-text-wrap" *ngIf="pcontractsupplier.supplier != null">{{'contracts.viewsuppliers_tags' | translate}}: {{ pcontractsupplier.tags }}</p>
<p text-wrap *ngIf="pcontractsupplier.supplier != null">{{'contracts.viewsuppliers_fee' | translate}}: ${{ pcontractsupplier.fee }}</p> <p class="ion-text-wrap" *ngIf="pcontractsupplier.supplier != null">{{'contracts.viewsuppliers_fee' | translate}}: ${{ pcontractsupplier.fee }}</p>
<p text-wrap *ngIf="pcontractsupplier.supplier != null">{{'contracts.viewsuppliers_rate' | translate}}: {{ pcontractsupplier.score }} de 5 <ion-icon name="star"></ion-icon></p> <p class="ion-text-wrap" *ngIf="pcontractsupplier.supplier != null">{{'contracts.viewsuppliers_rate' | translate}}: {{ pcontractsupplier.score }} de 5 <ion-icon name="star"></ion-icon></p>
</ion-label> </ion-label>
<ion-button style="height: 3em; padding-left: 0.5em;" color="secondary" (click)="hiresupplier(pcontractsupplier.supplier_id, pcontractsupplier.id, pcontractsupplier.supplier, pcontractsupplier.fee)">{{'contracts.viewsuppliers_hire' | translate}}</ion-button> <ion-button style="height: 3em; padding-left: 0.5em;" color="secondary" (click)="hiresupplier(pcontractsupplier.supplier_id, pcontractsupplier.id, pcontractsupplier.supplier, pcontractsupplier.fee)">{{'contracts.viewsuppliers_hire' | translate}}</ion-button>
</ion-item> </ion-item>

View File

@@ -14,19 +14,20 @@ import { HirePage } from '../hire/hire.page';
selector: 'app-viewsuppliers', selector: 'app-viewsuppliers',
templateUrl: './viewsuppliers.page.html', templateUrl: './viewsuppliers.page.html',
styleUrls: ['./viewsuppliers.page.scss'], styleUrls: ['./viewsuppliers.page.scss'],
standalone: false
}) })
export class ViewsuppliersPage implements OnInit { export class ViewsuppliersPage implements OnInit {
postulation_id = null; postulation_id: string | null = null;
pcontractssuppliers = []; pcontractssuppliers: any[] = [];
pcontractssuppliers_dates = []; pcontractssuppliers_dates: any[] = [];
pcontractssuppliers_certified = []; pcontractssuppliers_certified: any[] = [];
pcontractssuppliers_category = []; pcontractssuppliers_category: any[] = [];
pcontractssuppliers_en_category = []; pcontractssuppliers_en_category: any[] = [];
pcontractssuppliers_address = []; pcontractssuppliers_address: any[] = [];
pcontractssuppliers_amount = []; pcontractssuppliers_amount: any[] = [];
pcontractssuppliers_fee = []; pcontractssuppliers_fee: any[] = [];
lang: boolean; lang: boolean = false;
constructor( constructor(
private modalController: ModalController, private modalController: ModalController,
@@ -45,7 +46,7 @@ export class ViewsuppliersPage implements OnInit {
ngOnInit() { ngOnInit() {
this.postulation_id = this.activatedRoute.snapshot.paramMap.get('postulation_id'); this.postulation_id = this.activatedRoute.snapshot.paramMap.get('postulation_id');
this.getPostulants(this.postulation_id); this.getPostulants(this.postulation_id ?? '');
if (this.languageService.getDefaultLanguage() == 'es') { if (this.languageService.getDefaultLanguage() == 'es') {
this.lang = true; this.lang = true;
} else { } else {
@@ -53,9 +54,9 @@ export class ViewsuppliersPage implements OnInit {
} }
} }
refresh(event) { refresh(event: any) {
this.ichambaService.getPostulants(this.postulation_id).subscribe( this.ichambaService.getPostulants(this.postulation_id ?? '').subscribe(
data => { (data: any) => {
this.pcontractssuppliers = data; this.pcontractssuppliers = data;
for (var i of this.pcontractssuppliers) { for (var i of this.pcontractssuppliers) {
if (this.languageService.getDefaultLanguage() == 'es') { if (this.languageService.getDefaultLanguage() == 'es') {
@@ -80,7 +81,7 @@ export class ViewsuppliersPage implements OnInit {
getPostulants(postulation_id: String) { getPostulants(postulation_id: String) {
this.ichambaService.getPostulants(postulation_id).subscribe( this.ichambaService.getPostulants(postulation_id).subscribe(
data => { (data: any) => {
this.pcontractssuppliers = data; this.pcontractssuppliers = data;
for (var i of this.pcontractssuppliers) { for (var i of this.pcontractssuppliers) {
if (this.languageService.getDefaultLanguage() == 'es') { if (this.languageService.getDefaultLanguage() == 'es') {

View File

@@ -26,7 +26,7 @@ ion-item:active:after {
<ion-title>{{'dashboard.header' | translate}}</ion-title> <ion-title>{{'dashboard.header' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<div class="autocomplete"> <div class="autocomplete">
<ion-searchbar <ion-searchbar
(ionInput)="search($event)" (ionInput)="search($event)"
@@ -42,47 +42,47 @@ ion-item:active:after {
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)"> <ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
<ion-refresher-content></ion-refresher-content> <ion-refresher-content></ion-refresher-content>
</ion-refresher> </ion-refresher>
<ng-container *ngIf="user != undefined"> <ng-container *ngIf="authService.isLoggedIn">
<div *ngIf="!isContract"> <div *ngIf="!isContract">
<ion-card style="box-shadow:none"> <ion-card style="box-shadow:none">
<img class="ion-justify-content-center" src="/assets/slides/ic_dashboard.svg" style="max-width:22.5em; margin:auto"/> <img class="ion-justify-content-center" src="/assets/slides/ic_dashboard.svg" style="max-width:22.5em; margin:auto"/>
<ion-text color="dark" class="ion-text-center"><h2>{{'dashboard.slogan' | translate}}</h2></ion-text> <ion-text color="dark" class="ion-text-center"><h2>{{'dashboard.slogan' | translate}}</h2></ion-text>
<h3 class="ion-text-center" color="medium">{{'dashboard.welcome' | translate}} {{ user["name"] }}</h3> <h3 class="ion-text-center" color="medium">{{'dashboard.welcome' | translate}} {{ authService.userName }}</h3>
</ion-card> </ion-card>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="pcontracts?.length > 0"> <ng-container *ngIf="pcontracts?.length > 0">
<h2 text-capitalize style="padding-bottom: 0.5em">{{'contracts.header_1' | translate}}</h2> <h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">{{'contracts.header_1' | translate}}</h2>
</ng-container> </ng-container>
<ion-card *ngFor="let pcontract of pcontracts; let i = index"> <ion-card *ngFor="let pcontract of pcontracts; let i = index">
<ion-item> <ion-item>
<ion-label> <ion-label>
<h2 text-capitalize>{{ pcontract.category }}</h2> <h2 class="ion-text-capitalize">{{ pcontract.category }}</h2>
<ion-progress-bar type="indeterminate"></ion-progress-bar> <ion-progress-bar type="indeterminate"></ion-progress-bar>
<p text-wrap><ion-icon name="search"></ion-icon> {{'contracts.postulating' | translate}}</p> <p class="ion-text-wrap"><ion-icon name="search"></ion-icon> {{'contracts.postulating' | translate}}</p>
<p text-wrap>{{pcontract.address}}</p> <p class="ion-text-wrap">{{pcontract.address}}</p>
<p text-wrap text-capitalize>{{pcontracts_dates[i]}}</p> <p class="ion-text-wrap ion-text-capitalize">{{pcontracts_dates[i]}}</p>
</ion-label> </ion-label>
<ion-button style="height: 3em; padding-left: 0.5em;" color="secondary" (click)="viewsuppliers(pcontract.id)">{{'contracts.viewsuppliers_1.1' | translate}}<br>{{'contracts.viewsuppliers_1.2' | translate}}</ion-button> <ion-button style="height: 3em; padding-left: 0.5em;" color="secondary" (click)="viewsuppliers(pcontract.id)">{{'contracts.viewsuppliers_1.1' | translate}}<br>{{'contracts.viewsuppliers_1.2' | translate}}</ion-button>
</ion-item> </ion-item>
</ion-card> </ion-card>
<ng-container *ngIf="ccontracts?.length > 0"> <ng-container *ngIf="ccontracts?.length > 0">
<h2 text-capitalize style="padding-bottom: 0.5em">{{'contracts.header_2' | translate}}</h2> <h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">{{'contracts.header_2' | translate}}</h2>
</ng-container> </ng-container>
<ng-container *ngFor="let ccontract of ccontracts; let i = index"> <ng-container *ngFor="let ccontract of ccontracts; let i = index">
<ion-card *ngIf="ccontract.status==1"> <ion-card *ngIf="ccontract.status==1">
<ion-item style="--border-color: #fff"> <ion-item style="--border-color: #fff">
<ion-label> <ion-label>
<h2 *ngIf="lang===true" text-capitalize>{{ ccontract.category }}</h2> <h2 *ngIf="lang===true" class="ion-text-capitalize">{{ ccontract.category }}</h2>
<h2 *ngIf="lang===false" text-capitalize>{{ ccontract.en_category }}</h2> <h2 *ngIf="lang===false" class="ion-text-capitalize">{{ ccontract.en_category }}</h2>
<p text-wrap><ion-icon name="briefcase"></ion-icon> {{'contracts.hired' | translate}}</p> <p class="ion-text-wrap"><ion-icon name="briefcase"></ion-icon> {{'contracts.hired' | translate}}</p>
<p text-wrap>{{ ccontract.address }}</p> <p class="ion-text-wrap">{{ ccontract.address }}</p>
<p text-wrap text-capitalize>{{ ccontracts_dates[i] }}</p> <p class="ion-text-wrap ion-text-capitalize">{{ ccontracts_dates[i] }}</p>
<p text-wrap text-capitalize>{{'contracts.supplier' | translate}}: {{ ccontract.supplier }}</p> <p class="ion-text-wrap ion-text-capitalize">{{'contracts.supplier' | translate}}: {{ ccontract.supplier }}</p>
<p text-wrap>{{'contracts.phone' | translate}}: <a href="tel:{{ ccontract.phone }}">{{ ccontract.phone }}</a></p> <p class="ion-text-wrap">{{'contracts.phone' | translate}}: <a href="tel:{{ ccontract.phone }}">{{ ccontract.phone }}</a></p>
<p text-wrap>{{'contracts.amount' | translate}}: ${{ ccontract.amount }}</p> <p class="ion-text-wrap">{{'contracts.amount' | translate}}: ${{ ccontract.amount }}</p>
<h2 text-wrap>{{'contracts.start_pin' | translate}}<b>{{ ccontract.code }}</b></h2> <h2 class="ion-text-wrap">{{'contracts.start_pin' | translate}}<b>{{ ccontract.code }}</b></h2>
<p text-wrap>{{'contracts.info_pin' | translate}}</p> <p class="ion-text-wrap">{{'contracts.info_pin' | translate}}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-button *ngIf="ccontract.past_due < 0" style="height: 4.0em; padding-right: 1.0em; padding-bottom:1.0em; float: right" color="danger" (click)="cancelContract(ccontract.id, ccontract.date)">{{'contracts.cancel_1.1' | translate}}<br>{{'contracts.cancel_1.2' | translate}}</ion-button> <ion-button *ngIf="ccontract.past_due < 0" style="height: 4.0em; padding-right: 1.0em; padding-bottom:1.0em; float: right" color="danger" (click)="cancelContract(ccontract.id, ccontract.date)">{{'contracts.cancel_1.1' | translate}}<br>{{'contracts.cancel_1.2' | translate}}</ion-button>

View File

@@ -13,11 +13,13 @@ import { User } from 'src/app/models/user';
selector: 'app-dashboard', selector: 'app-dashboard',
templateUrl: './dashboard.page.html', templateUrl: './dashboard.page.html',
styleUrls: ['./dashboard.page.scss'], styleUrls: ['./dashboard.page.scss'],
standalone: false
}) })
export class DashboardPage implements OnInit { export class DashboardPage implements OnInit {
private loading: any; private loading: any;
user!: User; user!: User;
userName: string = '';
categories: any[] = []; categories: any[] = [];
aux_categories: any[] = []; aux_categories: any[] = [];
showList: boolean = false; showList: boolean = false;
@@ -31,7 +33,7 @@ export class DashboardPage implements OnInit {
constructor( constructor(
private menu: MenuController, private menu: MenuController,
private navCtrl: NavController, private navCtrl: NavController,
private authService: AuthService, public authService: AuthService,
private ichambaService: IchambaService, private ichambaService: IchambaService,
private alertService: AlertService, private alertService: AlertService,
private translateService: TranslateService, private translateService: TranslateService,
@@ -62,7 +64,7 @@ export class DashboardPage implements OnInit {
this.getccontracts(); this.getccontracts();
} }
refresh(event) { refresh(event: any) {
this.getpcontracts(); this.getpcontracts();
this.ichambaService.getCurrentcontracts().subscribe( this.ichambaService.getCurrentcontracts().subscribe(
data => { data => {
@@ -144,10 +146,10 @@ export class DashboardPage implements OnInit {
this.presentAlert(id) this.presentAlert(id)
} else { */ } else { */
this.ichambaService.cancelContract(id).subscribe( this.ichambaService.cancelContract(id).subscribe(
data => { (data: any) => {
this.getccontracts(); this.getccontracts();
this.alertService.presentToast(data['message']); this.alertService.presentToast(data['message']);
}, error => { }, (error: any) => {
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']); this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
}); });
//} //}
@@ -188,7 +190,7 @@ check(category_string: String) {
this.navCtrl.navigateForward(['/category/', category_string]) this.navCtrl.navigateForward(['/category/', category_string])
} else { } else {
this.loading.dismiss(); this.loading.dismiss();
this.alertService.presentToast(this.translateService.instant('alerts.error')); this.alertService.presentToast(this.translateService.instant('alerts.no_service'));
} }
}, error => { }, error => {
this.loading.dismiss(); this.loading.dismiss();
@@ -198,12 +200,6 @@ check(category_string: String) {
} }
ionViewWillEnter() { ionViewWillEnter() {
this.authService.user().subscribe( this.authService.user().subscribe({ error: () => {} });
user => {
this.user = user;
// TODO: Send OneSignal tag when Capacitor plugin is configured
// OneSignal.sendTag("iChamba_ID", user['id'].toString());
}
);
} }
} }

View File

@@ -8,7 +8,7 @@
</ion-header> </ion-header>
<ion-content> <ion-content>
<h2 padding text-capitalize style="padding-bottom: 0.25em">{{'faq.header' | translate}}</h2> <h2 class="ion-padding ion-text-capitalize" style="padding-bottom: 0.25em">{{'faq.header' | translate}}</h2>
<ion-accordion-group [multiple]="true" [value]="['first', 'third']"> <ion-accordion-group [multiple]="true" [value]="['first', 'third']">
<ion-accordion> <ion-accordion>
<ion-item slot="header" color="light"> <ion-item slot="header" color="light">
@@ -18,49 +18,49 @@
</ion-accordion> </ion-accordion>
<ion-accordion> <ion-accordion>
<ion-item slot="header" color="light"> <ion-item slot="header" color="light">
<ion-label>{{'faq.faq_2' | translate}}®</ion-label> <ion-label>{{'faq.faq_2' | translate}}</ion-label>
</ion-item> </ion-item>
<div class="ion-padding" slot="content">{{'faq.faq_2.1' | translate}}</div> <div class="ion-padding" slot="content">{{'faq.faq_2.1' | translate}}</div>
</ion-accordion> </ion-accordion>
<ion-accordion> <ion-accordion>
<ion-item slot="header" color="light"> <ion-item slot="header" color="light">
<ion-label>{{'faq.faq_3' | translate}}®</ion-label> <ion-label>{{'faq.faq_3' | translate}}</ion-label>
</ion-item> </ion-item>
<div class="ion-padding" slot="content">{{'faq.faq_3.1' | translate}}</div> <div class="ion-padding" slot="content">{{'faq.faq_3.1' | translate}}</div>
</ion-accordion> </ion-accordion>
<ion-accordion> <ion-accordion>
<ion-item slot="header" color="light"> <ion-item slot="header" color="light">
<ion-label>{{'faq.faq_4' | translate}}®</ion-label> <ion-label>{{'faq.faq_4' | translate}}</ion-label>
</ion-item> </ion-item>
<div class="ion-padding" slot="content">{{'faq.faq_4.1' | translate}}</div> <div class="ion-padding" slot="content">{{'faq.faq_4.1' | translate}}</div>
</ion-accordion> </ion-accordion>
<ion-accordion> <ion-accordion>
<ion-item slot="header" color="light"> <ion-item slot="header" color="light">
<ion-label>{{'faq.faq_5' | translate}}®</ion-label> <ion-label>{{'faq.faq_5' | translate}}</ion-label>
</ion-item> </ion-item>
<div class="ion-padding" slot="content">{{'faq.faq_5.1' | translate}}</div> <div class="ion-padding" slot="content">{{'faq.faq_5.1' | translate}}</div>
</ion-accordion> </ion-accordion>
<ion-accordion> <ion-accordion>
<ion-item slot="header" color="light"> <ion-item slot="header" color="light">
<ion-label>{{'faq.faq_6' | translate}}®</ion-label> <ion-label>{{'faq.faq_6' | translate}}</ion-label>
</ion-item> </ion-item>
<div class="ion-padding" slot="content">{{'faq.faq_6.1' | translate}}</div> <div class="ion-padding" slot="content">{{'faq.faq_6.1' | translate}}</div>
</ion-accordion> </ion-accordion>
<ion-accordion> <ion-accordion>
<ion-item slot="header" color="light"> <ion-item slot="header" color="light">
<ion-label>{{'faq.faq_7' | translate}}®</ion-label> <ion-label>{{'faq.faq_7' | translate}}</ion-label>
</ion-item> </ion-item>
<div class="ion-padding" slot="content">{{'faq.faq_7.1' | translate}}</div> <div class="ion-padding" slot="content">{{'faq.faq_7.1' | translate}}</div>
</ion-accordion> </ion-accordion>
<ion-accordion> <ion-accordion>
<ion-item slot="header" color="light"> <ion-item slot="header" color="light">
<ion-label>{{'faq.faq_8' | translate}}®</ion-label> <ion-label>{{'faq.faq_8' | translate}}</ion-label>
</ion-item> </ion-item>
<div class="ion-padding" slot="content">{{'faq.faq_8.1' | translate}}</div> <div class="ion-padding" slot="content">{{'faq.faq_8.1' | translate}}</div>
</ion-accordion> </ion-accordion>
<ion-accordion> <ion-accordion>
<ion-item slot="header" color="light"> <ion-item slot="header" color="light">
<ion-label>{{'faq.faq_9' | translate}}®</ion-label> <ion-label>{{'faq.faq_9' | translate}}</ion-label>
</ion-item> </ion-item>
<div class="ion-padding" slot="content">{{'faq.faq_9.1' | translate}}</div> <div class="ion-padding" slot="content">{{'faq.faq_9.1' | translate}}</div>
</ion-accordion> </ion-accordion>

View File

@@ -4,6 +4,7 @@ import { Component, OnInit } from '@angular/core';
selector: 'app-faq', selector: 'app-faq',
templateUrl: './faq.page.html', templateUrl: './faq.page.html',
styleUrls: ['./faq.page.scss'], styleUrls: ['./faq.page.scss'],
standalone: false
}) })
export class FaqPage implements OnInit { export class FaqPage implements OnInit {

View File

@@ -6,6 +6,10 @@ ion-footer {
--ion-background-color: #175ccc; --ion-background-color: #175ccc;
} }
ion-footer ion-button::part(native) {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
ion-header ion-toolbar { ion-header ion-toolbar {
--min-height: 60px; --min-height: 60px;
} }

View File

@@ -11,6 +11,7 @@ register();
selector: 'app-landing', selector: 'app-landing',
templateUrl: './landing.page.html', templateUrl: './landing.page.html',
styleUrls: ['./landing.page.scss'], styleUrls: ['./landing.page.scss'],
standalone: false
}) })
export class LandingPage implements OnInit { export class LandingPage implements OnInit {

View File

@@ -6,24 +6,43 @@
<ion-title>Postulaciones</ion-title> <ion-title>Postulaciones</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<h2 text-capitalize style="padding-bottom: 0.5em">Ya contratado</h2> <h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">Ya contratado</h2>
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)"> <ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
<ion-refresher-content></ion-refresher-content> <ion-refresher-content></ion-refresher-content>
</ion-refresher> </ion-refresher>
<ng-container *ngFor="let postulation of postulations; let i = index">
<ion-card> <ng-container *ngIf="loading">
<ion-item> <ion-card *ngFor="let _ of [1,2]">
<ion-item style="--border-color: #fff">
<ion-label> <ion-label>
<h2 text-capitalize>{{ postulation.category }}</h2> <ion-skeleton-text [animated]="true" style="width: 55%; height: 18px; margin-bottom: 8px"></ion-skeleton-text>
<ion-text color="primary"><p text-wrap (click)="openMaps(postulation.lat, postulation.lng)"><b><u>{{postulation.address}}</u></b></p></ion-text> <ion-skeleton-text [animated]="true" style="width: 85%"></ion-skeleton-text>
<p text-wrap>lefono: <a href="tel:{{ postulation.phone }}">{{ postulation.phone }}</a></p> <ion-skeleton-text [animated]="true" style="width: 40%"></ion-skeleton-text>
<p text-wrap>Referencias: {{postulation.references}}</p> <ion-skeleton-text [animated]="true" style="width: 70%"></ion-skeleton-text>
<p text-wrap text-capitalize>{{postulations_dates[i]}}</p> <ion-skeleton-text [animated]="true" style="width: 75%"></ion-skeleton-text>
<p text-wrap>Monto: {{postulation.amount}}</p> <ion-skeleton-text [animated]="true" style="width: 35%"></ion-skeleton-text>
<p text-wrap>Detalles: {{postulation.details}}</p> <ion-skeleton-text [animated]="true" style="width: 80%"></ion-skeleton-text>
</ion-label> </ion-label>
</ion-item> </ion-item>
</ion-card> </ion-card>
</ng-container> </ng-container>
<ng-container *ngIf="!loading">
<ng-container *ngFor="let postulation of postulations; let i = index">
<ion-card>
<ion-item>
<ion-label>
<h2 class="ion-text-capitalize">{{ postulation.category }}</h2>
<ion-text color="primary"><p class="ion-text-wrap" (click)="openMaps(postulation.lat, postulation.lng)"><b><u>{{postulation.address}}</u></b></p></ion-text>
<p class="ion-text-wrap">Télefono: <a href="tel:{{ postulation.phone }}">{{ postulation.phone }}</a></p>
<p class="ion-text-wrap">Referencias: {{postulation.references}}</p>
<p class="ion-text-wrap ion-text-capitalize">{{postulations_dates[i]}}</p>
<p class="ion-text-wrap">Monto: {{postulation.amount}}</p>
<p class="ion-text-wrap">Detalles: {{postulation.details}}</p>
</ion-label>
</ion-item>
</ion-card>
</ng-container>
</ng-container>
</ion-content> </ion-content>

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, ChangeDetectorRef } from '@angular/core';
import { MenuController, NavController } from '@ionic/angular'; import { MenuController, NavController } from '@ionic/angular';
import { EventService } from '../../../services/event.service'; import { EventService } from '../../../services/event.service';
import { EnvService } from 'src/app/services/env.service'; import { EnvService } from 'src/app/services/env.service';
@@ -11,11 +11,13 @@ import { Browser } from '@capacitor/browser';
selector: 'app-already', selector: 'app-already',
templateUrl: './already.page.html', templateUrl: './already.page.html',
styleUrls: ['./already.page.scss'], styleUrls: ['./already.page.scss'],
standalone: false
}) })
export class AlreadyPage implements OnInit { export class AlreadyPage {
postulations: any[] = []; postulations: any[] = [];
postulations_dates: any[] = []; postulations_dates: any[] = [];
loading = true;
constructor( constructor(
private menu: MenuController, private menu: MenuController,
@@ -25,13 +27,15 @@ export class AlreadyPage implements OnInit {
private alertService: AlertService, private alertService: AlertService,
private ichambaService: IchambaService, private ichambaService: IchambaService,
private env: EnvService, private env: EnvService,
private cdr: ChangeDetectorRef,
) { ) {
this.events.subscribe('refreshpostulations', (data) => { this.events.subscribe('refreshpostulations', (data) => {
this.getpostulations(); this.getpostulations();
}); });
} }
ngOnInit() { ionViewWillEnter() {
this.loading = true;
this.getpostulations(); this.getpostulations();
} }
@@ -39,9 +43,11 @@ export class AlreadyPage implements OnInit {
this.ichambaService.getContractedPostulation().subscribe( this.ichambaService.getContractedPostulation().subscribe(
data => { data => {
this.postulations = data; this.postulations = data;
this.postulations_dates = [];
for (var i of this.postulations) { for (var i of this.postulations) {
this.postulations_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'})); this.postulations_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace(',', '')).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'long' }));
} }
this.cdr.detectChanges();
event.target.complete(); event.target.complete();
}, error => { }, error => {
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']); this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
@@ -53,10 +59,14 @@ export class AlreadyPage implements OnInit {
this.ichambaService.getContractedPostulation().subscribe( this.ichambaService.getContractedPostulation().subscribe(
data => { data => {
this.postulations = data; this.postulations = data;
this.postulations_dates = [];
for (var i of this.postulations) { for (var i of this.postulations) {
this.postulations_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'})); this.postulations_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace(',', '')).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'long' }));
} }
this.loading = false;
this.cdr.detectChanges();
}, error => { }, error => {
this.loading = false;
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']); this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
}); });
} }

View File

@@ -6,24 +6,43 @@
<ion-title>Postulaciones</ion-title> <ion-title>Postulaciones</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<h2 text-capitalize style="padding-bottom: 0.5em">Por postularse</h2> <h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">Por postularse</h2>
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)"> <ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
<ion-refresher-content></ion-refresher-content> <ion-refresher-content></ion-refresher-content>
</ion-refresher> </ion-refresher>
<ng-container *ngFor="let postulation of postulations; let i = index">
<ion-card *ngIf="!postulation.already_post"> <ng-container *ngIf="loading">
<ion-item> <ion-card *ngFor="let _ of [1,2]">
<ion-item style="--border-color: #fff">
<ion-label> <ion-label>
<h2 text-capitalize>{{ postulation.category }}</h2> <ion-skeleton-text [animated]="true" style="width: 55%; height: 18px; margin-bottom: 8px"></ion-skeleton-text>
<ion-text color="primary"><p text-wrap (click)="openMaps(postulation.lat, postulation.lng)"><b><u>{{postulation.address}}</u></b></p></ion-text> <ion-skeleton-text [animated]="true" style="width: 85%"></ion-skeleton-text>
<p text-wrap>Referencias: {{postulation.references}}</p> <ion-skeleton-text [animated]="true" style="width: 70%"></ion-skeleton-text>
<p text-wrap text-capitalize>{{postulations_dates[i]}}</p> <ion-skeleton-text [animated]="true" style="width: 75%"></ion-skeleton-text>
<p text-wrap>Detalles: {{postulation.details}}</p> <ion-skeleton-text [animated]="true" style="width: 80%"></ion-skeleton-text>
<p text-wrap>Tiempo restante: {{postulation.time_limit}} minutos</p> <ion-skeleton-text [animated]="true" style="width: 45%"></ion-skeleton-text>
</ion-label> </ion-label>
<ion-button style="height: 3em; padding-left: 0.5em;" color="secondary" (click)="addpostulation(postulation.id)">Postularse</ion-button> <ion-skeleton-text slot="end" [animated]="true" style="width: 72px; height: 36px; border-radius: 4px"></ion-skeleton-text>
</ion-item> </ion-item>
</ion-card> </ion-card>
</ng-container> </ng-container>
<ng-container *ngIf="!loading">
<ng-container *ngFor="let postulation of postulations; let i = index">
<ion-card *ngIf="!postulation.already_post">
<ion-item>
<ion-label>
<h2 class="ion-text-capitalize">{{ postulation.category }}</h2>
<ion-text color="primary"><p class="ion-text-wrap" (click)="openMaps(postulation.lat, postulation.lng)"><b><u>{{postulation.address}}</u></b></p></ion-text>
<p class="ion-text-wrap">Referencias: {{postulation.references}}</p>
<p class="ion-text-wrap ion-text-capitalize">{{postulations_dates[i]}}</p>
<p class="ion-text-wrap">Detalles: {{postulation.details}}</p>
<p class="ion-text-wrap">Tiempo restante: {{postulation.time_limit}} minutos</p>
</ion-label>
<ion-button slot="end" style="height: 3em;" color="secondary" (click)="addpostulation(postulation.id)">Postularse</ion-button>
</ion-item>
</ion-card>
</ng-container>
</ng-container>
</ion-content> </ion-content>

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, ChangeDetectorRef } from '@angular/core';
import { MenuController, NavController } from '@ionic/angular'; import { MenuController, NavController } from '@ionic/angular';
import { EventService } from '../../../services/event.service'; import { EventService } from '../../../services/event.service';
import { EnvService } from 'src/app/services/env.service'; import { EnvService } from 'src/app/services/env.service';
@@ -11,11 +11,13 @@ import { Browser } from '@capacitor/browser';
selector: 'app-current', selector: 'app-current',
templateUrl: './current.page.html', templateUrl: './current.page.html',
styleUrls: ['./current.page.scss'], styleUrls: ['./current.page.scss'],
standalone: false
}) })
export class CurrentPage implements OnInit { export class CurrentPage {
postulations: any[] = []; postulations: any[] = [];
postulations_dates: any[] = []; postulations_dates: any[] = [];
loading = true;
constructor( constructor(
private menu: MenuController, private menu: MenuController,
@@ -25,23 +27,27 @@ export class CurrentPage implements OnInit {
private alertService: AlertService, private alertService: AlertService,
private ichambaService: IchambaService, private ichambaService: IchambaService,
private env: EnvService, private env: EnvService,
private cdr: ChangeDetectorRef,
) { ) {
this.events.subscribe('refreshpostulations', (data) => { this.events.subscribe('refreshpostulations', (data) => {
this.getpostulations(); this.getpostulations();
}); });
} }
ngOnInit() { ionViewWillEnter() {
this.loading = true;
this.getpostulations(); this.getpostulations();
} }
refresh(event) { refresh(event: any) {
this.ichambaService.getPostulation().subscribe( this.ichambaService.getPostulation().subscribe(
data => { data => {
this.postulations = data; this.postulations = data;
this.postulations_dates = [];
for (var i of this.postulations) { for (var i of this.postulations) {
this.postulations_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'})); this.postulations_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace(',', '')).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'long' }));
} }
this.cdr.detectChanges();
event.target.complete(); event.target.complete();
}, error => { }, error => {
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']); this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
@@ -53,20 +59,24 @@ export class CurrentPage implements OnInit {
this.ichambaService.getPostulation().subscribe( this.ichambaService.getPostulation().subscribe(
data => { data => {
this.postulations = data; this.postulations = data;
this.postulations_dates = [];
for (var i of this.postulations) { for (var i of this.postulations) {
this.postulations_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'})); this.postulations_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace(',', '')).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'long' }));
} }
this.loading = false;
this.cdr.detectChanges();
}, error => { }, error => {
this.loading = false;
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']); this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
}); });
} }
addpostulation(id: String) { addpostulation(id: String) {
this.ichambaService.setPostulation(id).subscribe( this.ichambaService.setPostulation(id).subscribe(
data => { (data: any) => {
this.getpostulations(); this.getpostulations();
this.alertService.presentToast(data['message']); this.alertService.presentToast(data['message']);
}, error => { }, (error: any) => {
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']); this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
}); });
} }

View File

@@ -6,22 +6,41 @@
<ion-title>Postulaciones</ion-title> <ion-title>Postulaciones</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<h2 text-capitalize style="padding-bottom: 0.5em">Finalizadas</h2> <h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">Finalizadas</h2>
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)"> <ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
<ion-refresher-content></ion-refresher-content> <ion-refresher-content></ion-refresher-content>
</ion-refresher> </ion-refresher>
<ng-container *ngIf="loading">
<ion-card *ngFor="let _ of [1,2]">
<ion-item style="--border-color: #fff">
<ion-label>
<ion-skeleton-text [animated]="true" style="width: 55%; height: 18px; margin-bottom: 8px"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 85%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 40%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 70%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 75%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 35%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 80%"></ion-skeleton-text>
</ion-label>
</ion-item>
</ion-card>
</ng-container>
<ng-container *ngIf="!loading">
<ng-container *ngFor="let postulation of postulations; let i = index"> <ng-container *ngFor="let postulation of postulations; let i = index">
<ion-card> <ion-card>
<ion-item> <ion-item>
<ion-label> <ion-label>
<h2 text-capitalize>{{ postulation.category }}</h2> <h2 class="ion-text-capitalize">{{ postulation.category }}</h2>
<p text-wrap>{{ postulation.address }}</p> <p class="ion-text-wrap">{{ postulation.address }}</p>
<p text-wrap text-capitalize>{{ postulations_dates[i] }}</p> <p class="ion-text-wrap ion-text-capitalize">{{ postulations_dates[i] }}</p>
<p text-wrap text-capitalize>Estado: {{ postulation.status }}</p> <p class="ion-text-wrap ion-text-capitalize">Estado: {{ postulation.status }}</p>
</ion-label> </ion-label>
<br> <br>
</ion-item> </ion-item>
</ion-card> </ion-card>
</ng-container> </ng-container>
</ng-container>
</ion-content> </ion-content>

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, ChangeDetectorRef } from '@angular/core';
import { ModalController, MenuController, NavController } from '@ionic/angular'; import { ModalController, MenuController, NavController } from '@ionic/angular';
import { EventService } from '../../../services/event.service'; import { EventService } from '../../../services/event.service';
import { EnvService } from 'src/app/services/env.service'; import { EnvService } from 'src/app/services/env.service';
@@ -10,11 +10,13 @@ import { AlertService } from 'src/app/services/alert.service';
selector: 'app-ended', selector: 'app-ended',
templateUrl: './ended.page.html', templateUrl: './ended.page.html',
styleUrls: ['./ended.page.scss'], styleUrls: ['./ended.page.scss'],
standalone: false
}) })
export class EndedPage implements OnInit { export class EndedPage {
postulations = []; postulations: any[] = [];
postulations_dates = []; postulations_dates: any[] = [];
loading = true;
constructor( constructor(
private modalController: ModalController, private modalController: ModalController,
@@ -25,23 +27,27 @@ export class EndedPage implements OnInit {
private alertService: AlertService, private alertService: AlertService,
private ichambaService: IchambaService, private ichambaService: IchambaService,
private env: EnvService, private env: EnvService,
private cdr: ChangeDetectorRef,
) { ) {
this.events.subscribe('refreshpostulations', (data) => { this.events.subscribe('refreshpostulations', (data) => {
this.getfinishedpostulations(); this.getfinishedpostulations();
}); });
} }
ngOnInit() { ionViewWillEnter() {
this.loading = true;
this.getfinishedpostulations(); this.getfinishedpostulations();
} }
refresh(event) { refresh(event: any) {
this.ichambaService.getFinishedPostulation().subscribe( this.ichambaService.getFinishedPostulation().subscribe(
data => { data => {
this.postulations = data; this.postulations = data;
this.postulations_dates = [];
for (var i of this.postulations) { for (var i of this.postulations) {
this.postulations_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'})); this.postulations_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace(',', '')).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'long' }));
} }
this.cdr.detectChanges();
event.target.complete(); event.target.complete();
}, error => { }, error => {
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']); this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
@@ -53,10 +59,14 @@ export class EndedPage implements OnInit {
this.ichambaService.getFinishedPostulation().subscribe( this.ichambaService.getFinishedPostulation().subscribe(
data => { data => {
this.postulations = data; this.postulations = data;
this.postulations_dates = [];
for (var i of this.postulations) { for (var i of this.postulations) {
this.postulations_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace("," ,"")).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute:'numeric', timeZoneName: 'long'})); this.postulations_dates.push(new Date((new Date(i.date).toLocaleString('en-US') + ' UTC').replace(',', '')).toLocaleDateString('es-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'long' }));
} }
this.loading = false;
this.cdr.detectChanges();
}, error => { }, error => {
this.loading = false;
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']); this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
}); });
} }

View File

@@ -28,6 +28,10 @@ const routes: Routes = [
{ {
path: 'ended', path: 'ended',
loadChildren: () => import('./ended/ended.module').then(m => m.EndedPageModule) loadChildren: () => import('./ended/ended.module').then(m => m.EndedPageModule)
},
{
path: 'reported',
loadChildren: () => import('./reported/reported.module').then(m => m.PostulationReportedPageModule)
} }
] ]
} }

View File

@@ -24,5 +24,10 @@
<ion-label>Finalizadas</ion-label> <ion-label>Finalizadas</ion-label>
</ion-tab-button> </ion-tab-button>
<ion-tab-button tab="reported">
<ion-icon name="flag"></ion-icon>
<ion-label>Reportadas</ion-label>
</ion-tab-button>
</ion-tab-bar> </ion-tab-bar>
</ion-tabs> </ion-tabs>

View File

@@ -10,6 +10,7 @@ import { AlertService } from 'src/app/services/alert.service';
selector: 'app-postulations', selector: 'app-postulations',
templateUrl: './postulations.page.html', templateUrl: './postulations.page.html',
styleUrls: ['./postulations.page.scss'], styleUrls: ['./postulations.page.scss'],
standalone: false
}) })
export class PostulationsPage implements OnInit { export class PostulationsPage implements OnInit {

View File

@@ -0,0 +1,26 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Routes, RouterModule } from '@angular/router';
import { IonicModule } from '@ionic/angular';
import { PostulationReportedPage } from './reported.page';
import { ReportDiscussionPageModule } from '../../report-discussion/report-discussion.module';
const routes: Routes = [
{
path: '',
component: PostulationReportedPage
}
];
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
RouterModule.forChild(routes),
ReportDiscussionPageModule
],
declarations: [PostulationReportedPage]
})
export class PostulationReportedPageModule {}

View File

@@ -0,0 +1,53 @@
<ion-header>
<ion-toolbar color="primary">
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>Postulaciones</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">Reportadas</h2>
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
<ion-refresher-content></ion-refresher-content>
</ion-refresher>
<ng-container *ngIf="loading">
<ion-card *ngFor="let _ of [1,2]">
<ion-item style="--border-color: #fff">
<ion-label>
<ion-skeleton-text [animated]="true" style="width: 55%; height: 18px; margin-bottom: 8px"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 50%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 85%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 70%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 35%"></ion-skeleton-text>
</ion-label>
</ion-item>
</ion-card>
</ng-container>
<ng-container *ngIf="!loading">
<p *ngIf="reports.length === 0" class="ion-text-center ion-padding-top">
No tienes postulaciones reportadas
</p>
<ng-container *ngFor="let report of reports; let i = index">
<ion-card>
<ion-item style="--border-color: #fff">
<ion-label>
<h2 class="ion-text-capitalize">{{ report.category }}</h2>
<p class="ion-text-wrap ion-text-capitalize">Cliente: {{ report.client }}</p>
<p class="ion-text-wrap">{{ report.address }}</p>
<p class="ion-text-wrap ion-text-capitalize">{{ reports_dates[i] }}</p>
<p>Monto: ${{ report.amount }}</p>
</ion-label>
<ion-button color="secondary" fill="outline" slot="end" (click)="viewDiscussion(report.id)">
<ion-icon slot="icon-only" name="chatbubbles-outline"></ion-icon>
</ion-button>
</ion-item>
</ion-card>
</ng-container>
</ng-container>
</ion-content>

View File

@@ -0,0 +1,77 @@
import { Component, ChangeDetectorRef } from '@angular/core';
import { ModalController } from '@ionic/angular';
import { IchambaService } from 'src/app/services/ichamba.service';
import { AlertService } from 'src/app/services/alert.service';
import { ReportDiscussionPage } from '../../report-discussion/report-discussion.page';
@Component({
selector: 'app-postulation-reported',
templateUrl: './reported.page.html',
styleUrls: ['./reported.page.scss'],
standalone: false
})
export class PostulationReportedPage {
reports: any[] = [];
reports_dates: string[] = [];
loading = true;
constructor(
private alertService: AlertService,
private ichambaService: IchambaService,
private cdr: ChangeDetectorRef,
private modalCtrl: ModalController,
) { }
ionViewWillEnter() {
this.loading = true;
this.loadReports();
}
refresh(event: any) {
this.ichambaService.getPostulationReports().subscribe({
next: data => {
this.reports = data;
this.buildDates();
this.cdr.detectChanges();
event.target.complete();
},
error: error => {
this.alertService.presentToast('Por favor contacte a soporte técnico, Estatus:' + error['status']);
event.target.complete();
}
});
}
loadReports() {
this.ichambaService.getPostulationReports().subscribe({
next: data => {
this.reports = data;
this.buildDates();
this.loading = false;
this.cdr.detectChanges();
},
error: error => {
this.loading = false;
this.alertService.presentToast('Por favor contacte a soporte técnico, Estatus:' + error['status']);
}
});
}
async viewDiscussion(reportId: any) {
const modal = await this.modalCtrl.create({
component: ReportDiscussionPage,
componentProps: { reportId },
});
await modal.present();
}
private buildDates() {
this.reports_dates = this.reports.map(r =>
new Date((new Date(r.appointment).toLocaleString('en-US') + ' UTC').replace(',', '')).toLocaleDateString('es-US', {
weekday: 'long', year: 'numeric', month: 'short', day: 'numeric',
hour: 'numeric', minute: 'numeric', timeZoneName: 'long'
})
);
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { ReportDiscussionPage } from './report-discussion.page';
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
],
declarations: [ReportDiscussionPage],
exports: [ReportDiscussionPage]
})
export class ReportDiscussionPageModule {}

View File

@@ -0,0 +1,94 @@
<ion-header>
<ion-toolbar color="primary">
<ion-buttons slot="start">
<ion-button (click)="dismiss()">
<ion-icon slot="icon-only" name="arrow-back"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>Discusión del Reporte</ion-title>
</ion-toolbar>
</ion-header>
<ion-content #content>
<div class="messages-container">
<ng-container *ngIf="loading">
<div class="message-wrapper left">
<ion-skeleton-text [animated]="true" style="width: 55%; height: 54px; border-radius: 18px;"></ion-skeleton-text>
</div>
<div class="message-wrapper right">
<ion-skeleton-text [animated]="true" style="width: 42%; height: 40px; border-radius: 18px;"></ion-skeleton-text>
</div>
<div class="message-wrapper center">
<ion-skeleton-text [animated]="true" style="width: 64%; height: 48px; border-radius: 10px;"></ion-skeleton-text>
</div>
<div class="message-wrapper left">
<ion-skeleton-text [animated]="true" style="width: 50%; height: 36px; border-radius: 18px;"></ion-skeleton-text>
</div>
<div class="message-wrapper right">
<ion-skeleton-text [animated]="true" style="width: 60%; height: 60px; border-radius: 18px;"></ion-skeleton-text>
</div>
</ng-container>
<div class="empty-state" *ngIf="!loading && messages.length === 0">
<ion-icon name="chatbubbles-outline" style="font-size: 48px; margin-bottom: 12px;"></ion-icon>
<p>No hay mensajes aún.</p>
</div>
<ng-container *ngIf="!loading">
<ng-container *ngFor="let msg of messages">
<!-- Moderador — centrado -->
<div class="message-wrapper center" *ngIf="msg.sender_type === 'moderator'">
<div class="message-bubble moderator">
<span class="sender-name">Moderador</span>
<p>{{ msg.comment }}</p>
<span class="timestamp">{{ msg.created_at | date:'d MMM, HH:mm' }}</span>
</div>
</div>
<!-- Mensaje propio — derecha -->
<div class="message-wrapper right"
*ngIf="msg.sender_type !== 'moderator' && msg.sender_id === currentUserId">
<div class="message-bubble mine">
<p>{{ msg.comment }}</p>
<span class="timestamp">{{ msg.created_at | date:'d MMM, HH:mm' }}</span>
</div>
</div>
<!-- Mensaje del otro — izquierda -->
<div class="message-wrapper left"
*ngIf="msg.sender_type !== 'moderator' && msg.sender_id !== currentUserId">
<div class="message-bubble other">
<span class="sender-name">{{ msg.sender_name }}</span>
<p>{{ msg.comment }}</p>
<span class="timestamp">{{ msg.created_at | date:'d MMM, HH:mm' }}</span>
</div>
</div>
</ng-container>
</ng-container>
</div>
</ion-content>
<ion-footer>
<div class="input-bar">
<ion-button fill="clear" color="medium" (click)="attachPhoto()">
<ion-icon slot="icon-only" name="camera-outline"></ion-icon>
</ion-button>
<ion-textarea
[(ngModel)]="newMessage"
placeholder="Escribe un comentario..."
rows="1"
autoGrow="true"
></ion-textarea>
<ion-button
fill="clear"
color="primary"
[disabled]="!newMessage.trim() || sending"
(click)="sendMessage()">
<ion-icon slot="icon-only" name="send"></ion-icon>
</ion-button>
</div>
</ion-footer>

View File

@@ -0,0 +1,117 @@
.messages-container {
display: flex;
flex-direction: column;
padding: 16px;
min-height: 100%;
}
.message-wrapper {
display: flex;
margin-bottom: 10px;
&.right { justify-content: flex-end; }
&.left { justify-content: flex-start; }
&.center { justify-content: center; }
}
.message-bubble {
max-width: 72%;
padding: 10px 14px;
border-radius: 18px;
word-break: break-word;
p {
margin: 0 0 4px 0;
font-size: 0.95rem;
line-height: 1.4;
}
.sender-name {
display: block;
font-size: 0.72rem;
font-weight: 600;
margin-bottom: 4px;
opacity: 0.75;
}
.timestamp {
display: block;
font-size: 0.68rem;
margin-top: 4px;
opacity: 0.65;
text-align: right;
}
&.mine {
background: var(--ion-color-primary);
color: #fff;
border-bottom-right-radius: 4px;
}
&.other {
background: var(--ion-color-light);
color: var(--ion-color-dark);
border-bottom-left-radius: 4px;
}
&.moderator {
background: var(--ion-color-medium);
color: #fff;
max-width: 80%;
text-align: center;
border-radius: 10px;
font-style: italic;
.sender-name {
text-align: center;
font-weight: 700;
opacity: 1;
}
.timestamp {
text-align: center;
}
}
}
.empty-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 16px;
color: var(--ion-color-medium);
p {
margin: 0;
font-size: 0.95rem;
}
}
.input-bar {
display: flex;
align-items: flex-end;
padding: 6px 6px;
gap: 2px;
background: var(--ion-background-color, #fff);
border-top: 1px solid var(--ion-color-light-shade);
ion-textarea {
flex: 1;
--padding-start: 14px;
--padding-end: 14px;
--padding-top: 8px;
--padding-bottom: 8px;
background: var(--ion-color-light);
border-radius: 20px;
margin: 0;
max-height: 120px;
}
ion-button {
--padding-start: 6px;
--padding-end: 6px;
min-height: 40px;
}
}

View File

@@ -0,0 +1,80 @@
import { Component, Input, OnInit, ViewChild, ChangeDetectorRef } from '@angular/core';
import { IonContent, ModalController } from '@ionic/angular';
import { IchambaService } from 'src/app/services/ichamba.service';
import { AuthService } from 'src/app/services/auth.service';
import { AlertService } from 'src/app/services/alert.service';
@Component({
selector: 'app-report-discussion',
templateUrl: './report-discussion.page.html',
styleUrls: ['./report-discussion.page.scss'],
standalone: false
})
export class ReportDiscussionPage implements OnInit {
@Input() reportId: any;
@ViewChild('content') content!: IonContent;
messages: any[] = [];
newMessage: string = '';
currentUserId: any;
loading = true;
sending = false;
constructor(
private modalCtrl: ModalController,
private ichambaService: IchambaService,
private authService: AuthService,
private alertService: AlertService,
private cdr: ChangeDetectorRef,
) {}
ngOnInit() {
this.currentUserId = this.authService.userId;
this.loadMessages();
}
loadMessages() {
this.loading = true;
this.ichambaService.getReportDiscussion(this.reportId).subscribe({
next: data => {
this.messages = data;
this.loading = false;
this.cdr.detectChanges();
setTimeout(() => this.content.scrollToBottom(300), 100);
},
error: () => {
this.loading = false;
this.cdr.detectChanges();
}
});
}
sendMessage() {
const text = this.newMessage.trim();
if (!text || this.sending) return;
this.newMessage = '';
this.sending = true;
this.ichambaService.sendReportMessage(this.reportId, text).subscribe({
next: (msg: any) => {
this.messages.push(msg);
this.sending = false;
this.cdr.detectChanges();
setTimeout(() => this.content.scrollToBottom(300), 100);
},
error: () => {
this.sending = false;
this.alertService.presentToast('No se pudo enviar el mensaje.');
}
});
}
attachPhoto() {
// TODO: implementar captura y envío de foto
}
dismiss() {
this.modalCtrl.dismiss();
}
}

View File

@@ -4,8 +4,10 @@ import { FormsModule } from '@angular/forms';
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router';
import { IonicModule } from '@ionic/angular'; import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
import { ReportsPage } from './reports.page'; import { ReportsPage } from './reports.page';
import { ReportDiscussionPageModule } from '../report-discussion/report-discussion.module';
const routes: Routes = [ const routes: Routes = [
{ {
@@ -19,7 +21,9 @@ const routes: Routes = [
CommonModule, CommonModule,
FormsModule, FormsModule,
IonicModule, IonicModule,
RouterModule.forChild(routes) RouterModule.forChild(routes),
TranslateModule,
ReportDiscussionPageModule
], ],
declarations: [ReportsPage] declarations: [ReportsPage]
}) })

View File

@@ -1,9 +1,53 @@
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar color="primary">
<ion-title>reports</ion-title> <ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>{{'contracts.header' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content> <ion-content class="ion-padding">
<h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">{{'contracts.header_4' | translate}}</h2>
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
<ion-refresher-content></ion-refresher-content>
</ion-refresher>
<ng-container *ngIf="loading">
<ion-card *ngFor="let _ of [1,2]">
<ion-item style="--border-color: #fff">
<ion-label>
<ion-skeleton-text [animated]="true" style="width: 55%; height: 18px; margin-bottom: 8px"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 85%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 70%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 50%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 40%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 35%"></ion-skeleton-text>
<ion-skeleton-text [animated]="true" style="width: 65%; height: 16px; margin-top: 6px"></ion-skeleton-text>
</ion-label>
</ion-item>
</ion-card>
</ng-container>
<ng-container *ngIf="!loading">
<ng-container *ngFor="let report of reports; let i = index">
<ion-card>
<ion-item style="--border-color: #fff">
<ion-label>
<h2 class="ion-text-capitalize" *ngIf="lang">{{ report.category }}</h2>
<h2 class="ion-text-capitalize" *ngIf="!lang">{{ report.en_category }}</h2>
<p class="ion-text-wrap ion-text-capitalize">{{'contracts.supplier' | translate}}: {{ report.supplier }}</p>
<p class="ion-text-wrap ion-text-capitalize" *ngIf="report.company">{{'reports.company' | translate}}: {{ report.company }}</p>
<p class="ion-text-wrap">{{ report.address }}</p>
<p class="ion-text-wrap ion-text-capitalize">{{ reports_dates[i] }}</p>
<p>{{'contracts.amount' | translate}}: ${{ report.amount }}</p>
</ion-label>
<ion-button color="secondary" fill="outline" slot="end" size="large" (click)="viewDiscussion(report.id)">
<ion-icon slot="icon-only" name="chatbubbles-outline"></ion-icon>
</ion-button>
</ion-item>
</ion-card>
</ng-container>
</ng-container>
</ion-content> </ion-content>

View File

@@ -1,15 +1,91 @@
import { Component, OnInit } from '@angular/core'; import { Component, ChangeDetectorRef } from '@angular/core';
import { ModalController } from '@ionic/angular';
import { IchambaService } from 'src/app/services/ichamba.service';
import { TranslateService } from '@ngx-translate/core';
import { LanguageService } from 'src/app/services/language.service';
import { AlertService } from 'src/app/services/alert.service';
import { ReportDiscussionPage } from '../report-discussion/report-discussion.page';
@Component({ @Component({
selector: 'app-reports', selector: 'app-reports',
templateUrl: './reports.page.html', templateUrl: './reports.page.html',
styleUrls: ['./reports.page.scss'], styleUrls: ['./reports.page.scss'],
standalone: false
}) })
export class ReportsPage implements OnInit { export class ReportsPage {
constructor() { } reports: any[] = [];
reports_dates: string[] = [];
lang: boolean = false;
loading = true;
ngOnInit() { constructor(
private alertService: AlertService,
private ichambaService: IchambaService,
private translateService: TranslateService,
private languageService: LanguageService,
private cdr: ChangeDetectorRef,
private modalCtrl: ModalController,
) { }
ionViewWillEnter() {
this.lang = this.languageService.getDefaultLanguage() === 'es';
this.loading = true;
this.loadReports();
} }
refresh(event: any) {
this.reports = [];
this.reports_dates = [];
this.ichambaService.getReports().subscribe({
next: data => {
this.reports = data;
this.buildDates();
event.target.complete();
},
error: error => {
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
event.target.complete();
}
});
}
loadReports() {
this.ichambaService.getReports().subscribe({
next: data => {
this.reports = data;
this.buildDates();
this.loading = false;
this.cdr.detectChanges();
},
error: error => {
this.loading = false;
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
}
});
}
async viewDiscussion(reportId: any) {
const modal = await this.modalCtrl.create({
component: ReportDiscussionPage,
componentProps: { reportId },
});
await modal.present();
}
getStatusColor(status: string): string {
if (status === 'resuelto') return 'success';
if (status === 'en revision' || status === 'en revisión') return 'primary';
return 'warning';
}
private buildDates() {
const locale = this.lang ? 'es-US' : 'en-US';
this.reports_dates = this.reports.map(r =>
new Date((new Date(r.appointment).toLocaleString('en-US') + ' UTC').replace(',', '')).toLocaleDateString(locale, {
weekday: 'long', year: 'numeric', month: 'short', day: 'numeric',
hour: 'numeric', minute: 'numeric', timeZoneName: 'long'
})
);
}
} }

View File

@@ -0,0 +1,33 @@
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { NavController } from '@ionic/angular';
import { Preferences } from '@capacitor/preferences';
import { getApps } from 'firebase/app';
import { getAuth, signOut as firebaseSignOut } from 'firebase/auth';
@Injectable({ providedIn: 'root' })
export class AuthInterceptorService implements HttpInterceptor {
constructor(private navCtrl: NavController) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
this.clearAndRedirect();
}
return throwError(() => error);
})
);
}
private clearAndRedirect() {
Preferences.remove({ key: 'token' });
if (getApps().length > 0) {
firebaseSignOut(getAuth()).catch(() => {});
}
this.navCtrl.navigateRoot('/login');
}
}

View File

@@ -1,11 +1,14 @@
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { EventService } from './event.service'; import { EventService } from './event.service';
import { tap } from 'rxjs/operators'; import { of, firstValueFrom } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Preferences } from '@capacitor/preferences'; import { Preferences } from '@capacitor/preferences';
import { EnvService } from './env.service'; import { EnvService } from './env.service';
import { User } from '../models/user'; import { User } from '../models/user';
import { getAuth, signOut as firebaseSignOut } from 'firebase/auth';
import { getApps } from 'firebase/app';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@@ -15,9 +18,13 @@ export class AuthService {
isLoggedIn = false; isLoggedIn = false;
isVerified = false; isVerified = false;
isReported = false; isReported = false;
userName: string = '';
userRole: number = 0;
userId: any = null;
role: any; role: any;
token:any; token: any;
userInfo:any; userInfo: any;
private initPromise: Promise<void> | null = null;
constructor( constructor(
private http: HttpClient, private http: HttpClient,
@@ -26,11 +33,11 @@ export class AuthService {
private router: Router private router: Router
) { } ) { }
login(email: String, password: String) { loginWithFirebase(idToken: string) {
return this.http.post(this.env.API_URL + 'auth/login', return this.http.post(this.env.API_URL + 'auth/firebase',
{email: email, password: password} { firebase_token: idToken }
).pipe( ).pipe(
tap(token => { tap((token: any) => {
if (token['verified'] != null) { if (token['verified'] != null) {
this.isVerified = true; this.isVerified = true;
} }
@@ -38,101 +45,90 @@ export class AuthService {
this.isReported = true; this.isReported = true;
} }
Preferences.set({ key: 'token', value: JSON.stringify(token) }) Preferences.set({ key: 'token', value: JSON.stringify(token) })
.then( .then(() => {
() => { console.log('Token Stored');
console.log('Token Stored'); }).catch(error => console.error('Error storing item', error));
}
).catch(error => console.error('Error storing item', error));
this.token = token; this.token = token;
this.isLoggedIn = true; this.isLoggedIn = true;
this.userRole = token['role'] ?? 0;
this.events.publish('set_role', token['role']); this.events.publish('set_role', token['role']);
return token; return token;
}), }),
); );
} }
login_register_fb(fb_token: String, fb_id: String) { registerWithFirebase(idToken: string, name: string, phone: string, email: string) {
return this.http.post(this.env.API_URL + 'auth/fb',
{access_token: fb_token, social_id: fb_id + "_facebook"}
).pipe(
tap(token => {
if (token['verified'] != null) {
this.isVerified = true;
}
if (token['reported'] == 1) {
this.isReported = true;
}
Preferences.set({ key: 'token', value: JSON.stringify(token) })
.then(
() => {
console.log('Token Stored');
}
).catch(error => console.error('Error storing item', error));
this.token = token;
this.isLoggedIn = true;
this.events.publish('set_role', token['role']);
return token;
}),
);
}
login_register_googlePlus(google_token: String, google_id: String) {
return this.http.post(this.env.API_URL + 'auth/google',
{access_token: google_token}
).pipe(
tap(token => {
if (token['verified'] != null) {
this.isVerified = true;
}
if (token['reported'] == 1) {
this.isReported = true;
}
Preferences.set({ key: 'token', value: JSON.stringify(token) })
.then(
() => {
console.log('Token Stored');
}
).catch(error => console.error('Error storing item', error));
this.token = token;
this.isLoggedIn = true;
this.events.publish('set_role', token['role']);
return token;
}),
);
}
register(name: String, email: String, phone: Number, password: String) {
return this.http.post(this.env.API_URL + 'auth/register', return this.http.post(this.env.API_URL + 'auth/register',
{name: name, email: email, phone: phone, password: password, secret: this.env.SECRET} { firebase_token: idToken, name: name, phone: phone, email: email }
) ).pipe(
tap((token: any) => {
if (token['verified'] != null) {
this.isVerified = true;
}
Preferences.set({ key: 'token', value: JSON.stringify(token) })
.catch(error => console.error('Error storing item', error));
this.token = token;
this.isLoggedIn = true;
this.events.publish('set_role', token['role']);
return token;
})
);
} }
logout() { logout() {
const headers = new HttpHeaders({ this.clearSession();
'Authorization': this.token["token_type"]+" "+this.token["access_token"]
}); const tokenType = this.token?.['token_type'];
return this.http.get(this.env.API_URL + 'auth/logout', { headers: headers }) const accessToken = this.token?.['access_token'];
.pipe( if (!tokenType || !accessToken) {
tap(data => { return of(null);
Preferences.remove({ key: 'token' }); }
this.isLoggedIn = false;
this.isVerified = false; const headers = new HttpHeaders({ 'Authorization': `${tokenType} ${accessToken}` });
delete this.token; return this.http.get(this.env.API_URL + 'auth/logout', { headers }).pipe(
return data; catchError(() => of(null))
}, );
error => { }
Preferences.remove({ key: 'token' });
this.isLoggedIn = false; initialize(): Promise<void> {
this.isVerified = false; if (!this.initPromise) {
}) this.initPromise = this.runInitialize();
) }
return this.initPromise;
}
private async runInitialize(): Promise<void> {
await this.getToken();
if (this.isLoggedIn) {
try {
await firstValueFrom(this.user());
} catch {
this.clearSession();
}
}
}
clearSession() {
this.initPromise = null;
if (getApps().length > 0) {
firebaseSignOut(getAuth()).catch(() => {});
}
Preferences.remove({ key: 'token' });
this.isLoggedIn = false;
this.isVerified = false;
this.userName = '';
this.userRole = 0;
delete this.token;
} }
user() { user() {
if (!this.token?.['token_type'] || !this.token?.['access_token']) {
return of(null);
}
const headers = new HttpHeaders({ const headers = new HttpHeaders({
'Authorization': this.token["token_type"]+" "+this.token["access_token"] 'Authorization': this.token["token_type"]+" "+this.token["access_token"]
}); });
return this.http.get<User>(this.env.API_URL + 'auth/user', { headers: headers }) return this.http.get<User>(this.env.API_URL + 'auth/user', { headers: headers })
.pipe( .pipe(
tap(user => { tap((user: any) => {
if (user['phone_verified_at'] != null) { if (user['phone_verified_at'] != null) {
this.isVerified = true; this.isVerified = true;
} else { } else {
@@ -143,9 +139,11 @@ export class AuthService {
} else { } else {
this.isReported = false; this.isReported = false;
} }
this.userName = user['name'] ?? '';
this.userRole = user['role_id'] ?? 0;
this.userId = user['id'] ?? null;
this.events.publish('set_role', user['role_id']); this.events.publish('set_role', user['role_id']);
console.log("after login:", user); console.log("after login:", user);
return user;
}) })
) )
} }
@@ -177,8 +175,4 @@ export class AuthService {
{ phone: phone_string }, { headers: headers }); { phone: phone_string }, { headers: headers });
} }
forgot(email: String) {
return this.http.post(this.env.API_URL + 'auth/forgot/password',
{ email: email });
}
} }

View File

@@ -4,7 +4,7 @@ import { Injectable } from '@angular/core';
providedIn: 'root' providedIn: 'root'
}) })
export class EnvService { export class EnvService {
API_URL = 'https://jobhero-api.consultoria-as.com/api/'; API_URL = 'http://jobhero.test/api/';
SECRET = 'wBIIKuDbrxNKzQhAUGiZLoaoQ4MichAN3wP2AP7B'; SECRET = 'wBIIKuDbrxNKzQhAUGiZLoaoQ4MichAN3wP2AP7B';
MERCHANT_ID = 'm9k4beuso5az0wjqztvt'; MERCHANT_ID = 'm9k4beuso5az0wjqztvt';
PUBLIC_API_KEY = 'pk_9465179493384689a8d2da9adc825411'; PUBLIC_API_KEY = 'pk_9465179493384689a8d2da9adc825411';

View File

@@ -0,0 +1,63 @@
import { Injectable } from '@angular/core';
import { initializeApp, getApps, FirebaseApp } from 'firebase/app';
import {
getAuth,
Auth,
GoogleAuthProvider,
OAuthProvider,
signInWithPopup,
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
sendPasswordResetEmail,
signOut,
UserCredential
} from 'firebase/auth';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class FirebaseAuthService {
private app: FirebaseApp;
private auth: Auth;
constructor() {
// Avoid re-initializing if Firebase is already initialized
this.app = getApps().length > 0
? getApps()[0]
: initializeApp(environment.firebaseConfig);
this.auth = getAuth(this.app);
}
async signInWithGoogle(): Promise<string> {
const provider = new GoogleAuthProvider();
const credential: UserCredential = await signInWithPopup(this.auth, provider);
return credential.user.getIdToken();
}
async signInWithApple(): Promise<string> {
const provider = new OAuthProvider('apple.com');
provider.addScope('email');
provider.addScope('name');
const credential: UserCredential = await signInWithPopup(this.auth, provider);
return credential.user.getIdToken();
}
async signInWithEmail(email: string, password: string): Promise<string> {
const credential: UserCredential = await signInWithEmailAndPassword(this.auth, email, password);
return credential.user.getIdToken();
}
async registerWithEmail(email: string, password: string): Promise<string> {
const credential: UserCredential = await createUserWithEmailAndPassword(this.auth, email, password);
return credential.user.getIdToken();
}
async sendPasswordReset(email: string): Promise<void> {
return sendPasswordResetEmail(this.auth, email);
}
async signOut(): Promise<void> {
return signOut(this.auth);
}
}

View File

@@ -26,6 +26,10 @@ export class IchambaService {
return this.http.get<any[]>(this.env.API_URL + 'parameters'); return this.http.get<any[]>(this.env.API_URL + 'parameters');
} }
getBanks() {
return this.http.get<any[]>(this.env.API_URL + 'banks');
}
checkCategories(category_string: String) { checkCategories(category_string: String) {
const headers = new HttpHeaders({ const headers = new HttpHeaders({
'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"] 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
@@ -177,6 +181,34 @@ export class IchambaService {
{ contract_id: contract_id, comment: comment }, { headers: headers }); { contract_id: contract_id, comment: comment }, { headers: headers });
} }
getReports() {
const headers = new HttpHeaders({
'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
});
return this.http.get<any[]>(this.env.API_URL + 'contracts/reports', { headers: headers });
}
getPostulationReports() {
const headers = new HttpHeaders({
'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
});
return this.http.get<any[]>(this.env.API_URL + 'postulations/reports', { headers: headers });
}
getReportDiscussion(reportId: any) {
const headers = new HttpHeaders({
'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
});
return this.http.get<any[]>(this.env.API_URL + 'contracts/reports/' + reportId + '/comments', { headers });
}
sendReportMessage(reportId: any, comment: string) {
const headers = new HttpHeaders({
'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
});
return this.http.post<any>(this.env.API_URL + 'contracts/reports/' + reportId + '/comments', { comment: comment }, { headers });
}
noHomeCheck() { noHomeCheck() {
const headers = new HttpHeaders({ const headers = new HttpHeaders({
'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"] 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
@@ -201,11 +233,11 @@ export class IchambaService {
{ contract_id: contract_id, amount: amount, card_id: card_id, code: code, device_id: device_id }, { headers: headers }); { contract_id: contract_id, amount: amount, card_id: card_id, code: code, device_id: device_id }, { headers: headers });
} }
addHero(name: String, categories: String, tags: String, address: String, lat: Number, lng: Number, reference_options: Number, reference: String) { addHero(name: String, rfc: String, categories: String, tags: String, address: String, lat: Number, lng: Number, bank: Number, bank_account: Number, fee: Number, reference_options: Number, reference: String) {
const headers = new HttpHeaders({ const headers = new HttpHeaders({
'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"] 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
}); });
return this.http.post(this.env.API_URL + 'add-hero', return this.http.post(this.env.API_URL + 'add-hero',
{name: name, categories: categories, tags: tags, address: address, lat: lat, lng: lng, reference_options: reference_options, reference: reference}, { headers: headers }); {name: name, rfc: rfc, categories: categories, tags: tags, address: address, lat: lat, lng: lng, bank: bank, bank_account: bank_account, fee: fee, reference_options: reference_options, reference: reference}, { headers: headers });
} }
} }

View File

@@ -11,13 +11,13 @@ export class LanguageService {
) { } ) { }
getDefaultLanguage() { getDefaultLanguage() {
let language = this.translate.getBrowserLang(); let language = this.translate.getBrowserLang() ?? 'es';
this.translate.setDefaultLang(language); this.translate.setDefaultLang(language);
console.log(language); console.log(language);
return language; return language;
} }
setLanguage(setLang) { setLanguage(setLang: string) {
this.translate.use(setLang); this.translate.use(setLang);
} }
} }

View File

@@ -6,7 +6,7 @@
<ion-title>Verificar Telefono</ion-title> <ion-title>Verificar Telefono</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content class="ion-padding">
<form #form="ngForm" (ngSubmit)="verifyMe(form)" method="post"> <form #form="ngForm" (ngSubmit)="verifyMe(form)" method="post">
<ion-button type="submit" expand="full" color="facebook">Verificame</ion-button> <ion-button type="submit" expand="full" color="facebook">Verificame</ion-button>
</form> </form>

View File

@@ -8,7 +8,8 @@ import { AlertService } from 'src/app/services/alert.service';
@Component({ @Component({
selector: 'app-verify', selector: 'app-verify',
templateUrl: 'verify.page.html', templateUrl: 'verify.page.html',
styleUrls: ['verify.page.scss'] styleUrls: ['verify.page.scss'],
standalone: false
}) })
export class VerifyPage implements OnInit { export class VerifyPage implements OnInit {
@@ -28,12 +29,12 @@ export class VerifyPage implements OnInit {
// auth.service.ts // auth.service.ts
verifyMe(form: NgForm) { verifyMe(form: NgForm) {
this.authService.verifyUser(this.phone_string).subscribe( this.authService.verifyUser(this.phone_string).subscribe(
data => { (data: any) => {
console.log(data); console.log(data);
this.authService.isVerified = true this.authService.isVerified = true
this.navCtrl.navigateRoot('/dashboard') this.navCtrl.navigateRoot('/dashboard')
}, },
error => { (error: any) => {
console.log(error); console.log(error);
} }
); );
@@ -44,11 +45,11 @@ export class VerifyPage implements OnInit {
await this.loading.present(); await this.loading.present();
this.authService.logout().subscribe( this.authService.logout().subscribe(
data => { (data: any) => {
this.alertService.presentToast("Sesión finalizada"); this.alertService.presentToast("Sesión finalizada");
// TODO: Clear OneSignal tag when Capacitor plugin is configured // TODO: Clear OneSignal tag when Capacitor plugin is configured
}, },
error => { (error: any) => {
if (this.loading) this.loading.dismiss(); if (this.loading) this.loading.dismiss();
console.log(error); console.log(error);
}, },

View File

@@ -14,7 +14,7 @@
"login": "Log in", "login": "Log in",
"signup": "Sign up", "signup": "Sign up",
"forgot": "Forgot your password?", "forgot": "Forgot your password?",
"fb_login": "Continue with Facebook", "apple_login": "Continue with Apple",
"google_login": "Continue with Google", "google_login": "Continue with Google",
"email": "Email", "email": "Email",
"password": "Password", "password": "Password",
@@ -44,7 +44,7 @@
"dashboard": { "dashboard": {
"header": "Find services", "header": "Find services",
"searchbox_placeholder": "Enter the service you are looking for", "searchbox_placeholder": "Enter the service you are looking for",
"slogan": "Everything begins with a search", "slogan": "It all starts with a search",
"welcome": "Welcome" "welcome": "Welcome"
}, },
@@ -62,6 +62,7 @@
"header_1": "Postulated", "header_1": "Postulated",
"header_2": "Confirmed", "header_2": "Confirmed",
"header_3": "Finished", "header_3": "Finished",
"header_4": "Reported",
"parent": "Extra charge from service: ", "parent": "Extra charge from service: ",
"status": "Status", "status": "Status",
"rate": "Rate", "rate": "Rate",
@@ -73,7 +74,7 @@
"validate": "Validate", "validate": "Validate",
"hire_info_1": "You are about to hire", "hire_info_1": "You are about to hire",
"hire_info_2": "to provide the service you have requested.", "hire_info_2": "to provide the service you have requested.",
"hire_pay": "Please select with which card do you want to pay the service and enter the CVV of the card to continue.", "hire_pay": "Please select which card do you want to pay the service with, and enter the CVV of the card to continue.",
"hire_confirm": "Hire service", "hire_confirm": "Hire service",
"coupon": "Coupon", "coupon": "Coupon",
"no_home": "Not at home", "no_home": "Not at home",
@@ -142,7 +143,12 @@
"keywords_hint": "Separate keywords with commas", "keywords_hint": "Separate keywords with commas",
"address": "Address", "address": "Address",
"discover": "How did you hear about us?", "discover": "How did you hear about us?",
"select": "Select",
"discover_details": "Please specify how you found out about us", "discover_details": "Please specify how you found out about us",
"bank": "Bank",
"bank_account": "Bank account",
"fee": "Minimum fee",
"required": "Field required",
"signup": "Sign up", "signup": "Sign up",
"radio": "Radio", "radio": "Radio",
"tv": "TV", "tv": "TV",
@@ -173,6 +179,12 @@
"faq_9.1": "Phone numbers so you can contact each other and the user names." "faq_9.1": "Phone numbers so you can contact each other and the user names."
}, },
"reports": {
"header": "My Reports",
"company": "Company",
"empty": "You have no reports registered"
},
"alerts": { "alerts": {
"login": "Logged In", "login": "Logged In",
"login_error": "Wrong email or password", "login_error": "Wrong email or password",

View File

@@ -14,7 +14,7 @@
"login": "Iniciar sesión", "login": "Iniciar sesión",
"signup": "Registrate", "signup": "Registrate",
"forgot": "¿Olvidó su contraseña?", "forgot": "¿Olvidó su contraseña?",
"fb_login": "Iniciar sesión con Facebook", "apple_login": "Iniciar sesión con Apple",
"google_login": "Iniciar sesión con Google", "google_login": "Iniciar sesión con Google",
"email": "Correo electrónico", "email": "Correo electrónico",
"password": "Contraseña", "password": "Contraseña",
@@ -62,6 +62,7 @@
"header_1": "Postulados", "header_1": "Postulados",
"header_2": "Confirmados", "header_2": "Confirmados",
"header_3": "Finalizados", "header_3": "Finalizados",
"header_4": "Reportados",
"parent": "Fondo extra del servicio: ", "parent": "Fondo extra del servicio: ",
"status": "Estado", "status": "Estado",
"rate": "Calificar", "rate": "Calificar",
@@ -142,7 +143,12 @@
"keywords_hint": "Separa las palabras clave con comas", "keywords_hint": "Separa las palabras clave con comas",
"address": "Dirección", "address": "Dirección",
"discover": "¿Cómo supiste de nosotros?", "discover": "¿Cómo supiste de nosotros?",
"select": "Seleccionar",
"discover_details": "Específica como supiste de nosotros", "discover_details": "Específica como supiste de nosotros",
"bank": "Banco",
"bank_account": "CLABE Interbancaria",
"fee": "Cuota mínima",
"required": "Campo requerido",
"signup": "Registrarse", "signup": "Registrarse",
"radio": "Radio", "radio": "Radio",
"tv": "TV", "tv": "TV",
@@ -173,6 +179,12 @@
"faq_9.1": "El celular para que puedan contactarse y el nombre de usuario." "faq_9.1": "El celular para que puedan contactarse y el nombre de usuario."
}, },
"reports": {
"header": "Mis Reportes",
"company": "Empresa",
"empty": "No tienes reportes registrados"
},
"alerts": { "alerts": {
"login": "Sesión iniciada", "login": "Sesión iniciada",
"login_error": "Email o contraseña incorrectos", "login_error": "Email o contraseña incorrectos",

View File

@@ -1,3 +1,12 @@
export const environment = { export const environment = {
production: true production: true,
firebaseConfig: {
apiKey: 'AIzaSyAX-O73xSGMAXL6OaQYyTEsZ6ykxPG2ZOo',
authDomain: 'ichamba-1562349005909.firebaseapp.com',
databaseURL: 'https://ichamba-1562349005909.firebaseio.com',
projectId: 'ichamba-1562349005909',
storageBucket: 'ichamba-1562349005909.firebasestorage.app',
messagingSenderId: '679874302148',
appId: '1:679874302148:web:ffb4c82c0f94e412007991'
}
}; };

View File

@@ -1,16 +1,12 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = { export const environment = {
production: false production: false,
firebaseConfig: {
apiKey: 'AIzaSyAX-O73xSGMAXL6OaQYyTEsZ6ykxPG2ZOo',
authDomain: 'ichamba-1562349005909.firebaseapp.com',
databaseURL: 'https://ichamba-1562349005909.firebaseio.com',
projectId: 'ichamba-1562349005909',
storageBucket: 'ichamba-1562349005909.firebasestorage.app',
messagingSenderId: '679874302148',
appId: '1:679874302148:web:ffb4c82c0f94e412007991'
}
}; };
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.

View File

@@ -35,7 +35,7 @@
* will put import in the top of bundle, so user need to create a separate file * will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags * in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js. * into that file, and then add the following code before importing zone.js.
* import './zone-flags.ts'; * import './zone-flags';
* *
* The flags allowed in zone-flags.ts are listed here. * The flags allowed in zone-flags.ts are listed here.
* *
@@ -52,7 +52,7 @@
* *
*/ */
import './zone-flags.ts'; import './zone-flags';
/*************************************************************************************************** /***************************************************************************************************
* Zone JS is required by default for Angular itself. * Zone JS is required by default for Angular itself.

View File

@@ -1,22 +1,24 @@
{ {
"compileOnSave": false, "compileOnSave": false,
"compilerOptions": { "compilerOptions": {
"skipLibCheck": true,
"baseUrl": "./", "baseUrl": "./",
"skipLibCheck": true,
"outDir": "./dist/out-tsc", "outDir": "./dist/out-tsc",
"sourceMap": true, "sourceMap": true,
"declaration": false, "declaration": false,
"module": "esnext", "module": "ES2022",
"moduleResolution": "node", "moduleResolution": "bundler",
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"importHelpers": true, "importHelpers": true,
"target": "es5", "target": "ES2022",
"useDefineForClassFields": false,
"ignoreDeprecations": "6.0",
"typeRoots": [ "typeRoots": [
"node_modules/@types" "node_modules/@types"
], ],
"lib": [ "lib": [
"es2018", "ES2022",
"dom" "dom"
] ]
}, },