Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 48979dca6f | |||
| 9098b126ad | |||
| c677fdcb59 | |||
| aa8b0061c9 | |||
| db0d0001fa | |||
| 0efe318db3 | |||
|
|
9f2a0d62e0 | ||
|
|
22e4585bb9 | ||
| 4afaf69d10 | |||
| 68673baf99 |
@@ -16,8 +16,17 @@ android {
|
||||
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile file('../../jobhero-release.keystore')
|
||||
storePassword 'JobHero2024!'
|
||||
keyAlias 'jobhero'
|
||||
keyPassword 'JobHero2024!'
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ android {
|
||||
|
||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||
dependencies {
|
||||
implementation project(':capacitor-community-facebook-login')
|
||||
implementation project(':capacitor-community-http')
|
||||
implementation project(':capacitor-app')
|
||||
implementation project(':capacitor-browser')
|
||||
@@ -21,9 +20,7 @@ dependencies {
|
||||
implementation project(':capacitor-preferences')
|
||||
implementation project(':capacitor-splash-screen')
|
||||
implementation project(':capacitor-status-bar')
|
||||
implementation project(':codetrix-studio-capacitor-google-auth')
|
||||
implementation "com.onesignal:OneSignal:5.1.38"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10"
|
||||
implementation "com.onesignal:OneSignal:5.9.4"
|
||||
}
|
||||
apply from: "../../node_modules/onesignal-cordova-plugin/build-extras-onesignal.gradle"
|
||||
|
||||
|
||||
12
android/app/src/main/res/values-night/styles.xml
Normal file
12
android/app/src/main/res/values-night/styles.xml
Normal 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>
|
||||
@@ -13,6 +13,8 @@
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:background">@null</item>
|
||||
<item name="android:navigationBarColor">#FFFFFF</item>
|
||||
<item name="android:windowLightNavigationBar">true</item>
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
include ':capacitor-android'
|
||||
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'
|
||||
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'
|
||||
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')
|
||||
|
||||
@@ -177,7 +177,8 @@
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"defaultCollection": "@ionic/angular-toolkit"
|
||||
"defaultCollection": "@ionic/angular-toolkit",
|
||||
"analytics": false
|
||||
},
|
||||
"schematics": {
|
||||
"@ionic/angular-toolkit:component": {
|
||||
|
||||
1
capacitor.config.json
Normal file
1
capacitor.config.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
22092
package-lock.json
generated
22092
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
53
package.json
53
package.json
@@ -12,15 +12,14 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~18.2.14",
|
||||
"@angular/common": "~18.2.14",
|
||||
"@angular/compiler": "~18.2.14",
|
||||
"@angular/core": "~18.2.14",
|
||||
"@angular/forms": "~18.2.14",
|
||||
"@angular/platform-browser": "~18.2.14",
|
||||
"@angular/platform-browser-dynamic": "~18.2.14",
|
||||
"@angular/router": "~18.2.14",
|
||||
"@capacitor-community/facebook-login": "^7.0.1",
|
||||
"@angular/animations": "~22.0.1",
|
||||
"@angular/common": "~22.0.1",
|
||||
"@angular/compiler": "~22.0.1",
|
||||
"@angular/core": "~22.0.1",
|
||||
"@angular/forms": "~22.0.1",
|
||||
"@angular/platform-browser": "~22.0.1",
|
||||
"@angular/platform-browser-dynamic": "~22.0.1",
|
||||
"@angular/router": "~22.0.1",
|
||||
"@capacitor-community/http": "^1.4.1",
|
||||
"@capacitor/android": "^6.0.0",
|
||||
"@capacitor/app": "^6.0.0",
|
||||
@@ -34,40 +33,40 @@
|
||||
"@capacitor/preferences": "^6.0.0",
|
||||
"@capacitor/splash-screen": "^6.0.0",
|
||||
"@capacitor/status-bar": "^6.0.0",
|
||||
"@codetrix-studio/capacitor-google-auth": "^3.4.0-rc.4",
|
||||
"@ionic/angular": "^8.7.17",
|
||||
"@ionic/angular": "^8.8.10",
|
||||
"@ngx-translate/core": "~14.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",
|
||||
"swiper": "^11.2.10",
|
||||
"tslib": "^2.6.0",
|
||||
"zone.js": "~0.14.10"
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~18.2.21",
|
||||
"@angular-eslint/builder": "^21.1.0",
|
||||
"@angular-eslint/eslint-plugin": "^21.1.0",
|
||||
"@angular-eslint/eslint-plugin-template": "^21.1.0",
|
||||
"@angular-eslint/schematics": "^21.1.0",
|
||||
"@angular-eslint/template-parser": "^21.1.0",
|
||||
"@angular/cli": "~18.2.21",
|
||||
"@angular/compiler-cli": "~18.2.14",
|
||||
"@angular/language-service": "~18.2.14",
|
||||
"@angular-devkit/build-angular": "~22.0.1",
|
||||
"@angular-eslint/builder": "^22.0.0",
|
||||
"@angular-eslint/eslint-plugin": "^22.0.0",
|
||||
"@angular-eslint/eslint-plugin-template": "^22.0.0",
|
||||
"@angular-eslint/schematics": "^22.0.0",
|
||||
"@angular-eslint/template-parser": "^22.0.0",
|
||||
"@angular/cli": "~22.0.1",
|
||||
"@angular/compiler-cli": "~22.0.1",
|
||||
"@angular/language-service": "~22.0.1",
|
||||
"@capacitor/cli": "5.2.1",
|
||||
"@ionic/angular-toolkit": "^12.3.0",
|
||||
"@types/jasmine": "~4.3.0",
|
||||
"@types/node": "~18.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.53.0",
|
||||
"@typescript-eslint/parser": "^8.53.0",
|
||||
"eslint": "^8.57.1",
|
||||
"@types/node": "^20.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.61.0",
|
||||
"@typescript-eslint/parser": "^8.61.0",
|
||||
"eslint": "^10.0.0",
|
||||
"jasmine-core": "~4.6.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.0.0",
|
||||
"typescript": "~5.4.5"
|
||||
"typescript": "~6.0.3"
|
||||
},
|
||||
"description": "An Ionic project"
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ const routes: Routes = [
|
||||
{ path: 'home', loadChildren: () => import('./home/home.module').then(m => m.HomePageModule), canActivate: [AuthGuard] },
|
||||
{ path: 'verify', loadChildren: () => import('./verify/verify.module').then(m => m.verifyPageModule), canActivate: [AuthoriginGuard]},
|
||||
{ path: 'category/:category', loadChildren: () => import('./pages/category/category.module').then(m => m.CategoryPageModule), canActivate: [AuthGuard] },
|
||||
{ path: 'addcard', loadChildren: () => import('./pages/card/addcard/addcard.module').then(m => m.AddcardPageModule), canActivate: [AuthGuard] },
|
||||
{ path: 'cards', loadChildren: () => import('./pages/card/cards/cards.module').then(m => m.CardsPageModule), canActivate: [AuthGuard] },
|
||||
{ path: 'postulations', loadChildren: () => import('./pages/postulations/postulations.module').then(m => m.PostulationsPageModule), canActivate: [AuthGuard] },
|
||||
{ path: 'contracts', loadChildren: () => import('./pages/contracts/contracts.module').then(m => m.ContractsPageModule), canActivate: [AuthGuard] },
|
||||
{ path: 'viewsuppliers/:postulation_id', loadChildren: () => import('./pages/contracts/viewsuppliers/viewsuppliers.module').then(m => m.ViewsuppliersPageModule), canActivate: [AuthGuard] },
|
||||
@@ -27,13 +25,13 @@ const routes: Routes = [
|
||||
{ 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: '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: '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: 'hero', loadChildren: () => import('./pages/hero/hero.module').then(m => m.HeroPageModule), canActivate: [AuthGuard] },
|
||||
{ path: 'comments', loadChildren: () => import('./pages/contracts/viewsuppliers/comments/comments.module').then(m => m.CommentsPageModule), canActivate: [AuthGuard] },
|
||||
{ path: 'faq', loadChildren: () => import('./pages/faq/faq.module').then(m => m.FaqPageModule), canActivate: [AuthGuard] },
|
||||
{ path: 'team', loadChildren: () => import('./pages/team/team.module').then(m => m.TeamPageModule), canActivate: [AuthGuard] },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -8,12 +8,46 @@
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-menu-toggle auto-hide="false" *ngFor="let p of appPages">
|
||||
<ion-item [routerDirection]="'root'" [routerLink]="[p.url]">
|
||||
<ion-icon slot="start" [name]="p.icon"></ion-icon>
|
||||
<ion-label>
|
||||
{{p.title}}
|
||||
</ion-label>
|
||||
<ion-menu-toggle auto-hide="false" [class.ion-hide]="userRole === 0">
|
||||
<ion-item [routerDirection]="'root'" [routerLink]="['/dashboard']">
|
||||
<ion-icon slot="start" name="home"></ion-icon>
|
||||
<ion-label>{{'menu.home' | 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-menu-toggle>
|
||||
<ion-menu-toggle auto-hide="false" [class.ion-hide]="userRole !== 2">
|
||||
<ion-item [routerDirection]="'root'" [routerLink]="['/team']">
|
||||
<ion-icon slot="start" name="people"></ion-icon>
|
||||
<ion-label>{{'menu.team' | translate}}</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-item (click)="logout()">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
|
||||
import { Platform, NavController, LoadingController } from '@ionic/angular';
|
||||
import { EventService } from './services/event.service';
|
||||
import { StatusBar, Style } from '@capacitor/status-bar';
|
||||
@@ -13,17 +13,17 @@ import { OneSignalService } from './services/onesignal.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: 'app.component.html'
|
||||
templateUrl: 'app.component.html',
|
||||
standalone: false
|
||||
})
|
||||
export class AppComponent {
|
||||
export class AppComponent implements OnInit {
|
||||
|
||||
private loading: any;
|
||||
|
||||
public appPages: any[] = [];
|
||||
userRole: number = 0;
|
||||
|
||||
constructor(
|
||||
private platform: Platform,
|
||||
private authService: AuthService,
|
||||
public authService: AuthService,
|
||||
private languageService: LanguageService,
|
||||
private translateService: TranslateService,
|
||||
private navCtrl: NavController,
|
||||
@@ -31,75 +31,20 @@ export class AppComponent {
|
||||
private alertService: AlertService,
|
||||
private loadingCtrl: LoadingController,
|
||||
private oneSignalService: OneSignalService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
) {
|
||||
this.initializeApp();
|
||||
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);
|
||||
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() {
|
||||
await this.platform.ready();
|
||||
|
||||
@@ -114,7 +59,6 @@ export class AppComponent {
|
||||
}
|
||||
|
||||
this.languageService.getDefaultLanguage();
|
||||
this.authService.getToken();
|
||||
}
|
||||
|
||||
private async setupOneSignalUser(role: number) {
|
||||
|
||||
@@ -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 { RouteReuseStrategy } from '@angular/router';
|
||||
|
||||
@@ -6,7 +7,8 @@ import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
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 { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
|
||||
@@ -16,6 +18,10 @@ export function createTranslateLoader(http: HttpClient) {
|
||||
return new TranslateHttpLoader(http, 'assets/i18n/', '.json');
|
||||
}
|
||||
|
||||
function initAuth(authService: AuthService): () => Promise<void> {
|
||||
return () => authService.initialize();
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
bootstrap: [AppComponent],
|
||||
@@ -34,6 +40,8 @@ export function createTranslateLoader(http: HttpClient) {
|
||||
],
|
||||
providers: [
|
||||
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptorService, multi: true },
|
||||
{ provide: APP_INITIALIZER, useFactory: initAuth, deps: [AuthService], multi: true },
|
||||
provideHttpClient(withInterceptorsFromDi())
|
||||
]
|
||||
})
|
||||
|
||||
@@ -44,7 +44,8 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
useExisting: forwardRef(() => RatingComponent),
|
||||
multi: true
|
||||
}
|
||||
]
|
||||
],
|
||||
standalone: false
|
||||
})
|
||||
export class RatingComponent implements ControlValueAccessor {
|
||||
@Input() readonly: string = 'false';
|
||||
|
||||
@@ -2,15 +2,12 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [AccordionComponent],
|
||||
declarations: [],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule
|
||||
],
|
||||
exports: [
|
||||
]
|
||||
exports: []
|
||||
})
|
||||
export class SharedComponentsModule { }
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
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';
|
||||
|
||||
@Injectable({
|
||||
@@ -9,34 +7,24 @@ import { AuthService } from '../services/auth.service';
|
||||
})
|
||||
export class AuthGuard implements CanActivate {
|
||||
|
||||
user: User;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private authService: AuthService
|
||||
) {}
|
||||
|
||||
canActivate(
|
||||
next: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
|
||||
state: RouterStateSnapshot): boolean {
|
||||
|
||||
const currentUser = this.authService.isLoggedIn;
|
||||
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) {
|
||||
if (this.authService.isReported) {
|
||||
this.router.navigate(['/landing']);
|
||||
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']);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { AuthService } from '../services/auth.service';
|
||||
})
|
||||
export class AuthoriginGuard implements CanActivate {
|
||||
|
||||
user: User;
|
||||
user!: User;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Component } from '@angular/core';
|
||||
selector: 'app-home',
|
||||
templateUrl: 'home.page.html',
|
||||
styleUrls: ['home.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class HomePage {
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export class User {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
phone_verified_at: string;
|
||||
reported: boolean;
|
||||
id!: number;
|
||||
name!: string;
|
||||
email!: string;
|
||||
phone!: string;
|
||||
phone_verified_at!: string;
|
||||
reported!: boolean;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<ion-title>{{'auth.forgot' | translate}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<p text-wrap>{{'auth.forgot_instructions' | translate}}</p><br>
|
||||
<ion-content class="ion-padding">
|
||||
<p class="ion-text-wrap">{{'auth.forgot_instructions' | translate}}</p><br>
|
||||
<form #form="ngForm" (ngSubmit)="forgot(form)" method="post">
|
||||
<ion-item>
|
||||
<ion-label position="floating">{{'auth.email' | translate}}</ion-label>
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { NavController, LoadingController } from '@ionic/angular';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { AuthService } from 'src/app/services/auth.service';
|
||||
import { AlertService } from 'src/app/services/alert.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { FirebaseAuthService } from 'src/app/services/firebase-auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-forgot',
|
||||
templateUrl: './forgot.page.html',
|
||||
styleUrls: ['./forgot.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class ForgotPage implements OnInit {
|
||||
|
||||
private loading;
|
||||
private loading: HTMLIonLoadingElement | null = null;
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private firebaseAuthService: FirebaseAuthService,
|
||||
private navCtrl: NavController,
|
||||
private alertService: AlertService,
|
||||
private translateService: TranslateService,
|
||||
private loadingCtrl: LoadingController
|
||||
) { }
|
||||
ngOnInit() {
|
||||
}
|
||||
// Dismiss Forgot Modal
|
||||
|
||||
ngOnInit() { }
|
||||
|
||||
dismissForgot() {
|
||||
this.navCtrl.back();
|
||||
}
|
||||
// On Register button tap, dismiss login modal and open register modal
|
||||
|
||||
forgot(form: NgForm) {
|
||||
this.loadingCtrl.create().then((overlay) => {
|
||||
this.loading = overlay;
|
||||
this.loading.present();
|
||||
});
|
||||
this.authService.forgot(form.value.email).subscribe(
|
||||
data => {
|
||||
this.loading.dismiss();
|
||||
async forgot(form: NgForm) {
|
||||
this.loading = await this.loadingCtrl.create();
|
||||
await this.loading.present();
|
||||
|
||||
try {
|
||||
await this.firebaseAuthService.sendPasswordReset(form.value.email);
|
||||
if (this.loading) 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'));
|
||||
},
|
||||
error => {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['message']);
|
||||
console.log(error);
|
||||
},
|
||||
() => {
|
||||
this.dismissForgot();
|
||||
} else {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + e.message);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<ion-title>{{'auth.login' | translate}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<ion-content class="ion-padding">
|
||||
<form #form="ngForm" (ngSubmit)="login(form)" method="post">
|
||||
<ion-item>
|
||||
<ion-label position="floating">{{'auth.email' | translate}}</ion-label>
|
||||
@@ -22,11 +22,27 @@
|
||||
<br>
|
||||
<ion-button type="submit" expand="full" color="primary">{{'auth.login' | translate}}</ion-button>
|
||||
</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>
|
||||
<ion-button expand="full" color="facebook" (click)="loginFacebook()">{{'auth.fb_login' | translate}}</ion-button>
|
||||
<ion-button expand="full" color="danger" (click)="loginGoogle()">{{'auth.google_login' | translate}}</ion-button>
|
||||
<p text-center>{{'auth.signup_ad' | translate}}</p>
|
||||
<!-- Google Sign-In — branding oficial: fondo blanco, logo G coloreado, borde gris -->
|
||||
<button class="btn-social btn-google" (click)="loginGoogle()">
|
||||
<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-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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,22 +4,24 @@ import { NgForm } from '@angular/forms';
|
||||
import { AuthService } from 'src/app/services/auth.service';
|
||||
import { AlertService } from 'src/app/services/alert.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { FacebookLogin, FacebookLoginResponse } from '@capacitor-community/facebook-login';
|
||||
import { GoogleAuth } from '@codetrix-studio/capacitor-google-auth';
|
||||
import { FirebaseAuthService } from 'src/app/services/firebase-auth.service';
|
||||
import { Browser } from '@capacitor/browser';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.page.html',
|
||||
styleUrls: ['./login.page.scss']
|
||||
styleUrls: ['./login.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class LoginPage implements OnInit {
|
||||
|
||||
private loading: any;
|
||||
private loading: HTMLIonLoadingElement | null = null;
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private firebaseAuthService: FirebaseAuthService,
|
||||
private navCtrl: NavController,
|
||||
private alertService: AlertService,
|
||||
private translateService: TranslateService,
|
||||
@@ -45,68 +47,73 @@ export class LoginPage implements OnInit {
|
||||
this.loading = await this.loadingCtrl.create();
|
||||
await this.loading.present();
|
||||
|
||||
this.authService.login(form.value.email, form.value.password).subscribe(
|
||||
data => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
},
|
||||
error => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
if (JSON.stringify(error['status']) == '401') {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.login_error'));
|
||||
}
|
||||
else {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['message']);
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.dismissLogin();
|
||||
this.navCtrl.navigateRoot('/dashboard');
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.login'));
|
||||
}
|
||||
);
|
||||
try {
|
||||
const idToken = await this.firebaseAuthService.signInWithEmail(
|
||||
form.value.email,
|
||||
form.value.password
|
||||
);
|
||||
|
||||
this.authService.loginWithFirebase(idToken).subscribe(
|
||||
(data: any) => {},
|
||||
(error: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
if (JSON.stringify(error['status']) == '401') {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.login_error'));
|
||||
} else {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['message']);
|
||||
}
|
||||
},
|
||||
() => { this.navigateAfterLogin(); }
|
||||
);
|
||||
} catch (e: any) {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
const msg = this.getFirebaseErrorMessage(e.code);
|
||||
this.alertService.presentToast(msg);
|
||||
}
|
||||
}
|
||||
|
||||
async loginFacebook() {
|
||||
this.loading = await this.loadingCtrl.create();
|
||||
await this.loading.present();
|
||||
private async navigateAfterLogin() {
|
||||
try {
|
||||
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 {
|
||||
// Login with permissions
|
||||
const result = await FacebookLogin.login({ permissions: ['public_profile', 'email'] });
|
||||
private getFirebaseErrorMessage(code: string): string {
|
||||
switch (code) {
|
||||
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) {
|
||||
// Get user ID and Token
|
||||
const fb_id = result.accessToken.userId;
|
||||
const fb_token = result.accessToken.token;
|
||||
async loginApple() {
|
||||
this.loading = await this.loadingCtrl.create();
|
||||
await this.loading.present();
|
||||
|
||||
console.log("ID: " + fb_id);
|
||||
console.log("Token: " + fb_token);
|
||||
try {
|
||||
const idToken = await this.firebaseAuthService.signInWithApple();
|
||||
|
||||
this.authService.login_register_fb(fb_token, fb_id).subscribe(
|
||||
data => {
|
||||
},
|
||||
error => {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error);
|
||||
console.log(error);
|
||||
},
|
||||
() => {
|
||||
this.loading.dismiss();
|
||||
this.dismissLogin();
|
||||
this.navCtrl.navigateRoot('/dashboard');
|
||||
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);
|
||||
}
|
||||
this.authService.loginWithFirebase(idToken).subscribe(
|
||||
(data: any) => {},
|
||||
(error: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error);
|
||||
console.log(error);
|
||||
},
|
||||
() => { this.navigateAfterLogin(); }
|
||||
);
|
||||
} catch (e) {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + e);
|
||||
}
|
||||
}
|
||||
|
||||
async loginGoogle() {
|
||||
@@ -114,32 +121,19 @@ export class LoginPage implements OnInit {
|
||||
await this.loading.present();
|
||||
|
||||
try {
|
||||
const res = await GoogleAuth.signIn();
|
||||
console.log(res);
|
||||
const idToken = await this.firebaseAuthService.signInWithGoogle();
|
||||
|
||||
const google_id = res.id;
|
||||
const google_token = res.authentication.accessToken;
|
||||
|
||||
console.log("ID: " + google_id);
|
||||
console.log("Token: " + google_token);
|
||||
|
||||
this.authService.login_register_googlePlus(google_token, google_id).subscribe(
|
||||
data => {
|
||||
},
|
||||
error => {
|
||||
this.loading.dismiss();
|
||||
this.authService.loginWithFirebase(idToken).subscribe(
|
||||
(data: any) => {},
|
||||
(error: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error);
|
||||
console.log(error);
|
||||
},
|
||||
() => {
|
||||
this.loading.dismiss();
|
||||
this.dismissLogin();
|
||||
this.navCtrl.navigateRoot('/dashboard');
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.login'));
|
||||
}
|
||||
() => { this.navigateAfterLogin(); }
|
||||
);
|
||||
} catch (err) {
|
||||
this.loading.dismiss();
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + err);
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<ion-title>{{'auth.signup' | translate}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<ion-content class="ion-padding">
|
||||
<form #form="ngForm" (ngSubmit)="register(form)" method="post">
|
||||
<ion-item>
|
||||
<ion-label position="floating">{{'auth.name' | translate}}</ion-label>
|
||||
@@ -37,7 +37,7 @@
|
||||
<br><br>
|
||||
<ion-row>
|
||||
<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 size="2.5">
|
||||
<ion-item lines="none">
|
||||
@@ -48,6 +48,6 @@
|
||||
<br>
|
||||
<ion-button type="submit" expand="full" color="secondary">{{'auth.signup' | translate}}</ion-button>
|
||||
</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-content>
|
||||
|
||||
@@ -4,85 +4,93 @@ import { AuthService } from 'src/app/services/auth.service';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { AlertService } from 'src/app/services/alert.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { FirebaseAuthService } from 'src/app/services/firebase-auth.service';
|
||||
import { Browser } from '@capacitor/browser';
|
||||
|
||||
@Component({
|
||||
selector: 'app-register',
|
||||
templateUrl: './register.page.html',
|
||||
styleUrls: ['./register.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class RegisterPage implements OnInit {
|
||||
|
||||
loading: any = null;
|
||||
loading: HTMLIonLoadingElement | null = null;
|
||||
terms = false;
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private firebaseAuthService: FirebaseAuthService,
|
||||
private translateService: TranslateService,
|
||||
private navCtrl: NavController,
|
||||
private loadingCtrl: LoadingController,
|
||||
private alertService: AlertService
|
||||
) { }
|
||||
ngOnInit() {
|
||||
}
|
||||
// Dismiss Register Modal
|
||||
|
||||
ngOnInit() { }
|
||||
|
||||
dismissRegister() {
|
||||
this.navCtrl.navigateRoot('/landing');
|
||||
}
|
||||
// On Login button tap, dismiss Register modal and open login Modal
|
||||
|
||||
loginModal() {
|
||||
this.navCtrl.navigateForward('/login');
|
||||
}
|
||||
|
||||
updateChecked () {
|
||||
if (this.terms == true) {
|
||||
this.terms = false;
|
||||
console.log(this.terms);
|
||||
} else {
|
||||
this.terms = true;
|
||||
console.log(this.terms);
|
||||
}
|
||||
updateChecked() {
|
||||
this.terms = !this.terms;
|
||||
}
|
||||
|
||||
register(form: NgForm) {
|
||||
|
||||
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 {
|
||||
async register(form: NgForm) {
|
||||
if (!this.terms) {
|
||||
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() {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<ion-title>{{'cards.add' | translate}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<ion-content class="ion-padding">
|
||||
<form #form="ngForm" id="card_form" (ngSubmit)="addcard(form)" method="post">
|
||||
|
||||
<ion-item [class.ng-invalid]="!holder_check">
|
||||
@@ -52,10 +52,10 @@
|
||||
</ion-row>
|
||||
<ion-row>
|
||||
<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 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-row>
|
||||
<ion-row>
|
||||
|
||||
@@ -14,22 +14,23 @@ declare var OpenPay: any;
|
||||
selector: 'app-addcard',
|
||||
templateUrl: './addcard.page.html',
|
||||
styleUrls: ['./addcard.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class AddcardPage implements OnInit {
|
||||
|
||||
private loading;
|
||||
private loading: HTMLIonLoadingElement | null = null;
|
||||
card_check = true;
|
||||
date_check = true;
|
||||
cvv_check = true;
|
||||
holder_check = null;
|
||||
line1_check = null;
|
||||
line2_check = null;
|
||||
line3_check = null;
|
||||
postal_code_check = null;
|
||||
city_check = null;
|
||||
state_check = null;
|
||||
month_number = null;
|
||||
year_number = null;
|
||||
holder_check: boolean | null = null;
|
||||
line1_check: boolean | null = null;
|
||||
line2_check: boolean | null = null;
|
||||
line3_check: boolean | null = null;
|
||||
postal_code_check: boolean | null = null;
|
||||
city_check: boolean | null = null;
|
||||
state_check: boolean | null = null;
|
||||
month_number: any = null;
|
||||
year_number: any = null;
|
||||
|
||||
constructor(
|
||||
private modalController: ModalController,
|
||||
@@ -147,28 +148,28 @@ export class AddcardPage implements OnInit {
|
||||
"country_code":"MX"
|
||||
}*/
|
||||
},
|
||||
onSuccess => {
|
||||
(onSuccess: any) => {
|
||||
let token = onSuccess.data.id;
|
||||
let device_id = (<HTMLInputElement>document.getElementById('deviceDataId')).value;
|
||||
this.ichambaService.addCard(token, device_id).subscribe(
|
||||
data => {
|
||||
(data: any) => {
|
||||
if (data['type'] == "error") {
|
||||
this.loading.dismiss();
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(data['message']);
|
||||
} else {
|
||||
this.events.publish('refreshcards', 'data');
|
||||
this.loading.dismiss();
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.dismissAddcard();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.added_card'));
|
||||
}
|
||||
}, error => {
|
||||
this.loading.dismiss();
|
||||
}, (error: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
});
|
||||
//console.log(onSuccess.data.id);
|
||||
//console.log(document.getElementById('deviceDataId').value);
|
||||
}, onError => {
|
||||
this.loading.dismiss();
|
||||
}, (onError: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.geo_error') + onError['status']);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<ion-title>{{'cards.header' | translate}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<ion-content class="ion-padding">
|
||||
<ion-card *ngFor="let card of cards">
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<h2 text-capitalize>{{ card.brand }}</h2>
|
||||
<h2 class="ion-text-capitalize">{{ card.brand }}</h2>
|
||||
<p>{{card.card_number}}</p>
|
||||
</ion-label>
|
||||
<ion-button color="danger" (click)="deletecard(card.id)">
|
||||
|
||||
@@ -12,10 +12,11 @@ import { AlertService } from 'src/app/services/alert.service';
|
||||
selector: 'app-cards',
|
||||
templateUrl: './cards.page.html',
|
||||
styleUrls: ['./cards.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class CardsPage implements OnInit {
|
||||
|
||||
cards = [];
|
||||
cards: any[] = [];
|
||||
|
||||
constructor(
|
||||
private modalController: ModalController,
|
||||
|
||||
@@ -2,17 +2,13 @@ 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 { CategoryPage } from './category.page';
|
||||
import { TranslateModule } from '@ngx-translate/core'
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddPropertyPageModule } from '../property/add-property/add-property.module';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: CategoryPage
|
||||
}
|
||||
{ path: '', component: CategoryPage }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
@@ -21,7 +17,8 @@ const routes: Routes = [
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
TranslateModule
|
||||
TranslateModule,
|
||||
AddPropertyPageModule,
|
||||
],
|
||||
declarations: [CategoryPage]
|
||||
})
|
||||
|
||||
@@ -1,64 +1,66 @@
|
||||
<style>
|
||||
ion-list {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
max-height: 50%;
|
||||
z-index: 999;
|
||||
}
|
||||
ion-item.place:active:after {
|
||||
cursor: pointer;
|
||||
background-color: #ff7d00 !important;
|
||||
}
|
||||
</style>
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{'categories.header' | translate}}</ion-title>
|
||||
<ion-title>Nueva postulación</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="fixed">{{'categories.category' | translate}}</ion-label>
|
||||
<ion-input value="{{ category }}" disabled></ion-input>
|
||||
<ion-label position="fixed">Categoría</ion-label>
|
||||
<ion-input [value]="category" disabled></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<!-- Selector de propiedad -->
|
||||
<ion-item>
|
||||
<ion-label position="fixed">{{'categories.address' | translate}}</ion-label>
|
||||
<ion-input [(ngModel)]="addressAutocomplete" (ionChange)="autocomplete($event)" value="{{ myAddress }}" (ionFocus)="showlist()"></ion-input>
|
||||
<ion-label position="fixed">Propiedad</ion-label>
|
||||
<ion-select [(ngModel)]="selectedPropertyId" placeholder="Selecciona una propiedad" interface="action-sheet">
|
||||
<ion-select-option *ngFor="let p of properties" [value]="p.id">
|
||||
<ion-icon [name]="p.icon"></ion-icon> {{ p.name }}: {{ p.address }}
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-list *ngIf="showif">
|
||||
<ion-item button=true (click)="geoloc(places.place_id, places.description, places.terms[1].value)" *ngFor="let places of placesSearch" class="place">
|
||||
{{ places.description }}
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<ion-item hidden>
|
||||
<ion-label position="fixed">{{'categories.int_number' | translate}}</ion-label>
|
||||
<ion-input value="{{ myIntnumber }}" disabled></ion-input>
|
||||
<ion-item button="true" (click)="openAddProperty()" lines="none">
|
||||
<ion-icon name="add-circle-outline" slot="start" color="primary"></ion-icon>
|
||||
<ion-label color="primary">Agregar propiedad</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="fixed">{{'categories.references' | translate}}</ion-label>
|
||||
<ion-textarea [(ngModel)]="references" placeholder="{{'categories.references_placeholder' | translate}}"></ion-textarea>
|
||||
</ion-item>
|
||||
<ion-item hidden>
|
||||
<ion-label position="fixed">Latitud</ion-label>
|
||||
<ion-input value="{{ myPosition.latitude }}" disabled></ion-input>
|
||||
</ion-item>
|
||||
<ion-item hidden>
|
||||
<ion-label position="fixed">Longitud</ion-label>
|
||||
<ion-input value="{{ myPosition.longitude }}" disabled></ion-input>
|
||||
<ion-label position="fixed">Referencias</ion-label>
|
||||
<ion-textarea [(ngModel)]="references" placeholder="Ej. Frente al parque, casa azul..."></ion-textarea>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="fixed">{{'categories.date' | translate}}</ion-label>
|
||||
<ion-datetime presentation="date" [(ngModel)]="setDate" (ionChange)="changedate($event)" [min]="minyear" [max]="maxyear"></ion-datetime>
|
||||
<ion-label position="fixed">Comentarios</ion-label>
|
||||
<ion-textarea [(ngModel)]="details" placeholder="Describe lo que necesitas..."></ion-textarea>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label position="fixed">{{'categories.time' | translate}}</ion-label>
|
||||
<ion-datetime presentation="time" [(ngModel)]="setHour" (ionChange)="changehour($event)"></ion-datetime>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label position="fixed">{{'categories.commentaries' | translate}}</ion-label>
|
||||
<ion-textarea [(ngModel)]="details"></ion-textarea>
|
||||
</ion-item>
|
||||
<ion-button type="submit" expand="full" color="primary" (click)="addpostulation()">{{'categories.add_postulation' | translate}}</ion-button>
|
||||
|
||||
<!-- Fotos -->
|
||||
<div style="padding: 12px 0">
|
||||
<ion-label style="font-size: 0.9rem; color: var(--ion-color-medium); padding-left: 4px">
|
||||
Fotos (opcional, máx. 3)
|
||||
</ion-label>
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px">
|
||||
<div *ngFor="let photo of photos; let i = index" style="position: relative">
|
||||
<img [src]="photo" style="width: 80px; height: 80px; object-fit: cover; border-radius: 8px">
|
||||
<ion-button fill="clear" color="danger" size="small"
|
||||
style="position: absolute; top: -8px; right: -8px; --padding-start: 0; --padding-end: 0"
|
||||
(click)="removePhoto(i)">
|
||||
<ion-icon slot="icon-only" name="close-circle"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
<ion-button *ngIf="photos.length < 3" fill="outline" color="medium"
|
||||
style="width: 80px; height: 80px; --border-radius: 8px" (click)="addPhoto()">
|
||||
<ion-icon slot="icon-only" name="camera-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ion-button type="submit" expand="full" color="primary" style="margin-top: 2em" (click)="addpostulation()">
|
||||
Crear postulación
|
||||
</ion-button>
|
||||
|
||||
</ion-content>
|
||||
|
||||
@@ -1,228 +1,117 @@
|
||||
import { Component, OnInit, NgZone } from '@angular/core';
|
||||
import { NavController, LoadingController } from '@ionic/angular';
|
||||
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
|
||||
import { NavController, LoadingController, ModalController } from '@ionic/angular';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { AuthService } from 'src/app/services/auth.service';
|
||||
import { AlertService } from 'src/app/services/alert.service';
|
||||
import { IchambaService } from 'src/app/services/ichamba.service';
|
||||
import { Geolocation } from '@capacitor/geolocation';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
declare var google: any;
|
||||
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
|
||||
import { AddPropertyPage } from '../property/add-property/add-property.page';
|
||||
|
||||
@Component({
|
||||
selector: 'app-category',
|
||||
templateUrl: './category.page.html',
|
||||
styleUrls: ['./category.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class CategoryPage implements OnInit {
|
||||
|
||||
private loading: any;
|
||||
category: any = null;
|
||||
//money: number = null;
|
||||
//amount: number = null;
|
||||
//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();
|
||||
maxyear: string = null;
|
||||
mintime: number;
|
||||
maxtime: number;
|
||||
setHour: any;
|
||||
setDate: any;
|
||||
references: string;
|
||||
details: string;
|
||||
differenceDate: number;
|
||||
differenceHour: number;
|
||||
myPosition: any = {};
|
||||
myAddress: string = null;
|
||||
myIntnumber: string = null;
|
||||
addressAutocomplete: string = '';
|
||||
placesSearch: string = '';
|
||||
showif = true;
|
||||
references: string = '';
|
||||
details: string = '';
|
||||
photos: string[] = [];
|
||||
|
||||
properties: any[] = [];
|
||||
selectedPropertyId: number | null = null;
|
||||
loadingProperties = true;
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private navCtrl: NavController,
|
||||
private alertService: AlertService,
|
||||
private ichambaService: IchambaService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private translateService: TranslateService,
|
||||
private ngZone: NgZone,
|
||||
private loadingCtrl: LoadingController
|
||||
) {
|
||||
console.log(google);
|
||||
}
|
||||
private loadingCtrl: LoadingController,
|
||||
private modalCtrl: ModalController,
|
||||
private cdr: ChangeDetectorRef,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.category = this.activatedRoute.snapshot.paramMap.get('category');
|
||||
this.ichambaService.getParameters()
|
||||
.subscribe( data => {
|
||||
this.mintime = data['min_time'];
|
||||
this.maxtime = data['max_time'];
|
||||
});
|
||||
this.getCurrentLocation();
|
||||
console.log(this.minyear);
|
||||
// Máximo 2 días de anticipación
|
||||
const maxDate = new Date();
|
||||
maxDate.setDate(maxDate.getDate() + 2);
|
||||
this.maxyear = maxDate.toISOString().slice(0, 10);
|
||||
console.log(this.maxyear);
|
||||
this.loadProperties();
|
||||
}
|
||||
|
||||
async getCurrentLocation() {
|
||||
try {
|
||||
// Request permissions first
|
||||
const permissionStatus = await Geolocation.checkPermissions();
|
||||
if (permissionStatus.location !== 'granted') {
|
||||
await Geolocation.requestPermissions();
|
||||
loadProperties() {
|
||||
this.loadingProperties = true;
|
||||
this.ichambaService.getProperties().subscribe({
|
||||
next: (data: any[]) => {
|
||||
this.properties = data;
|
||||
this.loadingProperties = false;
|
||||
this.cdr.detectChanges();
|
||||
if (data.length === 0) {
|
||||
this.openAddProperty();
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
this.loadingProperties = false;
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
const position = await Geolocation.getCurrentPosition({
|
||||
enableHighAccuracy: true,
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
console.log(position);
|
||||
this.myPosition = {
|
||||
latitude: position.coords.latitude,
|
||||
longitude: position.coords.longitude
|
||||
};
|
||||
this.doGeocode();
|
||||
} catch (error) {
|
||||
console.log(this.translateService.instant('alerts.geo_error') + error);
|
||||
}
|
||||
}
|
||||
|
||||
doGeocode() {
|
||||
// Use Google Maps JavaScript API for geocoding
|
||||
const geocoder = new google.maps.Geocoder();
|
||||
const latlng = { lat: this.myPosition.latitude, lng: this.myPosition.longitude };
|
||||
|
||||
geocoder.geocode({ location: latlng }, (results: any, status: any) => {
|
||||
this.ngZone.run(() => {
|
||||
if (status === 'OK' && results[0]) {
|
||||
const address = results[0].formatted_address;
|
||||
console.log("data_: ", address);
|
||||
this.myAddress = address;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* check (ev: any) {
|
||||
this.amount = parseFloat(ev.target.value.replace(",", ""));
|
||||
console.log(this.amount);
|
||||
if (this.amount < 150) {
|
||||
this.amount_check = false;
|
||||
} else {
|
||||
this.amount_check = true;
|
||||
async openAddProperty() {
|
||||
const modal = await this.modalCtrl.create({ component: AddPropertyPage });
|
||||
await modal.present();
|
||||
const { data } = await modal.onDidDismiss();
|
||||
if (data?.property) {
|
||||
this.properties.push(data.property);
|
||||
this.selectedPropertyId = data.property.id;
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
}*/
|
||||
|
||||
changehour (ev: any) {
|
||||
console.log(this.setHour);
|
||||
}
|
||||
|
||||
changedate (ev: any) {
|
||||
console.log(ev.target.value);
|
||||
let current_date = (new Date().toISOString().slice(0, 10)).toString();
|
||||
let selected_date = (new Date(this.setDate).toISOString().slice(0, 10)).toString();
|
||||
this.differenceDate = (Number(new Date(selected_date)) - Number(new Date(current_date)));
|
||||
console.log(this.differenceDate);
|
||||
}
|
||||
|
||||
autocomplete(ev: any) {
|
||||
if (!this.addressAutocomplete.trim().length) {
|
||||
this.placesSearch = null;
|
||||
}
|
||||
console.log(this.addressAutocomplete)
|
||||
new google.maps.places.AutocompleteService().getPredictions({ input: this.addressAutocomplete }, predictions => {
|
||||
this.ngZone.run(() => {
|
||||
this.placesSearch = predictions;
|
||||
console.log(predictions);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
geoloc(place_id: string, place_description: string, place_intnumber: string) {
|
||||
this.myAddress = place_description;
|
||||
this.myIntnumber = place_intnumber;
|
||||
console.log(place_id);
|
||||
this.hidelist();
|
||||
new google.maps.Geocoder().geocode({ placeId: place_id }, coordinates => {
|
||||
this.ngZone.run(() => {
|
||||
console.log(coordinates[0].geometry.location.lat() + ", " + coordinates[0].geometry.location.lng());
|
||||
this.myPosition = {
|
||||
latitude: coordinates[0].geometry.location.lat(),
|
||||
longitude: coordinates[0].geometry.location.lng()
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addpostulation() {
|
||||
// Validar que fecha y hora estén seleccionadas
|
||||
if (!this.setDate || !this.setHour) {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.select_date_time') || 'Selecciona fecha y hora');
|
||||
async addPhoto() {
|
||||
if (this.photos.length >= 3) {
|
||||
this.alertService.presentToast('Máximo 3 fotos.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const image = await Camera.getPhoto({
|
||||
quality: 70,
|
||||
resultType: CameraResultType.Base64,
|
||||
source: CameraSource.Prompt,
|
||||
});
|
||||
this.photos.push('data:image/jpeg;base64,' + image.base64String);
|
||||
this.cdr.detectChanges();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Calcular diferencia de fecha directamente
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const selectedDate = new Date(this.setDate);
|
||||
selectedDate.setHours(0, 0, 0, 0);
|
||||
this.differenceDate = selectedDate.getTime() - today.getTime();
|
||||
removePhoto(index: number) {
|
||||
this.photos.splice(index, 1);
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
// Calcular diferencia de hora
|
||||
this.differenceHour = (new Date(this.setHour).getTime() - new Date().getTime());
|
||||
|
||||
console.log("differenceDate:", this.differenceDate);
|
||||
console.log("differenceHour:", this.differenceHour);
|
||||
console.log("mintime:", this.mintime * 3600000);
|
||||
|
||||
// Máximo 2 días de anticipación (172800000 ms = 2 días)
|
||||
const maxDays = 2 * 24 * 60 * 60 * 1000;
|
||||
console.log("maxDays:", maxDays);
|
||||
|
||||
// Validación de tiempo mínimo (mismo día y menos de mintime horas)
|
||||
if (this.differenceDate < 86400000 && this.differenceHour < (this.mintime * 3600000)) {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.categories_mintime'));
|
||||
} else if (this.differenceDate <= maxDays) {
|
||||
if (this.myAddress && this.myPosition.latitude && this.myPosition.longitude) {
|
||||
this.loadingCtrl.create().then((overlay) => {
|
||||
this.loading = overlay;
|
||||
this.loading.present();
|
||||
});
|
||||
|
||||
if (!Number.isInteger(parseInt(this.myIntnumber))) {
|
||||
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(
|
||||
data => {
|
||||
this.loading.dismiss();
|
||||
if (data['message'] == 'No providers') {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.no_provider'));
|
||||
} else {
|
||||
this.navCtrl.navigateRoot('/dashboard');
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.categories_success'));
|
||||
}
|
||||
}, error => {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
});
|
||||
} else {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.valid_address'));
|
||||
}
|
||||
} else {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.categories_maxtime'));
|
||||
async addpostulation() {
|
||||
if (!this.selectedPropertyId) {
|
||||
this.alertService.presentToast('Selecciona una propiedad.');
|
||||
return;
|
||||
}
|
||||
this.loading = await this.loadingCtrl.create();
|
||||
await this.loading.present();
|
||||
this.ichambaService.addPostulation(
|
||||
this.category, this.selectedPropertyId, this.references, this.details, this.photos
|
||||
).subscribe({
|
||||
next: (data: any) => {
|
||||
this.loading.dismiss();
|
||||
if (data['message'] === 'No providers') {
|
||||
this.alertService.presentToast('No hay proveedores disponibles para esta categoría.');
|
||||
} else {
|
||||
this.navCtrl.navigateRoot('/dashboard');
|
||||
this.alertService.presentToast('¡Postulación creada con éxito!');
|
||||
}
|
||||
},
|
||||
error: (error: any) => {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast('Error: ' + error['status']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showlist() {
|
||||
this.showif = true;
|
||||
}
|
||||
|
||||
hidelist() {
|
||||
this.showif = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { ContractDiscussionPage } from './contract-discussion.page';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, FormsModule, IonicModule],
|
||||
declarations: [ContractDiscussionPage],
|
||||
exports: [ContractDiscussionPage]
|
||||
})
|
||||
export class ContractDiscussionPageModule {}
|
||||
@@ -0,0 +1,80 @@
|
||||
<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>Chat del Contrato</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 left">
|
||||
<ion-skeleton-text [animated]="true" style="width: 60%; height: 48px; 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">
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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-textarea
|
||||
[(ngModel)]="newMessage"
|
||||
placeholder="Escribe un mensaje..."
|
||||
rows="1"
|
||||
autoGrow="true"
|
||||
style="--highlight-height: 0"
|
||||
(keydown.enter)="$event.preventDefault(); newMessage.trim() && !sending && sendMessage()"
|
||||
></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>
|
||||
@@ -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;
|
||||
gap: 2px;
|
||||
background: var(--ion-tab-bar-background, #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-step-100, #f4f5f8);
|
||||
border-radius: 20px;
|
||||
margin: 0;
|
||||
max-height: 120px;
|
||||
}
|
||||
|
||||
ion-button {
|
||||
--padding-start: 6px;
|
||||
--padding-end: 6px;
|
||||
min-height: 40px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
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-contract-discussion',
|
||||
templateUrl: './contract-discussion.page.html',
|
||||
styleUrls: ['./contract-discussion.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class ContractDiscussionPage implements OnInit {
|
||||
|
||||
@Input() contractId: 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.getContractDiscussion(this.contractId).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.sendContractMessage(this.contractId, 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.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dismiss() {
|
||||
this.modalCtrl.dismiss();
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,13 @@ 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 { ContractedPage } from './contracted.page';
|
||||
import { TranslateModule } from '@ngx-translate/core'
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ContractDiscussionPageModule } from '../contract-discussion/contract-discussion.module';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: ContractedPage
|
||||
}
|
||||
{ path: '', component: ContractedPage }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
@@ -21,7 +17,8 @@ const routes: Routes = [
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
TranslateModule
|
||||
TranslateModule,
|
||||
ContractDiscussionPageModule,
|
||||
],
|
||||
declarations: [ContractedPage]
|
||||
})
|
||||
|
||||
@@ -6,27 +6,56 @@
|
||||
<ion-title>{{'contracts.header' | translate}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<h2 text-capitalize style="padding-bottom: 0.5em">{{'contracts.header_2' | translate}}</h2>
|
||||
<ion-content class="ion-padding">
|
||||
<h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">{{'contracts.header_2' | 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 class="no-separator">
|
||||
<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">
|
||||
<ion-card *ngIf="ccontract.status==1">
|
||||
<ion-item style="--border-color: #fff">
|
||||
<ion-item class="no-separator">
|
||||
<ion-avatar slot="start">
|
||||
<img *ngIf="ccontract.profile_photo" [src]="ccontract.profile_photo" (error)="ccontract.profile_photo=null">
|
||||
<ion-icon *ngIf="!ccontract.profile_photo" name="person-circle" style="width:100%;height:100%;color:var(--ion-color-medium)"></ion-icon>
|
||||
</ion-avatar>
|
||||
<ion-label>
|
||||
<h2 *ngIf="lang===true"text-capitalize>{{ ccontract.category }}</h2>
|
||||
<h2 *ngIf="lang===false"text-capitalize>{{ ccontract.en_category }}</h2>
|
||||
<p text-wrap>{{ ccontract.address }}</p>
|
||||
<p text-wrap text-capitalize>{{ ccontracts_dates[i] }}</p>
|
||||
<p text-wrap text-capitalize>{{'contracts.supplier' | translate}}: {{ ccontract.supplier }}</p>
|
||||
<p 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>
|
||||
<h2 text-wrap>{{'contracts.start_pin' | translate}}<b>{{ ccontract.code }}</b></h2>
|
||||
<p text-wrap>{{'contracts.info_pin' | translate}}</p>
|
||||
<h2 *ngIf="lang===true" class="ion-text-capitalize">{{ ccontract.category }}</h2>
|
||||
<h2 *ngIf="lang===false" class="ion-text-capitalize">{{ ccontract.en_category }}</h2>
|
||||
<p class="ion-text-wrap">{{ ccontract.address }}</p>
|
||||
<p class="ion-text-wrap ion-text-capitalize">{{ ccontracts_dates[i] }}</p>
|
||||
<p class="ion-text-wrap ion-text-capitalize">{{'contracts.supplier' | translate}}: {{ ccontract.supplier }}</p>
|
||||
<p class="ion-text-wrap">{{'contracts.phone' | translate}}: <a href="tel:{{ ccontract.phone }}">{{ ccontract.phone }}</a></p>
|
||||
<p class="ion-text-wrap ion-text-capitalize">{{'contracts.amount' | translate}}: ${{ ccontract.amount }}</p>
|
||||
<h2 class="ion-text-wrap">{{'contracts.start_pin' | translate}}<b>{{ ccontract.code }}</b></h2>
|
||||
<p class="ion-text-wrap">{{'contracts.info_pin' | translate}}</p>
|
||||
</ion-label>
|
||||
</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-item>
|
||||
<div style="display: flex; justify-content: space-between; padding: 0.5em 1em 1.25em; align-items: center">
|
||||
<ion-button fill="outline" color="primary" (click)="openDiscussion(ccontract.id)">
|
||||
<ion-icon slot="start" name="chatbubbles-outline"></ion-icon>
|
||||
Chat
|
||||
</ion-button>
|
||||
<ion-button *ngIf="ccontract.past_due < 0" style="height: 4.0em" color="danger" (click)="cancelContract(ccontract.id, ccontract.date)">{{'contracts.cancel_1.1' | translate}}<br>{{'contracts.cancel_1.2' | translate}}</ion-button>
|
||||
</div>
|
||||
</ion-card>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ion-content>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, ChangeDetectorRef } from '@angular/core';
|
||||
import { ModalController, MenuController, NavController } from '@ionic/angular';
|
||||
import { ContractDiscussionPage } from '../contract-discussion/contract-discussion.page';
|
||||
import { EventService } from '../../../services/event.service';
|
||||
import { EnvService } from 'src/app/services/env.service';
|
||||
import { AuthService } from 'src/app/services/auth.service';
|
||||
@@ -13,12 +14,14 @@ import { AlertController } from '@ionic/angular';
|
||||
selector: 'app-contracted',
|
||||
templateUrl: './contracted.page.html',
|
||||
styleUrls: ['./contracted.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class ContractedPage implements OnInit {
|
||||
export class ContractedPage {
|
||||
|
||||
ccontracts = [];
|
||||
ccontracts_dates = [];
|
||||
lang: boolean;
|
||||
ccontracts: any[] = [];
|
||||
ccontracts_dates: any[] = [];
|
||||
lang: boolean = false;
|
||||
loading = true;
|
||||
|
||||
constructor(
|
||||
private modalController: ModalController,
|
||||
@@ -32,32 +35,29 @@ export class ContractedPage implements OnInit {
|
||||
private translateService: TranslateService,
|
||||
private languageService: LanguageService,
|
||||
private env: EnvService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
) {
|
||||
this.events.subscribe('refreshccontracts', (data) => {
|
||||
this.getccontracts();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
ionViewWillEnter() {
|
||||
this.lang = this.languageService.getDefaultLanguage() === 'es';
|
||||
this.loading = true;
|
||||
this.getccontracts();
|
||||
if (this.languageService.getDefaultLanguage() == 'es') {
|
||||
this.lang = true;
|
||||
} else {
|
||||
this.lang = false
|
||||
}
|
||||
}
|
||||
|
||||
refresh(event) {
|
||||
refresh(event: any) {
|
||||
this.ichambaService.getCurrentcontracts().subscribe(
|
||||
data => {
|
||||
this.ccontracts = data;
|
||||
this.ccontracts_dates = [];
|
||||
for (var i of this.ccontracts) {
|
||||
if (this.languageService.getDefaultLanguage() == 'es') {
|
||||
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'}));
|
||||
} 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'}));
|
||||
}
|
||||
const locale = this.lang ? 'es-US' : 'en-US';
|
||||
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' }));
|
||||
}
|
||||
this.cdr.detectChanges();
|
||||
event.target.complete();
|
||||
}, error => {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
@@ -69,14 +69,15 @@ export class ContractedPage implements OnInit {
|
||||
this.ichambaService.getCurrentcontracts().subscribe(
|
||||
data => {
|
||||
this.ccontracts = data;
|
||||
this.ccontracts_dates = [];
|
||||
for (var i of this.ccontracts) {
|
||||
if (this.languageService.getDefaultLanguage() == 'es') {
|
||||
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'}));
|
||||
} 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'}));
|
||||
}
|
||||
const locale = this.lang ? 'es-US' : 'en-US';
|
||||
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' }));
|
||||
}
|
||||
this.loading = false;
|
||||
this.cdr.detectChanges();
|
||||
}, error => {
|
||||
this.loading = false;
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
});
|
||||
}
|
||||
@@ -90,15 +91,23 @@ export class ContractedPage implements OnInit {
|
||||
this.presentAlert(id)
|
||||
} else { */
|
||||
this.ichambaService.cancelContract(id).subscribe(
|
||||
data => {
|
||||
(data: any) => {
|
||||
this.getccontracts();
|
||||
this.alertService.presentToast(data['message']);
|
||||
}, error => {
|
||||
}, (error: any) => {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
});
|
||||
//}
|
||||
}
|
||||
|
||||
async openDiscussion(contractId: any) {
|
||||
const modal = await this.modalController.create({
|
||||
component: ContractDiscussionPage,
|
||||
componentProps: { contractId },
|
||||
});
|
||||
await modal.present();
|
||||
}
|
||||
|
||||
async presentAlert(id: String) {
|
||||
const alert = await this.alertController.create({
|
||||
header: 'Atención',
|
||||
@@ -114,10 +123,10 @@ export class ContractedPage implements OnInit {
|
||||
text: 'Confirmar',
|
||||
handler: () => {
|
||||
this.ichambaService.cancelContract(id).subscribe(
|
||||
data => {
|
||||
(data: any) => {
|
||||
this.getccontracts();
|
||||
this.alertService.presentToast(data['message']);
|
||||
}, error => {
|
||||
}, (error: any) => {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'finished',
|
||||
loadChildren: () => import('./finished/finished.module').then(m => m.FinishedPageModule)
|
||||
},
|
||||
{
|
||||
path: 'reported',
|
||||
loadChildren: () => import('../reports/reports.module').then(m => m.ReportsPageModule)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -24,5 +24,10 @@
|
||||
<ion-label>{{'contracts.header_3' | translate}}</ion-label>
|
||||
</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-tabs>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { AlertService } from 'src/app/services/alert.service';
|
||||
selector: 'app-contracts',
|
||||
templateUrl: './contracts.page.html',
|
||||
styleUrls: ['./contracts.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class ContractsPage implements OnInit {
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<ion-title>Añadir Fondos</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<p text-wrap>Está por añadir un fondo extra al servicio realizado por <b>{{ supplier }}</b>.
|
||||
<ion-content class="ion-padding">
|
||||
<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
|
||||
de seguridad de la tarjeta.</p><br>
|
||||
<ion-item [class.ng-invalid]="!amount_check">
|
||||
|
||||
@@ -15,18 +15,19 @@ declare var OpenPay: any;
|
||||
selector: 'app-extra',
|
||||
templateUrl: './extra.page.html',
|
||||
styleUrls: ['./extra.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class ExtraPage implements OnInit {
|
||||
|
||||
private loading;
|
||||
amount: number = null;
|
||||
private loading: HTMLIonLoadingElement | null = null;
|
||||
amount: number | null = null;
|
||||
amount_check = true;
|
||||
code_check = true;
|
||||
cards = [];
|
||||
card_id = null;
|
||||
supplier = null;
|
||||
cards_menu = null;
|
||||
contract_id: number = null;
|
||||
cards: any[] = [];
|
||||
card_id: any = null;
|
||||
supplier: any = null;
|
||||
cards_menu: any = null;
|
||||
contract_id: number | null = null;
|
||||
|
||||
constructor(
|
||||
private modalController: ModalController,
|
||||
@@ -104,16 +105,16 @@ export class ExtraPage implements OnInit {
|
||||
this.loading = overlay;
|
||||
this.loading.present();
|
||||
});
|
||||
this.ichambaService.addExtra(this.contract_id, this.amount, this.card_id, code, device_id).subscribe(
|
||||
data => {
|
||||
this.loading.dismiss();
|
||||
if (data['message'] = 'extra_added') {
|
||||
this.ichambaService.addExtra(this.contract_id!, this.amount!, this.card_id, code, device_id).subscribe(
|
||||
(data: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
if (data['message'] == 'extra_added') {
|
||||
this.alertService.presentToast("Fondo extra añadido con éxito");
|
||||
}
|
||||
this.events.publish('refreshpcontracts', 'data');
|
||||
this.navCtrl.navigateRoot('/contracts/contracted');
|
||||
}, error => {
|
||||
this.loading.dismiss();
|
||||
}, (error: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
|
||||
});
|
||||
//console.log(onSuccess.data.id);
|
||||
|
||||
@@ -3,25 +3,43 @@
|
||||
<ion-buttons slot="start">
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{'contract.header' | translate}}</ion-title>
|
||||
<ion-title>{{'contracts.header' | translate}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<h2 text-capitalize style="padding-bottom: 0.5em">{{'contracts.header_3' | translate}}</h2>
|
||||
<ion-content class="ion-padding">
|
||||
<h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">{{'contracts.header_3' | 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 fcontract of fcontracts; let i = index">
|
||||
<ion-card>
|
||||
<ion-item style="--border-color: #fff">
|
||||
<ion-label>
|
||||
<h2 text-capitalize *ngIf="lang===true">{{ fcontract.category }}</h2>
|
||||
<h2 text-capitalize *ngIf="lang===false">{{ fcontract.en_category }}</h2>
|
||||
<h2 text-capitalize *ngIf="fcontract.parent">{{'contracts.parent' | translate}}</h2>
|
||||
<p text-wrap>{{ fcontract.address }}</p>
|
||||
<p text-wrap text-capitalize>{{ fcontracts_dates[i] }}</p>
|
||||
<p text-wrap text-capitalize>{{'contracts.supplier' | translate}}: {{ fcontract.supplier }}</p>
|
||||
<p text-wrap text-capitalize>{{'contracts.amount' | translate}}: ${{ fcontract.amount }}</p>
|
||||
<h2 class="ion-text-capitalize" *ngIf="lang===true">{{ fcontract.category }}</h2>
|
||||
<h2 class="ion-text-capitalize" *ngIf="lang===false">{{ fcontract.en_category }}</h2>
|
||||
<h2 class="ion-text-capitalize" *ngIf="fcontract.parent">{{'contracts.parent' | translate}}</h2>
|
||||
<p class="ion-text-wrap">{{ fcontract.address }}</p>
|
||||
<p class="ion-text-wrap ion-text-capitalize">{{ fcontracts_dates[i] }}</p>
|
||||
<p class="ion-text-wrap ion-text-capitalize">{{'contracts.supplier' | translate}}: {{ fcontract.supplier }}</p>
|
||||
<p class="ion-text-wrap ion-text-capitalize">{{'contracts.amount' | translate}}: ${{ fcontract.amount }}</p>
|
||||
<p>{{'contracts.status' | translate}}: {{ fcontract.status }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
@@ -35,4 +53,5 @@
|
||||
</ion-row>
|
||||
</ion-card>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ion-content>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, ChangeDetectorRef } from '@angular/core';
|
||||
import { ReviewPage } from '../review/review.page';
|
||||
import { ModalController, MenuController, NavController } from '@ionic/angular';
|
||||
import { EventService } from '../../../services/event.service';
|
||||
@@ -15,12 +15,14 @@ import { AlertController } from '@ionic/angular';
|
||||
selector: 'app-finished',
|
||||
templateUrl: './finished.page.html',
|
||||
styleUrls: ['./finished.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class FinishedPage implements OnInit {
|
||||
export class FinishedPage {
|
||||
|
||||
fcontracts = [];
|
||||
fcontracts_dates = [];
|
||||
lang: boolean;
|
||||
fcontracts: any[] = [];
|
||||
fcontracts_dates: any[] = [];
|
||||
lang: boolean = false;
|
||||
loading = true;
|
||||
|
||||
constructor(
|
||||
private modalController: ModalController,
|
||||
@@ -36,32 +38,29 @@ export class FinishedPage implements OnInit {
|
||||
private translateService: TranslateService,
|
||||
private languageService: LanguageService,
|
||||
private env: EnvService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
) {
|
||||
this.events.subscribe('refreshccontracts', (data) => {
|
||||
this.getfcontracts();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
ionViewWillEnter() {
|
||||
this.lang = this.languageService.getDefaultLanguage() === 'es';
|
||||
this.loading = true;
|
||||
this.getfcontracts();
|
||||
if (this.languageService.getDefaultLanguage() == 'es') {
|
||||
this.lang = true;
|
||||
} else {
|
||||
this.lang = false
|
||||
}
|
||||
}
|
||||
|
||||
refresh(event) {
|
||||
refresh(event: any) {
|
||||
this.ichambaService.getFinishedcontracts().subscribe(
|
||||
data => {
|
||||
this.fcontracts = data;
|
||||
this.fcontracts_dates = [];
|
||||
for (var i of this.fcontracts) {
|
||||
if (this.languageService.getDefaultLanguage() == 'es') {
|
||||
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'}));
|
||||
} 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'}));
|
||||
}
|
||||
const locale = this.lang ? 'es-US' : 'en-US';
|
||||
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' }));
|
||||
}
|
||||
this.cdr.detectChanges();
|
||||
event.target.complete();
|
||||
}, error => {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
@@ -73,14 +72,15 @@ export class FinishedPage implements OnInit {
|
||||
this.ichambaService.getFinishedcontracts().subscribe(
|
||||
data => {
|
||||
this.fcontracts = data;
|
||||
this.fcontracts_dates = [];
|
||||
for (var i of this.fcontracts) {
|
||||
if (this.languageService.getDefaultLanguage() == 'es') {
|
||||
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'}));
|
||||
} 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'}));
|
||||
}
|
||||
const locale = this.lang ? 'es-US' : 'en-US';
|
||||
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' }));
|
||||
}
|
||||
this.loading = false;
|
||||
this.cdr.detectChanges();
|
||||
}, error => {
|
||||
this.loading = false;
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,104 +6,66 @@
|
||||
<ion-title>{{'contracts.hire' | translate}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<p text-wrap>{{'contracts.hire_info_1' | translate}} <b>{{ supplier }}</b> {{'contracts.hire_info_2' | translate}}<br>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<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>
|
||||
<ion-row>
|
||||
<ion-col size="9">
|
||||
<p text-left style="margin:0">{{'contracts.fee_amount' | translate}}:</p>
|
||||
</ion-col>
|
||||
<ion-col size="3">
|
||||
<p text-right style="margin:0">${{ fee | number: '1.2-2'}}</p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row>
|
||||
<ion-col size="9">
|
||||
<p text-left style="margin:0">{{'contracts.discount_amount' | translate}}:</p>
|
||||
</ion-col>
|
||||
<ion-col size="3">
|
||||
<p text-right style="margin:0">${{ discount | number: '1.2-2'}}</p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row>
|
||||
<ion-col size="9">
|
||||
<p text-left style="margin:0"><b>{{'contracts.final_amount' | translate}}:</b></p>
|
||||
</ion-col>
|
||||
<ion-col size="3">
|
||||
<p text-right style="margin:0"><b>${{ final_amount | number: '1.2-2'}}</b></p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<br>
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-label position="fixed" >{{'contracts.coupon' | translate}}:</ion-label>
|
||||
<ion-input [(ngModel)]="coupon" (ngModelChange)="coupon = $event.toLocaleUpperCase()"></ion-input>
|
||||
<ion-button style="height: 3em" item-end (click)="checkCoupon()" expand="full" color="primary">{{'contracts.validate' | translate}}</ion-button>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<ion-item>
|
||||
<ion-label>{{'cards.card' | translate}}</ion-label>
|
||||
<ion-select style="max-width:90%" interface="action-sheet" [(ngModel)]="cards_menu" (ionChange)="selected_card($event)">
|
||||
<ion-select-option>Agregar tarjeta</ion-select-option>
|
||||
<ion-select-option *ngFor="let card of cards" [value]="card.id">{{card.brand}} - {{card.card_number}}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<form #form="ngForm" id="card_form" (ngSubmit)="createContract(form)" method="post">
|
||||
<ion-item [class.ng-invalid]="!code_check">
|
||||
<ion-label position="floating">CVV</ion-label>
|
||||
<ion-input type="password" ngModel name="code" (ionChange)="code_checker($event)" [class.ng-invalid]="!code_check" size="3" maxlength="4"></ion-input>
|
||||
</ion-item><br><br>
|
||||
<ion-button type="submit" expand="full" color="secondary">{{'contracts.hire_confirm' | translate}}</ion-button>
|
||||
</form>
|
||||
|
||||
<ion-row>
|
||||
<ion-col size="9">
|
||||
<p class="ion-text-start" style="margin:0">{{'contracts.fee_amount' | translate}}:</p>
|
||||
</ion-col>
|
||||
<ion-col size="3">
|
||||
<p class="ion-text-end" style="margin:0">${{ fee | number: '1.2-2'}}</p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row>
|
||||
<ion-col size="9">
|
||||
<p class="ion-text-start" style="margin:0">{{'contracts.discount_amount' | translate}}:</p>
|
||||
</ion-col>
|
||||
<ion-col size="3">
|
||||
<p class="ion-text-end" style="margin:0">${{ discount | number: '1.2-2'}}</p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row>
|
||||
<ion-col size="9">
|
||||
<p class="ion-text-start" style="margin:0"><b>{{'contracts.final_amount' | translate}}:</b></p>
|
||||
</ion-col>
|
||||
<ion-col size="3">
|
||||
<p class="ion-text-end" style="margin:0"><b>${{ final_amount | number: '1.2-2'}}</b></p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<br>
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col size="4">
|
||||
<img src="/assets/openpay/openpay_color.png" style="padding:0.25em 0.5em"/>
|
||||
</ion-col>
|
||||
<ion-col size=8>
|
||||
<p>{{'cards.openpay_1' | translate}}</p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row>
|
||||
<ion-col size="6">
|
||||
<p text-center>{{'cards.openpay_2' | translate}}<p>
|
||||
</ion-col>
|
||||
<ion-col size="6" style="border-left: 1px solid #d0d0d0">
|
||||
<p text-center>{{'cards.openpay_3' | translate}}<p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row>
|
||||
<ion-col size="3">
|
||||
<img src="/assets/openpay/visa.png" style="padding:0.1em 1.0em"/>
|
||||
</ion-col>
|
||||
<ion-col size="3">
|
||||
<img src="/assets/openpay/masterCard.png" style="padding:0.25em 1.5em;"/>
|
||||
</ion-col>
|
||||
<ion-col size="2" style="border-left: 1px solid #d0d0d0">
|
||||
<img src="/assets/openpay/citibanamex.png"/>
|
||||
</ion-col>
|
||||
<ion-col size=2>
|
||||
<img src="/assets/openpay/bancoAzteca.png"/>
|
||||
</ion-col>
|
||||
<ion-col size=2>
|
||||
<img src="/assets/openpay/scotiabank.png"/>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row>
|
||||
<ion-col size="3">
|
||||
<img src="/assets/openpay/americanExpress.png" style="padding:0.5em 1.5em"/>
|
||||
</ion-col>
|
||||
<ion-col size="3">
|
||||
<img src="/assets/openpay/carnet.png"/>
|
||||
</ion-col>
|
||||
<ion-col size="2" style="border-left: 1px solid #d0d0d0">
|
||||
<img src="/assets/openpay/inbursa.png"/>
|
||||
</ion-col>
|
||||
<ion-col size=2>
|
||||
<img src="/assets/openpay/santander.png"/>
|
||||
</ion-col>
|
||||
<ion-col size=2>
|
||||
<img src="/assets/openpay/bbva.png"/>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
|
||||
<p><b>Selecciona la fecha en que recibirás el servicio:</b></p>
|
||||
<ion-list>
|
||||
<ion-radio-group [(ngModel)]="selected_date">
|
||||
<ion-item>
|
||||
<ion-radio value="date_1" slot="start"></ion-radio>
|
||||
<ion-label class="ion-text-wrap">{{ date_1_formatted }}</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-radio value="date_2" slot="start"></ion-radio>
|
||||
<ion-label class="ion-text-wrap">{{ date_2_formatted }}</ion-label>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
<br>
|
||||
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-label position="fixed">{{'contracts.coupon' | translate}}:</ion-label>
|
||||
<ion-input [(ngModel)]="coupon" (ngModelChange)="coupon = $event.toLocaleUpperCase()"></ion-input>
|
||||
<ion-button style="height: 3em" slot="end" (click)="checkCoupon()" color="primary">{{'contracts.validate' | translate}}</ion-button>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-note *ngIf="paymentBypass" color="warning" style="display: block; text-align: center; margin: 1em 0;">
|
||||
<b>MODO DE PRUEBA:</b> El pago será simulado
|
||||
</ion-note>
|
||||
|
||||
<br>
|
||||
<ion-button expand="full" color="secondary" (click)="createContract()">
|
||||
{{'contracts.hire_confirm' | translate}}
|
||||
</ion-button>
|
||||
</ion-content>
|
||||
|
||||
@@ -1,43 +1,38 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ModalController, MenuController, NavController, LoadingController } from '@ionic/angular';
|
||||
import { NavController, LoadingController } from '@ionic/angular';
|
||||
import { EventService } from '../../../services/event.service';
|
||||
import { AddcardPage } from 'src/app/pages/card/addcard/addcard.page';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { EnvService } from 'src/app/services/env.service';
|
||||
import { AuthService } from 'src/app/services/auth.service';
|
||||
import { IchambaService } from 'src/app/services/ichamba.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { AlertService } from 'src/app/services/alert.service';
|
||||
|
||||
declare var OpenPay: any;
|
||||
import { Stripe, PaymentSheetEventsEnum } from '@capacitor-community/stripe';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hire',
|
||||
templateUrl: './hire.page.html',
|
||||
styleUrls: ['./hire.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class HirePage implements OnInit {
|
||||
|
||||
private loading;
|
||||
code_check = true;
|
||||
cards = [];
|
||||
code= null;
|
||||
device_id = null;
|
||||
card_id = null;
|
||||
supplier = null;
|
||||
supplier_id = null;
|
||||
postulation_id = null;
|
||||
fee = null;
|
||||
private loading: HTMLIonLoadingElement | null = null;
|
||||
supplier: any = null;
|
||||
supplier_id: any = null;
|
||||
postulation_id: any = null;
|
||||
fee: any = null;
|
||||
discount = 0;
|
||||
final_amount = null;
|
||||
coupon = null;
|
||||
cards_menu = null;
|
||||
final_amount: any = null;
|
||||
coupon: any = null;
|
||||
paymentBypass = false;
|
||||
date_1: string | null = null;
|
||||
date_2: string | null = null;
|
||||
selected_date: 'date_1' | 'date_2' | null = null;
|
||||
date_1_formatted: string = '';
|
||||
date_2_formatted: string = '';
|
||||
|
||||
constructor(
|
||||
private modalController: ModalController,
|
||||
private menu: MenuController,
|
||||
private authService: AuthService,
|
||||
private alertService: AlertService,
|
||||
private ichambaService: IchambaService,
|
||||
private translateService: TranslateService,
|
||||
@@ -47,136 +42,135 @@ export class HirePage implements OnInit {
|
||||
private events: EventService,
|
||||
private env: EnvService,
|
||||
) {
|
||||
this.events.subscribe('refreshcards', (data) => {
|
||||
this.card_id = null;
|
||||
this.getcards();
|
||||
});
|
||||
this.supplier = this.activatedRoute.snapshot.paramMap.get('supplier');
|
||||
this.supplier_id = this.activatedRoute.snapshot.paramMap.get('supplier_id');
|
||||
this.postulation_id = this.activatedRoute.snapshot.paramMap.get('postulation_id');
|
||||
this.fee = this.activatedRoute.snapshot.paramMap.get('fee');
|
||||
this.final_amount = this.fee;
|
||||
}
|
||||
this.date_1 = this.activatedRoute.snapshot.paramMap.get('date_1');
|
||||
this.date_2 = this.activatedRoute.snapshot.paramMap.get('date_2');
|
||||
this.date_1_formatted = this.date_1 ? this.formatDate(this.date_1) : '';
|
||||
this.date_2_formatted = this.date_2 ? this.formatDate(this.date_2) : '';
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
OpenPay.setId(this.env.MERCHANT_ID);
|
||||
OpenPay.setApiKey(this.env.PUBLIC_API_KEY);
|
||||
console.log(OpenPay.getSandboxMode());
|
||||
var deviceDataId = OpenPay.deviceData.setup("card_form");
|
||||
this.getcards();
|
||||
this.paymentBypass = this.env.PAYMENT_BYPASS;
|
||||
}
|
||||
|
||||
newcard() {
|
||||
this.navCtrl.navigateForward('/addcard');
|
||||
private formatDate(iso: string): string {
|
||||
return new Date((new Date(iso).toLocaleString('en-US') + ' UTC').replace(',', ''))
|
||||
.toLocaleDateString('es-MX', { weekday: 'long', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' });
|
||||
}
|
||||
|
||||
getcards() {
|
||||
this.ichambaService.getCard().subscribe(
|
||||
data => {
|
||||
this.cards = data;
|
||||
}, error => {
|
||||
checkCoupon() {
|
||||
if (!this.coupon) return;
|
||||
if (!this.selected_date) {
|
||||
this.alertService.presentToast('Selecciona una fecha antes de aplicar el cupón.');
|
||||
return;
|
||||
}
|
||||
this.loadingCtrl.create().then(overlay => {
|
||||
this.loading = overlay;
|
||||
this.loading.present();
|
||||
});
|
||||
this.ichambaService.checkCoupon(this.postulation_id, this.supplier_id, this.selected_date, this.coupon).subscribe(
|
||||
(data: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
if (!data) {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.nocoupon'));
|
||||
return;
|
||||
}
|
||||
if (data['name'] == 'used') {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.coupon_used'));
|
||||
} else if (data['name'] == 'expired') {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.coupon_expired'));
|
||||
} else if (data['name'] == 'success') {
|
||||
this.events.publish('refreshpcontracts', 'data');
|
||||
this.navCtrl.navigateRoot('/contracts/contracted');
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.hire'));
|
||||
} else if (data['name'] == this.coupon) {
|
||||
this.discount = data['amount'] + (this.fee * (data['percentage'] / 100));
|
||||
if (this.discount > this.fee) this.discount = this.fee;
|
||||
this.final_amount = this.fee - this.discount;
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.coupon_valid'));
|
||||
}
|
||||
}, (error: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
});
|
||||
}
|
||||
|
||||
code_checker(ev: any) {
|
||||
if (ev.target.value.length > 4){
|
||||
console.log(ev.target.value.length);
|
||||
ev.target.value = ev.target.value.slice(0, 4);
|
||||
async createContract() {
|
||||
if (!this.selected_date) {
|
||||
this.alertService.presentToast('Selecciona una fecha para continuar.');
|
||||
return;
|
||||
}
|
||||
this.code_check = OpenPay.card.validateCVC(ev.target.value);
|
||||
console.log(this.code_check);
|
||||
}
|
||||
|
||||
selected_card(ev: any) {
|
||||
this.card_id = ev.target.value;
|
||||
console.log(this.card_id);
|
||||
if (this.card_id == "Agregar tarjeta") {
|
||||
console.log(this.card_id);
|
||||
ev.target.value = null;
|
||||
this.newcard();
|
||||
}
|
||||
}
|
||||
|
||||
checkCoupon() {
|
||||
if (this.coupon) {
|
||||
this.loadingCtrl.create().then((overlay) => {
|
||||
this.loading = overlay;
|
||||
this.loading.present();
|
||||
});
|
||||
this.ichambaService.checkCoupon(this.postulation_id, this.supplier_id, this.coupon).subscribe(
|
||||
data => {
|
||||
if (data) {
|
||||
if (data['name'] == "used") {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.coupon_used'));
|
||||
} else if (data['name'] == "expired") {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.coupon_expired'));
|
||||
} else if (data['name'] == this.coupon) {
|
||||
this.loading.dismiss();
|
||||
this.discount = (data['amount'] + (this.fee * (data['percentage']/100)));
|
||||
console.log(this.discount);
|
||||
if (this.discount > this.fee){
|
||||
this.discount = this.fee
|
||||
}
|
||||
this.final_amount = (this.fee - this.discount);
|
||||
console.log(this.final_amount);
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.coupon_valid'));
|
||||
} else if (data['name'] == "success") {
|
||||
this.loading.dismiss();
|
||||
this.events.publish('refreshpcontracts', 'data');
|
||||
this.navCtrl.navigateRoot('/contracts/contracted');
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.hire'));
|
||||
}
|
||||
if (this.paymentBypass) {
|
||||
this.loading = await this.loadingCtrl.create();
|
||||
await this.loading.present();
|
||||
this.ichambaService.createContract(this.postulation_id, this.supplier_id, 'BYPASS', this.selected_date, this.coupon).subscribe(
|
||||
(data: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
if (data['type'] == 'error') {
|
||||
this.alertService.presentToast(data['message']);
|
||||
} else {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.nocoupon'));
|
||||
this.events.publish('refreshpcontracts', 'data');
|
||||
this.navCtrl.navigateRoot('/contracts/contracted');
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.hire'));
|
||||
}
|
||||
}, error => {
|
||||
this.loading.dismiss();
|
||||
}, (error: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
createContract(form: NgForm) {
|
||||
if (this.card_id != "Agregar tarjeta") {
|
||||
if (this.code_check) {
|
||||
let code = form.value.code;
|
||||
let device_id = (<HTMLInputElement>document.getElementById('deviceDataId')).value;
|
||||
console.log(device_id);
|
||||
this.loading = await this.loadingCtrl.create();
|
||||
await this.loading.present();
|
||||
|
||||
this.loadingCtrl.create().then((overlay) => {
|
||||
this.loading = overlay;
|
||||
this.loading.present();
|
||||
});
|
||||
this.ichambaService.createContract(this.postulation_id, this.supplier_id, this.card_id, this.coupon, code, device_id).subscribe(
|
||||
data => {
|
||||
if (data['type'] == "error") {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast(data['message']);
|
||||
} else if (data['name'] == "used") {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('contracts.coupon_used'));
|
||||
} else if (data['name'] == "expired") {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('contracts.coupon_expired'));
|
||||
} else {
|
||||
this.loading.dismiss();
|
||||
this.events.publish('refreshpcontracts', 'data');
|
||||
this.navCtrl.navigateRoot('/contracts/contracted');
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.hire'));
|
||||
}
|
||||
}, error => {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
});
|
||||
//console.log(onSuccess.data.id);
|
||||
//console.log(document.getElementById('deviceDataId').value);
|
||||
try {
|
||||
const intentData = await firstValueFrom(
|
||||
this.ichambaService.getPaymentIntent(this.postulation_id, this.supplier_id, this.coupon)
|
||||
);
|
||||
|
||||
await Stripe.createPaymentSheet({
|
||||
paymentIntentClientSecret: intentData.client_secret,
|
||||
customerId: intentData.customer_id,
|
||||
customerEphemeralKeySecret: intentData.ephemeral_key,
|
||||
merchantDisplayName: 'JobHero',
|
||||
});
|
||||
|
||||
await this.loading.dismiss();
|
||||
|
||||
const result = await Stripe.presentPaymentSheet();
|
||||
|
||||
if (result.paymentResult !== PaymentSheetEventsEnum.Completed) return;
|
||||
|
||||
this.loading = await this.loadingCtrl.create();
|
||||
await this.loading.present();
|
||||
|
||||
this.ichambaService.createContract(
|
||||
this.postulation_id, this.supplier_id, intentData.payment_intent_id, this.selected_date!, this.coupon
|
||||
).subscribe({
|
||||
next: (data: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
if (data['type'] == 'error') {
|
||||
this.alertService.presentToast(data['message']);
|
||||
} else {
|
||||
this.events.publish('refreshpcontracts', 'data');
|
||||
this.navCtrl.navigateRoot('/contracts/contracted');
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.hire'));
|
||||
}
|
||||
},
|
||||
error: (error: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
await this.loading?.dismiss();
|
||||
if (error?.paymentResult !== PaymentSheetEventsEnum.Canceled) {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error'));
|
||||
}
|
||||
} else {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.select_card'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { DomSanitizer } from '@angular/platform-browser';
|
||||
selector: 'app-nohome',
|
||||
templateUrl: './nohome.page.html',
|
||||
styleUrls: ['./nohome.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class NohomePage implements OnInit {
|
||||
|
||||
@@ -81,7 +82,7 @@ export class NohomePage implements OnInit {
|
||||
|
||||
send() {
|
||||
this.ichambaService.noHomeConfirm(this.contract_id, this.myPosition.latitude, this.myPosition.longitude, this.description).subscribe(
|
||||
data => {
|
||||
(data: any) => {
|
||||
this.alertService.presentToast(data['message']);
|
||||
this.navCtrl.navigateRoot('/dashboard');
|
||||
}, error => {
|
||||
|
||||
@@ -6,7 +6,8 @@ import { Routes, RouterModule } from '@angular/router';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { PendingPage } from './pending.page';
|
||||
import { TranslateModule } from '@ngx-translate/core'
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RepostulatePageModule } from '../../postulations/repostulate/repostulate.module';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -21,7 +22,8 @@ const routes: Routes = [
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
TranslateModule
|
||||
TranslateModule,
|
||||
RepostulatePageModule,
|
||||
],
|
||||
declarations: [PendingPage]
|
||||
})
|
||||
|
||||
@@ -6,20 +6,49 @@
|
||||
<ion-title>{{'contracts.header' | translate}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<h2 text-capitalize style="padding-bottom: 0.5em">{{'contracts.header_1' | translate}}</h2>
|
||||
<ion-content class="ion-padding">
|
||||
<h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">{{'contracts.header_1' | 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 class="no-separator">
|
||||
<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-item>
|
||||
<ion-item class="no-separator">
|
||||
<ion-label>
|
||||
<h2 *ngIf="lang===true" text-capitalize>{{ pcontract.category }}</h2>
|
||||
<h2 *ngIf="lang===false" text-capitalize>{{ pcontract.en_category }}</h2>
|
||||
<p text-wrap>{{pcontract.address}}</p>
|
||||
<p text-wrap text-capitalize>{{pcontracts_dates[i]}}</p>
|
||||
<h2 *ngIf="lang===true" class="ion-text-capitalize">{{ pcontract.category }}</h2>
|
||||
<h2 *ngIf="lang===false" class="ion-text-capitalize">{{ pcontract.en_category }}</h2>
|
||||
<p class="ion-text-wrap">{{pcontract.address}}</p>
|
||||
<p class="ion-text-wrap ion-text-capitalize">{{pcontracts_dates[i]}}</p>
|
||||
</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-item>
|
||||
<div style="display: flex; justify-content: flex-end; gap: 8px; padding: 0 1em 1em">
|
||||
<ion-button fill="outline" color="medium" size="small" (click)="openRepostulate(pcontract)">
|
||||
<ion-icon slot="start" name="refresh-outline"></ion-icon>
|
||||
Repostular
|
||||
</ion-button>
|
||||
<ion-button fill="outline" color="danger" size="small" (click)="confirmCancel(pcontract)">
|
||||
<ion-icon slot="start" name="trash-outline"></ion-icon>
|
||||
Cancelar
|
||||
</ion-button>
|
||||
</div>
|
||||
</ion-card>
|
||||
</ng-container>
|
||||
</ion-content>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ModalController, MenuController, NavController } from '@ionic/angular';
|
||||
import { Component, ChangeDetectorRef } from '@angular/core';
|
||||
import { AlertController, ModalController, MenuController, NavController } from '@ionic/angular';
|
||||
import { EventService } from '../../../services/event.service';
|
||||
import { EnvService } from 'src/app/services/env.service';
|
||||
import { AuthService } from 'src/app/services/auth.service';
|
||||
@@ -7,20 +7,24 @@ 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 { RepostulateePage } from '../../postulations/repostulate/repostulate.page';
|
||||
|
||||
@Component({
|
||||
selector: 'app-pending',
|
||||
templateUrl: './pending.page.html',
|
||||
styleUrls: ['./pending.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class PendingPage implements OnInit {
|
||||
export class PendingPage {
|
||||
|
||||
pcontracts = [];
|
||||
pcontracts_dates = [];
|
||||
lang: boolean;
|
||||
pcontracts: any[] = [];
|
||||
pcontracts_dates: any[] = [];
|
||||
lang: boolean = false;
|
||||
loading = true;
|
||||
|
||||
constructor(
|
||||
private modalController: ModalController,
|
||||
private alertController: AlertController,
|
||||
private menu: MenuController,
|
||||
private navCtrl: NavController,
|
||||
private events: EventService,
|
||||
@@ -30,32 +34,29 @@ export class PendingPage implements OnInit {
|
||||
private translateService: TranslateService,
|
||||
private languageService: LanguageService,
|
||||
private env: EnvService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
) {
|
||||
this.events.subscribe('refreshpcontracts', (data) => {
|
||||
this.getpcontracts();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
ionViewWillEnter() {
|
||||
this.lang = this.languageService.getDefaultLanguage() === 'es';
|
||||
this.loading = true;
|
||||
this.getpcontracts();
|
||||
if (this.languageService.getDefaultLanguage() == 'es') {
|
||||
this.lang = true;
|
||||
} else {
|
||||
this.lang = false
|
||||
}
|
||||
}
|
||||
|
||||
refresh (event) {
|
||||
refresh(event: any) {
|
||||
this.ichambaService.getPendingcontracts().subscribe(
|
||||
data => {
|
||||
this.pcontracts = data;
|
||||
this.pcontracts_dates = [];
|
||||
for (var i of this.pcontracts) {
|
||||
if (this.languageService.getDefaultLanguage() == 'es') {
|
||||
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'}));
|
||||
} 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'}));
|
||||
}
|
||||
const locale = this.lang ? 'es-US' : 'en-US';
|
||||
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' }));
|
||||
}
|
||||
this.cdr.detectChanges();
|
||||
event.target.complete();
|
||||
}, error => {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
@@ -67,14 +68,15 @@ export class PendingPage implements OnInit {
|
||||
this.ichambaService.getPendingcontracts().subscribe(
|
||||
data => {
|
||||
this.pcontracts = data;
|
||||
this.pcontracts_dates = [];
|
||||
for (var i of this.pcontracts) {
|
||||
if (this.languageService.getDefaultLanguage() == 'es') {
|
||||
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'}));
|
||||
} 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'}));
|
||||
}
|
||||
const locale = this.lang ? 'es-US' : 'en-US';
|
||||
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' }));
|
||||
}
|
||||
this.loading = false;
|
||||
this.cdr.detectChanges();
|
||||
}, error => {
|
||||
this.loading = false;
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
});
|
||||
}
|
||||
@@ -83,4 +85,45 @@ export class PendingPage implements OnInit {
|
||||
this.navCtrl.navigateForward(['/viewsuppliers/', postulation_id]);
|
||||
}
|
||||
|
||||
async confirmCancel(postulation: any) {
|
||||
const alert = await this.alertController.create({
|
||||
header: 'Cancelar postulación',
|
||||
message: ' ',
|
||||
buttons: [
|
||||
{ text: 'No', role: 'cancel' },
|
||||
{
|
||||
text: 'Sí, cancelar',
|
||||
role: 'destructive',
|
||||
handler: () => this.doCancel(postulation.id)
|
||||
}
|
||||
]
|
||||
});
|
||||
await alert.present();
|
||||
alert.querySelector('.alert-message')!.innerHTML = '¿Estás seguro de que deseas cancelar esta postulación de <strong>' + postulation.category + '</strong>?';
|
||||
}
|
||||
|
||||
private doCancel(id: any) {
|
||||
this.ichambaService.cancelPostulation(id).subscribe({
|
||||
next: () => {
|
||||
this.alertService.presentToast('Postulación cancelada.');
|
||||
this.getpcontracts();
|
||||
},
|
||||
error: (error: any) => {
|
||||
this.alertService.presentToast('Error al cancelar: ' + error['status']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async openRepostulate(postulation: any) {
|
||||
const modal = await this.modalController.create({
|
||||
component: RepostulateePage,
|
||||
componentProps: { postulation },
|
||||
});
|
||||
await modal.present();
|
||||
const { data } = await modal.onDidDismiss();
|
||||
if (data?.reposted) {
|
||||
this.getpcontracts();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
<ion-title>Contratos</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<h2 text-capitalize style="padding-bottom: 0.5em">Reagendados</h2>
|
||||
<ion-content class="ion-padding">
|
||||
<h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">Reagendados</h2>
|
||||
<ng-container *ngFor="let ccontract of ccontracts; let i = index">
|
||||
<ion-card *ngIf="ccontract.status==2">
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<h2 text-capitalize>{{ ccontract.category }}</h2>
|
||||
<p text-wrap>{{ ccontract.address }}</p>
|
||||
<p text-wrap text-capitalize>{{ ccontracts_dates[i] }}</p>
|
||||
<p text-wrap text-capitalize>Proveedor: {{ ccontract.supplier }}</p>
|
||||
<h2 class="ion-text-capitalize">{{ ccontract.category }}</h2>
|
||||
<p class="ion-text-wrap">{{ ccontract.address }}</p>
|
||||
<p class="ion-text-wrap ion-text-capitalize">{{ ccontracts_dates[i] }}</p>
|
||||
<p class="ion-text-wrap ion-text-capitalize">Proveedor: {{ ccontract.supplier }}</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>
|
||||
</ion-label>
|
||||
<ion-button style="height: 3em; padding-left: 0.5em;" color="danger">Cancelar<br>contrato</ion-button>
|
||||
|
||||
@@ -10,11 +10,12 @@ import { AlertService } from 'src/app/services/alert.service';
|
||||
selector: 'app-redated',
|
||||
templateUrl: './redated.page.html',
|
||||
styleUrls: ['./redated.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class RedatedPage implements OnInit {
|
||||
|
||||
ccontracts = [];
|
||||
ccontracts_dates = [];
|
||||
ccontracts: any[] = [];
|
||||
ccontracts_dates: any[] = [];
|
||||
|
||||
constructor(
|
||||
private modalController: ModalController,
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<ion-title>{{'contracts.report_header' | translate}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<h2 text-center>{{'contracts.report_description' | translate}}</h2>
|
||||
<ion-content class="ion-padding">
|
||||
<h2 class="ion-text-center">{{'contracts.report_description' | translate}}</h2>
|
||||
<ion-item>
|
||||
<ion-textarea [(ngModel)]="comment">{{'contracts.report_commentaries' | translate}}</ion-textarea>
|
||||
</ion-item>
|
||||
|
||||
@@ -12,11 +12,12 @@ import { AlertService } from 'src/app/services/alert.service';
|
||||
selector: 'app-report',
|
||||
templateUrl: './report.page.html',
|
||||
styleUrls: ['./report.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class ReportPage implements OnInit {
|
||||
|
||||
private loading;
|
||||
contract_id: number = null;
|
||||
private loading: HTMLIonLoadingElement | null = null;
|
||||
contract_id: number | null = null;
|
||||
comment: any;
|
||||
|
||||
constructor(
|
||||
@@ -40,13 +41,13 @@ export class ReportPage implements OnInit {
|
||||
this.loading = overlay;
|
||||
this.loading.present();
|
||||
});
|
||||
this.ichambaService.reportContract(this.contract_id, this.comment).subscribe(
|
||||
data => {
|
||||
this.loading.dismiss();
|
||||
this.ichambaService.reportContract(this.contract_id!, this.comment).subscribe(
|
||||
(data: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.report_send'));
|
||||
this.navCtrl.navigateRoot('/dashboard');
|
||||
}, error => {
|
||||
this.loading.dismiss();
|
||||
}, (error: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<ion-title>{{'contracts.review_header' | translate}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<h2 text-center>{{'contracts.review_description' | translate}}</h2>
|
||||
<ion-content class="ion-padding">
|
||||
<h2 class="ion-text-center">{{'contracts.review_description' | translate}}</h2>
|
||||
<div class="rate_div">
|
||||
<rating [(ngModel)]="rate"
|
||||
readonly="false"
|
||||
|
||||
@@ -11,12 +11,13 @@ import { AlertService } from 'src/app/services/alert.service';
|
||||
selector: 'app-review',
|
||||
templateUrl: './review.page.html',
|
||||
styleUrls: ['./review.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class ReviewPage implements OnInit {
|
||||
|
||||
private loading;
|
||||
private loading: HTMLIonLoadingElement | null = null;
|
||||
rate: any;
|
||||
contract_id: number = null;
|
||||
contract_id: number | null = null;
|
||||
comment: any;
|
||||
|
||||
constructor(
|
||||
@@ -33,7 +34,7 @@ export class ReviewPage implements OnInit {
|
||||
this.contract_id = Number(this.activatedRoute.snapshot.paramMap.get('contract_id'));
|
||||
}
|
||||
|
||||
onRateChange(rating){
|
||||
onRateChange(rating: any){
|
||||
console.log("changed rating: " + rating);
|
||||
// do your stuff
|
||||
}
|
||||
@@ -44,13 +45,13 @@ export class ReviewPage implements OnInit {
|
||||
this.loading = overlay;
|
||||
this.loading.present();
|
||||
});
|
||||
this.ichambaService.reviewContract(this.contract_id, this.rate, this.comment).subscribe(
|
||||
data => {
|
||||
this.loading.dismiss();
|
||||
this.ichambaService.reviewContract(this.contract_id!, this.rate, this.comment).subscribe(
|
||||
(data: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(data['message']);
|
||||
this.navCtrl.navigateRoot('/dashboard');
|
||||
}, error => {
|
||||
this.loading.dismiss();
|
||||
}, (error: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -18,10 +18,10 @@ ion-input{
|
||||
<ion-title>{{'contracts.start_header' | translate}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<ion-content class="ion-padding">
|
||||
<form #form="ngForm" id="start_form" (ngSubmit)="startservice(form)" method="post">
|
||||
<h1 text-center>{{'contracts.start_description_1.1' | translate}}</h1>
|
||||
<ion-row text-center>
|
||||
<h1 class="ion-text-center">{{'contracts.start_description_1.1' | translate}}</h1>
|
||||
<ion-row class="ion-text-center">
|
||||
<ion-col>
|
||||
<ion-input ngModel name="otp1" #otp1 required maxLength="1" type="number" (keyup)="otpController($event,otp2,'')">
|
||||
</ion-input>
|
||||
@@ -37,7 +37,7 @@ ion-input{
|
||||
</ion-input>
|
||||
</ion-col>
|
||||
</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>
|
||||
<ion-button type="submit" expand="full" color="primary">{{'contracts.start_send' | translate}}</ion-button>
|
||||
</form>
|
||||
|
||||
@@ -10,11 +10,12 @@ import { AlertService } from 'src/app/services/alert.service';
|
||||
selector: 'app-start',
|
||||
templateUrl: './start.page.html',
|
||||
styleUrls: ['./start.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class StartPage implements OnInit {
|
||||
|
||||
private loading;
|
||||
pin: number = null;
|
||||
private loading: HTMLIonLoadingElement | null = null;
|
||||
pin: number | null = null;
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
@@ -28,7 +29,7 @@ export class StartPage implements OnInit {
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
otpController(event,next,prev){
|
||||
otpController(event: any, next: any, prev: any){
|
||||
if (event.target.value.length > 1){
|
||||
event.target.value = event.target.value.slice(-1);
|
||||
next.setFocus();
|
||||
@@ -49,18 +50,18 @@ export class StartPage implements OnInit {
|
||||
this.loading = overlay;
|
||||
this.loading.present();
|
||||
});
|
||||
this.ichambaService.startContract(this.pin).subscribe(
|
||||
data => {
|
||||
this.ichambaService.startContract(this.pin!).subscribe(
|
||||
(data: any) => {
|
||||
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");
|
||||
} else {
|
||||
this.loading.dismiss();
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(data['message']);
|
||||
this.navCtrl.navigateRoot('/dashboard');
|
||||
}
|
||||
}, error => {
|
||||
this.loading.dismiss();
|
||||
}, (error: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
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)
|
||||
@@ -72,19 +73,19 @@ export class StartPage implements OnInit {
|
||||
this.loading.present();
|
||||
});
|
||||
this.ichambaService.noHomeCheck().subscribe(
|
||||
data => {
|
||||
(data: any) => {
|
||||
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");
|
||||
} 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");
|
||||
} else {
|
||||
this.loading.dismiss();
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.navCtrl.navigateForward(['/nohome/', data['id']]);
|
||||
}
|
||||
}, error => {
|
||||
this.loading.dismiss();
|
||||
}, (error: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Component, OnInit } from '@angular/core';
|
||||
selector: 'app-comments',
|
||||
templateUrl: './comments.page.html',
|
||||
styleUrls: ['./comments.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class CommentsPage implements OnInit {
|
||||
|
||||
|
||||
@@ -6,27 +6,59 @@
|
||||
<ion-title>{{'contracts.viewsuppliers_header' | translate}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<h3 *ngIf="lang===true" text-capitalize>{{ pcontractssuppliers_category[0] }}</h3>
|
||||
<h3 *ngIf="lang===false" text-capitalize>{{ pcontractssuppliers_en_category[0] }}</h3>
|
||||
<p text-capitalize>{{ pcontractssuppliers_address[0] }}</p>
|
||||
<p text-capitalize style="margin-top: -0.6em">{{ pcontractssuppliers_dates[0] }}</p>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<h3 class="ion-text-capitalize">{{ headerCategory }}</h3>
|
||||
<p class="ion-text-capitalize" style="margin-top: -0.6em">{{ headerAddress }}</p>
|
||||
|
||||
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
|
||||
<ion-refresher-content></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<ion-card *ngFor="let pcontractsupplier of pcontractssuppliers; let i = index">
|
||||
<ion-card-header style="padding:0;height:8em" *ngIf="pcontractsupplier.supplier != null">
|
||||
<img style="display:block;width:100%;height:100%" src="{{ pcontractsupplier.cover_photo }}" *ngIf="pcontractsupplier.supplier != null"/>
|
||||
|
||||
<!-- Sort chips -->
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px">
|
||||
<ion-chip [color]="activeSort === 'date' ? 'primary' : 'medium'" (click)="sortBy('date')">
|
||||
<ion-icon name="calendar-outline"></ion-icon>
|
||||
<ion-label>Fecha más pronta</ion-label>
|
||||
</ion-chip>
|
||||
<ion-chip [color]="activeSort === 'fee' ? 'primary' : 'medium'" (click)="sortBy('fee')">
|
||||
<ion-icon name="cash-outline"></ion-icon>
|
||||
<ion-label>Más económico</ion-label>
|
||||
</ion-chip>
|
||||
<ion-chip [color]="activeSort === 'certified' ? 'primary' : 'medium'" (click)="sortBy('certified')">
|
||||
<ion-icon name="checkmark-circle-outline"></ion-icon>
|
||||
<ion-label>Certificados</ion-label>
|
||||
</ion-chip>
|
||||
</div>
|
||||
|
||||
<ion-card *ngFor="let s of sortedSuppliers">
|
||||
<ion-card-header style="padding:0;height:8em" *ngIf="s.supplier != null">
|
||||
<img style="display:block;width:100%;height:100%;object-fit:cover" [src]="s.cover_photo" *ngIf="s.cover_photo"/>
|
||||
</ion-card-header>
|
||||
<ion-item *ngIf="pcontractsupplier.supplier != null">
|
||||
<ion-item *ngIf="s.supplier != null">
|
||||
<ion-avatar slot="start">
|
||||
<img *ngIf="s.profile_photo" [src]="s.profile_photo" (error)="s.profile_photo=null">
|
||||
<ion-icon *ngIf="!s.profile_photo" name="person-circle" style="width:100%;height:100%;color:var(--ion-color-medium)"></ion-icon>
|
||||
</ion-avatar>
|
||||
<ion-label>
|
||||
<h2 text-wrap 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 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 text-wrap *ngIf="pcontractsupplier.supplier != null">{{'contracts.viewsuppliers_rate' | translate}}: {{ pcontractsupplier.score }} de 5 <ion-icon name="star"></ion-icon></p>
|
||||
<h2 class="ion-text-wrap ion-text-capitalize">{{ s.supplier }}</h2>
|
||||
<p class="ion-text-wrap">
|
||||
<ion-icon name="checkmark-circle" color="secondary" *ngIf="s.membership"></ion-icon>
|
||||
{{ s._certified }}
|
||||
</p>
|
||||
<p class="ion-text-wrap">{{'contracts.viewsuppliers_tags' | translate}}: {{ s.tags }}</p>
|
||||
<p class="ion-text-wrap">{{'contracts.viewsuppliers_fee' | translate}}: ${{ s.fee }}</p>
|
||||
<p class="ion-text-wrap" *ngIf="s.date1">
|
||||
<ion-icon name="calendar-outline"></ion-icon> {{ s._dateFormatted }}
|
||||
<span *ngIf="s.date2"> · opción 2 disponible</span>
|
||||
</p>
|
||||
<p class="ion-text-wrap">{{'contracts.viewsuppliers_rate' | translate}}: {{ s.score }} de 5 <ion-icon name="star"></ion-icon></p>
|
||||
</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(s.supplier_id, s.id, s.supplier, s.fee, s.date_1, s.date_2)">
|
||||
{{'contracts.viewsuppliers_hire' | translate}}
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
|
||||
</ion-content>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
|
||||
import { ModalController, MenuController, NavController } from '@ionic/angular';
|
||||
import { EventService } from '../../../services/event.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
@@ -14,19 +14,18 @@ import { HirePage } from '../hire/hire.page';
|
||||
selector: 'app-viewsuppliers',
|
||||
templateUrl: './viewsuppliers.page.html',
|
||||
styleUrls: ['./viewsuppliers.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class ViewsuppliersPage implements OnInit {
|
||||
|
||||
postulation_id = null;
|
||||
pcontractssuppliers = [];
|
||||
pcontractssuppliers_dates = [];
|
||||
pcontractssuppliers_certified = [];
|
||||
pcontractssuppliers_category = [];
|
||||
pcontractssuppliers_en_category = [];
|
||||
pcontractssuppliers_address = [];
|
||||
pcontractssuppliers_amount = [];
|
||||
pcontractssuppliers_fee = [];
|
||||
lang: boolean;
|
||||
postulation_id: string | null = null;
|
||||
suppliers: any[] = [];
|
||||
sortedSuppliers: any[] = [];
|
||||
headerCategory: string = '';
|
||||
headerAddress: string = '';
|
||||
headerDate: string = '';
|
||||
lang: boolean = false;
|
||||
activeSort: string = '';
|
||||
|
||||
constructor(
|
||||
private modalController: ModalController,
|
||||
@@ -41,68 +40,83 @@ export class ViewsuppliersPage implements OnInit {
|
||||
private translateService: TranslateService,
|
||||
private languageService: LanguageService,
|
||||
private env: EnvService,
|
||||
) { }
|
||||
private cdr: ChangeDetectorRef,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.postulation_id = this.activatedRoute.snapshot.paramMap.get('postulation_id');
|
||||
this.getPostulants(this.postulation_id);
|
||||
if (this.languageService.getDefaultLanguage() == 'es') {
|
||||
this.lang = true;
|
||||
} else {
|
||||
this.lang = false
|
||||
this.lang = this.languageService.getDefaultLanguage() === 'es';
|
||||
this.loadPostulants();
|
||||
}
|
||||
|
||||
refresh(event: any) {
|
||||
this.ichambaService.getPostulants(this.postulation_id ?? '').subscribe({
|
||||
next: (data: any) => {
|
||||
this.processSuppliers(data);
|
||||
event.target.complete();
|
||||
},
|
||||
error: () => {
|
||||
this.alertService.presentToast("Por favor contacte a soporte técnico.");
|
||||
event.target.complete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadPostulants() {
|
||||
this.ichambaService.getPostulants(this.postulation_id ?? '').subscribe({
|
||||
next: (data: any) => {
|
||||
this.processSuppliers(data);
|
||||
this.cdr.detectChanges();
|
||||
},
|
||||
error: () => {
|
||||
this.alertService.presentToast("Por favor contacte a soporte técnico.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
processSuppliers(data: any[]) {
|
||||
const locale = this.lang ? 'es-US' : 'en-US';
|
||||
this.suppliers = data.map(s => ({
|
||||
...s,
|
||||
_certified: this.lang ? (s.membership == 1 ? 'Certificado' : '') : (s.membership == 1 ? 'Certified' : ''),
|
||||
_dateFormatted: s.date1
|
||||
? new Date((new Date(s.date1).toLocaleString('en-US') + ' UTC').replace(',', '')).toLocaleDateString(locale, { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' })
|
||||
: ''
|
||||
}));
|
||||
if (this.suppliers.length > 0) {
|
||||
const first = this.suppliers[0];
|
||||
this.headerCategory = this.lang ? first.category : first.en_category;
|
||||
this.headerAddress = first.address;
|
||||
}
|
||||
this.sortedSuppliers = [...this.suppliers];
|
||||
if (this.activeSort) this.applySort(this.activeSort);
|
||||
}
|
||||
|
||||
refresh(event) {
|
||||
this.ichambaService.getPostulants(this.postulation_id).subscribe(
|
||||
data => {
|
||||
this.pcontractssuppliers = data;
|
||||
for (var i of this.pcontractssuppliers) {
|
||||
if (this.languageService.getDefaultLanguage() == 'es') {
|
||||
this.pcontractssuppliers_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.pcontractssuppliers_certified.push(i.membership == 1 ? 'Certificado' : '');
|
||||
} else {
|
||||
this.pcontractssuppliers_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.pcontractssuppliers_certified.push(i.membership == 1 ? 'Certified' : '');
|
||||
}
|
||||
this.pcontractssuppliers_category.push(i.category);
|
||||
this.pcontractssuppliers_en_category.push(i.en_category);
|
||||
this.pcontractssuppliers_address.push(i.address);
|
||||
this.pcontractssuppliers_amount.push(i.amount);
|
||||
this.pcontractssuppliers_fee.push(i.fee);
|
||||
}
|
||||
event.target.complete();
|
||||
}, error => {
|
||||
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
|
||||
event.target.complete();
|
||||
});
|
||||
sortBy(criteria: string) {
|
||||
if (this.activeSort === criteria) {
|
||||
this.activeSort = '';
|
||||
this.sortedSuppliers = [...this.suppliers];
|
||||
} else {
|
||||
this.activeSort = criteria;
|
||||
this.applySort(criteria);
|
||||
}
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
getPostulants(postulation_id: String) {
|
||||
this.ichambaService.getPostulants(postulation_id).subscribe(
|
||||
data => {
|
||||
this.pcontractssuppliers = data;
|
||||
for (var i of this.pcontractssuppliers) {
|
||||
if (this.languageService.getDefaultLanguage() == 'es') {
|
||||
this.pcontractssuppliers_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.pcontractssuppliers_certified.push(i.membership == 1 ? 'Certificado' : '');
|
||||
} else {
|
||||
this.pcontractssuppliers_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.pcontractssuppliers_certified.push(i.membership == 1 ? 'Certified' : '');
|
||||
}
|
||||
this.pcontractssuppliers_category.push(i.category);
|
||||
this.pcontractssuppliers_en_category.push(i.en_category);
|
||||
this.pcontractssuppliers_address.push(i.address);
|
||||
this.pcontractssuppliers_amount.push(i.amount);
|
||||
this.pcontractssuppliers_fee.push(i.fee);
|
||||
}
|
||||
}, error => {
|
||||
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
|
||||
});
|
||||
private applySort(criteria: string) {
|
||||
this.sortedSuppliers = [...this.suppliers].sort((a, b) => {
|
||||
if (criteria === 'date') {
|
||||
const da = a.date1 ? new Date(a.date1).getTime() : Infinity;
|
||||
const db = b.date1 ? new Date(b.date1).getTime() : Infinity;
|
||||
return da - db;
|
||||
}
|
||||
if (criteria === 'fee') return (a.fee ?? Infinity) - (b.fee ?? Infinity);
|
||||
if (criteria === 'certified') return (b.membership ?? 0) - (a.membership ?? 0);
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
hiresupplier(supplier_id: String, postulation_id: String, supplier: String, fee: Number) {
|
||||
this.router.navigate(['/hire', {supplier: supplier, supplier_id: supplier_id, postulation_id: postulation_id, fee: fee}]);
|
||||
hiresupplier(supplier_id: String, postulation_id: String, supplier: String, fee: Number, date_1: String, date_2: String) {
|
||||
this.router.navigate(['/hire', { supplier, supplier_id, postulation_id, fee, date_1, date_2 }]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ ion-item:active:after {
|
||||
<ion-title>{{'dashboard.header' | translate}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<ion-content class="ion-padding">
|
||||
<div class="autocomplete">
|
||||
<ion-searchbar
|
||||
(ionInput)="search($event)"
|
||||
@@ -42,47 +42,47 @@ ion-item:active:after {
|
||||
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
|
||||
<ion-refresher-content></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<ng-container *ngIf="user != undefined">
|
||||
<ng-container *ngIf="authService.isLoggedIn">
|
||||
<div *ngIf="!isContract">
|
||||
<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"/>
|
||||
<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>
|
||||
</div>
|
||||
</ng-container>
|
||||
<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>
|
||||
<ion-card *ngFor="let pcontract of pcontracts; let i = index">
|
||||
<ion-item>
|
||||
<ion-progress-bar type="indeterminate"></ion-progress-bar>
|
||||
<ion-label>
|
||||
<h2 text-capitalize>{{ pcontract.category }}</h2>
|
||||
<p text-wrap><ion-icon name="search"></ion-icon> {{'contracts.postulating' | translate}}</p>
|
||||
<p text-wrap>{{pcontract.address}}</p>
|
||||
<p text-wrap text-capitalize>{{pcontracts_dates[i]}}</p>
|
||||
<h2 class="ion-text-capitalize">{{ pcontract.category }}</h2>
|
||||
<ion-progress-bar type="indeterminate"></ion-progress-bar>
|
||||
<p class="ion-text-wrap"><ion-icon name="search"></ion-icon> {{'contracts.postulating' | translate}}</p>
|
||||
<p class="ion-text-wrap">{{pcontract.address}}</p>
|
||||
<p class="ion-text-wrap ion-text-capitalize">{{pcontracts_dates[i]}}</p>
|
||||
</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-item>
|
||||
</ion-card>
|
||||
<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 *ngFor="let ccontract of ccontracts; let i = index">
|
||||
<ion-card *ngIf="ccontract.status==1">
|
||||
<ion-item style="--border-color: #fff">
|
||||
<ion-label>
|
||||
<h2 *ngIf="lang===true" text-capitalize>{{ ccontract.category }}</h2>
|
||||
<h2 *ngIf="lang===false" text-capitalize>{{ ccontract.en_category }}</h2>
|
||||
<p text-wrap><ion-icon name="briefcase"></ion-icon> {{'contracts.hired' | translate}}</p>
|
||||
<p text-wrap>{{ ccontract.address }}</p>
|
||||
<p text-wrap text-capitalize>{{ ccontracts_dates[i] }}</p>
|
||||
<p text-wrap text-capitalize>{{'contracts.supplier' | translate}}: {{ ccontract.supplier }}</p>
|
||||
<p text-wrap>{{'contracts.phone' | translate}}: <a href="tel:{{ ccontract.phone }}">{{ ccontract.phone }}</a></p>
|
||||
<p text-wrap>{{'contracts.amount' | translate}}: ${{ ccontract.amount }}</p>
|
||||
<h2 text-wrap>{{'contracts.start_pin' | translate}}<b>{{ ccontract.code }}</b></h2>
|
||||
<p text-wrap>{{'contracts.info_pin' | translate}}</p>
|
||||
<h2 *ngIf="lang===true" class="ion-text-capitalize">{{ ccontract.category }}</h2>
|
||||
<h2 *ngIf="lang===false" class="ion-text-capitalize">{{ ccontract.en_category }}</h2>
|
||||
<p class="ion-text-wrap"><ion-icon name="briefcase"></ion-icon> {{'contracts.hired' | translate}}</p>
|
||||
<p class="ion-text-wrap">{{ ccontract.address }}</p>
|
||||
<p class="ion-text-wrap ion-text-capitalize">{{ ccontracts_dates[i] }}</p>
|
||||
<p class="ion-text-wrap ion-text-capitalize">{{'contracts.supplier' | translate}}: {{ ccontract.supplier }}</p>
|
||||
<p class="ion-text-wrap">{{'contracts.phone' | translate}}: <a href="tel:{{ ccontract.phone }}">{{ ccontract.phone }}</a></p>
|
||||
<p class="ion-text-wrap">{{'contracts.amount' | translate}}: ${{ ccontract.amount }}</p>
|
||||
<h2 class="ion-text-wrap">{{'contracts.start_pin' | translate}}<b>{{ ccontract.code }}</b></h2>
|
||||
<p class="ion-text-wrap">{{'contracts.info_pin' | translate}}</p>
|
||||
</ion-label>
|
||||
</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>
|
||||
|
||||
@@ -13,11 +13,13 @@ import { User } from 'src/app/models/user';
|
||||
selector: 'app-dashboard',
|
||||
templateUrl: './dashboard.page.html',
|
||||
styleUrls: ['./dashboard.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class DashboardPage implements OnInit {
|
||||
|
||||
private loading: any;
|
||||
user!: User;
|
||||
userName: string = '';
|
||||
categories: any[] = [];
|
||||
aux_categories: any[] = [];
|
||||
showList: boolean = false;
|
||||
@@ -31,7 +33,7 @@ export class DashboardPage implements OnInit {
|
||||
constructor(
|
||||
private menu: MenuController,
|
||||
private navCtrl: NavController,
|
||||
private authService: AuthService,
|
||||
public authService: AuthService,
|
||||
private ichambaService: IchambaService,
|
||||
private alertService: AlertService,
|
||||
private translateService: TranslateService,
|
||||
@@ -62,7 +64,7 @@ export class DashboardPage implements OnInit {
|
||||
this.getccontracts();
|
||||
}
|
||||
|
||||
refresh(event) {
|
||||
refresh(event: any) {
|
||||
this.getpcontracts();
|
||||
this.ichambaService.getCurrentcontracts().subscribe(
|
||||
data => {
|
||||
@@ -144,10 +146,10 @@ export class DashboardPage implements OnInit {
|
||||
this.presentAlert(id)
|
||||
} else { */
|
||||
this.ichambaService.cancelContract(id).subscribe(
|
||||
data => {
|
||||
(data: any) => {
|
||||
this.getccontracts();
|
||||
this.alertService.presentToast(data['message']);
|
||||
}, error => {
|
||||
}, (error: any) => {
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
});
|
||||
//}
|
||||
@@ -188,7 +190,7 @@ check(category_string: String) {
|
||||
this.navCtrl.navigateForward(['/category/', category_string])
|
||||
} else {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error'));
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.no_service'));
|
||||
}
|
||||
}, error => {
|
||||
this.loading.dismiss();
|
||||
@@ -198,12 +200,6 @@ check(category_string: String) {
|
||||
}
|
||||
|
||||
ionViewWillEnter() {
|
||||
this.authService.user().subscribe(
|
||||
user => {
|
||||
this.user = user;
|
||||
// TODO: Send OneSignal tag when Capacitor plugin is configured
|
||||
// OneSignal.sendTag("iChamba_ID", user['id'].toString());
|
||||
}
|
||||
);
|
||||
this.authService.user().subscribe({ error: () => {} });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</ion-header>
|
||||
|
||||
<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>
|
||||
<ion-item slot="header" color="light">
|
||||
@@ -18,49 +18,49 @@
|
||||
</ion-accordion>
|
||||
<ion-accordion>
|
||||
<ion-item slot="header" color="light">
|
||||
<ion-label>{{'faq.faq_2' | translate}}®</ion-label>
|
||||
<ion-label>{{'faq.faq_2' | translate}}</ion-label>
|
||||
</ion-item>
|
||||
<div class="ion-padding" slot="content">{{'faq.faq_2.1' | translate}}</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion>
|
||||
<ion-item slot="header" color="light">
|
||||
<ion-label>{{'faq.faq_3' | translate}}®</ion-label>
|
||||
<ion-label>{{'faq.faq_3' | translate}}</ion-label>
|
||||
</ion-item>
|
||||
<div class="ion-padding" slot="content">{{'faq.faq_3.1' | translate}}</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion>
|
||||
<ion-item slot="header" color="light">
|
||||
<ion-label>{{'faq.faq_4' | translate}}®</ion-label>
|
||||
<ion-label>{{'faq.faq_4' | translate}}</ion-label>
|
||||
</ion-item>
|
||||
<div class="ion-padding" slot="content">{{'faq.faq_4.1' | translate}}</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion>
|
||||
<ion-item slot="header" color="light">
|
||||
<ion-label>{{'faq.faq_5' | translate}}®</ion-label>
|
||||
<ion-label>{{'faq.faq_5' | translate}}</ion-label>
|
||||
</ion-item>
|
||||
<div class="ion-padding" slot="content">{{'faq.faq_5.1' | translate}}</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion>
|
||||
<ion-item slot="header" color="light">
|
||||
<ion-label>{{'faq.faq_6' | translate}}®</ion-label>
|
||||
<ion-label>{{'faq.faq_6' | translate}}</ion-label>
|
||||
</ion-item>
|
||||
<div class="ion-padding" slot="content">{{'faq.faq_6.1' | translate}}</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion>
|
||||
<ion-item slot="header" color="light">
|
||||
<ion-label>{{'faq.faq_7' | translate}}®</ion-label>
|
||||
<ion-label>{{'faq.faq_7' | translate}}</ion-label>
|
||||
</ion-item>
|
||||
<div class="ion-padding" slot="content">{{'faq.faq_7.1' | translate}}</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion>
|
||||
<ion-item slot="header" color="light">
|
||||
<ion-label>{{'faq.faq_8' | translate}}®</ion-label>
|
||||
<ion-label>{{'faq.faq_8' | translate}}</ion-label>
|
||||
</ion-item>
|
||||
<div class="ion-padding" slot="content">{{'faq.faq_8.1' | translate}}</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion>
|
||||
<ion-item slot="header" color="light">
|
||||
<ion-label>{{'faq.faq_9' | translate}}®</ion-label>
|
||||
<ion-label>{{'faq.faq_9' | translate}}</ion-label>
|
||||
</ion-item>
|
||||
<div class="ion-padding" slot="content">{{'faq.faq_9.1' | translate}}</div>
|
||||
</ion-accordion>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ion-accordion.accordion-expanding ion-item[slot='header'],
|
||||
ion-accordion.accordion-expanded ion-item[slot='header'] {
|
||||
--background: var(--ion-color-primary);
|
||||
--color: var(--ion-color-primary-contrast);
|
||||
--ion-color-base: var(--ion-color-primary) !important;
|
||||
--ion-color-contrast: var(--ion-color-primary-contrast) !important;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Component, OnInit } from '@angular/core';
|
||||
selector: 'app-faq',
|
||||
templateUrl: './faq.page.html',
|
||||
styleUrls: ['./faq.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class FaqPage implements OnInit {
|
||||
|
||||
|
||||
@@ -9,60 +9,124 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content padding>
|
||||
<ion-item>
|
||||
<ion-content class="ion-padding">
|
||||
<ion-item [class.ion-invalid]="(submitted || touched['name']) && !name" [class.ion-touched]="submitted || touched['name']">
|
||||
<ion-label position="floating">{{'hero.name' | translate}}</ion-label>
|
||||
<ion-input [(ngModel)]="name"></ion-input>
|
||||
<ion-input [(ngModel)]="name" (ionBlur)="markTouched('name')"></ion-input>
|
||||
</ion-item>
|
||||
<br>
|
||||
<span class="error-msg" *ngIf="(submitted || touched['name']) && !name">{{'hero.required' | translate}}</span>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>{{'hero.categories' | translate}}</ion-label>
|
||||
<ion-select [(ngModel)]="categories_input" multiple="true" okText="Aceptar" cancelText="Cancelar" placeholder="{{'hero.categories_placeholder' | translate}}">
|
||||
<ion-select-option *ngFor="let category of categories" [value]="category">{{category}}</ion-select-option>
|
||||
</ion-select>
|
||||
<ion-item [class.ion-invalid]="(submitted || touched['rfc']) && !rfc" [class.ion-touched]="submitted || touched['rfc']">
|
||||
<ion-label position="floating">RFC</ion-label>
|
||||
<ion-input [(ngModel)]="rfc" (ionBlur)="markTouched('rfc')"></ion-input>
|
||||
</ion-item>
|
||||
<br>
|
||||
<span class="error-msg" *ngIf="(submitted || touched['rfc']) && !rfc">{{'hero.required' | translate}}</span>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="floating">{{'hero.keywords' | translate}}</ion-label>
|
||||
<ion-input [(ngModel)]="keywords_text" placeholder="{{'hero.keywords_placeholder' | translate}}"></ion-input>
|
||||
</ion-item>
|
||||
<ion-note padding>{{'hero.keywords_hint' | translate}}</ion-note>
|
||||
|
||||
<ion-label color="light">_</ion-label>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="floating">Dirección</ion-label>
|
||||
<ion-input [(ngModel)]="addressAutocomplete" (ionChange)="autocomplete($event)" value="{{ myAddress }}" (ionFocus)="showlist()"></ion-input>
|
||||
</ion-item>
|
||||
<ion-list *ngIf="showif">
|
||||
<ion-item button=true (click)="geoloc(places.place_id, places.description, places.terms[1].value)" *ngFor="let places of placesSearch" class="place">
|
||||
{{ places.description }}
|
||||
<!-- CATEGORÍAS CON CHIPS -->
|
||||
<div class="dropdown-container">
|
||||
<ion-item class="tags-item" [class.ion-invalid]="(submitted || touched['categories']) && categories_input.length === 0" [class.ion-touched]="submitted || touched['categories']">
|
||||
<div class="tags-row">
|
||||
<span class="chip-section-label">{{'hero.categories' | translate}}</span>
|
||||
<ion-chip *ngFor="let cat of categories_input" color="primary">
|
||||
<ion-label>{{cat}}</ion-label>
|
||||
<ion-icon name="close-circle" (click)="removeCategory(cat)"></ion-icon>
|
||||
</ion-chip>
|
||||
<ion-input
|
||||
[(ngModel)]="categorySearchText"
|
||||
(ionInput)="filterCategories($event)"
|
||||
(ionBlur)="hideCategoryList()"
|
||||
[clearInput]="true"
|
||||
placeholder="{{'hero.categories_placeholder' | translate}}">
|
||||
</ion-input>
|
||||
</div>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<ion-item hidden>
|
||||
<span class="error-msg" *ngIf="(submitted || touched['categories']) && categories_input.length === 0">Selecciona al menos una categoría</span>
|
||||
<ion-list *ngIf="showCategoryDropdown && filteredCategories.length > 0" class="dropdown-list">
|
||||
<ion-item *ngFor="let category of filteredCategories" button (click)="selectCategory(category)">
|
||||
{{category}}
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</div>
|
||||
|
||||
<!-- PALABRAS CLAVE CON CHIPS -->
|
||||
<ion-item class="tags-item">
|
||||
<div class="tags-row">
|
||||
<span class="chip-section-label">{{'hero.keywords' | translate}}</span>
|
||||
<ion-chip *ngFor="let keyword of keywords" color="primary">
|
||||
<ion-label>{{keyword}}</ion-label>
|
||||
<ion-icon name="close-circle" (click)="removeKeyword(keyword)"></ion-icon>
|
||||
</ion-chip>
|
||||
<ion-input
|
||||
[(ngModel)]="keywordInput"
|
||||
(keydown)="onKeywordKeydown($event)"
|
||||
(ionBlur)="addKeyword($event)"
|
||||
placeholder="{{'hero.keywords_placeholder' | translate}}">
|
||||
</ion-input>
|
||||
</div>
|
||||
</ion-item>
|
||||
<ion-note class="ion-padding">{{'hero.keywords_hint' | translate}}</ion-note>
|
||||
|
||||
<!-- DIRECCIÓN CON AUTOCOMPLETE FLOTANTE -->
|
||||
<div class="dropdown-container">
|
||||
<ion-item [class.ion-invalid]="(submitted || touched['address']) && !myAddress" [class.ion-touched]="submitted || touched['address']">
|
||||
<ion-label position="floating">{{'categories.address' | translate}}</ion-label>
|
||||
<ion-input [(ngModel)]="addressAutocomplete" (ionInput)="autocomplete($event)" (ionFocus)="showlist()" (ionBlur)="blurAddressList()" [clearInput]="true"></ion-input>
|
||||
</ion-item>
|
||||
<span class="error-msg" *ngIf="(submitted || touched['address']) && !myAddress">{{'alerts.valid_address' | translate}}</span>
|
||||
<ion-list *ngIf="showif && placesSearch?.length" class="dropdown-list">
|
||||
<ion-item button=true (click)="geoloc(places.place_id, places.description, places.terms[1].value)" *ngFor="let places of placesSearch" class="place">
|
||||
{{ places.description }}
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</div>
|
||||
|
||||
<ion-item class="ion-hide">
|
||||
<ion-label position="fixed">Latitud</ion-label>
|
||||
<ion-input value="{{ myPosition.latitude }}" disabled></ion-input>
|
||||
</ion-item>
|
||||
<ion-item hidden>
|
||||
<ion-item class="ion-hide">
|
||||
<ion-label position="fixed">Longitud</ion-label>
|
||||
<ion-input value="{{ myPosition.longitude }}" disabled></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>{{'hero.discover' | translate}}</ion-label>
|
||||
<ion-select value={selectedReference} okText="Aceptar" cancelText="Cancelar" (ionChange)="selected_reference($event)">
|
||||
<ion-select-option value="1">{{'hero.radio' | translate}}</ion-select-option>
|
||||
<ion-select-option value="2">{{'hero.tv' | translate}}</ion-select-option>
|
||||
<ion-select-option value="3">{{'hero.social' | translate}}</ion-select-option>
|
||||
<ion-select-option value="4">{{'hero.friends' | translate}}</ion-select-option>
|
||||
<ion-select-option value="5">{{'hero.other' | translate}}</ion-select-option>
|
||||
|
||||
<ion-item [class.ion-invalid]="(submitted || touched['bank']) && selectedBank === null" [class.ion-touched]="submitted || touched['bank']">
|
||||
<ion-label>{{'hero.bank' | translate}}</ion-label>
|
||||
<ion-select [(ngModel)]="selectedBank" placeholder="{{'hero.select' | translate}}" okText="Aceptar" cancelText="Cancelar" (ionBlur)="markTouched('bank')">
|
||||
<ion-select-option *ngFor="let bank of banks" [value]="bank.id">{{bank.name}}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="showinput">
|
||||
<ion-label position="floating">{{'hero.discover_details' | translate}}</ion-label>
|
||||
<ion-input [(ngModel)]="reference"></ion-input>
|
||||
<span class="error-msg" *ngIf="(submitted || touched['bank']) && selectedBank === null">{{'hero.required' | translate}}</span>
|
||||
|
||||
<ion-item [class.ion-invalid]="(submitted || touched['bankAccount']) && !bankAccount" [class.ion-touched]="submitted || touched['bankAccount']">
|
||||
<ion-label position="floating">{{'hero.bank_account' | translate}}</ion-label>
|
||||
<ion-input [(ngModel)]="bankAccount" type="number" (ionBlur)="markTouched('bankAccount')"></ion-input>
|
||||
</ion-item>
|
||||
<span class="error-msg" *ngIf="(submitted || touched['bankAccount']) && !bankAccount">{{'hero.required' | translate}}</span>
|
||||
|
||||
<ion-item [class.ion-invalid]="(submitted || touched['fee']) && fee === null" [class.ion-touched]="submitted || touched['fee']">
|
||||
<ion-label position="floating">{{'hero.fee' | translate}}</ion-label>
|
||||
<ion-input [(ngModel)]="feeDisplay" (ionInput)="onFeeInput($event)" (ionBlur)="markTouched('fee')" type="text" inputmode="decimal"></ion-input>
|
||||
</ion-item>
|
||||
<span class="error-msg" *ngIf="(submitted || touched['fee']) && fee === null">{{'hero.required' | translate}}</span>
|
||||
|
||||
<ion-item [class.ion-invalid]="(submitted || touched['reference']) && !selectedReference" [class.ion-touched]="submitted || touched['reference']">
|
||||
<ion-label>{{'hero.discover' | translate}}</ion-label>
|
||||
<ion-select [(ngModel)]="selectedReference" placeholder="{{'hero.select' | translate}}" okText="Aceptar" cancelText="Cancelar" (ionChange)="selected_reference($event)" (ionBlur)="markTouched('reference')">
|
||||
<ion-select-option [value]="1">{{'hero.radio' | translate}}</ion-select-option>
|
||||
<ion-select-option [value]="2">{{'hero.tv' | translate}}</ion-select-option>
|
||||
<ion-select-option [value]="3">{{'hero.social' | translate}}</ion-select-option>
|
||||
<ion-select-option [value]="4">{{'hero.friends' | translate}}</ion-select-option>
|
||||
<ion-select-option [value]="5">{{'hero.other' | translate}}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<span class="error-msg" *ngIf="(submitted || touched['reference']) && !selectedReference">{{'hero.required' | translate}}</span>
|
||||
|
||||
<ion-item *ngIf="showinput" [class.ion-invalid]="(submitted || touched['referenceDetail']) && !reference" [class.ion-touched]="submitted || touched['referenceDetail']">
|
||||
<ion-label position="floating">{{'hero.discover_details' | translate}}</ion-label>
|
||||
<ion-input [(ngModel)]="reference" (ionBlur)="markTouched('referenceDetail')"></ion-input>
|
||||
</ion-item>
|
||||
<span class="error-msg" *ngIf="(submitted || touched['referenceDetail']) && showinput && !reference">{{'hero.required' | translate}}</span>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
<ion-button type="submit" expand="full" color="secondary" (click)="addHero()">{{'hero.signup' | translate}}</ion-button>
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
.tags-item {
|
||||
--inner-padding-end: 8px;
|
||||
--padding-top: 4px;
|
||||
--padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.chip-section-label {
|
||||
flex-basis: 100%;
|
||||
font-size: 12px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.tags-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 0 0 6px;
|
||||
width: 100%;
|
||||
|
||||
ion-chip {
|
||||
margin: 0;
|
||||
height: 30px;
|
||||
flex-shrink: 0;
|
||||
|
||||
ion-label {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
ion-icon {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
ion-input {
|
||||
min-width: 120px;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropdown-list {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
z-index: 100;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--ion-color-light-shade);
|
||||
border-radius: 8px;
|
||||
background: var(--ion-background-color, #fff);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
|
||||
ion-item {
|
||||
--min-height: 40px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
--background: var(--ion-color-light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ion-item.ion-invalid.ion-touched {
|
||||
--border-color: var(--ion-color-danger);
|
||||
--highlight-color-invalid: var(--ion-color-danger);
|
||||
|
||||
ion-label {
|
||||
color: var(--ion-color-danger) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--ion-color-danger);
|
||||
padding: 2px 16px 4px;
|
||||
}
|
||||
|
||||
ion-chip[color="primary"] {
|
||||
--background: var(--ion-color-primary);
|
||||
--color: var(--ion-color-primary-contrast);
|
||||
}
|
||||
|
||||
ion-chip[color="secondary"] {
|
||||
--background: var(--ion-color-secondary);
|
||||
--color: var(--ion-color-secondary-contrast);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Component, OnInit, NgZone } from '@angular/core';
|
||||
import { NavController, LoadingController } from '@ionic/angular';
|
||||
import { AuthService } from 'src/app/services/auth.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { LanguageService } from 'src/app/services/language.service';
|
||||
import { IchambaService } from 'src/app/services/ichamba.service';
|
||||
import { AlertService } from 'src/app/services/alert.service';
|
||||
|
||||
@@ -10,6 +12,7 @@ declare var google: any;
|
||||
selector: 'app-hero',
|
||||
templateUrl: './hero.page.html',
|
||||
styleUrls: ['./hero.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class HeroPage implements OnInit {
|
||||
|
||||
@@ -17,139 +20,224 @@ export class HeroPage implements OnInit {
|
||||
categories: any[] = [];
|
||||
categories_input: any[] = [];
|
||||
categories_rearranged: any[] = [];
|
||||
keywords: any[] = [];
|
||||
keywords_string: any[] = [];
|
||||
keywords_text: string = '';
|
||||
aux_categories: any[] = [];
|
||||
filteredCategories: any[] = [];
|
||||
categorySearchText: string = '';
|
||||
showCategoryDropdown: boolean = false;
|
||||
keywords: string[] = [];
|
||||
keywordInput: string = '';
|
||||
myPosition: any = {};
|
||||
myAddress: string | null = null;
|
||||
myIntnumber: string | null = null;
|
||||
banks: any[] = [];
|
||||
reference: string | null = null;
|
||||
name: string | null = null;
|
||||
rfc: string | null = null;
|
||||
selectedBank: number | null = null;
|
||||
bankAccount: number | null = null;
|
||||
fee: number | null = null;
|
||||
feeDisplay: string = '';
|
||||
selectedReference: number = 0;
|
||||
addressAutocomplete: string = '';
|
||||
placesSearch: any = '';
|
||||
showinput: boolean = false;
|
||||
showif = true;
|
||||
submitted: boolean = false;
|
||||
touched: { [key: string]: boolean } = {};
|
||||
|
||||
constructor(
|
||||
private navCtrl: NavController,
|
||||
private loadingCtrl: LoadingController,
|
||||
private authService: AuthService,
|
||||
private alertService: AlertService,
|
||||
private translateService: TranslateService,
|
||||
private languageService: LanguageService,
|
||||
private ichambaService: IchambaService,
|
||||
private ngZone: NgZone,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
/*this.categories = ["carpintero","jardinero","abogado","administrador","agente inmobiliario","agente de seguros","albañil",
|
||||
"arquitecto","asistente personal","becario","cerrajero","chef","chofer","contratista","contador","diseñador de interiores",
|
||||
"cantante","diseñador grafico","edecan","electricista","estilista","servicios financieros","fontanero","fotografo","produccion de videos",
|
||||
"hostess","lavado de tapicerias","aire acondicionado","almacenista","ayudante","azulejero","cerrajero automotriz","cortinas metalicas",
|
||||
"electrico automotriz","entretenimiento","fisioterapeuta","grua","herrero","ingeniero civil","laminado automotriz","limpieza","limpieza del hogar",
|
||||
"mantenimiento","mariachi","masajista","marmolero","mecanico","mercadotecnia","mesero","modelo","musico","niñera","pintor","pintura automotriz",
|
||||
"plomero","programador","publicista","recepcionista","remodelacion","repartidor","reparacion de celulares","reparacion de electronicos","soldador",
|
||||
"tabla roquero","tapicero","tecnico en gas","tuneame la nave","traductor","tutor","vendedor","veterinario","vidrio y aluminio","reparacion de computadoras",
|
||||
"mantenimiento de camiones","consultor","capacitacion","maestro","barbero","agencia de colocacion","agencia de viajes","paseador de perros","banquete",
|
||||
"almacenaje","impermeabilizacion","redes de internet"];*/
|
||||
this.ichambaService.getCategories()
|
||||
.subscribe( categories => {
|
||||
this.categories = categories;
|
||||
this.aux_categories = categories;
|
||||
this.filteredCategories = categories;
|
||||
})
|
||||
this.ichambaService.getBanks()
|
||||
.subscribe( banks => {
|
||||
this.banks = banks;
|
||||
})
|
||||
}
|
||||
|
||||
// ========== CATEGORÍAS CON CHIPS ==========
|
||||
filterCategories(event: any) {
|
||||
const searchTerm = (event.detail?.value || event.target?.value || '').toLowerCase();
|
||||
this.categorySearchText = searchTerm;
|
||||
if (searchTerm.length > 0) {
|
||||
this.filteredCategories = this.categories.filter(cat =>
|
||||
cat.toLowerCase().includes(searchTerm) && !this.categories_input.includes(cat)
|
||||
);
|
||||
this.showCategoryDropdown = true;
|
||||
} else {
|
||||
this.filteredCategories = this.categories.filter(cat => !this.categories_input.includes(cat));
|
||||
this.showCategoryDropdown = false;
|
||||
}
|
||||
}
|
||||
|
||||
selectCategory(category: string) {
|
||||
if (!this.categories_input.includes(category)) {
|
||||
this.categories_input.push(category);
|
||||
}
|
||||
this.categorySearchText = '';
|
||||
this.showCategoryDropdown = false;
|
||||
this.filteredCategories = this.categories.filter(cat => !this.categories_input.includes(cat));
|
||||
}
|
||||
|
||||
removeCategory(category: string) {
|
||||
const index = this.categories_input.indexOf(category);
|
||||
if (index > -1) {
|
||||
this.categories_input.splice(index, 1);
|
||||
}
|
||||
this.filteredCategories = this.categories.filter(cat => !this.categories_input.includes(cat));
|
||||
}
|
||||
|
||||
hideCategoryList() {
|
||||
setTimeout(() => {
|
||||
this.showCategoryDropdown = false;
|
||||
this.touched['categories'] = true;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// ========== PALABRAS CLAVE CON CHIPS ==========
|
||||
addKeyword(event?: any) {
|
||||
this.touched['keywords'] = true;
|
||||
const value = this.keywordInput?.trim();
|
||||
if (value && value.length > 0) {
|
||||
// Separar por comas si hay varias palabras
|
||||
const newKeywords = value.split(',').map((k: string) => k.trim()).filter((k: string) => k.length > 0);
|
||||
newKeywords.forEach((keyword: string) => {
|
||||
if (!this.keywords.includes(keyword)) {
|
||||
this.keywords.push(keyword);
|
||||
}
|
||||
});
|
||||
this.keywordInput = '';
|
||||
}
|
||||
}
|
||||
|
||||
onKeywordKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter' || event.key === ',') {
|
||||
event.preventDefault();
|
||||
this.addKeyword();
|
||||
} else if (event.key === 'Backspace' && !this.keywordInput && this.keywords.length > 0) {
|
||||
this.keywords.pop();
|
||||
}
|
||||
}
|
||||
|
||||
removeKeyword(keyword: string) {
|
||||
const index = this.keywords.indexOf(keyword);
|
||||
if (index > -1) {
|
||||
this.keywords.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
dismissHero() {
|
||||
this.navCtrl.navigateRoot('/landing');
|
||||
this.navCtrl.navigateRoot('/dashboard');
|
||||
}
|
||||
|
||||
autocomplete(ev: any) {
|
||||
if (!this.addressAutocomplete.trim().length) {
|
||||
const value = (ev.detail?.value ?? this.addressAutocomplete).trim();
|
||||
this.myAddress = null;
|
||||
if (!value.length) {
|
||||
this.placesSearch = null;
|
||||
return;
|
||||
}
|
||||
console.log(this.addressAutocomplete)
|
||||
new google.maps.places.AutocompleteService().getPredictions({ input: this.addressAutocomplete }, predictions => {
|
||||
new google.maps.places.AutocompleteService().getPredictions({ input: value }, (predictions: any) => {
|
||||
this.ngZone.run(() => {
|
||||
this.placesSearch = predictions;
|
||||
console.log(predictions);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
geoloc(place_id: string, place_description: string, place_intnumber: string) {
|
||||
this.myAddress = place_description;
|
||||
this.addressAutocomplete = place_description;
|
||||
this.myIntnumber = place_intnumber;
|
||||
console.log(place_id);
|
||||
this.placesSearch = null;
|
||||
this.hidelist();
|
||||
new google.maps.Geocoder().geocode({ placeId: place_id }, coordinates => {
|
||||
new google.maps.Geocoder().geocode({ placeId: place_id }, (coordinates: any) => {
|
||||
this.ngZone.run(() => {
|
||||
console.log(coordinates[0].geometry.location.lat() + ", " + coordinates[0].geometry.location.lng());
|
||||
const result = coordinates[0];
|
||||
this.myPosition = {
|
||||
latitude: coordinates[0].geometry.location.lat(),
|
||||
longitude: coordinates[0].geometry.location.lng()
|
||||
}
|
||||
latitude: result.geometry.location.lat(),
|
||||
longitude: result.geometry.location.lng()
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
selected_reference(ev: any) {
|
||||
console.log(ev.target.value);
|
||||
this.selectedReference = ev.target.value;
|
||||
if (this.selectedReference == 5) {
|
||||
this.showinput = true;
|
||||
} else {
|
||||
this.showinput = false;
|
||||
}
|
||||
this.showinput = ev.detail.value === 5;
|
||||
}
|
||||
|
||||
onFeeInput(ev: any) {
|
||||
const raw = (ev.detail?.value ?? '').replace(/[^0-9.]/g, '');
|
||||
const parts = raw.split('.');
|
||||
const integer = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
const decimal = parts.length > 1 ? '.' + parts[1].slice(0, 2) : '';
|
||||
this.feeDisplay = raw ? `$${integer}${decimal}` : '';
|
||||
this.fee = raw ? parseFloat(raw) : null;
|
||||
}
|
||||
|
||||
addHero(){
|
||||
// categories_input is now an array of values directly from ion-select
|
||||
this.categories_rearranged = this.categories_input.slice();
|
||||
this.submitted = true;
|
||||
const categoriesString = this.categories_input.join(',');
|
||||
const keywordsString = this.keywords.length > 0 ? this.keywords.join(', ') : '';
|
||||
|
||||
// keywords_text is now a string, split by comma
|
||||
if (this.keywords_text && this.keywords_text.trim().length > 0) {
|
||||
this.keywords_string = this.keywords_text.split(',').map(k => k.trim()).filter(k => k.length > 0);
|
||||
}
|
||||
|
||||
if (this.name && this.categories_input && this.myAddress && this.myPosition.latitude && this.myPosition.longitude && this.selectedReference){
|
||||
if (this.selectedReference == 5 && this.reference) {
|
||||
this.loadingCtrl.create().then((overlay) => {
|
||||
this.loading = overlay;
|
||||
this.loading.present();
|
||||
});
|
||||
|
||||
this.ichambaService.addHero(this.name, this.categories_rearranged.join(','), (this.keywords_string.join(', ') == "" ? null : this.keywords_string.join(', ')), this.myAddress, this.myPosition.latitude, this.myPosition.longitude, this.selectedReference, this.reference).subscribe(
|
||||
data => {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast(data['message']);
|
||||
this.navCtrl.navigateRoot('/dashboard');
|
||||
}, error => {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
|
||||
});
|
||||
} else if (this.selectedReference !== 5) {
|
||||
this.loadingCtrl.create().then((overlay) => {
|
||||
this.loading = overlay;
|
||||
this.loading.present();
|
||||
});
|
||||
|
||||
this.ichambaService.addHero(this.name, this.categories_rearranged.join(','), (this.keywords_string.join(', ') == "" ? null : this.keywords_string.join(', ')), this.myAddress, this.myPosition.latitude, this.myPosition.longitude, this.selectedReference, this.reference).subscribe(
|
||||
data => {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast(data['message']);
|
||||
this.navCtrl.navigateRoot('/dashboard');
|
||||
}, error => {
|
||||
this.loading.dismiss();
|
||||
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
|
||||
});
|
||||
} else {
|
||||
if (
|
||||
this.name &&
|
||||
this.rfc &&
|
||||
this.categories_input.length > 0 &&
|
||||
this.myAddress &&
|
||||
this.myPosition.latitude &&
|
||||
this.myPosition.longitude &&
|
||||
this.selectedBank !== null &&
|
||||
this.bankAccount &&
|
||||
this.fee !== null &&
|
||||
this.selectedReference
|
||||
) {
|
||||
if (this.selectedReference === 5 && !this.reference) {
|
||||
this.alertService.presentToast("Por favor, específique cómo supo de nosotros");
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadingCtrl.create().then((overlay) => {
|
||||
this.loading = overlay;
|
||||
this.loading.present();
|
||||
});
|
||||
|
||||
this.ichambaService.addHero(this.name!, this.rfc!, categoriesString, keywordsString, this.myAddress!, this.myPosition.latitude, this.myPosition.longitude, this.selectedBank ?? 0, this.bankAccount ?? 0, this.fee ?? 0, this.selectedReference, this.reference ?? '').subscribe(
|
||||
(data: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(data['message']);
|
||||
this.navCtrl.navigateRoot('/dashboard');
|
||||
}, (error: any) => {
|
||||
if (this.loading) this.loading.dismiss();
|
||||
this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
|
||||
});
|
||||
} else {
|
||||
this.alertService.presentToast("Llene todos los datos solicitados");
|
||||
}
|
||||
}
|
||||
|
||||
markTouched(field: string) {
|
||||
this.touched[field] = true;
|
||||
}
|
||||
|
||||
blurAddressList() {
|
||||
setTimeout(() => {
|
||||
this.hidelist();
|
||||
this.touched['address'] = true;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
showlist() {
|
||||
this.showif = true;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ ion-footer {
|
||||
--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 {
|
||||
--min-height: 60px;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ register();
|
||||
selector: 'app-landing',
|
||||
templateUrl: './landing.page.html',
|
||||
styleUrls: ['./landing.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class LandingPage implements OnInit {
|
||||
|
||||
|
||||
@@ -6,24 +6,43 @@
|
||||
<ion-title>Postulaciones</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<h2 text-capitalize style="padding-bottom: 0.5em">Ya contratado</h2>
|
||||
<ion-content class="ion-padding">
|
||||
<h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">Ya contratado</h2>
|
||||
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
|
||||
<ion-refresher-content></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<ng-container *ngFor="let postulation of postulations; let i = index">
|
||||
<ion-card>
|
||||
<ion-item>
|
||||
|
||||
<ng-container *ngIf="loading">
|
||||
<ion-card *ngFor="let _ of [1,2]">
|
||||
<ion-item style="--border-color: #fff">
|
||||
<ion-label>
|
||||
<h2 text-capitalize>{{ postulation.category }}</h2>
|
||||
<ion-text color="primary"><p text-wrap (click)="openMaps(postulation.lat, postulation.lng)"><b><u>{{postulation.address}}</u></b></p></ion-text>
|
||||
<p text-wrap>Télefono: <a href="tel:{{ postulation.phone }}">{{ postulation.phone }}</a></p>
|
||||
<p text-wrap>Referencias: {{postulation.references}}</p>
|
||||
<p text-wrap text-capitalize>{{postulations_dates[i]}}</p>
|
||||
<p text-wrap>Monto: {{postulation.amount}}</p>
|
||||
<p text-wrap>Detalles: {{postulation.details}}</p>
|
||||
<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">
|
||||
<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>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, ChangeDetectorRef } from '@angular/core';
|
||||
import { MenuController, NavController } from '@ionic/angular';
|
||||
import { EventService } from '../../../services/event.service';
|
||||
import { EnvService } from 'src/app/services/env.service';
|
||||
@@ -11,11 +11,13 @@ import { Browser } from '@capacitor/browser';
|
||||
selector: 'app-already',
|
||||
templateUrl: './already.page.html',
|
||||
styleUrls: ['./already.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class AlreadyPage implements OnInit {
|
||||
export class AlreadyPage {
|
||||
|
||||
postulations: any[] = [];
|
||||
postulations_dates: any[] = [];
|
||||
loading = true;
|
||||
|
||||
constructor(
|
||||
private menu: MenuController,
|
||||
@@ -25,13 +27,15 @@ export class AlreadyPage implements OnInit {
|
||||
private alertService: AlertService,
|
||||
private ichambaService: IchambaService,
|
||||
private env: EnvService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
) {
|
||||
this.events.subscribe('refreshpostulations', (data) => {
|
||||
this.getpostulations();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
ionViewWillEnter() {
|
||||
this.loading = true;
|
||||
this.getpostulations();
|
||||
}
|
||||
|
||||
@@ -39,9 +43,11 @@ export class AlreadyPage implements OnInit {
|
||||
this.ichambaService.getContractedPostulation().subscribe(
|
||||
data => {
|
||||
this.postulations = data;
|
||||
this.postulations_dates = [];
|
||||
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();
|
||||
}, error => {
|
||||
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(
|
||||
data => {
|
||||
this.postulations = data;
|
||||
this.postulations_dates = [];
|
||||
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 => {
|
||||
this.loading = false;
|
||||
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,16 +2,12 @@ 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 { CurrentPage } from './current.page';
|
||||
import { PostulateOfferPageModule } from '../postulate-offer/postulate-offer.module';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: CurrentPage
|
||||
}
|
||||
{ path: '', component: CurrentPage }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
@@ -19,7 +15,8 @@ const routes: Routes = [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes)
|
||||
RouterModule.forChild(routes),
|
||||
PostulateOfferPageModule,
|
||||
],
|
||||
declarations: [CurrentPage]
|
||||
})
|
||||
|
||||
@@ -6,24 +6,48 @@
|
||||
<ion-title>Postulaciones</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<h2 text-capitalize style="padding-bottom: 0.5em">Por postularse</h2>
|
||||
<ion-content class="ion-padding">
|
||||
<h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">Por postularse</h2>
|
||||
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
|
||||
<ion-refresher-content></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<ng-container *ngFor="let postulation of postulations; let i = index">
|
||||
<ion-card *ngIf="!postulation.already_post">
|
||||
<ion-item>
|
||||
|
||||
<ng-container *ngIf="loading">
|
||||
<ion-card *ngFor="let _ of [1,2]">
|
||||
<ion-item class="no-separator">
|
||||
<ion-label>
|
||||
<h2 text-capitalize>{{ postulation.category }}</h2>
|
||||
<ion-text color="primary"><p text-wrap (click)="openMaps(postulation.lat, postulation.lng)"><b><u>{{postulation.address}}</u></b></p></ion-text>
|
||||
<p text-wrap>Referencias: {{postulation.references}}</p>
|
||||
<p text-wrap text-capitalize>{{postulations_dates[i]}}</p>
|
||||
<p text-wrap>Detalles: {{postulation.details}}</p>
|
||||
<p text-wrap>Tiempo restante: {{postulation.time_limit}} minutos</p>
|
||||
<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: 75%"></ion-skeleton-text>
|
||||
<ion-skeleton-text [animated]="true" style="width: 80%"></ion-skeleton-text>
|
||||
<ion-skeleton-text [animated]="true" style="width: 45%"></ion-skeleton-text>
|
||||
</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-card>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!loading">
|
||||
<ng-container *ngFor="let postulation of postulations; let i = index">
|
||||
<ion-card *ngIf="!postulation.already_post && postulation._timer !== 'Expirada'">
|
||||
<ion-item class="no-separator">
|
||||
<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">
|
||||
<ion-text [color]="postulation._timerCritical ? 'danger' : 'medium'">
|
||||
<ion-icon name="time-outline" style="vertical-align: middle"></ion-icon>
|
||||
{{ postulation._timer }}
|
||||
</ion-text>
|
||||
</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>
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MenuController, NavController } from '@ionic/angular';
|
||||
import { Component, ChangeDetectorRef } from '@angular/core';
|
||||
import { MenuController, NavController, ModalController } from '@ionic/angular';
|
||||
import { EventService } from '../../../services/event.service';
|
||||
import { EnvService } from 'src/app/services/env.service';
|
||||
import { AuthService } from 'src/app/services/auth.service';
|
||||
import { IchambaService } from 'src/app/services/ichamba.service';
|
||||
import { AlertService } from 'src/app/services/alert.service';
|
||||
import { Browser } from '@capacitor/browser';
|
||||
import { PostulateOfferPage } from '../postulate-offer/postulate-offer.page';
|
||||
|
||||
@Component({
|
||||
selector: 'app-current',
|
||||
templateUrl: './current.page.html',
|
||||
styleUrls: ['./current.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class CurrentPage implements OnInit {
|
||||
export class CurrentPage {
|
||||
|
||||
postulations: any[] = [];
|
||||
postulations_dates: any[] = [];
|
||||
loading = true;
|
||||
minFee: number = 0;
|
||||
private timerInterval: any;
|
||||
|
||||
constructor(
|
||||
private menu: MenuController,
|
||||
@@ -24,24 +29,57 @@ export class CurrentPage implements OnInit {
|
||||
private authService: AuthService,
|
||||
private alertService: AlertService,
|
||||
private ichambaService: IchambaService,
|
||||
private modalCtrl: ModalController,
|
||||
private env: EnvService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
) {
|
||||
this.events.subscribe('refreshpostulations', (data) => {
|
||||
this.getpostulations();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
ionViewWillEnter() {
|
||||
this.loading = true;
|
||||
this.getpostulations();
|
||||
this.ichambaService.getParameters().subscribe((data: any) => {
|
||||
this.minFee = data['min_fee'] ?? 0;
|
||||
});
|
||||
this.timerInterval = setInterval(() => {
|
||||
this.postulations.forEach(p => this.computeTimer(p));
|
||||
this.cdr.detectChanges();
|
||||
}, 60000);
|
||||
}
|
||||
|
||||
refresh(event) {
|
||||
ionViewWillLeave() {
|
||||
clearInterval(this.timerInterval);
|
||||
}
|
||||
|
||||
private computeTimer(p: any) {
|
||||
const remaining = Math.max(0, Math.floor(
|
||||
(new Date(p.time_created).getTime() + 1000 * 60 * 1000 - Date.now()) / 60000
|
||||
));
|
||||
if (remaining <= 0) {
|
||||
p._timer = 'Expirada';
|
||||
} else if (remaining < 60) {
|
||||
p._timer = `${remaining}m restantes`;
|
||||
} else {
|
||||
const h = Math.floor(remaining / 60);
|
||||
const m = remaining % 60;
|
||||
p._timer = `${h}h${m > 0 ? ' ' + m + 'm' : ''} restantes`;
|
||||
}
|
||||
p._timerCritical = remaining < 60;
|
||||
}
|
||||
|
||||
refresh(event: any) {
|
||||
this.ichambaService.getPostulation().subscribe(
|
||||
data => {
|
||||
this.postulations = data;
|
||||
this.postulations_dates = [];
|
||||
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.computeTimer(i);
|
||||
}
|
||||
this.cdr.detectChanges();
|
||||
event.target.complete();
|
||||
}, error => {
|
||||
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
|
||||
@@ -53,26 +91,39 @@ export class CurrentPage implements OnInit {
|
||||
this.ichambaService.getPostulation().subscribe(
|
||||
data => {
|
||||
this.postulations = data;
|
||||
this.postulations_dates = [];
|
||||
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.computeTimer(i);
|
||||
}
|
||||
this.loading = false;
|
||||
this.cdr.detectChanges();
|
||||
}, error => {
|
||||
this.loading = false;
|
||||
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
|
||||
});
|
||||
}
|
||||
|
||||
addpostulation(id: String) {
|
||||
this.ichambaService.setPostulation(id).subscribe(
|
||||
data => {
|
||||
async addpostulation(id: string) {
|
||||
const modal = await this.modalCtrl.create({
|
||||
component: PostulateOfferPage,
|
||||
componentProps: { postulationId: id, minFee: this.minFee }
|
||||
});
|
||||
await modal.present();
|
||||
const { data } = await modal.onDidDismiss();
|
||||
if (!data) return;
|
||||
this.ichambaService.setPostulation(id, data.date1, data.date2, data.amount).subscribe({
|
||||
next: (res: any) => {
|
||||
this.getpostulations();
|
||||
this.alertService.presentToast(data['message']);
|
||||
}, error => {
|
||||
this.alertService.presentToast(res['message']);
|
||||
},
|
||||
error: (error: any) => {
|
||||
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async openMaps(lat: Number, lng: Number) {
|
||||
await Browser.open({ url: 'http://maps.google.com/maps?q=' + lat + ',' + lng });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,22 +6,41 @@
|
||||
<ion-title>Postulaciones</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<h2 text-capitalize style="padding-bottom: 0.5em">Finalizadas</h2>
|
||||
<ion-content class="ion-padding">
|
||||
<h2 class="ion-text-capitalize" style="padding-bottom: 0.5em">Finalizadas</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: 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">
|
||||
<ion-card>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<h2 text-capitalize>{{ postulation.category }}</h2>
|
||||
<p text-wrap>{{ postulation.address }}</p>
|
||||
<p text-wrap text-capitalize>{{ postulations_dates[i] }}</p>
|
||||
<p text-wrap text-capitalize>Estado: {{ postulation.status }}</p>
|
||||
<h2 class="ion-text-capitalize">{{ postulation.category }}</h2>
|
||||
<p class="ion-text-wrap">{{ postulation.address }}</p>
|
||||
<p class="ion-text-wrap ion-text-capitalize">{{ postulations_dates[i] }}</p>
|
||||
<p class="ion-text-wrap ion-text-capitalize">Estado: {{ postulation.status }}</p>
|
||||
</ion-label>
|
||||
<br>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ion-content>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, ChangeDetectorRef } from '@angular/core';
|
||||
import { ModalController, MenuController, NavController } from '@ionic/angular';
|
||||
import { EventService } from '../../../services/event.service';
|
||||
import { EnvService } from 'src/app/services/env.service';
|
||||
@@ -10,11 +10,13 @@ import { AlertService } from 'src/app/services/alert.service';
|
||||
selector: 'app-ended',
|
||||
templateUrl: './ended.page.html',
|
||||
styleUrls: ['./ended.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class EndedPage implements OnInit {
|
||||
export class EndedPage {
|
||||
|
||||
postulations = [];
|
||||
postulations_dates = [];
|
||||
postulations: any[] = [];
|
||||
postulations_dates: any[] = [];
|
||||
loading = true;
|
||||
|
||||
constructor(
|
||||
private modalController: ModalController,
|
||||
@@ -25,23 +27,27 @@ export class EndedPage implements OnInit {
|
||||
private alertService: AlertService,
|
||||
private ichambaService: IchambaService,
|
||||
private env: EnvService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
) {
|
||||
this.events.subscribe('refreshpostulations', (data) => {
|
||||
this.getfinishedpostulations();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
ionViewWillEnter() {
|
||||
this.loading = true;
|
||||
this.getfinishedpostulations();
|
||||
}
|
||||
|
||||
refresh(event) {
|
||||
refresh(event: any) {
|
||||
this.ichambaService.getFinishedPostulation().subscribe(
|
||||
data => {
|
||||
this.postulations = data;
|
||||
this.postulations_dates = [];
|
||||
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();
|
||||
}, error => {
|
||||
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(
|
||||
data => {
|
||||
this.postulations = data;
|
||||
this.postulations_dates = [];
|
||||
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 => {
|
||||
this.loading = false;
|
||||
this.alertService.presentToast("Por favor contacte a soporte técnico, Estatus:" + error['status']);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { PostulateOfferPage } from './postulate-offer.page';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, FormsModule, IonicModule],
|
||||
declarations: [PostulateOfferPage],
|
||||
exports: [PostulateOfferPage]
|
||||
})
|
||||
export class PostulateOfferPageModule {}
|
||||
@@ -0,0 +1,48 @@
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-buttons slot="start">
|
||||
<ion-button (click)="dismiss()">
|
||||
<ion-icon slot="icon-only" name="close"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Tu oferta</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="stacked">Fecha tentativa 1 *</ion-label>
|
||||
<ion-datetime-button datetime="date1Picker" style="margin-top: 8px"></ion-datetime-button>
|
||||
</ion-item>
|
||||
<ion-modal [keepContentsMounted]="true">
|
||||
<ng-template>
|
||||
<ion-datetime id="date1Picker" presentation="date-time" [(ngModel)]="date1"
|
||||
[min]="minDate" [showDefaultButtons]="true" doneText="Aceptar" cancelText="Cancelar">
|
||||
</ion-datetime>
|
||||
</ng-template>
|
||||
</ion-modal>
|
||||
|
||||
<ion-item style="margin-top: 8px">
|
||||
<ion-label position="stacked">Fecha tentativa 2 *</ion-label>
|
||||
<ion-datetime-button datetime="date2Picker" style="margin-top: 8px"></ion-datetime-button>
|
||||
</ion-item>
|
||||
<ion-modal [keepContentsMounted]="true">
|
||||
<ng-template>
|
||||
<ion-datetime id="date2Picker" presentation="date-time" [(ngModel)]="date2"
|
||||
[min]="minDate" [showDefaultButtons]="true" doneText="Aceptar" cancelText="Cancelar">
|
||||
</ion-datetime>
|
||||
</ng-template>
|
||||
</ion-modal>
|
||||
|
||||
<ion-item style="margin-top: 8px">
|
||||
<ion-label position="stacked">Monto a cobrar ($) *</ion-label>
|
||||
<ion-input type="number" [(ngModel)]="amount" [placeholder]="'Mínimo $' + minFee"></ion-input>
|
||||
</ion-item>
|
||||
<ion-note style="padding: 4px 16px; display: block" color="medium">Cuota mínima del sistema: ${{ minFee }}</ion-note>
|
||||
|
||||
<ion-button expand="full" color="secondary" style="margin-top: 2em" (click)="submit()">
|
||||
Enviar oferta
|
||||
</ion-button>
|
||||
|
||||
</ion-content>
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core';
|
||||
import { ModalController } from '@ionic/angular';
|
||||
import { AlertService } from 'src/app/services/alert.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-postulate-offer',
|
||||
templateUrl: './postulate-offer.page.html',
|
||||
styleUrls: ['./postulate-offer.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class PostulateOfferPage implements OnInit {
|
||||
|
||||
@Input() postulationId: any;
|
||||
@Input() minFee: number = 0;
|
||||
|
||||
date1: string | null = null;
|
||||
date2: string | null = null;
|
||||
amount: number | null = null;
|
||||
minDate: string = new Date().toISOString().slice(0, 10);
|
||||
|
||||
constructor(
|
||||
private modalCtrl: ModalController,
|
||||
private alertService: AlertService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
submit() {
|
||||
if (!this.date1 || !this.date2) {
|
||||
this.alertService.presentToast('Debes seleccionar las dos fechas tentativas.');
|
||||
return;
|
||||
}
|
||||
if (!this.amount || this.amount < this.minFee) {
|
||||
this.alertService.presentToast(`El monto mínimo es $${this.minFee}.`);
|
||||
return;
|
||||
}
|
||||
this.modalCtrl.dismiss({
|
||||
date1: this.date1,
|
||||
date2: this.date2,
|
||||
amount: this.amount
|
||||
});
|
||||
}
|
||||
|
||||
dismiss() {
|
||||
this.modalCtrl.dismiss(null);
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,10 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'ended',
|
||||
loadChildren: () => import('./ended/ended.module').then(m => m.EndedPageModule)
|
||||
},
|
||||
{
|
||||
path: 'reported',
|
||||
loadChildren: () => import('./reported/reported.module').then(m => m.PostulationReportedPageModule)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -24,5 +24,10 @@
|
||||
<ion-label>Finalizadas</ion-label>
|
||||
</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-tabs>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { AlertService } from 'src/app/services/alert.service';
|
||||
selector: 'app-postulations',
|
||||
templateUrl: './postulations.page.html',
|
||||
styleUrls: ['./postulations.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class PostulationsPage implements OnInit {
|
||||
|
||||
|
||||
26
src/app/pages/postulations/reported/reported.module.ts
Normal file
26
src/app/pages/postulations/reported/reported.module.ts
Normal 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 {}
|
||||
53
src/app/pages/postulations/reported/reported.page.html
Normal file
53
src/app/pages/postulations/reported/reported.page.html
Normal 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>
|
||||
77
src/app/pages/postulations/reported/reported.page.ts
Normal file
77
src/app/pages/postulations/reported/reported.page.ts
Normal 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'
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
13
src/app/pages/postulations/repostulate/repostulate.module.ts
Normal file
13
src/app/pages/postulations/repostulate/repostulate.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { RepostulateePage } from './repostulate.page';
|
||||
import { AddPropertyPageModule } from '../../property/add-property/add-property.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, FormsModule, IonicModule, AddPropertyPageModule],
|
||||
declarations: [RepostulateePage],
|
||||
exports: [RepostulateePage],
|
||||
})
|
||||
export class RepostulatePageModule {}
|
||||
70
src/app/pages/postulations/repostulate/repostulate.page.html
Normal file
70
src/app/pages/postulations/repostulate/repostulate.page.html
Normal file
@@ -0,0 +1,70 @@
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-buttons slot="start">
|
||||
<ion-button (click)="dismiss()">
|
||||
<ion-icon slot="icon-only" name="close"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Repostular</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="fixed">Categoría</ion-label>
|
||||
<ion-input [value]="postulation?.category" disabled></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="fixed">Propiedad</ion-label>
|
||||
<ion-select [(ngModel)]="selectedPropertyId" placeholder="Selecciona una propiedad" interface="action-sheet">
|
||||
<ion-select-option *ngFor="let p of properties" [value]="p.id">
|
||||
{{ p.name }}: {{ p.address }}
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-item button="true" (click)="openAddProperty()" lines="none">
|
||||
<ion-icon name="add-circle-outline" slot="start" color="primary"></ion-icon>
|
||||
<ion-label color="primary">Agregar propiedad</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="fixed">Referencias</ion-label>
|
||||
<ion-textarea [(ngModel)]="references" placeholder="Ej. Frente al parque, casa azul..." style="--highlight-height: 0"></ion-textarea>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="fixed">Comentarios</ion-label>
|
||||
<ion-textarea [(ngModel)]="details" placeholder="Describe lo que necesitas..." style="--highlight-height: 0"></ion-textarea>
|
||||
</ion-item>
|
||||
|
||||
<div style="padding: 12px 0">
|
||||
<ion-label style="font-size: 0.9rem; color: var(--ion-color-medium); padding-left: 4px">
|
||||
Fotos (opcional, máx. 3)
|
||||
</ion-label>
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px">
|
||||
<div *ngFor="let photo of photos; let i = index" style="position: relative">
|
||||
<img [src]="photo" style="width: 80px; height: 80px; object-fit: cover; border-radius: 8px">
|
||||
<ion-button fill="clear" color="danger" size="small"
|
||||
style="position: absolute; top: -8px; right: -8px; --padding-start: 0; --padding-end: 0"
|
||||
(click)="removePhoto(i)">
|
||||
<ion-icon slot="icon-only" name="close-circle"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
<ion-button *ngIf="photos.length < 3" fill="outline" color="medium"
|
||||
style="width: 80px; height: 80px; --border-radius: 8px" (click)="addPhoto()">
|
||||
<ion-icon slot="icon-only" name="camera-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ion-note color="medium" style="display: block; padding: 4px 0 12px">
|
||||
Se archivará la postulación actual y se creará una nueva. Los proveedores anteriores deberán volver a postularse.
|
||||
</ion-note>
|
||||
|
||||
<ion-button expand="full" color="primary" (click)="submit()">
|
||||
Repostular
|
||||
</ion-button>
|
||||
|
||||
</ion-content>
|
||||
130
src/app/pages/postulations/repostulate/repostulate.page.ts
Normal file
130
src/app/pages/postulations/repostulate/repostulate.page.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core';
|
||||
import { LoadingController, ModalController } from '@ionic/angular';
|
||||
import { AlertService } from 'src/app/services/alert.service';
|
||||
import { IchambaService } from 'src/app/services/ichamba.service';
|
||||
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
|
||||
import { AddPropertyPage } from '../../property/add-property/add-property.page';
|
||||
|
||||
@Component({
|
||||
selector: 'app-repostulate',
|
||||
templateUrl: './repostulate.page.html',
|
||||
styleUrls: ['./repostulate.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class RepostulateePage implements OnInit {
|
||||
|
||||
@Input() postulation: any;
|
||||
|
||||
properties: any[] = [];
|
||||
selectedPropertyId: number | null = null;
|
||||
references: string = '';
|
||||
details: string = '';
|
||||
photos: string[] = [];
|
||||
loadingProperties = true;
|
||||
|
||||
constructor(
|
||||
private modalCtrl: ModalController,
|
||||
private alertService: AlertService,
|
||||
private ichambaService: IchambaService,
|
||||
private loadingCtrl: LoadingController,
|
||||
private cdr: ChangeDetectorRef,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.references = this.postulation?.references ?? '';
|
||||
this.details = this.postulation?.details ?? '';
|
||||
this.loadProperties();
|
||||
}
|
||||
|
||||
loadProperties() {
|
||||
this.ichambaService.getProperties().subscribe({
|
||||
next: (data: any[]) => {
|
||||
this.properties = data;
|
||||
this.selectedPropertyId = this.postulation?.property_id ?? (data[0]?.id ?? null);
|
||||
this.loadingProperties = false;
|
||||
this.cdr.detectChanges();
|
||||
if (data.length === 0) this.openAddProperty();
|
||||
},
|
||||
error: () => {
|
||||
this.loadingProperties = false;
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async openAddProperty() {
|
||||
const modal = await this.modalCtrl.create({ component: AddPropertyPage });
|
||||
await modal.present();
|
||||
const { data } = await modal.onDidDismiss();
|
||||
if (data?.property) {
|
||||
this.properties.push(data.property);
|
||||
this.selectedPropertyId = data.property.id;
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
async addPhoto() {
|
||||
if (this.photos.length >= 3) {
|
||||
this.alertService.presentToast('Máximo 3 fotos.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const image = await Camera.getPhoto({
|
||||
quality: 70,
|
||||
resultType: CameraResultType.Base64,
|
||||
source: CameraSource.Prompt,
|
||||
});
|
||||
this.photos.push('data:image/jpeg;base64,' + image.base64String);
|
||||
this.cdr.detectChanges();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
removePhoto(index: number) {
|
||||
this.photos.splice(index, 1);
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (!this.selectedPropertyId) {
|
||||
this.alertService.presentToast('Selecciona una propiedad.');
|
||||
return;
|
||||
}
|
||||
const loading = await this.loadingCtrl.create();
|
||||
await loading.present();
|
||||
|
||||
this.ichambaService.cancelPostulation(this.postulation.id).subscribe({
|
||||
next: () => {
|
||||
this.ichambaService.addPostulation(
|
||||
this.postulation.category,
|
||||
this.selectedPropertyId!,
|
||||
this.references,
|
||||
this.details,
|
||||
this.photos,
|
||||
this.postulation.id
|
||||
).subscribe({
|
||||
next: (data: any) => {
|
||||
loading.dismiss();
|
||||
if (data['message'] === 'No providers') {
|
||||
this.alertService.presentToast('No hay proveedores disponibles para esta categoría.');
|
||||
} else {
|
||||
this.alertService.presentToast('¡Postulación actualizada con éxito!');
|
||||
this.modalCtrl.dismiss({ reposted: true });
|
||||
}
|
||||
},
|
||||
error: (error: any) => {
|
||||
loading.dismiss();
|
||||
this.alertService.presentToast('Error al crear la postulación: ' + error['status']);
|
||||
}
|
||||
});
|
||||
},
|
||||
error: (error: any) => {
|
||||
loading.dismiss();
|
||||
this.alertService.presentToast('Error al cancelar la postulación: ' + error['status']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dismiss() {
|
||||
this.modalCtrl.dismiss(null);
|
||||
}
|
||||
}
|
||||
12
src/app/pages/property/add-property/add-property.module.ts
Normal file
12
src/app/pages/property/add-property/add-property.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { AddPropertyPage } from './add-property.page';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, FormsModule, IonicModule],
|
||||
declarations: [AddPropertyPage],
|
||||
exports: [AddPropertyPage]
|
||||
})
|
||||
export class AddPropertyPageModule {}
|
||||
63
src/app/pages/property/add-property/add-property.page.html
Normal file
63
src/app/pages/property/add-property/add-property.page.html
Normal file
@@ -0,0 +1,63 @@
|
||||
<style>
|
||||
ion-list.dropdown-list {
|
||||
position: absolute;
|
||||
width: calc(100% - 32px);
|
||||
max-height: 50%;
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-buttons slot="start">
|
||||
<ion-button (click)="dismiss()">
|
||||
<ion-icon slot="icon-only" name="close"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Agregar Propiedad</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="floating">Nombre de la propiedad</ion-label>
|
||||
<ion-input [(ngModel)]="name" placeholder="Ej. Mi casa, Oficina centro"></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-item style="margin-top: 8px">
|
||||
<ion-label>Ícono</ion-label>
|
||||
<div slot="end" style="display:flex; gap: 16px; padding: 8px 0">
|
||||
<ion-button [fill]="icon === 'home' ? 'solid' : 'outline'" color="primary" (click)="icon = 'home'">
|
||||
<ion-icon slot="icon-only" name="home"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button [fill]="icon === 'briefcase' ? 'solid' : 'outline'" color="primary" (click)="icon = 'briefcase'">
|
||||
<ion-icon slot="icon-only" name="briefcase"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="floating">Dirección</ion-label>
|
||||
<ion-input
|
||||
[(ngModel)]="addressAutocomplete"
|
||||
(ionInput)="autocomplete($event)"
|
||||
(ionFocus)="showif = true"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-list *ngIf="showif && placesSearch && placesSearch.length > 0" class="dropdown-list">
|
||||
<ion-item button="true" (click)="geoloc(place.place_id, place.description)" *ngFor="let place of placesSearch">
|
||||
{{ place.description }}
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="floating">Número exterior (opcional)</ion-label>
|
||||
<ion-input [(ngModel)]="myIntnumber" type="number"></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-button expand="full" color="primary" style="margin-top: 2em" (click)="save()" [disabled]="saving">
|
||||
Guardar propiedad
|
||||
</ion-button>
|
||||
|
||||
</ion-content>
|
||||
100
src/app/pages/property/add-property/add-property.page.ts
Normal file
100
src/app/pages/property/add-property/add-property.page.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Component, NgZone, ChangeDetectorRef } from '@angular/core';
|
||||
import { ModalController, LoadingController } from '@ionic/angular';
|
||||
import { IchambaService } from 'src/app/services/ichamba.service';
|
||||
import { AlertService } from 'src/app/services/alert.service';
|
||||
|
||||
declare var google: any;
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-property',
|
||||
templateUrl: './add-property.page.html',
|
||||
styleUrls: ['./add-property.page.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class AddPropertyPage {
|
||||
|
||||
name: string = '';
|
||||
icon: string = 'home';
|
||||
addressAutocomplete: string = '';
|
||||
myAddress: string | null = null;
|
||||
myPosition: any = {};
|
||||
myIntnumber: string = '';
|
||||
placesSearch: any = null;
|
||||
showif = true;
|
||||
saving = false;
|
||||
|
||||
constructor(
|
||||
private modalCtrl: ModalController,
|
||||
private loadingCtrl: LoadingController,
|
||||
private ichambaService: IchambaService,
|
||||
private alertService: AlertService,
|
||||
private ngZone: NgZone,
|
||||
private cdr: ChangeDetectorRef,
|
||||
) {}
|
||||
|
||||
autocomplete(ev: any) {
|
||||
if (!this.addressAutocomplete.trim().length) {
|
||||
this.placesSearch = null;
|
||||
return;
|
||||
}
|
||||
new google.maps.places.AutocompleteService().getPredictions(
|
||||
{ input: this.addressAutocomplete },
|
||||
(predictions: any) => {
|
||||
this.ngZone.run(() => {
|
||||
this.placesSearch = predictions;
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
geoloc(place_id: string, place_description: string) {
|
||||
this.myAddress = place_description;
|
||||
this.addressAutocomplete = place_description;
|
||||
this.showif = false;
|
||||
new google.maps.Geocoder().geocode({ placeId: place_id }, (coordinates: any) => {
|
||||
this.ngZone.run(() => {
|
||||
this.myPosition = {
|
||||
latitude: coordinates[0].geometry.location.lat(),
|
||||
longitude: coordinates[0].geometry.location.lng()
|
||||
};
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (!this.name.trim()) {
|
||||
this.alertService.presentToast('Ingresa un nombre para la propiedad.');
|
||||
return;
|
||||
}
|
||||
if (!this.myAddress || !this.myPosition.latitude) {
|
||||
this.alertService.presentToast('Selecciona una dirección del listado.');
|
||||
return;
|
||||
}
|
||||
if (!Number.isInteger(parseInt(this.myIntnumber))) {
|
||||
this.myIntnumber = '';
|
||||
}
|
||||
this.saving = true;
|
||||
const loading = await this.loadingCtrl.create();
|
||||
await loading.present();
|
||||
this.ichambaService.addProperty(
|
||||
this.name, this.myAddress, this.myPosition.latitude,
|
||||
this.myPosition.longitude, this.myIntnumber, this.icon
|
||||
).subscribe({
|
||||
next: (property: any) => {
|
||||
loading.dismiss();
|
||||
this.modalCtrl.dismiss({ property });
|
||||
},
|
||||
error: () => {
|
||||
loading.dismiss();
|
||||
this.saving = false;
|
||||
this.alertService.presentToast('No se pudo guardar la propiedad.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dismiss() {
|
||||
this.modalCtrl.dismiss();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user