Files
Jobhero_front/src/app/pages/hero/hero.page.ts
Carlos 0efe318db3 feat: Mejoras en formulario hero, estilos globales y OneSignal
- Formulario hero: validación por campo con estado touched/submitted,
  mensajes de error inline, máscara de moneda en cuota, autocomplete
  flotante para dirección y categorías con clearInput, chips para
  categorías y palabras clave, campos banco/CLABE/RFC
- Estilos globales: border-radius y sombra en ion-button, borde para
  botones light sobre fondo claro, sin borde en toolbars de color
- OneSignal: reescritura del servicio con login/logout, addTag y
  routing por título de notificación usando NgZone
- FAQ: color primary en acordeón activo/desplegado

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 21:23:13 -06:00

250 lines
7.7 KiB
TypeScript
Executable File

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';
declare var google: any;
@Component({
selector: 'app-hero',
templateUrl: './hero.page.html',
styleUrls: ['./hero.page.scss'],
standalone: false
})
export class HeroPage implements OnInit {
private loading: any;
categories: any[] = [];
categories_input: any[] = [];
categories_rearranged: 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.ichambaService.getCategories()
.subscribe( categories => {
this.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('/dashboard');
}
autocomplete(ev: any) {
const value = (ev.detail?.value ?? this.addressAutocomplete).trim();
this.myAddress = null;
if (!value.length) {
this.placesSearch = null;
return;
}
new google.maps.places.AutocompleteService().getPredictions({ input: value }, (predictions: any) => {
this.ngZone.run(() => {
this.placesSearch = predictions;
});
});
}
geoloc(place_id: string, place_description: string, place_intnumber: string) {
this.myAddress = place_description;
this.addressAutocomplete = place_description;
this.myIntnumber = place_intnumber;
this.placesSearch = null;
this.hidelist();
new google.maps.Geocoder().geocode({ placeId: place_id }, (coordinates: any) => {
this.ngZone.run(() => {
const result = coordinates[0];
this.myPosition = {
latitude: result.geometry.location.lat(),
longitude: result.geometry.location.lng()
};
});
});
}
selected_reference(ev: any) {
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(){
this.submitted = true;
const categoriesString = this.categories_input.join(',');
const keywordsString = this.keywords.length > 0 ? this.keywords.join(', ') : '';
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;
}
hidelist() {
this.showif = false;
}
}