Update Map Tanaman & User
This commit is contained in:
@@ -10,6 +10,7 @@ use App\Enums\Role;
|
|||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class UserController extends Controller
|
class UserController extends Controller
|
||||||
{
|
{
|
||||||
@@ -70,6 +71,7 @@ class UserController extends Controller
|
|||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
$validator = Validator::make($request->all(), [
|
$validator = Validator::make($request->all(), [
|
||||||
|
'username' => ['required', 'string', 'max:255', 'unique:users,name'],
|
||||||
'name' => ['required', 'string', 'max:255'],
|
'name' => ['required', 'string', 'max:255'],
|
||||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
||||||
'password' => ['required', 'string', 'min:8', 'confirmed'],
|
'password' => ['required', 'string', 'min:8', 'confirmed'],
|
||||||
@@ -90,14 +92,17 @@ class UserController extends Controller
|
|||||||
|
|
||||||
$user = User::create([
|
$user = User::create([
|
||||||
'name' => $request->name,
|
'name' => $request->name,
|
||||||
|
'username' => $request->username,
|
||||||
'email' => $request->email,
|
'email' => $request->email,
|
||||||
'password' => Hash::make($request->password),
|
'password' => Hash::make($request->password),
|
||||||
'role' => $request->role,
|
'role' => $request->role,
|
||||||
|
'uid' => Str::random(10),
|
||||||
'created_by' => auth()->id(),
|
'created_by' => auth()->id(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Profile::create([
|
Profile::create([
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
|
'user_uid' => $user->uid,
|
||||||
'role' => $request->role,
|
'role' => $request->role,
|
||||||
'nama' => $request->name,
|
'nama' => $request->name,
|
||||||
'email' => $request->email,
|
'email' => $request->email,
|
||||||
|
|||||||
@@ -44,6 +44,16 @@ class VarietasController extends Controller
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getByJenisKomoditas($jenisKomoditasId)
|
||||||
|
{
|
||||||
|
$varietas = Varietas::where('jenis_komoditas_id', $jenisKomoditasId)->get();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $varietas,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
$komoditasTable = (new Komoditas)->getTable();
|
$komoditasTable = (new Komoditas)->getTable();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use App\Models\Pertanian\Adat;
|
|||||||
use App\Models\Wilayah\Provinsi;
|
use App\Models\Wilayah\Provinsi;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class LahanController extends Controller
|
class LahanController extends Controller
|
||||||
{
|
{
|
||||||
@@ -76,6 +77,25 @@ class LahanController extends Controller
|
|||||||
return view('produktivitas.lahan.index', compact('lahan', 'provinsi', 'profiles', 'adat', 'kabupaten', 'kecamatan', 'desa'));
|
return view('produktivitas.lahan.index', compact('lahan', 'provinsi', 'profiles', 'adat', 'kabupaten', 'kecamatan', 'desa'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getByProfile(Request $request, $profileId)
|
||||||
|
{
|
||||||
|
$desaKelurahanId = $request->get('desa_kelurahan_id');
|
||||||
|
|
||||||
|
$lahan = Lahan::with('profile')
|
||||||
|
->where('profile_id', $profileId)
|
||||||
|
->when($desaKelurahanId, function ($query) use ($desaKelurahanId) {
|
||||||
|
$query->where('desa_kelurahan_id', $desaKelurahanId);
|
||||||
|
})
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->get(['id', 'nama', 'path', 'profile_id', 'desa_kelurahan_id']);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $lahan,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
$provinsi = Provinsi::orderBy('nama', 'asc')->get();
|
$provinsi = Provinsi::orderBy('nama', 'asc')->get();
|
||||||
@@ -113,7 +133,11 @@ class LahanController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$userUID = Profile::where('id', $request->profile_id)->value('user_uid');
|
||||||
|
$userUID = substr($userUID, 2);
|
||||||
|
|
||||||
Lahan::create([
|
Lahan::create([
|
||||||
|
'uid' => 'LH' . $userUID . '-' . strtoupper(Str::random(10)),
|
||||||
'nama' => $request->nama,
|
'nama' => $request->nama,
|
||||||
'status_kepemilikan' => $request->status_kepemilikan,
|
'status_kepemilikan' => $request->status_kepemilikan,
|
||||||
'profile_id' => $request->profile_id,
|
'profile_id' => $request->profile_id,
|
||||||
|
|||||||
@@ -0,0 +1,421 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Web\Produktivitas;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Pertanian\Tanaman;
|
||||||
|
use App\Models\Profile;
|
||||||
|
use App\Models\Pertanian\Komoditas;
|
||||||
|
use App\Models\Master\JenisKomoditas;
|
||||||
|
use App\Models\Master\Varietas;
|
||||||
|
use App\Models\Pertanian\Lahan;
|
||||||
|
use App\Models\Wilayah\Provinsi;
|
||||||
|
use App\Models\Wilayah\KabupatenKota;
|
||||||
|
use App\Models\Wilayah\Kecamatan;
|
||||||
|
use App\Models\Wilayah\DesaKelurahan;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class MapTanamanController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$perPage = $request->get('per_page', 10);
|
||||||
|
$search = $request->get('search');
|
||||||
|
|
||||||
|
$tanaman = Tanaman::query()
|
||||||
|
->select([
|
||||||
|
DB::raw('MIN(id) as id'),
|
||||||
|
'profile_id',
|
||||||
|
'lahan_id',
|
||||||
|
'komoditas_id',
|
||||||
|
'jenis_komoditas_id',
|
||||||
|
'varietas_id',
|
||||||
|
DB::raw('COUNT(*) as total_tanaman'),
|
||||||
|
DB::raw("SUM(CASE WHEN kondisi_tanaman = 'sehat' THEN 1 ELSE 0 END) as total_sehat"),
|
||||||
|
DB::raw("SUM(CASE WHEN kondisi_tanaman = 'sakit' THEN 1 ELSE 0 END) as total_sakit"),
|
||||||
|
DB::raw("SUM(CASE WHEN kondisi_tanaman = 'mati' THEN 1 ELSE 0 END) as total_mati"),
|
||||||
|
DB::raw('MAX(created_at) as max_created_at'),
|
||||||
|
])
|
||||||
|
->with([
|
||||||
|
'profile',
|
||||||
|
'lahan.desaKelurahan.kecamatan.kabupatenkota.provinsi',
|
||||||
|
'komoditas',
|
||||||
|
'jenisKomoditas',
|
||||||
|
'varietas',
|
||||||
|
])
|
||||||
|
->when($search, function ($query) use ($search) {
|
||||||
|
$query->where(function ($q) use ($search) {
|
||||||
|
$q->whereHas('profile', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%"))
|
||||||
|
->orWhereHas('lahan', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%"))
|
||||||
|
->orWhereHas('komoditas', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%"))
|
||||||
|
->orWhereHas('jenisKomoditas', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%"))
|
||||||
|
->orWhereHas('varietas', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%"))
|
||||||
|
->orWhereHas('lahan.desaKelurahan', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%"))
|
||||||
|
->orWhereHas('lahan.desaKelurahan.kecamatan', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%"))
|
||||||
|
->orWhereHas('lahan.desaKelurahan.kecamatan.kabupatenkota', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%"))
|
||||||
|
->orWhereHas('lahan.desaKelurahan.kecamatan.kabupatenkota.provinsi', fn ($q) => $q->where('nama', 'ILIKE', "%{$search}%"));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->groupBy([
|
||||||
|
'profile_id',
|
||||||
|
'lahan_id',
|
||||||
|
'komoditas_id',
|
||||||
|
'jenis_komoditas_id',
|
||||||
|
'varietas_id',
|
||||||
|
])
|
||||||
|
->orderByDesc('max_created_at')
|
||||||
|
->paginate($perPage);
|
||||||
|
|
||||||
|
return view('produktivitas.mapTanaman.index', compact('tanaman'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTanamanGroupByRepresentativeId($id)
|
||||||
|
{
|
||||||
|
$representative = Tanaman::findOrFail($id);
|
||||||
|
|
||||||
|
$points = Tanaman::with([
|
||||||
|
'profile',
|
||||||
|
'lahan.desaKelurahan.kecamatan.kabupatenkota.provinsi',
|
||||||
|
'komoditas',
|
||||||
|
'jenisKomoditas',
|
||||||
|
'varietas',
|
||||||
|
])
|
||||||
|
->where('profile_id', $representative->profile_id)
|
||||||
|
->where('lahan_id', $representative->lahan_id)
|
||||||
|
->where('komoditas_id', $representative->komoditas_id)
|
||||||
|
->where('jenis_komoditas_id', $representative->jenis_komoditas_id)
|
||||||
|
->where('varietas_id', $representative->varietas_id)
|
||||||
|
->orderBy('id')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'representative' => $representative,
|
||||||
|
'points' => $points,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$provinsi = Provinsi::select('id', 'nama', 'path')
|
||||||
|
->orderBy('nama')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$komoditas = Komoditas::orderBy('nama')->get();
|
||||||
|
$profiles = Profile::orderBy('nama')->get();
|
||||||
|
|
||||||
|
return view('produktivitas.mapTanaman.add', compact(
|
||||||
|
'provinsi',
|
||||||
|
'komoditas',
|
||||||
|
'profiles'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$desaKelurahanTable = (new DesaKelurahan)->getTable();
|
||||||
|
$profileTable = (new Profile)->getTable();
|
||||||
|
$lahanTable = (new Lahan)->getTable();
|
||||||
|
$komoditasTable = (new Komoditas)->getTable();
|
||||||
|
$jenisKomoditasTable = (new JenisKomoditas)->getTable();
|
||||||
|
$varietasTable = (new Varietas)->getTable();
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'desa_kelurahan_id' => [
|
||||||
|
'required',
|
||||||
|
'integer',
|
||||||
|
Rule::exists($desaKelurahanTable, 'id'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'profile_id' => [
|
||||||
|
'required',
|
||||||
|
'integer',
|
||||||
|
Rule::exists($profileTable, 'id'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'lahan_id' => [
|
||||||
|
'required',
|
||||||
|
'integer',
|
||||||
|
Rule::exists($lahanTable, 'id')
|
||||||
|
->where('profile_id', $request->profile_id)
|
||||||
|
->where('desa_kelurahan_id', $request->desa_kelurahan_id),
|
||||||
|
],
|
||||||
|
|
||||||
|
'komoditas_id' => [
|
||||||
|
'required',
|
||||||
|
'integer',
|
||||||
|
Rule::exists($komoditasTable, 'id'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'jenis_komoditas_id' => [
|
||||||
|
'required',
|
||||||
|
'integer',
|
||||||
|
Rule::exists($jenisKomoditasTable, 'id')
|
||||||
|
->where('komoditas_id', $request->komoditas_id),
|
||||||
|
],
|
||||||
|
|
||||||
|
'varietas_id' => [
|
||||||
|
'required',
|
||||||
|
'integer',
|
||||||
|
Rule::exists($varietasTable, 'id')
|
||||||
|
->where('jenis_komoditas_id', $request->jenis_komoditas_id),
|
||||||
|
],
|
||||||
|
|
||||||
|
'points' => [
|
||||||
|
'required',
|
||||||
|
'array',
|
||||||
|
'min:1',
|
||||||
|
],
|
||||||
|
|
||||||
|
'points.*.latitude' => [
|
||||||
|
'required',
|
||||||
|
'numeric',
|
||||||
|
],
|
||||||
|
|
||||||
|
'points.*.longitude' => [
|
||||||
|
'required',
|
||||||
|
'numeric',
|
||||||
|
],
|
||||||
|
|
||||||
|
'points.*.kondisi_tanaman' => [
|
||||||
|
'required',
|
||||||
|
Rule::in(['sehat', 'sakit', 'mati']),
|
||||||
|
],
|
||||||
|
|
||||||
|
'metode_pengumpulan' => [
|
||||||
|
'nullable',
|
||||||
|
'string',
|
||||||
|
'max:50',
|
||||||
|
],
|
||||||
|
|
||||||
|
'deskripsi' => [
|
||||||
|
'nullable',
|
||||||
|
'string',
|
||||||
|
],
|
||||||
|
], [
|
||||||
|
'points.required' => 'Minimal tambahkan satu titik tanaman pada peta.',
|
||||||
|
'points.min' => 'Minimal tambahkan satu titik tanaman pada peta.',
|
||||||
|
'points.*.latitude.required' => 'Latitude titik tanaman wajib ada.',
|
||||||
|
'points.*.longitude.required' => 'Longitude titik tanaman wajib ada.',
|
||||||
|
'points.*.kondisi_tanaman.required' => 'Kondisi tanaman wajib dipilih.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
$lahan = Lahan::findOrFail($validated['lahan_id']);
|
||||||
|
$komoditas = Komoditas::findOrFail($validated['komoditas_id']);
|
||||||
|
$profile = Profile::findOrFail($validated['profile_id']);
|
||||||
|
|
||||||
|
if (!$lahan->uid) {
|
||||||
|
throw new \Exception('UID lahan tidak ditemukan.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$profileName = str_replace(' ', '', $profile->nama);
|
||||||
|
$lahanUidPart = substr($lahan->uid, 2);
|
||||||
|
|
||||||
|
$existingCount = Tanaman::where('lahan_id', $validated['lahan_id'])
|
||||||
|
->where('komoditas_id', $validated['komoditas_id'])
|
||||||
|
->where('jenis_komoditas_id', $validated['jenis_komoditas_id'])
|
||||||
|
->where('varietas_id', $validated['varietas_id'])
|
||||||
|
->count();
|
||||||
|
|
||||||
|
$createdTanaman = [];
|
||||||
|
|
||||||
|
foreach ($validated['points'] as $index => $point) {
|
||||||
|
$number = $existingCount + $index + 1;
|
||||||
|
|
||||||
|
$createdTanaman[] = Tanaman::create([
|
||||||
|
'uid' => 'TM' . $lahanUidPart . '-' . strtoupper(Str::random(10)),
|
||||||
|
'nama' => $komoditas->nama . ' - ' . $profileName . ' - ' . $number,
|
||||||
|
'desa_kelurahan_id' => $validated['desa_kelurahan_id'],
|
||||||
|
|
||||||
|
'profile_id' => $validated['profile_id'],
|
||||||
|
'lahan_id' => $validated['lahan_id'],
|
||||||
|
|
||||||
|
'komoditas_id' => $validated['komoditas_id'],
|
||||||
|
'jenis_komoditas_id' => $validated['jenis_komoditas_id'],
|
||||||
|
'varietas_id' => $validated['varietas_id'],
|
||||||
|
|
||||||
|
'latitude' => $point['latitude'],
|
||||||
|
'longitude' => $point['longitude'],
|
||||||
|
'kondisi_tanaman' => $point['kondisi_tanaman'],
|
||||||
|
'metode_pengumpulan' => $validated['metode_pengumpulan'] ?? 'manual',
|
||||||
|
'deskripsi' => $validated['deskripsi'] ?? null,
|
||||||
|
'created_by' => auth()->id(),
|
||||||
|
'updated_by' => auth()->id(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => count($createdTanaman) . ' data tanaman berhasil disimpan.',
|
||||||
|
'redirect' => route('map-tanaman.index'),
|
||||||
|
'data' => $createdTanaman,
|
||||||
|
]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
|
||||||
|
Log::error('Gagal menyimpan tanaman bulk', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'request' => $request->all(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Gagal menyimpan data: ' . $e->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(string $id)
|
||||||
|
{
|
||||||
|
$group = $this->getTanamanGroupByRepresentativeId($id);
|
||||||
|
|
||||||
|
$tanaman = $group['representative']->load([
|
||||||
|
'profile',
|
||||||
|
'lahan.desaKelurahan.kecamatan.kabupatenkota.provinsi',
|
||||||
|
'komoditas',
|
||||||
|
'jenisKomoditas',
|
||||||
|
'varietas',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$points = $group['points'];
|
||||||
|
|
||||||
|
return view('produktivitas.mapTanaman.view', compact(
|
||||||
|
'tanaman',
|
||||||
|
'points'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
public function edit(string $id)
|
||||||
|
{
|
||||||
|
$group = $this->getTanamanGroupByRepresentativeId($id);
|
||||||
|
|
||||||
|
$tanaman = $group['representative']->load([
|
||||||
|
'profile',
|
||||||
|
'lahan.desaKelurahan.kecamatan.kabupatenkota.provinsi',
|
||||||
|
'komoditas',
|
||||||
|
'jenisKomoditas',
|
||||||
|
'varietas',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$points = $group['points'];
|
||||||
|
|
||||||
|
return view('produktivitas.mapTanaman.edit', compact(
|
||||||
|
'tanaman',
|
||||||
|
'points'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, string $id)
|
||||||
|
{
|
||||||
|
$group = $this->getTanamanGroupByRepresentativeId($id);
|
||||||
|
|
||||||
|
$representative = $group['representative'];
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'points' => ['required', 'array', 'min:1'],
|
||||||
|
'points.*.id' => ['nullable', 'integer', 'exists:map_tanaman,id'],
|
||||||
|
'points.*.latitude' => ['required', 'numeric'],
|
||||||
|
'points.*.longitude' => ['required', 'numeric'],
|
||||||
|
'points.*.kondisi_tanaman' => [
|
||||||
|
'required',
|
||||||
|
Rule::in(['sehat', 'sakit', 'mati']),
|
||||||
|
],
|
||||||
|
'points.*._delete' => ['nullable'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
foreach ($validated['points'] as $point) {
|
||||||
|
if (!empty($point['_delete']) && !empty($point['id'])) {
|
||||||
|
$tanaman = Tanaman::where('id', $point['id'])
|
||||||
|
->where('profile_id', $representative->profile_id)
|
||||||
|
->where('lahan_id', $representative->lahan_id)
|
||||||
|
->where('komoditas_id', $representative->komoditas_id)
|
||||||
|
->where('jenis_komoditas_id', $representative->jenis_komoditas_id)
|
||||||
|
->where('varietas_id', $representative->varietas_id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($tanaman) {
|
||||||
|
$tanaman->deleted_by = auth()->id();
|
||||||
|
$tanaman->save();
|
||||||
|
$tanaman->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($point['id'])) {
|
||||||
|
$tanaman = Tanaman::where('id', $point['id'])
|
||||||
|
->where('profile_id', $representative->profile_id)
|
||||||
|
->where('lahan_id', $representative->lahan_id)
|
||||||
|
->where('komoditas_id', $representative->komoditas_id)
|
||||||
|
->where('jenis_komoditas_id', $representative->jenis_komoditas_id)
|
||||||
|
->where('varietas_id', $representative->varietas_id)
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
$tanaman->update([
|
||||||
|
'latitude' => $point['latitude'],
|
||||||
|
'longitude' => $point['longitude'],
|
||||||
|
'kondisi_tanaman' => $point['kondisi_tanaman'],
|
||||||
|
'updated_by' => auth()->id(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Data titik tanaman berhasil diperbarui.',
|
||||||
|
'redirect' => route('map-tanaman.index'),
|
||||||
|
]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Gagal memperbarui data: ' . $e->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(string $id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
$group = $this->getTanamanGroupByRepresentativeId($id);
|
||||||
|
$points = $group['points'];
|
||||||
|
|
||||||
|
foreach ($points as $point) {
|
||||||
|
$point->update([
|
||||||
|
'deleted_by' => auth()->id(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$point->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => $points->count() . ' titik tanaman berhasil dihapus.',
|
||||||
|
]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Gagal menghapus data: ' . $e->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,25 @@ class ProfileController extends Controller
|
|||||||
return view('master.profile.index', compact('profiles', 'provinsi'));
|
return view('master.profile.index', compact('profiles', 'provinsi'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getByDesaKelurahan($desaId)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$profiles = Profile::where('desa_kelurahan_id', $desaId)
|
||||||
|
->orderBy('nama', 'asc')
|
||||||
|
->get(['id', 'nama']);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $profiles,
|
||||||
|
]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
$provinsi = Provinsi::orderBy('nama', 'asc')->get();
|
$provinsi = Provinsi::orderBy('nama', 'asc')->get();
|
||||||
|
|||||||
@@ -25,4 +25,9 @@ class JenisKomoditas extends Model
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(Komoditas::class, 'komoditas_id');
|
return $this->belongsTo(Komoditas::class, 'komoditas_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function varietas()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Varietas::class, 'jenis_komoditas_id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ namespace App\Models\Pertanian;
|
|||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use App\Models\Master\JenisKomoditas;
|
||||||
|
use App\Models\Master\Varietas;
|
||||||
|
|
||||||
class Komoditas extends Model
|
class Komoditas extends Model
|
||||||
{
|
{
|
||||||
@@ -30,4 +32,14 @@ class Komoditas extends Model
|
|||||||
$builder->whereIn('id', app('SystemProgram')->komoditas()->pluck('id'));
|
$builder->whereIn('id', app('SystemProgram')->komoditas()->pluck('id'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function jenisKomoditas()
|
||||||
|
{
|
||||||
|
return $this->hasMany(JenisKomoditas::class, 'komoditas_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function varietas()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Varietas::class, 'komoditas_id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ namespace App\Models\Pertanian;
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use App\Models\Pertanian\Komoditas;
|
|
||||||
use App\Models\Pertanian\Lahan;
|
|
||||||
use App\Models\Profile;
|
use App\Models\Profile;
|
||||||
|
use App\Models\Pertanian\Lahan;
|
||||||
|
use App\Models\Pertanian\Komoditas;
|
||||||
|
use App\Models\Master\JenisKomoditas;
|
||||||
|
use App\Models\Master\Varietas;
|
||||||
|
|
||||||
class Tanaman extends Model
|
class Tanaman extends Model
|
||||||
{
|
{
|
||||||
@@ -15,13 +17,18 @@ class Tanaman extends Model
|
|||||||
protected $table = 'map_tanaman';
|
protected $table = 'map_tanaman';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
|
'uid',
|
||||||
'nama',
|
'nama',
|
||||||
|
'jenis_komoditas_id',
|
||||||
|
'varietas_id',
|
||||||
'komoditas_id',
|
'komoditas_id',
|
||||||
'profile_id',
|
'profile_id',
|
||||||
'lahan_id',
|
'lahan_id',
|
||||||
'latitude',
|
'latitude',
|
||||||
'longitude',
|
'longitude',
|
||||||
'kondisi_tanaman',
|
'kondisi_tanaman',
|
||||||
|
'metode_pengumpulan',
|
||||||
|
'deskripsi',
|
||||||
'created_by',
|
'created_by',
|
||||||
'updated_by',
|
'updated_by',
|
||||||
'deleted_by',
|
'deleted_by',
|
||||||
@@ -32,6 +39,16 @@ class Tanaman extends Model
|
|||||||
return $this->belongsTo(Komoditas::class, 'komoditas_id');
|
return $this->belongsTo(Komoditas::class, 'komoditas_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function jenisKomoditas()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(JenisKomoditas::class, 'jenis_komoditas_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function varietas()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Varietas::class, 'varietas_id');
|
||||||
|
}
|
||||||
|
|
||||||
public function profile()
|
public function profile()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Profile::class, 'profile_id');
|
return $this->belongsTo(Profile::class, 'profile_id');
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
<label class="form-label" for="add-user-fullname">Nama Lengkap</label>
|
<label class="form-label" for="add-user-fullname">Nama Lengkap</label>
|
||||||
<input type="text" class="form-control" id="add-user-fullname" placeholder="John Doe" name="name" required />
|
<input type="text" class="form-control" id="add-user-fullname" placeholder="John Doe" name="name" required />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label" for="add-user-username">Nama akun</label>
|
||||||
|
<input type="text" class="form-control" id="add-user-username" placeholder="john" name="username" required />
|
||||||
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="form-label" for="add-user-email">Email</label>
|
<label class="form-label" for="add-user-email">Email</label>
|
||||||
<input type="email" id="add-user-email" class="form-control" placeholder="john.doe@example.com" name="email" required />
|
<input type="email" id="add-user-email" class="form-control" placeholder="john.doe@example.com" name="email" required />
|
||||||
|
|||||||
@@ -116,7 +116,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<ul class="menu-sub">
|
<ul class="menu-sub">
|
||||||
<li class="menu-item">
|
<li class="menu-item">
|
||||||
<a href="{{ route('tanaman.index') }}" class="menu-link">
|
<a href="{{ route('map-tanaman.index') }}" class="menu-link">
|
||||||
<div data-i18n="Pemantauan Tanaman">Pemantauan Tanaman</div>
|
<div data-i18n="Pemantauan Tanaman">Pemantauan Tanaman</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -251,11 +251,28 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="menu-item">
|
<li class="menu-item">
|
||||||
<a href="{{ route('komoditas.index') }}" class="menu-link">
|
<a href="javascript:void(0);" class="menu-link menu-toggle">
|
||||||
<i class="menu-icon icon-base ti tabler-plant"></i>
|
<i class="menu-icon icon-base ti tabler-plant"></i>
|
||||||
|
<div data-i18n="Tanaman">Tanaman</div>
|
||||||
|
</a>
|
||||||
|
<ul class="menu-sub">
|
||||||
|
<li class="menu-item">
|
||||||
|
<a href="{{ route('komoditas.index') }}" class="menu-link">
|
||||||
<div data-i18n="Komoditas">Komoditas</div>
|
<div data-i18n="Komoditas">Komoditas</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="menu-item">
|
||||||
|
<a href="{{ route('jenis-komoditas.index') }}" class="menu-link">
|
||||||
|
<div data-i18n="Jenis Komoditas">Jenis Komoditas</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="menu-item">
|
||||||
|
<a href="{{ route('varietas.index') }}" class="menu-link">
|
||||||
|
<div data-i18n="Varietas">Varietas</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<!-- Konfigurasi -->
|
<!-- Konfigurasi -->
|
||||||
<li class="menu-header small">
|
<li class="menu-header small">
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,832 @@
|
|||||||
|
@extends('layout.main')
|
||||||
|
|
||||||
|
@push('style')
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#map {
|
||||||
|
height: 100%;
|
||||||
|
min-height: 620px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-wrapper {
|
||||||
|
min-height: 620px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.points-table-wrapper {
|
||||||
|
max-height: 320px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-popup-content {
|
||||||
|
color: #2f2b3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-marker-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(var(--bs-success-rgb), 0.95);
|
||||||
|
border: 2px solid #fff;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-marker-icon.sakit {
|
||||||
|
background: rgba(var(--bs-danger-rgb), 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-marker-icon.mati {
|
||||||
|
background: rgba(var(--bs-dark-rgb), 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-dot-marker {
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(var(--bs-success-rgb), 0.95);
|
||||||
|
border: 2px solid #fff;
|
||||||
|
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-dot-marker.sakit {
|
||||||
|
background: rgba(var(--bs-danger-rgb), 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-dot-marker.mati {
|
||||||
|
background: rgba(var(--bs-dark-rgb), 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-box {
|
||||||
|
padding: .75rem;
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
border-radius: .375rem;
|
||||||
|
background-color: rgba(var(--bs-primary-rgb), .04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
font-size: .75rem;
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
margin-bottom: .25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--bs-heading-color);
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-plant-popup .leaflet-popup-content-wrapper {
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--bs-card-bg);
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-plant-popup .leaflet-popup-content {
|
||||||
|
margin: 0;
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-plant-popup .leaflet-popup-tip {
|
||||||
|
background: var(--bs-card-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-card {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-header {
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: rgba(var(--bs-primary-rgb), 0.14);
|
||||||
|
border-bottom: 1px solid var(--bs-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-title {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--bs-heading-color);
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-subtitle {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-body {
|
||||||
|
padding: 10px 12px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-label {
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-value {
|
||||||
|
color: var(--bs-heading-color);
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-actions .btn {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-help {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
margin-top: 8px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
@php
|
||||||
|
$groupName = ($tanaman->komoditas->nama ?? 'Tanaman') . ' - ' . ($tanaman->profile->nama ?? 'Pemilik');
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="container-xxl flex-grow-1 container-p-y">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header border-bottom mb-4 d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<h5 class="card-title mb-0">Edit Kelompok Tanaman</h5>
|
||||||
|
<small class="text-muted">{{ $groupName }}</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="{{ route('map-tanaman.index') }}" class="btn btn-sm btn-label-secondary">
|
||||||
|
<i class="ti tabler-arrow-left me-1"></i> Kembali
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<form id="editTanamanForm" action="{{ route('map-tanaman.update', $tanaman->id) }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
@method('PUT')
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-5 mb-4">
|
||||||
|
|
||||||
|
<h6 class="mb-3 text-primary">
|
||||||
|
<i class="ti tabler-seeding me-1"></i> Informasi Kelompok
|
||||||
|
</h6>
|
||||||
|
|
||||||
|
<div class="alert alert-info mb-3">
|
||||||
|
<i class="ti tabler-info-circle me-1"></i>
|
||||||
|
Data lahan, pemilik, komoditas, jenis, dan varietas tidak diubah dari halaman ini. Perubahan hanya untuk titik tanaman.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3 mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="detail-box">
|
||||||
|
<div class="detail-label">Pemilik/Pengelola</div>
|
||||||
|
<div class="detail-value">{{ $tanaman->profile->nama ?? '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="detail-box">
|
||||||
|
<div class="detail-label">Lahan</div>
|
||||||
|
<div class="detail-value">{{ $tanaman->lahan->nama ?? '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="detail-box">
|
||||||
|
<div class="detail-label">Komoditas</div>
|
||||||
|
<div class="detail-value">{{ $tanaman->komoditas->nama ?? '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="detail-box">
|
||||||
|
<div class="detail-label">Jenis Komoditas</div>
|
||||||
|
<div class="detail-value">{{ $tanaman->jenisKomoditas->nama ?? '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="detail-box">
|
||||||
|
<div class="detail-label">Varietas</div>
|
||||||
|
<div class="detail-value">{{ $tanaman->varietas->nama ?? '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 form-control-validation">
|
||||||
|
<label class="form-label">Deskripsi</label>
|
||||||
|
<textarea
|
||||||
|
name="deskripsi"
|
||||||
|
class="form-control"
|
||||||
|
rows="3"
|
||||||
|
placeholder="Catatan tambahan">{{ old('deskripsi', $tanaman->deskripsi ?? '') }}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-4">
|
||||||
|
|
||||||
|
<h6 class="mb-3 text-primary">
|
||||||
|
<i class="ti tabler-current-location me-1"></i> Titik Koordinat Tanaman
|
||||||
|
</h6>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<div>
|
||||||
|
<label class="form-label text-success fw-bold mb-0">
|
||||||
|
Klik map untuk menambahkan titik baru
|
||||||
|
</label>
|
||||||
|
<small class="text-muted d-block">
|
||||||
|
Kondisi dan hapus titik bisa dilakukan dari tabel atau popup map.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-group" role="group" aria-label="Marker Mode">
|
||||||
|
<input type="radio" class="btn-check" name="marker_mode" id="marker-mode-plant" value="plant" checked>
|
||||||
|
<label class="btn btn-sm btn-outline-primary" for="marker-mode-plant">
|
||||||
|
<i class="ti tabler-plant-2 me-1"></i> Ikon
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input type="radio" class="btn-check" name="marker_mode" id="marker-mode-dot" value="dot">
|
||||||
|
<label class="btn btn-sm btn-outline-primary" for="marker-mode-dot">
|
||||||
|
<i class="ti tabler-point me-1"></i> Titik
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 form-control-validation">
|
||||||
|
<input type="hidden" id="points_validation" name="points_validation">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="points-table-wrapper border rounded">
|
||||||
|
<table class="table table-sm table-hover mb-0" id="points-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 50px;">No</th>
|
||||||
|
<th>Latitude</th>
|
||||||
|
<th>Longitude</th>
|
||||||
|
<th style="width: 150px;">Kondisi</th>
|
||||||
|
<th style="width: 60px;" class="text-center">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr id="empty-points-row">
|
||||||
|
<td colspan="5" class="text-center text-muted py-4">
|
||||||
|
Belum ada titik tanaman. Klik area pada peta.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="points-hidden-wrapper"></div>
|
||||||
|
|
||||||
|
<div class="mt-4 text-end">
|
||||||
|
<a href="{{ route('map-tanaman.index') }}" class="btn btn-label-secondary me-2">
|
||||||
|
Batal
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-primary data-submit">
|
||||||
|
Simpan Perubahan
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-7">
|
||||||
|
<div class="border rounded h-100 map-wrapper">
|
||||||
|
<div id="map"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
$.ajaxSetup({
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': "{{ csrf_token() }}"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const editForm = document.getElementById('editTanamanForm');
|
||||||
|
|
||||||
|
const lahanPath = {{ Illuminate\Support\Js::from($tanaman->lahan->path ?? null) }};
|
||||||
|
|
||||||
|
let plantPoints = {{ Illuminate\Support\Js::from(
|
||||||
|
$points->map(function ($point) {
|
||||||
|
$kondisi = $point->kondisi_tanaman;
|
||||||
|
$kondisiValue = is_object($kondisi) && property_exists($kondisi, 'value')
|
||||||
|
? $kondisi->value
|
||||||
|
: $kondisi;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $point->id,
|
||||||
|
'nama' => $point->nama,
|
||||||
|
'latitude' => $point->latitude,
|
||||||
|
'longitude' => $point->longitude,
|
||||||
|
'kondisi_tanaman' => $kondisiValue,
|
||||||
|
'_delete' => false,
|
||||||
|
'_new' => false,
|
||||||
|
];
|
||||||
|
})->values()
|
||||||
|
) }};
|
||||||
|
|
||||||
|
const map = L.map('map').setView([-2.5489, 118.0149], 5);
|
||||||
|
|
||||||
|
L.tileLayer('https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
|
||||||
|
maxZoom: 22,
|
||||||
|
subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
|
||||||
|
attribution: '© Google Maps Satellite'
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
let currentPolygon = null;
|
||||||
|
let plantMarkers = [];
|
||||||
|
|
||||||
|
function showError(xhr, fallback = 'Terjadi kesalahan sistem.') {
|
||||||
|
let message = fallback;
|
||||||
|
|
||||||
|
if (xhr.responseJSON?.message) {
|
||||||
|
message = xhr.responseJSON.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xhr.responseJSON?.errors) {
|
||||||
|
message = Object.values(xhr.responseJSON.errors).flat().join('<br>');
|
||||||
|
}
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal!',
|
||||||
|
html: message,
|
||||||
|
customClass: {
|
||||||
|
confirmButton: 'btn btn-primary'
|
||||||
|
},
|
||||||
|
buttonsStyling: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(value) {
|
||||||
|
return String(value ?? '')
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePath(path) {
|
||||||
|
if (!path) return null;
|
||||||
|
|
||||||
|
let parsed = path;
|
||||||
|
|
||||||
|
if (typeof path === 'string') {
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(path);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Path bukan JSON valid:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.type && parsed.coordinates) {
|
||||||
|
return {
|
||||||
|
type: 'geojson',
|
||||||
|
data: parsed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(parsed)) {
|
||||||
|
return {
|
||||||
|
type: 'array',
|
||||||
|
data: parsed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLahanPolygon(path) {
|
||||||
|
const parsed = parsePath(path);
|
||||||
|
|
||||||
|
if (!parsed) return null;
|
||||||
|
|
||||||
|
let layer = null;
|
||||||
|
|
||||||
|
if (parsed.type === 'geojson') {
|
||||||
|
layer = L.geoJSON(parsed.data, {
|
||||||
|
style: {
|
||||||
|
color: '#28c76f',
|
||||||
|
fillColor: '#28c76f',
|
||||||
|
fillOpacity: 0.35,
|
||||||
|
weight: 2
|
||||||
|
}
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
map.fitBounds(layer.getBounds(), { padding: [30, 30] });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.type === 'array') {
|
||||||
|
const latLngs = parsed.data.map(coord => [coord[1], coord[0]]);
|
||||||
|
|
||||||
|
layer = L.polygon(latLngs, {
|
||||||
|
color: '#28c76f',
|
||||||
|
fillColor: '#28c76f',
|
||||||
|
fillOpacity: 0.35,
|
||||||
|
weight: 2
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
map.fitBounds(layer.getBounds(), { padding: [30, 30] });
|
||||||
|
}
|
||||||
|
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMarkerMode() {
|
||||||
|
return $('input[name="marker_mode"]:checked').val() || 'plant';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlantIcon(point) {
|
||||||
|
const kondisi = point.kondisi_tanaman || 'sehat';
|
||||||
|
|
||||||
|
return L.divIcon({
|
||||||
|
className: '',
|
||||||
|
html: `
|
||||||
|
<div class="plant-marker-icon ${kondisi}">
|
||||||
|
<i class="ti tabler-plant-2"></i>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
iconSize: [32, 32],
|
||||||
|
iconAnchor: [16, 16],
|
||||||
|
popupAnchor: [0, -16]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDotIcon(point) {
|
||||||
|
const kondisi = point.kondisi_tanaman || 'sehat';
|
||||||
|
|
||||||
|
return L.divIcon({
|
||||||
|
className: '',
|
||||||
|
html: `<div class="plant-dot-marker ${kondisi}"></div>`,
|
||||||
|
iconSize: [11, 11],
|
||||||
|
iconAnchor: [5.5, 5.5],
|
||||||
|
popupAnchor: [0, -8]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatKondisiLabel(kondisi) {
|
||||||
|
const labels = {
|
||||||
|
sehat: 'Sehat',
|
||||||
|
sakit: 'Sakit',
|
||||||
|
mati: 'Mati'
|
||||||
|
};
|
||||||
|
|
||||||
|
return labels[kondisi] || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActivePoints() {
|
||||||
|
return plantPoints.filter(point => !point._delete);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPlantPopup(point, index, visibleIndex) {
|
||||||
|
const title = point.nama && point.nama !== 'null'
|
||||||
|
? point.nama
|
||||||
|
: `Titik Tanaman ${visibleIndex + 1}`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="plant-popup-card">
|
||||||
|
<div class="plant-popup-header">
|
||||||
|
<p class="plant-popup-title">${escapeHtml(title)}</p>
|
||||||
|
<div class="plant-popup-subtitle">Titik Tanaman ${visibleIndex + 1}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="plant-popup-body">
|
||||||
|
<div class="plant-popup-row">
|
||||||
|
<span class="plant-popup-label">Latitude</span>
|
||||||
|
<span class="plant-popup-value">${point.latitude}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="plant-popup-row">
|
||||||
|
<span class="plant-popup-label">Longitude</span>
|
||||||
|
<span class="plant-popup-value">${point.longitude}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label mb-1" style="font-size: .75rem;">Kondisi Tanaman</label>
|
||||||
|
<select class="form-select form-select-sm popup-point-condition" data-index="${index}">
|
||||||
|
<option value="sehat" ${point.kondisi_tanaman === 'sehat' ? 'selected' : ''}>Sehat</option>
|
||||||
|
<option value="sakit" ${point.kondisi_tanaman === 'sakit' ? 'selected' : ''}>Sakit</option>
|
||||||
|
<option value="mati" ${point.kondisi_tanaman === 'mati' ? 'selected' : ''}>Mati</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="plant-popup-actions">
|
||||||
|
<button type="button" class="btn btn-sm btn-label-danger popup-remove-point" data-index="${index}">
|
||||||
|
<i class="ti tabler-trash me-1"></i> Hapus Titik
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="plant-popup-help">
|
||||||
|
Perubahan akan tersimpan setelah klik <strong>Simpan Perubahan</strong>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPointsTable() {
|
||||||
|
const $tbody = $('#points-table tbody');
|
||||||
|
const $hiddenWrapper = $('#points-hidden-wrapper');
|
||||||
|
|
||||||
|
$tbody.empty();
|
||||||
|
$hiddenWrapper.empty();
|
||||||
|
|
||||||
|
const activePoints = getActivePoints();
|
||||||
|
|
||||||
|
if (!activePoints.length) {
|
||||||
|
$tbody.append(`
|
||||||
|
<tr id="empty-points-row">
|
||||||
|
<td colspan="5" class="text-center text-muted py-4">
|
||||||
|
Belum ada titik tanaman. Klik area pada peta.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
|
||||||
|
$('#points_validation').val('');
|
||||||
|
} else {
|
||||||
|
$('#points_validation').val('filled');
|
||||||
|
}
|
||||||
|
|
||||||
|
let visibleNumber = 1;
|
||||||
|
|
||||||
|
plantPoints.forEach((point, index) => {
|
||||||
|
const prefix = `points[${index}]`;
|
||||||
|
|
||||||
|
$hiddenWrapper.append(`
|
||||||
|
<input type="hidden" name="${prefix}[id]" value="${point.id || ''}">
|
||||||
|
<input type="hidden" name="${prefix}[latitude]" value="${point.latitude}">
|
||||||
|
<input type="hidden" name="${prefix}[longitude]" value="${point.longitude}">
|
||||||
|
<input type="hidden" name="${prefix}[kondisi_tanaman]" value="${point.kondisi_tanaman}">
|
||||||
|
<input type="hidden" name="${prefix}[_delete]" value="${point._delete ? 1 : 0}">
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (point._delete) return;
|
||||||
|
|
||||||
|
$tbody.append(`
|
||||||
|
<tr>
|
||||||
|
<td>${visibleNumber}</td>
|
||||||
|
<td>${point.latitude}</td>
|
||||||
|
<td>${point.longitude}</td>
|
||||||
|
<td>
|
||||||
|
<select class="form-select form-select-sm point-condition" data-index="${index}">
|
||||||
|
<option value="sehat" ${point.kondisi_tanaman === 'sehat' ? 'selected' : ''}>Sehat</option>
|
||||||
|
<option value="sakit" ${point.kondisi_tanaman === 'sakit' ? 'selected' : ''}>Sakit</option>
|
||||||
|
<option value="mati" ${point.kondisi_tanaman === 'mati' ? 'selected' : ''}>Mati</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button type="button" class="btn btn-sm btn-icon btn-label-danger remove-point" data-index="${index}">
|
||||||
|
<i class="ti tabler-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
|
||||||
|
visibleNumber++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshPlantMarkers() {
|
||||||
|
plantMarkers.forEach(marker => map.removeLayer(marker));
|
||||||
|
plantMarkers = [];
|
||||||
|
|
||||||
|
const mode = getMarkerMode();
|
||||||
|
let visibleIndex = 0;
|
||||||
|
|
||||||
|
plantPoints.forEach((point, index) => {
|
||||||
|
if (point._delete) return;
|
||||||
|
|
||||||
|
const icon = mode === 'dot'
|
||||||
|
? getDotIcon(point)
|
||||||
|
: getPlantIcon(point);
|
||||||
|
|
||||||
|
const marker = L.marker([point.latitude, point.longitude], {
|
||||||
|
icon: icon
|
||||||
|
}).addTo(map)
|
||||||
|
.bindPopup(buildPlantPopup(point, index, visibleIndex), {
|
||||||
|
className: 'custom-plant-popup',
|
||||||
|
closeButton: true,
|
||||||
|
maxWidth: 280
|
||||||
|
});
|
||||||
|
|
||||||
|
plantMarkers.push(marker);
|
||||||
|
visibleIndex++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshField(name) {
|
||||||
|
if (typeof fvEdit !== 'undefined' && name) {
|
||||||
|
fvEdit.revalidateField(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshAll() {
|
||||||
|
renderPointsTable();
|
||||||
|
refreshPlantMarkers();
|
||||||
|
refreshField('points_validation');
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPolygon = renderLahanPolygon(lahanPath);
|
||||||
|
|
||||||
|
renderPointsTable();
|
||||||
|
refreshPlantMarkers();
|
||||||
|
|
||||||
|
const activePoints = getActivePoints();
|
||||||
|
|
||||||
|
if (activePoints.length > 0) {
|
||||||
|
const bounds = L.latLngBounds(
|
||||||
|
activePoints
|
||||||
|
.filter(point => point.latitude && point.longitude)
|
||||||
|
.map(point => [point.latitude, point.longitude])
|
||||||
|
);
|
||||||
|
|
||||||
|
if (bounds.isValid()) {
|
||||||
|
map.fitBounds(bounds, { padding: [40, 40] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map.on('click', function (e) {
|
||||||
|
const lat = e.latlng.lat.toFixed(8);
|
||||||
|
const lng = e.latlng.lng.toFixed(8);
|
||||||
|
|
||||||
|
plantPoints.push({
|
||||||
|
id: '',
|
||||||
|
nama: 'Titik Baru',
|
||||||
|
latitude: lat,
|
||||||
|
longitude: lng,
|
||||||
|
kondisi_tanaman: 'sehat',
|
||||||
|
_delete: false,
|
||||||
|
_new: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
refreshAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('change', '.point-condition', function () {
|
||||||
|
const index = $(this).data('index');
|
||||||
|
const value = $(this).val();
|
||||||
|
|
||||||
|
if (plantPoints[index]) {
|
||||||
|
plantPoints[index].kondisi_tanaman = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('change', '.popup-point-condition', function () {
|
||||||
|
const index = $(this).data('index');
|
||||||
|
const value = $(this).val();
|
||||||
|
|
||||||
|
if (plantPoints[index]) {
|
||||||
|
plantPoints[index].kondisi_tanaman = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.closePopup();
|
||||||
|
refreshAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.remove-point', function () {
|
||||||
|
const index = $(this).data('index');
|
||||||
|
|
||||||
|
if (!plantPoints[index]) return;
|
||||||
|
|
||||||
|
if (plantPoints[index].id) {
|
||||||
|
plantPoints[index]._delete = true;
|
||||||
|
} else {
|
||||||
|
plantPoints.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.popup-remove-point', function () {
|
||||||
|
const index = $(this).data('index');
|
||||||
|
|
||||||
|
if (!plantPoints[index]) return;
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Hapus titik ini?',
|
||||||
|
text: 'Titik tanaman akan dihapus setelah kamu klik Simpan Perubahan.',
|
||||||
|
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.isConfirmed) return;
|
||||||
|
|
||||||
|
if (plantPoints[index].id) {
|
||||||
|
plantPoints[index]._delete = true;
|
||||||
|
} else {
|
||||||
|
plantPoints.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
map.closePopup();
|
||||||
|
refreshAll();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('change', 'input[name="marker_mode"]', function () {
|
||||||
|
refreshPlantMarkers();
|
||||||
|
});
|
||||||
|
|
||||||
|
let fvEdit = FormValidation.formValidation(editForm, {
|
||||||
|
fields: {
|
||||||
|
points_validation: {
|
||||||
|
validators: {
|
||||||
|
callback: {
|
||||||
|
message: 'Minimal harus ada satu titik tanaman aktif.',
|
||||||
|
callback: function () {
|
||||||
|
return getActivePoints().length > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
trigger: new FormValidation.plugins.Trigger(),
|
||||||
|
bootstrap5: new FormValidation.plugins.Bootstrap5({
|
||||||
|
eleValidClass: '',
|
||||||
|
rowSelector: '.form-control-validation'
|
||||||
|
}),
|
||||||
|
submitButton: new FormValidation.plugins.SubmitButton(),
|
||||||
|
}
|
||||||
|
}).on('core.form.valid', function () {
|
||||||
|
const formData = $(editForm).serialize();
|
||||||
|
const $button = $(editForm).find('button[type="submit"]');
|
||||||
|
|
||||||
|
$button.prop('disabled', true)
|
||||||
|
.html('<span class="spinner-border spinner-border-sm me-1"></span>Menyimpan...');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: editForm.action,
|
||||||
|
type: 'POST',
|
||||||
|
data: formData,
|
||||||
|
success: function (res) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil!',
|
||||||
|
text: res.message || 'Data titik tanaman berhasil diperbarui.',
|
||||||
|
timer: 1500,
|
||||||
|
showConfirmButton: false
|
||||||
|
}).then(() => {
|
||||||
|
window.location.href = res.redirect || "{{ route('map-tanaman.index') }}";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function (xhr) {
|
||||||
|
showError(xhr, 'Gagal memperbarui data tanaman.');
|
||||||
|
},
|
||||||
|
complete: function () {
|
||||||
|
$button.prop('disabled', false).html('Simpan Perubahan');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => map.invalidateSize(), 300);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
@extends('layout.main')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container-xxl flex-grow-1 container-p-y">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body border-bottom">
|
||||||
|
<form method="GET" action="{{ route('tanaman.index') }}" class="d-flex justify-content-between align-items-center row gap-3 gap-md-0">
|
||||||
|
<div class="col-md-auto">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<label class="me-2 text-nowrap">Show</label>
|
||||||
|
<select name="per_page" class="form-select form-select-sm w-auto" onchange="this.form.submit()">
|
||||||
|
@foreach([10, 25, 50, 100] as $limit)
|
||||||
|
<option value="{{ $limit }}" {{ request('per_page', 10) == $limit ? 'selected' : '' }}>
|
||||||
|
{{ $limit }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
<label class="ms-2 text-nowrap">entries</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-auto d-flex gap-2">
|
||||||
|
<div class="input-group input-group-sm w-auto">
|
||||||
|
<input type="text" name="search" class="form-control" placeholder="Cari tanaman..." value="{{ request('search') }}">
|
||||||
|
|
||||||
|
@if(request('search'))
|
||||||
|
<a href="{{ route('tanaman.index', ['per_page' => request('per_page', 10)]) }}" class="btn btn-outline-secondary">
|
||||||
|
<i class="ti tabler-x"></i>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-outline-primary">
|
||||||
|
<i class="ti tabler-search"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="{{ route('map-tanaman.create') }}" class="btn btn-sm btn-primary">
|
||||||
|
<i class="ti tabler-plus me-1"></i> Tambah
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive text-nowrap">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead class="border-top">
|
||||||
|
<tr>
|
||||||
|
<th>No</th>
|
||||||
|
<th>Kelompok Tanaman</th>
|
||||||
|
<th>Pemilik</th>
|
||||||
|
<th>Lahan</th>
|
||||||
|
<th>Wilayah</th>
|
||||||
|
<th>Varietas</th>
|
||||||
|
<th>Jumlah Titik</th>
|
||||||
|
<th>Kondisi</th>
|
||||||
|
<th class="text-center">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
@forelse($tanaman as $index => $item)
|
||||||
|
@php
|
||||||
|
$states = ['success', 'danger', 'warning', 'info', 'primary', 'secondary'];
|
||||||
|
$state = $states[$item->id % count($states)];
|
||||||
|
|
||||||
|
$groupName = ($item->komoditas->nama ?? 'Tanaman') . ' - ' . ($item->profile->nama ?? 'Pemilik');
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>{{ $tanaman->firstItem() + $index }}</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="avatar-wrapper me-3">
|
||||||
|
<div class="avatar avatar-sm">
|
||||||
|
<span class="avatar-initial rounded-circle bg-label-{{ $state }}">
|
||||||
|
{{ strtoupper(substr($item->komoditas->nama ?? 'TN', 0, 2)) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
<span class="fw-medium text-heading">{{ $groupName }}</span>
|
||||||
|
<small class="text-muted">
|
||||||
|
{{ $item->jenisKomoditas->nama ?? '-' }}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>{{ $item->profile->nama ?? '-' }}</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
<span>{{ $item->lahan->nama ?? '-' }}</span>
|
||||||
|
<small class="text-muted">{{ $item->lahan->uid ?? '-' }}</small>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<small>
|
||||||
|
{{ $item->lahan->desaKelurahan->nama ?? '-' }}<br>
|
||||||
|
<span class="text-muted">
|
||||||
|
{{ $item->lahan->desaKelurahan->kecamatan->nama ?? '-' }}
|
||||||
|
</span>
|
||||||
|
</small>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>{{ $item->varietas->nama ?? '-' }}</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-label-primary">
|
||||||
|
{{ $item->total_tanaman }} titik
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<div class="d-flex flex-column gap-1">
|
||||||
|
<span class="badge bg-label-success">
|
||||||
|
Sehat: {{ $item->total_sehat ?? 0 }}
|
||||||
|
</span>
|
||||||
|
<span class="badge bg-label-danger">
|
||||||
|
Sakit: {{ $item->total_sakit ?? 0 }}
|
||||||
|
</span>
|
||||||
|
<span class="badge bg-label-dark">
|
||||||
|
Mati: {{ $item->total_mati ?? 0 }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<div class="d-flex justify-content-center align-items-center">
|
||||||
|
<a href="{{ route('map-tanaman.show', $item->id) }}"
|
||||||
|
class="btn btn-icon btn-text-secondary rounded-pill"
|
||||||
|
title="Lihat">
|
||||||
|
<i class="ti tabler-eye ti-md"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="{{ route('map-tanaman.edit', $item->id) }}"
|
||||||
|
class="btn btn-icon btn-text-secondary rounded-pill"
|
||||||
|
title="Edit">
|
||||||
|
<i class="ti tabler-edit ti-md"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-icon btn-text-secondary rounded-pill delete-group"
|
||||||
|
data-id="{{ $item->id }}"
|
||||||
|
data-nama="{{ $groupName }}"
|
||||||
|
title="Hapus">
|
||||||
|
<i class="ti tabler-trash ti-md"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="9" class="text-center py-4">
|
||||||
|
Tidak ada data ditemukan.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($tanaman->hasPages())
|
||||||
|
<div class="card-footer d-flex justify-content-center justify-content-md-end border-top">
|
||||||
|
{{ $tanaman->appends(request()->query())->links('pagination::bootstrap-5') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
$.ajaxSetup({
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': "{{ csrf_token() }}"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.delete-group', function () {
|
||||||
|
const id = $(this).data('id');
|
||||||
|
const nama = $(this).data('nama');
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Apakah Anda yakin?',
|
||||||
|
text: `Semua titik tanaman pada kelompok "${nama}" akan dihapus.`,
|
||||||
|
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.isConfirmed) return;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: `/map-tanaman/${id}`,
|
||||||
|
type: 'DELETE',
|
||||||
|
success: function (res) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Terhapus!',
|
||||||
|
text: res.message || 'Data berhasil dihapus.',
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 1500
|
||||||
|
}).then(() => window.location.reload());
|
||||||
|
},
|
||||||
|
error: function (xhr) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal!',
|
||||||
|
text: xhr.responseJSON?.message || 'Gagal menghapus data.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
@@ -0,0 +1,698 @@
|
|||||||
|
@extends('layout.main')
|
||||||
|
|
||||||
|
@push('style')
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#map {
|
||||||
|
height: 560px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--bs-heading-color);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section {
|
||||||
|
padding-bottom: .75rem;
|
||||||
|
margin-bottom: .75rem;
|
||||||
|
border-bottom: 1px solid var(--bs-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-note {
|
||||||
|
background-color: rgba(var(--bs-info-rgb), 0.16);
|
||||||
|
color: var(--bs-info);
|
||||||
|
border: 1px solid rgba(var(--bs-info-rgb), 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.points-table-wrapper {
|
||||||
|
max-height: 280px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-popup-content {
|
||||||
|
color: #2f2b3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-marker-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(var(--bs-success-rgb), 0.95);
|
||||||
|
border: 2px solid #fff;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-marker-icon.sakit {
|
||||||
|
background: rgba(var(--bs-danger-rgb), 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-marker-icon.mati {
|
||||||
|
background: rgba(var(--bs-dark-rgb), 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-dot-marker {
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(var(--bs-success-rgb), 0.95);
|
||||||
|
border: 2px solid #fff;
|
||||||
|
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-dot-marker.sakit {
|
||||||
|
background: rgba(var(--bs-danger-rgb), 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-dot-marker.mati {
|
||||||
|
background: rgba(var(--bs-dark-rgb), 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-plant-popup .leaflet-popup-content-wrapper {
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--bs-card-bg);
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-plant-popup .leaflet-popup-content {
|
||||||
|
margin: 0;
|
||||||
|
min-width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-plant-popup .leaflet-popup-tip {
|
||||||
|
background: var(--bs-card-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-card {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-header {
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: rgba(var(--bs-primary-rgb), 0.14);
|
||||||
|
border-bottom: 1px solid var(--bs-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-title {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--bs-heading-color);
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-subtitle {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-body {
|
||||||
|
padding: 10px 12px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-row:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-label {
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-value {
|
||||||
|
color: var(--bs-heading-color);
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-badge.sehat {
|
||||||
|
background: rgba(var(--bs-success-rgb), 0.16);
|
||||||
|
color: var(--bs-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-badge.sakit {
|
||||||
|
background: rgba(var(--bs-danger-rgb), 0.16);
|
||||||
|
color: var(--bs-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-popup-badge.mati {
|
||||||
|
background: rgba(var(--bs-dark-rgb), 0.16);
|
||||||
|
color: var(--bs-dark);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
@php
|
||||||
|
$totalTanaman = $points->count();
|
||||||
|
|
||||||
|
$totalSehat = $points->filter(function ($point) {
|
||||||
|
$kondisi = $point->kondisi_tanaman;
|
||||||
|
$value = is_object($kondisi) && property_exists($kondisi, 'value') ? $kondisi->value : $kondisi;
|
||||||
|
return $value === 'sehat';
|
||||||
|
})->count();
|
||||||
|
|
||||||
|
$totalSakit = $points->filter(function ($point) {
|
||||||
|
$kondisi = $point->kondisi_tanaman;
|
||||||
|
$value = is_object($kondisi) && property_exists($kondisi, 'value') ? $kondisi->value : $kondisi;
|
||||||
|
return $value === 'sakit';
|
||||||
|
})->count();
|
||||||
|
|
||||||
|
$totalMati = $points->filter(function ($point) {
|
||||||
|
$kondisi = $point->kondisi_tanaman;
|
||||||
|
$value = is_object($kondisi) && property_exists($kondisi, 'value') ? $kondisi->value : $kondisi;
|
||||||
|
return $value === 'mati';
|
||||||
|
})->count();
|
||||||
|
|
||||||
|
$groupName = ($tanaman->komoditas->nama ?? 'Tanaman') . ' - ' . ($tanaman->profile->nama ?? 'Pemilik');
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="container-xxl flex-grow-1 container-p-y">
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<h4 class="mb-1">Detail Kelompok Tanaman</h4>
|
||||||
|
<small class="text-muted">{{ $groupName }}</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a href="{{ route('map-tanaman.index') }}" class="btn btn-label-secondary">
|
||||||
|
<i class="ti tabler-arrow-left me-1"></i> Kembali
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="{{ route('map-tanaman.edit', $tanaman->id) }}" class="btn btn-primary">
|
||||||
|
<i class="ti tabler-edit me-1"></i> Edit
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
{{-- DETAIL DATA --}}
|
||||||
|
<div class="col-lg-5 mb-4">
|
||||||
|
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header border-bottom">
|
||||||
|
<h5 class="card-title mb-0">
|
||||||
|
<i class="ti tabler-seeding me-1"></i> Ringkasan Kelompok
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body pt-4">
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-label">Kelompok Tanaman</div>
|
||||||
|
<div class="detail-value">{{ $groupName }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-label">Jumlah Titik Tanaman</div>
|
||||||
|
<div class="detail-value">
|
||||||
|
<span class="badge bg-label-primary">{{ $totalTanaman }} titik</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-label">Ringkasan Kondisi</div>
|
||||||
|
<div class="d-flex flex-wrap gap-2">
|
||||||
|
<span class="badge bg-label-success">Sehat: {{ $totalSehat }}</span>
|
||||||
|
<span class="badge bg-label-danger">Sakit: {{ $totalSakit }}</span>
|
||||||
|
<span class="badge bg-label-dark">Mati: {{ $totalMati }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-label">Deskripsi</div>
|
||||||
|
<div class="detail-value">{{ $tanaman->deskripsi ?? '-' }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-label">Metode Pengumpulan</div>
|
||||||
|
<div class="detail-value">{{ $tanaman->metode_pengumpulan ?? '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header border-bottom">
|
||||||
|
<h5 class="card-title mb-0">
|
||||||
|
<i class="ti tabler-user-pin me-1"></i> Pemilik & Lahan
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body pt-4">
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-label">Pemilik/Pengelola</div>
|
||||||
|
<div class="detail-value">{{ $tanaman->profile->nama ?? '-' }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-label">Lahan</div>
|
||||||
|
<div class="detail-value">{{ $tanaman->lahan->nama ?? '-' }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-label">UID Lahan</div>
|
||||||
|
<div class="detail-value">{{ $tanaman->lahan->uid ?? '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header border-bottom">
|
||||||
|
<h5 class="card-title mb-0">
|
||||||
|
<i class="ti tabler-map-pin me-1"></i> Wilayah
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body pt-4">
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-label">Provinsi</div>
|
||||||
|
<div class="detail-value">
|
||||||
|
{{ $tanaman->lahan->desaKelurahan->kecamatan->kabupatenkota->provinsi->nama ?? '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-label">Kabupaten/Kota</div>
|
||||||
|
<div class="detail-value">
|
||||||
|
{{ $tanaman->lahan->desaKelurahan->kecamatan->kabupatenkota->nama ?? '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-label">Kecamatan</div>
|
||||||
|
<div class="detail-value">
|
||||||
|
{{ $tanaman->lahan->desaKelurahan->kecamatan->nama ?? '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-label">Desa/Kelurahan</div>
|
||||||
|
<div class="detail-value">
|
||||||
|
{{ $tanaman->lahan->desaKelurahan->nama ?? '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header border-bottom">
|
||||||
|
<h5 class="card-title mb-0">
|
||||||
|
<i class="ti tabler-leaf me-1"></i> Komoditas
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body pt-4">
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-label">Komoditas</div>
|
||||||
|
<div class="detail-value">{{ $tanaman->komoditas->nama ?? '-' }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-label">Jenis Komoditas</div>
|
||||||
|
<div class="detail-value">{{ $tanaman->jenisKomoditas->nama ?? '-' }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-label">Varietas</div>
|
||||||
|
<div class="detail-value">{{ $tanaman->varietas->nama ?? '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- MAP --}}
|
||||||
|
<div class="col-lg-7 mb-4">
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header border-bottom d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="card-title mb-0">
|
||||||
|
<i class="ti tabler-map-pin me-1"></i> Lokasi Semua Titik Tanaman
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div class="btn-group" role="group" aria-label="Marker Mode">
|
||||||
|
<input type="radio" class="btn-check" name="marker_mode" id="marker-mode-plant" value="plant" checked>
|
||||||
|
<label class="btn btn-sm btn-outline-primary" for="marker-mode-plant">
|
||||||
|
<i class="ti tabler-plant-2 me-1"></i> Ikon
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input type="radio" class="btn-check" name="marker_mode" id="marker-mode-dot" value="dot">
|
||||||
|
<label class="btn btn-sm btn-outline-primary" for="marker-mode-dot">
|
||||||
|
<i class="ti tabler-point me-1"></i> Titik
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body pt-4">
|
||||||
|
<div id="map"></div>
|
||||||
|
|
||||||
|
<div class="alert map-note mt-3 mb-0">
|
||||||
|
<i class="ti tabler-info-circle me-1"></i>
|
||||||
|
Area hijau menunjukkan polygon lahan. Marker menunjukkan seluruh titik tanaman dalam kelompok ini.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header border-bottom">
|
||||||
|
<h5 class="card-title mb-0">
|
||||||
|
<i class="ti tabler-list-details me-1"></i> Daftar Titik Tanaman
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body pt-4">
|
||||||
|
<div class="points-table-wrapper border rounded">
|
||||||
|
<table class="table table-sm table-hover mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 50px;">No</th>
|
||||||
|
<th>Nama</th>
|
||||||
|
<th>Latitude</th>
|
||||||
|
<th>Longitude</th>
|
||||||
|
<th>Kondisi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($points as $index => $point)
|
||||||
|
@php
|
||||||
|
$kondisi = $point->kondisi_tanaman;
|
||||||
|
$kondisiValue = is_object($kondisi) && property_exists($kondisi, 'value')
|
||||||
|
? $kondisi->value
|
||||||
|
: $kondisi;
|
||||||
|
|
||||||
|
$conditionClass = match($kondisiValue) {
|
||||||
|
'sehat' => 'success',
|
||||||
|
'sakit' => 'danger',
|
||||||
|
'mati' => 'dark',
|
||||||
|
default => 'secondary',
|
||||||
|
};
|
||||||
|
@endphp
|
||||||
|
<tr>
|
||||||
|
<td>{{ $index + 1 }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="fw-medium">{{ $point->nama ?? '-' }}</div>
|
||||||
|
<small class="text-muted">{{ $point->uid ?? '-' }}</small>
|
||||||
|
</td>
|
||||||
|
<td>{{ $point->latitude ?? '-' }}</td>
|
||||||
|
<td>{{ $point->longitude ?? '-' }}</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-label-{{ $conditionClass }}">
|
||||||
|
{{ $kondisiValue ? ucfirst(str_replace('_', ' ', $kondisiValue)) : '-' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-center text-muted py-4">
|
||||||
|
Belum ada titik tanaman.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
const lahanPath = {{ Illuminate\Support\Js::from($tanaman->lahan->path ?? null) }};
|
||||||
|
|
||||||
|
const points = {{ Illuminate\Support\Js::from(
|
||||||
|
$points->map(function ($point) {
|
||||||
|
$kondisi = $point->kondisi_tanaman;
|
||||||
|
$kondisiValue = is_object($kondisi) && property_exists($kondisi, 'value')
|
||||||
|
? $kondisi->value
|
||||||
|
: $kondisi;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $point->id,
|
||||||
|
'uid' => $point->uid,
|
||||||
|
'nama' => $point->nama,
|
||||||
|
'latitude' => $point->latitude,
|
||||||
|
'longitude' => $point->longitude,
|
||||||
|
'kondisi_tanaman' => $kondisiValue,
|
||||||
|
];
|
||||||
|
})->values()
|
||||||
|
) }};
|
||||||
|
|
||||||
|
const map = L.map('map').setView([-2.5489, 118.0149], 5);
|
||||||
|
|
||||||
|
L.tileLayer('https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
|
||||||
|
maxZoom: 22,
|
||||||
|
subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
|
||||||
|
attribution: '© Google Maps Satellite'
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
let pointMarkers = [];
|
||||||
|
|
||||||
|
function parsePath(path) {
|
||||||
|
if (!path) return null;
|
||||||
|
|
||||||
|
let parsed = path;
|
||||||
|
|
||||||
|
if (typeof path === 'string') {
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(path);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Path bukan JSON valid:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.type && parsed.coordinates) {
|
||||||
|
return {
|
||||||
|
type: 'geojson',
|
||||||
|
data: parsed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(parsed)) {
|
||||||
|
return {
|
||||||
|
type: 'array',
|
||||||
|
data: parsed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLahanPolygon(path) {
|
||||||
|
const parsed = parsePath(path);
|
||||||
|
|
||||||
|
if (!parsed) {
|
||||||
|
console.warn('Format koordinat lahan tidak valid atau kosong.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let layer = null;
|
||||||
|
|
||||||
|
if (parsed.type === 'geojson') {
|
||||||
|
layer = L.geoJSON(parsed.data, {
|
||||||
|
style: {
|
||||||
|
color: '#28c76f',
|
||||||
|
fillColor: '#28c76f',
|
||||||
|
fillOpacity: 0.35,
|
||||||
|
weight: 2
|
||||||
|
}
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
map.fitBounds(layer.getBounds(), { padding: [30, 30] });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.type === 'array') {
|
||||||
|
const latLngs = parsed.data.map(coord => [coord[1], coord[0]]);
|
||||||
|
|
||||||
|
layer = L.polygon(latLngs, {
|
||||||
|
color: '#28c76f',
|
||||||
|
fillColor: '#28c76f',
|
||||||
|
fillOpacity: 0.35,
|
||||||
|
weight: 2
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
map.fitBounds(layer.getBounds(), { padding: [30, 30] });
|
||||||
|
}
|
||||||
|
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMarkerMode() {
|
||||||
|
return $('input[name="marker_mode"]:checked').val() || 'plant';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlantIcon(point) {
|
||||||
|
const kondisi = point.kondisi_tanaman || 'sehat';
|
||||||
|
|
||||||
|
return L.divIcon({
|
||||||
|
className: '',
|
||||||
|
html: `
|
||||||
|
<div class="plant-marker-icon ${kondisi}">
|
||||||
|
<i class="ti tabler-plant-2"></i>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
iconSize: [32, 32],
|
||||||
|
iconAnchor: [16, 16],
|
||||||
|
popupAnchor: [0, -16]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDotIcon(point) {
|
||||||
|
const kondisi = point.kondisi_tanaman || 'sehat';
|
||||||
|
|
||||||
|
return L.divIcon({
|
||||||
|
className: '',
|
||||||
|
html: `<div class="plant-dot-marker ${kondisi}"></div>`,
|
||||||
|
iconSize: [11, 11],
|
||||||
|
iconAnchor: [5.5, 5.5],
|
||||||
|
popupAnchor: [0, -8]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatKondisiLabel(kondisi) {
|
||||||
|
const labels = {
|
||||||
|
sehat: 'Sehat',
|
||||||
|
sakit: 'Sakit',
|
||||||
|
mati: 'Mati'
|
||||||
|
};
|
||||||
|
|
||||||
|
return labels[kondisi] || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPointMarkers() {
|
||||||
|
pointMarkers.forEach(marker => map.removeLayer(marker));
|
||||||
|
pointMarkers = [];
|
||||||
|
|
||||||
|
const mode = getMarkerMode();
|
||||||
|
|
||||||
|
points.forEach((point, index) => {
|
||||||
|
if (!point.latitude || !point.longitude) return;
|
||||||
|
|
||||||
|
const icon = mode === 'dot'
|
||||||
|
? getDotIcon(point)
|
||||||
|
: getPlantIcon(point);
|
||||||
|
|
||||||
|
const popupContent = `
|
||||||
|
<div class="plant-popup-card">
|
||||||
|
<div class="plant-popup-header">
|
||||||
|
<p class="plant-popup-title">
|
||||||
|
${point.nama || 'Titik Tanaman ' + (index + 1)}
|
||||||
|
</p>
|
||||||
|
<div class="plant-popup-subtitle">
|
||||||
|
Titik Tanaman ${index + 1}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="plant-popup-body">
|
||||||
|
<div class="plant-popup-row">
|
||||||
|
<span class="plant-popup-label">Latitude</span>
|
||||||
|
<span class="plant-popup-value">${point.latitude}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="plant-popup-row">
|
||||||
|
<span class="plant-popup-label">Longitude</span>
|
||||||
|
<span class="plant-popup-value">${point.longitude}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="plant-popup-row">
|
||||||
|
<span class="plant-popup-label">Kondisi</span>
|
||||||
|
<span class="plant-popup-value">
|
||||||
|
<span class="plant-popup-badge ${point.kondisi_tanaman || 'sehat'}">
|
||||||
|
${formatKondisiLabel(point.kondisi_tanaman)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const marker = L.marker([point.latitude, point.longitude], {
|
||||||
|
icon: icon
|
||||||
|
}).addTo(map)
|
||||||
|
.bindPopup(popupContent, {
|
||||||
|
className: 'custom-plant-popup',
|
||||||
|
closeButton: true,
|
||||||
|
maxWidth: 260
|
||||||
|
});
|
||||||
|
|
||||||
|
pointMarkers.push(marker);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const lahanLayer = renderLahanPolygon(lahanPath);
|
||||||
|
|
||||||
|
renderPointMarkers();
|
||||||
|
|
||||||
|
if (points.length > 0) {
|
||||||
|
const bounds = L.latLngBounds(
|
||||||
|
points
|
||||||
|
.filter(point => point.latitude && point.longitude)
|
||||||
|
.map(point => [point.latitude, point.longitude])
|
||||||
|
);
|
||||||
|
|
||||||
|
if (bounds.isValid()) {
|
||||||
|
map.fitBounds(bounds, { padding: [40, 40] });
|
||||||
|
}
|
||||||
|
} else if (lahanLayer) {
|
||||||
|
map.fitBounds(lahanLayer.getBounds(), { padding: [30, 30] });
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('change', 'input[name="marker_mode"]', function () {
|
||||||
|
renderPointMarkers();
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
map.invalidateSize();
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
@@ -79,8 +79,10 @@ Route::middleware('auth')
|
|||||||
Route::get('/profile/edit/{id}', [App\Http\Controllers\Web\Produktivitas\ProfileController::class, 'edit'])->name('profile.edit');
|
Route::get('/profile/edit/{id}', [App\Http\Controllers\Web\Produktivitas\ProfileController::class, 'edit'])->name('profile.edit');
|
||||||
Route::put('/profile/{id}', [App\Http\Controllers\Web\Produktivitas\ProfileController::class, 'update'])->name('profile.update');
|
Route::put('/profile/{id}', [App\Http\Controllers\Web\Produktivitas\ProfileController::class, 'update'])->name('profile.update');
|
||||||
Route::delete('/profile/{id}', [App\Http\Controllers\Web\Produktivitas\ProfileController::class, 'destroy'])->name('profile.destroy');
|
Route::delete('/profile/{id}', [App\Http\Controllers\Web\Produktivitas\ProfileController::class, 'destroy'])->name('profile.destroy');
|
||||||
|
Route::get('/profile-by-desa-kelurahan/{desaId}', [App\Http\Controllers\Web\Produktivitas\ProfileController::class, 'getByDesaKelurahan'])->name('profile.by-desa-kelurahan');
|
||||||
|
|
||||||
Route::resource('lahan', App\Http\Controllers\Web\Produktivitas\LahanController::class);
|
Route::resource('lahan', App\Http\Controllers\Web\Produktivitas\LahanController::class);
|
||||||
|
Route::get('/lahan-by-profile/{profileId}', [App\Http\Controllers\Web\Produktivitas\LahanController::class, 'getByProfile'])->name('lahan.by-profile');
|
||||||
|
|
||||||
Route::get('/komoditas', [App\Http\Controllers\Web\Master\KomoditasController::class, 'index'])->name('komoditas.index');
|
Route::get('/komoditas', [App\Http\Controllers\Web\Master\KomoditasController::class, 'index'])->name('komoditas.index');
|
||||||
Route::post('/komoditas', [App\Http\Controllers\Web\Master\KomoditasController::class, 'store'])->name('komoditas.store');
|
Route::post('/komoditas', [App\Http\Controllers\Web\Master\KomoditasController::class, 'store'])->name('komoditas.store');
|
||||||
@@ -91,6 +93,9 @@ Route::middleware('auth')
|
|||||||
Route::resource('jenis-komoditas', App\Http\Controllers\Web\Master\JenisKomoditasController::class);
|
Route::resource('jenis-komoditas', App\Http\Controllers\Web\Master\JenisKomoditasController::class);
|
||||||
Route::get('/jenis-komoditas-by-komoditas/{komoditas_id}', [App\Http\Controllers\Web\Master\JenisKomoditasController::class, 'getByKomoditas'])->name('jenis-komoditas.by-komoditas');
|
Route::get('/jenis-komoditas-by-komoditas/{komoditas_id}', [App\Http\Controllers\Web\Master\JenisKomoditasController::class, 'getByKomoditas'])->name('jenis-komoditas.by-komoditas');
|
||||||
Route::resource('varietas', App\Http\Controllers\Web\Master\VarietasController::class);
|
Route::resource('varietas', App\Http\Controllers\Web\Master\VarietasController::class);
|
||||||
|
Route::get('/varietas-by-jenis-komoditas/{jenis_komoditas_id}', [App\Http\Controllers\Web\Master\VarietasController::class, 'getByJenisKomoditas'])->name('varietas.by-jenis-komoditas');
|
||||||
|
|
||||||
|
Route::resource('map-tanaman', App\Http\Controllers\Web\Produktivitas\MapTanamanController::class);
|
||||||
|
|
||||||
Route::get('/keluarga', [App\Http\Controllers\Web\Master\KeluargaController::class, 'index'])->name('keluarga.index');
|
Route::get('/keluarga', [App\Http\Controllers\Web\Master\KeluargaController::class, 'index'])->name('keluarga.index');
|
||||||
Route::get('/keluarga/add', [App\Http\Controllers\Web\Master\KeluargaController::class, 'create'])->name('keluarga.create');
|
Route::get('/keluarga/add', [App\Http\Controllers\Web\Master\KeluargaController::class, 'create'])->name('keluarga.create');
|
||||||
|
|||||||
Reference in New Issue
Block a user