diff --git a/backend/app/Http/Controllers/AuthController.php b/backend/app/Http/Controllers/AuthController.php
index ee348cb..bf63e0e 100644
--- a/backend/app/Http/Controllers/AuthController.php
+++ b/backend/app/Http/Controllers/AuthController.php
@@ -3,7 +3,7 @@
namespace App\Http\Controllers;
use App\Models\User;
-use Illuminate\Support\Facades\Auth;
+use App\Models\Option;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
use Illuminate\Http\Request;
@@ -94,13 +94,26 @@ class AuthController extends Controller
public function me(Request $request)
{
$impersonateManager = app('impersonate');
+
+ $options = Option::all(["name", "value", "type"]);
+ //Cast the value to the correct type and remove type
+ foreach($options as $option) {
+ if($option->type == "boolean") {
+ $option->value = boolval($option->value);
+ } else if($option->type == "number") {
+ $option->value = floatval($option->value);
+ }
+ unset($option->type);
+ }
+
return [
...$request->user()->toArray(),
"permissions" => array_map(function($p) {
return $p["name"];
}, $request->user()->allPermissions()->toArray()),
"impersonating_user" => $impersonateManager->isImpersonating(),
- "impersonator_id" => $impersonateManager->getImpersonatorId()
+ "impersonator_id" => $impersonateManager->getImpersonatorId(),
+ "options" => $options
];
}
@@ -148,7 +161,7 @@ class AuthController extends Controller
'token_type' => 'Bearer',
]);
}
-
+
public function stopImpersonating(Request $request)
{
$manager = app('impersonate');
diff --git a/backend/app/Http/Controllers/PlacesController.php b/backend/app/Http/Controllers/PlacesController.php
index ef385b5..e2ce771 100644
--- a/backend/app/Http/Controllers/PlacesController.php
+++ b/backend/app/Http/Controllers/PlacesController.php
@@ -64,7 +64,9 @@ class PlacesController extends Controller
User::where('id', $request->user()->id)->update(['last_access' => now()]);
return response()->json(
- Place::find($id)
+ Place::where('id', $id)
+ ->with('municipality', 'municipality.province')
+ ->firstOrFail()
);
}
}
diff --git a/backend/app/Http/Controllers/ServiceController.php b/backend/app/Http/Controllers/ServiceController.php
index cf6918a..b816237 100644
--- a/backend/app/Http/Controllers/ServiceController.php
+++ b/backend/app/Http/Controllers/ServiceController.php
@@ -3,13 +3,17 @@
namespace App\Http\Controllers;
use App\Models\Place;
+use App\Models\PlaceMunicipality;
+use App\Models\PlaceProvince;
use App\Models\Service;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
+use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use App\Utils\Logger;
use App\Utils\DBTricks;
+use App\Utils\Helpers;
class ServiceController extends Controller
{
@@ -18,31 +22,48 @@ class ServiceController extends Controller
*/
public function index(Request $request)
{
- if(!$request->user()->hasPermission("services-read")) abort(401);
+ if (!$request->user()->hasPermission("services-read")) abort(401);
User::where('id', $request->user()->id)->update(['last_access' => now()]);
$query = Service::join('users', 'users.id', '=', 'chief_id')
->join('services_types', 'services_types.id', '=', 'type_id')
- ->select('services.*', DBTricks::nameSelect("chief", "users"), 'services_types.name as type')
+ ->select(
+ 'services.*', DBTricks::nameSelect("chief", "users"),
+ 'services_types.name as type'
+ )
->with('drivers:name,surname')
->with('crew:name,surname')
- ->with('place')
+ ->with('place.municipality.province')
->orderBy('start', 'desc');
- if($request->has('from')) {
+ if ($request->has('from')) {
try {
$from = Carbon::parse($request->input('from'));
$query->whereDate('start', '>=', $from->toDateString());
- } catch (\Carbon\Exceptions\InvalidFormatException $e) { }
+ } catch (\Carbon\Exceptions\InvalidFormatException $e) {
+ }
}
- if($request->has('to')) {
+ if ($request->has('to')) {
try {
$to = Carbon::parse($request->input('to'));
$query->whereDate('start', '<=', $to->toDateString());
- } catch (\Carbon\Exceptions\InvalidFormatException $e) { }
+ } catch (\Carbon\Exceptions\InvalidFormatException $e) {
+ }
}
- return response()->json(
- $query->get()
- );
+
+ $result = $query->get();
+ foreach ($result as $service) {
+ if($service->place->municipality) {
+ $m = $service->place->municipality;
+ unset(
+ $m->cadastral_code, $m->email, $m->fax, $m->latitude, $m->longitude,
+ $m->phone, $m->pec, $m->prefix, $m->foreign_name, $m->province_id
+ );
+ }
+
+ $p = $service->place;
+ unset($p->lat, $p->lon, $p->place_id, $p->osm_id, $p->osm_type, $p->licence, $p->addresstype, $p->country, $p->country_code, $p->display_name, $p->road, $p->house_number, $p->postcode, $p->state, $p->suburb, $p->city, $p->municipality_id);
+ }
+ return response()->json($result);
}
/**
@@ -50,7 +71,7 @@ class ServiceController extends Controller
*/
public function show(Request $request, $id)
{
- if(!$request->user()->hasPermission("services-read")) abort(401);
+ if (!$request->user()->hasPermission("services-read")) abort(401);
User::where('id', $request->user()->id)->update(['last_access' => now()]);
return response()->json(
@@ -59,7 +80,7 @@ class ServiceController extends Controller
->select('services.*', DBTricks::nameSelect("chief", "users"), 'services_types.name as type')
->with('drivers:name,surname')
->with('crew:name,surname')
- ->with('place')
+ ->with('place.municipality.province')
->find($id)
);
}
@@ -67,10 +88,10 @@ class ServiceController extends Controller
private function extractServiceUsers($service)
{
$usersList = [$service->chief_id];
- foreach($service->drivers as $driver) {
+ foreach ($service->drivers as $driver) {
$usersList[] = $driver->id;
}
- foreach($service->crew as $crew) {
+ foreach ($service->crew as $crew) {
$usersList[] = $crew->id;
}
return array_unique($usersList);
@@ -83,14 +104,14 @@ class ServiceController extends Controller
{
$adding = !isset($request->id) || is_null($request->id);
- if(!$adding && !$request->user()->hasPermission("services-update")) abort(401);
- if($adding && !$request->user()->hasPermission("services-create")) abort(401);
+ if (!$adding && !$request->user()->hasPermission("services-update")) abort(401);
+ if ($adding && !$request->user()->hasPermission("services-create")) abort(401);
- $service = $adding ? new Service() : Service::where("id",$request->id)->with('drivers')->with('crew')->first();
+ $service = $adding ? new Service() : Service::where("id", $request->id)->with('drivers')->with('crew')->first();
- if(is_null($service)) abort(404);
+ if (is_null($service)) abort(404);
- if(!$adding) {
+ if (!$adding) {
$usersToDecrement = $this->extractServiceUsers($service);
User::whereIn('id', $usersToDecrement)->decrement('services');
@@ -99,36 +120,111 @@ class ServiceController extends Controller
$service->save();
}
- //Find Place by lat lon
- $place = Place::where('lat', $request->lat)->where('lon', $request->lon)->first();
- if(!$place) {
+ $is_map_picker = Helpers::get_option('service_place_selection_use_map_picker', false);
+
+ if ($is_map_picker) {
+ //Find Place by lat lon
+ $place = Place::where('lat', $request->place->lat)->where('lon', $request->place->lon)->first();
+ if (!$place) {
+ $place = new Place();
+ $place->lat = $request->place->lat;
+ $place->lon = $request->place->lon;
+
+ $response = Http::withUrlParameters([
+ 'lat' => $request->place->lat,
+ 'lon' => $request->place->lon,
+ ])->get('https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}');
+ if (!$response->ok()) abort(500);
+
+ $place->place_id = isset($response["place_id"]) ? $response["place_id"] : null;
+ $place->osm_id = isset($response["osm_id"]) ? $response["osm_id"] : null;
+ $place->osm_type = isset($response["osm_type"]) ? $response["osm_type"] : null;
+ $place->licence = isset($response["licence"]) ? $response["licence"] : null;
+ $place->addresstype = isset($response["addresstype"]) ? $response["addresstype"] : null;
+ $place->country = isset($response["address"]["country"]) ? $response["address"]["country"] : null;
+ $place->country_code = isset($response["address"]["country_code"]) ? $response["address"]["country_code"] : null;
+ $place->name = isset($response["name"]) ? $response["name"] : null;
+ $place->display_name = isset($response["display_name"]) ? $response["display_name"] : null;
+ $place->road = isset($response["address"]["road"]) ? $response["address"]["road"] : null;
+ $place->house_number = isset($response["address"]["house_number"]) ? $response["address"]["house_number"] : null;
+ $place->postcode = isset($response["address"]["postcode"]) ? $response["address"]["postcode"] : null;
+ $place->state = isset($response["address"]["state"]) ? $response["address"]["state"] : null;
+ $place->village = isset($response["address"]["village"]) ? $response["address"]["village"] : null;
+ $place->suburb = isset($response["address"]["suburb"]) ? $response["address"]["suburb"] : null;
+ $place->city = isset($response["address"]["city"]) ? $response["address"]["city"] : null;
+
+ $place->save();
+ }
+ } else {
+ if (!$adding) {
+ //Delete old place
+ $place = $service->place;
+ $service->place()->dissociate();
+ $service->save();
+ $place->delete();
+ }
+
$place = new Place();
- $place->lat = $request->lat;
- $place->lon = $request->lon;
+ $place->name = $request->place["address"];
- $response = Http::withUrlParameters([
- 'lat' => $request->lat,
- 'lon' => $request->lon,
- ])->get('https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}');
- if(!$response->ok()) abort(500);
+ //Check if municipality exists
+ $municipality = PlaceMunicipality::where('code', $request->place["municipalityCode"])->first();
+ if (!$municipality) {
+ //Check if province exists
+ $province = PlaceProvince::where('code', $request->place["provinceCode"])->first();
+ if (!$province) {
+ $provinces = Cache::remember('italy_provinces_all', 60 * 60 * 24 * 365, function () {
+ return Http::get('https://axqvoqvbfjpaamphztgd.functions.supabase.co/province/')->object();
+ });
- $place->place_id = isset($response["place_id"]) ? $response["place_id"] : null;
- $place->osm_id = isset($response["osm_id"]) ? $response["osm_id"] : null;
- $place->osm_type = isset($response["osm_type"]) ? $response["osm_type"] : null;
- $place->licence = isset($response["licence"]) ? $response["licence"] : null;
- $place->addresstype = isset($response["addresstype"]) ? $response["addresstype"] : null;
- $place->country = isset($response["address"]["country"]) ? $response["address"]["country"] : null;
- $place->country_code = isset($response["address"]["country_code"]) ? $response["address"]["country_code"] : null;
- $place->name = isset($response["name"]) ? $response["name"] : null;
- $place->display_name = isset($response["display_name"]) ? $response["display_name"] : null;
- $place->road = isset($response["address"]["road"]) ? $response["address"]["road"] : null;
- $place->house_number = isset($response["address"]["house_number"]) ? $response["address"]["house_number"] : null;
- $place->postcode = isset($response["address"]["postcode"]) ? $response["address"]["postcode"] : null;
- $place->state = isset($response["address"]["state"]) ? $response["address"]["state"] : null;
- $place->village = isset($response["address"]["village"]) ? $response["address"]["village"] : null;
- $place->suburb = isset($response["address"]["suburb"]) ? $response["address"]["suburb"] : null;
- $place->city = isset($response["address"]["city"]) ? $response["address"]["city"] : null;
- $place->municipality = isset($response["address"]["municipality"]) ? $response["address"]["municipality"] : null;
+ //Find province
+ foreach ($provinces as $p) {
+ if ($p->codice == $request->place["provinceCode"]) {
+ $province = new PlaceProvince();
+ $province->code = $p->codice;
+ $province->name = $p->nome;
+ $province->short_name = $p->sigla;
+ $province->region = $p->regione;
+ $province->save();
+ break;
+ }
+ }
+ if (!$province) {
+ abort(400);
+ }
+ }
+
+ $province_name = $province->name;
+ $municipalities = Cache::remember('italy_municipalities_' . $province_name, 60 * 60 * 24 * 365, function () use ($province_name) {
+ return Http::get('https://axqvoqvbfjpaamphztgd.functions.supabase.co/comuni/provincia/' . $province_name)->object();
+ });
+
+ //Find municipality
+ foreach ($municipalities as $m) {
+ if ($m->codice == $request->place["municipalityCode"]) {
+ $municipality = new PlaceMunicipality();
+ $municipality->code = $m->codice;
+ $municipality->name = $m->nome;
+ $municipality->foreign_name = $m->nomeStraniero;
+ $municipality->cadastral_code = $m->codiceCatastale;
+ $municipality->postal_code = $m->cap;
+ $municipality->prefix = $m->prefisso;
+ $municipality->email = $m->email;
+ $municipality->pec = $m->pec;
+ $municipality->phone = $m->telefono;
+ $municipality->fax = $m->fax;
+ $municipality->latitude = $m->coordinate->lat;
+ $municipality->longitude = $m->coordinate->lng;
+ $municipality->province()->associate($province);
+ $municipality->save();
+ break;
+ }
+ }
+ if (!$municipality) {
+ abort(400);
+ }
+ }
+ $place->municipality()->associate($municipality);
$place->save();
}
@@ -137,8 +233,8 @@ class ServiceController extends Controller
$service->chief()->associate($request->chief);
$service->type()->associate($request->type);
$service->notes = $request->notes;
- $service->start = $request->start/1000;
- $service->end = $request->end/1000;
+ $service->start = $request->start / 1000;
+ $service->end = $request->end / 1000;
$service->place()->associate($place);
$service->addedBy()->associate($request->user());
$service->updatedBy()->associate($request->user());
@@ -163,7 +259,7 @@ class ServiceController extends Controller
*/
public function destroy(Request $request, $id)
{
- if(!$request->user()->hasPermission("services-delete")) abort(401);
+ if (!$request->user()->hasPermission("services-delete")) abort(401);
$service = Service::find($id);
$usersToDecrement = $this->extractServiceUsers($service);
User::whereIn('id', $usersToDecrement)->decrement('services');
diff --git a/backend/app/Http/Controllers/UserController.php b/backend/app/Http/Controllers/UserController.php
index 5a4d55d..a4f614d 100644
--- a/backend/app/Http/Controllers/UserController.php
+++ b/backend/app/Http/Controllers/UserController.php
@@ -33,7 +33,7 @@ class UserController extends Controller
->orderBy('name', 'asc')
->orderBy('surname', 'asc')
->get();
-
+
$now = now();
foreach($list as $user) {
//Add online status
@@ -67,7 +67,7 @@ class UserController extends Controller
->join('document_files', 'document_files.id', '=', 'documents.document_file_id')
->select('documents.doc_type', 'documents.doc_number', 'documents.expiration_date', 'document_files.uuid as scan_uuid')
->get();
-
+
if($dl_tmp->count() > 0) {
$user->driving_license = $dl_tmp[0];
}
@@ -78,7 +78,7 @@ class UserController extends Controller
->leftJoin('training_course_types', 'training_course_types.id', '=', 'documents.doc_type')
->select('documents.doc_number as doc_number', 'documents.date', 'document_files.uuid as doc_uuid', 'training_course_types.name as type')
->get();
-
+
if($tc_tmp->count() > 0) {
$user->training_courses = $tc_tmp;
foreach($user->training_courses as $tc) {
@@ -98,7 +98,7 @@ class UserController extends Controller
->leftJoin('document_files', 'document_files.id', '=', 'documents.document_file_id')
->select('documents.doc_certifier as certifier', 'documents.date', 'documents.expiration_date', 'document_files.uuid as cert_uuid')
->get();
-
+
if($me_tmp->count() > 0) {
$user->medical_examinations = $me_tmp;
foreach($user->medical_examinations as $me) {
@@ -251,12 +251,4 @@ class UserController extends Controller
return response()->json($user);
}
-
- /**
- * Remove the specified resource from storage.
- */
- public function destroy(User $user)
- {
- //
- }
}
diff --git a/backend/app/Models/Place.php b/backend/app/Models/Place.php
index 02a85c5..8ce5c83 100644
--- a/backend/app/Models/Place.php
+++ b/backend/app/Models/Place.php
@@ -4,6 +4,8 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use App\Models\PlaceMunicipality;
class Place extends Model
{
@@ -32,7 +34,13 @@ class Place extends Model
'state',
'village',
'suburb',
- 'city',
- 'municipality'
+ 'city'
];
+
+ /**
+ * Get the municipality
+ */
+ public function municipality(): BelongsTo {
+ return $this->belongsTo(PlaceMunicipality::class);
+ }
}
diff --git a/backend/app/Utils/Helpers.php b/backend/app/Utils/Helpers.php
new file mode 100644
index 0000000..963c47d
--- /dev/null
+++ b/backend/app/Utils/Helpers.php
@@ -0,0 +1,23 @@
+first();
+ if($option) {
+ // Cast to correct type
+ if($option->type == "boolean") {
+ return $option->value == "true";
+ } else if($option->type == "number") {
+ return floatval($option->value);
+ } else {
+ return $option->value;
+ }
+ }
+ return $default;
+ }
+
+}
diff --git a/backend/database/migrations/2024_01_23_011004_add_italian_places_indicators.php b/backend/database/migrations/2024_01_23_011004_add_italian_places_indicators.php
index 1bc46ef..bf76f73 100644
--- a/backend/database/migrations/2024_01_23_011004_add_italian_places_indicators.php
+++ b/backend/database/migrations/2024_01_23_011004_add_italian_places_indicators.php
@@ -11,14 +11,14 @@ return new class extends Migration
*/
public function up(): void
{
- Schema::create('PlaceProvince', function (Blueprint $table) {
+ Schema::create('place_provinces', function (Blueprint $table) {
$table->id();
- $table->string('code', 2)->unique();
+ $table->string('code', 20)->unique();
$table->string('name', 100);
$table->string('short_name', 2);
$table->string('region', 25);
});
- Schema::create('PlaceMunicipality', function (Blueprint $table) {
+ Schema::create('place_municipalities', function (Blueprint $table) {
$table->id();
$table->string('code', 6)->unique();
$table->string('name', 200);
@@ -32,7 +32,7 @@ return new class extends Migration
$table->string('fax', 30)->nullable();
$table->decimal('latitude', 10, 8)->nullable();
$table->decimal('longitude', 11, 8)->nullable();
- $table->foreignId('province_id')->constrained('PlaceProvince');
+ $table->foreignId('province_id')->constrained('place_provinces');
});
}
@@ -41,7 +41,7 @@ return new class extends Migration
*/
public function down(): void
{
- Schema::dropIfExists('PlaceMunicipality');
- Schema::dropIfExists('PlaceProvince');
+ Schema::dropIfExists('place_municipalities');
+ Schema::dropIfExists('place_provinces');
}
};
diff --git a/backend/database/migrations/2024_01_23_011936_drop_old_municipality_column_from_places.php b/backend/database/migrations/2024_01_23_011936_drop_old_municipality_column_from_places.php
new file mode 100644
index 0000000..7e410a5
--- /dev/null
+++ b/backend/database/migrations/2024_01_23_011936_drop_old_municipality_column_from_places.php
@@ -0,0 +1,34 @@
+dropColumn('municipality');
+ $table->foreignId('municipality_id')->constrained('place_municipalities')->nullable();
+ $table->float('lat', 10, 6)->nullable()->change();
+ $table->float('lon', 10, 6)->nullable()->change();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('places', function (Blueprint $table) {
+ $table->dropColumn('municipality_id');
+ $table->string('municipality')->nullable();
+ $table->float('lat', 10, 6)->nullable(false)->change();
+ $table->float('lon', 10, 6)->nullable(false)->change();
+ });
+ }
+};
diff --git a/backend/database/seeders/OptionsSeeder.php b/backend/database/seeders/OptionsSeeder.php
index 6bd177e..a2d8c95 100644
--- a/backend/database/seeders/OptionsSeeder.php
+++ b/backend/database/seeders/OptionsSeeder.php
@@ -14,7 +14,7 @@ class OptionsSeeder extends Seeder
{
$options = [
[
- 'name' => 'service_place_selection_manual',
+ 'name' => 'service_place_selection_use_map_picker',
'value' => true,
'type' => 'boolean'
]
diff --git a/backend/routes/api.php b/backend/routes/api.php
index 361216b..a132074 100644
--- a/backend/routes/api.php
+++ b/backend/routes/api.php
@@ -82,7 +82,7 @@ Route::middleware('auth:sanctum')->group( function () {
Route::get('/places/italy/regions', [PlacesController::class, 'italyListRegions']);
Route::get('/places/italy/provinces/{region_name}', [PlacesController::class, 'italyListProvincesByRegion']);
Route::get('/places/italy/municipalities/{province_name}', [PlacesController::class, 'italyListMunicipalitiesByProvince']);
-
+
Route::get('/places/{id}', [PlacesController::class, 'show']);
Route::get('/trainings', [TrainingController::class, 'index'])->middleware(ETag::class);
diff --git a/frontend/src/app/_components/place-picker/place-picker.component.html b/frontend/src/app/_components/place-picker/place-picker.component.html
new file mode 100644
index 0000000..761a1d0
--- /dev/null
+++ b/frontend/src/app/_components/place-picker/place-picker.component.html
@@ -0,0 +1,22 @@
+
diff --git a/frontend/src/app/_components/place-picker/place-picker.component.scss b/frontend/src/app/_components/place-picker/place-picker.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/app/_components/place-picker/place-picker.component.ts b/frontend/src/app/_components/place-picker/place-picker.component.ts
new file mode 100644
index 0000000..e464846
--- /dev/null
+++ b/frontend/src/app/_components/place-picker/place-picker.component.ts
@@ -0,0 +1,99 @@
+import { Component, OnInit, Output, EventEmitter } from '@angular/core';
+import { ToastrService } from 'ngx-toastr';
+import { TranslateService } from '@ngx-translate/core';
+import { ApiClientService } from 'src/app/_services/api-client.service';
+
+interface Test {
+ codice: string;
+}
+
+@Component({
+ selector: 'place-picker',
+ templateUrl: './place-picker.component.html',
+ styleUrls: ['./place-picker.component.scss']
+})
+export class PlacePickerComponent implements OnInit {
+ selectedRegion?: string;
+ regions: string[] = [];
+
+ selectedProvince?: string;
+ selectedProvinceCodice?: string;
+ provinces: string[] = [];
+
+ selectedMunicipality?: string;
+ selectedMunicipalityCodice?: string;
+ municipalities: string[] = [];
+
+ selectedAddress?: string;
+
+ regionSelected = false;
+ provinceSelected = false;
+ municipalitySelected = false;
+ addressSelected = false;
+
+ @Output() addrSel = new EventEmitter();
+
+ constructor(private toastr: ToastrService, private api: ApiClientService, private translate: TranslateService) {
+ this.api.get('places/italy/regions').then((res: any) => {
+ this.regions = res;
+ console.log(this.regions);
+ }).catch((err: any) => {
+ console.error(err);
+ this.toastr.error(this.translate.instant("error_loading_regions"));
+ });
+ }
+
+ ngOnInit() {
+ }
+
+ onRegionSelected() {
+ this.selectedProvince = "";
+ this.selectedMunicipality = "";
+ this.selectedAddress = "";
+ this.provinceSelected = false;
+ this.municipalitySelected = false;
+
+ this.api.get('places/italy/provinces/' + this.selectedRegion).then((res: any) => {
+ this.provinces = res;
+ console.log(this.provinces);
+ this.regionSelected = true;
+ }).catch((err: any) => {
+ console.error(err);
+ this.toastr.error(this.translate.instant("error_loading_provinces"));
+ });
+ }
+
+ onProvinceSelected(event: any) {
+ this.selectedMunicipality = "";
+ this.selectedAddress = "";
+ this.municipalitySelected = false;
+
+ this.api.get('places/italy/municipalities/' + this.selectedProvince).then((res: any) => {
+ this.municipalities = res;
+ console.log(this.municipalities);
+
+ this.selectedProvinceCodice = event.item.codice;
+ this.provinceSelected = true;
+ }).catch((err: any) => {
+ console.error(err);
+ this.toastr.error(this.translate.instant("error_loading_municipalities"));
+ });
+ }
+
+ onMunicipalitySelected(event: any) {
+ this.selectedAddress = "";
+
+ this.selectedMunicipalityCodice = event.item.codice;
+
+ this.municipalitySelected = true;
+ }
+
+ onAddressChanged() {
+ this.addrSel.emit({
+ region: this.selectedRegion,
+ province: this.selectedProvinceCodice,
+ municipality: this.selectedMunicipalityCodice,
+ address: this.selectedAddress
+ });
+ }
+}
diff --git a/frontend/src/app/_components/place-picker/place-picker.module.ts b/frontend/src/app/_components/place-picker/place-picker.module.ts
new file mode 100644
index 0000000..5689552
--- /dev/null
+++ b/frontend/src/app/_components/place-picker/place-picker.module.ts
@@ -0,0 +1,24 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { FormsModule } from '@angular/forms';
+import { TypeaheadModule } from 'ngx-bootstrap/typeahead';
+import { TranslationModule } from '../../translation.module';
+
+import { PlacePickerComponent } from './place-picker.component';
+
+@NgModule({
+ declarations: [
+ PlacePickerComponent
+ ],
+ imports: [
+ CommonModule,
+ FormsModule,
+ TypeaheadModule,
+ TranslationModule
+ ],
+ exports: [
+ PlacePickerComponent
+ ]
+})
+export class PlacePickerModule { }
diff --git a/frontend/src/app/_components/table/table.component.html b/frontend/src/app/_components/table/table.component.html
index 606f2dd..8da0cfe 100644
--- a/frontend/src/app/_components/table/table.component.html
+++ b/frontend/src/app/_components/table/table.component.html
@@ -138,6 +138,7 @@
{{ row.place.name }}
{{ row.place.village }}
+ {{ row.place.municipality.name }} {{ row.place.municipality.province.short_name }}
{{ 'more details'|translate|ftitlecase }}
|
{{ row.notes }} |
@@ -206,19 +207,19 @@
{{ page.number }}
-
+
{{ 'next'|translate|ftitlecase }}
-
+
{{ 'previous'|translate|ftitlecase }}
-
+
{{ 'last'|translate|ftitlecase }}
-
+
{{ 'first'|translate|ftitlecase }}
diff --git a/frontend/src/app/_routes/edit-service/edit-service.component.html b/frontend/src/app/_routes/edit-service/edit-service.component.html
index 19b2f85..8331f39 100644
--- a/frontend/src/app/_routes/edit-service/edit-service.component.html
+++ b/frontend/src/app/_routes/edit-service/edit-service.component.html
@@ -63,10 +63,15 @@
-
+
-
-
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/frontend/src/app/_routes/edit-service/edit-service.component.ts b/frontend/src/app/_routes/edit-service/edit-service.component.ts
index 4a3bcbf..010d4d7 100644
--- a/frontend/src/app/_routes/edit-service/edit-service.component.ts
+++ b/frontend/src/app/_routes/edit-service/edit-service.component.ts
@@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { AbstractControl, UntypedFormBuilder, ValidationErrors, Validators } from '@angular/forms';
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';
@@ -22,11 +23,15 @@ export class EditServiceComponent implements OnInit {
crew: [],
lat: -1,
lon: -1,
+ provinceCode: '',
+ municipalityCode: '',
+ address: '',
notes: '',
type: ''
};
loadedServiceLat = "";
loadedServiceLng = "";
+ usingMapSelector = true;
users: any[] = [];
types: any[] = [];
@@ -44,8 +49,9 @@ export class EditServiceComponent implements OnInit {
get chief() { return this.serviceForm.get('chief'); }
get drivers() { return this.serviceForm.get('drivers'); }
get crew() { return this.serviceForm.get('crew'); }
- get lat() { return this.serviceForm.get('lat'); }
- get lon() { return this.serviceForm.get('lon'); }
+ get lat() { return this.serviceForm.get('place.lat'); }
+ get lon() { return this.serviceForm.get('place.lon'); }
+ get address() { return this.serviceForm.get('place.address'); }
get type() { return this.serviceForm.get('type'); }
ngOnInit() {
@@ -56,14 +62,26 @@ export class EditServiceComponent implements OnInit {
chief: [this.loadedService.chief, [Validators.required]],
drivers: [this.loadedService.drivers, []],
crew: [this.loadedService.crew, [Validators.required]],
- lat: [this.loadedService.lat, [Validators.required, (control: AbstractControl): ValidationErrors | null => {
- const valid = control.value >= -90 && control.value <= 90;
- return valid ? null : { 'invalidLatitude': { value: control.value } };
- }]],
- lon: [this.loadedService.lon, [Validators.required, (control: AbstractControl): ValidationErrors | null => {
- const valid = control.value >= -180 && control.value <= 180;
- return valid ? null : { 'invalidLongitude': { value: control.value } };
- }]],
+ place: this.fb.group({
+ lat: [this.loadedService.lat, this.usingMapSelector ?
+ [Validators.required, (control: AbstractControl): ValidationErrors | null => {
+ const valid = control.value >= -90 && control.value <= 90;
+ return valid ? null : { 'invalidLatitude': { value: control.value } };
+ }] : []
+ ],
+ lon: [this.loadedService.lon, this.usingMapSelector ?
+ [Validators.required, (control: AbstractControl): ValidationErrors | null => {
+ const valid = control.value >= -180 && control.value <= 180;
+ return valid ? null : { 'invalidLongitude': { value: control.value } };
+ }] : []
+ ],
+ provinceCode: [this.loadedService.provinceCode, this.usingMapSelector ?
+ [] : [Validators.required, Validators.minLength(3)]],
+ municipalityCode: [this.loadedService.municipalityCode, this.usingMapSelector ?
+ [] : [Validators.required, Validators.minLength(3)]],
+ address: [this.loadedService.address, this.usingMapSelector ?
+ [] : [Validators.required, Validators.minLength(3)]]
+ }),
notes: [this.loadedService.notes],
type: [this.loadedService.type, [Validators.required, Validators.minLength(1)]]
});
@@ -72,10 +90,12 @@ export class EditServiceComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private api: ApiClientService,
+ public auth: AuthService,
private toastr: ToastrService,
private fb: UntypedFormBuilder,
private translate: TranslateService
) {
+ this.usingMapSelector = this.auth.profile.getOption("service_place_selection_use_map_picker", true);
this.route.paramMap.subscribe(params => {
this.serviceId = params.get('id') || undefined;
if (this.serviceId === "new") {
@@ -182,15 +202,24 @@ export class EditServiceComponent implements OnInit {
return this.crew.value.find((x: number) => x == id);
}
- setPlace(lat: number, lng: number) {
+ setPlaceMap(lat: number, lng: number) {
this.lat.setValue(lat);
this.lon.setValue(lng);
console.log("Place selected", lat, lng);
}
+ setPlace(provinceCode: string, municipalityCode: string, address: string) {
+ this.serviceForm.get('place.provinceCode').setValue(provinceCode);
+ this.serviceForm.get('place.municipalityCode').setValue(municipalityCode);
+ this.address.setValue(address);
+ console.log("Place selected", provinceCode, municipalityCode, address);
+ }
+
//https://loiane.com/2017/08/angular-reactive-forms-trigger-validation-on-submit/
isFieldValid(field: string) {
- return this.formSubmitAttempt ? this.serviceForm.get(field).valid : true;
+ if(!this.formSubmitAttempt) return true;
+ if(this.serviceForm.get(field) == null) return false;
+ return this.serviceForm.get(field).valid;
}
formSubmit() {
diff --git a/frontend/src/app/_routes/edit-service/edit-service.module.ts b/frontend/src/app/_routes/edit-service/edit-service.module.ts
index a9695a8..b0c0057 100644
--- a/frontend/src/app/_routes/edit-service/edit-service.module.ts
+++ b/frontend/src/app/_routes/edit-service/edit-service.module.ts
@@ -4,6 +4,7 @@ import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
import { MapPickerModule } from '../../_components/map-picker/map-picker.module';
+import { PlacePickerModule } from 'src/app/_components/place-picker/place-picker.module';
import { DatetimePickerModule } from '../../_components/datetime-picker/datetime-picker.module';
import { BackBtnModule } from '../../_components/back-btn/back-btn.module';
import { TranslationModule } from '../../translation.module';
@@ -23,6 +24,7 @@ import { EditServiceComponent } from './edit-service.component';
ReactiveFormsModule,
BsDatepickerModule.forRoot(),
MapPickerModule,
+ PlacePickerModule,
DatetimePickerModule,
BackBtnModule,
TranslationModule,
diff --git a/frontend/src/app/_routes/place-details/place-details.component.html b/frontend/src/app/_routes/place-details/place-details.component.html
index 899ed83..37b6ca5 100644
--- a/frontend/src/app/_routes/place-details/place-details.component.html
+++ b/frontend/src/app/_routes/place-details/place-details.component.html
@@ -3,10 +3,11 @@
-
+
-
+
+
@@ -26,13 +27,43 @@
- {{ place_info.suburb }}
({{ 'place_details.postcode'|translate }}
{{ place_info.postcode }})
-
- {{ 'place_details.municipality'|translate|ftitlecase }}: {{ place_info.municipality }}
-
{{ 'place_details.road'|translate|ftitlecase }}: {{ place_info.road }}
{{ 'place_details.house_number'|translate|ftitlecase }}: {{ place_info.house_number }}
-
\ No newline at end of file
+
+
+
+
+
+
+ {{ 'name'|translate|ftitlecase }}: {{ place_info.name }} - {{ place_info.municipality.name }}
+ {{ 'province'|translate|ftitlecase }}: {{ place_info.municipality.province.name }} {{ place_info.municipality.province.short_name }}
+ {{ 'region'|translate|ftitlecase }}: {{ place_info.municipality.province.region }}
+
+
+
+ {{ 'cadastral_code'|translate|ftitlecase }}: {{ place_info.municipality.cadastral_code }}
+ {{ 'zip_code'|translate|ftitlecase }}: {{ place_info.municipality.postal_code }}
+ {{ 'prefix'|translate|ftitlecase }}: {{ place_info.municipality.prefix }}
+
+
+
+
diff --git a/frontend/src/app/_routes/place-details/place-details.component.ts b/frontend/src/app/_routes/place-details/place-details.component.ts
index 0429723..0171fa9 100644
--- a/frontend/src/app/_routes/place-details/place-details.component.ts
+++ b/frontend/src/app/_routes/place-details/place-details.component.ts
@@ -14,6 +14,7 @@ export class PlaceDetailsComponent implements OnInit {
id: number = 0;
lat: number = 0;
lon: number = 0;
+ place_query: string = '';
place_info: any = {};
place_loaded = false;
@@ -37,32 +38,36 @@ export class PlaceDetailsComponent implements OnInit {
this.lon = parseFloat(place_info.lon || '');
console.log(this.lat, this.lon);
- this.options = {
- layers: [
- tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '©
OpenStreetMap contributors' })
- ],
- zoom: 17,
- center: latLng(this.lat, this.lon)
- };
+ if(Number.isNaN(this.lat) || Number.isNaN(this.lon)) {
+ this.place_query = encodeURIComponent(place_info.name + ", " + place_info.municipality.name + " " + place_info.municipality.province.short_name);
+ } else {
+ this.options = {
+ layers: [
+ tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '©
OpenStreetMap contributors' })
+ ],
+ zoom: 17,
+ center: latLng(this.lat, this.lon)
+ };
- const iconRetinaUrl = "./assets/icons/marker-icon-2x.png";
- const iconUrl = "./assets/icons/marker-icon.png";
- const shadowUrl = "./assets/icons/marker-shadow.png";
- const iconDefault = new Icon({
- iconRetinaUrl,
- iconUrl,
- shadowUrl,
- iconSize: [25, 41],
- iconAnchor: [12, 41],
- popupAnchor: [1, -34],
- tooltipAnchor: [16, -28],
- shadowSize: [41, 41]
- });
- this.layers = [
- marker([this.lat, this.lon], {
- icon: iconDefault
- })
- ];
+ const iconRetinaUrl = "./assets/icons/marker-icon-2x.png";
+ const iconUrl = "./assets/icons/marker-icon.png";
+ const shadowUrl = "./assets/icons/marker-shadow.png";
+ const iconDefault = new Icon({
+ iconRetinaUrl,
+ iconUrl,
+ shadowUrl,
+ iconSize: [25, 41],
+ iconAnchor: [12, 41],
+ popupAnchor: [1, -34],
+ tooltipAnchor: [16, -28],
+ shadowSize: [41, 41]
+ });
+ this.layers = [
+ marker([this.lat, this.lon], {
+ icon: iconDefault
+ })
+ ];
+ }
this.place_loaded = true;
}).catch((err) => {
diff --git a/frontend/src/app/_routes/stats/stats-services/stats-services.component.html b/frontend/src/app/_routes/stats/stats-services/stats-services.component.html
index 6fa3a09..f07d436 100644
--- a/frontend/src/app/_routes/stats/stats-services/stats-services.component.html
+++ b/frontend/src/app/_routes/stats/stats-services/stats-services.component.html
@@ -21,6 +21,3 @@
Interventi per paese
-
-
Interventi per area di competenza
-
diff --git a/frontend/src/app/_routes/stats/stats-services/stats-services.component.ts b/frontend/src/app/_routes/stats/stats-services/stats-services.component.ts
index dcb2328..16ba318 100644
--- a/frontend/src/app/_routes/stats/stats-services/stats-services.component.ts
+++ b/frontend/src/app/_routes/stats/stats-services/stats-services.component.ts
@@ -21,7 +21,6 @@ export class StatsServicesComponent implements OnInit {
chartServicesByDriverData: any;
chartServicesByTypeData: any;
chartServicesByVillageData: any;
- chartServicesByMunicipalityData: any;
@ViewChild("servicesMap") servicesMap!: MapComponent;
@@ -109,14 +108,6 @@ export class StatsServicesComponent implements OnInit {
villages[service.place.village] = 0;
}
villages[service.place.village]++;
-
- if (service.place.municipality === null) service.place.municipality = this.translate.instant("unknown");
- // Capitalize first letter
- service.place.municipality = service.place.municipality.charAt(0).toUpperCase() + service.place.municipality.slice(1);
- if (!municipalities[service.place.municipality]) {
- municipalities[service.place.municipality] = 0;
- }
- municipalities[service.place.municipality]++;
}
console.log(people, chiefs, drivers, types, villages, municipalities);
@@ -190,20 +181,6 @@ export class StatsServicesComponent implements OnInit {
}
]
};
-
- let municipalitiesLabels: string[] = [], municipalitiesValues: number[] = [];
- Object.entries(municipalities).sort(([,a],[,b]) => b-a).forEach(([key, value]) => {
- municipalitiesLabels.push(key);
- municipalitiesValues.push(value);
- });
- this.chartServicesByMunicipalityData = {
- labels: municipalitiesLabels,
- datasets: [
- {
- data: municipalitiesValues,
- }
- ]
- };
}
loadServices() {
diff --git a/frontend/src/app/_services/auth.service.ts b/frontend/src/app/_services/auth.service.ts
index fa14fef..9b606ad 100644
--- a/frontend/src/app/_services/auth.service.ts
+++ b/frontend/src/app/_services/auth.service.ts
@@ -14,175 +14,190 @@ export interface LoginResponse {
providedIn: 'root'
})
export class AuthService {
- private defaultPlaceholderProfile: any = {
- id: undefined,
- impersonating: false,
- can: (permission: string) => false
- };
- public profile: any = this.defaultPlaceholderProfile;
- public authChanged = new Subject
();
- public _authLoaded = false;
+ private defaultPlaceholderProfile: any = {
+ id: undefined,
+ impersonating: false,
+ options: {},
+ can: (permission: string) => false,
+ getOption: (option: string, defaultValue: string) => defaultValue,
+ };
+ public profile: any = this.defaultPlaceholderProfile;
+ public authChanged = new Subject();
+ public _authLoaded = false;
- public loadProfile() {
- console.log("Loading profile data...");
- return new Promise((resolve, reject) => {
- this.api.post("me").then((data: any) => {
- this.profile = data;
-
- this.profile.can = (permission: string) => {
- return this.profile.permissions.includes(permission);
- }
+ public loadProfile() {
+ console.log("Loading profile data...");
+ return new Promise((resolve, reject) => {
+ this.api.post("me").then((data: any) => {
+ this.profile = data;
+ this.profile.options = this.profile.options.reduce((acc: any, val: any) => {
+ acc[val.name] = val.value;
+ return acc;
+ }, {});
- this.profile.profilePageLink = "/users/" + this.profile.id;
-
- Sentry.setUser({
- id: this.profile.id,
- name: this.profile.name
- });
-
- resolve();
- }).catch((e) => {
- console.error(e);
- this.profile = this.defaultPlaceholderProfile;
- reject();
- }).finally(() => {
- this.authChanged.next();
- });
- });
- }
-
- authLoaded() {
- return this._authLoaded;
- }
-
- constructor(
- private api: ApiClientService,
- private authToken: AuthTokenService,
- private router: Router
- ) {
- this.loadProfile().then(() => {
- console.log("User is authenticated");
- }).catch(() => {
- console.log("User is not logged in");
- }).finally(() => {
- this._authLoaded = true;
- });
- }
-
- public isAuthenticated() {
- return this.profile.id !== undefined;
- }
-
- public login(username: string, password: string) {
- return new Promise((resolve) => {
- this.api.get("csrf-cookie").then((data: any) => {
- this.api.post("login", {
- username: username,
- password: password,
- // use_sessions: true //Disabled because on cheap hosting it can cause problems
- }).then((data: any) => {
- this.authToken.updateToken(data.access_token);
- this.loadProfile().then(() => {
- resolve({
- loginOk: true,
- message: data.message
- });
- }).catch(() => {
- resolve({
- loginOk: false,
- message: "Unknown error"
- });
- });
- }).catch((err) => {
- let error_message = "";
- if(err.status === 401 || err.status === 422) {
- error_message = err.error.message;
- } else if (err.status === 400) {
- let error_messages = err.error.errors;
- error_message = error_messages.map((val: any) => {
- return `${val.msg} in ${val.param}`;
- }).join(" & ");
- } else if (err.status === 500) {
- error_message = "Server error";
- } else {
- error_message = "Unknown error";
- }
- resolve({
- loginOk: false,
- message: error_message
- });
- });
- }).catch((err) => {
- if(err.status = 500) {
- resolve({
- loginOk: false,
- message: "Server error"
- });
- } else {
- resolve({
- loginOk: false,
- message: "Unknown error"
- });
- }
- });
- })
- }
-
- public impersonate(user_id: number): Promise {
- return new Promise((resolve, reject) => {
- this.api.post(`impersonate/${user_id}`).then((data) => {
- this.authToken.updateToken(data.access_token);
- this.loadProfile().then(() => {
- resolve();
- }).catch((err) => {
- console.error(err);
- this.logout();
- this.profile.impersonating_user = false;
- this.logout();
- });
- }).catch((err) => {
- console.error(err);
- reject(err.error.message);
- });
- });
- }
-
- public stop_impersonating(): Promise {
- return new Promise((resolve, reject) => {
- this.api.post("stop_impersonating").then((data) => {
- this.authToken.updateToken(data.access_token);
- this.api.post("refresh_token").then((data) => {
- this.authToken.updateToken(data.access_token);
- Sentry.setUser(null);
- resolve();
- }).catch((err) => {
- this.logout(undefined, true);
- reject();
- });
- }).catch((err) => {
- this.logout(undefined, true);
- reject();
- });
- });
- }
-
- public logout(routerDestination?: string[] | undefined, forceLogout: boolean = false) {
- if(!forceLogout && this.profile.impersonating_user) {
- this.stop_impersonating().then(() => {
- this.loadProfile();
- }).catch((err) => {
- console.error(err);
- });
- } else {
- this.api.post("logout").then((data: any) => {
- this.profile = this.defaultPlaceholderProfile;
- if(routerDestination === undefined) {
- routerDestination = ["login", "list"];
- }
- this.authToken.clearToken();
- Sentry.setUser(null);
- this.router.navigate(routerDestination);
- });
+ this.profile.can = (permission: string) => {
+ return this.profile.permissions.includes(permission);
}
+
+ this.profile.getOption = (option: string, defaultValue: any) => {
+ let value = this.profile.options[option];
+ if (value === undefined) {
+ return defaultValue;
+ } else {
+ return value;
+ }
+ }
+
+ this.profile.profilePageLink = "/users/" + this.profile.id;
+
+ Sentry.setUser({
+ id: this.profile.id,
+ name: this.profile.name
+ });
+
+ resolve();
+ }).catch((e) => {
+ console.error(e);
+ this.profile = this.defaultPlaceholderProfile;
+ reject();
+ }).finally(() => {
+ this.authChanged.next();
+ });
+ });
+ }
+
+ authLoaded() {
+ return this._authLoaded;
+ }
+
+ constructor(
+ private api: ApiClientService,
+ private authToken: AuthTokenService,
+ private router: Router
+ ) {
+ this.loadProfile().then(() => {
+ console.log("User is authenticated");
+ }).catch(() => {
+ console.log("User is not logged in");
+ }).finally(() => {
+ this._authLoaded = true;
+ });
+ }
+
+ public isAuthenticated() {
+ return this.profile.id !== undefined;
+ }
+
+ public login(username: string, password: string) {
+ return new Promise((resolve) => {
+ this.api.get("csrf-cookie").then((data: any) => {
+ this.api.post("login", {
+ username: username,
+ password: password,
+ // use_sessions: true //Disabled because on cheap hosting it can cause problems
+ }).then((data: any) => {
+ this.authToken.updateToken(data.access_token);
+ this.loadProfile().then(() => {
+ resolve({
+ loginOk: true,
+ message: data.message
+ });
+ }).catch(() => {
+ resolve({
+ loginOk: false,
+ message: "Unknown error"
+ });
+ });
+ }).catch((err) => {
+ let error_message = "";
+ if (err.status === 401 || err.status === 422) {
+ error_message = err.error.message;
+ } else if (err.status === 400) {
+ let error_messages = err.error.errors;
+ error_message = error_messages.map((val: any) => {
+ return `${val.msg} in ${val.param}`;
+ }).join(" & ");
+ } else if (err.status === 500) {
+ error_message = "Server error";
+ } else {
+ error_message = "Unknown error";
+ }
+ resolve({
+ loginOk: false,
+ message: error_message
+ });
+ });
+ }).catch((err) => {
+ if (err.status = 500) {
+ resolve({
+ loginOk: false,
+ message: "Server error"
+ });
+ } else {
+ resolve({
+ loginOk: false,
+ message: "Unknown error"
+ });
+ }
+ });
+ })
+ }
+
+ public impersonate(user_id: number): Promise {
+ return new Promise((resolve, reject) => {
+ this.api.post(`impersonate/${user_id}`).then((data) => {
+ this.authToken.updateToken(data.access_token);
+ this.loadProfile().then(() => {
+ resolve();
+ }).catch((err) => {
+ console.error(err);
+ this.logout();
+ this.profile.impersonating_user = false;
+ this.logout();
+ });
+ }).catch((err) => {
+ console.error(err);
+ reject(err.error.message);
+ });
+ });
+ }
+
+ public stop_impersonating(): Promise {
+ return new Promise((resolve, reject) => {
+ this.api.post("stop_impersonating").then((data) => {
+ this.authToken.updateToken(data.access_token);
+ this.api.post("refresh_token").then((data) => {
+ this.authToken.updateToken(data.access_token);
+ Sentry.setUser(null);
+ resolve();
+ }).catch((err) => {
+ this.logout(undefined, true);
+ reject();
+ });
+ }).catch((err) => {
+ this.logout(undefined, true);
+ reject();
+ });
+ });
+ }
+
+ public logout(routerDestination?: string[] | undefined, forceLogout: boolean = false) {
+ if (!forceLogout && this.profile.impersonating_user) {
+ this.stop_impersonating().then(() => {
+ this.loadProfile();
+ }).catch((err) => {
+ console.error(err);
+ });
+ } else {
+ this.api.post("logout").then((data: any) => {
+ this.profile = this.defaultPlaceholderProfile;
+ if (routerDestination === undefined) {
+ routerDestination = ["login", "list"];
+ }
+ this.authToken.clearToken();
+ Sentry.setUser(null);
+ this.router.navigate(routerDestination);
+ });
}
+ }
}
diff --git a/frontend/src/assets/i18n/en.json b/frontend/src/assets/i18n/en.json
index e592e09..9166fa6 100644
--- a/frontend/src/assets/i18n/en.json
+++ b/frontend/src/assets/i18n/en.json
@@ -1,333 +1,337 @@
{
- "menu": {
- "list": "List",
- "services": "Services",
- "trainings": "Trainings",
- "logs": "Logs",
- "stats": "Stats",
- "admin": "Admin",
- "logout": "Logout",
- "stop_impersonating": "Stop impersonating",
- "hi": "hi"
- },
- "admin": {
- "info": "info",
- "maintenance": "maintenance",
- "roles": "roles",
- "installed_migrations": "installed migrations",
- "open_connections": "open connections",
- "db_engine_name": "database engine name",
- "database": "database",
- "host": "host",
- "port": "port",
- "charset": "charset",
- "prefix": "prefix",
- "operations": "operations",
- "run_migrations": "run migrations",
- "run_migrations_success": "Migrations executed successfully",
- "run_seeding": "run seeding",
- "run_seeding_confirm_title": "Are you sure you want to run the seeding?",
- "run_seeding_confirm_text": "This action cannot be undone, and data can be loss in the DB.",
- "run_seeding_success": "Seeding executed successfully",
- "show_tables": "show tables list",
- "hide_tables": "hide tables list",
- "table": "table",
- "rows": "rows",
- "updates_and_maintenance_title": "Updates and maintenance",
- "maintenance_mode_success": "Maintenance mode updated successfully",
- "optimization": "optimization",
- "run_optimization": "run optimization",
- "run_optimization_success": "Optimization executed successfully",
- "clear_optimization": "clear optimization",
- "clear_optimization_success": "Optimization cleared successfully",
- "clear_cache": "clear cache",
- "clear_cache_success": "Cache cleared successfully",
- "telegram_bot": "Telegram Bot",
- "telegram_webhook": "Telegram Webhook",
- "telegram_webhook_set": "Set Telegram Webhook",
- "telegram_webhook_set_success": "Telegram Webhook set successfully",
- "telegram_webhook_unset": "Unset Telegram Webhook",
- "telegram_webhook_unset_success": "Telegram Webhook unset successfully",
- "manual_execution": "manual execution",
- "run": "run",
- "run_confirm_title": "Are you sure you want to run this command?",
- "run_confirm_text": "This action cannot be undone.",
- "run_success": "Command executed successfully",
- "env_operations": "environment variables operations",
- "env_encrypt": "encrypt .env",
- "env_encrypt_title": "Encrypt .env file",
- "env_encrypt_confirm": "Insert the password to encrypt the .env file",
- "env_encrypt_success": ".env encrypted successfully",
- "env_decrypt": "decrypt .env",
- "env_decrypt_title": "Decrypt .env file",
- "env_decrypt_confirm": "Insert the password to decrypt the .env file",
- "env_decrypt_success": ".env decrypted successfully",
- "env_delete": "delete .env",
- "env_delete_title": "Delete .env file",
- "env_delete_confirm": "Are you sure you want to delete the .env file?",
- "env_delete_success": ".env deleted successfully",
- "options": "Options",
- "option_update_success": "Option updated successfully"
- },
- "table": {
- "remove_service_confirm": "Are you sure you want to remove this service?",
- "remove_service_confirm_text": "This action cannot be undone.",
- "service_deleted_successfully": "Service deleted successfully",
- "service_deleted_error": "Service could not be deleted. Please try again."
- },
- "list": {
- "your_availability_is": "You are:",
- "enable_schedules": "Enable hour schedules",
- "disable_schedules": "Disable hour schedules",
- "update_schedules": "Update availability schedules",
- "connect_telegram_bot": "Connect your account to the Telegram bot",
- "tooltip_change_availability": "Change your availability to {{state}}",
- "manual_mode_updated_successfully": "Manual mode updated successfully",
- "schedule_load_failed": "Schedule could not be loaded. Please try again",
- "schedule_update_failed": "Schedule could not be updated. Please try again",
- "availability_minutes_updated_at_deactivation": "Availability minutes are updated when the availability is removed, to allow a more precise calculation.",
- "availability_change_failed": "Availability could not be changed. Please try again",
- "manual_mode_update_failed": "Manual mode could not be updated. Please try again",
- "telegram_bot_token_request_failed": "Telegram bot token could not be generated. Please try again"
- },
- "alert": {
- "warning_body": "Alert in progress.",
- "current_alert": "Current alert",
- "current_alerts": "Current alerts",
- "state": "Alert state",
- "closed": "Alert closed",
- "request_response_question": "Do you respond to the alert?",
- "response_status": "Response status",
- "no_response": "No response",
- "waiting_for_response": "Waiting for response",
- "response_yes": "Available",
- "response_no": "Not available",
- "details": "Alert details",
- "delete": "Remove current alert",
- "delete_confirm_title": "Are you sure you want to remove this alert?",
- "delete_confirm_text": "This action cannot be undone.",
- "deleted_successfully": "Alert removed successfully",
- "delete_failed": "Alert could not be removed. Please try again",
- "settings_updated_successfully": "Settings updated successfully",
- "response_updated_successfully": "Response updated successfully"
- },
- "login": {
- "submit_btn": "Login"
- },
- "place_details": {
- "open_in_google_maps": "Open in Google Maps",
- "place_name": "Place name",
- "house_number": "house number",
- "road": "road",
- "village": "village",
- "postcode": "postcode",
- "hamlet": "hamlet",
- "municipality": "municipality",
- "country": "country",
- "place_load_failed": "Place could not be loaded. Please try again"
- },
- "map_picker": {
- "loading_error": "Error loading search results. Please try again later"
- },
- "edit_service": {
- "select_start_datetime": "Select start date and time for the service",
- "select_end_datetime": "Select end date and time for the service",
- "insert_code": "Insert service code",
- "other_crew_members": "Other crew members",
- "select_service_type": "Select a service type",
- "type_added_successfully": "Type added successfully",
- "service_added_successfully": "Service added successfully",
- "service_add_failed": "Service could not be added. Please try again",
- "service_updated_successfully": "Service updated successfully",
- "service_update_failed": "Service could not be updated. Please try again",
- "service_load_failed": "Service could not be loaded. Please try again",
- "users_load_failed": "Users could not be loaded. Please try again",
- "types_load_failed": "Types could not be loaded. Please try again",
- "type_add_failed": "Type could not be added. Please try again"
- },
- "edit_training": {
- "select_start_datetime": "Select start date and time for the training",
- "select_end_datetime": "Select end date and time for the training",
- "insert_name": "Insert training name",
- "name_placeholder": "Training name",
- "other_crew_members": "Other crew members",
- "training_added_successfully": "Training added successfully",
- "training_add_failed": "Training could not be added. Please try again",
- "training_updated_successfully": "Training updated successfully",
- "training_update_failed": "Training could not be updated. Please try again",
- "training_load_failed": "Error loading training. Please try again",
- "users_load_failed": "Error loading users. Please try again"
- },
- "edit_user": {
- "success_text": "User updated successfully",
- "error_text": "User could not be updated. Please try again",
- "creation_date": "User creation date",
- "last_update": "User last update",
- "last_access": "User last access"
- },
- "user_info_modal": {
- "title": "User info"
- },
- "training_course_modal": {
- "title": "Add training course",
- "doc_number": "document number"
- },
- "medical_examination_modal": {
- "title": "Add medical examination"
- },
- "useragent_info_modal": {
- "title": "Client information"
- },
- "validation": {
- "place_min_length": "Place name must be at least 3 characters long",
- "type_must_be_two_characters_long": "Type must be at least 2 characters long",
- "type_already_exists": "Type already exists",
- "image_format_not_supported": "Image format not supported",
- "document_format_not_supported": "Document format not supported",
- "file_too_big": "File too big",
- "password_min_length": "Password must be at least 6 characters long"
- },
- "options": {
- "service_place_selection_manual": "Manual place selection for services",
- "no_selection_available": "No selection available"
- },
- "update_available": "Update available",
- "update_available_text": "A new version of the application is available. Do you want to update now?",
- "update_now": "Update now",
- "yes_remove": "Yes, remove",
- "confirm": "Confirm",
- "cancel": "Cancel",
- "enable": "enable",
- "disable": "disable",
- "maintenance_mode": "maintenance mode",
- "maintenance_mode_warning": "The application is currently in maintenance mode. Some features may not be available.",
- "offline": "offline",
- "offline_warning": "You're offline. Some features may not be available.",
- "property": "property",
- "value": "value",
- "user_agent": "User Agent",
- "browser": "browser",
- "engine": "engine",
- "os": "Operating System",
- "device": "device",
- "cpu": "CPU",
- "username": "username",
- "password": "password",
- "new_password": "new password",
- "confirm_password": "confirm password",
- "password_not_match": "Passwords do not match. Please try again.",
- "password_changed_successfully": "Password changed successfully",
- "password_change_title": "Change password",
- "change_password": "Change password",
- "warning": "warning",
- "press_for_more_info": "press here for more info",
- "update_availability_schedule": "Update availability schedule",
- "select_type": "Select a type",
- "save_changes": "Save changes",
- "close": "Close",
- "monday": "Monday",
- "monday_short": "Mon",
- "tuesday": "Tuesday",
- "tuesday_short": "Tue",
- "wednesday": "Wednesday",
- "wednesday_short": "Wed",
- "thursday": "Thursday",
- "thursday_short": "Thu",
- "friday": "Friday",
- "friday_short": "Fri",
- "saturday": "Saturday",
- "saturday_short": "Sat",
- "sunday": "Sunday",
- "sunday_short": "Sun",
- "programmed": "programmed",
- "available": "available",
- "unavailable": "unavailable",
- "set_available": "available",
- "set_unavailable": "unavailable",
- "name": "name",
- "surname": "surname",
- "ssn": "Social Security Number",
- "address": "address",
- "zip_code": "zip code",
- "phone_number": "phone number",
- "email": "email",
- "birthday": "birthday",
- "birthplace": "birthplace",
- "personal_information": "personal information",
- "contact_information": "contact information",
- "service_information": "service information",
- "device_information": "device information",
- "course_date": "course date",
- "documents": "documents",
- "driving_license": "driving license",
- "driving_license_expiration_date": "driving license expiration date",
- "driving_license_number": "driving license number",
- "driving_license_type": "driving license type",
- "driving_license_scan": "driving license scan",
- "upload_scan": "upload scan",
- "upload_medical_examination_certificate": "upload medical examination certificate",
- "upload_training_course_doc": "upload training course document",
- "clothings": "clothings",
- "size": "size",
- "suit_size": "suit size",
- "boot_size": "boot size",
- "medical_examinations": "medical examinations",
- "training_courses": "training courses",
- "date": "date",
- "expiration_date": "expiration date",
- "certifier": "certifier",
- "certificate_short": "cert.",
- "banned": "banned",
- "hidden": "hidden",
- "driver": "driver",
- "drivers": "drivers",
- "call": "call",
- "service": "service",
- "services": "services",
- "training": "training",
- "trainings": "trainings",
- "user": "user",
- "users": "users",
- "availability_minutes": "availability_minutes",
- "action": "action",
- "changed": "changed",
- "editor": "editor",
- "datetime": "datetime",
- "start": "start",
- "end": "end",
- "code": "code",
- "chief": "chief",
- "crew": "crew",
- "place": "place",
- "province": "province",
- "notes": "notes",
- "type": "type",
- "add": "add",
- "update": "update",
- "remove": "remove",
- "more details": "more details",
- "search": "search",
- "submit": "invia",
- "reset": "reset",
- "go_back": "Go back",
- "next": "next",
- "previous": "previous",
- "last": "last",
- "first": "first",
- "total_elements_with_filters": "Total elements (curr. selection)",
- "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",
- "edit": "edit",
- "never": "never",
- "optional": "optional",
- "not_enough_permissions": "You don't have enough permissions to access this page.",
- "open_services_stats": "To view the statistics, go to the \"Stats\" page.",
- "error_title": "Error",
- "success_title": "Success",
- "select_date_range": "Select date range",
- "remove_date_filters": "Remove date filters",
- "yes": "yes",
- "no": "no"
-}
\ No newline at end of file
+ "menu": {
+ "list": "List",
+ "services": "Services",
+ "trainings": "Trainings",
+ "logs": "Logs",
+ "stats": "Stats",
+ "admin": "Admin",
+ "logout": "Logout",
+ "stop_impersonating": "Stop impersonating",
+ "hi": "hi"
+ },
+ "admin": {
+ "info": "info",
+ "maintenance": "maintenance",
+ "roles": "roles",
+ "installed_migrations": "installed migrations",
+ "open_connections": "open connections",
+ "db_engine_name": "database engine name",
+ "database": "database",
+ "host": "host",
+ "port": "port",
+ "charset": "charset",
+ "prefix": "prefix",
+ "operations": "operations",
+ "run_migrations": "run migrations",
+ "run_migrations_success": "Migrations executed successfully",
+ "run_seeding": "run seeding",
+ "run_seeding_confirm_title": "Are you sure you want to run the seeding?",
+ "run_seeding_confirm_text": "This action cannot be undone, and data can be loss in the DB.",
+ "run_seeding_success": "Seeding executed successfully",
+ "show_tables": "show tables list",
+ "hide_tables": "hide tables list",
+ "table": "table",
+ "rows": "rows",
+ "updates_and_maintenance_title": "Updates and maintenance",
+ "maintenance_mode_success": "Maintenance mode updated successfully",
+ "optimization": "optimization",
+ "run_optimization": "run optimization",
+ "run_optimization_success": "Optimization executed successfully",
+ "clear_optimization": "clear optimization",
+ "clear_optimization_success": "Optimization cleared successfully",
+ "clear_cache": "clear cache",
+ "clear_cache_success": "Cache cleared successfully",
+ "telegram_bot": "Telegram Bot",
+ "telegram_webhook": "Telegram Webhook",
+ "telegram_webhook_set": "Set Telegram Webhook",
+ "telegram_webhook_set_success": "Telegram Webhook set successfully",
+ "telegram_webhook_unset": "Unset Telegram Webhook",
+ "telegram_webhook_unset_success": "Telegram Webhook unset successfully",
+ "manual_execution": "manual execution",
+ "run": "run",
+ "run_confirm_title": "Are you sure you want to run this command?",
+ "run_confirm_text": "This action cannot be undone.",
+ "run_success": "Command executed successfully",
+ "env_operations": "environment variables operations",
+ "env_encrypt": "encrypt .env",
+ "env_encrypt_title": "Encrypt .env file",
+ "env_encrypt_confirm": "Insert the password to encrypt the .env file",
+ "env_encrypt_success": ".env encrypted successfully",
+ "env_decrypt": "decrypt .env",
+ "env_decrypt_title": "Decrypt .env file",
+ "env_decrypt_confirm": "Insert the password to decrypt the .env file",
+ "env_decrypt_success": ".env decrypted successfully",
+ "env_delete": "delete .env",
+ "env_delete_title": "Delete .env file",
+ "env_delete_confirm": "Are you sure you want to delete the .env file?",
+ "env_delete_success": ".env deleted successfully",
+ "options": "Options",
+ "option_update_success": "Option updated successfully"
+ },
+ "table": {
+ "remove_service_confirm": "Are you sure you want to remove this service?",
+ "remove_service_confirm_text": "This action cannot be undone.",
+ "service_deleted_successfully": "Service deleted successfully",
+ "service_deleted_error": "Service could not be deleted. Please try again."
+ },
+ "list": {
+ "your_availability_is": "You are:",
+ "enable_schedules": "Enable hour schedules",
+ "disable_schedules": "Disable hour schedules",
+ "update_schedules": "Update availability schedules",
+ "connect_telegram_bot": "Connect your account to the Telegram bot",
+ "tooltip_change_availability": "Change your availability to {{state}}",
+ "manual_mode_updated_successfully": "Manual mode updated successfully",
+ "schedule_load_failed": "Schedule could not be loaded. Please try again",
+ "schedule_update_failed": "Schedule could not be updated. Please try again",
+ "availability_minutes_updated_at_deactivation": "Availability minutes are updated when the availability is removed, to allow a more precise calculation.",
+ "availability_change_failed": "Availability could not be changed. Please try again",
+ "manual_mode_update_failed": "Manual mode could not be updated. Please try again",
+ "telegram_bot_token_request_failed": "Telegram bot token could not be generated. Please try again"
+ },
+ "alert": {
+ "warning_body": "Alert in progress.",
+ "current_alert": "Current alert",
+ "current_alerts": "Current alerts",
+ "state": "Alert state",
+ "closed": "Alert closed",
+ "request_response_question": "Do you respond to the alert?",
+ "response_status": "Response status",
+ "no_response": "No response",
+ "waiting_for_response": "Waiting for response",
+ "response_yes": "Available",
+ "response_no": "Not available",
+ "details": "Alert details",
+ "delete": "Remove current alert",
+ "delete_confirm_title": "Are you sure you want to remove this alert?",
+ "delete_confirm_text": "This action cannot be undone.",
+ "deleted_successfully": "Alert removed successfully",
+ "delete_failed": "Alert could not be removed. Please try again",
+ "settings_updated_successfully": "Settings updated successfully",
+ "response_updated_successfully": "Response updated successfully"
+ },
+ "login": {
+ "submit_btn": "Login"
+ },
+ "place_details": {
+ "open_in_google_maps": "Open in Google Maps",
+ "place_name": "Place name",
+ "house_number": "house number",
+ "road": "road",
+ "village": "village",
+ "postcode": "postcode",
+ "hamlet": "hamlet",
+ "municipality": "municipality",
+ "country": "country",
+ "place_load_failed": "Place could not be loaded. Please try again"
+ },
+ "map_picker": {
+ "loading_error": "Error loading search results. Please try again later"
+ },
+ "edit_service": {
+ "select_start_datetime": "Select start date and time for the service",
+ "select_end_datetime": "Select end date and time for the service",
+ "insert_code": "Insert service code",
+ "other_crew_members": "Other crew members",
+ "select_service_type": "Select a service type",
+ "type_added_successfully": "Type added successfully",
+ "service_added_successfully": "Service added successfully",
+ "service_add_failed": "Service could not be added. Please try again",
+ "service_updated_successfully": "Service updated successfully",
+ "service_update_failed": "Service could not be updated. Please try again",
+ "service_load_failed": "Service could not be loaded. Please try again",
+ "users_load_failed": "Users could not be loaded. Please try again",
+ "types_load_failed": "Types could not be loaded. Please try again",
+ "type_add_failed": "Type could not be added. Please try again"
+ },
+ "edit_training": {
+ "select_start_datetime": "Select start date and time for the training",
+ "select_end_datetime": "Select end date and time for the training",
+ "insert_name": "Insert training name",
+ "name_placeholder": "Training name",
+ "other_crew_members": "Other crew members",
+ "training_added_successfully": "Training added successfully",
+ "training_add_failed": "Training could not be added. Please try again",
+ "training_updated_successfully": "Training updated successfully",
+ "training_update_failed": "Training could not be updated. Please try again",
+ "training_load_failed": "Error loading training. Please try again",
+ "users_load_failed": "Error loading users. Please try again"
+ },
+ "edit_user": {
+ "success_text": "User updated successfully",
+ "error_text": "User could not be updated. Please try again",
+ "creation_date": "User creation date",
+ "last_update": "User last update",
+ "last_access": "User last access"
+ },
+ "user_info_modal": {
+ "title": "User info"
+ },
+ "training_course_modal": {
+ "title": "Add training course",
+ "doc_number": "document number"
+ },
+ "medical_examination_modal": {
+ "title": "Add medical examination"
+ },
+ "useragent_info_modal": {
+ "title": "Client information"
+ },
+ "validation": {
+ "place_min_length": "Place name must be at least 3 characters long",
+ "type_must_be_two_characters_long": "Type must be at least 2 characters long",
+ "type_already_exists": "Type already exists",
+ "image_format_not_supported": "Image format not supported",
+ "document_format_not_supported": "Document format not supported",
+ "file_too_big": "File too big",
+ "password_min_length": "Password must be at least 6 characters long"
+ },
+ "options": {
+ "service_place_selection_use_map_picker": "Use map to select service place",
+ "no_selection_available": "No selection available"
+ },
+ "update_available": "Update available",
+ "update_available_text": "A new version of the application is available. Do you want to update now?",
+ "update_now": "Update now",
+ "yes_remove": "Yes, remove",
+ "confirm": "Confirm",
+ "cancel": "Cancel",
+ "enable": "enable",
+ "disable": "disable",
+ "maintenance_mode": "maintenance mode",
+ "maintenance_mode_warning": "The application is currently in maintenance mode. Some features may not be available.",
+ "offline": "offline",
+ "offline_warning": "You're offline. Some features may not be available.",
+ "property": "property",
+ "value": "value",
+ "user_agent": "User Agent",
+ "browser": "browser",
+ "engine": "engine",
+ "os": "Operating System",
+ "device": "device",
+ "cpu": "CPU",
+ "username": "username",
+ "password": "password",
+ "new_password": "new password",
+ "confirm_password": "confirm password",
+ "password_not_match": "Passwords do not match. Please try again.",
+ "password_changed_successfully": "Password changed successfully",
+ "password_change_title": "Change password",
+ "change_password": "Change password",
+ "warning": "warning",
+ "press_for_more_info": "press here for more info",
+ "update_availability_schedule": "Update availability schedule",
+ "select_type": "Select a type",
+ "save_changes": "Save changes",
+ "close": "Close",
+ "monday": "Monday",
+ "monday_short": "Mon",
+ "tuesday": "Tuesday",
+ "tuesday_short": "Tue",
+ "wednesday": "Wednesday",
+ "wednesday_short": "Wed",
+ "thursday": "Thursday",
+ "thursday_short": "Thu",
+ "friday": "Friday",
+ "friday_short": "Fri",
+ "saturday": "Saturday",
+ "saturday_short": "Sat",
+ "sunday": "Sunday",
+ "sunday_short": "Sun",
+ "programmed": "programmed",
+ "available": "available",
+ "unavailable": "unavailable",
+ "set_available": "available",
+ "set_unavailable": "unavailable",
+ "name": "name",
+ "surname": "surname",
+ "ssn": "Social Security Number",
+ "address": "address",
+ "zip_code": "zip code",
+ "cadastral_code": "cadastral code",
+ "phone_number": "phone number",
+ "fax": "fax",
+ "email": "email",
+ "pec": "PEC",
+ "birthday": "birthday",
+ "birthplace": "birthplace",
+ "personal_information": "personal information",
+ "contact_information": "contact information",
+ "service_information": "service information",
+ "device_information": "device information",
+ "course_date": "course date",
+ "documents": "documents",
+ "driving_license": "driving license",
+ "driving_license_expiration_date": "driving license expiration date",
+ "driving_license_number": "driving license number",
+ "driving_license_type": "driving license type",
+ "driving_license_scan": "driving license scan",
+ "upload_scan": "upload scan",
+ "upload_medical_examination_certificate": "upload medical examination certificate",
+ "upload_training_course_doc": "upload training course document",
+ "clothings": "clothings",
+ "size": "size",
+ "suit_size": "suit size",
+ "boot_size": "boot size",
+ "medical_examinations": "medical examinations",
+ "training_courses": "training courses",
+ "date": "date",
+ "expiration_date": "expiration date",
+ "certifier": "certifier",
+ "certificate_short": "cert.",
+ "banned": "banned",
+ "hidden": "hidden",
+ "driver": "driver",
+ "drivers": "drivers",
+ "call": "call",
+ "service": "service",
+ "services": "services",
+ "training": "training",
+ "trainings": "trainings",
+ "user": "user",
+ "users": "users",
+ "availability_minutes": "availability_minutes",
+ "action": "action",
+ "changed": "changed",
+ "editor": "editor",
+ "datetime": "datetime",
+ "start": "start",
+ "end": "end",
+ "code": "code",
+ "chief": "chief",
+ "crew": "crew",
+ "place": "place",
+ "province": "province",
+ "region": "region",
+ "notes": "notes",
+ "type": "type",
+ "add": "add",
+ "update": "update",
+ "remove": "remove",
+ "more details": "more details",
+ "search": "search",
+ "submit": "invia",
+ "reset": "reset",
+ "go_back": "Go back",
+ "next": "next",
+ "previous": "previous",
+ "last": "last",
+ "first": "first",
+ "total_elements_with_filters": "Total elements (curr. selection)",
+ "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",
+ "edit": "edit",
+ "never": "never",
+ "optional": "optional",
+ "not_enough_permissions": "You don't have enough permissions to access this page.",
+ "open_services_stats": "To view the statistics, go to the \"Stats\" page.",
+ "error_title": "Error",
+ "success_title": "Success",
+ "select_date_range": "Select date range",
+ "remove_date_filters": "Remove date filters",
+ "yes": "yes",
+ "no": "no"
+}
diff --git a/frontend/src/assets/i18n/it.json b/frontend/src/assets/i18n/it.json
index 2f961d0..b7c2d8a 100644
--- a/frontend/src/assets/i18n/it.json
+++ b/frontend/src/assets/i18n/it.json
@@ -1,333 +1,337 @@
{
- "menu": {
- "list": "Lista disponibilità",
- "services": "Interventi",
- "trainings": "Esercitazioni",
- "logs": "Logs",
- "stats": "Statistiche",
- "admin": "Amministrazione",
- "logout": "Logout",
- "stop_impersonating": "Torna al vero account",
- "hi": "Ciao"
- },
- "admin": {
- "info": "Info",
- "maintenance": "Manutenzione",
- "roles": "Ruoli",
- "installed_migrations": "migrazioni installate",
- "open_connections": "connessioni aperte",
- "db_engine_name": "nome del motore del database",
- "database": "database",
- "host": "host",
- "port": "porta",
- "charset": "charset",
- "prefix": "prefisso",
- "operations": "operazioni",
- "run_migrations": "esegui migrazioni",
- "run_migrations_success": "Migrazioni eseguite con successo",
- "run_seeding": "esegui seeding",
- "run_seeding_confirm_title": "Sei sicuro di voler eseguire il seeding?",
- "run_seeding_confirm_text": "Questa operazione potrebbe sovrascrivere i dati presenti nel database.",
- "run_seeding_success": "Seeding eseguito con successo",
- "show_tables": "mostra lista tabelle",
- "hide_tables": "nascondi lista tabelle",
- "table": "tabella",
- "rows": "righe",
- "updates_and_maintenance_title": "Aggiornamenti e manutenzione",
- "maintenance_mode_success": "Modalità manutenzione aggiornata con successo",
- "optimization": "ottimizzazione",
- "run_optimization": "esegui ottimizzazione",
- "run_optimization_success": "Ottimizzazione eseguita con successo",
- "clear_optimization": "rimuovi ottimizzazione",
- "clear_optimization_success": "Ottimizzazione rimossa con successo",
- "clear_cache": "svuota cache",
- "clear_cache_success": "Cache svuotata con successo",
- "telegram_bot": "Bot Telegram",
- "telegram_webhook": "Webhook Telegram",
- "telegram_webhook_set": "Imposta Webhook Telegram",
- "telegram_webhook_set_success": "Webhook Telegram impostato con successo",
- "telegram_webhook_unset": "Rimuovi Webhook Telegram",
- "telegram_webhook_unset_success": "Webhook Telegram rimosso con successo",
- "manual_execution": "esecuzione manuale",
- "run": "esegui",
- "run_confirm_title": "Sei sicuro di voler eseguire questo comando?",
- "run_confirm_text": "Questa operazione non potrà essere annullata.",
- "run_success": "Comando eseguito con successo",
- "env_operations": "operazioni alle variabili d'ambiente",
- "env_encrypt": "cripta .env",
- "env_encrypt_title": "Cripta il file .env",
- "env_encrypt_text": "Inserisci la password per criptare il file .env",
- "env_encrypt_success": ".env criptato con successo",
- "env_decrypt": "decripta .env",
- "env_decrypt_title": "Decripta il file .env",
- "env_decrypt_text": "Inserisci la password per decriptare il file .env",
- "env_decrypt_success": ".env decriptato con successo",
- "env_delete": "rimuovi .env",
- "env_delete_title": "Rimuovi il file .env",
- "env_delete_text": "Sei sicuro di voler rimuovere il file .env?",
- "env_delete_success": ".env rimosso con successo",
- "options": "Opzioni",
- "option_update_success": "Opzione aggiornata con successo"
- },
- "table": {
- "remove_service_confirm": "Sei sicuro di voler rimuovere questo intervento?",
- "remove_service_confirm_text": "Questa operazione non può essere annullata.",
- "service_deleted_successfully": "Intervento rimosso con successo",
- "service_deleted_error": "Errore durante la rimozione dell'intervento. Riprova più tardi"
- },
- "list": {
- "your_availability_is": "Attualmente sei:",
- "enable_schedules": "Abilita programmazione oraria",
- "disable_schedules": "Disattiva programmazione oraria",
- "update_schedules": "Modifica orari disponibilità",
- "connect_telegram_bot": "Collega l'account al bot Telegram",
- "tooltip_change_availability": "Cambia la tua disponibilità in {{state}}",
- "manual_mode_updated_successfully": "Modalità manuale aggiornata con successo",
- "schedule_load_failed": "Errore durante il caricamento della programmazione. Riprova più tardi",
- "schedule_update_failed": "Errore durante l'aggiornamento della programmazione. Riprova più tardi",
- "availability_minutes_updated_at_deactivation": "I minuti di disponibilità vengono aggiornati al momento della rimozione della disponibilità, per permettere un calcolo più preciso.",
- "availability_change_failed": "Errore durante il cambio di disponibilità. Riprova più tardi",
- "manual_mode_update_failed": "Errore durante l'aggiornamento della modalità manuale. Riprova più tardi",
- "telegram_bot_token_request_failed": "Errore durante la richiesta del token del bot Telegram. Riprova più tardi"
- },
- "alert": {
- "warning_body": "Allertamento in corso.",
- "current_alert": "Emergenza in corso",
- "current_alerts": "Emergenze in corso",
- "state": "Stato dell'allerta",
- "closed": "Allerta rientrata",
- "request_response_question": "Sarai presente alla chiamata?",
- "response_status": "Stato della risposta",
- "no_response": "Nessuna risposta",
- "waiting_for_response": "In attesa di risposta",
- "response_yes": "Presente",
- "response_no": "Non presente",
- "details": "Dettagli dell'allerta",
- "delete": "Ritira allerta",
- "delete_confirm_title": "Sei sicuro di voler rimuovere questa allerta?",
- "delete_confirm_text": "I vigili saranno avvisati della rimozione.",
- "deleted_successfully": "Allerta rimossa con successo",
- "delete_failed": "L'eliminazione dell'allerta è fallita. Riprova più tardi",
- "settings_updated_successfully": "Impostazioni aggiornate con successo",
- "response_updated_successfully": "Risposta aggiornata con successo"
- },
- "login": {
- "submit_btn": "Login"
- },
- "place_details": {
- "open_in_google_maps": "Apri il luogo in Google Maps",
- "place_name": "Nome del luogo",
- "house_number": "numero civico",
- "road": "strada",
- "village": "comune",
- "postcode": "CAP",
- "hamlet": "frazione",
- "municipality": "raggruppamento del comune",
- "country": "nazione/zona",
- "place_load_failed": "Errore durante il caricamento del luogo. Riprova più tardi"
- },
- "map_picker": {
- "loading_error": "Errore di caricamento dei risultati della ricerca. Riprovare più tardi"
- },
- "edit_service": {
- "select_start_datetime": "Seleziona data e ora di inizio dell'intervento",
- "select_end_datetime": "Seleziona data e ora di fine dell'intervento",
- "insert_code": "Inserisci il progressivo dell'intervento",
- "other_crew_members": "Altri membri della squadra",
- "select_service_type": "Seleziona una tipologia di intervento",
- "type_added_successfully": "Tipologia aggiunta con successo",
- "service_added_successfully": "Intervento aggiunto con successo",
- "service_add_failed": "Errore durante l'aggiunta dell'intervento. Riprovare più tardi",
- "service_updated_successfully": "Intervento aggiornato con successo",
- "service_update_failed": "Errore durante l'aggiornamento dell'intervento. Riprovare più tardi",
- "service_load_failed": "Errore durante il caricamento dell'intervento. Riprovare più tardi",
- "users_load_failed": "Errore durante il caricamento degli utenti. Riprovare più tardi",
- "types_load_failed": "Errore durante il caricamento delle tipologie di intervento. Riprovare più tardi",
- "type_add_failed": "Errore durante l'aggiunta della tipologia. Riprovare più tardi"
- },
- "edit_training": {
- "select_start_datetime": "Seleziona data e ora di inizio dell'esercitazione",
- "select_end_datetime": "Seleziona data e ora di fine dell'esercitazione",
- "insert_name": "Inserisci il nome dell'esercitazione",
- "name_placeholder": "Esercitazione di gennaio",
- "other_crew_members": "Altri membri della squadra",
- "training_added_successfully": "Esercitazione aggiunta con successo",
- "training_add_failed": "Errore durante l'aggiunta dell'esercitazione. Riprovare più tardi",
- "training_updated_successfully": "Esercitazione aggiornata con successo",
- "training_update_failed": "Errore durante l'aggiornamento dell'esercitazione. Riprovare più tardi",
- "training_load_failed": "Errore durante il caricamento dell'intervento. Riprovare più tardi",
- "users_load_failed": "Errore durante il caricamento degli utenti. Riprovare più tardi"
- },
- "edit_user": {
- "success_text": "Utente aggiornato con successo",
- "error_text": "L'utente non può essere aggiornato. Riprova più tardi",
- "creation_date": "Data di creazione dell'utente",
- "last_update": "Data di ultima modifica dell'utente",
- "last_access": "Ultimo accesso dell'utente"
- },
- "user_info_modal": {
- "title": "Scheda utente"
- },
- "training_course_modal": {
- "title": "Aggiungi corso di formazione",
- "doc_number": "numero Ordine del Giorno"
- },
- "medical_examination_modal": {
- "title": "Aggiungi visita medica"
- },
- "useragent_info_modal": {
- "title": "Informazioni sul client"
- },
- "validation": {
- "place_min_length": "Il nome della località deve essere di almeno 3 caratteri",
- "type_must_be_two_characters_long": "La tipologia deve essere di almeno 2 caratteri",
- "type_already_exists": "La tipologia è già presente",
- "image_format_not_supported": "Formato immagine non supportato",
- "document_format_not_supported": "Formato documento non supportato",
- "file_too_big": "File troppo grande",
- "password_min_length": "La password deve essere di almeno 6 caratteri"
- },
- "options": {
- "service_place_selection_manual": "Seleziona manualmente il luogo dell'intervento",
- "no_selection_available": "Nessuna selezione disponibile"
- },
- "update_available": "Aggiornamento disponibile",
- "update_available_text": "È disponibile un aggiornamento per Allerta. Vuoi aggiornare ora?",
- "update_now": "Aggiorna ora",
- "yes_remove": "Si, rimuovi",
- "confirm": "Conferma",
- "cancel": "Annulla",
- "enable": "attiva",
- "disable": "disattiva",
- "maintenance_mode": "modalità manutenzione",
- "maintenance_mode_warning": "Il gestionale è in manutenzione. Alcune funzionalità potrebbero non essere disponibili.",
- "offline": "offline",
- "offline_warning": "Sei offline. Non è possibile interagire con il gestionale.",
- "property": "proprietà",
- "value": "valore",
- "user_agent": "User Agent",
- "browser": "browser",
- "engine": "motore",
- "os": "Sistema Operativo",
- "device": "dispositivo",
- "cpu": "CPU",
- "username": "username",
- "password": "password",
- "new_password": "nuova password",
- "confirm_password": "conferma password",
- "password_not_match": "Le password non corrispondono. Riprova.",
- "password_changed_successfully": "Password cambiata con successo",
- "password_change_title": "Cambio password",
- "change_password": "Cambia password",
- "warning": "attenzione",
- "press_for_more_info": "premi qui per informazioni",
- "update_availability_schedule": "Aggiorna programmazione disponibilità",
- "select_type": "Seleziona una tipologia",
- "save_changes": "Salva modifiche",
- "close": "Chiudi",
- "monday": "Lunedì",
- "monday_short": "Lun",
- "tuesday": "Martedì",
- "tuesday_short": "Mar",
- "wednesday": "Mercoledì",
- "wednesday_short": "Mer",
- "thursday": "Giovedì",
- "thursday_short": "Gio",
- "friday": "Venerdì",
- "friday_short": "Ven",
- "saturday": "Sabato",
- "saturday_short": "Sab",
- "sunday": "Domenica",
- "sunday_short": "Dom",
- "programmed": "programmata",
- "available": "disponibile",
- "unavailable": "non disponibile",
- "set_available": "attiva",
- "set_unavailable": "disattiva",
- "name": "nome",
- "surname": "cognome",
- "ssn": "codice fiscale",
- "address": "indirizzo",
- "zip_code": "CAP",
- "phone_number": "numero di telefono",
- "email": "email",
- "birthday": "data di nascita",
- "birthplace": "luogo di nascita",
- "personal_information": "anagrafica personale",
- "contact_information": "recapiti",
- "service_information": "informazioni di servizio",
- "device_information": "informazioni sul dispositivo",
- "course_date": "data corso",
- "documents": "documenti",
- "driving_license": "patente",
- "driving_license_expiration_date": "scadenza patente",
- "driving_license_number": "numero patente",
- "driving_license_type": "tipologia patente",
- "driving_license_scan": "scansione patente",
- "upload_scan": "carica scansione",
- "upload_medical_examination_certificate": "carica certificato visita medica",
- "upload_training_course_doc": "carica Ordine del Giorno",
- "clothings": "indumenti",
- "size": "dimensione",
- "suit_size": "taglia tuta",
- "boot_size": "taglia scarponi",
- "medical_examinations": "visite mediche",
- "training_courses": "corsi di formazione",
- "date": "data",
- "expiration_date": "data di scadenza",
- "certifier": "ente/certificatore",
- "certificate_short": "cert.",
- "banned": "bannato",
- "hidden": "nascosto",
- "driver": "autista",
- "drivers": "autisti",
- "call": "chiama",
- "service": "intervento",
- "services": "interventi",
- "training": "esercitazione",
- "trainings": "esercitazioni",
- "user": "utente",
- "users": "utenti",
- "availability_minutes": "minuti di disponibilità",
- "action": "azione",
- "changed": "interessato",
- "editor": "fatto da",
- "datetime": "data e ora",
- "start": "inizio",
- "end": "fine",
- "code": "codice",
- "chief": "caposquadra",
- "crew": "squadra",
- "place": "luogo",
- "province": "provincia",
- "notes": "note",
- "type": "tipologia",
- "add": "aggiungi",
- "update": "modifica",
- "remove": "rimuovi",
- "more details": "altri dettagli",
- "search": "cerca",
- "submit": "invia",
- "reset": "reset",
- "go_back": "Torna indietro",
- "next": "successiva",
- "previous": "precedente",
- "last": "ultima",
- "first": "prima",
- "total_elements_with_filters": "# Elementi (selezione corrente)",
- "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",
- "edit": "modifica",
- "never": "mai",
- "optional": "opzionale",
- "not_enough_permissions": "Non hai i permessi necessari per accedere a questa pagina.",
- "open_services_stats": "Per visualizzare statistiche e mappa degli interventi, vai alla sezione \"Statistiche\".",
- "error_title": "Errore",
- "success_title": "Successo",
- "select_date_range": "Seleziona un intervallo di date",
- "remove_date_filters": "Rimuovi filtri",
- "yes": "si",
- "no": "no"
-}
\ No newline at end of file
+ "menu": {
+ "list": "Lista disponibilità",
+ "services": "Interventi",
+ "trainings": "Esercitazioni",
+ "logs": "Logs",
+ "stats": "Statistiche",
+ "admin": "Amministrazione",
+ "logout": "Logout",
+ "stop_impersonating": "Torna al vero account",
+ "hi": "Ciao"
+ },
+ "admin": {
+ "info": "Info",
+ "maintenance": "Manutenzione",
+ "roles": "Ruoli",
+ "installed_migrations": "migrazioni installate",
+ "open_connections": "connessioni aperte",
+ "db_engine_name": "nome del motore del database",
+ "database": "database",
+ "host": "host",
+ "port": "porta",
+ "charset": "charset",
+ "prefix": "prefisso",
+ "operations": "operazioni",
+ "run_migrations": "esegui migrazioni",
+ "run_migrations_success": "Migrazioni eseguite con successo",
+ "run_seeding": "esegui seeding",
+ "run_seeding_confirm_title": "Sei sicuro di voler eseguire il seeding?",
+ "run_seeding_confirm_text": "Questa operazione potrebbe sovrascrivere i dati presenti nel database.",
+ "run_seeding_success": "Seeding eseguito con successo",
+ "show_tables": "mostra lista tabelle",
+ "hide_tables": "nascondi lista tabelle",
+ "table": "tabella",
+ "rows": "righe",
+ "updates_and_maintenance_title": "Aggiornamenti e manutenzione",
+ "maintenance_mode_success": "Modalità manutenzione aggiornata con successo",
+ "optimization": "ottimizzazione",
+ "run_optimization": "esegui ottimizzazione",
+ "run_optimization_success": "Ottimizzazione eseguita con successo",
+ "clear_optimization": "rimuovi ottimizzazione",
+ "clear_optimization_success": "Ottimizzazione rimossa con successo",
+ "clear_cache": "svuota cache",
+ "clear_cache_success": "Cache svuotata con successo",
+ "telegram_bot": "Bot Telegram",
+ "telegram_webhook": "Webhook Telegram",
+ "telegram_webhook_set": "Imposta Webhook Telegram",
+ "telegram_webhook_set_success": "Webhook Telegram impostato con successo",
+ "telegram_webhook_unset": "Rimuovi Webhook Telegram",
+ "telegram_webhook_unset_success": "Webhook Telegram rimosso con successo",
+ "manual_execution": "esecuzione manuale",
+ "run": "esegui",
+ "run_confirm_title": "Sei sicuro di voler eseguire questo comando?",
+ "run_confirm_text": "Questa operazione non potrà essere annullata.",
+ "run_success": "Comando eseguito con successo",
+ "env_operations": "operazioni alle variabili d'ambiente",
+ "env_encrypt": "cripta .env",
+ "env_encrypt_title": "Cripta il file .env",
+ "env_encrypt_text": "Inserisci la password per criptare il file .env",
+ "env_encrypt_success": ".env criptato con successo",
+ "env_decrypt": "decripta .env",
+ "env_decrypt_title": "Decripta il file .env",
+ "env_decrypt_text": "Inserisci la password per decriptare il file .env",
+ "env_decrypt_success": ".env decriptato con successo",
+ "env_delete": "rimuovi .env",
+ "env_delete_title": "Rimuovi il file .env",
+ "env_delete_text": "Sei sicuro di voler rimuovere il file .env?",
+ "env_delete_success": ".env rimosso con successo",
+ "options": "Opzioni",
+ "option_update_success": "Opzione aggiornata con successo"
+ },
+ "table": {
+ "remove_service_confirm": "Sei sicuro di voler rimuovere questo intervento?",
+ "remove_service_confirm_text": "Questa operazione non può essere annullata.",
+ "service_deleted_successfully": "Intervento rimosso con successo",
+ "service_deleted_error": "Errore durante la rimozione dell'intervento. Riprova più tardi"
+ },
+ "list": {
+ "your_availability_is": "Attualmente sei:",
+ "enable_schedules": "Abilita programmazione oraria",
+ "disable_schedules": "Disattiva programmazione oraria",
+ "update_schedules": "Modifica orari disponibilità",
+ "connect_telegram_bot": "Collega l'account al bot Telegram",
+ "tooltip_change_availability": "Cambia la tua disponibilità in {{state}}",
+ "manual_mode_updated_successfully": "Modalità manuale aggiornata con successo",
+ "schedule_load_failed": "Errore durante il caricamento della programmazione. Riprova più tardi",
+ "schedule_update_failed": "Errore durante l'aggiornamento della programmazione. Riprova più tardi",
+ "availability_minutes_updated_at_deactivation": "I minuti di disponibilità vengono aggiornati al momento della rimozione della disponibilità, per permettere un calcolo più preciso.",
+ "availability_change_failed": "Errore durante il cambio di disponibilità. Riprova più tardi",
+ "manual_mode_update_failed": "Errore durante l'aggiornamento della modalità manuale. Riprova più tardi",
+ "telegram_bot_token_request_failed": "Errore durante la richiesta del token del bot Telegram. Riprova più tardi"
+ },
+ "alert": {
+ "warning_body": "Allertamento in corso.",
+ "current_alert": "Emergenza in corso",
+ "current_alerts": "Emergenze in corso",
+ "state": "Stato dell'allerta",
+ "closed": "Allerta rientrata",
+ "request_response_question": "Sarai presente alla chiamata?",
+ "response_status": "Stato della risposta",
+ "no_response": "Nessuna risposta",
+ "waiting_for_response": "In attesa di risposta",
+ "response_yes": "Presente",
+ "response_no": "Non presente",
+ "details": "Dettagli dell'allerta",
+ "delete": "Ritira allerta",
+ "delete_confirm_title": "Sei sicuro di voler rimuovere questa allerta?",
+ "delete_confirm_text": "I vigili saranno avvisati della rimozione.",
+ "deleted_successfully": "Allerta rimossa con successo",
+ "delete_failed": "L'eliminazione dell'allerta è fallita. Riprova più tardi",
+ "settings_updated_successfully": "Impostazioni aggiornate con successo",
+ "response_updated_successfully": "Risposta aggiornata con successo"
+ },
+ "login": {
+ "submit_btn": "Login"
+ },
+ "place_details": {
+ "open_in_google_maps": "Apri il luogo in Google Maps",
+ "place_name": "Nome del luogo",
+ "house_number": "numero civico",
+ "road": "strada",
+ "village": "comune",
+ "postcode": "CAP",
+ "hamlet": "frazione",
+ "municipality": "comune",
+ "country": "nazione/zona",
+ "place_load_failed": "Errore durante il caricamento del luogo. Riprova più tardi"
+ },
+ "map_picker": {
+ "loading_error": "Errore di caricamento dei risultati della ricerca. Riprovare più tardi"
+ },
+ "edit_service": {
+ "select_start_datetime": "Seleziona data e ora di inizio dell'intervento",
+ "select_end_datetime": "Seleziona data e ora di fine dell'intervento",
+ "insert_code": "Inserisci il progressivo dell'intervento",
+ "other_crew_members": "Altri membri della squadra",
+ "select_service_type": "Seleziona una tipologia di intervento",
+ "type_added_successfully": "Tipologia aggiunta con successo",
+ "service_added_successfully": "Intervento aggiunto con successo",
+ "service_add_failed": "Errore durante l'aggiunta dell'intervento. Riprovare più tardi",
+ "service_updated_successfully": "Intervento aggiornato con successo",
+ "service_update_failed": "Errore durante l'aggiornamento dell'intervento. Riprovare più tardi",
+ "service_load_failed": "Errore durante il caricamento dell'intervento. Riprovare più tardi",
+ "users_load_failed": "Errore durante il caricamento degli utenti. Riprovare più tardi",
+ "types_load_failed": "Errore durante il caricamento delle tipologie di intervento. Riprovare più tardi",
+ "type_add_failed": "Errore durante l'aggiunta della tipologia. Riprovare più tardi"
+ },
+ "edit_training": {
+ "select_start_datetime": "Seleziona data e ora di inizio dell'esercitazione",
+ "select_end_datetime": "Seleziona data e ora di fine dell'esercitazione",
+ "insert_name": "Inserisci il nome dell'esercitazione",
+ "name_placeholder": "Esercitazione di gennaio",
+ "other_crew_members": "Altri membri della squadra",
+ "training_added_successfully": "Esercitazione aggiunta con successo",
+ "training_add_failed": "Errore durante l'aggiunta dell'esercitazione. Riprovare più tardi",
+ "training_updated_successfully": "Esercitazione aggiornata con successo",
+ "training_update_failed": "Errore durante l'aggiornamento dell'esercitazione. Riprovare più tardi",
+ "training_load_failed": "Errore durante il caricamento dell'intervento. Riprovare più tardi",
+ "users_load_failed": "Errore durante il caricamento degli utenti. Riprovare più tardi"
+ },
+ "edit_user": {
+ "success_text": "Utente aggiornato con successo",
+ "error_text": "L'utente non può essere aggiornato. Riprova più tardi",
+ "creation_date": "Data di creazione dell'utente",
+ "last_update": "Data di ultima modifica dell'utente",
+ "last_access": "Ultimo accesso dell'utente"
+ },
+ "user_info_modal": {
+ "title": "Scheda utente"
+ },
+ "training_course_modal": {
+ "title": "Aggiungi corso di formazione",
+ "doc_number": "numero Ordine del Giorno"
+ },
+ "medical_examination_modal": {
+ "title": "Aggiungi visita medica"
+ },
+ "useragent_info_modal": {
+ "title": "Informazioni sul client"
+ },
+ "validation": {
+ "place_min_length": "Il nome della località deve essere di almeno 3 caratteri",
+ "type_must_be_two_characters_long": "La tipologia deve essere di almeno 2 caratteri",
+ "type_already_exists": "La tipologia è già presente",
+ "image_format_not_supported": "Formato immagine non supportato",
+ "document_format_not_supported": "Formato documento non supportato",
+ "file_too_big": "File troppo grande",
+ "password_min_length": "La password deve essere di almeno 6 caratteri"
+ },
+ "options": {
+ "service_place_selection_use_map_picker": "Utilizza una mappa per selezionare il luogo dell'intervento",
+ "no_selection_available": "Nessuna selezione disponibile"
+ },
+ "update_available": "Aggiornamento disponibile",
+ "update_available_text": "È disponibile un aggiornamento per Allerta. Vuoi aggiornare ora?",
+ "update_now": "Aggiorna ora",
+ "yes_remove": "Si, rimuovi",
+ "confirm": "Conferma",
+ "cancel": "Annulla",
+ "enable": "attiva",
+ "disable": "disattiva",
+ "maintenance_mode": "modalità manutenzione",
+ "maintenance_mode_warning": "Il gestionale è in manutenzione. Alcune funzionalità potrebbero non essere disponibili.",
+ "offline": "offline",
+ "offline_warning": "Sei offline. Non è possibile interagire con il gestionale.",
+ "property": "proprietà",
+ "value": "valore",
+ "user_agent": "User Agent",
+ "browser": "browser",
+ "engine": "motore",
+ "os": "Sistema Operativo",
+ "device": "dispositivo",
+ "cpu": "CPU",
+ "username": "username",
+ "password": "password",
+ "new_password": "nuova password",
+ "confirm_password": "conferma password",
+ "password_not_match": "Le password non corrispondono. Riprova.",
+ "password_changed_successfully": "Password cambiata con successo",
+ "password_change_title": "Cambio password",
+ "change_password": "Cambia password",
+ "warning": "attenzione",
+ "press_for_more_info": "premi qui per informazioni",
+ "update_availability_schedule": "Aggiorna programmazione disponibilità",
+ "select_type": "Seleziona una tipologia",
+ "save_changes": "Salva modifiche",
+ "close": "Chiudi",
+ "monday": "Lunedì",
+ "monday_short": "Lun",
+ "tuesday": "Martedì",
+ "tuesday_short": "Mar",
+ "wednesday": "Mercoledì",
+ "wednesday_short": "Mer",
+ "thursday": "Giovedì",
+ "thursday_short": "Gio",
+ "friday": "Venerdì",
+ "friday_short": "Ven",
+ "saturday": "Sabato",
+ "saturday_short": "Sab",
+ "sunday": "Domenica",
+ "sunday_short": "Dom",
+ "programmed": "programmata",
+ "available": "disponibile",
+ "unavailable": "non disponibile",
+ "set_available": "attiva",
+ "set_unavailable": "disattiva",
+ "name": "nome",
+ "surname": "cognome",
+ "ssn": "codice fiscale",
+ "address": "indirizzo",
+ "zip_code": "CAP",
+ "cadastral_code": "codice catastale",
+ "phone_number": "numero di telefono",
+ "fax": "fax",
+ "email": "email",
+ "pec": "PEC",
+ "birthday": "data di nascita",
+ "birthplace": "luogo di nascita",
+ "personal_information": "anagrafica personale",
+ "contact_information": "recapiti",
+ "service_information": "informazioni di servizio",
+ "device_information": "informazioni sul dispositivo",
+ "course_date": "data corso",
+ "documents": "documenti",
+ "driving_license": "patente",
+ "driving_license_expiration_date": "scadenza patente",
+ "driving_license_number": "numero patente",
+ "driving_license_type": "tipologia patente",
+ "driving_license_scan": "scansione patente",
+ "upload_scan": "carica scansione",
+ "upload_medical_examination_certificate": "carica certificato visita medica",
+ "upload_training_course_doc": "carica Ordine del Giorno",
+ "clothings": "indumenti",
+ "size": "dimensione",
+ "suit_size": "taglia tuta",
+ "boot_size": "taglia scarponi",
+ "medical_examinations": "visite mediche",
+ "training_courses": "corsi di formazione",
+ "date": "data",
+ "expiration_date": "data di scadenza",
+ "certifier": "ente/certificatore",
+ "certificate_short": "cert.",
+ "banned": "bannato",
+ "hidden": "nascosto",
+ "driver": "autista",
+ "drivers": "autisti",
+ "call": "chiama",
+ "service": "intervento",
+ "services": "interventi",
+ "training": "esercitazione",
+ "trainings": "esercitazioni",
+ "user": "utente",
+ "users": "utenti",
+ "availability_minutes": "minuti di disponibilità",
+ "action": "azione",
+ "changed": "interessato",
+ "editor": "fatto da",
+ "datetime": "data e ora",
+ "start": "inizio",
+ "end": "fine",
+ "code": "codice",
+ "chief": "caposquadra",
+ "crew": "squadra",
+ "place": "luogo",
+ "province": "provincia",
+ "region": "regione",
+ "notes": "note",
+ "type": "tipologia",
+ "add": "aggiungi",
+ "update": "modifica",
+ "remove": "rimuovi",
+ "more details": "altri dettagli",
+ "search": "cerca",
+ "submit": "invia",
+ "reset": "reset",
+ "go_back": "Torna indietro",
+ "next": "successiva",
+ "previous": "precedente",
+ "last": "ultima",
+ "first": "prima",
+ "total_elements_with_filters": "# Elementi (selezione corrente)",
+ "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",
+ "edit": "modifica",
+ "never": "mai",
+ "optional": "opzionale",
+ "not_enough_permissions": "Non hai i permessi necessari per accedere a questa pagina.",
+ "open_services_stats": "Per visualizzare statistiche e mappa degli interventi, vai alla sezione \"Statistiche\".",
+ "error_title": "Errore",
+ "success_title": "Successo",
+ "select_date_range": "Seleziona un intervallo di date",
+ "remove_date_filters": "Rimuovi filtri",
+ "yes": "si",
+ "no": "no"
+}