Add user info modal

This commit is contained in:
Matteo Gheza 2024-01-04 00:41:41 +01:00
parent a044ead825
commit c63a9789cc
14 changed files with 252 additions and 9 deletions

View File

@ -51,9 +51,11 @@ class UserController extends Controller
/**
* Display the specified resource.
*/
public function show(User $user)
public function show(Request $request, $id)
{
//
User::where('id', $request->user()->id)->update(['last_access' => now()]);
return response()->json(User::findOrFail($id));
}
/**

View File

@ -24,6 +24,7 @@ class User extends Authenticatable implements LaratrustUser
*/
protected $fillable = [
'name',
'surname',
'username',
'email',
'phone_number',
@ -37,6 +38,10 @@ class User extends Authenticatable implements LaratrustUser
'banned',
'hidden',
'password',
'birthplace',
'birthplace_province',
'ssn',
'address'
];
/**
@ -58,6 +63,8 @@ class User extends Authenticatable implements LaratrustUser
'email_verified_at' => 'datetime',
'last_access' => 'datetime',
'last_availability_change' => 'datetime',
'birthday' => 'datetime',
'course_date' => 'datetime'
];
/**

View File

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('surname')->nullable()->after('name');
$table->string('birthplace')->nullable()->after('surname');
$table->string('birthplace_province')->nullable()->after('birthplace');
$table->string('ssn')->nullable()->after('birthplace_province');
$table->string('address')->nullable()->after('ssn');
$table->timestamp('birthday')->nullable()->after('last_availability_change');
$table->timestamp('course_date')->nullable()->after('birthday');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('surname');
$table->dropColumn('birthplace');
$table->dropColumn('birthplace_province');
$table->dropColumn('ssn');
$table->dropColumn('address');
$table->dropColumn('birthday');
$table->dropColumn('course_date');
});
}
};

View File

@ -45,6 +45,8 @@ Route::middleware('auth:sanctum')->group( function () {
Route::get('/list', [UserController::class, 'index'])->middleware(ETag::class);
Route::get('/users/{id}', [UserController::class, 'show']);
Route::get('/schedules', [ScheduleSlotsController::class, 'index']);
Route::post('/schedules', [ScheduleSlotsController::class, 'store']);

View File

@ -0,0 +1,117 @@
<div class="modal-header">
<h4 class="modal-title pull-left" translate>user_info_modal.title</h4>
<button
type="button"
class="btn-close close pull-right"
[attr.aria-label]="'close' | translate | titlecase"
(click)="bsModalRef.hide()"
>
<span aria-hidden="true" class="visually-hidden">&times;</span>
</button>
</div>
<div class="modal-body" *ngIf="id == 0 || loaded == false">
<div class="d-flex justify-content-center mt-2 pt-2 mb-3">
<div class="spinner spinner-border"></div>
</div>
</div>
<div class="modal-body" *ngIf="id !== 0 && loaded">
<h2 class="text-center">{{ user.surname }} {{ user.name }}</h2>
<table class="mx-auto" style="border-collapse: separate; border-spacing: 0px">
<th></th>
<th></th>
<tr *ngIf="user.surname">
<td>Cognome</td>
<td>{{ user.surname }}</td>
</tr>
<tr>
<td>Nome</td>
<td>{{ user.name }}</td>
</tr>
<tr>
<td>Username</td>
<td>{{ user.username }}</td>
</tr>
<tr *ngIf="user.birthday">
<td>Data di nascita</td>
<td>{{ user.birthday | date:'dd/MM/yyyy' }}</td>
</tr>
<tr *ngIf="user.birthplace">
<td>Luogo di nascita</td>
<td>{{ user.birthplace }}<ng-container *ngIf="user.birthplace_province"> ({{ user.birthplace_province }})</ng-container></td>
</tr>
<tr *ngIf="user.ssn">
<td>Codice fiscale</td>
<td>{{ user.ssn }}</td>
</tr>
<tr>
<td colspan="2" class="text-center">
<br />
<h5>RECAPITI</h5>
</td>
</tr>
<tr *ngIf="user.address">
<td>Indirizzo</td>
<td>
<a href="https://maps.google.com/?q={{ user.address }}" target="_blank">
{{ user.address }}
</a>
</td>
</tr>
<tr>
<td>Telefono</td>
<td><a href="tel:{{ user.phone_number }}">{{ user.phone_number }}</a></td>
</tr>
<tr>
<td>Email</td>
<td><a href="mailto:{{ user.email }}">{{ user.email }}</a></td>
</tr>
<!--
<tr>
<td colspan="2" class="text-center">
<br />
<h5>DOCUMENTI</h5>
</td>
</tr>
<tr>
<td>
<a href='./aj/download.php?f=p&d=pat' target='_blank'>Patente</a>
</td>
<td>
<small>Nr:</small> 123456789<br><small>Scad:</small> 11/02/2024
</td>
</tr>
-->
<tr>
<td colspan="2" class="text-center">
<br />
<h5>INFORMAZIONI SERVIZIO</h5>
</td>
</tr>
<tr>
<td>Autista</td>
<td>
<i class="fa fa-check" style="color:green" *ngIf="user.driver"></i>
<i class="fa fa-times" style="color:red" *ngIf="!user.driver"></i>
</td>
</tr>
<tr>
<td>Tipologia</td>
<td>
<img alt="red helmet" src="./assets/icons/red_helmet.png" width="20px" *ngIf="user.chief">
<img alt="black helmet" src="./assets/icons/black_helmet.png" width="20px" *ngIf="!user.chief">
</td>
</tr>
<tr *ngIf="user.course_date">
<td>Data corso</td>
<td>{{ user.course_date }}</td>
</tr>
</table>
</div>
<div class="modal-footer">
<button *ngIf="false" type="button" class="btn btn-primary" (click)="goToEditPage()" [disabled]="!canGoToEditPage">
{{ "edit" | translate | titlecase }}
</button>
<button type="button" class="btn btn-secondary" (click)="bsModalRef.hide()">
{{ "close" | translate }}
</button>
</div>

View File

@ -0,0 +1,3 @@
td {
border-bottom: 1px solid #ddd;
}

View File

@ -0,0 +1,46 @@
import { Component, OnInit } from '@angular/core';
import { BsModalRef } from 'ngx-bootstrap/modal';
import { ApiClientService } from 'src/app/_services/api-client.service';
import { AuthService } from 'src/app/_services/auth.service';
import { ToastrService } from 'ngx-toastr';
import { TranslateService } from '@ngx-translate/core';
import Swal from 'sweetalert2';
@Component({
selector: 'modal-user-info',
templateUrl: './modal-user-info.component.html',
styleUrls: ['./modal-user-info.component.scss']
})
export class ModalUserInfoComponent implements OnInit {
id = 0;
loaded = false;
canGoToEditPage = false;
user: any = {};
constructor(
public bsModalRef: BsModalRef,
private api: ApiClientService,
public auth: AuthService,
private toastr: ToastrService,
private translate: TranslateService
) { }
ngOnInit() {
this.api.get(`users/${this.id}`).then((response) => {
this.user = response;
this.loaded = true;
console.log(response);
}).catch((err) => {
console.log(err);
});
this.canGoToEditPage = this.auth.profile.id === this.id || this.auth.profile.can('users-read');
}
goToEditPage() {
if(!this.canGoToEditPage) return;
this.bsModalRef.hide();
}
}

View File

@ -27,10 +27,12 @@
<tr *ngFor="let row of data">
<td>
<i *ngIf="auth.profile.can('users-impersonate') && row.id !== auth.profile.id" class="fa fa-user me-2" (click)="onUserImpersonate(row.id)"></i>
<img alt="red helmet" src="./assets/icons/red_helmet.png" width="20px" *ngIf="row.chief">
<img alt="red helmet" src="./assets/icons/black_helmet.png" width="20px" *ngIf="!row.chief">
<ng-container *ngIf="row.online;else userOffline"><u>{{ row.name }}</u></ng-container>
<ng-template #userOffline>{{ row.name }}</ng-template>
<div (click)="onMoreDetails(row.id)" class="d-inline">
<img alt="red helmet" src="./assets/icons/red_helmet.png" width="20px" *ngIf="row.chief">
<img alt="black helmet" src="./assets/icons/black_helmet.png" width="20px" *ngIf="!row.chief">
<ng-container *ngIf="row.online;else userOffline"><u>{{ row.name }}</u></ng-container>
<ng-template #userOffline>{{ row.name }}</ng-template>
</div>
</td>
<td (click)="onChangeAvailability(row.id, row.available ? 0 : 1)">
<i class="fa fa-check" style="color:green" *ngIf="row.available"></i>

View File

@ -63,6 +63,7 @@ export class TableComponent implements OnInit, OnDestroy {
@Output() changeAvailability: EventEmitter<{user: number, newState: 0|1}> = new EventEmitter<{user: number, newState: 0|1}>();
@Output() userImpersonate: EventEmitter<number> = new EventEmitter<number>();
@Output() moreDetails: EventEmitter<{rowId: number}> = new EventEmitter<{rowId: number}>();
public data: any = [];
public displayedData: any = [];
@ -199,6 +200,10 @@ export class TableComponent implements OnInit, OnDestroy {
}
}
onMoreDetails(rowId: number) {
this.moreDetails.emit({rowId});
}
openPlaceDetails(id: number) {
this.router.navigate(['/place-details', id]);
}

View File

@ -28,7 +28,7 @@
</button>
</div>
</div>
<app-table [sourceType]="'list'" (changeAvailability)="changeAvailibility($event.newState, $event.user)" #table></app-table>
<app-table [sourceType]="'list'" (changeAvailability)="changeAvailibility($event.newState, $event.user)" (moreDetails)="openUserInfoPage($event.rowId)" #table></app-table>
<div class="text-center">
<button (click)="requestTelegramToken()" class="btn btn-md btn-success mt-3">{{ 'list.connect_telegram_bot'|translate }}</button>
<div class="alert alert-primary mt-4" role="alert" translate>

View File

@ -2,6 +2,7 @@ import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { TableComponent } from '../../_components/table/table.component';
import { ModalAvailabilityScheduleComponent } from '../../_components/modal-availability-schedule/modal-availability-schedule.component';
import { ModalAlertComponent } from 'src/app/_components/modal-alert/modal-alert.component';
import { ModalUserInfoComponent } from 'src/app/_components/modal-user-info/modal-user-info.component';
import { ApiClientService } from 'src/app/_services/api-client.service';
import { ToastrService } from 'ngx-toastr';
import { BsModalService, BsModalRef } from 'ngx-bootstrap/modal';
@ -119,6 +120,14 @@ export class ListComponent implements OnInit, OnDestroy {
});
}
openUserInfoPage(id: number) {
this.modalService.show(ModalUserInfoComponent, {
initialState: {
id
}
});
}
ngOnInit(): void {
this.loadAvailabilityInterval = setInterval(() => {
this.loadAvailability();

View File

@ -23,6 +23,7 @@ import { environment } from '../environments/environment';
import { TableComponent } from './_components/table/table.component';
import { ModalAvailabilityScheduleComponent } from './_components/modal-availability-schedule/modal-availability-schedule.component';
import { ModalAlertComponent } from './_components/modal-alert/modal-alert.component';
import { ModalUserInfoComponent } from './_components/modal-user-info/modal-user-info.component';
import { OwnerImageComponent } from './_components/owner-image/owner-image.component';
import { DaterangePickerModule } from './_components/daterange-picker/daterange-picker.module';
@ -43,6 +44,7 @@ import { AuthInterceptor } from './_providers/auth-interceptor.provider';
TableComponent,
ModalAvailabilityScheduleComponent,
ModalAlertComponent,
ModalUserInfoComponent,
OwnerImageComponent,
//
LoginComponent,

View File

@ -100,6 +100,9 @@
"training_load_failed": "Errore durante il caricamento dell'intervento. Riprovare più tardi",
"users_load_failed": "Errore durante il caricamento degli utenti. Riprovare più tardi"
},
"user_info_modal": {
"title": "User info"
},
"warning": "warning",
"press_for_more_info": "press here for more info",
"update_availability_schedule": "Update availability schedule",
@ -162,5 +165,6 @@
"press_to_select_a_date": "press to select a date",
"footer_text": "Allerta-VVF, free software developed for volunteer firefighters brigades.",
"revision": "revision",
"unknown": "unknown"
"unknown": "unknown",
"edit": "edit"
}

View File

@ -100,6 +100,9 @@
"training_load_failed": "Errore durante il caricamento dell'intervento. Riprovare più tardi",
"users_load_failed": "Errore durante il caricamento degli utenti. Riprovare più tardi"
},
"user_info_modal": {
"title": "Scheda utente"
},
"warning": "attenzione",
"press_for_more_info": "premi qui per informazioni",
"update_availability_schedule": "Aggiorna programmazione disponibilità",
@ -162,5 +165,6 @@
"press_to_select_a_date": "premi per selezionare una data",
"footer_text": "Allerta-VVF, software libero realizzato per i Vigili del Fuoco volontari.",
"revision": "revisione",
"unknown": "sconosciuto"
"unknown": "sconosciuto",
"edit": "modifica"
}