1375 lines
67 KiB
PHP
1375 lines
67 KiB
PHP
@extends('layouts.admin')
|
|
|
|
@section('title', 'Kelola Kriteria SAW')
|
|
|
|
@section('content')
|
|
<div class="container-fluid px-2 px-md-4 page-kriteria">
|
|
<!-- Enhanced Mobile-First Styling -->
|
|
<style>
|
|
/* Page Background & Pattern */
|
|
.page-kriteria {
|
|
position: relative;
|
|
min-height: 100vh;
|
|
background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 50%, #faf5ff 100%);
|
|
}
|
|
|
|
.page-kriteria::before {
|
|
content: '';
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-image:
|
|
radial-gradient(circle at 15% 40%, rgba(251, 146, 60, 0.08) 0%, transparent 40%),
|
|
radial-gradient(circle at 85% 60%, rgba(102, 126, 234, 0.06) 0%, transparent 40%),
|
|
radial-gradient(circle at 50% 80%, rgba(118, 75, 162, 0.04) 0%, transparent 50%);
|
|
pointer-events: none;
|
|
z-index: -1;
|
|
}
|
|
|
|
/* Floating Decorations */
|
|
.floating-decoration {
|
|
position: fixed;
|
|
pointer-events: none;
|
|
z-index: 0;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.floating-decoration.deco-1 {
|
|
top: 12%;
|
|
right: 10%;
|
|
width: 130px;
|
|
height: 130px;
|
|
background: linear-gradient(135deg, rgba(251, 146, 60, 0.15) 0%, rgba(102, 126, 234, 0.05) 100%);
|
|
border-radius: 50%;
|
|
filter: blur(28px);
|
|
animation: kritFloat 8s ease-in-out infinite;
|
|
}
|
|
|
|
.floating-decoration.deco-2 {
|
|
bottom: 30%;
|
|
left: 8%;
|
|
width: 160px;
|
|
height: 160px;
|
|
background: linear-gradient(135deg, rgba(118, 75, 162, 0.12) 0%, rgba(251, 146, 60, 0.05) 100%);
|
|
border-radius: 50%;
|
|
filter: blur(35px);
|
|
animation: kritFloat 10s ease-in-out infinite reverse;
|
|
}
|
|
|
|
.floating-decoration.deco-3 {
|
|
top: 65%;
|
|
right: 18%;
|
|
width: 80px;
|
|
height: 80px;
|
|
background: linear-gradient(135deg, rgba(40, 167, 69, 0.12) 0%, transparent 100%);
|
|
border-radius: 50%;
|
|
filter: blur(20px);
|
|
animation: kritFloat 6s ease-in-out infinite 1s;
|
|
}
|
|
|
|
@keyframes kritFloat {
|
|
0%, 100% { transform: translateY(0px); }
|
|
50% { transform: translateY(-15px); }
|
|
}
|
|
|
|
/* Stats Cards */
|
|
.stats-overview {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.stat-card {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(10px);
|
|
border-radius: 16px;
|
|
padding: 1.25rem;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
|
|
border: 1px solid rgba(255, 255, 255, 0.8);
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.stat-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 4px;
|
|
background: linear-gradient(90deg, var(--stat-color), transparent);
|
|
}
|
|
|
|
.stat-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 8px 30px rgba(251, 146, 60, 0.15);
|
|
}
|
|
|
|
.stat-card .stat-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.4rem;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.stat-card .stat-value {
|
|
font-size: 1.6rem;
|
|
font-weight: 800;
|
|
color: var(--stat-color);
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.stat-card .stat-label {
|
|
font-size: 0.82rem;
|
|
color: #6c757d;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Enhanced Content Card */
|
|
.main-content-card {
|
|
background: rgba(255, 255, 255, 0.98);
|
|
backdrop-filter: blur(20px);
|
|
border-radius: 20px;
|
|
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.08);
|
|
border: 1px solid rgba(255, 255, 255, 0.9);
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.main-content-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 5px;
|
|
background: linear-gradient(90deg, #818cf8 0%, #667eea 50%, #764ba2 100%);
|
|
background-size: 200% 100%;
|
|
animation: gradientMove 3s ease infinite;
|
|
}
|
|
|
|
@keyframes gradientMove {
|
|
0%, 100% { background-position: 0% 50%; }
|
|
50% { background-position: 100% 50%; }
|
|
}
|
|
|
|
.kriteria-header {
|
|
background: linear-gradient(135deg, #818cf8 0%, #667eea 50%, #764ba2 100%);
|
|
border-radius: 1rem;
|
|
padding: 1.5rem;
|
|
color: white;
|
|
margin-bottom: 1.5rem;
|
|
box-shadow: 0 10px 30px rgba(251, 146, 60, 0.25);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.kriteria-header::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -50%;
|
|
right: -50%;
|
|
width: 200%;
|
|
height: 200%;
|
|
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
|
|
animation: kriteriaFloat 8s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes kriteriaFloat {
|
|
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
|
50% { transform: translateY(-15px) rotate(3deg); }
|
|
}
|
|
|
|
.btn-kriteria {
|
|
background: rgba(255,255,255,0.95);
|
|
color: #818cf8;
|
|
font-weight: 600;
|
|
border: 2px solid rgba(255,255,255,0.3);
|
|
padding: 0.6rem 1rem;
|
|
border-radius: 0.75rem;
|
|
transition: all 0.3s ease;
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.btn-kriteria:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 8px 25px rgba(0,0,0,0.2);
|
|
background: white;
|
|
color: #667eea;
|
|
}
|
|
|
|
.enhanced-kriteria-card {
|
|
background: rgba(255,255,255,0.9);
|
|
backdrop-filter: blur(10px);
|
|
border: none;
|
|
border-radius: 1rem;
|
|
box-shadow: 0 8px 25px rgba(251, 146, 60, 0.1);
|
|
transition: all 0.3s ease;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.enhanced-kriteria-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 15px 35px rgba(251, 146, 60, 0.15);
|
|
}
|
|
|
|
/* Enhanced Table */
|
|
.enhanced-table thead {
|
|
background: linear-gradient(135deg, #818cf8 0%, #667eea 100%);
|
|
}
|
|
|
|
.enhanced-table thead th {
|
|
color: white;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
font-size: 0.75rem;
|
|
letter-spacing: 0.5px;
|
|
padding: 1rem 0.75rem;
|
|
border: none;
|
|
}
|
|
|
|
.enhanced-table tbody tr {
|
|
background: rgba(255, 255, 255, 0.7);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.enhanced-table tbody tr:nth-child(even) {
|
|
background: rgba(251, 146, 60, 0.03);
|
|
}
|
|
|
|
.enhanced-table tbody tr:hover {
|
|
background: rgba(251, 146, 60, 0.08);
|
|
transform: scale(1.005);
|
|
}
|
|
|
|
.enhanced-table tbody td {
|
|
padding: 1rem 0.75rem;
|
|
vertical-align: middle;
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.kriteria-header {
|
|
padding: 1rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
.btn-kriteria {
|
|
width: 100%;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
.stats-overview {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
.stat-card {
|
|
padding: 1rem;
|
|
}
|
|
.stat-card .stat-value {
|
|
font-size: 1.35rem;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 576px) {
|
|
.stats-overview {
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 0.75rem;
|
|
}
|
|
}
|
|
|
|
.glass-card {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255, 255, 255, 0.8);
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
.glass-card-light {
|
|
background: rgba(255, 255, 255, 0.7);
|
|
backdrop-filter: blur(5px);
|
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
}
|
|
</style>
|
|
|
|
<!-- Floating Decorations -->
|
|
<div class="floating-decoration deco-1"></div>
|
|
<div class="floating-decoration deco-2"></div>
|
|
<div class="floating-decoration deco-3"></div>
|
|
|
|
<!-- Enhanced Header Section -->
|
|
<div class="kriteria-header">
|
|
<div class="row align-items-center position-relative" style="z-index: 2;">
|
|
<div class="col-12 col-md-8">
|
|
<h2 class="mb-2 fw-bold">
|
|
<i class="bi bi-diagram-3 me-2 me-md-3"></i>
|
|
<span class="d-none d-sm-inline">📊 Kelola Kriteria SAW</span>
|
|
<span class="d-sm-none">📊 Kriteria</span>
|
|
</h2>
|
|
<p class="mb-0 opacity-90">
|
|
<span class="d-none d-md-inline">Atur dan kelola kriteria penilaian untuk metode Simple Additive Weighting</span>
|
|
<span class="d-md-none">Kelola kriteria SAW</span>
|
|
</p>
|
|
</div>
|
|
<div class="col-12 col-md-4 text-md-end mt-3 mt-md-0">
|
|
<a href="{{ route('kriteria.create') }}" class="btn btn-kriteria">
|
|
<i class="bi bi-plus-circle me-2"></i>
|
|
<span class="d-none d-sm-inline">Tambah Kriteria</span>
|
|
<span class="d-sm-none">Tambah</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enhanced Alert Success -->
|
|
@if(session('success'))
|
|
<div class="alert alert-success alert-dismissible fade show shadow-lg rounded-3 border-0" role="alert" style="background: linear-gradient(135deg, var(--success-color), #20c997); color: white; border-left: 4px solid white;">
|
|
<i class="bi bi-check-circle-fill me-2"></i>
|
|
<strong>Berhasil!</strong>
|
|
<span class="small">{{ session('success') }}</span>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Enhanced Alert Error -->
|
|
@if(session('error'))
|
|
<div class="alert alert-danger alert-dismissible fade show shadow-lg rounded-3 border-0" role="alert" style="background: linear-gradient(135deg, var(--danger-color), #e74c3c); color: white; border-left: 4px solid white;">
|
|
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
|
<strong>Terjadi Kesalahan!</strong>
|
|
<span class="small">{{ session('error') }}</span>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Enhanced Alert Validasi Bobot -->
|
|
@if(!empty($bobotWarnings))
|
|
<div class="alert alert-dismissible fade show shadow-lg rounded-3 border-0 mb-4" role="alert" style="background: linear-gradient(135deg, var(--warning-color), #f39c12); color: white; border-left: 4px solid white;">
|
|
<div class="d-flex align-items-start">
|
|
<div class="me-3">
|
|
<i class="bi bi-exclamation-triangle-fill" style="font-size: 1.8rem; opacity: 0.9;"></i>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<h6 class="fw-bold mb-2">⚠️ Peringatan Total Bobot!</h6>
|
|
<ul class="mb-3 ps-3" style="line-height: 1.6;">
|
|
@foreach($bobotWarnings as $warning)
|
|
<li class="mb-1">{{ $warning }}</li>
|
|
@endforeach
|
|
</ul>
|
|
<div class="p-2 rounded" style="background: rgba(255,255,255,0.2); backdrop-filter: blur(5px);">
|
|
<small class="d-block">
|
|
<i class="bi bi-info-circle me-1"></i>
|
|
<span class="d-none d-md-inline">Total bobot untuk setiap tipe bisnis harus = 1.00 (atau 100%) agar perhitungan SAW valid dan akurat.</span>
|
|
<span class="d-md-none">Total bobot tiap tipe bisnis harus = 1.00 (100%)</span>
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Enhanced Main Card -->
|
|
<div class="glass-card border-0 shadow-lg">
|
|
<!-- Enhanced Search & Filter Section -->
|
|
<div class="card-header border-0 py-3 py-md-4" style="border-radius: 1rem 1rem 0 0; background: rgba(255,255,255,0.95); backdrop-filter: blur(10px);">
|
|
<form action="{{ route('kriteria.index') }}" method="GET" id="filterForm">
|
|
<!-- Hidden inputs untuk maintain sorting saat filter -->
|
|
<input type="hidden" name="sort_by" id="hiddenSortBy" value="{{ $sortBy ?? '' }}">
|
|
<input type="hidden" name="sort_order" id="hiddenSortOrder" value="{{ $sortOrder ?? '' }}">
|
|
|
|
<div class="row g-3 align-items-center">
|
|
<!-- Enhanced Search Input -->
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label fw-bold mb-2 d-block d-md-none">
|
|
<i class="bi bi-search me-1" style="color: var(--primary-color);"></i>Cari Kriteria
|
|
</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text bg-light border-end-0" style="border-color: var(--border-color);">
|
|
<i class="bi bi-search" style="color: var(--primary-color);"></i>
|
|
</span>
|
|
<input
|
|
type="text"
|
|
class="form-control border-start-0"
|
|
id="searchInput"
|
|
placeholder="Cari nama kriteria..."
|
|
style="border-color: var(--border-color);"
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enhanced Filter Dropdown -->
|
|
<div class="col-12 col-md-3">
|
|
<label class="form-label fw-bold mb-2 d-block d-md-none">
|
|
<i class="bi bi-funnel me-1" style="color: var(--secondary-color);"></i>Filter Tipe
|
|
</label>
|
|
<select class="form-select" id="filterTipeBisnis" name="filter" onchange="this.form.submit()" style="border-color: var(--border-color);">
|
|
<option value="" {{ empty($filterTipeBisnis) ? 'selected' : '' }}>🔍 Semua Tipe Bisnis</option>
|
|
<option value="kontrakan" {{ $filterTipeBisnis == 'kontrakan' ? 'selected' : '' }}>🏠 Kontrakan</option>
|
|
<option value="laundry" {{ $filterTipeBisnis == 'laundry' ? 'selected' : '' }}>👕 Laundry</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Enhanced Count Badges -->
|
|
<div class="col-12 col-md-5">
|
|
<div class="d-flex flex-column flex-md-row gap-2 justify-content-md-end">
|
|
<span class="badge px-3 py-2 rounded-3" style="background: rgba(var(--primary-color-rgb), 0.1); color: var(--primary-color); font-size: 0.85rem; font-weight: 600;">
|
|
🏠 Kontrakan: <strong id="countKontrakan">{{ isset($allKriteria) ? $allKriteria->where('tipe_bisnis', 'kontrakan')->count() : 0 }}</strong>
|
|
</span>
|
|
<span class="badge px-3 py-2 rounded-3" style="background: rgba(var(--success-color-rgb), 0.1); color: var(--success-color); font-size: 0.85rem; font-weight: 600;">
|
|
👕 Laundry: <strong id="countLaundry">{{ isset($allKriteria) ? $allKriteria->where('tipe_bisnis', 'laundry')->count() : 0 }}</strong>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Enhanced Card Body -->
|
|
<div class="card-body p-0" style="background: rgba(255,255,255,0.98); backdrop-filter: blur(5px);">
|
|
@if($kriteria->isEmpty())
|
|
<!-- Enhanced Empty State -->
|
|
<div class="text-center py-5 px-3">
|
|
<div class="mb-4" style="animation: pulse 2s infinite;">
|
|
<div class="d-inline-flex align-items-center justify-content-center"
|
|
style="width: 100px; height: 100px; background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); border-radius: 50%; opacity: 0.1;">
|
|
<i class="bi bi-list-check" style="font-size: 3rem; color: var(--primary-color);"></i>
|
|
</div>
|
|
</div>
|
|
<h5 class="mb-3" style="color: var(--text-secondary); font-weight: 600;">Belum ada kriteria</h5>
|
|
<p class="text-muted mb-4 px-md-5">Mulai tambahkan kriteria untuk penilaian SAW (Simple Additive Weighting) agar dapat melakukan perhitungan rekomendasi yang akurat.</p>
|
|
<a href="{{ route('kriteria.create') }}" class="btn btn-primary btn-lg rounded-3 px-4" style="box-shadow: 0 6px 20px rgba(var(--primary-color-rgb), 0.3); border: none; background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));">
|
|
<i class="bi bi-plus-circle me-2"></i>Tambah Kriteria Pertama
|
|
</a>
|
|
</div>
|
|
@else
|
|
<!-- Mobile Card View (Hidden on desktop) -->
|
|
<div class="d-block d-lg-none">
|
|
<div class="p-3 p-md-4">
|
|
@foreach($kriteria as $index => $item)
|
|
<div class="card border-0 mb-3 glass-card-light"
|
|
data-tipe-bisnis="{{ $item->tipe_bisnis }}"
|
|
data-bobot="{{ $item->bobot }}"
|
|
data-tipe="{{ strtolower($item->tipe) }}"
|
|
style="transition: all 0.3s ease; cursor: pointer;"
|
|
onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 8px 25px rgba(0,0,0,0.15)'"
|
|
onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 2px 10px rgba(0,0,0,0.1)'">
|
|
<div class="card-body p-3">
|
|
<!-- Header Row -->
|
|
<div class="d-flex justify-content-between align-items-start mb-3">
|
|
<div class="d-flex align-items-center">
|
|
<div class="badge bg-light text-primary rounded-circle me-2" style="width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 0.8rem;">{{ $index + 1 }}</div>
|
|
@if($item->tipe_bisnis == 'kontrakan')
|
|
<span class="badge px-3 py-2 rounded-3" style="background: rgba(var(--primary-color-rgb), 0.15); color: var(--primary-color); font-weight: 600;">
|
|
🏠 Kontrakan
|
|
</span>
|
|
@else
|
|
<span class="badge px-3 py-2 rounded-3" style="background: rgba(var(--success-color-rgb), 0.15); color: var(--success-color); font-weight: 600;">
|
|
👕 Laundry
|
|
</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Kriteria Name -->
|
|
<h6 class="fw-bold mb-3" style="color: var(--text-primary); line-height: 1.4;">{{ $item->nama_kriteria }}</h6>
|
|
|
|
<!-- Details Grid -->
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-6">
|
|
<div class="d-flex flex-column">
|
|
<small class="text-muted fw-bold mb-1">BOBOT</small>
|
|
<div class="d-flex align-items-center">
|
|
<span class="badge px-2 py-1 rounded-3 me-2" style="background: rgba(var(--warning-color-rgb), 0.15); color: var(--warning-color); font-weight: 700; font-size: 0.9rem;">{{ $item->bobot }}</span>
|
|
<small class="text-muted">({{ number_format($item->bobot * 100, 1) }}%)</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="d-flex flex-column">
|
|
<small class="text-muted fw-bold mb-1">TIPE</small>
|
|
@if(strtolower($item->tipe) == 'benefit')
|
|
<span class="badge px-2 py-1 rounded-3" style="background: rgba(var(--success-color-rgb), 0.15); color: var(--success-color); font-weight: 600; font-size: 0.85rem;">
|
|
📈 {{ ucfirst($item->tipe) }}
|
|
</span>
|
|
@else
|
|
<span class="badge px-2 py-1 rounded-3" style="background: rgba(var(--danger-color-rgb), 0.15); color: var(--danger-color); font-weight: 600; font-size: 0.85rem;">
|
|
📉 {{ ucfirst($item->tipe) }}
|
|
</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Description -->
|
|
@if($item->keterangan)
|
|
<div class="mb-3">
|
|
<small class="text-muted fw-bold d-block mb-1">KETERANGAN</small>
|
|
<p class="small mb-0" style="color: var(--text-secondary); line-height: 1.5;">{{ $item->keterangan }}</p>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="d-flex gap-2">
|
|
<a href="{{ route('kriteria.edit', $item->id) }}" class="btn btn-sm rounded-3 flex-fill" style="background: rgba(var(--warning-color-rgb), 0.15); color: var(--warning-color); border: 1px solid rgba(var(--warning-color-rgb), 0.3); font-weight: 600;">
|
|
<i class="bi bi-pencil-square me-1"></i>Edit
|
|
</a>
|
|
<button type="button" class="btn btn-sm rounded-3 flex-fill"
|
|
onclick="confirmDelete('{{ $item->id }}', '{{ addslashes($item->nama_kriteria) }}')"
|
|
style="background: rgba(var(--danger-color-rgb), 0.15); color: var(--danger-color); border: 1px solid rgba(var(--danger-color-rgb), 0.3); font-weight: 600;">
|
|
<i class="bi bi-trash me-1"></i>Hapus
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Desktop Table View (Hidden on mobile) -->
|
|
<div class="d-none d-lg-block">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle mb-0" id="kriteriaTable">
|
|
<thead style="background: rgba(248, 249, 250, 0.8); backdrop-filter: blur(5px);">
|
|
<tr>
|
|
<th class="border-0 px-4 py-3" style="border-radius: 0;">
|
|
<small class="text-uppercase fw-bold" style="color: var(--text-muted); letter-spacing: 0.5px;">No</small>
|
|
</th>
|
|
<th class="border-0 py-3 sortable" data-sort="tipe_bisnis" style="cursor: pointer; transition: all 0.2s ease;">
|
|
<small class="text-uppercase fw-bold d-flex align-items-center" style="color: var(--text-muted); letter-spacing: 0.5px;">
|
|
Tipe Bisnis
|
|
<i class="bi bi-arrow-down-up ms-2 sort-icon" style="opacity: 0.6;"></i>
|
|
</small>
|
|
</th>
|
|
<th class="border-0 py-3 sortable" data-sort="nama_kriteria" style="cursor: pointer; transition: all 0.2s ease;">
|
|
<small class="text-uppercase fw-bold d-flex align-items-center" style="color: var(--text-muted); letter-spacing: 0.5px;">
|
|
Nama Kriteria
|
|
<i class="bi bi-arrow-down-up ms-2 sort-icon" style="opacity: 0.6;"></i>
|
|
</small>
|
|
</th>
|
|
<th class="border-0 py-3 text-center sortable" data-sort="bobot" style="cursor: pointer; transition: all 0.2s ease;">
|
|
<small class="text-uppercase fw-bold d-flex align-items-center justify-content-center" style="color: var(--text-muted); letter-spacing: 0.5px;">
|
|
Bobot
|
|
<i class="bi bi-arrow-down-up ms-2 sort-icon" style="opacity: 0.6;"></i>
|
|
</small>
|
|
</th>
|
|
<th class="border-0 py-3 text-center sortable" data-sort="tipe" style="cursor: pointer; transition: all 0.2s ease;">
|
|
<small class="text-uppercase fw-bold d-flex align-items-center justify-content-center" style="color: var(--text-muted); letter-spacing: 0.5px;">
|
|
Tipe
|
|
<i class="bi bi-arrow-down-up ms-2 sort-icon" style="opacity: 0.6;"></i>
|
|
</small>
|
|
</th>
|
|
<th class="border-0 py-3">
|
|
<small class="text-uppercase fw-bold" style="color: var(--text-muted); letter-spacing: 0.5px;">Keterangan</small>
|
|
</th>
|
|
<th class="border-0 py-3 text-center">
|
|
<small class="text-uppercase fw-bold" style="color: var(--text-muted); letter-spacing: 0.5px;">Aksi</small>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach($kriteria as $index => $item)
|
|
<tr data-tipe-bisnis="{{ $item->tipe_bisnis }}" data-bobot="{{ $item->bobot }}" data-tipe="{{ strtolower($item->tipe) }}" style="transition: all 0.2s ease;">
|
|
<td class="px-4">
|
|
<div class="badge bg-light rounded-circle d-flex align-items-center justify-content-center" style="width: 32px; height: 32px; color: var(--primary-color); font-weight: 600;">{{ $index + 1 }}</div>
|
|
</td>
|
|
<td>
|
|
@if($item->tipe_bisnis == 'kontrakan')
|
|
<span class="badge px-3 py-2 rounded-3" style="background: rgba(var(--primary-color-rgb), 0.15); color: var(--primary-color); font-weight: 600;">
|
|
🏠 Kontrakan
|
|
</span>
|
|
@else
|
|
<span class="badge px-3 py-2 rounded-3" style="background: rgba(var(--success-color-rgb), 0.15); color: var(--success-color); font-weight: 600;">
|
|
👕 Laundry
|
|
</span>
|
|
@endif
|
|
</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="avatar-sm d-flex align-items-center justify-content-center me-3 rounded-3" style="width: 42px; height: 42px; background: rgba(var(--primary-color-rgb), 0.1); color: var(--primary-color);">
|
|
<i class="bi bi-star-fill" style="font-size: 1.2rem;"></i>
|
|
</div>
|
|
<div>
|
|
<h6 class="mb-1 fw-bold" style="color: var(--text-primary); font-size: 0.95rem;">{{ $item->nama_kriteria }}</h6>
|
|
<small style="color: var(--text-muted);">Kriteria {{ $index + 1 }}</small>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="text-center">
|
|
<div class="d-flex flex-column align-items-center">
|
|
<span class="badge px-3 py-2 rounded-3 mb-1" style="background: rgba(var(--info-color-rgb), 0.15); color: var(--info-color); font-weight: 700; font-size: 1rem;">{{ $item->bobot }}</span>
|
|
<small style="color: var(--text-muted); font-size: 0.75rem;">({{ number_format($item->bobot * 100, 1) }}%)</small>
|
|
</div>
|
|
</td>
|
|
<td class="text-center">
|
|
@if(strtolower($item->tipe) == 'benefit')
|
|
<span class="badge px-3 py-2 rounded-3" style="background: rgba(var(--success-color-rgb), 0.15); color: var(--success-color); font-weight: 600;">
|
|
<i class="bi bi-arrow-up-circle me-1"></i>📈 Benefit
|
|
</span>
|
|
@else
|
|
<span class="badge px-3 py-2 rounded-3" style="background: rgba(var(--danger-color-rgb), 0.15); color: var(--danger-color); font-weight: 600;">
|
|
<i class="bi bi-arrow-down-circle me-1"></i>📉 Cost
|
|
</span>
|
|
@endif
|
|
</td>
|
|
<td>
|
|
<div style="max-width: 200px;">
|
|
<small style="color: var(--text-secondary); line-height: 1.4;">
|
|
{{ $item->keterangan ? Str::limit($item->keterangan, 60) : '-' }}
|
|
</small>
|
|
</div>
|
|
</td>
|
|
<td class="text-center">
|
|
<div class="btn-group" role="group">
|
|
<a href="{{ route('kriteria.edit', $item->id) }}"
|
|
class="btn btn-sm rounded-3 me-2"
|
|
style="background: rgba(var(--warning-color-rgb), 0.15); color: var(--warning-color); border: 1px solid rgba(var(--warning-color-rgb), 0.3); font-weight: 600; transition: all 0.2s ease;"
|
|
data-bs-toggle="tooltip" title="Edit Kriteria"
|
|
onmouseover="this.style.background='var(--warning-color)'; this.style.color='white';"
|
|
onmouseout="this.style.background='rgba(var(--warning-color-rgb), 0.15)'; this.style.color='var(--warning-color)';">
|
|
<i class="bi bi-pencil-square"></i>
|
|
</a>
|
|
|
|
@if(auth()->user()->role == 'super_admin')
|
|
<button type="button"
|
|
onclick="confirmDelete('{{ $item->id }}', '{{ addslashes($item->nama_kriteria) }}')"
|
|
class="btn btn-sm rounded-3"
|
|
style="background: rgba(var(--danger-color-rgb), 0.15); color: var(--danger-color); border: 1px solid rgba(var(--danger-color-rgb), 0.3); font-weight: 600; transition: all 0.2s ease;"
|
|
data-bs-toggle="tooltip" title="Hapus Kriteria"
|
|
onmouseover="this.style.background='var(--danger-color)'; this.style.color='white';"
|
|
onmouseout="this.style.background='rgba(var(--danger-color-rgb), 0.15)'; this.style.color='var(--danger-color)';">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
@else
|
|
<button type="button"
|
|
class="btn btn-sm rounded-3"
|
|
disabled
|
|
style="background: rgba(108, 117, 125, 0.15); color: #6c757d; border: 1px solid rgba(108, 117, 125, 0.3);"
|
|
data-bs-toggle="tooltip" title="Hanya Super Admin yang dapat menghapus">
|
|
<i class="bi bi-lock"></i>
|
|
</button>
|
|
@endif
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
|
|
<!-- Enhanced Table Footer dengan Validasi Bobot -->
|
|
<tfoot style="background: rgba(248, 249, 250, 0.9); backdrop-filter: blur(5px); border-top: 2px solid rgba(var(--primary-color-rgb), 0.1);">
|
|
<tr>
|
|
<td colspan="3" class="px-4 py-4">
|
|
<div class="d-flex flex-column gap-3">
|
|
<h6 class="mb-0 fw-bold" style="color: var(--text-primary);">📊 Validasi Total Bobot</h6>
|
|
<div class="d-flex gap-4">
|
|
<!-- Bobot Kontrakan -->
|
|
<span id="labelBobotKontrakan" class="d-flex align-items-center">
|
|
<span class="badge px-3 py-2 rounded-3 me-2" style="background: rgba(var(--primary-color-rgb), 0.15); color: var(--primary-color); font-weight: 700;">🏠</span>
|
|
<strong style="color: var(--text-primary);">Kontrakan:</strong>
|
|
<span class="ms-2 badge px-2 py-1 rounded-3" id="totalBobotKontrakan" style="font-weight: 700;"></span>
|
|
</span>
|
|
|
|
<!-- Bobot Laundry -->
|
|
<span id="labelBobotLaundry" class="d-flex align-items-center">
|
|
<span class="badge px-3 py-2 rounded-3 me-2" style="background: rgba(var(--success-color-rgb), 0.15); color: var(--success-color); font-weight: 700;">👕</span>
|
|
<strong style="color: var(--text-primary);">Laundry:</strong>
|
|
<span class="ms-2 badge px-2 py-1 rounded-3" id="totalBobotLaundry" style="font-weight: 700;"></span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td colspan="4" class="py-4 text-end">
|
|
<div class="d-flex align-items-center justify-content-end">
|
|
<div class="p-3 rounded-3" style="background: rgba(var(--info-color-rgb), 0.1); border: 1px solid rgba(var(--info-color-rgb), 0.2);">
|
|
<small style="color: var(--text-muted); line-height: 1.5;">
|
|
<i class="bi bi-info-circle me-1" style="color: var(--info-color);"></i>
|
|
<strong>Catatan:</strong> Total bobot untuk setiap tipe bisnis harus <span class="fw-bold" style="color: var(--success-color);">= 1.00 (100%)</span><br>
|
|
agar perhitungan SAW (Simple Additive Weighting) memberikan hasil yang akurat.
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
Total Bobot Kontrakan:
|
|
@if(isset($bobotKontrakanValid) && $bobotKontrakanValid)
|
|
<span class="badge bg-success" data-bs-toggle="tooltip" title="Total bobot sudah sesuai (1.00)">
|
|
<i class="bi bi-check-circle me-1"></i>{{ number_format($bobotKontrakan ?? 0, 2) }}
|
|
</span>
|
|
@else
|
|
<span class="badge bg-danger" data-bs-toggle="tooltip" title="Total bobot harus = 1.00">
|
|
<i class="bi bi-exclamation-triangle me-1"></i>{{ number_format($bobotKontrakan ?? 0, 2) }}
|
|
</span>
|
|
@endif
|
|
</span>
|
|
|
|
<!-- ✅ VALIDASI BOBOT LAUNDRY -->
|
|
<span id="labelBobotLaundry">
|
|
Total Bobot Laundry:
|
|
@if(isset($bobotLaundryValid) && $bobotLaundryValid)
|
|
<span class="badge bg-success" data-bs-toggle="tooltip" title="Total bobot sudah sesuai (1.00)">
|
|
<i class="bi bi-check-circle me-1"></i>{{ number_format($bobotLaundry ?? 0, 2) }}
|
|
</span>
|
|
@else
|
|
<span class="badge bg-danger" data-bs-toggle="tooltip" title="Total bobot harus = 1.00">
|
|
<i class="bi bi-exclamation-triangle me-1"></i>{{ number_format($bobotLaundry ?? 0, 2) }}
|
|
</span>
|
|
@endif
|
|
</span>
|
|
</div>
|
|
</td>
|
|
<td class="text-center py-3">
|
|
<span class="badge bg-info px-3 py-2 fs-6" id="totalBobotKeseluruhan">
|
|
Total: <span id="totalBobotValue">{{ number_format($kriteria->sum('bobot'), 2) }}</span>
|
|
</span>
|
|
</td>
|
|
<td colspan="3"></td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enhanced Info Cards -->
|
|
@if($kriteria->count() > 0)
|
|
<div class="row mt-4 g-4">
|
|
<div class="col-lg-6">
|
|
<div class="glass-card border-0">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div class="avatar-sm d-flex align-items-center justify-content-center me-3 rounded-3" style="background: rgba(var(--info-color-rgb), 0.15); color: var(--info-color);">
|
|
<i class="bi bi-info-circle-fill" style="font-size: 1.2rem;"></i>
|
|
</div>
|
|
<h6 class="mb-0 fw-bold" style="color: var(--text-primary);">📋 Informasi Kriteria SAW</h6>
|
|
</div>
|
|
<div class="ps-3 border-start border-3" style="border-color: var(--info-color) !important;">
|
|
<ul class="mb-0 small" style="color: var(--text-secondary); line-height: 1.6;">
|
|
<li class="mb-2"><strong style="color: var(--success-color);">📈 Benefit:</strong> Semakin tinggi nilai, semakin baik <br><small class="text-muted">(contoh: Fasilitas, Kualitas Layanan)</small></li>
|
|
<li class="mb-2"><strong style="color: var(--danger-color);">📉 Cost:</strong> Semakin rendah nilai, semakin baik <br><small class="text-muted">(contoh: Harga, Jarak, Biaya)</small></li>
|
|
<li><strong style="color: var(--warning-color);">⚖️ Total Bobot:</strong> Harus = 1.00 (100%) untuk setiap tipe bisnis <br><small class="text-muted">agar perhitungan SAW akurat dan valid</small></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-6">
|
|
<div class="glass-card border-0">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div class="avatar-sm d-flex align-items-center justify-content-center me-3 rounded-3" style="background: rgba(var(--success-color-rgb), 0.15); color: var(--success-color);">
|
|
<i class="bi bi-graph-up-arrow" style="font-size: 1.2rem;"></i>
|
|
</div>
|
|
<h6 class="mb-0 fw-bold" style="color: var(--text-primary);">📊 Statistik Kriteria</h6>
|
|
</div>
|
|
<div class="row text-center g-3">
|
|
<div class="col-4">
|
|
<div class="p-3 rounded-3" style="background: rgba(var(--primary-color-rgb), 0.1);">
|
|
<div class="fw-bold fs-4" style="color: var(--primary-color);" id="statTotalKriteria">{{ $kriteria->count() }}</div>
|
|
<small style="color: var(--text-muted); font-weight: 600;">Total Kriteria</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-4">
|
|
<div class="p-3 rounded-3" style="background: rgba(var(--success-color-rgb), 0.1);">
|
|
<div class="fw-bold fs-4" style="color: var(--success-color);" id="statBenefit">{{ $totalBenefit ?? 0 }}</div>
|
|
<small style="color: var(--text-muted); font-weight: 600;">Benefit</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-4">
|
|
<div class="p-3 rounded-3" style="background: rgba(var(--danger-color-rgb), 0.1);">
|
|
<div class="fw-bold fs-4" style="color: var(--danger-color);" id="statCost">{{ $totalCost ?? 0 }}</div>
|
|
<small style="color: var(--text-muted); font-weight: 600;">Cost</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<!-- Enhanced Bootstrap Modal Delete Confirmation -->
|
|
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content border-0" style="border-radius: 1rem; overflow: hidden; box-shadow: 0 20px 40px rgba(0,0,0,0.15);">
|
|
<div class="modal-header border-0 py-4" style="background: linear-gradient(135deg, var(--danger-color), #e74c3c); color: white;">
|
|
<h5 class="modal-title fw-bold d-flex align-items-center" id="deleteModalLabel">
|
|
<div class="avatar-sm d-flex align-items-center justify-content-center me-3 rounded-circle" style="background: rgba(255,255,255,0.2); backdrop-filter: blur(5px);">
|
|
<i class="bi bi-exclamation-triangle-fill" style="font-size: 1.2rem;"></i>
|
|
</div>
|
|
Konfirmasi Hapus Kriteria
|
|
</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body p-4 p-md-5">
|
|
<div class="text-center mb-4">
|
|
<div class="d-inline-flex align-items-center justify-content-center rounded-circle mb-3" style="width: 80px; height: 80px; background: linear-gradient(135deg, rgba(var(--danger-color-rgb), 0.1), rgba(231, 76, 60, 0.1)); animation: pulse 2s infinite;">
|
|
<i class="bi bi-trash" style="font-size: 2.5rem; color: var(--danger-color);"></i>
|
|
</div>
|
|
<h6 class="fw-bold mb-2" style="color: var(--text-primary);">Apakah Anda yakin?</h6>
|
|
</div>
|
|
<p class="text-center mb-3" style="color: var(--text-secondary); line-height: 1.6;">
|
|
Kriteria <strong id="deleteKriteriaName" style="color: var(--danger-color);"></strong> akan dihapus secara permanen.
|
|
</p>
|
|
<div class="alert border-0 rounded-3 mb-0" style="background: rgba(var(--warning-color-rgb), 0.1); color: var(--warning-color);">
|
|
<div class="d-flex align-items-start">
|
|
<i class="bi bi-info-circle-fill me-2 mt-1" style="font-size: 1rem;"></i>
|
|
<small style="line-height: 1.5;">
|
|
<strong>Peringatan:</strong> Tindakan ini tidak dapat dibatalkan. Data kriteria yang dihapus akan hilang selamanya dan dapat mempengaruhi perhitungan SAW.
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 justify-content-center pt-0 pb-4">
|
|
<div class="d-flex gap-3 w-100">
|
|
<button type="button" class="btn btn-light border rounded-3 flex-fill py-2" data-bs-dismiss="modal" style="font-weight: 600;">
|
|
<i class="bi bi-x-circle me-2"></i>Batal
|
|
</button>
|
|
<button type="button" class="btn rounded-3 flex-fill py-2" id="confirmDeleteBtn"
|
|
style="background: linear-gradient(135deg, var(--danger-color), #e74c3c); color: white; border: none; font-weight: 600;">
|
|
<i class="bi bi-trash me-2"></i>Ya, Hapus Kriteria
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Enhanced Search, Filter & Sorting Script with Mobile Optimization -->
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const searchInput = document.getElementById('searchInput');
|
|
const table = document.getElementById('kriteriaTable');
|
|
const mobileCards = document.querySelectorAll('.d-block.d-lg-none .card');
|
|
const sortableHeaders = document.querySelectorAll('.sortable');
|
|
|
|
// ========== ENHANCED MOBILE INTERACTIONS ==========
|
|
// Touch animations for mobile cards
|
|
mobileCards.forEach(card => {
|
|
// Touch start effect
|
|
card.addEventListener('touchstart', function() {
|
|
this.style.transition = 'all 0.1s ease';
|
|
this.style.transform = 'scale(0.98)';
|
|
this.style.opacity = '0.9';
|
|
});
|
|
|
|
// Touch end effect
|
|
card.addEventListener('touchend', function() {
|
|
this.style.transition = 'all 0.3s ease';
|
|
this.style.transform = 'scale(1)';
|
|
this.style.opacity = '1';
|
|
});
|
|
|
|
// Cancel touch effect if moved
|
|
card.addEventListener('touchcancel', function() {
|
|
this.style.transition = 'all 0.3s ease';
|
|
this.style.transform = 'scale(1)';
|
|
this.style.opacity = '1';
|
|
});
|
|
});
|
|
|
|
// ========== ENHANCED SORTING FUNCTIONALITY ==========
|
|
let currentSortBy = '{{ $sortBy ?? '' }}';
|
|
let currentSortOrder = '{{ $sortOrder ?? 'asc' }}';
|
|
|
|
// Update sort icons dengan enhanced styling
|
|
updateSortIcons();
|
|
|
|
function updateSortIcons() {
|
|
sortableHeaders.forEach(header => {
|
|
const sortField = header.getAttribute('data-sort');
|
|
const icon = header.querySelector('.sort-icon');
|
|
|
|
if (sortField === currentSortBy) {
|
|
// Active header dengan enhanced styling
|
|
if (currentSortOrder === 'asc') {
|
|
icon.className = 'bi bi-sort-up ms-2 sort-icon';
|
|
icon.style.color = 'var(--primary-color)';
|
|
} else {
|
|
icon.className = 'bi bi-sort-down ms-2 sort-icon';
|
|
icon.style.color = 'var(--primary-color)';
|
|
}
|
|
header.style.color = 'var(--primary-color)';
|
|
header.style.fontWeight = '700';
|
|
} else {
|
|
// Inactive header
|
|
icon.className = 'bi bi-arrow-down-up ms-2 sort-icon';
|
|
icon.style.color = '';
|
|
header.style.color = '';
|
|
header.style.fontWeight = '';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Enhanced sorting dengan feedback visual
|
|
sortableHeaders.forEach(header => {
|
|
header.addEventListener('click', function() {
|
|
// Loading effect
|
|
const icon = this.querySelector('.sort-icon');
|
|
const originalClass = icon.className;
|
|
icon.className = 'bi bi-arrow-clockwise ms-2 sort-icon';
|
|
icon.style.animation = 'spin 0.5s linear';
|
|
|
|
const sortField = this.getAttribute('data-sort');
|
|
|
|
// Toggle order logic
|
|
if (currentSortBy === sortField) {
|
|
currentSortOrder = currentSortOrder === 'asc' ? 'desc' : 'asc';
|
|
} else {
|
|
currentSortBy = sortField;
|
|
currentSortOrder = 'asc';
|
|
}
|
|
|
|
// Update hidden inputs
|
|
document.getElementById('hiddenSortBy').value = currentSortBy;
|
|
document.getElementById('hiddenSortOrder').value = currentSortOrder;
|
|
|
|
// Submit dengan delay untuk animation
|
|
setTimeout(() => {
|
|
document.getElementById('filterForm').submit();
|
|
}, 300);
|
|
});
|
|
|
|
// Enhanced hover effects
|
|
header.addEventListener('mouseenter', function() {
|
|
if (this.style.color !== 'var(--primary-color)') {
|
|
this.style.background = 'rgba(var(--primary-color-rgb), 0.05)';
|
|
this.style.color = 'var(--primary-color)';
|
|
this.style.transition = 'all 0.2s ease';
|
|
}
|
|
});
|
|
|
|
header.addEventListener('mouseleave', function() {
|
|
if (this.getAttribute('data-sort') !== currentSortBy) {
|
|
this.style.background = '';
|
|
this.style.color = '';
|
|
}
|
|
});
|
|
});
|
|
|
|
// ========== ENHANCED SEARCH FUNCTIONALITY ==========
|
|
function filterTable() {
|
|
if (!table) return;
|
|
|
|
const searchTerm = searchInput.value.toLowerCase().trim();
|
|
|
|
// Filter desktop table
|
|
const rows = table.getElementsByTagName('tbody')[0].getElementsByTagName('tr');
|
|
let visibleCount = 0;
|
|
|
|
Array.from(rows).forEach(row => {
|
|
const text = row.textContent.toLowerCase();
|
|
const matchSearch = !searchTerm || text.includes(searchTerm);
|
|
|
|
if (matchSearch) {
|
|
row.style.display = '';
|
|
row.style.animation = 'fadeIn 0.3s ease';
|
|
visibleCount++;
|
|
} else {
|
|
row.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
// Filter mobile cards
|
|
mobileCards.forEach(card => {
|
|
const text = card.textContent.toLowerCase();
|
|
const matchSearch = !searchTerm || text.includes(searchTerm);
|
|
|
|
if (matchSearch) {
|
|
card.style.display = '';
|
|
card.style.animation = 'fadeIn 0.3s ease';
|
|
} else {
|
|
card.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
// Update nomor urut untuk desktop
|
|
let visibleIndex = 1;
|
|
Array.from(rows).forEach(row => {
|
|
if (row.style.display !== 'none') {
|
|
const badge = row.querySelector('.badge.bg-light');
|
|
if (badge) {
|
|
badge.textContent = visibleIndex++;
|
|
badge.style.animation = 'pulse 0.3s ease';
|
|
}
|
|
}
|
|
});
|
|
|
|
// Update count badges
|
|
updateCountBadges();
|
|
|
|
// Show no results message if needed
|
|
showNoResultsMessage(visibleCount === 0 && mobileCards.length === 0);
|
|
}
|
|
|
|
// Enhanced search dengan debounce untuk performance
|
|
let searchTimeout;
|
|
if (searchInput) {
|
|
searchInput.addEventListener('input', function() {
|
|
clearTimeout(searchTimeout);
|
|
searchTimeout = setTimeout(filterTable, 150);
|
|
});
|
|
|
|
// Clear button untuk mobile
|
|
if (this.value) {
|
|
addClearButton();
|
|
}
|
|
}
|
|
|
|
function addClearButton() {
|
|
if (!document.querySelector('.search-clear-btn')) {
|
|
const clearBtn = document.createElement('button');
|
|
clearBtn.type = 'button';
|
|
clearBtn.className = 'btn btn-sm search-clear-btn';
|
|
clearBtn.innerHTML = '<i class="bi bi-x"></i>';
|
|
clearBtn.style.cssText = 'position: absolute; right: 10px; top: 50%; transform: translateY(-50%); z-index: 10; background: none; border: none; color: var(--text-muted);';
|
|
|
|
const inputGroup = searchInput.closest('.input-group');
|
|
inputGroup.style.position = 'relative';
|
|
inputGroup.appendChild(clearBtn);
|
|
|
|
clearBtn.addEventListener('click', () => {
|
|
searchInput.value = '';
|
|
filterTable();
|
|
clearBtn.remove();
|
|
searchInput.focus();
|
|
});
|
|
}
|
|
}
|
|
|
|
function updateCountBadges() {
|
|
const kontrakanCount = Array.from(document.querySelectorAll('[data-tipe-bisnis="kontrakan"]')).filter(el => el.style.display !== 'none').length;
|
|
const laundryCount = Array.from(document.querySelectorAll('[data-tipe-bisnis="laundry"]')).filter(el => el.style.display !== 'none').length;
|
|
|
|
const kontrakanBadge = document.getElementById('countKontrakan');
|
|
const laundryBadge = document.getElementById('countLaundry');
|
|
|
|
if (kontrakanBadge) kontrakanBadge.textContent = kontrakanCount;
|
|
if (laundryBadge) laundryBadge.textContent = laundryCount;
|
|
}
|
|
|
|
function showNoResultsMessage(show) {
|
|
let noResultsDiv = document.getElementById('noResultsMessage');
|
|
|
|
if (show && !noResultsDiv) {
|
|
noResultsDiv = document.createElement('div');
|
|
noResultsDiv.id = 'noResultsMessage';
|
|
noResultsDiv.className = 'text-center py-5';
|
|
noResultsDiv.innerHTML = `
|
|
<div class="mb-3">
|
|
<i class="bi bi-search text-muted" style="font-size: 3rem; opacity: 0.5;"></i>
|
|
</div>
|
|
<h6 class="text-muted mb-2">Tidak ada kriteria ditemukan</h6>
|
|
<small class="text-muted">Coba ubah kata kunci pencarian Anda</small>
|
|
`;
|
|
|
|
const cardBody = document.querySelector('.card-body');
|
|
if (cardBody) cardBody.appendChild(noResultsDiv);
|
|
} else if (!show && noResultsDiv) {
|
|
noResultsDiv.remove();
|
|
}
|
|
}
|
|
|
|
// ========== ENHANCED TOOLTIPS ==========
|
|
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
|
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
|
return new bootstrap.Tooltip(tooltipTriggerEl, {
|
|
animation: true,
|
|
delay: { show: 300, hide: 100 }
|
|
});
|
|
});
|
|
|
|
// ========== ENHANCED BOBOT VALIDATION ==========
|
|
function updateBobotValidation() {
|
|
// Hanya ambil rows dari desktop table (visible) atau mobile cards
|
|
const isMobile = window.innerWidth < 992; // Bootstrap lg breakpoint
|
|
|
|
let kontrakanRows, laundryRows;
|
|
|
|
if (isMobile) {
|
|
// Mobile view: ambil dari card
|
|
kontrakanRows = document.querySelectorAll('.d-block.d-lg-none [data-tipe-bisnis="kontrakan"]');
|
|
laundryRows = document.querySelectorAll('.d-block.d-lg-none [data-tipe-bisnis="laundry"]');
|
|
} else {
|
|
// Desktop view: ambil dari table tbody
|
|
kontrakanRows = document.querySelectorAll('table tbody tr[data-tipe-bisnis="kontrakan"]');
|
|
laundryRows = document.querySelectorAll('table tbody tr[data-tipe-bisnis="laundry"]');
|
|
}
|
|
|
|
let totalKontrakan = 0;
|
|
let totalLaundry = 0;
|
|
|
|
kontrakanRows.forEach(row => {
|
|
const bobot = parseFloat(row.getAttribute('data-bobot') || 0);
|
|
totalKontrakan += bobot;
|
|
});
|
|
|
|
laundryRows.forEach(row => {
|
|
const bobot = parseFloat(row.getAttribute('data-bobot') || 0);
|
|
totalLaundry += bobot;
|
|
});
|
|
|
|
updateBobotDisplay('totalBobotKontrakan', totalKontrakan);
|
|
updateBobotDisplay('totalBobotLaundry', totalLaundry);
|
|
}
|
|
|
|
function updateBobotDisplay(elementId, total) {
|
|
const element = document.getElementById(elementId);
|
|
if (!element) return;
|
|
|
|
const isValid = Math.abs(total - 1.00) < 0.01;
|
|
const percentage = (total * 100).toFixed(1);
|
|
|
|
if (isValid) {
|
|
element.style.background = 'rgba(var(--success-color-rgb), 0.15)';
|
|
element.style.color = 'var(--success-color)';
|
|
element.innerHTML = `<i class="bi bi-check-circle me-1"></i>${total.toFixed(2)} (${percentage}%)`;
|
|
} else {
|
|
element.style.background = 'rgba(var(--danger-color-rgb), 0.15)';
|
|
element.style.color = 'var(--danger-color)';
|
|
element.innerHTML = `<i class="bi bi-exclamation-triangle me-1"></i>${total.toFixed(2)} (${percentage}%)`;
|
|
}
|
|
}
|
|
|
|
// Initialize bobot validation
|
|
updateBobotValidation();
|
|
});
|
|
|
|
// ========== ENHANCED DELETE CONFIRMATION ==========
|
|
function confirmDelete(id, namaKriteria) {
|
|
// Animate button yang diklik
|
|
const clickedButton = event.target.closest('button');
|
|
if (clickedButton) {
|
|
clickedButton.style.transform = 'scale(0.95)';
|
|
setTimeout(() => {
|
|
clickedButton.style.transform = 'scale(1)';
|
|
}, 150);
|
|
}
|
|
|
|
document.getElementById('deleteKriteriaName').textContent = '"' + namaKriteria + '"';
|
|
|
|
const modalElement = document.getElementById('deleteModal');
|
|
const deleteModal = new bootstrap.Modal(modalElement, {
|
|
backdrop: 'static',
|
|
keyboard: false
|
|
});
|
|
|
|
deleteModal.show();
|
|
|
|
// Setup delete action
|
|
const confirmBtn = document.getElementById('confirmDeleteBtn');
|
|
confirmBtn.onclick = function() {
|
|
// Loading state
|
|
const originalContent = this.innerHTML;
|
|
this.innerHTML = '<i class="bi bi-arrow-clockwise me-2" style="animation: spin 1s linear infinite;"></i>Menghapus...';
|
|
this.disabled = true;
|
|
|
|
// Create and submit form
|
|
const form = document.createElement('form');
|
|
form.method = 'POST';
|
|
form.action = `/kriteria/${id}`;
|
|
|
|
const csrfInput = document.createElement('input');
|
|
csrfInput.type = 'hidden';
|
|
csrfInput.name = '_token';
|
|
csrfInput.value = document.querySelector('meta[name="csrf-token"]').content;
|
|
|
|
const methodInput = document.createElement('input');
|
|
methodInput.type = 'hidden';
|
|
methodInput.name = '_method';
|
|
methodInput.value = 'DELETE';
|
|
|
|
form.appendChild(csrfInput);
|
|
form.appendChild(methodInput);
|
|
document.body.appendChild(form);
|
|
|
|
setTimeout(() => {
|
|
form.submit();
|
|
}, 800);
|
|
};
|
|
}
|
|
|
|
// ========== CSS ANIMATIONS ==========
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(10px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { transform: scale(1); }
|
|
50% { transform: scale(1.05); }
|
|
}
|
|
|
|
.glass-card-light {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
border-radius: 1rem;
|
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.sortable:hover {
|
|
background: rgba(var(--primary-color-rgb), 0.05) !important;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Mobile optimizations */
|
|
@media (max-width: 768px) {
|
|
.table-responsive {
|
|
border-radius: 1rem;
|
|
}
|
|
|
|
.modal-dialog {
|
|
margin: 1rem;
|
|
}
|
|
|
|
.glass-card {
|
|
margin-left: -15px;
|
|
margin-right: -15px;
|
|
border-radius: 0 0 1rem 1rem;
|
|
}
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
</script>
|
|
|
|
<style>
|
|
.modal-content {
|
|
border-radius: 12px;
|
|
animation: modalFadeIn 0.3s ease;
|
|
}
|
|
|
|
@keyframes modalFadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: scale(0.9) translateY(-20px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: scale(1) translateY(0);
|
|
}
|
|
}
|
|
|
|
.modal-header.bg-danger {
|
|
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
|
|
}
|
|
|
|
.btn:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.table tbody tr {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.table tbody tr:hover {
|
|
background-color: #f8f9fa;
|
|
transform: scale(1.01);
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
}
|
|
|
|
.btn-group .btn {
|
|
border-radius: 0;
|
|
}
|
|
|
|
.btn-group .btn:first-child {
|
|
border-top-left-radius: 0.25rem;
|
|
border-bottom-left-radius: 0.25rem;
|
|
}
|
|
|
|
.btn-group .btn:last-child {
|
|
border-top-right-radius: 0.25rem;
|
|
border-bottom-right-radius: 0.25rem;
|
|
}
|
|
|
|
.avatar-sm {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.form-select {
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* ========== SORTING STYLES ========== */
|
|
.sortable {
|
|
transition: all 0.2s ease;
|
|
user-select: none;
|
|
position: relative;
|
|
}
|
|
|
|
.sortable:hover {
|
|
background-color: #f8f9fa !important;
|
|
}
|
|
|
|
.sortable:active {
|
|
background-color: #e9ecef !important;
|
|
}
|
|
|
|
.sortable.text-primary {
|
|
font-weight: 700;
|
|
}
|
|
|
|
.sort-icon {
|
|
font-size: 0.8rem;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.sortable:hover .sort-icon {
|
|
transform: scale(1.2);
|
|
}
|
|
|
|
.text-primary .sort-icon {
|
|
animation: sortPulse 1.5s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes sortPulse {
|
|
0%, 100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0.6;
|
|
}
|
|
}
|
|
|
|
/* Sort indicator animation */
|
|
@keyframes sortIndicator {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-5px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.sortable.text-primary::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 3px;
|
|
background: linear-gradient(90deg, transparent, #0d6efd, transparent);
|
|
animation: sortIndicator 0.3s ease;
|
|
}
|
|
</style>
|
|
@endsection
|