diff --git a/app/Http/Controllers/Web/Ekosistem/GroupBasisDataController.php b/app/Http/Controllers/Web/Ekosistem/GroupBasisDataController.php
new file mode 100644
index 0000000..f83f687
--- /dev/null
+++ b/app/Http/Controllers/Web/Ekosistem/GroupBasisDataController.php
@@ -0,0 +1,138 @@
+query('per_page', 10);
+ $search = strtolower($request->query('search', ''));
+
+ $group = GroupBasisData::with('tabel')
+ ->whereRaw('lower(\'group\') LIKE ?', ["%$search%"])
+ ->paginate($perPage);
+ return view('ekosistem.group-basis-data.index', compact('title', 'group'));
+ }
+
+ /**
+ * Show the form for creating a new resource.
+ */
+ public function create(): View
+ {
+ return view('ekosistem.group-basis-data.add');
+ }
+
+ /**
+ * Store a newly created resource in storage.
+ */
+ public function store(Request $request): RedirectResponse
+ {
+ Gate::authorize('super_admin');
+
+ $validated = $request->validate([
+ 'group' => ['required',
+ 'string',
+ 'regex:/^[a-z0-9]+(_[a-z0-9]+)*$/',
+ 'unique:ekosistem_basis_data_group,group'
+ ],
+ 'label' => ['required', 'string'],
+ 'urutan' => ['nullable', 'integer', 'min:1'],
+ 'deskripsi' => ['nullable', 'string'],
+ ]);
+
+ GroupBasisData::create([...$validated,
+ 'created_by' => auth()->id(),
+ 'updated_by' => auth()->id(),
+ ]);
+
+ return redirect()->route('group.index')->with('success', 'Kelompok basis data berhasil ditambahkan.');
+ }
+
+ /**
+ * Display the specified resource.
+ */
+ public function show(string $id): View
+ {
+ Gate::authorize('super_admin');
+ $group = GroupBasisData::find($id);
+ if (!$group) {
+ return redirect()->route('group-basis-data.index')->with('error', 'Kelompok basis data tidak ditemukan.');
+ }
+ return view('ekosistem.group-basis-data.view', compact('group'));
+ }
+
+ /**
+ * Show the form for editing the specified resource.
+ */
+ public function edit(string $id): View
+ {
+ Gate::authorize('super_admin');
+
+ $group = GroupBasisData::find($id);
+ if (!$group) {
+ return redirect()->route('group-basis-data.index')->with('error', 'Kelompok basis data tidak ditemukan.');
+ }
+
+ return view('ekosistem.group-basis-data.edit', compact('group'));
+ }
+
+ /**
+ * Update the specified resource in storage.
+ */
+ public function update(Request $request, string $id): RedirectResponse
+ {
+ Gate::authorize('super_admin');
+
+ $group = GroupBasisData::find($id);
+ if (!$group) {
+ return redirect()->route('group-basis-data.index')->with('error', 'Kelompok basis data tidak ditemukan.');
+ }
+
+ $validated = $request->validate([
+ 'group' => ['required',
+ 'string',
+ 'regex:/^[a-z0-9]+(_[a-z0-9]+)*$/',
+ 'unique:ekosistem_basis_data_tabel,tabel'
+ ],
+ 'label' => ['required', 'string'],
+ 'urutan' => ['nullable', 'integer', 'min:1'],
+ 'deskripsi' => ['nullable', 'string'],
+ ]);
+
+ $group->update([...$validated,
+ 'updated_by' => auth()->id(),
+ ]);
+
+ return redirect()->route('group.index')->with('success', 'Kelompok basis data berhasil diperbarui.');
+ }
+
+ /**
+ * Remove the specified resource from storage.
+ */
+ public function destroy(string $id)
+ {
+ Gate::authorize('super_admin');
+
+ $group = TabelBasisData::find($id);
+ if (!$group) {
+ return redirect()->route('group.index')->with('error', 'Kelompok basis data tidak ditemukan.');
+ }
+
+ $group->delete();
+
+ return redirect()->route('group.index')->with('success', 'Kelompok basis data berhasil dihapus.');
+ }
+}
diff --git a/app/Http/Controllers/Web/Ekosistem/KolomBasisDataController.php b/app/Http/Controllers/Web/Ekosistem/KolomBasisDataController.php
new file mode 100644
index 0000000..0a55575
--- /dev/null
+++ b/app/Http/Controllers/Web/Ekosistem/KolomBasisDataController.php
@@ -0,0 +1,218 @@
+json([
+ 'status' => 'error',
+ 'message' => 'Data tabel tidak ditemukan.'
+ ], 404);
+ }
+
+ $validated = $request->validate([
+ 'kolom' => ['required',
+ 'string',
+ 'regex:/^[a-z0-9]+(_[a-z0-9]+)*$/',
+ Rule::unique('ekosistem_basis_data_kolom')->where(function ($query) use ($request) {
+ return $query->where('tabel_id', $request->tabel_id)->whereNull('deleted_at');
+ })
+ ],
+ 'label' => ['required', 'string'],
+ 'tipe' => ['required', 'string', Rule::in(['varchar', 'integer', 'text', 'date', 'boolean', 'datetime', 'float'])],
+ 'validasi' => ['nullable', 'json'],
+ ]);
+
+ KolomBasisData::create([...$validated,
+ 'tabel_id' => $tabelId,
+ 'created_by' => auth()->id(),
+ 'updated_by' => auth()->id(),
+ ]);
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => 'Kolom basis data berhasil ditambahkan.'
+ ], 201);
+ }
+
+ /**
+ * Display the specified resource.
+ */
+ public function show(Request $request, string $tabelId, string $id): JsonResponse
+ {
+ Gate::authorize('super_admin');
+
+ $kolom = KolomBasisData::where('tabel_id', $tabelId)->find($id);
+ if (!$kolom) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Data kolom tidak ditemukan.'
+ ], 404);
+ }
+
+ return response()->json(['data' => $kolom, 'status' => 'success']);
+ }
+
+ /**
+ * 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 $tabelId, string $id): JsonResponse
+ {
+ Gate::authorize('super_admin');
+
+ $kolom = KolomBasisData::where('tabel_id', $tabelId)->find($id);
+ if (!$kolom) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Data kolom tidak ditemukan.'
+ ], 404);
+ }
+
+ $validated = $request->validate([
+ 'kolom' => ['required',
+ 'string',
+ 'regex:/^[a-z0-9]+(_[a-z0-9]+)*$/',
+ Rule::unique('ekosistem_basis_data_kolom')->where(function ($query) use ($request) {
+ return $query->where('tabel_id', $request->tabel_id)->whereNull('deleted_at');
+ })
+ ],
+ 'label' => ['required', 'string'],
+ 'tipe' => ['required', 'string', Rule::in(['varchar', 'integer', 'text', 'date', 'boolean', 'datetime', 'float'])],
+ 'validasi' => ['nullable', 'json'],
+ ]);
+
+ $kolom->update([...$validated,
+ 'created_by' => auth()->id(),
+ 'updated_by' => auth()->id(),
+ ]);
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => 'Kolom basis data berhasil diperbarui.'
+ ], 200);
+ }
+
+ /**
+ * Remove the specified resource from storage.
+ */
+ public function destroy(Request $request, string $tabelId, string $id): JsonResponse
+ {
+ Gate::authorize('super_admin');
+ $kolom = KolomBasisData::where('tabel_id', $tabelId)->find($id);
+ if (!$kolom) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Data kolom tidak ditemukan.'
+ ], 404);
+ }
+ $kolom->delete();
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => 'Kolom basis data berhasil dihapus.'
+ ], 200);
+ }
+
+ public function moveUp(Request $request, string $tabelId, string $id): JsonResponse
+ {
+ Gate::authorize('super_admin');
+
+ $current = KolomBasisData::where('tabel_id', $tabelId)->find($id);
+ if (!$current) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Data kolom tidak ditemukan.'
+ ], 404);
+ }
+
+ $prev = KolomBasisData::where('tabel_id', $current->tabel_id)
+ ->where('urutan', '<', $current->urutan)
+ ->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' => 'Kolom basis data berhasil diperbarui.'
+ ], 200);
+ }
+
+ public function moveDown(Request $request, string $tabelId, string $id): JsonResponse
+ {
+ Gate::authorize('super_admin');
+
+ $current = KolomBasisData::where('tabel_id', $tabelId)->find($id);
+ if (!$current) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Data kolom tidak ditemukan.'
+ ], 404);
+ }
+
+ $next = KolomBasisData::where('tabel_id', $current->tabel_id)
+ ->where('urutan', '>', $current->urutan)
+ ->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' => 'Kolom basis data berhasil diperbarui.'
+ ], 200);
+ }
+}
diff --git a/app/Http/Controllers/Web/Ekosistem/TabelBasisDataController.php b/app/Http/Controllers/Web/Ekosistem/TabelBasisDataController.php
new file mode 100644
index 0000000..6532d62
--- /dev/null
+++ b/app/Http/Controllers/Web/Ekosistem/TabelBasisDataController.php
@@ -0,0 +1,204 @@
+query('per_page', 10);
+ $search = strtolower($request->query('search', ''));
+
+ $tabel = TabelBasisData::with('kolom')
+ ->whereRaw('lower(tabel) LIKE ?', ["%$search%"])
+ ->paginate($perPage);
+ return view('ekosistem.tabel-basis-data.index', compact('title', 'tabel'));
+ }
+
+ /**
+ * Show the form for creating a new resource.
+ */
+ public function create(): View
+ {
+ $group = GroupBasisData::all();
+ return view('ekosistem.tabel-basis-data.add', compact('group'));
+ }
+
+ /**
+ * Store a newly created resource in storage.
+ */
+ public function store(Request $request): RedirectResponse
+ {
+ Gate::authorize('super_admin');
+
+ $validated = $request->validate([
+ 'tabel' => ['required',
+ 'string',
+ 'regex:/^[a-z0-9]+(_[a-z0-9]+)*$/',
+ 'unique:ekosistem_basis_data_tabel,tabel'
+ ],
+ 'label' => ['required', 'string'],
+ 'menu' => ['required', 'string'],
+ 'deskripsi' => ['nullable', 'string'],
+ 'group_id' => ['nullable', 'exists:ekosistem_basis_data_group,id'],
+ ]);
+
+ TabelBasisData::create([...$validated,
+ 'created_by' => auth()->id(),
+ 'updated_by' => auth()->id(),
+ ]);
+
+ return redirect()->route('tabel.index')->with('success', 'Tabel basis data berhasil ditambahkan.');
+ }
+
+ /**
+ * Display the specified resource.
+ */
+ public function show(string $id): View
+ {
+ Gate::authorize('super_admin');
+ $tabel = TabelBasisData::with('kolom')->find($id);
+ if (!$tabel) {
+ return redirect()->route('tabel-basis-data.index')->with('error', 'Tabel basis data tidak ditemukan.');
+ }
+ return view('ekosistem.tabel-basis-data.view', compact('tabel'));
+ }
+
+ /**
+ * Show the form for editing the specified resource.
+ */
+ public function edit(string $id): View
+ {
+ Gate::authorize('super_admin');
+
+ $tabel = TabelBasisData::find($id);
+ if (!$tabel) {
+ return redirect()->route('tabel-basis-data.index')->with('error', 'Tabel basis data tidak ditemukan.');
+ }
+
+ $group = GroupBasisData::all();
+
+ return view('ekosistem.tabel-basis-data.edit', compact('tabel', 'group'));
+ }
+
+ /**
+ * Update the specified resource in storage.
+ */
+ public function update(Request $request, string $id): RedirectResponse
+ {
+ Gate::authorize('super_admin');
+
+ $tabel = TabelBasisData::find($id);
+ if (!$tabel) {
+ return redirect()->route('tabel-basis-data.index')->with('error', 'Tabel basis data tidak ditemukan.');
+ }
+
+ $validated = $request->validate([
+ 'tabel' => ['required',
+ 'string',
+ 'regex:/^[a-z0-9]+(_[a-z0-9]+)*$/',
+ 'unique:ekosistem_basis_data_tabel,tabel'
+ ],
+ 'label' => ['required', 'string'],
+ 'menu' => ['required', 'string'],
+ 'deskripsi' => ['nullable', 'string'],
+ 'group_id' => ['nullable', 'exists:ekosistem_basis_data_group,id'],
+ ]);
+
+ $tabel->update([...$validated,
+ 'updated_by' => auth()->id(),
+ ]);
+
+ return redirect()->route('tabel.index')->with('success', 'Tabel basis data berhasil diperbarui.');
+ }
+
+ /**
+ * Remove the specified resource from storage.
+ */
+ public function destroy(string $id)
+ {
+ Gate::authorize('super_admin');
+
+ $tabel = TabelBasisData::find($id);
+ if (!$tabel) {
+ return redirect()->route('tabel.index')->with('error', 'Tabel basis data tidak ditemukan.');
+ }
+
+ $tabel->delete();
+
+ return redirect()->route('tabel.index')->with('success', 'Tabel basis data berhasil dihapus.');
+ }
+
+ public function moveUp(Request $request, string $id): JsonResponse
+ {
+ Gate::authorize('super_admin');
+
+ $current = TabelBasisData::find($id);
+ if (!$current) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Data tabel tidak ditemukan.'
+ ], 404);
+ }
+
+ $prev = TabelBasisData::where('urutan', '<', $current->urutan)
+ ->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' => 'Tabel basis data berhasil diperbarui.'
+ ], 200);
+ }
+
+ public function moveDown(Request $request, string $id): JsonResponse
+ {
+ Gate::authorize('super_admin');
+
+ $current = TabelBasisData::find($id);
+ if (!$current) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Data tabel tidak ditemukan.'
+ ], 404);
+ }
+
+ $next = TabelBasisData::where('urutan', '>', $current->urutan)
+ ->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' => 'Tabel basis data berhasil diperbarui.'
+ ], 200);
+ }
+}
diff --git a/app/Http/Controllers/Web/Ekosistem/ValueBasisDataController.php b/app/Http/Controllers/Web/Ekosistem/ValueBasisDataController.php
new file mode 100644
index 0000000..a1f832a
--- /dev/null
+++ b/app/Http/Controllers/Web/Ekosistem/ValueBasisDataController.php
@@ -0,0 +1,359 @@
+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::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.');
+ }
+
+ $selects = [];
+ foreach ($kolom as $k) {
+ $selects[] = "
+ MAX(
+ CASE
+ WHEN kolom_id = {$k->id}
+ THEN value_{$k->tipe}
+ ELSE NULL
+ END
+ ) AS \"{$k->kolom}\"
+ ";
+ }
+
+ $base = ValueBasisData::query()
+ ->where('tabel_id', $tabelId)
+ ->when($program, function ($query) use ($program) {
+ $query->where('program_id', $program->id);
+ })
+ ->selectRaw("
+ program_id,
+ tabel_id,
+ value_id,
+ " . implode(",\n", $selects) . "
+ ")
+ ->groupBy('program_id', '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 view('ekosistem.value-basis-data.index', compact('programList', 'program', 'tabel', 'kolom', 'values'));
+ }
+
+ /**
+ * Show the form for creating a new resource.
+ */
+ public function create(Request $request, string $tabelId): View
+ {
+ if (!Gate::any(['super_admin', 'admin'])) {
+ abort(403);
+ }
+
+ $tabel = TabelBasisData::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.');
+ }
+ }
+
+ return view('ekosistem.value-basis-data.add', compact('tabel', 'kolom', 'program'));
+ }
+
+ /**
+ * Store a newly created resource in storage.
+ */
+ public function store(Request $request, string $tabelId): RedirectResponse
+ {
+ if (!Gate::any(['super_admin', 'admin'])) {
+ abort(403);
+ }
+
+ $tabel = TabelBasisData::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.');
+ }
+ }
+
+ $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(),
+ ];
+ $values['value_' . $k->tipe] = $validated[$k->kolom] ?? null;
+ ValueBasisData::create($values);
+ }
+ });
+
+ return redirect()->route('tabel.data.index', [$tabelId, 'program' => $program->id])->with('success', 'Data berhasil ditambahkan.');
+ }
+
+ /**
+ * Display the specified resource.
+ */
+ public function show(string $id)
+ {
+ //
+ }
+
+ /**
+ * Show the form for editing the specified resource.
+ */
+ public function edit(Request $request, string $tabelId, string $id)
+ {
+ if (!Gate::any(['super_admin', 'admin'])) {
+ abort(403);
+ }
+
+ $tabel = TabelBasisData::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.');
+ }
+
+ $value_id = $id;
+
+ return view('ekosistem.value-basis-data.edit', compact('tabel', 'kolom', 'program', 'values', 'value_id'));
+ }
+
+ /**
+ * Update the specified resource in storage.
+ */
+ public function update(Request $request, string $tabelId, string $id)
+ {
+ if (!Gate::any(['super_admin', 'admin'])) {
+ abort(403);
+ }
+
+ $tabel = TabelBasisData::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.');
+ }
+
+ $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 ($values as $v) {
+ $k = $kolom->where('id', $v->kolom_id)->first();
+ $v->update([
+ 'value_' . $k->tipe => $validated[$k->kolom] ?? null,
+ 'updated_by' => auth()->id(),
+ ]);
+ }
+ });
+
+ return redirect()->route('tabel.data.index', [$tabelId, 'program' => $program->id])->with('success', 'Data berhasil diperbarui.');
+ }
+
+ /**
+ * Remove the specified resource from storage.
+ */
+ public function destroy(Request $request, string $tabelId, string $id)
+ {
+ if (!Gate::any(['super_admin', 'admin'])) {
+ abort(403);
+ }
+
+ $tabel = TabelBasisData::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 redirect()->route('tabel.data.index', [$tabelId, 'program' => $program->id])->with('success', 'Data berhasil dihapus.');
+ }
+}
diff --git a/app/Http/Controllers/Web/ProgramController.php b/app/Http/Controllers/Web/ProgramController.php
new file mode 100644
index 0000000..50f7cd0
--- /dev/null
+++ b/app/Http/Controllers/Web/ProgramController.php
@@ -0,0 +1,134 @@
+query('per_page', 10);
+ $search = $request->query('search', '');
+
+ $program = Program::where('nama', 'like', "%$search%")
+ ->paginate($perPage);
+
+ return view('core.program.index', compact('title', 'program'));
+ }
+
+ public function create()
+ {
+ $provinsi = Provinsi::orderBy('nama', 'asc')->get();
+ $komoditas = Komoditas::orderBy('nama', 'asc')->get();
+ return view('core.program.add', compact('provinsi', 'komoditas'));
+ }
+
+ public function getProgram()
+ {
+ $program = Program::select(['id', 'nama', 'sub_domain'])->get();
+ return response()->json(['data' => $program]);
+ }
+
+ public function store(Request $request)
+ {
+ $validator = Validator::make($request->all(), [
+ 'nama' => ['required'],
+ 'sub_domain' => ['required', Rule::unique(Program::class, 'sub_domain')],
+ 'tahun' => ['required', 'integer'],
+ 'instansi' => ['nullable', 'string'],
+ ]);
+
+ if ($validator->fails()) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => $validator->errors()->first()
+ ], 422);
+ }
+
+ Program::create([...$validator->validated(),
+ 'created_by' => auth()->id(),
+ 'updated_by' => auth()->id(),
+ ]);
+
+ return redirect()->route('program.index')->with('success', 'Data program berhasil disimpan!');
+ }
+
+ public function show($id)
+ {
+ $program = Program::find($id);
+ if (!$program) {
+ return redirect()->route('program.index')->with('error', 'Data program tidak ditemukan.');
+ }
+
+ $komoditas = Komoditas::all();
+ $provinsi = Provinsi::all();
+
+ return view('core.program.view', compact('program', 'komoditas', 'provinsi'));
+ }
+
+ public function edit($id)
+ {
+ $program = Program::find($id);
+ if (!$program) {
+ return redirect()->route('program.index')->with('error', 'Data program tidak ditemukan.');
+ }
+
+ return view('core.program.edit', compact('program'));
+ }
+
+ public function update(Request $request, $id)
+ {
+ $validator = Validator::make($request->all(), [
+ 'nama' => ['required'],
+ 'sub_domain' => ['required', Rule::unique(Program::class, 'sub_domain')->ignore($id)],
+ ]);
+
+ if ($validator->fails()) {
+ return redirect()->back()->withErrors($validator)->withInput();
+ }
+
+ $program = Program::find($id);
+
+ if (!$program) {
+ return redirect()->route('program.index')->with('error', 'Data program tidak ditemukan!');
+ }
+
+ $program->update([
+ 'nama' => $request->nama,
+ 'sub_domain' => $request->sub_domain,
+ 'updated_by' => auth()->id(),
+ ]);
+
+ return redirect()->route('program.index')->with('success', 'Data program berhasil diperbarui!');
+ }
+
+ public function destroy($id)
+ {
+ $program = Program::find($id);
+ if (!$program) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Data tidak ditemukan.'
+ ], 404);
+ }
+
+ $program->deleted_by = auth()->user()->id;
+ $program->save();
+ $program->delete();
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => 'Data program berhasil dihapus.'
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Web/ProgramKomoditasController.php b/app/Http/Controllers/Web/ProgramKomoditasController.php
new file mode 100644
index 0000000..565daf7
--- /dev/null
+++ b/app/Http/Controllers/Web/ProgramKomoditasController.php
@@ -0,0 +1,110 @@
+json([
+ 'status' => 'error',
+ 'message' => 'Data program tidak ditemukan.'
+ ], 404);
+ }
+
+ $validator = Validator::make($request->all(), [
+ 'komoditas_id' => [
+ 'required',
+ 'exists:master_komoditas,id',
+ Rule::unique('core_program_komoditas')
+ ->where(fn ($query) => $query->where('program_id', $programId))->whereNull('deleted_at'),
+ ],
+ 'default' => ['nullable', 'boolean'],
+ ]);
+
+ if ($validator->fails()) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => $validator->errors()->first()
+ ], 422);
+ }
+
+ KomoditasProgram::where('program_id', $programId)->update(['default' => false]);
+
+ KomoditasProgram::create([...$validator->validated(),
+ 'program_id' => $programId,
+ 'created_by' => auth()->id(),
+ 'updated_by' => auth()->id(),
+ ]);
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => 'Data komoditas program berhasil disimpan.'
+ ]);
+ }
+
+ public function update(Request $request, int $programId, int $id)
+ {
+ $komoditasProgram = KomoditasProgram::where('program_id', $programId)->where('id', $id)->first();
+ if (!$komoditasProgram) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Data komoditas program tidak ditemukan.'
+ ], 404);
+ }
+
+ $validator = Validator::make($request->all(), [
+ 'default' => ['nullable', 'boolean'],
+ ]);
+
+ if ($validator->fails()) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => $validator->errors()->first()
+ ], 422);
+ }
+
+ KomoditasProgram::where('program_id', $programId)->update(['default' => false]);
+ $komoditasProgram->update([...$validator->validated(),
+ 'updated_by' => auth()->id(),
+ ]);
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => 'Data komoditas program berhasil diperbarui.'
+ ]);
+ }
+
+ public function destroy(int $programId, int $id)
+ {
+ $komoditasProgram = KomoditasProgram::where('program_id', $programId)->where('id', $id)->first();
+ if (!$komoditasProgram) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Data komoditas program tidak ditemukan.'
+ ], 404);
+ }
+
+ $komoditasProgram->deleted_by = auth()->user()->id;
+ $komoditasProgram->save();
+ $komoditasProgram->delete();
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => 'Data komoditas program berhasil dihapus.'
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Web/ProgramLokasiController.php b/app/Http/Controllers/Web/ProgramLokasiController.php
new file mode 100644
index 0000000..b67816e
--- /dev/null
+++ b/app/Http/Controllers/Web/ProgramLokasiController.php
@@ -0,0 +1,144 @@
+get();
+ return response()->json(['data' => $program]);
+ }
+
+ public function store(Request $request, int $programId)
+ {
+ $program = Program::find($programId);
+ if (!$program) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Data program tidak ditemukan.'
+ ], 404);
+ }
+
+ $validator = Validator::make($request->all(), [
+ 'provinsi_id' => ['required', 'exists:master_provinsi,id'],
+ 'kabupaten_kota_id' => ['nullable', 'exists:master_kabupaten_kota,id'],
+ 'kecamatan_id' => ['nullable', 'exists:master_kecamatan,id'],
+ 'desa_kelurahan_id' => ['nullable', 'exists:master_desa_kelurahan,id'],
+
+ 'default' => ['nullable', 'boolean'],
+ ]);
+
+ if ($validator->fails()) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => $validator->errors()->first()
+ ], 422);
+ }
+
+ LokasiProgram::where('program_id', $programId)->update(['default' => false]);
+
+ LokasiProgram::create([...$validator->validated(),
+ //'default' => $request->has('default'),
+ 'program_id' => $programId,
+ 'created_by' => auth()->id(),
+ 'updated_by' => auth()->id(),
+ ]);
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => 'Data lokasi program berhasil disimpan.'
+ ]);
+ }
+
+ public function show($id)
+ {
+ $program = Program::find($id);
+ if (!$program) {
+ return redirect()->route('program.index')->with('error', 'Data program tidak ditemukan.');
+ }
+
+ return view('core.program.view', compact('program'));
+ }
+
+ public function edit($id)
+ {
+ $program = Program::find($id);
+ if (!$program) {
+ return redirect()->route('program.index')->with('error', 'Data program tidak ditemukan.');
+ }
+
+ return view('core.program.edit', compact('program'));
+ }
+
+ public function update(Request $request, int $programId, int $id)
+ {
+ $lokasiProgram = LokasiProgram::where('program_id', $programId)->where('id', $id)->first();
+ if (!$lokasiProgram) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Data lokasi program tidak ditemukan.'
+ ], 404);
+ }
+
+ $validator = Validator::make($request->all(), [
+ 'default' => ['nullable', 'boolean'],
+ ]);
+
+ if ($validator->fails()) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => $validator->errors()->first()
+ ], 422);
+ }
+
+ LokasiProgram::where('program_id', $programId)->update(['default' => false]);
+ $lokasiProgram->update([...$validator->validated(),
+ 'updated_by' => auth()->id(),
+ ]);
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => 'Data lokasi program berhasil diperbarui!'
+ ]);
+ }
+
+ public function destroy(int $programId, int $id)
+ {
+ $lokasiProgram = LokasiProgram::where('program_id', $programId)->where('id', $id)->first();
+ if (!$lokasiProgram) {
+ return response()->json([
+ 'status' => 'error',
+ 'message' => 'Data lokasi program tidak ditemukan.'
+ ], 404);
+ }
+
+ $lokasiProgram->deleted_by = auth()->user()->id;
+ $lokasiProgram->save();
+ $lokasiProgram->delete();
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => 'Data lokasi program berhasil dihapus.'
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Middleware/ResolveProgram.php b/app/Http/Middleware/ResolveProgram.php
new file mode 100644
index 0000000..9c84898
--- /dev/null
+++ b/app/Http/Middleware/ResolveProgram.php
@@ -0,0 +1,37 @@
+getHost();
+ $parts = explode('.', $host);
+
+ if ($host === 'localhost' || filter_var($host, FILTER_VALIDATE_IP)) {
+ return $next($request);
+ }
+
+ if (count($parts) < 3) {
+ return $next($request);
+ }
+
+ $subdomain = $parts[0];
+
+ $program = Program::where('sub_domain', $subdomain)->firstOrFail();
+ app()->instance('SystemProgram', $program);
+ return $next($request);
+ }
+}
diff --git a/app/Models/Core/KomoditasProgram.php b/app/Models/Core/KomoditasProgram.php
new file mode 100644
index 0000000..62b9596
--- /dev/null
+++ b/app/Models/Core/KomoditasProgram.php
@@ -0,0 +1,27 @@
+belongsTo(Komoditas::class, 'komoditas_id');
+ }
+}
diff --git a/app/Models/Core/LokasiProgram.php b/app/Models/Core/LokasiProgram.php
new file mode 100644
index 0000000..99fb321
--- /dev/null
+++ b/app/Models/Core/LokasiProgram.php
@@ -0,0 +1,48 @@
+belongsTo(Provinsi::class, 'provinsi_id');
+ }
+
+ public function kabupatenKota(): BelongsTo
+ {
+ return $this->belongsTo(KabupatenKota::class, 'kabupaten_kota_id');
+ }
+
+ public function kecamatan(): BelongsTo
+ {
+ return $this->belongsTo(Kecamatan::class, 'kecamatan_id');
+ }
+
+ public function desaKelurahan(): BelongsTo
+ {
+ return $this->belongsTo(DesaKelurahan::class, 'desa_kelurahan_id');
+ }
+}
diff --git a/app/Models/Core/Program.php b/app/Models/Core/Program.php
new file mode 100644
index 0000000..e4f7240
--- /dev/null
+++ b/app/Models/Core/Program.php
@@ -0,0 +1,34 @@
+hasMany(KomoditasProgram::class, 'program_id', 'id');
+ }
+
+ public function lokasi(): HasMany
+ {
+ return $this->hasMany(LokasiProgram::class, 'program_id', 'id');
+ }
+}
diff --git a/app/Models/Ekosistem/GroupBasisData.php b/app/Models/Ekosistem/GroupBasisData.php
new file mode 100644
index 0000000..1482077
--- /dev/null
+++ b/app/Models/Ekosistem/GroupBasisData.php
@@ -0,0 +1,28 @@
+hasMany(TabelBasisData::class, 'group_id')->orderBy('urutan');
+ }
+}
diff --git a/app/Models/Ekosistem/KolomBasisData.php b/app/Models/Ekosistem/KolomBasisData.php
new file mode 100644
index 0000000..a76a647
--- /dev/null
+++ b/app/Models/Ekosistem/KolomBasisData.php
@@ -0,0 +1,46 @@
+ 'array',
+ ];
+
+ 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');
+ }
+}
diff --git a/app/Models/Ekosistem/TabelBasisData.php b/app/Models/Ekosistem/TabelBasisData.php
new file mode 100644
index 0000000..9eb67b1
--- /dev/null
+++ b/app/Models/Ekosistem/TabelBasisData.php
@@ -0,0 +1,49 @@
+group_id)->max('urutan');
+ $model->urutan = ($max ?? 0) + 1;
+ });
+ static::addGlobalScope('order', function (Builder $builder) {
+ $builder->orderBy('urutan');
+ });
+ }
+
+ public function kolom(): HasMany
+ {
+ return $this->hasMany(KolomBasisData::class, 'tabel_id')->orderBy('urutan');
+ }
+
+ public function group(): BelongsTo
+ {
+ return $this->belongsTo(GroupBasisData::class, 'group_id');
+ }
+}
diff --git a/app/Models/Ekosistem/ValueBasisData.php b/app/Models/Ekosistem/ValueBasisData.php
new file mode 100644
index 0000000..feee72c
--- /dev/null
+++ b/app/Models/Ekosistem/ValueBasisData.php
@@ -0,0 +1,46 @@
+belongsTo(TabelBasisData::class, 'tabel_id');
+ }
+
+ public function kolom(): BelongsTo
+ {
+ return $this->belongsTo(KolomBasisData::class, 'kolom_id');
+ }
+
+ public function program(): BelongsTo
+ {
+ return $this->belongsTo(Program::class, 'program_id');
+ }
+}
diff --git a/app/Models/Pertanian/Komoditas.php b/app/Models/Pertanian/Komoditas.php
index e046249..a0b5858 100644
--- a/app/Models/Pertanian/Komoditas.php
+++ b/app/Models/Pertanian/Komoditas.php
@@ -4,6 +4,7 @@ namespace App\Models\Pertanian;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Database\Eloquent\Builder;
class Komoditas extends Model
{
@@ -17,4 +18,16 @@ class Komoditas extends Model
'updated_by',
'deleted_by',
];
+
+ protected static function booted()
+ {
+ static::addGlobalScope('program', function (Builder $builder) {
+
+ if (!app()->has('SystemProgram')) {
+ return;
+ }
+
+ $builder->whereIn('id', app('SystemProgram')->komoditas()->pluck('id'));
+ });
+ }
}
diff --git a/app/Models/Scopes/KomoditasProgramScope.php b/app/Models/Scopes/KomoditasProgramScope.php
new file mode 100644
index 0000000..b645a51
--- /dev/null
+++ b/app/Models/Scopes/KomoditasProgramScope.php
@@ -0,0 +1,21 @@
+has('SystemProgram')) {
+ $program = app('SystemProgram');
+ $builder->whereIn('komoditas_id', $program->komoditas()->pluck('id'));
+ }
+ }
+}
diff --git a/bootstrap/app.php b/bootstrap/app.php
index a81640d..178e8ce 100644
--- a/bootstrap/app.php
+++ b/bootstrap/app.php
@@ -14,7 +14,7 @@ return Application::configure(basePath: dirname(__DIR__))
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
- //
+ $middleware->append(\App\Http\Middleware\ResolveProgram::class);
})
->withExceptions(function (Exceptions $exceptions): void {
//
diff --git a/database/migrations/2026_02_20_131504_create_lahans_table.php b/database/migrations/2026_02_20_131504_create_lahans_table.php
index 9582ec3..70f30e5 100644
--- a/database/migrations/2026_02_20_131504_create_lahans_table.php
+++ b/database/migrations/2026_02_20_131504_create_lahans_table.php
@@ -18,7 +18,7 @@ return new class extends Migration
$table->foreignId('profile_id')->constrained('profiles');
$table->foreignId('keluarga_pengelola_id')->nullable()->constrained('keluarga')->onDelete('set null');
- $table->foreignId('adat_pemilik_id')->nullable()->constrained('keluarga')->onDelete('set null');
+ $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();
diff --git a/database/migrations/2026_04_10_131504_update_jsonb_path.php b/database/migrations/2026_04_10_131504_update_jsonb_path.php
new file mode 100644
index 0000000..d7534c8
--- /dev/null
+++ b/database/migrations/2026_04_10_131504_update_jsonb_path.php
@@ -0,0 +1,42 @@
+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
+ {
+
+ }
+};
diff --git a/database/migrations/2026_04_11_105538_create_programs_table.php b/database/migrations/2026_04_11_105538_create_programs_table.php
new file mode 100644
index 0000000..9af40e2
--- /dev/null
+++ b/database/migrations/2026_04_11_105538_create_programs_table.php
@@ -0,0 +1,74 @@
+id();
+
+ $table->string('nama');
+ $table->string('sub_domain');
+ $table->integer('tahun');
+ $table->string('instansi')->nullable();
+
+ $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('core_program_komoditas', function (Blueprint $table) {
+ $table->id();
+
+ $table->foreignId('program_id')->nullable()->constrained('core_program')->nullOnDelete();
+ $table->foreignId('komoditas_id')->nullable()->constrained('master_komoditas')->nullOnDelete();
+ $table->boolean('default')->default(false);
+
+ $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('core_program_lokasi', function (Blueprint $table) {
+ $table->id();
+
+ $table->foreignId('program_id')->nullable()->constrained('core_program')->nullOnDelete();
+
+ $table->foreignId('provinsi_id')->nullable()->constrained('master_provinsi')->nullOnDelete();
+ $table->foreignId('kabupaten_kota_id')->nullable()->constrained('master_kabupaten_kota')->nullOnDelete();
+ $table->foreignId('kecamatan_id')->nullable()->constrained('master_kecamatan')->nullOnDelete();
+ $table->foreignId('desa_kelurahan_id')->nullable()->constrained('master_desa_kelurahan')->nullOnDelete();
+ $table->boolean('default')->default(false);
+
+ $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('core_program_lokasi');
+ Schema::dropIfExists('core_program_komoditas');
+ Schema::dropIfExists('core_program');
+ }
+};
diff --git a/database/migrations/2026_04_15_103739_create_basis_data_table.php b/database/migrations/2026_04_15_103739_create_basis_data_table.php
new file mode 100644
index 0000000..e8d3fa8
--- /dev/null
+++ b/database/migrations/2026_04_15_103739_create_basis_data_table.php
@@ -0,0 +1,82 @@
+id();
+
+ $table->string('tabel')->unique();
+ $table->string('label');
+ $table->string('menu');
+ $table->string('deskripsi', 1000)->nullable();
+
+ $table->timestamps();
+ $table->softDeletes();
+ });
+
+ Schema::create('ekosistem_basis_data_kolom', function (Blueprint $table) {
+ $table->id();
+
+ $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->jsonb('validasi')->nullable();
+ $table->integer('urutan')->default(1);
+
+ $table->timestamps();
+ $table->softDeletes();
+ });
+
+ DB::statement("
+ CREATE UNIQUE INDEX ekosistem_basis_data_kolom_unique_active
+ ON ekosistem_basis_data_kolom (tabel_id, kolom)
+ WHERE deleted_at IS NULL
+ ");
+
+ Schema::create('ekosistem_basis_data_value', function (Blueprint $table) {
+ $table->id();
+
+ $table->foreignId('program_id')->constrained('core_program')->restrictOnDelete();
+
+ $table->foreignId('tabel_id')->constrained('ekosistem_basis_data_tabel')->restrictOnDelete();
+ $table->foreignId('kolom_id')->constrained('ekosistem_basis_data_kolom')->restrictOnDelete();
+
+ $table->string('value_id');
+
+ $table->string('value_varchar')->nullable();
+ $table->integer('value_integer')->nullable();
+ $table->float('value_float', 10, 2)->nullable();
+ $table->boolean('value_boolean')->nullable();
+ $table->date('value_date')->nullable();
+ $table->datetime('value_datetime')->nullable();
+ $table->text('value_text')->nullable();
+
+ $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_value');
+ Schema::dropIfExists('ekosistem_basis_data_kolom');
+ Schema::dropIfExists('ekosistem_basis_data_tabel');
+ }
+};
diff --git a/database/migrations/2026_04_16_172905_update_profiles_table.php b/database/migrations/2026_04_16_172905_update_profiles_table.php
new file mode 100644
index 0000000..f2ee0fb
--- /dev/null
+++ b/database/migrations/2026_04_16_172905_update_profiles_table.php
@@ -0,0 +1,25 @@
+foreignId('adat_id')->nullable()->constrained('adat');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ }
+};
diff --git a/database/migrations/2026_04_20_103739_create_kelompok_basis_data_table.php b/database/migrations/2026_04_20_103739_create_kelompok_basis_data_table.php
new file mode 100644
index 0000000..76f3fec
--- /dev/null
+++ b/database/migrations/2026_04_20_103739_create_kelompok_basis_data_table.php
@@ -0,0 +1,53 @@
+id();
+
+ $table->string('group')->unique();
+ $table->string('label');
+ $table->integer('urutan')->default(1);
+ $table->string('deskripsi', 1000)->nullable();
+
+ $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
+ $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
+ $table->foreignId('deleted_by')->nullable()->constrained('users')->nullOnDelete();
+
+ $table->timestamps();
+ $table->softDeletes();
+ });
+
+ Schema::table('ekosistem_basis_data_tabel', function (Blueprint $table) {
+ $table->foreignId('group_id')->nullable()->constrained('ekosistem_basis_data_group')->nullOnDelete();
+ $table->integer('urutan')->default(1);
+
+ $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
+ $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
+ $table->foreignId('deleted_by')->nullable()->constrained('users')->nullOnDelete();
+ });
+
+ Schema::table('ekosistem_basis_data_kolom', function (Blueprint $table) {
+ $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
+ $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
+ $table->foreignId('deleted_by')->nullable()->constrained('users')->nullOnDelete();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('ekosistem_basis_data_group');
+ }
+};
diff --git a/resources/views/core/program-komoditas/add.blade.php b/resources/views/core/program-komoditas/add.blade.php
new file mode 100644
index 0000000..d6aaa7b
--- /dev/null
+++ b/resources/views/core/program-komoditas/add.blade.php
@@ -0,0 +1,87 @@
+
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/core/program-komoditas/edit.blade.php b/resources/views/core/program-komoditas/edit.blade.php
new file mode 100644
index 0000000..9881bbe
--- /dev/null
+++ b/resources/views/core/program-komoditas/edit.blade.php
@@ -0,0 +1,88 @@
+
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/core/program-komoditas/index.blade.php b/resources/views/core/program-komoditas/index.blade.php
new file mode 100644
index 0000000..c1694be
--- /dev/null
+++ b/resources/views/core/program-komoditas/index.blade.php
@@ -0,0 +1,113 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+ Show
+
+ 10
+ 25
+ 50
+
+ entries
+
+
+
+
+
+
+
+
+
+
+ No
+ Nama Program
+ Subdomain
+ Tahun
+ Actions
+
+
+
+ @forelse($program as $index => $prg)
+
+ {{ $program->firstItem() + $index }}
+ {{ $prg->nama }}
+ {{ $prg->sub_domain }}
+ {{ $prg->tahun }}
+
+
+
+
+ @empty
+
+ Tidak ada data ditemukan.
+
+ @endforelse
+
+
+
+
+
+
+ @include('core.program.add')
+ @include('core.program.view')
+ @include('core.program.edit')
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/core/program-komoditas/view.blade.php b/resources/views/core/program-komoditas/view.blade.php
new file mode 100644
index 0000000..3205100
--- /dev/null
+++ b/resources/views/core/program-komoditas/view.blade.php
@@ -0,0 +1,52 @@
+
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/core/program-lokasi/add.blade.php b/resources/views/core/program-lokasi/add.blade.php
new file mode 100644
index 0000000..f4c0dc9
--- /dev/null
+++ b/resources/views/core/program-lokasi/add.blade.php
@@ -0,0 +1,156 @@
+
+
+
+
+ @csrf
+
+
+ Provinsi*
+
+ Pilih Provinsi
+ @foreach($provinsi as $p)
+ {{ $p->kode }} {{ $p->nama }}
+ @endforeach
+
+
+
+ Kabupaten/Kota
+
+ Pilih Kabupaten/Kota
+
+
+
+ Kecamatan
+
+ Pilih Kecamatan
+
+
+
+ Desa/Kelurahan
+
+ Pilih Desa/Kelurahan
+
+
+
+
+ Simpan
+ Batal
+
+
+
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/core/program/add.blade.php b/resources/views/core/program/add.blade.php
new file mode 100644
index 0000000..01b5099
--- /dev/null
+++ b/resources/views/core/program/add.blade.php
@@ -0,0 +1,75 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+ @csrf
+
+ Informasi Program
+
+
+
+
+
Kembali
+
Simpan Program
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/core/program/edit.blade.php b/resources/views/core/program/edit.blade.php
new file mode 100644
index 0000000..30603a3
--- /dev/null
+++ b/resources/views/core/program/edit.blade.php
@@ -0,0 +1,80 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+ @csrf
+ @method('PUT')
+
+ Informasi Program
+
+
+
+
+
Kembali
+
Update Program
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/core/program/index.blade.php b/resources/views/core/program/index.blade.php
new file mode 100644
index 0000000..89b8a8c
--- /dev/null
+++ b/resources/views/core/program/index.blade.php
@@ -0,0 +1,84 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+ Show
+
+ 10
+ 25
+ 50
+
+ entries
+
+
+
+
+
+
+
+
+
+
+ No
+ Nama Program
+ Subdomain
+ Tahun
+ Instansi
+ Aksi
+
+
+
+ @forelse($program as $index => $prg)
+
+ {{ $loop->iteration }}
+ {{ $prg->nama }}
+ {{ $prg->sub_domain }}
+ {{ $prg->tahun }}
+ {{ $prg->instansi ?? '-' }}
+
+
+
+
+ @empty
+
+ Tidak ada data ditemukan.
+
+ @endforelse
+
+
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/core/program/view.blade.php b/resources/views/core/program/view.blade.php
new file mode 100644
index 0000000..93a838c
--- /dev/null
+++ b/resources/views/core/program/view.blade.php
@@ -0,0 +1,279 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+
+ Informasi Program
+
+
+
+
+
+
+
+
+
Komoditas
+
+
+
+
+
+
+
+ No
+ Komoditas
+ Aksi
+
+
+
+ @forelse($program->komoditas as $index => $kmd)
+
+ {{ $loop->iteration }}
+
+ {{ $kmd->komoditas->nama }}
+ @if($kmd->default)
+ Default
+ @endif
+
+
+
+ @if(!$kmd->default)
+
+ @endif
+
+
+
+
+ @empty
+
+ Tidak ada data ditemukan.
+
+ @endforelse
+
+
+
+
+
+
Lokasi
+
+
+
+
+
+
+
+ No
+ Provinsi
+ Kabupaten/Kota
+ Kecamatan
+ Kelurahan/Desa
+ Aksi
+
+
+
+ @forelse($program->lokasi as $index => $lok)
+
+ {{ $loop->iteration }}
+
+ {{ $lok->provinsi->nama }}
+ @if($lok->default && !$lok->kabupaten_kota_id && !$lok->kecamatan_id && !$lok->desa_kelurahan_id)
+ Default
+ @endif
+
+
+ {{ $lok->kabupatenKota?->nama }}
+ @if($lok->default && $lok->kabupaten_kota_id && !$lok->kecamatan_id && !$lok->desa_kelurahan_id)
+ Default
+ @endif
+
+
+ {{ $lok->kecamatan?->nama }}
+ @if($lok->default && $lok->kabupaten_kota_id && $lok->kecamatan_id && !$lok->desa_kelurahan_id)
+ Default
+ @endif
+
+
+ {{ $lok->desaKelurahan?->nama }}
+ @if($lok->default && $lok->kabupaten_kota_id && $lok->kecamatan_id && $lok->desa_kelurahan_id)
+ Default
+ @endif
+
+
+ @if(!$lok->default)
+
+ @endif
+
+
+
+ @empty
+
+ Tidak ada data ditemukan.
+
+ @endforelse
+
+
+
+
+
+
+
+
+
+ @include('core.program-komoditas.add')
+ @include('core.program-lokasi.add')
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/ekosistem/group-basis-data/add.blade.php b/resources/views/ekosistem/group-basis-data/add.blade.php
new file mode 100644
index 0000000..41e00d2
--- /dev/null
+++ b/resources/views/ekosistem/group-basis-data/add.blade.php
@@ -0,0 +1,77 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+ @csrf
+
+ Informasi Kelompok Tabel
+
+
+
+
+
Kembali
+
Simpan Kelompok
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/ekosistem/group-basis-data/edit.blade.php b/resources/views/ekosistem/group-basis-data/edit.blade.php
new file mode 100644
index 0000000..b3fb688
--- /dev/null
+++ b/resources/views/ekosistem/group-basis-data/edit.blade.php
@@ -0,0 +1,78 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+ @csrf
+ @method('PUT')
+
+ Informasi Kelompok Tabel
+
+
+
+
+
Kembali
+
Simpan Kelompok
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/ekosistem/group-basis-data/index.blade.php b/resources/views/ekosistem/group-basis-data/index.blade.php
new file mode 100644
index 0000000..4feab02
--- /dev/null
+++ b/resources/views/ekosistem/group-basis-data/index.blade.php
@@ -0,0 +1,109 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+ Show
+
+ 10
+ 25
+ 50
+
+ entries
+
+
+
+
+
+
+
+
+
+
+ No
+ Nama Kelompok
+ Label
+ Aksi
+
+
+
+ @forelse($group as $index => $gr)
+
+ {{ $loop->iteration }}
+ {{ $gr->group }}
+ {{ $gr->label }}
+
+
+
+
+ @empty
+
+ Tidak ada data ditemukan.
+
+ @endforelse
+
+
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/ekosistem/group-basis-data/view.blade.php b/resources/views/ekosistem/group-basis-data/view.blade.php
new file mode 100644
index 0000000..7a49a18
--- /dev/null
+++ b/resources/views/ekosistem/group-basis-data/view.blade.php
@@ -0,0 +1,44 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+
+ Informasi Kelompok Tabel
+
+
+
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/ekosistem/kolom-basis-data/add.blade.php b/resources/views/ekosistem/kolom-basis-data/add.blade.php
new file mode 100644
index 0000000..fe91c9a
--- /dev/null
+++ b/resources/views/ekosistem/kolom-basis-data/add.blade.php
@@ -0,0 +1,104 @@
+
+
+
+
+ @csrf
+
+
+ Nama Kolom*
+
+
+
+ Label*
+
+
+
+ Jenis Data*
+
+ Pilih Jenis Data
+ Varchar
+ Integer
+ Float
+ Boolean
+ Date
+ Datetime
+ Text
+
+
+
+ Validasi*
+
+
+
+ Simpan
+ Batal
+
+
+
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/ekosistem/kolom-basis-data/edit.blade.php b/resources/views/ekosistem/kolom-basis-data/edit.blade.php
new file mode 100644
index 0000000..976a816
--- /dev/null
+++ b/resources/views/ekosistem/kolom-basis-data/edit.blade.php
@@ -0,0 +1,130 @@
+
+
+
+
+ @csrf
+ @method('PUT')
+
+
+
+
+ Nama Kolom*
+
+
+
+ Label*
+
+
+
+ Jenis Data*
+
+ Pilih Jenis Data
+ Varchar
+ Integer
+ Float
+ Boolean
+ Date
+ Datetime
+ Text
+
+
+
+ Validasi*
+
+
+
+ Simpan
+ Batal
+
+
+
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/ekosistem/tabel-basis-data/add.blade.php b/resources/views/ekosistem/tabel-basis-data/add.blade.php
new file mode 100644
index 0000000..234f020
--- /dev/null
+++ b/resources/views/ekosistem/tabel-basis-data/add.blade.php
@@ -0,0 +1,96 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+ @csrf
+
+ Informasi Tabel
+
+
+
+
+
Kembali
+
Simpan Tabel
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/ekosistem/tabel-basis-data/edit.blade.php b/resources/views/ekosistem/tabel-basis-data/edit.blade.php
new file mode 100644
index 0000000..518b468
--- /dev/null
+++ b/resources/views/ekosistem/tabel-basis-data/edit.blade.php
@@ -0,0 +1,97 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+ @csrf
+ @method('PUT')
+
+ Informasi Tabel
+
+
+
+
+
Kembali
+
Simpan Tabel
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/ekosistem/tabel-basis-data/index.blade.php b/resources/views/ekosistem/tabel-basis-data/index.blade.php
new file mode 100644
index 0000000..9d57299
--- /dev/null
+++ b/resources/views/ekosistem/tabel-basis-data/index.blade.php
@@ -0,0 +1,155 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+ Show
+
+ 10
+ 25
+ 50
+
+ entries
+
+
+
+
+
+
+
+
+
+
+ No
+ Nama Tabel
+ Label
+ Menu
+ Kelompok
+ Aksi
+
+
+
+ @forelse($tabel as $index => $tbl)
+
+ {{ $loop->iteration }}
+ {{ $tbl->tabel }}
+ {{ $tbl->label }}
+ {{ $tbl->menu }}
+ {{ $tbl->group?->group }}
+
+
+ @if (!$loop->first)
+
+
+
+ @endif
+ @if (!$loop->last)
+
+
+
+ @endif
+
+
+
+
+
+
+ @empty
+
+ Tidak ada data ditemukan.
+
+ @endforelse
+
+
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/ekosistem/tabel-basis-data/view.blade.php b/resources/views/ekosistem/tabel-basis-data/view.blade.php
new file mode 100644
index 0000000..a842c32
--- /dev/null
+++ b/resources/views/ekosistem/tabel-basis-data/view.blade.php
@@ -0,0 +1,173 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+
+ Informasi Tabel
+
+
+ Nama Tabel
+
+
+
+ Label
+
+
+
+ Judul Menu
+
+
+
+
+
+ Informasi Kolom-Kolom
+
+
+
+
+
+
+
+
+
+ @include('ekosistem.kolom-basis-data.add')
+ @include('ekosistem.kolom-basis-data.edit')
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/ekosistem/value-basis-data/add.blade.php b/resources/views/ekosistem/value-basis-data/add.blade.php
new file mode 100644
index 0000000..a644617
--- /dev/null
+++ b/resources/views/ekosistem/value-basis-data/add.blade.php
@@ -0,0 +1,92 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+ @csrf
+
+ Informasi {{ $tabel->label }}
+
+ @foreach($kolom as $k)
+
+ @endforeach
+
+
+
+
+
Kembali
+
Simpan Data
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/ekosistem/value-basis-data/edit.blade.php b/resources/views/ekosistem/value-basis-data/edit.blade.php
new file mode 100644
index 0000000..26d7a28
--- /dev/null
+++ b/resources/views/ekosistem/value-basis-data/edit.blade.php
@@ -0,0 +1,96 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+ @csrf
+ @method('PUT')
+
+ Informasi {{ $tabel->label }}
+
+ @foreach($kolom as $k)
+ where('kolom_id', $k->id)->first();
+ ?>
+
+ @endforeach
+
+
+
+
+
Kembali
+
Simpan Data
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/ekosistem/value-basis-data/index.blade.php b/resources/views/ekosistem/value-basis-data/index.blade.php
new file mode 100644
index 0000000..b516c03
--- /dev/null
+++ b/resources/views/ekosistem/value-basis-data/index.blade.php
@@ -0,0 +1,129 @@
+@extends('layout.main')
+
+@section('content')
+
+
+
+
+
+
+
+
+ Show
+
+ 10
+ 25
+ 50
+
+ entries
+
+
+
+ @if (app()->has('SystemProgram'))
+ @else
+
+
+ Program
+
+ Pilih Program
+ @foreach($programList as $prog)
+ id ? 'selected' : '' }}>{{ $prog->nama }}
+ @endforeach
+
+ entries
+
+
+ @endif
+
+
+
+
+
+ Tambah
+
+
+
+
+
+
+
+
+
+ No
+ @foreach($kolom as $k)
+ {{ $k->label }}
+ @endforeach
+ Aksi
+
+
+
+ @forelse($values as $v)
+
+ {{ $loop->iteration }}
+ @foreach($kolom as $k)
+ {{ $v->{$k->kolom} }}
+ @endforeach
+
+
+
+
+ @empty
+
+ Tidak ada data ditemukan.
+
+ @endforelse
+
+
+
+
+
+
+
+@endsection
+
+@push('scripts')
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/layout/navbar.blade.php b/resources/views/layout/navbar.blade.php
index c4d4ad9..629f8b6 100644
--- a/resources/views/layout/navbar.blade.php
+++ b/resources/views/layout/navbar.blade.php
@@ -7,44 +7,23 @@