resolve merge conflic

This commit is contained in:
ahmadafriadi 2026-03-10 14:16:53 +07:00
commit a83d90cea3
6 changed files with 445 additions and 95 deletions

View File

@ -0,0 +1,159 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Map\Inspeksi;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
class InspeksiController extends Controller{
public function index(Request $request): JsonResponse
{
$size = +$request->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);
}
}

View File

@ -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;
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Models\Map;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Inspeksi extends Model{
use SoftDeletes;
protected $table = 'tanaman_inspeksi';
protected $fillable = [
"plant_id",
"land_id",
"farmer_id",
"inspection_date",
"health_status",
"productivity_status",
"issue_type",
"issue_description",
"recommendation",
"notes",
"created_date",
"sync_status"
];
}

34
config/cors.php Normal file
View File

@ -0,0 +1,34 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
];

View File

@ -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: '<input type="checkbox" class="form-check-input">'
},
render: function() {
return '<input type="checkbox" class="dt-checkboxes form-check-input">';
render: function (data, type, full, meta) {
return '<span class="text-truncate d-flex align-items-center text-heading">' + (meta.row + 1) + '</span>';
}
},
{
targets: 2,
targets: 1,
render: function (data, type, full, meta) {
return '<span class="text-truncate d-flex align-items-center text-heading">' + full['kode'] + '</span>';
}
},
{
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 `
<div class="d-flex align-items-center">
<a href="javascript:;" class="btn btn-icon btn-text-secondary waves-effect waves-light rounded-pill delete-record"><i class="ti tabler-trash ti-md"></i></a>
<a href="javascript:;" class="btn btn-icon btn-text-secondary waves-effect waves-light rounded-pill"><i class="ti tabler-eye ti-md"></i></a>
<a href="javascript:;" class="btn btn-icon btn-text-secondary waves-effect waves-light rounded-pill view-edit-record" data-id="${full.id}"><i class="ti tabler-edit ti-md"></i></a>
<a href="javascript:;" class="btn btn-icon btn-text-secondary waves-effect waves-light rounded-pill delete-record" data-id="${full.id}"><i class="ti tabler-trash ti-md"></i></a>
<a href="javascript:;" class="btn btn-icon btn-text-secondary waves-effect waves-light rounded-pill view-record" data-id="${full.id}"><i class="ti tabler-eye ti-md"></i></a>
</div>`;
}
}
@ -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' }
});
}
});
}
});
});

View File

@ -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')