From d73a006ce00b94208b55553cdee62b79ff734a6e Mon Sep 17 00:00:00 2001 From: Daeng Deni Mardaeni Date: Sat, 10 Aug 2024 20:12:04 +0700 Subject: [PATCH] Feature #4 : Villages --- app/Exports/VillagesExport.php | 49 + app/Http/Controllers/DistrictsController.php | 4 + app/Http/Controllers/VillagesController.php | 161 + app/Http/Requests/VillageRequest.php | 42 + app/Models/Village.php | 17 + ...024_08_08_144309_create_villages_table.php | 38 + database/seeders/LocationDatabaseSeeder.php | 1 + database/seeders/VillageSeeder.php | 19 + database/seeders/villages.sql | 83467 ++++++++++++++++ module.json | 2 +- resources/assets/js/app.js | 99 +- resources/views/villages/create.blade.php | 161 + resources/views/villages/index.blade.php | 235 + routes/breadcrumbs.php | 16 + routes/web.php | 8 + 15 files changed, 84312 insertions(+), 7 deletions(-) create mode 100644 app/Exports/VillagesExport.php create mode 100644 app/Http/Controllers/VillagesController.php create mode 100644 app/Http/Requests/VillageRequest.php create mode 100644 app/Models/Village.php create mode 100644 database/migrations/2024_08_08_144309_create_villages_table.php create mode 100644 database/seeders/VillageSeeder.php create mode 100644 database/seeders/villages.sql create mode 100644 resources/views/villages/create.blade.php create mode 100644 resources/views/villages/index.blade.php diff --git a/app/Exports/VillagesExport.php b/app/Exports/VillagesExport.php new file mode 100644 index 0000000..fa9c26f --- /dev/null +++ b/app/Exports/VillagesExport.php @@ -0,0 +1,49 @@ +get(); + } + + public function map($row): array{ + return [ + $row->id, + $row->code, + $row->name, + $row->postal_code, + $row->district->name, + $row->district->city->name, + $row->district->city->province->name, + $row->created_at + ]; + } + public function headings(): array{ + return [ + 'ID', + 'Code', + 'Name', + 'Postal Code', + 'District', + 'City', + 'Province', + 'Created At' + ]; + } + + public function columnFormats(): array{ + return [ + 'A' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER, + 'D' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER, + 'H' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_DATETIME + ]; + } +} diff --git a/app/Http/Controllers/DistrictsController.php b/app/Http/Controllers/DistrictsController.php index e99533f..988b33d 100644 --- a/app/Http/Controllers/DistrictsController.php +++ b/app/Http/Controllers/DistrictsController.php @@ -132,4 +132,8 @@ class DistrictsController extends Controller public function export(Request $request){ return Excel::download(new DistrictsExport, 'districts.xlsx'); } + + public function getDistrictsByCityId($id){ + return response()->json(District::where('city_code', $id)->get()); + } } diff --git a/app/Http/Controllers/VillagesController.php b/app/Http/Controllers/VillagesController.php new file mode 100644 index 0000000..e55afde --- /dev/null +++ b/app/Http/Controllers/VillagesController.php @@ -0,0 +1,161 @@ +validated(); + + if ($validate) { + try { + $village = Village::create($validate); + return redirect() + ->route('locations.villages.index') + ->with('success', 'Village created successfully'); + } catch (Exception $e) { + return redirect()->back()->with('error', 'Failed to create village. ' . $e->getMessage()); + } + } + } + + public function create() + { + $provinces = Province::all(); + return view('location::villages.create', compact('provinces')); + } + + public function edit($id) + { + $village = Village::find($id); + $provinces = Province::all(); + $cities = City::where('province_code', $village->province_code)->get(); + $districts = District::where('city_code', $village->city_code)->get(); + return view('location::villages.create', compact('village', 'provinces', 'cities', 'districts')); + } + + public function update(VillageRequest $request, $id) + { + $validate = $request->validated(); + + if ($validate) { + try { + $village = Village::find($id); + $village->update($validate); + return redirect() + ->route('locations.villages.index') + ->with('success', 'Village updated successfully'); + } catch (Exception $e) { + return redirect()->back()->with('error', 'Failed to update village. ' . $e->getMessage()); + } + } + } + + public function destroy($id) + { + try { + Village::destroy($id); + echo json_encode(['message' => 'Village deleted successfully', 'success' => true]); + } catch (Exception $e) { + echo json_encode(['message' => 'Failed to delete Village', 'success' => false]); + } + } + + public function export(Request $request) + { + return Excel::download(new VillagesExport, 'villages.xlsx'); + } + + public function dataForDatatables(Request $request) + { + if (is_null($this->user) || !$this->user->can('provinces.view')) { + //abort(403, 'Sorry! You are not allowed to view users.'); + } + + // Retrieve data from the database + $query = Village::query(); + + // Apply search filter if provided + if ($request->has('search') && !empty($request->get('search'))) { + $search = $request->get('search'); + $search = explode('|', $search); + if(isset($search[0]) &&!empty($search[0])){ + $query->where('province_code',$search[0]); + } + if(isset($search[1]) &&!empty($search[1])){ + $query->where('city_code',$search[1]); + } + + if(isset($search[2]) &&!empty($search[2])){ + $query->where('district_code',$search[2]); + } + $query->where(function ($q) use ($search) { + $q->where('code', 'LIKE', "%$search[3]%"); + $q->orWhere('name', 'LIKE', "%$search[3]%"); + }); + } + + // Apply sorting if provided + if ($request->has('sortOrder') && !empty($request->get('sortOrder'))) { + $order = $request->get('sortOrder'); + $column = $request->get('sortField'); + $query->orderBy($column, $order); + } + + // Get the total count of records + $totalRecords = $query->count(); + + // Apply pagination if provided + if ($request->has('page') && $request->has('size')) { + $page = $request->get('page'); + $size = $request->get('size'); + $offset = ($page - 1) * $size; // Calculate the offset + + $query->skip($offset)->take($size); + } + + // Get the filtered count of records + $filteredRecords = $query->count(); + + // Get the data for the current page + $data = $query->with('district.city.province')->get(); + + // Calculate the page count + $pageCount = ceil($totalRecords / $request->get('size')); + + // Calculate the current page number + $currentPage = 0 + 1; + + // Return the response data as a JSON object + return response()->json([ + 'draw' => $request->get('draw'), + 'recordsTotal' => $totalRecords, + 'recordsFiltered' => $filteredRecords, + 'pageCount' => $pageCount, + 'page' => $currentPage, + 'totalCount' => $totalRecords, + 'data' => $data, + ]); + } + + } diff --git a/app/Http/Requests/VillageRequest.php b/app/Http/Requests/VillageRequest.php new file mode 100644 index 0000000..c3b33fe --- /dev/null +++ b/app/Http/Requests/VillageRequest.php @@ -0,0 +1,42 @@ + 'required|exists:provinces,code', + 'city_code' => 'required|exists:cities,code', + 'district_code' => 'required|exists:districts,code', + 'postal_code' => 'required|string|max:5', + 'alt_name' => 'nullable|string|max:255', + ]; + + if ($this->method() === 'PUT') { + $rules['code'] = 'required|string|max:6|unique:villages,code,' . $this->id; + $rules['name'] = 'required|string|max:2554|unique:villages,name,' . $this->id; + } else { + $rules['code'] = 'required|string|max:6|unique:villages,code'; + $rules['name'] = 'required|string|max:2554|unique:villages,name'; + } + + return $rules; + } + + /** + * Determine if the user is authorized to make this request. + */ + public function authorize() + : bool + { + return true; + } + } diff --git a/app/Models/Village.php b/app/Models/Village.php new file mode 100644 index 0000000..0415c5e --- /dev/null +++ b/app/Models/Village.php @@ -0,0 +1,17 @@ +belongsTo(District::class, 'district_code', 'code'); + } + +} diff --git a/database/migrations/2024_08_08_144309_create_villages_table.php b/database/migrations/2024_08_08_144309_create_villages_table.php new file mode 100644 index 0000000..52c6dcf --- /dev/null +++ b/database/migrations/2024_08_08_144309_create_villages_table.php @@ -0,0 +1,38 @@ +id(); + $table->string('province_code')->index(); + $table->string('city_code')->index(); + $table->string('district_code')->index(); + $table->string('code'); + $table->string('name'); + $table->string('alt_name')->nullable(); + $table->string('postal_code', 5)->nullable(); + $table->timestamps(); + $table->softDeletes(); + $table->uuid('created_by')->nullable(); + $table->uuid('updated_by')->nullable(); + $table->uuid('deleted_by')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('villages'); + } +}; diff --git a/database/seeders/LocationDatabaseSeeder.php b/database/seeders/LocationDatabaseSeeder.php index 7ac7528..3437d1f 100644 --- a/database/seeders/LocationDatabaseSeeder.php +++ b/database/seeders/LocationDatabaseSeeder.php @@ -16,6 +16,7 @@ class LocationDatabaseSeeder extends Seeder ProvinceSeeder::class, CitySeeder::class, DistrictSeeder::class, + VillageSeeder::class, ]); } } diff --git a/database/seeders/VillageSeeder.php b/database/seeders/VillageSeeder.php new file mode 100644 index 0000000..e6881ee --- /dev/null +++ b/database/seeders/VillageSeeder.php @@ -0,0 +1,19 @@ + response.json()) .then(data => { @@ -44,3 +45,89 @@ if (citySelect) { } }); } + +if (districtSelect) { + let tomdistrict = new TomSelect('#district_code', settings); + let tomcities = new TomSelect('#city_code',{ + ...settings, + onChange: function (value) { + console.log('City selected:', value); + // Destroy only if necessary (prevents unnecessary re-initialization) + if (tomdistrict) { + tomdistrict.destroy(); + } + + let url = 'locations/districts/city/' + value; + fetch(url) + .then(response => response.json()) + .then(data => { + const options = data.map(item => ({ + value: item.code, + text: item.name + })); + + tomdistrict = new TomSelect('#district_code', { + ...settings, // Spread existing settings (including createOnBlur: false) + options: options + }); + }) + .catch(error => { + console.error('Error fetching data:', error); + }); + } + }); + + let tomprovince = new TomSelect('#province_code', { + ...settings, // Spread existing settings + onChange: function (value) { + console.log('Province selected:', value); + // Destroy only if necessary (prevents unnecessary re-initialization) + if (tomcities) { + tomcities.destroy(); + } + + let url = 'locations/cities/province/' + value; + fetch(url) + .then(response => response.json()) + .then(data => { + const options = data.map(item => ({ + value: item.code, + text: item.name + })); + + tomcities = new TomSelect('#city_code', { + ...settings, // Spread existing settings (including createOnBlur: false) + options: options, + onChange: function (value) { + console.log('City selected:', value); + // Destroy only if necessary (prevents unnecessary re-initialization) + if (tomdistrict) { + tomdistrict.destroy(); + } + + let url = 'locations/districts/city/' + value; + fetch(url) + .then(response => response.json()) + .then(data => { + const options = data.map(item => ({ + value: item.code, + text: item.name + })); + + tomdistrict = new TomSelect('#district_code', { + ...settings, // Spread existing settings (including createOnBlur: false) + options: options + }); + }) + .catch(error => { + console.error('Error fetching data:', error); + }); + } + }); + }) + .catch(error => { + console.error('Error fetching data:', error); + }); + } + }); +} diff --git a/resources/views/villages/create.blade.php b/resources/views/villages/create.blade.php new file mode 100644 index 0000000..49f2587 --- /dev/null +++ b/resources/views/villages/create.blade.php @@ -0,0 +1,161 @@ +@extends('layouts.main') + +@section('breadcrumbs') + {{ Breadcrumbs::render(request()->route()->getName()) }} +@endsection + +@section('content') +
+ @if(isset($village->id)) +
+ + @method('PUT') + @else + + @endif + @csrf +
+
+

+ {{ isset($village->id) ? 'Edit' : 'Add' }} Village +

+
+ Back +
+
+
+
+ +
+ + @error('province_code') + {{ $message }} + @enderror +
+
+
+ +
+ + @error('city_code') + {{ $message }} + @enderror +
+
+
+ +
+ + @error('district_code') + {{ $message }} + @enderror +
+
+
+ +
+ + @error('code') + {{ $message }} + @enderror +
+
+
+ +
+ + @error('name') + {{ $message }} + @enderror +
+
+
+ +
+ + @error('alt_name') + {{ $message }} + @enderror +
+
+
+ +
+ + @error('postal_code') + {{ $message }} + @enderror +
+
+
+ +
+
+
+
+
+@endsection + +@push('scripts') +@endpush diff --git a/resources/views/villages/index.blade.php b/resources/views/villages/index.blade.php new file mode 100644 index 0000000..b126abf --- /dev/null +++ b/resources/views/villages/index.blade.php @@ -0,0 +1,235 @@ +@extends('layouts.main') + +@section('breadcrumbs') + {{ Breadcrumbs::render('locations.villages') }} +@endsection + +@push('styles') + +
+
+
+

+ List of Vilalges +

+
+
+ + + + + + + + + +
+ Export to Excel + Add District +
+
+
+
+
+ + + + + + + + + + + + + + +
+ + + Code + + + Village + + + Alternative Name + + + Postal Code + + + District + + + City + + + Province + + Action
+
+ +
+
+
+ +@endsection + +@push('scripts') + + + +@endpush + diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index 2606fe1..845fc80 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -57,3 +57,19 @@ $trail->parent('locations.districts'); $trail->push('Edit District'); }); + + //Villages + Breadcrumbs::for('locations.villages', function (BreadcrumbTrail $trail) { + $trail->parent('locations'); + $trail->push('Villages', route('locations.villages.index')); + }); + + Breadcrumbs::for('locations.villages.create', function (BreadcrumbTrail $trail) { + $trail->parent('locations.villages'); + $trail->push('Create Village', route('locations.villages.create')); + }); + + Breadcrumbs::for('locations.villages.edit', function (BreadcrumbTrail $trail) { + $trail->parent('locations.villages'); + $trail->push('Edit Village'); + }); diff --git a/routes/web.php b/routes/web.php index f46076f..df4624d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Route; use Modules\Location\Http\Controllers\DistrictsController; use Modules\Location\Http\Controllers\LocationController; use Modules\Location\Http\Controllers\ProvincesController; + use Modules\Location\Http\Controllers\VillagesController; /* |-------------------------------------------------------------------------- @@ -41,5 +42,12 @@ use Illuminate\Support\Facades\Route; Route::get('city/{city_code}', [DistrictsController::class, 'getDistrictsByCityId'])->name('city'); }); Route::resource('districts', DistrictsController::class); + + Route::name('villages.')->prefix('villages')->group(function () { + Route::get('restore/{id}', [VillagesController::class, 'restore'])->name('restore'); + Route::get('datatables', [VillagesController::class, 'dataForDatatables'])->name('datatables'); + Route::get('export', [VillagesController ::class, 'export'])->name('export'); + }); + Route::resource('villages', VillagesController::class); }); });