Add new service place selection procedure
This commit is contained in:
parent
12fdfb3058
commit
ee310a3155
|
@ -3,7 +3,7 @@
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use App\Models\Option;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
@ -94,13 +94,26 @@ class AuthController extends Controller
|
||||||
public function me(Request $request)
|
public function me(Request $request)
|
||||||
{
|
{
|
||||||
$impersonateManager = app('impersonate');
|
$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 [
|
return [
|
||||||
...$request->user()->toArray(),
|
...$request->user()->toArray(),
|
||||||
"permissions" => array_map(function($p) {
|
"permissions" => array_map(function($p) {
|
||||||
return $p["name"];
|
return $p["name"];
|
||||||
}, $request->user()->allPermissions()->toArray()),
|
}, $request->user()->allPermissions()->toArray()),
|
||||||
"impersonating_user" => $impersonateManager->isImpersonating(),
|
"impersonating_user" => $impersonateManager->isImpersonating(),
|
||||||
"impersonator_id" => $impersonateManager->getImpersonatorId()
|
"impersonator_id" => $impersonateManager->getImpersonatorId(),
|
||||||
|
"options" => $options
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,9 @@ class PlacesController extends Controller
|
||||||
User::where('id', $request->user()->id)->update(['last_access' => now()]);
|
User::where('id', $request->user()->id)->update(['last_access' => now()]);
|
||||||
|
|
||||||
return response()->json(
|
return response()->json(
|
||||||
Place::find($id)
|
Place::where('id', $id)
|
||||||
|
->with('municipality', 'municipality.province')
|
||||||
|
->firstOrFail()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,17 @@
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Place;
|
use App\Models\Place;
|
||||||
|
use App\Models\PlaceMunicipality;
|
||||||
|
use App\Models\PlaceProvince;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use App\Utils\Logger;
|
use App\Utils\Logger;
|
||||||
use App\Utils\DBTricks;
|
use App\Utils\DBTricks;
|
||||||
|
use App\Utils\Helpers;
|
||||||
|
|
||||||
class ServiceController extends Controller
|
class ServiceController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -18,39 +22,56 @@ class ServiceController extends Controller
|
||||||
*/
|
*/
|
||||||
public function index(Request $request)
|
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()]);
|
User::where('id', $request->user()->id)->update(['last_access' => now()]);
|
||||||
|
|
||||||
$query = Service::join('users', 'users.id', '=', 'chief_id')
|
$query = Service::join('users', 'users.id', '=', 'chief_id')
|
||||||
->join('services_types', 'services_types.id', '=', 'type_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('drivers:name,surname')
|
||||||
->with('crew:name,surname')
|
->with('crew:name,surname')
|
||||||
->with('place')
|
->with('place.municipality.province')
|
||||||
->orderBy('start', 'desc');
|
->orderBy('start', 'desc');
|
||||||
if($request->has('from')) {
|
if ($request->has('from')) {
|
||||||
try {
|
try {
|
||||||
$from = Carbon::parse($request->input('from'));
|
$from = Carbon::parse($request->input('from'));
|
||||||
$query->whereDate('start', '>=', $from->toDateString());
|
$query->whereDate('start', '>=', $from->toDateString());
|
||||||
} catch (\Carbon\Exceptions\InvalidFormatException $e) { }
|
} catch (\Carbon\Exceptions\InvalidFormatException $e) {
|
||||||
}
|
}
|
||||||
if($request->has('to')) {
|
}
|
||||||
|
if ($request->has('to')) {
|
||||||
try {
|
try {
|
||||||
$to = Carbon::parse($request->input('to'));
|
$to = Carbon::parse($request->input('to'));
|
||||||
$query->whereDate('start', '<=', $to->toDateString());
|
$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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get single Service
|
* Get single Service
|
||||||
*/
|
*/
|
||||||
public function show(Request $request, $id)
|
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()]);
|
User::where('id', $request->user()->id)->update(['last_access' => now()]);
|
||||||
|
|
||||||
return response()->json(
|
return response()->json(
|
||||||
|
@ -59,7 +80,7 @@ class ServiceController extends Controller
|
||||||
->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('drivers:name,surname')
|
||||||
->with('crew:name,surname')
|
->with('crew:name,surname')
|
||||||
->with('place')
|
->with('place.municipality.province')
|
||||||
->find($id)
|
->find($id)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -67,10 +88,10 @@ class ServiceController extends Controller
|
||||||
private function extractServiceUsers($service)
|
private function extractServiceUsers($service)
|
||||||
{
|
{
|
||||||
$usersList = [$service->chief_id];
|
$usersList = [$service->chief_id];
|
||||||
foreach($service->drivers as $driver) {
|
foreach ($service->drivers as $driver) {
|
||||||
$usersList[] = $driver->id;
|
$usersList[] = $driver->id;
|
||||||
}
|
}
|
||||||
foreach($service->crew as $crew) {
|
foreach ($service->crew as $crew) {
|
||||||
$usersList[] = $crew->id;
|
$usersList[] = $crew->id;
|
||||||
}
|
}
|
||||||
return array_unique($usersList);
|
return array_unique($usersList);
|
||||||
|
@ -83,14 +104,14 @@ class ServiceController extends Controller
|
||||||
{
|
{
|
||||||
$adding = !isset($request->id) || is_null($request->id);
|
$adding = !isset($request->id) || is_null($request->id);
|
||||||
|
|
||||||
if(!$adding && !$request->user()->hasPermission("services-update")) abort(401);
|
if (!$adding && !$request->user()->hasPermission("services-update")) abort(401);
|
||||||
if($adding && !$request->user()->hasPermission("services-create")) 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);
|
$usersToDecrement = $this->extractServiceUsers($service);
|
||||||
User::whereIn('id', $usersToDecrement)->decrement('services');
|
User::whereIn('id', $usersToDecrement)->decrement('services');
|
||||||
|
|
||||||
|
@ -99,18 +120,21 @@ class ServiceController extends Controller
|
||||||
$service->save();
|
$service->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$is_map_picker = Helpers::get_option('service_place_selection_use_map_picker', false);
|
||||||
|
|
||||||
|
if ($is_map_picker) {
|
||||||
//Find Place by lat lon
|
//Find Place by lat lon
|
||||||
$place = Place::where('lat', $request->lat)->where('lon', $request->lon)->first();
|
$place = Place::where('lat', $request->place->lat)->where('lon', $request->place->lon)->first();
|
||||||
if(!$place) {
|
if (!$place) {
|
||||||
$place = new Place();
|
$place = new Place();
|
||||||
$place->lat = $request->lat;
|
$place->lat = $request->place->lat;
|
||||||
$place->lon = $request->lon;
|
$place->lon = $request->place->lon;
|
||||||
|
|
||||||
$response = Http::withUrlParameters([
|
$response = Http::withUrlParameters([
|
||||||
'lat' => $request->lat,
|
'lat' => $request->place->lat,
|
||||||
'lon' => $request->lon,
|
'lon' => $request->place->lon,
|
||||||
])->get('https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}');
|
])->get('https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}');
|
||||||
if(!$response->ok()) abort(500);
|
if (!$response->ok()) abort(500);
|
||||||
|
|
||||||
$place->place_id = isset($response["place_id"]) ? $response["place_id"] : null;
|
$place->place_id = isset($response["place_id"]) ? $response["place_id"] : null;
|
||||||
$place->osm_id = isset($response["osm_id"]) ? $response["osm_id"] : null;
|
$place->osm_id = isset($response["osm_id"]) ? $response["osm_id"] : null;
|
||||||
|
@ -128,7 +152,79 @@ class ServiceController extends Controller
|
||||||
$place->village = isset($response["address"]["village"]) ? $response["address"]["village"] : null;
|
$place->village = isset($response["address"]["village"]) ? $response["address"]["village"] : null;
|
||||||
$place->suburb = isset($response["address"]["suburb"]) ? $response["address"]["suburb"] : null;
|
$place->suburb = isset($response["address"]["suburb"]) ? $response["address"]["suburb"] : null;
|
||||||
$place->city = isset($response["address"]["city"]) ? $response["address"]["city"] : null;
|
$place->city = isset($response["address"]["city"]) ? $response["address"]["city"] : null;
|
||||||
$place->municipality = isset($response["address"]["municipality"]) ? $response["address"]["municipality"] : null;
|
|
||||||
|
$place->save();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!$adding) {
|
||||||
|
//Delete old place
|
||||||
|
$place = $service->place;
|
||||||
|
$service->place()->dissociate();
|
||||||
|
$service->save();
|
||||||
|
$place->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
$place = new Place();
|
||||||
|
$place->name = $request->place["address"];
|
||||||
|
|
||||||
|
//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();
|
||||||
|
});
|
||||||
|
|
||||||
|
//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();
|
$place->save();
|
||||||
}
|
}
|
||||||
|
@ -137,8 +233,8 @@ class ServiceController extends Controller
|
||||||
$service->chief()->associate($request->chief);
|
$service->chief()->associate($request->chief);
|
||||||
$service->type()->associate($request->type);
|
$service->type()->associate($request->type);
|
||||||
$service->notes = $request->notes;
|
$service->notes = $request->notes;
|
||||||
$service->start = $request->start/1000;
|
$service->start = $request->start / 1000;
|
||||||
$service->end = $request->end/1000;
|
$service->end = $request->end / 1000;
|
||||||
$service->place()->associate($place);
|
$service->place()->associate($place);
|
||||||
$service->addedBy()->associate($request->user());
|
$service->addedBy()->associate($request->user());
|
||||||
$service->updatedBy()->associate($request->user());
|
$service->updatedBy()->associate($request->user());
|
||||||
|
@ -163,7 +259,7 @@ class ServiceController extends Controller
|
||||||
*/
|
*/
|
||||||
public function destroy(Request $request, $id)
|
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);
|
$service = Service::find($id);
|
||||||
$usersToDecrement = $this->extractServiceUsers($service);
|
$usersToDecrement = $this->extractServiceUsers($service);
|
||||||
User::whereIn('id', $usersToDecrement)->decrement('services');
|
User::whereIn('id', $usersToDecrement)->decrement('services');
|
||||||
|
|
|
@ -251,12 +251,4 @@ class UserController extends Controller
|
||||||
|
|
||||||
return response()->json($user);
|
return response()->json($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the specified resource from storage.
|
|
||||||
*/
|
|
||||||
public function destroy(User $user)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use App\Models\PlaceMunicipality;
|
||||||
|
|
||||||
class Place extends Model
|
class Place extends Model
|
||||||
{
|
{
|
||||||
|
@ -32,7 +34,13 @@ class Place extends Model
|
||||||
'state',
|
'state',
|
||||||
'village',
|
'village',
|
||||||
'suburb',
|
'suburb',
|
||||||
'city',
|
'city'
|
||||||
'municipality'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the municipality
|
||||||
|
*/
|
||||||
|
public function municipality(): BelongsTo {
|
||||||
|
return $this->belongsTo(PlaceMunicipality::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Utils;
|
||||||
|
|
||||||
|
use App\Models\Option;
|
||||||
|
|
||||||
|
class Helpers {
|
||||||
|
public static function get_option($key, $default = null) {
|
||||||
|
$option = Option::where('name', $key)->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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -11,14 +11,14 @@ return new class extends Migration
|
||||||
*/
|
*/
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('PlaceProvince', function (Blueprint $table) {
|
Schema::create('place_provinces', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('code', 2)->unique();
|
$table->string('code', 20)->unique();
|
||||||
$table->string('name', 100);
|
$table->string('name', 100);
|
||||||
$table->string('short_name', 2);
|
$table->string('short_name', 2);
|
||||||
$table->string('region', 25);
|
$table->string('region', 25);
|
||||||
});
|
});
|
||||||
Schema::create('PlaceMunicipality', function (Blueprint $table) {
|
Schema::create('place_municipalities', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('code', 6)->unique();
|
$table->string('code', 6)->unique();
|
||||||
$table->string('name', 200);
|
$table->string('name', 200);
|
||||||
|
@ -32,7 +32,7 @@ return new class extends Migration
|
||||||
$table->string('fax', 30)->nullable();
|
$table->string('fax', 30)->nullable();
|
||||||
$table->decimal('latitude', 10, 8)->nullable();
|
$table->decimal('latitude', 10, 8)->nullable();
|
||||||
$table->decimal('longitude', 11, 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
|
public function down(): void
|
||||||
{
|
{
|
||||||
Schema::dropIfExists('PlaceMunicipality');
|
Schema::dropIfExists('place_municipalities');
|
||||||
Schema::dropIfExists('PlaceProvince');
|
Schema::dropIfExists('place_provinces');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('places', function (Blueprint $table) {
|
||||||
|
$table->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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -14,7 +14,7 @@ class OptionsSeeder extends Seeder
|
||||||
{
|
{
|
||||||
$options = [
|
$options = [
|
||||||
[
|
[
|
||||||
'name' => 'service_place_selection_manual',
|
'name' => 'service_place_selection_use_map_picker',
|
||||||
'value' => true,
|
'value' => true,
|
||||||
'type' => 'boolean'
|
'type' => 'boolean'
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<div class="border border-2 p-2 pb-0">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text" id="region-label">{{ 'region'|translate|titlecase }}</span>
|
||||||
|
<input [(ngModel)]="selectedRegion" [typeahead]="regions" (typeaheadOnSelect)="onRegionSelected()"
|
||||||
|
class="form-control" aria-describedby="region-label">
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3" *ngIf="regionSelected">
|
||||||
|
<span class="input-group-text" id="province-label">{{ 'province'|translate|titlecase }}</span>
|
||||||
|
<input [(ngModel)]="selectedProvince" [typeahead]="provinces" typeaheadOptionField="nome" (typeaheadOnSelect)="onProvinceSelected($event)"
|
||||||
|
class="form-control" aria-describedby="province-label">
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3" *ngIf="provinceSelected">
|
||||||
|
<span class="input-group-text" id="municipality-label">{{ 'place_details.municipality'|translate|titlecase }}</span>
|
||||||
|
<input [(ngModel)]="selectedMunicipality" [typeahead]="municipalities" typeaheadOptionField="nome" (typeaheadOnSelect)="onMunicipalitySelected($event)"
|
||||||
|
class="form-control" aria-describedby="municipality-label">
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3" *ngIf="municipalitySelected">
|
||||||
|
<span class="input-group-text" id="address-label">{{ 'address'|translate|titlecase }}</span>
|
||||||
|
<input [(ngModel)]="selectedAddress" (change)="onAddressChanged()"
|
||||||
|
class="form-control" aria-describedby="address-label" type="text">
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -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<any>();
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 { }
|
|
@ -138,6 +138,7 @@
|
||||||
<td>
|
<td>
|
||||||
<ng-container *ngIf="row.place.name"><i>{{ row.place.name }}</i></ng-container><br>
|
<ng-container *ngIf="row.place.name"><i>{{ row.place.name }}</i></ng-container><br>
|
||||||
<ng-container *ngIf="row.place.village">{{ row.place.village }}</ng-container><br>
|
<ng-container *ngIf="row.place.village">{{ row.place.village }}</ng-container><br>
|
||||||
|
<ng-container *ngIf="row.place.municipality">{{ row.place.municipality.name }} {{ row.place.municipality.province.short_name }}</ng-container><br>
|
||||||
<a class="place_details_link cursor-pointer" (click)="openPlaceDetails(row.place.id)">{{ 'more details'|translate|ftitlecase }}</a>
|
<a class="place_details_link cursor-pointer" (click)="openPlaceDetails(row.place.id)">{{ 'more details'|translate|ftitlecase }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ row.notes }}</td>
|
<td>{{ row.notes }}</td>
|
||||||
|
|
|
@ -63,10 +63,15 @@
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div [class.is-invalid-div]="!isFieldValid('lat')" class="mb-2">
|
<div [class.is-invalid-div]="!isFieldValid('lat')" class="mb-2" *ngIf="usingMapSelector">
|
||||||
<label>{{ 'place'|translate|ftitlecase }}</label>
|
<label>{{ 'place'|translate|ftitlecase }}</label>
|
||||||
<map-picker *ngIf="addingService" (markerSet)="setPlace($event.lat, $event.lng)"></map-picker>
|
<map-picker *ngIf="addingService" (markerSet)="setPlaceMap($event.lat, $event.lng)"></map-picker>
|
||||||
<map-picker *ngIf="!addingService && loadedServiceLat !== ''" (markerSet)="setPlace($event.lat, $event.lng)" [selectLat]="loadedServiceLat" [selectLng]="loadedServiceLng"></map-picker>
|
<map-picker *ngIf="!addingService && loadedServiceLat !== ''" (markerSet)="setPlaceMap($event.lat, $event.lng)" [selectLat]="loadedServiceLat" [selectLng]="loadedServiceLng"></map-picker>
|
||||||
|
</div>
|
||||||
|
<div [class.is-invalid-div]="!isFieldValid('address')" class="mb-2" *ngIf="!usingMapSelector">
|
||||||
|
<label>{{ 'place'|translate|ftitlecase }}</label>
|
||||||
|
<place-picker *ngIf="addingService" (addrSel)="setPlace($event.province, $event.municipality, $event.address)"></place-picker>
|
||||||
|
<place-picker *ngIf="!addingService && loadedServiceLat !== ''" (addrSel)="setPlace($event.province, $event.municipality, $event.address)"></place-picker>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="notes">{{ 'notes'|translate|ftitlecase }}</label><br>
|
<label for="notes">{{ 'notes'|translate|ftitlecase }}</label><br>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { AbstractControl, UntypedFormBuilder, ValidationErrors, Validators } from '@angular/forms';
|
import { AbstractControl, UntypedFormBuilder, ValidationErrors, Validators } from '@angular/forms';
|
||||||
import { ApiClientService } from 'src/app/_services/api-client.service';
|
import { ApiClientService } from 'src/app/_services/api-client.service';
|
||||||
|
import { AuthService } from 'src/app/_services/auth.service';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
@ -22,11 +23,15 @@ export class EditServiceComponent implements OnInit {
|
||||||
crew: [],
|
crew: [],
|
||||||
lat: -1,
|
lat: -1,
|
||||||
lon: -1,
|
lon: -1,
|
||||||
|
provinceCode: '',
|
||||||
|
municipalityCode: '',
|
||||||
|
address: '',
|
||||||
notes: '',
|
notes: '',
|
||||||
type: ''
|
type: ''
|
||||||
};
|
};
|
||||||
loadedServiceLat = "";
|
loadedServiceLat = "";
|
||||||
loadedServiceLng = "";
|
loadedServiceLng = "";
|
||||||
|
usingMapSelector = true;
|
||||||
|
|
||||||
users: any[] = [];
|
users: any[] = [];
|
||||||
types: any[] = [];
|
types: any[] = [];
|
||||||
|
@ -44,8 +49,9 @@ export class EditServiceComponent implements OnInit {
|
||||||
get chief() { return this.serviceForm.get('chief'); }
|
get chief() { return this.serviceForm.get('chief'); }
|
||||||
get drivers() { return this.serviceForm.get('drivers'); }
|
get drivers() { return this.serviceForm.get('drivers'); }
|
||||||
get crew() { return this.serviceForm.get('crew'); }
|
get crew() { return this.serviceForm.get('crew'); }
|
||||||
get lat() { return this.serviceForm.get('lat'); }
|
get lat() { return this.serviceForm.get('place.lat'); }
|
||||||
get lon() { return this.serviceForm.get('lon'); }
|
get lon() { return this.serviceForm.get('place.lon'); }
|
||||||
|
get address() { return this.serviceForm.get('place.address'); }
|
||||||
get type() { return this.serviceForm.get('type'); }
|
get type() { return this.serviceForm.get('type'); }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
@ -56,14 +62,26 @@ export class EditServiceComponent implements OnInit {
|
||||||
chief: [this.loadedService.chief, [Validators.required]],
|
chief: [this.loadedService.chief, [Validators.required]],
|
||||||
drivers: [this.loadedService.drivers, []],
|
drivers: [this.loadedService.drivers, []],
|
||||||
crew: [this.loadedService.crew, [Validators.required]],
|
crew: [this.loadedService.crew, [Validators.required]],
|
||||||
lat: [this.loadedService.lat, [Validators.required, (control: AbstractControl): ValidationErrors | null => {
|
place: this.fb.group({
|
||||||
|
lat: [this.loadedService.lat, this.usingMapSelector ?
|
||||||
|
[Validators.required, (control: AbstractControl): ValidationErrors | null => {
|
||||||
const valid = control.value >= -90 && control.value <= 90;
|
const valid = control.value >= -90 && control.value <= 90;
|
||||||
return valid ? null : { 'invalidLatitude': { value: control.value } };
|
return valid ? null : { 'invalidLatitude': { value: control.value } };
|
||||||
}]],
|
}] : []
|
||||||
lon: [this.loadedService.lon, [Validators.required, (control: AbstractControl): ValidationErrors | null => {
|
],
|
||||||
|
lon: [this.loadedService.lon, this.usingMapSelector ?
|
||||||
|
[Validators.required, (control: AbstractControl): ValidationErrors | null => {
|
||||||
const valid = control.value >= -180 && control.value <= 180;
|
const valid = control.value >= -180 && control.value <= 180;
|
||||||
return valid ? null : { 'invalidLongitude': { value: control.value } };
|
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],
|
notes: [this.loadedService.notes],
|
||||||
type: [this.loadedService.type, [Validators.required, Validators.minLength(1)]]
|
type: [this.loadedService.type, [Validators.required, Validators.minLength(1)]]
|
||||||
});
|
});
|
||||||
|
@ -72,10 +90,12 @@ export class EditServiceComponent implements OnInit {
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private api: ApiClientService,
|
private api: ApiClientService,
|
||||||
|
public auth: AuthService,
|
||||||
private toastr: ToastrService,
|
private toastr: ToastrService,
|
||||||
private fb: UntypedFormBuilder,
|
private fb: UntypedFormBuilder,
|
||||||
private translate: TranslateService
|
private translate: TranslateService
|
||||||
) {
|
) {
|
||||||
|
this.usingMapSelector = this.auth.profile.getOption("service_place_selection_use_map_picker", true);
|
||||||
this.route.paramMap.subscribe(params => {
|
this.route.paramMap.subscribe(params => {
|
||||||
this.serviceId = params.get('id') || undefined;
|
this.serviceId = params.get('id') || undefined;
|
||||||
if (this.serviceId === "new") {
|
if (this.serviceId === "new") {
|
||||||
|
@ -182,15 +202,24 @@ export class EditServiceComponent implements OnInit {
|
||||||
return this.crew.value.find((x: number) => x == id);
|
return this.crew.value.find((x: number) => x == id);
|
||||||
}
|
}
|
||||||
|
|
||||||
setPlace(lat: number, lng: number) {
|
setPlaceMap(lat: number, lng: number) {
|
||||||
this.lat.setValue(lat);
|
this.lat.setValue(lat);
|
||||||
this.lon.setValue(lng);
|
this.lon.setValue(lng);
|
||||||
console.log("Place selected", lat, 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/
|
//https://loiane.com/2017/08/angular-reactive-forms-trigger-validation-on-submit/
|
||||||
isFieldValid(field: string) {
|
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() {
|
formSubmit() {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { CommonModule } from '@angular/common';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
|
import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
|
||||||
import { MapPickerModule } from '../../_components/map-picker/map-picker.module';
|
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 { DatetimePickerModule } from '../../_components/datetime-picker/datetime-picker.module';
|
||||||
import { BackBtnModule } from '../../_components/back-btn/back-btn.module';
|
import { BackBtnModule } from '../../_components/back-btn/back-btn.module';
|
||||||
import { TranslationModule } from '../../translation.module';
|
import { TranslationModule } from '../../translation.module';
|
||||||
|
@ -23,6 +24,7 @@ import { EditServiceComponent } from './edit-service.component';
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
BsDatepickerModule.forRoot(),
|
BsDatepickerModule.forRoot(),
|
||||||
MapPickerModule,
|
MapPickerModule,
|
||||||
|
PlacePickerModule,
|
||||||
DatetimePickerModule,
|
DatetimePickerModule,
|
||||||
BackBtnModule,
|
BackBtnModule,
|
||||||
TranslationModule,
|
TranslationModule,
|
||||||
|
|
|
@ -3,10 +3,11 @@
|
||||||
<div class="spinner spinner-border"></div>
|
<div class="spinner spinner-border"></div>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<div style="height: 300px;" leaflet [leafletOptions]="options" *ngIf="place_loaded">
|
<div style="height: 300px;" leaflet [leafletOptions]="options" *ngIf="place_loaded && place_info.lat && place_info.lon">
|
||||||
<div [leafletLayers]="layers"></div>
|
<div [leafletLayers]="layers"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="place_info" *ngIf="place_loaded">
|
|
||||||
|
<div class="place_info" *ngIf="place_loaded && place_info.lat && place_info.lon">
|
||||||
<h3>
|
<h3>
|
||||||
<a href="https://www.google.com/maps/@?api=1&map_action=map¢er={{ place_info.lat }},{{ place_info.lon }}&zoom=19&basemap=satellite" target="_blank">{{ 'place_details.open_in_google_maps'|translate }}</a>
|
<a href="https://www.google.com/maps/@?api=1&map_action=map¢er={{ place_info.lat }},{{ place_info.lon }}&zoom=19&basemap=satellite" target="_blank">{{ 'place_details.open_in_google_maps'|translate }}</a>
|
||||||
</h3>
|
</h3>
|
||||||
|
@ -26,9 +27,6 @@
|
||||||
<ng-container *ngIf="place_info.suburb">- {{ place_info.suburb }}</ng-container>
|
<ng-container *ngIf="place_info.suburb">- {{ place_info.suburb }}</ng-container>
|
||||||
</b> ({{ 'place_details.postcode'|translate }} <b>{{ place_info.postcode }}</b>)
|
</b> ({{ 'place_details.postcode'|translate }} <b>{{ place_info.postcode }}</b>)
|
||||||
</h4>
|
</h4>
|
||||||
<h4 *ngIf="place_info.municipality">
|
|
||||||
{{ 'place_details.municipality'|translate|ftitlecase }}: <b>{{ place_info.municipality }}</b>
|
|
||||||
</h4>
|
|
||||||
<h4 *ngIf="place_info.road">
|
<h4 *ngIf="place_info.road">
|
||||||
{{ 'place_details.road'|translate|ftitlecase }}: <b>{{ place_info.road }}</b>
|
{{ 'place_details.road'|translate|ftitlecase }}: <b>{{ place_info.road }}</b>
|
||||||
</h4>
|
</h4>
|
||||||
|
@ -36,3 +34,36 @@
|
||||||
{{ 'place_details.house_number'|translate|ftitlecase }}: <b>{{ place_info.house_number }}</b>
|
{{ 'place_details.house_number'|translate|ftitlecase }}: <b>{{ place_info.house_number }}</b>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="place_info" *ngIf="place_loaded && place_info.municipality">
|
||||||
|
<h3>
|
||||||
|
<a href="https://www.google.com/maps/search/?api=1&query={{ place_query }}&zoom=19&basemap=satellite" target="_blank">{{ 'place_details.open_in_google_maps'|translate }}</a>
|
||||||
|
</h3>
|
||||||
|
<br>
|
||||||
|
<h4>
|
||||||
|
{{ 'name'|translate|ftitlecase }}: <b>{{ place_info.name }} - {{ place_info.municipality.name }}</b><br>
|
||||||
|
{{ 'province'|translate|ftitlecase }}: <b>{{ place_info.municipality.province.name }} {{ place_info.municipality.province.short_name }}</b><br>
|
||||||
|
{{ 'region'|translate|ftitlecase }}: <b>{{ place_info.municipality.province.region }}</b>
|
||||||
|
</h4>
|
||||||
|
<br>
|
||||||
|
<h4>
|
||||||
|
{{ 'cadastral_code'|translate|ftitlecase }}: <b>{{ place_info.municipality.cadastral_code }}</b><br>
|
||||||
|
{{ 'zip_code'|translate|ftitlecase }}: <b>{{ place_info.municipality.postal_code }}</b><br>
|
||||||
|
{{ 'prefix'|translate|ftitlecase }}: <b>{{ place_info.municipality.prefix }}</b>
|
||||||
|
</h4>
|
||||||
|
<br>
|
||||||
|
<h4>
|
||||||
|
{{ 'email'|translate|ftitlecase }}: <a href="mailto:{{ place_info.municipality.email }}">
|
||||||
|
{{ place_info.municipality.email }}
|
||||||
|
</a><br>
|
||||||
|
{{ 'pec'|translate|ftitlecase }}: <a href="mailto:{{ place_info.municipality.pec }}">
|
||||||
|
{{ place_info.municipality.pec }}
|
||||||
|
</a><br>
|
||||||
|
{{ 'phone_number'|translate|ftitlecase }}: <a href="tel:{{ place_info.municipality.phone }}">
|
||||||
|
{{ place_info.municipality.phone }}
|
||||||
|
</a><br>
|
||||||
|
{{ 'fax'|translate|ftitlecase }}: <a href="tel:{{ place_info.municipality.fax }}">
|
||||||
|
{{ place_info.municipality.fax }}
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
|
@ -14,6 +14,7 @@ export class PlaceDetailsComponent implements OnInit {
|
||||||
id: number = 0;
|
id: number = 0;
|
||||||
lat: number = 0;
|
lat: number = 0;
|
||||||
lon: number = 0;
|
lon: number = 0;
|
||||||
|
place_query: string = '';
|
||||||
place_info: any = {};
|
place_info: any = {};
|
||||||
place_loaded = false;
|
place_loaded = false;
|
||||||
|
|
||||||
|
@ -37,6 +38,9 @@ export class PlaceDetailsComponent implements OnInit {
|
||||||
this.lon = parseFloat(place_info.lon || '');
|
this.lon = parseFloat(place_info.lon || '');
|
||||||
console.log(this.lat, this.lon);
|
console.log(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 = {
|
this.options = {
|
||||||
layers: [
|
layers: [
|
||||||
tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' })
|
tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' })
|
||||||
|
@ -63,6 +67,7 @@ export class PlaceDetailsComponent implements OnInit {
|
||||||
icon: iconDefault
|
icon: iconDefault
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
this.place_loaded = true;
|
this.place_loaded = true;
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
|
|
@ -21,6 +21,3 @@
|
||||||
|
|
||||||
<h3 class="mt-5 text-center">Interventi per paese</h3>
|
<h3 class="mt-5 text-center">Interventi per paese</h3>
|
||||||
<chart type="pie" [data]="chartServicesByVillageData"></chart>
|
<chart type="pie" [data]="chartServicesByVillageData"></chart>
|
||||||
|
|
||||||
<h3 class="mt-5 text-center">Interventi per area di competenza</h3>
|
|
||||||
<chart type="pie" [data]="chartServicesByMunicipalityData"></chart>
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ export class StatsServicesComponent implements OnInit {
|
||||||
chartServicesByDriverData: any;
|
chartServicesByDriverData: any;
|
||||||
chartServicesByTypeData: any;
|
chartServicesByTypeData: any;
|
||||||
chartServicesByVillageData: any;
|
chartServicesByVillageData: any;
|
||||||
chartServicesByMunicipalityData: any;
|
|
||||||
|
|
||||||
@ViewChild("servicesMap") servicesMap!: MapComponent;
|
@ViewChild("servicesMap") servicesMap!: MapComponent;
|
||||||
|
|
||||||
|
@ -109,14 +108,6 @@ export class StatsServicesComponent implements OnInit {
|
||||||
villages[service.place.village] = 0;
|
villages[service.place.village] = 0;
|
||||||
}
|
}
|
||||||
villages[service.place.village]++;
|
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);
|
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() {
|
loadServices() {
|
||||||
|
|
|
@ -17,7 +17,9 @@ export class AuthService {
|
||||||
private defaultPlaceholderProfile: any = {
|
private defaultPlaceholderProfile: any = {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
impersonating: false,
|
impersonating: false,
|
||||||
can: (permission: string) => false
|
options: {},
|
||||||
|
can: (permission: string) => false,
|
||||||
|
getOption: (option: string, defaultValue: string) => defaultValue,
|
||||||
};
|
};
|
||||||
public profile: any = this.defaultPlaceholderProfile;
|
public profile: any = this.defaultPlaceholderProfile;
|
||||||
public authChanged = new Subject<void>();
|
public authChanged = new Subject<void>();
|
||||||
|
@ -28,11 +30,24 @@ export class AuthService {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
this.api.post("me").then((data: any) => {
|
this.api.post("me").then((data: any) => {
|
||||||
this.profile = data;
|
this.profile = data;
|
||||||
|
this.profile.options = this.profile.options.reduce((acc: any, val: any) => {
|
||||||
|
acc[val.name] = val.value;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
this.profile.can = (permission: string) => {
|
this.profile.can = (permission: string) => {
|
||||||
return this.profile.permissions.includes(permission);
|
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;
|
this.profile.profilePageLink = "/users/" + this.profile.id;
|
||||||
|
|
||||||
Sentry.setUser({
|
Sentry.setUser({
|
||||||
|
@ -95,7 +110,7 @@ export class AuthService {
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
let error_message = "";
|
let error_message = "";
|
||||||
if(err.status === 401 || err.status === 422) {
|
if (err.status === 401 || err.status === 422) {
|
||||||
error_message = err.error.message;
|
error_message = err.error.message;
|
||||||
} else if (err.status === 400) {
|
} else if (err.status === 400) {
|
||||||
let error_messages = err.error.errors;
|
let error_messages = err.error.errors;
|
||||||
|
@ -113,7 +128,7 @@ export class AuthService {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
if(err.status = 500) {
|
if (err.status = 500) {
|
||||||
resolve({
|
resolve({
|
||||||
loginOk: false,
|
loginOk: false,
|
||||||
message: "Server error"
|
message: "Server error"
|
||||||
|
@ -128,7 +143,7 @@ export class AuthService {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public impersonate(user_id: number): Promise<void|string> {
|
public impersonate(user_id: number): Promise<void | string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.api.post(`impersonate/${user_id}`).then((data) => {
|
this.api.post(`impersonate/${user_id}`).then((data) => {
|
||||||
this.authToken.updateToken(data.access_token);
|
this.authToken.updateToken(data.access_token);
|
||||||
|
@ -167,7 +182,7 @@ export class AuthService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public logout(routerDestination?: string[] | undefined, forceLogout: boolean = false) {
|
public logout(routerDestination?: string[] | undefined, forceLogout: boolean = false) {
|
||||||
if(!forceLogout && this.profile.impersonating_user) {
|
if (!forceLogout && this.profile.impersonating_user) {
|
||||||
this.stop_impersonating().then(() => {
|
this.stop_impersonating().then(() => {
|
||||||
this.loadProfile();
|
this.loadProfile();
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
@ -176,7 +191,7 @@ export class AuthService {
|
||||||
} else {
|
} else {
|
||||||
this.api.post("logout").then((data: any) => {
|
this.api.post("logout").then((data: any) => {
|
||||||
this.profile = this.defaultPlaceholderProfile;
|
this.profile = this.defaultPlaceholderProfile;
|
||||||
if(routerDestination === undefined) {
|
if (routerDestination === undefined) {
|
||||||
routerDestination = ["login", "list"];
|
routerDestination = ["login", "list"];
|
||||||
}
|
}
|
||||||
this.authToken.clearToken();
|
this.authToken.clearToken();
|
||||||
|
|
|
@ -188,7 +188,7 @@
|
||||||
"password_min_length": "Password must be at least 6 characters long"
|
"password_min_length": "Password must be at least 6 characters long"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"service_place_selection_manual": "Manual place selection for services",
|
"service_place_selection_use_map_picker": "Use map to select service place",
|
||||||
"no_selection_available": "No selection available"
|
"no_selection_available": "No selection available"
|
||||||
},
|
},
|
||||||
"update_available": "Update available",
|
"update_available": "Update available",
|
||||||
|
@ -249,8 +249,11 @@
|
||||||
"ssn": "Social Security Number",
|
"ssn": "Social Security Number",
|
||||||
"address": "address",
|
"address": "address",
|
||||||
"zip_code": "zip code",
|
"zip_code": "zip code",
|
||||||
|
"cadastral_code": "cadastral code",
|
||||||
"phone_number": "phone number",
|
"phone_number": "phone number",
|
||||||
|
"fax": "fax",
|
||||||
"email": "email",
|
"email": "email",
|
||||||
|
"pec": "PEC",
|
||||||
"birthday": "birthday",
|
"birthday": "birthday",
|
||||||
"birthplace": "birthplace",
|
"birthplace": "birthplace",
|
||||||
"personal_information": "personal information",
|
"personal_information": "personal information",
|
||||||
|
@ -300,6 +303,7 @@
|
||||||
"crew": "crew",
|
"crew": "crew",
|
||||||
"place": "place",
|
"place": "place",
|
||||||
"province": "province",
|
"province": "province",
|
||||||
|
"region": "region",
|
||||||
"notes": "notes",
|
"notes": "notes",
|
||||||
"type": "type",
|
"type": "type",
|
||||||
"add": "add",
|
"add": "add",
|
||||||
|
|
|
@ -122,7 +122,7 @@
|
||||||
"village": "comune",
|
"village": "comune",
|
||||||
"postcode": "CAP",
|
"postcode": "CAP",
|
||||||
"hamlet": "frazione",
|
"hamlet": "frazione",
|
||||||
"municipality": "raggruppamento del comune",
|
"municipality": "comune",
|
||||||
"country": "nazione/zona",
|
"country": "nazione/zona",
|
||||||
"place_load_failed": "Errore durante il caricamento del luogo. Riprova più tardi"
|
"place_load_failed": "Errore durante il caricamento del luogo. Riprova più tardi"
|
||||||
},
|
},
|
||||||
|
@ -188,7 +188,7 @@
|
||||||
"password_min_length": "La password deve essere di almeno 6 caratteri"
|
"password_min_length": "La password deve essere di almeno 6 caratteri"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"service_place_selection_manual": "Seleziona manualmente il luogo dell'intervento",
|
"service_place_selection_use_map_picker": "Utilizza una mappa per selezionare il luogo dell'intervento",
|
||||||
"no_selection_available": "Nessuna selezione disponibile"
|
"no_selection_available": "Nessuna selezione disponibile"
|
||||||
},
|
},
|
||||||
"update_available": "Aggiornamento disponibile",
|
"update_available": "Aggiornamento disponibile",
|
||||||
|
@ -249,8 +249,11 @@
|
||||||
"ssn": "codice fiscale",
|
"ssn": "codice fiscale",
|
||||||
"address": "indirizzo",
|
"address": "indirizzo",
|
||||||
"zip_code": "CAP",
|
"zip_code": "CAP",
|
||||||
|
"cadastral_code": "codice catastale",
|
||||||
"phone_number": "numero di telefono",
|
"phone_number": "numero di telefono",
|
||||||
|
"fax": "fax",
|
||||||
"email": "email",
|
"email": "email",
|
||||||
|
"pec": "PEC",
|
||||||
"birthday": "data di nascita",
|
"birthday": "data di nascita",
|
||||||
"birthplace": "luogo di nascita",
|
"birthplace": "luogo di nascita",
|
||||||
"personal_information": "anagrafica personale",
|
"personal_information": "anagrafica personale",
|
||||||
|
@ -300,6 +303,7 @@
|
||||||
"crew": "squadra",
|
"crew": "squadra",
|
||||||
"place": "luogo",
|
"place": "luogo",
|
||||||
"province": "provincia",
|
"province": "provincia",
|
||||||
|
"region": "regione",
|
||||||
"notes": "note",
|
"notes": "note",
|
||||||
"type": "tipologia",
|
"type": "tipologia",
|
||||||
"add": "aggiungi",
|
"add": "aggiungi",
|
||||||
|
|
Loading…
Reference in New Issue