Add edit-user page WIP
This commit is contained in:
parent
80b4c2ac91
commit
dbc50da95a
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules\File;
|
||||
use App\Models\DocumentFile;
|
||||
|
||||
class DocumentsController extends Controller
|
||||
{
|
||||
public function uploadDrivingLicenseScan(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'file' => [
|
||||
'required',
|
||||
File::image()
|
||||
->max(5 * 1024)
|
||||
]
|
||||
]);
|
||||
|
||||
$fileName = time() . '_' . $request->file->getClientOriginalName();
|
||||
$filePath = $request->file('file')->storeAs('driving_license_scans', $fileName, 'public');
|
||||
|
||||
$uuid = Str::uuid()->toString();
|
||||
|
||||
$document = new DocumentFile();
|
||||
$document->uuid = $uuid;
|
||||
$document->type = 'driving_license';
|
||||
$document->file_path = $filePath;
|
||||
$document->uploadedBy()->associate(auth()->user());
|
||||
$document->save();
|
||||
|
||||
return response()->json([
|
||||
"uuid" => $document->uuid
|
||||
]);
|
||||
}
|
||||
|
||||
public function serveDrivingLicenseScan($uuid)
|
||||
{
|
||||
//TODO: check if the user has access to the document
|
||||
$document = DocumentFile::where('uuid', $uuid)->firstOrFail();
|
||||
|
||||
return response()->file(storage_path('app/public/' . $document->file_path));
|
||||
}
|
||||
}
|
|
@ -3,8 +3,11 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Document;
|
||||
use App\Models\DocumentFile;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
|
@ -37,6 +40,8 @@ class UserController extends Controller
|
|||
unset($user->last_access);
|
||||
}
|
||||
|
||||
//TODO: support for more data selections, follow user permissions, do not share information that should not be shared to that user etc. see notes
|
||||
|
||||
return response()->json($list);
|
||||
}
|
||||
|
||||
|
@ -51,11 +56,23 @@ class UserController extends Controller
|
|||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(Request $request, $id)
|
||||
public function show(Request $request, User $user)
|
||||
{
|
||||
User::where('id', $request->user()->id)->update(['last_access' => now()]);
|
||||
|
||||
return response()->json(User::findOrFail($id));
|
||||
//TODO: do not display useless or hidden info to the user about documentFile (like filePath and id) but only the url (see below)
|
||||
$user->driving_license = Document::where('added_by', $user->id)
|
||||
->where('type', 'driving_license')
|
||||
->with('documentFile')
|
||||
->first();
|
||||
|
||||
if(!is_null($user->driving_license) && !is_null($user->driving_license->documentFile)) {
|
||||
$user->driving_license->documentFile->url = URL::temporarySignedRoute(
|
||||
'driving_license_scan_serve', now()->addMinutes(2), ['uuid' => $user->driving_license->documentFile->uuid]
|
||||
);
|
||||
}
|
||||
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,7 +80,87 @@ class UserController extends Controller
|
|||
*/
|
||||
public function update(Request $request, User $user)
|
||||
{
|
||||
//
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'surname' => 'string|max:255',
|
||||
'username' => 'required|string|max:255|unique:users,username,' . $user->id,
|
||||
'birthday' => 'date',
|
||||
'birthplace' => 'string|max:255',
|
||||
'birthplace_province' => 'string|max:255',
|
||||
'ssn' => 'string|max:255',
|
||||
'course_date' => 'date',
|
||||
'driver' => 'required|boolean',
|
||||
'chief' => 'required|boolean',
|
||||
'banned' => 'boolean',
|
||||
'hidden' => 'boolean',
|
||||
'driving_license' => 'array',
|
||||
'driving_license.number' => 'alpha_num|max:255',
|
||||
'driving_license.type' => 'string|max:255',
|
||||
'driving_license.expiration_date' => 'date',
|
||||
'driving_license.scan' => 'string|max:255',
|
||||
'address' => 'string|max:255',
|
||||
'address_zip_code' => 'integer|max:255',
|
||||
'phone_number' => 'string|max:255',
|
||||
'email' => 'string|max:255',
|
||||
'suit_size' => 'string|max:255',
|
||||
'boot_size' => 'string|max:255'
|
||||
]);
|
||||
|
||||
/*
|
||||
//TODO: new user permissions
|
||||
if($request->user()->isAbleTo("users-update")) {
|
||||
$user->update($request->all());
|
||||
} else {
|
||||
$user->update($request->except(['chief', 'driver', 'banned', 'hidden']));
|
||||
}
|
||||
*/
|
||||
|
||||
$user->update($request->all());
|
||||
|
||||
if($request->has('birthday')) {
|
||||
$user->birthday = \Carbon\Carbon::parse($request->birthday);
|
||||
$user->save();
|
||||
}
|
||||
if($request->has('course_date')) {
|
||||
$user->course_date = \Carbon\Carbon::parse($request->course_date);
|
||||
$user->save();
|
||||
}
|
||||
|
||||
//Check if driving license is present
|
||||
if($request->has('driving_license')) {
|
||||
$drivingLicense = Document::where('added_by', $user->id)
|
||||
->where('type', 'driving_license')
|
||||
->first();
|
||||
|
||||
if(is_null($drivingLicense)) {
|
||||
$drivingLicense = new Document();
|
||||
$drivingLicense->added_by = $user->id;
|
||||
$drivingLicense->type = 'driving_license';
|
||||
}
|
||||
|
||||
if ($request->has('driving_license.number')) {
|
||||
$drivingLicense->doc_number = $request->driving_license['number'];
|
||||
}
|
||||
if ($request->has('driving_license.type')) {
|
||||
$drivingLicense->doc_type = $request->driving_license['type'];
|
||||
}
|
||||
if ($request->has('driving_license.expiration_date')) {
|
||||
$drivingLicense->expiration_date = \Carbon\Carbon::parse($request->driving_license['expiration_date']);
|
||||
}
|
||||
if($request->has('driving_license.scan') && !is_null($request->driving_license['scan'])) {
|
||||
$documentFile = DocumentFile::where('uuid', $request->driving_license['scan'])->first();
|
||||
//Ensure that the document file exists
|
||||
if(is_null($documentFile)) {
|
||||
return response()->json([
|
||||
'message' => 'Document file not found'
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
$drivingLicense->documentFile()->associate($documentFile);
|
||||
}
|
||||
$drivingLicense->save();
|
||||
}
|
||||
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Document extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'type',
|
||||
'doc_number',
|
||||
'doc_type'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'expiration_date' => 'datetime'
|
||||
];
|
||||
|
||||
public function addedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function documentFile(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(DocumentFile::class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class DocumentFile extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = ["created_at"];
|
||||
const UPDATED_AT = null;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'uuid',
|
||||
'type',
|
||||
'file_path'
|
||||
];
|
||||
|
||||
public function uploadedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
|
@ -41,7 +41,10 @@ class User extends Authenticatable implements LaratrustUser
|
|||
'birthplace',
|
||||
'birthplace_province',
|
||||
'ssn',
|
||||
'address'
|
||||
'address',
|
||||
'address_zip_code',
|
||||
'suit_size',
|
||||
'boot_size'
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,6 +17,9 @@ return new class extends Migration
|
|||
$table->string('birthplace_province')->nullable()->after('birthplace');
|
||||
$table->string('ssn')->nullable()->after('birthplace_province');
|
||||
$table->string('address')->nullable()->after('ssn');
|
||||
$table->string('address_zip_code')->nullable()->after('address');
|
||||
$table->string('suit_size')->nullable()->after('address_zip_code');
|
||||
$table->string('boot_size')->nullable()->after('suit_size');
|
||||
$table->timestamp('birthday')->nullable()->after('last_availability_change');
|
||||
$table->timestamp('course_date')->nullable()->after('birthday');
|
||||
});
|
||||
|
@ -33,6 +36,9 @@ return new class extends Migration
|
|||
$table->dropColumn('birthplace_province');
|
||||
$table->dropColumn('ssn');
|
||||
$table->dropColumn('address');
|
||||
$table->dropColumn('address_zip_code');
|
||||
$table->dropColumn('suit_size');
|
||||
$table->dropColumn('boot_size');
|
||||
$table->dropColumn('birthday');
|
||||
$table->dropColumn('course_date');
|
||||
});
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?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::create('document_files', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid');
|
||||
$table->string('type');
|
||||
$table->string('file_path');
|
||||
$table->foreignId('uploaded_by_id')->constrained('users');
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::create('documents', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('type');
|
||||
$table->string('doc_number')->nullable();
|
||||
$table->string('doc_type')->nullable();
|
||||
$table->foreignId('added_by')->constrained('users');
|
||||
$table->foreignId('document_file_id')->nullable()->constrained('document_files');
|
||||
$table->dateTime('expiration_date')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('documents');
|
||||
Schema::dropIfExists('document_files');
|
||||
}
|
||||
};
|
|
@ -6,6 +6,7 @@ use App\Http\Controllers\UserController;
|
|||
use App\Http\Controllers\ScheduleSlotsController;
|
||||
use App\Http\Controllers\AvailabilityController;
|
||||
use App\Http\Controllers\AlertController;
|
||||
use App\Http\Controllers\DocumentsController;
|
||||
use App\Http\Controllers\LogsController;
|
||||
use App\Http\Controllers\TelegramController;
|
||||
use App\Http\Controllers\ServiceController;
|
||||
|
@ -45,7 +46,10 @@ Route::middleware('auth:sanctum')->group( function () {
|
|||
|
||||
Route::get('/list', [UserController::class, 'index'])->middleware(ETag::class);
|
||||
|
||||
Route::get('/users/{id}', [UserController::class, 'show']);
|
||||
Route::get('/users/{user}', [UserController::class, 'show']);
|
||||
Route::put('/users/{user}', [UserController::class, 'update']);
|
||||
|
||||
Route::post('/documents/driving_license', [DocumentsController::class, 'uploadDrivingLicenseScan']);
|
||||
|
||||
Route::get('/schedules', [ScheduleSlotsController::class, 'index']);
|
||||
Route::post('/schedules', [ScheduleSlotsController::class, 'store']);
|
||||
|
@ -84,6 +88,10 @@ Route::middleware('auth:sanctum')->group( function () {
|
|||
Route::post('/logout', [AuthController::class, 'logout']);
|
||||
});
|
||||
|
||||
Route::middleware('signed')->group( function () {
|
||||
Route::get('/documents/driving_license/{uuid}', [DocumentsController::class, 'serveDrivingLicenseScan'])->name('driving_license_scan_serve');
|
||||
});
|
||||
|
||||
Route::get('/owner_image', function() {
|
||||
return response()
|
||||
->file(
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<td>Indirizzo</td>
|
||||
<td>
|
||||
<a href="https://maps.google.com/?q={{ user.address }}" target="_blank">
|
||||
{{ user.address }}
|
||||
{{ user.address }}<ng-container *ngIf="user.address_zip_code"> (CAP <i>{{ user.address_zip_code }}</i>)</ng-container>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -88,6 +88,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!--TODO: translate -->
|
||||
<td>Autista</td>
|
||||
<td>
|
||||
<i class="fa fa-check" style="color:green" *ngIf="user.driver"></i>
|
||||
|
@ -108,7 +109,7 @@
|
|||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button *ngIf="false" type="button" class="btn btn-primary" (click)="goToEditPage()" [disabled]="!canGoToEditPage">
|
||||
<button type="button" class="btn btn-primary" (click)="goToEditPage()" [disabled]="!canGoToEditPage">
|
||||
{{ "edit" | translate | titlecase }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" (click)="bsModalRef.hide()">
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
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',
|
||||
|
@ -23,8 +21,7 @@ export class ModalUserInfoComponent implements OnInit {
|
|||
public bsModalRef: BsModalRef,
|
||||
private api: ApiClientService,
|
||||
public auth: AuthService,
|
||||
private toastr: ToastrService,
|
||||
private translate: TranslateService
|
||||
private router: Router
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -41,6 +38,6 @@ export class ModalUserInfoComponent implements OnInit {
|
|||
goToEditPage() {
|
||||
if(!this.canGoToEditPage) return;
|
||||
this.bsModalRef.hide();
|
||||
|
||||
this.router.navigate(['/users', this.id]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
</ng-container>
|
||||
<th>{{ 'services'|translate|titlecase }}</th>
|
||||
<th>{{ 'availability_minutes'|translate|titlecase }}</th>
|
||||
<th>{{ 'edit'|translate|titlecase }}</th>
|
||||
<!-- TODO: hide if user can't access -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="table_body">
|
||||
|
@ -48,6 +50,8 @@
|
|||
</td>
|
||||
<td>{{ row.services }}</td>
|
||||
<td>{{ row.availability_minutes }}</td>
|
||||
<td (click)="editUser(row.id)"><i class="fa fa-edit"></i></td>
|
||||
<!-- TODO: hide if user can't access -->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -204,6 +204,10 @@ export class TableComponent implements OnInit, OnDestroy {
|
|||
this.moreDetails.emit({rowId});
|
||||
}
|
||||
|
||||
editUser(id: number) {
|
||||
this.router.navigate(['/users', id]);
|
||||
}
|
||||
|
||||
openPlaceDetails(id: number) {
|
||||
this.router.navigate(['/place-details', id]);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { EditUserComponent } from './edit-user.component';
|
||||
|
||||
const routes: Routes = [{ path: '', component: EditUserComponent }];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class EditUserRoutingModule { }
|
|
@ -0,0 +1,208 @@
|
|||
<back-btn></back-btn>
|
||||
|
||||
<div class="page-header">
|
||||
<h1 class="text-center">ANAGRAFICA PERSONALE</h1>
|
||||
</div>
|
||||
|
||||
<h2 class="text-center mb-3">{{ user.surname }} {{ user.name }}</h2>
|
||||
|
||||
<form [formGroup]="profileForm" (ngSubmit)="formSubmit()" class="row g-4 m-3">
|
||||
<div class="col-md-6">
|
||||
<label for="name" class="form-label">Nome</label>
|
||||
<input formControlName="name" autocomplete="name" type="text" class="form-control" id="name">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="surname" class="form-label">Cognome</label>
|
||||
<input formControlName="surname" autocomplete="additional-name" type="text" class="form-control" id="surname">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input formControlName="username" autocomplete="username" type="text" class="form-control" id="username">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="ssn" class="form-label">Codice fiscale</label>
|
||||
<input formControlName="ssn" autocomplete="ssn" type="text" class="form-control" id="ssn">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="birthday" class="form-label">Data di nascita</label>
|
||||
<input formControlName="birthday" autocomplete="bday" type="text" class="form-control" placeholder="Premi per selezionare" id="birthday" bsDatepicker [bsConfig]="{ adaptivePosition: true, dateInputFormat: 'DD/MM/YYYY' }" [maxDate]="birthdayMaxDate">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="birthplace" class="form-label">Luogo di nascita</label>
|
||||
<div class="input-group" id="birthplace">
|
||||
<input formControlName="birthplace" type="text" class="form-control" placeholder="Luogo">
|
||||
<input formControlName="birthplace_province" type="text" class="form-control" placeholder="Provincia">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="text-center mt-5"><i class="fas fa-fire-extinguisher"></i> Informazioni di servizio</h2>
|
||||
<div class="col-md-12">
|
||||
<label for="course_date" class="form-label">Data corso</label>
|
||||
<input formControlName="course_date" type="text" class="form-control" placeholder="Premi per selezionare" id="course_date" bsDatepicker [bsConfig]="{ adaptivePosition: true, dateInputFormat: 'DD/MM/YYYY' }" [maxDate]="birthdayMaxDate">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="driver" class="form-label">Autista</label>
|
||||
<div class="form-check form-switch">
|
||||
<input formControlName="driver" class="form-check-input custom-check-input" type="checkbox" role="switch" id="driver">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="chief" class="form-label">Caposquadra</label>
|
||||
<div class="form-check form-switch">
|
||||
<input formControlName="chief" class="form-check-input custom-check-input" type="checkbox" role="switch" id="chief">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="banned" class="form-label">Bannato (non potrà accedere o interagire)</label>
|
||||
<div class="form-check form-switch">
|
||||
<input formControlName="banned" class="form-check-input custom-check-input" type="checkbox" role="switch" id="banned">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="hidden" class="form-label">Nascosto nella lista</label>
|
||||
<div class="form-check form-switch">
|
||||
<input formControlName="hidden" class="form-check-input custom-check-input" type="checkbox" role="switch" id="hidden">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="creation_date" class="form-label">Data aggiunta utente</label>
|
||||
<input type="text" class="form-control" disabled id="creation_date" [value]="creation_date">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="update_date" class="form-label">Data ultima modifica all'utente</label>
|
||||
<input type="text" class="form-control" disabled id="update_date" [value]="update_date">
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label for="last_access_date" class="form-label">Ultimo accesso</label>
|
||||
<input type="text" class="form-control" disabled id="last_access_date" [value]="last_access_date">
|
||||
</div>
|
||||
|
||||
<h2 class="text-center mt-5"><i class="fas fa-phone"></i> Recapiti</h2>
|
||||
<div class="col-md-8">
|
||||
<label for="address" class="form-label">Indirizzo di residenza, civico</label>
|
||||
<input formControlName="address" type="text" class="form-control" id="address" placeholder="Via/Piazza...">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="address_zip_code" class="form-label">CAP</label>
|
||||
<input formControlName="address_zip_code" type="number" class="form-control" id="address_zip_code" placeholder="12345" pattern="[0-9]{5}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="phone_number" class="form-label">Numero di telefono</label>
|
||||
<input formControlName="phone_number" type="number" class="form-control" id="phone_number" placeholder="Fisso o cellulare">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="email" class="form-label">Indirizzo email</label>
|
||||
<input formControlName="email" type="email" class="form-control" id="email" placeholder="">
|
||||
</div>
|
||||
|
||||
<h2 class="text-center mt-5"><i class="fas fa-id-card"></i> Documenti</h2>
|
||||
<ng-container formGroupName="driving_license">
|
||||
<h3 class="text-center mt-3">Patente <i class="fas fa-car-side"></i></h3>
|
||||
<div class="col-md-12" *ngIf="dlCurrScanUrl !== null">
|
||||
<img [src]="dlCurrScanUrl" class="img-fluid rounded mx-auto d-block w-50">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="dl_number" class="form-label">Numero patente</label>
|
||||
<input formControlName="number" autocomplete="dl-number" type="text" class="form-control" id="dl_number" placeholder="AB1234567">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="dl_type" class="form-label">Grado</label>
|
||||
<input formControlName="type" autocomplete="dl-type" type="text" class="form-control" id="dl_type" placeholder="">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="dl_expiration_date" class="form-label">Scadenza patente</label>
|
||||
<input formControlName="expiration_date" autocomplete="dl-expiration-date" type="text" class="form-control" placeholder="Premi per selezionare" id="dl_expiration_date" bsDatepicker [bsConfig]="{ adaptivePosition: true, dateInputFormat: 'DD/MM/YYYY' }" [minDate]="dlExpirationMinDate">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="dl_scan" class="form-label">Scansione patente</label>
|
||||
<input class="form-control" type="file" id="dl_scan" (change)="onDrivingLicenseScanSelected($event)" #drivingLicenseFileUpload>
|
||||
<div *ngIf="tmpDrivingLicenseImgData !== null">
|
||||
<img [src]="tmpDrivingLicenseImgData" class="img-fluid p-1 w-75">
|
||||
<button type="button" class="btn btn-primary" *ngIf="dlScanNotUploadedYet" (click)="uploadDrivingLicenseScan(drivingLicenseFileUpload)">
|
||||
<i class="fas fa-upload"></i> Carica scansione
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<h2 class="text-center mt-5"><i class="fas fa-tshirt"></i> Indumenti</h2>
|
||||
<div class="col-md-6">
|
||||
<label for="suit_size" class="form-label">Taglia tuta</label>
|
||||
<input formControlName="suit_size" type="text" class="form-control" id="suit_size" placeholder="M, L, XL...">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="boot_size" class="form-label">Taglia scarponi</label>
|
||||
<input formControlName="boot_size" type="text" class="form-control" id="boot_size" placeholder="41, 42, 43...">
|
||||
</div>
|
||||
|
||||
<div class="col-12 m-3 mt-4">
|
||||
<button type="submit" class="btn btn-lg btn-primary">Aggiorna</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="text-center">
|
||||
<h3>Corsi di formazione</h3>
|
||||
<button disabled class="btn btn-sm btn-outline-info m-2">
|
||||
<i class="fas fa-download"></i> LIFM
|
||||
</button>
|
||||
<table class="mx-auto table table-striped table-bordered table-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>TIPOLOGIA</th>
|
||||
<th>NOME</th>
|
||||
<th>DATA</th>
|
||||
<th>SCADENZA</th>
|
||||
<th>CERT.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr *ngIf="false">
|
||||
<td>TIPOCORSO</td>
|
||||
<td>Nome</td>
|
||||
<td>31/12/2024</td>
|
||||
<td>31/12/2027</td>
|
||||
<td>
|
||||
<a href=''><i class='fas fa-graduation-cap'></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="text-center">
|
||||
<h3>Visite mediche</h3>
|
||||
<table class="mx-auto table table-striped table-bordered table-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>DATA</th>
|
||||
<th>MEDICO</th>
|
||||
<th>SCADENZA</th>
|
||||
<th>CERT.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr *ngIf="false">
|
||||
<td>31/12/2024</td>
|
||||
<td>Medico</td>
|
||||
<td>31/12/2027</td>
|
||||
<td>
|
||||
<a href=''><i class='fas fa-clipboard-list'></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
.custom-check-input {
|
||||
width: 3.6rem;
|
||||
height: 1.8rem;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.page-header {
|
||||
margin-top: 3.5rem;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { ApiClientService } from 'src/app/_services/api-client.service';
|
||||
import { AuthService } from 'src/app/_services/auth.service';
|
||||
import Swal from 'sweetalert2';
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-user',
|
||||
templateUrl: './edit-user.component.html',
|
||||
styleUrls: ['./edit-user.component.scss']
|
||||
})
|
||||
export class EditUserComponent implements OnInit {
|
||||
id: number | undefined;
|
||||
user: any = {};
|
||||
|
||||
profileForm: FormGroup = this.formBuilder.group({
|
||||
name: ['', [Validators.required, Validators.minLength(3)]],
|
||||
surname: [''],
|
||||
username: ['', [Validators.required, Validators.minLength(3)]],
|
||||
birthday: [null],
|
||||
birthplace: [''],
|
||||
birthplace_province: [''],
|
||||
ssn: [''],
|
||||
course_date: [null],
|
||||
driver: [false, [Validators.required]],
|
||||
chief: [false, [Validators.required]],
|
||||
banned: [false, [Validators.required]],
|
||||
hidden: [false, [Validators.required]],
|
||||
address: [''],
|
||||
address_zip_code: [''],
|
||||
phone_number: [''],
|
||||
email: [''],
|
||||
driving_license: this.formBuilder.group({
|
||||
number: [''],
|
||||
type: [''],
|
||||
expiration_date: [null],
|
||||
scan: [null]
|
||||
}),
|
||||
suit_size: [''],
|
||||
boot_size: ['']
|
||||
});
|
||||
|
||||
birthdayMaxDate = new Date(new Date().setFullYear(new Date().getFullYear() - 18)); //18 years ago
|
||||
dlExpirationMinDate = new Date(new Date().setDate(new Date().getDate() + 1)); //Tomorrow
|
||||
allowedImageTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
|
||||
maxImageSize = 1024 * 1024 * 5; //5MB
|
||||
|
||||
creation_date: string = "MAI";
|
||||
update_date: string = "MAI";
|
||||
last_access_date: string = "MAI";
|
||||
|
||||
tmpDrivingLicenseImgData: string | null = null;
|
||||
dlScanNotUploadedYet = true;
|
||||
dlCurrScanUrl: string | null = null;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private formBuilder: FormBuilder,
|
||||
private api: ApiClientService,
|
||||
private auth: AuthService
|
||||
) {
|
||||
this.route.paramMap.subscribe(params => {
|
||||
this.id = typeof params.get('id') === 'string' ? parseInt(params.get('id') || '') : undefined;
|
||||
if (this.id) {
|
||||
this.api.get(`users/${this.id}`).then((response) => {
|
||||
this.user = response;
|
||||
console.log(response);
|
||||
|
||||
this.profileForm.patchValue({
|
||||
name: this.user.name,
|
||||
surname: this.user.surname,
|
||||
username: this.user.username,
|
||||
birthday: this.user.birthday ? new Date(this.user.birthday) : null,
|
||||
birthplace: this.user.birthplace,
|
||||
birthplace_province: this.user.birthplace_province,
|
||||
ssn: this.user.ssn,
|
||||
course_date: this.user.course_date ? new Date(this.user.course_date) : null,
|
||||
driver: this.user.driver,
|
||||
chief: this.user.chief,
|
||||
banned: this.user.banned,
|
||||
hidden: this.user.hidden,
|
||||
address: this.user.address,
|
||||
address_zip_code: this.user.address_zip_code,
|
||||
phone_number: this.user.phone_number,
|
||||
email: this.user.email,
|
||||
driving_license: {
|
||||
number: this.user.driving_license ? this.user.driving_license.doc_number : null,
|
||||
type: this.user.driving_license ? this.user.driving_license.doc_type : null,
|
||||
expiration_date: (this.user.driving_license && this.user.driving_license.expiration_date) ? new Date(this.user.driving_license.expiration_date) : null,
|
||||
scan: this.user.driving_license ? this.user.driving_license.uuid : null
|
||||
},
|
||||
suit_size: this.user.suit_size,
|
||||
boot_size: this.user.boot_size
|
||||
});
|
||||
|
||||
const convertToItalianDate = (date: string | null): string => {
|
||||
if(!date) return "MAI";
|
||||
const dateObj = new Date(date);
|
||||
return dateObj.toLocaleString('it-IT', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
this.creation_date = convertToItalianDate(this.user.created_at);
|
||||
this.update_date = convertToItalianDate(this.user.updated_at);
|
||||
this.last_access_date = convertToItalianDate(this.user.last_access);
|
||||
|
||||
if(this.user.driving_license && this.user.driving_license.document_file) {
|
||||
this.dlCurrScanUrl = this.api.apiEndpoint(this.user.driving_license.document_file.url);
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if(!this.auth.profile.can('users-ban') || this.id === this.auth.profile.id) {
|
||||
this.profileForm.get('banned')?.disable();
|
||||
}
|
||||
if(!this.auth.profile.can('users-hide') || (!this.auth.profile.can('user-hide') && this.id === this.auth.profile.id)) {
|
||||
this.profileForm.get('hidden')?.disable();
|
||||
}
|
||||
}
|
||||
|
||||
onDrivingLicenseScanSelected(event: any) {
|
||||
const file: File = event.target.files[0];
|
||||
this.dlScanNotUploadedYet = true;
|
||||
|
||||
if (file) {
|
||||
if(!this.allowedImageTypes.includes(file.type)) {
|
||||
event.target.value = null;
|
||||
Swal.fire({
|
||||
title: 'Errore',
|
||||
text: 'Formato immagine non supportato',
|
||||
icon: 'error',
|
||||
confirmButtonText: 'Ok'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if(file.size > this.maxImageSize) {
|
||||
event.target.value = null;
|
||||
Swal.fire({
|
||||
title: 'Errore',
|
||||
text: 'File troppo grande',
|
||||
icon: 'error',
|
||||
confirmButtonText: 'Ok'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = (_event) => {
|
||||
this.tmpDrivingLicenseImgData = reader.result as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uploadDrivingLicenseScan(input: HTMLInputElement) {
|
||||
const filename = "test.png"
|
||||
|
||||
if(!input.files || !input.files[0]) return;
|
||||
|
||||
console.log(input.files[0]);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', input.files[0], input.files[0].name);
|
||||
|
||||
this.api.post("documents/driving_license", formData).then((response) => {
|
||||
console.log(response);
|
||||
this.dlScanNotUploadedYet = false;
|
||||
|
||||
this.profileForm.patchValue({
|
||||
driving_license: {
|
||||
scan: response.uuid
|
||||
}
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
formSubmit() {
|
||||
let data = this.profileForm.value;
|
||||
data.birthday = data.birthday ? new Date(data.birthday) : null;
|
||||
data.course_date = data.course_date ? new Date(data.course_date) : null;
|
||||
data.driving_license.expiration_date = data.driving_license.expiration_date ? new Date(data.driving_license.expiration_date) : null;
|
||||
|
||||
if (this.id) {
|
||||
//TODO: translate
|
||||
this.api.put(`users/${this.id}`, data).then((response) => {
|
||||
console.log(response);
|
||||
Swal.fire({
|
||||
title: 'Utente modificato',
|
||||
text: 'L\'utente è stato modificato con successo',
|
||||
icon: 'success',
|
||||
confirmButtonText: 'Ok'
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
Swal.fire({
|
||||
title: 'Errore',
|
||||
text: 'Si è verificato un errore durante la modifica dell\'utente',
|
||||
icon: 'error',
|
||||
confirmButtonText: 'Ok'
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
|
||||
import { BackBtnModule } from '../../_components/back-btn/back-btn.module';
|
||||
import { TranslationModule } from '../../translation.module';
|
||||
|
||||
import { EditUserRoutingModule } from './edit-user-routing.module';
|
||||
import { EditUserComponent } from './edit-user.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
EditUserComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
EditUserRoutingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
BsDatepickerModule.forRoot(),
|
||||
BackBtnModule,
|
||||
TranslationModule
|
||||
]
|
||||
})
|
||||
export class EditUserModule { }
|
|
@ -11,6 +11,11 @@ import { LoginComponent } from './_routes/login/login.component';
|
|||
|
||||
const routes: Routes = [
|
||||
{ path: 'list', component: ListComponent, canActivate: [AuthorizeGuard] },
|
||||
{
|
||||
path: 'users/:id',
|
||||
loadChildren: () => import('./_routes/edit-user/edit-user.module').then(m => m.EditUserModule),
|
||||
canActivate: [AuthorizeGuard]
|
||||
},
|
||||
{ path: 'logs', component: LogsComponent, canActivate: [AuthorizeGuard] },
|
||||
{ path: 'services', component: ServicesComponent, canActivate: [AuthorizeGuard] },
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue