This commit is contained in:
2026-06-08 10:09:54 +07:00
160 changed files with 11667 additions and 1688 deletions
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App\Enums;
enum LayoutForm: string
{
case ROW = 'form-row';
case COLUMN = 'form-column';
case VALUE = 'form-value';
case SECTION = 'form-section';
case HR_LINE = 'form-hr-line';
public function label(): string
{
return match($this) {
self::ROW => 'Row',
self::COLUMN => 'Column',
self::VALUE => 'Value',
self::SECTION => 'Section',
self::HR_LINE => 'HR Line',
};
}
public static function options(): array
{
return collect(self::cases())->map(fn ($case) => [
'value' => $case->value,
'label' => $case->label(),
])->toArray();
}
}
@@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers\Api\Core;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use App\Models\Core\Program;
use App\Http\Resources\Core\ProgramResource;
class ProgramController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request): AnonymousResourceCollection
{
$perPage = $request->query('per_page', 10);
$search = $request->query('search', '');
$program = Program::where('nama', 'like', "%$search%")
->paginate($perPage);
return ProgramResource::collection($program);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(string $id): ProgramResource
{
$program = Program::findOrFail($id);
return new ProgramResource($program);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
//
}
}
@@ -0,0 +1,140 @@
<?php
namespace App\Http\Controllers\Api\Ekosistem;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\DB;
use App\Models\Ekosistem\TabelBasisData;
use App\Models\Ekosistem\KolomBasisData;
use App\Models\Core\Program;
class FormBasisDataController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request, string $tabelId): JsonResponse
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$tabel = TabelBasisData::with('kolom')
->whereHas('kolom', function ($query) {
$query->where('is_active', true);
})->find($tabelId);
if (!$tabel) {
abort(404, 'Data tabel tidak ditemukan.');
}
$kolom = KolomBasisData::where('tabel_id', $tabelId)
->where('is_active', true)->get();
if ($kolom->isEmpty()) {
abort(404, 'Data kolom tidak ditemukan untuk tabel ini.');
}
$program = null;
if (app()->has('SystemProgram')) {
$program = app('SystemProgram');
} else {
if ($request->has('program_id') && $request->query('program_id')) {
$programId = $request->query('program_id');
$program = Program::find($programId);
}
}
if (!$program) {
abort(400, 'Program tidak ditemukan.');
}
$method = $request->query('method', 'add');
$list_relasi = null;
if ($method == 'add' || $method == 'edit') {
$list_relasi = [];
foreach ($kolom as $k) {
if ($k->tipe == 'foreign_key') {
$relasi['kolom_id'] = $k->id;
try {
$options_kolom = DB::table($k->relasi_tabel)->selectRaw($k->relasi_kolom_label . ' as label, ' . $k->relasi_kolom_id . ' as value')->whereNull('deleted_at');
$relasi['options'] = $options_kolom->get();
$relasi['sql'] = $options_kolom->toSql();
} catch (\Throwable $th) {
$relasi['options'] = [];
}
$list_relasi[] = $relasi;
}
}
$list_relasi = collect($list_relasi);
} elseif ($method == 'view') {
$id = $request->query('value_id');
if (!$id) {
abort(400, 'value_id query parameter diperlukan untuk view method.');
}
$values = ValueBasisData::query()
->where('tabel_id', $tabelId)
->where('value_id', $id)
->where('program_id', $program->id)
->get();
if ($values->isEmpty()) {
abort(404, 'Data tidak ditemukan.');
}
foreach ($values as $v) {
$k = $kolom->where('id', $v->kolom_id)->first();
if ($k->tipe == 'foreign_key') {
$v->value_relasi_label = DB::table($k->relasi_tabel)->where($k->relasi_kolom_id, $v->value_relasi)->value($k->relasi_kolom_label);
} else if ($k->tipe == 'enum') {
$opsi = collect($k->enum_opsi);
$found = $opsi->first(function ($item) use ($v) {
return (string) $item['value'] === (string) $v->value_enum;
});
$v->value_enum_label = $found['label'] ?? null;
}
}
$value_id = $id;
}
$errors = $request->query('errors', null);
return response()->json($tabel->renderHtml($method, $errors, $list_relasi));
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
//
}
}
@@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers\Api\Ekosistem;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Http\JsonResponse;
use App\Models\Ekosistem\TabelBasisData;
use App\Http\Resources\Ekosistem\TabelBasisDataResource;
class TabelBasisDataController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request): AnonymousResourceCollection
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$perPage = $request->query('per_page', 10);
$search = strtolower($request->query('search', ''));
$tabel = TabelBasisData::with('kolom')
->whereHas('kolom', function ($query) {
$query->where('is_active', true);
})
->whereRaw('lower(tabel) LIKE ?', ["%$search%"])
->paginate($perPage);
return TabelBasisDataResource::collection($tabel);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(Request $request, string $id): JsonResponse
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$tabel = TabelBasisData::with('kolom', 'form', 'form.form', 'form.form.form', 'form.form.form.kolom')->findOrFail($id);
return (new TabelBasisDataResource($tabel))
->response();
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
//
}
}
@@ -0,0 +1,368 @@
<?php
namespace App\Http\Controllers\Api\Ekosistem;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\DB;
use App\Models\Ekosistem\TabelBasisData;
use App\Models\Ekosistem\KolomBasisData;
use App\Models\Ekosistem\ValueBasisData;
use App\Models\Core\Program;
class ValueBasisDataController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request, string $tabelId): JsonResponse
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$perPage = $request->query('per_page', 10);
$search = strtolower($request->query('search', ''));
$program = null;
$programList = Program::get();
if (app()->has('SystemProgram')) {
$program = app('SystemProgram');
} else {
if ($request->has('program_id') && $request->query('program_id')) {
$programId = $request->query('program_id');
$program = Program::find($programId);
if (!$program) {
abort(400, 'Program tidak ditemukan.');
}
}
}
$tabel = TabelBasisData::with('kolom')
->whereHas('kolom', function ($query) {
$query->where('is_active', true);
})
->find($tabelId);
if (!$tabel) {
abort(404, 'Data tabel tidak ditemukan.');
}
$kolom = KolomBasisData::where('tabel_id', $tabelId)
->where('is_active', true)
->when('is_browse', function ($q) {
$q->where('is_browse', true);
})
->orderBy('urutan', 'asc')
->get();
if ($kolom->isEmpty()) {
abort(404, 'Data kolom tidak ditemukan untuk tabel ini.');
}
$selects = [];
foreach ($kolom as $k) {
if ($k->tipe == 'boolean') {
$selects[] = "
MAX(
CASE
WHEN kolom_id = {$k->id}
THEN CASE
WHEN value_boolean = true THEN 'Ya'
WHEN value_boolean = false THEN 'Tidak'
ELSE NULL
END
ELSE NULL
END
) AS \"{$k->kolom}\"
";
} elseif ($k->tipe == 'foreign_key') {
$selects[] = "
MAX(
CASE
WHEN kolom_id = {$k->id}
THEN (select {$k->relasi_kolom_label} from {$k->relasi_tabel} where {$k->relasi_kolom_id} = value_relasi)
ELSE NULL
END
) AS \"{$k->kolom}\"
";
} elseif ($k->tipe == 'enum') {
$selects[] = "
MAX(
CASE
WHEN kolom_id = {$k->id}
AND value_enum IS NOT NULL
THEN (
SELECT elem->>'label'
FROM ekosistem_basis_data_kolom k,
jsonb_array_elements(k.enum_opsi::jsonb) AS elem
WHERE k.id = kolom_id
AND elem->>'value' = value_enum
LIMIT 1
)
ELSE NULL
END
) AS \"{$k->kolom}\"
";
} else {
$selects[] = "
MAX(
CASE
WHEN kolom_id = {$k->id}
THEN value_{$k->tipe}
ELSE NULL
END
) AS \"{$k->kolom}\"
";
}
}
$base = ValueBasisData::query()
->join('core_program', 'core_program.id', '=', 'ekosistem_basis_data_value.program_id')
->where('tabel_id', $tabelId)
->when($program, function ($query) use ($program) {
$query->where('program_id', $program->id);
})
->selectRaw("
core_program.id AS program_id,
core_program.nama AS program_nama,
tabel_id,
value_id,
" . implode(",\n", $selects) . "
")
->groupBy('core_program.id', 'core_program.nama', 'tabel_id', 'value_id')
->orderBy('value_id', 'asc');
$values = DB::query()->fromSub($base, 'pivot')
->when($search, function ($query) use ($search, $kolom) {
$query->where(function ($q) use ($search, $kolom) {
foreach ($kolom as $k) {
if ($k->tipe == 'text' || $k->tipe == 'varchar') {
$q->orWhereRaw("lower({$k->kolom}) LIKE ?", ["%$search%"]);
}
}
});
})
->paginate($perPage);
return response()->json($values);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request, string $tabelId): JsonResponse
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$tabel = TabelBasisData::whereHas('kolom', function ($query) {
$query->where('is_active', true);
})->find($tabelId);
if (!$tabel) {
abort(404, 'Data tabel tidak ditemukan.');
}
$kolom = KolomBasisData::where('tabel_id', $tabelId)
->where('is_active', true)->get();
if ($kolom->isEmpty()) {
abort(404, 'Data kolom tidak ditemukan untuk tabel ini.');
}
if (app()->has('SystemProgram')) {
$program = app('SystemProgram');
} else {
if ($request->has('program_id') && $request->query('program_id')) {
$programId = $request->query('program_id');
$program = Program::find($programId);
if (!$program) {
abort(400, 'Program tidak ditemukan.');
}
} else {
abort(400, 'Program tidak ditemukan.');
}
}
$validationRules = [];
foreach ($kolom as $k) {
if ($k->validasi && is_array($k->validasi)) {
$validationRules[$k->kolom] = $k->validasi;
} else {
$validationRules[$k->kolom] = ['nullable'];
}
}
$validated = $request->validate($validationRules);
DB::transaction(function () use ($program, $tabelId, $validated, $kolom) {
$maxId = ValueBasisData::where('tabel_id', $tabelId)
->where('program_id', $program->id)
->whereNotNull('value_id')
->max('value_id');
foreach ($kolom as $k) {
$values = [
'program_id' => $program->id,
'tabel_id' => (int)$tabelId,
'kolom_id' => $k->id,
'value_id' => ($maxId ?? 0) + 1,
'created_by' => auth()->id(),
'updated_by' => auth()->id(),
];
if ($k->tipe == 'foreign_key') {
$values['value_relasi'] = $validated[$k->kolom] ?? null;
} else {
$values['value_' . $k->tipe] = $validated[$k->kolom] ?? null;
}
ValueBasisData::create($values);
}
});
return response()->json(['message' => 'Data berhasil disimpan.']);
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $tabelId, string $id): JsonResponse
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$tabel = TabelBasisData::whereHas('kolom', function ($query) {
$query->where('is_active', true);
})->find($tabelId);
if (!$tabel) {
abort(404, 'Data tabel tidak ditemukan.');
}
$kolom = KolomBasisData::where('tabel_id', $tabelId)
->where('is_active', true)->get();
if ($kolom->isEmpty()) {
abort(404, 'Data kolom tidak ditemukan untuk tabel ini.');
}
if (app()->has('SystemProgram')) {
$program = app('SystemProgram');
} else {
if ($request->has('program_id') && $request->query('program_id')) {
$programId = $request->query('program_id');
$program = Program::find($programId);
if (!$program) {
abort(400, 'Program tidak ditemukan.');
}
} else {
abort(400, 'Program tidak ditemukan.');
}
}
$values = ValueBasisData::query()
->where('tabel_id', $tabelId)
->where('value_id', $id)
->where('program_id', $program->id)
->get();
if ($values->isEmpty()) {
abort(404, 'Data tidak ditemukan.');
}
$validationRules = [];
foreach ($kolom as $k) {
if ($k->validasi && is_array($k->validasi)) {
$validationRules[$k->kolom] = $k->validasi;
} else {
$validationRules[$k->kolom] = ['nullable'];
}
}
$validated = $request->validate($validationRules);
DB::transaction(function () use ($program, $tabelId, $validated, $kolom, $id, $values) {
foreach ($validated as $kol => $val) {
$k = $kolom->where('kolom', $kol)->first();
if (!$k) {
continue;
}
$v = $values->where('kolom_id', $k->id)->first();
if (!$v) {
$vals = [
'program_id' => $program->id,
'tabel_id' => (int)$tabelId,
'kolom_id' => $k->id,
'value_id' => $id,
'created_by' => auth()->id(),
'updated_by' => auth()->id(),
];
if ($k->tipe == 'foreign_key') {
$vals['value_relasi'] = $validated[$k->kolom] ?? null;
} else {
$vals['value_' . $k->tipe] = $validated[$k->kolom] ?? null;
}
ValueBasisData::create($vals);
} else {
$v->update([
'value_' . ($k->tipe == 'foreign_key' ? 'relasi' : $k->tipe) => $val,
'updated_by' => auth()->id(),
]);
}
}
});
return response()->json(['message' => 'Data berhasil diperbarui.']);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Request $request, string $tabelId, string $id): JsonResponse
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$tabel = TabelBasisData::whereHas('kolom', function ($query) {
$query->where('is_active', true);
})->find($tabelId);
if (!$tabel) {
abort(404, 'Data tabel tidak ditemukan.');
}
$kolom = KolomBasisData::where('tabel_id', $tabelId)->get();
if ($kolom->isEmpty()) {
abort(404, 'Data kolom tidak ditemukan untuk tabel ini.');
}
if (app()->has('SystemProgram')) {
$program = app('SystemProgram');
} else {
if ($request->has('program_id') && $request->query('program_id')) {
$programId = $request->query('program_id');
$program = Program::find($programId);
if (!$program) {
abort(400, 'Program tidak ditemukan.');
}
} else {
abort(400, 'Program tidak ditemukan.');
}
}
$values = ValueBasisData::query()
->where('tabel_id', $tabelId)
->where('value_id', $id)
->where('program_id', $program->id)
->get();
if ($values->isEmpty()) {
abort(404, 'Data tidak ditemukan.');
}
ValueBasisData::query()
->where('tabel_id', $tabelId)
->where('value_id', $id)
->where('program_id', $program->id)
->delete();
return response()->json(['message' => 'Data berhasil dihapus.']);
}
}
@@ -1,16 +1,14 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\Konfigurasi;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
use App\Models\Profile;
use App\Http\Resources\UserResource;
class AuthController extends Controller
@@ -45,20 +43,25 @@ class AuthController extends Controller
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
'username' => ['required', 'string', 'max:255'],
'role' => ['string'],
'uid' => ['required','string', 'unique:users'],
]);
$user = User::create([
'name' => $credentials['name'],
'email' => $credentials['email'],
'password' => Hash::make($credentials['password']),
'username' => $credentials['username'],
'role' => $credentials['role'],
'uid' => $credentials['uid'],
]);
$profile = Profile::create([
'nama' => $credentials['name'],
'email' => $credentials['email'],
'role' => $credentials['role'],
'user_uid' => $credentials['uid'],
'user_id' => $user->id,
'created_by' => $user->id,
'updated_by' => $user->id,
@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\Master;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\Master;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@@ -0,0 +1,141 @@
<?php
namespace App\Http\Controllers\Api\Master;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Validation\Rule;
use App\Http\Resources\Pertanian\JenisKomoditasResource;
use App\Models\Pertanian\JenisKomoditas;
class JenisKomoditasController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request): AnonymousResourceCollection
{
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) {
abort(403);
}
$size = $request->integer('size') ?: 10;
$jenisKomoditasList = JenisKomoditas::when($request->search, function ($q, $search) {
$search = strtolower($search);
$q->whereRaw('lower(nama) like ?', ["%{$search}%"]);
})
->when($request->kode, fn($q, $v) => $q->where('kode', $v));
if ($request->filled('sort')) {
$dir = str_starts_with($request->sort, '-') ? 'desc' : 'asc';
$column = ltrim($request->sort, '-');
$allowed = ['id', 'nama', 'kode'];
if (in_array($column, $allowed)) {
$jenisKomoditasList->orderBy($column, $dir);
}
} else {
$jenisKomoditasList->orderBy('nama', 'asc');
}
$jenisKomoditasList = $jenisKomoditasList->paginate($size);
return JenisKomoditasResource::collection($jenisKomoditasList);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) {
abort(403);
}
$validated = $request->validate([
'nama' => ['required', 'string', 'max:255'],
'kode' => ['required', 'string', 'max:50', 'unique:jenis_komoditas,kode'],
'keterangan' => ['nullable', 'string'],
]);
$jenisKomoditas = JenisKomoditas::create($validated);
return (new JenisKomoditasResource($jenisKomoditas))
->response()
->setStatusCode(201);
}
/**
* Display the specified resource.
*/
public function show(string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) {
abort(403);
}
$jenisKomoditas = JenisKomoditas::findOrFail($id);
return (new JenisKomoditasResource($jenisKomoditas))->response();
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) {
abort(403);
}
$jenisKomoditas = JenisKomoditas::findOrFail($id);
$validated = $request->validate([
'nama' => ['required', 'string', 'max:255'],
'kode' => ['required', 'string', 'max:50', Rule::unique('jenis_komoditas', 'kode')->ignore($jenisKomoditas->id)],
'keterangan' => ['nullable', 'string'],
]);
$jenisKomoditas->update($validated);
return (new JenisKomoditasResource($jenisKomoditas))->response();
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) {
abort(403);
}
$jenisKomoditas = JenisKomoditas::findOrFail($id);
$jenisKomoditas->delete();
return response()->json(null, 204);
}
}
@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\Master;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\Master;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\Master;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\Master;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\Produktivitas;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\Produktivitas;
use App\Http\Controllers\Controller;
use App\Models\Map\InsLahan;
@@ -1,5 +1,5 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\Produktivitas;
use App\Http\Controllers\Controller;
use App\Models\Map\Inspeksi;
@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\Produktivitas;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@@ -87,7 +87,10 @@ class LahanController extends Controller
if (!$profile) {
abort(404, 'Profile tidak ditemukan');
}
if ($request->has('profile_id')) {
$request->merge(['profile_id' => (int) $request->profile_id]);
}
$profileRule = $user->role === 'fasilitator' ? ['required', 'numeric', 'exists:profiles,id'] : ['nullable'];
$validated = $request->validate([
@@ -97,6 +100,7 @@ class LahanController extends Controller
'path' => ['nullable', 'array'],
'path.*' => ['array'],
'luas_lahan' => ['nullable', 'numeric'],
'uid' => ['required', 'string'],
'profile_id' => $profileRule,
]);
@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\Produktivitas;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\Produktivitas;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\Produktivitas;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@@ -0,0 +1,210 @@
<?php
namespace App\Http\Controllers\Api\Produktivitas;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\Rule;
use App\Http\Resources\Pertanian\TanamanResource;
use App\Models\Pertanian\Tanaman;
use App\Enums\KondisiTanaman;
class TanamanController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request): AnonymousResourceCollection
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
abort(403);
}
$user = Auth::user();
$profile = $user->profile;
$profileId = $user->role === 'petani' ? $profile->id : $request->profile_id;
$size = $request->integer('size') ?: 10;
$tanamanList = Tanaman::when($request->search, function ($q, $search) {
$search = strtolower($search);
$q->whereRaw('lower(nama) like ?', ["%{$search}%"]);
})
->when($profileId, fn($q,$v) => $q->where('profile_id',$v))
->when($request->status_kepemilikan, fn($q,$v) => $q->where('status_kepemilikan',$v))
->when($request->lahan_id, fn($q,$v) => $q->where('lahan_id',$v))
->when($request->komoditas_id, fn($q,$v) => $q->where('komoditas_id',$v))
->when($request->desa_kelurahan_id, fn($q,$v) =>
$q->whereHas('lahan', fn($k)
=> $k->where('desa_kelurahan_id',$v)
)
)
->when($request->kecamatan_id, fn ($q, $v) =>
$q->whereHas('lahan.desaKelurahan', fn ($k) =>
$k->where('kecamatan_id', $v)
)
)
->when($request->kabupaten_kota_id, fn ($q, $v) =>
$q->whereHas('lahan.desaKelurahan.kecamatan', fn ($k) =>
$k->where('kabupaten_kota_id', $v)
)
)
->when($request->provinsi_id, fn ($q, $v) =>
$q->whereHas('lahan.desaKelurahan.kecamatan.kabupatenkota', fn ($k) =>
$k->where('provinsi_id', $v)
)
);
if ($request->filled('sort')) {
$dir = str_starts_with($request->sort, '-') ? 'desc' : 'asc';
$column = ltrim($request->sort, '-');
$allowed = ['id', 'nama'];
if (in_array($column, $allowed)) {
$tanamanList->orderBy($column, $dir);
}
} else {
$tanamanList->orderBy('nama', 'asc');
}
$tanamanList = $tanamanList->paginate($size);
return TanamanResource::collection($tanamanList);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
abort(403);
}
$user = Auth::user();
$profile = $user->profile;
$profileId = $user->role === 'petani' ? $profile->id : $request->profile_id;
$request->merge([
'komoditas_id' => $request->input('komoditas.id') ?? $request->input('komoditas_id'),
'lahan_id' => $request->input('lahan.id') ?? $request->input('lahan_id'),
'profile_id' => $profileId,
]);
$validated = $request->validate([
'nama' => ['required', 'string', 'max:255'],
'komoditas_id' => ['required', 'exists:master_komoditas,id'],
'lahan_id' => [
'required',
Rule::exists('map_lahan', 'id')->where(function ($query) use ($profileId) {
$query->where('profile_id', $profileId)
->whereNull('deleted_at');
}),
],
'latitude' => ['nullable', 'numeric', 'between:-90,90', 'required_with:longitude'],
'longitude' => ['nullable', 'numeric', 'between:-180,180', 'required_with:latitude'],
'kondisi_tanaman' => ['nullable', 'string', new Enum(KondisiTanaman::class)],
]);
$tanaman = Tanaman::create([
...$validated,
'profile_id' => $profile->id,
]);
return (new TanamanResource($tanaman))
->response()
->setStatusCode(201);
}
/**
* Display the specified resource.
*/
public function show(string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
abort(403);
}
$tanaman = Tanaman::findOrFail($id);
if ($tanaman->profile_id !== Auth::user()->profile->id) {
return response()->json(['message' => 'Unauthorized'], 403);
}
return (new TanamanResource($tanaman))->response();
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
abort(403);
}
$tanaman = Tanaman::findOrFail($id);
if ($tanaman->profile_id !== Auth::user()->profile->id) {
return response()->json(['message' => 'Unauthorized'], 403);
}
$profile = Auth::user()->profile;
$validated = $request->validate([
'nama' => ['required', 'string', 'max:255'],
'komoditas_id' => ['required', 'exists:master_komoditas,id'],
'lahan_id' => [
'required',
Rule::exists('map_lahan', 'id')->where(function ($query) use ($profile) {
$query->where('profile_id', $profile->id)
->whereNull('deleted_at');
}),
],
'latitude' => ['nullable', 'numeric', 'between:-90,90', 'required_with:longitude'],
'longitude' => ['nullable', 'numeric', 'between:-180,180', 'required_with:latitude'],
'kondisi_tanaman' => ['nullable', 'string', new Enum(KondisiTanaman::class)],
]);
$tanaman->update($validated);
return (new TanamanResource($tanaman))->response();
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id): JsonResponse
{
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
abort(403);
}
$tanaman = Tanaman::findOrFail($id);
if ($tanaman->profile_id !== Auth::user()->profile->id) {
return response()->json(['message' => 'Unauthorized'], 403);
}
$tanaman->delete();
return response()->json(null, 204);
}
}
+19 -4
View File
@@ -94,26 +94,41 @@ class TanamanController extends Controller
if (!Gate::any(['petani', 'fasilitator','super_admin'])) {
abort(403);
}
$profile = Auth::user()->profile;
$user = Auth::user();
$profile = $user->profile;
$profileId = $user->role === 'petani' ? $profile->id : $request->profile_id;
$request->merge([
'komoditas_id' => $request->input('komoditas.id') ?? $request->input('komoditas_id'),
'lahan_id' => $request->input('lahan.id') ?? $request->input('lahan_id'),
'profile_id' => $profileId,
]);
$validated = $request->validate([
'nama' => ['required', 'string', 'max:255'],
'komoditas_id' => ['required', 'exists:master_komoditas,id'],
'lahan_id' => [
'required',
Rule::exists('map_lahan', 'id')->where(function ($query) use ($profile) {
$query->where('profile_id', $profile->id)
Rule::exists('map_lahan', 'id')->where(function ($query) use ($profileId) {
$query->where('profile_id', $profileId)
->whereNull('deleted_at');
}),
],
'latitude' => ['nullable', 'numeric', 'between:-90,90', 'required_with:longitude'],
'longitude' => ['nullable', 'numeric', 'between:-180,180', 'required_with:latitude'],
'longitude' => ['nullable ', 'numeric', 'between:-180,180', 'required_with:latitude'],
'kondisi_tanaman' => ['nullable', 'string', new Enum(KondisiTanaman::class)],
'uid' => ['required', 'string'],
'jenis_komoditas_id' => ['nullable', 'exists:jenis_komoditas,id'],
'komoditas_kode' => ['nullable', 'string', 'max:255'],
]);
$tanaman = Tanaman::create([
...$validated,
'profile_id' => $profile->id,
'uid' => $validated['uid'] ?? $request->input('uid'),
'komoditas_kode' => $validated['komoditas_kode'] ?? $request->input('komoditas_kode'),
'jenis_komoditas_id' => $validated['jenis_komoditas_id'] ?? $request->input('jenis_komoditas_id'),
]);
return (new TanamanResource($tanaman))
@@ -4,13 +4,8 @@ namespace App\Http\Controllers\Web\Dashboard;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Profile;
use App\Models\Wilayah\Provinsi;
use App\Models\wilayah\KabupatenKota;
use App\Models\Wilayah\Kecamatan;
use App\Models\Wilayah\DesaKelurahan;
use App\Models\Pertanian\Lahan;
use Illuminate\Support\Facades\Gate;
class SummaryController extends Controller
{
public function index()
@@ -21,128 +16,4 @@ class SummaryController extends Controller
$title = 'Summary Dashboard';
return view('dashboard.summary', compact('title'));
}
public function produktivitasBackup(Request $request)
{
if (!Gate::allows('super_admin') && !Gate::allows('admin')) {
abort(403);
}
$title = 'Produktivitas Dashboard';
$luasTotal = Lahan::whereNotNull('path')->sum('luas_lahan');
$totalpetani = Profile::where('role', 'petani')->count();
$totallahan = Lahan::whereNotNull('path')->count();
$petaniList = Profile::where('role', 'petani')
->with(['lahan.tanaman.komoditas', 'desaKelurahan'])
->paginate(20);
$allLahan = Lahan::whereNotNull('path')
->with(['profile', 'tanaman.komoditas', 'desaKelurahan.kecamatan.kabupatenKota.provinsi'])
->get()
->map(function($lahan) {
return [
'id' => $lahan->id,
'polygon' => $lahan->path,
'nama_petani' => $lahan->profile->nama ?? 'N/A',
'luas' => $lahan->luas_lahan,
'tanaman' => $lahan->tanaman->map(fn($t) => $t->komoditas->nama ?? null)->filter()->unique()->values()
];
});
return view('dashboard.produktivitas', compact('title', 'petaniList', 'allLahan', 'totalpetani', 'totallahan', 'luasTotal'));
}
public function produktivitas(Request $request)
{
if (!Gate::allows('super_admin') && !Gate::allows('admin')) {
abort(403);
}
$title = 'Produktivitas Dashboard';
$luasTotal = Lahan::whereNotNull('path')->sum('luas_lahan');
$totalpetani = Profile::where('role', 'petani')->count();
$totallahan = Lahan::whereNotNull('path')->count();
$petaniList = Profile::where('role', 'petani')
->with(['lahan.tanaman.komoditas', 'desaKelurahan'])
->paginate(20);
$allLahan = Lahan::whereNotNull('path')
->with(['profile', 'tanaman.komoditas', 'desaKelurahan.kecamatan.kabupatenKota.provinsi'])
->get();
// Penampung data per level (menggunakan ID sebagai key agar unique)
$geoProvinsi = [];
$geoKabupaten = [];
$geoKecamatan = [];
$geoDesa = [];
$geoLahan = [];
foreach ($allLahan as $lahan) {
$desa = $lahan->desaKelurahan;
$kec = $desa ? $desa->kecamatan : null;
$kab = $kec ? $kec->kabupatenKota : null;
$prov = $kab ? $kab->provinsi : null;
// --- Akumulasi Provinsi ---
if ($prov && $prov->path) {
if (!isset($geoProvinsi[$prov->id])) {
$geoProvinsi[$prov->id] = ['id' => $prov->id, 'nama' => $prov->nama, 'polygon' => $prov->path, 'total_lahan' => 0, 'total_luas' => 0];
}
$geoProvinsi[$prov->id]['total_lahan']++;
$geoProvinsi[$prov->id]['total_luas'] += $lahan->luas_lahan;
}
// --- Akumulasi Kabupaten ---
if ($kab && $kab->path) {
if (!isset($geoKabupaten[$kab->id])) {
$geoKabupaten[$kab->id] = ['id' => $kab->id, 'parent_id' => $prov->id ?? null, 'nama' => $kab->nama, 'polygon' => $kab->path, 'total_lahan' => 0, 'total_luas' => 0];
}
$geoKabupaten[$kab->id]['total_lahan']++;
$geoKabupaten[$kab->id]['total_luas'] += $lahan->luas_lahan;
}
// --- Akumulasi Kecamatan ---
if ($kec && $kec->path) {
if (!isset($geoKecamatan[$kec->id])) {
$geoKecamatan[$kec->id] = ['id' => $kec->id, 'parent_id' => $kab->id ?? null, 'nama' => $kec->nama, 'polygon' => $kec->path, 'total_lahan' => 0, 'total_luas' => 0];
}
$geoKecamatan[$kec->id]['total_lahan']++;
$geoKecamatan[$kec->id]['total_luas'] += $lahan->luas_lahan;
}
// --- Akumulasi Desa ---
if ($desa && $desa->path) {
if (!isset($geoDesa[$desa->id])) {
$geoDesa[$desa->id] = ['id' => $desa->id, 'parent_id' => $kec->id ?? null, 'nama' => $desa->nama, 'polygon' => $desa->path, 'total_lahan' => 0, 'total_luas' => 0];
}
$geoDesa[$desa->id]['total_lahan']++;
$geoDesa[$desa->id]['total_luas'] += $lahan->luas_lahan;
}
// Data Lahan Tetap
$geoLahan[] = [
'id' => $lahan->id,
'parent_id' => $desa->id ?? null,
'polygon' => $lahan->path,
'nama_petani' => $lahan->profile->nama ?? 'N/A',
'luas' => $lahan->luas_lahan,
'tanaman' => $lahan->tanaman->map(fn($t) => $t->komoditas->nama ?? null)->filter()->unique()->values()
];
}
return view('dashboard.produktivitas', [
'title' => $title,
'petaniList' => $petaniList,
'totalpetani' => $totalpetani,
'totallahan' => $totallahan,
'luasTotal' => $luasTotal,
// Reset key array agar menjadi format list normal untuk JSON
'geoProvinsi' => array_values($geoProvinsi),
'geoKabupaten' => array_values($geoKabupaten),
'geoKecamatan' => array_values($geoKecamatan),
'geoDesa' => array_values($geoDesa),
'geoLahan' => $geoLahan
]);
}
}
@@ -0,0 +1,296 @@
<?php
namespace App\Http\Controllers\Web\Ekosistem;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\View\View;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Http\RedirectResponse;
use App\Models\Ekosistem\TabelBasisData;
use App\Models\Ekosistem\FormBasisData;
use App\Enums\LayoutForm;
class FormBasisDataController extends Controller
{
public function index(Request $request, string $tabelId): View
{
Gate::authorize('super_admin');
$tabel = TabelBasisData::find($tabelId);
if (!$tabel) {
return response()->json([
'status' => 'error',
'message' => 'Data tabel tidak ditemukan.'
], 404);
}
$listEnum = LayoutForm::options();
$tab = $request->tab ?? 'form';
$tabel->state = 'preview';
return view('ekosistem.form-basis-data.index', compact('tabel', 'listEnum', 'tab'));
}
public function create()
{
//
}
public function store(Request $request, string $tabelId): JsonResponse
{
Gate::authorize('super_admin');
$tabel = TabelBasisData::find($tabelId);
if (!$tabel) {
return response()->json([
'status' => 'error',
'message' => 'Data tabel tidak ditemukan.'
], 404);
}
$validated = $request->validate([
'parent_id' => ['nullable', 'exists:ekosistem_basis_data_form,id'],
'tipe' => ['required', Rule::enum(LayoutForm::class)],
'column_width' => [
Rule::requiredIf($request->tipe === LayoutForm::COLUMN->value),
'nullable', 'integer', 'min:1', 'max:12'],
'kolom_id' => [
Rule::requiredIf($request->tipe === LayoutForm::VALUE->value),
'nullable', 'exists:ekosistem_basis_data_kolom,id'],
'section_label' => [
Rule::requiredIf($request->tipe === LayoutForm::SECTION->value),
'nullable', 'string'],
]);
FormBasisData::create([...$validated,
'tabel_id' => $tabelId,
'created_by' => auth()->user()->id,
'updated_by' => auth()->user()->id
]);
return response()->json([
'status' => 'success',
'message' => 'Form basis data berhasil ditambahkan.'
], 201);
}
public function show(string $tabelId, string $id): JsonResponse
{
Gate::authorize('super_admin');
$form = FormBasisData::where('tabel_id', $tabelId)->find($id);
if (!$form) {
return response()->json([
'status' => 'error',
'message' => 'Data formulir basis data tidak ditemukan.'
], 404);
}
return response()->json([
'status' => 'success',
'data' => $form
], 200);
}
public function edit(string $id)
{
//
}
public function update(Request $request, string $tabelId, string $id)
{
Gate::authorize('super_admin');
$form = FormBasisData::where('tabel_id', $tabelId)->find($id);
if (!$form) {
return response()->json([
'status' => 'error',
'message' => 'Data formulir basis data tidak ditemukan.'
], 404);
}
$validated = $request->validate([
'parent_id' => ['nullable', 'exists:ekosistem_basis_data_form,id'],
'tipe' => ['required', Rule::enum(LayoutForm::class)],
'column_width' => [
Rule::requiredIf($request->tipe === LayoutForm::COLUMN->value),
'nullable', 'integer', 'min:1', 'max:12'],
'kolom_id' => [
Rule::requiredIf($request->tipe === LayoutForm::VALUE->value),
'nullable', 'exists:ekosistem_basis_data_kolom,id'],
'section_label' => [
Rule::requiredIf($request->tipe === LayoutForm::SECTION->value),
'nullable', 'string'],
]);
$form->update([...$validated,
'updated_by' => auth()->user()->id
]);
return response()->json([
'status' => 'success',
'message' => 'Form basis data berhasil diubah.'
]);
}
public function destroy(string $tabelId, string $id): JsonResponse
{
Gate::authorize('super_admin');
$form = FormBasisData::where('tabel_id', $tabelId)->find($id);
if (!$form) {
return response()->json([
'status' => 'error',
'message' => 'Data form basis data tidak ditemukan.'
], 404);
}
if ($form->form->count() > 0) {
return response()->json([
'status' => 'error',
'message' => 'Tidak dapat menghapus formulir basis data yang memiliki formulir.'
], 400);
}
$form->update([
'deleted_by' => auth()->user()->id
]);
$form->delete();
return response()->json([
'status' => 'success',
'message' => 'Form basis data berhasil dihapus.'
], 200);
}
public function generate(Request $request, string $tabelId): RedirectResponse
{
Gate::authorize('super_admin');
$tabel = TabelBasisData::find($tabelId);
if (!$tabel) {
return redirect()->route('tabel.formulir.index', $tabelId)->with('error', 'Tabel basis data tidak ditemukan.');
}
DB::transaction(function () use ($tabel) {
FormBasisData::where('tabel_id', $tabel->id)->delete();
FormBasisData::create([
'tabel_id' => $tabel->id,
'tipe' => LayoutForm::SECTION,
'section_label' => $tabel->label,
'created_by' => auth()->user()->id,
'updated_by' => auth()->user()->id
]);
FormBasisData::create([
'tabel_id' => $tabel->id,
'tipe' => LayoutForm::HR_LINE,
'created_by' => auth()->user()->id,
'updated_by' => auth()->user()->id
]);
$chunks = $tabel->kolom->chunk(4);
foreach ($chunks as $chunk) {
$row = FormBasisData::create([
'tabel_id' => $tabel->id,
'tipe' => LayoutForm::ROW,
'created_by' => auth()->user()->id,
'updated_by' => auth()->user()->id
]);
foreach ($chunk as $kolom) {
$column = FormBasisData::create([
'tabel_id' => $tabel->id,
'tipe' => LayoutForm::COLUMN,
'column_width' => 3,
'parent_id' => $row->id,
'created_by' => auth()->user()->id,
'updated_by' => auth()->user()->id
]);
FormBasisData::create([
'tabel_id' => $tabel->id,
'tipe' => LayoutForm::VALUE,
'parent_id' => $column->id,
'kolom_id' => $kolom->id,
'created_by' => auth()->user()->id,
'updated_by' => auth()->user()->id
]);
}
}
});
return redirect()->route('tabel.formulir.index', $tabelId)->with('success', 'Formulir berhasil dibuat.');
}
public function moveUp(Request $request, string $tabelId, string $id): JsonResponse
{
Gate::authorize('super_admin');
$current = FormBasisData::where('tabel_id', $tabelId)->find($id);
if (!$current) {
return response()->json([
'status' => 'error',
'message' => 'Data formulir tidak ditemukan.'
], 404);
}
$prev = FormBasisData::where('tabel_id', $tabelId)
->where('urutan', '<', $current->urutan)
->where('parent_id', $current->parent_id)
->orderBy('urutan', 'desc')
->first();
if ($prev) {
// swap
[$current->urutan, $prev->urutan] = [$prev->urutan, $current->urutan];
$current->save();
$prev->save();
}
return response()->json([
'status' => 'success',
'message' => 'Formulir basis data berhasil diperbarui.'
], 200);
}
public function moveDown(Request $request, string $tabelId, string $id): JsonResponse
{
Gate::authorize('super_admin');
$current = FormBasisData::where('tabel_id', $tabelId)->find($id);
if (!$current) {
return response()->json([
'status' => 'error',
'message' => 'Data formulir tidak ditemukan.'
], 404);
}
$next = FormBasisData::where('tabel_id', $tabelId)
->where('urutan', '>', $current->urutan)
->where('parent_id', $current->parent_id)
->orderBy('urutan', 'asc')
->first();
if ($next) {
// swap
[$current->urutan, $next->urutan] = [$next->urutan, $current->urutan];
$current->save();
$next->save();
}
return response()->json([
'status' => 'success',
'message' => 'Formulir basis data berhasil diperbarui.'
], 200);
}
}
@@ -145,6 +145,13 @@ class KolomBasisDataController extends Controller
'is_browse' => ['nullable', 'boolean'],
]);
if (!empty($validated['enum_opsi'])) {
$validated['enum_opsi'] = json_decode($validated['enum_opsi'], true);
}
if (!empty($validated['validasi'])) {
$validated['validasi'] = json_decode($validated['validasi'], true);
}
$kolom->update([...$validated,
'updated_by' => auth()->id(),
]);
@@ -188,7 +195,7 @@ class KolomBasisDataController extends Controller
], 404);
}
$prev = KolomBasisData::where('tabel_id', $current->tabel_id)
$prev = KolomBasisData::where('tabel_id', $tabelId)
->where('urutan', '<', $current->urutan)
->orderBy('urutan', 'desc')
->first();
@@ -218,7 +225,7 @@ class KolomBasisDataController extends Controller
], 404);
}
$next = KolomBasisData::where('tabel_id', $current->tabel_id)
$next = KolomBasisData::where('tabel_id', $tabelId)
->where('urutan', '>', $current->urutan)
->orderBy('urutan', 'asc')
->first();
@@ -8,6 +8,13 @@ use Illuminate\Support\Facades\Gate;
use Illuminate\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;
use App\Models\Ekosistem\TabelBasisData;
use App\Models\Ekosistem\KolomBasisData;
@@ -17,9 +24,16 @@ use App\Models\Core\Program;
class ValueBasisDataController extends Controller
{
private $tableModelClassMap = [
'master_provinsi' => \App\Models\Wilayah\Provinsi::class,
'master_kabupaten_kota' => \App\Models\Wilayah\KabupatenKota::class,
'master_kecamatan' => \App\Models\Wilayah\Kecamatan::class,
'master_desa_kelurahan' => \App\Models\Wilayah\DesaKelurahan::class,
];
public function index(Request $request, string $tabelId): View
{
if (!Gate::any(['super_admin', 'admin'])) {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
@@ -40,13 +54,15 @@ class ValueBasisDataController extends Controller
}
}
$tabel = TabelBasisData::find($tabelId);
$tabel = TabelBasisData::with('kolom')
->whereHas('kolom', function ($query) {
$query->where('is_active', true);
})->find($tabelId);
if (!$tabel) {
abort(404, 'Data tabel tidak ditemukan.');
}
$kolom = KolomBasisData::where('tabel_id', $tabelId)
$kolom = KolomBasisData::where('tabel_id', $tabel->id)
->where('is_active', true)
->where('is_browse', true)
->get();
if ($kolom->isEmpty()) {
abort(404, 'Data kolom tidak ditemukan untuk tabel ini.');
@@ -83,6 +99,7 @@ class ValueBasisDataController extends Controller
MAX(
CASE
WHEN kolom_id = {$k->id}
AND value_enum IS NOT NULL
THEN (
SELECT elem->>'label'
FROM ekosistem_basis_data_kolom k,
@@ -160,8 +177,11 @@ class ValueBasisDataController extends Controller
if (!Gate::any(['super_admin', 'admin'])) {
abort(403);
}
$tabel = TabelBasisData::find($tabelId);
$tabel = TabelBasisData::with('kolom')
->whereHas('kolom', function ($query) {
$query->where('is_active', true);
})->find($tabelId);
if (!$tabel) {
abort(404, 'Data tabel tidak ditemukan.');
}
@@ -189,7 +209,12 @@ class ValueBasisDataController extends Controller
if ($k->tipe == 'foreign_key') {
$relasi['kolom_id'] = $k->id;
try {
$options_kolom = DB::table($k->relasi_tabel)->selectRaw($k->relasi_kolom_label . ' as label, ' . $k->relasi_kolom_id . ' as value')->whereNull('deleted_at');
$modelClass = Arr::get($this->tableModelClassMap, $k->relasi_tabel);
if ($modelClass) {
$options_kolom = $modelClass::select($k->relasi_kolom_label . ' as label', $k->relasi_kolom_id . ' as value')->limit(10000);
} else {
$options_kolom = DB::table($k->relasi_tabel)->selectRaw($k->relasi_kolom_label . ' as label, ' . $k->relasi_kolom_id . ' as value')->whereNull('deleted_at')->limit(10000);
}
$relasi['options'] = $options_kolom->get();
$relasi['sql'] = $options_kolom->toSql();
} catch (\Throwable $th) {
@@ -199,18 +224,84 @@ class ValueBasisDataController extends Controller
}
}
$list_relasi = collect($list_relasi);
// dd($kolom->where('tipe', 'enum')->first()?->enum_opsi);
return view('ekosistem.value-basis-data.add', compact('tabel', 'kolom', 'program', 'list_relasi'));
}
private function prepareValidationRules($kolom)
{
$rules = [];
foreach ($kolom as $k) {
if ($k->validasi && is_array($k->validasi)) {
$rules[$k->kolom] = $k->validasi;
if (!in_array('required', $k->validasi) && !in_array('nullable', $k->validasi)) {
$rules[$k->kolom][] = 'nullable';
}
if (!in_array('string', $k->validasi) && ($k->tipe == 'varchar' || $k->tipe == 'text')) {
$rules[$k->kolom][] = 'string';
}
if (!in_array('size', $k->validasi) && ($k->tipe == 'varchar')) {
$rules[$k->kolom][] = 'size:255';
}
if (!in_array('integer', $k->validasi) && $k->tipe == 'integer') {
$rules[$k->kolom][] = 'integer';
}
if (!in_array('boolean', $k->validasi) && $k->tipe == 'boolean') {
$rules[$k->kolom][] = 'boolean';
}
if (!in_array('date', $k->validasi) && ($k->tipe == 'date' || $k->tipe == 'datetime')) {
$rules[$k->kolom][] = 'date';
}
if ($k->tipe == 'foreign_key') {
$rules[$k->kolom][] = 'exists:' . $k->relasi_tabel . ',' . $k->relasi_kolom_id;
}
if ($k->tipe == 'enum') {
$enumValues = collect($k->enum_opsi)->pluck('value')->toArray();
$rules[$k->kolom][] = Rule::in($enumValues);
}
} else {
$rules[$k->kolom] = ['nullable'];
if ($k->tipe == 'varchar' || $k->tipe == 'text') {
$rules[$k->kolom][] = 'string';
}
if ($k->tipe == 'varchar') {
$rules[$k->kolom][] = 'size:255';
}
if ($k->tipe == 'integer') {
$rules[$k->kolom][] = 'integer';
}
if ($k->tipe == 'boolean') {
$rules[$k->kolom][] = 'boolean';
}
if ($k->tipe == 'date' || $k->tipe == 'datetime') {
$rules[$k->kolom][] = 'date';
}
if ($k->tipe == 'foreign_key') {
$rules[$k->kolom][] = 'exists:' . $k->relasi_tabel . ',' . $k->relasi_kolom_id;
}
if ($k->tipe == 'enum') {
$enumValues = collect($k->enum_opsi)->pluck('value')->toArray();
$rules[$k->kolom][] = Rule::in($enumValues);
}
}
}
return $rules;
}
public function store(Request $request, string $tabelId): RedirectResponse
{
if (!Gate::any(['super_admin', 'admin'])) {
abort(403);
}
$tabel = TabelBasisData::find($tabelId);
$tabel = TabelBasisData::with('kolom')
->whereHas('kolom', function ($query) {
$query->where('is_active', true);
})->find($tabelId);
if (!$tabel) {
abort(404, 'Data tabel tidak ditemukan.');
}
@@ -233,15 +324,7 @@ class ValueBasisDataController extends Controller
}
}
$validationRules = [];
foreach ($kolom as $k) {
if ($k->validasi && is_array($k->validasi)) {
$validationRules[$k->kolom] = $k->validasi;
} else {
$validationRules[$k->kolom] = ['nullable'];
}
}
$validated = $request->validate($validationRules);
$validated = $request->validate($this->prepareValidationRules($kolom));
DB::transaction(function () use ($program, $tabelId, $validated, $kolom) {
$maxId = ValueBasisData::where('tabel_id', $tabelId)
@@ -270,16 +353,16 @@ class ValueBasisDataController extends Controller
return redirect()->route('tabel.data.index', [$tabelId, 'program_id' => $program->id])->with('success', 'Data berhasil ditambahkan.');
}
/**
* Display the specified resource.
*/
public function show(Request $request, string $tabelId, string $id): View
{
if (!Gate::any(['super_admin', 'admin'])) {
abort(403);
}
$tabel = TabelBasisData::find($tabelId);
$tabel = TabelBasisData::with('kolom')
->whereHas('kolom', function ($query) {
$query->where('is_active', true);
})->find($tabelId);
if (!$tabel) {
abort(404, 'Data tabel tidak ditemukan.');
}
@@ -315,7 +398,9 @@ class ValueBasisDataController extends Controller
foreach ($values as $v) {
$k = $kolom->where('id', $v->kolom_id)->first();
if ($k->tipe == 'foreign_key') {
$v->value_relasi_label = DB::table($k->relasi_tabel)->where($k->relasi_kolom_id, $v->value_relasi)->value($k->relasi_kolom_label);
$v->value_relasi_label = DB::table($k->relasi_tabel)
->where($k->relasi_kolom_id, $v->value_relasi)
->value($k->relasi_kolom_label);
} else if ($k->tipe == 'enum') {
$opsi = collect($k->enum_opsi);
$found = $opsi->first(function ($item) use ($v) {
@@ -336,7 +421,10 @@ class ValueBasisDataController extends Controller
abort(403);
}
$tabel = TabelBasisData::find($tabelId);
$tabel = TabelBasisData::with('kolom')
->whereHas('kolom', function ($query) {
$query->where('is_active', true);
})->find($tabelId);
if (!$tabel) {
abort(404, 'Data tabel tidak ditemukan.');
}
@@ -376,7 +464,12 @@ class ValueBasisDataController extends Controller
if ($k->tipe == 'foreign_key') {
$relasi['kolom_id'] = $k->id;
try {
$options_kolom = DB::table($k->relasi_tabel)->selectRaw($k->relasi_kolom_label . ' as label, ' . $k->relasi_kolom_id . ' as value')->whereNull('deleted_at');
$modelClass = Arr::get($this->tableModelClassMap, 'master_desa_kelurahan');
if ($modelClass) {
$options_kolom = $modelClass::select($k->relasi_kolom_label . ' as label', $k->relasi_kolom_id . ' as value')->limit(10000);
} else {
$options_kolom = DB::table($k->relasi_tabel)->selectRaw($k->relasi_kolom_label . ' as label, ' . $k->relasi_kolom_id . ' as value')->whereNull('deleted_at')->limit(10000);
}
$relasi['options'] = $options_kolom->get();
$relasi['sql'] = $options_kolom->toSql();
} catch (\Throwable $th) {
@@ -390,16 +483,16 @@ class ValueBasisDataController extends Controller
return view('ekosistem.value-basis-data.edit', compact('tabel', 'kolom', 'program', 'values', 'value_id', 'list_relasi'));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $tabelId, string $id): RedirectResponse
{
if (!Gate::any(['super_admin', 'admin'])) {
abort(403);
}
$tabel = TabelBasisData::find($tabelId);
$tabel = TabelBasisData::with('kolom')
->whereHas('kolom', function ($query) {
$query->where('is_active', true);
})->find($tabelId);
if (!$tabel) {
abort(404, 'Data tabel tidak ditemukan.');
}
@@ -431,15 +524,7 @@ class ValueBasisDataController extends Controller
abort(404, 'Data tidak ditemukan.');
}
$validationRules = [];
foreach ($kolom as $k) {
if ($k->validasi && is_array($k->validasi)) {
$validationRules[$k->kolom] = $k->validasi;
} else {
$validationRules[$k->kolom] = ['nullable'];
}
}
$validated = $request->validate($validationRules);
$validated = $request->validate($this->prepareValidationRules($kolom));
DB::transaction(function () use ($program, $tabelId, $validated, $kolom, $id, $values) {
foreach ($validated as $kol => $val) {
@@ -15,13 +15,13 @@ class AuthController extends Controller
public function login()
{
$title = 'Login';
return view('konfigurasi.auth.login', compact('title'));
return view('auth.login', compact('title'));
}
public function register()
{
$title = 'Register';
return view('konfigurasi.auth.register', compact('title'));
return view('auth.register', compact('title'));
}
public function createAccount(Request $request)
@@ -42,7 +42,7 @@ class AuthController extends Controller
'deleted_by' => null,
]);
return redirect()->route('konfigurasi.auth.login')->with('success', 'Account created successfully. Please login.');
return redirect()->route('auth.login')->with('success', 'Account created successfully. Please login.');
}
public function verify(User $user)
@@ -10,6 +10,7 @@ use App\Enums\Role;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
class UserController extends Controller
{
@@ -70,6 +71,7 @@ class UserController extends Controller
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'username' => ['required', 'string', 'max:255', 'unique:users,name'],
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
@@ -90,14 +92,17 @@ class UserController extends Controller
$user = User::create([
'name' => $request->name,
'username' => $request->username,
'email' => $request->email,
'password' => Hash::make($request->password),
'role' => $request->role,
'uid' => Str::random(10),
'created_by' => auth()->id(),
]);
Profile::create([
'user_id' => $user->id,
'user_uid' => $user->uid,
'role' => $request->role,
'nama' => $request->name,
'email' => $request->email,
@@ -0,0 +1,228 @@
<?php
namespace App\Http\Controllers\Web\Master;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Master\JenisKomoditas;
use App\Models\Pertanian\Komoditas;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class JenisKomoditasController extends Controller
{
public function index(Request $request)
{
$perPage = $request->get('per_page', 10);
$search = $request->get('search');
$jenisKomoditas = JenisKomoditas::with('komoditas')
->when($search, function ($query) use ($search) {
$query->where(function ($q) use ($search) {
$q->where('nama', 'ILIKE', "%{$search}%")
->orWhere('path', 'ILIKE', "%{$search}%")
->orWhereHas('komoditas', function ($komoditasQuery) use ($search) {
$komoditasQuery->where('nama', 'ILIKE', "%{$search}%");
});
});
})
->latest()
->paginate($perPage);
$komoditas = Komoditas::orderBy('nama', 'asc')->get();
return view('master.jeniskomoditas.index', compact(
'jenisKomoditas',
'komoditas'
));
}
public function getByKomoditas($komoditas_id)
{
$jenisKomoditas = JenisKomoditas::where('komoditas_id', $komoditas_id)
->orderBy('nama', 'asc')
->get(['id', 'nama']);
return response()->json([
'status' => 'success',
'data' => $jenisKomoditas,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$validated = $request->validate([
'komoditas_id' => [
'required',
'integer',
'exists:master_komoditas,id',
],
'nama' => [
'required',
'string',
'max:255',
Rule::unique('jenis_komoditas', 'nama')
->where('komoditas_id', $request->komoditas_id),
],
], [
'komoditas_id.required' => 'Komoditas wajib dipilih.',
'komoditas_id.exists' => 'Komoditas yang dipilih tidak valid.',
'nama.required' => 'Nama jenis komoditas wajib diisi.',
'nama.unique' => 'Nama jenis komoditas sudah tersedia pada komoditas ini.',
]);
try {
DB::beginTransaction();
$jenisKomoditas = JenisKomoditas::create([
'komoditas_id' => $validated['komoditas_id'],
'nama' => $validated['nama'],
'created_by' => auth()->id(),
'updated_by' => auth()->id(),
]);
DB::commit();
return response()->json([
'status' => 'success',
'message' => 'Data jenis komoditas berhasil disimpan.',
'data' => $jenisKomoditas,
]);
} catch (\Throwable $e) {
DB::rollBack();
Log::error('Gagal menyimpan jenis komoditas', [
'error' => $e->getMessage(),
'request' => $request->all(),
]);
return response()->json([
'status' => 'error',
'message' => 'Gagal menyimpan data: ' . $e->getMessage(),
], 500);
}
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
try {
$jenisKomoditas = JenisKomoditas::with('komoditas')->findOrFail($id);
return response()->json([
'status' => 'success',
'data' => $jenisKomoditas,
]);
} catch (\Throwable $e) {
return response()->json([
'status' => 'error',
'message' => 'Data jenis komoditas tidak ditemukan.',
], 404);
}
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
$jenisKomoditas = JenisKomoditas::findOrFail($id);
$validated = $request->validate([
'komoditas_id' => [
'required',
'integer',
'exists:master_komoditas,id',
],
'nama' => [
'required',
'string',
'max:255',
Rule::unique('jenis_komoditas', 'nama')
->where('komoditas_id', $request->komoditas_id)
->ignore($jenisKomoditas->id),
],
'path' => [
'nullable',
'string',
],
], [
'komoditas_id.required' => 'Komoditas wajib dipilih.',
'komoditas_id.exists' => 'Komoditas yang dipilih tidak valid.',
'nama.required' => 'Nama jenis komoditas wajib diisi.',
'nama.unique' => 'Nama jenis komoditas sudah tersedia pada komoditas ini.',
]);
try {
DB::beginTransaction();
$jenisKomoditas->update([
'komoditas_id' => $validated['komoditas_id'],
'nama' => $validated['nama'],
'updated_by' => auth()->id(),
]);
DB::commit();
return response()->json([
'status' => 'success',
'message' => 'Data jenis komoditas berhasil diperbarui.',
'data' => $jenisKomoditas->fresh('komoditas'),
]);
} catch (\Throwable $e) {
DB::rollBack();
Log::error('Gagal update jenis komoditas', [
'error' => $e->getMessage(),
'id' => $id,
'request' => $request->all(),
]);
return response()->json([
'status' => 'error',
'message' => 'Gagal mengupdate data: ' . $e->getMessage(),
], 500);
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
try {
DB::beginTransaction();
$jenisKomoditas = JenisKomoditas::findOrFail($id);
$jenisKomoditas->update(['deleted_by' => auth()->id()]);
$jenisKomoditas->delete();
DB::commit();
return response()->json([
'status' => 'success',
'message' => 'Data jenis komoditas berhasil dihapus.',
]);
} catch (\Throwable $e) {
DB::rollBack();
Log::error('Gagal hapus jenis komoditas', [
'error' => $e->getMessage(),
'id' => $id,
]);
return response()->json([
'status' => 'error',
'message' => 'Gagal menghapus data: ' . $e->getMessage(),
], 500);
}
}
}
@@ -17,9 +17,12 @@ class ProvinsiController extends Controller
$perPage = $request->query('per_page', 10);
$search = $request->query('search', '');
$provinsi = Provinsi::where('nama', 'like', "%$search%")
->orWhere('kode', 'like', "%$search%")
->orWhere('iso', 'like', "%$search%")
$provinsi = Provinsi::withCount('kabupatenKota')
->where(function($query) use ($search) {
$query->where('nama', 'like', "%$search%")
->orWhere('kode', 'like', "%$search%")
->orWhere('iso', 'like', "%$search%");
})
->paginate($perPage);
return view('master.provinsi.index', compact('title', 'provinsi'));
@@ -0,0 +1,236 @@
<?php
namespace App\Http\Controllers\Web\Master;
use App\Http\Controllers\Controller;
use App\Models\Master\Varietas;
use App\Models\Pertanian\Komoditas;
use App\Models\Master\JenisKomoditas;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Http\Request;
class VarietasController extends Controller
{
public function index(Request $request)
{
$perPage = $request->get('per_page', 10);
$search = $request->get('search');
$varietas = Varietas::with(['komoditas', 'jenisKomoditas'])
->when($search, function ($query) use ($search) {
$query->where(function ($q) use ($search) {
$q->where('nama', 'ILIKE', "%{$search}%")
->orWhere('path', 'ILIKE', "%{$search}%")
->orWhereHas('komoditas', function ($komoditasQuery) use ($search) {
$komoditasQuery->where('nama', 'ILIKE', "%{$search}%");
})
->orWhereHas('jenisKomoditas', function ($jenisQuery) use ($search) {
$jenisQuery->where('nama', 'ILIKE', "%{$search}%");
});
});
})
->latest()
->paginate($perPage);
$komoditas = Komoditas::orderBy('nama', 'asc')->get();
return view('master.varietas.index', compact(
'varietas',
'komoditas'
));
}
public function getByJenisKomoditas($jenisKomoditasId)
{
$varietas = Varietas::where('jenis_komoditas_id', $jenisKomoditasId)->get();
return response()->json([
'status' => 'success',
'data' => $varietas,
]);
}
public function store(Request $request)
{
$komoditasTable = (new Komoditas)->getTable();
$jenisKomoditasTable = (new JenisKomoditas)->getTable();
$varietasTable = (new Varietas)->getTable();
$validated = $request->validate([
'komoditas_id' => [
'required',
'integer',
Rule::exists($komoditasTable, 'id'),
],
'jenis_komoditas_id' => [
'required',
'integer',
Rule::exists($jenisKomoditasTable, 'id')
->where('komoditas_id', $request->komoditas_id),
],
'nama' => [
'required',
'string',
'max:255',
Rule::unique($varietasTable, 'nama')
->where('komoditas_id', $request->komoditas_id)
->where('jenis_komoditas_id', $request->jenis_komoditas_id),
],
'deskripsi' => [
'nullable',
'string',
],
]);
try {
DB::beginTransaction();
$varietas = Varietas::create([
'komoditas_id' => $validated['komoditas_id'],
'jenis_komoditas_id' => $validated['jenis_komoditas_id'],
'nama' => $validated['nama'],
'deskripsi' => $validated['deskripsi'] ?? null,
'created_by' => auth()->id(),
'updated_by' => auth()->id(),
]);
DB::commit();
return response()->json([
'status' => 'success',
'message' => 'Data varietas berhasil disimpan.',
'data' => $varietas,
]);
} catch (\Throwable $e) {
DB::rollBack();
Log::error('Gagal menyimpan varietas', [
'error' => $e->getMessage(),
'request' => $request->all(),
]);
return response()->json([
'status' => 'error',
'message' => 'Gagal menyimpan data: ' . $e->getMessage(),
], 500);
}
}
public function show(string $id)
{
try {
$varietas = Varietas::with(['komoditas', 'jenisKomoditas'])->findOrFail($id);
return response()->json([
'status' => 'success',
'data' => $varietas,
]);
} catch (\Throwable $e) {
return response()->json([
'status' => 'error',
'message' => 'Data varietas tidak ditemukan.',
], 404);
}
}
public function update(Request $request, string $id)
{
$komoditasTable = (new Komoditas)->getTable();
$jenisKomoditasTable = (new JenisKomoditas)->getTable();
$varietasTable = (new Varietas)->getTable();
$validated = $request->validate([
'komoditas_id' => [
'required',
'integer',
Rule::exists($komoditasTable, 'id'),
],
'jenis_komoditas_id' => [
'required',
'integer',
Rule::exists($jenisKomoditasTable, 'id')
->where('komoditas_id', $request->komoditas_id),
],
'nama' => [
'required',
'string',
'max:255',
Rule::unique($varietasTable, 'nama')
->where('komoditas_id', $request->komoditas_id)
->where('jenis_komoditas_id', $request->jenis_komoditas_id),
],
'deskripsi' => [
'nullable',
'string',
],
]);
try {
DB::beginTransaction();
$varietas = Varietas::findOrFail($id);
$varietas->update([
'komoditas_id' => $validated['komoditas_id'],
'jenis_komoditas_id' => $validated['jenis_komoditas_id'],
'nama' => $validated['nama'],
'deskripsi' => $validated['deskripsi'] ?? null,
'updated_by' => auth()->id(),
]);
DB::commit();
return response()->json([
'status' => 'success',
'message' => 'Data varietas berhasil diperbarui.',
'data' => $varietas->fresh(['komoditas', 'jenisKomoditas']),
]);
} catch (\Throwable $e) {
DB::rollBack();
Log::error('Gagal update varietas', [
'error' => $e->getMessage(),
'id' => $id,
'request' => $request->all(),
]);
return response()->json([
'status' => 'error',
'message' => 'Gagal mengupdate data: ' . $e->getMessage(),
], 500);
}
}
public function destroy(string $id)
{
try {
DB::beginTransaction();
$varietas = Varietas::findOrFail($id);
$varietas->update(['deleted_by' => auth()->id()]);
$varietas->delete();
DB::commit();
return response()->json([
'status' => 'success',
'message' => 'Data varietas berhasil dihapus.',
]);
} catch (\Throwable $e) {
DB::rollBack();
Log::error('Gagal hapus varietas', [
'error' => $e->getMessage(),
'id' => $id,
]);
return response()->json([
'status' => 'error',
'message' => 'Gagal menghapus data: ' . $e->getMessage(),
], 500);
}
}
}
@@ -10,6 +10,7 @@ use App\Models\Pertanian\Adat;
use App\Models\Wilayah\Provinsi;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
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'));
}
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()
{
$provinsi = Provinsi::orderBy('nama', 'asc')->get();
@@ -112,8 +132,12 @@ class LahanController extends Controller
return response()->json(['status' => 'error', 'message' => 'Format koordinat (path) bukan JSON array yang valid.'], 422);
}
}
$userUID = Profile::where('id', $request->profile_id)->value('user_uid');
$userUID = substr($userUID, 2);
Lahan::create([
'uid' => 'LH' . $userUID . '-' . strtoupper(Str::random(10)),
'nama' => $request->nama,
'status_kepemilikan' => $request->status_kepemilikan,
'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);
}
}
}
@@ -35,6 +35,25 @@ class ProfileController extends Controller
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()
{
$provinsi = Provinsi::orderBy('nama', 'asc')->get();
@@ -54,6 +54,9 @@ class AktivitasController extends Controller
'jenis_aktivitas' => ['required','string','max:255', Rule::in(['tujuan', 'aktivitas']),],
'indikator' => ['required','string'],
'deskripsi' => ['required','string'],
'parent_id' => [
Rule::requiredIf($request->jenis_aktivitas === 'aktivitas'),
'nullable','exists:program_aktivitas,id',],
]);
Aktivitas::create([...$validated,
@@ -125,6 +128,9 @@ class AktivitasController extends Controller
'jenis_aktivitas' => ['required','string','max:255', Rule::in(['tujuan', 'aktivitas']),],
'indikator' => ['required','string'],
'deskripsi' => ['required','string'],
'parent_id' => [
Rule::requiredIf($request->jenis_aktivitas === 'aktivitas'),
'nullable','exists:program_aktivitas,id'],
]);
$aktivitas->update([...$validated,
@@ -102,7 +102,8 @@ class FrameworkController extends Controller
abort(403);
}
$framework = Framework::findOrFail($id);
$framework = Framework::with('transformasi')->with('transformasi.tujuan')
->findOrFail($id);
$program = Program::all();
return view('program.framework.edit', compact('framework', 'program'));
@@ -0,0 +1,208 @@
<?php
namespace App\Http\Controllers\Web\Program;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\View\View;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use App\Models\Program\Framework;
use App\Models\Program\Realisasi;
class RealisasiController extends Controller
{
public function index(Request $request): View
{
if (!Gate::any(['super_admin', 'admin'])) {
abort(403);
}
$framework = Framework::when($request->has('frameworkid'), function ($query) use ($request) {
$query->where('id', $request->frameworkid);
})->latest()->first();
if ($framework == null) {
abort(404);
}
$ringkasanIsiRealisasi = DB::select('select count(pt.id) count_target, sum(case when rl.id > 0 then 1 else 0 end) count_realisasi
from program_target pt
inner join program_aktivitas pa on pt.aktivitas_id = pa.id
inner join program_transformasi ptr on pa.transformasi_id = ptr.id
inner join program_framework pf on ptr.framework_id = pf.id
left join
(select target_id, max(id) id from program_realisasi pr
group by target_id) rl on pt.id = rl.target_id
where
pt.deleted_at is null
and pa.deleted_at is null
and ptr.deleted_at is null
and pf.id = ?
', [$framework->id]);
$ringkasanIsiRealisasi = collect($ringkasanIsiRealisasi)->map(function ($item) {
$item->persentase = $item->count_target > 0 ? round(($item->count_realisasi / $item->count_target) * 100, 2) : 0;
return $item;
})->first();
$ringkasanCapaianRealisasi = DB::select("select round(avg(percent_realisasi)::numeric * 100, 2) persentase,
sum(case when percent_realisasi >= 0.8 then 1 else 0 end) capaian_80,
sum(case when percent_realisasi >= 0.5 and percent_realisasi < 0.8 then 1 else 0 end) capaian_50_80,
sum(case when percent_realisasi < 0.5 then 1 else 0 end) capaian_50
from
(select pt.id target_id, pr.id realisasi_id,
pt.tipe,
case
when pt.tipe = 'integer' then pt.target_integer
when pt.tipe = 'float' then pt.target_float
when pt.tipe = 'boolean' then pt.target_boolean::integer
end target,
case
when pt.tipe = 'integer' then pr.realisasi_integer
when pt.tipe = 'float' then pr.realisasi_float
when pt.tipe = 'boolean' then pr.realisasi_boolean::integer
end realisasi,
case
when pt.tipe = 'integer' then pr.realisasi_integer::float / pt.target_integer::float
when pt.tipe = 'float' then pr.realisasi_float / pt.target_float
when pt.tipe = 'boolean' then pr.realisasi_boolean::integer / pt.target_boolean::integer
end percent_realisasi
from program_target pt
inner join program_aktivitas pa on pt.aktivitas_id = pa.id
inner join program_transformasi ptr on pa.transformasi_id = ptr.id
inner join program_framework pf on ptr.framework_id = pf.id
left join
(select target_id, max(id) id from program_realisasi pr
group by target_id) rl on pt.id = rl.target_id
left join program_realisasi pr on rl.id = pr.id and pr.target_id = pt.id
where
pt.deleted_at is null
and pa.deleted_at is null
and ptr.deleted_at is null
and pf.id = ?) v",
[$framework->id]);
$ringkasanCapaianRealisasi = collect($ringkasanCapaianRealisasi)->first();
$ringkasanVerifikasi = DB::select("select count(pt.id) count_target,
sum(case when count_verifikasi > 0 then 1 else 0 end) count_verifikasi,
sum(case when count_verifikasi = 0 or count_verifikasi is null then 1 else 0 end) count_unverifikasi
from program_target pt
inner join program_aktivitas pa on pt.aktivitas_id = pa.id
inner join program_transformasi ptr on pa.transformasi_id = ptr.id
inner join program_framework pf on ptr.framework_id = pf.id
left join
(select target_id, max(id) id from program_realisasi pr
group by target_id) rl on pt.id = rl.target_id
left join
(select realisasi_id, count(pv.id) count_verifikasi from program_verifikasi pv
group by realisasi_id) rv
on rv.realisasi_id = rl.id
where
pt.deleted_at is null
and pa.deleted_at is null
and ptr.deleted_at is null
and pf.id = ?",
[$framework->id]);
$ringkasanVerifikasi = collect($ringkasanVerifikasi)->map(function ($item) {
$item->persentase = $item->count_target > 0 ? round(($item->count_verifikasi / $item->count_target) * 100, 2) : 0;
return $item;
})->first();
return view('program.realisasi.index', compact('framework', 'ringkasanIsiRealisasi', 'ringkasanCapaianRealisasi', 'ringkasanVerifikasi'));
}
public function create()
{
//
}
public function store(Request $request): JsonResponse
{
if (!Gate::any(['super_admin', 'admin'])) {
abort(403);
}
$validated = $request->validate([
'target_id' => ['required', 'integer', 'exists:program_target,id'],
'realisasi_integer' => ['nullable', 'integer'],
'realisasi_float' => ['nullable', 'numeric'],
'realisasi_boolean' => ['nullable', 'boolean'],
]);
$realisasi = Realisasi::create([...$validated,
'created_by' => auth()->id(),
'updated_by' => auth()->id()
]);
return response()->json([
'status' => 'success',
'message' => 'Target berhasil ditambahkan.'
], 201);
}
public function show(string $id)
{
if (!Gate::any(['super_admin', 'admin'])) {
abort(403);
}
$realisasi = Realisasi::with('target', 'target.aktivitas', 'target.aktivitas.transformasi', 'verifikasi')
->find($id);
if (!$realisasi) {
return response()->json([
'status' => 'error',
'message' => 'Data realisasi tidak ditemukan.'
], 404);
}
return response()->json([
'status' => 'success',
'data' => $realisasi
]);
}
public function edit(string $id)
{
//
}
public function update(Request $request, string $id): JsonResponse
{
if (!Gate::any(['super_admin', 'admin'])) {
abort(403);
}
$realisasi = Realisasi::find($id);
if (!$realisasi) {
return response()->json([
'status' => 'error',
'message' => 'Data realisasi tidak ditemukan.'
], 404);
}
$validated = $request->validate([
'target_id' => ['required', 'integer', 'exists:program_target,id'],
'realisasi_integer' => ['nullable', 'integer'],
'realisasi_float' => ['nullable', 'numeric'],
'realisasi_boolean' => ['nullable', 'boolean'],
]);
$realisasi->update([...$validated,
'updated_by' => auth()->id(),
]);
return response()->json([
'status' => 'success',
'message' => 'Target berhasil diperbarui.'
], 200);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
//
}
}
@@ -69,6 +69,16 @@ class TargetController extends Controller
'nullable',
'boolean'
],
'tipe_realisasi' => [
'nullable',
'string',
Rule::in(['manual', 'referensi'])
],
'value_query' => [
Rule::requiredIf($request->tipe_realisasi === 'referensi'),
'nullable',
'string'
]
]);
}
@@ -77,9 +87,6 @@ class TargetController extends Controller
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request, string $frameworkId, string $transformasiId, string $aktivitasId): JsonResponse
{
if (!Gate::any(['super_admin', 'admin'])) {
@@ -109,12 +116,32 @@ class TargetController extends Controller
], 201);
}
/**
* Display the specified resource.
*/
public function show(string $id)
public function show(string $frameworkId, string $transformasiId, string $aktivitasId, string $id)
{
//
if (!Gate::any(['super_admin', 'admin'])) {
abort(403);
}
$target = Target::with('realisasi', 'latestRealisasi')
->whereHas('aktivitas.transformasi', function ($query) use ($frameworkId) {
$query->where('framework_id', $frameworkId);
})
->whereHas('aktivitas', function ($query) use ($transformasiId) {
$query->where('transformasi_id', $transformasiId);
})
->where('aktivitas_id', $aktivitasId)->find($id);
if (!$target) {
return response()->json([
'status' => 'error',
'message' => 'Data target tidak ditemukan.'
], 404);
}
return response()->json([
'status' => 'success',
'data' => $target
]);
}
/**
@@ -128,16 +155,67 @@ class TargetController extends Controller
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
public function update(Request $request, string $frameworkId, string $transformasiId, string $aktivitasId, string $id)
{
//
if (!Gate::any(['super_admin', 'admin'])) {
abort(403);
}
$target = Target::whereHas('aktivitas.transformasi', function ($query) use ($frameworkId) {
$query->where('framework_id', $frameworkId);
})
->whereHas('aktivitas', function ($query) use ($transformasiId) {
$query->where('transformasi_id', $transformasiId);
})
->where('aktivitas_id', $aktivitasId)->find($id);
if (!$target) {
return response()->json([
'status' => 'error',
'message' => 'Data target tidak ditemukan.'
], 404);
}
$validated = $this->validated($request);
$target->update([...$validated,
'updated_by' => auth()->id(),
]);
return response()->json([
'status' => 'success',
'message' => 'Target berhasil diperbarui.'
], 200);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
public function destroy(string $frameworkId, string $transformasiId, string $aktivitasId, string $id)
{
//
if (!Gate::any(['super_admin', 'admin'])) {
abort(403);
}
$target = Target::whereHas('aktivitas.transformasi', function ($query) use ($frameworkId) {
$query->where('framework_id', $frameworkId);
})
->whereHas('aktivitas', function ($query) use ($transformasiId) {
$query->where('transformasi_id', $transformasiId);
})
->where('aktivitas_id', $aktivitasId)->find($id);
if (!$target) {
return response()->json([
'status' => 'error',
'message' => 'Data target tidak ditemukan.'
], 404);
}
$target->update([
'deleted_by' => auth()->id(),
]);
$target->delete();
return response()->json([
'status' => 'success',
'message' => 'Target berhasil dihapus.'
], 200);
}
}
@@ -0,0 +1,107 @@
<?php
namespace App\Http\Controllers\Web\Program;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\View\View;
use Illuminate\Http\JsonResponse;
use App\Models\Program\Framework;
use App\Models\Program\Realisasi;
use App\Models\Program\Verifikasi;
class VerifikasiController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request): JsonResponse
{
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request): JsonResponse
{
if (!Gate::any(['super_admin', 'admin'])) {
abort(403);
}
$validated = $request->validate([
'realisasi_id' => ['required', 'integer', 'exists:program_realisasi,id'],
'dokumen' => ['required', 'string'],
'link_dokumen' => ['required', 'url'],
]);
$verifikasi = Verifikasi::create([...$validated,
'created_by' => auth()->id(),
'updated_by' => auth()->id()
]);
return response()->json([
'status' => 'success',
'message' => 'Dokumen bukti berhasil ditambahkan.'
], 201);
}
/**
* Display the specified resource.
*/
public function show(string $id): JsonResponse
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id): JsonResponse
{
if (!Gate::any(['super_admin', 'admin'])) {
abort(403);
}
$verifikasi = Verifikasi::find($id);
if (!$verifikasi) {
return response()->json([
'status' => 'error',
'message' => 'Data verifikasi tidak ditemukan.'
], 404);
}
$verifikasi->update(['updated_by' => auth()->id()]);
$verifikasi->delete();
return response()->json([
'status' => 'success',
'message' => 'Dokumen bukti berhasil dihapus.'
]);
}
}
@@ -49,6 +49,7 @@ class ProgramController extends Controller
'sub_domain' => ['required', Rule::unique(Program::class, 'sub_domain')],
'tahun' => ['required', 'integer'],
'instansi' => ['nullable', 'string'],
'nama_singkat' => ['required', 'string'],
]);
if ($validator->fails()) {
@@ -94,6 +95,9 @@ class ProgramController extends Controller
$validator = Validator::make($request->all(), [
'nama' => ['required'],
'sub_domain' => ['required', Rule::unique(Program::class, 'sub_domain')->ignore($id)],
'tahun' => ['required', 'integer'],
'instansi' => ['nullable', 'string'],
'nama_singkat' => ['required', 'string'],
]);
if ($validator->fails()) {
@@ -106,9 +110,7 @@ class ProgramController extends Controller
return redirect()->route('program.index')->with('error', 'Data program tidak ditemukan!');
}
$program->update([
'nama' => $request->nama,
'sub_domain' => $request->sub_domain,
$program->update([...$validator->validated(),
'updated_by' => auth()->id(),
]);
@@ -0,0 +1,27 @@
<?php
namespace App\Http\Resources\Core;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources\Pertanian\KomoditasResource;
class ProgramKomoditasResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'program_id' => $this->program_id,
'komoditas_id' => $this->komoditas_id,
'default' => $this->default,
'komoditas' => $this->komoditas ? new KomoditasResource($this->komoditas) : null,
];
}
}
@@ -0,0 +1,36 @@
<?php
namespace App\Http\Resources\Core;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources\Wilayah\ProvinsiResource;
use App\Http\Resources\Wilayah\KabupatenKotaResource;
use App\Http\Resources\Wilayah\KecamatanResource;
use App\Http\Resources\Wilayah\DesaKelurahanResource;
class ProgramLokasiResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'program_id' => $this->program_id,
'provinsi_id' => $this->provinsi_id,
'kabupaten_kota_id' => $this->kabupaten_kota_id,
'kecamatan_id' => $this->kecamatan_id,
'desa_kelurahan_id' => $this->desa_kelurahan_id,
'default' => $this->default,
'provinsi' => $this->provinsi ? new ProvinsiResource($this->provinsi) : null,
'kabupatenKota' => $this->kabupatenKota ? new KabupatenKotaResource($this->kabupatenKota) : null,
'kecamatan' => $this->kecamatan ? new KecamatanResource($this->kecamatan) : null,
'desaKelurahan' => $this->desaKelurahan ? new DesaKelurahanResource($this->desaKelurahan) : null,
];
}
}
@@ -0,0 +1,34 @@
<?php
namespace App\Http\Resources\Core;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources\Core\ProgramKomoditasResource;
use App\Http\Resources\Core\ProgramLokasiResource;
class ProgramResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'nama' => $this->nama,
'sub_domain' => $this->sub_domain,
'tahun' => $this->tahun,
'instansi' => $this->instansi,
'nama_singkat' => $this->nama_singkat,
'komoditas' => $this->komoditas()->with('komoditas')->get()->map(function ($item) {
return new ProgramKomoditasResource($item);
}),
'lokasi' => $this->lokasi()->with(['provinsi', 'kabupatenKota', 'kecamatan', 'desaKelurahan'])->get()->map(function ($item) {
return new ProgramLokasiResource($item);
}),
];
}
}
@@ -0,0 +1,32 @@
<?php
namespace App\Http\Resources\Ekosistem;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources\Ekosistem\KolomBasisDataResource;
class FormBasisDataResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'tabel_id' => $this->tabel_id,
'parent_id' => $this->parent_id,
'tipe' => $this->tipe,
'column_width' => $this->column_width,
'kolom_id' => $this->kolom_id,
'section_label' => $this->section_label,
'urutan' => $this->urutan,
'kolom' => new KolomBasisDataResource($this->whenLoaded('kolom')),
'form' => FormBasisDataResource::collection($this->whenLoaded('form')),
];
}
}
@@ -0,0 +1,32 @@
<?php
namespace App\Http\Resources\Ekosistem;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class KolomBasisDataResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'label' => $this->label,
'kolom' => $this->kolom,
'tipe' => $this->tipe,
'validasi' => $this->validasi,
'urutan' => $this->urutan,
'relasi_tabel' => $this->relasi_tabel,
'relasi_kolom_id' => $this->relasi_kolom_id,
'relasi_kolom_label' => $this->relasi_kolom_label,
'enum_opsi' => $this->enum_opsi,
'is_active' => $this->is_active,
'is_browse' => $this->is_browse,
];
}
}
@@ -0,0 +1,35 @@
<?php
namespace App\Http\Resources\Ekosistem;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Models\Ekosistem\TabelBasisData;
use App\Models\Ekosistem\KolomBasisData;
use App\Http\Resources\Ekosistem\KolomBasisDataResource;
class TabelBasisDataResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'tabel' => $this->tabel,
'label' => $this->label,
'menu' => $this->menu,
'deskripsi' => $this->deskripsi,
// 'group_id' => $this->group_id,
'urutan' => $this->urutan,
// 'group' => new GroupBasisDataResource($this->whenLoaded('group')),
'kolom' => KolomBasisDataResource::collection($this->whenLoaded('kolom')),
//'ringkasan' => RingkasanBasisDataResource::collection($this->whenLoaded('ringkasan')),
'form' => FormBasisDataResource::collection($this->whenLoaded('form')),
];
}
}
@@ -0,0 +1,34 @@
<?php
namespace App\Http\Resources\Ekosistem;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ValueBasisDataResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'program_id' => $this->program_id,
'tabel_id' => $this->tabel_id,
'kolom_id' => $this->kolom_id,
'value_id' => $this->value_id,
'value_varchar' => $this->value_varchar,
'value_integer' => $this->value_integer,
'value_float' => $this->value_float,
'value_boolean' => $this->value_boolean,
'value_date' => $this->value_date,
'value_datetime' => $this->value_datetime,
'value_text' => $this->value_text,
'value_relasi' => $this->value_relasi,
'value_enum' => $this->value_enum,
];
}
}
@@ -0,0 +1,26 @@
<?php
namespace App\Http\Resources\Pertanian;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class JenisKomoditasResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'nama' => $this->nama,
'kode' => $this->kode,
'keterangan' => $this->keterangan,
'created_at' => $this->created_at?->toIso8601String(),
'updated_at' => $this->updated_at?->toIso8601String(),
];
}
}
@@ -25,6 +25,7 @@ class LahanResource extends JsonResource
'luas_lahan' => $this->luas_lahan,
'keluarga_pengelola_id' => $this->keluarga_pengelola_id,
'adat_pemilik_id' => $this->adat_pemilik_id,
'uid' => $this->uid,
];
}
}
@@ -24,6 +24,7 @@ class TanamanResource extends JsonResource
'latitude' => $this->latitude,
'longitude' => $this->longitude,
'kondisi_tanaman' => $this->kondisi_tanaman,
'uid' => $this->uid,
];
}
}
+2
View File
@@ -7,6 +7,7 @@ use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources\Wilayah\DesaKelurahanResource;
use App\Http\Resources\UserResource;
use phpDocumentor\Reflection\Types\This;
class ProfileResource extends JsonResource
{
@@ -31,6 +32,7 @@ class ProfileResource extends JsonResource
'desa_kelurahan_id' => $this->desa_kelurahan_id,
'desa_kelurahan' => $this->desaKelurahan ? new DesaKelurahanResource($this->desaKelurahan) : null,
'user' => $this->user ? new UserResource($this->user) : null,
'user_uid' => $this->user_uid,
];
}
}
+1
View File
@@ -20,6 +20,7 @@ class UserResource extends JsonResource
'email' => $this->email,
'email_verified_at' => $this->email_verified_at,
'role' => $this->role,
'uid' => $this->uid,
];
}
}
+1
View File
@@ -20,6 +20,7 @@ class Program extends Model
"sub_domain",
"tahun",
"instansi",
"nama_singkat",
];
public function komoditas(): HasMany
+383
View File
@@ -0,0 +1,383 @@
<?php
namespace App\Models\Ekosistem;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Str;
use App\Models\Ekosistem\TabelBasisData;
use App\Enums\LayoutForm;
class FormBasisData extends Model
{
use SoftDeletes;
protected $table = 'ekosistem_basis_data_form';
protected $fillable = [
'tabel_id',
'parent_id',
'tipe',
'column_width',
'kolom_id',
'section_label',
'urutan',
'created_by',
'updated_by',
'deleted_by',
];
protected $casts = [
'tipe' => LayoutForm::class
];
protected static function booted()
{
static::creating(function ($model) {
$max = self::where('tabel_id', $model->tabel_id)->max('urutan');
$model->urutan = ($max ?? 0) + 1;
});
static::addGlobalScope('order', function (Builder $builder) {
$builder->orderBy('urutan');
});
}
public function tabel(): BelongsTo
{
return $this->belongsTo(TabelBasisData::class, 'tabel_id');
}
public function form(): HasMany
{
return $this->hasMany(FormBasisData::class, 'parent_id')
->with('form')
->orderBy('urutan');
}
public function kolom(): BelongsTo
{
return $this->belongsTo(KolomBasisData::class, 'kolom_id');
}
public function renderHtml(string $state, $errors = null, $optionsFK = null, $values = null, $framework = 'bootstrap'): string
{
if ($framework == 'tailwind') {
return match ($this->tipe) {
LayoutForm::COLUMN => '<div className="space-y-2 form-control-validation">'
.$this->childHtml($state, $errors, $optionsFK, $values, $framework).'</div>',
LayoutForm::VALUE => $this->value($state, $errors, $optionsFK, $values, $framework),
LayoutForm::SECTION => '<h3 className="font-semibold text-lg text-slate-900">'.$this->section_label.'</h3>',
LayoutForm::ROW => '<div className="grid grid-cols-1 gap-5">'.$this->childHtml($state, $errors, $optionsFK, $values, $framework).'</div>',
LayoutForm::HR_LINE => '<hr/>',
default => ''
};
} else {
return match ($this->tipe) {
LayoutForm::COLUMN => '<div class="col-'.($this->column_width ?? 3).' mb-4 form-control-validation">'
.$this->childHtml($state, $errors, $optionsFK, $values, $framework).'</div>',
LayoutForm::VALUE => $this->value($state, $errors, $optionsFK, $values, $framework),
LayoutForm::SECTION => '<h6 class="mb-3">'.$this->section_label.'</h6>',
LayoutForm::ROW => '<div class="row">'.$this->childHtml($state, $errors, $optionsFK, $values, $framework).'</div>',
LayoutForm::HR_LINE => '<hr/>',
default => ''
};
}
}
private function childHtml(string $state, $errors = null, $optionsFK = null, $values = null, $framework = 'bootstrap'): string
{
$html = '';
foreach ($this->form as $form) {
$html .= $form->renderHtml($state, $errors, $optionsFK, $values, $framework);
}
return $html;
}
private function value(string $state, $errors = null, $optionsFK = null, $values = null, $framework = 'bootstrap'): string
{
$html = '';
if ($this->kolom) {
$k = $this->kolom;
$value = $values ? $values->where('kolom_id', $k->id)->first() : null;
$oldValue = old($k->kolom);
if ($framework == 'tailwind') {
$html .= '<Label htmlFor="label" className="text-sm font-medium text-slate-700">kolom_required'.$this->kolom->label;
$html .= '</Label>';
} else {
$html .= '<label class="form-label" for="value-data-kolom_name">kolom_required'.$this->kolom->label;
$html .= '</label>';
}
if($k->tipe == 'varchar') {
if ($framework == 'tailwind') {
$html .= '<Input className="form-control kolom_error h-11" id="value-data-kolom_name" readonly
name="kolom_name" value="kolom_value" />';
} else {
$html .= '<input type="text" class="form-control kolom_error" id="value-data-kolom_name" readonly
name="kolom_name" value="kolom_value" />';
}
}
elseif($k->tipe == 'text') {
if ($framework == 'tailwind') {
$html .= '<Textarea className="form-control kolom_error h-11" id="value-data-kolom_name" readonly
name="kolom_name">kolom_value</Textarea>';
} else {
$html .= '<textarea class="form-control kolom_error" id="value-data-kolom_name" readonly
name="kolom_name">kolom_value</textarea>';
}
}
elseif($k->tipe == 'integer') {
if ($framework == 'tailwind') {
$html .= '<Input type="number" className="form-control kolom_error h-11" id="value-data-kolom_name" readonly
name="kolom_name" step="1" value="kolom_value" />';
} else {
$html .= '<input type="number" class="form-control" id="value-data-kolom_name" readonly
name="kolom_name" step="1" value="kolom_value" />';
}
}
elseif($k->tipe == 'float') {
if ($framework == 'tailwind') {
$html .= '<Input className="form-control kolom_error h-11" id="value-data-kolom_name" readonly
name="kolom_name" value="kolom_value" step="any" />';
} else {
$html .= '<input type="number" class="form-control kolom_error" id="add-data-kolom_name" readonly
name="kolom_name" value="kolom_value" step="any" />';
}
}
elseif($k->tipe == 'date') {
if ($framework == 'tailwind') {
$html .= '<Input type="date" className="form-control kolom_error h-11" id="value-data-kolom_name" readonly
name="kolom_name" value="kolom_value" />';
} else {
$html .= '<input type="date" class="form-control kolom_error" id="value-data-kolom_name" readonly
name="kolom_name" value="kolom_value" />';
}
}
elseif($k->tipe == 'datetime'){
if ($framework == 'tailwind') {
$html .= '<Input type="datetime-local" className="form-control kolom_error h-11" id="value-data-kolom_name" readonly
name="kolom_name" value="kolom_value" />';
} else {
$html .= '<input type="datetime-local" class="form-control kolom_error" id="value-data-kolom_name" readonly
name="kolom_name" value="kolom_value" />';
}
}
elseif($k->tipe == 'boolean') {
if ($state == 'preview' || $state == 'view') {
if ($framework == 'tailwind') {
$html .= '<Input type="text" className="form-control kolom_error h-11" id="value-data-kolom_name" readonly
name="kolom_name" value="kolom_value" />';
} else {
$html .= '<input type="text" class="form-control kolom_error" id="value-data-kolom_name" readonly
name="kolom_name" value="kolom_value" />';
}
} elseif ($state == 'add') {
if ($framework == 'tailwind') {
$html .= '<Select value="kolom_value">
<SelectTrigger className="border-slate-200 focus:ring-emerald-500 rounded-xl h-11 bg-white text-left">
<SelectValue />
</SelectTrigger>
<SelectContent className="rounded-xl">
<SelectItem value="1" className="rounded-lg">Ya</SelectItem>
<SelectItem value="0" className="rounded-lg">Tidak</SelectItem>
</SelectContent>
</Select>';
} else {
$html .= '<select class="form-select tipe-boolean kolom_error" id="value-data-kolom_name" name="kolom_name">
<option value="1" '. (old($k->kolom) == 1 ? 'selected' : '') .'>Ya</option>
<option value="0" '. (old($k->kolom) == 0 ? 'selected' : '') .'>Tidak</option>
</select>';
}
} elseif ($state == 'edit') {
if ($framework == 'tailwind') {
$html .= '<Select value="kolom_value">
<SelectTrigger className="border-slate-200 focus:ring-emerald-500 rounded-xl h-11 bg-white text-left">
<SelectValue />
</SelectTrigger>
<SelectContent className="rounded-xl">
<SelectItem value="1" className="rounded-lg">Ya</SelectItem>
<SelectItem value="0" className="rounded-lg">Tidak</SelectItem>
</SelectContent>
</Select>';
} else {
$html .= '<select class="form-select tipe-boolean kolom_error" id="value-data-kolom_name" name="kolom_name">
<option value="1" '. ((old($k->kolom) ?? (($value ? $value['value_'.$k->tipe] : null) ?? null) == 1) ? 'selected' : '') .'>Ya</option>
<option value="0" '. ((old($k->kolom) ?? (($value ? $value['value_'.$k->tipe] : null) ?? null) == 0) ? 'selected' : '') .'>Tidak</option>
</select>';
}
}
}
elseif($k->tipe == 'foreign_key') {
if ($state == 'preview' || $state == 'view') {
if ($framework == 'tailwind') {
$html .= '<Input type="text" className="form-control kolom_error h-11" id="value-data-kolom_name" readonly
name="kolom_name" value="kolom_value" />';
} else {
$html .= '<input type="text" class="form-control kolom_error" id="value-data-kolom_name" readonly
name="kolom_name" value="kolom_value" />';
}
} elseif ($state == 'add') {
if ($framework == 'tailwind') {
$html .= '<Select value="kolom_value" id="value-data-kolom_name" name="kolom_name" className="form-select tipe-foreign_key kolom_error">
<SelectTrigger className="border-slate-200 focus:ring-emerald-500 rounded-xl h-11 bg-white text-left">
<SelectValue />
</SelectTrigger>';
if ($optionsFK && $optionsFK->where('kolom_id', $k->id)->first() != null) {
foreach ($optionsFK->where('kolom_id', $k->id)->first()['options'] as $option) {
$html .= '<SelectContent className="rounded-xl">
<SelectItem value="'.$option->value.'" className="rounded-lg">'.$option->label.'</SelectItem>
</SelectContent>';
}
}
$html .= '</Select>';
} else {
$html .= '<select class="form-select tipe-foreign_key kolom_error" id="value-data-kolom_name" name="kolom_name">';
if ($optionsFK && $optionsFK->where('kolom_id', $k->id)->first() != null) {
foreach ($optionsFK->where('kolom_id', $k->id)->first()['options'] as $option) {
$html .= '<option value="'.$option->value.'" '.(old($k->kolom) == $option->value ? 'selected' : '').'>'.$option->label.'</option>';
}
}
$html .= '</select>';
}
} elseif ($state == 'edit') {
if ($framework == 'tailwind') {
$html .= '<Select value="kolom_value" id="value-data-kolom_name" name="kolom_name" className="form-select tipe-foreign_key kolom_error">
<SelectTrigger className="border-slate-200 focus:ring-emerald-500 rounded-xl h-11 bg-white text-left">
<SelectValue />
</SelectTrigger>';
if ($optionsFK && $optionsFK->where('kolom_id', $k->id)->first() != null) {
foreach ($optionsFK->where('kolom_id', $k->id)->first()['options'] as $option) {
$html .= '<SelectContent className="rounded-xl">
<SelectItem value="'.$option->value.'" className="rounded-lg" '.(old($k->kolom) ?? ($value ? ($value['value_relasi'] == $option->value) : null) ? 'selected' : '') .'>'.$option->label.'</SelectItem>
</SelectContent>';
}
}
$html .= '</Select>';
} else {
$html .= '<select class="form-select tipe-foreign_key kolom_error" id="value-data-kolom_name" name="kolom_name">';
if ($optionsFK && $optionsFK->where('kolom_id', $k->id)->first() != null) {
foreach ($optionsFK->where('kolom_id', $k->id)->first()['options'] as $option) {
$html .= '<option value="'.$option->value.'" '.(old($k->kolom) ?? ($value ? $value['value_relasi'] : null) == $option->value ? 'selected' : '').'>'.$option->label.'</option>';
}
}
$html .= '</select>';
}
}
}
elseif($k->tipe == 'enum') {
if ($state == 'preview' || $state == 'view') {
if ($framework == 'tailwind') {
$html .= '<Input type="text" className="form-control kolom_error h-11" id="value-data-kolom_name" readonly
name="kolom_name" value="kolom_value" />';
} else {
$html .= '<input type="text" class="form-control kolom_error" id="value-data-kolom_name" readonly
name="kolom_name" value="kolom_value" />';
}
} elseif ($state == 'add') {
if ($framework == 'tailwind') {
$html .= '<Select value="kolom_value" id="value-data-kolom_name" name="kolom_name" className="form-select tipe-enum kolom_error">
<SelectTrigger className="border-slate-200 focus:ring-emerald-500 rounded-xl h-11 bg-white text-left">
<SelectValue />
</SelectTrigger>';
foreach($k->enum_opsi as $opsi) {
$html .= '<SelectContent className="rounded-xl">
<SelectItem value="'.$opsi['value'].'" className="rounded-lg">'.$opsi['label'].'</SelectItem>
</SelectContent>';
}
$html .= '</Select>';
} else {
$html .= '<select class="form-select tipe-enum kolom_error" id="value-data-kolom_name" name="kolom_name">';
foreach($k->enum_opsi as $opsi) {
$html .= '<option value="' .$opsi['value']. '" ' .(old($k->kolom) == $opsi['value'] ? 'selected' : ''). '>' .$opsi['label']. '</option>';
}
$html .= '</select>';
}
} elseif ($state == 'edit') {
if ($framework == 'tailwind') {
$html .= '<Select value="kolom_value" id="value-data-kolom_name" name="kolom_name" className="form-select tipe-enum kolom_error">
<SelectTrigger className="border-slate-200 focus:ring-emerald-500 rounded-xl h-11 bg-white text-left">
<SelectValue />
</SelectTrigger>';
foreach($k->enum_opsi as $opsi) {
$html .= '<SelectContent className="rounded-xl">
<SelectItem value="'.$opsi['value'].'" className="rounded-lg">'.$opsi['label'].'</SelectItem>
</SelectContent>';
}
$html .= '</Select>';
} else {
$html .= '<select class="form-select tipe-enum kolom_error" id="value-data-kolom_name" name="kolom_name">';
foreach($k->enum_opsi as $opsi) {
$html .= '<option value="' .$opsi['value']. '" ' .(old($k->kolom) ?? ($value ? $value['value_'.$k->tipe] : null) == $opsi['value'] ? 'selected' : ''). '>' .$opsi['label']. '</option>';
}
$html .= '</select>';
}
}
}
else {
if ($framework == 'tailwind') {
$html .= '<Input type="text" className="form-control kolom_error h-11" id="value-data-kolom_name" readonly
name="kolom_name" value="kolom_value" />';
} else {
$html .= '<input type="text" class="form-control kolom_error" id="value-data-kolom_name" readonly
name="kolom_name" value="kolom_value" />';
}
}
$errorClass = $errors && $errors->has($k->kolom)
? 'is-invalid'
: '';
if ($state == 'preview') {
$html = Str::of($html)
->replace('kolom_value', '')
->replace('kolom_name', $k->kolom)
->replace('kolom_error', '')
->replace('kolom_required', '');
} elseif ($state == 'add') {
$html = Str::of($html)
->replace('kolom_value', e($oldValue))
->replace('kolom_name', $k->kolom)
->replace('kolom_error', $errorClass)
->replace('kolom_required', '')
->replace('readonly', '');
} elseif ($state == 'edit') {
$html = Str::of($html)
->replace('kolom_value', e($oldValue ?? ($value ? ($value['value_'.$k->tipe]) : null)))
->replace('kolom_name', $k->kolom)
->replace('kolom_error', $errorClass)
->replace('kolom_required', '')
->replace('readonly', '');
} elseif ($state == 'view') {
$html = Str::of($html)
->replace('kolom_name', $k->kolom)
->replace('kolom_error', '')
->replace('kolom_required', '');
if ($k->tipe == 'boolean') {
$html = Str::of($html)
->replace('kolom_value', e($value ? ($value['value_'.$k->tipe] == 1 ? 'Ya' : 'Tidak') : null));
} elseif($k->tipe == 'foreign_key') {
$html = Str::of($html)
->replace('kolom_value', e($value ? $value['value_relasi_label'] : null));
} elseif($k->tipe == 'enum') {
$html = Str::of($html)
->replace('kolom_value', e($value ? $value['value_enum_label'] : null));
} else {
$html = Str::of($html)
->replace('kolom_value', e($value ? $value['value_'.$k->tipe] : null));
}
}
}
return $html;
}
}
+20
View File
@@ -7,10 +7,14 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Casts\Attribute;
use App\Models\Ekosistem\KolomBasisData;
use App\Models\Ekosistem\GroupBasisData;
use App\Models\Ekosistem\RingkasanBasisData;
use App\Models\Ekosistem\FormBasisData;
class TabelBasisData extends Model
{
@@ -53,4 +57,20 @@ class TabelBasisData extends Model
{
return $this->hasMany(RingkasanBasisData::class, 'tabel_id')->orderBy('urutan');
}
public function form(): HasMany
{
return $this->hasMany(FormBasisData::class, 'tabel_id')
->whereNull('parent_id')
->orderBy('urutan');
}
public function renderHtml(string $state, $errors = null, $optionsFK = null, $values = null): string
{
$html = '';
foreach ($this->form as $form) {
$html .= $form->renderHtml($state, $errors, $optionsFK, $values);
}
return $html;
}
}
+1
View File
@@ -17,6 +17,7 @@ class Lahan extends Model
'status_kepemilikan',
'profile_id',
'desa_kelurahan_id',
'uid',
'path',
'luas_lahan',
'created_by',
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Models\Master;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Pertanian\Komoditas;
class JenisKomoditas extends Model
{
use HasFactory, SoftDeletes;
protected $table = 'jenis_komoditas';
protected $fillable = [
'nama',
'komoditas_id',
'created_by',
'updated_by',
'deleted_by',
];
public function komoditas()
{
return $this->belongsTo(Komoditas::class, 'komoditas_id');
}
public function varietas()
{
return $this->hasMany(Varietas::class, 'jenis_komoditas_id');
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Models\Master;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Pertanian\Komoditas;
use App\Models\Master\JenisKomoditas;
class Varietas extends Model
{
protected $table = 'master_varietas';
protected $fillable = [
'komoditas_id',
'jenis_komoditas_id',
'nama',
'deskripsi',
'created_by',
'updated_by',
];
public function jenisKomoditas()
{
return $this->belongsTo(JenisKomoditas::class, 'jenis_komoditas_id');
}
public function komoditas()
{
return $this->belongsTo(Komoditas::class, 'komoditas_id');
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace App\Models\Pertanian;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\HasMany;
class JenisKomoditas extends Model
{
use HasFactory, SoftDeletes;
/**
* Nama tabel yang terkait dengan model ini.
* Un-comment baris di bawah jika nama tabel di database tidak jamak (plural).
*
* @var string
*/
protected $table = 'jenis_komoditas';
/**
* Atribut yang dapat diisi secara massal (mass assignable).
*
* @var array<int, string>
*/
protected $fillable = [
'nama',
'kode',
'keterangan',
];
/**
* Atribut yang harus dikonversi ke tipe data lain (casting).
*
* @var array<string, string>
*/
protected $casts = [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
];
/**
* Relasi ke model Komoditas atau Tanaman (jika ada).
* Contoh: Satu Jenis Komoditas memiliki banyak data Tanaman/Komoditas turunannya.
*/
public function tanaman(): HasMany
{
return $this->hasMany(Tanaman::class, 'jenis_komoditas_id', 'id');
}
}
+12
View File
@@ -5,6 +5,8 @@ namespace App\Models\Pertanian;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Builder;
use App\Models\Master\JenisKomoditas;
use App\Models\Master\Varietas;
class Komoditas extends Model
{
@@ -30,4 +32,14 @@ class Komoditas extends Model
$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');
}
}
+5 -4
View File
@@ -19,6 +19,7 @@ class Lahan extends Model
protected $table = 'map_lahan';
protected $fillable = [
'uid',
'nama',
'status_kepemilikan',
'profile_id',
@@ -37,22 +38,22 @@ class Lahan extends Model
'path' => 'array',
];
public function profile()
public function profile(): BelongsTo
{
return $this->belongsTo(Profile::class, 'profile_id');
}
public function desaKelurahan()
public function desaKelurahan(): BelongsTo
{
return $this->belongsTo(DesaKelurahan::class, 'desa_kelurahan_id');
}
public function adat()
public function adat(): BelongsTo
{
return $this->belongsTo(Adat::class, 'adat_pemilik_id');
}
public function tanaman()
public function tanaman(): HasMany
{
return $this->hasMany(Tanaman::class, 'lahan_id');
}
+19 -5
View File
@@ -4,12 +4,11 @@ namespace App\Models\Pertanian;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Pertanian\Komoditas;
use App\Models\Pertanian\Lahan;
use App\Models\Profile;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use App\Models\Pertanian\Lahan;
use App\Models\Pertanian\Komoditas;
use App\Models\Master\JenisKomoditas;
use App\Models\Master\Varietas;
class Tanaman extends Model
{
@@ -18,13 +17,18 @@ class Tanaman extends Model
protected $table = 'map_tanaman';
protected $fillable = [
'uid',
'nama',
'jenis_komoditas_id',
'varietas_id',
'komoditas_id',
'profile_id',
'lahan_id',
'latitude',
'longitude',
'kondisi_tanaman',
'metode_pengumpulan',
'deskripsi',
'created_by',
'updated_by',
'deleted_by',
@@ -35,6 +39,16 @@ class Tanaman extends Model
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()
{
return $this->belongsTo(Profile::class, 'profile_id');
+3 -9
View File
@@ -4,11 +4,9 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\User;
use App\Models\Pertanian\Lahan;
use App\Models\Wilayah\DesaKelurahan;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Profile extends Model
{
@@ -17,13 +15,14 @@ class Profile extends Model
protected $fillable = [
'user_id',
'role',
'user_uid',
'nama',
'email',
'telepon',
'alamat',
'kk',
'ktp',
'uid',
'desa_kelurahan_id',
'path_kk',
'file_kk',
@@ -44,9 +43,4 @@ class Profile extends Model
{
return $this->belongsTo(User::class, 'user_id');
}
public function lahan(): HasMany
{
return $this->hasMany(Lahan::class, 'profile_id');
}
}
+18 -1
View File
@@ -23,6 +23,9 @@ class Aktivitas extends Model
"indikator",
"deskripsi",
"transformasi_id",
"parent_id",
"created_by",
"updated_by"
];
public function transformasi(): BelongsTo
@@ -32,6 +35,20 @@ class Aktivitas extends Model
public function target(): HasMany
{
return $this->hasMany(Target::class, 'aktivitas_id', 'id');
return $this->hasMany(Target::class, 'aktivitas_id', 'id')
->orderBy('id', 'asc');
}
public function parent(): BelongsTo
{
return $this->belongsTo(Aktivitas::class, 'parent_id', 'id');
}
public function aktivitas(): HasMany
{
return $this->hasMany(Aktivitas::class, 'parent_id', 'id')
->where('jenis_aktivitas', 'aktivitas')
->orderBy('kode', 'asc')
->orderBy('id', 'asc');
}
}
+5 -1
View File
@@ -20,6 +20,8 @@ class Framework extends Model
"nama",
"tahun",
"program_id",
"created_by",
"updated_by",
];
public function program(): BelongsTo
@@ -29,6 +31,8 @@ class Framework extends Model
public function transformasi(): HasMany
{
return $this->hasMany(Transformasi::class, 'framework_id', 'id');
return $this->hasMany(Transformasi::class, 'framework_id', 'id')
->orderBy('kode', 'asc')
->orderBy('id', 'asc');
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace App\Models\Program;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use App\Models\Program\Target;
class Realisasi extends Model
{
use SoftDeletes;
protected $table = 'program_realisasi';
protected $fillable = [
"target_id",
"realisasi_integer",
"realisasi_float",
"realisasi_boolean",
"created_by",
"updated_by",
"deleted_by",
];
public function target(): BelongsTo
{
return $this->belongsTo(Target::class, 'target_id', 'id');
}
public function verifikasi(): HasMany
{
return $this->hasMany(Verifikasi::class, 'realisasi_id', 'id')->oldest();
}
}
+17
View File
@@ -5,6 +5,8 @@ namespace App\Models\Program;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use App\Models\Program\Aktivitas;
@@ -27,10 +29,25 @@ class Target extends Model
"realisasi_float",
"realisasi_boolean",
"aktivitas_id",
"tipe_realisasi",
"value_query",
"created_by",
"updated_by",
"deleted_by",
];
public function aktivitas(): BelongsTo
{
return $this->belongsTo(Aktivitas::class, 'aktivitas_id', 'id');
}
public function realisasi(): HasMany
{
return $this->hasMany(Realisasi::class, 'target_id', 'id')->latest();
}
public function latestRealisasi(): HasOne
{
return $this->hasOne(Realisasi::class, 'target_id', 'id')->latest();
}
}
+6 -2
View File
@@ -27,8 +27,12 @@ class Transformasi extends Model
return $this->belongsTo(Framework::class, 'framework_id', 'id');
}
public function aktivitas(): HasMany
public function tujuan(): HasMany
{
return $this->hasMany(Aktivitas::class, 'transformasi_id', 'id');
return $this->hasMany(Aktivitas::class, 'transformasi_id', 'id')
->whereNull('parent_id')
->where('jenis_aktivitas', 'tujuan')
->orderBy('kode', 'asc')
->orderBy('id', 'asc');
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace App\Models\Program;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use App\Models\Program\Realisasi;
class Verifikasi extends Model
{
use SoftDeletes;
protected $table = 'program_verifikasi';
protected $fillable = [
"realisasi_id",
"dokumen",
"link_dokumen",
"created_by",
"updated_by",
"deleted_by",
];
public function realisasi(): BelongsTo
{
return $this->belongsTo(Realisasi::class, 'realisasi_id', 'id');
}
}
+2
View File
@@ -21,6 +21,8 @@ class User extends Authenticatable
'password',
'email_verified_at',
'role',
'username',
'uid'
];
protected $hidden = [
+1 -1
View File
@@ -1,6 +1,6 @@
<?php
namespace App\Models\wilayah;
namespace App\Models\Wilayah;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
+3 -1
View File
@@ -67,7 +67,9 @@ class AppServiceProvider extends ServiceProvider
});
View::composer('*', function ($view) {
$tabel_menu = TabelBasisData::has('kolom')->get();
$tabel_menu = TabelBasisData::whereHas('kolom', function ($query) {
$query->where('is_active', true);
})->get();
$view->with('tabel_menu', $tabel_menu);
});
+70
View File
@@ -0,0 +1,70 @@
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
use App\Models\Wilayah\Provinsi;
use App\Models\Wilayah\KabupatenKota;
use App\Models\Wilayah\Kecamatan;
use App\Models\Wilayah\DesaKelurahan;
class WilayahDashboard extends Component
{
public function render(): View|Closure|string
{
$kodeJawa = ['31', '32', '33', '34', '35', '36'];
$totalProvinsi = Provinsi::count();
$totalKabKota = KabupatenKota::count();
$totalKecamatan = Kecamatan::count();
$totalDesaKelurahan = DesaKelurahan::count();
$provinsiAktif = Provinsi::has('kabupatenKota')->count();
$provinsiKosong = $totalProvinsi - $provinsiAktif;
$totalKabupaten = KabupatenKota::whereRaw("SUBSTRING(kode, 3, 2) < '70'")->count();
$totalKota = KabupatenKota::whereRaw("SUBSTRING(kode, 3, 2) >= '70'")->count();
if ($totalKabupaten == 0 && $totalKota == 0 && $totalKabKota > 0) {
$totalKabupaten = $totalKabKota;
}
$kecamatanPerdesaan = Kecamatan::whereHas('kabupatenKota', function($query) {
$query->whereRaw("SUBSTRING(kode, 3, 2) < '70'");
})->count();
$kecamatanPerkotaan = max(0, $totalKecamatan - $kecamatanPerdesaan);
$terpetakan = DesaKelurahan::whereNotNull('path')->count();
$belumTerpetakan = max(0, $totalDesaKelurahan - $terpetakan);
$totalDesa = DesaKelurahan::where('nama', 'like', 'DESA%')->count() ?: $totalDesaKelurahan;
$totalKelurahan = DesaKelurahan::where('nama', 'like', 'KEL%')->count();
$data = [
'totalProvinsi' => $totalProvinsi,
'provinsiAktif' => $provinsiAktif,
'provinsiKosong' => $provinsiKosong,
'totalKabKota' => $totalKabKota,
'totalKabupaten' => $totalKabupaten,
'totalKota' => $totalKota,
'totalKecamatan' => $totalKecamatan,
'kecamatanPerdesaan' => $kecamatanPerdesaan,
'kecamatanPerkotaan' => $kecamatanPerkotaan,
'totalDesaKelurahan' => $totalDesaKelurahan,
'totalDesa' => $totalDesa,
'totalKelurahan' => $totalKelurahan,
'terpetakan' => $terpetakan,
'belumTerpetakan' => $belumTerpetakan,
];
return view('components.wilayah-dashboard', $data);
}
}
@@ -10,8 +10,10 @@ return new class extends Migration
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('uid')->unique();
$table->string('name');
$table->string('email')->unique();
$table->string('username')->unique();
$table->string('email')->unique()->nullable();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
@@ -23,17 +25,6 @@ return new class extends Migration
$table->softDeletes();
});
// Schema::create('user_roles', function (Blueprint $table) {
// $table->id();
// $table->foreignId('user_id')->unique()->constrained('users')->onDelete('cascade');
// $table->enum('role_name', ['Super Admin', 'Admin', 'Koordinator', 'Penyuluh', 'Pendamping', 'Petani'])->default('Petani');
// $table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
// $table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
// $table->foreignId('deleted_by')->nullable()->constrained('users')->onDelete('set null');
// $table->timestamps();
// $table->softDeletes();
// });
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
@@ -53,7 +44,6 @@ return new class extends Migration
public function down(): void
{
Schema::dropIfExists('users');
// Schema::dropIfExists('user_roles');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('sessions');
}
@@ -67,6 +67,7 @@ return new class extends Migration
$table->string('file_kk')->nullable();
$table->string('path_ktp', 500)->nullable();
$table->string('file_ktp')->nullable();
$table->string('user_uid');
$table->foreignId('keluarga_id')->nullable()->constrained('keluarga')->onDelete('set null');
@@ -22,13 +22,39 @@ return new class extends Migration
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
Schema::create('jenis_komoditas', function (Blueprint $table) {
$table->id();
$table->foreignId('komoditas_id')->constrained('master_komoditas')->onDelete('cascade');
$table->string('nama');
$table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('deleted_by')->nullable()->constrained('users')->onDelete('set null');
$table->timestamps();
$table->softDeletes();
});
Schema::create('master_varietas', function (Blueprint $table) {
$table->id();
$table->foreignId('komoditas_id')->constrained('master_komoditas')->onDelete('cascade');
$table->foreignId('jenis_komoditas_id')->constrained('jenis_komoditas')->onDelete('cascade');
$table->string('nama');
$table->text('deskripsi')->unique();
$table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('deleted_by')->nullable()->constrained('users')->onDelete('set null');
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('master_varietas');
Schema::dropIfExists('jenis_komoditas');
Schema::dropIfExists('master_komoditas');
}
};
@@ -13,6 +13,7 @@ return new class extends Migration
{
Schema::create('map_lahan', function (Blueprint $table) {
$table->id();
$table->string('uid')->unique();
$table->string('nama');
$table->string('status_kepemilikan');
@@ -21,7 +22,13 @@ return new class extends Migration
$table->foreignId('adat_pemilik_id')->nullable()->constrained('adat')->onDelete('set null');
$table->foreignId('desa_kelurahan_id')->nullable()->constrained('master_desa_kelurahan');
$table->text('path')->nullable();
$table->string('lat')->nullable();
$table->string('long')->nullable();
$table->jsonb('path')->nullable();
$table->boolean('sertifikat')->default(false);
$table->boolean('konflik')->default(false);
$table->text('deskripsi')->nullable();
$table->float('luas_lahan', 8)->nullable();
@@ -33,11 +40,32 @@ return new class extends Migration
$table->softDeletes();
});
Schema::create('sertifikat_lahan', function (Blueprint $table) {
$table->id();
$table->string('uid')->unique();
$table->string('nomor_sertifikat');
$table->date('tanggal_terbit');
$table->date('tanggal_kadaluarsa')->nullable();
$table->string('lembaga_penerbit')->nullable();
$table->foreignId('lahan_id')->constrained('map_lahan')->onDelete('cascade');
$table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('deleted_by')->nullable()->constrained('users')->onDelete('set null');
$table->timestamps();
$table->softDeletes();
});
Schema::create('map_tanaman', function (Blueprint $table) {
$table->id();
$table->string('uid')->unique();
$table->string('nama');
$table->foreignId('komoditas_id')->constrained('master_komoditas')->onDelete('set null');
$table->foreignId('jenis_komoditas_id')->constrained('jenis_komoditas')->onDelete('set null');
$table->foreignId('varietas_id')->constrained('master_varietas')->onDelete('set null');
$table->foreignId('profile_id')->constrained('profiles')->onDelete('set null');
$table->foreignId('lahan_id')->constrained('map_lahan')->onDelete('set null');
@@ -45,6 +73,8 @@ return new class extends Migration
$table->float('longitude')->nullable();
$table->string('kondisi_tanaman')->nullable();
$table->string('metode_pengumpulan')->nullable();
$table->text('deskripsi')->nullable();
$table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
@@ -61,6 +91,7 @@ return new class extends Migration
public function down(): void
{
Schema::dropIfExists('map_tanaman');
Schema::dropIfExists('sertifikat_lahan');
Schema::dropIfExists('map_lahan');
}
};
@@ -1,42 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('map_lahan', function (Blueprint $table) {
$table->jsonb('path')->nullable()->change();
});
Schema::table('master_provinsi', function (Blueprint $table) {
$table->jsonb('path')->nullable()->change();
});
Schema::table('master_kabupaten_kota', function (Blueprint $table) {
$table->jsonb('path')->nullable()->change();
});
Schema::table('master_kecamatan', function (Blueprint $table) {
$table->jsonb('path')->nullable()->change();
});
Schema::table('master_desa_kelurahan', function (Blueprint $table) {
$table->jsonb('path')->nullable()->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
}
};
@@ -18,6 +18,7 @@ return new class extends Migration
$table->string('sub_domain');
$table->integer('tahun');
$table->string('instansi')->nullable();
$table->string('nama_singkat', length: 50);
$table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
@@ -29,9 +29,15 @@ return new class extends Migration
$table->foreignId('tabel_id')->constrained('ekosistem_basis_data_tabel')->restrictOnDelete();
$table->string('kolom');
$table->string('label');
$table->enum('tipe', ['varchar', 'integer', 'float', 'boolean', 'date', 'datetime', 'text'])->default('varchar');
$table->enum('tipe', ['varchar', 'integer', 'float', 'boolean', 'date', 'datetime', 'text', 'foreign_key', 'enum'])->default('varchar');
$table->jsonb('validasi')->nullable();
$table->integer('urutan')->default(1);
$table->string('relasi_tabel')->nullable();
$table->string('relasi_kolom_id')->nullable();
$table->string('relasi_kolom_label')->nullable();
$table->boolean('is_active')->default('true');
$table->boolean('is_browse')->default('false');
$table->jsonb('enum_opsi')->nullable();
$table->timestamps();
$table->softDeletes();
@@ -60,6 +66,8 @@ return new class extends Migration
$table->date('value_date')->nullable();
$table->datetime('value_datetime')->nullable();
$table->text('value_text')->nullable();
$table->integer('value_relasi')->nullable();
$table->string('value_enum')->nullable();
$table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
@@ -1,25 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('profiles', function (Blueprint $table) {
$table->foreignId('adat_id')->nullable()->constrained('adat');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
}
};
@@ -13,9 +13,11 @@ return new class extends Migration
{
Schema::create('ekosistem_petani_cpm', function (Blueprint $table) {
$table->id();
$table->string('uid')->unique();
$table->foreignId('program_id')->nullable()->constrained('core_program')->nullOnDelete();
$table->string('nama');
$table->boolean('jenis_kelamin');
$table->string('tempat_lahir')->nullable();
$table->date('tanggal_lahir');
$table->string('pendidikan_terakhir')->nullable();
@@ -28,11 +30,17 @@ return new class extends Migration
$table->float('lama_bertani', 8)->nullable();
$table->float('luas_lahan', 8)->nullable();
$table->string('dusun_rt_rw')->nullable();
$table->string('nomor_telepon')->nullable();
$table->string('foto')->nullable();
$table->string('pekerjaan_sampingan')->nullable();
$table->foreignId('komoditas_sampingan_id')->nullable()->constrained('master_komoditas');
$table->float('lama_bertani_sampingan', 8)->nullable();
$table->float('luas_lahan_sampingan', 8)->nullable();
$table->string('status_perkawinan')->nullable();
$table->string('status_keluarga')->nullable();
$table->integer('jumlah_tanggungan')->nullable();
@@ -48,9 +56,6 @@ return new class extends Migration
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('ekosistem_petani_cpm');
@@ -1,55 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('ekosistem_basis_data_kolom', function (Blueprint $table) {
$table->string('relasi_tabel')->nullable();
$table->string('relasi_kolom_id')->nullable();
$table->string('relasi_kolom_label')->nullable();
$table->jsonb('enum_opsi')->nullable();
});
DB::statement("alter table public.ekosistem_basis_data_kolom drop constraint ekosistem_basis_data_kolom_tipe_check;");
DB::statement("
alter table public.ekosistem_basis_data_kolom add constraint ekosistem_basis_data_kolom_tipe_check check (((tipe)::text = any
((array[
'varchar'::character varying,
'integer'::character varying,
'float'::character varying,
'boolean'::character varying,
'date'::character varying,
'datetime'::character varying,
'text'::character varying,
'foreign_key'::character varying,
'enum'::character varying
])::text[])))
");
Schema::table('ekosistem_basis_data_value', function (Blueprint $table) {
$table->integer('value_relasi')->nullable();
$table->string('value_enum')->nullable();
});
Schema::table('ekosistem_petani_cpm', function (Blueprint $table) {
$table->foreignId('program_id')->nullable()->constrained('core_program')->nullOnDelete();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
}
};
@@ -13,6 +13,7 @@ return new class extends Migration
{
Schema::create('ekosistem_lahan_cpcl', function (Blueprint $table) {
$table->id();
$table->string('uid')->unique();
$table->string('nama');
$table->string('status_kepemilikan');
@@ -1,27 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('ekosistem_basis_data_kolom', function (Blueprint $table) {
$table->boolean('is_active')->default('true');
$table->boolean('is_browse')->default('false');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
}
};
@@ -0,0 +1,47 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('ekosistem_basis_data_form', function (Blueprint $table) {
$table->id();
$table->foreignId('tabel_id')->constrained('ekosistem_basis_data_tabel')->restrictOnDelete();
$table->foreignId('parent_id')->nullable()->constrained('ekosistem_basis_data_form')->restrictOnDelete();
$table->enum('tipe', ['form-row', 'form-column', 'form-value', 'form-section', 'form-hr-line'])->default('form-value');
$table->integer('column_width')->nullable();
$table->foreignId('kolom_id')->nullable()->constrained('ekosistem_basis_data_kolom')->nullOnDelete();
$table->string('section_label')->nullable();
$table->integer('urutan')->default(1);
$table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('deleted_by')->nullable()->constrained('users')->onDelete('set null');
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('ekosistem_basis_data_form');
}
};
@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('program_aktivitas', function (Blueprint $table) {
$table->foreignId('parent_id')->nullable()->constrained('program_aktivitas')->nullOnDelete();
});
Schema::table('program_target', function (Blueprint $table) {
$table->enum('tipe_realisasi', ['manual', 'referensi'])->default('manual');
$table->text('value_query')->nullable();
});
Schema::table('program_verifikasi', function (Blueprint $table) {
$table->foreignId('realisasi_id')->nullable()->constrained('program_realisasi')->nullOnDelete();
$table->dropColumn('aktivitas_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
}
};
+4 -2
View File
@@ -15,7 +15,9 @@ class AdminUserSeeder extends Seeder
public function run(): void
{
DB::table('users')->insert([
'uid' => 'AD-1234567891',
'name' => 'Admin',
'username' => 'admin',
'email' => 'admin@example.com',
'email_verified_at' => now(),
'password' => Hash::make('Asalada123'),
@@ -24,7 +26,7 @@ class AdminUserSeeder extends Seeder
'updated_at' => now(),
]);
Db::table('profiles')->insert([
DB::table('profiles')->insert([
'user_id' => 1,
'role' => 'super_admin',
'nama' => 'Admin',
@@ -43,7 +45,7 @@ class AdminUserSeeder extends Seeder
'updated_by' => 1,
'created_at' => now(),
'updated_at' => now(),
'adat_id' => null,
'user_uid' => 'AD-1234567891',
]);
}
}
+1
View File
@@ -20,6 +20,7 @@ class DatabaseSeeder extends Seeder
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
'uid' => 'AD-1234567890',
]);
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

+1 -1
View File
@@ -4,7 +4,7 @@
<div class="card-body">
<!-- Logo -->
<div class="app-brand justify-content-center mb-6">
<a href="{{ route('dashboard.produktivitas') }}" class="app-brand-link">
<a href="{{ route('dashboard.summary') }}" class="app-brand-link">
<span class="app-brand-logo demo">
<span class="text-primary">
<svg width="32" height="24" viewBox="0 0 32 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -0,0 +1,28 @@
@props(['title', 'mainCount', 'icon', 'color' => 'primary', 'subLabel1', 'subCount1', 'subLabel2', 'subCount2'])
<div class="card h-100 shadow-sm border-0">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<p class="text-muted fw-medium mb-1 small text-uppercase">{{ $title }}</p>
<h3 class="mb-0 fw-bold text-secondary">{{ number_format($mainCount, 0, ',', '.') }}</h3>
</div>
<div class="badge bg-label-{{ $color }} p-2 rounded">
<i class="ti tabler-{{ $icon }}" style="font-size: 1.5rem;"></i>
</div>
</div>
<div class="bg-lighter rounded p-2 mt-auto">
<div class="row text-center gx-0">
<div class="col-6 border-end">
<span class="text-muted d-block" style="font-size: 0.75rem;">{{ $subLabel1 }}</span>
<span class="fw-semibold text-dark small">{{ number_format($subCount1, 0, ',', '.') }}</span>
</div>
<div class="col-6">
<span class="text-muted d-block" style="font-size: 0.75rem;">{{ $subLabel2 }}</span>
<span class="fw-semibold text-dark small">{{ number_format($subCount2, 0, ',', '.') }}</span>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,50 @@
<div class="row g-3 mb-4">
<div class="col-12 col-sm-6 col-xl-3">
<x-wilayah-card
title="TOTAL PROVINSI"
:mainCount="$totalProvinsi"
icon="map-pin"
color="primary"
subLabel1="Data Aktif"
:subCount1="$provinsiAktif"
subLabel2="Data Kosong"
:subCount2="$provinsiKosong"
/>
</div>
<div class="col-12 col-sm-6 col-xl-3">
<x-wilayah-card
title="Kabupaten & Kota"
mainCount="{{ $totalKabKota }}"
icon="building-skyscraper"
color="info"
subLabel1="Kabupaten"
subCount1="{{ $totalKabupaten }}"
subLabel2="Kota"
subCount2="{{ $totalKota }}"
/>
</div>
<div class="col-12 col-sm-6 col-xl-3">
<x-wilayah-card
title="TOTAL KECAMATAN"
:mainCount="$totalKecamatan"
icon="map-pin"
color="warning"
subLabel1="Zona Perdesaan"
:subCount1="$kecamatanPerdesaan"
subLabel2="Zona Perkotaan"
:subCount2="$kecamatanPerkotaan"
/>
</div>
<div class="col-12 col-sm-6 col-xl-3">
<x-wilayah-card
title="DESA & KELURAHAN"
:mainCount="$totalDesaKelurahan"
icon="home-2"
color="success"
subLabel1="Terpetakan"
:subCount1="$terpetakan"
subLabel2="Blm Pemetaan"
:subCount2="$belumTerpetakan"
/>
</div>
</div>
+10 -2
View File
@@ -18,6 +18,12 @@
<input type="text" class="form-control" id="add-program-nama" name="nama" placeholder="Cth: Program Pertanian" />
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label" for="add-program-nama-singkat">Nama Singkat<span class="text-danger">*</span></label>
<input type="text" class="form-control" id="add-program-nama-singkat" name="nama_singkat" placeholder="Cth: PP" />
</div>
</div>
<div class="row">
<div class="col-md-3 mb-4 form-control-validation">
<label class="form-label" for="add-program-subdomain">Sub Domain<span class="text-danger">*</span></label>
@@ -51,12 +57,14 @@
rules: {
nama: { required: true },
sub_domain: { required: true },
tahun: { required: true }
tahun: { required: true },
nama_singkat: { required: true }
},
messages: {
nama: { required: "Nama program wajib diisi" },
sub_domain: { required: "Subdomain wajib diisi" },
tahun: { required: "Tahun wajib diisi" }
tahun: { required: "Tahun wajib diisi" },
nama_singkat: { required: "Nama singkat wajib diisi" }
},
errorElement: 'div',
errorPlacement: function (error, element) {
+10 -2
View File
@@ -19,6 +19,12 @@
<input type="text" class="form-control" id="add-program-nama" name="nama" placeholder="Cth: Program Pertanian" value="{{ old('nama', $program->nama) }}" />
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label" for="add-program-nama-singkat">Nama Singkat<span class="text-danger">*</span></label>
<input type="text" class="form-control" id="add-program-nama-singkat" name="nama_singkat" placeholder="Cth: PP" value="{{ old('nama_singkat', $program->nama_singkat) }}" />
</div>
</div>
<div class="row">
<div class="col-md-3 mb-4 form-control-validation">
<label class="form-label" for="add-program-subdomain">Sub Domain<span class="text-danger">*</span></label>
@@ -56,12 +62,14 @@
rules: {
nama: { required: true },
sub_domain: { required: true },
tahun: { required: true }
tahun: { required: true },
nama_singkat: { required: true }
},
messages: {
nama: { required: "Nama program wajib diisi" },
sub_domain: { required: "Subdomain wajib diisi" },
tahun: { required: "Tahun wajib diisi" }
tahun: { required: "Tahun wajib diisi" },
nama_singkat: { required: "Nama singkat wajib diisi" }
},
errorElement: 'div',
errorPlacement: function (error, element) {
@@ -17,6 +17,12 @@
<input type="text" class="form-control" id="add-program-nama" name="nama" placeholder="Cth: Program Pertanian" value="{{ old('nama', $program->nama) }}" readonly />
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label" for="add-program-nama-singkat">Nama Singkat<span class="text-danger">*</span></label>
<input type="text" class="form-control" id="add-program-nama-singkat" name="nama_singkat" placeholder="Cth: PP" value="{{ old('nama_singkat', $program->nama_singkat) }}" readonly />
</div>
</div>
<div class="row">
<div class="col-md-3 mb-4 form-control-validation">
<label class="form-label" for="add-program-subdomain">Sub Domain<span class="text-danger">*</span></label>
@@ -1,619 +0,0 @@
@extends('layout.main')
@push('style')
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>
body { background-color: #f8faf9; font-family: 'Public Sans', sans-serif; }
.card { border: none; border-radius: 15px; box-shadow: 0 4px 20px rgba(0,0,0,0.05); }
/* Map Container */
#map { height: 600px; width: 100%; border-radius: 15px; }
/* Sidebar Mini Stats */
.stat-card-mini { transition: all 0.3s ease; border-left: 5px solid transparent; }
.stat-card-mini:hover { transform: translateX(5px); border-left-color: var(--agri-green); }
/* Map Switcher Manual */
.map-controls-custom {
position: absolute; top: 20px; right: 20px; z-index: 1000;
background: white; padding: 5px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
/* Table Styling */
.table thead th { background-color: #f8f9fa; text-transform: uppercase; font-size: 11px; letter-spacing: 1px; }
.avatar-label { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border-radius: 10px; font-weight: bold; }
.leaflet-tooltip.leaflet-modern-tooltip {
background-color: transparent;
border: none;
box-shadow: none;
padding: 0;
color: #333;
font-family: 'Public Sans', sans-serif; /* Pastikan font dashboard Anda */
}
/* Menghilangkan panah kecil di bawah tooltip */
.leaflet-modern-tooltip:before {
border: none !important;
}
/* 2. Gaya untuk Modern Map Card kita */
.modern-map-card {
background: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12);
padding: 16px;
width: 190px; /* Lebar card */
border: 1px solid rgba(0,0,0,0.05);
}
/* Bagian Header */
.card-header-region {
margin-bottom: 8px;
}
.region-label {
font-size: 11px;
text-transform: uppercase;
color: #888;
font-weight: 500;
letter-spacing: 0.5px;
display: block;
margin-bottom: 2px;
}
.region-name {
font-weight: 700;
font-size: 15px;
margin: 0;
color: #1e1e1e;
line-height: 1.2;
}
/* Bagian Stats Row (Flexbox untuk menyusun ikon & teks) */
.d-flex { display: flex; }
.align-items-center { align-items: center; }
.mb-2 { margin-bottom: 8px; }
.me-2 { margin-right: 10px; }
.fw-bold { font-weight: 700; }
.small { font-size: 12px; }
/* Gaya Ikon Baris Statistik */
.stat-icon {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
font-size: 16px;
}
/* Warna Latar Belakang Ringan untuk Ikon (Bootstrap style) */
.bg-success-light { background-color: rgba(40, 167, 69, 0.1); }
.bg-primary-light { background-color: rgba(0, 123, 255, 0.1); }
.text-success { color: #28a745; }
.text-primary { color: #007bff; }
.stat-info {
flex: 1;
}
.stat-value {
font-size: 14px;
color: #222;
line-height: 1;
margin-top: 2px;
}
.text-muted { color: #6c757d; }
/* Bagian Footer Action */
.action-prompt {
font-size: 12px;
}
/* Sedikit animasi pada chevron saat di-hover */
.action-prompt i {
transition: transform 0.2s ease;
}
.leaflet-modern-tooltip:hover .action-prompt i {
transform: translateX(3px);
}
/* Gaya Bootstrap Helper Ringan (untuk jaga-jaga jika dashboard tidak memuatnya) */
.border-top { border-top: 1px solid rgba(0,0,0,0.08) !important; }
.border-bottom { border-bottom: 1px solid rgba(0,0,0,0.08) !important; }
.py-2 { padding-top: 8px !important; padding-bottom: 8px !important; }
.my-2 { margin-top: 8px !important; margin-bottom: 8px !important; }
.leaflet-popup-wrapper.leaflet-modern-popup-wrapper {
box-shadow: none;
}
.leaflet-modern-popup-wrapper .leaflet-popup-content-wrapper {
border-radius: 12px;
padding: 0 !important; /* Hapus padding default Leaflet agar full width */
overflow: hidden;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
border: 1px solid rgba(0,0,0,0.05);
}
.leaflet-modern-popup-wrapper .leaflet-popup-content {
margin: 0 !important;
padding: 0 !important;
}
/* Penyesuaian panah kecil di bawah */
.leaflet-modern-popup-wrapper .leaflet-popup-tip {
background: #f8f9fa; /* Sesuaikan dengan warna body footer popup (bg-light) */
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
/* 2. Gaya untuk Modern Lahan Popup kita */
.modern-lahan-popup {
font-family: 'Public Sans', sans-serif;
min-width: 270px; /* Lebar card */
}
/* Typography Hierarki */
.modern-lahan-popup .h5 { font-size: 16px; margin: 0; }
.modern-lahan-popup .fs-5 { font-size: 1.25rem; }
.modern-lahan-popup small { font-size: 11px; }
/* Header Farmer */
.rounded-top-12 { border-radius: 12px 12px 0 0; }
.header-farmer {
border-bottom: 1px solid rgba(0,0,0,0.06);
}
.label-header {
letter-spacing: 0.5px;
}
.farmer-name {
line-height: 1.2;
}
/* Body Details */
.details-body {
background-color: #f8f9fa;
}
.rounded-bottom-12 { border-radius: 0 0 12px 12px; }
.label-detail {
margin-bottom: 0px;
}
.value-detail {
font-size: 14px;
margin-top: 1px;
line-height: 1.1;
}
.value-commodity {
font-size: 13px;
line-height: 1.2;
}
/* Ikon Container (derived from stats card style) */
.icon-stat-container {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
}
/* Helper Colors (for icons & backgrounds) */
.text-danger-light { color: #dc3545 !important; }
.text-danger { color: #dc3545 !important; }
.text-success { color: #28a745 !important; }
/* Background Ringan (Bootstrap Style) */
.text-success-light { background-color: rgba(40, 167, 69, 0.1) !important; }
.text-danger-light { background-color: rgba(220, 53, 69, 0.1) !important; }
.bg-primary-light { background-color: rgba(0, 123, 255, 0.1) !important; }
/* Bootstrap-like Utilities (untuk berjaga-jaga jika dash belum memuatnya) */
.me-2 { margin-right: 10px !important; }
.mb-3 { margin-bottom: 16px !important; }
.ps-2 { padding-left: 12px !important; }
.col-6 { width: 50%; }
.col-12 { width: 100%; }
.border { border: 1px solid rgba(0,0,0,0.08) !important; }
</style>
@endpush
@section('content')
<div class="container-xxl flex-grow-1 container-p-y">
<div class="row">
<div class="col-lg-3">
<div class="card mb-3 stat-card-mini">
<div class="card-body">
<div class="d-flex justify-content-between">
<div class="avatar bg-label-success rounded p-2"><i class="ti tabler-mountain fs-3"></i></div>
<!-- <span class="badge bg-label-success">+2.4%</span> -->
</div>
<div class="mt-3">
<small class="text-muted text-uppercase">Total Land Area</small>
<h4 class="mb-0 fw-bold">{{ number_format($luasTotal, 1) }} <small class="text-muted">ha</small></h4>
</div>
</div>
</div>
<div class="card mb-3 stat-card-mini">
<div class="card-body">
<div class="d-flex justify-content-between">
<div class="avatar bg-label-success rounded p-2"><i class="ti tabler-map fs-3"></i></div>
<!-- <span class="badge bg-label-success"></span> -->
</div>
<div class="mt-3">
<small class="text-muted text-uppercase">Total Lahan</small>
<h4 class="mb-0 fw-bold">{{ $totallahan }}</h4>
</div>
</div>
</div>
<div class="card mb-3 stat-card-mini">
<div class="card-body">
<div class="d-flex justify-content-between">
<div class="avatar bg-label-primary rounded p-2"><i class="ti tabler-users fs-3"></i></div>
<span class="badge bg-success text-white text-center">Active</span>
</div>
<div class="mt-3">
<small class="text-muted text-uppercase">Registered Farmers</small>
<h4 class="mb-0 fw-bold">{{ $totalpetani }}</h4>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header"><h6 class="mb-0 fw-bold">Distribusi Komoditas</h6></div>
<div class="card-body">
@foreach(['Vanilla' => 45, 'Candlenut' => 30, 'Cocoa' => 15, 'Coffee' => 10] as $name => $val)
<div class="mb-3">
<div class="d-flex justify-content-between mb-1">
<small class="fw-bold">{{ $name }}</small>
<small class="text-muted">{{ $val }}%</small>
</div>
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" style="width: {{ $val }}%"></div>
</div>
</div>
@endforeach
</div>
</div>
</div>
<div class="col-lg-9">
<div class="card mb-4 overflow-hidden position-relative">
<div class="card-header d-flex justify-content-between align-items-center bg-white py-3">
<h6 class="mb-0 fw-bold"><i class="ti tabler-map-2 me-2"></i>Peta Sebaran Lahan</h6>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-secondary active" onclick="setMapLayer('satellite')">Satellite</button>
<button class="btn btn-outline-secondary" onclick="setMapLayer('topo')">Topographic</button>
<button class="btn btn-outline-secondary" onclick="setMapLayer('parcels')">Parcels</button>
</div>
</div>
<div id="map">
<button id="btnBackMap" class="btn btn-sm btn-dark shadow" style="display:none; position:absolute; top:20px; left:50px; z-index:1000;">
<i class="ti tabler-arrow-left me-1"></i> Kembali ke Atas
</button>
</div>
</div>
<div class="card">
<div class="card-header border-bottom d-flex justify-content-between align-items-center">
<h6 class="mb-0 fw-bold">Produktivitas Petani Regional</h6>
<button class="btn btn-sm btn-success"><i class="ti tabler-plus me-1"></i> Bulk Action</button>
</div>
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>Nama Petani</th>
<th>ID Lahan</th>
<th>Komoditas</th>
<th>Luas (HA)</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@foreach($petaniList as $p)
<tr>
<td>
<div class="d-flex align-items-center">
<div class="avatar-label bg-label-secondary me-2">{{ substr($p->nama, 0, 1) }}</div>
<span class="fw-bold">{{ $p->nama }}</span>
</div>
</td>
<td><span class="text-muted small">TR-{{ $p->id }}</span></td>
<td><span class="badge bg-label-info">Vanilla</span></td>
<td class="fw-bold">{{ number_format($p->lahan->sum('luas_lahan'), 1) }}</td>
<td><span class="badge bg-success">Verified</span></td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
var satellite = L.tileLayer('https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', { maxZoom: 22 });
var topo = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png');
var parcels = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png');
var map = L.map('map', { center: [-0.789, 113.921], zoom: 5, layers: [satellite], zoomControl: false });
L.control.zoom({ position: 'topleft' }).addTo(map);
function setMapLayer(type) {
map.removeLayer(satellite); map.removeLayer(topo); map.removeLayer(parcels);
if(type === 'satellite') satellite.addTo(map);
if(type === 'topo') topo.addTo(map);
if(type === 'parcels') parcels.addTo(map);
event.target.parentElement.querySelectorAll('.btn').forEach(b => b.classList.remove('active'));
event.target.classList.add('active');
}
// 2. Data dari Server
var dataLevels = {
'provinsi': @json($geoProvinsi),
'kabupaten': @json($geoKabupaten),
'kecamatan': @json($geoKecamatan),
'desa': @json($geoDesa),
'lahan': @json($geoLahan)
};
var currentLayerGroup = L.featureGroup().addTo(map);
var mapHistory = [];
var btnBack = document.getElementById('btnBackMap');
function ensureLatLon(coords) {
if (typeof coords[0] === 'number') {
if (Math.abs(coords[0]) > 90) {
return [coords[1], coords[0]];
}
return [coords[0], coords[1]];
}
return coords.map(ensureLatLon);
}
function renderLevel(level, parentId = null, fallbackBounds = null) {
var dataToRender = dataLevels[level];
if (parentId !== null) {
dataToRender = dataToRender.filter(item => item.parent_id == parentId);
}
if (dataToRender.length === 0) {
alert(`Belum ada data pemetaan ${level} yang terdaftar di wilayah ini.`);
if (mapHistory.length > 0) {
mapHistory.pop();
}
return;
}
currentLayerGroup.clearLayers();
if (level === 'lahan' && parentId !== null) {
var parentDesa = dataLevels['desa'].find(d => d.id == parentId);
if (parentDesa && parentDesa.polygon) {
try {
var desaGeojson = typeof parentDesa.polygon === 'string' ? JSON.parse(parentDesa.polygon) : parentDesa.polygon;
var desaLayer;
var desaStyle = {
color: '#dc3545',
weight: 2,
dashArray: '5, 8',
fillColor: 'none',
interactive: false
};
if (desaGeojson !== null && typeof desaGeojson === 'object' && desaGeojson.type) {
desaLayer = L.geoJSON(desaGeojson, {
style: desaStyle,
coordsToLatLng: function (coords) {
var a = coords[0], b = coords[1];
if (Math.abs(a) > 90) return L.latLng(b, a);
if (Math.abs(b) > 90) return L.latLng(a, b);
return L.latLng(b, a);
}
});
} else if (Array.isArray(desaGeojson)) {
var safeCoordsDesa = ensureLatLon(desaGeojson);
desaLayer = L.polygon(safeCoordsDesa, desaStyle);
}
if (desaLayer) {
currentLayerGroup.addLayer(desaLayer);
}
} catch (e) {
console.error("Gagal render batas desa:", e);
}
}
}
dataToRender.forEach(function(item) {
if (!item.polygon) return;
try {
var geojsonData = typeof item.polygon === 'string' ? JSON.parse(item.polygon) : item.polygon;
var layer;
var styleOpts = level === 'lahan'
? { color: '#28a745', weight: 2, fillColor: '#28a745', fillOpacity: 0.6 }
: { color: '#f39c12', weight: 2, fillColor: '#f1c40f', fillOpacity: 0.3 };
if (geojsonData !== null && typeof geojsonData === 'object' && geojsonData.type) {
layer = L.geoJSON(geojsonData, {
style: styleOpts,
coordsToLatLng: function (coords) {
var a = coords[0], b = coords[1];
if (Math.abs(a) > 90) return L.latLng(b, a);
if (Math.abs(b) > 90) return L.latLng(a, b);
return L.latLng(b, a);
}
});
} else if (Array.isArray(geojsonData)) {
var safeCoords = ensureLatLon(geojsonData);
layer = L.polygon(safeCoords, styleOpts);
}
if (layer) {
if (level === 'lahan') {
var listTanaman = item.tanaman && item.tanaman.length > 0 ? item.tanaman.join(', ') : null;
var komoditasText = listTanaman
? listTanaman
: '<span class="text-danger-light fw-medium small">Belum terdaftar</span>';
var cropIcon = listTanaman ? 'ti tabler-plant' : 'ti tabler-leaf-off';
var cropIconColor = listTanaman ? 'text-success' : 'text-danger';
var popupContent = `
<div class="modern-lahan-popup">
<div class="p-3 bg-white rounded-top-12 header-farmer">
<div class="d-flex align-items-center mb-1">
<small class="label-header text-uppercase text-muted fw-semibold">Pemilik Lahan</small>
</div>
<h6 class="mb-0 fw-bold farmer-name h5">${item.nama_petani}</h6>
</div>
<div class="p-3 bg-light rounded-bottom-12 details-body">
<div class="row g-2 mb-3">
<div class="col-12 d-flex align-items-center">
<div class="icon-stat-container rounded-circle bg-white text-muted border me-2">
<i class="ti tabler-hash fs-5"></i>
</div>
<div>
<small class="d-block text-muted text-uppercase fw-medium label-detail">ID Lahan</small>
<span class="fw-bold text-dark value-detail">TR-${item.id}</span>
</div>
</div>
</div>
<div class="row g-1">
<div class="col-6 d-flex align-items-center">
<div class="icon-stat-container rounded ${cropIconColor}-light me-2">
<i class="${cropIcon} fs-5 ${cropIconColor}"></i>
</div>
<div class="flex-grow-1">
<small class="d-block text-muted text-uppercase fw-medium label-detail">Komoditas</small>
<span class="fw-semibold text-dark value-commodity">${komoditasText}</span>
</div>
</div>
<div class="col-6 d-flex align-items-center border-start ps-2">
<div class="icon-stat-container rounded bg-primary-light me-2">
<i class="ti tabler-dimensions fs-5 text-primary"></i>
</div>
<div>
<small class="d-block text-muted text-uppercase fw-medium label-detail">Luas Lahan</small>
<span class="fw-bold text-dark value-detail">${parseFloat(item.luas).toFixed(1)} Ha</span>
</div>
</div>
</div>
</div>
</div>
`;
layer.bindPopup(popupContent, {
maxWidth: 300,
closeButton: true,
offset: [0, 0],
className: 'leaflet-modern-popup-wrapper'
});
} else {
var tooltipContent = `
<div class="modern-map-card">
<div class="card-header-region">
<span class="region-label">Wilayah</span>
<h6 class="region-name">${item.nama}</h6>
</div>
<div class="card-body-stats border-top border-bottom py-2 my-2">
<div class="stat-row d-flex align-items-center mb-2">
<div class="stat-icon bg-success-light text-success me-2 rounded-p-2">
<i class="ti tabler-map"></i>
</div>
<div class="stat-info">
<small class="text-muted text-uppercase fw-medium" style="font-size: 10px;">Jumlah Lahan</small>
<div class="stat-value fw-bold">${parseInt(item.total_lahan)}</div>
</div>
</div>
<div class="stat-row d-flex align-items-center">
<div class="stat-icon bg-primary-light text-primary me-2 rounded-p-2">
<i class="ti tabler-area"></i>
</div>
<div class="stat-info">
<small class="text-muted text-uppercase fw-medium" style="font-size: 10px;">Total Luas</small>
<div class="stat-value fw-bold">${parseFloat(item.total_luas).toFixed(2)} <small class="text-muted">Ha</small></div>
</div>
</div>
</div>
<div class="card-footer-action text-center mt-2">
<span class="action-prompt text-primary fw-medium small">
Klik untuk detail <i class="ti tabler-chevron-right ms-1"></i>
</span>
</div>
</div>
`;
layer.bindTooltip(tooltipContent, {
sticky: true,
direction: 'top',
className: 'leaflet-modern-tooltip',
offset: [0, -10]
});
layer.on('click', function(e) {
L.DomEvent.stopPropagation(e);
var nextLevel = level === 'provinsi' ? 'kabupaten'
: level === 'kabupaten' ? 'kecamatan'
: level === 'kecamatan' ? 'desa' : 'lahan';
mapHistory.push({ level: level, parentId: parentId, bounds: currentLayerGroup.getBounds() });
renderLevel(nextLevel, item.id, e.target.getBounds());
});
}
currentLayerGroup.addLayer(layer);
}
} catch (e) {
console.error("Gagal memproses data level", level, "ID", item.id, e);
}
});
if (btnBack) btnBack.style.display = mapHistory.length > 0 ? 'block' : 'none';
if (currentLayerGroup.getLayers().length > 0) {
var bounds = currentLayerGroup.getBounds();
if (bounds.isValid()) {
map.fitBounds(bounds, { padding: [30, 30], maxZoom: 18 });
}
} else if (fallbackBounds && fallbackBounds.isValid()) {
map.fitBounds(fallbackBounds, { padding: [30, 30], maxZoom: 18 });
}
}
if (btnBack) {
btnBack.addEventListener('click', function() {
var prevState = mapHistory.pop();
if (prevState) {
renderLevel(prevState.level, prevState.parentId);
if (prevState.bounds && prevState.bounds.isValid()) {
map.fitBounds(prevState.bounds, { padding: [30, 30] });
}
}
});
}
setTimeout(() => {
map.invalidateSize();
// Render Awal: Provinsi
renderLevel('provinsi', null);
}, 300);
</script>
@endpush
+8 -8
View File
@@ -33,14 +33,14 @@
<!-- Header Section -->
<div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-6">
<div>
<h2 class="text-2xl font-bold teks-header-judul">
Ringkasan M&E (Theory of Change)
</h2>
<p class="text-sm mt-1 teks-header-sub">
Capaian Utama Transformasi Bioekonomi Desa Ngada
</p>
</div>
<h2 class="text-2xl font-bold teks-header-judul">
Ringkasan M&E (Theory of Change)
</h2>
<p class="text-sm mt-1 teks-header-sub">
Capaian Utama Transformasi Bioekonomi Desa Ngada
</p>
</div>
<div class="flex gap-2">
<button class="bg-white border border-gray-200 text-gray-600 px-4 py-2 rounded-lg text-sm font-medium hover:bg-gray-50 flex items-center gap-2">
<i data-lucide="download" class="w-4 h-4"></i> Export PDF
@@ -0,0 +1,81 @@
<div
class="offcanvas offcanvas-end"
tabindex="-1"
id="offcanvasAddColumn"
aria-labelledby="offcanvasAddColumnLabel">
<div class="offcanvas-header border-bottom">
<h5 id="offcanvasAddColumnLabel" class="offcanvas-title">Tambah Kolom</h5>
<button
type="button"
class="btn-close text-reset"
data-bs-dismiss="offcanvas"
aria-label="Tutup"></button>
</div>
<div class="offcanvas-body mx-0 flex-grow-0 p-6 h-100">
<form class="add-new-column pt-0" id="addColumnForm" action="{{ route('tabel.formulir.store', $tabel->id) }}" method="POST">
@csrf
<input type="hidden" id="add-column-tipe" name="tipe" value="form-column" />
<input type="hidden" id="add-column-parent" name="parent_id" value="" />
<div class="mb-6 form-control-validation">
<label class="form-label" for="add-column-width">Lebar Kolom (1-12)</label>
<input type="number" min="1" max="12" step="1" value="3"
class="form-control" id="add-column-width" name="column_width" placeholder="Cth: 3" />
</div>
<button type="submit" class="btn btn-success me-3 data-submit">Simpan</button>
<button type="reset" class="btn btn-label-danger" data-bs-dismiss="offcanvas">Batal</button>
</form>
</div>
</div>
@push('scripts')
<script>
$(document).ready(function() {
const offcanvasAddColumn = new bootstrap.Offcanvas(document.getElementById('offcanvasAddColumn'));
$('#addColumnForm').validate({
rules: {
column_width: { required: true, number: true },
},
messages: {
column_width: { required: "Lebar kolom wajib diisi" },
},
submitHandler: function (form) {
$.ajax({
url: $(form).attr('action'),
type: 'POST',
data: $(form).serialize(),
success: function () {
offcanvasAddColumn.hide();
form.reset();
Swal.fire({ icon: 'success', title: 'Berhasil!', showConfirmButton: false, timer: 1500 })
.then(() => window.location.reload());
},
error: function (xhr) {
Swal.fire({ icon: 'error', title: 'Gagal!', text: xhr.responseJSON?.message || 'Error' });
}
});
},
errorElement: 'div',
errorPlacement: function (error, element) {
error.addClass('invalid-feedback');
element.closest('.mb-6, .form-control-validation').append(error);
},
highlight: function (element) {
$(element).addClass('is-invalid');
},
unhighlight: function (element) {
$(element).removeClass('is-invalid');
}
});
$('.add-column').click(function(e) {
e.preventDefault();
$('#add-column-parent').val($(this).data('parent'));
offcanvasAddColumn.show();
});
});
</script>
@endpush
@@ -0,0 +1,27 @@
@push('scripts')
<script>
$(document).ready(function() {
function addForm(tipe) {
$.ajax({
url: '{{ route('tabel.formulir.store', [$tabel->id]) }}',
type: 'POST',
data: {
tipe: tipe,
_token: '{{ csrf_token() }}',
},
success: function () {
Swal.fire({ icon: 'success', title: 'Berhasil!', showConfirmButton: false, timer: 1500 })
.then(() => window.location.reload());
},
error: function (xhr) {
Swal.fire({ icon: 'error', title: 'Gagal!', text: xhr.responseJSON?.message || 'Error' });
}
});
}
$(".add-hr-line").click(function (e) {
e.preventDefault();
addForm('form-hr-line');
});
});
</script>
@endpush

Some files were not shown because too many files have changed in this diff Show More