diff --git a/app/Http/Controllers/Api/InspeksiController.php b/app/Http/Controllers/Api/InspeksiController.php new file mode 100644 index 0000000..ee61fc5 --- /dev/null +++ b/app/Http/Controllers/Api/InspeksiController.php @@ -0,0 +1,159 @@ +get('size') ?: 10; + + $master = Inspeksi::query(); + + if ($request->has('search')) { + $s = $request->get('search'); + $s = strtolower($s); + $master->where(function($query) use ($s) { + $query->whereRaw('lower(kode) like (?)',["%{$s}%"]) + ->orWhereRaw('lower(nama) like (?)',["%{$s}%"]); + }); + } + if ($request->has('sort')) { + $order = $request->get('sort'); + $d = substr($order, 0, 1); + $dir = $d === '-' ? 'desc' : 'asc'; + $order = $d === '-' ? substr($order, 1) : $order; + $master->orderBy($order, $dir); + } + if ($request->has('kecamatan_id')) { + $master->where('kecamatan_id', $request->get('kecamatan_id')); + } + + $masterList = $master->paginate($size); + + return response()->json($masterList); + } + public function show(string $id) + { + Gate::authorize('petani'); + + $lahan = Inspeksi::findOrFail($id); + + return response()->json($lahan); + } + public function store(Request $request): JsonResponse + { + Gate::authorize('petani'); + + $user = Auth::user(); + $profile = $user->profile; + + $validated = $request->validate([ + 'plant_id' => ['required', 'numeric'], + 'land_id' => ['required', 'numeric'], + 'farmer_id' => ['numeric'], + 'health_status' => ['string'], + 'productivity_status' => ['string'], + ]); + // dd($validated); + $lahan = Inspeksi::create([ + "tanaman_id" => $validated["plant_id"], + "lahan_id" => $validated['land_id'], + "petani_id"=> $validated['farmer_id'], + "inspection_date"=> $request['inspection_date'], + "health_status"=> $request['health_status'], + "productivity_status"=> $validated['productivity_status'], + "issue_type"=> $request['issue_type'], + "issue_description"=> $request['issue_description'], + "recommendation"=> $request['recommendation'], + "notes"=> $request['notes'], + "created_date"=> $validated['created_date'] ?? null, + "sync_status"=> $validated['sync_status'] ?? 'synced' + ]); + + return response()->json($lahan, 201); + } + public function update(Request $request, string $id) + { + Gate::authorize('petani'); + + $lahan = Inspeksi::findOrFail($id); + + $validated = $request->validate([ + 'tanaman_id' => ['required', 'string', 'max:255'], + 'lahan_id' => ['required', 'string'], + 'petani_id' => ['numeric'], + 'health_status' => ['string'], + 'productivity_status' => ['numeric'], + ]); + + $lahan->update($validated); + + return response()->json($lahan); + } + public function destroy(string $id) + { + Gate::authorize('petani'); + + $lahan = Inspeksi::findOrFail($id); + $lahan->delete(); + + return response()->json(null, 204); + } + public function batchUpsert(Request $request): JsonResponse + { + Gate::authorize('petani'); + $user = Auth::user(); + $profile = $user->profile; + + $validated = $request->validate([ + 'tanaman_id' => ['required', 'string', 'max:255'], + 'lahan_id' => ['required', 'string'], + 'petani_id' => ['numeric'], + 'health_status' => ['string'], + 'productivity_status' => ['numeric'], + ]); + + $inspections = []; + foreach ($validated['inspections'] as $insoection) { + $inspection[] = [ + "plant_id" => $insoection["plant_id"], + "land_id" => $insoection['land_id'], + "farmer_id"=> $insoection['farmer_id'], + "inspection_date"=> $insoection['inspection_date'], + "health_status"=> $insoection['health_status'], + "productivity_status"=> $insoection['productivity_status'], + "issue_type"=> $insoection['issue_type'], + "issue_description"=> $insoection['issue_description'], + "recommendation"=> $insoection['recommendation'], + "notes"=> $insoection['notes'], + "created_date"=> $insoection['created_date'], + "sync_status"=> $insoection['sync_status'] + ]; + } + + Inspeksi::upsert($inspections, + ['id'], + [ + "plant_id", + "land_id", + "farmer_id", + "inspection_date", + "health_status", + "productivity_status", + "issue_type", + "issue_description", + "recommendation", + "notes", + "created_date", + "sync_status" + ]); + + return response()->json(null, 204); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 8677cd5..46cdcd4 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -2,7 +2,11 @@ namespace App\Http\Controllers; -abstract class Controller +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; +use Illuminate\Foundation\Validation\ValidatesRequests; +use Illuminate\Routing\Controller as BaseController; + +class Controller extends BaseController { - // -} + use AuthorizesRequests, ValidatesRequests; +} \ No newline at end of file diff --git a/app/Models/Map/Inspeksi.php b/app/Models/Map/Inspeksi.php new file mode 100644 index 0000000..15f1472 --- /dev/null +++ b/app/Models/Map/Inspeksi.php @@ -0,0 +1,28 @@ + ['api/*', 'sanctum/csrf-cookie'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 0, + + 'supports_credentials' => false, + +]; diff --git a/public/assets/js/master-provinsi.js b/public/assets/js/master-provinsi.js index 135207a..24880c7 100644 --- a/public/assets/js/master-provinsi.js +++ b/public/assets/js/master-provinsi.js @@ -1,10 +1,5 @@ -/** - * Page User List - */ - 'use strict'; -// Datatable (js) document.addEventListener('DOMContentLoaded', function (e) { let borderColor, bodyBg, headingColor; @@ -12,14 +7,7 @@ document.addEventListener('DOMContentLoaded', function (e) { bodyBg = config.colors.bodyBg; headingColor = config.colors.headingColor; - // Variable declaration for table - const dt_user_table = document.querySelector('.datatables-users'), - userView = 'app-user-view-account.html', - statusObj = { - 1: { title: 'Pending', class: 'bg-label-warning' }, - 2: { title: 'Active', class: 'bg-label-success' }, - 3: { title: 'Inactive', class: 'bg-label-secondary' } - }; + const dt_provinsi_table = document.querySelector('.datatables-provinsi'); var select2 = $('.select2'); if (select2.length) { @@ -30,11 +18,10 @@ document.addEventListener('DOMContentLoaded', function (e) { }); } - if (dt_user_table) { - const dt_user = new DataTable(dt_user_table, { + if (dt_provinsi_table) { + const dt_provinsi = new DataTable(dt_provinsi_table, { ajax: provinsiListUrl, columns: [ - { data: 'id' }, { data: 'id' }, { data: 'kode' }, { data: 'nama' }, @@ -43,29 +30,20 @@ document.addEventListener('DOMContentLoaded', function (e) { columnDefs: [ { targets: 0, - className: 'control', searchable: false, orderable: false, - render: function() { return ''; } - }, - { - targets: 1, - orderable: false, - checkboxes: { - selectAllRender: '' - }, - render: function() { - return ''; + render: function (data, type, full, meta) { + return '' + (meta.row + 1) + ''; } }, { - targets: 2, + targets: 1, render: function (data, type, full, meta) { return '' + full['kode'] + ''; } }, { - targets: 3, + targets: 2, render: function (data, type, full, meta) { var name = full['nama'], iso = full['iso']; var stateNum = Math.floor(Math.random() * 6); @@ -95,8 +73,9 @@ document.addEventListener('DOMContentLoaded', function (e) { render: function (data, type, full, meta) { return `
- - + + +
`; } } @@ -148,8 +127,6 @@ document.addEventListener('DOMContentLoaded', function (e) { }); } - // Filter form control to default size - // ? setTimeout used for user-list table initialization setTimeout(() => { const elementsToModify = [ { selector: '.dt-buttons .btn', classToRemove: 'btn-secondary' }, @@ -166,7 +143,6 @@ document.addEventListener('DOMContentLoaded', function (e) { { selector: '.dt-layout-full', classToRemove: 'col-md col-12', classToAdd: 'table-responsive' } ]; - // Delete record elementsToModify.forEach(({ selector, classToRemove, classToAdd }) => { document.querySelectorAll(selector).forEach(element => { if (classToRemove) { @@ -179,43 +155,35 @@ document.addEventListener('DOMContentLoaded', function (e) { }); }, 100); - // Validation & Phone mask - const phoneMaskList = document.querySelectorAll('.phone-mask'), - addNewUserForm = document.getElementById('addNewUserForm'); - - // Phone Number - if (phoneMaskList) { - phoneMaskList.forEach(function (phoneMask) { - phoneMask.addEventListener('input', event => { - const cleanValue = event.target.value.replace(/\D/g, ''); - phoneMask.value = formatGeneral(cleanValue, { - blocks: [3, 3, 4], - delimiters: [' ', ' '] - }); - }); - registerCursorTracker({ - input: phoneMask, - delimiter: ' ' - }); - }); - } - // Add New User Form Validation - const fv = FormValidation.formValidation(addNewUserForm, { + const fv = FormValidation.formValidation(addNewProvinsiForm, { fields: { - userFullname: { + kode: { validators: { notEmpty: { - message: 'Please enter fullname ' + message: 'Silakan masukkan kode provinsi' + }, + stringLength: { + min: 2, + max: 2, + message: 'Kode provinsi harus terdiri dari 2 karakter' } } }, - userEmail: { + nama: { validators: { notEmpty: { - message: 'Please enter your email' + message: 'Silakan masukkan nama provinsi' + } + } + }, + iso: { + validators: { + notEmpty: { + message: 'Silakan masukkan kode ISO provinsi' }, - emailAddress: { - message: 'The value is not a valid email address' + regexp: { + regexp: /^[A-Z]{2}-[A-Z]{2}$/, + message: 'The value is not a valid ISO code (e.g., ID-AC)' } } } @@ -223,44 +191,200 @@ document.addEventListener('DOMContentLoaded', function (e) { plugins: { trigger: new FormValidation.plugins.Trigger(), bootstrap5: new FormValidation.plugins.Bootstrap5({ - // Use this for enabling/changing valid/invalid class eleValidClass: '', - rowSelector: function (field, ele) { - // field is the field name & ele is the field element - return '.form-control-validation'; - } + rowSelector: '.form-control-validation' }), submitButton: new FormValidation.plugins.SubmitButton(), - // Submit the form when all fields are valid - // defaultSubmit: new FormValidation.plugins.DefaultSubmit(), autoFocus: new FormValidation.plugins.AutoFocus() - } + }, + }).on('core.form.valid', function () { + const form = document.getElementById('addNewProvinsiForm'); + const formData = new FormData(form); + const offcanvasAdd = bootstrap.Offcanvas.getInstance(document.querySelector('#offcanvasAddProvinsi')); + + $.ajax({ + url: form.action, + type: 'POST', + data: $(form).serialize(), + success: function (response) { + if (response.status === 'success') { + offcanvasAdd.hide(); + form.reset(); + Swal.fire({ + icon: 'success', + title: 'Berhasil!', + text: response.message, + showConfirmButton: false, + timer: 2000, + customClass: { confirmButton: 'btn btn-primary' } + }); + + $('.datatables-provinsi').DataTable().ajax.reload(); + } + }, + error: function (xhr) { + let msg = 'Terjadi kesalahan saat menyimpan data.'; + if (xhr.responseJSON && xhr.responseJSON.message) { + msg = xhr.responseJSON.message; + } + + Swal.fire({ + icon: 'error', + title: 'Gagal!', + text: msg, + customClass: { confirmButton: 'btn btn-primary' } + }); + } + }); }); }); -$('.datatables-users tbody').on('click', '.verify-user', function () { - const userId = $(this).data('id'); - const currentStatus = $(this).data('status'); // 1 = Active, 2 = Pending - const actionText = (currentStatus === 1) ? 'menonaktifkan (set Pending)' : 'mengaktifkan'; +$(function () { + var offcanvasElement = document.getElementById('offcanvasViewProvinsi'); + var offcanvasView = new bootstrap.Offcanvas(offcanvasElement); - if (confirm('Apakah Anda yakin ingin ' + actionText + ' user ini?')) { - $.ajax({ - url: '/users/' + userId + '/verify', - type: 'POST', - data: { - _token: $('meta[name="csrf-token"]').attr('content'), - _method: 'PATCH' - }, - success: function (response) { - // Reload tabel tanpa reset posisi pagination (false) - $('.datatables-users').DataTable().ajax.reload(null, false); - - // Opsional: Notifikasi kecil di pojok (jika pakai library toastr) - // toastr.success('Status berhasil diperbarui'); - }, - error: function (xhr) { - alert('Gagal mengubah status user.'); + $(document).on('click', '.view-record', function () { + var provinsiId = $(this).data('id'); + + var $btn = $(this); + $btn.addClass('disabled'); + + $.ajax({ + url: '/provinsi/' + provinsiId, + type: 'GET', + success: function (response) { + $btn.removeClass('disabled'); + + if (response.status === 'success') { + var data = response.data; + $('#view-provinsi-code').val(data.kode); + $('#view-provinsi-name').val(data.nama); + $('#view-provinsi-path').val(data.path); + $('#view-provinsi-iso').val(data.iso); + + offcanvasView.show(); + } + }, + error: function (xhr) { + $btn.removeClass('disabled'); + alert('Gagal mengambil data: ' + xhr.statusText); + } + }); + }); +}); + +$(function () { + const offcanvasEl = document.getElementById('offcanvasEditProvinsi'); + const offcanvasEdit = new bootstrap.Offcanvas(offcanvasEl); + + $(document).on('click', '.view-edit-record', function () { + const id = $(this).data('id'); + + $.get(`/provinsi/${id}`, function (response) { + if (response.status === 'success') { + const d = response.data; + $('#edit-provinsi-id').val(d.id); + $('#edit-provinsi-code').val(d.kode); + $('#edit-provinsi-name').val(d.nama); + $('#edit-provinsi-path').val(d.path); + $('#edit-provinsi-iso').val(d.iso); + + offcanvasEdit.show(); } }); - } + }); + + $('#editProvinsiForm').on('submit', function (e) { + e.preventDefault(); + + const id = $('#edit-provinsi-id').val(); + const formData = $(this).serialize(); + + $.ajax({ + url: `/provinsi/${id}`, + type: 'PUT', + data: formData, + headers: { + 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') + }, + success: function (response) { + if (response.status === 'success') { + offcanvasEdit.hide(); + + Swal.fire({ + icon: 'success', + title: 'Berhasil!', + text: response.message, + showConfirmButton: false, + timer: 1500 + }); + + $('.datatables-provinsi').DataTable().ajax.reload(); + } + }, + error: function (xhr) { + let errorMsg = 'Terjadi kesalahan sistem.'; + if (xhr.responseJSON && xhr.responseJSON.message) { + errorMsg = xhr.responseJSON.message; + } + + Swal.fire({ + icon: 'error', + title: 'Oops...', + text: errorMsg, + confirmButtonColor: '#7367f0' + }); + } + }); + }); +}); + +$(document).on('click', '.delete-record', function () { + const id = $(this).data('id'); + const name = $(this).closest('tr').find('.fw-medium').text(); + + Swal.fire({ + title: 'Apakah Anda yakin?', + text: `Provinsi "${name}" akan dihapus secara permanen!`, + icon: 'warning', + showCancelButton: true, + confirmButtonText: 'Ya, Hapus!', + cancelButtonText: 'Batal', + customClass: { + confirmButton: 'btn btn-primary me-3', + cancelButton: 'btn btn-label-secondary' + }, + buttonsStyling: false + }).then(function (result) { + if (result.value) { + $.ajax({ + url: `/provinsi/${id}`, + type: 'DELETE', + headers: { + 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') + }, + success: function (response) { + if (response.status === 'success') { + Swal.fire({ + icon: 'success', + title: 'Terhapus!', + text: response.message, + showConfirmButton: false, + timer: 1500 + }); + + $('.datatables-provinsi').DataTable().ajax.reload(); + } + }, + error: function (xhr) { + Swal.fire({ + icon: 'error', + title: 'Oops...', + text: 'Terjadi kesalahan saat menghapus data.', + customClass: { confirmButton: 'btn btn-primary' } + }); + } + }); + } + }); }); \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index c91bc39..6e74b61 100644 --- a/routes/api.php +++ b/routes/api.php @@ -29,6 +29,7 @@ Route::apiResource('/lahan', \App\Http\Controllers\Api\LahanController::class)->except(['create', 'edit']); Route::apiResource('/tanaman', \App\Http\Controllers\Api\TanamanController::class)->except(['create', 'edit']); + Route::apiResource('/inspeksi', \App\Http\Controllers\Api\InspeksiController::class)->except(['create', 'edit']); }); Route::middleware('auth:sanctum')