-
{{'contracts.cancel_1.1' | translate}}
{{'contracts.cancel_1.2' | translate}}
+
+
+
+ Chat
+
+ {{'contracts.cancel_1.1' | translate}}
{{'contracts.cancel_1.2' | translate}}
diff --git a/src/app/pages/contracts/contracted/contracted.page.ts b/src/app/pages/contracts/contracted/contracted.page.ts
index c2f9e2a..0a3db20 100755
--- a/src/app/pages/contracts/contracted/contracted.page.ts
+++ b/src/app/pages/contracts/contracted/contracted.page.ts
@@ -1,5 +1,6 @@
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';
@@ -99,6 +100,14 @@ export class ContractedPage {
//}
}
+ 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',
diff --git a/src/app/pages/contracts/hire/hire.page.html b/src/app/pages/contracts/hire/hire.page.html
index 496dd8b..3d3cfca 100755
--- a/src/app/pages/contracts/hire/hire.page.html
+++ b/src/app/pages/contracts/hire/hire.page.html
@@ -6,110 +6,66 @@
{{'contracts.hire' | translate}}
+
{{'contracts.hire_info_1' | translate}} {{ supplier }} {{'contracts.hire_info_2' | translate}}
{{'contracts.hire_pay' | translate}}
-
-
- {{'contracts.fee_amount' | translate}}:
-
-
- ${{ fee | number: '1.2-2'}}
-
-
-
-
- {{'contracts.discount_amount' | translate}}:
-
-
- ${{ discount | number: '1.2-2'}}
-
-
-
-
- {{'contracts.final_amount' | translate}}:
-
-
- ${{ final_amount | number: '1.2-2'}}
-
-
-
-
-
- {{'contracts.coupon' | translate}}:
-
- {{'contracts.validate' | translate}}
-
-
-
- {{'cards.card' | translate}}
-
- Agregar tarjeta
- {{card.brand}} - {{card.card_number}}
-
-
-
+
+
+
+ {{'contracts.fee_amount' | translate}}:
+
+
+ ${{ fee | number: '1.2-2'}}
+
+
+
+
+ {{'contracts.discount_amount' | translate}}:
+
+
+ ${{ discount | number: '1.2-2'}}
+
+
+
+
+ {{'contracts.final_amount' | translate}}:
+
+
+ ${{ final_amount | number: '1.2-2'}}
+
+
-
-
-
-
-
-
- {{'cards.openpay_1' | translate}}
-
-
-
-
- {{'cards.openpay_2' | translate}}
-
-
- {{'cards.openpay_3' | translate}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ Selecciona la fecha en que recibirás el servicio:
+
+
+
+
+ {{ date_1_formatted }}
+
+
+
+ {{ date_2_formatted }}
+
+
+
+
+
+
+
+ {{'contracts.coupon' | translate}}:
+
+ {{'contracts.validate' | translate}}
+
+
+
+
+ MODO DE PRUEBA: El pago será simulado
+
+
+
+
+ {{'contracts.hire_confirm' | translate}}
+
diff --git a/src/app/pages/contracts/hire/hire.page.ts b/src/app/pages/contracts/hire/hire.page.ts
index 866844c..3a7f2e6 100755
--- a/src/app/pages/contracts/hire/hire.page.ts
+++ b/src/app/pages/contracts/hire/hire.page.ts
@@ -1,16 +1,13 @@
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',
@@ -21,11 +18,6 @@ declare var OpenPay: any;
export class HirePage implements OnInit {
private loading: HTMLIonLoadingElement | null = null;
- code_check = true;
- cards: any[] = [];
- code: any = null;
- device_id: any = null;
- card_id: any = null;
supplier: any = null;
supplier_id: any = null;
postulation_id: any = null;
@@ -33,13 +25,14 @@ export class HirePage implements OnInit {
discount = 0;
final_amount: any = null;
coupon: any = null;
- cards_menu: 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,
@@ -49,122 +42,77 @@ 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() {
this.paymentBypass = this.env.PAYMENT_BYPASS;
- if (!this.paymentBypass) {
- 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();
+ }
+
+ 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' });
+ }
+
+ checkCoupon() {
+ if (!this.coupon) return;
+ if (!this.selected_date) {
+ this.alertService.presentToast('Selecciona una fecha antes de aplicar el cupón.');
+ return;
}
- }
-
- newcard() {
- this.navCtrl.navigateForward('/addcard');
- }
-
- getcards() {
- this.ichambaService.getCard().subscribe(
- data => {
- this.cards = data;
- }, error => {
+ 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: any) => {
- if (data) {
- if (data['name'] == "used") {
- if (this.loading) this.loading.dismiss();
- this.alertService.presentToast(this.translateService.instant('alerts.coupon_used'));
- } else if (data['name'] == "expired") {
- if (this.loading) this.loading.dismiss();
- this.alertService.presentToast(this.translateService.instant('alerts.coupon_expired'));
- } else if (data['name'] == this.coupon) {
- if (this.loading) 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") {
- if (this.loading) this.loading.dismiss();
- this.events.publish('refreshpcontracts', 'data');
- this.navCtrl.navigateRoot('/contracts/contracted');
- this.alertService.presentToast(this.translateService.instant('alerts.hire'));
- }
- } else {
- if (this.loading) this.loading.dismiss();
- this.alertService.presentToast(this.translateService.instant('alerts.nocoupon'));
- }
- }, (error: any) => {
- if (this.loading) this.loading.dismiss();
- this.alertService.presentToast(this.translateService.instant('alerts.error') + error['status']);
- });
- }
- }
-
- createContract(form: NgForm) {
- // Si el bypass está activo, enviar con valores dummy
if (this.paymentBypass) {
- this.loadingCtrl.create().then((overlay) => {
- this.loading = overlay;
- this.loading.present();
- });
- this.ichambaService.createContract(this.postulation_id, this.supplier_id, 'BYPASS', this.coupon, '000', 'BYPASS').subscribe(
+ 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 (data['type'] == "error") {
- if (this.loading) this.loading.dismiss();
+ if (this.loading) this.loading.dismiss();
+ if (data['type'] == 'error') {
this.alertService.presentToast(data['message']);
- } else if (data['name'] == "used") {
- if (this.loading) this.loading.dismiss();
- this.alertService.presentToast(this.translateService.instant('contracts.coupon_used'));
- } else if (data['name'] == "expired") {
- if (this.loading) this.loading.dismiss();
- this.alertService.presentToast(this.translateService.instant('contracts.coupon_expired'));
} else {
- if (this.loading) this.loading.dismiss();
this.events.publish('refreshpcontracts', 'data');
this.navCtrl.navigateRoot('/contracts/contracted');
this.alertService.presentToast(this.translateService.instant('alerts.hire'));
@@ -176,42 +124,53 @@ export class HirePage implements OnInit {
return;
}
- if (this.card_id != "Agregar tarjeta") {
- if (this.code_check) {
- let code = form.value.code;
- let device_id = (
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: any) => {
- if (data['type'] == "error") {
- if (this.loading) this.loading.dismiss();
- this.alertService.presentToast(data['message']);
- } else if (data['name'] == "used") {
- if (this.loading) this.loading.dismiss();
- this.alertService.presentToast(this.translateService.instant('contracts.coupon_used'));
- } else if (data['name'] == "expired") {
- if (this.loading) this.loading.dismiss();
- this.alertService.presentToast(this.translateService.instant('contracts.coupon_expired'));
- } else {
- if (this.loading) this.loading.dismiss();
- this.events.publish('refreshpcontracts', 'data');
- this.navCtrl.navigateRoot('/contracts/contracted');
- this.alertService.presentToast(this.translateService.instant('alerts.hire'));
- }
- }, (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);
+ 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'));
}
}
diff --git a/src/app/pages/contracts/pending/pending.module.ts b/src/app/pages/contracts/pending/pending.module.ts
index 25d700f..9cafdbe 100755
--- a/src/app/pages/contracts/pending/pending.module.ts
+++ b/src/app/pages/contracts/pending/pending.module.ts
@@ -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]
})
diff --git a/src/app/pages/contracts/pending/pending.page.html b/src/app/pages/contracts/pending/pending.page.html
index 75d7df1..daf61b4 100755
--- a/src/app/pages/contracts/pending/pending.page.html
+++ b/src/app/pages/contracts/pending/pending.page.html
@@ -14,7 +14,7 @@
-
+
@@ -30,7 +30,7 @@
-
+
{{ pcontract.category }}
{{ pcontract.en_category }}
@@ -39,6 +39,16 @@
{{'contracts.viewsuppliers_1.1' | translate}}
{{'contracts.viewsuppliers_1.2' | translate}}
+
+
+
+ Repostular
+
+
+
+ Cancelar
+
+
diff --git a/src/app/pages/contracts/pending/pending.page.ts b/src/app/pages/contracts/pending/pending.page.ts
index fcd3e53..819c40f 100755
--- a/src/app/pages/contracts/pending/pending.page.ts
+++ b/src/app/pages/contracts/pending/pending.page.ts
@@ -1,5 +1,5 @@
import { Component, ChangeDetectorRef } from '@angular/core';
-import { ModalController, MenuController, NavController } from '@ionic/angular';
+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,6 +7,7 @@ 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',
@@ -23,6 +24,7 @@ export class PendingPage {
constructor(
private modalController: ModalController,
+ private alertController: AlertController,
private menu: MenuController,
private navCtrl: NavController,
private events: EventService,
@@ -83,4 +85,45 @@ export class PendingPage {
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 ' + postulation.category + '?';
+ }
+
+ 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();
+ }
+ }
+
}
diff --git a/src/app/pages/contracts/viewsuppliers/viewsuppliers.page.html b/src/app/pages/contracts/viewsuppliers/viewsuppliers.page.html
index 1a75987..7c31ba6 100755
--- a/src/app/pages/contracts/viewsuppliers/viewsuppliers.page.html
+++ b/src/app/pages/contracts/viewsuppliers/viewsuppliers.page.html
@@ -6,31 +6,59 @@
{{'contracts.viewsuppliers_header' | translate}}
+
- {{ pcontractssuppliers_category[0] }}
- {{ pcontractssuppliers_en_category[0] }}
- {{ pcontractssuppliers_address[0] }}
- {{ pcontractssuppliers_dates[0] }}
+ {{ headerCategory }}
+ {{ headerAddress }}
+
-
-
-
+
+
+
+
+
+ Fecha más pronta
+
+
+
+ Más económico
+
+
+
+ Certificados
+
+
+
+
+
+
-
+
-
-
+
+
- {{ pcontractsupplier.supplier }}
- {{ pcontractssuppliers_certified[i] }}
- {{'contracts.viewsuppliers_tags' | translate}}: {{ pcontractsupplier.tags }}
- {{'contracts.viewsuppliers_fee' | translate}}: ${{ pcontractsupplier.fee }}
- {{'contracts.viewsuppliers_rate' | translate}}: {{ pcontractsupplier.score }} de 5
+ {{ s.supplier }}
+
+
+ {{ s._certified }}
+
+ {{'contracts.viewsuppliers_tags' | translate}}: {{ s.tags }}
+ {{'contracts.viewsuppliers_fee' | translate}}: ${{ s.fee }}
+
+ {{ s._dateFormatted }}
+ · opción 2 disponible
+
+ {{'contracts.viewsuppliers_rate' | translate}}: {{ s.score }} de 5
- {{'contracts.viewsuppliers_hire' | translate}}
+
+ {{'contracts.viewsuppliers_hire' | translate}}
+
+
diff --git a/src/app/pages/contracts/viewsuppliers/viewsuppliers.page.ts b/src/app/pages/contracts/viewsuppliers/viewsuppliers.page.ts
index 5c92072..af68c95 100755
--- a/src/app/pages/contracts/viewsuppliers/viewsuppliers.page.ts
+++ b/src/app/pages/contracts/viewsuppliers/viewsuppliers.page.ts
@@ -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';
@@ -19,15 +19,13 @@ import { HirePage } from '../hire/hire.page';
export class ViewsuppliersPage implements OnInit {
postulation_id: string | null = null;
- pcontractssuppliers: any[] = [];
- pcontractssuppliers_dates: any[] = [];
- pcontractssuppliers_certified: any[] = [];
- pcontractssuppliers_category: any[] = [];
- pcontractssuppliers_en_category: any[] = [];
- pcontractssuppliers_address: any[] = [];
- pcontractssuppliers_amount: any[] = [];
- pcontractssuppliers_fee: any[] = [];
+ suppliers: any[] = [];
+ sortedSuppliers: any[] = [];
+ headerCategory: string = '';
+ headerAddress: string = '';
+ headerDate: string = '';
lang: boolean = false;
+ activeSort: string = '';
constructor(
private modalController: ModalController,
@@ -42,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(
- (data: any) => {
- 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);
- }
+ 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, Estatus:" + error['status']);
+ },
+ error: () => {
+ this.alertService.presentToast("Por favor contacte a soporte técnico.");
event.target.complete();
- });
+ }
+ });
}
- getPostulants(postulation_id: String) {
- this.ichambaService.getPostulants(postulation_id).subscribe(
- (data: any) => {
- 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']);
- });
+ 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.");
+ }
+ });
}
- 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}]);
+ 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);
}
+ sortBy(criteria: string) {
+ if (this.activeSort === criteria) {
+ this.activeSort = '';
+ this.sortedSuppliers = [...this.suppliers];
+ } else {
+ this.activeSort = criteria;
+ this.applySort(criteria);
+ }
+ this.cdr.detectChanges();
+ }
+
+ 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, date_1: String, date_2: String) {
+ this.router.navigate(['/hire', { supplier, supplier_id, postulation_id, fee, date_1, date_2 }]);
+ }
}
diff --git a/src/app/pages/postulations/current/current.module.ts b/src/app/pages/postulations/current/current.module.ts
index 4f373b0..d681adc 100755
--- a/src/app/pages/postulations/current/current.module.ts
+++ b/src/app/pages/postulations/current/current.module.ts
@@ -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]
})
diff --git a/src/app/pages/postulations/current/current.page.html b/src/app/pages/postulations/current/current.page.html
index 33f63d6..651f540 100755
--- a/src/app/pages/postulations/current/current.page.html
+++ b/src/app/pages/postulations/current/current.page.html
@@ -14,7 +14,7 @@
-
+
@@ -30,15 +30,20 @@
-
-
+
+
{{ postulation.category }}
{{postulation.address}}
Referencias: {{postulation.references}}
{{postulations_dates[i]}}
Detalles: {{postulation.details}}
- Tiempo restante: {{postulation.time_limit}} minutos
+
+
+
+ {{ postulation._timer }}
+
+
Postularse
diff --git a/src/app/pages/postulations/current/current.page.ts b/src/app/pages/postulations/current/current.page.ts
index dcbe3e4..479837b 100755
--- a/src/app/pages/postulations/current/current.page.ts
+++ b/src/app/pages/postulations/current/current.page.ts
@@ -1,11 +1,12 @@
import { Component, ChangeDetectorRef } from '@angular/core';
-import { MenuController, NavController } from '@ionic/angular';
+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',
@@ -18,6 +19,8 @@ export class CurrentPage {
postulations: any[] = [];
postulations_dates: any[] = [];
loading = true;
+ minFee: number = 0;
+ private timerInterval: any;
constructor(
private menu: MenuController,
@@ -26,6 +29,7 @@ export class CurrentPage {
private authService: AuthService,
private alertService: AlertService,
private ichambaService: IchambaService,
+ private modalCtrl: ModalController,
private env: EnvService,
private cdr: ChangeDetectorRef,
) {
@@ -37,6 +41,33 @@ export class CurrentPage {
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);
+ }
+
+ 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) {
@@ -46,6 +77,7 @@ export class CurrentPage {
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.computeTimer(i);
}
this.cdr.detectChanges();
event.target.complete();
@@ -62,6 +94,7 @@ export class CurrentPage {
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.computeTimer(i);
}
this.loading = false;
this.cdr.detectChanges();
@@ -71,18 +104,26 @@ export class CurrentPage {
});
}
- addpostulation(id: String) {
- this.ichambaService.setPostulation(id).subscribe(
- (data: any) => {
+ 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: any) => {
+ 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 });
}
-
}
diff --git a/src/app/pages/postulations/postulate-offer/postulate-offer.module.ts b/src/app/pages/postulations/postulate-offer/postulate-offer.module.ts
new file mode 100644
index 0000000..7071ea9
--- /dev/null
+++ b/src/app/pages/postulations/postulate-offer/postulate-offer.module.ts
@@ -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 {}
diff --git a/src/app/pages/postulations/postulate-offer/postulate-offer.page.html b/src/app/pages/postulations/postulate-offer/postulate-offer.page.html
new file mode 100644
index 0000000..18e4fb6
--- /dev/null
+++ b/src/app/pages/postulations/postulate-offer/postulate-offer.page.html
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+ Tu oferta
+
+
+
+
+
+
+ Fecha tentativa 1 *
+
+
+
+
+
+
+
+
+
+
+ Fecha tentativa 2 *
+
+
+
+
+
+
+
+
+
+
+ Monto a cobrar ($) *
+
+
+ Cuota mínima del sistema: ${{ minFee }}
+
+
+ Enviar oferta
+
+
+
diff --git a/src/app/pages/postulations/postulate-offer/postulate-offer.page.scss b/src/app/pages/postulations/postulate-offer/postulate-offer.page.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/pages/postulations/postulate-offer/postulate-offer.page.ts b/src/app/pages/postulations/postulate-offer/postulate-offer.page.ts
new file mode 100644
index 0000000..301c8a5
--- /dev/null
+++ b/src/app/pages/postulations/postulate-offer/postulate-offer.page.ts
@@ -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);
+ }
+}
diff --git a/src/app/pages/postulations/repostulate/repostulate.module.ts b/src/app/pages/postulations/repostulate/repostulate.module.ts
new file mode 100644
index 0000000..e7f0edf
--- /dev/null
+++ b/src/app/pages/postulations/repostulate/repostulate.module.ts
@@ -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 {}
diff --git a/src/app/pages/postulations/repostulate/repostulate.page.html b/src/app/pages/postulations/repostulate/repostulate.page.html
new file mode 100644
index 0000000..125281f
--- /dev/null
+++ b/src/app/pages/postulations/repostulate/repostulate.page.html
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+ Repostular
+
+
+
+
+
+
+ Categoría
+
+
+
+
+ Propiedad
+
+
+ {{ p.name }}: {{ p.address }}
+
+
+
+
+
+ Agregar propiedad
+
+
+
+ Referencias
+
+
+
+
+ Comentarios
+
+
+
+
+
+ Fotos (opcional, máx. 3)
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+
+ Se archivará la postulación actual y se creará una nueva. Los proveedores anteriores deberán volver a postularse.
+
+
+
+ Repostular
+
+
+
diff --git a/src/app/pages/postulations/repostulate/repostulate.page.scss b/src/app/pages/postulations/repostulate/repostulate.page.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/pages/postulations/repostulate/repostulate.page.ts b/src/app/pages/postulations/repostulate/repostulate.page.ts
new file mode 100644
index 0000000..71976bc
--- /dev/null
+++ b/src/app/pages/postulations/repostulate/repostulate.page.ts
@@ -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);
+ }
+}
diff --git a/src/app/pages/property/add-property/add-property.module.ts b/src/app/pages/property/add-property/add-property.module.ts
new file mode 100644
index 0000000..dca2645
--- /dev/null
+++ b/src/app/pages/property/add-property/add-property.module.ts
@@ -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 {}
diff --git a/src/app/pages/property/add-property/add-property.page.html b/src/app/pages/property/add-property/add-property.page.html
new file mode 100644
index 0000000..d24e1c6
--- /dev/null
+++ b/src/app/pages/property/add-property/add-property.page.html
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+ Agregar Propiedad
+
+
+
+
+
+
+ Nombre de la propiedad
+
+
+
+
+ Ícono
+
+
+
+
+
+
+
+
+
+
+
+ Dirección
+
+
+ 0" class="dropdown-list">
+
+ {{ place.description }}
+
+
+
+
+ Número exterior (opcional)
+
+
+
+
+ Guardar propiedad
+
+
+
diff --git a/src/app/pages/property/add-property/add-property.page.scss b/src/app/pages/property/add-property/add-property.page.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/pages/property/add-property/add-property.page.ts b/src/app/pages/property/add-property/add-property.page.ts
new file mode 100644
index 0000000..fd52e27
--- /dev/null
+++ b/src/app/pages/property/add-property/add-property.page.ts
@@ -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();
+ }
+}
diff --git a/src/app/pages/team/assign-technician/assign-technician.module.ts b/src/app/pages/team/assign-technician/assign-technician.module.ts
new file mode 100644
index 0000000..eedbee4
--- /dev/null
+++ b/src/app/pages/team/assign-technician/assign-technician.module.ts
@@ -0,0 +1,14 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from '@ionic/angular';
+import { AssignTechnicianPage } from './assign-technician.page';
+
+@NgModule({
+ imports: [
+ CommonModule,
+ IonicModule,
+ ],
+ declarations: [AssignTechnicianPage],
+ exports: [AssignTechnicianPage]
+})
+export class AssignTechnicianPageModule {}
diff --git a/src/app/pages/team/assign-technician/assign-technician.page.scss b/src/app/pages/team/assign-technician/assign-technician.page.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/pages/team/assign-technician/assign-technician.page.ts b/src/app/pages/team/assign-technician/assign-technician.page.ts
new file mode 100644
index 0000000..3f1e7ae
--- /dev/null
+++ b/src/app/pages/team/assign-technician/assign-technician.page.ts
@@ -0,0 +1,62 @@
+import { Component, Input, OnInit, 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';
+
+@Component({
+ selector: 'app-assign-technician',
+ templateUrl: './assign-technician.page.html',
+ styleUrls: ['./assign-technician.page.scss'],
+ standalone: false
+})
+export class AssignTechnicianPage implements OnInit {
+
+ @Input() contractId: any;
+
+ technicians: any[] = [];
+ loading = true;
+ assigning = false;
+
+ constructor(
+ private modalCtrl: ModalController,
+ private ichambaService: IchambaService,
+ private alertService: AlertService,
+ private cdr: ChangeDetectorRef,
+ ) {}
+
+ ngOnInit() {
+ this.ichambaService.getTeamTechnicians().subscribe({
+ next: data => {
+ this.technicians = data;
+ this.loading = false;
+ this.cdr.detectChanges();
+ },
+ error: () => {
+ this.loading = false;
+ this.cdr.detectChanges();
+ }
+ });
+ }
+
+ assign(technician: any) {
+ if (this.assigning) return;
+ this.assigning = true;
+ this.cdr.detectChanges();
+
+ this.ichambaService.assignTechnician(this.contractId, technician.id).subscribe({
+ next: () => {
+ this.alertService.presentToast(`${technician.name} asignado al contrato`);
+ this.modalCtrl.dismiss({ assigned: true, technician });
+ },
+ error: () => {
+ this.assigning = false;
+ this.alertService.presentToast('No se pudo asignar el técnico.');
+ this.cdr.detectChanges();
+ }
+ });
+ }
+
+ dismiss() {
+ this.modalCtrl.dismiss();
+ }
+}
diff --git a/src/app/pages/team/team.module.ts b/src/app/pages/team/team.module.ts
new file mode 100644
index 0000000..c9e00f7
--- /dev/null
+++ b/src/app/pages/team/team.module.ts
@@ -0,0 +1,21 @@
+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 { TeamPage } from './team.page';
+
+const routes: Routes = [
+ { path: '', component: TeamPage }
+];
+
+@NgModule({
+ imports: [
+ CommonModule,
+ FormsModule,
+ IonicModule,
+ RouterModule.forChild(routes)
+ ],
+ declarations: [TeamPage]
+})
+export class TeamPageModule {}
diff --git a/src/app/pages/team/team.page.scss b/src/app/pages/team/team.page.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/services/ichamba.service.ts b/src/app/services/ichamba.service.ts
index 3c811fc..9d67c8d 100755
--- a/src/app/services/ichamba.service.ts
+++ b/src/app/services/ichamba.service.ts
@@ -38,20 +38,42 @@ export class IchambaService {
{ headers: headers });
}
- addCard(token: String, device_id: String) {
+ getSetupIntent() {
const headers = new HttpHeaders({
- 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
+ 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
});
- return this.http.post(this.env.API_URL + 'payments/addcard',
- {token: token, device_id: device_id}, { headers: headers });
+ return this.http.post(this.env.API_URL + 'payments/setup-intent', {}, { headers });
}
- addPostulation(category: String, address: String, int_number: String, references: String, lat: Number, lng: Number, setdate: String, sethour:String, details: String) {
+ getPaymentIntent(postulation_id: String, supplier_id: String, coupon: String) {
+ const headers = new HttpHeaders({
+ 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
+ });
+ return this.http.post(this.env.API_URL + 'payments/intent',
+ { postulation_id, supplier_id, coupon }, { headers });
+ }
+
+ getExtraPaymentIntent(contract_id: Number, amount: Number) {
+ const headers = new HttpHeaders({
+ 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
+ });
+ return this.http.post(this.env.API_URL + 'payments/extra-intent',
+ { contract_id, amount }, { headers });
+ }
+
+ addPostulation(category: String, property_id: Number, references: String, details: String, photos: string[], related_postulation_id?: Number) {
const headers = new HttpHeaders({
'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
});
return this.http.post(this.env.API_URL + 'create-postulation',
- {category: category, address: address, int_number: int_number, references: references, lat: lat, lng: lng, setdate: setdate, sethour:sethour, details: details}, { headers: headers });
+ { category, property_id, references, details, photos, ...(related_postulation_id ? { related_postulation_id } : {}) }, { headers });
+ }
+
+ cancelPostulation(id: any) {
+ const headers = new HttpHeaders({
+ 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
+ });
+ return this.http.delete(this.env.API_URL + 'postulations/' + id, { headers });
}
deleteCard(card_id: String) {
@@ -118,12 +140,40 @@ export class IchambaService {
{ headers: headers });
}
- setPostulation(postulation_id: String) {
+ setPostulation(postulation_id: String, date1: String, date2: String, amount: Number) {
const headers = new HttpHeaders({
'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
});
return this.http.post(this.env.API_URL + 'postulate',
- {postulation_id: postulation_id}, { headers: headers });
+ { postulation_id, date_1: date1, date_2: date2, amount }, { headers });
+ }
+
+ getProperties() {
+ const headers = new HttpHeaders({
+ 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
+ });
+ return this.http.get(this.env.API_URL + 'properties', { headers });
+ }
+
+ addProperty(name: String, address: String, lat: Number, lng: Number, int_number: String, icon: String) {
+ const headers = new HttpHeaders({
+ 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
+ });
+ return this.http.post(this.env.API_URL + 'properties', { name, address, lat, lng, int_number, icon }, { headers });
+ }
+
+ getContractDiscussion(contractId: any) {
+ const headers = new HttpHeaders({
+ 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
+ });
+ return this.http.get(this.env.API_URL + 'contracts/' + contractId + '/comments', { headers });
+ }
+
+ sendContractMessage(contractId: any, comment: string) {
+ const headers = new HttpHeaders({
+ 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
+ });
+ return this.http.post(this.env.API_URL + 'contracts/' + contractId + '/comments', { comment }, { headers });
}
getPostulants(postulation_id: String) {
@@ -134,20 +184,20 @@ export class IchambaService {
{ headers: headers });
}
- checkCoupon(postulation_id: String, supplier_id: String, coupon: String) {
+ checkCoupon(postulation_id: String, supplier_id: String, selected_date: String, coupon: String) {
const headers = new HttpHeaders({
'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
});
return this.http.post(this.env.API_URL + 'contracts/coupon',
- { postulation_id: postulation_id, supplier_id: supplier_id, coupon: coupon}, { headers: headers });
+ { postulation_id, supplier_id, selected_date, coupon }, { headers: headers });
}
- createContract(postulation_id: String, supplier_id: String, card_id: String, coupon: String, code: String, device_id: String) {
+ createContract(postulation_id: String, supplier_id: String, payment_intent_id: String, selected_date: String, coupon: String) {
const headers = new HttpHeaders({
'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
});
return this.http.post(this.env.API_URL + 'contracts/create',
- { postulation_id: postulation_id, supplier_id: supplier_id, card_id: card_id, coupon: coupon, code: code, device_id: device_id }, { headers: headers });
+ { postulation_id, supplier_id, payment_intent_id, selected_date, coupon }, { headers });
}
cancelContract(contract_id: String) {
@@ -225,12 +275,12 @@ export class IchambaService {
{ contract_id: contract_id, lat: lat, lng: lng, description: description }, { headers: headers });
}
- addExtra(contract_id: Number, amount: Number, card_id: String, code: String, device_id: String) {
+ addExtra(contract_id: Number, amount: Number, payment_intent_id: String) {
const headers = new HttpHeaders({
'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
});
return this.http.post(this.env.API_URL + 'contracts/extra',
- { contract_id: contract_id, amount: amount, card_id: card_id, code: code, device_id: device_id }, { headers: headers });
+ { contract_id, amount, payment_intent_id }, { headers });
}
addHero(name: String, rfc: String, categories: String, tags: String, address: String, lat: Number, lng: Number, bank: Number, bank_account: Number, fee: Number, reference_options: Number, reference: String) {
@@ -240,4 +290,43 @@ export class IchambaService {
return this.http.post(this.env.API_URL + 'add-hero',
{name: name, rfc: rfc, categories: categories, tags: tags, address: address, lat: lat, lng: lng, bank: bank, bank_account: bank_account, fee: fee, reference_options: reference_options, reference: reference}, { headers: headers });
}
+
+ getTeamTechnicians() {
+ const headers = new HttpHeaders({
+ 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
+ });
+ return this.http.get(this.env.API_URL + 'technicians', { headers });
+ }
+
+ findUserByEmail(email: string) {
+ const headers = new HttpHeaders({
+ 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
+ });
+ return this.http.get(this.env.API_URL + 'technicians/find?email=' + encodeURIComponent(email), { headers });
+ }
+
+ addTechnicianByUserId(user_id: any) {
+ const headers = new HttpHeaders({
+ 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
+ });
+ return this.http.post(this.env.API_URL + 'technicians/add', { user_id }, { headers });
+ }
+
+ removeTechnician(technician_id: any) {
+ const headers = new HttpHeaders({
+ 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
+ });
+ return this.http.delete(this.env.API_URL + 'technicians/' + technician_id, { headers });
+ }
+
+ assignTechnician(contract_id: any, technician_id: any) {
+ const headers = new HttpHeaders({
+ 'Authorization': this.authService.token["token_type"]+" "+this.authService.token["access_token"]
+ });
+ return this.http.post(
+ this.env.API_URL + 'contracts/assign-technician',
+ { contract_id, technician_id },
+ { headers }
+ );
+ }
}