first commit
This commit is contained in:
parent
e8c1e4bbad
commit
c2c79c2a13
58
.env.example
58
.env.example
|
|
@ -1,58 +0,0 @@
|
|||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=laravel
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=file
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=sync
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=mailpit
|
||||
MAIL_PORT=1025
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
PUSHER_HOST=
|
||||
PUSHER_PORT=443
|
||||
PUSHER_SCHEME=https
|
||||
PUSHER_APP_CLUSTER=mt1
|
||||
|
||||
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
VITE_PUSHER_HOST="${PUSHER_HOST}"
|
||||
VITE_PUSHER_PORT="${PUSHER_PORT}"
|
||||
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
|
||||
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
use App\Models\Role;
|
||||
use App\Models\Makanan;
|
||||
use App\Models\Rekomendasi;
|
||||
use App\Models\WaktuMakan;
|
||||
use App\Http\Requests\UserRequest;
|
||||
use App\Http\Requests\RoleRequest;
|
||||
use App\Http\Requests\UpdateUserRequest;
|
||||
|
|
@ -21,24 +22,39 @@ class AdminController extends Controller
|
|||
{
|
||||
//
|
||||
public function admindash()
|
||||
{
|
||||
$userCount = User::count();
|
||||
$makananCount = Makanan::count();
|
||||
{
|
||||
$userCount = User::whereHas('role', function($query) {
|
||||
$query->where('name', 'user');
|
||||
})->count();
|
||||
$makananCount = Makanan::count();
|
||||
$waktuMakanCount = DB::table('waktu_makans')->count();
|
||||
$komponenCount = DB::table('komponens')->count();
|
||||
|
||||
// Ambil 5 rekomendasi tertinggi dengan relasi makanan
|
||||
$chartData = Rekomendasi::with('makanan')
|
||||
->orderByDesc('nilai_akhir')
|
||||
->take(5)
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
return [
|
||||
'name' => $item->makanan->nama,
|
||||
'value' => $item->nilai_akhir
|
||||
];
|
||||
});
|
||||
// Ambil data waktu makan dengan eager loading
|
||||
$waktuMakans = WaktuMakan::with(['rekomendasis' => function($query) {
|
||||
$query->orderBy('tanggal_rekomendasi', 'desc');
|
||||
}, 'komponens'])
|
||||
->get()
|
||||
->map(function($waktuMakan) {
|
||||
$waktuMakan->has_recommendation = $waktuMakan->rekomendasis->isNotEmpty();
|
||||
$waktuMakan->latest_calculation = $waktuMakan->rekomendasis->first()?->tanggal_rekomendasi;
|
||||
return $waktuMakan;
|
||||
});
|
||||
|
||||
return view('admin.admindash', compact('userCount', 'makananCount', 'chartData'));
|
||||
}
|
||||
// Ambil 5 rekomendasi tertinggi dengan relasi makanan
|
||||
$chartData = Rekomendasi::with('makanan')
|
||||
->orderByDesc('nilai_akhir')
|
||||
->take(5)
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
return [
|
||||
'name' => $item->makanan->nama,
|
||||
'value' => $item->nilai_akhir
|
||||
];
|
||||
});
|
||||
|
||||
return view('admin.admindash', compact('userCount', 'makananCount', 'chartData', 'waktuMakanCount', 'komponenCount', 'waktuMakans'));
|
||||
}
|
||||
|
||||
|
||||
// USER
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\UserAlertSetting;
|
||||
use App\Models\WaktuMakan;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class AlertSettingController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$user = Auth::user();
|
||||
$alertSetting = UserAlertSetting::where('user_id', $user->id)->first();
|
||||
|
||||
if (!$alertSetting) {
|
||||
$alertSetting = UserAlertSetting::create([
|
||||
'user_id' => $user->id,
|
||||
'is_enabled' => true,
|
||||
'enabled_waktu_makan_ids' => WaktuMakan::where('is_active', true)->pluck('id')->toArray()
|
||||
]);
|
||||
}
|
||||
|
||||
$waktuMakans = WaktuMakan::where('is_active', true)->get();
|
||||
|
||||
return view('user.alert-settings', compact('alertSetting', 'waktuMakans'));
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
$alertSetting = UserAlertSetting::where('user_id', $user->id)->first();
|
||||
|
||||
if (!$alertSetting) {
|
||||
$alertSetting = new UserAlertSetting();
|
||||
$alertSetting->user_id = $user->id;
|
||||
}
|
||||
|
||||
$alertSetting->is_enabled = $request->has('is_enabled');
|
||||
$alertSetting->enabled_waktu_makan_ids = $request->enabled_waktu_makan_ids ?? [];
|
||||
$alertSetting->save();
|
||||
|
||||
return redirect()->back()->with('success', 'Pengaturan alert berhasil diperbarui!');
|
||||
}
|
||||
|
||||
public function toggle()
|
||||
{
|
||||
$user = Auth::user();
|
||||
$alertSetting = UserAlertSetting::where('user_id', $user->id)->first();
|
||||
|
||||
if (!$alertSetting) {
|
||||
$alertSetting = UserAlertSetting::create([
|
||||
'user_id' => $user->id,
|
||||
'is_enabled' => true,
|
||||
'enabled_waktu_makan_ids' => WaktuMakan::where('is_active', true)->pluck('id')->toArray()
|
||||
]);
|
||||
}
|
||||
|
||||
$alertSetting->is_enabled = !$alertSetting->is_enabled;
|
||||
$alertSetting->save();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'is_enabled' => $alertSetting->is_enabled,
|
||||
'message' => $alertSetting->is_enabled ? 'Alert diaktifkan' : 'Alert dinonaktifkan'
|
||||
]);
|
||||
}
|
||||
|
||||
public function getCurrentAlerts()
|
||||
{
|
||||
$user = Auth::user();
|
||||
$alertSetting = UserAlertSetting::where('user_id', $user->id)->first();
|
||||
|
||||
if (!$alertSetting || !$alertSetting->is_enabled) {
|
||||
return response()->json(['alerts' => []]);
|
||||
}
|
||||
|
||||
$currentTime = Carbon::now(config('app.timezone'));
|
||||
$enabledWaktuMakan = $alertSetting->enabledWaktuMakan;
|
||||
|
||||
$alerts = [];
|
||||
foreach ($enabledWaktuMakan as $waktuMakan) {
|
||||
// Gunakan method baru untuk cek waktu makan
|
||||
if ($waktuMakan->isWaktuMakanSekarang(30)) {
|
||||
$alerts[] = [
|
||||
'id' => $waktuMakan->id,
|
||||
'nama' => $waktuMakan->nama,
|
||||
'alert_text' => $waktuMakan->alert_text,
|
||||
'waktu' => $waktuMakan->getWaktuFormatted(),
|
||||
'waktu_ampm' => $waktuMakan->getWaktuAMPM(),
|
||||
'timezone' => config('app.timezone')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['alerts' => $alerts]);
|
||||
}
|
||||
|
||||
public function testAlert()
|
||||
{
|
||||
$user = Auth::user();
|
||||
$alertSetting = UserAlertSetting::where('user_id', $user->id)->first();
|
||||
|
||||
if (!$alertSetting) {
|
||||
$alertSetting = UserAlertSetting::create([
|
||||
'user_id' => $user->id,
|
||||
'is_enabled' => true,
|
||||
'enabled_waktu_makan_ids' => WaktuMakan::where('is_active', true)->pluck('id')->toArray()
|
||||
]);
|
||||
}
|
||||
|
||||
$enabledWaktuMakan = $alertSetting->enabledWaktuMakan;
|
||||
|
||||
if ($enabledWaktuMakan->isEmpty()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Pilih minimal satu waktu makan untuk test alert!'
|
||||
]);
|
||||
}
|
||||
|
||||
// Ambil waktu makan pertama yang aktif
|
||||
$testWaktuMakan = $enabledWaktuMakan->first();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'alert' => [
|
||||
'nama' => $testWaktuMakan->nama,
|
||||
'alert_text' => $testWaktuMakan->alert_text,
|
||||
'waktu' => $testWaktuMakan->getWaktuFormatted(),
|
||||
'waktu_ampm' => $testWaktuMakan->getWaktuAMPM(),
|
||||
'timezone' => config('app.timezone')
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,213 +4,516 @@
|
|||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Kriteria;
|
||||
use App\Models\Makanan; // alternatif
|
||||
use App\Models\Makanan;
|
||||
use App\Models\Kategori;
|
||||
use App\Models\PerbandinganAlternatif;
|
||||
use App\Models\SkorMakanan;
|
||||
use App\Models\RekomendasiAhli;
|
||||
use App\Models\Komponen;
|
||||
use App\Models\WaktuMakan;
|
||||
use App\Models\MakananKomponenWaktu;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
use App\Models\BobotKriteria;
|
||||
use App\Models\Rekomendasi;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Carbon\Carbon;
|
||||
use App\Models\PerbandinganKriteria;
|
||||
use App\Models\ConsistencyRatioAlternatif;
|
||||
use App\Traits\KriteriaTrait;
|
||||
|
||||
class AlternatifController extends Controller
|
||||
{
|
||||
//
|
||||
// 1. Tampilkan form pemilihan alternatif
|
||||
use KriteriaTrait;
|
||||
|
||||
// 1. Tampilkan form pemilihan alternatif berdasarkan rekomendasi ahli
|
||||
public function formPilihAlternatif(Request $request)
|
||||
{
|
||||
$kategoris = Kategori::all();
|
||||
$sort = $request->query('sort'); // lemak | natrium | energi | karbohidrat
|
||||
$order = $request->query('order'); // asc | desc
|
||||
$search = $request->query('q'); // kata kunci nama makanan
|
||||
$kategoriFilter = $request->kategori; // kategori
|
||||
{
|
||||
// Cek apakah tahap kriteria sudah selesai
|
||||
if (!session('tahap_kriteria_selesai')) {
|
||||
return redirect()->route('perbandingan')
|
||||
->with('error', 'Selesaikan perhitungan bobot kriteria terlebih dahulu.');
|
||||
}
|
||||
|
||||
$query = Makanan::query();
|
||||
$komponens = Komponen::all();
|
||||
$waktuMakans = WaktuMakan::all();
|
||||
|
||||
// Get filter parameters
|
||||
$waktuMakanId = $request->waktu_makan_id;
|
||||
$search = $request->q;
|
||||
|
||||
// 1. Search by name
|
||||
if ($search) {
|
||||
$query->where('nama', 'like', "%{$search}%");
|
||||
// Get expert recommendations and available foods
|
||||
$rekomendasiAhli = collect();
|
||||
$makanans = collect();
|
||||
|
||||
if ($waktuMakanId) {
|
||||
// Get expert recommendations for all components with eager loading
|
||||
$rekomendasiAhli = RekomendasiAhli::with(['makanan', 'komponen', 'waktuMakan'])
|
||||
->where('waktu_makan_id', $waktuMakanId)
|
||||
->get();
|
||||
|
||||
// Get all foods from pivot table makanan_komponen_waktu
|
||||
$query = MakananKomponenWaktu::with(['makanan', 'komponen'])
|
||||
->where('waktu_makan_id', $waktuMakanId);
|
||||
|
||||
// Try with status filter first
|
||||
$makananKomponenWaktu = $query->where('status', true)->get();
|
||||
|
||||
// If no data with status true, try without status filter
|
||||
if ($makananKomponenWaktu->isEmpty()) {
|
||||
$makananKomponenWaktu = $query->get();
|
||||
\Log::info('No data with status=true, using all data without status filter');
|
||||
}
|
||||
|
||||
if ($search) {
|
||||
$makananKomponenWaktu = $makananKomponenWaktu->filter(function($item) use ($search) {
|
||||
return stripos($item->makanan->nama, $search) !== false;
|
||||
});
|
||||
}
|
||||
|
||||
// Debug: Check all data without status filter
|
||||
$allData = MakananKomponenWaktu::with(['makanan', 'komponen'])
|
||||
->where('waktu_makan_id', $waktuMakanId)
|
||||
->get();
|
||||
|
||||
\Log::info('Debug - All data without status filter:', [
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'total_all_data' => $allData->count(),
|
||||
'status_counts' => $allData->groupBy('status')->map->count(),
|
||||
'all_data' => $allData->toArray()
|
||||
]);
|
||||
|
||||
// Log untuk debugging
|
||||
\Log::info('Data makanan dari pivot table:', [
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'total_makanan' => $makananKomponenWaktu->count(),
|
||||
'makanan_data' => $makananKomponenWaktu->toArray()
|
||||
]);
|
||||
|
||||
// Transform data to match expected format
|
||||
$makanans = $makananKomponenWaktu->map(function($item) {
|
||||
$makanan = $item->makanan;
|
||||
$makanan->komponen_id = $item->komponen_id;
|
||||
return $makanan;
|
||||
});
|
||||
}
|
||||
|
||||
return view('admin.alternatif.pilih', compact(
|
||||
'rekomendasiAhli',
|
||||
'makanans',
|
||||
'komponens',
|
||||
'waktuMakans',
|
||||
'waktuMakanId',
|
||||
'search'
|
||||
));
|
||||
}
|
||||
|
||||
if ($kategoriFilter) {
|
||||
$query->where('kategori_id', $kategoriFilter);
|
||||
}
|
||||
|
||||
// 2. Sort
|
||||
if (in_array($sort, ['lemak','natrium','energi','karbohidrat'])) {
|
||||
$order = $order === 'asc' ? 'asc' : 'desc'; // default desc
|
||||
$query->orderBy($sort, $order);
|
||||
}
|
||||
|
||||
$makanans = $query->get();
|
||||
|
||||
return view('admin.alternatif.pilih', compact('makanans','sort','order','search', 'kategoriFilter', 'kategoris'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 2. Simpan pilihan alternatif ke session
|
||||
// 2. Simpan pilihan alternatif dan hitung bobot
|
||||
public function pilihAlternatif(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'alternatifs' => 'required|array|min:2',
|
||||
]);
|
||||
try {
|
||||
$alternatifIds = $request->input('alternatifs');
|
||||
$waktuMakanId = $request->input('waktu_makan_id');
|
||||
$komponenIds = $request->input('komponen_ids');
|
||||
$tanggalRekomendasi = now()->toDateString();
|
||||
|
||||
session(['alternatifs_dipilih' => $request->alternatifs]);
|
||||
if (!$alternatifIds || !$waktuMakanId || !$komponenIds) {
|
||||
return redirect()->back()
|
||||
->with('error', 'Pilih minimal 4 alternatif untuk setiap komponen.');
|
||||
}
|
||||
|
||||
return redirect()->route('alternatif.perbandingan');
|
||||
// Validasi minimal 4 alternatif per komponen
|
||||
$makananKomponenWaktu = MakananKomponenWaktu::with(['makanan', 'komponen'])
|
||||
->whereIn('makanan_id', $alternatifIds)
|
||||
->where('waktu_makan_id', $waktuMakanId)
|
||||
->get();
|
||||
|
||||
// Group by komponen dan cek jumlah
|
||||
$alternatifPerKomponen = $makananKomponenWaktu->groupBy('komponen_id');
|
||||
$errorMessages = [];
|
||||
|
||||
foreach ($alternatifPerKomponen as $komponenId => $items) {
|
||||
if ($items->count() < 4) {
|
||||
$komponen = $items->first()->komponen;
|
||||
$errorMessages[] = "Komponen {$komponen->nama}: hanya {$items->count()} alternatif (minimal 4)";
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($errorMessages)) {
|
||||
return redirect()->back()
|
||||
->with('error', 'Validasi gagal:<br>' . implode('<br>', $errorMessages));
|
||||
}
|
||||
|
||||
// Log request data untuk debugging
|
||||
\Log::info('Request data:', [
|
||||
'alternatifIds' => $alternatifIds,
|
||||
'waktuMakanId' => $waktuMakanId,
|
||||
'komponenIds' => $komponenIds,
|
||||
'all_request' => $request->all()
|
||||
]);
|
||||
|
||||
// Ambil data makanan yang dipilih dari pivot table
|
||||
$makananKomponenWaktu = MakananKomponenWaktu::with(['makanan'])
|
||||
->whereIn('makanan_id', $alternatifIds)
|
||||
->where('waktu_makan_id', $waktuMakanId)
|
||||
->where('status', true)
|
||||
->get();
|
||||
|
||||
// If no data with status true, try without status filter
|
||||
if ($makananKomponenWaktu->isEmpty()) {
|
||||
$makananKomponenWaktu = MakananKomponenWaktu::with(['makanan'])
|
||||
->whereIn('makanan_id', $alternatifIds)
|
||||
->where('waktu_makan_id', $waktuMakanId)
|
||||
->get();
|
||||
\Log::info('No data with status=true in pilihAlternatif, using all data without status filter');
|
||||
}
|
||||
|
||||
// Group makanan berdasarkan komponen
|
||||
$alternatifsByKomponen = [];
|
||||
foreach ($makananKomponenWaktu as $item) {
|
||||
$makanan = $item->makanan;
|
||||
$komponenId = $item->komponen_id;
|
||||
|
||||
if (!isset($alternatifsByKomponen[$komponenId])) {
|
||||
$komponen = Komponen::find($komponenId);
|
||||
$alternatifsByKomponen[$komponenId] = [
|
||||
'nama_komponen' => $komponen->nama,
|
||||
'alternatifs' => []
|
||||
];
|
||||
}
|
||||
|
||||
// Simpan data makanan dalam format array
|
||||
$alternatifsByKomponen[$komponenId]['alternatifs'][] = [
|
||||
'id' => $makanan->id,
|
||||
'nama' => $makanan->nama,
|
||||
'energi' => $makanan->energi,
|
||||
'lemak' => $makanan->lemak,
|
||||
'karbohidrat' => $makanan->karbohidrat,
|
||||
'natrium' => $makanan->natrium,
|
||||
'lemak_invers' => $makanan->lemak > 0 ? 1 / $makanan->lemak : 0,
|
||||
'natrium_invers' => $makanan->natrium > 0 ? 1 / $makanan->natrium : 0
|
||||
];
|
||||
}
|
||||
|
||||
// Log data untuk debugging
|
||||
\Log::info('Data alternatif yang akan disimpan:', [
|
||||
'alternatifs_by_komponen' => $alternatifsByKomponen,
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'tanggal_rekomendasi' => $tanggalRekomendasi
|
||||
]);
|
||||
|
||||
// Simpan ke session
|
||||
session([
|
||||
'alternatifs_by_komponen' => $alternatifsByKomponen,
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'hasil_rekomendasi' => [
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'tanggal_rekomendasi' => $tanggalRekomendasi,
|
||||
'waktu_makan_nama' => WaktuMakan::find($waktuMakanId)->nama,
|
||||
'alternatifs_dipilih' => $alternatifIds
|
||||
]
|
||||
]);
|
||||
|
||||
return redirect()->route('alternatif.view')
|
||||
->with('success', 'Alternatif berhasil dipilih.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Error in simpanPilihan:', [
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return redirect()->back()
|
||||
->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Tampilkan form perbandingan alternatif per kriteria
|
||||
public function viewAlternatif()
|
||||
{
|
||||
try {
|
||||
// Ambil data dari session
|
||||
$alternatifsByKomponen = session('alternatifs_by_komponen');
|
||||
$hasilRekomendasi = session('hasil_rekomendasi');
|
||||
|
||||
if (!$hasilRekomendasi || !$alternatifsByKomponen) {
|
||||
return redirect()->route('alternatif.pilih')
|
||||
->with('error', 'Silakan pilih alternatif terlebih dahulu');
|
||||
}
|
||||
|
||||
// Log data untuk debugging
|
||||
\Log::info('Data untuk view:', [
|
||||
'alternatifs_by_komponen' => $alternatifsByKomponen,
|
||||
'hasil_rekomendasi' => $hasilRekomendasi
|
||||
]);
|
||||
|
||||
return view('admin.alternatif.view', compact('alternatifsByKomponen', 'hasilRekomendasi'));
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Error in viewAlternatif:', [
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return redirect()->route('alternatif.pilih')
|
||||
->with('error', 'Terjadi kesalahan saat memproses data: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Hitung bobot alternatif
|
||||
private function hitungBobotAlternatif($alternativesByComponent, $kriterias, $waktuMakanId)
|
||||
{
|
||||
try {
|
||||
// Random Index values
|
||||
$RI = [
|
||||
1 => 0.00, 2 => 0.00, 3 => 0.58, 4 => 0.90, 5 => 1.12,
|
||||
6 => 1.24, 7 => 1.32, 8 => 1.41, 9 => 1.45, 10 => 1.49,
|
||||
11 => 1.51, 12 => 1.48, 13 => 1.56, 14 => 1.57, 15 => 1.59
|
||||
];
|
||||
|
||||
// Ambil bobot kriteria berdasarkan waktu makan
|
||||
$bobotKriteria = [];
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$bobot = BobotKriteria::where([
|
||||
'kriteria_id' => $kriteria->id,
|
||||
'waktu_makan_id' => $waktuMakanId
|
||||
])->first();
|
||||
|
||||
if (!$bobot) {
|
||||
throw new \Exception("Bobot kriteria untuk {$kriteria->nama} pada waktu makan ini belum dihitung.");
|
||||
}
|
||||
$bobotKriteria[$kriteria->id] = $bobot->bobot;
|
||||
}
|
||||
|
||||
foreach ($alternativesByComponent as $currentKomponenId => $komponenAlternatifs) {
|
||||
// Untuk setiap kriteria
|
||||
foreach ($kriterias as $kriteria) {
|
||||
// 1. Buat matriks perbandingan berpasangan untuk komponen ini
|
||||
$matriksPerbandingan = [];
|
||||
|
||||
// Isi matriks perbandingan
|
||||
foreach ($komponenAlternatifs as $alt1) {
|
||||
foreach ($komponenAlternatifs as $alt2) {
|
||||
$nilai1 = $alt1->{strtolower($kriteria->nama)};
|
||||
$nilai2 = $alt2->{strtolower($kriteria->nama)};
|
||||
|
||||
// Untuk kriteria cost (lemak dan natrium), gunakan nilai inverse
|
||||
if ($this->isCostCriteria($kriteria->nama)) {
|
||||
$nilai1 = $nilai1 > 0 ? 1 / $nilai1 : 0;
|
||||
$nilai2 = $nilai2 > 0 ? 1 / $nilai2 : 0;
|
||||
}
|
||||
|
||||
if ($nilai2 == 0) {
|
||||
$matriksPerbandingan[$alt1->id][$alt2->id] = 0;
|
||||
} else {
|
||||
$matriksPerbandingan[$alt1->id][$alt2->id] = $nilai1 / $nilai2;
|
||||
}
|
||||
|
||||
// Simpan perbandingan ke database
|
||||
PerbandinganAlternatif::updateOrCreate(
|
||||
[
|
||||
'alternatif_id_1' => $alt1->id,
|
||||
'alternatif_id_2' => $alt2->id,
|
||||
'kriteria_id' => $kriteria->id,
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'komponen_id' => $currentKomponenId
|
||||
],
|
||||
['nilai' => $matriksPerbandingan[$alt1->id][$alt2->id]]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Hitung jumlah kolom
|
||||
$jumlahKolom = [];
|
||||
foreach ($komponenAlternatifs as $alt2) {
|
||||
$jumlahKolom[$alt2->id] = 0;
|
||||
foreach ($komponenAlternatifs as $alt1) {
|
||||
$jumlahKolom[$alt2->id] += $matriksPerbandingan[$alt1->id][$alt2->id];
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Normalisasi matriks
|
||||
$matriksNormal = [];
|
||||
foreach ($komponenAlternatifs as $alt1) {
|
||||
foreach ($komponenAlternatifs as $alt2) {
|
||||
if ($jumlahKolom[$alt2->id] > 0) {
|
||||
$matriksNormal[$alt1->id][$alt2->id] =
|
||||
$matriksPerbandingan[$alt1->id][$alt2->id] / $jumlahKolom[$alt2->id];
|
||||
} else {
|
||||
$matriksNormal[$alt1->id][$alt2->id] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Hitung prioritas lokal
|
||||
$priorityVector = [];
|
||||
foreach ($komponenAlternatifs as $alt) {
|
||||
$priorityVector[$alt->id] = array_sum($matriksNormal[$alt->id]) / count($komponenAlternatifs);
|
||||
}
|
||||
|
||||
// 5. Hitung CI dan CR
|
||||
$lambdaMax = 0;
|
||||
foreach ($komponenAlternatifs as $alt) {
|
||||
$sum = 0;
|
||||
foreach ($komponenAlternatifs as $alt2) {
|
||||
$sum += $matriksPerbandingan[$alt->id][$alt2->id] * $priorityVector[$alt2->id];
|
||||
}
|
||||
if ($priorityVector[$alt->id] > 0) {
|
||||
$lambdaMax += $sum / $priorityVector[$alt->id];
|
||||
}
|
||||
}
|
||||
$lambdaMax = $lambdaMax / count($komponenAlternatifs);
|
||||
|
||||
$CI = ($lambdaMax - count($komponenAlternatifs)) / (count($komponenAlternatifs) - 1);
|
||||
$CR = $CI / ($RI[count($komponenAlternatifs)] ?? 1.59);
|
||||
|
||||
// Simpan CR ke database
|
||||
ConsistencyRatioAlternatif::updateOrCreate(
|
||||
[
|
||||
'kriteria_id' => $kriteria->id,
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'komponen_id' => $currentKomponenId
|
||||
],
|
||||
[
|
||||
'ci' => $CI,
|
||||
'cr' => $CR,
|
||||
'is_consistent' => $CR <= 0.1
|
||||
]
|
||||
);
|
||||
|
||||
// Simpan skor akhir ke database
|
||||
foreach ($komponenAlternatifs as $alt) {
|
||||
SkorMakanan::updateOrCreate(
|
||||
[
|
||||
'makanan_id' => $alt->id,
|
||||
'kriteria_id' => $kriteria->id,
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'komponen_id' => $currentKomponenId
|
||||
],
|
||||
['nilai' => $priorityVector[$alt->id] * $bobotKriteria[$kriteria->id]]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Error in hitungBobotAlternatif: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Tampilkan form perbandingan alternatif
|
||||
public function tampilPerbandingan()
|
||||
{
|
||||
$idAlternatif = session('alternatifs_dipilih');
|
||||
try {
|
||||
// Ambil data dari session
|
||||
$alternatifsByKomponen = session('alternatifs_by_komponen');
|
||||
|
||||
if (!$idAlternatif || count($idAlternatif) < 2) {
|
||||
return redirect()->route('alternatif.pilih')->with('error', 'Pilih minimal dua alternatif terlebih dahulu.');
|
||||
if (!$alternatifsByKomponen) {
|
||||
return redirect()->route('alternatif.view')
|
||||
->with('error', 'Silakan lihat data alternatif terlebih dahulu');
|
||||
}
|
||||
|
||||
$alternatifs = Makanan::whereIn('id', $idAlternatif)->get();
|
||||
$kriterias = Kriteria::all();
|
||||
// Ambil semua kriteria
|
||||
$kriterias = \App\Models\Kriteria::all();
|
||||
|
||||
return view('admin.alternatif.perbandingan', compact('kriterias', 'alternatifs'));
|
||||
// Data sudah siap di session, tampilkan view dengan data kriteria
|
||||
return view('admin.alternatif.perbandingan', [
|
||||
'alternatifsByKomponen' => $alternatifsByKomponen,
|
||||
'kriterias' => $kriterias
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Error in tampilPerbandingan: ' . $e->getMessage());
|
||||
return redirect()->route('alternatif.view')
|
||||
->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Simpan hasil perbandingan ke database
|
||||
public function simpanPerbandingan(Request $request)
|
||||
{
|
||||
$data = $request->input('nilai');
|
||||
|
||||
foreach ($data as $kriteria_id => $baris) {
|
||||
foreach ($baris as $alt1 => $kolom) {
|
||||
foreach ($kolom as $alt2 => $nilai) {
|
||||
if ($alt1 != $alt2 && $nilai != null) {
|
||||
// Simpan nilai asli
|
||||
PerbandinganAlternatif::updateOrCreate(
|
||||
[
|
||||
'kriteria_id' => $kriteria_id,
|
||||
'alternatif_id_1' => $alt1,
|
||||
'alternatif_id_2' => $alt2,
|
||||
],
|
||||
['nilai' => $nilai]
|
||||
);
|
||||
|
||||
// Simpan nilai kebalikannya
|
||||
PerbandinganAlternatif::updateOrCreate(
|
||||
[
|
||||
'kriteria_id' => $kriteria_id,
|
||||
'alternatif_id_1' => $alt2,
|
||||
'alternatif_id_2' => $alt1,
|
||||
],
|
||||
['nilai' => 1 / $nilai]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->input('action') == 'lanjut') {
|
||||
return redirect()->route('alternatif.normalisasi')->with('success', 'Data berhasil disimpan, lanjut ke tahap normalisasi.');
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', 'Perbandingan alternatif berhasil disimpan.');
|
||||
}
|
||||
|
||||
|
||||
public function tampilNormalisasi()
|
||||
{
|
||||
$idAlternatif = session('alternatifs_dipilih');
|
||||
|
||||
if (!$idAlternatif || count($idAlternatif) < 2) {
|
||||
return redirect()->route('alternatif.pilih')->with('error', 'Pilih minimal dua alternatif terlebih dahulu.');
|
||||
}
|
||||
|
||||
$alternatifs = Makanan::whereIn('id', $idAlternatif)->get();
|
||||
$kriterias = Kriteria::all();
|
||||
$matriks = [];
|
||||
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$matriks[$kriteria->id] = [];
|
||||
|
||||
foreach ($alternatifs as $alt1) {
|
||||
$matriks[$kriteria->id][$alt1->id] = [];
|
||||
|
||||
foreach ($alternatifs as $alt2) {
|
||||
if ($alt1->id == $alt2->id) {
|
||||
$matriks[$kriteria->id][$alt1->id][$alt2->id] = 1;
|
||||
} else {
|
||||
$nilai = DB::table('perbandingan_alternatifs')
|
||||
->where('kriteria_id', $kriteria->id)
|
||||
->where('alternatif_id_1', $alt1->id)
|
||||
->where('alternatif_id_2', $alt2->id)
|
||||
->value('nilai');
|
||||
|
||||
$matriks[$kriteria->id][$alt1->id][$alt2->id] = $nilai ?? 1; // default 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return view('admin.alternatif.normalisasi', compact('alternatifs', 'kriterias', 'matriks'));
|
||||
}
|
||||
|
||||
public function simpanNormalisasi()
|
||||
{
|
||||
$idAlternatif = session('alternatifs_dipilih');
|
||||
|
||||
if (!$idAlternatif || count($idAlternatif) < 2) {
|
||||
return redirect()->route('alternatif.pilih')->with('error', 'Pilih minimal dua alternatif terlebih dahulu.');
|
||||
}
|
||||
|
||||
$alternatifs = Makanan::whereIn('id', $idAlternatif)->get();
|
||||
$kriterias = Kriteria::all();
|
||||
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$jumlahKolom = [];
|
||||
foreach ($alternatifs as $colAlt) {
|
||||
$jumlahKolom[$colAlt->id] = 0;
|
||||
}
|
||||
|
||||
// Hitung jumlah kolom
|
||||
foreach ($alternatifs as $rowAlt) {
|
||||
foreach ($alternatifs as $colAlt) {
|
||||
$nilai = DB::table('perbandingan_alternatifs')
|
||||
->where('kriteria_id', $kriteria->id)
|
||||
->where('alternatif_id_1', $rowAlt->id)
|
||||
->where('alternatif_id_2', $colAlt->id)
|
||||
->value('nilai') ?? 1;
|
||||
$jumlahKolom[$colAlt->id] += $nilai;
|
||||
}
|
||||
}
|
||||
|
||||
// Hitung bobot (normalisasi + rata-rata baris)
|
||||
foreach ($alternatifs as $rowAlt) {
|
||||
$jumlahBaris = 0;
|
||||
|
||||
foreach ($alternatifs as $colAlt) {
|
||||
$nilai = DB::table('perbandingan_alternatifs')
|
||||
->where('kriteria_id', $kriteria->id)
|
||||
->where('alternatif_id_1', $rowAlt->id)
|
||||
->where('alternatif_id_2', $colAlt->id)
|
||||
->value('nilai') ?? 1;
|
||||
|
||||
$normalized = $nilai / $jumlahKolom[$colAlt->id];
|
||||
$jumlahBaris += $normalized;
|
||||
public function simpanNormalisasi(Request $request)
|
||||
{
|
||||
try {
|
||||
// Ambil data normalisasi dari request dalam format JSON
|
||||
$normalisasiDataJson = $request->input('normalisasi_data_json');
|
||||
|
||||
if (!$normalisasiDataJson) {
|
||||
return redirect()->back()->with('error', 'Data normalisasi tidak ditemukan');
|
||||
}
|
||||
|
||||
$bobot = $jumlahBaris / count($alternatifs);
|
||||
// Decode JSON data
|
||||
$normalisasiData = json_decode($normalisasiDataJson, true);
|
||||
|
||||
if (!$normalisasiData || !isset($normalisasiData['data'])) {
|
||||
return redirect()->back()->with('error', 'Format data normalisasi tidak valid');
|
||||
}
|
||||
|
||||
// Simpan ke skor_makanan
|
||||
SkorMakanan::updateOrCreate(
|
||||
[
|
||||
'kriteria_id' => $kriteria->id,
|
||||
'makanan_id' => $rowAlt->id
|
||||
],
|
||||
[
|
||||
'nilai' => $bobot
|
||||
]
|
||||
);
|
||||
// Simpan ke session
|
||||
session(['alternatifs_by_komponen' => $normalisasiData['data']]);
|
||||
|
||||
// Log untuk debugging
|
||||
\Log::info('Data normalisasi disimpan:', [
|
||||
'normalisasi_data' => $normalisasiData['data']
|
||||
]);
|
||||
|
||||
return redirect()->route('rekomendasi.hitung.otomatis')
|
||||
->with('success', 'Data normalisasi berhasil disimpan');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Error in simpanNormalisasi:', [
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return redirect()->back()
|
||||
->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('rekomendasi.proses')->with('success', 'Bobot alternatif berhasil disimpan ke skor_makanan.');
|
||||
}
|
||||
private function hitungNilaiPerbandingan($alt1, $alt2, $kriteria)
|
||||
{
|
||||
switch ($kriteria->kode) {
|
||||
case 'C1': // Lemak (cost)
|
||||
return $alt2['lemak'] > 0 ? $alt1['lemak_invers'] / $alt2['lemak_invers'] : 1;
|
||||
|
||||
case 'C2': // Natrium (cost)
|
||||
return $alt2['natrium'] > 0 ? $alt1['natrium_invers'] / $alt2['natrium_invers'] : 1;
|
||||
|
||||
case 'C3': // Energi (benefit)
|
||||
return $alt2['energi'] > 0 ? $alt1['energi'] / $alt2['energi'] : 1;
|
||||
|
||||
case 'C4': // Karbohidrat (benefit)
|
||||
return $alt2['karbohidrat'] > 0 ? $alt1['karbohidrat'] / $alt2['karbohidrat'] : 1;
|
||||
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private function getNilaiKriteria($makanan, $kriteria)
|
||||
{
|
||||
switch (strtolower($kriteria->nama)) {
|
||||
case 'energi':
|
||||
return $makanan->energi;
|
||||
case 'lemak':
|
||||
return $makanan->lemak;
|
||||
case 'karbohidrat':
|
||||
return $makanan->karbohidrat;
|
||||
case 'natrium':
|
||||
return $makanan->natrium;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private function normalisasiNilai($nilai, $kriteria, $makanans)
|
||||
{
|
||||
// Hitung jumlah total untuk kriteria ini
|
||||
$total = 0;
|
||||
foreach ($makanans as $makanan) {
|
||||
$nilaiKriteria = $this->getNilaiKriteria($makanan, $kriteria);
|
||||
$total += $nilaiKriteria;
|
||||
}
|
||||
|
||||
// Normalisasi sesuai perhitungan manual (nilai/jumlah)
|
||||
// Hindari pembagian dengan 0
|
||||
return $total > 0 ? $nilai / $total : 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\BobotKriteria;
|
||||
use App\Models\PerbandinganKriteria;
|
||||
use App\Models\PerbandinganAlternatif;
|
||||
use App\Models\ConsistencyRatioAlternatif;
|
||||
use App\Models\SkorMakanan;
|
||||
use App\Models\Kriteria;
|
||||
use App\Models\Makanan;
|
||||
use Illuminate\Http\Request;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class HistoryAHPController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
try {
|
||||
// Ambil tanggal dari request atau gunakan hari ini
|
||||
$tanggal = $request->input('tanggal')
|
||||
? Carbon::parse($request->input('tanggal'))->format('Y-m-d')
|
||||
: now()->format('Y-m-d');
|
||||
|
||||
// Parse tanggal untuk query
|
||||
$tanggalCarbon = Carbon::parse($tanggal)->startOfDay();
|
||||
|
||||
// Ambil data history perhitungan untuk tanggal yang dipilih
|
||||
$bobotKriteria = [
|
||||
$tanggal => BobotKriteria::with('kriteria')
|
||||
->whereDate('created_at', $tanggalCarbon)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get()
|
||||
];
|
||||
|
||||
$perbandinganKriteria = [
|
||||
$tanggal => PerbandinganKriteria::with(['kriteria1', 'kriteria2'])
|
||||
->whereDate('created_at', $tanggalCarbon)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get()
|
||||
];
|
||||
|
||||
$perbandinganAlternatif = [
|
||||
$tanggal => PerbandinganAlternatif::with(['alternatif1', 'alternatif2', 'kriteria'])
|
||||
->whereDate('created_at', $tanggalCarbon)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get()
|
||||
];
|
||||
|
||||
$consistencyRatios = [
|
||||
$tanggal => ConsistencyRatioAlternatif::with(['kriteria', 'komponen'])
|
||||
->whereDate('created_at', $tanggalCarbon)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get()
|
||||
];
|
||||
|
||||
$skorMakanan = [
|
||||
$tanggal => SkorMakanan::with(['makanan', 'kriteria'])
|
||||
->whereDate('created_at', $tanggalCarbon)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get()
|
||||
];
|
||||
|
||||
// Filter out empty collections
|
||||
$bobotKriteria = array_filter($bobotKriteria, function($collection) {
|
||||
return $collection->isNotEmpty();
|
||||
});
|
||||
|
||||
$perbandinganKriteria = array_filter($perbandinganKriteria, function($collection) {
|
||||
return $collection->isNotEmpty();
|
||||
});
|
||||
|
||||
$perbandinganAlternatif = array_filter($perbandinganAlternatif, function($collection) {
|
||||
return $collection->isNotEmpty();
|
||||
});
|
||||
|
||||
$consistencyRatios = array_filter($consistencyRatios, function($collection) {
|
||||
return $collection->isNotEmpty();
|
||||
});
|
||||
|
||||
$skorMakanan = array_filter($skorMakanan, function($collection) {
|
||||
return $collection->isNotEmpty();
|
||||
});
|
||||
|
||||
return view('admin.history.ahp', compact(
|
||||
'bobotKriteria',
|
||||
'perbandinganKriteria',
|
||||
'perbandinganAlternatif',
|
||||
'consistencyRatios',
|
||||
'skorMakanan',
|
||||
'tanggal'
|
||||
));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', 'Terjadi kesalahan saat memuat data history: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,9 +9,12 @@ class LandingPageController extends Controller
|
|||
{
|
||||
//
|
||||
public function lp(){
|
||||
$totalData = DB::table('makanans')->count(); // atau model: DataMakanan::count()
|
||||
$totalData = DB::table('makanans')->count();
|
||||
$totalKriteria = DB::table('kriterias')->count();
|
||||
$totalUser = DB::table('users')->count();
|
||||
$totalUser = DB::table('users')
|
||||
->join('roles', 'users.role_id', '=', 'roles.id')
|
||||
->where('roles.name', '=', 'user')
|
||||
->count();
|
||||
return view('landingpage', compact('totalData', 'totalKriteria', 'totalUser'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class MakananController extends Controller
|
|||
//
|
||||
|
||||
public function makanan(){
|
||||
$makanans = Makanan::with('kategori', 'jenis')->get(); // Ambil semua data makanan dengan relasi kategori
|
||||
$makanans = Makanan::with('kategori', 'jenis')->paginate(10); // Add pagination with 10 items per page
|
||||
return view('admin.makanan.makanan', compact('makanans'));
|
||||
}
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ public function deletemakanan(Makanan $makanan) {
|
|||
|
||||
|
||||
public function kategori(){
|
||||
$kategoris = Kategori::all();
|
||||
$kategoris = Kategori::paginate(10);
|
||||
return view('admin.kategori.kategori', compact('kategoris'));
|
||||
}
|
||||
public function editkategori(Kategori $kategori){
|
||||
|
|
@ -135,10 +135,10 @@ public function deletekategori(Kategori $kategori) {
|
|||
|
||||
|
||||
public function jenismakanan()
|
||||
{
|
||||
$jenis_makanans = JenisMakanan::all();
|
||||
return view('admin.jenismakanan.jenismakanan', compact('jenis_makanans'));
|
||||
}
|
||||
{
|
||||
$jenis_makanans = JenisMakanan::paginate(10);
|
||||
return view('admin.jenismakanan.jenismakanan', compact('jenis_makanans'));
|
||||
}
|
||||
|
||||
public function editjenismakanan(JenisMakanan $jenis)
|
||||
{
|
||||
|
|
@ -173,7 +173,7 @@ public function deletejenismakanan(JenisMakanan $jenis)
|
|||
|
||||
|
||||
public function komponen(){
|
||||
$komponens = Komponen::all();
|
||||
$komponens = Komponen::paginate(10); // Add pagination with 10 items per page
|
||||
return view('admin.komponen.komponen', compact('komponens'));
|
||||
}
|
||||
public function editkomponen(Komponen $komponen){
|
||||
|
|
@ -196,7 +196,7 @@ public function tambahkomponen(){
|
|||
|
||||
public function deletekomponen(Komponen $komponen) {
|
||||
$komponen->delete();
|
||||
return redirect()->route('komponen')->with('success', 'user deleted successfully');
|
||||
return redirect()->route('komponen')->with('success', 'Komponen berhasil dihapus');
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -207,7 +207,7 @@ public function deletekomponen(Komponen $komponen) {
|
|||
|
||||
|
||||
public function waktumakan(){
|
||||
$waktu_makans = WaktuMakan::all();
|
||||
$waktu_makans = WaktuMakan::paginate(10); // Add pagination with 10 items per page
|
||||
return view('admin.waktumakan.waktumakan', compact('waktu_makans'));
|
||||
}
|
||||
public function editwaktumakan(WaktuMakan $waktumakan){
|
||||
|
|
@ -230,52 +230,42 @@ public function tambahwaktumakan(){
|
|||
|
||||
public function deletewaktumakan(WaktuMakan $waktumakan) {
|
||||
$waktumakan->delete();
|
||||
return redirect()->route('waktumakan')->with('success', 'user deleted successfully');
|
||||
return redirect()->route('waktumakan')->with('success', 'Waktu makan berhasil dihapus');
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = MakananKomponenWaktu::with(['makanan', 'komponen', 'waktuMakan']);
|
||||
public function index()
|
||||
{
|
||||
$relasis = MakananKomponenWaktu::with(['makanan', 'komponen', 'waktuMakan'])
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate(10);
|
||||
|
||||
if ($request->komponen_id) {
|
||||
$query->where('komponen_id', $request->komponen_id);
|
||||
return view('admin.relasi.relasi', compact('relasis'));
|
||||
}
|
||||
|
||||
if ($request->waktu_makan_id) {
|
||||
$query->where('waktu_makan_id', $request->waktu_makan_id);
|
||||
}
|
||||
|
||||
$relasis = $query->get();
|
||||
$makanans = Makanan::all();
|
||||
$komponens = Komponen::all();
|
||||
$waktuMakans = WaktuMakan::all();
|
||||
|
||||
return view('admin.relasi.relasi', compact('relasis', 'makanans', 'komponens', 'waktuMakans'));
|
||||
}
|
||||
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
$validatedData = $request->validate([
|
||||
'makanan_id' => 'required|exists:makanans,id',
|
||||
'komponen_id' => 'required|exists:komponens,id',
|
||||
'waktu_makan_id' => 'required|exists:waktu_makans,id',
|
||||
'status' => 'required|boolean'
|
||||
]);
|
||||
|
||||
// Cegah duplikasi
|
||||
$exists = MakananKomponenWaktu::where([
|
||||
'makanan_id' => $request->makanan_id,
|
||||
'komponen_id' => $request->komponen_id,
|
||||
'waktu_makan_id' => $request->waktu_makan_id
|
||||
])->exists();
|
||||
|
||||
if (!$exists) {
|
||||
MakananKomponenWaktu::create($request->only(['makanan_id', 'komponen_id', 'waktu_makan_id']));
|
||||
return redirect()->back()->with('success', 'Relasi berhasil ditambahkan.');
|
||||
} else {
|
||||
return redirect()->back()->with('error', 'Relasi sudah ada.');
|
||||
try {
|
||||
MakananKomponenWaktu::create($validatedData);
|
||||
return redirect()->route('relasi.index')->with('success', 'Relasi berhasil ditambahkan!');
|
||||
} catch (\Illuminate\Database\QueryException $e) {
|
||||
if ($e->errorInfo[1] == 1062) { // MySQL error code for duplicate entry
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('error', 'Relasi ini sudah ada dalam database.');
|
||||
}
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -291,32 +281,31 @@ public function edit($id)
|
|||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$request->validate([
|
||||
$validatedData = $request->validate([
|
||||
'makanan_id' => 'required|exists:makanans,id',
|
||||
'komponen_id' => 'required|exists:komponens,id',
|
||||
'waktu_makan_id' => 'required|exists:waktu_makans,id',
|
||||
'status' => 'required|boolean'
|
||||
]);
|
||||
|
||||
$relasi = MakananKomponenWaktu::findOrFail($id);
|
||||
|
||||
// Cek apakah ada duplikasi selain record yang sedang diupdate
|
||||
$exists = MakananKomponenWaktu::where('id', '!=', $id)
|
||||
->where('makanan_id', $request->makanan_id)
|
||||
->where('komponen_id', $request->komponen_id)
|
||||
->where('waktu_makan_id', $request->waktu_makan_id)
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', 'Relasi sudah ada.');
|
||||
try {
|
||||
$relasi = MakananKomponenWaktu::findOrFail($id);
|
||||
$relasi->update($validatedData);
|
||||
return redirect()->route('relasi.index')->with('success', 'Relasi berhasil diperbarui!');
|
||||
} catch (\Illuminate\Database\QueryException $e) {
|
||||
if ($e->errorInfo[1] == 1062) { // MySQL error code for duplicate entry
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('error', 'Relasi ini sudah ada dalam database.');
|
||||
}
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$relasi->update([
|
||||
'makanan_id' => $request->makanan_id,
|
||||
'komponen_id' => $request->komponen_id,
|
||||
'waktu_makan_id' => $request->waktu_makan_id,
|
||||
]);
|
||||
|
||||
return redirect()->route('relasi')->with('success', 'Relasi berhasil diperbarui.');
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -327,4 +316,13 @@ public function destroy($id)
|
|||
|
||||
return redirect()->back()->with('success', 'Relasi berhasil dihapus.');
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$makanans = Makanan::all();
|
||||
$komponens = Komponen::all();
|
||||
$waktuMakans = WaktuMakan::all();
|
||||
|
||||
return view('admin.relasi.create', compact('makanans', 'komponens', 'waktuMakans'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,213 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\UserNotification;
|
||||
use App\Services\NotificationService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class NotificationController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$user = Auth::user();
|
||||
$notifications = $user->notifications()
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate(20);
|
||||
|
||||
$unreadCount = $user->unreadNotifications()->count();
|
||||
|
||||
return view('user.notifications.index', compact('notifications', 'unreadCount'));
|
||||
}
|
||||
|
||||
public function markAsRead($id)
|
||||
{
|
||||
$success = NotificationService::markAsRead($id, Auth::id());
|
||||
|
||||
return response()->json([
|
||||
'success' => $success,
|
||||
'message' => $success ? 'Notifikasi ditandai sebagai dibaca' : 'Notifikasi tidak ditemukan'
|
||||
]);
|
||||
}
|
||||
|
||||
public function markAllAsRead()
|
||||
{
|
||||
$count = NotificationService::markAllAsRead(Auth::id());
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => "{$count} notifikasi ditandai sebagai dibaca"
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
$notification = UserNotification::where('id', $id)
|
||||
->where('user_id', Auth::id())
|
||||
->first();
|
||||
|
||||
if ($notification) {
|
||||
$notification->delete();
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Notifikasi berhasil dihapus'
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Notifikasi tidak ditemukan'
|
||||
]);
|
||||
}
|
||||
|
||||
public function getUnreadCount()
|
||||
{
|
||||
$count = Auth::user()->unreadNotifications()->count();
|
||||
|
||||
return response()->json([
|
||||
'count' => $count
|
||||
]);
|
||||
}
|
||||
|
||||
public function getLatestNotifications()
|
||||
{
|
||||
$notifications = Auth::user()->notifications()
|
||||
->orderBy('created_at', 'desc')
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'notifications' => $notifications
|
||||
]);
|
||||
}
|
||||
|
||||
public function testNotification()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$notification = NotificationService::sendGeneralNotification(
|
||||
$user->id,
|
||||
'Test Notifikasi',
|
||||
'Ini adalah test notifikasi dari sistem. Sistem notifikasi berfungsi dengan baik!',
|
||||
'info',
|
||||
['test' => true]
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Test notifikasi berhasil dikirim',
|
||||
'notification' => $notification
|
||||
]);
|
||||
}
|
||||
|
||||
public function testBrowserNotification()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// Kirim notifikasi ke database
|
||||
$notification = NotificationService::sendGeneralNotification(
|
||||
$user->id,
|
||||
'Test Browser Notification',
|
||||
'Ini adalah test notifikasi browser yang akan muncul sebagai popup di device Anda.',
|
||||
'info',
|
||||
['browser_test' => true, 'timestamp' => now()]
|
||||
);
|
||||
|
||||
// Data untuk browser notification
|
||||
$notificationData = [
|
||||
'title' => 'HeartChoice - Test Browser Notification',
|
||||
'body' => 'Ini adalah test notifikasi popup di device Anda. Jika Anda melihat ini, berarti notifikasi browser berfungsi dengan baik!',
|
||||
'icon' => asset('logo/baru/dutdut.png'),
|
||||
'badge' => asset('logo/baru/dutdut.png'),
|
||||
'tag' => 'test-browser-notification',
|
||||
'requireInteraction' => false,
|
||||
'silent' => false,
|
||||
'vibrate' => [200, 100, 200],
|
||||
'data' => [
|
||||
'url' => route('user.notifications'),
|
||||
'notification_id' => $notification->id,
|
||||
'type' => 'browser_test'
|
||||
]
|
||||
];
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Test browser notification berhasil dikirim',
|
||||
'notification' => $notification,
|
||||
'browser_data' => $notificationData
|
||||
]);
|
||||
}
|
||||
|
||||
public function sendBackgroundNotification(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
$title = $request->input('title', 'HeartChoice Notification');
|
||||
$body = $request->input('body', 'Anda memiliki notifikasi baru!');
|
||||
$type = $request->input('type', 'info');
|
||||
|
||||
// Kirim notifikasi ke database
|
||||
$notification = NotificationService::sendGeneralNotification(
|
||||
$user->id,
|
||||
$title,
|
||||
$body,
|
||||
$type,
|
||||
['background' => true, 'timestamp' => now()]
|
||||
);
|
||||
|
||||
// Data untuk background notification
|
||||
$notificationData = [
|
||||
'title' => $title,
|
||||
'body' => $body,
|
||||
'icon' => asset('logo/baru/dutdut.png'),
|
||||
'badge' => asset('logo/baru/dutdut.png'),
|
||||
'tag' => 'background-notification',
|
||||
'requireInteraction' => false,
|
||||
'silent' => false,
|
||||
'vibrate' => [200, 100, 200],
|
||||
'data' => [
|
||||
'url' => route('user.notifications'),
|
||||
'notification_id' => $notification->id,
|
||||
'type' => 'background'
|
||||
]
|
||||
];
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Background notification berhasil dikirim',
|
||||
'notification' => $notification,
|
||||
'browser_data' => $notificationData
|
||||
]);
|
||||
}
|
||||
|
||||
public function getAvailableSounds()
|
||||
{
|
||||
$audioPath = public_path('audio');
|
||||
$availableSounds = [];
|
||||
|
||||
if (is_dir($audioPath)) {
|
||||
$files = scandir($audioPath);
|
||||
|
||||
foreach ($files as $file) {
|
||||
if ($file !== '.' && $file !== '..') {
|
||||
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
|
||||
|
||||
// Hanya ambil file audio
|
||||
if (in_array($extension, ['mp3', 'wav', 'ogg', 'm4a'])) {
|
||||
$name = pathinfo($file, PATHINFO_FILENAME);
|
||||
$availableSounds[] = [
|
||||
'name' => $name,
|
||||
'file' => $file,
|
||||
'path' => asset("audio/{$file}"),
|
||||
'display_name' => ucfirst(str_replace(['_', '-'], ' ', $name))
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'sounds' => $availableSounds
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ class PerbandinganKriteriaController extends Controller
|
|||
{
|
||||
//
|
||||
public function kriteria(){
|
||||
$kriterias = Kriteria::all();
|
||||
$kriterias = Kriteria::paginate(10);
|
||||
return view('admin.kriteria.kriteria', compact('kriterias'));
|
||||
}
|
||||
public function editkriteria(Kriteria $kriteria){
|
||||
|
|
|
|||
|
|
@ -7,11 +7,31 @@
|
|||
use App\Models\Alternatif;
|
||||
use App\Models\PerbandinganKriteria;
|
||||
use App\Models\BobotKriteria;
|
||||
use App\Models\ConsistencyRatioCriteria;
|
||||
use App\Models\Makanan;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
class ProsesController extends Controller
|
||||
{
|
||||
// Random Index value for n criteria (1-15)
|
||||
private $RI = [
|
||||
1 => 0.00,
|
||||
2 => 0.00,
|
||||
3 => 0.58,
|
||||
4 => 0.90,
|
||||
5 => 1.12,
|
||||
6 => 1.24,
|
||||
7 => 1.32,
|
||||
8 => 1.41,
|
||||
9 => 1.45,
|
||||
10 => 1.49,
|
||||
11 => 1.51,
|
||||
12 => 1.48,
|
||||
13 => 1.56,
|
||||
14 => 1.57,
|
||||
15 => 1.59
|
||||
];
|
||||
|
||||
public function showPerbandingan()
|
||||
{
|
||||
$kriterias = Kriteria::all();
|
||||
|
|
@ -22,17 +42,24 @@ public function prosesSementara(Request $request)
|
|||
{
|
||||
$request->validate([
|
||||
'nilai' => 'required|array',
|
||||
'waktu_makan_id' => 'required|exists:waktu_makans,id'
|
||||
]);
|
||||
|
||||
// Simpan waktu_makan_id ke session
|
||||
Session::put('waktu_makan_id', $request->waktu_makan_id);
|
||||
$waktuMakanId = $request->waktu_makan_id;
|
||||
|
||||
$kriterias = Kriteria::all();
|
||||
$nilai = [];
|
||||
|
||||
// Hapus data perbandingan lama untuk waktu makan ini
|
||||
foreach ($kriterias as $baris) {
|
||||
foreach ($kriterias as $kolom) {
|
||||
if ($baris->id != $kolom->id) {
|
||||
PerbandinganKriteria::where([
|
||||
['kriteria_id_1', $baris->id],
|
||||
['kriteria_id_2', $kolom->id]
|
||||
['kriteria_id_2', $kolom->id],
|
||||
['waktu_makan_id', $waktuMakanId]
|
||||
])->delete();
|
||||
}
|
||||
}
|
||||
|
|
@ -47,10 +74,12 @@ public function prosesSementara(Request $request)
|
|||
$nilai[$baris->id][$kolom->id] = $val;
|
||||
$nilai[$kolom->id][$baris->id] = 1 / $val;
|
||||
|
||||
// Simpan ke database dengan waktu_makan_id
|
||||
PerbandinganKriteria::updateOrCreate(
|
||||
[
|
||||
'kriteria_id_1' => $baris->id,
|
||||
'kriteria_id_2' => $kolom->id
|
||||
'kriteria_id_2' => $kolom->id,
|
||||
'waktu_makan_id' => $waktuMakanId
|
||||
],
|
||||
[
|
||||
'nilai' => $val
|
||||
|
|
@ -61,66 +90,126 @@ public function prosesSementara(Request $request)
|
|||
}
|
||||
|
||||
Session::put('matriks', $nilai);
|
||||
return redirect()->route('perbandingan');
|
||||
return redirect()->route('perbandingan')
|
||||
->with('success', 'Perbandingan kriteria berhasil disimpan.');
|
||||
}
|
||||
|
||||
public function simpanPerbandingan()
|
||||
public function simpanPerbandingan(Request $request)
|
||||
{
|
||||
$matriks = Session::get('matriks');
|
||||
$waktuMakanId = Session::get('waktu_makan_id');
|
||||
|
||||
if (!$matriks) {
|
||||
return redirect()->route('perbandingan')->with('error', 'Belum ada data untuk disimpan.');
|
||||
return redirect()->route('perbandingan')
|
||||
->with('error', 'Belum ada data untuk disimpan.');
|
||||
}
|
||||
|
||||
if (!$waktuMakanId) {
|
||||
return redirect()->route('perbandingan')
|
||||
->with('error', 'Waktu makan belum dipilih.');
|
||||
}
|
||||
|
||||
// Simpan matriks dan waktu makan ke session
|
||||
Session::put('matriks_perbandingan', $matriks);
|
||||
return redirect()->route('hasil.normalisasi')->with('success', 'Data berhasil disimpan!');
|
||||
Session::put('waktu_makan_id', $waktuMakanId);
|
||||
|
||||
return redirect()->route('hasil.normalisasi')
|
||||
->with('success', 'Data perbandingan berhasil disimpan!');
|
||||
}
|
||||
|
||||
public function hasilNormalisasi()
|
||||
public function normalisasi()
|
||||
{
|
||||
$kriterias = Kriteria::all();
|
||||
$matriks = Session::get('matriks_perbandingan');
|
||||
$waktuMakanId = Session::get('waktu_makan_id');
|
||||
|
||||
if (!$matriks) {
|
||||
return redirect()->route('perbandingan')->with('error', 'Data perbandingan belum tersedia.');
|
||||
}
|
||||
|
||||
$jumlahKolom = [];
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$id = $kriteria->id;
|
||||
$jumlahKolom[$id] = 0;
|
||||
foreach ($matriks as $baris) {
|
||||
$jumlahKolom[$id] += $baris[$id];
|
||||
}
|
||||
if (!$matriks || !$waktuMakanId) {
|
||||
return redirect()->route('perbandingan')
|
||||
->with('error', 'Belum ada data untuk dinormalisasi.');
|
||||
}
|
||||
|
||||
$kriterias = Kriteria::all();
|
||||
$normalisasi = [];
|
||||
foreach ($kriterias as $baris) {
|
||||
$row = [];
|
||||
foreach ($kriterias as $kolom) {
|
||||
$row[] = $matriks[$baris->id][$kolom->id] / $jumlahKolom[$kolom->id];
|
||||
}
|
||||
$normalisasi[] = $row;
|
||||
}
|
||||
|
||||
$jumlahKolom = [];
|
||||
$bobot = [];
|
||||
foreach ($normalisasi as $baris) {
|
||||
$bobot[] = array_sum($baris) / count($baris);
|
||||
$totalBobot = 0;
|
||||
|
||||
// Hitung jumlah kolom
|
||||
foreach ($kriterias as $kolom) {
|
||||
$jumlahKolom[$kolom->id] = 0;
|
||||
foreach ($kriterias as $baris) {
|
||||
$jumlahKolom[$kolom->id] += $matriks[$baris->id][$kolom->id];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($bobot as $index => $value) {
|
||||
// Normalisasi
|
||||
foreach ($kriterias as $baris) {
|
||||
foreach ($kriterias as $kolom) {
|
||||
$normalisasi[$baris->id][$kolom->id] = $matriks[$baris->id][$kolom->id] / $jumlahKolom[$kolom->id];
|
||||
}
|
||||
}
|
||||
|
||||
// Hitung bobot
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$bobot[$kriteria->id] = array_sum($normalisasi[$kriteria->id]) / count($kriterias);
|
||||
$totalBobot += $bobot[$kriteria->id];
|
||||
}
|
||||
|
||||
// Normalisasi bobot
|
||||
foreach ($bobot as $key => $value) {
|
||||
$bobot[$key] = $value / $totalBobot;
|
||||
}
|
||||
|
||||
// Hitung CI dan CR
|
||||
$lambdaMax = 0;
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$sum = 0;
|
||||
foreach ($kriterias as $k) {
|
||||
$sum += $matriks[$kriteria->id][$k->id] * $bobot[$k->id];
|
||||
}
|
||||
$lambdaMax += $sum / $bobot[$kriteria->id];
|
||||
}
|
||||
$lambdaMax = $lambdaMax / count($kriterias);
|
||||
$CI = ($lambdaMax - count($kriterias)) / (count($kriterias) - 1);
|
||||
$CR = $CI / $this->RI[count($kriterias)];
|
||||
|
||||
// Simpan bobot ke database
|
||||
foreach ($bobot as $kriteriaId => $value) {
|
||||
BobotKriteria::updateOrCreate(
|
||||
['kriteria_id' => $kriterias[$index]->id],
|
||||
[
|
||||
'kriteria_id' => $kriteriaId,
|
||||
'waktu_makan_id' => $waktuMakanId
|
||||
],
|
||||
['bobot' => $value]
|
||||
);
|
||||
}
|
||||
|
||||
// Simpan CR ke database
|
||||
ConsistencyRatioCriteria::updateOrCreate(
|
||||
[
|
||||
'waktu_makan_id' => $waktuMakanId
|
||||
],
|
||||
[
|
||||
'ci' => $CI,
|
||||
'cr' => $CR,
|
||||
'is_consistent' => $CR <= 0.1
|
||||
]
|
||||
);
|
||||
|
||||
Session::put('bobot_kriteria', $bobot);
|
||||
Session::put('tahap_kriteria_selesai', true);
|
||||
|
||||
// Ambil data waktu makan
|
||||
$waktuMakan = \App\Models\WaktuMakan::findOrFail($waktuMakanId);
|
||||
|
||||
return view('admin.proses.normalisasi', [
|
||||
'kriterias' => $kriterias,
|
||||
'normalisasi' => $normalisasi,
|
||||
'bobot' => $bobot
|
||||
'bobot' => $bobot,
|
||||
'ci' => $CI,
|
||||
'cr' => $CR,
|
||||
'is_consistent' => $CR <= 0.1,
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'waktu_makan' => $waktuMakan // Tambahkan ini untuk menghindari query di view
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\RekomendasiAhli;
|
||||
use App\Models\WaktuMakan;
|
||||
use App\Models\Komponen;
|
||||
use App\Models\Makanan;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class RekomendasiAhliController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
// Ambil semua data rekomendasi ahli beserta relasi
|
||||
$rekomendasiAhli = \App\Models\RekomendasiAhli::with(['komponen', 'makanan', 'waktuMakan'])->get();
|
||||
// Group by hari
|
||||
$rekomendasiByHari = $rekomendasiAhli->groupBy('hari');
|
||||
// Ambil semua hari (urutan enum)
|
||||
$daftarHari = ['Senin','Selasa','Rabu','Kamis','Jumat','Sabtu','Minggu'];
|
||||
return view('admin.rekomendasi-ahli-list', compact('rekomendasiByHari', 'daftarHari'));
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
$hari = $request->query('hari');
|
||||
$waktuMakans = WaktuMakan::all();
|
||||
$komponens = Komponen::all();
|
||||
$makanans = Makanan::all();
|
||||
return view('admin.rekomendasi-ahli.create', compact('hari', 'waktuMakans', 'komponens', 'makanans'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'makanan_id' => 'required|exists:makanans,id',
|
||||
'komponen_id' => 'required|exists:komponens,id',
|
||||
'waktu_makan_id' => 'required|exists:waktu_makans,id',
|
||||
'hari' => 'required'
|
||||
]);
|
||||
RekomendasiAhli::create($request->all());
|
||||
return redirect()->route('rekomendasi_ahli.index')->with('success', 'Rekomendasi ahli berhasil ditambahkan.');
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$rekomendasi = RekomendasiAhli::findOrFail($id);
|
||||
$waktuMakans = WaktuMakan::all();
|
||||
$komponens = Komponen::all();
|
||||
$makanans = Makanan::all();
|
||||
return view('admin.rekomendasi-ahli.edit', compact('rekomendasi', 'waktuMakans', 'komponens', 'makanans'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$request->validate([
|
||||
'makanan_id' => 'required|exists:makanans,id',
|
||||
'komponen_id' => 'required|exists:komponens,id',
|
||||
'waktu_makan_id' => 'required|exists:waktu_makans,id',
|
||||
'hari' => 'required'
|
||||
]);
|
||||
$rekomendasi = RekomendasiAhli::findOrFail($id);
|
||||
$rekomendasi->update($request->all());
|
||||
return redirect()->route('rekomendasi_ahli.index')->with('success', 'Rekomendasi ahli berhasil diupdate.');
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
RekomendasiAhli::findOrFail($id)->delete();
|
||||
return redirect()->route('rekomendasi_ahli.index')->with('success', 'Rekomendasi ahli berhasil dihapus.');
|
||||
}
|
||||
}
|
||||
|
|
@ -3,136 +3,552 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Makanan;
|
||||
use App\Models\Kriteria;
|
||||
use App\Models\Makanan;
|
||||
use App\Models\BobotKriteria;
|
||||
use App\Models\SkorMakanan;
|
||||
use App\Models\Rekomendasi;
|
||||
use App\Models\WaktuMakan;
|
||||
use App\Models\RekomendasiAhli;
|
||||
use App\Models\Komponen;
|
||||
use App\Models\WaktuMakan;
|
||||
use App\Models\PreferensiWaktuKriteria;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Carbon\Carbon;
|
||||
use App\Models\PerbandinganKriteria;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Traits\KriteriaTrait;
|
||||
use App\Models\PerbandinganAlternatif;
|
||||
use App\Models\ConsistencyRatioCriteria;
|
||||
use App\Models\ConsistencyRatioAlternatif;
|
||||
|
||||
class RekomendasiController extends Controller
|
||||
{
|
||||
use KriteriaTrait;
|
||||
|
||||
public function hitungDanSimpanOtomatis()
|
||||
{
|
||||
$kriterias = Kriteria::all();
|
||||
$defaultBobots = BobotKriteria::pluck('bobot', 'kriteria_id');
|
||||
$waktuMakans = WaktuMakan::all();
|
||||
$komponens = Komponen::all();
|
||||
{
|
||||
try {
|
||||
// Ambil data dari session
|
||||
$sessionData = session('hasil_rekomendasi');
|
||||
$alternatifsByKomponen = session('alternatifs_by_komponen');
|
||||
$waktuMakanId = session('waktu_makan_id');
|
||||
|
||||
SkorMakanan::truncate();
|
||||
Rekomendasi::where('user_id', Auth::id())->delete();
|
||||
if (!$sessionData || !$alternatifsByKomponen || !$waktuMakanId) {
|
||||
\Log::error('Data session tidak lengkap:', [
|
||||
'hasil_rekomendasi' => $sessionData,
|
||||
'alternatifs_by_komponen' => $alternatifsByKomponen,
|
||||
'waktu_makan_id' => $waktuMakanId
|
||||
]);
|
||||
return redirect()->route('alternatif.pilih')
|
||||
->with('error', 'Data session tidak lengkap. Silakan pilih alternatif terlebih dahulu.');
|
||||
}
|
||||
|
||||
foreach ($waktuMakans as $waktu) {
|
||||
// Ambil preferensi bobot kriteria untuk waktu makan ini
|
||||
$preferensiBobot = \App\Models\PreferensiWaktuKriteria::where('waktu_makan_id', $waktu->id)
|
||||
->pluck('bobot', 'kriteria_id');
|
||||
|
||||
foreach ($komponens as $komponen) {
|
||||
$makanans = Makanan::whereHas('komponenWaktu', function ($query) use ($komponen, $waktu) {
|
||||
$query->where('komponen_id', $komponen->id)
|
||||
->where('waktu_makan_id', $waktu->id);
|
||||
})->get();
|
||||
|
||||
if ($makanans->isEmpty()) continue;
|
||||
|
||||
$total = [
|
||||
'lemak' => $makanans->sum('lemak') ?: 1,
|
||||
'natrium' => $makanans->sum('natrium') ?: 1,
|
||||
'energi' => $makanans->sum('energi') ?: 1,
|
||||
'karbohidrat' => $makanans->sum('karbohidrat') ?: 1,
|
||||
];
|
||||
|
||||
foreach ($makanans as $makanan) {
|
||||
$skor = 0;
|
||||
// Ambil semua kriteria
|
||||
$kriterias = Kriteria::all();
|
||||
$bobotKriteria = $this->getBobotKriteria($kriterias);
|
||||
|
||||
// Proses untuk setiap komponen
|
||||
foreach ($alternatifsByKomponen as $komponenId => $komponenData) {
|
||||
$alternatifs = $komponenData['alternatifs'];
|
||||
|
||||
// Log untuk debugging
|
||||
\Log::info('Memproses komponen:', [
|
||||
'komponen_id' => $komponenId,
|
||||
'jumlah_alternatif' => count($alternatifs)
|
||||
]);
|
||||
|
||||
// Inisialisasi matriks perbandingan
|
||||
$matriksPerbandingan = [];
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$attr = strtolower($kriteria->nama);
|
||||
$nilai = $makanan->$attr ?? 0;
|
||||
|
||||
// Normalisasi
|
||||
$normalized = in_array($attr, ['lemak', 'natrium'])
|
||||
? 1 - ($nilai / $total[$attr])
|
||||
: $nilai / $total[$attr];
|
||||
|
||||
// Gunakan preferensi waktu jika ada, jika tidak fallback
|
||||
$bobot = $preferensiBobot[$kriteria->id] ?? $defaultBobots[$kriteria->id] ?? 0;
|
||||
|
||||
$skor += $normalized * $bobot;
|
||||
|
||||
SkorMakanan::updateOrCreate([
|
||||
'kriteria_id' => $kriteria->id,
|
||||
'makanan_id' => $makanan->id,
|
||||
], [
|
||||
'nilai' => $normalized
|
||||
]);
|
||||
$matriksPerbandingan[$kriteria->id] = [];
|
||||
foreach ($alternatifs as $alt1) {
|
||||
$matriksPerbandingan[$kriteria->id][$alt1['id']] = [];
|
||||
foreach ($alternatifs as $alt2) {
|
||||
// Menggunakan nilai yang sudah dinormalisasi dari session
|
||||
$nilai1 = $alt1[strtolower($kriteria->nama) . '_normalized'] ?? 0;
|
||||
$nilai2 = $alt2[strtolower($kriteria->nama) . '_normalized'] ?? 0;
|
||||
|
||||
if ($nilai2 == 0) {
|
||||
$matriksPerbandingan[$kriteria->id][$alt1['id']][$alt2['id']] = 0;
|
||||
} else {
|
||||
$matriksPerbandingan[$kriteria->id][$alt1['id']][$alt2['id']] = $nilai1 / $nilai2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simpan nilai akhir ke rekomendasi
|
||||
Rekomendasi::updateOrCreate(
|
||||
[
|
||||
'user_id' => Auth::id(),
|
||||
'makanan_id' => $makanan->id,
|
||||
'komponen_id' => $komponen->id,
|
||||
'waktu_makan_id' => $waktu->id,
|
||||
],
|
||||
[
|
||||
'nilai_akhir' => $skor,
|
||||
'tanggal_rekomendasi' => now(),
|
||||
]
|
||||
);
|
||||
// Hitung prioritas lokal untuk setiap kriteria
|
||||
$prioritasLokal = [];
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$prioritasLokal[$kriteria->id] = [];
|
||||
|
||||
// Hitung jumlah kolom
|
||||
$jumlahKolom = [];
|
||||
foreach ($alternatifs as $alt2) {
|
||||
$jumlahKolom[$alt2['id']] = 0;
|
||||
foreach ($alternatifs as $alt1) {
|
||||
$jumlahKolom[$alt2['id']] += $matriksPerbandingan[$kriteria->id][$alt1['id']][$alt2['id']];
|
||||
}
|
||||
}
|
||||
|
||||
// Normalisasi matriks
|
||||
$matriksNormal = [];
|
||||
foreach ($alternatifs as $alt1) {
|
||||
$matriksNormal[$alt1['id']] = [];
|
||||
foreach ($alternatifs as $alt2) {
|
||||
if ($jumlahKolom[$alt2['id']] > 0) {
|
||||
$matriksNormal[$alt1['id']][$alt2['id']] =
|
||||
$matriksPerbandingan[$kriteria->id][$alt1['id']][$alt2['id']] / $jumlahKolom[$alt2['id']];
|
||||
} else {
|
||||
$matriksNormal[$alt1['id']][$alt2['id']] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hitung prioritas lokal (rata-rata baris)
|
||||
foreach ($alternatifs as $alt) {
|
||||
$jumlahBaris = array_sum($matriksNormal[$alt['id']]);
|
||||
$prioritasLokal[$kriteria->id][$alt['id']] = $jumlahBaris / count($alternatifs);
|
||||
}
|
||||
|
||||
// Simpan skor ke database
|
||||
foreach ($alternatifs as $alt) {
|
||||
SkorMakanan::updateOrCreate(
|
||||
[
|
||||
'makanan_id' => $alt['id'],
|
||||
'kriteria_id' => $kriteria->id,
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'komponen_id' => $komponenId
|
||||
],
|
||||
['nilai' => $prioritasLokal[$kriteria->id][$alt['id']]]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Hitung skor akhir
|
||||
foreach ($alternatifs as $alt) {
|
||||
$skorAkhir = 0;
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$skorAkhir += $prioritasLokal[$kriteria->id][$alt['id']] * $bobotKriteria[$kriteria->id];
|
||||
}
|
||||
|
||||
// Simpan rekomendasi
|
||||
Rekomendasi::updateOrCreate(
|
||||
[
|
||||
'makanan_id' => $alt['id'],
|
||||
'komponen_id' => $komponenId,
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'user_id' => Auth::id(),
|
||||
'tanggal_rekomendasi' => $sessionData['tanggal_rekomendasi']
|
||||
],
|
||||
['nilai_akhir' => $skorAkhir]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('rekomendasi.index')
|
||||
->with('success', 'Perhitungan AHP berhasil dilakukan.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Error in hitungDanSimpanOtomatis:', [
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'session_data' => session()->all()
|
||||
]);
|
||||
return redirect()->back()
|
||||
->with('error', 'Terjadi kesalahan saat melakukan perhitungan: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('rekomendasi.hasil')->with('success', 'Rekomendasi berhasil dihitung dengan bobot preferensi waktu.');
|
||||
}
|
||||
private function getBobotKriteria($kriterias)
|
||||
{
|
||||
try {
|
||||
$waktuMakanId = session('waktu_makan_id');
|
||||
|
||||
if (!$waktuMakanId) {
|
||||
throw new \Exception('Waktu makan ID tidak ditemukan di session');
|
||||
}
|
||||
|
||||
$bobotKriteria = [];
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$bobot = BobotKriteria::where([
|
||||
'kriteria_id' => $kriteria->id,
|
||||
'waktu_makan_id' => $waktuMakanId
|
||||
])->first();
|
||||
|
||||
if (!$bobot) {
|
||||
// Jika tidak ada bobot, coba ambil dari perbandingan kriteria
|
||||
$perbandinganKriteria = PerbandinganKriteria::where([
|
||||
'kriteria_id_1' => $kriteria->id,
|
||||
'waktu_makan_id' => $waktuMakanId
|
||||
])->first();
|
||||
|
||||
if ($perbandinganKriteria) {
|
||||
$total = PerbandinganKriteria::where([
|
||||
'kriteria_id_1' => $kriteria->id,
|
||||
'waktu_makan_id' => $waktuMakanId
|
||||
])->sum('nilai');
|
||||
|
||||
$bobotKriteria[$kriteria->id] = $total > 0 ? $perbandinganKriteria->nilai / $total : 0;
|
||||
} else {
|
||||
\Log::warning("Bobot kriteria tidak ditemukan untuk kriteria {$kriteria->nama} pada waktu makan ID: {$waktuMakanId}");
|
||||
$bobotKriteria[$kriteria->id] = 0;
|
||||
}
|
||||
} else {
|
||||
$bobotKriteria[$kriteria->id] = $bobot->bobot;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalisasi bobot kriteria
|
||||
$totalBobot = array_sum($bobotKriteria);
|
||||
if ($totalBobot > 0) {
|
||||
foreach ($bobotKriteria as $key => $value) {
|
||||
$bobotKriteria[$key] = $value / $totalBobot;
|
||||
}
|
||||
}
|
||||
|
||||
// Log untuk debugging
|
||||
\Log::info('Bobot kriteria yang diambil:', [
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'bobot' => $bobotKriteria
|
||||
]);
|
||||
|
||||
return $bobotKriteria;
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Error in getBobotKriteria: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function tampilHasil()
|
||||
{
|
||||
$rekomendasi = Rekomendasi::with('makanan', 'komponen', 'waktuMakan')
|
||||
->where('user_id', Auth::id())
|
||||
// ->whereDate('tanggal_rekomendasi', now())
|
||||
->get();
|
||||
|
||||
// Urutan komponen manual
|
||||
$urutanKomponen = ['karbohidrat', 'protein', 'sayur', 'buah', 'susu'];
|
||||
|
||||
// Hitung total nilai_akhir per waktu & komponen
|
||||
$totalPerGroup = [];
|
||||
foreach ($rekomendasi as $r) {
|
||||
$waktu = strtolower($r->waktuMakan->nama);
|
||||
$komponen = strtolower($r->komponen->nama);
|
||||
$totalPerGroup[$waktu][$komponen] = ($totalPerGroup[$waktu][$komponen] ?? 0) + $r->nilai_akhir;
|
||||
}
|
||||
|
||||
// Group dan sort data
|
||||
$rekomendasiGrouped = $rekomendasi
|
||||
->groupBy([
|
||||
fn($item) => strtolower($item->waktuMakan->nama),
|
||||
fn($item) => strtolower($item->komponen->nama),
|
||||
]);
|
||||
|
||||
foreach ($rekomendasiGrouped as $waktu => $komponens) {
|
||||
$sorted = collect();
|
||||
foreach ($urutanKomponen as $target) {
|
||||
if ($komponens->has($target)) {
|
||||
// Tambahkan properti persentase ke setiap item
|
||||
$komponens[$target]->each(function ($item) use ($totalPerGroup, $waktu, $target) {
|
||||
$total = $totalPerGroup[$waktu][$target] ?: 1;
|
||||
$item->persentase = ($item->nilai_akhir / $total) * 100;
|
||||
});
|
||||
|
||||
$sorted[$target] = $komponens[$target]->sortByDesc('nilai_akhir');
|
||||
{
|
||||
try {
|
||||
// Ambil data dari session
|
||||
$hasilRekomendasi = session('hasil_rekomendasi');
|
||||
|
||||
if (!$hasilRekomendasi) {
|
||||
return redirect()->route('alternatif.pilih')
|
||||
->with('error', 'Data rekomendasi tidak ditemukan.');
|
||||
}
|
||||
|
||||
// Ambil data yang diperlukan dengan select spesifik
|
||||
$kriterias = Kriteria::select('id', 'nama')->get();
|
||||
$komponens = Komponen::select('id', 'nama')->get();
|
||||
$waktuMakan = WaktuMakan::select('id', 'nama')->findOrFail($hasilRekomendasi['waktu_makan_id']);
|
||||
|
||||
// Inisialisasi array untuk menyimpan hasil per komponen
|
||||
$hasilPerKomponen = [];
|
||||
|
||||
// Proses setiap komponen
|
||||
foreach ($komponens as $komponen) {
|
||||
// Ambil rekomendasi untuk komponen ini
|
||||
$rekomendasis = Rekomendasi::with('makanan')
|
||||
->where('waktu_makan_id', $hasilRekomendasi['waktu_makan_id'])
|
||||
->where('komponen_id', $komponen->id)
|
||||
->orderByDesc('nilai_akhir')
|
||||
->get();
|
||||
|
||||
if ($rekomendasis->isNotEmpty()) {
|
||||
// Ambil detail skor hanya untuk makanan yang ada di rekomendasi
|
||||
$detailSkor = [];
|
||||
foreach ($rekomendasis as $rekomendasi) {
|
||||
$detailSkor[$rekomendasi->makanan_id] = [];
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$skor = SkorMakanan::select('nilai')
|
||||
->where('makanan_id', $rekomendasi->makanan_id)
|
||||
->where('kriteria_id', $kriteria->id)
|
||||
->first();
|
||||
$detailSkor[$rekomendasi->makanan_id][$kriteria->nama] = $skor ? $skor->nilai : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Simpan hasil untuk komponen ini
|
||||
$hasilPerKomponen[$komponen->id] = [
|
||||
'komponen' => $komponen,
|
||||
'rekomendasis' => $rekomendasis,
|
||||
'detailSkor' => $detailSkor
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Ambil bobot kriteria
|
||||
$bobotKriteria = [];
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$bobot = BobotKriteria::select('bobot')
|
||||
->where('kriteria_id', $kriteria->id)
|
||||
->first();
|
||||
$bobotKriteria[$kriteria->id] = $bobot ? $bobot->bobot : 0;
|
||||
}
|
||||
|
||||
// Normalisasi bobot
|
||||
$totalBobot = array_sum($bobotKriteria);
|
||||
if ($totalBobot > 0) {
|
||||
foreach ($bobotKriteria as $key => $value) {
|
||||
$bobotKriteria[$key] = $value / $totalBobot;
|
||||
}
|
||||
}
|
||||
|
||||
// Set CR to null since it's temporarily disabled
|
||||
$consistencyRatio = null;
|
||||
|
||||
return view('admin.rekomendasi', compact(
|
||||
'hasilPerKomponen',
|
||||
'kriterias',
|
||||
'waktuMakan',
|
||||
'consistencyRatio',
|
||||
'bobotKriteria'
|
||||
));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Error in tampilHasil:', [
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return redirect()->route('alternatif.pilih')
|
||||
->with('error', 'Terjadi kesalahan saat menampilkan hasil: ' . $e->getMessage());
|
||||
}
|
||||
$rekomendasiGrouped[$waktu] = $sorted;
|
||||
}
|
||||
|
||||
return view('admin.rekomendasi', ['rekomendasi' => $rekomendasiGrouped]);
|
||||
}
|
||||
private function hitungConsistencyRatio()
|
||||
{
|
||||
$waktuMakanId = session('waktu_makan_id');
|
||||
|
||||
if (!$waktuMakanId) {
|
||||
return 0; // Return 0 if no waktu_makan_id in session
|
||||
}
|
||||
|
||||
$perbandinganKriterias = PerbandinganKriteria::where('waktu_makan_id', $waktuMakanId)->get();
|
||||
$n = Kriteria::count();
|
||||
|
||||
if ($perbandinganKriterias->isEmpty()) {
|
||||
return 0; // Return 0 if no perbandingan data
|
||||
}
|
||||
|
||||
// Buat matriks perbandingan
|
||||
$matrix = array_fill(0, $n, array_fill(0, $n, 1));
|
||||
foreach ($perbandinganKriterias as $perbandingan) {
|
||||
$matrix[$perbandingan->kriteria_id_1 - 1][$perbandingan->kriteria_id_2 - 1] = $perbandingan->nilai;
|
||||
$matrix[$perbandingan->kriteria_id_2 - 1][$perbandingan->kriteria_id_1 - 1] = 1 / $perbandingan->nilai;
|
||||
}
|
||||
|
||||
// Hitung eigenvalue maksimum
|
||||
$rowSums = array_map(function($row) {
|
||||
return array_sum($row);
|
||||
}, $matrix);
|
||||
|
||||
$totalSum = array_sum($rowSums);
|
||||
$normalizedMatrix = array_map(function($row) use ($totalSum) {
|
||||
return array_map(function($val) use ($totalSum) {
|
||||
return $val / $totalSum;
|
||||
}, $row);
|
||||
}, $matrix);
|
||||
|
||||
$eigenvalue = 0;
|
||||
for ($i = 0; $i < $n; $i++) {
|
||||
$sum = 0;
|
||||
for ($j = 0; $j < $n; $j++) {
|
||||
$sum += $matrix[$i][$j] * array_sum($normalizedMatrix[$j]);
|
||||
}
|
||||
$eigenvalue += $sum / array_sum($normalizedMatrix[$i]);
|
||||
}
|
||||
$eigenvalue /= $n;
|
||||
|
||||
// Random Index values for n = 1 to 10
|
||||
$RI = [0, 0, 0.58, 0.90, 1.12, 1.24, 1.32, 1.41, 1.45, 1.49];
|
||||
|
||||
// Hitung Consistency Index
|
||||
$CI = ($eigenvalue - $n) / ($n - 1);
|
||||
|
||||
// Hitung Consistency Ratio
|
||||
return $n <= 2 ? 0 : $CI / $RI[$n - 1];
|
||||
}
|
||||
|
||||
private function getNilaiKriteria($makanan, $kriteria)
|
||||
{
|
||||
switch (strtolower($kriteria->nama)) {
|
||||
case 'energi':
|
||||
return is_array($makanan) ? $makanan['energi'] : $makanan->energi;
|
||||
case 'protein':
|
||||
return is_array($makanan) ? $makanan['protein'] : $makanan->protein;
|
||||
case 'lemak':
|
||||
return is_array($makanan) ? $makanan['lemak'] : $makanan->lemak;
|
||||
case 'karbohidrat':
|
||||
return is_array($makanan) ? $makanan['karbohidrat'] : $makanan->karbohidrat;
|
||||
case 'natrium':
|
||||
return is_array($makanan) ? $makanan['natrium'] : $makanan->natrium;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private function isCostCriteria($kriteriaNama)
|
||||
{
|
||||
return in_array(strtolower($kriteriaNama), ['lemak', 'natrium']);
|
||||
}
|
||||
|
||||
private function normalisasiNilai($nilai, $kriteria, $makanans)
|
||||
{
|
||||
// Kumpulkan semua nilai untuk kriteria ini
|
||||
$nilaiKriteria = [];
|
||||
foreach ($makanans as $makanan) {
|
||||
$nilaiKriteria[] = $this->getNilaiKriteria($makanan, $kriteria);
|
||||
}
|
||||
|
||||
// Cek jenis kriteria (benefit atau cost)
|
||||
if ($this->isCostCriteria($kriteria->nama)) {
|
||||
// Untuk kriteria cost (lemak dan natrium), nilai lebih kecil lebih baik
|
||||
$nilaiInverse = $nilai > 0 ? 1 / $nilai : 0;
|
||||
$totalInverse = 0;
|
||||
foreach ($nilaiKriteria as $n) {
|
||||
$totalInverse += ($n > 0 ? 1 / $n : 0);
|
||||
}
|
||||
return $totalInverse > 0 ? $nilaiInverse / $totalInverse : 0;
|
||||
} else {
|
||||
// Untuk kriteria benefit (energi dan karbohidrat), nilai lebih besar lebih baik
|
||||
$total = array_sum($nilaiKriteria);
|
||||
return $total > 0 ? $nilai / $total : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$waktuMakans = WaktuMakan::with(['komponens', 'consistencyRatios' => function($query) {
|
||||
$query->where('user_id', Auth::id())
|
||||
->latest('tanggal_perhitungan');
|
||||
}])
|
||||
->select('waktu_makans.*')
|
||||
->selectRaw('(
|
||||
SELECT COUNT(*) > 0
|
||||
FROM rekomendasis r
|
||||
WHERE r.waktu_makan_id = waktu_makans.id
|
||||
AND r.user_id = ?
|
||||
) as has_recommendation', [Auth::id()])
|
||||
->selectRaw('(
|
||||
SELECT MAX(tanggal_rekomendasi)
|
||||
FROM rekomendasis r
|
||||
WHERE r.waktu_makan_id = waktu_makans.id
|
||||
AND r.user_id = ?
|
||||
) as latest_calculation', [Auth::id()])
|
||||
->get();
|
||||
|
||||
return view('admin.rekomendasi-list', compact('waktuMakans'));
|
||||
}
|
||||
|
||||
public function detail($waktuMakanId)
|
||||
{
|
||||
try {
|
||||
$waktuMakan = WaktuMakan::findOrFail($waktuMakanId);
|
||||
|
||||
// Ambil rekomendasi terbaru untuk waktu makan ini
|
||||
$latestRekomendasi = Rekomendasi::where('waktu_makan_id', $waktuMakanId)
|
||||
->where('user_id', Auth::id())
|
||||
->orderBy('tanggal_rekomendasi', 'desc')
|
||||
->first();
|
||||
|
||||
if (!$latestRekomendasi) {
|
||||
return redirect()->route('rekomendasi.index')
|
||||
->with('error', 'Belum ada rekomendasi untuk waktu makan ini.');
|
||||
}
|
||||
|
||||
// Set data untuk tampilan detail
|
||||
$hasilRekomendasi = [
|
||||
'tanggal_rekomendasi' => $latestRekomendasi->tanggal_rekomendasi,
|
||||
'waktu_makan_id' => $waktuMakanId
|
||||
];
|
||||
|
||||
// Ambil semua kriteria
|
||||
$kriterias = Kriteria::all();
|
||||
|
||||
// Ambil bobot kriteria dari database
|
||||
$bobotKriteria = [];
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$bobot = BobotKriteria::where('kriteria_id', $kriteria->id)->first();
|
||||
$bobotKriteria[$kriteria->id] = $bobot ? $bobot->bobot : 0;
|
||||
}
|
||||
|
||||
// Normalisasi bobot
|
||||
$totalBobot = array_sum($bobotKriteria);
|
||||
if ($totalBobot > 0) {
|
||||
foreach ($bobotKriteria as $key => $value) {
|
||||
$bobotKriteria[$key] = $value / $totalBobot;
|
||||
}
|
||||
}
|
||||
|
||||
// Ambil komponen yang memiliki rekomendasi
|
||||
$komponens = Komponen::whereExists(function ($query) use ($waktuMakanId, $latestRekomendasi) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('rekomendasis')
|
||||
->whereColumn('rekomendasis.komponen_id', 'komponens.id')
|
||||
->where('rekomendasis.waktu_makan_id', $waktuMakanId)
|
||||
->where('rekomendasis.tanggal_rekomendasi', $latestRekomendasi->tanggal_rekomendasi);
|
||||
})->get();
|
||||
|
||||
$hasilPerKomponen = [];
|
||||
foreach ($komponens as $komponen) {
|
||||
// Ambil rekomendasi untuk komponen ini dengan eager loading
|
||||
$rekomendasis = Rekomendasi::with(['makanan' => function($query) {
|
||||
$query->select('makanans.id', 'makanans.nama', 'makanans.energi', 'makanans.lemak',
|
||||
'makanans.karbohidrat', 'makanans.natrium');
|
||||
}])
|
||||
->where('waktu_makan_id', $waktuMakanId)
|
||||
->where('komponen_id', $komponen->id)
|
||||
->where('user_id', Auth::id())
|
||||
->where('tanggal_rekomendasi', $latestRekomendasi->tanggal_rekomendasi)
|
||||
->orderByDesc('nilai_akhir')
|
||||
->get();
|
||||
|
||||
// Ambil detail skor untuk setiap makanan
|
||||
$detailSkor = [];
|
||||
foreach ($rekomendasis as $rekomendasi) {
|
||||
$detailSkor[$rekomendasi->makanan_id] = [];
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$skor = SkorMakanan::where('makanan_id', $rekomendasi->makanan_id)
|
||||
->where('kriteria_id', $kriteria->id)
|
||||
->where('waktu_makan_id', $waktuMakanId)
|
||||
->where('komponen_id', $komponen->id)
|
||||
->first();
|
||||
$detailSkor[$rekomendasi->makanan_id][$kriteria->nama] = $skor ? $skor->nilai : 0;
|
||||
}
|
||||
|
||||
// Ambil data perbandingan untuk makanan ini
|
||||
$perbandinganData = PerbandinganAlternatif::where('waktu_makan_id', $waktuMakanId)
|
||||
->where('komponen_id', $komponen->id)
|
||||
->where(function($query) use ($rekomendasi) {
|
||||
$query->where('alternatif_id_1', $rekomendasi->makanan_id)
|
||||
->orWhere('alternatif_id_2', $rekomendasi->makanan_id);
|
||||
})
|
||||
->get();
|
||||
|
||||
if ($perbandinganData->isNotEmpty()) {
|
||||
$detailSkor[$rekomendasi->makanan_id]['perbandingan'] = $perbandinganData;
|
||||
}
|
||||
}
|
||||
|
||||
$hasilPerKomponen[$komponen->id] = [
|
||||
'komponen' => $komponen,
|
||||
'rekomendasis' => $rekomendasis,
|
||||
'detailSkor' => $detailSkor
|
||||
];
|
||||
}
|
||||
|
||||
// Ambil CR terbaru dari ConsistencyRatioCriteria untuk waktu makan ini
|
||||
$latestCR = ConsistencyRatioCriteria::where('waktu_makan_id', $waktuMakanId)
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
return view('admin.rekomendasi', compact(
|
||||
'waktuMakan',
|
||||
'hasilRekomendasi',
|
||||
'kriterias',
|
||||
'bobotKriteria',
|
||||
'hasilPerKomponen',
|
||||
'latestCR'
|
||||
));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('rekomendasi.index')
|
||||
->with('error', 'Terjadi kesalahan saat menampilkan detail: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,355 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Makanan;
|
||||
use App\Models\Komponen;
|
||||
use App\Models\WaktuMakan;
|
||||
use App\Models\MakananKomponenWaktu;
|
||||
use App\Models\PerbandinganAlternatif;
|
||||
use App\Models\SkorMakanan;
|
||||
use App\Models\Kriteria;
|
||||
use App\Models\BobotKriteria;
|
||||
use App\Models\ConsistencyRatioAlternatif;
|
||||
use App\Models\SimulasiAhp;
|
||||
use App\Models\SimulasiAhpPairwise;
|
||||
use App\Models\SimulasiAhpNormalisasi;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class SimulasiController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$waktuMakans = WaktuMakan::all();
|
||||
$komponens = Komponen::all();
|
||||
$kriterias = Kriteria::all();
|
||||
$makanans = null;
|
||||
|
||||
$waktuMakanId = $request->waktu_makan_id;
|
||||
$komponenId = $request->komponen_id;
|
||||
|
||||
if ($waktuMakanId && $komponenId) {
|
||||
$query = MakananKomponenWaktu::with('makanan')
|
||||
->where('waktu_makan_id', $waktuMakanId);
|
||||
$makananKomponenWaktu = $query->where('status', true)->get();
|
||||
if ($makananKomponenWaktu->isEmpty()) {
|
||||
$makananKomponenWaktu = MakananKomponenWaktu::with('makanan')
|
||||
->where('waktu_makan_id', $waktuMakanId)
|
||||
->get();
|
||||
}
|
||||
$filtered = $makananKomponenWaktu->where('komponen_id', $komponenId);
|
||||
$makanans = $filtered->map(function($item) {
|
||||
$makanan = $item->makanan;
|
||||
$makanan->komponen_id = $item->komponen_id;
|
||||
return $makanan;
|
||||
})->values();
|
||||
}
|
||||
|
||||
return view('user.simulasi.index', compact('waktuMakans', 'komponens', 'kriterias', 'makanans'));
|
||||
}
|
||||
|
||||
public function getMakananByKomponen($komponen, $waktuMakan)
|
||||
{
|
||||
// Ambil semua data makanan_komponen_waktu untuk waktu makan tertentu
|
||||
$query = MakananKomponenWaktu::with('makanan')
|
||||
->where('waktu_makan_id', $waktuMakan);
|
||||
|
||||
// Coba filter status true dulu
|
||||
$makananKomponenWaktu = $query->where('status', true)->get();
|
||||
|
||||
// Jika kosong, ambil semua tanpa filter status
|
||||
if ($makananKomponenWaktu->isEmpty()) {
|
||||
$makananKomponenWaktu = MakananKomponenWaktu::with('makanan')
|
||||
->where('waktu_makan_id', $waktuMakan)
|
||||
->get();
|
||||
}
|
||||
|
||||
// Filter berdasarkan komponen
|
||||
$filtered = $makananKomponenWaktu->where('komponen_id', $komponen);
|
||||
|
||||
// Map ke array makanan, tambahkan komponen_id
|
||||
$makanans = $filtered->map(function($item) {
|
||||
$makanan = $item->makanan;
|
||||
$makanan->komponen_id = $item->komponen_id;
|
||||
return $makanan;
|
||||
})->values();
|
||||
|
||||
return response()->json($makanans);
|
||||
}
|
||||
|
||||
public function prosesSimulasi(Request $request)
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
// Validasi minimal 4 makanan
|
||||
$selectedMakanans = collect($request->makanan_ids)->filter()->values();
|
||||
if ($selectedMakanans->count() < 4) {
|
||||
return redirect()->back()->with('error', 'Pilih minimal 4 makanan untuk simulasi');
|
||||
}
|
||||
|
||||
$waktuMakanId = $request->waktu_makan_id;
|
||||
$komponenId = $request->komponen_id;
|
||||
$kriterias = Kriteria::all();
|
||||
|
||||
// Ambil bobot kriteria berdasarkan waktu makan
|
||||
$bobotKriteria = [];
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$bobot = BobotKriteria::where('kriteria_id', $kriteria->id)
|
||||
->where('waktu_makan_id', $waktuMakanId)
|
||||
->first();
|
||||
$bobotKriteria[$kriteria->id] = $bobot ? $bobot->bobot : 0;
|
||||
}
|
||||
|
||||
// Normalisasi bobot kriteria
|
||||
$totalBobot = array_sum($bobotKriteria);
|
||||
if ($totalBobot > 0) {
|
||||
foreach ($bobotKriteria as $key => $value) {
|
||||
$bobotKriteria[$key] = $value / $totalBobot;
|
||||
}
|
||||
}
|
||||
|
||||
// Buat record simulasi baru
|
||||
$simulasi = SimulasiAhp::create([
|
||||
'user_id' => Auth::id(),
|
||||
'judul' => 'Simulasi ' . WaktuMakan::find($waktuMakanId)->nama . ' - ' . Komponen::find($komponenId)->nama,
|
||||
'pilihan_makanan_ids' => $selectedMakanans->toArray(),
|
||||
'hasil_rangking' => []
|
||||
]);
|
||||
|
||||
// Proses perbandingan berpasangan untuk setiap kriteria
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$matriksPerbandingan = [];
|
||||
$jumlahKolom = [];
|
||||
|
||||
// Inisialisasi matriks
|
||||
foreach ($selectedMakanans as $makananId) {
|
||||
$jumlahKolom[$makananId] = 0;
|
||||
foreach ($selectedMakanans as $makananId2) {
|
||||
$matriksPerbandingan[$makananId][$makananId2] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Hitung nilai perbandingan berdasarkan kriteria
|
||||
foreach ($selectedMakanans as $makananId1) {
|
||||
$makanan1 = Makanan::find($makananId1);
|
||||
foreach ($selectedMakanans as $makananId2) {
|
||||
if ($makananId1 != $makananId2) {
|
||||
$makanan2 = Makanan::find($makananId2);
|
||||
$nilai1 = $this->getNilaiKriteria($makanan1, $kriteria);
|
||||
$nilai2 = $this->getNilaiKriteria($makanan2, $kriteria);
|
||||
|
||||
if ($nilai1 > 0 && $nilai2 > 0) {
|
||||
// Terapkan nilai invers untuk kriteria cost (lemak dan natrium)
|
||||
if (in_array(strtolower($kriteria->nama), ['lemak', 'natrium'])) {
|
||||
$ratio = $nilai2 / $nilai1; // Invers untuk cost
|
||||
} else {
|
||||
$ratio = $nilai1 / $nilai2;
|
||||
}
|
||||
|
||||
if ($ratio > 9) $ratio = 9;
|
||||
if ($ratio < 1/9) $ratio = 1/9;
|
||||
|
||||
$matriksPerbandingan[$makananId1][$makananId2] = $ratio;
|
||||
$matriksPerbandingan[$makananId2][$makananId1] = 1 / $ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simpan matriks perbandingan berpasangan
|
||||
foreach ($selectedMakanans as $makananId1) {
|
||||
foreach ($selectedMakanans as $makananId2) {
|
||||
if ($makananId1 != $makananId2) {
|
||||
SimulasiAhpPairwise::create([
|
||||
'simulasi_ahp_id' => $simulasi->id,
|
||||
'makanan_1_id' => $makananId1,
|
||||
'makanan_2_id' => $makananId2,
|
||||
'nilai' => $matriksPerbandingan[$makananId1][$makananId2]
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hitung jumlah kolom
|
||||
foreach ($selectedMakanans as $makananId2) {
|
||||
foreach ($selectedMakanans as $makananId1) {
|
||||
$jumlahKolom[$makananId2] += $matriksPerbandingan[$makananId1][$makananId2];
|
||||
}
|
||||
}
|
||||
|
||||
// Normalisasi matriks dan hitung priority vector
|
||||
$matriksNormal = [];
|
||||
$priorityVector = [];
|
||||
foreach ($selectedMakanans as $makananId1) {
|
||||
$jumlahBaris = 0;
|
||||
foreach ($selectedMakanans as $makananId2) {
|
||||
if ($jumlahKolom[$makananId2] != 0) {
|
||||
$nilai = $matriksPerbandingan[$makananId1][$makananId2] / $jumlahKolom[$makananId2];
|
||||
$matriksNormal[$makananId1][$makananId2] = $nilai;
|
||||
$jumlahBaris += $nilai;
|
||||
}
|
||||
}
|
||||
$priorityVector[$makananId1] = $jumlahBaris / count($selectedMakanans);
|
||||
}
|
||||
|
||||
// Simpan hasil normalisasi
|
||||
foreach ($selectedMakanans as $makananId) {
|
||||
SimulasiAhpNormalisasi::create([
|
||||
'simulasi_ahp_id' => $simulasi->id,
|
||||
'makanan_id' => $makananId,
|
||||
'nilai_normalisasi' => $priorityVector[$makananId],
|
||||
'bobot_akhir' => $priorityVector[$makananId] * $bobotKriteria[$kriteria->id]
|
||||
]);
|
||||
}
|
||||
|
||||
// Hitung Consistency Ratio
|
||||
$n = count($selectedMakanans);
|
||||
if ($n > 1) {
|
||||
$lambdaMax = 0;
|
||||
foreach ($selectedMakanans as $makananId1) {
|
||||
$sum = 0;
|
||||
foreach ($selectedMakanans as $makananId2) {
|
||||
$sum += $matriksPerbandingan[$makananId1][$makananId2] * $priorityVector[$makananId2];
|
||||
}
|
||||
$lambdaMax += $sum / $priorityVector[$makananId1];
|
||||
}
|
||||
$lambdaMax /= $n;
|
||||
|
||||
$CI = ($lambdaMax - $n) / ($n - 1);
|
||||
$RI = [
|
||||
1 => 0.00, 2 => 0.00, 3 => 0.58, 4 => 0.90, 5 => 1.12,
|
||||
6 => 1.24, 7 => 1.32, 8 => 1.41, 9 => 1.45, 10 => 1.49
|
||||
];
|
||||
$CR = $CI / ($RI[$n] ?? end($RI));
|
||||
|
||||
// Simpan CR ke database
|
||||
ConsistencyRatioAlternatif::updateOrCreate(
|
||||
[
|
||||
'kriteria_id' => $kriteria->id,
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'komponen_id' => $komponenId
|
||||
],
|
||||
[
|
||||
'ci' => $CI,
|
||||
'cr' => $CR,
|
||||
'is_consistent' => $CR <= 0.1
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Simpan skor ke database
|
||||
foreach ($selectedMakanans as $makananId) {
|
||||
SkorMakanan::updateOrCreate(
|
||||
[
|
||||
'makanan_id' => $makananId,
|
||||
'kriteria_id' => $kriteria->id,
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'komponen_id' => $komponenId
|
||||
],
|
||||
['nilai' => $priorityVector[$makananId]]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Hitung skor akhir
|
||||
$hasilRekomendasi = [];
|
||||
foreach ($selectedMakanans as $makananId) {
|
||||
$skorAkhir = 0;
|
||||
foreach ($kriterias as $kriteria) {
|
||||
$skor = SkorMakanan::where([
|
||||
'makanan_id' => $makananId,
|
||||
'kriteria_id' => $kriteria->id,
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'komponen_id' => $komponenId
|
||||
])->first();
|
||||
|
||||
if ($skor) {
|
||||
$skorAkhir += $skor->nilai * $bobotKriteria[$kriteria->id];
|
||||
}
|
||||
}
|
||||
$hasilRekomendasi[$makananId] = $skorAkhir;
|
||||
}
|
||||
|
||||
// Urutkan hasil berdasarkan skor
|
||||
arsort($hasilRekomendasi);
|
||||
|
||||
// Update hasil ranking di tabel simulasi
|
||||
$simulasi->update(['hasil_rangking' => $hasilRekomendasi]);
|
||||
|
||||
// Simpan hasil ke session
|
||||
Session::put('hasil_simulasi', [
|
||||
'simulasi_id' => $simulasi->id,
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'komponen_id' => $komponenId,
|
||||
'hasil' => $hasilRekomendasi,
|
||||
'makanan_ids' => $selectedMakanans->toArray(),
|
||||
'bobot_kriteria' => $bobotKriteria
|
||||
]);
|
||||
|
||||
DB::commit();
|
||||
return redirect()->route('user.simulasi.hasil');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
return redirect()->back()->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function hasil()
|
||||
{
|
||||
$hasilSimulasi = Session::get('hasil_simulasi');
|
||||
if (!$hasilSimulasi) {
|
||||
return redirect()->route('user.simulasi.index');
|
||||
}
|
||||
|
||||
$waktuMakan = WaktuMakan::find($hasilSimulasi['waktu_makan_id']);
|
||||
$komponen = Komponen::find($hasilSimulasi['komponen_id']);
|
||||
$makanans = Makanan::whereIn('id', $hasilSimulasi['makanan_ids'])->get();
|
||||
$kriterias = Kriteria::all();
|
||||
$crCriteria = \App\Models\ConsistencyRatioCriteria::where('waktu_makan_id', $waktuMakan->id ?? $hasilSimulasi['waktu_makan_id'])->orderByDesc('created_at')->first();
|
||||
|
||||
return view('user.simulasi.hasil', compact('hasilSimulasi', 'waktuMakan', 'komponen', 'makanans', 'kriterias', 'crCriteria'));
|
||||
}
|
||||
|
||||
public function history()
|
||||
{
|
||||
$simulasis = SimulasiAhp::where('user_id', Auth::id())
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate(10);
|
||||
|
||||
return view('user.simulasi.history', compact('simulasis'));
|
||||
}
|
||||
|
||||
public function detailHistory($id)
|
||||
{
|
||||
$simulasi = SimulasiAhp::where('user_id', Auth::id())
|
||||
->where('id', $id)
|
||||
->firstOrFail();
|
||||
|
||||
$makanans = Makanan::whereIn('id', $simulasi->pilihan_makanan_ids)->get();
|
||||
$pairwise = SimulasiAhpPairwise::where('simulasi_ahp_id', $id)->get();
|
||||
$normalisasi = SimulasiAhpNormalisasi::where('simulasi_ahp_id', $id)->get();
|
||||
|
||||
return view('user.simulasi.detail-history', compact('simulasi', 'makanans', 'pairwise', 'normalisasi'));
|
||||
}
|
||||
|
||||
private function getNilaiKriteria($makanan, $kriteria)
|
||||
{
|
||||
switch (strtolower($kriteria->nama)) {
|
||||
case 'energi':
|
||||
return $makanan->energi;
|
||||
case 'lemak':
|
||||
return $makanan->lemak;
|
||||
case 'karbohidrat':
|
||||
return $makanan->karbohidrat;
|
||||
case 'natrium':
|
||||
return $makanan->natrium;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Carbon\Carbon;
|
||||
use DateTimeZone;
|
||||
|
||||
class TimezoneController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$currentTimezone = config('app.timezone');
|
||||
$currentTime = Carbon::now();
|
||||
$availableTimezones = $this->getAvailableTimezones();
|
||||
|
||||
return view('user.timezone', compact('currentTimezone', 'currentTime', 'availableTimezones'));
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
$timezone = $request->input('timezone');
|
||||
|
||||
// Validate timezone
|
||||
if (!in_array($timezone, DateTimeZone::listIdentifiers())) {
|
||||
return redirect()->back()->with('error', 'Timezone tidak valid!');
|
||||
}
|
||||
|
||||
// Update .env file
|
||||
$this->updateEnvFile('APP_TIMEZONE', $timezone);
|
||||
|
||||
// Clear config cache
|
||||
\Artisan::call('config:clear');
|
||||
|
||||
return redirect()->back()->with('success', 'Timezone berhasil diperbarui ke ' . $timezone);
|
||||
}
|
||||
|
||||
public function getCurrentTime()
|
||||
{
|
||||
$timezone = request('timezone', config('app.timezone'));
|
||||
$currentTime = Carbon::now($timezone);
|
||||
|
||||
return response()->json([
|
||||
'timezone' => $timezone,
|
||||
'current_time' => $currentTime->format('Y-m-d H:i:s'),
|
||||
'formatted_time' => $currentTime->format('d M Y H:i:s'),
|
||||
'day_name' => $currentTime->format('l'),
|
||||
'time_only' => $currentTime->format('H:i:s'),
|
||||
'date_only' => $currentTime->format('d M Y')
|
||||
]);
|
||||
}
|
||||
|
||||
private function getAvailableTimezones()
|
||||
{
|
||||
$timezones = [
|
||||
'Asia/Jakarta' => 'WIB (UTC+7)',
|
||||
'Asia/Makassar' => 'WITA (UTC+8)',
|
||||
'Asia/Jayapura' => 'WIT (UTC+9)',
|
||||
'UTC' => 'UTC (UTC+0)',
|
||||
'Asia/Singapore' => 'Singapore (UTC+8)',
|
||||
'Asia/Kuala_Lumpur' => 'Malaysia (UTC+8)',
|
||||
'Asia/Bangkok' => 'Thailand (UTC+7)',
|
||||
'Asia/Manila' => 'Philippines (UTC+8)',
|
||||
'Asia/Ho_Chi_Minh' => 'Vietnam (UTC+7)',
|
||||
'Asia/Seoul' => 'Korea (UTC+9)',
|
||||
'Asia/Tokyo' => 'Japan (UTC+9)',
|
||||
'Asia/Shanghai' => 'China (UTC+8)',
|
||||
'America/New_York' => 'New York (UTC-5)',
|
||||
'America/Los_Angeles' => 'Los Angeles (UTC-8)',
|
||||
'Europe/London' => 'London (UTC+0)',
|
||||
'Europe/Paris' => 'Paris (UTC+1)',
|
||||
'Europe/Berlin' => 'Berlin (UTC+1)',
|
||||
'Australia/Sydney' => 'Sydney (UTC+10)',
|
||||
'Australia/Perth' => 'Perth (UTC+8)',
|
||||
];
|
||||
|
||||
return $timezones;
|
||||
}
|
||||
|
||||
private function updateEnvFile($key, $value)
|
||||
{
|
||||
$path = base_path('.env');
|
||||
|
||||
if (file_exists($path)) {
|
||||
$content = file_get_contents($path);
|
||||
|
||||
// Check if key exists
|
||||
if (strpos($content, $key . '=') !== false) {
|
||||
// Update existing key
|
||||
$content = preg_replace(
|
||||
'/^' . $key . '=.*/m',
|
||||
$key . '=' . $value,
|
||||
$content
|
||||
);
|
||||
} else {
|
||||
// Add new key
|
||||
$content .= "\n" . $key . '=' . $value;
|
||||
}
|
||||
|
||||
file_put_contents($path, $content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,10 +16,12 @@ class UserController extends Controller
|
|||
{
|
||||
//
|
||||
public function userdash(){
|
||||
|
||||
$totalData = DB::table('makanans')->count(); // atau model: DataMakanan::count()
|
||||
$totalData = DB::table('makanans')->count();
|
||||
$totalKriteria = DB::table('kriterias')->count();
|
||||
$totalUser = DB::table('users')->count();
|
||||
$totalUser = DB::table('users')
|
||||
->join('roles', 'users.role_id', '=', 'roles.id')
|
||||
->where('roles.name', '=', 'user')
|
||||
->count();
|
||||
return view('user.userdash', compact('totalData', 'totalKriteria', 'totalUser'));
|
||||
}
|
||||
public function userabout(){
|
||||
|
|
@ -90,22 +92,49 @@ public function userdata(Request $request)
|
|||
|
||||
public function userresult()
|
||||
{
|
||||
$rekomendasi = \App\Models\RekomendasiAhli::with('komponen', 'waktuMakan')
|
||||
// Ambil rekomendasi ahli dan group by hari
|
||||
$rekomendasiAhli = \App\Models\RekomendasiAhli::with(['makanan', 'komponen', 'waktuMakan'])
|
||||
->get()
|
||||
->groupBy([
|
||||
fn($item) => $item->waktu_makan_id,
|
||||
fn($item) => strtolower($item->komponen->nama),
|
||||
'hari',
|
||||
'waktuMakan.nama',
|
||||
'komponen.nama'
|
||||
]);
|
||||
|
||||
$waktuMakans = \App\Models\WaktuMakan::all()->keyBy('id');
|
||||
// Ambil data alternatif AHP untuk setiap kombinasi hari, waktu makan, dan komponen
|
||||
$alternatifAHPData = [];
|
||||
|
||||
foreach ($rekomendasiAhli as $hari => $waktuMakans) {
|
||||
$alternatifAHPData[$hari] = [];
|
||||
|
||||
foreach ($waktuMakans as $waktuMakan => $komponens) {
|
||||
$alternatifAHPData[$hari][$waktuMakan] = [];
|
||||
|
||||
foreach ($komponens as $komponen => $makananCollection) {
|
||||
// Karena groupBy, $makananCollection adalah collection, ambil item pertama
|
||||
$makananItem = $makananCollection->first();
|
||||
|
||||
if ($makananItem) {
|
||||
// Ambil waktu makan ID dan komponen ID dari item pertama
|
||||
$waktuMakanId = $makananItem->waktu_makan_id;
|
||||
$komponenId = $makananItem->komponen_id;
|
||||
|
||||
// Ambil alternatif dari tabel rekomendasis
|
||||
$alternatifGrouped = \App\Models\Rekomendasi::with('makanan')
|
||||
// Ambil alternatif AHP untuk kombinasi ini
|
||||
$alternatifAHP = \App\Models\Rekomendasi::with('makanan')
|
||||
->where('waktu_makan_id', $waktuMakanId)
|
||||
->where('komponen_id', $komponenId)
|
||||
->where('nilai_akhir', '>', 0)
|
||||
->get()
|
||||
->groupBy(fn($item) => $item->waktu_makan_id . '-' . $item->komponen_id);
|
||||
->orderBy('nilai_akhir', 'desc')
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
$alternatifAHPData[$hari][$waktuMakan][$komponen] = $alternatifAHP;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return view('user.userresult', compact('rekomendasi', 'waktuMakans', 'alternatifGrouped'));
|
||||
return view('user.userresult', compact('rekomendasiAhli', 'alternatifAHPData'));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\ValidasiRekomendasiService;
|
||||
use App\Models\ValidasiRekomendasi;
|
||||
use App\Models\WaktuMakan;
|
||||
use App\Models\Makanan;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ValidasiRekomendasiController extends Controller
|
||||
{
|
||||
protected $validasiService;
|
||||
|
||||
public function __construct(ValidasiRekomendasiService $validasiService)
|
||||
{
|
||||
$this->validasiService = $validasiService;
|
||||
$this->middleware('cekrole:admin');
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = ValidasiRekomendasi::with(['makananPakar', 'waktuMakan', 'komponen']);
|
||||
|
||||
// Filter berdasarkan hari
|
||||
if ($request->has('hari')) {
|
||||
$query->where('hari', $request->hari);
|
||||
}
|
||||
|
||||
// Filter berdasarkan waktu makan
|
||||
if ($request->has('waktu_makan_id')) {
|
||||
$query->where('waktu_makan_id', $request->waktu_makan_id);
|
||||
}
|
||||
|
||||
$validasi = $query->get();
|
||||
$waktuMakans = WaktuMakan::all();
|
||||
|
||||
// Hitung statistik
|
||||
$total = $validasi->count();
|
||||
$lebihBaik = $validasi->where('status_kecocokan', 'lebih_baik')->count();
|
||||
$setara = $validasi->where('status_kecocokan', 'setara')->count();
|
||||
$lebihBuruk = $validasi->where('status_kecocokan', 'lebih_buruk')->count();
|
||||
$persentaseCocok = $total > 0
|
||||
? (($lebihBaik + $setara) / $total) * 100
|
||||
: 0;
|
||||
|
||||
|
||||
return view('admin.validasi.index', compact(
|
||||
'validasi',
|
||||
'waktuMakans',
|
||||
'total',
|
||||
'lebihBaik',
|
||||
'setara',
|
||||
'lebihBuruk',
|
||||
'persentaseCocok'
|
||||
));
|
||||
}
|
||||
|
||||
public function bandingkan()
|
||||
{
|
||||
try {
|
||||
$hasil = $this->validasiService->bandingkanRekomendasi();
|
||||
|
||||
return redirect()
|
||||
->route('validasi.index')
|
||||
->with('success', 'Validasi berhasil dilakukan. Persentase kecocokan: ' .
|
||||
number_format($hasil['statistik']['persentase_cocok'], 2) . '%');
|
||||
} catch (\Exception $e) {
|
||||
return redirect()
|
||||
->route('validasi.index')
|
||||
->with('error', 'Terjadi kesalahan saat melakukan validasi: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function exportExcel()
|
||||
{
|
||||
$validasi = ValidasiRekomendasi::with(['makananPakar', 'waktuMakan', 'komponen'])
|
||||
->get()
|
||||
->map(function($item) {
|
||||
$makananSistemNames = collect($item->makanan_sistem_ids)
|
||||
->map(function($id) {
|
||||
$makanan = Makanan::find($id);
|
||||
return $makanan ? $makanan->nama : 'Makanan tidak ditemukan';
|
||||
})
|
||||
->implode(', ');
|
||||
|
||||
return [
|
||||
'Hari' => $item->hari,
|
||||
'Waktu Makan' => $item->waktuMakan->nama,
|
||||
'Komponen' => $item->komponen->nama,
|
||||
'Makanan Pakar' => $item->makananPakar->nama,
|
||||
'Makanan Sistem' => $makananSistemNames,
|
||||
'Status Kecocokan' => $this->formatStatusKecocokan($item->status_kecocokan)
|
||||
];
|
||||
});
|
||||
|
||||
// TODO: Implementasi export Excel menggunakan maatwebsite/excel
|
||||
// Untuk sementara return view dengan data
|
||||
return view('admin.validasi.export', compact('validasi'));
|
||||
}
|
||||
|
||||
private function formatStatusKecocokan($status)
|
||||
{
|
||||
return match($status) {
|
||||
'lebih_baik' => '✅ Lebih Baik',
|
||||
'setara' => '⚠️ Setara',
|
||||
'lebih_buruk' => '❌ Lebih Buruk',
|
||||
default => $status
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -64,5 +64,6 @@ class Kernel extends HttpKernel
|
|||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
'cekrole' => \App\Http\Middleware\CekRole::class,
|
||||
'role' => \App\Http\Middleware\Role::class,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class Role
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, string $role): Response
|
||||
{
|
||||
if ($request->user()->role !== $role) {
|
||||
abort(403, 'Unauthorized action.');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,10 +9,15 @@ class BobotKriteria extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
public $timestamps = false;
|
||||
protected $fillable = ['kriteria_id', 'bobot'];
|
||||
protected $fillable = ['kriteria_id', 'bobot', 'waktu_makan_id'];
|
||||
|
||||
public function kriteria()
|
||||
{
|
||||
return $this->belongsTo(Kriteria::class);
|
||||
}
|
||||
|
||||
public function waktuMakan()
|
||||
{
|
||||
return $this->belongsTo(WaktuMakan::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ConsistencyRatio extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'kriteria_id',
|
||||
'waktu_makan_id',
|
||||
'komponen_id',
|
||||
'user_id',
|
||||
'nilai_cr',
|
||||
'tanggal_perhitungan'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'tanggal_perhitungan' => 'datetime',
|
||||
'nilai_cr' => 'float'
|
||||
];
|
||||
|
||||
public function kriteria()
|
||||
{
|
||||
return $this->belongsTo(Kriteria::class);
|
||||
}
|
||||
|
||||
public function waktuMakan()
|
||||
{
|
||||
return $this->belongsTo(WaktuMakan::class);
|
||||
}
|
||||
|
||||
public function komponen()
|
||||
{
|
||||
return $this->belongsTo(Komponen::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ConsistencyRatioAlternatif extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'kriteria_id',
|
||||
'waktu_makan_id',
|
||||
'komponen_id',
|
||||
'ci',
|
||||
'cr',
|
||||
'is_consistent'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'ci' => 'float',
|
||||
'cr' => 'float',
|
||||
'is_consistent' => 'boolean'
|
||||
];
|
||||
|
||||
public function kriteria()
|
||||
{
|
||||
return $this->belongsTo(Kriteria::class);
|
||||
}
|
||||
|
||||
public function waktuMakan()
|
||||
{
|
||||
return $this->belongsTo(WaktuMakan::class);
|
||||
}
|
||||
|
||||
public function komponen()
|
||||
{
|
||||
return $this->belongsTo(Komponen::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ConsistencyRatioCriteria extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'ci',
|
||||
'cr',
|
||||
'is_consistent',
|
||||
'waktu_makan_id'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'ci' => 'float',
|
||||
'cr' => 'float',
|
||||
'is_consistent' => 'boolean',
|
||||
'waktu_makan_id' => 'integer'
|
||||
];
|
||||
|
||||
public function waktuMakan()
|
||||
{
|
||||
return $this->belongsTo(WaktuMakan::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,16 @@ class Komponen extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['nama'];
|
||||
protected $table = 'komponens';
|
||||
|
||||
protected $fillable = [
|
||||
'nama'
|
||||
];
|
||||
|
||||
public function rekomendasiAhli()
|
||||
{
|
||||
return $this->hasMany(RekomendasiAhli::class);
|
||||
}
|
||||
|
||||
public function makananKomponenWaktu()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ class Makanan extends Model
|
|||
'lemak',
|
||||
'natrium',
|
||||
'energi',
|
||||
'karbohidrat'
|
||||
'karbohidrat',
|
||||
'komponen_id'
|
||||
];
|
||||
|
||||
protected $guarded = [];
|
||||
|
|
@ -63,5 +64,8 @@ public function waktuMakans()
|
|||
->withTimestamps();
|
||||
}
|
||||
|
||||
|
||||
public function komponen()
|
||||
{
|
||||
return $this->belongsTo(Komponen::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,12 @@ class MakananKomponenWaktu extends Model
|
|||
|
||||
protected $table = 'makanan_komponen_waktu';
|
||||
|
||||
protected $fillable = ['makanan_id', 'komponen_id', 'waktu_makan_id'];
|
||||
protected $fillable = [
|
||||
'makanan_id',
|
||||
'komponen_id',
|
||||
'waktu_makan_id',
|
||||
'status'
|
||||
];
|
||||
|
||||
public function makanan()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,6 +13,33 @@ class PerbandinganAlternatif extends Model
|
|||
'kriteria_id',
|
||||
'alternatif_id_1',
|
||||
'alternatif_id_2',
|
||||
'nilai',
|
||||
'waktu_makan_id',
|
||||
'komponen_id',
|
||||
'nilai'
|
||||
];
|
||||
|
||||
public function kriteria()
|
||||
{
|
||||
return $this->belongsTo(Kriteria::class);
|
||||
}
|
||||
|
||||
public function alternatif1()
|
||||
{
|
||||
return $this->belongsTo(Makanan::class, 'alternatif_id_1');
|
||||
}
|
||||
|
||||
public function alternatif2()
|
||||
{
|
||||
return $this->belongsTo(Makanan::class, 'alternatif_id_2');
|
||||
}
|
||||
|
||||
public function waktuMakan()
|
||||
{
|
||||
return $this->belongsTo(WaktuMakan::class);
|
||||
}
|
||||
|
||||
public function komponen()
|
||||
{
|
||||
return $this->belongsTo(Komponen::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class PerbandinganKriteria extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
public $timestamps = false;
|
||||
protected $fillable = ['kriteria_id_1', 'kriteria_id_2', 'nilai'];
|
||||
protected $fillable = ['kriteria_id_1', 'kriteria_id_2', 'nilai', 'waktu_makan_id'];
|
||||
|
||||
public function kriteria1()
|
||||
{
|
||||
|
|
@ -20,4 +20,8 @@ public function kriteria2()
|
|||
{
|
||||
return $this->belongsTo(Kriteria::class, 'kriteria_id_2');
|
||||
}
|
||||
public function waktuMakan()
|
||||
{
|
||||
return $this->belongsTo(WaktuMakan::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,26 +13,25 @@ class RekomendasiAhli extends Model
|
|||
|
||||
protected $fillable = [
|
||||
'makanan_id',
|
||||
'waktu_makan_id',
|
||||
'komponen_id',
|
||||
'catatan',
|
||||
'waktu_makan_id',
|
||||
'hari'
|
||||
];
|
||||
|
||||
// Relasi ke makanan
|
||||
public function makanan()
|
||||
{
|
||||
return $this->belongsTo(Makanan::class);
|
||||
return $this->belongsTo(Makanan::class, 'makanan_id');
|
||||
}
|
||||
|
||||
// Relasi ke waktu makan
|
||||
public function waktuMakan()
|
||||
{
|
||||
return $this->belongsTo(WaktuMakan::class);
|
||||
return $this->belongsTo(WaktuMakan::class, 'waktu_makan_id');
|
||||
}
|
||||
|
||||
// Relasi ke komponen
|
||||
public function komponen()
|
||||
{
|
||||
return $this->belongsTo(Komponen::class);
|
||||
return $this->belongsTo(Komponen::class, 'komponen_id');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SimulasiAhp extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'simulasi_ahp';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'judul',
|
||||
'pilihan_makanan_ids',
|
||||
'hasil_rangking'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'pilihan_makanan_ids' => 'array',
|
||||
'hasil_rangking' => 'array',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function pairwise()
|
||||
{
|
||||
return $this->hasMany(SimulasiAhpPairwise::class);
|
||||
}
|
||||
|
||||
public function normalisasi()
|
||||
{
|
||||
return $this->hasMany(SimulasiAhpNormalisasi::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SimulasiAhpNormalisasi extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'simulasi_ahp_normalisasi';
|
||||
|
||||
protected $fillable = [
|
||||
'simulasi_ahp_id',
|
||||
'makanan_id',
|
||||
'nilai_normalisasi',
|
||||
'bobot_akhir'
|
||||
];
|
||||
|
||||
public function simulasi()
|
||||
{
|
||||
return $this->belongsTo(SimulasiAhp::class, 'simulasi_ahp_id');
|
||||
}
|
||||
|
||||
public function makanan()
|
||||
{
|
||||
return $this->belongsTo(Makanan::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SimulasiAhpPairwise extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'simulasi_ahp_pairwise';
|
||||
|
||||
protected $fillable = [
|
||||
'simulasi_ahp_id',
|
||||
'makanan_1_id',
|
||||
'makanan_2_id',
|
||||
'nilai'
|
||||
];
|
||||
|
||||
public function simulasi()
|
||||
{
|
||||
return $this->belongsTo(SimulasiAhp::class, 'simulasi_ahp_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,17 @@ class SkorMakanan extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
public $timestamps = false;
|
||||
protected $fillable = ['makanan_id', 'kriteria_id', 'nilai', 'tanggal_rekomendasi'];
|
||||
protected $fillable = [
|
||||
'makanan_id',
|
||||
'kriteria_id',
|
||||
'waktu_makan_id',
|
||||
'komponen_id',
|
||||
'nilai'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'nilai' => 'float'
|
||||
];
|
||||
|
||||
public function makanan()
|
||||
{
|
||||
|
|
@ -20,4 +30,14 @@ public function kriteria()
|
|||
{
|
||||
return $this->belongsTo(Kriteria::class);
|
||||
}
|
||||
|
||||
public function waktuMakan()
|
||||
{
|
||||
return $this->belongsTo(WaktuMakan::class);
|
||||
}
|
||||
|
||||
public function komponen()
|
||||
{
|
||||
return $this->belongsTo(Komponen::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,4 +49,36 @@ public function role()
|
|||
{
|
||||
return $this->belongsTo(Role::class); // Defines the relationship to the Role model
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alert setting associated with the user.
|
||||
*/
|
||||
public function alertSetting()
|
||||
{
|
||||
return $this->hasOne(UserAlertSetting::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notifications for the user.
|
||||
*/
|
||||
public function notifications()
|
||||
{
|
||||
return $this->hasMany(UserNotification::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unread notifications for the user.
|
||||
*/
|
||||
public function unreadNotifications()
|
||||
{
|
||||
return $this->notifications()->unread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get read notifications for the user.
|
||||
*/
|
||||
public function readNotifications()
|
||||
{
|
||||
return $this->notifications()->read();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserAlertSetting extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'is_enabled',
|
||||
'alert_time',
|
||||
'enabled_waktu_makan_ids'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_enabled' => 'boolean',
|
||||
'alert_time' => 'datetime',
|
||||
'enabled_waktu_makan_ids' => 'array'
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function getEnabledWaktuMakanAttribute()
|
||||
{
|
||||
if (!$this->enabled_waktu_makan_ids) {
|
||||
return WaktuMakan::where('is_active', true)->get();
|
||||
}
|
||||
return WaktuMakan::whereIn('id', $this->enabled_waktu_makan_ids)
|
||||
->where('is_active', true)
|
||||
->get();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserNotification extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'waktu_makan_id',
|
||||
'title',
|
||||
'message',
|
||||
'type',
|
||||
'is_read',
|
||||
'read_at',
|
||||
'data'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_read' => 'boolean',
|
||||
'read_at' => 'datetime',
|
||||
'data' => 'array'
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function waktuMakan()
|
||||
{
|
||||
return $this->belongsTo(WaktuMakan::class);
|
||||
}
|
||||
|
||||
public function scopeUnread($query)
|
||||
{
|
||||
return $query->where('is_read', false);
|
||||
}
|
||||
|
||||
public function scopeRead($query)
|
||||
{
|
||||
return $query->where('is_read', true);
|
||||
}
|
||||
|
||||
public function scopeByType($query, $type)
|
||||
{
|
||||
return $query->where('type', $type);
|
||||
}
|
||||
|
||||
public function markAsRead()
|
||||
{
|
||||
$this->update([
|
||||
'is_read' => true,
|
||||
'read_at' => now()
|
||||
]);
|
||||
}
|
||||
|
||||
public function markAsUnread()
|
||||
{
|
||||
$this->update([
|
||||
'is_read' => false,
|
||||
'read_at' => null
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ValidasiRekomendasi extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'validasi_rekomendasi';
|
||||
|
||||
protected $fillable = [
|
||||
'hari',
|
||||
'waktu_makan_id',
|
||||
'komponen_id',
|
||||
'makanan_pakar_id',
|
||||
'makanan_sistem_ids',
|
||||
'status_kecocokan'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'makanan_sistem_ids' => 'array',
|
||||
];
|
||||
|
||||
// Relasi ke makanan
|
||||
public function makananPakar()
|
||||
{
|
||||
return $this->belongsTo(Makanan::class, 'makanan_pakar_id');
|
||||
}
|
||||
|
||||
public function komponen()
|
||||
{
|
||||
return $this->belongsTo(Komponen::class, 'komponen_id');
|
||||
}
|
||||
|
||||
public function waktuMakan()
|
||||
{
|
||||
return $this->belongsTo(WaktuMakan::class, 'waktu_makan_id');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class WaktuMakan extends Model
|
||||
{
|
||||
|
|
@ -11,7 +12,22 @@ class WaktuMakan extends Model
|
|||
|
||||
protected $table = 'waktu_makans'; // custom table name
|
||||
|
||||
protected $fillable = ['nama', 'keterangan'];
|
||||
protected $fillable = [
|
||||
'nama',
|
||||
'keterangan',
|
||||
'is_active',
|
||||
'alert_text'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'keterangan' => 'string',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
public function rekomendasiAhli()
|
||||
{
|
||||
return $this->hasMany(RekomendasiAhli::class);
|
||||
}
|
||||
|
||||
public function makananKomponenWaktu()
|
||||
{
|
||||
|
|
@ -24,4 +40,83 @@ public function makanans()
|
|||
->withPivot('komponen_id')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
public function komponens()
|
||||
{
|
||||
return $this->belongsToMany(Komponen::class, 'makanan_komponen_waktu', 'waktu_makan_id', 'komponen_id')
|
||||
->distinct();
|
||||
}
|
||||
|
||||
public function rekomendasis()
|
||||
{
|
||||
return $this->hasMany(Rekomendasi::class, 'waktu_makan_id');
|
||||
}
|
||||
|
||||
public function consistencyRatios()
|
||||
{
|
||||
return $this->hasMany(ConsistencyRatio::class);
|
||||
}
|
||||
|
||||
public function getLatestConsistencyRatioAttribute()
|
||||
{
|
||||
return $this->consistencyRatios()
|
||||
->where('user_id', auth()->id())
|
||||
->orderBy('tanggal_perhitungan', 'desc')
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get waktu makan dalam timezone yang sesuai
|
||||
*/
|
||||
public function getWaktuDalamTimezone($timezone = null)
|
||||
{
|
||||
$timezone = $timezone ?: config('app.timezone');
|
||||
|
||||
// Parse waktu dari database (format time)
|
||||
$waktu = Carbon::createFromFormat('H:i:s', $this->keterangan);
|
||||
|
||||
// Set timezone
|
||||
$waktu->setTimezone($timezone);
|
||||
|
||||
return $waktu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get waktu makan yang sudah disesuaikan dengan timezone saat ini
|
||||
*/
|
||||
public function getWaktuSekarang()
|
||||
{
|
||||
return $this->getWaktuDalamTimezone(config('app.timezone'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check apakah sekarang adalah waktu makan ini
|
||||
*/
|
||||
public function isWaktuMakanSekarang($tolerance = 30)
|
||||
{
|
||||
$waktuMakan = $this->getWaktuSekarang();
|
||||
$waktuSekarang = Carbon::now(config('app.timezone'));
|
||||
|
||||
$selisih = abs($waktuMakan->diffInMinutes($waktuSekarang));
|
||||
|
||||
return $selisih <= $tolerance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get waktu makan dalam format yang mudah dibaca
|
||||
*/
|
||||
public function getWaktuFormatted($timezone = null)
|
||||
{
|
||||
$waktu = $this->getWaktuDalamTimezone($timezone);
|
||||
return $waktu->format('H:i');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get waktu makan dengan AM/PM
|
||||
*/
|
||||
public function getWaktuAMPM($timezone = null)
|
||||
{
|
||||
$waktu = $this->getWaktuDalamTimezone($timezone);
|
||||
return $waktu->format('h:i A');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
|
@ -19,6 +20,7 @@ public function register(): void
|
|||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
Paginator::defaultView('vendor.pagination.custom');
|
||||
Paginator::defaultSimpleView('vendor.pagination.custom');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\UserNotification;
|
||||
use App\Models\WaktuMakan;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class NotificationService
|
||||
{
|
||||
/**
|
||||
* Kirim notifikasi waktu makan
|
||||
*/
|
||||
public static function sendMealTimeNotification($userId, $waktuMakanId)
|
||||
{
|
||||
$user = User::find($userId);
|
||||
$waktuMakan = WaktuMakan::find($waktuMakanId);
|
||||
|
||||
if (!$user || !$waktuMakan) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cek apakah sudah ada notifikasi untuk waktu makan ini hari ini
|
||||
$existingNotification = UserNotification::where('user_id', $userId)
|
||||
->where('waktu_makan_id', $waktuMakanId)
|
||||
->whereDate('created_at', today())
|
||||
->first();
|
||||
|
||||
if ($existingNotification) {
|
||||
return false; // Sudah ada notifikasi hari ini
|
||||
}
|
||||
|
||||
return UserNotification::create([
|
||||
'user_id' => $userId,
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'title' => "Waktu Makan {$waktuMakan->nama}",
|
||||
'message' => $waktuMakan->alert_text ?? "Waktunya makan {$waktuMakan->nama}!",
|
||||
'type' => 'reminder',
|
||||
'data' => [
|
||||
'waktu_makan' => $waktuMakan->nama,
|
||||
'waktu' => $waktuMakan->keterangan
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Kirim notifikasi simulasi selesai
|
||||
*/
|
||||
public static function sendSimulationCompletedNotification($userId, $data = [])
|
||||
{
|
||||
return UserNotification::create([
|
||||
'user_id' => $userId,
|
||||
'title' => 'Simulasi AHP Selesai',
|
||||
'message' => 'Hasil simulasi AHP Anda sudah siap. Lihat rekomendasi makanan terbaik!',
|
||||
'type' => 'success',
|
||||
'data' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Kirim notifikasi umum
|
||||
*/
|
||||
public static function sendGeneralNotification($userId, $title, $message, $type = 'info', $data = [])
|
||||
{
|
||||
return UserNotification::create([
|
||||
'user_id' => $userId,
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
'type' => $type,
|
||||
'data' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cek dan kirim notifikasi waktu makan otomatis
|
||||
*/
|
||||
public static function checkAndSendMealTimeNotifications()
|
||||
{
|
||||
$currentTime = now()->format('H:i:s');
|
||||
$users = User::whereHas('alertSetting', function($query) {
|
||||
$query->where('is_enabled', true);
|
||||
})->get();
|
||||
|
||||
foreach ($users as $user) {
|
||||
$alertSetting = $user->alertSetting;
|
||||
if (!$alertSetting || !$alertSetting->is_enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$enabledWaktuMakan = $alertSetting->enabledWaktuMakan;
|
||||
|
||||
foreach ($enabledWaktuMakan as $waktuMakan) {
|
||||
$waktuMakanTime = $waktuMakan->keterangan;
|
||||
$timeDiff = abs(strtotime($currentTime) - strtotime($waktuMakanTime));
|
||||
|
||||
// Kirim notifikasi jika waktu makan dalam rentang 5 menit
|
||||
if ($timeDiff <= 300) { // 5 menit = 300 detik
|
||||
self::sendMealTimeNotification($user->id, $waktuMakan->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tandai notifikasi sebagai dibaca
|
||||
*/
|
||||
public static function markAsRead($notificationId, $userId)
|
||||
{
|
||||
$notification = UserNotification::where('id', $notificationId)
|
||||
->where('user_id', $userId)
|
||||
->first();
|
||||
|
||||
if ($notification) {
|
||||
$notification->markAsRead();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tandai semua notifikasi sebagai dibaca
|
||||
*/
|
||||
public static function markAllAsRead($userId)
|
||||
{
|
||||
return UserNotification::where('user_id', $userId)
|
||||
->where('is_read', false)
|
||||
->update([
|
||||
'is_read' => true,
|
||||
'read_at' => now()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hapus notifikasi lama (lebih dari 30 hari)
|
||||
*/
|
||||
public static function cleanOldNotifications()
|
||||
{
|
||||
return UserNotification::where('created_at', '<', now()->subDays(30))->delete();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\RekomendasiAhli;
|
||||
use App\Models\ValidasiRekomendasi;
|
||||
use App\Models\Rekomendasi;
|
||||
use App\Models\Makanan;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ValidasiRekomendasiService
|
||||
{
|
||||
/**
|
||||
* Bandingkan rekomendasi pakar dengan hasil AHP
|
||||
* @param int $topN Jumlah makanan teratas yang akan dibandingkan (default: 4)
|
||||
* @return array Hasil validasi dan persentase kecocokan
|
||||
*/
|
||||
public function bandingkanRekomendasi(int $topN = 4): array
|
||||
{
|
||||
try {
|
||||
// Ambil semua rekomendasi pakar
|
||||
$rekomendasiPakar = RekomendasiAhli::with(['makanan', 'waktuMakan', 'komponen'])->get();
|
||||
|
||||
\Log::info('Rekomendasi pakar count: ' . $rekomendasiPakar->count());
|
||||
|
||||
// Ambil hasil AHP terbaru untuk setiap waktu makan dan komponen
|
||||
$hasilAHP = $this->ambilHasilAHP($topN);
|
||||
|
||||
\Log::info('Hasil AHP count: ' . $hasilAHP->count());
|
||||
\Log::info('Hasil AHP structure:', $hasilAHP->toArray());
|
||||
|
||||
$hasilValidasi = [];
|
||||
$totalLebihBaik = 0;
|
||||
$totalSetara = 0;
|
||||
$totalLebihBuruk = 0;
|
||||
|
||||
|
||||
foreach ($rekomendasiPakar as $pakar) {
|
||||
// Cari hasil AHP yang sesuai dengan waktu makan dan komponen
|
||||
$hasilAHPUntukKomponen = $hasilAHP
|
||||
->where('waktu_makan_id', $pakar->waktu_makan_id)
|
||||
->where('komponen_id', $pakar->komponen_id)
|
||||
->first();
|
||||
|
||||
if (!$hasilAHPUntukKomponen) {
|
||||
\Log::info('Tidak ada hasil AHP untuk waktu_makan_id: ' . $pakar->waktu_makan_id . ', komponen_id: ' . $pakar->komponen_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
$makananPakar = $pakar->makanan;
|
||||
$makananSistem = collect($hasilAHPUntukKomponen['makanans']);
|
||||
$makananSistemIds = $makananSistem->pluck('id')->toArray();
|
||||
|
||||
\Log::info('Processing pakar:', [
|
||||
'pakar_id' => $pakar->id,
|
||||
'makanan_pakar_id' => $pakar->makanan_id,
|
||||
'makanan_sistem_ids' => $makananSistemIds
|
||||
]);
|
||||
|
||||
// Cek kecocokan
|
||||
$status = $this->tentukanStatusKecocokan($makananPakar, $makananSistem);
|
||||
|
||||
// Update counter
|
||||
switch ($status) {
|
||||
case 'lebih_baik':
|
||||
$totalLebihBaik++;
|
||||
break;
|
||||
case 'setara':
|
||||
$totalSetara++;
|
||||
break;
|
||||
case 'lebih_buruk':
|
||||
$totalLebihBuruk++;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Simpan hasil validasi
|
||||
$hasilValidasi[] = [
|
||||
'hari' => $pakar->hari,
|
||||
'waktu_makan_id' => $pakar->waktu_makan_id,
|
||||
'komponen_id' => $pakar->komponen_id,
|
||||
'makanan_pakar_id' => $pakar->makanan_id,
|
||||
'makanan_sistem_ids' => $makananSistemIds,
|
||||
'status_kecocokan' => $status
|
||||
];
|
||||
}
|
||||
|
||||
// Hitung persentase kecocokan
|
||||
// $total = count($hasilValidasi);
|
||||
$total = $totalLebihBaik + $totalSetara + $totalLebihBuruk;
|
||||
$persentaseCocok = $total > 0
|
||||
? (($totalLebihBaik + $totalSetara) / $total) * 100 : 0;
|
||||
|
||||
|
||||
// Simpan hasil ke database
|
||||
$this->simpanHasilValidasi($hasilValidasi);
|
||||
|
||||
return [
|
||||
'hasil_validasi' => $hasilValidasi,
|
||||
'statistik' => [
|
||||
'total' => $total,
|
||||
'lebih_baik' => $totalLebihBaik,
|
||||
'setara' => $totalSetara,
|
||||
'lebih_buruk' => $totalLebihBuruk,
|
||||
'persentase_cocok' => round($persentaseCocok, 2)
|
||||
]
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Error in bandingkanRekomendasi: ' . $e->getMessage(), [
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ambil hasil AHP terbaru untuk setiap waktu makan dan komponen
|
||||
*/
|
||||
private function ambilHasilAHP(int $topN): Collection
|
||||
{
|
||||
try {
|
||||
// Ambil data rekomendasi dengan eager loading
|
||||
$rekomendasis = Rekomendasi::with(['makanan', 'waktuMakan', 'komponen'])
|
||||
->orderBy('waktu_makan_id')
|
||||
->orderBy('komponen_id')
|
||||
->orderBy('nilai_akhir', 'desc')
|
||||
->get();
|
||||
|
||||
\Log::info('Total rekomendasi found: ' . $rekomendasis->count());
|
||||
|
||||
// Group by waktu makan dan komponen
|
||||
$groupedData = $rekomendasis->groupBy(['waktu_makan_id', 'komponen_id']);
|
||||
|
||||
// Transform data structure
|
||||
$transformedData = collect();
|
||||
|
||||
foreach ($groupedData as $waktuMakanId => $komponenGroups) {
|
||||
foreach ($komponenGroups as $komponenId => $items) {
|
||||
// Ambil top N makanan berdasarkan nilai_akhir
|
||||
$topMakanans = $items->take($topN)
|
||||
->map(function($item) {
|
||||
return [
|
||||
'id' => $item->makanan_id,
|
||||
'nama' => $item->makanan->nama,
|
||||
'nilai_akhir' => $item->nilai_akhir,
|
||||
'energi' => $item->makanan->energi,
|
||||
'lemak' => $item->makanan->lemak,
|
||||
'karbohidrat' => $item->makanan->karbohidrat,
|
||||
'natrium' => $item->makanan->natrium
|
||||
];
|
||||
});
|
||||
|
||||
$transformedData->push([
|
||||
'waktu_makan_id' => $waktuMakanId,
|
||||
'komponen_id' => $komponenId,
|
||||
'makanans' => $topMakanans
|
||||
]);
|
||||
|
||||
\Log::info("Added data for waktu_makan_id: $waktuMakanId, komponen_id: $komponenId, makanan_count: " . $topMakanans->count());
|
||||
}
|
||||
}
|
||||
|
||||
\Log::info('Transformed data count: ' . $transformedData->count());
|
||||
return $transformedData;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Error in ambilHasilAHP: ' . $e->getMessage(), [
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tentukan status kecocokan antara makanan pakar dan sistem
|
||||
*/
|
||||
private function tentukanStatusKecocokan($makananPakar, Collection $makananSistem): string
|
||||
{
|
||||
$toleransi = 0.10; // 10%
|
||||
$lebihBaik = 0;
|
||||
$setara = 0;
|
||||
$lebihBuruk = 0;
|
||||
|
||||
foreach ($makananSistem as $makanan) {
|
||||
$lemakPakar = $makananPakar->lemak;
|
||||
$natriumPakar = $makananPakar->natrium;
|
||||
$lemakAHP = $makanan['lemak'];
|
||||
$natriumAHP = $makanan['natrium'];
|
||||
|
||||
$lemakSelisih = $this->hitungSelisihPersen($lemakPakar, $lemakAHP);
|
||||
$natriumSelisih = $this->hitungSelisihPersen($natriumPakar, $natriumAHP);
|
||||
|
||||
if ($lemakAHP < $lemakPakar && $natriumAHP < $natriumPakar) {
|
||||
$lebihBaik++;
|
||||
} elseif ($lemakSelisih <= $toleransi && $natriumSelisih <= $toleransi) {
|
||||
$setara++;
|
||||
} else {
|
||||
$lebihBuruk++;
|
||||
}
|
||||
|
||||
}
|
||||
// Tentukan status dominan
|
||||
// Logika khusus: Jika lebih_baik = 1 dan setara = 1, maka dianggap lebih_baik
|
||||
if ($lebihBaik === 1 && $setara === 1) {
|
||||
return 'lebih_baik';
|
||||
}
|
||||
// Tentukan status dominan (logika umum)
|
||||
if ($lebihBaik >= max($setara, $lebihBuruk)) {
|
||||
return 'lebih_baik';
|
||||
} elseif ($setara >= max($lebihBaik, $lebihBuruk)) {
|
||||
return 'setara';
|
||||
} else {
|
||||
return 'lebih_buruk';
|
||||
}
|
||||
|
||||
}
|
||||
private function hitungSelisihPersen($nilai1, $nilai2): float
|
||||
{
|
||||
if ($nilai1 == 0 && $nilai2 == 0) return 0;
|
||||
if ($nilai1 == 0 || $nilai2 == 0) return 1; // 100%
|
||||
return abs($nilai1 - $nilai2) / max($nilai1, $nilai2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cek apakah kandungan gizi dua makanan mirip (±10%)
|
||||
*/
|
||||
private function isGiziMirip($makanan1, $makanan2): bool
|
||||
{
|
||||
$toleransi = 0.10; // 10%
|
||||
|
||||
$giziMirip = true;
|
||||
$giziMirip &= $this->isDalamToleransi($makanan1->energi, $makanan2['energi'], $toleransi);
|
||||
$giziMirip &= $this->isDalamToleransi($makanan1->lemak, $makanan2['lemak'], $toleransi);
|
||||
$giziMirip &= $this->isDalamToleransi($makanan1->karbohidrat, $makanan2['karbohidrat'], $toleransi);
|
||||
$giziMirip &= $this->isDalamToleransi($makanan1->natrium, $makanan2['natrium'], $toleransi);
|
||||
|
||||
return $giziMirip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cek apakah dua nilai dalam toleransi yang ditentukan
|
||||
*/
|
||||
private function isDalamToleransi($nilai1, $nilai2, $toleransi): bool
|
||||
{
|
||||
if ($nilai1 == 0 && $nilai2 == 0) return true;
|
||||
if ($nilai1 == 0 || $nilai2 == 0) return false;
|
||||
|
||||
$selisih = abs($nilai1 - $nilai2) / max($nilai1, $nilai2);
|
||||
return $selisih <= $toleransi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simpan hasil validasi ke database
|
||||
*/
|
||||
private function simpanHasilValidasi(array $hasilValidasi): void
|
||||
{
|
||||
// Hapus data validasi lama
|
||||
ValidasiRekomendasi::truncate();
|
||||
|
||||
// Simpan data baru
|
||||
foreach ($hasilValidasi as $validasi) {
|
||||
ValidasiRekomendasi::create($validasi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
trait KriteriaTrait
|
||||
{
|
||||
/**
|
||||
* Daftar kriteria yang termasuk cost (semakin kecil semakin baik)
|
||||
*/
|
||||
protected function getCostCriteria(): array
|
||||
{
|
||||
return ['lemak', 'natrium'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Daftar kriteria yang termasuk benefit (semakin besar semakin baik)
|
||||
*/
|
||||
protected function getBenefitCriteria(): array
|
||||
{
|
||||
return ['energi', 'karbohidrat'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cek apakah kriteria termasuk cost
|
||||
*/
|
||||
protected function isCostCriteria(string $kriteriaNama): bool
|
||||
{
|
||||
return in_array(strtolower($kriteriaNama), $this->getCostCriteria());
|
||||
}
|
||||
|
||||
/**
|
||||
* Cek apakah kriteria termasuk benefit
|
||||
*/
|
||||
protected function isBenefitCriteria(string $kriteriaNama): bool
|
||||
{
|
||||
return in_array(strtolower($kriteriaNama), $this->getBenefitCriteria());
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,12 @@
|
|||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"doctrine/dbal": "*",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"laravel/framework": "^10.0",
|
||||
"laravel/sanctum": "^3.2",
|
||||
"laravel/tinker": "^2.8"
|
||||
"laravel/tinker": "^2.8",
|
||||
"maatwebsite/excel": "^1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "bfe12996eeecb6fdc8713a9fd9d431f8",
|
||||
"content-hash": "4519d1f6308e7ad5cb4d8d14d0f993b5",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
|
|
@ -210,6 +210,349 @@
|
|||
},
|
||||
"time": "2024-07-08T12:26:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/cache",
|
||||
"version": "2.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/cache.git",
|
||||
"reference": "1ca8f21980e770095a31456042471a57bc4c68fb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb",
|
||||
"reference": "1ca8f21980e770095a31456042471a57bc4c68fb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "~7.1 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/common": ">2.2,<2.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"cache/integration-tests": "dev-master",
|
||||
"doctrine/coding-standard": "^9",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
|
||||
"psr/cache": "^1.0 || ^2.0 || ^3.0",
|
||||
"symfony/cache": "^4.4 || ^5.4 || ^6",
|
||||
"symfony/var-exporter": "^4.4 || ^5.4 || ^6"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de"
|
||||
},
|
||||
{
|
||||
"name": "Jonathan Wage",
|
||||
"email": "jonwage@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Johannes Schmitt",
|
||||
"email": "schmittjoh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.",
|
||||
"homepage": "https://www.doctrine-project.org/projects/cache.html",
|
||||
"keywords": [
|
||||
"abstraction",
|
||||
"apcu",
|
||||
"cache",
|
||||
"caching",
|
||||
"couchdb",
|
||||
"memcached",
|
||||
"php",
|
||||
"redis",
|
||||
"xcache"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/cache/issues",
|
||||
"source": "https://github.com/doctrine/cache/tree/2.2.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.doctrine-project.org/sponsorship.html",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/phpdoctrine",
|
||||
"type": "patreon"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-05-20T20:07:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/dbal",
|
||||
"version": "3.9.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/dbal.git",
|
||||
"reference": "ec16c82f20be1a7224e65ac67144a29199f87959"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/ec16c82f20be1a7224e65ac67144a29199f87959",
|
||||
"reference": "ec16c82f20be1a7224e65ac67144a29199f87959",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer-runtime-api": "^2",
|
||||
"doctrine/cache": "^1.11|^2.0",
|
||||
"doctrine/deprecations": "^0.5.3|^1",
|
||||
"doctrine/event-manager": "^1|^2",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"psr/cache": "^1|^2|^3",
|
||||
"psr/log": "^1|^2|^3"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "12.0.0",
|
||||
"fig/log-test": "^1",
|
||||
"jetbrains/phpstorm-stubs": "2023.1",
|
||||
"phpstan/phpstan": "2.1.1",
|
||||
"phpstan/phpstan-strict-rules": "^2",
|
||||
"phpunit/phpunit": "9.6.22",
|
||||
"slevomat/coding-standard": "8.13.1",
|
||||
"squizlabs/php_codesniffer": "3.10.2",
|
||||
"symfony/cache": "^5.4|^6.0|^7.0",
|
||||
"symfony/console": "^4.4|^5.4|^6.0|^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/console": "For helpful console commands such as SQL execution and import of files."
|
||||
},
|
||||
"bin": [
|
||||
"bin/doctrine-dbal"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\DBAL\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de"
|
||||
},
|
||||
{
|
||||
"name": "Jonathan Wage",
|
||||
"email": "jonwage@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.",
|
||||
"homepage": "https://www.doctrine-project.org/projects/dbal.html",
|
||||
"keywords": [
|
||||
"abstraction",
|
||||
"database",
|
||||
"db2",
|
||||
"dbal",
|
||||
"mariadb",
|
||||
"mssql",
|
||||
"mysql",
|
||||
"oci8",
|
||||
"oracle",
|
||||
"pdo",
|
||||
"pgsql",
|
||||
"postgresql",
|
||||
"queryobject",
|
||||
"sasql",
|
||||
"sql",
|
||||
"sqlite",
|
||||
"sqlserver",
|
||||
"sqlsrv"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/dbal/issues",
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.9.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.doctrine-project.org/sponsorship.html",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/phpdoctrine",
|
||||
"type": "patreon"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-01-16T08:28:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/deprecations",
|
||||
"version": "1.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/deprecations.git",
|
||||
"reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38",
|
||||
"reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpunit/phpunit": "<=7.5 || >=13"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^9 || ^12 || ^13",
|
||||
"phpstan/phpstan": "1.4.10 || 2.1.11",
|
||||
"phpstan/phpstan-phpunit": "^1.0 || ^2",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12",
|
||||
"psr/log": "^1 || ^2 || ^3"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Deprecations\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
|
||||
"homepage": "https://www.doctrine-project.org/",
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/deprecations/issues",
|
||||
"source": "https://github.com/doctrine/deprecations/tree/1.1.5"
|
||||
},
|
||||
"time": "2025-04-07T20:06:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/event-manager",
|
||||
"version": "2.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/event-manager.git",
|
||||
"reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e",
|
||||
"reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/common": "<2.9"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^12",
|
||||
"phpstan/phpstan": "^1.8.8",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"vimeo/psalm": "^5.24"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Common\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de"
|
||||
},
|
||||
{
|
||||
"name": "Jonathan Wage",
|
||||
"email": "jonwage@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Johannes Schmitt",
|
||||
"email": "schmittjoh@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Marco Pivetta",
|
||||
"email": "ocramius@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.",
|
||||
"homepage": "https://www.doctrine-project.org/projects/event-manager.html",
|
||||
"keywords": [
|
||||
"event",
|
||||
"event dispatcher",
|
||||
"event manager",
|
||||
"event system",
|
||||
"events"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/event-manager/issues",
|
||||
"source": "https://github.com/doctrine/event-manager/tree/2.0.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.doctrine-project.org/sponsorship.html",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/phpdoctrine",
|
||||
"type": "patreon"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-22T20:47:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/inflector",
|
||||
"version": "2.0.10",
|
||||
|
|
@ -1884,6 +2227,65 @@
|
|||
],
|
||||
"time": "2024-01-28T23:22:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maatwebsite/excel",
|
||||
"version": "v1.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Maatwebsite/Laravel-Excel.git",
|
||||
"reference": "0c67aba8387726458d42461eae91a3415593bbc4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Maatwebsite/Laravel-Excel/zipball/0c67aba8387726458d42461eae91a3415593bbc4",
|
||||
"reference": "0c67aba8387726458d42461eae91a3415593bbc4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"phpoffice/phpexcel": "~1.8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "~0.9",
|
||||
"orchestra/testbench": "~2.2.0@dev",
|
||||
"phpunit/phpunit": "~4.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Maatwebsite\\Excel\\": "src/"
|
||||
},
|
||||
"classmap": [
|
||||
"src/Maatwebsite/Excel",
|
||||
"tests/TestCase.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Maatwebsite.nl",
|
||||
"email": "patrick@maatwebsite.nl"
|
||||
}
|
||||
],
|
||||
"description": "An eloquent way of importing and exporting Excel and CSV in Laravel 4 with the power of PHPExcel",
|
||||
"keywords": [
|
||||
"PHPExcel",
|
||||
"batch",
|
||||
"csv",
|
||||
"excel",
|
||||
"export",
|
||||
"import",
|
||||
"laravel"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Maatwebsite/Laravel-Excel/issues",
|
||||
"source": "https://github.com/Maatwebsite/Laravel-Excel/tree/master"
|
||||
},
|
||||
"time": "2014-07-10T09:06:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "3.7.0",
|
||||
|
|
@ -2384,6 +2786,68 @@
|
|||
],
|
||||
"time": "2023-02-08T01:06:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoffice/phpexcel",
|
||||
"version": "1.8.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPOffice/PHPExcel.git",
|
||||
"reference": "372c7cbb695a6f6f1e62649381aeaa37e7e70b32"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPOffice/PHPExcel/zipball/372c7cbb695a6f6f1e62649381aeaa37e7e70b32",
|
||||
"reference": "372c7cbb695a6f6f1e62649381aeaa37e7e70b32",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-xml": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"php": ">=5.2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"PHPExcel": "Classes/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Maarten Balliauw",
|
||||
"homepage": "http://blog.maartenballiauw.be"
|
||||
},
|
||||
{
|
||||
"name": "Mark Baker"
|
||||
},
|
||||
{
|
||||
"name": "Franck Lefevre",
|
||||
"homepage": "http://blog.rootslabs.net"
|
||||
},
|
||||
{
|
||||
"name": "Erik Tilt"
|
||||
}
|
||||
],
|
||||
"description": "PHPExcel - OpenXML - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
|
||||
"homepage": "http://phpexcel.codeplex.com",
|
||||
"keywords": [
|
||||
"OpenXML",
|
||||
"excel",
|
||||
"php",
|
||||
"spreadsheet",
|
||||
"xls",
|
||||
"xlsx"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPOffice/PHPExcel/issues",
|
||||
"source": "https://github.com/PHPOffice/PHPExcel/tree/master"
|
||||
},
|
||||
"abandoned": "phpoffice/phpspreadsheet",
|
||||
"time": "2015-05-01T07:00:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.9.3",
|
||||
|
|
@ -2459,6 +2923,55 @@
|
|||
],
|
||||
"time": "2024-07-20T21:41:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/cache",
|
||||
"version": "3.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/cache.git",
|
||||
"reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
|
||||
"reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Cache\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for caching libraries",
|
||||
"keywords": [
|
||||
"cache",
|
||||
"psr",
|
||||
"psr-6"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/cache/tree/3.0.0"
|
||||
},
|
||||
"time": "2021-02-03T23:26:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/clock",
|
||||
"version": "1.0.0",
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'UTC',
|
||||
'timezone' => 'Asia/Jakarta',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::create('user_notifications', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->onDelete('cascade');
|
||||
$table->foreignId('waktu_makan_id')->nullable()->constrained('waktu_makans')->onDelete('cascade');
|
||||
$table->string('title');
|
||||
$table->text('message');
|
||||
$table->enum('type', ['alert', 'reminder', 'info', 'success', 'warning'])->default('info');
|
||||
$table->boolean('is_read')->default(false);
|
||||
$table->timestamp('read_at')->nullable();
|
||||
$table->json('data')->nullable(); // Data tambahan dalam format JSON
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('user_notifications');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::create('consistency_ratio_criterias', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->float('ci')->comment('Consistency Index');
|
||||
$table->float('cr')->comment('Consistency Ratio');
|
||||
$table->boolean('is_consistent')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('consistency_ratio_criterias');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
<?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
|
||||
{
|
||||
// Add timestamps to bobot_kriterias
|
||||
Schema::table('bobot_kriterias', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('bobot_kriterias', 'created_at')) {
|
||||
$table->timestamp('created_at')->nullable();
|
||||
}
|
||||
if (!Schema::hasColumn('bobot_kriterias', 'updated_at')) {
|
||||
$table->timestamp('updated_at')->nullable();
|
||||
}
|
||||
});
|
||||
|
||||
// Add timestamps to perbandingan_kriterias
|
||||
Schema::table('perbandingan_kriterias', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('perbandingan_kriterias', 'created_at')) {
|
||||
$table->timestamp('created_at')->nullable();
|
||||
}
|
||||
if (!Schema::hasColumn('perbandingan_kriterias', 'updated_at')) {
|
||||
$table->timestamp('updated_at')->nullable();
|
||||
}
|
||||
});
|
||||
|
||||
// Add timestamps to perbandingan_alternatifs
|
||||
Schema::table('perbandingan_alternatifs', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('perbandingan_alternatifs', 'created_at')) {
|
||||
$table->timestamp('created_at')->nullable();
|
||||
}
|
||||
if (!Schema::hasColumn('perbandingan_alternatifs', 'updated_at')) {
|
||||
$table->timestamp('updated_at')->nullable();
|
||||
}
|
||||
});
|
||||
|
||||
// Add timestamps to consistency_ratios
|
||||
Schema::table('consistency_ratios', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('consistency_ratios', 'created_at')) {
|
||||
$table->timestamp('created_at')->nullable();
|
||||
}
|
||||
if (!Schema::hasColumn('consistency_ratios', 'updated_at')) {
|
||||
$table->timestamp('updated_at')->nullable();
|
||||
}
|
||||
});
|
||||
|
||||
// Add timestamps to skor_makanans
|
||||
Schema::table('skor_makanans', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('skor_makanans', 'created_at')) {
|
||||
$table->timestamp('created_at')->nullable();
|
||||
}
|
||||
if (!Schema::hasColumn('skor_makanans', 'updated_at')) {
|
||||
$table->timestamp('updated_at')->nullable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Remove timestamps from bobot_kriterias
|
||||
Schema::table('bobot_kriterias', function (Blueprint $table) {
|
||||
$table->dropColumn(['created_at', 'updated_at']);
|
||||
});
|
||||
|
||||
// Remove timestamps from perbandingan_kriterias
|
||||
Schema::table('perbandingan_kriterias', function (Blueprint $table) {
|
||||
$table->dropColumn(['created_at', 'updated_at']);
|
||||
});
|
||||
|
||||
// Remove timestamps from perbandingan_alternatifs
|
||||
Schema::table('perbandingan_alternatifs', function (Blueprint $table) {
|
||||
$table->dropColumn(['created_at', 'updated_at']);
|
||||
});
|
||||
|
||||
// Remove timestamps from consistency_ratios
|
||||
Schema::table('consistency_ratios', function (Blueprint $table) {
|
||||
$table->dropColumn(['created_at', 'updated_at']);
|
||||
});
|
||||
|
||||
// Remove timestamps from skor_makanans
|
||||
Schema::table('skor_makanans', function (Blueprint $table) {
|
||||
$table->dropColumn(['created_at', 'updated_at']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('perbandingan_alternatifs', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('waktu_makan_id')->after('kriteria_id')->nullable();
|
||||
$table->unsignedBigInteger('komponen_id')->after('waktu_makan_id')->nullable();
|
||||
|
||||
$table->foreign('waktu_makan_id')->references('id')->on('waktu_makans')->onDelete('set null');
|
||||
$table->foreign('komponen_id')->references('id')->on('komponens')->onDelete('set null');
|
||||
});
|
||||
|
||||
Schema::table('skor_makanans', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('waktu_makan_id')->after('kriteria_id')->nullable();
|
||||
$table->unsignedBigInteger('komponen_id')->after('waktu_makan_id')->nullable();
|
||||
|
||||
$table->foreign('waktu_makan_id')->references('id')->on('waktu_makans')->onDelete('set null');
|
||||
$table->foreign('komponen_id')->references('id')->on('komponens')->onDelete('set null');
|
||||
});
|
||||
|
||||
if (!Schema::hasTable('consistency_ratios')) {
|
||||
Schema::create('consistency_ratios', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('kriteria_id');
|
||||
$table->unsignedBigInteger('komponen_id');
|
||||
$table->unsignedBigInteger('waktu_makan_id');
|
||||
$table->float('nilai');
|
||||
$table->date('tanggal');
|
||||
$table->string('status');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('kriteria_id')->references('id')->on('kriterias')->onDelete('cascade');
|
||||
$table->foreign('komponen_id')->references('id')->on('komponens')->onDelete('cascade');
|
||||
$table->foreign('waktu_makan_id')->references('id')->on('waktu_makans')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('perbandingan_alternatifs', function (Blueprint $table) {
|
||||
$table->dropForeign(['waktu_makan_id']);
|
||||
$table->dropForeign(['komponen_id']);
|
||||
$table->dropColumn(['waktu_makan_id', 'komponen_id']);
|
||||
});
|
||||
|
||||
Schema::table('skor_makanans', function (Blueprint $table) {
|
||||
$table->dropForeign(['waktu_makan_id']);
|
||||
$table->dropForeign(['komponen_id']);
|
||||
$table->dropColumn(['waktu_makan_id', 'komponen_id']);
|
||||
});
|
||||
|
||||
Schema::dropIfExists('consistency_ratios');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('consistency_ratios', function (Blueprint $table) {
|
||||
// Tambahkan foreign key constraints
|
||||
if (!Schema::hasColumn('consistency_ratios', 'waktu_makan_id')) {
|
||||
$table->unsignedBigInteger('waktu_makan_id')->nullable()->after('kriteria_id');
|
||||
}
|
||||
if (!Schema::hasColumn('consistency_ratios', 'komponen_id')) {
|
||||
$table->unsignedBigInteger('komponen_id')->nullable()->after('waktu_makan_id');
|
||||
}
|
||||
|
||||
// Tambahkan foreign key constraints jika belum ada
|
||||
$foreignKeys = Schema::getConnection()->getDoctrineSchemaManager()->listTableForeignKeys('consistency_ratios');
|
||||
$foreignKeyNames = array_map(function($fk) { return $fk->getName(); }, $foreignKeys);
|
||||
|
||||
if (!in_array('consistency_ratios_waktu_makan_id_foreign', $foreignKeyNames)) {
|
||||
$table->foreign('waktu_makan_id')->references('id')->on('waktu_makans')->onDelete('set null');
|
||||
}
|
||||
if (!in_array('consistency_ratios_komponen_id_foreign', $foreignKeyNames)) {
|
||||
$table->foreign('komponen_id')->references('id')->on('komponens')->onDelete('set null');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('consistency_ratios', function (Blueprint $table) {
|
||||
// Drop foreign keys jika ada
|
||||
if (Schema::hasColumn('consistency_ratios', 'waktu_makan_id')) {
|
||||
$table->dropForeign(['waktu_makan_id']);
|
||||
$table->dropColumn('waktu_makan_id');
|
||||
}
|
||||
if (Schema::hasColumn('consistency_ratios', 'komponen_id')) {
|
||||
$table->dropForeign(['komponen_id']);
|
||||
$table->dropColumn('komponen_id');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?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::dropIfExists('consistency_ratios');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::create('consistency_ratios', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('kriteria_id');
|
||||
$table->unsignedBigInteger('komponen_id')->nullable();
|
||||
$table->unsignedBigInteger('waktu_makan_id')->nullable();
|
||||
$table->float('nilai');
|
||||
$table->date('tanggal');
|
||||
$table->string('status');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('kriteria_id')->references('id')->on('kriterias')->onDelete('cascade');
|
||||
$table->foreign('komponen_id')->references('id')->on('komponens')->onDelete('set null');
|
||||
$table->foreign('waktu_makan_id')->references('id')->on('waktu_makans')->onDelete('set null');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('consistency_ratios', function (Blueprint $table) {
|
||||
// Tambah kolom kriteria_id jika belum ada
|
||||
if (!Schema::hasColumn('consistency_ratios', 'kriteria_id')) {
|
||||
$table->unsignedBigInteger('kriteria_id')->after('id')->nullable();
|
||||
$table->foreign('kriteria_id')->references('id')->on('kriterias')->onDelete('cascade');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('consistency_ratios', function (Blueprint $table) {
|
||||
// Hapus foreign key dan kolom jika ada
|
||||
if (Schema::hasColumn('consistency_ratios', 'kriteria_id')) {
|
||||
$table->dropForeign(['kriteria_id']);
|
||||
$table->dropColumn('kriteria_id');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('makanan_komponen_waktu', function (Blueprint $table) {
|
||||
$table->boolean('status')->default(true)->after('waktu_makan_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('makanan_komponen_waktu', function (Blueprint $table) {
|
||||
$table->dropColumn('status');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('makanan_komponen_waktu', function (Blueprint $table) {
|
||||
$table->unique(['makanan_id', 'komponen_id', 'waktu_makan_id'], 'unique_makanan_komponen_waktu');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('makanan_komponen_waktu', function (Blueprint $table) {
|
||||
$table->dropUnique('unique_makanan_komponen_waktu');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::create('consistency_ratio_alternatifs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('kriteria_id');
|
||||
$table->unsignedBigInteger('waktu_makan_id');
|
||||
$table->unsignedBigInteger('komponen_id');
|
||||
$table->double('ci', 8, 4)->comment('Consistency Index');
|
||||
$table->double('cr', 8, 4)->comment('Consistency Ratio');
|
||||
$table->boolean('is_consistent')->default(false);
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('kriteria_id')->references('id')->on('kriterias')->onDelete('cascade');
|
||||
$table->foreign('waktu_makan_id')->references('id')->on('waktu_makans')->onDelete('cascade');
|
||||
$table->foreign('komponen_id')->references('id')->on('komponens')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('consistency_ratio_alternatifs');
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
ini_set('memory_limit', '1G');
|
||||
|
||||
use Illuminate\Contracts\Http\Kernel;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
// Service Worker untuk HeartChoice Notifications
|
||||
const CACHE_NAME = 'heartchoice-v1';
|
||||
const urlsToCache = [
|
||||
'/',
|
||||
'/audio/notification.mp3',
|
||||
'/audio/default.mp3',
|
||||
'/logo/baru/dutdut.png'
|
||||
];
|
||||
|
||||
// Install event
|
||||
self.addEventListener('install', event => {
|
||||
console.log('Service Worker installing...');
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME)
|
||||
.then(cache => {
|
||||
console.log('Opened cache');
|
||||
return cache.addAll(urlsToCache);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Cache installation failed:', error);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Fetch event
|
||||
self.addEventListener('fetch', event => {
|
||||
event.respondWith(
|
||||
caches.match(event.request)
|
||||
.then(response => {
|
||||
// Return cached version or fetch from network
|
||||
return response || fetch(event.request);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Fetch failed:', error);
|
||||
return fetch(event.request);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Push event untuk notifikasi background
|
||||
self.addEventListener('push', event => {
|
||||
console.log('Push event received:', event);
|
||||
|
||||
let notificationData = {
|
||||
title: 'HeartChoice',
|
||||
body: 'Anda memiliki notifikasi baru!',
|
||||
icon: '/logo/baru/dutdut.png',
|
||||
badge: '/logo/baru/dutdut.png',
|
||||
tag: 'heartchoice-notification',
|
||||
requireInteraction: false,
|
||||
silent: false,
|
||||
vibrate: [200, 100, 200],
|
||||
data: {
|
||||
url: '/user/notifications'
|
||||
}
|
||||
};
|
||||
|
||||
// Jika ada data dari push message
|
||||
if (event.data) {
|
||||
try {
|
||||
const data = event.data.json();
|
||||
notificationData = {
|
||||
...notificationData,
|
||||
...data
|
||||
};
|
||||
} catch (e) {
|
||||
console.log('Error parsing push data:', e);
|
||||
}
|
||||
}
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(notificationData.title, notificationData)
|
||||
);
|
||||
});
|
||||
|
||||
// Notification click event
|
||||
self.addEventListener('notificationclick', event => {
|
||||
console.log('Notification clicked:', event);
|
||||
|
||||
event.notification.close();
|
||||
|
||||
// Focus ke window yang ada atau buat window baru
|
||||
event.waitUntil(
|
||||
clients.matchAll({ type: 'window' })
|
||||
.then(clientList => {
|
||||
// Cari window yang sudah terbuka
|
||||
for (let client of clientList) {
|
||||
if (client.url.includes('/user/notifications') && 'focus' in client) {
|
||||
return client.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Jika tidak ada window yang terbuka, buka window baru
|
||||
if (clients.openWindow) {
|
||||
return clients.openWindow('/user/notifications');
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Notification close event
|
||||
self.addEventListener('notificationclose', event => {
|
||||
console.log('Notification closed:', event);
|
||||
});
|
||||
|
||||
// Background sync (untuk fitur offline)
|
||||
self.addEventListener('sync', event => {
|
||||
console.log('Background sync:', event);
|
||||
|
||||
if (event.tag === 'background-sync') {
|
||||
event.waitUntil(
|
||||
// Lakukan sinkronisasi data
|
||||
console.log('Performing background sync...')
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Message event untuk komunikasi dengan main thread
|
||||
self.addEventListener('message', event => {
|
||||
console.log('Service Worker received message:', event.data);
|
||||
|
||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
}
|
||||
|
||||
// Handle show notification message
|
||||
if (event.data && event.data.type === 'SHOW_NOTIFICATION') {
|
||||
const notificationData = event.data.notification;
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(notificationData.title, notificationData)
|
||||
.then(() => {
|
||||
console.log('Background notification shown successfully');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error showing background notification:', error);
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Service Worker loaded successfully');
|
||||
|
|
@ -1,372 +1,436 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Admin Dashboard')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<h1>Dashboard</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="index.html">Home</a></li>
|
||||
<li class="breadcrumb-item active">Dashboard</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<section class="section dashboard">
|
||||
<div class="row">
|
||||
|
||||
<!-- Left side columns -->
|
||||
<div class="col-lg-8">
|
||||
<div class="row">
|
||||
|
||||
<!-- Users Card -->
|
||||
<!-- Users Card -->
|
||||
<div class="col-xxl-6 col-md-6">
|
||||
<div class="card info-card customers-card">
|
||||
<div class="card-body position-relative">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">Users <span>| Total</span></h5>
|
||||
<small>
|
||||
<a href="{{ route('datauser') }}" class="text-primary d-flex align-items-center" style="text-decoration: none;">
|
||||
<span>Selengkapnya<span> <i class="bi bi-arrow-right-circle ms-1"></i>
|
||||
</a>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center mt-3">
|
||||
<div class="card-icon rounded-circle d-flex align-items-center justify-content-center">
|
||||
<i class="bi bi-people"></i>
|
||||
</div>
|
||||
<div class="ps-3">
|
||||
<h6>{{ $userCount }} Users</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Makanan Card -->
|
||||
<div class="col-xxl-6 col-md-6">
|
||||
<div class="card info-card sales-card">
|
||||
<div class="card-body position-relative">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">Data Makanan <span>| Total</span></h5>
|
||||
<small>
|
||||
<a href="{{ route('makanan') }}" class="text-primary d-flex align-items-center" style="text-decoration: none;">
|
||||
<span>Selengkapnya<span> <i class="bi bi-arrow-right-circle ms-1"></i>
|
||||
</a>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center mt-3">
|
||||
<div class="card-icon rounded-circle d-flex align-items-center justify-content-center">
|
||||
<i class="bi bi-basket"></i>
|
||||
</div>
|
||||
<div class="ps-3">
|
||||
<h6>{{ $makananCount }} Data</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- End Data Makanan Card -->
|
||||
|
||||
|
||||
<div class="card shadow-lg border-start border-4 border-primary mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-primary fw-bold mb-0 d-flex justify-content-between align-items-center">
|
||||
📌 Aturan Penilaian <span class="text-muted">| AHP</span>
|
||||
<button class="btn btn-sm btn-outline-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAHP" aria-expanded="false" aria-controls="collapseAHP">
|
||||
Lihat Detail
|
||||
</button>
|
||||
</h5>
|
||||
|
||||
<div class="collapse mt-3" id="collapseAHP">
|
||||
<p class="fs-6">Sebelum melakukan perbandingan, pahami skala penilaian berikut. Skala ini digunakan untuk menilai tingkat kepentingan antar kriteria maupun alternatif.</p>
|
||||
|
||||
<div class="row row-cols-1 row-cols-md-2 g-3 mt-2">
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-start">
|
||||
<span class="badge bg-success rounded-pill me-3">1</span>
|
||||
<div>
|
||||
<strong>Equal Importance</strong><br>
|
||||
Kedua elemen sama pentingnya
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-start">
|
||||
<span class="badge bg-primary rounded-pill me-3">3</span>
|
||||
<div>
|
||||
<strong>Moderate Importance</strong><br>
|
||||
Salah satu elemen sedikit lebih penting
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-start">
|
||||
<span class="badge bg-warning text-dark rounded-pill me-3">5</span>
|
||||
<div>
|
||||
<strong>Strong Importance</strong><br>
|
||||
Salah satu elemen lebih penting secara kuat
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-start">
|
||||
<span class="badge bg-danger rounded-pill me-3">7</span>
|
||||
<div>
|
||||
<strong>Very Strong Importance</strong><br>
|
||||
Salah satu elemen sangat penting
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-start">
|
||||
<span class="badge bg-dark rounded-pill me-3">9</span>
|
||||
<div>
|
||||
<strong>Extreme Importance</strong><br>
|
||||
Salah satu elemen mutlak lebih penting
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-start">
|
||||
<span class="badge bg-secondary rounded-pill me-3">2, 4, 6, 8</span>
|
||||
<div>
|
||||
<strong>Intermediate Values</strong><br>
|
||||
Nilai antara dua pertimbangan yang berdekatan
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-lg border-start border-4 border-primary mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-primary fw-bold mb-0 d-flex justify-content-between align-items-center">
|
||||
📋 Langkah-langkah Mengisi Data <span class="text-muted">| Aplikasi</span>
|
||||
<button class="btn btn-sm btn-outline-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSteps" aria-expanded="false" aria-controls="collapseSteps">
|
||||
Lihat Detail
|
||||
</button>
|
||||
</h5>
|
||||
|
||||
<div class="collapse mt-3" id="collapseSteps">
|
||||
<p class="fs-6">Berikut adalah langkah-langkah yang harus diikuti untuk mengisi data dalam aplikasi ini. Pastikan Anda mengikuti setiap langkah untuk mendapatkan hasil yang tepat.</p>
|
||||
|
||||
<div class="row row-cols-1 row-cols-md-2 g-3 mt-2">
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-start">
|
||||
<span class="badge bg-success rounded-pill me-3">1</span>
|
||||
<div>
|
||||
<strong>Masukkan Data Makanan</strong><br>
|
||||
Pilih kategori makanan dan masukkan informasi seperti lemak, natrium, energi, dan karbohidrat.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-start">
|
||||
<span class="badge bg-primary rounded-pill me-3">2</span>
|
||||
<div>
|
||||
<strong>Input Kriteria</strong><br>
|
||||
Tentukan kriteria yang relevan untuk perbandingan antar makanan, seperti kandungan gizi dan kecocokan dengan kebutuhan diet.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-start">
|
||||
<span class="badge bg-warning text-dark rounded-pill me-3">3</span>
|
||||
<div>
|
||||
<strong>Perbandingan Berpasangan</strong><br>
|
||||
Bandingkan alternatif makanan menggunakan skala penilaian untuk menentukan prioritas berdasarkan kriteria.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-start">
|
||||
<span class="badge bg-danger rounded-pill me-3">4</span>
|
||||
<div>
|
||||
<strong>Normalisasi Data</strong><br>
|
||||
Lakukan normalisasi terhadap data yang telah dimasukkan untuk memastikan konsistensi perbandingan antar makanan.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-start">
|
||||
<span class="badge bg-dark rounded-pill me-3">5</span>
|
||||
<div>
|
||||
<strong>Simpan dan Hasilkan Rekomendasi</strong><br>
|
||||
Setelah data selesai diinput dan dinormalisasi, simpan perubahan dan hasilkan rekomendasi menu makanan berdasarkan analisis AHP.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-start">
|
||||
<span class="badge bg-secondary rounded-pill me-3">6</span>
|
||||
<div>
|
||||
<strong>Evaluasi dan Sesuaikan</strong><br>
|
||||
Evaluasi rekomendasi yang diberikan dan sesuaikan jika diperlukan berdasarkan kebutuhan diet atau preferensi pengguna.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Recent Sales -->
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<div class="card recent-sales overflow-auto">
|
||||
|
||||
<div class="filter">
|
||||
<a class="icon" href="#" data-bs-toggle="dropdown"><i class="bi bi-three-dots"></i></a>
|
||||
<ul class="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
|
||||
<li class="dropdown-header text-start">
|
||||
<h6>Filter</h6>
|
||||
</li>
|
||||
|
||||
<li><a class="dropdown-item" href="#">Today</a></li>
|
||||
<li><a class="dropdown-item" href="#">This Month</a></li>
|
||||
<li><a class="dropdown-item" href="#">This Year</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Recent Sales <span>| Today</span></h5>
|
||||
|
||||
<table class="table table-borderless datatable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Customer</th>
|
||||
<th scope="col">Product</th>
|
||||
<th scope="col">Price</th>
|
||||
<th scope="col">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row"><a href="#">#2457</a></th>
|
||||
<td>Brandon Jacob</td>
|
||||
<td><a href="#" class="text-primary">At praesentium minu</a></td>
|
||||
<td>$64</td>
|
||||
<td><span class="badge bg-success">Approved</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><a href="#">#2147</a></th>
|
||||
<td>Bridie Kessler</td>
|
||||
<td><a href="#" class="text-primary">Blanditiis dolor omnis similique</a></td>
|
||||
<td>$47</td>
|
||||
<td><span class="badge bg-warning">Pending</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><a href="#">#2049</a></th>
|
||||
<td>Ashleigh Langosh</td>
|
||||
<td><a href="#" class="text-primary">At recusandae consectetur</a></td>
|
||||
<td>$147</td>
|
||||
<td><span class="badge bg-success">Approved</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><a href="#">#2644</a></th>
|
||||
<td>Angus Grady</td>
|
||||
<td><a href="#" class="text-primar">Ut voluptatem id earum et</a></td>
|
||||
<td>$67</td>
|
||||
<td><span class="badge bg-danger">Rejected</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><a href="#">#2644</a></th>
|
||||
<td>Raheem Lehner</td>
|
||||
<td><a href="#" class="text-primary">Sunt similique distinctio</a></td>
|
||||
<td>$165</td>
|
||||
<td><span class="badge bg-success">Approved</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div><!-- End Recent Sales -->
|
||||
</div>
|
||||
</div><!-- End Left side columns -->
|
||||
|
||||
<!-- Right side columns -->
|
||||
<div class="col-lg-4">
|
||||
|
||||
<!-- Recent Activity -->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Website Traffic -->
|
||||
<div class="card">
|
||||
<div class="card-body pb-0">
|
||||
<h5 class="card-title">Rekomendasi Tertinggi</h5>
|
||||
<div id="trafficChart" style="min-height: 400px;" class="echart"></div>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const chart = echarts.init(document.querySelector("#trafficChart"));
|
||||
const chartData = @json($chartData);
|
||||
|
||||
chart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
top: '5%',
|
||||
left: 'center'
|
||||
},
|
||||
series: [{
|
||||
name: 'Rekomendasi Makanan',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '18',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: chartData
|
||||
}]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-tachometer-alt me-2"></i>Dashboard
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Dashboard</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistics Overview -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="feature-icon bg-primary bg-gradient">
|
||||
<i class="fas fa-users"></i>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h6 class="mb-1">Total Users</h6>
|
||||
<h4 class="mb-0">{{ $userCount ?? 0 }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="feature-icon bg-success bg-gradient">
|
||||
<i class="fas fa-utensils"></i>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h6 class="mb-1">Total Foods</h6>
|
||||
<h4 class="mb-0">{{ $makananCount ?? 0 }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="feature-icon bg-warning bg-gradient">
|
||||
<i class="fas fa-clock"></i>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h6 class="mb-1">Meal Times</h6>
|
||||
<h4 class="mb-0">{{ $waktuMakanCount ?? 0 }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="feature-icon bg-info bg-gradient">
|
||||
<i class="fas fa-chart-pie"></i>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h6 class="mb-1">Components</h6>
|
||||
<h4 class="mb-0">{{ $komponenCount ?? 0 }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Features Overview -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-th-large me-2"></i>Daftar Rekomendasi AHP
|
||||
</h5>
|
||||
<div class="row g-4">
|
||||
@foreach($waktuMakans as $waktuMakan)
|
||||
@php
|
||||
$isNewlyCalculated = $waktuMakan->latest_calculation &&
|
||||
\Carbon\Carbon::parse($waktuMakan->latest_calculation)->isToday();
|
||||
@endphp
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="meal-card {{ $isNewlyCalculated ? 'newly-calculated' : '' }}">
|
||||
<div class="meal-header {{ $isNewlyCalculated ? 'bg-gradient-success' : 'bg-gradient-primary' }}">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-clock me-2"></i>{{ $waktuMakan->nama }}
|
||||
</h6>
|
||||
@if($waktuMakan->has_recommendation)
|
||||
<span class="status-badge bg-light text-dark">
|
||||
<i class="fas fa-check me-1"></i>Terhitung
|
||||
</span>
|
||||
@else
|
||||
<span class="status-badge bg-warning">
|
||||
<i class="fas fa-hourglass-half me-1"></i>Pending
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="meal-content">
|
||||
@if($waktuMakan->latest_calculation)
|
||||
<div class="info-row">
|
||||
<i class="fas fa-calendar me-2"></i>
|
||||
<span>{{ \Carbon\Carbon::parse($waktuMakan->latest_calculation)->format('d M Y H:i') }}</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="meal-actions">
|
||||
@if($waktuMakan->has_recommendation)
|
||||
<a href="{{ route('rekomendasi.detail', $waktuMakan->id) }}"
|
||||
class="btn btn-sm btn-primary">
|
||||
<i class="fas fa-eye me-1"></i>Detail
|
||||
</a>
|
||||
@endif
|
||||
<a href="{{ route('alternatif.pilih', ['waktu_makan' => $waktuMakan->id]) }}"
|
||||
class="btn btn-sm {{ $waktuMakan->has_recommendation ? 'btn-outline-primary' : 'btn-primary' }}">
|
||||
<i class="fas fa-calculator me-1"></i>
|
||||
{{ $waktuMakan->has_recommendation ? 'Hitung Ulang' : 'Hitung' }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- End Right side columns -->
|
||||
<!-- AHP Guide -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-lg-6">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-scale-balanced me-2"></i>AHP Scale Guide
|
||||
</h5>
|
||||
<div class="row g-3">
|
||||
<div class="col-sm-6">
|
||||
<div class="d-flex align-items-center p-3 bg-light rounded">
|
||||
<span class="admin-badge bg-success me-3">1</span>
|
||||
<div>
|
||||
<strong>Equal Importance</strong><br>
|
||||
<small class="text-muted">Kedua elemen sama pentingnya</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="d-flex align-items-center p-3 bg-light rounded">
|
||||
<span class="admin-badge bg-primary me-3">3</span>
|
||||
<div>
|
||||
<strong>Moderate</strong><br>
|
||||
<small class="text-muted">Sedikit lebih penting</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="d-flex align-items-center p-3 bg-light rounded">
|
||||
<span class="admin-badge bg-warning me-3">5</span>
|
||||
<div>
|
||||
<strong>Strong</strong><br>
|
||||
<small class="text-muted">Lebih penting</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="d-flex align-items-center p-3 bg-light rounded">
|
||||
<span class="admin-badge bg-danger me-3">7</span>
|
||||
<div>
|
||||
<strong>Very Strong</strong><br>
|
||||
<small class="text-muted">Sangat penting</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-list-check me-2"></i>Quick Guide
|
||||
</h5>
|
||||
<div class="steps">
|
||||
<div class="step d-flex align-items-start mb-4">
|
||||
<span class="admin-badge bg-primary me-3">1</span>
|
||||
<div>
|
||||
<h6 class="mb-2">Data Master</h6>
|
||||
<p class="text-muted mb-0">Input kriteria, komponen, dan waktu makan</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step d-flex align-items-start mb-4">
|
||||
<span class="admin-badge bg-primary me-3">2</span>
|
||||
<div>
|
||||
<h6 class="mb-2">Pengaturan</h6>
|
||||
<p class="text-muted mb-0">Atur relasi antar komponen</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step d-flex align-items-start">
|
||||
<span class="admin-badge bg-primary me-3">3</span>
|
||||
<div>
|
||||
<h6 class="mb-2">Analisis</h6>
|
||||
<p class="text-muted mb-0">Lakukan perhitungan dan review hasil</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-history me-2"></i>Recent Activity
|
||||
</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>User</th>
|
||||
<th>Activity</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>10:30</td>
|
||||
<td>Admin</td>
|
||||
<td>Updated food database</td>
|
||||
<td><span class="admin-badge bg-success">Completed</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11:15</td>
|
||||
<td>User</td>
|
||||
<td>Generated recommendations</td>
|
||||
<td><span class="admin-badge bg-warning">Processing</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12:00</td>
|
||||
<td>System</td>
|
||||
<td>Backup completed</td>
|
||||
<td><span class="admin-badge bg-info">Info</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
/* Status Cards */
|
||||
.status-card {
|
||||
padding: 1.5rem;
|
||||
border-radius: 1rem;
|
||||
color: white;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.status-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
font-size: 2rem;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.status-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.status-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.status-footer {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 0.875rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Gradients */
|
||||
.bg-gradient-primary {
|
||||
background: linear-gradient(135deg, #2196f3, #1976d2);
|
||||
}
|
||||
|
||||
.bg-gradient-success {
|
||||
background: linear-gradient(135deg, #4caf50, #388e3c);
|
||||
}
|
||||
|
||||
.bg-gradient-warning {
|
||||
background: linear-gradient(135deg, #ff9800, #f57c00);
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.status-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.meal-card {
|
||||
background: white;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.meal-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.meal-header {
|
||||
padding: 1rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.meal-header h6 {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.meal-content {
|
||||
padding: 1rem;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.meal-actions {
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
background: white;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.newly-calculated {
|
||||
border: 2px solid #4caf50;
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.meal-header {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.meal-content, .meal-actions {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
@endsection
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="pagetitle">
|
||||
<h1>Normalisasi Alternatif</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('alternatif.pilih') }}">Pemilihan Alternatif</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('alternatif.perbandingan') }}">Perbandingan Alternatif</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('alternatif.normalisasi') }}">Normalisasi Alternatif</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<section class="section">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@foreach ($kriterias as $kriteria)
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ $kriteria->nama }}</h5>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Alternatif</th>
|
||||
@foreach ($alternatifs as $alt)
|
||||
<th>{{ $alt->nama }}</th>
|
||||
@endforeach
|
||||
<th>Jumlah</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@php
|
||||
$jumlahKolom = [];
|
||||
foreach ($alternatifs as $colAlt) {
|
||||
$jumlahKolom[$colAlt->id] = 0;
|
||||
}
|
||||
|
||||
// hitung jumlah kolom
|
||||
foreach ($alternatifs as $rowAlt) {
|
||||
foreach ($alternatifs as $colAlt) {
|
||||
$jumlahKolom[$colAlt->id] += $matriks[$kriteria->id][$rowAlt->id][$colAlt->id];
|
||||
}
|
||||
}
|
||||
@endphp
|
||||
|
||||
@foreach ($alternatifs as $rowAlt)
|
||||
<tr>
|
||||
<th>{{ $rowAlt->nama }}</th>
|
||||
@php $jumlahBaris = 0; @endphp
|
||||
@foreach ($alternatifs as $colAlt)
|
||||
@php
|
||||
$val = $matriks[$kriteria->id][$rowAlt->id][$colAlt->id];
|
||||
$normalized = $val / $jumlahKolom[$colAlt->id];
|
||||
$jumlahBaris += $normalized;
|
||||
@endphp
|
||||
<td>{{ number_format($normalized, 4) }}</td>
|
||||
@endforeach
|
||||
<td><strong>{{ number_format($jumlahBaris, 4) }}</strong></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<!-- Form untuk menyimpan hasil normalisasi -->
|
||||
<form action="{{ route('alternatif.simpanNormalisasi') }}" method="POST">
|
||||
@csrf
|
||||
<div class="text-center mb-5">
|
||||
<button type="submit" class="btn btn-success">Simpan Normalisasi</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@endsection
|
||||
|
|
@ -1,80 +1,480 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Perbandingan Alternatif')
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
margin-bottom: 2rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: linear-gradient(45deg, #0d6efd, #0dcaf0);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 15px 15px 0 0 !important;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.table td {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.table-hover tbody tr:hover {
|
||||
background-color: rgba(0,0,0,.075);
|
||||
}
|
||||
|
||||
.score-cell {
|
||||
font-weight: 500;
|
||||
color: #0d6efd;
|
||||
}
|
||||
|
||||
.highest-score {
|
||||
background-color: #d1e7dd !important;
|
||||
color: #0f5132;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background-color: #e8f4f8;
|
||||
border-color: #d6e9f9;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.komponen-section {
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
margin-bottom: 2rem;
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.komponen-section:hover {
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.komponen-header {
|
||||
background: linear-gradient(45deg, #0d6efd, #0dcaf0);
|
||||
color: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: 15px 15px 0 0;
|
||||
}
|
||||
|
||||
.komponen-body {
|
||||
background: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: 0 0 15px 15px;
|
||||
}
|
||||
|
||||
.rekomendasi-section {
|
||||
background: #d1e7dd;
|
||||
border-radius: 10px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.rekomendasi-section:hover {
|
||||
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.rekomendasi-section h6 {
|
||||
color: #0f5132;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.rekomendasi-section h6 i {
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.rekomendasi-list {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.rekomendasi-list li {
|
||||
padding: 1rem;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 0.75rem;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.rekomendasi-list li:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.rekomendasi-list li i {
|
||||
margin-right: 0.75rem;
|
||||
color: #0f5132;
|
||||
}
|
||||
|
||||
.rekomendasi-list li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.breadcrumb-item a {
|
||||
color: #0d6efd;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.breadcrumb-item.active {
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
#loadingOverlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9999;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.loading-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
color: white;
|
||||
background: rgba(13, 110, 253, 0.9);
|
||||
padding: 2rem;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.table-responsive {
|
||||
border-radius: 10px;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.card-header, .komponen-header {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.card-body, .komponen-body {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.table td, .table th {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.rekomendasi-section {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.rekomendasi-list li {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Consistency Ratio Cards */
|
||||
.cr-card {
|
||||
padding: 1rem;
|
||||
border-radius: 0.75rem;
|
||||
color: white;
|
||||
height: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.cr-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.cr-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.cr-value {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.cr-status {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.bg-success {
|
||||
background: linear-gradient(135deg, #4caf50, #388e3c);
|
||||
}
|
||||
|
||||
.bg-warning {
|
||||
background: linear-gradient(135deg, #ff9800, #f57c00);
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="pagetitle">
|
||||
<h1>Perbandingan Alternatif</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('alternatif.pilih') }}">Pemilihan Alternatif</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('alternatif.perbandingan') }}">Perbandingan Alternatif</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<section class="section">
|
||||
<div class="content-wrapper">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="col-12">
|
||||
<!-- Page Title -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="card-title mb-0">Hasil Normalisasi Kriteria</h4>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('alternatif.pilih') }}">Pilih Alternatif</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('alternatif.view') }}">Lihat Alternatif</a></li>
|
||||
<li class="breadcrumb-item active">Perbandingan</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success">{{ session('success') }}</div>
|
||||
@if(session('error'))
|
||||
<div class="alert alert-danger">
|
||||
<i class="fas fa-exclamation-circle me-2"></i>
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Matriks Perbandingan Alternatif</h5>
|
||||
<div class="alert alert-info mb-4">
|
||||
<h5 class="alert-heading mb-2">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
Informasi Kriteria
|
||||
</h5>
|
||||
<ul class="mb-0">
|
||||
<li>Untuk kriteria cost (Lemak dan Natrium): Nilai yang lebih kecil lebih baik</li>
|
||||
<li>Untuk kriteria benefit (Energi dan Karbohidrat): Nilai yang lebih besar lebih baik</li>
|
||||
<li>Total normalisasi untuk setiap kriteria selalu berjumlah 1.0000</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('alternatif.simpan') }}" method="POST">
|
||||
@csrf
|
||||
@php
|
||||
$alternatifsByKomponen = session('alternatifs_by_komponen');
|
||||
@endphp
|
||||
|
||||
@foreach ($kriterias as $kriteria)
|
||||
<h6 class="mt-4">{{ $kriteria->nama }}</h6>
|
||||
@foreach($alternatifsByKomponen as $komponenId => $komponenData)
|
||||
<div class="komponen-section mb-4">
|
||||
<div class="komponen-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-utensils me-2"></i>
|
||||
{{ $komponenData['nama_komponen'] }}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="komponen-body">
|
||||
<!-- Consistency Ratio Section -->
|
||||
<div class="consistency-info mb-4">
|
||||
<h6 class="section-title mb-3">
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
Consistency Ratio (CR) per Kriteria
|
||||
</h6>
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
Perhitungan CR sementara dinonaktifkan untuk troubleshooting.
|
||||
</div>
|
||||
<!-- <div class="row g-3">
|
||||
@foreach($kriterias as $kriteria)
|
||||
@php
|
||||
$crData = \App\Models\ConsistencyRatioAlternatif::where([
|
||||
'kriteria_id' => $kriteria->id,
|
||||
'komponen_id' => $komponenId,
|
||||
'waktu_makan_id' => session('hasil_rekomendasi.waktu_makan_id')
|
||||
])->first();
|
||||
@endphp
|
||||
<div class="col-md-3">
|
||||
<div class="cr-card {{ $crData && $crData->is_consistent ? 'bg-success' : 'bg-warning' }}">
|
||||
<div class="cr-title">{{ $kriteria->nama }}</div>
|
||||
<div class="cr-value">
|
||||
CR: {{ $crData ? number_format($crData->cr, 4) : 'N/A' }}
|
||||
</div>
|
||||
<div class="cr-status">
|
||||
@if($crData && $crData->is_consistent)
|
||||
<i class="fas fa-check-circle me-1"></i> Konsisten
|
||||
@else
|
||||
<i class="fas fa-exclamation-circle me-1"></i> Perlu Review
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- Normalization Table -->
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered text-center">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Alternatif</th>
|
||||
@foreach ($alternatifs as $alt)
|
||||
<th>{{ $alt->nama }}</th>
|
||||
@endforeach
|
||||
<th style="width: 60px">No</th>
|
||||
<th>Nama Makanan</th>
|
||||
<th>Lemak (Normalisasi)</th>
|
||||
<th>Natrium (Normalisasi)</th>
|
||||
<th>Energi (Normalisasi)</th>
|
||||
<th>Karbohidrat (Normalisasi)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($alternatifs as $alt1)
|
||||
<tr>
|
||||
<th>{{ $alt1->nama }}</th>
|
||||
@foreach ($alternatifs as $alt2)
|
||||
<td>
|
||||
@if ($alt1->id == $alt2->id)
|
||||
1
|
||||
@elseif ($alt1->id < $alt2->id)
|
||||
<input type="number" step="0.01" min="0.01"
|
||||
name="nilai[{{ $kriteria->id }}][{{ $alt1->id }}][{{ $alt2->id }}]"
|
||||
class="form-control" required>
|
||||
@else
|
||||
-
|
||||
@endif
|
||||
</td>
|
||||
@endforeach
|
||||
@php
|
||||
$no = 1;
|
||||
// Hitung total untuk setiap kriteria
|
||||
$totalLemakInvers = array_sum(array_column($komponenData['alternatifs'], 'lemak_invers'));
|
||||
$totalNatriumInvers = array_sum(array_column($komponenData['alternatifs'], 'natrium_invers'));
|
||||
$totalEnergi = array_sum(array_column($komponenData['alternatifs'], 'energi'));
|
||||
$totalKarbohidrat = array_sum(array_column($komponenData['alternatifs'], 'karbohidrat'));
|
||||
@endphp
|
||||
|
||||
@foreach($komponenData['alternatifs'] as $alternatif)
|
||||
<tr>
|
||||
<td>{{ $no++ }}</td>
|
||||
<td class="text-start">{{ $alternatif['nama'] }}</td>
|
||||
<td class="score-cell">{{ number_format($alternatif['lemak_invers'] / $totalLemakInvers, 4) }}</td>
|
||||
<td class="score-cell">{{ number_format($alternatif['natrium_invers'] / $totalNatriumInvers, 4) }}</td>
|
||||
<td class="score-cell">{{ number_format($alternatif['energi'] / $totalEnergi, 4) }}</td>
|
||||
<td class="score-cell">{{ number_format($alternatif['karbohidrat'] / $totalKarbohidrat, 4) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
|
||||
<tr>
|
||||
<td colspan="2" class="text-end fw-bold">Total</td>
|
||||
<td class="fw-bold">1.0000</td>
|
||||
<td class="fw-bold">1.0000</td>
|
||||
<td class="fw-bold">1.0000</td>
|
||||
<td class="fw-bold">1.0000</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<div class="text-end mt-4">
|
||||
<button type="submit" name="action" value="simpan" class="btn btn-success">Simpan Saja</button>
|
||||
<button type="submit" name="action" value="lanjut" class="btn btn-primary">Simpan & Lanjut</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<!-- Form untuk menyimpan normalisasi -->
|
||||
<form action="{{ route('alternatif.simpan.normalisasi') }}" method="POST" id="formNormalisasi">
|
||||
@csrf
|
||||
<!-- Simpan semua data dalam satu input JSON -->
|
||||
<input type="hidden" name="normalisasi_data_json" value="{{ json_encode([
|
||||
'data' => collect($alternatifsByKomponen)->map(function($komponenData, $komponenId) use ($totalLemakInvers, $totalNatriumInvers, $totalEnergi, $totalKarbohidrat) {
|
||||
return [
|
||||
'nama_komponen' => $komponenData['nama_komponen'],
|
||||
'alternatifs' => collect($komponenData['alternatifs'])->map(function($alternatif) use ($totalLemakInvers, $totalNatriumInvers, $totalEnergi, $totalKarbohidrat) {
|
||||
return [
|
||||
'id' => $alternatif['id'],
|
||||
'nama' => $alternatif['nama'],
|
||||
'lemak_normalized' => $alternatif['lemak_invers'] / $totalLemakInvers,
|
||||
'natrium_normalized' => $alternatif['natrium_invers'] / $totalNatriumInvers,
|
||||
'energi_normalized' => $alternatif['energi'] / $totalEnergi,
|
||||
'karbohidrat_normalized' => $alternatif['karbohidrat'] / $totalKarbohidrat
|
||||
];
|
||||
})->values()->all()
|
||||
];
|
||||
})->all()
|
||||
]) }}">
|
||||
|
||||
<div class="text-center mt-4 mb-5">
|
||||
<button type="button" id="btnSimpanNormalisasi" class="btn btn-success btn-lg">
|
||||
<i class="fas fa-save me-2"></i>Simpan dan Lanjutkan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Loading Overlay -->
|
||||
<div id="loadingOverlay">
|
||||
<div class="loading-content">
|
||||
<div class="spinner-border text-light loading-spinner" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<h5 class="mb-0">Memproses Data...</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Initialize tooltips
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||
});
|
||||
|
||||
// Animate on scroll
|
||||
AOS.init({
|
||||
duration: 800,
|
||||
easing: 'ease-in-out',
|
||||
once: true,
|
||||
mirror: false
|
||||
});
|
||||
|
||||
// Handle form submission with confirmation
|
||||
$('#btnSimpanNormalisasi').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Konfirmasi sebelum submit
|
||||
Swal.fire({
|
||||
title: 'Konfirmasi',
|
||||
text: 'Apakah Anda yakin data perbandingan ini sudah benar dan ingin disimpan untuk proses normalisasi?',
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Ya, Simpan',
|
||||
cancelButtonText: 'Batal',
|
||||
reverseButtons: true
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
// Tampilkan loading overlay
|
||||
$('#loadingOverlay').fadeIn();
|
||||
// Submit form secara normal
|
||||
$('#formNormalisasi').submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -1,134 +1,411 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Pilih Alternatif')
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.card {
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.komponen-card {
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.komponen-card .card-header {
|
||||
background: linear-gradient(45deg, #0d6efd, #0dcaf0);
|
||||
padding: 1rem;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.makanan-card {
|
||||
border-radius: 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.makanan-card .card-body {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.custom-control-input:checked ~ .custom-control-label::before {
|
||||
background-color: #198754;
|
||||
border-color: #198754;
|
||||
}
|
||||
|
||||
.nutrition-list {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.nutrition-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.nutrition-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-box .form-control {
|
||||
padding-left: 2.5rem;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.search-box i {
|
||||
position: absolute;
|
||||
left: 1rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
background: #fff;
|
||||
padding: 1.5rem;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.rekomendasi-section {
|
||||
background: #d1e7dd;
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.rekomendasi-section h6 {
|
||||
color: #0f5132;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.rekomendasi-list {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.rekomendasi-list li {
|
||||
padding: 0.5rem;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-calculate {
|
||||
padding: 1rem 2rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-calculate:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.col-md-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<h1>Pilih Alternatif</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<!-- Page Title -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="font-weight-bold">Pilih Alternatif Makanan</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active">Pilih Alternatif</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<section class="section">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<!-- Validation Alert -->
|
||||
<div class="alert alert-info alert-dismissible fade show mb-4" role="alert">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
<strong>Penting!</strong> Pilih minimal 4 alternatif makanan untuk setiap komponen untuk perbandingan yang optimal.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Pilih Alternatif (6 makanan)</h5>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success">{{ session('success') }}</div>
|
||||
@endif
|
||||
|
||||
{{-- Form Filter & Search --}}
|
||||
{{-- Input pencarian (lebih pendek) --}}
|
||||
<form method="GET" class="d-flex align-items-end flex-wrap gap-2 mb-4">
|
||||
{{-- Input pencarian --}}
|
||||
<div style="min-width: 100px; max-width: 170px;">
|
||||
<input type="text" name="q" class="form-control form-control-sm" placeholder="Cari nama makanan..." value="{{ $search }}">
|
||||
</div>
|
||||
{{-- Dropdown kategori --}}
|
||||
<div style="min-width: 150px; max-width: 180px;">
|
||||
<select name="kategori" class="form-select form-select-sm">
|
||||
<option value="">Semua Kategori</option>
|
||||
@foreach($kategoris as $kategori)
|
||||
<option value="{{ $kategori->id }}" {{ ($kategoriFilter == $kategori->id) ? 'selected' : '' }}>
|
||||
{{ $kategori->kategori }}
|
||||
<!-- Filter Section -->
|
||||
<div class="filter-section">
|
||||
<form method="GET" action="{{ route('alternatif.pilih') }}" class="mb-4">
|
||||
<div class="row align-items-end">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="waktu_makan_id" class="form-label">Waktu Makan</label>
|
||||
<select name="waktu_makan_id" id="waktu_makan_id" class="form-select" required>
|
||||
<option value="">Pilih Waktu Makan</option>
|
||||
@foreach($waktuMakans as $waktu)
|
||||
<option value="{{ $waktu->id }}" {{ $waktuMakanId == $waktu->id ? 'selected' : '' }}>
|
||||
{{ $waktu->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
{{-- Dropdown urutkan --}}
|
||||
<div style="min-width: 150px;">
|
||||
<select name="sort" class="form-select form-select-sm">
|
||||
<option value="">Semua Kriteria</option>
|
||||
<option value="lemak" {{ $sort == 'lemak' ? 'selected' : '' }}>Lemak</option>
|
||||
<option value="natrium" {{ $sort == 'natrium' ? 'selected' : '' }}>Natrium</option>
|
||||
<option value="energi" {{ $sort == 'energi' ? 'selected' : '' }}>Energi</option>
|
||||
<option value="karbohidrat" {{ $sort == 'karbohidrat' ? 'selected' : '' }}>Karbohidrat</option>
|
||||
</select>
|
||||
</div>
|
||||
{{-- Dropdown order --}}
|
||||
<div style="min-width: 140px;">
|
||||
<select name="order" class="form-select form-select-sm">
|
||||
<option value="desc" {{ $order == 'desc' ? 'selected' : '' }}>Tertinggi</option>
|
||||
<option value="asc" {{ $order == 'asc' ? 'selected' : '' }}>Terendah</option>
|
||||
</select>
|
||||
</div>
|
||||
{{-- Tombol --}}
|
||||
<div class="d-flex gap-2" style="min-width: 140px;">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Terapkan</button>
|
||||
<a href="{{ route('alternatif.pilih') }}" class="btn btn-secondary btn-sm">Reset</a>
|
||||
</div>
|
||||
</form>
|
||||
{{-- Form Pemilihan Alternatif --}}
|
||||
<form action="{{ route('alternatif.pilih.simpan') }}" method="POST">
|
||||
@csrf
|
||||
<div class="row">
|
||||
@foreach ($makanans as $makanan)
|
||||
<div class="col-12 col-sm-6 col-lg-4 mb-4">
|
||||
<div class="card h-100 border-0 shadow-sm hover-shadow" style="transition: 0.3s;">
|
||||
<div class="card-body d-flex align-items-start gap-2">
|
||||
<input
|
||||
class="form-check-input mt-1 alternatif-checkbox"
|
||||
type="checkbox"
|
||||
name="alternatifs[]"
|
||||
value="{{ $makanan->id }}"
|
||||
id="alt{{ $makanan->id }}"
|
||||
style="transform: scale(1.2);"
|
||||
>
|
||||
<label class="form-check-label ms-2 w-100" for="alt{{ $makanan->id }}">
|
||||
<h6 class="fw-bold mb-1">{{ $makanan->nama }}</h6>
|
||||
<ul class="list-unstyled small text-muted mb-0">
|
||||
<li><i class="bi bi-droplet-half me-1"></i> Lemak: {{ $makanan->lemak }} g</li>
|
||||
<li><i class="bi bi-shield-check me-1"></i> Natrium: {{ $makanan->natrium }} mg</li>
|
||||
<li><i class="bi bi-lightning me-1"></i> Energi: {{ $makanan->energi }} kal</li>
|
||||
<li><i class="bi bi-activity me-1"></i> Karbohidrat: {{ $makanan->karbohidrat }} g</li>
|
||||
</ul>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<button type="submit" class="btn btn-success btn-lg px-4 mt-3" id="submitBtn" disabled>
|
||||
<i class="bi bi-arrow-right-circle-fill me-1"></i> Lanjutkan
|
||||
<div class="col-md-6">
|
||||
<div class="form-group search-box">
|
||||
<label for="q" class="form-label">Cari Makanan</label>
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" name="q" id="q" class="form-control"
|
||||
value="{{ $search ?? '' }}" placeholder="Ketik nama makanan...">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="fas fa-filter me-2"></i>Filter
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@if($waktuMakanId)
|
||||
<!-- Form Pilih Alternatif -->
|
||||
<form action="{{ route('alternatif.pilih.simpan') }}" method="POST" id="formPilihAlternatif">
|
||||
@csrf
|
||||
<input type="hidden" name="waktu_makan_id" value="{{ $waktuMakanId }}">
|
||||
|
||||
<!-- Komponen Cards -->
|
||||
@foreach($komponens as $komponen)
|
||||
<div class="komponen-card">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<h5 class="card-title mb-0 text-white d-flex align-items-center">
|
||||
<i class="fas fa-utensils me-2"></i>{{ $komponen->nama }}
|
||||
</h5>
|
||||
<div class="form-check ms-auto">
|
||||
<input class="form-check-input pilih-semua"
|
||||
type="checkbox"
|
||||
data-komponen-id="{{ $komponen->id }}"
|
||||
id="pilihSemua{{ $komponen->id }}">
|
||||
<label class="form-check-label text-white ms-2" for="pilihSemua{{ $komponen->id }}">
|
||||
<strong>Pilih Semua</strong>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body bg-white">
|
||||
<!-- Hidden input for komponen_id -->
|
||||
<input type="hidden" name="komponen_ids[]" value="{{ $komponen->id }}" class="komponen-input">
|
||||
|
||||
<!-- Rekomendasi Ahli Section -->
|
||||
@php
|
||||
$rekomendasiKomponen = $rekomendasiAhli->where('komponen_id', $komponen->id)
|
||||
->where('waktu_makan_id', $waktuMakanId);
|
||||
@endphp
|
||||
|
||||
@if($rekomendasiKomponen->isNotEmpty())
|
||||
<div class="rekomendasi-section">
|
||||
<h6><i class="fas fa-lightbulb me-2"></i>Rekomendasi Ahli:</h6>
|
||||
<ul class="rekomendasi-list">
|
||||
@foreach($rekomendasiKomponen as $rekomendasi)
|
||||
<li>{{ $rekomendasi->makanan->nama ?? 'Makanan tidak ditemukan' }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Alternatif Makanan Section -->
|
||||
@php
|
||||
$makananKomponen = $makanans->where('komponen_id', $komponen->id);
|
||||
@endphp
|
||||
|
||||
@if($makananKomponen->isNotEmpty())
|
||||
<div class="row">
|
||||
@foreach($makananKomponen as $makanan)
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="makanan-card card h-100">
|
||||
<div class="card-body">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox"
|
||||
class="custom-control-input komponen-{{ $komponen->id }}"
|
||||
name="alternatifs[]"
|
||||
value="{{ $makanan->id }}"
|
||||
id="makanan{{ $makanan->id }}">
|
||||
<label class="custom-control-label" for="makanan{{ $makanan->id }}">
|
||||
<h6 class="mb-2">{{ $makanan->nama }}</h6>
|
||||
</label>
|
||||
</div>
|
||||
<div class="nutrition-list">
|
||||
<div class="nutrition-item">
|
||||
<span><i class="fas fa-circle-dot me-2"></i>Lemak</span>
|
||||
<span>{{ $makanan->lemak }}g</span>
|
||||
</div>
|
||||
<div class="nutrition-item">
|
||||
<span><i class="fas fa-circle-dot me-2"></i>Natrium</span>
|
||||
<span>{{ $makanan->natrium }}mg</span>
|
||||
</div>
|
||||
<div class="nutrition-item">
|
||||
<span><i class="fas fa-circle-dot me-2"></i>Energi</span>
|
||||
<span>{{ $makanan->energi }}kal</span>
|
||||
</div>
|
||||
<div class="nutrition-item">
|
||||
<span><i class="fas fa-circle-dot me-2"></i>Karbohidrat</span>
|
||||
<span>{{ $makanan->karbohidrat }}g</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
Tidak ada alternatif makanan yang tersedia untuk komponen ini.
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<div class="text-center mt-4 mb-5">
|
||||
<button type="submit" class="btn btn-success btn-lg btn-calculate">
|
||||
<i class="fas fa-calculator me-2"></i>Proses Perhitungan AHP
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@else
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
Silakan pilih Waktu Makan untuk melihat rekomendasi dan alternatif yang tersedia.
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const checkboxes = document.querySelectorAll('.alternatif-checkbox');
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
const max = 6;
|
||||
const min = 6;
|
||||
$(document).ready(function() {
|
||||
// event handler untuk checkbox pilih semua
|
||||
$('.pilih-semua').on('change', function () {
|
||||
let komponenId = $(this).data('komponen-id');
|
||||
let checked = $(this).is(':checked');
|
||||
$(`.komponen-${komponenId}`).prop('checked', checked);
|
||||
updateCounter(komponenId);
|
||||
updateSubmitButton();
|
||||
});
|
||||
|
||||
// Fungsi untuk menghitung jumlah checkbox yang dicentang per komponen
|
||||
function getCheckedCountByKomponen(komponenId) {
|
||||
return $(`.komponen-${komponenId}:checked`).length;
|
||||
}
|
||||
|
||||
// Fungsi untuk update counter di card header
|
||||
function updateCounter(komponenId) {
|
||||
const checkedCount = getCheckedCountByKomponen(komponenId);
|
||||
const cardHeader = $(`.komponen-${komponenId}`).first().closest('.komponen-card').find('.card-title');
|
||||
const originalText = cardHeader.text().replace(/\(\d+\/4\+\)$/, '').trim();
|
||||
cardHeader.text(`${originalText} (${checkedCount}/4+)`);
|
||||
}
|
||||
|
||||
// Fungsi untuk memvalidasi form
|
||||
function validateForm() {
|
||||
let isValid = true;
|
||||
let errorMessages = [];
|
||||
|
||||
// Cek setiap komponen
|
||||
$('.komponen-input').each(function() {
|
||||
const komponenId = $(this).val();
|
||||
const checkedCount = getCheckedCountByKomponen(komponenId);
|
||||
|
||||
if (checkedCount < 4) {
|
||||
isValid = false;
|
||||
const komponenName = $(this).closest('.komponen-card').find('.card-title').text().replace(/\(\d+\/4\+\)$/, '').trim();
|
||||
errorMessages.push(`Pilih minimal 4 alternatif untuk komponen ${komponenName} (saat ini: ${checkedCount})`);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
isValid: isValid,
|
||||
messages: errorMessages
|
||||
};
|
||||
}
|
||||
|
||||
function update() {
|
||||
const checked = document.querySelectorAll('.alternatif-checkbox:checked').length;
|
||||
submitBtn.disabled = !(checked >= min && checked <= max);
|
||||
checkboxes.forEach(cb => {
|
||||
cb.disabled = !cb.checked && checked >= max;
|
||||
// Event handler untuk checkbox
|
||||
$('input[type="checkbox"]').on('change', function() {
|
||||
const komponenId = $(this).attr('class').split(' ').find(cls => cls.startsWith('komponen-'))?.split('-')[1];
|
||||
if (komponenId) {
|
||||
updateCounter(komponenId);
|
||||
updateSubmitButton();
|
||||
}
|
||||
});
|
||||
|
||||
// Update tombol submit berdasarkan validasi
|
||||
function updateSubmitButton() {
|
||||
const validation = validateForm();
|
||||
const submitBtn = $('.btn-calculate');
|
||||
|
||||
if (!validation.isValid) {
|
||||
submitBtn.prop('disabled', true)
|
||||
.attr('title', validation.messages.join('\n'));
|
||||
} else {
|
||||
submitBtn.prop('disabled', false)
|
||||
.attr('title', 'Lanjutkan ke perhitungan AHP');
|
||||
}
|
||||
}
|
||||
|
||||
// Inisialisasi tombol submit dan counter
|
||||
updateSubmitButton();
|
||||
|
||||
// Inisialisasi counter di setiap card header
|
||||
$('.komponen-input').each(function() {
|
||||
const komponenId = $(this).val();
|
||||
updateCounter(komponenId);
|
||||
});
|
||||
|
||||
// Validasi form sebelum submit
|
||||
$('#formPilihAlternatif').on('submit', function(e) {
|
||||
const validation = validateForm();
|
||||
|
||||
if (!validation.isValid) {
|
||||
e.preventDefault();
|
||||
|
||||
Swal.fire({
|
||||
title: 'Peringatan',
|
||||
html: validation.messages.join('<br>'),
|
||||
icon: 'warning',
|
||||
confirmButtonText: 'Mengerti'
|
||||
});
|
||||
}
|
||||
|
||||
checkboxes.forEach(cb => {
|
||||
cb.addEventListener('change', update);
|
||||
});
|
||||
});
|
||||
|
||||
// Animasi smooth scroll untuk cards
|
||||
if (typeof AOS !== 'undefined') {
|
||||
AOS.init({
|
||||
duration: 800,
|
||||
easing: 'ease-in-out',
|
||||
once: true,
|
||||
mirror: false
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -0,0 +1,266 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Lihat Alternatif Terpilih')
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
margin-bottom: 2rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: linear-gradient(45deg, #0d6efd, #0dcaf0);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 15px 15px 0 0 !important;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.table td {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.nutrition-value {
|
||||
font-weight: 500;
|
||||
color: #0d6efd;
|
||||
}
|
||||
|
||||
.invers-value {
|
||||
font-weight: 500;
|
||||
color: #198754;
|
||||
}
|
||||
|
||||
.btn-next {
|
||||
padding: 1rem 2rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-next:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.table-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
margin: 1rem 0;
|
||||
padding: 1rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.table-title i {
|
||||
margin-right: 0.75rem;
|
||||
color: #0d6efd;
|
||||
}
|
||||
|
||||
.table-invers .table-title i {
|
||||
color: #198754;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background-color: #e8f4f8;
|
||||
border-color: #d6e9f9;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.breadcrumb-item a {
|
||||
color: #0d6efd;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.breadcrumb-item.active {
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.table-hover tbody tr:hover {
|
||||
background-color: rgba(0,0,0,.075);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.table-responsive {
|
||||
border-radius: 10px;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.table td, .table th {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="content-wrapper">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<!-- Page Title -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="card-title mb-0">Alternatif Terpilih</h4>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('alternatif.pilih') }}">Pilih Alternatif</a></li>
|
||||
<li class="breadcrumb-item active">Lihat Alternatif</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@php
|
||||
$alternatifsByKomponen = session('alternatifs_by_komponen');
|
||||
$hasilRekomendasi = session('hasil_rekomendasi');
|
||||
@endphp
|
||||
|
||||
@if($alternatifsByKomponen && $hasilRekomendasi)
|
||||
<!-- Info Section -->
|
||||
<div class="alert alert-info mb-4">
|
||||
<h5 class="alert-heading mb-2">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
Informasi Pemilihan
|
||||
</h5>
|
||||
<p class="mb-2">
|
||||
Waktu Makan: <strong>{{ $hasilRekomendasi['waktu_makan_nama'] }}</strong><br>
|
||||
Tanggal: <strong>{{ \Carbon\Carbon::parse($hasilRekomendasi['tanggal_rekomendasi'])->format('d F Y') }}</strong>
|
||||
</p>
|
||||
<hr>
|
||||
<p class="mb-0">
|
||||
<i class="fas fa-lightbulb me-2"></i>
|
||||
Nilai invers digunakan untuk kriteria cost (Lemak dan Natrium), dimana semakin kecil nilainya semakin baik.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Alternatif Cards -->
|
||||
@foreach($alternatifsByKomponen as $komponenId => $komponen)
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-utensils me-2"></i>
|
||||
{{ $komponen['nama_komponen'] }}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Normal Values Table -->
|
||||
<div class="table-normal mb-4">
|
||||
<div class="table-title">
|
||||
<i class="fas fa-table"></i>
|
||||
Nilai Nutrisi
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nama Makanan</th>
|
||||
<th>Lemak (g)</th>
|
||||
<th>Natrium (mg)</th>
|
||||
<th>Energi (kal)</th>
|
||||
<th>Karbohidrat (g)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($komponen['alternatifs'] as $alternatif)
|
||||
<tr>
|
||||
<td>{{ $alternatif['nama'] }}</td>
|
||||
<td class="nutrition-value">{{ number_format($alternatif['lemak'], 2) }}</td>
|
||||
<td class="nutrition-value">{{ number_format($alternatif['natrium'], 2) }}</td>
|
||||
<td class="nutrition-value">{{ number_format($alternatif['energi'], 2) }}</td>
|
||||
<td class="nutrition-value">{{ number_format($alternatif['karbohidrat'], 2) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inverse Values Table -->
|
||||
<div class="table-invers">
|
||||
<div class="table-title">
|
||||
<i class="fas fa-calculator"></i>
|
||||
Nilai Invers (Cost Criteria)
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nama Makanan</th>
|
||||
<th>Lemak (Invers)</th>
|
||||
<th>Natrium (Invers)</th>
|
||||
<th>Energi (kal)</th>
|
||||
<th>Karbohidrat (g)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($komponen['alternatifs'] as $alternatif)
|
||||
<tr>
|
||||
<td>{{ $alternatif['nama'] }}</td>
|
||||
<td class="invers-value">{{ number_format($alternatif['lemak_invers'], 4) }}</td>
|
||||
<td class="invers-value">{{ number_format($alternatif['natrium_invers'], 4) }}</td>
|
||||
<td class="nutrition-value">{{ number_format($alternatif['energi'], 2) }}</td>
|
||||
<td class="nutrition-value">{{ number_format($alternatif['karbohidrat'], 2) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<!-- Next Step Button -->
|
||||
<div class="text-center mt-4 mb-5">
|
||||
<a href="{{ route('alternatif.perbandingan') }}" class="btn btn-success btn-lg btn-next">
|
||||
<i class="fas fa-arrow-right me-2"></i>
|
||||
Lanjut ke Perbandingan
|
||||
</a>
|
||||
</div>
|
||||
@else
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
Tidak ada data alternatif yang dipilih. Silakan pilih alternatif terlebih dahulu.
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -1,65 +1,115 @@
|
|||
@extends('layout.app')
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<h1>Data User</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('datauser') }}"> User</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<section class="section">
|
||||
@section('title', 'User Management')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-users me-2"></i>User Management
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Users</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-2">Data User</h5>
|
||||
<div class="text-center">
|
||||
<a href="{{ route('tambahuser') }}" class="btn btn-primary">Tambah</a>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-list me-2"></i>User List
|
||||
</h5>
|
||||
<a href="{{ route('tambahuser') }}" class="admin-btn btn-primary">
|
||||
<i class="fas fa-plus me-2"></i>Add New User
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Table with stripped rows -->
|
||||
<table class="table datatable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">No</th>
|
||||
<th class="text-center">Name</th>
|
||||
<th class="text-center">Email</th>
|
||||
<th class="text-center">No telp</th>
|
||||
<th class="text-center">Password</th>
|
||||
<th class="text-center">Role</th>
|
||||
<th class="text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($users as $usr)
|
||||
<tr>
|
||||
<td class="text-center">{{ $loop->iteration }}</td>
|
||||
<td class="text-center">{{ $usr->name }}</td>
|
||||
<td class="text-center">{{ $usr->email }}</td>
|
||||
<td class="text-center">{{ $usr->no_telp }}</td>
|
||||
<td class="text-center">****</td> <!-- Tampilkan bintang, bukan password asli -->
|
||||
<td class="text-center">{{ $usr->role->name }}</td> <!-- Display role name -->
|
||||
<td class="text-center">
|
||||
<a href="{{ route('edituser', $usr->id) }}" class="btn btn-sm btn-warning">Edit</a>
|
||||
<form action="{{ route('deleteuser', $usr->id) }}" method="POST" class="d-inline delete-form">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger delete-button">Hapus</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- End Table with stripped rows -->
|
||||
<div class="table-responsive">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
<th>Role</th>
|
||||
<th class="text-center">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($users as $usr)
|
||||
<tr>
|
||||
<td>{{ $loop->iteration }}</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
{{ $usr->name }}
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ $usr->email }}</td>
|
||||
<td>{{ $usr->no_telp }}</td>
|
||||
<td>
|
||||
<span class="admin-badge bg-info">
|
||||
{{ $usr->role->name }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ route('edituser', $usr->id) }}"
|
||||
class="admin-btn btn-warning btn-sm me-2">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<form action="{{ route('deleteuser', $usr->id) }}"
|
||||
method="POST"
|
||||
class="d-inline delete-form"
|
||||
onsubmit="return confirm('Are you sure you want to delete this user?');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="admin-btn btn-danger btn-sm">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -1,113 +1,310 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Edit User')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<h1>Edit Data User</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('datauser') }}">User</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('edituser', $user->id) }}">Edit User</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Edit User</h5>
|
||||
|
||||
<!-- General Form Elements -->
|
||||
<form id="editUserForm" method="POST" action="{{ url("/datauser/{$user->id}") }}">
|
||||
@method('PUT')
|
||||
@csrf
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Name</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="name" value="{{ $user->name }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputEmail" class="col-sm-2 col-form-label">Email</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="email" class="form-control" name="email" value="{{ $user->email }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputNumber" class="col-sm-2 col-form-label">No Telp</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="no_telp" value="{{ $user->no_telp }}">
|
||||
</div>
|
||||
</div>
|
||||
{{-- <div class="row mb-3">
|
||||
<label for="inputPassword" class="col-sm-2 col-form-label">Address</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea class="form-control" style="height: 100px" name="address">{{ $admin->address }}</textarea>
|
||||
</div>
|
||||
</div> --}}
|
||||
<div class="row mb-3">
|
||||
<label for="inputPassword" class="col-sm-2 col-form-label">Password</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="password" class="form-control" name="password" value="{{ $user->password }}"
|
||||
id="passwordField">
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button type="button" class="btn btn-primary" onclick="togglePasswordVisibility()">Show</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="role" class="col-sm-2 col-form-label">Role</label>
|
||||
<div class="col-sm-10">
|
||||
<select id="role" class="form-select" name="role_id">
|
||||
@foreach ($roles as $id => $name)
|
||||
<option value="{{ $id }}" {{ $user->role_id == $id ? 'selected' : '' }}>{{ $name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12 text-center">
|
||||
<a href="{{ route('datauser') }}" class="btn btn-secondary mx-3">Back</a>
|
||||
<button type="button" class="btn btn-primary mx-3" onclick="confirmSave()">Save</button>
|
||||
<!-- Reset button to reset form fields -->
|
||||
<button type="reset" class="btn btn-secondary">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</form><!-- End General Form Elements -->
|
||||
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-user-edit me-2"></i>Edit User
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('datauser') }}" class="text-white-50">Users</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Edit User</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-user-cog me-2"></i>User Information
|
||||
</h5>
|
||||
|
||||
<!-- Confirmation Dialog Script -->
|
||||
<script>
|
||||
function togglePasswordVisibility() {
|
||||
var passwordField = document.getElementById("passwordField");
|
||||
if (passwordField.type === "password") {
|
||||
passwordField.type = "text";
|
||||
document.querySelector('button[onclick="togglePasswordVisibility()"]').textContent = "Hide";
|
||||
} else {
|
||||
passwordField.type = "password";
|
||||
document.querySelector('button[onclick="togglePasswordVisibility()"]').textContent = "Show";
|
||||
}
|
||||
}
|
||||
<form id="editUserForm" method="POST" action="{{ url("/datauser/{$user->id}") }}">
|
||||
@method('PUT')
|
||||
@csrf
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-user me-2"></i>Name
|
||||
</label>
|
||||
<input type="text"
|
||||
class="admin-form-control"
|
||||
name="name"
|
||||
value="{{ $user->name }}"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-envelope me-2"></i>Email
|
||||
</label>
|
||||
<input type="email"
|
||||
class="admin-form-control"
|
||||
name="email"
|
||||
value="{{ $user->email }}"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-phone me-2"></i>Phone Number
|
||||
</label>
|
||||
<input type="text"
|
||||
class="admin-form-control"
|
||||
name="no_telp"
|
||||
value="{{ $user->no_telp }}"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-user-tag me-2"></i>Role
|
||||
</label>
|
||||
<select class="admin-form-control" name="role_id" required>
|
||||
@foreach ($roles as $id => $name)
|
||||
<option value="{{ $id }}" {{ $user->role_id == $id ? 'selected' : '' }}>
|
||||
{{ $name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-key me-2"></i>Password
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="password"
|
||||
class="admin-form-control"
|
||||
name="password"
|
||||
id="passwordField"
|
||||
placeholder="Leave blank to keep current password">
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
onclick="togglePasswordVisibility('passwordField', 'toggleIcon')">
|
||||
<i class="fas fa-eye" id="toggleIcon"></i>
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-muted">Only fill this if you want to change the password</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-key me-2"></i>Confirm Password
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="password"
|
||||
class="admin-form-control"
|
||||
name="password_confirmation"
|
||||
id="confirmPasswordField"
|
||||
placeholder="Confirm new password">
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
onclick="togglePasswordVisibility('confirmPasswordField', 'toggleConfirmIcon')">
|
||||
<i class="fas fa-eye" id="toggleConfirmIcon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-end mt-4">
|
||||
<button type="reset" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-undo me-2"></i>Reset
|
||||
</button>
|
||||
<a href="{{ route('datauser') }}" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>Back
|
||||
</a>
|
||||
<button type="button" class="btn btn-primary" onclick="confirmSave()">
|
||||
<i class="fas fa-save me-2"></i>Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.admin-form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.admin-form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.admin-form-label i {
|
||||
width: 20px;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.admin-form-control {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.admin-form-control:focus {
|
||||
border-color: #2196f3;
|
||||
box-shadow: 0 0 0 0.2rem rgba(33, 150, 243, 0.25);
|
||||
}
|
||||
|
||||
.input-group .btn {
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #dee2e6;
|
||||
background: #f8f9fa;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.input-group .btn:hover {
|
||||
background: #e9ecef;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.input-group .admin-form-control {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.input-group .btn {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.admin-form-control {
|
||||
padding: 0.625rem 0.875rem;
|
||||
}
|
||||
|
||||
.input-group .btn {
|
||||
padding: 0.625rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
function confirmSave() {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "Do you want to save the changes?",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, save it!'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('editUserForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
function togglePasswordVisibility(fieldId, iconId) {
|
||||
var field = document.getElementById(fieldId);
|
||||
var icon = document.getElementById(iconId);
|
||||
|
||||
if (field.type === "password") {
|
||||
field.type = "text";
|
||||
icon.classList.remove('fa-eye');
|
||||
icon.classList.add('fa-eye-slash');
|
||||
} else {
|
||||
field.type = "password";
|
||||
icon.classList.remove('fa-eye-slash');
|
||||
icon.classList.add('fa-eye');
|
||||
}
|
||||
}
|
||||
|
||||
function confirmSave() {
|
||||
// Get password fields
|
||||
const password = document.getElementById('passwordField').value;
|
||||
const confirmPassword = document.getElementById('confirmPasswordField').value;
|
||||
|
||||
// Check if either password field is filled
|
||||
if (password || confirmPassword) {
|
||||
// If one is filled, both must be filled and match
|
||||
if (!password || !confirmPassword) {
|
||||
Swal.fire({
|
||||
title: 'Password Error',
|
||||
text: 'Both password fields must be filled when changing password',
|
||||
icon: 'error',
|
||||
confirmButtonColor: '#2196f3',
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (password !== confirmPassword) {
|
||||
Swal.fire({
|
||||
title: 'Password Mismatch',
|
||||
text: 'Password and confirmation do not match',
|
||||
icon: 'error',
|
||||
confirmButtonColor: '#2196f3',
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
title: 'Save Changes',
|
||||
text: "Are you sure you want to update this user?",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#2196f3',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: '<i class="fas fa-save me-2"></i>Yes, save it!',
|
||||
cancelButtonText: '<i class="fas fa-times me-2"></i>Cancel'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('editUserForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -1,93 +1,300 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Add New User')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
|
||||
<h1>Tambah Data User</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('datauser') }}">User</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('tambahuser') }}">Tambah User</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Tambah User</h5>
|
||||
|
||||
<!-- General Form Elements -->
|
||||
<form id="AddUserForm" method="POST" action="{{ route('storeuser') }}">
|
||||
@csrf <!-- Add this to include CSRF token -->
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Name</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" placeholder="Your name" aria-label="Your Name"
|
||||
aria-describedby="basic-addon1" name="name" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputEmail" class="col-sm-2 col-form-label">Email</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="email" class="form-control" name="email" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputNoTelp" class="col-sm-2 col-form-label">No. Telp</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="no_telp" required>
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-user-plus me-2"></i>Add New User
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('datauser') }}" class="text-white-50">Users</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Add User</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputPassword" class="col-sm-2 col-form-label">Password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" placeholder="Input min 4 characters"
|
||||
aria-label="Password" aria-describedby="basic-addon1" name="password" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label">Role</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-select" name="role_id" aria-label="Select Role" required>
|
||||
<option selected disabled>Select a role</option>
|
||||
@foreach($roles as $role)
|
||||
<option value="{{ $role->id }}">{{ $role->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12 text-center">
|
||||
<a href="{{ route('datauser') }}" class="btn btn-secondary mx-3">Back</a>
|
||||
<button type="button" class="btn btn-primary mx-3" onclick="confirmSave()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form><!-- End General Form Elements -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-user-edit me-2"></i>User Information
|
||||
</h5>
|
||||
|
||||
<form id="AddUserForm" method="POST" action="{{ route('storeuser') }}">
|
||||
@csrf
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-user me-2"></i>Name
|
||||
</label>
|
||||
<input type="text"
|
||||
class="admin-form-control"
|
||||
name="name"
|
||||
placeholder="Enter user's name"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-envelope me-2"></i>Email
|
||||
</label>
|
||||
<input type="email"
|
||||
class="admin-form-control"
|
||||
name="email"
|
||||
placeholder="Enter email address"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-phone me-2"></i>Phone Number
|
||||
</label>
|
||||
<input type="text"
|
||||
class="admin-form-control"
|
||||
name="no_telp"
|
||||
placeholder="Enter phone number"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-user-tag me-2"></i>Role
|
||||
</label>
|
||||
<select class="admin-form-control" name="role_id" required>
|
||||
<option value="" disabled selected>Select a role</option>
|
||||
@foreach($roles as $role)
|
||||
<option value="{{ $role->id }}">{{ $role->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-key me-2"></i>Password
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="password"
|
||||
class="admin-form-control"
|
||||
name="password"
|
||||
id="passwordField"
|
||||
placeholder="Minimum 4 characters"
|
||||
required>
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
onclick="togglePasswordVisibility('passwordField', 'toggleIcon')">
|
||||
<i class="fas fa-eye" id="toggleIcon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-key me-2"></i>Confirm Password
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="password"
|
||||
class="admin-form-control"
|
||||
name="password_confirmation"
|
||||
id="confirmPasswordField"
|
||||
placeholder="Re-enter password"
|
||||
required>
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
onclick="togglePasswordVisibility('confirmPasswordField', 'toggleConfirmIcon')">
|
||||
<i class="fas fa-eye" id="toggleConfirmIcon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-end mt-4">
|
||||
<a href="{{ route('datauser') }}" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>Back
|
||||
</a>
|
||||
<button type="button" class="btn btn-primary" onclick="confirmSave()">
|
||||
<i class="fas fa-save me-2"></i>Save User
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.admin-form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.admin-form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.admin-form-label i {
|
||||
width: 20px;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.admin-form-control {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.admin-form-control:focus {
|
||||
border-color: #2196f3;
|
||||
box-shadow: 0 0 0 0.2rem rgba(33, 150, 243, 0.25);
|
||||
}
|
||||
|
||||
.input-group .btn {
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #dee2e6;
|
||||
background: #f8f9fa;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.input-group .btn:hover {
|
||||
background: #e9ecef;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.input-group .admin-form-control {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.input-group .btn {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.admin-form-control {
|
||||
padding: 0.625rem 0.875rem;
|
||||
}
|
||||
|
||||
.input-group .btn {
|
||||
padding: 0.625rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<!-- Confirmation Dialog Script -->
|
||||
<script>
|
||||
function confirmSave() {
|
||||
function togglePasswordVisibility(fieldId, iconId) {
|
||||
var field = document.getElementById(fieldId);
|
||||
var icon = document.getElementById(iconId);
|
||||
|
||||
if (field.type === "password") {
|
||||
field.type = "text";
|
||||
icon.classList.remove('fa-eye');
|
||||
icon.classList.add('fa-eye-slash');
|
||||
} else {
|
||||
field.type = "password";
|
||||
icon.classList.remove('fa-eye-slash');
|
||||
icon.classList.add('fa-eye');
|
||||
}
|
||||
}
|
||||
|
||||
function confirmSave() {
|
||||
// Get password fields
|
||||
const password = document.getElementById('passwordField').value;
|
||||
const confirmPassword = document.getElementById('confirmPasswordField').value;
|
||||
|
||||
// Validate password
|
||||
if (password.length < 4) {
|
||||
Swal.fire({
|
||||
title: 'Password Error',
|
||||
text: 'Password must be at least 4 characters long',
|
||||
icon: 'error',
|
||||
confirmButtonColor: '#2196f3',
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if passwords match
|
||||
if (password !== confirmPassword) {
|
||||
Swal.fire({
|
||||
title: 'Password Mismatch',
|
||||
text: 'Password and confirmation do not match',
|
||||
icon: 'error',
|
||||
confirmButtonColor: '#2196f3',
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "Do you want to save the changes?",
|
||||
title: 'Save User',
|
||||
text: "Are you sure you want to save this user?",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, save it!'
|
||||
confirmButtonColor: '#2196f3',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: '<i class="fas fa-save me-2"></i>Yes, save it!',
|
||||
cancelButtonText: '<i class="fas fa-times me-2"></i>Cancel'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('AddUserForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -0,0 +1,300 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'History Perhitungan AHP')
|
||||
|
||||
@section('content')
|
||||
@include('admin.shared.admin-styles')
|
||||
<div class="admin-container">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-0">
|
||||
<i class="fas fa-history me-2"></i>History Perhitungan AHP
|
||||
</h3>
|
||||
<p class="text-white-50 mb-3">Riwayat perhitungan AHP untuk rekomendasi menu makanan</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(session('error'))
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<i class="fas fa-exclamation-circle me-2"></i>
|
||||
{{ session('error') }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="admin-card">
|
||||
<div class="card-header">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6 mb-3 mb-md-0">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-calculator me-2"></i>Data Perhitungan
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<form id="filterForm" class="d-flex gap-2">
|
||||
<input type="date" class="form-control" id="tanggalFilter" name="tanggal" value="{{ $tanggal }}">
|
||||
<button type="submit" class="admin-btn btn-primary">
|
||||
<i class="fas fa-filter me-2"></i>Filter
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-pills nav-fill mb-4" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-bs-toggle="tab" href="#bobotKriteria">
|
||||
<i class="fas fa-weight me-2"></i>Bobot Kriteria
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#perbandinganKriteria">
|
||||
<i class="fas fa-balance-scale me-2"></i>Perbandingan Kriteria
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#perbandinganAlternatif">
|
||||
<i class="fas fa-random me-2"></i>Perbandingan Alternatif
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#consistencyRatio">
|
||||
<i class="fas fa-check-circle me-2"></i>Consistency Ratio
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#skorMakanan">
|
||||
<i class="fas fa-star me-2"></i>Bobot Alternatif atau Makanan
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<!-- Bobot Kriteria Tab -->
|
||||
<div id="bobotKriteria" class="tab-pane active">
|
||||
@forelse($bobotKriteria as $tanggalData => $bobots)
|
||||
<div class="table-responsive">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%">No</th>
|
||||
<th style="width: 65%">Kriteria</th>
|
||||
<th style="width: 30%">Bobot</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($bobots as $index => $bobot)
|
||||
<tr>
|
||||
<td>{{ $index + 1 }}</td>
|
||||
<td>{{ $bobot->kriteria->nama }}</td>
|
||||
<td>{{ number_format($bobot->bobot, 4) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@empty
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-folder-open fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">Tidak ada data bobot kriteria untuk tanggal ini.</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Perbandingan Kriteria Tab -->
|
||||
<div id="perbandinganKriteria" class="tab-pane fade">
|
||||
@forelse($perbandinganKriteria as $tanggalData => $perbandinganKriterias)
|
||||
<div class="table-responsive">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%">No</th>
|
||||
<th style="width: 35%">Kriteria 1</th>
|
||||
<th style="width: 35%">Kriteria 2</th>
|
||||
<th style="width: 25%">Nilai</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($perbandinganKriterias as $index => $perbandingan)
|
||||
<tr>
|
||||
<td>{{ $index + 1 }}</td>
|
||||
<td>{{ $perbandingan->kriteria1->nama }}</td>
|
||||
<td>{{ $perbandingan->kriteria2->nama }}</td>
|
||||
<td>{{ number_format($perbandingan->nilai, 4) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@empty
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-folder-open fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">Tidak ada data perbandingan kriteria untuk tanggal ini.</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Perbandingan Alternatif Tab -->
|
||||
<div id="perbandinganAlternatif" class="tab-pane fade">
|
||||
@forelse($perbandinganAlternatif as $tanggalData => $perbandinganAlternatifs)
|
||||
<div class="table-responsive">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%">No</th>
|
||||
<th style="width: 20%">Alternatif 1</th>
|
||||
<th style="width: 20%">Alternatif 2</th>
|
||||
<th style="width: 15%">Kriteria</th>
|
||||
<th style="width: 20%">Waktu Makan</th>
|
||||
<th style="width: 20%">Komponen</th>
|
||||
<th style="width: 10%">Nilai</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($perbandinganAlternatifs as $index => $perbandingan)
|
||||
<tr>
|
||||
<td>{{ $index + 1 }}</td>
|
||||
<td>{{ $perbandingan->alternatif1->nama }}</td>
|
||||
<td>{{ $perbandingan->alternatif2->nama }}</td>
|
||||
<td>{{ $perbandingan->kriteria->nama }}</td>
|
||||
<td>{{ optional($perbandingan->waktuMakan)->nama ?? '-' }}</td>
|
||||
<td>{{ optional($perbandingan->komponen)->nama ?? '-' }}</td>
|
||||
<td>{{ number_format($perbandingan->nilai, 4) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@empty
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-folder-open fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">Tidak ada data perbandingan alternatif untuk tanggal ini.</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Consistency Ratio Tab -->
|
||||
<div id="consistencyRatio" class="tab-pane fade">
|
||||
@forelse($consistencyRatios as $tanggalData => $CRs)
|
||||
<div class="table-responsive">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%">No</th>
|
||||
<th style="width: 20%">Kriteria</th>
|
||||
<th style="width: 20%">Waktu Makan</th>
|
||||
<th style="width: 20%">Komponen</th>
|
||||
<th style="width: 15%">Nilai CR</th>
|
||||
<th style="width: 20%">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($CRs as $index => $cr)
|
||||
<tr>
|
||||
<td>{{ $index + 1 }}</td>
|
||||
<td>{{ optional($cr->kriteria)->nama ?? 'Semua Kriteria' }}</td>
|
||||
<td>{{ optional($cr->waktuMakan)->nama ?? '-' }}</td>
|
||||
<td>{{ optional($cr->komponen)->nama ?? '-' }}</td>
|
||||
<td>{{ number_format($cr->nilai_cr, 4) }}</td>
|
||||
<td>
|
||||
<span class="admin-badge {{ $cr->nilai_cr < 0.1 ? 'bg-success' : 'bg-warning' }}">
|
||||
{{ $cr->nilai_cr < 0.1 ? 'Konsisten' : 'Perlu Review' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@empty
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-folder-open fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">Tidak ada data consistency ratio untuk tanggal ini.</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Skor Makanan Tab -->
|
||||
<div id="skorMakanan" class="tab-pane fade">
|
||||
@forelse($skorMakanan as $tanggalData => $skors)
|
||||
<div class="table-responsive">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%">No</th>
|
||||
<th style="width: 25%">Makanan</th>
|
||||
<th style="width: 20%">Kriteria</th>
|
||||
<th style="width: 20%">Waktu Makan</th>
|
||||
<th style="width: 20%">Komponen</th>
|
||||
<th style="width: 10%">Nilai</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($skors as $index => $skor)
|
||||
<tr>
|
||||
<td>{{ $index + 1 }}</td>
|
||||
<td>{{ $skor->makanan->nama }}</td>
|
||||
<td>{{ $skor->kriteria->nama }}</td>
|
||||
<td>{{ optional($skor->waktuMakan)->nama ?? '-' }}</td>
|
||||
<td>{{ optional($skor->komponen)->nama ?? '-' }}</td>
|
||||
<td>{{ number_format($skor->nilai, 4) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@empty
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-folder-open fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">Tidak ada data skor makanan untuk tanggal ini.</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Inisialisasi tooltips
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||
});
|
||||
|
||||
// Filter form handling
|
||||
const filterForm = document.getElementById('filterForm');
|
||||
if (filterForm) {
|
||||
filterForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const tanggal = document.getElementById('tanggalFilter').value;
|
||||
window.location.href = `{{ route('history.ahp') }}?tanggal=${tanggal}`;
|
||||
});
|
||||
}
|
||||
|
||||
// Simpan tab aktif ke localStorage
|
||||
$('a[data-bs-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
localStorage.setItem('lastActiveHistoryTab', $(e.target).attr('href'));
|
||||
});
|
||||
|
||||
// Restore tab aktif dari localStorage
|
||||
var lastTab = localStorage.getItem('lastActiveHistoryTab');
|
||||
if (lastTab) {
|
||||
$('a[href="' + lastTab + '"]').tab('show');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -1,73 +1,159 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Edit Jenis Makanan')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<h1>Edit Data Jenis Makanan</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('jenismakanan') }}">Jenis Makanan</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('editjenismakanan', $jenis->id) }}">Edit Jenis Makanan</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Edit Jenis Makanan</h5>
|
||||
<!-- General Form Elements -->
|
||||
<form id="editUserForm" method="POST" action="{{ url("/jenismakanan/{$jenis->id}") }}">
|
||||
@method('PUT')
|
||||
@csrf
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Name</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="name" value="{{ $jenis->name }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12 text-center">
|
||||
<a href="{{ route('jenismakanan') }}" class="btn btn-secondary mx-3">Back</a>
|
||||
<button type="button" class="btn btn-primary mx-3" onclick="confirmSave()">Save</button>
|
||||
<!-- Reset button to reset form fields -->
|
||||
<button type="reset" class="btn btn-secondary">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</form><!-- End General Form Elements -->
|
||||
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-utensils me-2"></i>Edit Jenis Makanan
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('jenismakanan') }}" class="text-white-50">Jenis Makanan</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Edit Jenis Makanan</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-edit me-2"></i>Informasi Jenis Makanan
|
||||
</h5>
|
||||
|
||||
<!-- Confirmation Dialog Script -->
|
||||
<script>
|
||||
function togglePasswordVisibility() {
|
||||
var passwordField = document.getElementById("passwordField");
|
||||
if (passwordField.type === "password") {
|
||||
passwordField.type = "text";
|
||||
document.querySelector('button[onclick="togglePasswordVisibility()"]').textContent = "Hide";
|
||||
} else {
|
||||
passwordField.type = "password";
|
||||
document.querySelector('button[onclick="togglePasswordVisibility()"]').textContent = "Show";
|
||||
}
|
||||
}
|
||||
<form id="editUserForm" method="POST" action="{{ url("/jenismakanan/{$jenis->id}") }}">
|
||||
@method('PUT')
|
||||
@csrf
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-12">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-tag me-2"></i>Nama Jenis Makanan
|
||||
</label>
|
||||
<input type="text"
|
||||
class="admin-form-control @error('name') is-invalid @enderror"
|
||||
name="name"
|
||||
value="{{ $jenis->name }}"
|
||||
placeholder="Masukkan nama jenis makanan"
|
||||
required>
|
||||
@error('name')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-end mt-4">
|
||||
<button type="reset" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-undo me-2"></i>Reset
|
||||
</button>
|
||||
<a href="{{ route('jenismakanan') }}" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>Kembali
|
||||
</a>
|
||||
<button type="button" class="btn btn-primary" onclick="confirmSave()">
|
||||
<i class="fas fa-save me-2"></i>Simpan Perubahan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.admin-form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.admin-form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.admin-form-label i {
|
||||
width: 20px;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.admin-form-control {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.admin-form-control:focus {
|
||||
border-color: #2196f3;
|
||||
box-shadow: 0 0 0 0.2rem rgba(33, 150, 243, 0.25);
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.admin-form-control {
|
||||
padding: 0.625rem 0.875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
function confirmSave() {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "Do you want to save the changes?",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, save it!'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('editUserForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
function confirmSave() {
|
||||
Swal.fire({
|
||||
title: 'Simpan Perubahan',
|
||||
text: "Apakah Anda yakin ingin menyimpan perubahan ini?",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#2196f3',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: '<i class="fas fa-save me-2"></i>Ya, simpan!',
|
||||
cancelButtonText: '<i class="fas fa-times me-2"></i>Batal'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('editUserForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -1,59 +1,148 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Food Types Management')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-layer-group me-2"></i>Food Types Management
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Food Types</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Jenis Makanan</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('jenismakanan') }}"> Jenis Makanan</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<section class="section">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-2">Jenis Makanan</h5>
|
||||
<div class="text-center">
|
||||
<a href="{{ route('tambahjenismakanan') }}" class="btn btn-primary">Tambah</a>
|
||||
</div>
|
||||
<!-- Header with Add Button -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-list me-2"></i>Food Types List
|
||||
</h5>
|
||||
<a href="{{ route('tambahjenismakanan') }}" class="admin-btn btn-primary">
|
||||
<i class="fas fa-plus me-2"></i>Add New Food Type
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Table with stripped rows -->
|
||||
<table class="table datatable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">No</th>
|
||||
<th class="text-center">Jenis Makanan</th>
|
||||
<th class="text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($jenis_makanans as $jm)
|
||||
<tr>
|
||||
<td class="text-center">{{ $loop->iteration }}</td>
|
||||
<td class="text-center">{{ $jm->name }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ route('editjenismakanan', $jm->id) }}" class="btn btn-sm btn-warning">Edit</a>
|
||||
<form action="{{ url('/jenismakanan', $jm->id) }}" method="POST" style="display: inline;">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger">Hapus</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- End Table with stripped rows -->
|
||||
<!-- Food Types Table -->
|
||||
<div class="table-responsive">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>Type Name</th>
|
||||
<th class="text-center">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($jenis_makanans as $jm)
|
||||
<tr>
|
||||
<td>{{ ($jenis_makanans->currentPage() - 1) * $jenis_makanans->perPage() + $loop->iteration }}</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
{{ $jm->name }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ route('editjenismakanan', $jm->id) }}"
|
||||
class="admin-btn btn-warning btn-sm me-2">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<form action="{{ url('/jenismakanan', $jm->id) }}"
|
||||
method="POST"
|
||||
class="d-inline"
|
||||
onsubmit="return confirm('Are you sure you want to delete this food type?');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="admin-btn btn-danger btn-sm">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="d-flex justify-content-between align-items-center mt-4">
|
||||
<div class="text-muted">
|
||||
Showing {{ $jenis_makanans->firstItem() ?? 0 }} to {{ $jenis_makanans->lastItem() ?? 0 }} of {{ $jenis_makanans->total() }} entries
|
||||
</div>
|
||||
<div class="admin-pagination">
|
||||
{{ $jenis_makanans->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.admin-pagination nav {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.admin-pagination .pagination {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.admin-pagination .page-link {
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
border-radius: 0.25rem;
|
||||
margin: 0 0.125rem;
|
||||
color: var(--primary-color);
|
||||
background-color: var(--white);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.admin-pagination .page-item.active .page-link {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
color: var(--white);
|
||||
}
|
||||
.admin-pagination .page-item.disabled .page-link {
|
||||
color: var(--gray);
|
||||
pointer-events: none;
|
||||
background-color: var(--light);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -1,67 +1,158 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Tambah Jenis Makanan')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-plus me-2"></i>Tambah Jenis Makanan
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('jenismakanan') }}" class="text-white-50">Jenis Makanan</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Tambah Jenis Makanan</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Tambah Data Jenis Makanan</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('jenismakanan') }}">Jenis Makanan</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('tambahjenismakanan') }}">Tambah Jenis Makanan</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-utensils me-2"></i>Informasi Jenis Makanan
|
||||
</h5>
|
||||
|
||||
</div><!-- End Page Title -->
|
||||
<form id="AddUserForm" method="POST" action="{{ url('/jenismakanan/storejenismakanan') }}">
|
||||
@csrf
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Tambah Jenis Makanan</h5>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-12">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-tag me-2"></i>Nama Jenis Makanan
|
||||
</label>
|
||||
<input type="text"
|
||||
class="admin-form-control @error('name') is-invalid @enderror"
|
||||
name="name"
|
||||
value="{{ old('name') }}"
|
||||
placeholder="Masukkan nama jenis makanan"
|
||||
required>
|
||||
@error('name')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- General Form Elements -->
|
||||
<form id="AddUserForm" method="POST" action="{{ url('/jenismakanan/storejenismakanan') }}">
|
||||
@csrf <!-- Add this to include CSRF token -->
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Jenis Makanan</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" placeholder="jenismakanan" aria-label="Your Name"
|
||||
aria-describedby="basic-addon1" name="name">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12 text-center">
|
||||
<a href="{{ route('jenismakanan') }}" class="btn btn-secondary mx-3">Back</a>
|
||||
<button type="button" class="btn btn-primary mx-3" onclick="confirmSave()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form><!-- End General Form Elements -->
|
||||
|
||||
</div>
|
||||
<div class="text-end mt-4">
|
||||
<button type="reset" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-undo me-2"></i>Reset
|
||||
</button>
|
||||
<a href="{{ route('jenismakanan') }}" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>Kembali
|
||||
</a>
|
||||
<button type="button" class="btn btn-primary" onclick="confirmSave()">
|
||||
<i class="fas fa-save me-2"></i>Simpan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<style>
|
||||
.admin-form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.admin-form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.admin-form-label i {
|
||||
width: 20px;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.admin-form-control {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.admin-form-control:focus {
|
||||
border-color: #2196f3;
|
||||
box-shadow: 0 0 0 0.2rem rgba(33, 150, 243, 0.25);
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.admin-form-control {
|
||||
padding: 0.625rem 0.875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<!-- Confirmation Dialog Script -->
|
||||
<script>
|
||||
function confirmSave() {
|
||||
function confirmSave() {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "Do you want to save the changes?",
|
||||
title: 'Simpan Data',
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, save it!'
|
||||
confirmButtonColor: '#2196f3',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: '<i class="fas fa-save me-2"></i>Ya, simpan!',
|
||||
cancelButtonText: '<i class="fas fa-times me-2"></i>Batal'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
console.log("Form submitted"); // Tambahkan ini di dalam `if (result.isConfirmed)` sebelum submit form
|
||||
document.getElementById('AddUserForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -1,76 +1,180 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Edit Kategori')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<h1>Edit Data Kategori</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('kategori') }}">Kategori</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('editkategori', $kategori->id) }}">Edit Kategori</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Edit Kategori</h5>
|
||||
|
||||
<!-- General Form Elements -->
|
||||
<form id="editUserForm" method="POST" action="{{ url("/kategori/{$kategori->id}") }}">
|
||||
@method('PUT')
|
||||
@csrf
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Name</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="kategori" value="{{ $kategori->kategori }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12 text-center">
|
||||
<a href="{{ route('kategori') }}" class="btn btn-secondary mx-3">Back</a>
|
||||
<button type="button" class="btn btn-primary mx-3" onclick="confirmSave()">Save</button>
|
||||
|
||||
<!-- Reset button to reset form fields -->
|
||||
<button type="reset" class="btn btn-secondary">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</form><!-- End General Form Elements -->
|
||||
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-edit me-2"></i>Edit Kategori
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('kategori') }}" class="text-white-50">Kategori</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Edit Kategori</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-tag me-2"></i>Detail Kategori
|
||||
</h5>
|
||||
|
||||
<!-- Confirmation Dialog Script -->
|
||||
<script>
|
||||
function togglePasswordVisibility() {
|
||||
var passwordField = document.getElementById("passwordField");
|
||||
if (passwordField.type === "password") {
|
||||
passwordField.type = "text";
|
||||
document.querySelector('button[onclick="togglePasswordVisibility()"]').textContent = "Hide";
|
||||
} else {
|
||||
passwordField.type = "password";
|
||||
document.querySelector('button[onclick="togglePasswordVisibility()"]').textContent = "Show";
|
||||
}
|
||||
}
|
||||
<form id="editUserForm" method="POST" action="{{ url("/kategori/{$kategori->id}") }}">
|
||||
@method('PUT')
|
||||
@csrf
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-12">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-tag me-2"></i>Nama Kategori
|
||||
</label>
|
||||
<input type="text"
|
||||
class="admin-form-control @error('kategori') is-invalid @enderror"
|
||||
name="kategori"
|
||||
value="{{ old('kategori', $kategori->kategori) }}"
|
||||
placeholder="Masukkan nama kategori"
|
||||
required>
|
||||
@error('kategori')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-end mt-4">
|
||||
<button type="reset" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-undo me-2"></i>Reset
|
||||
</button>
|
||||
<a href="{{ route('kategori') }}" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>Kembali
|
||||
</a>
|
||||
<button type="button" class="btn btn-primary" onclick="confirmSave()">
|
||||
<i class="fas fa-save me-2"></i>Simpan Perubahan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.admin-form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.admin-form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.admin-form-label i {
|
||||
width: 20px;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.admin-form-control {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.admin-form-control:focus {
|
||||
border-color: #2196f3;
|
||||
box-shadow: 0 0 0 0.2rem rgba(33, 150, 243, 0.25);
|
||||
}
|
||||
|
||||
.admin-form-control.is-invalid {
|
||||
border-color: #dc3545;
|
||||
padding-right: calc(1.5em + 0.75rem);
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right calc(0.375em + 0.1875rem) center;
|
||||
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
|
||||
}
|
||||
|
||||
.invalid-feedback {
|
||||
display: none;
|
||||
width: 100%;
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.875em;
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.is-invalid ~ .invalid-feedback {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.admin-form-control {
|
||||
padding: 0.625rem 0.875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
function confirmSave() {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "Do you want to save the changes?",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, save it!'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('editUserForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
function confirmSave() {
|
||||
Swal.fire({
|
||||
title: 'Simpan Perubahan',
|
||||
text: "Apakah Anda yakin ingin menyimpan perubahan ini?",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#2196f3',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: '<i class="fas fa-save me-2"></i>Ya, simpan!',
|
||||
cancelButtonText: '<i class="fas fa-times me-2"></i>Batal'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('editUserForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -1,59 +1,148 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Category Management')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-tags me-2"></i>Category Management
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Categories</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Kategori</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('kategori') }}"> kategori</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<section class="section">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-2">Kategori</h5>
|
||||
<div class="text-center">
|
||||
<a href="{{ route('tambahkategori') }}" class="btn btn-primary">Tambah</a>
|
||||
</div>
|
||||
<!-- Header with Add Button -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-list me-2"></i>Category List
|
||||
</h5>
|
||||
<a href="{{ route('tambahkategori') }}" class="admin-btn btn-primary">
|
||||
<i class="fas fa-plus me-2"></i>Add New Category
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Table with stripped rows -->
|
||||
<table class="table datatable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">No</th>
|
||||
<th class="text-center">Kategori</th>
|
||||
<th class="text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($kategoris as $ktgr)
|
||||
<tr>
|
||||
<td class="text-center">{{ $loop->iteration }}</td>
|
||||
<td class="text-center">{{ $ktgr->kategori }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ route('editkategori', $ktgr->id) }}" class="btn btn-sm btn-warning">Edit</a>
|
||||
<form action="{{ url('/kategori', $ktgr->id) }}" method="POST" style="display: inline;">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger">Hapus</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- End Table with stripped rows -->
|
||||
<!-- Category Table -->
|
||||
<div class="table-responsive">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>Category Name</th>
|
||||
<th class="text-center">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($kategoris as $ktgr)
|
||||
<tr>
|
||||
<td>{{ ($kategoris->currentPage() - 1) * $kategoris->perPage() + $loop->iteration }}</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
{{ $ktgr->kategori }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ route('editkategori', $ktgr->id) }}"
|
||||
class="admin-btn btn-warning btn-sm me-2">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<form action="{{ url('/kategori', $ktgr->id) }}"
|
||||
method="POST"
|
||||
class="d-inline"
|
||||
onsubmit="return confirm('Are you sure you want to delete this category?');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="admin-btn btn-danger btn-sm">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="d-flex justify-content-between align-items-center mt-4">
|
||||
<div class="text-muted">
|
||||
Showing {{ $kategoris->firstItem() ?? 0 }} to {{ $kategoris->lastItem() ?? 0 }} of {{ $kategoris->total() }} entries
|
||||
</div>
|
||||
<div class="admin-pagination">
|
||||
{{ $kategoris->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.admin-pagination nav {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.admin-pagination .pagination {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.admin-pagination .page-link {
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
border-radius: 0.25rem;
|
||||
margin: 0 0.125rem;
|
||||
color: var(--primary-color);
|
||||
background-color: var(--white);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.admin-pagination .page-item.active .page-link {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
color: var(--white);
|
||||
}
|
||||
.admin-pagination .page-item.disabled .page-link {
|
||||
color: var(--gray);
|
||||
pointer-events: none;
|
||||
background-color: var(--light);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -1,67 +1,179 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Tambah Kategori')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-plus me-2"></i>Tambah Kategori
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('kategori') }}" class="text-white-50">Kategori</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Tambah Kategori</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Tambah Data Kategori</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('kategori') }}">Kategori</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('tambahkategori') }}">Tambah Kategori</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-tag me-2"></i>Detail Kategori
|
||||
</h5>
|
||||
|
||||
</div><!-- End Page Title -->
|
||||
<form id="AddUserForm" method="POST" action="{{ url('/kategori/storekategori') }}">
|
||||
@csrf
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Tambah Kategori</h5>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-12">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-tag me-2"></i>Nama Kategori
|
||||
</label>
|
||||
<input type="text"
|
||||
class="admin-form-control @error('kategori') is-invalid @enderror"
|
||||
name="kategori"
|
||||
value="{{ old('kategori') }}"
|
||||
placeholder="Masukkan nama kategori"
|
||||
required>
|
||||
@error('kategori')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- General Form Elements -->
|
||||
<form id="AddUserForm" method="POST" action="{{ url('/kategori/storekategori') }}">
|
||||
@csrf <!-- Add this to include CSRF token -->
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Kategori</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" placeholder="kategori" aria-label="Your Name"
|
||||
aria-describedby="basic-addon1" name="kategori">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12 text-center">
|
||||
<a href="{{ route('kategori') }}" class="btn btn-secondary mx-3">Back</a>
|
||||
<button type="button" class="btn btn-primary mx-3" onclick="confirmSave()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form><!-- End General Form Elements -->
|
||||
|
||||
</div>
|
||||
<div class="text-end mt-4">
|
||||
<button type="reset" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-undo me-2"></i>Reset
|
||||
</button>
|
||||
<a href="{{ route('kategori') }}" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>Kembali
|
||||
</a>
|
||||
<button type="button" class="btn btn-primary" onclick="confirmSave()">
|
||||
<i class="fas fa-save me-2"></i>Simpan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<style>
|
||||
.admin-form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.admin-form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.admin-form-label i {
|
||||
width: 20px;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.admin-form-control {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.admin-form-control:focus {
|
||||
border-color: #2196f3;
|
||||
box-shadow: 0 0 0 0.2rem rgba(33, 150, 243, 0.25);
|
||||
}
|
||||
|
||||
.admin-form-control.is-invalid {
|
||||
border-color: #dc3545;
|
||||
padding-right: calc(1.5em + 0.75rem);
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right calc(0.375em + 0.1875rem) center;
|
||||
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
|
||||
}
|
||||
|
||||
.invalid-feedback {
|
||||
display: none;
|
||||
width: 100%;
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.875em;
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.is-invalid ~ .invalid-feedback {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.admin-form-control {
|
||||
padding: 0.625rem 0.875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<!-- Confirmation Dialog Script -->
|
||||
<script>
|
||||
function confirmSave() {
|
||||
function confirmSave() {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "Do you want to save the changes?",
|
||||
title: 'Simpan Data',
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, save it!'
|
||||
confirmButtonColor: '#2196f3',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: '<i class="fas fa-save me-2"></i>Ya, simpan!',
|
||||
cancelButtonText: '<i class="fas fa-times me-2"></i>Batal'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
console.log("Form submitted"); // Tambahkan ini di dalam `if (result.isConfirmed)` sebelum submit form
|
||||
document.getElementById('AddUserForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -1,57 +1,141 @@
|
|||
@extends('layout.app')
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<h1>Data komponen</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('komponen') }}"> komponen</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<section class="section">
|
||||
@section('title', 'Component Management')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-puzzle-piece me-2"></i>Component Management
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Components</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-2">Data komponen</h5>
|
||||
{{-- <div class="text-center">
|
||||
<a href="{{ route('tambahkomponen') }}" class="btn btn-primary">Tambah</a>
|
||||
</div> --}}
|
||||
<!-- Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-list me-2"></i>Component List
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<!-- Table with stripped rows -->
|
||||
<table class="table datatable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">No</th>
|
||||
<th class="text-center">Name</th>
|
||||
<th class="text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($komponens as $kmpn)
|
||||
<tr>
|
||||
<td class="text-center">{{ $loop->iteration }}</td>
|
||||
<td class="text-center">{{ $kmpn->nama }}</td>
|
||||
<td class="text-center">
|
||||
{{-- <a href="{{ route('edituser', $usr->id) }}" class="btn btn-sm btn-warning">Edit</a>
|
||||
<form action="{{ route('deleteuser', $usr->id) }}" method="POST" class="d-inline delete-form">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger delete-button">Hapus</button>
|
||||
</form> --}}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- End Table with stripped rows -->
|
||||
<!-- Component Table -->
|
||||
<div class="table-responsive">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>Name</th>
|
||||
<th class="text-center">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($komponens as $kmpn)
|
||||
<tr>
|
||||
<td>{{ ($komponens->currentPage() - 1) * $komponens->perPage() + $loop->iteration }}</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
{{ $kmpn->nama }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="#" class="admin-btn btn-info btn-sm" title="View Details">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
<form action="{{ route('deletekomponen', $kmpn->id) }}" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this component?');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="admin-btn btn-danger btn-sm" title="Delete">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="d-flex justify-content-between align-items-center mt-4">
|
||||
<div class="text-muted">
|
||||
Showing {{ $komponens->firstItem() ?? 0 }} to {{ $komponens->lastItem() ?? 0 }} of {{ $komponens->total() }} entries
|
||||
</div>
|
||||
<div class="admin-pagination">
|
||||
{{ $komponens->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.admin-pagination nav {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.admin-pagination .pagination {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.admin-pagination .page-link {
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
border-radius: 0.25rem;
|
||||
margin: 0 0.125rem;
|
||||
color: var(--primary-color);
|
||||
background-color: var(--white);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.admin-pagination .page-item.active .page-link {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
color: var(--white);
|
||||
}
|
||||
.admin-pagination .page-item.disabled .page-link {
|
||||
color: var(--gray);
|
||||
pointer-events: none;
|
||||
background-color: var(--light);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -1,81 +1,199 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Edit Kriteria')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<h1>Edit Data User</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('kriteria') }}">User</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('editkriteria', $kriteria->id) }}">Edit User</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Edit User</h5>
|
||||
|
||||
<!-- General Form Elements -->
|
||||
<form id="editUserForm" method="POST" action="{{ url("/kriteria/{$kriteria->id}") }}">
|
||||
@method('PUT')
|
||||
@csrf
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Name</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="nama" value="{{ $kriteria->nama }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Satuan</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="satuan" value="{{ $kriteria->satuan }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12 text-center">
|
||||
<a href="{{ route('kriteria') }}" class="btn btn-secondary mx-3">Back</a>
|
||||
<button type="button" class="btn btn-primary mx-3" onclick="confirmSave()">Save</button>
|
||||
<!-- Reset button to reset form fields -->
|
||||
<button type="reset" class="btn btn-secondary">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</form><!-- End General Form Elements -->
|
||||
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-edit me-2"></i>Edit Kriteria
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('kriteria') }}" class="text-white-50">Kriteria</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Edit Kriteria</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-list-check me-2"></i>Detail Kriteria
|
||||
</h5>
|
||||
|
||||
<!-- Confirmation Dialog Script -->
|
||||
<script>
|
||||
function togglePasswordVisibility() {
|
||||
var passwordField = document.getElementById("passwordField");
|
||||
if (passwordField.type === "password") {
|
||||
passwordField.type = "text";
|
||||
document.querySelector('button[onclick="togglePasswordVisibility()"]').textContent = "Hide";
|
||||
} else {
|
||||
passwordField.type = "password";
|
||||
document.querySelector('button[onclick="togglePasswordVisibility()"]').textContent = "Show";
|
||||
}
|
||||
}
|
||||
<form id="editUserForm" method="POST" action="{{ url("/kriteria/{$kriteria->id}") }}">
|
||||
@method('PUT')
|
||||
@csrf
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-list-check me-2"></i>Nama Kriteria
|
||||
</label>
|
||||
<input type="text"
|
||||
class="admin-form-control @error('nama') is-invalid @enderror"
|
||||
name="nama"
|
||||
value="{{ old('nama', $kriteria->nama) }}"
|
||||
placeholder="Masukkan nama kriteria"
|
||||
required>
|
||||
@error('nama')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-ruler me-2"></i>Satuan Ukur
|
||||
</label>
|
||||
<input type="text"
|
||||
class="admin-form-control @error('satuan') is-invalid @enderror"
|
||||
name="satuan"
|
||||
value="{{ old('satuan', $kriteria->satuan) }}"
|
||||
placeholder="Masukkan satuan ukur"
|
||||
required>
|
||||
@error('satuan')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-end mt-4">
|
||||
<button type="reset" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-undo me-2"></i>Reset
|
||||
</button>
|
||||
<a href="{{ route('kriteria') }}" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>Kembali
|
||||
</a>
|
||||
<button type="button" class="btn btn-primary" onclick="confirmSave()">
|
||||
<i class="fas fa-save me-2"></i>Simpan Perubahan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.admin-form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.admin-form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.admin-form-label i {
|
||||
width: 20px;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.admin-form-control {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.admin-form-control:focus {
|
||||
border-color: #2196f3;
|
||||
box-shadow: 0 0 0 0.2rem rgba(33, 150, 243, 0.25);
|
||||
}
|
||||
|
||||
.admin-form-control.is-invalid {
|
||||
border-color: #dc3545;
|
||||
padding-right: calc(1.5em + 0.75rem);
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right calc(0.375em + 0.1875rem) center;
|
||||
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
|
||||
}
|
||||
|
||||
.invalid-feedback {
|
||||
display: none;
|
||||
width: 100%;
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.875em;
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.is-invalid ~ .invalid-feedback {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.admin-form-control {
|
||||
padding: 0.625rem 0.875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
function confirmSave() {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "Do you want to save the changes?",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, save it!'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('editUserForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
function confirmSave() {
|
||||
Swal.fire({
|
||||
title: 'Simpan Perubahan',
|
||||
text: "Apakah Anda yakin ingin menyimpan perubahan ini?",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#2196f3',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: '<i class="fas fa-save me-2"></i>Ya, simpan!',
|
||||
cancelButtonText: '<i class="fas fa-times me-2"></i>Batal'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('editUserForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -1,61 +1,157 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Criteria Management')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-list-check me-2"></i>Criteria Management
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Criteria</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Kriteria</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('kriteria') }}"> Kriteria</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<section class="section">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-2">Kriteria</h5>
|
||||
<div class="text-center">
|
||||
<a href="{{ route('tambahkriteria') }}" class="btn btn-primary">Tambah</a>
|
||||
</div>
|
||||
<!-- Header with Add Button -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-list me-2"></i>Criteria List
|
||||
</h5>
|
||||
<a href="{{ route('tambahkriteria') }}" class="admin-btn btn-primary">
|
||||
<i class="fas fa-plus me-2"></i>Add New Criteria
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Table with stripped rows -->
|
||||
<table class="table datatable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">No</th>
|
||||
<th class="text-center">Nama</th>
|
||||
<th class="text-center">Satuan</th>
|
||||
<th class="text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($kriterias as $krt)
|
||||
<tr>
|
||||
<td class="text-center">{{ $loop->iteration }}</td>
|
||||
<td class="text-center">{{ $krt->nama }}</td>
|
||||
<td class="text-center">{{ $krt->satuan }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ route('editkriteria', $krt->id) }}" class="btn btn-sm btn-warning">Edit</a>
|
||||
<form action="{{ url('/kriteria', $krt->id) }}" method="POST" style="display: inline;">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger">Hapus</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- End Table with stripped rows -->
|
||||
<!-- Criteria Table -->
|
||||
<div class="table-responsive">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>Name</th>
|
||||
<th>Unit</th>
|
||||
<th class="text-center">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($kriterias as $krt)
|
||||
<tr>
|
||||
<td>{{ ($kriterias->currentPage() - 1) * $kriterias->perPage() + $loop->iteration }}</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="feature-icon bg-primary bg-gradient me-3" style="width: 32px; height: 32px; font-size: 14px;">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</div>
|
||||
{{ $krt->nama }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="admin-badge bg-info">
|
||||
{{ $krt->satuan }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ route('editkriteria', $krt->id) }}"
|
||||
class="admin-btn btn-warning btn-sm me-2">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<form action="{{ url('/kriteria', $krt->id) }}"
|
||||
method="POST"
|
||||
class="d-inline"
|
||||
onsubmit="return confirm('Are you sure you want to delete this criteria?');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="admin-btn btn-danger btn-sm">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="d-flex justify-content-between align-items-center mt-4">
|
||||
<div class="text-muted">
|
||||
Showing {{ $kriterias->firstItem() ?? 0 }} to {{ $kriterias->lastItem() ?? 0 }} of {{ $kriterias->total() }} entries
|
||||
</div>
|
||||
<div class="admin-pagination">
|
||||
{{ $kriterias->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.admin-pagination nav {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.admin-pagination .pagination {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.admin-pagination .page-link {
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
border-radius: 0.25rem;
|
||||
margin: 0 0.125rem;
|
||||
color: var(--primary-color);
|
||||
background-color: var(--white);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.admin-pagination .page-item.active .page-link {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
color: var(--white);
|
||||
}
|
||||
.admin-pagination .page-item.disabled .page-link {
|
||||
color: var(--gray);
|
||||
pointer-events: none;
|
||||
background-color: var(--light);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -1,74 +1,198 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Tambah Kriteria')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-plus me-2"></i>Tambah Kriteria
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('kriteria') }}" class="text-white-50">Kriteria</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Tambah Kriteria</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Tambah Data User</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('kriteria') }}">Kriteria</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('tambahkriteria') }}">Tambah Kriteria</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-list-check me-2"></i>Detail Kriteria
|
||||
</h5>
|
||||
|
||||
</div><!-- End Page Title -->
|
||||
<form id="AddUserForm" method="POST" action="{{ url('/kriteria/storekriteria') }}">
|
||||
@csrf
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Tambah Kriteria</h5>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-list-check me-2"></i>Nama Kriteria
|
||||
</label>
|
||||
<input type="text"
|
||||
class="admin-form-control @error('nama') is-invalid @enderror"
|
||||
name="nama"
|
||||
value="{{ old('nama') }}"
|
||||
placeholder="Masukkan nama kriteria"
|
||||
required>
|
||||
@error('nama')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- General Form Elements -->
|
||||
<form id="AddUserForm" method="POST" action="{{ url('/kriteria/storekriteria') }}">
|
||||
@csrf <!-- Add this to include CSRF token -->
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Kriteria</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" placeholder="kriteria yang mau dipakai" aria-label="Your Name"
|
||||
aria-describedby="basic-addon1" name="nama">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Satuan</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" placeholder="satuan yang mau dipakai" aria-label="Your Name"
|
||||
aria-describedby="basic-addon1" name="satuan">
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-ruler me-2"></i>Satuan Ukur
|
||||
</label>
|
||||
<input type="text"
|
||||
class="admin-form-control @error('satuan') is-invalid @enderror"
|
||||
name="satuan"
|
||||
value="{{ old('satuan') }}"
|
||||
placeholder="Masukkan satuan ukur"
|
||||
required>
|
||||
@error('satuan')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-end mt-4">
|
||||
<button type="reset" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-undo me-2"></i>Reset
|
||||
</button>
|
||||
<a href="{{ route('kriteria') }}" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>Kembali
|
||||
</a>
|
||||
<button type="button" class="btn btn-primary" onclick="confirmSave()">
|
||||
<i class="fas fa-save me-2"></i>Simpan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12 text-center">
|
||||
<a href="{{ route('kriteria') }}" class="btn btn-secondary mx-3">Back</a>
|
||||
<button type="button" class="btn btn-primary mx-3" onclick="confirmSave()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form><!-- End General Form Elements -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<style>
|
||||
.admin-form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.admin-form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.admin-form-label i {
|
||||
width: 20px;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.admin-form-control {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.admin-form-control:focus {
|
||||
border-color: #2196f3;
|
||||
box-shadow: 0 0 0 0.2rem rgba(33, 150, 243, 0.25);
|
||||
}
|
||||
|
||||
.admin-form-control.is-invalid {
|
||||
border-color: #dc3545;
|
||||
padding-right: calc(1.5em + 0.75rem);
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right calc(0.375em + 0.1875rem) center;
|
||||
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
|
||||
}
|
||||
|
||||
.invalid-feedback {
|
||||
display: none;
|
||||
width: 100%;
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.875em;
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.is-invalid ~ .invalid-feedback {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.admin-form-control {
|
||||
padding: 0.625rem 0.875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<!-- Confirmation Dialog Script -->
|
||||
<script>
|
||||
function confirmSave() {
|
||||
function confirmSave() {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "Do you want to save the changes?",
|
||||
title: 'Simpan Kriteria',
|
||||
text: "Apakah Anda yakin ingin menyimpan kriteria ini?",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, save it!'
|
||||
confirmButtonColor: '#2196f3',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: '<i class="fas fa-save me-2"></i>Ya, simpan!',
|
||||
cancelButtonText: '<i class="fas fa-times me-2"></i>Batal'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
console.log("Form submitted"); // Tambahkan ini di dalam `if (result.isConfirmed)` sebelum submit form
|
||||
document.getElementById('AddUserForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -1,120 +1,292 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Edit Makanan')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<h1>Edit Data Makanan</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('makanan') }}">Makanan></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('editmakanan', $makanan->id) }}">Edit Makanan</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Edit Makanan</h5>
|
||||
|
||||
<!-- General Form Elements -->
|
||||
<form id="editUserForm" method="POST" action="{{ url("/makanan/{$makanan->id}") }}">
|
||||
@method('PUT')
|
||||
@csrf
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Name</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="nama" value="{{ $makanan->nama }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="role" class="col-sm-2 col-form-label">Kategori</label>
|
||||
<div class="col-sm-10">
|
||||
<select id="role" class="form-select" name="kategori_id">
|
||||
@foreach ($kategoris as $id => $kategori)
|
||||
<option value="{{ $id }}" {{ $makanan->kategori_id == $id ? 'selected' : '' }}>{{ $kategori }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="role" class="col-sm-2 col-form-label">Jenis Makanan</label>
|
||||
<div class="col-sm-10">
|
||||
<select id="role" class="form-select" name="jenis_id">
|
||||
@foreach ($jenis_makanans as $id => $jenis)
|
||||
<option value="{{ $id }}" {{ $makanan->jenis_id == $id ? 'selected' : '' }}>{{ $jenis }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Lemak</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="lemak" value="{{ $makanan->lemak }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Natrium</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="natrium" value="{{ $makanan->natrium }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Energi</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="energi" value="{{ $makanan->energi }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Karbohidrat</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="karbohidrat" value="{{ $makanan->karbohidrat }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12 text-center">
|
||||
<a href="{{ route('makanan') }}" class="btn btn-secondary mx-3">Back</a>
|
||||
<button type="button" class="btn btn-primary mx-3" onclick="confirmSave()">Save</button>
|
||||
{{-- <button type="submit" class="btn btn-primary mx-3" onclick="return confirmSave()">Save</button> --}}
|
||||
<!-- Reset button to reset form fields -->
|
||||
<button type="reset" class="btn btn-secondary">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</form><!-- End General Form Elements -->
|
||||
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-utensils me-2"></i>Edit Makanan
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('makanan') }}" class="text-white-50">Makanan</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Edit Makanan</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-utensils me-2"></i>Detail Makanan
|
||||
</h5>
|
||||
|
||||
<!-- Confirmation Dialog Script -->
|
||||
<script>
|
||||
function togglePasswordVisibility() {
|
||||
var passwordField = document.getElementById("passwordField");
|
||||
if (passwordField.type === "password") {
|
||||
passwordField.type = "text";
|
||||
document.querySelector('button[onclick="togglePasswordVisibility()"]').textContent = "Hide";
|
||||
} else {
|
||||
passwordField.type = "password";
|
||||
document.querySelector('button[onclick="togglePasswordVisibility()"]').textContent = "Show";
|
||||
}
|
||||
}
|
||||
<form id="editUserForm" method="POST" action="{{ url("/makanan/{$makanan->id}") }}">
|
||||
@method('PUT')
|
||||
@csrf
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-tag me-2"></i>Nama Makanan
|
||||
</label>
|
||||
<input type="text"
|
||||
class="admin-form-control @error('nama') is-invalid @enderror"
|
||||
name="nama"
|
||||
value="{{ old('nama', $makanan->nama) }}"
|
||||
placeholder="Masukkan nama makanan"
|
||||
required>
|
||||
@error('nama')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-list me-2"></i>Kategori
|
||||
</label>
|
||||
<select class="admin-form-control @error('kategori_id') is-invalid @enderror"
|
||||
name="kategori_id"
|
||||
required>
|
||||
<option value="" disabled>Pilih Kategori</option>
|
||||
@foreach($kategoris as $id => $kategori)
|
||||
<option value="{{ $id }}" {{ old('kategori_id', $makanan->kategori_id) == $id ? 'selected' : '' }}>
|
||||
{{ $kategori }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('kategori_id')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-cookie-bite me-2"></i>Jenis Makanan
|
||||
</label>
|
||||
<select class="admin-form-control @error('jenis_id') is-invalid @enderror"
|
||||
name="jenis_id"
|
||||
required>
|
||||
<option value="" disabled>Pilih Jenis Makanan</option>
|
||||
@foreach($jenis_makanans as $id => $jenis)
|
||||
<option value="{{ $id }}" {{ old('jenis_id', $makanan->jenis_id) == $id ? 'selected' : '' }}>
|
||||
{{ $jenis }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('jenis_id')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-oil-can me-2"></i>Lemak (g)
|
||||
</label>
|
||||
<input type="number"
|
||||
step="0.01"
|
||||
class="admin-form-control @error('lemak') is-invalid @enderror"
|
||||
name="lemak"
|
||||
value="{{ old('lemak', $makanan->lemak) }}"
|
||||
placeholder="Masukkan kandungan lemak"
|
||||
required>
|
||||
@error('lemak')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-salt-shaker me-2"></i>Natrium (mg)
|
||||
</label>
|
||||
<input type="number"
|
||||
step="0.01"
|
||||
class="admin-form-control @error('natrium') is-invalid @enderror"
|
||||
name="natrium"
|
||||
value="{{ old('natrium', $makanan->natrium) }}"
|
||||
placeholder="Masukkan kandungan natrium"
|
||||
required>
|
||||
@error('natrium')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-bolt me-2"></i>Energi (kkal)
|
||||
</label>
|
||||
<input type="number"
|
||||
step="0.01"
|
||||
class="admin-form-control @error('energi') is-invalid @enderror"
|
||||
name="energi"
|
||||
value="{{ old('energi', $makanan->energi) }}"
|
||||
placeholder="Masukkan kandungan energi"
|
||||
required>
|
||||
@error('energi')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-bread-slice me-2"></i>Karbohidrat (g)
|
||||
</label>
|
||||
<input type="number"
|
||||
step="0.01"
|
||||
class="admin-form-control @error('karbohidrat') is-invalid @enderror"
|
||||
name="karbohidrat"
|
||||
value="{{ old('karbohidrat', $makanan->karbohidrat) }}"
|
||||
placeholder="Masukkan kandungan karbohidrat"
|
||||
required>
|
||||
@error('karbohidrat')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-end mt-4">
|
||||
<button type="reset" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-undo me-2"></i>Reset
|
||||
</button>
|
||||
<a href="{{ route('makanan') }}" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>Kembali
|
||||
</a>
|
||||
<button type="button" class="btn btn-primary" onclick="confirmSave()">
|
||||
<i class="fas fa-save me-2"></i>Simpan Perubahan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.admin-form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.admin-form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.admin-form-label i {
|
||||
width: 20px;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.admin-form-control {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.admin-form-control:focus {
|
||||
border-color: #2196f3;
|
||||
box-shadow: 0 0 0 0.2rem rgba(33, 150, 243, 0.25);
|
||||
}
|
||||
|
||||
.admin-form-control.is-invalid {
|
||||
border-color: #dc3545;
|
||||
padding-right: calc(1.5em + 0.75rem);
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right calc(0.375em + 0.1875rem) center;
|
||||
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
|
||||
}
|
||||
|
||||
.invalid-feedback {
|
||||
display: none;
|
||||
width: 100%;
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.875em;
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.is-invalid ~ .invalid-feedback {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.admin-form-control {
|
||||
padding: 0.625rem 0.875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
function confirmSave() {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "Do you want to save the changes?",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, save it!'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('editUserForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
function confirmSave() {
|
||||
Swal.fire({
|
||||
title: 'Simpan Perubahan',
|
||||
text: "Apakah Anda yakin ingin menyimpan perubahan ini?",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#2196f3',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: '<i class="fas fa-save me-2"></i>Ya, simpan!',
|
||||
cancelButtonText: '<i class="fas fa-times me-2"></i>Batal'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('editUserForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -1,89 +1,181 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Food Management')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-utensils me-2"></i>Food Management
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Foods</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Data Makanan</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('makanan') }}"> Data makanan</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<section class="section">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-2">Data Makanan</h5>
|
||||
<div class="text-center">
|
||||
<a href="{{ route('tambahmakanan') }}" class="btn btn-primary">Tambah</a>
|
||||
<!-- Header with Add Button -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-list me-2"></i>Food List
|
||||
</h5>
|
||||
<a href="{{ route('tambahmakanan') }}" class="admin-btn btn-primary">
|
||||
<i class="fas fa-plus me-2"></i>Add New Food
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Important Notice -->
|
||||
<div class="admin-alert alert-info mb-4 animate-fade-in">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="feature-icon bg-info bg-gradient me-3" style="width: 48px; height: 48px;">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="fw-bold mb-1">Important Notice</h6>
|
||||
<p class="mb-0">Komposisi gizi pangan dihitung per <code>100 grams</code>, dengan Berat Dapat Dimakan (BDD) 100%.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.accordion-button {
|
||||
font-size: 1.3rem; /* Ukuran font lebih besar */
|
||||
}
|
||||
.accordion-body {
|
||||
font-size: 1.1rem; /* Ukuran font lebih besar */
|
||||
}
|
||||
</style>
|
||||
<div class="accordion-item mb-4">
|
||||
<h3 class="accordion-header" id="flush-headingOne">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#flush-collapseOne" aria-expanded="false" aria-controls="flush-collapseOne">
|
||||
<strong> Satuan yang harus di ikuti dan diperhatikan </strong>
|
||||
</button>
|
||||
</h3>
|
||||
<div id="flush-collapseOne" class="accordion-collapse collapse" aria-labelledby="flush-headingOne" data-bs-parent="#accordionFlushExample">
|
||||
<div class="accordion-body">Komposisi gizi pangan dihitung per <code>100 gram</code>, dengan Berat Dapat Dimakan (BDD) 100 % </div>
|
||||
|
||||
<!-- Food Table -->
|
||||
<div class="table-responsive">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>Name</th>
|
||||
<th>Kategori</th>
|
||||
<th>Jenis Makanan</th>
|
||||
<th>Lemak (g)</th>
|
||||
<th>Natrium (mg)</th>
|
||||
<th>Energi (Cal)</th>
|
||||
<th>Karbohidrat (g)</th>
|
||||
<th class="text-center">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($makanans as $mknn)
|
||||
<tr>
|
||||
<td>{{ ($makanans->currentPage() - 1) * $makanans->perPage() + $loop->iteration }}</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
{{ $mknn->nama }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="admin-badge bg-info">
|
||||
{{ $mknn->kategori->kategori }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="admin-badge bg-secondary">
|
||||
{{ $mknn->jenis->name }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ $mknn->lemak }}</td>
|
||||
<td>{{ $mknn->natrium }}</td>
|
||||
<td>{{ $mknn->energi }}</td>
|
||||
<td>{{ $mknn->karbohidrat }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ route('editmakanan', $mknn->id) }}"
|
||||
class="admin-btn btn-warning btn-sm me-2">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<form action="{{ url('/makanan', $mknn->id) }}"
|
||||
method="POST"
|
||||
class="d-inline"
|
||||
onsubmit="return confirm('Are you sure you want to delete this food item?');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="admin-btn btn-danger btn-sm">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="d-flex justify-content-between align-items-center mt-4">
|
||||
<div class="text-muted">
|
||||
Showing {{ $makanans->firstItem() ?? 0 }} to {{ $makanans->lastItem() ?? 0 }} of {{ $makanans->total() }} entries
|
||||
</div>
|
||||
<div class="admin-pagination">
|
||||
{{ $makanans->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table with stripped rows -->
|
||||
<table class="table datatable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">No</th>
|
||||
<th class="text-center">Name</th>
|
||||
<th class="text-center">Kategori</th>
|
||||
<th class="text-center">Jenis Makanan</th>
|
||||
<th class="text-center">Lemak (g)</th>
|
||||
<th class="text-center">Natrium (mg)</th>
|
||||
<th class="text-center">Energi (Kal)</th>
|
||||
<th class="text-center">Karbohidrat (g)</th>
|
||||
<th class="text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($makanans as $mknn)
|
||||
<tr>
|
||||
<td class="text-center">{{ $loop->iteration }}</td>
|
||||
<td class="text-center">{{ $mknn->nama }}</td>
|
||||
<td class="text-center">{{ $mknn->kategori->kategori }}</td>
|
||||
<td class="text-center">{{ $mknn->jenis->name }}</td>
|
||||
<td class="text-center">{{ $mknn->lemak }}</td>
|
||||
<td class="text-center">{{ $mknn->natrium }}</td>
|
||||
<td class="text-center">{{ $mknn->energi }}</td>
|
||||
<td class="text-center">{{ $mknn->karbohidrat }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ route('editmakanan', $mknn->id) }}" class="btn btn-sm btn-warning">Edit</a>
|
||||
<form action="{{ url('/makanan', $mknn->id) }}" method="POST" style="display: inline;">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger">Hapus</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- End Table with stripped rows -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.admin-pagination nav {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.admin-pagination .pagination {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.admin-pagination .page-link {
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
border-radius: 0.25rem;
|
||||
margin: 0 0.125rem;
|
||||
color: var(--primary-color);
|
||||
background-color: var(--white);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.admin-pagination .page-item.active .page-link {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
color: var(--white);
|
||||
}
|
||||
.admin-pagination .page-item.disabled .page-link {
|
||||
color: var(--gray);
|
||||
pointer-events: none;
|
||||
background-color: var(--light);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -1,118 +1,291 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Tambah Makanan')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
|
||||
<h1>Tambah Data makanan</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('makanan') }}">Makanan</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('tambahmakanan') }}">Tambah Makanan</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Tambah Makanan</h5>
|
||||
|
||||
<!-- General Form Elements -->
|
||||
<form id="AddUserForm" method="POST" action="{{ url('/makanan/storemakanan') }}">
|
||||
@csrf <!-- Add this to include CSRF token -->
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Name</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" placeholder="nama" aria-label="Your Name"
|
||||
aria-describedby="basic-addon1" name="nama">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label">Kategori</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-select" name="kategori_id" aria-label="Select Kategori" required>
|
||||
<option selected disabled>Select a Kategori</option>
|
||||
@foreach($kategoris as $kategori)
|
||||
<option value="{{ $kategori->id }}">{{ $kategori->kategori }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-utensils me-2"></i>Tambah Makanan
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('makanan') }}" class="text-white-50">Makanan</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Tambah Makanan</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label">Jenis Makanan</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-select" name="jenis_id" aria-label="Select Jenis Makanan" required>
|
||||
<option selected disabled>Select a Jenis Makanan</option>
|
||||
@foreach($jenis_makanans as $jenis)
|
||||
<option value="{{ $jenis->id }}">{{ $jenis->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Lemak</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" placeholder="lemak" aria-label="Your Name"
|
||||
aria-describedby="basic-addon1" name="lemak">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-utensils me-2"></i>Detail Makanan
|
||||
</h5>
|
||||
|
||||
<form id="AddUserForm" method="POST" action="{{ url('/makanan/storemakanan') }}">
|
||||
@csrf
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-tag me-2"></i>Nama Makanan
|
||||
</label>
|
||||
<input type="text"
|
||||
class="admin-form-control @error('nama') is-invalid @enderror"
|
||||
name="nama"
|
||||
value="{{ old('nama') }}"
|
||||
placeholder="Masukkan nama makanan"
|
||||
required>
|
||||
@error('nama')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-list me-2"></i>Kategori
|
||||
</label>
|
||||
<select class="admin-form-control @error('kategori_id') is-invalid @enderror"
|
||||
name="kategori_id"
|
||||
required>
|
||||
<option value="" selected disabled>Pilih Kategori</option>
|
||||
@foreach($kategoris as $kategori)
|
||||
<option value="{{ $kategori->id }}" {{ old('kategori_id') == $kategori->id ? 'selected' : '' }}>
|
||||
{{ $kategori->kategori }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('kategori_id')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-cookie-bite me-2"></i>Jenis Makanan
|
||||
</label>
|
||||
<select class="admin-form-control @error('jenis_id') is-invalid @enderror"
|
||||
name="jenis_id"
|
||||
required>
|
||||
<option value="" selected disabled>Pilih Jenis Makanan</option>
|
||||
@foreach($jenis_makanans as $jenis)
|
||||
<option value="{{ $jenis->id }}" {{ old('jenis_id') == $jenis->id ? 'selected' : '' }}>
|
||||
{{ $jenis->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('jenis_id')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-oil-can me-2"></i>Lemak (g)
|
||||
</label>
|
||||
<input type="number"
|
||||
step="0.01"
|
||||
class="admin-form-control @error('lemak') is-invalid @enderror"
|
||||
name="lemak"
|
||||
value="{{ old('lemak') }}"
|
||||
placeholder="Masukkan kandungan lemak"
|
||||
required>
|
||||
@error('lemak')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-salt-shaker me-2"></i>Natrium (mg)
|
||||
</label>
|
||||
<input type="number"
|
||||
step="0.01"
|
||||
class="admin-form-control @error('natrium') is-invalid @enderror"
|
||||
name="natrium"
|
||||
value="{{ old('natrium') }}"
|
||||
placeholder="Masukkan kandungan natrium"
|
||||
required>
|
||||
@error('natrium')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-bolt me-2"></i>Energi (kkal)
|
||||
</label>
|
||||
<input type="number"
|
||||
step="0.01"
|
||||
class="admin-form-control @error('energi') is-invalid @enderror"
|
||||
name="energi"
|
||||
value="{{ old('energi') }}"
|
||||
placeholder="Masukkan kandungan energi"
|
||||
required>
|
||||
@error('energi')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-bread-slice me-2"></i>Karbohidrat (g)
|
||||
</label>
|
||||
<input type="number"
|
||||
step="0.01"
|
||||
class="admin-form-control @error('karbohidrat') is-invalid @enderror"
|
||||
name="karbohidrat"
|
||||
value="{{ old('karbohidrat') }}"
|
||||
placeholder="Masukkan kandungan karbohidrat"
|
||||
required>
|
||||
@error('karbohidrat')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-end mt-4">
|
||||
<button type="reset" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-undo me-2"></i>Reset
|
||||
</button>
|
||||
<a href="{{ route('makanan') }}" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>Kembali
|
||||
</a>
|
||||
<button type="button" class="btn btn-primary" onclick="confirmSave()">
|
||||
<i class="fas fa-save me-2"></i>Simpan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Natrium</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" placeholder="natrium" aria-label="Your Name"
|
||||
aria-describedby="basic-addon1" name="natrium">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Energi</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" placeholder="energi" aria-label="Your Name"
|
||||
aria-describedby="basic-addon1" name="energi">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputText" class="col-sm-2 col-form-label">Karbohidrat</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" placeholder="karbohidrat" aria-label="Your Name"
|
||||
aria-describedby="basic-addon1" name="karbohidrat">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12 text-center">
|
||||
<a href="{{ route('makanan') }}" class="btn btn-secondary mx-3">Back</a>
|
||||
<button type="button" class="btn btn-primary mx-3" onclick="confirmSave()">Save</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form><!-- End General Form Elements -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<style>
|
||||
.admin-form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.admin-form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.admin-form-label i {
|
||||
width: 20px;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.admin-form-control {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.admin-form-control:focus {
|
||||
border-color: #2196f3;
|
||||
box-shadow: 0 0 0 0.2rem rgba(33, 150, 243, 0.25);
|
||||
}
|
||||
|
||||
.admin-form-control.is-invalid {
|
||||
border-color: #dc3545;
|
||||
padding-right: calc(1.5em + 0.75rem);
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right calc(0.375em + 0.1875rem) center;
|
||||
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
|
||||
}
|
||||
|
||||
.invalid-feedback {
|
||||
display: none;
|
||||
width: 100%;
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.875em;
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.is-invalid ~ .invalid-feedback {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.admin-form-control {
|
||||
padding: 0.625rem 0.875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<!-- Confirmation Dialog Script -->
|
||||
<script>
|
||||
function confirmSave() {
|
||||
function confirmSave() {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "Do you want to save the changes?",
|
||||
title: 'Simpan Makanan',
|
||||
text: "Apakah Anda yakin ingin menyimpan makanan ini?",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, save it!'
|
||||
confirmButtonColor: '#2196f3',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: '<i class="fas fa-save me-2"></i>Ya, simpan!',
|
||||
cancelButtonText: '<i class="fas fa-times me-2"></i>Batal'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
console.log("Form submitted"); // Tambahkan ini di dalam `if (result.isConfirmed)` sebelum submit form
|
||||
document.getElementById('AddUserForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -1,168 +1,164 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Profile')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<h1>Profile</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('admin.profile') }}">Profile</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<section class="section profile">
|
||||
<div class="row">
|
||||
<!-- Kartu info ringkas -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card border-0 shadow-sm text-center p-4 rounded-4">
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="rounded-circle bg-white shadow p-4 d-flex align-items-center justify-content-center" style="width: 100px; height: 100px;">
|
||||
<i class="bi bi-person-fill text-primary fs-1"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="fw-semibold mb-1">{{ $user->name }}</h5>
|
||||
<p class="text-muted mb-2">{{ $user->email }}</p>
|
||||
<span class="badge rounded-pill bg-primary">
|
||||
<i class="bi bi-award me-1"></i> {{ ucfirst($user->role->name ?? 'user') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-user me-2"></i>Profile
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Profile</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="col-xl-8">
|
||||
<div class="card">
|
||||
<div class="card-body pt-3">
|
||||
<ul class="nav nav-tabs nav-tabs-bordered">
|
||||
<li class="nav-item">
|
||||
<button class="nav-link active" data-bs-toggle="tab"
|
||||
data-bs-target="#tab-overview">Overview</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button class="nav-link" data-bs-toggle="tab"
|
||||
data-bs-target="#tab-edit">Edit Profile</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button class="nav-link" data-bs-toggle="tab"
|
||||
data-bs-target="#tab-password">Change Password</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content pt-2">
|
||||
|
||||
<!-- ========= Overview ========= -->
|
||||
<div class="tab-pane fade show active profile-overview" id="tab-overview">
|
||||
<h5 class="card-title">Profile Details</h5>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-lg-3 col-md-4 label">Name</div>
|
||||
<div class="col-lg-9 col-md-8">{{ $user->name }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-lg-3 col-md-4 label">Email</div>
|
||||
<div class="col-lg-9 col-md-8">{{ $user->email }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-lg-3 col-md-4 label">Phone</div>
|
||||
<div class="col-lg-9 col-md-8">{{ $user->no_telp }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-lg-3 col-md-4 label">Role</div>
|
||||
<div class="col-lg-9 col-md-8">{{ $user->role->name ?? '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========= Edit profile ===== -->
|
||||
<div class="tab-pane fade profile-edit pt-3" id="tab-edit">
|
||||
<form action="{{ route('profile.update') }}" method="POST">
|
||||
@csrf @method('PATCH')
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-md-4 col-lg-3 col-form-label">Name</label>
|
||||
<div class="col-md-8 col-lg-9">
|
||||
<input name="name" type="text" class="form-control"
|
||||
value="{{ old('name', $user->name) }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-md-4 col-lg-3 col-form-label">Email</label>
|
||||
<div class="col-md-8 col-lg-9">
|
||||
<input name="email" type="email" class="form-control"
|
||||
value="{{ old('email', $user->email) }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-md-4 col-lg-3 col-form-label">Phone</label>
|
||||
<div class="col-md-8 col-lg-9">
|
||||
<input name="no_telp" type="phone" class="form-control"
|
||||
value="{{ old('no_telp', $user->no_telp) }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-md-4 col-lg-3 col-form-label">Role</label>
|
||||
<div class="col-md-8 col-lg-9">
|
||||
<select name="role_id" class="form-control" required>
|
||||
@foreach (\App\Models\Role::all() as $role)
|
||||
<option value="{{ $role->id }}" {{ $user->role_id == $role->id ? 'selected' : '' }}>
|
||||
{{ $role->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="text-center">
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- ======== Change password ==== -->
|
||||
<div class="tab-pane fade pt-3" id="tab-password">
|
||||
<form action="{{ route('profile.password') }}" method="POST">
|
||||
@csrf @method('PUT')
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-md-4 col-lg-3 col-form-label">Current Password</label>
|
||||
<div class="col-md-8 col-lg-9">
|
||||
<input name="current_password" type="password" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-md-4 col-lg-3 col-form-label">New Password</label>
|
||||
<div class="col-md-8 col-lg-9">
|
||||
<input name="password" type="password" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-md-4 col-lg-3 col-form-label">Confirm Password</label>
|
||||
<div class="col-md-8 col-lg-9">
|
||||
<input name="password_confirmation" type="password" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<button type="submit" class="btn btn-primary">Change Password</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div><!-- tab-content -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="row">
|
||||
<!-- Profile Card -->
|
||||
<div class="col-lg-4">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-4">
|
||||
<div class="rounded-circle bg-primary bg-opacity-10 mx-auto p-4 d-flex align-items-center justify-content-center" style="width: 120px; height: 120px;">
|
||||
<i class="bi bi-person-fill text-primary" style="font-size: 3rem;"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="fw-bold mb-2">{{ $user->name }}</h4>
|
||||
<p class="text-muted mb-3">{{ $user->email }}</p>
|
||||
<div class="admin-badge bg-primary">
|
||||
<i class="bi bi-award me-1"></i>
|
||||
{{ ucfirst($user->role->name ?? 'user') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profile Details -->
|
||||
<div class="col-lg-8">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-bs-toggle="tab" href="#tab-profile">
|
||||
<i class="fas fa-user-edit me-2"></i>Edit Profile
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#tab-password">
|
||||
<i class="fas fa-key me-2"></i>Change Password
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content pt-4">
|
||||
<!-- Profile Edit Form -->
|
||||
<div class="tab-pane fade show active" id="tab-profile">
|
||||
@if(session('success'))
|
||||
<div class="admin-alert alert-success" role="alert">
|
||||
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session('error'))
|
||||
<div class="admin-alert alert-danger" role="alert">
|
||||
<i class="fas fa-exclamation-circle me-2"></i>{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form action="{{ route('profile.update') }}" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="admin-form-group row">
|
||||
<label class="col-md-4 col-lg-3 admin-form-label">Full Name</label>
|
||||
<div class="col-md-8 col-lg-9">
|
||||
<input name="name" type="text" class="admin-form-control" value="{{ $user->name }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-form-group row">
|
||||
<label class="col-md-4 col-lg-3 admin-form-label">Email</label>
|
||||
<div class="col-md-8 col-lg-9">
|
||||
<input name="email" type="email" class="admin-form-control" value="{{ $user->email }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-form-group row">
|
||||
<label class="col-md-4 col-lg-3 admin-form-label">Phone</label>
|
||||
<div class="col-md-8 col-lg-9">
|
||||
<input name="no_telp" type="text" class="admin-form-control" value="{{ $user->no_telp }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-form-group row">
|
||||
<label class="col-md-4 col-lg-3 admin-form-label">Role</label>
|
||||
<div class="col-md-8 col-lg-9">
|
||||
<select name="role_id" class="admin-form-control" required>
|
||||
@foreach (\App\Models\Role::all() as $role)
|
||||
<option value="{{ $role->id }}" {{ $user->role_id == $role->id ? 'selected' : '' }}>
|
||||
{{ $role->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<button type="submit" class="admin-btn btn-primary">
|
||||
<i class="fas fa-save me-2"></i>Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Change Password Form -->
|
||||
<div class="tab-pane fade" id="tab-password">
|
||||
<form action="{{ route('profile.password') }}" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="admin-form-group row">
|
||||
<label class="col-md-4 col-lg-3 admin-form-label">Current Password</label>
|
||||
<div class="col-md-8 col-lg-9">
|
||||
<input name="current_password" type="password" class="admin-form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-form-group row">
|
||||
<label class="col-md-4 col-lg-3 admin-form-label">New Password</label>
|
||||
<div class="col-md-8 col-lg-9">
|
||||
<input name="password" type="password" class="admin-form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-form-group row">
|
||||
<label class="col-md-4 col-lg-3 admin-form-label">Confirm Password</label>
|
||||
<div class="col-md-8 col-lg-9">
|
||||
<input name="password_confirmation" type="password" class="admin-form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<button type="submit" class="admin-btn btn-primary">
|
||||
<i class="fas fa-key me-2"></i>Change Password
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -1,28 +1,46 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('content')
|
||||
@section('title', 'Normalisasi Matriks')
|
||||
|
||||
<div class="pagetitle">
|
||||
<h1>Normalisasi Matriks Perbandingan</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('perbandingan') }}">Perbandingan Kriteria</a></li>
|
||||
<li class="breadcrumb-item active">Normalisasi Kriteria</li>
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-calculator me-2"></i>Normalisasi Matriks
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('perbandingan') }}" class="text-white-50">Perbandingan Kriteria</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Normalisasi Kriteria</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="section">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
|
||||
<div class="card shadow-lg border-start border-4 border-primary mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-primary fw-bold mb-0">
|
||||
<i class="fas fa-clock me-2"></i>Waktu Makan: {{ $waktu_makan->nama }}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Matriks Normalisasi -->
|
||||
<div class="card">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">Normalisasi Matriks</h5>
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-table me-2"></i>Normalisasi Matriks
|
||||
</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered text-center">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Kriteria</th>
|
||||
|
|
@ -32,26 +50,33 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($kriterias as $i => $baris)
|
||||
<tr>
|
||||
<th>{{ $baris->nama }}</th>
|
||||
@foreach ($kriterias as $j => $kolom)
|
||||
<td>{{ number_format($normalisasi[$i][$j], 4) }}</td>
|
||||
@endforeach
|
||||
</tr>
|
||||
@endforeach
|
||||
@foreach ($kriterias as $baris)
|
||||
<tr>
|
||||
<th>{{ $baris->nama }}</th>
|
||||
@foreach ($kriterias as $kolom)
|
||||
<td>
|
||||
{{ isset($normalisasi[$baris->id][$kolom->id]) ? number_format($normalisasi[$baris->id][$kolom->id], 4) : '-' }}
|
||||
</td>
|
||||
@endforeach
|
||||
</tr>
|
||||
@endforeach
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bobot Kriteria -->
|
||||
<div class="card mt-4">
|
||||
<div class="col-12 mt-4">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Bobot Kriteria</h5>
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-weight me-2"></i>Bobot Kriteria
|
||||
</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered text-center">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Kriteria</th>
|
||||
|
|
@ -59,27 +84,84 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($kriterias as $index => $kriteria)
|
||||
<tr>
|
||||
<td>{{ $kriteria->nama }}</td>
|
||||
<td>{{ number_format($bobot[$index], 4) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@foreach ($kriterias as $kriteria)
|
||||
<tr>
|
||||
<td>{{ $kriteria->nama }}</td>
|
||||
<td>{{ number_format($bobot[$kriteria->id], 4) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tombol Proses Otomatis -->
|
||||
<div class="text-end mt-4">
|
||||
<a href="{{ route('rekomendasi.hitung.otomatis') }}" class="btn btn-success btn-lg">
|
||||
<i class="bi bi-lightning-fill me-1"></i> Proses Rekomendasi Otomatis
|
||||
</a>
|
||||
<!-- Consistency Ratio -->
|
||||
<div class="col-12 mt-4">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-check-circle me-2"></i>Rasio Konsistensi
|
||||
</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="admin-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Consistency Index (CI)</th>
|
||||
<td>{{ number_format($ci, 2) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Consistency Ratio (CR)</th>
|
||||
<td>{{ number_format($cr, 2) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td>
|
||||
@if($is_consistent)
|
||||
<span class="badge bg-success">Konsisten (CR < 0.1)</span>
|
||||
@else
|
||||
<span class="badge bg-danger">Tidak Konsisten (CR > 0.1)</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tombol Lanjut -->
|
||||
<div class="col-12 mt-4">
|
||||
<div class="text-end">
|
||||
<a href="{{ route('alternatif.pilih') }}" class="admin-btn btn-primary">
|
||||
<i class="fas fa-arrow-right me-2"></i>Lanjut ke Pemilihan Alternatif
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -110,6 +110,25 @@
|
|||
<h5 class="card-title mb-4">Input Perbandingan Kriteria</h5>
|
||||
<form method="POST" action="{{ route('sementara') }}">
|
||||
@csrf
|
||||
<!-- Tambahkan dropdown waktu makan -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="waktu_makan_id" class="form-label">Pilih Waktu Makan</label>
|
||||
<select name="waktu_makan_id" id="waktu_makan_id" class="form-select" required>
|
||||
<option value="">Pilih Waktu Makan</option>
|
||||
@foreach(\App\Models\WaktuMakan::all() as $waktu)
|
||||
<option value="{{ $waktu->id }}" {{ old('waktu_makan_id') == $waktu->id ? 'selected' : '' }}>
|
||||
{{ $waktu->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('waktu_makan_id')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered text-center">
|
||||
<thead>
|
||||
|
|
@ -133,6 +152,9 @@
|
|||
@for ($n = 1; $n <= 9; $n++)
|
||||
<option value="{{ $n }}">{{ $n }}</option>
|
||||
@endfor
|
||||
@for ($n = 2; $n <= 9; $n++)
|
||||
<option value="{{ 1/$n }}">{{ '1/'.$n }}</option>
|
||||
@endfor
|
||||
</select>
|
||||
@else
|
||||
<input type="text" class="form-control text-center" value="-" disabled>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,191 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Daftar Rekomendasi Ahli')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3">
|
||||
<div>
|
||||
<h3 class="mb-1 text-white d-flex align-items-center">
|
||||
<i class="fas fa-user-md me-2"></i>Daftar Rekomendasi Ahli
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Rekomendasi Ahli</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="admin-btn btn-primary" onclick="window.location.reload()">
|
||||
<i class="fas fa-sync-alt me-1"></i>Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card per Hari -->
|
||||
<div class="d-flex overflow-auto gap-4 flex-nowrap px-2" id="cardsContainer">
|
||||
@foreach($daftarHari as $hari)
|
||||
<div class="recommendation-card flex-shrink-0" style="min-width: 360px;">
|
||||
<div class="card-header bg-gradient-primary d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0 text-truncate">
|
||||
<i class="fas fa-calendar-day me-2"></i>{{ $hari }}
|
||||
</h5>
|
||||
<a href="{{ route('rekomendasi_ahli.create', ['hari' => $hari]) }}" class="btn btn-sm btn-light" title="Tambah Rekomendasi">
|
||||
<i class="fas fa-plus"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if(isset($rekomendasiByHari[$hari]) && $rekomendasiByHari[$hari]->count())
|
||||
@foreach(
|
||||
$rekomendasiByHari[$hari]
|
||||
->sortBy('waktuMakan.urutan') // pastikan ada kolom 'urutan' di waktu_makan
|
||||
->groupBy('waktu_makan_id') as $waktuMakanId => $groupByWaktu
|
||||
)
|
||||
<div class="mb-3">
|
||||
<span class="badge bg-info mb-1">
|
||||
{{ $groupByWaktu->first()->waktuMakan->nama ?? '-' }}
|
||||
</span>
|
||||
<div class="components-container">
|
||||
@foreach(
|
||||
collect($groupByWaktu->groupBy('komponen_id'))
|
||||
->sortBy(function ($item, $key) {
|
||||
$order = ['Karbohidrat', 'Protein', 'Sayur', 'Buah', 'Susu', 'Snack'];
|
||||
return array_search(optional($item->first()->komponen)->nama, $order);
|
||||
}) as $komponenId => $groupByKomponen
|
||||
)
|
||||
<div class="mb-2">
|
||||
<span class="component-badge">
|
||||
{{ $groupByKomponen->first()->komponen->nama ?? '-' }}
|
||||
</span>
|
||||
<ul class="mb-0 ps-3">
|
||||
@foreach($groupByKomponen as $rekomendasi)
|
||||
<li class="d-flex align-items-center justify-content-between">
|
||||
<span>{{ $rekomendasi->makanan->nama ?? '-' }}</span>
|
||||
<form action="{{ route('rekomendasi_ahli.destroy', $rekomendasi->id) }}" method="POST" class="ms-2 d-inline-block" onsubmit="return confirm('Yakin ingin menghapus rekomendasi ini?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger" title="Hapus">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@else
|
||||
<div class="text-muted">Tidak ada rekomendasi untuk hari ini.</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;700&display=swap');
|
||||
|
||||
#cardsContainer {
|
||||
scroll-snap-type: x mandatory;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
.recommendation-card {
|
||||
scroll-snap-align: start;
|
||||
width: 360px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stats-overview {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.stat-card {
|
||||
padding: 1.5rem;
|
||||
border-radius: 1rem;
|
||||
color: white;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
.stat-icon {
|
||||
font-size: 2.5rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.stat-details {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.stat-label {
|
||||
margin-bottom: 0;
|
||||
opacity: 0.9;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.recommendation-card {
|
||||
background: white;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.recommendation-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.card-header {
|
||||
padding: 1.25rem;
|
||||
color: white;
|
||||
}
|
||||
.bg-gradient-primary {
|
||||
background: linear-gradient(45deg, #2196f3, #64b5f6);
|
||||
}
|
||||
.card-body {
|
||||
padding: 1.25rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
color: #333;
|
||||
}
|
||||
.components-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.component-badge {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
padding: 0.35rem 0.75rem;
|
||||
border-radius: 2rem;
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 0.25rem;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
@endsection
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Tambah Rekomendasi Ahli')
|
||||
|
||||
@section('content')
|
||||
<div class="container py-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0">Tambah Rekomendasi Ahli</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ route('rekomendasi_ahli.store') }}" method="POST">
|
||||
@csrf
|
||||
<div class="mb-3">
|
||||
<label for="hari" class="form-label">Hari</label>
|
||||
<select name="hari" id="hari" class="form-select" required>
|
||||
<option value="">Pilih Hari</option>
|
||||
@foreach(['Senin','Selasa','Rabu','Kamis','Jumat','Sabtu','Minggu'] as $h)
|
||||
<option value="{{ $h }}" {{ (old('hari', $hari ?? '') == $h) ? 'selected' : '' }}>{{ $h }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="waktu_makan_id" class="form-label">Waktu Makan</label>
|
||||
<select name="waktu_makan_id" id="waktu_makan_id" class="form-select" required>
|
||||
<option value="">Pilih Waktu Makan</option>
|
||||
@foreach($waktuMakans as $waktu)
|
||||
<option value="{{ $waktu->id }}" {{ old('waktu_makan_id') == $waktu->id ? 'selected' : '' }}>{{ $waktu->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="komponen_id" class="form-label">Komponen</label>
|
||||
<select name="komponen_id" id="komponen_id" class="form-select" required>
|
||||
<option value="">Pilih Komponen</option>
|
||||
@foreach($komponens as $komponen)
|
||||
<option value="{{ $komponen->id }}" {{ old('komponen_id') == $komponen->id ? 'selected' : '' }}>{{ $komponen->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="makanan_id" class="form-label">Makanan</label>
|
||||
<select name="makanan_id" id="makanan_id" class="form-select" required>
|
||||
<option value="">Pilih Makanan</option>
|
||||
@foreach($makanans as $makanan)
|
||||
<option value="{{ $makanan->id }}" {{ old('makanan_id') == $makanan->id ? 'selected' : '' }}>{{ $makanan->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{{ route('rekomendasi_ahli.index') }}" class="btn btn-secondary">Kembali</a>
|
||||
<button type="submit" class="btn btn-primary">Simpan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Edit Rekomendasi Ahli')
|
||||
|
||||
@section('content')
|
||||
<div class="container py-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0">Edit Rekomendasi Ahli</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ route('rekomendasi_ahli.update', $rekomendasi->id) }}" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<div class="mb-3">
|
||||
<label for="hari" class="form-label">Hari</label>
|
||||
<select name="hari" id="hari" class="form-select" required>
|
||||
<option value="">Pilih Hari</option>
|
||||
@foreach(['Senin','Selasa','Rabu','Kamis','Jumat','Sabtu','Minggu'] as $h)
|
||||
<option value="{{ $h }}" {{ (old('hari', $rekomendasi->hari) == $h) ? 'selected' : '' }}>{{ $h }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="waktu_makan_id" class="form-label">Waktu Makan</label>
|
||||
<select name="waktu_makan_id" id="waktu_makan_id" class="form-select" required>
|
||||
<option value="">Pilih Waktu Makan</option>
|
||||
@foreach($waktuMakans as $waktu)
|
||||
<option value="{{ $waktu->id }}" {{ old('waktu_makan_id', $rekomendasi->waktu_makan_id) == $waktu->id ? 'selected' : '' }}>{{ $waktu->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="komponen_id" class="form-label">Komponen</label>
|
||||
<select name="komponen_id" id="komponen_id" class="form-select" required>
|
||||
<option value="">Pilih Komponen</option>
|
||||
@foreach($komponens as $komponen)
|
||||
<option value="{{ $komponen->id }}" {{ old('komponen_id', $rekomendasi->komponen_id) == $komponen->id ? 'selected' : '' }}>{{ $komponen->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="makanan_id" class="form-label">Makanan</label>
|
||||
<select name="makanan_id" id="makanan_id" class="form-select" required>
|
||||
<option value="">Pilih Makanan</option>
|
||||
@foreach($makanans as $makanan)
|
||||
<option value="{{ $makanan->id }}" {{ old('makanan_id', $rekomendasi->makanan_id) == $makanan->id ? 'selected' : '' }}>{{ $makanan->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{{ route('rekomendasi_ahli.index') }}" class="btn btn-secondary">Kembali</a>
|
||||
<button type="submit" class="btn btn-primary">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -0,0 +1,483 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Daftar Rekomendasi')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3">
|
||||
<div>
|
||||
<h3 class="mb-1 text-white d-flex align-items-center">
|
||||
<i class="fas fa-list-alt me-2"></i>Daftar Rekomendasi Makanan
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Daftar Rekomendasi</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="admin-btn btn-primary" onclick="window.location.reload()">
|
||||
<i class="fas fa-sync-alt me-1"></i>Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Overview -->
|
||||
<div class="stats-overview animate-fade-in">
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="stat-card bg-gradient-success">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</div>
|
||||
<div class="stat-details">
|
||||
<h4 class="stat-value">{{ $waktuMakans->where('has_recommendation', true)->count() }}</h4>
|
||||
<p class="stat-label">Waktu Makan Terhitung</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="stat-card bg-gradient-warning">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-clock"></i>
|
||||
</div>
|
||||
<div class="stat-details">
|
||||
<h4 class="stat-value">{{ $waktuMakans->where('has_recommendation', false)->count() }}</h4>
|
||||
<p class="stat-label">Menunggu Perhitungan</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="stat-card bg-gradient-info">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-calendar-alt"></i>
|
||||
</div>
|
||||
<div class="stat-details">
|
||||
<h4 class="stat-value">{{ $waktuMakans->max('latest_calculation') ? \Carbon\Carbon::parse($waktuMakans->max('latest_calculation'))->format('d M Y') : 'Belum ada' }}</h4>
|
||||
<p class="stat-label">Terakhir Update</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alerts -->
|
||||
@if(session('error'))
|
||||
<div class="admin-alert alert-danger animate-fade-in" role="alert">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-exclamation-circle me-2"></i>
|
||||
<div>{{ session('error') }}</div>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session('success'))
|
||||
<div class="admin-alert alert-success animate-fade-in" role="alert">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
<div>{{ session('success') }}</div>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Waktu Makan Cards -->
|
||||
<div class="row g-4" id="cardsContainer">
|
||||
@foreach($waktuMakans as $waktuMakan)
|
||||
@php
|
||||
$isNewlyCalculated = $waktuMakan->latest_calculation &&
|
||||
\Carbon\Carbon::parse($waktuMakan->latest_calculation)->isToday();
|
||||
@endphp
|
||||
<div class="col-12 col-md-6 col-lg-4 animate-fade-in" style="animation-delay: {{ $loop->iteration * 0.1 }}s">
|
||||
<div class="recommendation-card {{ $isNewlyCalculated ? 'newly-calculated' : '' }}">
|
||||
<div class="card-header {{ $isNewlyCalculated ? 'bg-gradient-success' : 'bg-gradient-primary' }}">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0 d-flex align-items-center">
|
||||
<i class="fas fa-clock me-2"></i>
|
||||
<span class="text-truncate">{{ $waktuMakan->nama }}</span>
|
||||
</h5>
|
||||
<div class="status-badge">
|
||||
@if($waktuMakan->has_recommendation)
|
||||
<span class="badge bg-light text-dark">
|
||||
<i class="fas fa-check me-1"></i>Terhitung
|
||||
</span>
|
||||
@else
|
||||
<span class="badge bg-warning">
|
||||
<i class="fas fa-hourglass-half me-1"></i>Pending
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="components-section mb-3">
|
||||
<h6 class="section-title">
|
||||
<i class="fas fa-utensils me-2"></i>Komponen Makanan
|
||||
</h6>
|
||||
<div class="components-container">
|
||||
@foreach($waktuMakan->komponens as $komponen)
|
||||
<span class="component-badge">
|
||||
{{ $komponen->nama }}
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($waktuMakan->latest_calculation)
|
||||
<div class="calculation-info">
|
||||
<div class="info-item">
|
||||
<i class="fas fa-calendar me-2"></i>
|
||||
<span class="info-text">
|
||||
Terakhir dihitung:
|
||||
{{ \Carbon\Carbon::parse($waktuMakan->latest_calculation)->format('d M Y H:i') }}
|
||||
@if($isNewlyCalculated)
|
||||
<span class="badge bg-success ms-2">Baru</span>
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
@if($waktuMakan->latestConsistencyRatio)
|
||||
<div class="info-item">
|
||||
<i class="fas fa-calculator me-2"></i>
|
||||
<span class="info-text">
|
||||
CR: {{ number_format($waktuMakan->latestConsistencyRatio->nilai_cr, 3) }}
|
||||
@if($waktuMakan->latestConsistencyRatio->nilai_cr < 0.1)
|
||||
<span class="badge bg-success ms-1">Konsisten</span>
|
||||
@else
|
||||
<span class="badge bg-warning ms-1">Perlu Review</span>
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
@if($waktuMakan->has_recommendation)
|
||||
<a href="{{ route('rekomendasi.detail', $waktuMakan->id) }}"
|
||||
class="action-btn btn-primary flex-grow-1">
|
||||
<i class="fas fa-eye me-1"></i>Detail
|
||||
</a>
|
||||
@endif
|
||||
<a href="{{ route('alternatif.pilih', ['waktu_makan' => $waktuMakan->id]) }}"
|
||||
class="action-btn {{ $waktuMakan->has_recommendation ? 'btn-outline-primary' : 'btn-primary' }} flex-grow-1">
|
||||
<i class="fas fa-calculator me-1"></i>
|
||||
{{ $waktuMakan->has_recommendation ? 'Hitung Ulang' : 'Hitung' }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Stats Cards */
|
||||
.stats-overview {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
padding: 1.5rem;
|
||||
border-radius: 1rem;
|
||||
color: white;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 2.5rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.stat-details {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
margin-bottom: 0;
|
||||
opacity: 0.9;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Recommendation Cards */
|
||||
.recommendation-card {
|
||||
background: white;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.recommendation-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.recommendation-card.newly-calculated {
|
||||
border: 2px solid var(--bs-success);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 1.25rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.bg-gradient-primary {
|
||||
background: linear-gradient(45deg, #2196f3, #64b5f6);
|
||||
}
|
||||
|
||||
.bg-gradient-success {
|
||||
background: linear-gradient(45deg, #4caf50, #81c784);
|
||||
}
|
||||
|
||||
.bg-gradient-warning {
|
||||
background: linear-gradient(45deg, #ff9800, #ffb74d);
|
||||
}
|
||||
|
||||
.bg-gradient-info {
|
||||
background: linear-gradient(45deg, #00bcd4, #4dd0e1);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1.25rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.components-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.component-badge {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
padding: 0.35rem 0.75rem;
|
||||
border-radius: 2rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.calculation-info {
|
||||
background: #f8f9fa;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 1.25rem;
|
||||
background: #f8f9fa;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.action-btn.btn-primary {
|
||||
background: #2196f3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.btn-outline-primary {
|
||||
border: 1px solid #2196f3;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.stat-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.recommendation-card {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.card-header h5 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.component-badge {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* List View Styles */
|
||||
.list-view .recommendation-card {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.list-view .card-header {
|
||||
width: 25%;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.list-view .card-body {
|
||||
width: 50%;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.list-view .card-footer {
|
||||
width: 25%;
|
||||
border-top: none;
|
||||
border-left: 1px solid #eee;
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.list-view .recommendation-card {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.list-view .card-header,
|
||||
.list-view .card-body,
|
||||
.list-view .card-footer {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
.animate-fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.animate-fade-in.show {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
</style>
|
||||
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Animation Observer
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('show');
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.1
|
||||
});
|
||||
|
||||
animateElements.forEach(element => observer.observe(element));
|
||||
|
||||
// Toggle View
|
||||
const toggleViewBtn = document.getElementById('toggleView');
|
||||
const cardsContainer = document.getElementById('cardsContainer');
|
||||
let isListView = localStorage.getItem('recommendationViewMode') === 'list';
|
||||
|
||||
function updateViewMode() {
|
||||
if (isListView) {
|
||||
cardsContainer.classList.add('list-view');
|
||||
toggleViewBtn.innerHTML = '<i class="fas fa-th-large me-1"></i>Grid View';
|
||||
} else {
|
||||
cardsContainer.classList.remove('list-view');
|
||||
toggleViewBtn.innerHTML = '<i class="fas fa-list me-1"></i>List View';
|
||||
}
|
||||
}
|
||||
|
||||
updateViewMode();
|
||||
|
||||
toggleViewBtn.addEventListener('click', () => {
|
||||
isListView = !isListView;
|
||||
localStorage.setItem('recommendationViewMode', isListView ? 'list' : 'grid');
|
||||
updateViewMode();
|
||||
});
|
||||
|
||||
// Smooth Scroll
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -1,102 +1,610 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Detail Rekomendasi')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<h1>🏆 Ranking Rekomendasi Makanan</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active">Hasil Rekomendasi</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<section class="section">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success shadow-sm rounded-3">
|
||||
<i class="bi bi-check-circle me-2"></i> {{ session('success') }}
|
||||
<div class="admin-container">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon-title-page bg-primary text-white me-3">
|
||||
<i class="fas fa-chart-bar"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="mb-1">Detail Rekomendasi Makanan</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('rekomendasi.index') }}">Daftar Rekomendasi</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Detail</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ route('rekomendasi.index') }}" class="admin-btn btn-secondary">
|
||||
<i class="fas fa-arrow-left me-2"></i>Kembali
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@forelse ($rekomendasi as $waktu => $komponens)
|
||||
<div class="card shadow-lg border-start border-4 border-success mt-4">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title fw-bold text-success mb-4">
|
||||
🍽️ Menu {{ ucfirst($waktu) }}
|
||||
@if(session('success'))
|
||||
<div class="admin-alert alert-success" role="alert">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
<div>{{ session('success') }}</div>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session('error'))
|
||||
<div class="admin-alert alert-danger" role="alert">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-exclamation-circle me-2"></i>
|
||||
<div>{{ session('error') }}</div>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Stats Overview -->
|
||||
<div class="stats-overview mb-4">
|
||||
<div class="row g-3">
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="stat-card bg-gradient-primary h-100">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-clock"></i>
|
||||
</div>
|
||||
<div class="stat-details">
|
||||
<h4 class="stat-value">{{ $waktuMakan->nama }}</h4>
|
||||
<p class="stat-label">Waktu Makan</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="stat-card bg-gradient-success h-100">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-list-check"></i>
|
||||
</div>
|
||||
<div class="stat-details">
|
||||
<h4 class="stat-value">{{ count($hasilPerKomponen) }}</h4>
|
||||
<p class="stat-label">Total Komponen</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="stat-card bg-gradient-info h-100">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-clipboard-list"></i>
|
||||
</div>
|
||||
<div class="stat-details">
|
||||
<h4 class="stat-value">{{ count($kriterias) }}</h4>
|
||||
<p class="stat-label">Total Kriteria</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="stat-card bg-gradient-orange h-100">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-calculator"></i>
|
||||
</div>
|
||||
<div class="stat-details">
|
||||
<h4 class="stat-value">
|
||||
@if(isset($latestCR))
|
||||
<span class="cr-value">{{ number_format($latestCR->cr, 4) }}</span>
|
||||
<span class="cr-status">({{ $latestCR->is_consistent ? 'Konsisten' : 'Perlu Review' }})</span>
|
||||
@else
|
||||
<span class="cr-value">Belum dihitung</span>
|
||||
@endif
|
||||
</h4>
|
||||
<p class="stat-label">Consistency Ratio (CR)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@foreach ($komponens as $komponen => $items)
|
||||
@php
|
||||
$utama = $items->first();
|
||||
$alternatif = $items->skip(1)->take(4)->values(); // top 5 total
|
||||
@endphp
|
||||
|
||||
<div class="mb-4">
|
||||
<h5 class="text-primary fw-semibold">
|
||||
📌 {{ ucfirst($komponen) }}
|
||||
</h5>
|
||||
|
||||
<div class="border rounded-3 p-3 bg-light shadow-sm mb-2 d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
🌟 <strong>{{ $utama->makanan->nama }}</strong>
|
||||
<!-- Recommendation Results -->
|
||||
<div class="recommendation-results">
|
||||
@foreach($hasilPerKomponen as $komponenId => $hasil)
|
||||
<div class="recommendation-card mb-4">
|
||||
<div class="card-header bg-gradient-primary">
|
||||
<h5 class="mb-0 text-white">
|
||||
<i class="fas fa-chart-bar me-2"></i>
|
||||
Hasil Perhitungan - {{ $hasil['komponen']->nama }}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Kriteria Section -->
|
||||
<div class="criteria-section mb-4">
|
||||
<h6 class="section-title mb-3">
|
||||
<i class="fas fa-list-check me-2"></i>Kriteria yang Digunakan
|
||||
</h6>
|
||||
<div class="row g-3">
|
||||
@foreach($kriterias as $kriteria)
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="criteria-card h-100">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="criteria-icon">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</div>
|
||||
<div class="criteria-details">
|
||||
<div class="criteria-name">{{ $kriteria->nama }}</div>
|
||||
@if(isset($bobotKriteria[$kriteria->id]))
|
||||
<div class="criteria-weight">
|
||||
Bobot: {{ number_format($bobotKriteria[$kriteria->id], 4) }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<span class="badge bg-success rounded-pill">
|
||||
{{ number_format($utama->persentase, 2) }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($alternatif->count() > 0)
|
||||
<button class="btn btn-sm btn-outline-primary toggle-btn mb-2"
|
||||
type="button"
|
||||
data-target="#alt-{{ $waktu }}-{{ $komponen }}">
|
||||
🔁 Lihat Alternatif
|
||||
</button>
|
||||
|
||||
<ol id="alt-{{ $waktu }}-{{ $komponen }}"
|
||||
class="list-group list-group-numbered shadow-sm d-none">
|
||||
@foreach ($alternatif as $index => $alt)
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
@if($index == 0) 🥈 @elseif($index == 1) 🥉 @else 🔹 @endif
|
||||
<strong>{{ $alt->makanan->nama }}</strong>
|
||||
</div>
|
||||
<span class="badge bg-secondary rounded-pill">
|
||||
{{ number_format($alt->persentase, 2) }}%
|
||||
</span>
|
||||
</li>
|
||||
@endforeach
|
||||
</ol>
|
||||
@endif
|
||||
<!-- Consistency Ratio Section -->
|
||||
<div class="consistency-section mb-4">
|
||||
<h6 class="section-title mb-3">
|
||||
<i class="fas fa-calculator me-2"></i>Consistency Ratio per Kriteria
|
||||
</h6>
|
||||
<div class="row g-3">
|
||||
@foreach($kriterias as $kriteria)
|
||||
@php
|
||||
$crData = \App\Models\ConsistencyRatioAlternatif::where([
|
||||
'kriteria_id' => $kriteria->id,
|
||||
'komponen_id' => $hasil['komponen']->id,
|
||||
'waktu_makan_id' => $waktuMakan->id
|
||||
])->latest()->first();
|
||||
@endphp
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="consistency-card h-100 {{ $crData && $crData->is_consistent ? 'consistent' : 'inconsistent' }}">
|
||||
<div class="consistency-header">
|
||||
<h6 class="mb-0">{{ $kriteria->nama }}</h6>
|
||||
<div class="consistency-icon">
|
||||
@if($crData && $crData->is_consistent)
|
||||
<i class="fas fa-check-circle"></i>
|
||||
@else
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="consistency-details">
|
||||
<div class="detail-item">
|
||||
<span class="label">CI:</span>
|
||||
<span class="value">{{ $crData ? number_format($crData->ci, 4) : 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="label">CR:</span>
|
||||
<span class="value">{{ $crData ? number_format($crData->cr, 4) : 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="status-badge {{ $crData && $crData->is_consistent ? 'consistent' : 'inconsistent' }}">
|
||||
@if($crData && $crData->is_consistent)
|
||||
<i class="fas fa-check me-1"></i>Konsisten
|
||||
@else
|
||||
<i class="fas fa-exclamation-triangle me-1"></i>Perlu Review
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="alert alert-warning mt-4">
|
||||
<i class="bi bi-exclamation-circle me-2"></i> Tidak ada data rekomendasi tersedia untuk hari ini.
|
||||
</div>
|
||||
@endforelse
|
||||
|
||||
<!-- Table Section -->
|
||||
<div class="table-section">
|
||||
<div class="table-header bg-gradient-success">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-table me-2"></i>
|
||||
Hasil Normalisasi
|
||||
</h6>
|
||||
</div>
|
||||
<div class="table-content">
|
||||
<div class="alert alert-info mb-3">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
Nilai yang ditampilkan adalah hasil normalisasi dari matriks perbandingan
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 60px">No</th>
|
||||
<th>Makanan</th>
|
||||
@foreach($kriterias as $kriteria)
|
||||
<th class="text-center">{{ $kriteria->nama }}</th>
|
||||
@endforeach
|
||||
<th class="text-center">Skor Akhir</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($hasil['rekomendasis'] as $rekomendasi)
|
||||
<tr>
|
||||
<td class="text-center">{{ $loop->iteration }}</td>
|
||||
<td>{{ $rekomendasi->makanan->nama }}</td>
|
||||
@foreach($kriterias as $kriteria)
|
||||
<td class="text-center score-cell">
|
||||
{{ number_format($hasil['detailSkor'][$rekomendasi->makanan_id][$kriteria->nama], 4) }}
|
||||
</td>
|
||||
@endforeach
|
||||
<td class="text-center fw-bold score-cell">
|
||||
{{ number_format($rekomendasi->nilai_akhir, 4) }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
/* Base Container */
|
||||
.admin-container {
|
||||
padding: 1.5rem;
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Page Header */
|
||||
.page-header {
|
||||
margin-bottom: 2rem;
|
||||
background: #fff;
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.icon-title-page {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 10px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
/* Stats Cards */
|
||||
.stats-overview {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
padding: 1.5rem;
|
||||
border-radius: 1rem;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
height: 100%;
|
||||
transition: transform 0.3s ease;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 2rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.stat-details {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
margin: 0;
|
||||
opacity: 0.9;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* Recommendation Card */
|
||||
.recommendation-card {
|
||||
background: #fff;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.recommendation-card:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 1.25rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
/* Criteria Cards */
|
||||
.criteria-card {
|
||||
background: #fff;
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.25rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.3s ease;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
height: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.criteria-card:hover {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.criteria-icon {
|
||||
color: #4caf50;
|
||||
font-size: 1.25rem;
|
||||
margin-right: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.criteria-details {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.criteria-name {
|
||||
font-weight: 600;
|
||||
color: #343a40;
|
||||
margin-bottom: 0.25rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.criteria-weight {
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
/* Table Section */
|
||||
.table-section {
|
||||
background: #fff;
|
||||
border-radius: 1rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
padding: 1rem 1.25rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.table-content {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.table th {
|
||||
background: #f8f9fa;
|
||||
font-weight: 600;
|
||||
padding: 1rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table td {
|
||||
padding: 1rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.score-cell {
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
/* Alerts */
|
||||
.admin-alert {
|
||||
border-radius: 0.75rem;
|
||||
padding: 1rem 1.25rem;
|
||||
margin-bottom: 1.5rem;
|
||||
border: none;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* Gradients */
|
||||
.bg-gradient-primary { background: linear-gradient(135deg, #2196f3, #1976d2); }
|
||||
.bg-gradient-success { background: linear-gradient(135deg, #4caf50, #388e3c); }
|
||||
.bg-gradient-info { background: linear-gradient(135deg, #00bcd4, #0097a7); }
|
||||
.bg-gradient-orange { background: linear-gradient(135deg, #ff8a00, #ff5f00); }
|
||||
|
||||
/* Consistency Cards */
|
||||
.consistency-section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.consistency-card {
|
||||
background: #fff;
|
||||
border-radius: 1rem;
|
||||
padding: 1.25rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.consistency-card.consistent {
|
||||
border-left: 4px solid #4caf50;
|
||||
}
|
||||
|
||||
.consistency-card.inconsistent {
|
||||
border-left: 4px solid #ff9800;
|
||||
}
|
||||
|
||||
.consistency-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.consistency-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.75rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.consistency-header h6 {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.consistency-icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.consistency-card.consistent .consistency-icon {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.consistency-card.inconsistent .consistency-icon {
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
.consistency-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.detail-item .label {
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.detail-item .value {
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
font-weight: 500;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge.consistent {
|
||||
background: rgba(76, 175, 80, 0.1);
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.status-badge.inconsistent {
|
||||
background: rgba(255, 152, 0, 0.1);
|
||||
color: #ef6c00;
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 1200px) {
|
||||
.stat-value {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.admin-container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
margin: 0 -1.25rem;
|
||||
}
|
||||
|
||||
.consistency-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.consistency-header {
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.stat-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.criteria-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('.toggle-btn').forEach(button => {
|
||||
button.addEventListener('click', function () {
|
||||
const target = document.querySelector(this.dataset.target);
|
||||
target.classList.toggle('d-none');
|
||||
this.textContent = target.classList.contains('d-none')
|
||||
? '🔁 Lihat Alternatif'
|
||||
: '🔼 Sembunyikan Alternatif';
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -0,0 +1,307 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Hasil Rekomendasi')
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.recommendation-card {
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
margin-bottom: 2rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.recommendation-card:hover {
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.recommendation-header {
|
||||
background: linear-gradient(45deg, #0d6efd, #0dcaf0);
|
||||
color: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: 15px 15px 0 0;
|
||||
}
|
||||
|
||||
.recommendation-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.component-section {
|
||||
margin-bottom: 2rem;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
|
||||
.component-header {
|
||||
background: linear-gradient(45deg, #0d6efd, #0dcaf0);
|
||||
color: white;
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 15px 15px 0 0;
|
||||
}
|
||||
|
||||
.component-body {
|
||||
background: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: 0 0 15px 15px;
|
||||
}
|
||||
|
||||
.nutrition-badge {
|
||||
display: inline-block;
|
||||
padding: 0.35rem 0.65rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border-radius: 0.25rem;
|
||||
background: linear-gradient(45deg, #0d6efd, #0dcaf0);
|
||||
margin: 0.25rem;
|
||||
}
|
||||
|
||||
.score-cell {
|
||||
font-weight: 500;
|
||||
color: #0d6efd;
|
||||
}
|
||||
|
||||
.highest-score {
|
||||
background-color: #d1e7dd !important;
|
||||
color: #0f5132;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.criteria-weight {
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.consistency-info {
|
||||
background: #e9ecef;
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.consistency-info p {
|
||||
margin-bottom: 0.5rem;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.consistency-info i {
|
||||
margin-right: 0.5rem;
|
||||
color: #0d6efd;
|
||||
}
|
||||
|
||||
.ahp-steps {
|
||||
counter-reset: step;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.ahp-steps li {
|
||||
position: relative;
|
||||
padding: 1rem 1rem 1rem 3rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.ahp-steps li::before {
|
||||
counter-increment: step;
|
||||
content: counter(step);
|
||||
position: absolute;
|
||||
left: 1rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
background: #0d6efd;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.875rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<!-- Page Title -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h3 class="mb-0">Hasil Rekomendasi Makanan</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('alternatif.pilih') }}">Pilih Alternatif</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('alternatif.perbandingan') }}">Perbandingan</a></li>
|
||||
<li class="breadcrumb-item active">Hasil Rekomendasi</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(session('error'))
|
||||
<div class="alert alert-danger">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($makanans->isEmpty())
|
||||
<div class="alert alert-warning">
|
||||
<h5><i class="icon fas fa-exclamation-triangle"></i> Tidak ada data!</h5>
|
||||
<p>Tidak ada makanan yang dipilih atau data tidak lengkap. Silakan kembali ke halaman pemilihan makanan.</p>
|
||||
<a href="{{ route('alternatif.pilih') }}" class="btn btn-warning mt-3">
|
||||
<i class="fas fa-arrow-left mr-2"></i> Kembali ke Pemilihan Makanan
|
||||
</a>
|
||||
</div>
|
||||
@else
|
||||
<!-- AHP Process Steps -->
|
||||
<div class="recommendation-card mb-4">
|
||||
<div class="recommendation-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-list-ol me-2"></i>Proses Perhitungan AHP
|
||||
@if($waktuMakan)
|
||||
- {{ $waktuMakan->nama }}
|
||||
@endif
|
||||
</h5>
|
||||
</div>
|
||||
<div class="recommendation-body">
|
||||
<ol class="ahp-steps">
|
||||
<li>Perhitungan bobot kriteria berdasarkan perbandingan berpasangan</li>
|
||||
<li>Normalisasi matriks perbandingan kriteria</li>
|
||||
<li>Perhitungan nilai alternatif untuk setiap kriteria</li>
|
||||
<li>Normalisasi nilai alternatif</li>
|
||||
<li>Perhitungan skor akhir dengan mengalikan bobot kriteria</li>
|
||||
<li>Perangkingan alternatif berdasarkan skor akhir</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Consistency Information -->
|
||||
<div class="consistency-info">
|
||||
<h5 class="mb-3"><i class="fas fa-check-circle"></i>Informasi Konsistensi</h5>
|
||||
<p><i class="fas fa-calculator"></i>Consistency Ratio (CR): {{ number_format($consistencyRatio, 3) }}</p>
|
||||
<p><i class="fas fa-info-circle"></i>Status:
|
||||
@if($consistencyRatio < 0.1)
|
||||
<span class="text-success">Konsisten (CR < 0.1)</span>
|
||||
@else
|
||||
<span class="text-danger">Tidak Konsisten (CR > 0.1)</span>
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Results by Component -->
|
||||
@foreach($komponens as $komponen)
|
||||
@php
|
||||
$komponenMakanans = $makanans->where('komponen_id', $komponen->id);
|
||||
@endphp
|
||||
|
||||
@if($komponenMakanans->isNotEmpty())
|
||||
<div class="component-section" data-aos="fade-up">
|
||||
<div class="component-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-utensils me-2"></i>{{ $komponen->nama }}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="component-body">
|
||||
<!-- Criteria Weights -->
|
||||
<div class="mb-4">
|
||||
<h6 class="mb-3">Bobot Kriteria:</h6>
|
||||
<div class="row">
|
||||
@foreach($kriterias as $kriteria)
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="p-3 bg-light rounded">
|
||||
<strong>{{ $kriteria->nama }}</strong>
|
||||
<div class="criteria-weight">
|
||||
Bobot: {{ number_format($bobotKriteria[$kriteria->id] * 100, 1) }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alternative Rankings -->
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ranking</th>
|
||||
<th>Alternatif Makanan</th>
|
||||
@foreach($kriterias as $kriteria)
|
||||
<th>{{ $kriteria->nama }}</th>
|
||||
@endforeach
|
||||
<th>Total Skor</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@php
|
||||
$sortedMakanans = $komponenMakanans->sortByDesc(function($makanan) use ($totalSkorMakanan) {
|
||||
return $totalSkorMakanan[$makanan->id] ?? 0;
|
||||
});
|
||||
@endphp
|
||||
|
||||
@foreach($sortedMakanans as $index => $makanan)
|
||||
<tr>
|
||||
<td>{{ $index + 1 }}</td>
|
||||
<td>
|
||||
<strong>{{ $makanan->nama }}</strong>
|
||||
<div class="mt-2">
|
||||
<span class="nutrition-badge">
|
||||
<i class="fas fa-fire me-1"></i>{{ $makanan->energi }} kal
|
||||
</span>
|
||||
<span class="nutrition-badge">
|
||||
<i class="fas fa-hamburger me-1"></i>{{ $makanan->lemak }}g
|
||||
</span>
|
||||
<span class="nutrition-badge">
|
||||
<i class="fas fa-bread-slice me-1"></i>{{ $makanan->karbohidrat }}g
|
||||
</span>
|
||||
<span class="nutrition-badge">
|
||||
<i class="fas fa-salt-shaker me-1"></i>{{ $makanan->natrium }}mg
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
@foreach($kriterias as $kriteria)
|
||||
<td class="score-cell">
|
||||
{{ number_format($bobotMakanan[$makanan->id][$kriteria->id], 3) }}
|
||||
</td>
|
||||
@endforeach
|
||||
<td class="score-cell {{ $index === 0 ? 'highest-score' : '' }}">
|
||||
{{ number_format($totalSkorMakanan[$makanan->id], 3) }}
|
||||
@if($index === 0)
|
||||
<span class="badge bg-success ms-1">Terbaik</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
<!-- Next Step Button -->
|
||||
<div class="d-flex justify-content-between mt-4 mb-5">
|
||||
<a href="{{ route('alternatif.perbandingan') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left me-2"></i>Kembali ke Perbandingan
|
||||
</a>
|
||||
<a href="{{ route('admindash') }}" class="btn btn-primary">
|
||||
<i class="fas fa-home me-2"></i>Kembali ke Dashboard
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Tambah Relasi')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-plus-circle me-2"></i>Tambah Relasi
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('relasi.index') }}" class="text-white-50">Relasi</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Tambah</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-plus me-2"></i>Tambah Relasi Baru
|
||||
</h5>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success" role="alert">
|
||||
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session('error'))
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<i class="fas fa-exclamation-circle me-2"></i>{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form action="{{ route('relasi.store') }}" method="POST">
|
||||
@csrf
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-4">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-utensils me-2"></i>Makanan
|
||||
</label>
|
||||
<select name="makanan_id" class="admin-form-control @error('makanan_id') is-invalid @enderror">
|
||||
<option value="">Pilih Makanan</option>
|
||||
@foreach($makanans as $makanan)
|
||||
<option value="{{ $makanan->id }}" {{ old('makanan_id') == $makanan->id ? 'selected' : '' }}>
|
||||
{{ $makanan->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('makanan_id')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-cube me-2"></i>Komponen
|
||||
</label>
|
||||
<select name="komponen_id" class="admin-form-control @error('komponen_id') is-invalid @enderror">
|
||||
<option value="">Pilih Komponen</option>
|
||||
@foreach($komponens as $komponen)
|
||||
<option value="{{ $komponen->id }}" {{ old('komponen_id') == $komponen->id ? 'selected' : '' }}>
|
||||
{{ $komponen->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('komponen_id')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-clock me-2"></i>Waktu Makan
|
||||
</label>
|
||||
<select name="waktu_makan_id" class="admin-form-control @error('waktu_makan_id') is-invalid @enderror">
|
||||
<option value="">Pilih Waktu Makan</option>
|
||||
@foreach($waktuMakans as $waktu)
|
||||
<option value="{{ $waktu->id }}" {{ old('waktu_makan_id') == $waktu->id ? 'selected' : '' }}>
|
||||
{{ $waktu->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('waktu_makan_id')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-toggle-on me-2"></i>Status
|
||||
</label>
|
||||
<select name="status" class="admin-form-control @error('status') is-invalid @enderror">
|
||||
<option value="1" {{ old('status', '1') == '1' ? 'selected' : '' }}>Aktif</option>
|
||||
<option value="0" {{ old('status') == '0' ? 'selected' : '' }}>Tidak Aktif</option>
|
||||
</select>
|
||||
@error('status')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-end mt-4">
|
||||
<a href="{{ route('relasi.index') }}" class="admin-btn btn-secondary me-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>Kembali
|
||||
</a>
|
||||
<button type="submit" class="admin-btn btn-primary">
|
||||
<i class="fas fa-save me-2"></i>Simpan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize Select2
|
||||
$('.admin-form-control').select2({
|
||||
theme: 'bootstrap-5',
|
||||
width: '100%'
|
||||
});
|
||||
|
||||
// Animation
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -1,52 +1,168 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Edit Relasi')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<h2 class="mb-4">Edit Relasi</h2>
|
||||
|
||||
@if(session('error'))
|
||||
<div class="alert alert-danger">{{ session('error') }}</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('relasi.update', $relasi->id) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="makanan_id">Makanan</label>
|
||||
<select name="makanan_id" class="form-control">
|
||||
@foreach($makanans as $makanan)
|
||||
<option value="{{ $makanan->id }}" {{ $relasi->makanan_id == $makanan->id ? 'selected' : '' }}>
|
||||
{{ $makanan->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-edit me-2"></i>Edit Relasi
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('relasi.index') }}" class="text-white-50">Relasi</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Edit</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="komponen_id">Komponen</label>
|
||||
<select name="komponen_id" class="form-control">
|
||||
@foreach($komponens as $komponen)
|
||||
<option value="{{ $komponen->id }}" {{ $relasi->komponen_id == $komponen->id ? 'selected' : '' }}>
|
||||
{{ $komponen->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-4">
|
||||
<i class="fas fa-edit me-2"></i>Edit Relasi
|
||||
</h5>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success" role="alert">
|
||||
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session('error'))
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<i class="fas fa-exclamation-circle me-2"></i>{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form action="{{ route('relasi.update', $relasi->id) }}" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-4">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-utensils me-2"></i>Makanan
|
||||
</label>
|
||||
<select name="makanan_id" class="admin-form-control @error('makanan_id') is-invalid @enderror">
|
||||
<option value="">Pilih Makanan</option>
|
||||
@foreach($makanans as $makanan)
|
||||
<option value="{{ $makanan->id }}" {{ old('makanan_id', $relasi->makanan_id) == $makanan->id ? 'selected' : '' }}>
|
||||
{{ $makanan->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('makanan_id')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-cube me-2"></i>Komponen
|
||||
</label>
|
||||
<select name="komponen_id" class="admin-form-control @error('komponen_id') is-invalid @enderror">
|
||||
<option value="">Pilih Komponen</option>
|
||||
@foreach($komponens as $komponen)
|
||||
<option value="{{ $komponen->id }}" {{ old('komponen_id', $relasi->komponen_id) == $komponen->id ? 'selected' : '' }}>
|
||||
{{ $komponen->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('komponen_id')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-clock me-2"></i>Waktu Makan
|
||||
</label>
|
||||
<select name="waktu_makan_id" class="admin-form-control @error('waktu_makan_id') is-invalid @enderror">
|
||||
<option value="">Pilih Waktu Makan</option>
|
||||
@foreach($waktuMakans as $waktu)
|
||||
<option value="{{ $waktu->id }}" {{ old('waktu_makan_id', $relasi->waktu_makan_id) == $waktu->id ? 'selected' : '' }}>
|
||||
{{ $waktu->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('waktu_makan_id')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-form-label">
|
||||
<i class="fas fa-toggle-on me-2"></i>Status
|
||||
</label>
|
||||
<select name="status" class="admin-form-control @error('status') is-invalid @enderror">
|
||||
<option value="1" {{ old('status', $relasi->status) == '1' ? 'selected' : '' }}>Aktif</option>
|
||||
<option value="0" {{ old('status', $relasi->status) == '0' ? 'selected' : '' }}>Tidak Aktif</option>
|
||||
</select>
|
||||
@error('status')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-end mt-4">
|
||||
<a href="{{ route('relasi.index') }}" class="admin-btn btn-secondary me-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>Kembali
|
||||
</a>
|
||||
<button type="submit" class="admin-btn btn-primary">
|
||||
<i class="fas fa-save me-2"></i>Simpan Perubahan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="waktu_makan_id">Waktu Makan</label>
|
||||
<select name="waktu_makan_id" class="form-control">
|
||||
@foreach($waktuMakans as $waktu)
|
||||
<option value="{{ $waktu->id }}" {{ $relasi->waktu_makan_id == $waktu->id ? 'selected' : '' }}>
|
||||
{{ $waktu->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success">Simpan Perubahan</button>
|
||||
<a href="{{ route('relasi') }}" class="btn btn-secondary">Batal</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize Select2
|
||||
$('.admin-form-control').select2({
|
||||
theme: 'bootstrap-5',
|
||||
width: '100%'
|
||||
});
|
||||
|
||||
// Animation
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -1,143 +1,128 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Relasi Makanan')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<h2 class="mb-4">Tambah Relasi Makanan - Komponen - Waktu Makan</h2>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success">{{ session('success') }}</div>
|
||||
@elseif(session('error'))
|
||||
<div class="alert alert-danger">{{ session('error') }}</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('relasi.store') }}" class="mb-4">
|
||||
@csrf
|
||||
<div class="form-group mb-2">
|
||||
<label for="makanan_id">Makanan</label>
|
||||
<select name="makanan_id" class="form-control">
|
||||
@foreach($makanans as $makanan)
|
||||
<option value="{{ $makanan->id }}">{{ $makanan->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-2">
|
||||
<label for="komponen_id">Komponen</label>
|
||||
<select name="komponen_id" class="form-control">
|
||||
@foreach($komponens as $komponen)
|
||||
<option value="{{ $komponen->id }}">{{ $komponen->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="waktu_makan_id">Waktu Makan</label>
|
||||
<select name="waktu_makan_id" class="form-control">
|
||||
@foreach($waktuMakans as $waktu)
|
||||
<option value="{{ $waktu->id }}">{{ $waktu->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Simpan Relasi</button>
|
||||
</form>
|
||||
|
||||
<section class="section">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Filter Relasi</h5>
|
||||
|
||||
<form method="GET" action="{{ route('relasi') }}" class="row g-3 align-items-end mb-4">
|
||||
<div class="col-md-4">
|
||||
<label for="komponen_id" class="form-label">Filter Komponen</label>
|
||||
<select name="komponen_id" class="form-select">
|
||||
<option value="">-- Semua Komponen --</option>
|
||||
@foreach($komponens as $komponen)
|
||||
<option value="{{ $komponen->id }}" {{ request('komponen_id') == $komponen->id ? 'selected' : '' }}>
|
||||
{{ $komponen->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label for="waktu_makan_id" class="form-label">Filter Waktu Makan</label>
|
||||
<select name="waktu_makan_id" class="form-select">
|
||||
<option value="">-- Semua Waktu Makan --</option>
|
||||
@foreach($waktuMakans as $waktu)
|
||||
<option value="{{ $waktu->id }}" {{ request('waktu_makan_id') == $waktu->id ? 'selected' : '' }}>
|
||||
{{ $waktu->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<button type="submit" class="btn btn-primary">Terapkan Filter</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h5 class="card-title mb-0">Data Relasi Tersimpan</h5>
|
||||
</div>
|
||||
|
||||
<!-- Table with stripped rows -->
|
||||
<table class="table datatable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">No</th>
|
||||
<th class="text-center">Makanan</th>
|
||||
<th class="text-center">Komponen</th>
|
||||
<th class="text-center">Waktu Makanan</th>
|
||||
<th class="text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($relasis as $relasi)
|
||||
<tr>
|
||||
<td class="text-center">{{ $loop->iteration }}</td>
|
||||
<td class="text-center">{{ $relasi->makanan->nama }}</td>
|
||||
<td class="text-center">{{ $relasi->komponen->nama }}</td>
|
||||
<td class="text-center">{{ $relasi->waktuMakan->nama }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ route('relasi.edit', $relasi->id) }}" class="btn btn-warning btn-sm" title="Edit">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<form action="{{ route('relasi.destroy', $relasi->id) }}" method="POST" onsubmit="return confirm('Yakin ingin menghapus?');" style="display:inline;">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button class="btn btn-danger btn-sm" title="Hapus">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
|
||||
{{-- <td class="text-center">
|
||||
<form action="{{ route('relasi.destroy', $relasi->id) }}" method="POST" onsubmit="return confirm('Yakin ingin menghapus?');" style="display:inline;">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button class="btn btn-danger btn-sm" title="Hapus">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td> --}}
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- End Table -->
|
||||
</div>
|
||||
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-link me-2"></i>Relasi Makanan
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Relasi Makanan</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-list me-2"></i>Daftar Relasi
|
||||
</h5>
|
||||
<a href="{{ route('relasi.create') }}" class="admin-btn btn-primary">
|
||||
<i class="fas fa-plus me-2"></i>Tambah Relasi
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>Makanan</th>
|
||||
<th>Komponen</th>
|
||||
<th>Waktu Makan</th>
|
||||
<th>Status</th>
|
||||
<th class="text-center">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($relasis as $index => $relasi)
|
||||
<tr>
|
||||
<td>{{ $index + 1 }}</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
{{ $relasi->makanan->nama }}
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ $relasi->komponen->nama }}</td>
|
||||
<td>{{ $relasi->waktuMakan->nama }}</td>
|
||||
<td>
|
||||
<span class="admin-badge {{ $relasi->status ? 'bg-success' : 'bg-danger' }}">
|
||||
{{ $relasi->status ? 'Aktif' : 'Tidak Aktif' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ route('relasi.edit', $relasi->id) }}"
|
||||
class="admin-btn btn-warning btn-sm me-2">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<form action="{{ route('relasi.destroy', $relasi->id) }}"
|
||||
method="POST"
|
||||
class="d-inline delete-form"
|
||||
onsubmit="return confirm('Apakah Anda yakin ingin menghapus data ini?');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="admin-btn btn-danger btn-sm">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-5">
|
||||
<i class="fas fa-folder-open fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">Tidak ada data relasi</p>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if($relasis->hasPages())
|
||||
<div class="d-flex justify-content-center mt-4">
|
||||
{{ $relasis->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animateElements = document.querySelectorAll('.animate-fade-in');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = 1;
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animateElements.forEach(element => {
|
||||
element.style.opacity = 0;
|
||||
element.style.transform = 'translateY(20px)';
|
||||
observer.observe(element);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -1,59 +1,78 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('title', 'Role Management')
|
||||
|
||||
@include('admin.shared.admin-styles')
|
||||
|
||||
@section('content')
|
||||
<div class="pagetitle">
|
||||
<div class="admin-container container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header animate-fade-in">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-white">
|
||||
<i class="fas fa-user-tag me-2"></i>Role Management
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}" class="text-white-50">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Roles</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Role</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admindash') }}">Home</a></li>
|
||||
<li class="breadcrumb-item active"><a href="{{ route('role') }}"> role</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<section class="section">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="col-12">
|
||||
<div class="admin-card animate-fade-in">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-2">role</h5>
|
||||
<div class="text-center">
|
||||
<a href="{{ route('tambahrole') }}" class="btn btn-primary">Tambah</a>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-list me-2"></i>Role List
|
||||
</h5>
|
||||
<a href="{{ route('tambahrole') }}" class="admin-btn btn-primary">
|
||||
<i class="fas fa-plus me-2"></i>Add New Role
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Table with stripped rows -->
|
||||
<table class="table datatable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">No</th>
|
||||
<th class="text-center">Role</th>
|
||||
<th class="text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($roles as $rl)
|
||||
<tr>
|
||||
<td class="text-center">{{ $loop->iteration }}</td>
|
||||
<td class="text-center">{{ $rl->name }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ route('editrole', $rl->id) }}" class="btn btn-sm btn-warning">Edit</a>
|
||||
<form action="{{ url('/role', $rl->id) }}" method="POST" style="display: inline;">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger">Hapus</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- End Table with stripped rows -->
|
||||
<div class="table-responsive">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center" width="10%">No</th>
|
||||
<th>Role Name</th>
|
||||
<th class="text-center" width="20%">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($roles as $rl)
|
||||
<tr>
|
||||
<td class="text-center">{{ $loop->iteration }}</td>
|
||||
<td>{{ $rl->name }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ route('editrole', $rl->id) }}"
|
||||
class="admin-btn btn-warning btn-sm me-2">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<form action="{{ url('/role', $rl->id) }}" method="POST"
|
||||
class="d-inline"
|
||||
onsubmit="return confirm('Are you sure you want to delete this role?');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="admin-btn btn-danger btn-sm">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
@push('styles')
|
||||
<style>
|
||||
/* Common Variables */
|
||||
:root {
|
||||
--primary-gradient: linear-gradient(135deg, #0d6efd 0%, #0099ff 100%);
|
||||
--secondary-gradient: linear-gradient(135deg, #6c757d 0%, #868e96 100%);
|
||||
--success-gradient: linear-gradient(135deg, #198754 0%, #28a745 100%);
|
||||
--warning-gradient: linear-gradient(135deg, #ffc107 0%, #ffcd39 100%);
|
||||
--danger-gradient: linear-gradient(135deg, #dc3545 0%, #e35d6a 100%);
|
||||
--card-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
--card-shadow-hover: 0 4px 8px rgba(0,0,0,0.2);
|
||||
--transition-speed: 0.3s;
|
||||
--border-radius: 12px;
|
||||
--spacing-unit: 1rem;
|
||||
}
|
||||
|
||||
/* Layout & Containers */
|
||||
.admin-container {
|
||||
padding: var(--spacing-unit) 0;
|
||||
background-color: #f8f9fa;
|
||||
min-height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: var(--primary-gradient);
|
||||
padding: calc(var(--spacing-unit) * 2);
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: calc(var(--spacing-unit) * 2);
|
||||
color: white;
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
background: white;
|
||||
border-radius: var(--border-radius);
|
||||
padding: var(--spacing-unit);
|
||||
margin-bottom: var(--spacing-unit);
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.admin-card {
|
||||
transition: all var(--transition-speed) ease;
|
||||
border: none;
|
||||
box-shadow: var(--card-shadow);
|
||||
border-radius: var(--border-radius);
|
||||
height: 100%;
|
||||
background: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.admin-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: var(--card-shadow-hover);
|
||||
}
|
||||
|
||||
.admin-card .card-header {
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0 !important;
|
||||
padding: var(--spacing-unit);
|
||||
border: none;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.admin-card .card-body {
|
||||
padding: var(--spacing-unit);
|
||||
background: white;
|
||||
}
|
||||
|
||||
.admin-card .card-footer {
|
||||
background: white;
|
||||
border-top: 1px solid rgba(0,0,0,0.1);
|
||||
padding: var(--spacing-unit);
|
||||
}
|
||||
|
||||
/* Feature Cards */
|
||||
.feature-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: var(--border-radius);
|
||||
background: white;
|
||||
transition: all var(--transition-speed) ease;
|
||||
}
|
||||
|
||||
.feature-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: var(--primary-gradient);
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: var(--card-shadow-hover);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
margin-bottom: 1rem;
|
||||
background: var(--primary-gradient);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Badges & Labels */
|
||||
.admin-badge {
|
||||
font-weight: 500;
|
||||
padding: 0.5em 0.8em;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
background: var(--primary-gradient);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.admin-badge:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.admin-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 6px;
|
||||
transition: all var(--transition-speed) ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.admin-btn.btn-primary {
|
||||
background: var(--primary-gradient);
|
||||
}
|
||||
|
||||
.admin-btn.btn-secondary {
|
||||
background: var(--secondary-gradient);
|
||||
}
|
||||
|
||||
.admin-btn.btn-success {
|
||||
background: var(--success-gradient);
|
||||
}
|
||||
|
||||
.admin-btn.btn-warning {
|
||||
background: var(--warning-gradient);
|
||||
}
|
||||
|
||||
.admin-btn.btn-danger {
|
||||
background: var(--danger-gradient);
|
||||
}
|
||||
|
||||
.admin-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.admin-btn i {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.admin-table {
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
background-color: white;
|
||||
border-collapse: collapse;
|
||||
border-radius: var(--border-radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.admin-table th,
|
||||
.admin-table td {
|
||||
padding: 1rem;
|
||||
vertical-align: middle;
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.admin-table thead th {
|
||||
vertical-align: bottom;
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.admin-table tbody tr:hover {
|
||||
background-color: rgba(13, 110, 253, 0.05);
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
.admin-form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.admin-form-label {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.admin-form-control {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: var(--border-radius);
|
||||
transition: border-color var(--transition-speed) ease-in-out;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.admin-form-control:focus {
|
||||
border-color: #0d6efd;
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
/* Alerts */
|
||||
.admin-alert {
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: calc(var(--spacing-unit) * 2);
|
||||
padding: 1rem;
|
||||
border: none;
|
||||
background: white;
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.5s ease-in;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* Responsive Utilities */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
padding: var(--spacing-unit);
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
padding: calc(var(--spacing-unit) * 0.5);
|
||||
}
|
||||
|
||||
.admin-card {
|
||||
margin-bottom: var(--spacing-unit);
|
||||
}
|
||||
|
||||
.admin-table {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue