Update workshop

This commit is contained in:
2026-05-04 11:55:58 +07:00
parent dec76e8b1d
commit f040dcdc55
17 changed files with 1248 additions and 69 deletions
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace App\Enums;
enum JenisWorkshop: string
{
case LOKAKARYA = 'lokakarya';
case PELATIHAN = 'pelatihan';
case SOSIALISASI = 'sosialisasi';
}
+12
View File
@@ -0,0 +1,12 @@
<?php
namespace App\Enums;
enum StatusWorkshop: string
{
case DRAFT = 'draft';
case AKTIF = 'aktif';
case TUNDA = 'tunda';
case BATAL = 'batal';
case SELESAI = 'selesai';
}
@@ -1,11 +0,0 @@
<?php
namespace App\Http\Controllers\Web\Program;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class PelatihanController extends Controller
{
//
}
@@ -0,0 +1,189 @@
<?php
namespace App\Http\Controllers\Web\Program;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Program\Workshop;
use App\Models\Program\SesiWorkshop;
use App\Models\Program\PesertaWorkshop;
use App\Models\Program\Aktivitas;
use App\Enums\StatusWorkshop;
class WorkshopController extends Controller
{
public function index(Request $request)
{
$title = 'Workshop';
$summary = ([
'kegiatan' => Workshop::count(),
'lokakarya' => Workshop::where('jenis', 'lokakarya')->count(),
'pelatihan' => Workshop::where('jenis', 'pelatihan')->count(),
'sosialisasi' => Workshop::where('jenis', 'sosialisasi')->count(),
'sesi' => SesiWorkshop::count(),
'peserta' => PesertaWorkshop::count(),
]);
$query = Workshop::query();
if ($request->filled('search')) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('judul', 'ilike', "%{$search}%")
->orWhere('deskripsi', 'ilike', "%{$search}%");
});
}
if ($request->filled('jenis')) {
$query->where('jenis', $request->jenis);
}
if ($request->filled('status')) {
$query->where('status', $request->status);
}
if ($request->filled('tanggal_awal') && $request->filled('tanggal_akhir')) {
$query->whereBetween('tanggal_awal', [$request->tanggal_awal, $request->tanggal_akhir]);
} elseif ($request->filled('tanggal_awal')) {
$query->where('tanggal_awal', '>=', $request->tanggal_awal);
} elseif ($request->filled('tanggal_akhir')) {
$query->where('tanggal_akhir', '<=', $request->tanggal_akhir);
}
$perPage = $request->input('per_page', 10);
$workshops = $query->latest()->paginate($perPage);
return view('program.workshop.index', compact('workshops', 'summary', 'title'));
}
public function create()
{
$programs = Aktivitas::latest()->get();
return view('program.workshop.add', compact('programs'));
}
public function store(Request $request)
{
$validated = $request->validate([
'program_aktivitas_id' => 'nullable|integer',
'judul' => 'required|string|max:255',
'jenis' => 'required|string',
'tanggal_awal' => 'required|date',
'tanggal_akhir' => 'required|date|after_or_equal:tanggal_awal',
'status' => 'required|string',
'deskripsi' => 'nullable|string',
], [
'program_aktivitas_id.integer' => 'Program aktivitas harus berupa angka.',
'judul.required' => 'Judul wajib diisi.',
'judul.string' => 'Judul harus berupa teks.',
'judul.max' => 'Judul maksimal 255 karakter.',
'jenis.required' => 'Jenis wajib diisi.',
'jenis.string' => 'Jenis harus berupa teks.',
'tanggal_awal.required' => 'Tanggal awal wajib diisi.',
'tanggal_awal.date' => 'Tanggal awal harus berupa tanggal yang valid.',
'tanggal_akhir.required' => 'Tanggal akhir wajib diisi.',
'tanggal_akhir.date' => 'Tanggal akhir harus berupa tanggal yang valid.',
'tanggal_akhir.after_or_equal' => 'Tanggal akhir harus setelah atau sama dengan tanggal awal.',
'status.required' => 'Status wajib diisi.',
'status.string' => 'Status harus berupa teks.',
'deskripsi.string' => 'Deskripsi harus berupa teks.',
]);
$workshop = Workshop::create([
'judul' => $request->judul,
'jenis' => $request->jenis,
'tanggal_awal' => $request->tanggal_awal,
'tanggal_akhir' => $request->tanggal_akhir,
'status' => $request->status,
'deskripsi' => $request->deskripsi,
'program_aktivitas_id' => $request->program_aktivitas_id ?? null,
'created_by' => auth()->user()->id,
'updated_by' => auth()->user()->id,
]);
return response()->json([
'status' => 'success',
'message' => 'Workshop berhasil disimpan sebagai Draft.'
]);
}
public function edit($id)
{
$workshop = Workshop::findOrFail($id);
$programs = Aktivitas::latest()->get();
return view('program.workshop.edit', compact('workshop', 'programs'));
}
public function update(Request $request, $id)
{
$validated = $request->validate([
'program_aktivitas_id' => 'nullable|integer',
'judul' => 'required|string|max:255',
'jenis' => 'required|string',
'tanggal_awal' => 'required|date',
'tanggal_akhir' => 'required|date|after_or_equal:tanggal_awal',
'status' => 'required|string',
'deskripsi' => 'nullable|string',
], [
'program_aktivitas_id.integer' => 'Program aktivitas harus berupa angka.',
'judul.required' => 'Judul wajib diisi.',
'judul.string' => 'Judul harus berupa teks.',
'judul.max' => 'Judul maksimal 255 karakter.',
'jenis.required' => 'Jenis wajib diisi.',
'jenis.string' => 'Jenis harus berupa teks.',
'tanggal_awal.required' => 'Tanggal awal wajib diisi.',
'tanggal_awal.date' => 'Tanggal awal harus berupa tanggal yang valid.',
'tanggal_akhir.required' => 'Tanggal akhir wajib diisi.',
'tanggal_akhir.date' => 'Tanggal akhir harus berupa tanggal yang valid.',
'tanggal_akhir.after_or_equal' => 'Tanggal akhir harus setelah atau sama dengan tanggal awal.',
'status.required' => 'Status wajib diisi.',
'status.string' => 'Status harus berupa teks.',
'deskripsi.string' => 'Deskripsi harus berupa teks.',
]);
$workshop = Workshop::findOrFail($id);
$workshop->update([
'judul' => $request->judul,
'jenis' => $request->jenis,
'tanggal_awal' => $request->tanggal_awal,
'tanggal_akhir' => $request->tanggal_akhir,
'status' => $request->status,
'deskripsi' => $request->deskripsi,
'program_aktivitas_id' => $request->program_aktivitas_id ?? null,
'updated_by' => auth()->user()->id,
]);
return response()->json([
'status' => 'success',
'message' => 'Perubahan workshop berhasil disimpan.'
]);
}
public function destroy($id)
{
$workshop = Workshop::findOrFail($id);
$workshop->update(['deleted_by' => auth()->user()->id]);
$workshop->delete();
return response()->json([
'status' => 'success',
'message' => 'Workshop berhasil dihapus.'
]);
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace App\Models\Program;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Profile;
use App\Models\Program\SesiWorkshop;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class PesertaWorkshop extends Model
{
use HasFactory, SoftDeletes;
protected $table = 'peserta_workshop';
protected $fillable = [
'sesi_workshop_id',
'profil_id',
'status',
'created_by',
'updated_by',
'deleted_by',
];
public function profil()
{
return $this->belongsTo(Profile::class, 'profil_id');
}
public function sesiWorkshop()
{
return $this->belongsTo(SesiWorkshop::class, 'sesi_workshop_id');
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace App\Models\Program;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Program\Workshop;
use App\Models\Program\PesertaWorkshop;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class SesiWorkshop extends Model
{
use HasFactory, SoftDeletes;
protected $table = 'sesi_workshop';
protected $fillable = [
'workshop_id',
'judul',
'deskripsi',
'tanggal_awal',
'tanggal_akhir',
'status',
'created_by',
'updated_by',
'deleted_by',
];
public function workshop()
{
return $this->belongsTo(Workshop::class, 'workshop_id');
}
public function pesertaWorkshop()
{
return $this->hasMany(PesertaWorkshop::class, 'sesi_workshop_id');
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace App\Models\Program;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Program\Aktivitas;
use App\Models\Program\SesiWorkshop;
use App\Enums\StatusWorkshop;
use App\Enums\JenisWorkshop;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Workshop extends Model
{
use HasFactory, SoftDeletes;
protected $table = 'workshop';
protected $casts = [
'status' => StatusWorkshop::class,
'jenis' => JenisWorkshop::class,
];
protected $fillable = [
'program_aktivitas_id',
'judul',
'deskripsi',
'jenis',
'tanggal_awal',
'tanggal_akhir',
'status',
'created_by',
'updated_by',
'deleted_by',
];
public function program()
{
return $this->belongsTo(Aktivitas::class, 'program_aktivitas_id');
}
public function sesiWorkshop()
{
return $this->hasMany(SesiWorkshop::class, 'workshop_id');
}
}
@@ -15,7 +15,7 @@ return new class extends Migration
$table->id(); $table->id();
$table->string('kode', 2)->unique(); $table->string('kode', 2)->unique();
$table->string('nama', 100); $table->string('nama', 100);
$table->string('path', 10000); $table->jsonb('path')->nullable();
$table->string('iso', 100); $table->string('iso', 100);
$table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null'); $table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null'); $table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
@@ -28,7 +28,7 @@ return new class extends Migration
$table->id(); $table->id();
$table->string('kode', 4)->unique(); $table->string('kode', 4)->unique();
$table->string('nama', 100); $table->string('nama', 100);
$table->string('path', 10000); $table->jsonb('path')->nullable();
$table->foreignId('provinsi_id')->constrained('master_provinsi')->onDelete('set null'); $table->foreignId('provinsi_id')->constrained('master_provinsi')->onDelete('set null');
$table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null'); $table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null'); $table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
@@ -41,7 +41,7 @@ return new class extends Migration
$table->id(); $table->id();
$table->string('kode', 6)->unique(); $table->string('kode', 6)->unique();
$table->string('nama', 100); $table->string('nama', 100);
$table->string('path', 10000); $table->jsonb('path')->nullable();
$table->foreignId('kabupaten_kota_id')->constrained('master_kabupaten_kota')->onDelete('set null'); $table->foreignId('kabupaten_kota_id')->constrained('master_kabupaten_kota')->onDelete('set null');
$table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null'); $table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null'); $table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
@@ -54,7 +54,7 @@ return new class extends Migration
$table->id(); $table->id();
$table->string('kode', 10)->unique(); $table->string('kode', 10)->unique();
$table->string('nama', 100); $table->string('nama', 100);
$table->string('path', 10000); $table->jsonb('path')->nullable();
$table->foreignId('kecamatan_id')->constrained('master_kecamatan')->onDelete('set null'); $table->foreignId('kecamatan_id')->constrained('master_kecamatan')->onDelete('set null');
$table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null'); $table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null'); $table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
@@ -0,0 +1,78 @@
<?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('workshop', function (Blueprint $table) {
$table->id();
$table->foreignId('program_aktivitas_id')->nullable()->constrained('program_aktivitas')->nullOnDelete();
$table->string('judul');
$table->text('deskripsi')->nullable();
$table->string('jenis')->nullable();
$table->date('tanggal_awal')->nullable();
$table->date('tanggal_akhir')->nullable();
$table->string('status')->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('sesi_workshop', function (Blueprint $table) {
$table->id();
$table->foreignId('workshop_id')->nullable()->constrained('workshop')->nullOnDelete();
$table->string('judul');
$table->text('deskripsi')->nullable();
$table->foreignId('desa_kelurahan_id')->nullable()->constrained('master_desa_kelurahan');
$table->string('lokasi')->nullable();
$table->timestamp('tanggal_awal')->nullable();
$table->timestamp('tanggal_akhir')->nullable();
$table->string('status')->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('peserta_workshop', function (Blueprint $table) {
$table->id();
$table->foreignId('sesi_workshop_id')->nullable()->constrained('sesi_workshop')->nullOnDelete();
$table->foreignId('profile_id')->nullable()->constrained('profiles')->nullOnDelete();
$table->string('status')->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('peserta_workshop');
Schema::dropIfExists('sesi_workshop');
Schema::dropIfExists('workshop');
}
};
@@ -338,7 +338,6 @@
@push('scripts') @push('scripts')
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script> <script>
// 1. Setup Base Map (Gunakan HTTPS untuk menghindari error mixed-content)
var satellite = L.tileLayer('https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', { maxZoom: 22 }); 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 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 parcels = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png');
@@ -346,7 +345,6 @@
var map = L.map('map', { center: [-0.789, 113.921], zoom: 5, layers: [satellite], zoomControl: false }); var map = L.map('map', { center: [-0.789, 113.921], zoom: 5, layers: [satellite], zoomControl: false });
L.control.zoom({ position: 'topleft' }).addTo(map); L.control.zoom({ position: 'topleft' }).addTo(map);
// Map Switcher Custom
function setMapLayer(type) { function setMapLayer(type) {
map.removeLayer(satellite); map.removeLayer(topo); map.removeLayer(parcels); map.removeLayer(satellite); map.removeLayer(topo); map.removeLayer(parcels);
if(type === 'satellite') satellite.addTo(map); if(type === 'satellite') satellite.addTo(map);
@@ -369,23 +367,16 @@
var mapHistory = []; var mapHistory = [];
var btnBack = document.getElementById('btnBackMap'); var btnBack = document.getElementById('btnBackMap');
// ==========================================
// FUNGSI PINTAR: DETEKSI LATITUDE/LONGITUDE
// ==========================================
function ensureLatLon(coords) { function ensureLatLon(coords) {
if (typeof coords[0] === 'number') { if (typeof coords[0] === 'number') {
// Indonesia: Longitude = ~95 s/d ~141 | Latitude = ~-11 s/d ~6
// Jika angka pertama melebihi 90, itu PASTI Longitude. Kita balik agar menjadi [Lat, Lon]
if (Math.abs(coords[0]) > 90) { if (Math.abs(coords[0]) > 90) {
return [coords[1], coords[0]]; return [coords[1], coords[0]];
} }
// Jika angka pertama kecil, berarti sudah benar [Lat, Lon], biarkan saja
return [coords[0], coords[1]]; return [coords[0], coords[1]];
} }
return coords.map(ensureLatLon); return coords.map(ensureLatLon);
} }
// 3. Fungsi Render Peta Dinamis
function renderLevel(level, parentId = null, fallbackBounds = null) { function renderLevel(level, parentId = null, fallbackBounds = null) {
var dataToRender = dataLevels[level]; var dataToRender = dataLevels[level];
@@ -403,11 +394,7 @@
currentLayerGroup.clearLayers(); currentLayerGroup.clearLayers();
// =========================================================
// FITUR BARU: GAMBAR BATAS DESA SAAT MELIHAT LAHAN
// =========================================================
if (level === 'lahan' && parentId !== null) { if (level === 'lahan' && parentId !== null) {
// Cari data desa yang sedang diklik
var parentDesa = dataLevels['desa'].find(d => d.id == parentId); var parentDesa = dataLevels['desa'].find(d => d.id == parentId);
if (parentDesa && parentDesa.polygon) { if (parentDesa && parentDesa.polygon) {
@@ -415,13 +402,12 @@
var desaGeojson = typeof parentDesa.polygon === 'string' ? JSON.parse(parentDesa.polygon) : parentDesa.polygon; var desaGeojson = typeof parentDesa.polygon === 'string' ? JSON.parse(parentDesa.polygon) : parentDesa.polygon;
var desaLayer; var desaLayer;
// Style untuk batas Desa (Garis putus-putus merah, isian transparan)
var desaStyle = { var desaStyle = {
color: '#dc3545', // Merah color: '#dc3545',
weight: 2, weight: 2,
dashArray: '5, 8', // Garis putus-putus dashArray: '5, 8',
fillColor: 'none', // Tidak ada warna isian fillColor: 'none',
interactive: false // Tidak bisa diklik agar tidak menutupi klik lahan interactive: false
}; };
if (desaGeojson !== null && typeof desaGeojson === 'object' && desaGeojson.type) { if (desaGeojson !== null && typeof desaGeojson === 'object' && desaGeojson.type) {
@@ -439,7 +425,6 @@
desaLayer = L.polygon(safeCoordsDesa, desaStyle); desaLayer = L.polygon(safeCoordsDesa, desaStyle);
} }
// Tambahkan layer desa ke peta
if (desaLayer) { if (desaLayer) {
currentLayerGroup.addLayer(desaLayer); currentLayerGroup.addLayer(desaLayer);
} }
@@ -448,9 +433,7 @@
} }
} }
} }
// =========================================================
// Render data utama (Lahan / Wilayah Administratif)
dataToRender.forEach(function(item) { dataToRender.forEach(function(item) {
if (!item.polygon) return; if (!item.polygon) return;
try { try {
@@ -478,19 +461,15 @@
if (layer) { if (layer) {
if (level === 'lahan') { if (level === 'lahan') {
// 1. Handling Khusus untuk Komoditas/Tanaman
var listTanaman = item.tanaman && item.tanaman.length > 0 ? item.tanaman.join(', ') : null; var listTanaman = item.tanaman && item.tanaman.length > 0 ? item.tanaman.join(', ') : null;
// Teks & Style conditional untuk komoditas kosong
var komoditasText = listTanaman var komoditasText = listTanaman
? listTanaman ? listTanaman
: '<span class="text-danger-light fw-medium small">Belum terdaftar</span>'; : '<span class="text-danger-light fw-medium small">Belum terdaftar</span>';
// Ikon conditional (ikon tanaman jika ada, ikon off jika kosong)
var cropIcon = listTanaman ? 'ti tabler-plant' : 'ti tabler-leaf-off'; var cropIcon = listTanaman ? 'ti tabler-plant' : 'ti tabler-leaf-off';
var cropIconColor = listTanaman ? 'text-success' : 'text-danger'; var cropIconColor = listTanaman ? 'text-success' : 'text-danger';
// 2. Tampilan Baru Popup Lahan (Modern Card)
var popupContent = ` var popupContent = `
<div class="modern-lahan-popup"> <div class="modern-lahan-popup">
<div class="p-3 bg-white rounded-top-12 header-farmer"> <div class="p-3 bg-white rounded-top-12 header-farmer">
@@ -538,16 +517,13 @@
</div> </div>
`; `;
// Pengaturan Popup Leaflet
layer.bindPopup(popupContent, { layer.bindPopup(popupContent, {
maxWidth: 300, // Membatasi lebar agar tetap rapi maxWidth: 300,
closeButton: true, // Menampilkan tombol close pro closeButton: true,
offset: [0, 0], // Penyesuaian jarak tip offset: [0, 0],
className: 'leaflet-modern-popup-wrapper' // Kelas CSS kustom (Langkah 2) className: 'leaflet-modern-popup-wrapper'
}); });
} else { } else {
// FORMAT TOOLTIP BARU: Struktur Card Modern
// Menggunakan template literal (backticks ``) agar rapi
var tooltipContent = ` var tooltipContent = `
<div class="modern-map-card"> <div class="modern-map-card">
<div class="card-header-region"> <div class="card-header-region">
@@ -585,15 +561,13 @@
</div> </div>
`; `;
// Pengaturan Tooltip Leaflet
layer.bindTooltip(tooltipContent, { layer.bindTooltip(tooltipContent, {
sticky: true, // Ikut kursor sticky: true,
direction: 'top', // Muncul di atas kursor direction: 'top',
className: 'leaflet-modern-tooltip', // Kelas CSS kustom (Langkah 2) className: 'leaflet-modern-tooltip',
offset: [0, -10] // Jarak dari kursor agar tidak menutupi poligon offset: [0, -10]
}); });
// Logika Klik (Hapalan dari sebelumnya - jangan diubah)
layer.on('click', function(e) { layer.on('click', function(e) {
L.DomEvent.stopPropagation(e); L.DomEvent.stopPropagation(e);
var nextLevel = level === 'provinsi' ? 'kabupaten' var nextLevel = level === 'provinsi' ? 'kabupaten'
@@ -623,7 +597,6 @@
} }
} }
// Aksi Tombol Kembali
if (btnBack) { if (btnBack) {
btnBack.addEventListener('click', function() { btnBack.addEventListener('click', function() {
var prevState = mapHistory.pop(); var prevState = mapHistory.pop();
@@ -636,7 +609,6 @@
}); });
} }
// Pastikan ukuran peta sesuai dengan container saat dimuat
setTimeout(() => { setTimeout(() => {
map.invalidateSize(); map.invalidateSize();
// Render Awal: Provinsi // Render Awal: Provinsi
+2
View File
@@ -55,6 +55,8 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
@stack('style') @stack('style')
<script src="{{ asset('assets/vendor/js/helpers.js') }}"></script> <script src="{{ asset('assets/vendor/js/helpers.js') }}"></script>
+16 -12
View File
@@ -82,6 +82,10 @@
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script src="https://npmcdn.com/flatpickr/dist/l10n/id.js"></script>
<script> <script>
$(document).ready(function() { $(document).ready(function() {
$('#logout').on('click', function(e) { $('#logout').on('click', function(e) {
@@ -90,23 +94,23 @@
}); });
}); });
</script> </script>
<script src="https://cdn.tailwindcss.com"></script> <!-- <script src="https://cdn.tailwindcss.com"></script> -->
<!-- Dark Mode Shifting --> <!-- Dark Mode Shifting -->
<style type="text/tailwindcss"> <!-- <style type="text/tailwindcss">
@layer utilities { @layer utilities {
.teks-header-judul { .teks-header-judul {
@apply text-gray-800 dark:text-gray-80; @apply text-gray-800 dark:text-gray-80;
}
.teks-header-sub {
@apply text-gray-500 dark:text-gray-300;
}
} }
</style> -->
.teks-header-sub {
@apply text-gray-500 dark:text-gray-300;
}
}
</style>
@stack('scripts') @stack('scripts')
</body> </body>
</html> </html>
+1 -1
View File
@@ -170,7 +170,7 @@
</ul> </ul>
<ul class="menu-sub"> <ul class="menu-sub">
<li class="menu-item"> <li class="menu-item">
<a href="#" class="menu-link"> <a href="{{ route('workshop.index') }}" class="menu-link">
<div data-i18n="Workshop & Lokakarya">Workshop & Lokakarya</div> <div data-i18n="Workshop & Lokakarya">Workshop & Lokakarya</div>
</a> </a>
</li> </li>
@@ -0,0 +1,210 @@
@extends('layout.main')
@section('content')
<div class="container-xxl flex-grow-1 container-p-y">
<div class="card mb-4">
<div class="card-header border-bottom d-flex justify-content-between align-items-center pt-4 pb-3">
<h5 class="mb-0">Tambah Workshop Baru</h5>
<span class="badge bg-label-secondary">Status: Draft</span>
</div>
<form action="{{ route('workshop.store') }}" method="POST" id="formAddWorkshop">
@csrf
<input type="hidden" name="status" value="draft">
<div class="card-body pt-4">
<div class="mb-2">
<div class="d-flex align-items-center mb-3">
<i class="ti tabler-settings me-2 text-primary"></i>
<h6 class="fw-medium mb-0 text-uppercase">Konfigurasi & Penjadwalan</h6>
</div>
<div class="row g-4 align-items-end">
<div class="col-md-3">
<label class="form-label" for="program_id">Pilih Program Induk</label>
<select class="form-select @error('program_id') is-invalid @enderror" id="program_id" name="program_id">
<option value="">-- Tidak Terikat Program --</option>
@foreach($programs as $program)
<option value="{{ $program->id }}" {{ old('program_id') == $program->id ? 'selected' : '' }}>
{{ $program->nama_program ?? $program->judul }}
</option>
@endforeach
</select>
@error('program_id') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div class="col-md-3">
<label class="form-label" for="jenis">Jenis Program <span class="text-danger">*</span></label>
<select class="form-select @error('jenis') is-invalid @enderror" id="jenis" name="jenis" required>
<option value="" disabled selected>-- Pilih Jenis Program --</option>
@foreach(App\Enums\JenisWorkshop::cases() as $jenisEnum)
<option value="{{ $jenisEnum->value }}" {{ old('jenis') == $jenisEnum->value ? 'selected' : '' }}>
{{ ucfirst($jenisEnum->value) }}
</option>
@endforeach
</select>
@error('jenis') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div class="col-md-3">
<label class="form-label" for="tanggal_awal">Mulai Kegiatan <span class="text-danger">*</span></label>
<div class="input-group">
<span class="input-group-text"><i class="ti tabler-calendar"></i></span>
<input type="text" class="form-control flatpickr-date" id="tanggal_awal" name="tanggal_awal" placeholder="DD/MM/YYYY" required>
</div>
</div>
<div class="col-md-3">
<label class="form-label" for="tanggal_akhir">Selesai Kegiatan <span class="text-danger">*</span></label>
<div class="input-group">
<span class="input-group-text"><i class="ti tabler-calendar"></i></span>
<input type="text" class="form-control flatpickr-date" id="tanggal_akhir" name="tanggal_akhir" placeholder="DD/MM/YYYY" required>
</div>
</div>
</div>
</div>
<hr class="my-4">
<div class="mb-4">
<div class="d-flex align-items-center mb-3">
<i class="ti tabler-info-circle me-2 text-primary"></i>
<h6 class="fw-medium mb-0 text-uppercase">Informasi Dasar</h6>
</div>
<div class="row g-4">
<div class="col-12">
<label class="form-label" for="judul">Judul Workshop / Kegiatan <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="judul" name="judul" placeholder="Masukkan judul workshop..." value="{{ old('judul') }}" required>
</div>
<div class="col-12">
<label class="form-label" for="deskripsi">Deskripsi Singkat</label>
<textarea class="form-control" id="deskripsi" name="deskripsi" rows="3" placeholder="Jelaskan tujuan atau ringkasan kegiatan..."></textarea>
</div>
</div>
<div class="col-12 mt-4">
<div class="alert alert-label-info d-flex align-items-center border-0 mb-0" role="alert">
<span class="alert-icon me-2">
<i class="ti ti-info-circle ti-xs"></i>
</span>
<div>
Kegiatan baru akan disimpan sebagai <strong>Draft</strong>. Anda dapat mengaktifkannya nanti melalui halaman kelola.
</div>
</div>
</div>
</div>
</div>
<div class="card-footer border-top pt-4 pb-4 d-flex justify-content-end gap-3 bg-lighter">
<a href="{{ route('workshop.index') }}" class="btn btn-label-secondary">Kembali ke Daftar</a>
<button type="submit" class="btn btn-primary px-4">
<i class="ti tabler-device-floppy me-1"></i> Simpan Workshop
</button>
</div>
</form>
</div>
</div>
@endsection
@push('scripts')
<script>
'use strict';
document.addEventListener('DOMContentLoaded', function () {
// 1. Inisialisasi Flatpickr
const dateInputs = document.querySelectorAll('.flatpickr-date');
if (dateInputs.length) {
flatpickr(dateInputs, {
locale: "id",
altInput: true,
altFormat: "l, d/m/Y",
dateFormat: "Y-m-d",
allowInput: true
});
}
// 2. Submit Form menggunakan AJAX (Fetch API) persis seperti halaman Lahan
const formAddWorkshop = document.getElementById('formAddWorkshop');
if(formAddWorkshop) {
formAddWorkshop.addEventListener('submit', function(e) {
e.preventDefault(); // Mencegah reload halaman
let form = this;
let formData = new FormData(form);
let submitBtn = form.querySelector('button[type="submit"]');
let originalBtnText = submitBtn.innerHTML;
// Disable tombol dan ubah teks jadi Menyimpan...
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span> Menyimpan...';
fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json' // Meminta respons JSON dari Laravel
}
})
.then(async response => {
// Karena Laravel mengembalikan 422 jika validasi gagal, kita tangkap statusnya
const isJson = response.headers.get('content-type')?.includes('application/json');
const data = isJson ? await response.json() : null;
if (!response.ok) {
const error = (data && data.message) || response.statusText;
return Promise.reject({ status: response.status, data: data, message: error });
}
return data;
})
.then(data => {
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
if (data.status === 'success') {
Swal.fire({
icon: 'success',
title: 'Berhasil!',
text: data.message,
showConfirmButton: false,
timer: 2000,
customClass: { confirmButton: 'btn btn-primary' },
buttonsStyling: false
}).then(() => {
// Redirect ke index setelah sukses
window.location.href = "{{ route('workshop.index') }}";
});
}
})
.catch(error => {
// Kembalikan tombol ke semula
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
let errorMsg = 'Terjadi kesalahan saat menyimpan data.';
// Jika error validasi Laravel (Status 422)
if (error.status === 422 && error.data && error.data.errors) {
errorMsg = Object.values(error.data.errors).map(err => err.join(', ')).join('<br>');
} else if (error.data && error.data.message) {
errorMsg = error.data.message;
}
Swal.fire({
icon: 'error',
title: 'Data Tidak Valid',
html: errorMsg,
customClass: { confirmButton: 'btn btn-primary' },
buttonsStyling: false
});
});
});
}
});
</script>
@endpush
@@ -0,0 +1,220 @@
@extends('layout.main')
@section('content')
<div class="container-xxl flex-grow-1 container-p-y">
<div class="card mb-4">
<div class="card-header border-bottom d-flex justify-content-between align-items-center pt-4 pb-3">
<h5 class="mb-0">Edit Workshop</h5>
@php
$badgeColor = match($workshop->status->value) {
'aktif' => 'bg-label-success',
'tunda' => 'bg-label-warning',
'batal' => 'bg-label-danger',
'draft' => 'bg-label-secondary',
'selesai' => 'bg-label-primary',
default => 'bg-label-secondary'
};
@endphp
<span class="badge {{ $badgeColor }}">Status: {{ ucfirst($workshop->status->value) }}</span>
</div>
<form action="{{ route('workshop.update', $workshop->id) }}" method="POST" id="formEditWorkshop">
@csrf
@method('PUT') <div class="card-body pt-4">
<div class="mb-2">
<div class="d-flex align-items-center mb-3">
<i class="ti tabler-settings me-2 text-primary"></i>
<h6 class="fw-medium mb-0 text-uppercase">Konfigurasi & Penjadwalan</h6>
</div>
<div class="row g-4 align-items-end">
<div class="col-md-3">
<label class="form-label" for="program_id">Pilih Program Induk</label>
<select class="form-select @error('program_id') is-invalid @enderror" id="program_id" name="program_id">
<option value="">-- Tidak Terikat Program --</option>
@foreach($programs as $program)
<option value="{{ $program->id }}" {{ old('program_id', $workshop->program_id) == $program->id ? 'selected' : '' }}>
{{ $program->nama_program ?? $program->judul }}
</option>
@endforeach
</select>
@error('program_id') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div class="col-md-3">
<label class="form-label" for="jenis">Jenis Program <span class="text-danger">*</span></label>
<select class="form-select @error('jenis') is-invalid @enderror" id="jenis" name="jenis" required>
<option value="" disabled>-- Pilih Jenis Program --</option>
@foreach(App\Enums\JenisWorkshop::cases() as $jenisEnum)
<option value="{{ $jenisEnum->value }}" {{ old('jenis', $workshop->jenis->value) == $jenisEnum->value ? 'selected' : '' }}>
{{ ucfirst($jenisEnum->value) }}
</option>
@endforeach
</select>
@error('jenis') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div class="col-md-3">
<label class="form-label" for="tanggal_awal">Mulai Kegiatan <span class="text-danger">*</span></label>
<div class="input-group">
<span class="input-group-text"><i class="ti tabler-calendar"></i></span>
<input type="text" class="form-control flatpickr-date" id="tanggal_awal" name="tanggal_awal" placeholder="DD/MM/YYYY" value="{{ old('tanggal_awal', $workshop->tanggal_awal) }}" required>
</div>
</div>
<div class="col-md-3">
<label class="form-label" for="tanggal_akhir">Selesai Kegiatan <span class="text-danger">*</span></label>
<div class="input-group">
<span class="input-group-text"><i class="ti tabler-calendar"></i></span>
<input type="text" class="form-control flatpickr-date" id="tanggal_akhir" name="tanggal_akhir" placeholder="DD/MM/YYYY" value="{{ old('tanggal_akhir', $workshop->tanggal_akhir) }}" required>
</div>
</div>
</div>
</div>
<hr class="my-4">
<div class="mb-4">
<div class="d-flex align-items-center mb-3">
<i class="ti tabler-info-circle me-2 text-primary"></i>
<h6 class="fw-medium mb-0 text-uppercase">Informasi Dasar & Status</h6>
</div>
<div class="row g-4">
<div class="col-md-8">
<label class="form-label" for="judul">Judul Workshop / Kegiatan <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="judul" name="judul" placeholder="Masukkan judul workshop..." value="{{ old('judul', $workshop->judul) }}" required>
</div>
<div class="col-md-4">
<label class="form-label" for="status">Status Saat Ini <span class="text-danger">*</span></label>
<select class="form-select @error('status') is-invalid @enderror" id="status" name="status" required>
@foreach(App\Enums\StatusWorkshop::cases() as $statusEnum)
<option value="{{ $statusEnum->value }}" {{ old('status', $workshop->status->value) == $statusEnum->value ? 'selected' : '' }}>
{{ ucfirst($statusEnum->value) }}
</option>
@endforeach
</select>
@error('status') <div class="invalid-feedback">{{ $message }}</div> @enderror
</div>
<div class="col-12">
<label class="form-label" for="deskripsi">Deskripsi Singkat</label>
<textarea class="form-control" id="deskripsi" name="deskripsi" rows="3" placeholder="Jelaskan tujuan atau ringkasan kegiatan...">{{ old('deskripsi', $workshop->deskripsi) }}</textarea>
</div>
</div>
</div>
</div>
<div class="card-footer border-top pt-4 pb-4 d-flex justify-content-end gap-3 bg-lighter">
<a href="{{ route('workshop.index') }}" class="btn btn-label-secondary">Batal</a>
<button type="submit" class="btn btn-primary px-4">
<i class="ti tabler-device-floppy me-1"></i> Simpan Perubahan
</button>
</div>
</form>
</div>
</div>
@endsection
@push('scripts')
<script>
'use strict';
document.addEventListener('DOMContentLoaded', function () {
// 1. Inisialisasi Flatpickr
const dateInputs = document.querySelectorAll('.flatpickr-date');
if (dateInputs.length) {
flatpickr(dateInputs, {
locale: "id",
altInput: true,
altFormat: "l, d/m/Y",
dateFormat: "Y-m-d",
allowInput: true
});
}
// 2. Submit Form menggunakan AJAX (Fetch API) persis seperti solusi Add Workshop
const formEditWorkshop = document.getElementById('formEditWorkshop');
if(formEditWorkshop) {
formEditWorkshop.addEventListener('submit', function(e) {
e.preventDefault(); // Mencegah reload halaman
let form = this;
let formData = new FormData(form);
let submitBtn = form.querySelector('button[type="submit"]');
let originalBtnText = submitBtn.innerHTML;
// Disable tombol dan ubah teks jadi Menyimpan...
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span> Menyimpan...';
fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json' // Meminta respons JSON dari Laravel
}
})
.then(async response => {
// Karena Laravel mengembalikan 422 jika validasi gagal, kita tangkap statusnya
const isJson = response.headers.get('content-type')?.includes('application/json');
const data = isJson ? await response.json() : null;
if (!response.ok) {
const error = (data && data.message) || response.statusText;
return Promise.reject({ status: response.status, data: data, message: error });
}
return data;
})
.then(data => {
// Kembalikan tombol ke semula
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
if (data.status === 'success') {
// Munculkan SweetAlert persis seperti di menu User
Swal.fire({
icon: 'success',
title: 'Berhasil!',
text: data.message,
showConfirmButton: false,
timer: 2000,
customClass: { confirmButton: 'btn btn-primary' },
buttonsStyling: false
}).then(() => {
// Redirect ke index setelah sukses
window.location.href = "{{ route('workshop.index') }}";
});
}
})
.catch(error => {
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
let errorMsg = 'Terjadi kesalahan saat menyimpan data.';
if (error.status === 422 && error.data && error.data.errors) {
errorMsg = Object.values(error.data.errors).map(err => err.join(', ')).join('<br>');
} else if (error.data && error.data.message) {
errorMsg = error.data.message;
}
Swal.fire({
icon: 'error',
title: 'Data Tidak Valid',
html: errorMsg,
customClass: { confirmButton: 'btn btn-primary' },
buttonsStyling: false
});
});
});
}
});
</script>
@endpush
@@ -0,0 +1,369 @@
@extends('layout.main')
@section('content')
<div class="container-xxl flex-grow-1 container-p-y">
<div class="row g-6 mb-6">
<div class="col-sm-6 col-xl-2">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-start justify-content-between">
<div class="content-left">
<span class="text-heading">Total Kegiatan</span>
<div class="d-flex align-items-center my-1">
<h4 class="mb-0 me-2">{{ number_format($summary['kegiatan'] ?? 0) }}</h4>
</div>
<small class="mb-0">Kegiatan</small>
</div>
<div class="avatar">
<span class="avatar-initial rounded bg-label-primary">
<i class="icon-base ti tabler-briefcase icon-26px"></i>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-2">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-start justify-content-between">
<div class="content-left">
<span class="text-heading">Total Lokakarya</span>
<div class="d-flex align-items-center my-1">
<h4 class="mb-0 me-2">{{ number_format($summary['lokakarya'] ?? 0) }}</h4>
</div>
<small class="mb-0">Lokakarya</small>
</div>
<div class="avatar">
<span class="avatar-initial rounded bg-label-primary">
<i class="icon-base ti tabler-brand-pnpm icon-26px"></i>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-2">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-start justify-content-between">
<div class="content-left">
<span class="text-heading">Total Pelatihan</span>
<div class="d-flex align-items-center my-1">
<h4 class="mb-0 me-2">{{ number_format($summary['pelatihan'] ?? 0) }}</h4>
</div>
<small class="mb-0">Pelatihan</small>
</div>
<div class="avatar">
<span class="avatar-initial rounded bg-label-primary">
<i class="icon-base ti tabler-artboard icon-26px"></i>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-2">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-start justify-content-between">
<div class="content-left">
<span class="text-heading">Total Sosialisasi</span>
<div class="d-flex align-items-center my-1">
<h4 class="mb-0 me-2">{{ number_format($summary['sosialisasi'] ?? 0) }}</h4>
</div>
<small class="mb-0">Sosialisasi</small>
</div>
<div class="avatar">
<span class="avatar-initial rounded bg-label-primary">
<i class="icon-base ti tabler-notification icon-26px"></i>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-2">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-start justify-content-between">
<div class="content-left">
<span class="text-heading">Total sesi</span>
<div class="d-flex align-items-center my-1">
<h4 class="mb-0 me-2">{{ number_format($summary['sesi'] ?? 0) }}</h4>
</div>
<small class="mb-0">Sesi</small>
</div>
<div class="avatar">
<span class="avatar-initial rounded bg-label-primary">
<i class="icon-base ti tabler-section icon-26px"></i>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-2">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-start justify-content-between">
<div class="content-left">
<span class="text-heading">Peserta</span>
<div class="d-flex align-items-center my-1">
<h4 class="mb-0 me-2">{{ number_format($summary['peserta'] ?? 0) }}</h4>
</div>
<small class="mb-0">Peserta</small>
</div>
<div class="avatar">
<span class="avatar-initial rounded bg-label-primary">
<i class="icon-base ti tabler-calendar-user icon-26px"></i>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<form method="GET" action="{{ route('workshop.index') }}" id="formFilterData"></form>
<div class="card mb-4">
<div class="card-header border-bottom d-flex flex-column flex-md-row justify-content-between align-items-center pt-4 pb-3">
<h5 class="card-title mb-2 mb-md-0">Data Workshop</h5>
<div class="d-flex gap-2">
<button type="button" class="btn btn-label-secondary" data-bs-toggle="collapse" data-bs-target="#collapseFilter" aria-expanded="{{ request()->anyFilled(['jenis', 'status', 'tanggal_awal', 'tanggal_akhir']) ? 'true' : 'false' }}">
<i class="ti tabler-filter me-1"></i> Filter
</button>
<a href="{{ route('workshop.create') }}" class="btn btn-primary">
<i class="ti tabler-plus me-1"></i> Tambah Workshop
</a>
</div>
</div>
<div class="collapse {{ request()->anyFilled(['jenis', 'status', 'tanggal_awal', 'tanggal_akhir']) ? 'show' : '' }}" id="collapseFilter">
<div class="card-body border-bottom bg-lighter pt-4 pb-4">
<div class="row g-3 align-items-end">
<div class="col-md-3">
<label class="form-label">Jenis Program</label>
<select name="jenis" form="formFilterData" class="form-select form-select-sm">
<option value="">Semua Jenis</option>
@foreach(App\Enums\JenisWorkshop::cases() as $jenis)
<option value="{{ $jenis->value }}" {{ request('jenis') == $jenis->value ? 'selected' : '' }}>{{ ucfirst($jenis->value) }}</option>
@endforeach
</select>
</div>
<div class="col-md-3">
<label class="form-label">Status</label>
<select name="status" form="formFilterData" class="form-select form-select-sm">
<option value="">Semua Status</option>
@foreach(App\Enums\StatusWorkshop::cases() as $status)
<option value="{{ $status->value }}" {{ request('status') == $status->value ? 'selected' : '' }}>{{ ucfirst($status->value) }}</option>
@endforeach
</select>
</div>
<div class="col-md-2">
<label class="form-label">Tanggal Awal</label>
<input type="text" name="tanggal_awal" form="formFilterData" class="form-control form-control-sm flatpickr-date" placeholder="DD/MM/YYYY" value="{{ request('tanggal_awal') }}">
</div>
<div class="col-md-2">
<label class="form-label">Tanggal Akhir</label>
<input type="text" name="tanggal_akhir" form="formFilterData" class="form-control form-control-sm flatpickr-date" placeholder="DD/MM/YYYY" value="{{ request('tanggal_akhir') }}">
</div>
<div class="col-md-2 d-flex gap-2">
<button type="submit" form="formFilterData" class="btn btn-sm btn-primary w-100">Terapkan</button>
@if(request()->anyFilled(['jenis', 'status', 'tanggal_awal', 'tanggal_akhir', 'search']))
<a href="{{ route('workshop.index') }}" class="btn btn-sm btn-label-danger" title="Reset Filter"><i class="ti tabler-refresh"></i></a>
@endif
</div>
</div>
</div>
</div>
<div class="card-body border-bottom pt-3 pb-3">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center gap-3">
<div class="d-flex align-items-center">
<label class="me-2 text-nowrap text-muted">Show</label>
<select name="per_page" form="formFilterData" class="form-select form-select-sm w-auto" onchange="document.getElementById('formFilterData').submit()">
<option value="10" {{ request('per_page') == 10 ? 'selected' : '' }}>10</option>
<option value="25" {{ request('per_page') == 25 ? 'selected' : '' }}>25</option>
<option value="50" {{ request('per_page') == 50 ? 'selected' : '' }}>50</option>
</select>
<label class="ms-2 text-nowrap text-muted">entries</label>
</div>
<div class="input-group input-group-sm w-auto" style="min-width: 250px;">
<input type="text" name="search" form="formFilterData" class="form-control" placeholder="Cari judul workshop..." value="{{ request('search') }}">
<button type="submit" form="formFilterData" class="btn btn-primary"><i class="ti tabler-search"></i></button>
</div>
</div>
</div>
<div class="table-responsive text-nowrap">
<table class="table table-hover">
<thead class="border-top">
<tr>
<th>No</th>
<th>Informasi Workshop</th>
<th>Jenis</th>
<th>Jadwal Pelaksanaan</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@forelse($workshops as $index => $item)
<tr>
<td>{{ $workshops->firstItem() + $index }}</td>
<td>
<div class="d-flex justify-content-start align-items-center">
<div class="avatar-wrapper me-3">
<div class="avatar avatar-sm">
<span class="avatar-initial rounded-circle bg-label-primary">{{ strtoupper(substr($item->judul, 0, 2)) }}</span>
</div>
</div>
<div class="d-flex flex-column">
<span class="fw-medium text-heading">{{ $item->judul }}</span>
<small class="text-muted text-truncate" style="max-width: 250px;">{{ $item->deskripsi ?? 'Tidak ada deskripsi' }}</small>
</div>
</div>
</td>
<td><span class="badge bg-label-info">{{ ucfirst($item->jenis->value) }}</span></td>
<td>
<div class="d-flex flex-column">
<span class="text-heading"><i class="ti tabler-calendar-event me-1 ti-xs"></i> {{ \Carbon\Carbon::parse($item->tanggal_awal)->format('d M Y') }}</span>
<small class="text-muted">s/d {{ \Carbon\Carbon::parse($item->tanggal_akhir)->format('d M Y') }}</small>
</div>
</td>
<td>
@php
$color = match($item->status->value) {
'aktif' => 'bg-label-success',
'tunda' => 'bg-label-warning',
'batal' => 'bg-label-danger',
'draft' => 'bg-label-secondary',
'selesai' => 'bg-label-primary',
default => 'bg-label-secondary'
};
@endphp
<span class="badge {{ $color }}">{{ ucfirst($item->status->value) }}</span>
</td>
<td>
<div class="d-flex align-items-center">
<a href="{{ route('workshop.edit', $item->id) }}" class="btn btn-icon btn-text-secondary rounded-pill" title="Edit">
<i class="ti tabler-edit ti-md"></i>
</a>
<form action="{{ route('workshop.destroy', $item->id) }}" method="POST" class="d-inline form-delete" data-name="{{ $item->judul }}">
@csrf @method('DELETE')
<button type="button" class="btn btn-icon btn-text-secondary rounded-pill btn-delete" title="Delete">
<i class="ti tabler-trash ti-md"></i>
</button>
</form>
</div>
</td>
</tr>
@empty
<tr><td colspan="6" class="text-center py-4">Data tidak ditemukan.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div class="card-footer d-flex justify-content-center justify-content-md-end border-top pt-4 pb-4">
{{ $workshops->appends(request()->query())->links('pagination::bootstrap-5') }}
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
'use strict';
$(document).ready(function () {
const dateInputs = document.querySelectorAll('.flatpickr-date');
if (dateInputs.length > 0) {
flatpickr(dateInputs, {
locale: "id",
altInput: true,
altFormat: "d/m/Y",
dateFormat: "Y-m-d",
allowInput: true,
});
}
@if(session('success'))
const Toast = Swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000,
timerProgressBar: true,
});
Toast.fire({ icon: 'success', title: "{{ session('success') }}" });
@endif
$(document).on('click', '.btn-delete', function (e) {
e.preventDefault();
const form = $(this).closest('.form-delete');
const name = form.data('name');
const url = form.attr('action');
const formData = new FormData(form[0]);
Swal.fire({
title: 'Hapus Workshop?',
text: `Apakah kamu yakin ingin menghapus "${name}"? Data yang terhapus tidak dapat dikembalikan.`,
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Ya, hapus!',
cancelButtonText: 'Batal',
customClass: {
confirmButton: 'btn btn-danger me-3',
cancelButton: 'btn btn-label-secondary'
},
buttonsStyling: false
}).then(function (result) {
if (result.value) {
fetch(url, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
Swal.fire({
icon: 'success',
title: 'Terhapus!',
text: data.message,
showConfirmButton: false,
timer: 2000,
customClass: { confirmButton: 'btn btn-primary' },
buttonsStyling: false
}).then(() => {
window.location.reload();
});
}
})
.catch(error => {
Swal.fire({
icon: 'error',
title: 'Gagal!',
text: 'Terjadi kesalahan saat menghapus data.',
customClass: { confirmButton: 'btn btn-primary' },
buttonsStyling: false
});
});
}
});
});
});
</script>
@endpush
+1
View File
@@ -104,6 +104,7 @@ Route::middleware('auth')
Route::resource('program', App\Http\Controllers\Web\ProgramController::class); Route::resource('program', App\Http\Controllers\Web\ProgramController::class);
Route::resource('program.komoditas', App\Http\Controllers\Web\ProgramKomoditasController::class)->only(['store', 'update', 'destroy']); Route::resource('program.komoditas', App\Http\Controllers\Web\ProgramKomoditasController::class)->only(['store', 'update', 'destroy']);
Route::resource('program.lokasi', App\Http\Controllers\Web\ProgramLokasiController::class)->only(['store', 'update', 'destroy']); Route::resource('program.lokasi', App\Http\Controllers\Web\ProgramLokasiController::class)->only(['store', 'update', 'destroy']);
Route::resource('workshop', App\Http\Controllers\Web\Program\WorkshopController::class);
// Basis Data Ekosistem // Basis Data Ekosistem
Route::resource('group', App\Http\Controllers\Web\Ekosistem\GroupBasisDataController::class); Route::resource('group', App\Http\Controllers\Web\Ekosistem\GroupBasisDataController::class);