MIF_E31222307/resources/views/admin/rekomendasi-list.blade.php

483 lines
15 KiB
PHP

@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