Files
Kent-logistics-Laravel/resources/views/admin/container.blade.php
2026-02-27 10:51:26 +05:30

803 lines
23 KiB
PHP

@extends('admin.layouts.app')
@section('page-title', 'Containers')
@section('content')
<style>
:root {
--primary-color: #4c6fff;
--primary-gradient: linear-gradient(135deg, #4c6fff, #8e54e9);
--success-color: #10b981;
--warning-color: #f59e0b;
--danger-color: #ef4444;
--info-color: #3b82f6;
--light-bg: #f8fafc;
--dark-text: #1e293b;
--gray-text: #64748b;
--border-color: #e2e8f0;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.1);
--shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1);
--shadow-lg: 0 10px 25px -5px rgba(0,0,0,0.1);
--radius-lg: 16px;
--radius-md: 12px;
--radius-sm: 8px;
}
.containers-wrapper {
min-height: calc(100vh - 180px);
padding: 20px 15px;
background: linear-gradient(135deg, #f6f9ff 0%, #f0f4ff 100%);
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding: 0 10px;
}
.header-content h1 {
font-size: 28px;
font-weight: 700;
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 5px;
}
.header-subtitle {
color: var(--gray-text);
font-size: 14px;
font-weight: 500;
}
.add-container-btn {
background: var(--primary-gradient);
color: white;
border: none;
padding: 12px 28px;
border-radius: var(--radius-md);
font-weight: 600;
font-size: 14px;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
box-shadow: 0 4px 12px rgba(76, 111, 255, 0.3);
}
.add-container-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(76, 111, 255, 0.4);
color: white;
}
.add-container-btn i {
font-size: 16px;
}
.filter-card {
background: white;
border-radius: var(--radius-lg);
padding: 24px;
margin-bottom: 30px;
box-shadow: var(--shadow-lg);
border: 1px solid rgba(255,255,255,0.9);
backdrop-filter: blur(10px);
}
.filter-title {
font-size: 16px;
font-weight: 600;
color: var(--dark-text);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.filter-title i {
color: var(--primary-color);
}
.filter-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.filter-group {
position: relative;
}
.filter-group label {
display: block;
font-size: 12px;
font-weight: 600;
color: var(--gray-text);
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.filter-input, .filter-select, .filter-date {
width: 100%;
padding: 12px 16px;
border: 2px solid var(--border-color);
border-radius: var(--radius-md);
font-size: 14px;
color: var(--dark-text);
background: white;
transition: all 0.3s ease;
}
.filter-input:focus, .filter-select:focus, .filter-date:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(76, 111, 255, 0.1);
}
.filter-input::placeholder {
color: #94a3b8;
}
.filter-actions {
display: flex;
gap: 10px;
align-items: flex-end;
}
.apply-btn {
background: var(--primary-gradient);
color: white;
border: none;
padding: 12px 24px;
border-radius: var(--radius-md);
font-weight: 600;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
min-height: 46px;
width: 100%;
}
.apply-btn:hover {
transform: translateY(-1px);
box-shadow: 0 6px 20px rgba(76, 111, 255, 0.3);
}
.reset-btn {
background: white;
color: var(--gray-text);
border: 2px solid var(--border-color);
padding: 12px 24px;
border-radius: var(--radius-md);
font-weight: 600;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
min-height: 46px;
width: 100%;
}
.reset-btn:hover {
border-color: var(--primary-color);
color: var(--primary-color);
}
.main-card {
background: white;
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-lg);
margin-bottom: 30px;
border: 1px solid rgba(255,255,255,0.9);
}
.card-header {
padding: 24px;
background: linear-gradient(135deg, #4c6fff, #8e54e9);
color: white;
}
.card-header h2 {
font-size: 20px;
font-weight: 700;
color: white;
margin: 0;
display: flex;
align-items: center;
gap: 12px;
}
.card-header h2 i {
color: white;
}
.stats-badge {
background: rgba(255, 255, 255, 0.2);
color: white;
font-size: 12px;
font-weight: 600;
padding: 4px 12px;
border-radius: 20px;
margin-left: 10px;
backdrop-filter: blur(10px);
}
.container-item {
padding: 16px 20px;
border-bottom: 1px solid var(--border-color);
transition: all 0.3s ease;
background: white;
}
.container-item:hover {
background: #f8fafc;
transform: translateX(4px);
}
.container-item:last-child {
border-bottom: none;
}
.container-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.container-info {
display: flex;
align-items: center;
gap: 12px;
}
.container-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--primary-gradient);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 16px;
font-weight: 700;
flex-shrink: 0;
box-shadow: 0 4px 12px rgba(76, 111, 255, 0.3);
}
.container-details h3 {
font-size: 16px;
font-weight: 700;
color: var(--dark-text);
margin: 0 0 4px 0;
}
.container-meta {
display: flex;
align-items: center;
gap: 12px;
font-size: 12px;
color: var(--gray-text);
}
.meta-item {
display: flex;
align-items: center;
gap: 4px;
}
.meta-item i {
font-size: 12px;
color: #94a3b8;
}
.status-badge {
padding: 6px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.3px;
display: inline-flex;
align-items: center;
gap: 6px;
}
.status-badge i {
font-size: 10px;
}
.status-pending {
background: #fef3c7;
color: #d97706;
}
.status-in-progress {
background: #dbeafe;
color: #1d4ed8;
}
.status-completed {
background: #d1fae5;
color: #065f46;
}
.status-cancelled {
background: #fee2e2;
color: #991b1b;
}
.action-buttons {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.action-btn {
padding: 8px 16px;
border-radius: var(--radius-md);
font-size: 13px;
font-weight: 600;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.3s ease;
text-decoration: none;
}
.view-btn {
background: #e0f2fe;
color: #0369a1;
border: none;
}
.view-btn:hover {
background: #0ea5e9;
color: white;
}
.delete-btn {
background: #fef2f2;
color: #dc2626;
border: 1px solid #fecaca;
}
.delete-btn:hover {
background: #dc2626;
color: white;
}
.update-form {
display: flex;
align-items: center;
gap: 8px;
}
.status-select {
padding: 8px 12px;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
font-size: 13px;
color: var(--dark-text);
background: white;
min-width: 140px;
cursor: pointer;
}
.update-btn {
background: var(--primary-color);
color: white;
border: none;
padding: 8px 16px;
border-radius: var(--radius-md);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
}
.update-btn:hover {
background: #3b5de6;
}
.no-results {
text-align: center;
padding: 60px 20px;
}
.no-results-icon {
font-size: 64px;
color: var(--border-color);
margin-bottom: 20px;
}
.no-results h4 {
font-size: 18px;
color: var(--gray-text);
margin-bottom: 10px;
}
.no-results p {
color: #94a3b8;
font-size: 14px;
max-width: 400px;
margin: 0 auto;
}
.success-message {
background: linear-gradient(135deg, #10b981, #059669);
color: white;
padding: 16px 24px;
border-radius: var(--radius-md);
margin-bottom: 24px;
display: flex;
align-items: center;
gap: 12px;
box-shadow: var(--shadow-md);
animation: slideIn 0.3s ease;
}
.success-message i {
font-size: 20px;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
/* 🔥 Totals section */
.totals-section {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 12px;
margin-top: 16px;
padding: 16px;
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-radius: var(--radius-md);
border-left: 4px solid var(--primary-color);
}
.total-card {
text-align: center;
padding: 12px;
background: white;
border-radius: var(--radius-sm);
box-shadow: var(--shadow-sm);
transition: all 0.3s ease;
}
.total-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.total-value {
font-size: 20px;
font-weight: 800;
color: var(--primary-color);
margin-bottom: 4px;
}
.total-label {
font-size: 11px;
font-weight: 600;
color: var(--gray-text);
text-transform: uppercase;
letter-spacing: 0.5px;
}
@media (max-width: 768px) {
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.add-container-btn {
width: 100%;
justify-content: center;
}
.filter-grid {
grid-template-columns: 1fr;
}
.container-header {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.action-buttons {
width: 100%;
justify-content: flex-start;
}
}
@media (max-width: 576px) {
.update-form {
flex-direction: column;
width: 100%;
}
.status-select, .update-btn {
width: 100%;
}
}
</style>
<div class="containers-wrapper">
<div class="page-header">
<div class="header-content">
<h1>Container Management</h1>
<div class="header-subtitle">
Manage all containers, track status, and view entries in real-time
</div>
</div>
@can('containers.create')
<a href="{{ route('containers.create') }}" class="add-container-btn">
<i class="fas fa-plus-circle"></i>
Add New Container
</a>
@endcan
</div>
@if(session('success'))
<div class="success-message">
<i class="fas fa-check-circle"></i>
<span>{{ session('success') }}</span>
</div>
@endif
<div class="filter-card">
<div class="filter-title">
<i class="fas fa-filter"></i>
Filter Containers
</div>
<form method="GET" class="filter-grid">
<div class="filter-group">
<label><i class="fas fa-search"></i> Search</label>
<input type="text" name="search" class="filter-input"
placeholder="Search by container name or number..."
value="{{ request('search') }}">
</div>
<div class="filter-group">
<label><i class="fas fa-tag"></i> Status</label>
<select name="status" class="filter-select">
<option value="">All Status</option>
<option value="pending" {{ request('status') == 'pending' ? 'selected' : '' }}>Pending</option>
<option value="in-progress" {{ request('status') == 'in-progress' ? 'selected' : '' }}>In Progress</option>
<option value="completed" {{ request('status') == 'completed' ? 'selected' : '' }}>Completed</option>
<option value="cancelled" {{ request('status') == 'cancelled' ? 'selected' : '' }}>Cancelled</option>
</select>
</div>
<div class="filter-group">
<label><i class="fas fa-calendar"></i> Date</label>
<input type="date" name="date" class="filter-date" value="{{ request('date') }}">
</div>
<div class="filter-actions">
<button type="submit" class="apply-btn">
<i class="fas fa-search"></i> Apply Filters
</button>
<a href="{{ route('containers.index') }}" class="reset-btn">
<i class="fas fa-redo"></i> Reset
</a>
</div>
</form>
</div>
<div class="main-card">
<div class="card-header">
<h2>
<i class="fas fa-boxes"></i>
Containers List
<span class="stats-badge">{{ $containers->count() }} containers</span>
</h2>
</div>
@if($containers->isEmpty())
<div class="no-results">
<div class="no-results-icon">
<i class="fas fa-box-open"></i>
</div>
<h4>No containers found</h4>
<p>Get started by creating your first container</p>
</div>
@else
@foreach($containers as $container)
@php
$status = $container->status;
$statusClass = match ($status) {
'completed' => 'status-completed',
'in-progress' => 'status-in-progress',
'cancelled' => 'status-cancelled',
default => 'status-pending',
};
@endphp
<div class="container-item">
<div class="container-header">
<div class="container-info">
<div class="container-avatar">
{{ substr($container->container_name, 0, 2) }}
</div>
<div class="container-details">
<h3>{{ $container->container_name }}</h3>
<div class="container-meta">
<div class="meta-item">
<i class="fas fa-hashtag"></i>
<span>{{ $container->container_number }}</span>
</div>
<div class="meta-item">
<i class="fas fa-calendar"></i>
<span>{{ $container->container_date?->format('M d, Y') ?: 'No date' }}</span>
</div>
<div class="meta-item">
<i class="fas fa-list"></i>
<span>{{ $container->rows->count() }} entries</span>
</div>
</div>
</div>
</div>
<div class="action-buttons">
<span class="status-badge {{ $statusClass }}">
<i class="fas fa-circle"></i>
<span class="status-text">
{{ ucfirst(str_replace('-', ' ', $status)) }}
</span>
</span>
@can('containers.view')
<a href="{{ route('containers.show', $container->id) }}" class="action-btn view-btn">
<i class="fas fa-eye"></i> View
</a>
@endcan
@can('containers.update_status')
<form action="{{ route('containers.update-status', $container->id) }}"
method="POST"
class="update-form ajax-status-form"
data-container-id="{{ $container->id }}">
@csrf
<select name="status" class="status-select">
<option value="pending" {{ $status === 'pending' ? 'selected' : '' }}>Pending</option>
<option value="in-progress" {{ $status === 'in-progress' ? 'selected' : '' }}>In Progress</option>
<option value="completed" {{ $status === 'completed' ? 'selected' : '' }}>Completed</option>
<option value="cancelled" {{ $status === 'cancelled' ? 'selected' : '' }}>Cancelled</option>
</select>
</form>
@endcan
@can('containers.delete')
<form action="{{ route('containers.destroy', $container->id) }}" method="POST"
onsubmit="return confirm('Are you sure you want to delete this container and all its entries?');">
@csrf
@method('DELETE')
<button type="submit" class="action-btn delete-btn">
<i class="fas fa-trash"></i> Delete
</button>
</form>
@endcan
</div>
</div>
<!-- 🔥 Totals instead of first row preview -->
<div class="totals-section">
<div class="total-card">
<div class="total-value">{{ number_format($container->summary['total_ctn'], 1) }}</div>
<div class="total-label">Total CTN</div>
</div>
<div class="total-card">
<div class="total-value">{{ number_format($container->summary['total_qty'], 0) }}</div>
<div class="total-label">Total QTY</div>
</div>
<div class="total-card">
<div class="total-value">{{ number_format($container->summary['total_cbm'], 3) }}</div>
<div class="total-label">Total CBM</div>
</div>
<div class="total-card">
<div class="total-value">{{ number_format($container->summary['total_kg'], 1) }}</div>
<div class="total-label">Total KG</div>
</div>
</div>
</div>
@endforeach
@endif
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.ajax-status-form .status-select').forEach(function (selectEl) {
selectEl.addEventListener('change', function (event) {
const form = event.target.closest('form');
const url = form.getAttribute('action');
const token = form.querySelector('input[name="_token"]').value;
const status = event.target.value;
console.log('Sending status update:', url, status);
fetch(url, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': token,
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ status: status })
})
.then(async res => {
console.log('Response status:', res.status);
let data = null;
try { data = await res.json(); } catch (e) {}
console.log('Response body:', data);
if (!res.ok || !data || !data.success) {
alert('Status update failed');
return;
}
// 👉 UI update (badge text + color)
const item = form.closest('.container-item');
const badge = item.querySelector('.status-badge');
// text
// text
const pretty = data.status.replace('-', ' ');
const textEl = badge.querySelector('.status-text');
if (textEl) {
textEl.textContent =
pretty.charAt(0).toUpperCase() + pretty.slice(1);
}
// classes
badge.classList.remove(
'status-pending',
'status-in-progress',
'status-completed',
'status-cancelled'
);
if (data.status === 'pending') {
badge.classList.add('status-pending');
} else if (data.status === 'in-progress') {
badge.classList.add('status-in-progress');
} else if (data.status === 'completed') {
badge.classList.add('status-completed');
} else if (data.status === 'cancelled') {
badge.classList.add('status-cancelled');
}
})
.catch(err => {
console.error('Error:', err);
alert('Network error while updating status');
});
});
});
});
</script>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
@endsection