feat: UI improvements — foto de perfil, tab-bar Samsung, toast dismiss y mejoras de equipo
- Foto de perfil (con fallback a icono) en contratos, viewsuppliers, team y assign-technician
- Eliminado item "Tarjetas" del side menu y sus rutas del router
- Fix tab-bar cortado en Samsung: --padding-start/end: 2px en ion-tab-button (global.scss)
- Safe area vars explícitas en variables.scss para edge-to-edge en Android
- Botón X para cerrar toasts manualmente (alert.service.ts)
- ion-input de equipo: ion-label separado para evitar color verde de validación en focus
- ion-textarea report-discussion: --highlight-height: 0 y fondo adaptable al tema
- Enter en textarea de discusión envía el mensaje
- HTML en alertas de equipo vía querySelector('.alert-message').innerHTML
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,8 +18,6 @@ const routes: Routes = [
|
|||||||
{ path: 'home', loadChildren: () => import('./home/home.module').then(m => m.HomePageModule), canActivate: [AuthGuard] },
|
{ 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: '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: '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: '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: '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] },
|
{ path: 'viewsuppliers/:postulation_id', loadChildren: () => import('./pages/contracts/viewsuppliers/viewsuppliers.module').then(m => m.ViewsuppliersPageModule), canActivate: [AuthGuard] },
|
||||||
@@ -33,6 +31,7 @@ const routes: Routes = [
|
|||||||
{ path: 'hero', loadChildren: () => import('./pages/hero/hero.module').then(m => m.HeroPageModule), 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: '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: '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({
|
@NgModule({
|
||||||
|
|||||||
@@ -14,12 +14,6 @@
|
|||||||
<ion-label>{{'menu.home' | translate}}</ion-label>
|
<ion-label>{{'menu.home' | translate}}</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-menu-toggle>
|
</ion-menu-toggle>
|
||||||
<ion-menu-toggle auto-hide="false" [class.ion-hide]="userRole === 0">
|
|
||||||
<ion-item [routerDirection]="'root'" [routerLink]="['/cards']">
|
|
||||||
<ion-icon slot="start" name="card"></ion-icon>
|
|
||||||
<ion-label>{{'menu.cards' | translate}}</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
</ion-menu-toggle>
|
|
||||||
<ion-menu-toggle auto-hide="false" [class.ion-hide]="userRole < 2">
|
<ion-menu-toggle auto-hide="false" [class.ion-hide]="userRole < 2">
|
||||||
<ion-item [routerDirection]="'root'" [routerLink]="['/postulations']">
|
<ion-item [routerDirection]="'root'" [routerLink]="['/postulations']">
|
||||||
<ion-icon slot="start" name="hammer"></ion-icon>
|
<ion-icon slot="start" name="hammer"></ion-icon>
|
||||||
@@ -50,6 +44,12 @@
|
|||||||
<ion-label>{{'menu.hero' | translate}}</ion-label>
|
<ion-label>{{'menu.hero' | translate}}</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-menu-toggle>
|
</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()">
|
<ion-item (click)="logout()">
|
||||||
<ion-icon slot="start" name="log-out"></ion-icon>
|
<ion-icon slot="start" name="log-out"></ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<ng-container *ngIf="loading">
|
<ng-container *ngIf="loading">
|
||||||
<ion-card *ngFor="let _ of [1,2]">
|
<ion-card *ngFor="let _ of [1,2]">
|
||||||
<ion-item style="--border-color: #fff">
|
<ion-item class="no-separator">
|
||||||
<ion-label>
|
<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: 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: 85%"></ion-skeleton-text>
|
||||||
@@ -31,7 +31,11 @@
|
|||||||
<ng-container *ngIf="!loading">
|
<ng-container *ngIf="!loading">
|
||||||
<ng-container *ngFor="let ccontract of ccontracts; let i = index">
|
<ng-container *ngFor="let ccontract of ccontracts; let i = index">
|
||||||
<ion-card *ngIf="ccontract.status==1">
|
<ion-card *ngIf="ccontract.status==1">
|
||||||
<ion-item style="--border-color: #fff">
|
<ion-item 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>
|
<ion-label>
|
||||||
<h2 *ngIf="lang===true" class="ion-text-capitalize">{{ ccontract.category }}</h2>
|
<h2 *ngIf="lang===true" class="ion-text-capitalize">{{ ccontract.category }}</h2>
|
||||||
<h2 *ngIf="lang===false" class="ion-text-capitalize">{{ ccontract.en_category }}</h2>
|
<h2 *ngIf="lang===false" class="ion-text-capitalize">{{ ccontract.en_category }}</h2>
|
||||||
@@ -43,8 +47,10 @@
|
|||||||
<h2 class="ion-text-wrap">{{'contracts.start_pin' | translate}}<b>{{ ccontract.code }}</b></h2>
|
<h2 class="ion-text-wrap">{{'contracts.start_pin' | translate}}<b>{{ ccontract.code }}</b></h2>
|
||||||
<p class="ion-text-wrap">{{'contracts.info_pin' | translate}}</p>
|
<p class="ion-text-wrap">{{'contracts.info_pin' | translate}}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-button *ngIf="ccontract.past_due < 0" style="height: 4.0em; padding-right: 1.0em; padding-bottom:1.0em; float: right" color="danger" (click)="cancelContract(ccontract.id, ccontract.date)">{{'contracts.cancel_1.1' | translate}}<br>{{'contracts.cancel_1.2' | translate}}</ion-button>
|
<div *ngIf="ccontract.past_due < 0" style="display: flex; justify-content: flex-end; padding: 0.5em 1em 1.25em;">
|
||||||
|
<ion-button 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>
|
</ion-card>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -19,6 +19,10 @@
|
|||||||
<img style="display:block;width:100%;height:100%" src="{{ pcontractsupplier.cover_photo }}" *ngIf="pcontractsupplier.supplier != null"/>
|
<img style="display:block;width:100%;height:100%" src="{{ pcontractsupplier.cover_photo }}" *ngIf="pcontractsupplier.supplier != null"/>
|
||||||
</ion-card-header>
|
</ion-card-header>
|
||||||
<ion-item *ngIf="pcontractsupplier.supplier != null">
|
<ion-item *ngIf="pcontractsupplier.supplier != null">
|
||||||
|
<ion-avatar slot="start">
|
||||||
|
<img *ngIf="pcontractsupplier.profile_photo" [src]="pcontractsupplier.profile_photo" (error)="pcontractsupplier.profile_photo=null">
|
||||||
|
<ion-icon *ngIf="!pcontractsupplier.profile_photo" name="person-circle" style="width:100%;height:100%;color:var(--ion-color-medium)"></ion-icon>
|
||||||
|
</ion-avatar>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2 class="ion-text-wrap ion-text-capitalize" *ngIf="pcontractsupplier.supplier != null">{{ pcontractsupplier.supplier }}</h2>
|
<h2 class="ion-text-wrap ion-text-capitalize" *ngIf="pcontractsupplier.supplier != null">{{ pcontractsupplier.supplier }}</h2>
|
||||||
<p class="ion-text-wrap" *ngIf="pcontractsupplier.supplier != null"><ion-icon name="checkmark-circle" color="secondary" *ngIf="pcontractsupplier.membership != false"></ion-icon> {{ pcontractssuppliers_certified[i] }}</p>
|
<p class="ion-text-wrap" *ngIf="pcontractsupplier.supplier != null"><ion-icon name="checkmark-circle" color="secondary" *ngIf="pcontractsupplier.membership != false"></ion-icon> {{ pcontractssuppliers_certified[i] }}</p>
|
||||||
|
|||||||
@@ -82,6 +82,8 @@
|
|||||||
placeholder="Escribe un comentario..."
|
placeholder="Escribe un comentario..."
|
||||||
rows="1"
|
rows="1"
|
||||||
autoGrow="true"
|
autoGrow="true"
|
||||||
|
style="--highlight-height: 0"
|
||||||
|
(keydown.enter)="$event.preventDefault(); newMessage.trim() && !sending && sendMessage()"
|
||||||
></ion-textarea>
|
></ion-textarea>
|
||||||
<ion-button
|
<ion-button
|
||||||
fill="clear"
|
fill="clear"
|
||||||
|
|||||||
@@ -94,8 +94,7 @@
|
|||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
padding: 6px 6px;
|
padding: 6px 6px;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
background: var(--ion-background-color, #fff);
|
background: var(--ion-tab-bar-background, #fff);
|
||||||
border-top: 1px solid var(--ion-color-light-shade);
|
|
||||||
|
|
||||||
ion-textarea {
|
ion-textarea {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -103,7 +102,7 @@
|
|||||||
--padding-end: 14px;
|
--padding-end: 14px;
|
||||||
--padding-top: 8px;
|
--padding-top: 8px;
|
||||||
--padding-bottom: 8px;
|
--padding-bottom: 8px;
|
||||||
background: var(--ion-color-light);
|
--background: var(--ion-color-step-100, #f4f5f8);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
max-height: 120px;
|
max-height: 120px;
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<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>Asignar Técnico</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content class="ion-padding">
|
||||||
|
<ng-container *ngIf="loading">
|
||||||
|
<ion-item *ngFor="let _ of [1,2,3]">
|
||||||
|
<ion-label>
|
||||||
|
<ion-skeleton-text [animated]="true" style="width: 60%; height: 16px; margin-bottom: 6px"></ion-skeleton-text>
|
||||||
|
<ion-skeleton-text [animated]="true" style="width: 45%"></ion-skeleton-text>
|
||||||
|
</ion-label>
|
||||||
|
<ion-skeleton-text [animated]="true" slot="end" style="width: 70px; height: 32px"></ion-skeleton-text>
|
||||||
|
</ion-item>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!loading">
|
||||||
|
<p *ngIf="technicians.length === 0" class="ion-text-center" style="color: var(--ion-color-medium); margin-top: 32px">
|
||||||
|
No tienes técnicos en tu equipo. Agrégalos desde la sección <strong>Técnicos</strong> en el menú.
|
||||||
|
</p>
|
||||||
|
<ion-list>
|
||||||
|
<ion-item *ngFor="let tech of technicians">
|
||||||
|
<ion-avatar slot="start">
|
||||||
|
<img *ngIf="tech.profile_photo" [src]="tech.profile_photo" (error)="tech.profile_photo=null">
|
||||||
|
<ion-icon *ngIf="!tech.profile_photo" name="person-circle" style="width:100%;height:100%;color:var(--ion-color-medium)"></ion-icon>
|
||||||
|
</ion-avatar>
|
||||||
|
<ion-label>
|
||||||
|
<h3>{{ tech.name }}</h3>
|
||||||
|
<p>{{ tech.email }}</p>
|
||||||
|
</ion-label>
|
||||||
|
<ion-button slot="end" (click)="assign(tech)" [disabled]="assigning">
|
||||||
|
Asignar
|
||||||
|
</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</ng-container>
|
||||||
|
</ion-content>
|
||||||
59
src/app/pages/team/team.page.html
Normal file
59
src/app/pages/team/team.page.html
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<ion-header>
|
||||||
|
<ion-toolbar color="primary">
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-menu-button></ion-menu-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>Técnicos</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content class="ion-padding">
|
||||||
|
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
|
||||||
|
<ion-refresher-content></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
|
||||||
|
<ion-item lines="full" style="margin-bottom: 16px; --padding-start: 0">
|
||||||
|
<ion-label position="stacked">Agregar técnico</ion-label>
|
||||||
|
<ion-input
|
||||||
|
[(ngModel)]="searchEmail"
|
||||||
|
type="email"
|
||||||
|
placeholder="Correo electrónico del técnico"
|
||||||
|
></ion-input>
|
||||||
|
<ion-button slot="end" (click)="addTechnician()" [disabled]="searching || !searchEmail.trim()">
|
||||||
|
<ion-icon slot="start" name="person-add-outline"></ion-icon>
|
||||||
|
Agregar
|
||||||
|
</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<h3 style="margin-top: 16px">Mi equipo</h3>
|
||||||
|
|
||||||
|
<ng-container *ngIf="loading">
|
||||||
|
<ion-item *ngFor="let _ of [1,2,3]">
|
||||||
|
<ion-label>
|
||||||
|
<ion-skeleton-text [animated]="true" style="width: 60%; height: 16px; margin-bottom: 6px"></ion-skeleton-text>
|
||||||
|
<ion-skeleton-text [animated]="true" style="width: 45%"></ion-skeleton-text>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!loading">
|
||||||
|
<p *ngIf="technicians.length === 0" class="ion-text-center" style="color: var(--ion-color-medium); margin-top: 32px">
|
||||||
|
Aún no tienes técnicos en tu equipo.
|
||||||
|
</p>
|
||||||
|
<ion-list>
|
||||||
|
<ion-item *ngFor="let tech of technicians">
|
||||||
|
<ion-avatar slot="start">
|
||||||
|
<img *ngIf="tech.profile_photo" [src]="tech.profile_photo" (error)="tech.profile_photo=null">
|
||||||
|
<ion-icon *ngIf="!tech.profile_photo" name="person-circle" style="width:100%;height:100%;color:var(--ion-color-medium)"></ion-icon>
|
||||||
|
</ion-avatar>
|
||||||
|
<ion-label>
|
||||||
|
<h3>{{ tech.name }}</h3>
|
||||||
|
<p>{{ tech.email }}</p>
|
||||||
|
</ion-label>
|
||||||
|
<ion-button slot="end" fill="clear" color="danger" (click)="confirmRemove(tech)">
|
||||||
|
<ion-icon slot="icon-only" name="trash"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</ng-container>
|
||||||
|
</ion-content>
|
||||||
124
src/app/pages/team/team.page.ts
Normal file
124
src/app/pages/team/team.page.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { Component, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { AlertController } from '@ionic/angular';
|
||||||
|
import { IchambaService } from 'src/app/services/ichamba.service';
|
||||||
|
import { AlertService } from 'src/app/services/alert.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-team',
|
||||||
|
templateUrl: './team.page.html',
|
||||||
|
styleUrls: ['./team.page.scss'],
|
||||||
|
standalone: false
|
||||||
|
})
|
||||||
|
export class TeamPage {
|
||||||
|
|
||||||
|
technicians: any[] = [];
|
||||||
|
loading = true;
|
||||||
|
searchEmail = '';
|
||||||
|
searching = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private ichambaService: IchambaService,
|
||||||
|
private alertService: AlertService,
|
||||||
|
private alertCtrl: AlertController,
|
||||||
|
private cdr: ChangeDetectorRef,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ionViewWillEnter() {
|
||||||
|
this.loadTechnicians();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTechnicians() {
|
||||||
|
this.loading = true;
|
||||||
|
this.ichambaService.getTeamTechnicians().subscribe({
|
||||||
|
next: data => {
|
||||||
|
this.technicians = data;
|
||||||
|
this.loading = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.loading = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh(event: any) {
|
||||||
|
this.ichambaService.getTeamTechnicians().subscribe({
|
||||||
|
next: data => {
|
||||||
|
this.technicians = data;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
event.target.complete();
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
event.target.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async addTechnician() {
|
||||||
|
const email = this.searchEmail.trim();
|
||||||
|
if (!email || this.searching) return;
|
||||||
|
this.searching = true;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
|
||||||
|
this.ichambaService.findUserByEmail(email).subscribe({
|
||||||
|
next: async (user: any) => {
|
||||||
|
this.searching = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
const alert = await this.alertCtrl.create({
|
||||||
|
header: 'Confirmar',
|
||||||
|
message: ' ',
|
||||||
|
buttons: [
|
||||||
|
{ text: 'Cancelar', role: 'cancel' },
|
||||||
|
{ text: 'Agregar', handler: () => this.confirmAdd(user.id) }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
await alert.present();
|
||||||
|
alert.querySelector('.alert-message')!.innerHTML = `¿Estás seguro de agregar a <strong>${user.name}</strong> a tu compañía?`;
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.searching = false;
|
||||||
|
this.alertService.presentToast('No existe ningún usuario asignado a esa cuenta');
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmAdd(userId: any) {
|
||||||
|
this.ichambaService.addTechnicianByUserId(userId).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.alertService.presentToast('Técnico agregado exitosamente');
|
||||||
|
this.searchEmail = '';
|
||||||
|
this.loadTechnicians();
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.alertService.presentToast('No se pudo agregar el técnico.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async confirmRemove(tech: any) {
|
||||||
|
const alert = await this.alertCtrl.create({
|
||||||
|
header: 'Eliminar técnico',
|
||||||
|
message: ' ',
|
||||||
|
buttons: [
|
||||||
|
{ text: 'Cancelar', role: 'cancel' },
|
||||||
|
{ text: 'Eliminar', role: 'destructive', handler: () => this.removeTechnician(tech.id) }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
await alert.present();
|
||||||
|
alert.querySelector('.alert-message')!.innerHTML = `¿Estás seguro de eliminar a <strong>${tech.name}</strong> de tu equipo?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTechnician(technicianId: any) {
|
||||||
|
this.ichambaService.removeTechnician(technicianId).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.alertService.presentToast('Técnico eliminado');
|
||||||
|
this.loadTechnicians();
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.alertService.presentToast('No se pudo eliminar el técnico.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,8 @@ export class AlertService {
|
|||||||
message: message,
|
message: message,
|
||||||
duration: 5000,
|
duration: 5000,
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
color: 'dark'
|
color: 'dark',
|
||||||
|
buttons: [{ icon: 'close', role: 'cancel' }]
|
||||||
});
|
});
|
||||||
toast.present();
|
toast.present();
|
||||||
}
|
}
|
||||||
|
|||||||
100
src/global.scss
100
src/global.scss
@@ -27,6 +27,106 @@
|
|||||||
|
|
||||||
/* Swiper styles - Swiper 11 uses bundled styles */
|
/* Swiper styles - Swiper 11 uses bundled styles */
|
||||||
|
|
||||||
|
/* Safe area bottom padding for edge-to-edge layout */
|
||||||
|
ion-content.ion-padding {
|
||||||
|
--padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-footer {
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode overrides para componentes que no heredan --ion-background-color */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
|
||||||
|
/* Menú lateral */
|
||||||
|
ion-menu ion-content {
|
||||||
|
--background: #1B1B1B;
|
||||||
|
}
|
||||||
|
ion-menu ion-list {
|
||||||
|
background: #1B1B1B;
|
||||||
|
}
|
||||||
|
ion-menu ion-item {
|
||||||
|
--border-color: #2d2d2d;
|
||||||
|
--color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Searchbar: .autocomplete sube especificidad a 0,1,1 para ganar a .sc-ion-searchbar-md-h (0,1,0) */
|
||||||
|
.autocomplete ion-searchbar {
|
||||||
|
--background: #2a2a2a;
|
||||||
|
--color: #ffffff;
|
||||||
|
--icon-color: #888888;
|
||||||
|
--cancel-button-color: #888888;
|
||||||
|
--clear-button-color: #888888;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Listboxes de autocomplete: color y borde en dark */
|
||||||
|
ion-list ion-item {
|
||||||
|
--background: transparent;
|
||||||
|
--color: #ffffff;
|
||||||
|
--border-color: #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown: borde y sombra en dark */
|
||||||
|
.dropdown-list {
|
||||||
|
border-color: #808080;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
.dropdown-list ion-item:hover {
|
||||||
|
--background: #252525;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FAQ acordeones: adaptar color="light" hardcodeado al dark theme */
|
||||||
|
ion-accordion ion-item {
|
||||||
|
--ion-color-light: var(--ion-card-background);
|
||||||
|
--ion-color-light-rgb: 40, 40, 40;
|
||||||
|
--ion-color-light-contrast: #ffffff;
|
||||||
|
--ion-color-light-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-light-shade: #161616;
|
||||||
|
--ion-color-light-tint: #252525;
|
||||||
|
}
|
||||||
|
ion-accordion [slot="content"] {
|
||||||
|
background: var(--ion-background-color);
|
||||||
|
color: var(--ion-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Welcome card del dashboard: sin fondo */
|
||||||
|
.welcome-card {
|
||||||
|
--background: transparent;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Texto dentro de ion-card en dark mode */
|
||||||
|
ion-card {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
ion-card ion-card-title,
|
||||||
|
ion-card ion-card-subtitle,
|
||||||
|
ion-card ion-card-header,
|
||||||
|
ion-card ion-card-content {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.sc-ion-label-md-s > p {
|
||||||
|
color: var(--ion-color-step-800, #cccccc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown de autocomplete: fondo sólido adaptable al tema */
|
||||||
|
.dropdown-list {
|
||||||
|
background: var(--ion-background-color, #ffffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Separador entre ion-item y botón en tarjetas de contrato */
|
||||||
|
.no-separator {
|
||||||
|
--border-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-tab-button {
|
||||||
|
--padding-start: 2px;
|
||||||
|
--padding-end: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Desktop/Web responsive styles */
|
/* Desktop/Web responsive styles */
|
||||||
ion-app {
|
ion-app {
|
||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
|
|||||||
@@ -11,6 +11,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
--ion-safe-area-top: env(safe-area-inset-top, 0px);
|
||||||
|
--ion-safe-area-bottom: env(safe-area-inset-bottom, 0px);
|
||||||
|
--ion-safe-area-left: env(safe-area-inset-left, 0px);
|
||||||
|
--ion-safe-area-right: env(safe-area-inset-right, 0px);
|
||||||
|
|
||||||
/** primary **/
|
/** primary **/
|
||||||
--ion-color-primary: #0080ff;
|
--ion-color-primary: #0080ff;
|
||||||
--ion-color-primary-rgb: 0, 128, 255;
|
--ion-color-primary-rgb: 0, 128, 255;
|
||||||
@@ -86,3 +91,40 @@
|
|||||||
/** social networks **/
|
/** social networks **/
|
||||||
--ion-color-facebook: #3b5998;
|
--ion-color-facebook: #3b5998;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--ion-background-color: #1B1B1B;
|
||||||
|
--ion-background-color-rgb: 27, 27, 27;
|
||||||
|
|
||||||
|
--ion-text-color: #ffffff;
|
||||||
|
--ion-text-color-rgb: 255, 255, 255;
|
||||||
|
|
||||||
|
--ion-item-background: transparent;
|
||||||
|
--ion-toolbar-background: #111111;
|
||||||
|
--ion-tab-bar-background: #111111;
|
||||||
|
--ion-card-background: #252525;
|
||||||
|
--ion-border-color: #808080;
|
||||||
|
|
||||||
|
--ion-color-step-50: #1a1a1a;
|
||||||
|
--ion-color-step-100: #1e1e1e;
|
||||||
|
--ion-color-step-150: #252525;
|
||||||
|
--ion-color-step-200: #2b2b2b;
|
||||||
|
--ion-color-step-250: #323232;
|
||||||
|
--ion-color-step-300: #393939;
|
||||||
|
--ion-color-step-350: #3f3f3f;
|
||||||
|
--ion-color-step-400: #464646;
|
||||||
|
--ion-color-step-500: #545454;
|
||||||
|
--ion-color-step-600: #616161;
|
||||||
|
--ion-color-step-700: #6f6f6f;
|
||||||
|
--ion-color-step-800: #d1d1d1;
|
||||||
|
--ion-color-step-800: #9e9e9e;
|
||||||
|
--ion-color-step-850: #b0b0b0;
|
||||||
|
--ion-color-step-900: #c3c3c3;
|
||||||
|
--ion-color-step-950: #d6d6d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #1B1B1B;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user