Compare commits

..

2 Commits

Author SHA256 Message Date
divya abdar
9423c79c80 changes of dashboard and order , requests 2025-12-24 13:32:47 +05:30
divya abdar
8a958b9c48 dashboard order and request file changes 2025-12-24 10:47:20 +05:30
99 changed files with 9158 additions and 15494 deletions

View File

@@ -1,6 +1,6 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:/ulhRgiCOFjZV6xUDkXLfiR9X8iFRZ4QIiX3UJbdwY4=
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
@@ -20,12 +20,12 @@ LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=kent_logistics6
DB_USERNAME=root
DB_PASSWORD=
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120

View File

@@ -1,63 +0,0 @@
<?php
namespace App\Exports;
use Illuminate\Contracts\View\View;
use Maatwebsite\Excel\Concerns\FromView;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class InvoicesExport implements FromView
{
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function view(): View
{
$request = $this->request;
$invoices = DB::table('invoices')
->leftJoin('containers', 'containers.id', '=', 'invoices.container_id')
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no')
->select(
'invoices.invoice_number',
'invoices.invoice_date',
'invoices.mark_no',
'containers.container_number',
'containers.container_date',
DB::raw('COALESCE(invoices.company_name, mark_list.company_name) as company_name'),
DB::raw('COALESCE(invoices.customer_name, mark_list.customer_name) as customer_name'),
'invoices.final_amount',
'invoices.final_amount_with_gst',
'invoices.status as invoice_status'
)
->when($request->filled('search'), function ($q) use ($request) {
$search = trim($request->search);
$q->where(function ($qq) use ($search) {
$qq->where('invoices.invoice_number', 'like', "%{$search}%")
->orWhere('invoices.mark_no', 'like', "%{$search}%")
->orWhere('containers.container_number', 'like', "%{$search}%")
->orWhere('mark_list.company_name', 'like', "%{$search}%")
->orWhere('mark_list.customer_name', 'like', "%{$search}%");
});
})
->when($request->filled('status'), function ($q) use ($request) {
$q->where('invoices.status', $request->status);
})
->when($request->filled('from_date'), function ($q) use ($request) {
$q->whereDate('invoices.invoice_date', '>=', $request->from_date);
})
->when($request->filled('to_date'), function ($q) use ($request) {
$q->whereDate('invoices.invoice_date', '<=', $request->to_date);
})
->orderByDesc('containers.container_date')
->orderByDesc('invoices.id')
->get();
return view('admin.pdf.invoices_excel', compact('invoices'));
}
}

View File

@@ -5,10 +5,6 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\Container;
use App\Models\Invoice;
use App\Models\User;
use App\Models\MarkList;
class AdminAuthController extends Controller
{
@@ -35,15 +31,16 @@ class AdminAuthController extends Controller
}
$credentials = [
$field => $loginInput,
$field => $loginInput,
'password' => $request->password,
];
// attempt login
if (Auth::guard('admin')->attempt($credentials)) {
$request->session()->regenerate();
$user = Auth::guard('admin')->user();
return redirect()->route('admin.dashboard')
->with('success', 'Welcome back, ' . $user->name . '!');
return redirect()->route('admin.dashboard')->with('success', 'Welcome back, ' . $user->name . '!');
}
return back()->withErrors(['login' => 'Invalid login credentials.']);
@@ -54,25 +51,6 @@ class AdminAuthController extends Controller
Auth::guard('admin')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('admin.login')
->with('success', 'Logged out successfully.');
return redirect()->route('admin.login')->with('success', 'Logged out successfully.');
}
public function profile()
{
$user = Auth::guard('admin')->user();
// ── Real Stats ──
$stats = [
'total_containers' => Container::count(),
'total_invoices' => Invoice::count(),
'paid_invoices' => Invoice::where('status', 'paid')->count(),
'pending_invoices' => Invoice::where('status', 'pending')->count(),
'total_customers' => User::count(),
'total_marklist' => MarkList::count(),
'active_marklist' => MarkList::where('status', 'active')->count(),
];
return view('admin.profile', compact('user', 'stats'));
}
}
}

View File

@@ -22,8 +22,7 @@ class AdminCustomerController extends Controller
$query = User::with([
'marks',
'orders',
'invoices.installments',
'invoices.chargeGroups', // 🔥 for order total calculation
'invoices.installments' // 🔥 IMPORTANT
])->orderBy('id', 'desc');
if (!empty($search)) {
@@ -160,4 +159,4 @@ class AdminCustomerController extends Controller
}
}
}

View File

@@ -2,70 +2,37 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; // ⬅️ हे नक्की असू दे
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\InvoiceChargeGroup;
use App\Models\InvoiceChargeGroupItem;
use App\Models\InvoiceInstallment;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Mpdf\Mpdf;
use App\Models\InvoiceInstallment;
use Illuminate\Support\Facades\Log;
class AdminInvoiceController extends Controller
{
// -------------------------------------------------------------
// INDEX (LIST ALL INVOICES WITH FILTERS)
// INVOICE LIST PAGE
// -------------------------------------------------------------
public function index(Request $request)
public function index()
{
$query = Invoice::query();
if ($request->filled('search')) {
$search = $request->search;
$query->where(function ($q) use ($search) {
$q->where('invoice_number', 'like', "%{$search}%")
->orWhere('customer_name', 'like', "%{$search}%");
});
}
if ($request->filled('status') && $request->status !== 'all') {
$query->where('status', $request->status);
}
if ($request->filled('start_date')) {
$query->whereDate('invoice_date', '>=', $request->start_date);
}
if ($request->filled('end_date')) {
$query->whereDate('invoice_date', '<=', $request->end_date);
}
$invoices = $query->latest()->get();
$invoices = Invoice::with(['order.shipments'])->latest()->get();
return view('admin.invoice', compact('invoices'));
}
// -------------------------------------------------------------
// POPUP VIEW
// POPUP VIEW (AJAX)
// -------------------------------------------------------------
public function popup($id)
{
$invoice = Invoice::with([
'items',
'chargeGroups.items',
])->findOrFail($id);
$invoice = Invoice::with(['items', 'order', 'installments'])->findOrFail($id);
$shipment = null;
$shipment = \App\Models\Shipment::whereHas('items', function ($q) use ($invoice) {
$q->where('order_id', $invoice->order_id);
})->first();
$groupedItemIds = $invoice->chargeGroups
->flatMap(fn($group) => $group->items->pluck('invoice_item_id'))
->unique()
->values()
->toArray();
return view('admin.popup_invoice', compact('invoice', 'shipment', 'groupedItemIds'));
return view('admin.popup_invoice', compact('invoice', 'shipment'));
}
// -------------------------------------------------------------
@@ -73,135 +40,90 @@ class AdminInvoiceController extends Controller
// -------------------------------------------------------------
public function edit($id)
{
$invoice = Invoice::with([
'items',
'customer',
'container',
'chargeGroups.items',
'installments',
])->findOrFail($id);
$invoice = Invoice::with(['order.shipments'])->findOrFail($id);
$shipment = $invoice->order?->shipments?->first();
// ✅ Customer details sync
if ($invoice->customer) {
$needsUpdate = [];
if (empty($invoice->customer_email) || $invoice->customer_email === 'test@demo.com') {
$needsUpdate['customer_email'] = $invoice->customer->email;
}
if (empty($invoice->customer_address) || $invoice->customer_address === 'TEST ADDRESS') {
$needsUpdate['customer_address'] = $invoice->customer->address;
}
if (empty($invoice->pincode) || $invoice->pincode === '999999') {
$needsUpdate['pincode'] = $invoice->customer->pincode;
}
if (!empty($needsUpdate)) {
$invoice->update($needsUpdate);
$invoice->refresh();
}
}
$shipment = null;
$groupedItemIds = $invoice->chargeGroups
->flatMap(function ($group) {
return $group->items->pluck('invoice_item_id');
})
->unique()
->values()
->toArray();
return view('admin.invoice_edit', compact('invoice', 'shipment', 'groupedItemIds'));
return view('admin.invoice_edit', compact('invoice', 'shipment'));
}
// -------------------------------------------------------------
// UPDATE INVOICE (HEADER ONLY)
// UPDATE INVOICE
// -------------------------------------------------------------
public function update(Request $request, $id)
{
Log::info('🟡 Invoice Update Request Received', [
Log::info("🟡 Invoice Update Request Received", [
'invoice_id' => $id,
'request' => $request->all(),
'request' => $request->all()
]);
$invoice = Invoice::findOrFail($id);
$data = $request->validate([
'invoice_date' => 'required|date',
'due_date' => 'required|date|after_or_equal:invoice_date',
'status' => 'required|in:pending,paying,paid,overdue',
'notes' => 'nullable|string',
'invoice_date' => 'required|date',
'due_date' => 'required|date|after_or_equal:invoice_date',
'final_amount' => 'required|numeric|min:0',
'tax_type' => 'required|in:gst,igst',
'tax_percent' => 'required|numeric|min:0|max:28',
'status' => 'required|in:pending,paid,overdue',
'notes' => 'nullable|string',
]);
Log::info('✅ Validated Invoice Header Update Data', $data);
Log::info("✅ Validated Invoice Update Data", $data);
$finalAmount = floatval($data['final_amount']);
$taxPercent = floatval($data['tax_percent']);
$taxAmount = 0;
if ($data['tax_type'] === 'gst') {
Log::info("🟢 GST Selected", compact('taxPercent'));
$data['cgst_percent'] = $taxPercent / 2;
$data['sgst_percent'] = $taxPercent / 2;
$data['igst_percent'] = 0;
} else {
Log::info("🔵 IGST Selected", compact('taxPercent'));
$data['cgst_percent'] = 0;
$data['sgst_percent'] = 0;
$data['igst_percent'] = $taxPercent;
}
$taxAmount = ($finalAmount * $taxPercent) / 100;
$data['gst_amount'] = $taxAmount;
$data['final_amount_with_gst'] = $finalAmount + $taxAmount;
$data['gst_percent'] = $taxPercent;
Log::info("📌 Final Calculated Invoice Values", [
'invoice_id' => $invoice->id,
'final_amount' => $finalAmount,
'gst_amount' => $data['gst_amount'],
'final_amount_with_gst' => $data['final_amount_with_gst'],
'tax_type' => $data['tax_type'],
'cgst_percent' => $data['cgst_percent'],
'sgst_percent' => $data['sgst_percent'],
'igst_percent' => $data['igst_percent'],
]);
$invoice->update($data);
$invoice->refresh();
Log::info('🔍 Invoice AFTER HEADER UPDATE', [
'invoice_id' => $invoice->id,
'charge_groups_total' => $invoice->charge_groups_total,
'gst_amount' => $invoice->gst_amount,
'grand_total_with_charges'=> $invoice->grand_total_with_charges,
Log::info(" Invoice Updated Successfully", [
'invoice_id' => $invoice->id
]);
// regenerate PDF
$this->generateInvoicePDF($invoice);
return redirect()
->route('admin.invoices.edit', $invoice->id)
->route('admin.invoices.index')
->with('success', 'Invoice updated & PDF generated successfully.');
}
// -------------------------------------------------------------
// UPDATE INVOICE ITEMS (फक्त items save)
// -------------------------------------------------------------
public function updateItems(Request $request, Invoice $invoice)
{
Log::info('🟡 Invoice Items Update Request', [
'invoice_id' => $invoice->id,
'payload' => $request->all(),
]);
$data = $request->validate([
'items' => ['required', 'array'],
'items.*.price' => ['required', 'numeric', 'min:0'],
'items.*.ttl_amount' => ['required', 'numeric', 'min:0'],
]);
foreach ($data['items'] as $itemId => $itemData) {
$item = InvoiceItem::where('id', $itemId)
->where('invoice_id', $invoice->id)
->first();
if (!$item) {
Log::warning('Invoice item not found or mismatched invoice', [
'invoice_id' => $invoice->id,
'item_id' => $itemId,
]);
continue;
}
$item->price = $itemData['price'];
$item->ttl_amount = $itemData['ttl_amount'];
$item->save();
}
Log::info('✅ Invoice items updated (no totals recalculation)', [
'invoice_id' => $invoice->id,
]);
return back()->with('success', 'Invoice items updated successfully.');
}
// -------------------------------------------------------------
// PDF GENERATION (EXISTING - केवळ chargeGroups load ला confirm कर)
// PDF GENERATION USING mPDF
// -------------------------------------------------------------
public function generateInvoicePDF($invoice)
{
// ✅ यामध्ये chargeGroups आणि installments load कर
$invoice->load(['items', 'customer', 'container', 'chargeGroups.items', 'installments']);
$shipment = null;
$invoice->load(['items', 'order.shipments']);
$shipment = $invoice->order?->shipments?->first();
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
$folder = public_path('invoices/');
@@ -210,295 +132,114 @@ class AdminInvoiceController extends Controller
}
$filePath = $folder . $fileName;
if (file_exists($filePath)) {
unlink($filePath);
}
$mpdf = new Mpdf([
'mode' => 'utf-8',
'format' => 'A4',
'default_font' => 'sans-serif',
]);
$html = view('admin.pdf.invoice', [
'invoice' => $invoice,
'shipment' => $shipment,
])->render();
$mpdf = new Mpdf(['mode' => 'utf-8', 'format' => 'A4', 'default_font' => 'sans-serif']);
$html = view('admin.pdf.invoice', ['invoice' => $invoice, 'shipment' => $shipment])->render();
$mpdf->WriteHTML($html);
$mpdf->Output($filePath, 'F');
$invoice->update(['pdf_path' => 'invoices/' . $fileName]);
}
public function downloadInvoice($id)
{
$invoice = Invoice::findOrFail($id);
// Generate PDF if missing
if (
!$invoice->pdf_path ||
!file_exists(public_path($invoice->pdf_path))
) {
$this->generateInvoicePDF($invoice);
$invoice->refresh();
}
return response()->download(public_path($invoice->pdf_path));
}
// -------------------------------------------------------------
// INSTALLMENTS (ADD)
// INSTALLMENTS (ADD/DELETE)
// -------------------------------------------------------------
public function storeInstallment(Request $request, $invoice_id)
{
$request->validate([
'installment_date' => 'required|date',
'payment_method' => 'required|string',
'reference_no' => 'nullable|string',
'amount' => 'required|numeric|min:1',
'payment_method' => 'required|string',
'reference_no' => 'nullable|string',
'amount' => 'required|numeric|min:1',
]);
$invoice = Invoice::findOrFail($invoice_id);
$grandTotal = $invoice->grand_total_with_charges ?? 0;
$paidTotal = $invoice->installments()->sum('amount');
$remaining = $grandTotal - $paidTotal;
// Use GST-inclusive total for all calculations/checks
$remaining = $invoice->final_amount_with_gst - $paidTotal;
if ($request->amount > $remaining) {
return response()->json([
'status' => 'error',
'message' => 'Installment amount exceeds remaining balance.',
'message' => 'Installment amount exceeds remaining balance.'
], 422);
}
$installment = InvoiceInstallment::create([
'invoice_id' => $invoice_id,
'invoice_id' => $invoice_id,
'installment_date' => $request->installment_date,
'payment_method' => $request->payment_method,
'reference_no' => $request->reference_no,
'amount' => $request->amount,
'payment_method' => $request->payment_method,
'reference_no' => $request->reference_no,
'amount' => $request->amount,
]);
$newPaid = $paidTotal + $request->amount;
$remaining = max(0, $grandTotal - $newPaid);
$isOverdue = now()->startOfDay()->gt(\Carbon\Carbon::parse($invoice->due_date)->startOfDay());
// Mark as 'paid' if GST-inclusive total is cleared
if ($newPaid >= $invoice->final_amount_with_gst) {
$invoice->update(['status' => 'paid']);
if ($grandTotal > 0 && $newPaid >= $grandTotal) {
$newStatus = 'paid';
} elseif ($newPaid > 0 && $isOverdue) {
$newStatus = 'overdue';
} elseif ($newPaid > 0 && !$isOverdue) {
$newStatus = 'paying';
} elseif ($newPaid <= 0 && $isOverdue) {
$newStatus = 'overdue';
} else {
$newStatus = 'pending';
$this->generateInvoicePDF($invoice);
}
$invoice->update([
'payment_method' => $request->payment_method,
'reference_no' => $request->reference_no,
'status' => $newStatus,
]);
return response()->json([
'status' => 'success',
'message' => 'Installment added successfully.',
'installment' => $installment,
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
'gstAmount' => $invoice->gst_amount ?? 0,
'grandTotal' => $grandTotal,
'totalPaid' => $newPaid,
'remaining' => $remaining,
'newStatus' => $newStatus,
'isCompleted' => $remaining <= 0,
'isZero' => $newPaid == 0,
'status' => 'success',
'message' => 'Installment added successfully.',
'installment' => $installment,
'totalPaid' => $newPaid,
'gstAmount' => $invoice->gst_amount,
'finalAmountWithGst' => $invoice->final_amount_with_gst,
'baseAmount' => $invoice->final_amount,
'remaining' => max(0, $invoice->final_amount_with_gst - $newPaid),
'isCompleted' => $newPaid >= $invoice->final_amount_with_gst
]);
}
// -------------------------------------------------------------
// INSTALLMENTS (DELETE)
// -------------------------------------------------------------
public function deleteInstallment($id)
{
$installment = InvoiceInstallment::findOrFail($id);
$invoice = $installment->invoice;
$installment->delete();
$invoice->refresh();
$grandTotal = $invoice->grand_total_with_charges ?? 0;
$paidTotal = $invoice->installments()->sum('amount');
$remaining = max(0, $grandTotal - $paidTotal);
$remaining = $invoice->final_amount_with_gst - $paidTotal;
// Update status if not fully paid anymore
if ($remaining > 0 && $invoice->status === "paid") {
$invoice->update(['status' => 'pending']);
$isOverdue = now()->startOfDay()->gt(\Carbon\Carbon::parse($invoice->due_date)->startOfDay());
if ($grandTotal > 0 && $paidTotal >= $grandTotal) {
$newStatus = 'paid';
} elseif ($paidTotal > 0 && $isOverdue) {
$newStatus = 'overdue';
} elseif ($paidTotal > 0 && !$isOverdue) {
$newStatus = 'paying';
} elseif ($paidTotal <= 0 && $isOverdue) {
$newStatus = 'overdue';
} else {
$newStatus = 'pending';
}
$invoice->update(['status' => $newStatus]);
return response()->json([
'status' => 'success',
'message' => 'Installment deleted.',
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
'gstAmount' => $invoice->gst_amount ?? 0,
'grandTotal' => $grandTotal,
'totalPaid' => $paidTotal,
'remaining' => $remaining,
'newStatus' => $newStatus,
'isZero' => $paidTotal == 0,
]);
}
// -------------------------------------------------------------
// CHARGE GROUP SAVE
// -------------------------------------------------------------
public function storeChargeGroup(Request $request, $invoiceId)
{
Log::info('🟡 storeChargeGroup HIT', [
'invoice_id' => $invoiceId,
'payload' => $request->all(),
]);
$invoice = Invoice::with('items', 'chargeGroups')->findOrFail($invoiceId);
$data = $request->validate([
'groupname' => 'required|string|max:255',
'basistype' => 'required|in:ttl_qty,amount,ttl_cbm,ttl_kg',
'basisvalue' => 'required|numeric',
'rate' => 'required|numeric|min:0.0001',
'autototal' => 'required|numeric|min:0.01',
'itemids' => 'required|array',
'itemids.*' => 'integer|exists:invoice_items,id',
'tax_type' => 'nullable|in:none,gst,igst',
'gst_percent' => 'nullable|numeric|min:0|max:28',
'total_with_gst' => 'nullable|numeric|min:0',
]);
Log::info('✅ storeChargeGroup VALIDATED', $data);
$exists = InvoiceChargeGroup::where('invoice_id', $invoice->id)
->where('group_name', $data['groupname'])
->exists();
if ($exists) {
return back()
->withErrors(['groupname' => 'This group name is already used for this invoice.'])
->withInput();
}
$taxType = $data['tax_type'] ?? 'gst';
$gstPercent = $data['gst_percent'] ?? 0;
$baseTotal = $data['autototal'];
$totalWithGst = $data['total_with_gst'] ?? $baseTotal;
if ($totalWithGst == 0 && $gstPercent > 0) {
$gstAmount = ($baseTotal * $gstPercent) / 100;
$totalWithGst = $baseTotal + $gstAmount;
}
$group = InvoiceChargeGroup::create([
'invoice_id' => $invoice->id,
'group_name' => $data['groupname'],
'basis_type' => $data['basistype'],
'basis_value' => $data['basisvalue'],
'rate' => $data['rate'],
'total_charge' => $baseTotal,
'tax_type' => $taxType,
'gst_percent' => $gstPercent,
'total_with_gst' => $totalWithGst,
]);
foreach ($data['itemids'] as $itemId) {
InvoiceChargeGroupItem::create([
'group_id' => $group->id,
'invoice_item_id' => $itemId,
]);
}
$invoice->load('chargeGroups');
$chargeGroupsBase = $invoice->chargeGroups->sum('total_charge');
$chargeGroupsWithG = $invoice->chargeGroups->sum('total_with_gst');
$chargeGroupsGst = $chargeGroupsWithG - $chargeGroupsBase;
$invoiceGstPercent = $group->gst_percent ?? 0;
$invoiceTaxType = $group->tax_type ?? 'gst';
$cgstPercent = 0;
$sgstPercent = 0;
$igstPercent = 0;
if ($invoiceTaxType === 'gst') {
$cgstPercent = $invoiceGstPercent / 2;
$sgstPercent = $invoiceGstPercent / 2;
} elseif ($invoiceTaxType === 'igst') {
$igstPercent = $invoiceGstPercent;
}
$invoice->update([
'charge_groups_total' => $chargeGroupsBase,
'gst_amount' => $chargeGroupsGst,
'gst_percent' => $invoiceGstPercent,
'tax_type' => $invoiceTaxType,
'cgst_percent' => $cgstPercent,
'sgst_percent' => $sgstPercent,
'igst_percent' => $igstPercent,
'final_amount' => $chargeGroupsBase,
'final_amount_with_gst' => $chargeGroupsWithG,
'grand_total_with_charges' => $chargeGroupsWithG,
]);
Log::info('✅ Invoice updated from Charge Group (CG total + GST)', [
'invoice_id' => $invoice->id,
'charge_groups_total' => $chargeGroupsBase,
'gst_amount' => $chargeGroupsGst,
'grand_total_with_charges'=> $invoice->grand_total_with_charges,
]);
return response()->json([
'success' => true,
'message' => 'Charge group saved successfully.',
'group_id' => $group->id,
]);
}
// ============================================
// 🆕 PDF DOWNLOAD (Direct browser download)
// ============================================
public function downloadPdf($id)
{
$invoice = Invoice::with(['items', 'customer', 'container', 'chargeGroups.items', 'installments'])
->findOrFail($id);
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
$folder = public_path('invoices/');
$filePath = $folder . $fileName;
// जर PDF exist नसेल तर generate कर
if (!file_exists($filePath)) {
$this->generateInvoicePDF($invoice);
}
return response()->download($filePath, $fileName);
}
// ============================================
// 🆕 EXCEL DOWNLOAD (CSV format - simple)
// ============================================
public function share($id)
{
$invoice = Invoice::findOrFail($id);
// इथे तुला जसं share करायचंय तसं logic टाक:
// उदा. public link generate करून redirect कर, किंवा WhatsApp deeplink, इ.
$url = route('admin.invoices.popup', $invoice->id); // example: popup link share
return redirect()->away('https://wa.me/?text=' . urlencode($url));
}
return response()->json([
'status' => 'success',
'message' => 'Installment deleted.',
'totalPaid' => $paidTotal,
'gstAmount' => $invoice->gst_amount,
'finalAmountWithGst' => $invoice->final_amount_with_gst,
'baseAmount' => $invoice->final_amount,
'remaining' => $remaining,
'isZero' => $paidTotal == 0
]);
}
}

View File

@@ -10,118 +10,26 @@ use App\Models\MarkList;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\User;
use App\Models\Container;
use App\Models\Admin;
use App\Models\Shipment;
use PDF;
use Maatwebsite\Excel\Facades\Excel;
use App\Exports\OrdersExport;
use App\Imports\OrderItemsPreviewImport;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\DB;
use App\Exports\InvoicesExport;
class AdminOrderController extends Controller
{
/* ---------------------------
* DASHBOARD (old UI: stats + recent orders)
* LIST / DASHBOARD
* ---------------------------*/
public function dashboard()
public function index()
{
// ── Order counts (from Invoice Management / Orders table) ──
$totalOrders = Order::count();
// "Pending" म्हणजे delivered नसलेले सर्व orders
// Order तयार होतो तेव्हा status = 'order_placed' असतो, 'pending' नाही
$deliveredStatuses = ['delivered'];
$pendingOrders = Order::whereNotIn('status', $deliveredStatuses)->count();
// ── Invoice counts ──
$totalContainers = Container::count();
$totalInvoices = Invoice::count();
$paidInvoices = Invoice::where('status', 'paid')->count();
$pendingInvoices = Invoice::where('status', 'pending')->count();
$overdueInvoices = Invoice::where('status', 'overdue')->count();
$totalRevenue = Invoice::sum('final_amount_with_gst');
// ── User / Staff counts ──
$activeCustomers = User::where('status', 'active')->count();
$inactiveCustomers = User::where('status', 'inactive')->count();
$totalStaff = Admin::where('type', 'staff')->count();
$markList = MarkList::where('status', 'active')->get();
$orders = Order::latest()->get();
$markList = MarkList::where('status', 'active')->get();
return view('admin.dashboard', compact(
'totalOrders',
'pendingOrders',
'totalContainers',
'totalInvoices',
'paidInvoices',
'pendingInvoices',
'overdueInvoices',
'totalRevenue',
'activeCustomers',
'inactiveCustomers',
'totalStaff',
'orders',
'markList'
));
}
/* ---------------------------
* LIST (new: Invoices Management for Orders page)
* ---------------------------*/
public function index(Request $request)
{
$invoices = DB::table('invoices')
->leftJoin('containers', 'containers.id', '=', 'invoices.container_id')
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no')
->select(
'invoices.id',
'invoices.invoice_number',
'invoices.invoice_date',
'invoices.final_amount',
'invoices.final_amount_with_gst',
'invoices.status as invoice_status',
'invoices.mark_no',
'invoices.container_id', // <<< हे नक्की घाल
'containers.container_number',
'containers.container_date',
DB::raw('COALESCE(invoices.company_name, mark_list.company_name) as company_name'),
DB::raw('COALESCE(invoices.customer_name, mark_list.customer_name) as customer_name')
)
->when($request->filled('search'), function ($q) use ($request) {
$search = trim($request->search);
$q->where(function ($qq) use ($search) {
$qq->where('invoices.invoice_number', 'like', "%{$search}%")
->orWhere('containers.container_number', 'like', "%{$search}%")
->orWhere('invoices.mark_no', 'like', "%{$search}%")
->orWhere('mark_list.company_name', 'like', "%{$search}%")
->orWhere('mark_list.customer_name', 'like', "%{$search}%");
});
})
->when($request->filled('status'), function ($q) use ($request) {
$q->where('invoices.status', $request->status);
})
->orderByDesc('invoices.invoice_date')
->orderByDesc('invoices.id')
->get();
// ── Real DB counts (filter-independent) ──
$totalInvoices = \App\Models\Invoice::count();
$paidInvoices = \App\Models\Invoice::where('status', 'paid')->count();
$pendingInvoices = \App\Models\Invoice::where('status', 'pending')->count();
$overdueInvoices = \App\Models\Invoice::where('status', 'overdue')->count();
return view('admin.orders', compact(
'invoices',
'totalInvoices',
'paidInvoices',
'pendingInvoices',
'overdueInvoices'
));
return view('admin.dashboard', compact('orders', 'markList'));
}
/* ---------------------------
@@ -160,39 +68,42 @@ class AdminOrderController extends Controller
* ORDER ITEM MANAGEMENT (existing orders)
* ---------------------------*/
public function addItem(Request $request, $orderId)
{
$order = Order::findOrFail($orderId);
{
$order = Order::findOrFail($orderId);
$data = $request->validate([
'description' => 'required|string',
'ctn' => 'nullable|numeric',
'qty' => 'nullable|numeric',
'unit' => 'nullable|string',
'price' => 'nullable|numeric',
'cbm' => 'nullable|numeric',
'kg' => 'nullable|numeric',
'shop_no' => 'nullable|string',
]);
$data = $request->validate([
'description' => 'required|string',
'ctn' => 'nullable|numeric',
'qty' => 'nullable|numeric',
'unit' => 'nullable|string',
'price' => 'nullable|numeric',
'cbm' => 'nullable|numeric',
'kg' => 'nullable|numeric',
'shop_no' => 'nullable|string',
]);
$ctn = (float) ($data['ctn'] ?? 0);
$qty = (float) ($data['qty'] ?? 0);
$price = (float) ($data['price'] ?? 0);
$cbm = (float) ($data['cbm'] ?? 0);
$kg = (float) ($data['kg'] ?? 0);
// ✅ BACKEND CALCULATION
$ctn = (float) ($data['ctn'] ?? 0);
$qty = (float) ($data['qty'] ?? 0);
$price = (float) ($data['price'] ?? 0);
$cbm = (float) ($data['cbm'] ?? 0);
$kg = (float) ($data['kg'] ?? 0);
$data['ttl_qty'] = $ctn * $qty;
$data['ttl_amount'] = $data['ttl_qty'] * $price;
$data['ttl_cbm'] = $cbm * $ctn;
$data['ttl_kg'] = $ctn * $kg;
$data['ttl_qty'] = $ctn * $qty;
$data['ttl_amount'] = $data['ttl_qty'] * $price;
$data['ttl_cbm'] = $cbm * $ctn;
$data['ttl_kg'] = $ctn * $kg;
$data['order_id'] = $order->id;
$data['order_id'] = $order->id;
OrderItem::create($data);
OrderItem::create($data);
$this->recalcTotals($order);
$this->recalcTotals($order);
$this->updateInvoiceFromOrder($order);
return redirect()->back()->with('success', 'Item added and totals updated.');
}
return redirect()->back()->with('success', 'Item added and totals updated.');
}
public function deleteItem($id)
{
@@ -202,6 +113,7 @@ class AdminOrderController extends Controller
$item->delete();
$this->recalcTotals($order);
$this->updateInvoiceFromOrder($order);
return redirect()->back()->with('success', 'Item deleted and totals updated.');
}
@@ -214,6 +126,7 @@ class AdminOrderController extends Controller
$item->restore();
$this->recalcTotals($order);
$this->updateInvoiceFromOrder($order);
return redirect()->back()->with('success', 'Item restored and totals updated.');
}
@@ -262,14 +175,14 @@ class AdminOrderController extends Controller
$items = $order->items()->get();
$order->update([
'ctn' => (int) $items->sum(fn($i) => (int) ($i->ctn ?? 0)),
'qty' => (int) $items->sum(fn($i) => (int) ($i->qty ?? 0)),
'ttl_qty' => (int) $items->sum(fn($i) => (int) ($i->ttl_qty ?? 0)),
'ttl_amount'=> (float) $items->sum(fn($i) => (float) ($i->ttl_amount ?? 0)),
'cbm' => (float) $items->sum(fn($i) => (float) ($i->cbm ?? 0)),
'ttl_cbm' => (float) $items->sum(fn($i) => (float) ($i->ttl_cbm ?? 0)),
'kg' => (float) $items->sum(fn($i) => (float) ($i->kg ?? 0)),
'ttl_kg' => (float) $items->sum(fn($i) => (float) ($i->ttl_kg ?? 0)),
'ctn' => (int) $items->sum(fn($i) => (int) ($i->ctn ?? 0)),
'qty' => (int) $items->sum(fn($i) => (int) ($i->qty ?? 0)),
'ttl_qty' => (int) $items->sum(fn($i) => (int) ($i->ttl_qty ?? 0)),
'ttl_amount' => (float) $items->sum(fn($i) => (float) ($i->ttl_amount ?? 0)),
'cbm' => (float) $items->sum(fn($i) => (float) ($i->cbm ?? 0)),
'ttl_cbm' => (float) $items->sum(fn($i) => (float) ($i->ttl_cbm ?? 0)),
'kg' => (float) $items->sum(fn($i) => (float) ($i->kg ?? 0)),
'ttl_kg' => (float) $items->sum(fn($i) => (float) ($i->ttl_kg ?? 0)),
]);
}
@@ -372,6 +285,7 @@ class AdminOrderController extends Controller
$order = Order::with([
'markList',
'items',
'invoice.items',
'shipments' => function ($q) use ($id) {
$q->whereHas('orders', function ($oq) use ($id) {
$oq->where('orders.id', $id)
@@ -390,14 +304,14 @@ class AdminOrderController extends Controller
'order_id' => $order->order_id,
'status' => $order->status,
'totals' => [
'ctn' => $order->ctn,
'qty' => $order->qty,
'ttl_qty' => $order->ttl_qty,
'cbm' => $order->cbm,
'ttl_cbm' => $order->ttl_cbm,
'kg' => $order->kg,
'ttl_kg' => $order->ttl_kg,
'amount' => $order->ttl_amount,
'ctn' => $order->ctn,
'qty' => $order->qty,
'ttl_qty' => $order->ttl_qty,
'cbm' => $order->cbm,
'ttl_cbm' => $order->ttl_cbm,
'kg' => $order->kg,
'ttl_kg' => $order->ttl_kg,
'amount' => $order->ttl_amount,
],
'items' => $order->items,
];
@@ -451,6 +365,29 @@ class AdminOrderController extends Controller
}
$invoiceData = null;
if ($order->invoice) {
$invoice = $order->invoice;
$invoiceData = [
'invoice_no' => $invoice->invoice_number,
'status' => $invoice->status,
'invoice_date' => $invoice->invoice_date,
'due_date' => $invoice->due_date,
'customer' => [
'name' => $invoice->customer_name,
'mobile' => $invoice->customer_mobile,
'email' => $invoice->customer_email,
'address' => $invoice->customer_address,
'pincode' => $invoice->pincode,
],
'items' => $invoice->items,
'summary' => [
'amount' => $invoice->final_amount,
'cgst' => 0,
'sgst' => 0,
'total' => $invoice->final_amount_with_gst,
],
];
}
return view('admin.see_order', compact(
'order',
@@ -461,13 +398,14 @@ class AdminOrderController extends Controller
}
/* ---------------------------
* FILTERED LIST + EXPORTS (old orders listing)
* FILTERED LIST + EXPORTS
* ---------------------------*/
public function orderShow()
{
$orders = Order::with([
'markList',
'shipments',
'invoice'
])->latest('id')->get();
return view('admin.orders', compact('orders'));
@@ -476,7 +414,7 @@ class AdminOrderController extends Controller
private function buildOrdersQueryFromRequest(Request $request)
{
$query = Order::query()
->with(['markList', 'shipments']);
->with(['markList', 'invoice', 'shipments']);
if ($request->filled('search')) {
$search = trim($request->search);
@@ -489,12 +427,23 @@ class AdminOrderController extends Controller
->orWhere('origin', 'like', "%{$search}%")
->orWhere('destination', 'like', "%{$search}%");
})
->orWhereHas('invoice', function ($q3) use ($search) {
$q3->where('invoice_number', 'like', "%{$search}%");
})
->orWhereHas('shipments', function ($q4) use ($search) {
$q4->where('shipments.shipment_id', 'like', "%{$search}%");
});
});
}
if ($request->filled('status')) {
$query->where(function ($q) use ($request) {
$q->whereHas('invoice', function ($q2) use ($request) {
$q2->where('status', $request->status);
})->orWhereDoesntHave('invoice');
});
}
if ($request->filled('shipment')) {
$query->where(function ($q) use ($request) {
$q->whereHas('shipments', function ($q2) use ($request) {
@@ -516,83 +465,62 @@ class AdminOrderController extends Controller
public function downloadPdf(Request $request)
{
$invoices = DB::table('invoices')
->leftJoin('containers', 'containers.id', '=', 'invoices.container_id')
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no')
->select(
'invoices.invoice_number',
'invoices.invoice_date',
'invoices.mark_no',
'containers.container_number',
'containers.container_date',
DB::raw('COALESCE(invoices.company_name, mark_list.company_name) as company_name'),
DB::raw('COALESCE(invoices.customer_name, mark_list.customer_name) as customer_name'),
'invoices.final_amount',
'invoices.final_amount_with_gst',
'invoices.status as invoice_status'
)
->when($request->filled('search'), function ($q) use ($request) {
$search = trim($request->search);
$q->where(function ($qq) use ($search) {
$qq->where('invoices.invoice_number', 'like', "%{$search}%")
->orWhere('invoices.mark_no', 'like', "%{$search}%")
->orWhere('containers.container_number', 'like', "%{$search}%")
->orWhere('mark_list.company_name', 'like', "%{$search}%")
->orWhere('mark_list.customer_name', 'like', "%{$search}%");
});
})
->when($request->filled('status'), function ($q) use ($request) {
$q->where('invoices.status', $request->status);
})
->when($request->filled('from_date'), function ($q) use ($request) {
$q->whereDate('invoices.invoice_date', '>=', $request->from_date);
})
->when($request->filled('to_date'), function ($q) use ($request) {
$q->whereDate('invoices.invoice_date', '<=', $request->to_date);
})
->orderByDesc('containers.container_date')
->orderByDesc('invoices.id')
->get();
$pdf = PDF::loadView('admin.pdf.invoices_report', compact('invoices'))
$orders = $this->buildOrdersQueryFromRequest($request)->get();
$filters = [
'search' => $request->search,
'status' => $request->status,
'shipment' => $request->shipment,
'from' => $request->from_date,
'to' => $request->to_date,
];
$pdf = PDF::loadView('admin.orders.pdf', compact('orders', 'filters'))
->setPaper('a4', 'landscape');
return $pdf->download(
'invoices-report-' . now()->format('Y-m-d') . '.pdf'
'orders-report-' . now()->format('Y-m-d') . '.pdf'
);
}
public function downloadExcel(Request $request)
{
return Excel::download(
new InvoicesExport($request),
'invoices-report-' . now()->format('Y-m-d') . '.xlsx'
new OrdersExport($request),
'orders-report-' . now()->format('Y-m-d') . '.xlsx'
);
}
/* --------------------------------------------------
* NEW: Create Order + Invoice directly from popup
* route: admin.orders.temp.add (Create New Order form)
* --------------------------------------------------*/
public function addTempItem(Request $request)
{
// 1) order-level fields
$request->validate([
'mark_no' => 'required',
'origin' => 'nullable',
'destination' => 'nullable',
'origin' => 'required',
'destination' => 'required',
]);
// 2) multi-row items
$items = $request->validate([
'items' => 'required|array',
'items.*.description' => 'required|string',
'items.*.ctn' => 'nullable|numeric',
'items.*.qty' => 'nullable|numeric',
'items.*.unit' => 'nullable|string',
'items.*.price' => 'nullable|numeric',
'items.*.cbm' => 'nullable|numeric',
'items.*.kg' => 'nullable|numeric',
'items.*.shop_no' => 'nullable|string',
])['items'];
// रिकामे rows काढा
$items = array_filter($items, function ($row) {
return trim($row['description'] ?? '') !== '';
});
@@ -601,31 +529,38 @@ class AdminOrderController extends Controller
return back()->with('error', 'Add at least one item.');
}
// ✅ BACKEND CALCULATION (DO NOT TRUST FRONTEND)
foreach ($items as &$item) {
$ctn = (float) ($item['ctn'] ?? 0);
$qty = (float) ($item['qty'] ?? 0);
$price = (float) ($item['price'] ?? 0);
$cbm = (float) ($item['cbm'] ?? 0);
$kg = (float) ($item['kg'] ?? 0);
// Calculated fields
$item['ttl_qty'] = $ctn * $qty;
$item['ttl_amount'] = $item['ttl_qty'] * $price;
$item['ttl_cbm'] = $cbm * $ctn;
$item['ttl_kg'] = $ctn * $kg;
}
unset($item);
unset($item); // VERY IMPORTANT
$total_ctn = array_sum(array_column($items, 'ctn'));
$total_qty = array_sum(array_column($items, 'qty'));
$total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
$total_amount = array_sum(array_column($items, 'ttl_amount'));
$total_cbm = array_sum(array_column($items, 'cbm'));
$total_ttl_cbm = array_sum(array_column($items, 'ttl_cbm'));
$total_kg = array_sum(array_column($items, 'kg'));
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
// 3) totals
$total_ctn = array_sum(array_column($items, 'ctn'));
$total_qty = array_sum(array_column($items, 'qty'));
$total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
$total_amount = array_sum(array_column($items, 'ttl_amount'));
$total_cbm = array_sum(array_column($items, 'cbm'));
$total_ttl_cbm = array_sum(array_column($items, 'ttl_cbm'));
$total_kg = array_sum(array_column($items, 'kg'));
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
// 4) order id generate
$orderId = $this->generateOrderId();
// 5) order create
$order = Order::create([
'order_id' => $orderId,
'mark_no' => $request->mark_no,
@@ -642,6 +577,7 @@ class AdminOrderController extends Controller
'status' => 'order_placed',
]);
// 6) order items
foreach ($items as $item) {
OrderItem::create([
'order_id' => $order->id,
@@ -660,14 +596,17 @@ class AdminOrderController extends Controller
]);
}
// 7) invoice number
$invoiceNumber = $this->generateInvoiceNumber();
// 8) customer fetch
$markList = MarkList::where('mark_no', $order->mark_no)->first();
$customer = null;
if ($markList && $markList->customer_id) {
$customer = User::where('customer_id', $markList->customer_id)->first();
}
// 9) invoice create
$invoice = Invoice::create([
'order_id' => $order->id,
'customer_id' => $customer->id ?? null,
@@ -692,6 +631,7 @@ class AdminOrderController extends Controller
'pdf_path' => null,
]);
// 10) invoice items
foreach ($order->items as $item) {
InvoiceItem::create([
'invoice_id' => $invoice->id,
@@ -718,72 +658,114 @@ class AdminOrderController extends Controller
* UPDATE ORDER ITEM (existing orders)
* ---------------------------*/
public function updateItem(Request $request, $id)
{
$item = OrderItem::findOrFail($id);
$order = $item->order;
$request->validate([
'description' => 'required|string',
'ctn' => 'nullable|numeric',
'qty' => 'nullable|numeric',
'unit' => 'nullable|string',
'price' => 'nullable|numeric',
'cbm' => 'nullable|numeric',
'kg' => 'nullable|numeric',
'shop_no' => 'nullable|string',
]);
// ✅ BACKEND CALCULATION
$ctn = (float) ($request->ctn ?? 0);
$qty = (float) ($request->qty ?? 0);
$price = (float) ($request->price ?? 0);
$cbm = (float) ($request->cbm ?? 0);
$kg = (float) ($request->kg ?? 0);
$item->update([
'description' => $request->description,
'ctn' => $ctn,
'qty' => $qty,
'ttl_qty' => $ctn * $qty,
'unit' => $request->unit,
'price' => $price,
'ttl_amount' => ($ctn * $qty) * $price,
'cbm' => $cbm,
'ttl_cbm' => $cbm * $ctn,
'kg' => $kg,
'ttl_kg' => $ctn * $kg,
'shop_no' => $request->shop_no,
]);
$this->recalcTotals($order);
$this->updateInvoiceFromOrder($order);
return back()->with('success', 'Item updated successfully');
}
private function updateInvoiceFromOrder(Order $order)
{
$item = OrderItem::findOrFail($id);
$order = $item->order;
$invoice = Invoice::where('order_id', $order->id)->first();
$request->validate([
'description' => 'required|string',
'ctn' => 'nullable|numeric',
'qty' => 'nullable|numeric',
'unit' => 'nullable|string',
'price' => 'nullable|numeric',
'cbm' => 'nullable|numeric',
'kg' => 'nullable|numeric',
'shop_no' => 'nullable|string',
]);
if (!$invoice) {
return;
}
$ctn = (float) ($request->ctn ?? 0);
$qty = (float) ($request->qty ?? 0);
$price = (float) ($request->price ?? 0);
$cbm = (float) ($request->cbm ?? 0);
$kg = (float) ($request->kg ?? 0);
$invoice->final_amount = $order->ttl_amount;
$invoice->gst_percent = 0;
$invoice->gst_amount = 0;
$invoice->final_amount_with_gst = $order->ttl_amount;
$invoice->save();
$item->update([
'description' => $request->description,
'ctn' => $ctn,
'qty' => $qty,
'ttl_qty' => $ctn * $qty,
'unit' => $request->unit,
'price' => $price,
'ttl_amount' => ($ctn * $qty) * $price,
'cbm' => $cbm,
'ttl_cbm' => $cbm * $ctn,
'kg' => $kg,
'ttl_kg' => $ctn * $kg,
'shop_no' => $request->shop_no,
]);
InvoiceItem::where('invoice_id', $invoice->id)->delete();
$this->recalcTotals($order);
return back()->with('success', 'Item updated successfully');
}
public function uploadExcelPreview(Request $request)
{
try {
$request->validate([
'excel' => 'required|file|mimes:xlsx,xls'
foreach ($order->items as $item) {
InvoiceItem::create([
'invoice_id' => $invoice->id,
'description' => $item->description,
'ctn' => $item->ctn,
'qty' => $item->qty,
'ttl_qty' => $item->ttl_qty,
'unit' => $item->unit,
'price' => $item->price,
'ttl_amount' => $item->ttl_amount,
'cbm' => $item->cbm,
'ttl_cbm' => $item->ttl_cbm,
'kg' => $item->kg,
'ttl_kg' => $item->ttl_kg,
'shop_no' => $item->shop_no,
]);
$import = new OrderItemsPreviewImport();
Excel::import($import, $request->file('excel'));
return response()->json([
'success' => true,
'items' => $import->rows
]);
} catch (ValidationException $e) {
return response()->json([
'success' => false,
'message' => 'Invalid Excel file format'
], 422);
} catch (\Throwable $e) {
\Log::error($e);
return response()->json([
'success' => false,
'message' => 'Server error'
], 500);
}
}
}
public function uploadExcelPreview(Request $request)
{
try {
$request->validate([
'excel' => 'required|file|mimes:xlsx,xls'
]);
$import = new OrderItemsPreviewImport();
Excel::import($import, $request->file('excel'));
return response()->json([
'success' => true,
'items' => $import->rows
]);
} catch (ValidationException $e) {
return response()->json([
'success' => false,
'message' => 'Invalid Excel file format'
], 422);
} catch (\Throwable $e) {
\Log::error($e);
return response()->json([
'success' => false,
'message' => 'Server error'
], 500);
}
}
}

View File

@@ -3,199 +3,54 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Order;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Facades\Excel;
use Mpdf\Mpdf;
class AdminReportController extends Controller
{
// UI साठी main action
public function containerReport(Request $request)
/**
* Display the reports page with joined data
*/
public function index(Request $request)
{
$reports = $this->buildContainerReportQuery($request)->get();
// -------------------------------
// FETCH REPORT DATA
// ONLY orders that have BOTH:
// 1. Invoice
// 2. Shipment
// -------------------------------
$reports = DB::table('orders')
->join('shipment_items', 'shipment_items.order_id', '=', 'orders.id')
->join('shipments', 'shipments.id', '=', 'shipment_items.shipment_id')
->join('invoices', 'invoices.order_id', '=', 'orders.id')
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'orders.mark_no')
->leftJoin('users', 'users.customer_id', '=', 'mark_list.customer_id')
->select(
'orders.id as order_pk',
'orders.order_id',
'orders.mark_no',
'orders.origin',
'orders.destination',
'shipments.id as shipment_pk',
'shipments.shipment_id',
'shipments.status as shipment_status',
'shipments.shipment_date',
'invoices.invoice_number',
'invoices.invoice_date',
'invoices.final_amount',
'invoices.status as invoice_status',
'mark_list.company_name',
'mark_list.customer_name'
)
->orderBy('shipments.shipment_date', 'desc')
->get();
return view('admin.reports', compact('reports'));
}
// ही common query — filters accept करते
protected function buildContainerReportQuery(Request $request = null)
{
$query = DB::table('invoices')
->join('containers', 'containers.id', '=', 'invoices.container_id')
->leftJoinSub(
DB::table('invoice_installments')
->select('invoice_id', DB::raw('COALESCE(SUM(amount), 0) as total_paid'))
->groupBy('invoice_id'),
'inst',
'inst.invoice_id',
'=',
'invoices.id'
)
->select(
'containers.id as container_id',
'containers.container_number',
'containers.container_date',
'containers.container_name',
DB::raw('COUNT(DISTINCT invoices.mark_no) as total_mark_nos'),
DB::raw('COUNT(DISTINCT invoices.customer_id) as total_customers'),
DB::raw('COUNT(invoices.id) as total_invoices'),
DB::raw('COALESCE(SUM(invoices.charge_groups_total), 0) as total_invoice_amount'),
DB::raw('COALESCE(SUM(invoices.gst_amount), 0) as total_gst_amount'),
DB::raw('COALESCE(SUM(invoices.grand_total_with_charges), 0) as total_payable'),
DB::raw('COALESCE(SUM(inst.total_paid), 0) as total_paid'),
DB::raw('GREATEST(0, COALESCE(SUM(invoices.grand_total_with_charges), 0) - COALESCE(SUM(inst.total_paid), 0)) as total_remaining'),
DB::raw("
CASE
WHEN COUNT(invoices.id) > 0
AND SUM(CASE WHEN invoices.status != 'paid' THEN 1 ELSE 0 END) = 0
THEN 'paid'
WHEN SUM(CASE WHEN invoices.status = 'overdue' THEN 1 ELSE 0 END) > 0
THEN 'overdue'
WHEN SUM(CASE WHEN invoices.status = 'paying' THEN 1 ELSE 0 END) > 0
THEN 'paying'
ELSE 'pending'
END as container_status
")
)
->groupBy(
'containers.id',
'containers.container_number',
'containers.container_date',
'containers.container_name'
)
->orderBy('containers.container_date', 'desc')
->orderBy('containers.id', 'desc');
// ── Filters ──────────────────────────────────────────────────────
if ($request) {
if ($request->filled('from_date')) {
$query->whereDate('containers.container_date', '>=', $request->from_date);
}
if ($request->filled('to_date')) {
$query->whereDate('containers.container_date', '<=', $request->to_date);
}
if ($request->filled('status')) {
// container_status हे aggregate expression आहे,
// त्यामुळे HAVING clause वापरतो
$query->havingRaw("
CASE
WHEN COUNT(invoices.id) > 0
AND SUM(CASE WHEN invoices.status != 'paid' THEN 1 ELSE 0 END) = 0
THEN 'paid'
WHEN SUM(CASE WHEN invoices.status = 'overdue' THEN 1 ELSE 0 END) > 0
THEN 'overdue'
WHEN SUM(CASE WHEN invoices.status = 'paying' THEN 1 ELSE 0 END) > 0
THEN 'paying'
ELSE 'pending'
END = ?
", [$request->status]);
}
}
return $query;
}
// ---- Excel export ----
public function containerReportExcel(Request $request)
{
$reports = $this->buildContainerReportQuery($request)->get();
$headings = [
'Container No',
'Container Date',
'Total Mark Nos',
'Total Customers',
'Total Invoices',
'Invoice Amount (Before GST)',
'GST Amount',
'Payable Amount (Incl. GST)',
'Paid Amount',
'Remaining Amount',
'Container Status',
];
$rows = $reports->map(function ($r) {
return [
$r->container_number,
$r->container_date,
$r->total_mark_nos,
$r->total_customers,
$r->total_invoices,
$r->total_invoice_amount,
$r->total_gst_amount,
$r->total_payable,
$r->total_paid,
$r->total_remaining,
$r->container_status,
];
})->toArray();
$export = new class($headings, $rows) implements
\Maatwebsite\Excel\Concerns\FromArray,
\Maatwebsite\Excel\Concerns\WithHeadings
{
private $headings;
private $rows;
public function __construct($headings, $rows)
{
$this->headings = $headings;
$this->rows = $rows;
}
public function array(): array
{
return $this->rows;
}
public function headings(): array
{
return $this->headings;
}
};
return Excel::download(
$export,
'container-report-' . now()->format('Ymd-His') . '.xlsx'
);
}
// ---- PDF export ----
public function containerReportPdf(Request $request)
{
$reports = $this->buildContainerReportQuery($request)->get();
$html = view('admin.reports', [
'reports' => $reports,
'isPdf' => true,
])->render();
$mpdf = new \Mpdf\Mpdf([
'mode' => 'utf-8',
'format' => 'A4-L',
'default_font' => 'dejavusans',
'margin_top' => 8,
'margin_right' => 8,
'margin_bottom' => 10,
'margin_left' => 8,
]);
$mpdf->SetHTMLHeader('');
$mpdf->SetHTMLFooter('');
$mpdf->WriteHTML($html);
$fileName = 'container-report-' . now()->format('Ymd-His') . '.pdf';
return response($mpdf->Output($fileName, 'S'), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="' . $fileName . '"',
]);
}
}
}

View File

@@ -85,6 +85,7 @@ public function approveProfileUpdate($id)
$req = \App\Models\UpdateRequest::findOrFail($id);
$user = \App\Models\User::findOrFail($req->user_id);
// FIX: Ensure data is array
$newData = is_array($req->data) ? $req->data : json_decode($req->data, true);
foreach ($newData as $key => $value) {
@@ -95,18 +96,8 @@ public function approveProfileUpdate($id)
}
}
// Update user table
$user->save();
// Update mark_list table
\App\Models\MarkList::where('customer_id', $user->customer_id)
->update([
'customer_name' => $user->customer_name,
'company_name' => $user->company_name,
'mobile_no' => $user->mobile_no
]);
// Update request status
$req->status = 'approved';
$req->admin_note = 'Approved by admin on ' . now();
$req->save();

View File

@@ -1,964 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Container;
use App\Models\ContainerRow;
use App\Models\MarkList;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
use Carbon\Carbon;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Facades\Storage;
class ContainerController extends Controller
{
public function index()
{
$containers = Container::with('rows')->latest()->get();
$containers->each(function ($container) {
$rows = $container->rows;
$totalCtn = 0;
$totalQty = 0;
$totalCbm = 0;
$totalKg = 0;
$ctnKeys = ['CTN', 'CTNS'];
$qtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY', 'QTY', 'PCS', 'PIECES'];
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
$getFirstNumeric = function (array $data, array $possibleKeys) {
$normalizedMap = [];
foreach ($data as $key => $value) {
if ($key === null || $key === '') continue;
$normKey = strtoupper((string)$key);
$normKey = str_replace([' ', '/', '-', '.'], '', $normKey);
$normalizedMap[$normKey] = $value;
}
foreach ($possibleKeys as $search) {
$normSearch = strtoupper($search);
$normSearch = str_replace([' ', '/', '-', '.'], '', $normSearch);
foreach ($normalizedMap as $nKey => $value) {
if (
strpos($nKey, $normSearch) !== false &&
(is_numeric($value) || (is_string($value) && is_numeric(trim($value))))
) {
return (float) trim($value);
}
}
}
return 0;
};
foreach ($rows as $row) {
$data = $row->data ?? [];
$totalCtn += $getFirstNumeric($data, $ctnKeys);
$totalQty += $getFirstNumeric($data, $qtyKeys);
$totalCbm += $getFirstNumeric($data, $cbmKeys);
$totalKg += $getFirstNumeric($data, $kgKeys);
}
$container->summary = [
'total_ctn' => round($totalCtn, 2),
'total_qty' => round($totalQty, 2),
'total_cbm' => round($totalCbm, 3),
'total_kg' => round($totalKg, 2),
];
});
return view('admin.container', compact('containers'));
}
public function create()
{
return view('admin.container_create');
}
private function isValidExcelFormat($rows, $header)
{
if (empty($header) || count($rows) < 2) return false;
$validKeywords = [
'MARK', 'DESCRIPTION', 'DESC', 'CTN', 'CTNS', 'QTY', 'TOTALQTY', 'ITLQTY', 'ITL QTY',
'UNIT', 'CBM', 'TOTAL CBM', 'KG', 'TOTAL KG',
'METAL BUCKLE', 'WATCH MOVEMENT', 'STEEL BOTTLE',
'MEHULPAID', 'ITEM NO', 'ITEM NO.', 'SAHILPAID', 'PINAKIN', 'GST',
'MOON LAMP', 'TRANSPARENT BOTTLE', 'PLASTIC FONDANT',
];
$headerText = implode(' ', array_map('strtoupper', $header));
$requiredHeaders = ['CTN', 'QTY', 'DESCRIPTION', 'DESC'];
$hasValidHeaders = false;
foreach ($requiredHeaders as $key) {
if (stripos($headerText, $key) !== false) {
$hasValidHeaders = true;
break;
}
}
if (!$hasValidHeaders) return false;
$dataPreview = '';
for ($i = 0; $i < min(5, count($rows)); $i++) {
$rowText = implode(' ', array_slice($rows[$i], 0, 10));
$dataPreview .= ' ' . strtoupper((string)$rowText);
}
$validMatches = 0;
foreach ($validKeywords as $keyword) {
if (stripos($headerText . $dataPreview, strtoupper($keyword)) !== false) {
$validMatches++;
}
}
return $validMatches >= 3;
}
private function normalizeKey($value): string
{
$norm = strtoupper((string)$value);
return str_replace([' ', '/', '-', '.'], '', $norm);
}
public function store(Request $request)
{
$request->validate([
'container_name' => 'required|string',
'container_number' => 'required|string|unique:containers,container_number',
'container_date' => 'required|date',
'excel_file' => 'required|file|mimes:xls,xlsx',
]);
$file = $request->file('excel_file');
$sheets = Excel::toArray([], $file);
$rows = $sheets[0] ?? [];
if (count($rows) < 2) {
return back()
->withErrors(['excel_file' => 'Excel file is empty.'])
->withInput();
}
// HEADER DETECTION
$headerRowIndex = null;
$header = [];
foreach ($rows as $i => $row) {
$trimmed = array_map(fn($v) => trim((string)$v), $row);
$nonEmpty = array_filter($trimmed, fn($v) => $v !== '');
if (empty($nonEmpty)) continue;
if (count($nonEmpty) >= 4) {
$headerRowIndex = $i;
$header = $trimmed;
break;
}
}
if ($headerRowIndex === null) {
return back()
->withErrors(['excel_file' => 'Header row not found in Excel.'])
->withInput();
}
if (!$this->isValidExcelFormat($rows, $header)) {
return back()
->withErrors(['excel_file' => 'Only MEHUL / SAHIL / PINAKIN / GST loading list formats allowed.'])
->withInput();
}
// COLUMN INDEXES
$essentialColumns = [
'desc_col' => null,
'ctn_col' => null,
'qty_col' => null,
'totalqty_col' => null,
'unit_col' => null,
'price_col' => null,
'amount_col' => null,
'cbm_col' => null,
'totalcbm_col' => null,
'kg_col' => null,
'totalkg_col' => null,
'itemno_col' => null,
'shopno_col' => null,
];
foreach ($header as $colIndex => $headingText) {
if (empty($headingText)) continue;
$normalized = $this->normalizeKey($headingText);
if (strpos($normalized, 'DESCRIPTION') !== false || strpos($normalized, 'DESC') !== false) {
$essentialColumns['desc_col'] = $colIndex;
} elseif (strpos($normalized, 'CTN') !== false || strpos($normalized, 'CTNS') !== false) {
$essentialColumns['ctn_col'] = $colIndex;
} elseif (
strpos($normalized, 'ITLQTY') !== false ||
strpos($normalized, 'TOTALQTY') !== false ||
strpos($normalized, 'TTLQTY') !== false
) {
$essentialColumns['totalqty_col'] = $colIndex;
} elseif (strpos($normalized, 'QTY') !== false) {
$essentialColumns['qty_col'] = $colIndex;
} elseif (strpos($normalized, 'UNIT') !== false) {
$essentialColumns['unit_col'] = $colIndex;
} elseif (strpos($normalized, 'PRICE') !== false) {
$essentialColumns['price_col'] = $colIndex;
} elseif (strpos($normalized, 'AMOUNT') !== false) {
$essentialColumns['amount_col'] = $colIndex;
} elseif (strpos($normalized, 'TOTALCBM') !== false || strpos($normalized, 'ITLCBM') !== false) {
$essentialColumns['totalcbm_col'] = $colIndex;
} elseif (strpos($normalized, 'CBM') !== false) {
$essentialColumns['cbm_col'] = $colIndex;
} elseif (strpos($normalized, 'TOTALKG') !== false || strpos($normalized, 'TTKG') !== false) {
$essentialColumns['totalkg_col'] = $colIndex;
} elseif (strpos($normalized, 'KG') !== false) {
$essentialColumns['kg_col'] = $colIndex;
} elseif (
strpos($normalized, 'MARKNO') !== false ||
strpos($normalized, 'MARK') !== false ||
strpos($normalized, 'ITEMNO') !== false ||
strpos($normalized, 'ITEM') !== false
) {
$essentialColumns['itemno_col'] = $colIndex;
} elseif (
strpos($normalized, 'SHOPNO') !== false ||
strpos($normalized, 'SHOP') !== false
) {
$essentialColumns['shopno_col'] = $colIndex;
}
}
if (is_null($essentialColumns['itemno_col'])) {
return back()
->withErrors(['excel_file' => 'Mark / Item column not found in Excel (expected headers like MARK NO / Mark_No / Item_No).'])
->withInput();
}
// ROWS CLEANING
$dataRows = array_slice($rows, $headerRowIndex + 1);
$cleanedRows = [];
$unmatchedRowsData = [];
foreach ($dataRows as $offset => $row) {
$trimmedRow = array_map(fn($v) => trim((string)$v), $row);
$nonEmptyCells = array_filter($trimmedRow, fn($v) => $v !== '');
if (count($nonEmptyCells) < 2) continue;
$rowText = strtoupper(implode(' ', $trimmedRow));
if (
stripos($rowText, 'TOTAL') !== false ||
stripos($rowText, 'TTL') !== false ||
stripos($rowText, 'GRAND') !== false
) {
continue;
}
$descValue = '';
if ($essentialColumns['desc_col'] !== null) {
$descValue = trim($row[$essentialColumns['desc_col']] ?? '');
}
if ($essentialColumns['desc_col'] !== null && $descValue === '' && count($nonEmptyCells) >= 1) {
continue;
}
$cleanedRows[] = [
'row' => $row,
'offset' => $offset,
];
}
if (empty($cleanedRows)) {
return back()
->withErrors(['excel_file' => 'No valid item rows found in Excel.'])
->withInput();
}
// FORMULA CHECK
$cleanNumber = function ($value) {
if (is_string($value)) {
$value = str_replace(',', '', trim($value));
}
return is_numeric($value) ? (float)$value : 0;
};
$formulaErrors = [];
foreach ($cleanedRows as $item) {
$row = $item['row'];
$offset = $item['offset'];
$ctn = $essentialColumns['ctn_col'] !== null ? $cleanNumber($row[$essentialColumns['ctn_col']] ?? 0) : 0;
$qty = $essentialColumns['qty_col'] !== null ? $cleanNumber($row[$essentialColumns['qty_col']] ?? 0) : 0;
$ttlQ = $essentialColumns['totalqty_col'] !== null ? $cleanNumber($row[$essentialColumns['totalqty_col']] ?? 0) : 0;
$cbm = $essentialColumns['cbm_col'] !== null ? $cleanNumber($row[$essentialColumns['cbm_col']] ?? 0) : 0;
$ttlC = $essentialColumns['totalcbm_col'] !== null ? $cleanNumber($row[$essentialColumns['totalcbm_col']] ?? 0) : 0;
$kg = $essentialColumns['kg_col'] !== null ? $cleanNumber($row[$essentialColumns['kg_col']] ?? 0) : 0;
$ttlK = $essentialColumns['totalkg_col'] !== null ? $cleanNumber($row[$essentialColumns['totalkg_col']] ?? 0) : 0;
$price = $essentialColumns['price_col'] !== null ? $cleanNumber($row[$essentialColumns['price_col']] ?? 0) : 0;
$ttlAmount = $essentialColumns['amount_col'] !== null ? $cleanNumber($row[$essentialColumns['amount_col']] ?? 0) : 0;
$desc = $essentialColumns['desc_col'] !== null ? (string)($row[$essentialColumns['desc_col']] ?? '') : '';
$mark = $essentialColumns['itemno_col'] !== null ? (string)($row[$essentialColumns['itemno_col']] ?? '') : '';
$expTtlQty = $qty * $ctn;
$expTtlCbm = $cbm * $ctn;
$expTtlKg = $kg * $ctn;
$expTtlAmount = ($qty * $ctn) * $price;
$rowErrors = [];
if (abs($ttlQ - $expTtlQty) > 0.01) {
$rowErrors['TOTAL QTY'] = [
'actual' => $ttlQ,
'expected' => $expTtlQty,
];
}
if (abs($ttlC - $expTtlCbm) > 0.0005) {
$rowErrors['TOTAL CBM'] = [
'actual' => $ttlC,
'expected' => $expTtlCbm,
];
}
if (abs($ttlK - $expTtlKg) > 0.01) {
$rowErrors['TOTAL KG'] = [
'actual' => $ttlK,
'expected' => $expTtlKg,
];
}
if ($essentialColumns['amount_col'] !== null && $essentialColumns['price_col'] !== null) {
if (abs($ttlAmount - $expTtlAmount) > 0.01) {
$rowErrors['TOTAL AMOUNT'] = [
'actual' => $ttlAmount,
'expected' => $expTtlAmount,
];
}
}
if (!empty($rowErrors)) {
$rowData = [];
foreach ($header as $colIndex => $headingText) {
$value = $row[$colIndex] ?? null;
if (is_string($value)) $value = trim($value);
$rowData[$headingText] = $value;
}
$formulaErrors[] = [
'excel_row' => $headerRowIndex + 1 + $offset,
'mark_no' => $mark,
'description' => $desc,
'errors' => $rowErrors,
'data' => $rowData,
];
}
}
// MARK CHECK
$marksFromExcel = [];
foreach ($cleanedRows as $item) {
$row = $item['row'];
$rawMark = $row[$essentialColumns['itemno_col']] ?? null;
$mark = trim((string)($rawMark ?? ''));
if ($mark !== '') {
$marksFromExcel[] = $mark;
}
}
$marksFromExcel = array_values(array_unique($marksFromExcel));
if (empty($marksFromExcel)) {
return back()
->withErrors(['excel_file' => 'No mark numbers found in Excel file.'])
->withInput();
}
$validMarks = MarkList::whereIn('mark_no', $marksFromExcel)
->where('status', 'active')
->pluck('mark_no')
->toArray();
$unmatchedMarks = array_values(array_diff($marksFromExcel, $validMarks));
$markErrors = [];
if (!empty($unmatchedMarks)) {
foreach ($cleanedRows as $item) {
$row = $item['row'];
$offset = $item['offset'];
$rowMark = trim((string)($row[$essentialColumns['itemno_col']] ?? ''));
if ($rowMark === '' || !in_array($rowMark, $unmatchedMarks)) {
continue;
}
$rowData = [];
foreach ($header as $colIndex => $headingText) {
$value = $row[$colIndex] ?? null;
if (is_string($value)) $value = trim($value);
$rowData[$headingText] = $value;
}
$markErrors[] = [
'excel_row' => $headerRowIndex + 1 + $offset,
'mark_no' => $rowMark,
'data' => $rowData,
];
}
}
if (!empty($formulaErrors) || !empty($markErrors)) {
return back()
->withInput()
->with([
'formula_errors' => $formulaErrors,
'mark_errors' => $markErrors,
]);
}
// STEP 1: Marks → customers mapping + grouping
$markRecords = MarkList::whereIn('mark_no', $marksFromExcel)
->where('status', 'active')
->get();
$markToCustomerId = [];
$markToSnapshot = [];
foreach ($markRecords as $mr) {
$markToCustomerId[$mr->mark_no] = $mr->customer_id;
$markToSnapshot[$mr->mark_no] = [
'customer_name' => $mr->customer_name,
'company_name' => $mr->company_name,
'mobile_no' => $mr->mobile_no,
];
}
$groupedByCustomer = [];
foreach ($cleanedRows as $item) {
$row = $item['row'];
$offset = $item['offset'];
$rawMark = $row[$essentialColumns['itemno_col']] ?? null;
$mark = trim((string)($rawMark ?? ''));
if ($mark === '') {
continue;
}
$customerId = $markToCustomerId[$mark] ?? null;
if (!$customerId) {
continue;
}
if (!isset($groupedByCustomer[$customerId])) {
$groupedByCustomer[$customerId] = [];
}
$groupedByCustomer[$customerId][] = [
'row' => $row,
'offset' => $offset,
'mark' => $mark,
];
}
// STEP 2: Container + ContainerRows save
$container = Container::create([
'container_name' => $request->container_name,
'container_number' => $request->container_number,
'container_date' => $request->container_date,
'status' => 'pending',
]);
$path = $file->store('containers');
$container->update(['excel_file' => $path]);
$savedCount = 0;
foreach ($cleanedRows as $item) {
$row = $item['row'];
$offset = $item['offset'];
$data = [];
foreach ($header as $colIndex => $headingText) {
$value = $row[$colIndex] ?? null;
if (is_string($value)) $value = trim($value);
$data[$headingText] = $value;
}
ContainerRow::create([
'container_id' => $container->id,
'row_index' => $headerRowIndex + 1 + $offset,
'data' => $data,
]);
$savedCount++;
}
// STEP 3: per-customer invoices + invoice items
$invoiceCount = 0;
foreach ($groupedByCustomer as $customerId => $rowsForCustomer) {
if (empty($rowsForCustomer)) {
continue;
}
$firstMark = $rowsForCustomer[0]['mark'];
$snap = $markToSnapshot[$firstMark] ?? null;
$customerUser = \App\Models\User::where('customer_id', $customerId)->first();
$invoice = new Invoice();
$invoice->container_id = $container->id;
$invoice->customer_id = $customerUser->id ?? null;
$invoice->mark_no = $firstMark;
$invoice->invoice_number = $this->generateInvoiceNumber();
$invoice->invoice_date = $container->container_date;
$invoice->due_date = Carbon::parse($invoice->invoice_date)
->addDays(10)
->format('Y-m-d');
if ($snap) {
$invoice->customer_name = $snap['customer_name'] ?? null;
$invoice->company_name = $snap['company_name'] ?? null;
$invoice->customer_mobile = $snap['mobile_no'] ?? null;
}
if ($customerUser) {
$invoice->customer_email = $customerUser->email ?? null;
$invoice->customer_address = $customerUser->address ?? null;
$invoice->pincode = $customerUser->pincode ?? null;
}
$invoice->final_amount = 0;
$invoice->gst_percent = 0;
$invoice->gst_amount = 0;
$invoice->final_amount_with_gst = 0;
$uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark'));
$invoice->notes = 'Auto-created from Container ' . $container->container_number
. ' for Mark(s): ' . implode(', ', $uniqueMarks);
$invoice->pdf_path = null;
$invoice->status = 'pending';
$invoice->save();
$invoiceCount++;
foreach ($rowsForCustomer as $item) {
$row = $item['row'];
$offset = $item['offset'];
$mark = $item['mark']; // ✅ mark_no from Excel
$description = $essentialColumns['desc_col'] !== null ? ($row[$essentialColumns['desc_col']] ?? null) : null;
$ctn = $essentialColumns['ctn_col'] !== null ? (int) ($row[$essentialColumns['ctn_col']] ?? 0) : 0;
$qty = $essentialColumns['qty_col'] !== null ? (int) ($row[$essentialColumns['qty_col']] ?? 0) : 0;
$ttlQty = $essentialColumns['totalqty_col'] !== null ? (int) ($row[$essentialColumns['totalqty_col']] ?? 0) : $qty;
$unit = $essentialColumns['unit_col'] !== null ? ($row[$essentialColumns['unit_col']] ?? null) : null;
$price = $essentialColumns['price_col'] !== null ? (float) ($row[$essentialColumns['price_col']] ?? 0) : 0;
$ttlAmount = $essentialColumns['amount_col'] !== null ? (float) ($row[$essentialColumns['amount_col']] ?? 0) : 0;
$cbm = $essentialColumns['cbm_col'] !== null ? (float) ($row[$essentialColumns['cbm_col']] ?? 0) : 0;
$ttlCbm = $essentialColumns['totalcbm_col'] !== null ? (float) ($row[$essentialColumns['totalcbm_col']] ?? $cbm) : $cbm;
$kg = $essentialColumns['kg_col'] !== null ? (float) ($row[$essentialColumns['kg_col']] ?? 0) : 0;
$ttlKg = $essentialColumns['totalkg_col'] !== null ? (float) ($row[$essentialColumns['totalkg_col']] ?? $kg) : $kg;
$shopNo = $essentialColumns['shopno_col'] !== null ? ($row[$essentialColumns['shopno_col']] ?? null) : null;
$rowIndex = $headerRowIndex + 1 + $offset;
InvoiceItem::create([
'invoice_id' => $invoice->id,
'container_id' => $container->id,
'container_row_index' => $rowIndex,
'description' => $description,
'ctn' => $ctn,
'qty' => $qty,
'ttl_qty' => $ttlQty,
'unit' => $unit,
'price' => $price,
'ttl_amount' => $ttlAmount,
'cbm' => $cbm,
'ttl_cbm' => $ttlCbm,
'kg' => $kg,
'ttl_kg' => $ttlKg,
'shop_no' => $shopNo,
'mark_no' => $mark, // ✅ save mark_no from Excel
]);
}
}
$msg = "Container '{$container->container_number}' created with {$savedCount} rows and {$invoiceCount} customer invoice(s).";
return redirect()->route('containers.index')->with('success', $msg);
}
public function show(Container $container)
{
$container->load('rows');
$lockedRowIndexes = \App\Models\Invoice::whereIn('invoices.status', ['paid', 'paying'])
->where('invoices.container_id', $container->id)
->join('invoice_items', 'invoices.id', '=', 'invoice_items.invoice_id')
->pluck('invoice_items.container_row_index')
->filter()
->unique()
->values()
->toArray();
return view('admin.container_show', compact('container', 'lockedRowIndexes'));
}
public function updateRows(Request $request, Container $container)
{
$rowsInput = $request->input('rows', []);
foreach ($rowsInput as $rowId => $cols) {
$row = ContainerRow::where('container_id', $container->id)
->where('id', $rowId)
->first();
if (!$row) {
continue;
}
$data = $row->data ?? [];
foreach ($cols as $colHeader => $value) {
$data[$colHeader] = $value;
}
$row->update(['data' => $data]);
$normalizedMap = [];
foreach ($data as $key => $value) {
if ($key === null || $key === '') {
continue;
}
$normKey = strtoupper((string)$key);
$normKey = str_replace([' ', '/', '-', '.'], '', $normKey);
$normalizedMap[$normKey] = $value;
}
$getFirstNumeric = function (array $map, array $possibleKeys) {
foreach ($possibleKeys as $search) {
$normSearch = strtoupper($search);
$normSearch = str_replace([' ', '/', '-', '.'], '', $normSearch);
foreach ($map as $nKey => $value) {
if (strpos($nKey, $normSearch) !== false) {
if (is_numeric($value)) {
return (float)$value;
}
if (is_string($value) && is_numeric(trim($value))) {
return (float)trim($value);
}
}
}
}
return 0;
};
$ctnKeys = ['CTN', 'CTNS'];
$qtyKeys = ['QTY', 'PCS', 'PIECES'];
$ttlQtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY'];
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
$amountKeys = ['AMOUNT', 'TTLAMOUNT', 'TOTALAMOUNT'];
$ctn = $getFirstNumeric($normalizedMap, $ctnKeys);
$qty = $getFirstNumeric($normalizedMap, $qtyKeys);
$ttlQ = $getFirstNumeric($normalizedMap, $ttlQtyKeys);
if ($ttlQ == 0 && $ctn && $qty) {
$ttlQ = $ctn * $qty;
}
$cbm = $getFirstNumeric($normalizedMap, ['CBM']);
$ttlC = $getFirstNumeric($normalizedMap, ['TOTALCBM', 'TTLCBM', 'ITLCBM']);
if ($ttlC == 0 && $cbm && $ctn) {
$ttlC = $cbm * $ctn;
}
$kg = $getFirstNumeric($normalizedMap, ['KG', 'WEIGHT']);
$ttlK = $getFirstNumeric($normalizedMap, ['TOTALKG', 'TTKG']);
if ($ttlK == 0 && $kg && $ctn) {
$ttlK = $kg * $ctn;
}
$price = $getFirstNumeric($normalizedMap, ['PRICE', 'RATE']);
$amount = $getFirstNumeric($normalizedMap, $amountKeys);
if ($amount == 0 && $price && $ttlQ) {
$amount = $price * $ttlQ;
}
$desc = null;
foreach (['DESCRIPTION', 'DESC'] as $dKey) {
$normD = str_replace([' ', '/', '-', '.'], '', strtoupper($dKey));
foreach ($normalizedMap as $nKey => $v) {
if (strpos($nKey, $normD) !== false) {
$desc = is_string($v) ? trim($v) : $v;
break 2;
}
}
}
$shopNo = null;
foreach (['SHOPNO', 'SHOP'] as $sKey) {
$normS = str_replace([' ', '/', '-', '.'], '', strtoupper($sKey));
foreach ($normalizedMap as $nKey => $v) {
if (strpos($nKey, $normS) !== false) {
$shopNo = is_string($v) ? trim($v) : $v;
break 2;
}
}
}
// ✅ Get mark_no
$markNo = null;
foreach (['MARKNO', 'MARK', 'ITEMNO', 'ITEM'] as $mKey) {
$normM = str_replace([' ', '/', '-', '.'], '', strtoupper($mKey));
foreach ($normalizedMap as $nKey => $v) {
if (strpos($nKey, $normM) !== false) {
$markNo = is_string($v) ? trim($v) : $v;
break 2;
}
}
}
$rowIndex = $row->row_index;
$items = InvoiceItem::where('container_id', $container->id)
->where('container_row_index', $rowIndex)
->get();
if ($items->isEmpty() && $desc) {
$items = InvoiceItem::where('container_id', $container->id)
->whereNull('container_row_index')
->where('description', $desc)
->get();
}
foreach ($items as $item) {
$item->description = $desc;
$item->ctn = $ctn;
$item->qty = $qty;
$item->ttl_qty = $ttlQ;
$item->price = $price;
$item->ttl_amount = $amount;
$item->cbm = $cbm;
$item->ttl_cbm = $ttlC;
$item->kg = $kg;
$item->ttl_kg = $ttlK;
$item->shop_no = $shopNo;
$item->mark_no = $markNo; // ✅ update mark_no
$item->save();
$invoice = $item->invoice;
if ($invoice) {
$newBaseAmount = InvoiceItem::where('invoice_id', $invoice->id)
->sum('ttl_amount');
$taxType = $invoice->tax_type;
$cgstPercent = (float) ($invoice->cgst_percent ?? 0);
$sgstPercent = (float) ($invoice->sgst_percent ?? 0);
$igstPercent = (float) ($invoice->igst_percent ?? 0);
$gstPercent = 0;
if ($taxType === 'gst') {
$gstPercent = $cgstPercent + $sgstPercent;
} elseif ($taxType === 'igst') {
$gstPercent = $igstPercent;
}
$gstAmount = $newBaseAmount * $gstPercent / 100;
$finalWithGst = $newBaseAmount + $gstAmount;
$invoice->final_amount = $newBaseAmount;
$invoice->gst_amount = $gstAmount;
$invoice->final_amount_with_gst = $finalWithGst;
$invoice->gst_percent = $gstPercent;
$invoice->save();
}
}
}
return redirect()
->route('containers.show', $container->id)
->with('success', 'Excel rows updated successfully.');
}
public function updateStatus(Request $request, Container $container)
{
$request->validate([
'status' => 'required|in:container-ready,export-custom,international-transit,arrived-at-india,import-custom,warehouse,domestic-distribution,out-for-delivery,delivered',
]);
$container->status = $request->status;
$container->save();
if ($request->wantsJson() || $request->ajax()) {
return response()->json([
'success' => true,
'status' => $container->status,
]);
}
return back()->with('success', 'Container status updated.');
}
public function destroy(Container $container)
{
$container->delete();
if (request()->wantsJson() || request()->ajax()) {
return response()->json([
'success' => true,
'message' => 'Container deleted',
]);
}
return redirect()
->route('containers.index')
->with('success', 'Container deleted.');
}
private function generateInvoiceNumber(): string
{
$year = now()->format('Y');
$last = Invoice::whereYear('created_at', $year)
->orderBy('id', 'desc')
->first();
if ($last) {
$parts = explode('-', $last->invoice_number);
$seq = 0;
if (count($parts) === 3) {
$seq = (int) $parts[2];
}
$nextSeq = $seq + 1;
} else {
$nextSeq = 1;
}
return 'INV-' . $year . '-' . str_pad($nextSeq, 6, '0', STR_PAD_LEFT);
}
public function downloadPdf(Container $container)
{
$container->load('rows');
$pdf = Pdf::loadView('admin.container_pdf', [
'container' => $container,
])->setPaper('a4', 'landscape');
$fileName = 'container-'.$container->container_number.'.pdf';
return $pdf->download($fileName);
}
public function downloadExcel(Container $container)
{
if (!$container->excel_file) {
abort(404, 'Excel file not found on record.');
}
$path = $container->excel_file;
if (!Storage::exists($path)) {
abort(404, 'Excel file missing on server.');
}
$fileName = 'container-'.$container->container_number.'.xlsx';
return Storage::download($path, $fileName);
}
public function popupPopup(Container $container)
{
$container->load('rows');
$rows = $container->rows ?? collect();
$totalCtn = 0;
$totalQty = 0;
$totalCbm = 0;
$totalKg = 0;
$ctnKeys = ['CTN', 'CTNS'];
$qtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY', 'QTY', 'PCS', 'PIECES'];
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
$getFirstNumeric = function (array $data, array $possibleKeys) {
$normalizedMap = [];
foreach ($data as $key => $value) {
if ($key === null) continue;
$normKey = strtoupper((string)$key);
$normKey = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normKey);
$normalizedMap[$normKey] = $value;
}
foreach ($possibleKeys as $search) {
$normSearch = strtoupper($search);
$normSearch = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normSearch);
foreach ($normalizedMap as $nKey => $value) {
if (strpos($nKey, $normSearch) !== false) {
if (is_numeric($value)) {
return (float)$value;
}
if (is_string($value) && is_numeric(trim($value))) {
return (float)trim($value);
}
}
}
}
return 0;
};
foreach ($rows as $row) {
$data = $row->data ?? [];
if (!is_array($data)) continue;
$totalCtn += $getFirstNumeric($data, $ctnKeys);
$totalQty += $getFirstNumeric($data, $qtyKeys);
$totalCbm += $getFirstNumeric($data, $cbmKeys);
$totalKg += $getFirstNumeric($data, $kgKeys);
}
$summary = [
'total_ctn' => round($totalCtn, 2),
'total_qty' => round($totalQty, 2),
'total_cbm' => round($totalCbm, 3),
'total_kg' => round($totalKg, 2),
];
return view('admin.partials.container_popup_readonly', [
'container' => $container,
'summary' => $summary,
]);
}
}

View File

@@ -21,33 +21,23 @@ class UserOrderController extends Controller
}
// -------------------------------------
// Get customer invoices with containers
// Get all orders
// -------------------------------------
$invoices = $user->invoices()->with('container')->get();
// Unique containers for this customer
$containers = $invoices->pluck('container')->filter()->unique('id');
$orders = $user->orders()->with('invoice')->get();
// -------------------------------------
// Counts based on container status
// Counts
// -------------------------------------
$totalOrders = $containers->count();
$delivered = $containers->where('status', 'delivered')->count();
$inTransit = $containers->whereNotIn('status', [
'delivered',
'warehouse',
'domestic-distribution'
])->count();
$active = $totalOrders;
$totalOrders = $orders->count();
$delivered = $orders->where('status', 'delivered')->count();
$inTransit = $orders->where('status', '!=', 'delivered')->count();
$active = $totalOrders;
// -------------------------------------
// Total Amount = sum of invoice totals
// Total Amount = Invoice.total_with_gst
// -------------------------------------
$totalAmount = $invoices->sum(function ($invoice) {
return $invoice->final_amount_with_gst ?? 0;
$totalAmount = $orders->sum(function ($o) {
return $o->invoice->final_amount_with_gst ?? 0;
});
// Format total amount in K, L, Cr
@@ -55,12 +45,13 @@ class UserOrderController extends Controller
return response()->json([
'status' => true,
'summary' => [
'active_orders' => $active,
'in_transit_orders' => $inTransit,
'delivered_orders' => $delivered,
'total_value' => $formattedAmount,
'total_raw' => $totalAmount
'total_value' => $formattedAmount, // formatted value
'total_raw' => $totalAmount // original value
]
]);
}
@@ -99,28 +90,20 @@ class UserOrderController extends Controller
], 401);
}
// Get invoices with containers for this customer
$invoices = $user->invoices()
->with('container')
// Fetch orders for this user
$orders = $user->orders()
->with(['invoice', 'shipments'])
->orderBy('id', 'desc')
->get();
// Extract unique containers
$containers = $invoices->pluck('container')
->filter()
->unique('id')
->values();
$orders = $containers->map(function ($container) {
return [
'order_id' => $container->id,
'container_number' => $container->container_number,
'status' => $container->status,
'container_date' => $container->container_date,
'created_at' => $container->created_at,
];
});
->get()
->map(function ($o) {
return [
'order_id' => $o->order_id,
'status' => $o->status,
'amount' => $o->ttl_amount,
'description'=> "Order from {$o->origin} to {$o->destination}",
'created_at' => $o->created_at,
];
});
return response()->json([
'success' => true,
@@ -132,73 +115,45 @@ public function orderDetails($order_id)
{
$user = JWTAuth::parseToken()->authenticate();
if (!$user) {
return response()->json([
'success' => false,
'message' => 'Unauthorized'
], 401);
}
// Find container first
$container = \App\Models\Container::find($order_id);
if (!$container) {
return response()->json([
'success' => false,
'message' => 'Container not found'
], 404);
}
// Find invoice belonging to this user for this container
$invoice = \App\Models\Invoice::where('customer_id', $user->id)
->where('container_id', $container->id)
$order = $user->orders()
->with(['items'])
->where('order_id', $order_id)
->first();
if (!$invoice) {
return response()->json([
'success' => false,
'message' => 'Order not found for this user'
], 404);
if (!$order) {
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
}
return response()->json([
'success' => true,
'order' => [
'container_id' => $container->id,
'container_number' => $container->container_number,
'container_date' => $container->container_date,
'status' => $container->status,
'invoice_id' => $invoice->id,
'items' => $invoice->items
]
'order' => $order
]);
}
// public function orderShipment($order_id)
// {
// $user = JWTAuth::parseToken()->authenticate();
public function orderShipment($order_id)
{
$user = JWTAuth::parseToken()->authenticate();
// // Get order
// $order = $user->orders()->where('order_id', $order_id)->first();
// Get order
$order = $user->orders()->where('order_id', $order_id)->first();
// if (!$order) {
// return response()->json(['success' => false, 'message' => 'Order not found'], 404);
// }
if (!$order) {
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
}
// // Find shipment only for this order
// $shipment = $order->shipments()
// ->with(['items' => function ($q) use ($order) {
// $q->where('order_id', $order->id);
// }])
// ->first();
// Find shipment only for this order
$shipment = $order->shipments()
->with(['items' => function ($q) use ($order) {
$q->where('order_id', $order->id);
}])
->first();
// return response()->json([
// 'success' => true,
// 'shipment' => $shipment
// ]);
// }
return response()->json([
'success' => true,
'shipment' => $shipment
]);
}
public function orderInvoice($order_id)
@@ -224,35 +179,23 @@ public function trackOrder($order_id)
{
$user = JWTAuth::parseToken()->authenticate();
if (!$user) {
return response()->json([
'success' => false,
'message' => 'Unauthorized'
], 401);
}
// Ensure the container belongs to this customer via invoice
$invoice = \App\Models\Invoice::where('customer_id', $user->id)
->where('container_id', $order_id)
->with('container')
$order = $user->orders()
->with('shipments')
->where('order_id', $order_id)
->first();
if (!$invoice || !$invoice->container) {
return response()->json([
'success' => false,
'message' => 'Order not found'
], 404);
if (!$order) {
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
}
$container = $invoice->container;
$shipment = $order->shipments()->first();
return response()->json([
'success' => true,
'track' => [
'order_id' => $container->id,
'container_number' => $container->container_number,
'status' => $container->status,
'container_date' => $container->container_date,
'order_id' => $order->order_id,
'shipment_status' => $shipment->status ?? 'pending',
'shipment_date' => $shipment->shipment_date ?? null,
]
]);
}
@@ -346,44 +289,44 @@ public function invoiceDetails($invoice_id)
]);
}
// public function confirmOrder($order_id)
// {
// $user = JWTAuth::parseToken()->authenticate();
public function confirmOrder($order_id)
{
$user = JWTAuth::parseToken()->authenticate();
// if (! $user) {
// return response()->json([
// 'success' => false,
// 'message' => 'Unauthorized'
// ], 401);
// }
if (! $user) {
return response()->json([
'success' => false,
'message' => 'Unauthorized'
], 401);
}
// $order = $user->orders()
// ->where('order_id', $order_id)
// ->first();
$order = $user->orders()
->where('order_id', $order_id)
->first();
// if (! $order) {
// return response()->json([
// 'success' => false,
// 'message' => 'Order not found'
// ], 404);
// }
if (! $order) {
return response()->json([
'success' => false,
'message' => 'Order not found'
], 404);
}
// // 🚫 Only allow confirm from order_placed
// if ($order->status !== 'order_placed') {
// return response()->json([
// 'success' => false,
// 'message' => 'Order cannot be confirmed'
// ], 422);
// }
// 🚫 Only allow confirm from order_placed
if ($order->status !== 'order_placed') {
return response()->json([
'success' => false,
'message' => 'Order cannot be confirmed'
], 422);
}
// $order->status = 'order_confirmed';
// $order->save();
$order->status = 'order_confirmed';
$order->save();
// return response()->json([
// 'success' => true,
// 'message' => 'Order confirmed successfully'
// ]);
// }
return response()->json([
'success' => true,
'message' => 'Order confirmed successfully'
]);
}

View File

@@ -20,6 +20,8 @@ class ChatMessage extends Model
'read_by_user',
'client_id',
];
/**
* The ticket this message belongs to.
*/

View File

@@ -1,30 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Container extends Model
{
protected $fillable = [
'container_name',
'container_number',
'container_date',
'status',
'excel_file',
];
protected $casts = [
'container_date' => 'date',
];
public function rows()
{
return $this->hasMany(ContainerRow::class);
}
public function invoices()
{
return $this->hasMany(Invoice::class);
}
}

View File

@@ -1,23 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ContainerRow extends Model
{
protected $fillable = [
'container_id',
'row_index',
'data',
];
protected $casts = [
'data' => 'array',
];
public function container()
{
return $this->belongsTo(Container::class);
}
}

View File

@@ -9,36 +9,41 @@ class Invoice extends Model
{
use HasFactory;
protected $fillable = [
'container_id',
'customer_id',
'mark_no',
'invoice_number',
'invoice_date',
'due_date',
'payment_method',
'reference_no',
'status',
'final_amount',
'gst_percent',
'gst_amount',
'final_amount_with_gst',
'customer_name',
'company_name',
'customer_email',
'customer_mobile',
'customer_address',
'pincode',
'pdf_path',
'notes',
// totals from charge groups
'charge_groups_total',
'grand_total_with_charges',
'tax_type',
'cgst_percent',
'sgst_percent',
'igst_percent',
];
protected $fillable = [
'order_id',
'customer_id',
'mark_no',
'invoice_number',
'invoice_date',
'due_date',
'payment_method',
'reference_no',
'status',
'final_amount', // without tax
'tax_type', // gst / igst
'gst_percent', // only used for gst UI input
'cgst_percent',
'sgst_percent',
'igst_percent',
'gst_amount', // total tax amount
'final_amount_with_gst',
'customer_name',
'company_name',
'customer_email',
'customer_mobile',
'customer_address',
'pincode',
'pdf_path',
'notes',
];
/****************************
* Relationships
@@ -49,38 +54,29 @@ class Invoice extends Model
return $this->hasMany(InvoiceItem::class)->orderBy('id', 'ASC');
}
// public function container()
// {
// return $this->belongsTo(Container::class);
// }
public function order()
{
return $this->belongsTo(Order::class);
}
public function customer()
{
return $this->belongsTo(User::class, 'customer_id');
}
public function installments()
{
return $this->hasMany(InvoiceInstallment::class);
}
public function chargeGroups()
{
return $this->hasMany(InvoiceChargeGroup::class, 'invoice_id');
}
/****************************
* Helper Functions
****************************/
// (Items based calculateTotals वापरणार नाहीस तरी ठेवू शकतोस)
// Auto calculate GST fields (you can call this in controller before saving)
public function calculateTotals()
{
$gst = ($this->final_amount * $this->gst_percent) / 100;
$this->gst_amount = $gst;
$this->gst_amount = $gst;
$this->final_amount_with_gst = $this->final_amount + $gst;
}
// Check overdue status condition
public function isOverdue()
{
return $this->status === 'pending' && now()->gt($this->due_date);
@@ -88,46 +84,28 @@ class Invoice extends Model
public function getShipment()
{
return null;
return $this->order?->shipments?->first();
}
// ✅ Charge groups base total (WITHOUT GST)
public function getChargeGroupsTotalAttribute()
{
// base = total_charge sum
return (float) $this->chargeGroups->sum('total_charge');
}
// ✅ Grand total: Charge groups base + GST (items ignore)
public function getGrandTotalWithChargesAttribute()
{
$base = (float) ($this->charge_groups_total ?? 0);
$gst = (float) ($this->gst_amount ?? 0);
return $base + $gst;
}
public function totalPaid(): float
{
return (float) $this->installments()->sum('amount');
}
public function remainingAmount(): float
{
$grand = (float) $this->grand_total_with_charges;
$paid = (float) $this->totalPaid();
return max(0, $grand - $paid);
}
public function isLockedForEdit(): bool
{
return $this->status === 'paid';
}
public function container()
public function installments()
{
return $this->belongsTo(\App\Models\Container::class, 'container_id');
return $this->hasMany(InvoiceInstallment::class);
}
// App\Models\Invoice.php
public function totalPaid()
{
return $this->installments()->sum('amount');
}
public function remainingAmount()
{
return max(
($this->final_amount_with_gst ?? 0) - $this->totalPaid(),
0
);
}
}

View File

@@ -1,31 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class InvoiceChargeGroup extends Model
{
protected $fillable = [
'invoice_id',
'group_name',
'basis_type',
'basis_value',
'rate',
'total_charge',
'tax_type',
'gst_percent',
'total_with_gst',
];
public function invoice()
{
return $this->belongsTo(Invoice::class);
}
public function items()
{
return $this->hasMany(InvoiceChargeGroupItem::class, 'group_id');
}
}

View File

@@ -1,23 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class InvoiceChargeGroupItem extends Model
{
protected $fillable = [
'group_id',
'invoice_item_id',
];
public function group()
{
return $this->belongsTo(InvoiceChargeGroup::class, 'group_id');
}
public function item()
{
return $this->belongsTo(InvoiceItem::class, 'invoice_item_id');
}
}

View File

@@ -11,8 +11,6 @@ class InvoiceItem extends Model
protected $fillable = [
'invoice_id',
'container_id', // Container mapping
'container_row_index', // Container row index
'description',
'ctn',
@@ -29,7 +27,6 @@ class InvoiceItem extends Model
'ttl_kg',
'shop_no',
'mark_no',
];
/****************************
@@ -40,79 +37,4 @@ class InvoiceItem extends Model
{
return $this->belongsTo(Invoice::class);
}
public function chargeGroupItems()
{
return $this->hasMany(InvoiceChargeGroupItem::class, 'invoice_item_id');
}
// हे helper: पहिला group fetch करून त्यावरून rate/total काढणे
public function getChargeRateAttribute()
{
$pivot = $this->chargeGroupItems->first();
if (!$pivot || !$pivot->group) {
return 0;
}
$group = $pivot->group;
// basis नुसार या item चा basis value
$basis = 0;
switch ($group->basis_type) {
case 'ttl_qty':
$basis = $this->ttl_qty;
break;
case 'amount':
$basis = $this->ttl_amount;
break;
case 'ttl_cbm':
$basis = $this->ttl_cbm;
break;
case 'ttl_kg':
$basis = $this->ttl_kg;
break;
}
if ($basis <= 0 || ($group->basis_value ?? 0) <= 0) {
return 0;
}
// group चा rate field आधीच आहे, ते direct वापरू
return (float) $group->rate;
}
public function getChargeTotalAttribute()
{
$pivot = $this->chargeGroupItems->first();
if (!$pivot || !$pivot->group) {
return 0;
}
$group = $pivot->group;
$basis = 0;
switch ($group->basis_type) {
case 'ttl_qty':
$basis = $this->ttl_qty;
break;
case 'amount':
$basis = $this->ttl_amount;
break;
case 'ttl_cbm':
$basis = $this->ttl_cbm;
break;
case 'ttl_kg':
$basis = $this->ttl_kg;
break;
}
if ($basis <= 0 || ($group->basis_value ?? 0) <= 0) {
return 0;
}
// per unit rate
$rate = (float) $group->rate;
// item total = basis * rate
return $basis * $rate;
}
}

View File

@@ -58,10 +58,10 @@ class Order extends Model
return $this->belongsToMany(\App\Models\Shipment::class, 'shipment_items', 'order_id', 'shipment_id');
}
// public function invoice()
// {
// return $this->hasOne(\App\Models\Invoice::class, 'order_id', 'id');
// }
public function invoice()
{
return $this->hasOne(\App\Models\Invoice::class, 'order_id', 'id');
}
const STATUS_LABELS = [

View File

@@ -89,8 +89,10 @@ class User extends Authenticatable implements JWTSubject
{
return [];
}
public function invoices()
{
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'id');
}
// App\Models\User.php
@@ -105,10 +107,6 @@ public function invoiceInstallments()
);
}
public function invoices()
{
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'id');
}
}

View File

@@ -33,9 +33,6 @@ class CreateInvoiceItemsTable extends Migration
$table->string('shop_no')->nullable();
$table->timestamps();
$table->unsignedBigInteger('container_id')->nullable()->after('invoice_id');
$table->integer('container_row_index')->nullable()->after('container_id');
// FK
$table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
@@ -52,6 +49,4 @@ class CreateInvoiceItemsTable extends Migration
});
Schema::dropIfExists('invoice_items');
}
}

View File

@@ -1,25 +0,0 @@
<?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('containers', function (Blueprint $table) {
$table->id();
$table->string('container_name');
$table->string('container_number')->unique();
$table->date('container_date');
$table->string('excel_file')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('containers');
}
};

View File

@@ -1,37 +0,0 @@
<?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('loading_list_items', function (Blueprint $table) {
$table->id();
$table->foreignId('container_id')
->constrained('containers')
->onDelete('cascade');
$table->string('mark')->nullable(); // MARK / ITEM NO
$table->string('description')->nullable();
$table->integer('ctn')->nullable();
$table->integer('qty')->nullable();
$table->integer('total_qty')->nullable();
$table->string('unit')->nullable();
$table->decimal('price', 15, 3)->nullable(); // SAHIL format साठी
$table->decimal('cbm', 15, 5)->nullable();
$table->decimal('total_cbm', 15, 5)->nullable();
$table->decimal('kg', 15, 3)->nullable();
$table->decimal('total_kg', 15, 3)->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('loading_list_items');
}
};

View File

@@ -1,31 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('container_rows', function (Blueprint $table) {
$table->id();
$table->foreignId('container_id')
->constrained('containers')
->onDelete('cascade');
// Excel मधल्या row क्रमांकासाठी (optional)
$table->unsignedInteger('row_index')->nullable();
// या row चा full data: "heading text" => "cell value"
$table->json('data');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('container_rows');
}
};

View File

@@ -1,24 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('containers', function (Blueprint $table) {
$table->string('status', 21)
->default('pending')
->after('container_date');
});
}
public function down(): void
{
Schema::table('containers', function (Blueprint $table) {
$table->dropColumn('status');
});
}
};

View File

@@ -1,43 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('invoices', function (Blueprint $table) {
// 1) order_id foreign key काढा
$table->dropForeign(['order_id']);
// 2) order_id column काढा
$table->dropColumn('order_id');
// 3) container_id add करा
$table->unsignedBigInteger('container_id')->nullable()->after('id');
// 4) container_id FK
$table->foreign('container_id')
->references('id')
->on('containers')
->onDelete('cascade');
});
}
public function down(): void
{
Schema::table('invoices', function (Blueprint $table) {
// rollback: container_id काढून order_id परत add
$table->dropForeign(['container_id']);
$table->dropColumn('container_id');
$table->unsignedBigInteger('order_id')->index();
$table->foreign('order_id')
->references('id')
->on('orders')
->onDelete('cascade');
});
}
};

View File

@@ -1,37 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('invoice_charge_groups', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('invoice_id');
$table->string('group_name')->nullable(); // उदा. "FREIGHT", "HANDLING"
$table->enum('basis_type', ['ttl_qty', 'amount', 'ttl_cbm', 'ttl_kg']);
$table->decimal('basis_value', 15, 3)->default(0); // auto calculate केलेला total basis
$table->decimal('rate', 15, 3)->default(0); // per basis rate (helper)
$table->decimal('total_charge', 15, 2); // admin नी manually टाकलेला total
$table->timestamps();
$table->foreign('invoice_id')
->references('id')->on('invoices')
->onDelete('cascade');
});
}
public function down(): void
{
Schema::dropIfExists('invoice_charge_groups');
}
};

View File

@@ -1,35 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('invoice_charge_group_items', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('group_id');
$table->unsignedBigInteger('invoice_item_id');
$table->timestamps();
$table->foreign('group_id')
->references('id')->on('invoice_charge_groups')
->onDelete('cascade');
$table->foreign('invoice_item_id')
->references('id')->on('invoice_items')
->onDelete('cascade');
});
}
public function down(): void
{
Schema::dropIfExists('invoice_charge_group_items');
}
};

View File

@@ -1,27 +0,0 @@
<?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()
{
Schema::table('invoice_items', function (Blueprint $table) {
$table->unsignedBigInteger('container_id')->nullable()->after('invoice_id');
$table->integer('container_row_index')->nullable()->after('container_id');
});
}
public function down()
{
Schema::table('invoice_items', function (Blueprint $table) {
$table->dropColumn(['container_id', 'container_row_index']);
});
}
};

View File

@@ -1,29 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('invoices', function (Blueprint $table) {
// column आधीच आहे का हे check करून, नसेल तरच add करायचा
if (!Schema::hasColumn('invoices', 'due_date')) {
$table->date('due_date')
->nullable()
->after('invoice_date');
}
});
}
public function down(): void
{
Schema::table('invoices', function (Blueprint $table) {
if (Schema::hasColumn('invoices', 'due_date')) {
$table->dropColumn('due_date');
}
});
}
};

View File

@@ -1,26 +0,0 @@
<?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('containers', function (Blueprint $table) {
$table->string('status', 50)->change();
});
}
public function down(): void
{
Schema::table('containers', function (Blueprint $table) {
$table->string('status', 20)->change();
});
}
};

View File

@@ -1,23 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('invoice_charge_groups', function (Blueprint $table) {
$table->string('tax_type')->nullable()->after('total_charge');
$table->decimal('gst_percent', 5, 2)->default(0)->after('tax_type');
$table->decimal('total_with_gst', 15, 2)->default(0)->after('gst_percent');
});
}
public function down(): void
{
Schema::table('invoice_charge_groups', function (Blueprint $table) {
$table->dropColumn(['tax_type', 'gst_percent', 'total_with_gst']);
});
}
};

View File

@@ -1,37 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddChargeColumnsToInvoicesTable extends Migration
{
public function up()
{
Schema::table('invoices', function (Blueprint $table) {
if (!Schema::hasColumn('invoices', 'charge_groups_total')) {
$table->decimal('charge_groups_total', 15, 2)
->nullable()
->after('final_amount_with_gst');
}
if (!Schema::hasColumn('invoices', 'grand_total_with_charges')) {
$table->decimal('grand_total_with_charges', 15, 2)
->nullable()
->after('charge_groups_total');
}
});
}
public function down()
{
Schema::table('invoices', function (Blueprint $table) {
if (Schema::hasColumn('invoices', 'charge_groups_total')) {
$table->dropColumn('charge_groups_total');
}
if (Schema::hasColumn('invoices', 'grand_total_with_charges')) {
$table->dropColumn('grand_total_with_charges');
}
});
}
}

View File

@@ -1,23 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration {
public function up(): void
{
DB::statement("
ALTER TABLE `invoices`
MODIFY `status` ENUM('pending','paying','paid','overdue')
NOT NULL DEFAULT 'pending'
");
}
public function down(): void
{
DB::statement("
ALTER TABLE `invoices`
MODIFY `status` ENUM('pending','paid','overdue')
NOT NULL DEFAULT 'pending'
");
}
};

View File

@@ -1,27 +0,0 @@
<?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()
{
Schema::table('invoice_items', function (Blueprint $table) {
$table->string('mark_no')->nullable(); // after() काहीही नको
});
}
public function down()
{
Schema::table('invoice_items', function (Blueprint $table) {
$table->dropColumn('mark_no');
});
}
};

View File

@@ -25,12 +25,6 @@ class PermissionSeeder extends Seeder
// EXTRA (ORDERS)
'orders.view', // you added this separately
// CONTAINER
'container.view',
'container.create',
'container.update',
'container.delete',
// SHIPMENT
'shipment.view',
'shipment.create',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -1,102 +0,0 @@
<div class="container-fluid py-2">
{{-- Top info cards (container / date / status) --}}
<div class="row mb-3">
<div class="col-md-3 mb-2">
<div class="card p-2">
<small class="text-muted">Container Name</small>
<div class="fw-semibold">{{ $container->container_name ?? '-' }}</div>
</div>
</div>
<div class="col-md-3 mb-2">
<div class="card p-2">
<small class="text-muted">Container No</small>
<div class="fw-semibold">{{ $container->container_number ?? '-' }}</div>
</div>
</div>
<div class="col-md-3 mb-2">
<div class="card p-2">
<small class="text-muted">Container Date</small>
<div class="fw-semibold">
{{ $container->container_date ? \Carbon\Carbon::parse($container->container_date)->format('d-m-Y') : '-' }}
</div>
</div>
</div>
<div class="col-md-3 mb-2">
<div class="card p-2">
<small class="text-muted">Status</small>
<div class="fw-semibold text-capitalize">{{ $container->status ?? '-' }}</div>
</div>
</div>
</div>
{{-- Totals (CTN / Qty / CBM / KG) --}}
<div class="row mb-3">
<div class="col-md-3 mb-2">
<div class="card p-2">
<small class="text-muted">Total CTN</small>
<div class="fw-semibold">{{ $summary['total_ctn'] ?? '-' }}</div>
</div>
</div>
<div class="col-md-3 mb-2">
<div class="card p-2">
<small class="text-muted">Total Qty</small>
<div class="fw-semibold">{{ $summary['total_qty'] ?? '-' }}</div>
</div>
</div>
<div class="col-md-3 mb-2">
<div class="card p-2">
<small class="text-muted">Total CBM</small>
<div class="fw-semibold">{{ $summary['total_cbm'] ?? '-' }}</div>
</div>
</div>
<div class="col-md-3 mb-2">
<div class="card p-2">
<small class="text-muted">Total KG</small>
<div class="fw-semibold">{{ $summary['total_kg'] ?? '-' }}</div>
</div>
</div>
</div>
{{-- Excel rows same headings as container_show --}}
@php
$allHeadings = [];
foreach ($container->rows as $row) {
if (is_array($row->data)) {
$allHeadings = array_unique(array_merge($allHeadings, array_keys($row->data)));
}
}
@endphp
<div class="table-responsive" style="max-height: 500px; border-radius: 8px;">
<table class="table table-sm table-bordered align-middle">
<thead class="table-warning">
<tr>
<th style="width: 40px;">#</th>
@foreach($allHeadings as $heading)
<th>{{ $heading }}</th>
@endforeach
</tr>
</thead>
<tbody>
@forelse($container->rows as $index => $row)
<tr>
<td>{{ $index + 1 }}</td>
@foreach($allHeadings as $heading)
@php
$val = is_array($row->data) ? ($row->data[$heading] ?? '') : '';
@endphp
<td>{{ $val }}</td>
@endforeach
</tr>
@empty
<tr>
<td colspan="{{ count($allHeadings) + 1 }}" class="text-center text-muted py-3">
No Excel rows for this container.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>

View File

@@ -6,7 +6,6 @@
<meta name="csrf-token" content="{{ csrf_token() }}">
<style>
/* ---------- Base ---------- */
:root{
--primary-1:#1a2951;
--primary-2:#243a72;
@@ -38,7 +37,7 @@ body {
/* header */
.account-header {
margin-bottom: 18px;
background:linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(90deg,var(--primary-1),var(--primary-2));
padding: 22px 26px;
border-radius: var(--rounded);
box-shadow: 0 6px 18px rgba(34,50,90,0.12);
@@ -63,7 +62,7 @@ body {
.btn {
display:inline-flex; align-items:center; justify-content:center; gap:8px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(90deg,var(--primary-1),var(--primary-2));
color:#fff; border:none; padding:10px 16px; border-radius:10px; font-weight:600;
cursor:pointer; transition: transform .15s ease, box-shadow .15s;
}
@@ -335,7 +334,7 @@ tr:hover td{ background:#fbfdff; }
display: flex;
align-items: center;
gap: 8px;
margin-right:-550px;
/* margin-right:-550px; */
}
@@ -343,7 +342,7 @@ tr:hover td{ background:#fbfdff; }
display: flex;
align-items: center;
gap: 8px;
margin-right:8=500px;
margin-right:-550px;
}
@@ -459,7 +458,7 @@ tr:hover td{ background:#fbfdff; }
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
}
/* ---------- Entry Details Modal (Enhanced) ---------- */
/* ---------- Entry Details Modal (existing) ---------- */
.modal-fade1 {
position:fixed;
inset:0;
@@ -472,18 +471,15 @@ tr:hover td{ background:#fbfdff; }
}
.modal-fade1.modal-open { display:flex; }
.modal-box1 {
background:#fff;
border-radius:12px;
padding:20px 24px;
box-shadow:0 14px 40px rgba(18,30,60,0.12);
max-width:1200px;
width:100%;
max-height:92vh;
overflow:auto;
min-height: 500px;
}
#entryOrdersModal {
z-index: 1300;
background:#fff;
border-radius:12px;
padding:20px 24px;
box-shadow:0 14px 40px rgba(18,30,60,0.12);
max-width:1200px;
width:100%;
max-height:92vh;
overflow:auto;
min-height: 500px;
}
#entryOrdersModal {
z-index: 1300;
@@ -493,7 +489,7 @@ tr:hover td{ background:#fbfdff; }
.entry-summary-cards {
display:flex;
gap:16px;
margin-top:25px;
margin-bottom:20px;
flex-wrap:wrap;
}
.entry-summary-card {
@@ -508,184 +504,6 @@ tr:hover td{ background:#fbfdff; }
.entry-summary-label{ font-size:12px; color:var(--muted); }
.entry-summary-value{ font-size:18px; font-weight:700; color:#253047; margin-top:6px; }
/* Enhanced dropdown */
.installment-status-dropdown {
cursor: pointer;
appearance: none;
width: 100%;
padding: 10px 40px 10px 12px;
border-radius: 8px;
border: 1.5px solid #e3eaf6;
background: white;
font-size: 13px;
font-weight: 500;
transition: all 0.2s ease;
background-image: url('data:image/svg+xml;charset=US-ASCII,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="%236b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>');
background-repeat: no-repeat;
background-position: right 12px center;
min-width: 140px;
}
.installment-status-dropdown:hover {
border-color: #c2d1f0;
background-color: #f8fafc;
}
.installment-status-dropdown:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(39, 109, 234, 0.1);
}
/* Status-specific dropdown options */
.installment-status-dropdown option {
padding: 12px !important;
font-size: 13px;
font-weight: 500;
}
.installment-status-dropdown option[value="Pending"] {
color: #f59e0b;
}
.installment-status-dropdown option[value="Loading"] {
color: #3b82f6;
}
.installment-status-dropdown option[value="Packed"] {
color: #8b5cf6;
}
.installment-status-dropdown option[value="Dispatched"] {
color: #10b981;
}
.installment-status-dropdown option[value="Delivered"] {
color: #0c6b2e;
}
/* ---------- Entry Orders Modal (Enhanced) ---------- */
.entry-orders-modal .modal-box1 {
padding: 0 !important;
overflow: hidden;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(18, 30, 60, 0.25);
}
.entry-orders-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 24px 30px;
color: white;
border-radius: 16px 16px 0 0;
}
.entry-orders-header h2 {
margin: 0 0 8px 0;
font-size: 24px;
font-weight: 700;
color: white;
}
.entry-orders-header .subtitle {
font-size: 14px;
opacity: 0.9;
display: flex;
align-items: center;
gap: 8px;
}
.entry-orders-content {
padding: 30px;
max-height: 70vh;
overflow-y: auto;
}
/* Orders table in modal */
.orders-table-container {
max-height: 400px;
overflow-y: auto;
border: 1px solid #eef3fb;
border-radius: 12px;
margin: 20px 0;
}
.orders-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.orders-table th {
background: linear-gradient(90deg, #f8fbff, #f5f9ff);
padding: 14px 16px;
text-align: left;
font-weight: 700;
color: var(--primary-1);
border-bottom: 2px solid #eef3fb;
position: sticky;
top: 0;
}
.orders-table td {
padding: 14px 16px;
border-bottom: 1px solid #f1f6ff;
}
.orders-table tr:hover {
background: #fbfdff;
}
.orders-table tr:last-child td {
border-bottom: none;
}
/* Order ID with badge style */
.order-id-badge {
display: inline-block;
background: linear-gradient(90deg, #f0f7ff, #e6f0ff);
color: var(--primary-1);
padding: 4px 10px;
border-radius: 6px;
font-weight: 600;
font-size: 12px;
border: 1px solid #dbe4f5;
}
/* Summary row */
.orders-summary-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
background: linear-gradient(90deg, #e6ebf5 0%, #f9fbff 100%);
border-radius: 10px;
margin-top: 20px;
border: 1px solid #eef3fb;
}
.orders-summary-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.orders-summary-value {
font-size: 20px;
font-weight: 800;
color: var(--primary-1);
}
.orders-summary-label {
font-size: 12px;
color: var(--muted);
margin-top: 4px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* installment modal */
#installmentModal .modal-box1 { max-width:720px; min-width:380px; }
@@ -1010,10 +828,7 @@ tr:hover td{ background:#fbfdff; }
flex-wrap: wrap;
margin-bottom: 16px;
padding: 12px 14px;
background:linear-gradient(90deg, #e6ebf5 0%, #f9fbff 100%);
background: linear-gradient(90deg, #f9fbff, #f7faff);
border-radius: 8px;
border: 1px solid #eef3fb;
}
@@ -1295,40 +1110,6 @@ tr:hover td{ background:#fbfdff; }
.pagination-container {
margin-right: 0 !important;
}
/* Responsive modals */
.entry-details-modal .modal-box1,
.entry-orders-modal .modal-box1 {
margin: 10px;
border-radius: 12px;
}
.entry-details-header,
.entry-orders-header {
padding: 18px 20px;
}
.entry-details-content,
.entry-orders-content {
padding: 20px;
}
.entry-summary-cards {
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 12px;
}
.entry-summary-value {
font-size: 20px;
}
.orders-summary-row {
flex-direction: column;
gap: 15px;
text-align: center;
}
}
/* Zoom out specific fix */
@@ -1365,10 +1146,6 @@ tr:hover td{ background:#fbfdff; }
.pagination-info {
font-size: 12px;
}
.entry-summary-cards {
grid-template-columns: 1fr;
}
}
/* Prevent horizontal scroll on very small screens */
@@ -1401,7 +1178,6 @@ html, body {
</style>
<div class="account-container">
@@ -1547,7 +1323,7 @@ html, body {
<!-- CREATE ORDER POPUP MODAL -->
<div class="create-order-modal" id="createOrderModal">
<div class="modal-box">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:16px; background:linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding:20px; border-radius:12px; color:white; margin-top:-20px; margin-left:-20px; margin-right:-15px; margin-top:-15px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:16px;">
<div style="font-size:20px; font-weight:800;">Create New Installment</div>
<button class="btn ghost" id="closeCreateModal" title="Close create form"></button>
</div>
@@ -1639,159 +1415,111 @@ html, body {
</div>
</div>
<!-- ENTRY DETAILS MODAL (Enhanced) -->
<div class="modal-fade1 entry-details-modal" id="entryDetailsModal">
<div class="modal-box1">
<div class="entry-details-header" >
<h2 >Entry Details</h2>
<div class="subtitle">
<span id="entryDetailsId">Loading...</span>
<span> Complete view of all installments for this entry</span>
<!-- ENTRY DETAILS MODAL -->
<div class="modal-fade1" id="entryDetailsModal">
<div class="modal-box1 entry-details-modal">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<div>
<h2 style="margin:0;font-size:20px;color:#223256;font-weight:800">Entry Details <span id="entryDetailsId">-</span></h2>
<div style="font-size:13px;color:var(--muted)">Complete view of all installments for this entry.</div>
</div>
<div><button class="btn ghost" onclick="closeEntryDetailsModal()">Close</button></div>
</div>
<div class="entry-details-content">
<div class="entry-summary-cards">
<div class="entry-summary-card">
<div class="entry-summary-label">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>
</svg>
Original Amount
</div>
<div class="entry-summary-value" id="originalAmount">-</div>
</div>
<div class="entry-summary-card">
<div class="entry-summary-label">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
</svg>
Total Processed
</div>
<div class="entry-summary-value" id="totalProcessed">-</div>
</div>
<div class="entry-summary-card">
<div class="entry-summary-label">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>
</svg>
Pending Balance
</div>
<div class="entry-summary-value" id="pendingBalance">-</div>
</div>
<div class="entry-summary-card">
<div class="entry-summary-label">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
</svg>
Total Installments
</div>
<div class="entry-summary-value" id="totalInstallments">-</div>
</div>
</div>
<div style="margin-top: 30px;">
<h3 style="font-size: 18px; font-weight: 700; color: var(--primary-1); margin-bottom: 16px;">
Installment History
</h3>
<div class="orders-table-container">
<table class="entry-installments-table">
<thead>
<tr>
<th>Installment</th>
<th>Date</th>
<th>Description</th>
<th>Region</th>
<th>Amount</th>
<th>Status</th>
</tr>
</thead>
<tbody id="installmentsTableBody">
<tr><td colspan="6" class="empty-state">No installments yet</td></tr>
</tbody>
</table>
</div>
<div class="entry-summary-cards">
<div class="entry-summary-card">
<div class="entry-summary-label">Original Amount</div>
<div class="entry-summary-value" id="originalAmount">-</div>
</div>
<div class="entry-summary-card">
<div class="entry-summary-label">Total Processed</div>
<div class="entry-summary-value" id="totalProcessed">-</div>
</div>
<div class="entry-summary-card">
<div class="entry-summary-label">Pending Balance</div>
<div class="entry-summary-value" id="pendingBalance">-</div>
</div>
<div class="entry-summary-card">
<div class="entry-summary-label">Total Installments</div>
<div class="entry-summary-value" id="totalInstallments">-</div>
</div>
</div>
<div style="padding: 20px 30px; border-top: 1px solid #eef3fb; display: flex; justify-content: space-between; align-items: center;">
<button type="button" class="btn ghost" onclick="closeEntryDetailsModal()">
Close
</button>
<table class="entry-installments-table" style="width:100%; border-collapse:collapse;">
<thead>
<tr>
<th>Installment</th>
<th>Date</th>
<th>Description</th>
<th>Region</th>
<th>Amount</th>
<th>Status</th>
</tr>
</thead>
<tbody id="installmentsTableBody">
<tr><td colspan="6" class="empty-state">No installments yet</td></tr>
</tbody>
</table>
<div style="display:flex; justify-content: flex-end; gap:12px; margin-top:16px;">
<button type="button" class="btn ghost" onclick="closeEntryDetailsModal()">Close</button>
@can('account.add_installment')
<button type="button" class="btn" id="addInstallmentFromDetails">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 6px;">
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
</svg>
Add New Installment
</button>
<button type="button" class="btn" id="addInstallmentFromDetails">+ Add New Installment</button>
@endcan
</div>
</div>
</div>
<!-- ENTRY ORDERS MODAL (Enhanced) -->
<div class="modal-fade1 entry-orders-modal" id="entryOrdersModal">
<div class="modal-box1">
<div class="entry-orders-header">
<h2>Entry Orders</h2>
<div class="subtitle">
<span id="entryOrdersEntryNo-span">Loading...</span>
<span> All orders associated with this entry</span>
</div>
</div>
<div class="entry-orders-content">
<div class="orders-table-container">
<table class="orders-table">
<thead>
<tr>
<th>Order ID</th>
<th>Mark No</th>
<th>Origin</th>
<th>Destination</th>
<th>CTN</th>
<th>QTY</th>
<th>Amount</th>
</tr>
</thead>
<tbody id="entryOrdersTableBody">
<tr>
<td colspan="7" class="empty-state">Loading orders...</td>
</tr>
</tbody>
</table>
</div>
<div class="orders-summary-row">
<div class="orders-summary-item">
<div class="orders-summary-value" id="ordersTotalCount">0</div>
<div class="orders-summary-label">Total Orders</div>
</div>
<div class="orders-summary-item">
<div class="orders-summary-value" id="ordersTotalQuantity">0</div>
<div class="orders-summary-label">Total Quantity</div>
</div>
<div class="orders-summary-item">
<div class="orders-summary-value" id="ordersTotalAmount">₹0</div>
<div class="orders-summary-label">Total Amount</div>
</div>
</div>
</div>
<div style="padding: 20px 30px; border-top: 1px solid #eef3fb; display: flex; justify-content: flex-end;">
<button class="btn ghost" type="button" onclick="closeEntryOrdersModal()">
Close
</button>
<!-- ENTRY ORDERS MODAL -->
<div class="modal-fade1" id="entryOrdersModal">
<div class="modal-box1" style="max-width: 1000px;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;">
<div>
<h2 style="margin:0;font-size:20px;color:#223256;font-weight:800;">
Entry Orders
<span id="entryOrdersEntryNo-span" style="font-size:14px;color:#6b7280;margin-left:8px;"></span>
</h2>
<div style="font-size:13px;color:#6f7b8f;">
All orders associated with this entry.
</div>
</div>
<button class="btn ghost" type="button" onclick="closeEntryOrdersModal()">
Close
</button>
</div>
<div class="orders-table-container">
<table class="orders-table" style="width:100%;border-collapse:collapse;font-size:13px;">
<thead>
<tr>
<th>Order ID</th>
<th>Mark No</th>
<th>Origin</th>
<th>Destination</th>
<th>CTN</th>
<th>QTY</th>
<th>Amount</th>
</tr>
</thead>
<tbody id="entryOrdersTableBody">
<tr>
<td colspan="7" class="empty-state">No orders associated with this entry</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Installment Modal -->
<div class="modal-fade1" id="installmentModal">
<div class="modal-box1" style="max-width:720px;">
<div style="display:flex;align-items:center; justify-content:space-between; margin-bottom:12px; background:linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding:16px; border-radius:8px; color:white; margin-top:-15px; margin-left:-20px; margin-right:-15px;">
<div style="display:flex;align-items:center; justify-content:space-between; margin-bottom:12px;">
<div style="font-size:18px;font-weight:800;color:#243a72;">+ Add New Installment</div>
<button class="btn ghost" onclick="closeInstallmentModal()"></button>
</div>
@@ -2603,12 +2331,11 @@ function renderPaymentTable(list){
<td>${escapeHtml(entry.entry_date)}</td>
<td>${escapeHtml(entry.description)}</td>
<!-- Order Quantity - Clickable number without box -->
<td>
<span onclick="openEntryOrdersModal('${escapeHtml(entry.entry_no)}')"
style="cursor: pointer; color: #276dea; font-weight: 600; text-decoration: underline; text-decoration-color: #ccc;">
<button type="button" class="entry-link"
onclick="openEntryOrdersModal('${escapeHtml(entry.entry_no)}')">
${entry.order_quantity ?? '-'}
</span>
</button>
</td>
<td>${escapeHtml(entry.region)}</td>
@@ -2681,6 +2408,7 @@ function renderPaymentTable(list){
function cycleToggle(btn) {
// वर्तमान position घेऊन पुढचा स्टेट कॅल्क्युलेट करा
let pos = parseInt(btn.dataset.pos || '0', 10); // 0 = unpaid, 1 = pending, 2 = paid
@@ -3056,98 +2784,76 @@ async function submitEditEntry(e) {
}
function openEntryOrdersModal(entryNo) {
// Set entry number in header
document.getElementById('entryOrdersEntryNo-span').textContent = `(${entryNo})`;
function openEntryOrdersModal(entryNo) {
document.getElementById('entryOrdersEntryNo-span').textContent = `(${entryNo})`;
// Reset summary values
document.getElementById('ordersTotalCount').textContent = '0';
document.getElementById('ordersTotalQuantity').textContent = '0';
document.getElementById('ordersTotalAmount').textContent = '₹0';
// table loading state
const tbody = document.getElementById('entryOrdersTableBody');
tbody.innerHTML = `
<tr>
<td colspan="7" class="empty-state">Loading orders...</td>
</tr>
`;
// API call: /admin/account/entry-orders/{entryno}
jsonFetch(`/admin/account/entry-orders/${encodeURIComponent(entryNo)}`, {
method: 'GET'
})
.then(res => {
if (!res.success) {
const tbody = document.getElementById('entryOrdersTableBody');
tbody.innerHTML = `
<tr>
<td colspan="7" class="empty-state">Failed to load orders</td>
</tr>
<tr>
<td colspan="7" class="empty-state">Loading orders...</td>
</tr>
`;
return;
}
const orders = res.orders || [];
if (!orders.length) {
tbody.innerHTML = `
<tr>
<td colspan="7" class="empty-state">No orders associated with this entry</td>
</tr>
`;
return;
}
jsonFetch(`/admin/account/entry-orders/${encodeURIComponent(entryNo)}`, {
method: 'GET'
})
.then(res => {
if (!res.success) {
tbody.innerHTML = `
<tr>
<td colspan="7" class="empty-state">Failed to load orders</td>
</tr>
`;
return;
}
tbody.innerHTML = '';
let totalQuantity = 0;
let totalAmount = 0;
const orders = res.orders || [];
if (!orders.length) {
tbody.innerHTML = `
<tr>
<td colspan="7" class="empty-state">No orders associated with this entry</td>
</tr>
`;
return;
}
orders.forEach(order => {
const tr = document.createElement('tr');
tbody.innerHTML = '';
const idString = (order.orderid ?? order.id ?? '').toString().trim();
const numericId = parseInt(idString, 10);
const formattedId = isNaN(numericId)
? escapeHtml(idString)
: 'KNT-25-' + String(numericId).padStart(8, '0');
orders.forEach(order => {
const tr = document.createElement('tr');
// Calculate values for summary
const quantity = Number(order.qty || order.order_qty || 0);
const amountValue = Number(order.ttl_amount ?? order.ttlamount ?? order.total_amount ?? order.order_amount ?? order.amount ?? 0);
totalQuantity += quantity;
totalAmount += amountValue;
const amountValue =
order.ttl_amount ??
order.ttlamount ??
order.total_amount ??
order.order_amount ??
order.amount ??
0;
tr.innerHTML = `
<td>
<span class="order-id-badge">${formattedId}</span>
</td>
<td>${escapeHtml(order.markno ?? order.mark_no ?? '')}</td>
<td>${escapeHtml(order.origin ?? '')}</td>
<td>${escapeHtml(order.destination ?? '')}</td>
<td>${escapeHtml(order.ctn ?? '')}</td>
<td><strong>${quantity}</strong></td>
<td><strong>${formatCurrency(amountValue)}</strong></td>
`;
tbody.appendChild(tr);
});
tr.innerHTML = `
<td>${escapeHtml(order.order_id)}</td>
<td>${escapeHtml(order.mark_no ?? '')}</td>
<td>${escapeHtml(order.origin ?? '')}</td>
<td>${escapeHtml(order.destination ?? '')}</td>
<td>${escapeHtml(order.ctn ?? '')}</td>
<td>${escapeHtml(order.qty ?? '')}</td>
<td>${formatCurrency(amountValue)}</td>
`;
// Update summary
document.getElementById('ordersTotalCount').textContent = orders.length;
document.getElementById('ordersTotalQuantity').textContent = totalQuantity.toLocaleString();
document.getElementById('ordersTotalAmount').textContent = formatCurrency(totalAmount);
tbody.appendChild(tr);
});
})
.catch(() => {
tbody.innerHTML = `
<tr>
<td colspan="7" class="empty-state">Error loading orders</td>
</tr>
`;
});
})
.catch(() => {
tbody.innerHTML = `
<tr>
<td colspan="7" class="empty-state">Error loading orders</td>
</tr>
`;
});
document.getElementById('entryOrdersModal').classList.add('modal-open');
}
document.getElementById('entryOrdersModal').classList.add('modal-open');
}
function closeEntryOrdersModal() {
document.getElementById('entryOrdersModal').classList.remove('modal-open');
@@ -3283,7 +2989,7 @@ function handleSearch(){
updatePaginationControls();
}
/* ---------- Entry details & installments (Enhanced) ---------- */
/* ---------- Entry details & installments ---------- */
async function openEntryDetailsModal(entryNo) {
try {
const res = await jsonFetch('/admin/account/entry/' + encodeURIComponent(entryNo));
@@ -3293,109 +2999,58 @@ async function openEntryDetailsModal(entryNo) {
const entry = res.entry;
currentEntry = entry;
// Set header info
document.getElementById('entryDetailsId').textContent = entry.entry_no;
// Calculate values
const originalAmount = Number(entry.amount || 0);
const pendingAmount = Number(entry.pending_amount || 0);
const totalProcessed = originalAmount - pendingAmount;
// Update summary cards
document.getElementById('originalAmount').textContent = formatCurrency(originalAmount);
document.getElementById('totalProcessed').textContent = formatCurrency(totalProcessed);
document.getElementById('pendingBalance').textContent = formatCurrency(pendingAmount);
document.getElementById('totalInstallments').textContent = entry.installments?.length || 0;
document.getElementById('originalAmount').textContent = formatCurrency(entry.amount);
const totalProcessed = Number(entry.amount) - Number(entry.pending_amount);
document.getElementById('totalProcessed').textContent = formatCurrency(totalProcessed);
document.getElementById('pendingBalance').textContent = formatCurrency(entry.pending_amount);
document.getElementById('totalInstallments').textContent = entry.installments.length;
// Render installments table
const tbody = document.getElementById('installmentsTableBody');
tbody.innerHTML = '';
if (!entry.installments || entry.installments.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="6" class="empty-state">
<div style="padding: 30px; text-align: center;">
<div style="font-size: 48px; color: #eef3fb; margin-bottom: 16px;">📋</div>
<div style="font-size: 16px; color: #6f7b8f; font-weight: 500;">
No installments found for this entry
</div>
<div style="font-size: 14px; color: #9ba5bb; margin-top: 8px;">
Add your first installment to get started
</div>
</div>
</td>
</tr>
entry.installments.forEach((ins, idx) => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${idx === 0 ? 'Original Entry' : 'Installment ' + idx}</td>
<td>${escapeHtml(ins.proc_date)}</td>
<td>${escapeHtml(ins.description)}</td>
<td>${escapeHtml(ins.region)}</td>
<td>${formatCurrency(ins.amount)}</td>
<td>
<select class="installment-status-dropdown"
data-id="${ins.id}"
onchange="updateInstallmentStatus(${ins.id}, this.value)">
<option value="Pending" ${ins.status === 'Pending' ? 'selected' : ''}
style="color: #f59e0b; font-weight: 500; padding: 10px;">
Pending
</option>
<option value="Loading" ${ins.status === 'Loading' ? 'selected' : ''}
style="color: #3b82f6; font-weight: 500; padding: 10px;">
📦 Loading
</option>
<option value="Packed" ${ins.status === 'Packed' ? 'selected' : ''}
style="color: #8b5cf6; font-weight: 500; padding: 10px;">
📦 Packed
</option>
<option value="Dispatched" ${ins.status === 'Dispatched' ? 'selected' : ''}
style="color: #10b981; font-weight: 500; padding: 10px;">
🚚 Dispatched
</option>
<option value="Delivered" ${ins.status === 'Delivered' ? 'selected' : ''}
style="color: #0c6b2e; font-weight: 500; padding: 10px;">
Delivered
</option>
</select>
</td>
`;
} else {
entry.installments.forEach((ins, idx) => {
const tr = document.createElement('tr');
// Determine installment label
let installmentLabel = 'Original Entry';
if (idx > 0) {
installmentLabel = `Installment ${idx}`;
}
// Status dropdown options with colors
const statusOptions = [
{ value: 'Pending', label: '⏳ Pending', color: '#f59e0b' },
{ value: 'Loading', label: '📦 Loading', color: '#3b82f6' },
{ value: 'Packed', label: '📦 Packed', color: '#8b5cf6' },
{ value: 'Dispatched', label: '🚚 Dispatched', color: '#10b981' },
{ value: 'Delivered', label: '✅ Delivered', color: '#0c6b2e' }
];
let statusOptionsHtml = '';
statusOptions.forEach(opt => {
const selected = ins.status === opt.value ? 'selected' : '';
statusOptionsHtml += `<option value="${opt.value}" ${selected} style="color: ${opt.color}; font-weight: 500;">${opt.label}</option>`;
});
tbody.appendChild(tr);
});
tr.innerHTML = `
<td>
<div style="display: flex; align-items: center; gap: 8px;">
<div style="width: 32px; height: 32px; border-radius: 8px; background: linear-gradient(135deg, #f0f7ff, #e6f0ff); display: flex; align-items: center; justify-content: center; font-weight: 700; color: var(--primary-1);">
${idx + 1}
</div>
<span style="font-weight: 600;">${installmentLabel}</span>
</div>
</td>
<td>
<div style="display: flex; align-items: center; gap: 6px;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
<line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/>
<line x1="3" y1="10" x2="21" y2="10"/>
</svg>
${escapeHtml(ins.proc_date || ins.date || '')}
</div>
</td>
<td>${escapeHtml(ins.description || '')}</td>
<td>
<span style="padding: 4px 8px; background: #f0f7ff; border-radius: 6px; font-size: 12px; font-weight: 600; color: var(--primary-1);">
${escapeHtml(ins.region || '')}
</span>
</td>
<td>
<span style="font-weight: 700; color: var(--primary-1);">
${formatCurrency(ins.amount || 0)}
</span>
</td>
<td>
<select class="installment-status-dropdown"
data-id="${ins.id}"
onchange="updateInstallmentStatus(${ins.id}, this.value)"
title="Update installment status">
${statusOptionsHtml}
</select>
</td>
`;
tbody.appendChild(tr);
});
}
// Show modal
document.getElementById('entryDetailsModal').classList.add('modal-open');
} catch (err) {

View File

@@ -1,855 +0,0 @@
@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 DROPDOWN (badge look) */
.status-dropdown {
position: relative;
min-width: 190px;
cursor: pointer;
}
.status-dropdown-toggle {
padding: 8px 16px;
border-radius: 999px;
border: 1px solid var(--border-color);
background: #ffffff;
font-size: 13px;
font-weight: 600;
color: var(--dark-text);
display: flex;
align-items: center;
justify-content: space-between;
gap: 6px;
}
.status-dropdown-toggle span { white-space: nowrap; }
.status-dropdown-menu {
position: absolute;
top: -230%;
right: 0;
z-index: 30;
background: #ffffff;
border-radius: 14px;
padding: 8px 0;
box-shadow: var(--shadow-lg);
border: 1px solid var(--border-color);
width: 220px;
max-height: 340px;
overflow-y: auto;
display: none;
}
.status-dropdown-menu.open { display: block; }
.status-option {
padding: 6px 14px;
font-size: 13px;
color: var(--dark-text);
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: background 0.15s;
line-height: 1.3;
}
.status-option:hover { background: #eef2ff; }
.status-option .dot {
width: 8px;
height: 8px;
border-radius: 999px;
background: #9ca3af;
}
.status-option.active .dot {
background: #22c55e;
}
/* COLOR MAPPING per status dropdown tint + main toggle text color */
.status-option.status-container-ready { background: #eff6ff; color: #1d4ed8; }
.status-option.status-export-custom { background: #fff7ed; color: #b45309; }
.status-option.status-international-transit { background: #f5f3ff; color: #4c1d95; }
.status-option.status-arrived-at-india { background: #ecfdf5; color: #15803d; }
.status-option.status-import-custom { background: #fffbeb; color: #92400e; }
.status-option.status-warehouse { background: #f4f4f5; color: #374151; }
.status-option.status-domestic-distribution { background: #faf5ff; color: #6d28d9; }
.status-option.status-out-for-delivery { background: #eff6ff; color: #1d4ed8; }
.status-option.status-delivered { background: #ecfdf5; color: #15803d; }
.status-dropdown-toggle.status-container-ready { background: #eff6ff; color: #1d4ed8; }
.status-dropdown-toggle.status-export-custom { background: #fff7ed; color: #b45309; }
.status-dropdown-toggle.status-international-transit { background: #f5f3ff; color: #4c1d95; }
.status-dropdown-toggle.status-arrived-at-india { background: #ecfdf5; color: #15803d; }
.status-dropdown-toggle.status-import-custom { background: #fffbeb; color: #92400e; }
.status-dropdown-toggle.status-warehouse { background: #f4f4f5; color: #374151; }
.status-dropdown-toggle.status-domestic-distribution { background: #faf5ff; color: #6d28d9; }
.status-dropdown-toggle.status-out-for-delivery { background: #eff6ff; color: #1d4ed8; }
.status-dropdown-toggle.status-delivered { background: #ecfdf5; color: #15803d; }
.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 { position: relative; }
.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 {
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 { width: 100%; }
.status-dropdown { 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('container.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="container-ready" {{ request('status') == 'container-ready' ? 'selected' : '' }}>Container Ready</option>
<option value="export-custom" {{ request('status') == 'export-custom' ? 'selected' : '' }}>Export Custom</option>
<option value="international-transit" {{ request('status') == 'international-transit' ? 'selected' : '' }}>International Transit</option>
<option value="arrived-at-india" {{ request('status') == 'arrived-at-india' ? 'selected' : '' }}>Arrived at India</option>
<option value="import-custom" {{ request('status') == 'import-custom' ? 'selected' : '' }}>Import Custom</option>
<option value="warehouse" {{ request('status') == 'warehouse' ? 'selected' : '' }}>Warehouse</option>
<option value="domestic-distribution" {{ request('status') == 'domestic-distribution' ? 'selected' : '' }}>Domestic Distribution</option>
<option value="out-for-delivery" {{ request('status') == 'out-for-delivery' ? 'selected' : '' }}>Out for Delivery</option>
<option value="delivered" {{ request('status') == 'delivered' ? 'selected' : '' }}>Delivered</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
@php
$labels = [
'container-ready' => 'Container Ready',
'export-custom' => 'Export Custom',
'international-transit' => 'International Transit',
'arrived-at-india' => 'Arrived at India',
'import-custom' => 'Import Custom',
'warehouse' => 'Warehouse',
'domestic-distribution' => 'Domestic Distribution',
'out-for-delivery' => 'Out for Delivery',
'delivered' => 'Delivered',
];
@endphp
@foreach($containers as $container)
@php
$status = $container->status ?? 'container-ready';
$statusLabel = $labels[$status] ?? ucfirst(str_replace('-', ' ', $status));
@endphp
<div class="container-item">
<div class="container-header">
<div class="container-info">
<div class="container-avatar">
{{ strtoupper(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">
@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
@php $statusClass = 'status-' . $status; @endphp
<div class="status-dropdown">
<div class="status-dropdown-toggle {{ $statusClass }}">
<span class="status-dropdown-label">
{{ $statusLabel }}
</span>
<i class="fas fa-chevron-down" style="font-size:11px;color:#4b5563;"></i>
</div>
<div class="status-dropdown-menu">
@foreach($labels as $value => $label)
@php $optClass = 'status-' . $value; @endphp
<div class="status-option {{ $optClass }} {{ $status === $value ? 'active' : '' }}"
data-status="{{ $value }}">
<span class="dot"></span>
<span>{{ $label }}</span>
</div>
@endforeach
</div>
</div>
</form>
@endcan
@can('container.update')
<a href="{{ route('containers.show', $container->id) }}" class="action-btn view-btn">
<i class="fas fa-eye"></i> View
</a>
@endcan
@can('container.delete')
<form action="{{ route('containers.destroy', $container->id) }}" method="POST"
class="delete-form"
data-container-id="{{ $container->id }}">
@csrf
@method('DELETE')
<button type="submit" class="action-btn delete-btn">
<i class="fas fa-trash"></i> Delete
</button>
</form>
@endcan
</div>
</div>
<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 () {
// STATUS DROPDOWN
document.querySelectorAll('.status-dropdown').forEach(function (wrapper) {
const toggle = wrapper.querySelector('.status-dropdown-toggle');
const menu = wrapper.querySelector('.status-dropdown-menu');
toggle.addEventListener('click', function (e) {
e.stopPropagation();
document.querySelectorAll('.status-dropdown-menu.open').forEach(m => {
if (m !== menu) m.classList.remove('open');
});
menu.classList.toggle('open');
});
menu.querySelectorAll('.status-option').forEach(function (opt) {
opt.addEventListener('click', function () {
const status = this.dataset.status;
const form = wrapper.closest('form');
const labelEl = wrapper.querySelector('.status-dropdown-label');
const toggleEl= wrapper.querySelector('.status-dropdown-toggle');
// UI: dropdown label + active item
menu.querySelectorAll('.status-option').forEach(o => o.classList.remove('active'));
this.classList.add('active');
labelEl.textContent = this.querySelector('span:nth-child(2)').textContent;
menu.classList.remove('open');
// toggle रंग class reset करून नवा status-* दे
toggleEl.className = 'status-dropdown-toggle';
toggleEl.classList.add('status-' + status);
const url = form.getAttribute('action');
const token = form.querySelector('input[name="_token"]').value;
const formData = new FormData();
formData.append('_token', token);
formData.append('status', status);
fetch(url, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json',
},
body: formData
})
.then(async res => {
let data = null;
try { data = await res.json(); } catch (e) {}
if (!res.ok || !data || !data.success) {
alert('Status update failed');
return;
}
})
.catch(() => {
alert('Network error while updating status');
});
});
});
});
document.addEventListener('click', function () {
document.querySelectorAll('.status-dropdown-menu.open')
.forEach(m => m.classList.remove('open'));
});
// DELETE VIA AJAX
document.querySelectorAll('.delete-form').forEach(function (form) {
form.addEventListener('submit', function (e) {
e.preventDefault();
if (!confirm('Are you sure you want to delete this container and all its entries?')) {
return;
}
const url = form.getAttribute('action');
const token = form.querySelector('input[name="_token"]').value;
const item = form.closest('.container-item');
const formData = new FormData();
formData.append('_token', token);
formData.append('_method', 'DELETE');
fetch(url, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json',
},
body: formData
})
.then(async res => {
let data = null;
try { data = await res.json(); } catch (e) {}
if (!res.ok || !data || !data.success) {
alert('Delete failed');
return;
}
if (item) {
item.style.opacity = '0';
item.style.transform = 'translateX(-10px)';
setTimeout(() => item.remove(), 200);
}
})
.catch(() => {
alert('Network error while deleting container');
});
});
});
});
</script>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
@endsection

View File

@@ -1,349 +0,0 @@
@extends('admin.layouts.app')
@section('page-title', 'Add Container')
@section('content')
<style>
.cm-add-wrapper { padding: 10px 0 20px 0; }
.cm-add-header-card {
border-radius: 14px;
border: none;
margin-bottom: 18px;
background: linear-gradient(90deg,#4c6fff,#8e54e9);
box-shadow: 0 6px 18px rgba(15,35,52,0.18);
color: #ffffff;
}
.cm-add-header-card .card-body {
padding: 14px 18px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.cm-add-title { margin: 0; font-size: 20px; font-weight: 600; }
.cm-add-sub { font-size: 12px; opacity: 0.9; }
.cm-add-main-card {
border-radius: 14px;
border: none;
box-shadow: 0 6px 18px rgba(15,35,52,0.12);
}
.cm-add-main-card .card-header {
background:#ffffff;
border-bottom: 1px solid #edf0f5;
padding: 10px 18px;
}
.cm-add-main-card .card-header h5 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.cm-form-label { font-size: 13px; font-weight: 500; color:#495057; margin-bottom: 4px; }
.cm-form-control {
font-size: 13px;
border-radius: 10px;
border:1px solid #d0d7e2;
padding: 8px 11px;
}
.cm-form-control:focus {
border-color:#4c6fff;
box-shadow:0 0 0 0.15rem rgba(76,111,255,.25);
}
.cm-help-text { font-size: 11px; color:#868e96; margin-top: 2px; }
.cm-btn-primary { border-radius: 20px; padding: 6px 22px; font-size: 13px; font-weight: 500; }
.cm-btn-secondary { border-radius: 20px; padding: 6px 18px; font-size: 13px; }
.error-card {
border-radius: 12px;
border: 1px solid #f5c2c7;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
}
.error-row-box {
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;
background: #f8f9fa;
}
.error-title {
font-weight: 600;
font-size: 13px;
margin-bottom: 6px;
}
.error-item {
font-size: 12px;
margin-left: 10px;
}
.error-item span {
font-weight: 500;
}
</style>
<div class="container-fluid cm-add-wrapper">
<div class="card cm-add-header-card">
<div class="card-body">
<div>
<h4 class="cm-add-title">Create New Container</h4>
<div class="cm-add-sub">
Add container details and upload Kent loading list Excel file.
</div>
</div>
<a href="{{ route('containers.index') }}" class="btn btn-light btn-sm">
Back to Containers
</a>
</div>
</div>
<div class="card cm-add-main-card">
<div class="card-header">
<h5>Add Container</h5>
</div>
<div class="card-body">
{{-- SUCCESS --}}
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
{{-- VALIDATION --}}
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
{{-- COMBINED ERROR PANEL (summary text) --}}
@if(session('formula_errors') || session('mark_errors'))
<div class="card error-card mb-3">
<div class="card-header bg-danger text-white small">
Excel Validation Issues Found
</div>
<div class="card-body">
<div class="small mb-1">
Some rows in your Excel file have formula or mark issues.
See the table below, and detailed messages after the table.
</div>
<ul class="small mb-0 ps-3">
<li>Red highlighted rows indicate formula mismatches in the uploaded Excel data.</li>
<li>Yellow highlighted rows indicate marks from the Excel file that do not match any record in the system.</li>
</ul>
</div>
</div>
{{-- 1) Excel-style table at top --}}
@php
$formulaErrors = session('formula_errors') ?? [];
$markErrors = session('mark_errors') ?? [];
$allRowsData = [];
foreach ($formulaErrors as $fe) {
if (!empty($fe['data'] ?? null)) {
$allRowsData[] = $fe['data'];
}
}
foreach ($markErrors as $me) {
if (!empty($me['data'] ?? null)) {
$allRowsData[] = $me['data'];
}
}
$headings = [];
if (!empty($allRowsData)) {
$headings = array_keys($allRowsData[0]);
}
@endphp
@if(!empty($headings))
<div class="card mb-3">
<div class="card-header small">
Error rows in Excel view
</div>
<div class="card-body p-2">
<div class="table-responsive" style="max-height:260px; overflow:auto;">
<table class="table table-sm table-bordered mb-0" style="font-size:11.5px;">
<thead class="table-light">
<tr>
<th>Excel Row</th>
<th>Mark No</th>
@foreach($headings as $head)
<th>{{ $head }}</th>
@endforeach
</tr>
</thead>
<tbody>
{{-- Formula error rows (red formula mismatch, critical) --}}
@foreach($formulaErrors as $fe)
@php $rowData = $fe['data'] ?? []; @endphp
@if(!empty($rowData))
<tr class="table-danger">
<td>{{ $fe['excel_row'] }}</td>
<td>{{ $fe['mark_no'] }}</td>
@foreach($headings as $head)
<td>{{ $rowData[$head] ?? '' }}</td>
@endforeach
</tr>
@endif
@endforeach
{{-- Mark error rows (yellow mark not found, warning) --}}
@foreach($markErrors as $me)
@php $rowData = $me['data'] ?? []; @endphp
@if(!empty($rowData))
<tr class="table-warning">
<td>{{ $me['excel_row'] }}</td>
<td>{{ $me['mark_no'] }}</td>
@foreach($headings as $head)
<td>{{ $rowData[$head] ?? '' }}</td>
@endforeach
</tr>
@endif
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endif
{{-- 2) Detailed per-row error boxes BELOW the table --}}
<div class="card error-card mb-3">
<div class="card-header bg-light small">
Detailed error messages
</div>
<div class="card-body">
{{-- Formula Errors (detailed) --}}
@if(session('formula_errors'))
@foreach(session('formula_errors') as $fe)
<div class="error-row-box">
<div class="error-title">
Row {{ $fe['excel_row'] }}
@if($fe['mark_no']) | Mark: {{ $fe['mark_no'] }} @endif
@if($fe['description']) | {{ $fe['description'] }} @endif
</div>
@foreach($fe['errors'] as $field => $detail)
<div class="error-item text-danger">
<span>{{ $field }}</span>
Expected: {{ number_format($detail['expected'],4) }}
| Got: {{ number_format($detail['actual'],4) }}
</div>
@endforeach
</div>
@endforeach
@endif
{{-- Mark Errors (detailed) --}}
@if(session('mark_errors'))
@foreach(session('mark_errors') as $me)
<div class="error-row-box bg-warning bg-opacity-10 border-warning">
<div class="error-title">
Row {{ $me['excel_row'] }}
</div>
<div class="error-item text-warning">
Mark <strong>{{ $me['mark_no'] }}</strong> not found in database
</div>
</div>
@endforeach
@endif
</div>
</div>
@endif
{{-- FORM --}}
@if (!session('formula_errors') && !session('mark_errors'))
<form action="{{ route('containers.store') }}" method="POST" enctype="multipart/form-data" class="mt-3">
@csrf
<div class="row g-3">
<div class="col-md-6">
<label class="cm-form-label">Container Name</label>
<input type="text" name="container_name"
class="form-control cm-form-control"
value="{{ old('container_name') }}"
placeholder="Enter container name">
</div>
<div class="col-md-6">
<label class="cm-form-label">Container Number</label>
<input type="text" name="container_number"
class="form-control cm-form-control"
value="{{ old('container_number') }}"
placeholder="Enter container number">
</div>
<div class="col-md-6">
<label class="cm-form-label">Container Date</label>
<input type="date"
name="container_date" {{-- name fix --}}
id="containerdate" {{-- JS साठी जुना id ठेवला --}}
class="form-control cm-form-control"
value="{{ old('container_date') }}">
</div>
<div class="col-md-6">
<label class="cm-form-label">Loading List Excel</label>
<input type="file" name="excel_file"
class="form-control cm-form-control"
accept=".xls,.xlsx">
<div class="cm-help-text">
Upload Kent loading list Excel file (.xls / .xlsx).
</div>
</div>
</div>
<div class="mt-4 d-flex gap-2">
<button type="submit" class="btn btn-primary cm-btn-primary">
Save Container
</button>
<a href="{{ route('containers.index') }}" class="btn btn-outline-secondary cm-btn-secondary">
Cancel
</a>
</div>
</form>
@endif
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const dateInput = document.getElementById('containerdate');
if (!dateInput) return;
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
const todayStr = `${year}-${month}-${day}`;
// Todays date
dateInput.min = todayStr;
// old date remove
if (dateInput.value && dateInput.value < todayStr) {
dateInput.value = todayStr;
}
});
</script>
@endsection

View File

@@ -1,251 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Container {{ $container->container_number }} Summary</title>
<style>
* { box-sizing: border-box; }
body {
font-family: DejaVu Sans, sans-serif;
font-size: 10px;
margin: 10px;
background: #e5e7ff;
}
/* LOGO HEADER */
.logo-header-wrap {
width: 100%;
margin-bottom: 6px;
}
.logo-header-inner {
display: table;
}
.logo-header-logo-cell {
display: table-cell;
vertical-align: middle;
}
.logo-header-text-cell {
display: table-cell;
vertical-align: middle;
padding-left: 8px;
}
.logo-header-logo {
height: 32px; /* banner type, कमी उंची */
object-fit: contain;
}
.logo-header-title-top {
font-size: 14px;
font-weight: 700;
color: #7b1111; /* dark maroon */
line-height: 1.1;
}
.logo-header-title-bottom {
font-size: 12px;
font-weight: 500;
color: #7b1111;
line-height: 1.1;
}
/* COMMON CARD GRID 4 equal columns, 2 rows */
.card-grid {
display: table;
width: 100%;
border-spacing: 8px 6px;
margin-bottom: 8px;
}
.card-row {
display: table-row;
}
.card {
display: table-cell;
width: 25%;
padding: 7px 10px;
border-radius: 14px;
box-shadow: 0 4px 12px rgba(15,35,52,0.18);
color: #0f172a;
vertical-align: middle;
}
/* INFO CARDS (FIRST ROW) */
.info-id { background: #e0f2ff; border-left: 4px solid #2563eb; }
.info-no { background: #dcfce7; border-left: 4px solid #22c55e; }
.info-date { background: #fee2e2; border-left: 4px solid #ef4444; }
.info-name { background: #fef9c3; border-left: 4px solid #f59e0b; }
/* TOTAL CARDS (SECOND ROW) */
.total-ctn { background: #dbeafe; border-left: 4px solid #1d4ed8; }
.total-qty { background: #bbf7d0; border-left: 4px solid #16a34a; }
.total-cbm { background: #fef3c7; border-left: 4px solid #d97706; }
.total-kg { background: #fecaca; border-left: 4px solid #dc2626; }
.label-text {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.4px;
color: #4b5563;
}
.value-text-small {
font-size: 12px;
font-weight: 800;
margin-top: 2px;
word-wrap: break-word;
}
.value-text-big {
font-size: 16px;
font-weight: 800;
margin-top: 2px;
}
/* TABLE solid yellow header */
table {
width: 100%;
border-collapse: collapse;
margin-top: 6px;
table-layout: fixed;
background: #ffffff;
border-radius: 12px;
overflow: hidden;
}
th, td {
border: 1px solid #e5e7eb;
padding: 4px 3px;
text-align: center;
word-wrap: break-word;
}
th {
background: #fbd85d;
font-size: 9px;
font-weight: 700;
color: #0c0909;
}
td {
font-size: 9px;
color: #111827;
}
tr:nth-child(even) td {
background: #f9fafb;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
</style>
</head>
<body>
@php
$totalCtn = 0;
$totalQty = 0;
$totalCbm = 0.0;
$totalKg = 0.0;
foreach ($container->rows as $row) {
if (!is_array($row->data)) continue;
foreach ($row->data as $h => $v) {
$norm = strtoupper(str_replace([' ', '/', '-', '.'],'', $h));
$val = is_numeric(str_replace([','], '', $v)) ? floatval(str_replace([','], '', $v)) : 0;
if (str_contains($norm, 'TOTALCTN') || $norm === 'CTN' || str_contains($norm,'TOTALCNTR') || str_contains($norm,'TOTALCARTON')) {
$totalCtn += $val;
}
if (str_contains($norm,'TOTALQTY') || str_contains($norm,'ITLQTY') || str_contains($norm,'TTLQTY')) {
$totalQty += $val;
}
if (str_contains($norm,'TOTALCBM') || str_contains($norm,'TTLCBM') || str_contains($norm,'ITLCBM')) {
$totalCbm += $val;
}
if (str_contains($norm,'TOTALKG') || str_contains($norm,'TTKG')) {
$totalKg += $val;
}
}
}
$allHeadings = [];
foreach ($container->rows as $row) {
if (is_array($row->data)) {
$allHeadings = array_unique(array_merge($allHeadings, array_keys($row->data)));
}
}
@endphp
{{-- LOGO + TEXT full left, text खाली (reference image) --}}
<div class="logo-header-wrap">
<div class="logo-header-inner">
<div class="logo-header-logo-cell">
<img src="{{ public_path('images/kentlogo1.png') }}"
class="logo-header-logo"
alt="Kent Logo">
</div>
<div class="logo-header-text-cell">
<div class="logo-header-title-top">KENT</div>
<div class="logo-header-title-bottom">International Pvt. Ltd.</div>
</div>
</div>
</div>
{{-- TWO ROW GRID FIRST: INFO / SECOND: TOTALS --}}
<div class="card-grid">
<div class="card-row">
<div class="card info-id">
<div class="label-text">Container ID</div>
<div class="value-text-small">{{ $container->id }}</div>
</div>
<div class="card info-no">
<div class="label-text">Container Number</div>
<div class="value-text-small">{{ $container->container_number }}</div>
</div>
<div class="card info-date">
<div class="label-text">Container Date</div>
<div class="value-text-small">
{{ $container->container_date ? $container->container_date->format('d-m-Y') : '-' }}
</div>
</div>
<div class="card info-name">
<div class="label-text">Container Name</div>
<div class="value-text-small">
{{ $container->container_name ?? '-' }}
</div>
</div>
</div>
<div class="card-row">
<div class="card total-ctn">
<div class="label-text">Total CTN</div>
<div class="value-text-big">{{ number_format($totalCtn, 0) }}</div>
</div>
<div class="card total-qty">
<div class="label-text">Total QTY</div>
<div class="value-text-big">{{ number_format($totalQty, 0) }}</div>
</div>
<div class="card total-cbm">
<div class="label-text">Total CBM</div>
<div class="value-text-big">{{ number_format($totalCbm, 3) }}</div>
</div>
<div class="card total-kg">
<div class="label-text">Total KG</div>
<div class="value-text-big">{{ number_format($totalKg, 2) }}</div>
</div>
</div>
</div>
{{-- FULL TABLE --}}
<table>
<thead>
<tr>
<th style="width:18px;">#</th>
@foreach($allHeadings as $heading)
<th>{{ $heading }}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach($container->rows as $index => $row)
<tr>
<td>{{ $index + 1 }}</td>
@foreach($allHeadings as $heading)
<td>{{ $row->data[$heading] ?? '' }}</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
</body>
</html>

View File

@@ -1,966 +0,0 @@
@extends('admin.layouts.app')
@section('page-title', 'Container Details')
@section('content')
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap');
html, body {
margin: 0;
padding: 0;
width: 100%;
overflow-x: hidden;
}
.cm-header-card {
background: linear-gradient(100deg, #4c6fff 0%, #8e54e9 100%);
border-radius: 14px;
border: none;
box-shadow: 0 6px 24px rgba(76,111,255,0.22);
margin-bottom: 18px;
color: #fff;
}
.cm-header-card .card-body {
padding: 14px 20px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
}
.cm-header-title {
margin: 0;
font-size: 19px;
font-weight: 700;
letter-spacing: -0.3px;
}
.cm-header-sub {
font-size: 12px;
opacity: 0.88;
margin-top: 2px;
}
.cm-download-pdf {
background: linear-gradient(100deg, #4c6fff 0%, #8e54e9 100%) !important;
color: #fff !important;
border: none !important;
font-weight: 600 !important;
font-size: 12.5px !important;
padding: 8px 16px !important;
border-radius: 8px !important;
box-shadow: 0 4px 14px rgba(76,111,255,0.4) !important;
transition: all 0.2s ease !important;
text-decoration: none !important;
display: inline-flex !important;
align-items: center !important;
gap: 6px !important;
}
.cm-download-pdf:hover {
transform: translateY(-1px) !important;
box-shadow: 0 6px 20px rgba(76,111,255,0.5) !important;
color: #fff !important;
opacity: 0.95 !important;
}
.cm-download-excel {
background: linear-gradient(100deg, #10b981 0%, #059669 100%) !important;
color: #fff !important;
border: none !important;
font-weight: 600 !important;
font-size: 12.5px !important;
padding: 8px 16px !important;
border-radius: 8px !important;
box-shadow: 0 4px 14px rgba(16,185,129,0.4) !important;
transition: all 0.2s ease !important;
text-decoration: none !important;
display: inline-flex !important;
align-items: center !important;
gap: 6px !important;
}
.cm-download-excel:hover {
transform: translateY(-1px) !important;
box-shadow: 0 6px 20px rgba(16,185,129,0.5) !important;
color: #fff !important;
opacity: 0.95 !important;
}
.cm-back-btn {
background: linear-gradient(100deg, #6b7280 0%, #4b5563 100%) !important;
color: #fff !important;
border: none !important;
font-weight: 600 !important;
font-size: 12.5px !important;
padding: 8px 16px !important;
border-radius: 8px !important;
box-shadow: 0 4px 14px rgba(107,114,128,0.4) !important;
transition: all 0.2s ease !important;
text-decoration: none !important;
display: inline-flex !important;
align-items: center !important;
gap: 6px !important;
}
.cm-back-btn:hover {
transform: translateY(-1px) !important;
box-shadow: 0 6px 20px rgba(107,114,128,0.5) !important;
color: #fff !important;
opacity: 0.95 !important;
}
.cm-main-card {
border-radius: 14px;
border: none;
box-shadow: 0 4px 20px rgba(15,35,52,0.10);
overflow: hidden;
background: #fff;
position: relative;
}
.cm-main-card .card-header {
background: #fff;
border-bottom: 1px solid #edf0f5;
padding: 12px 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.cm-main-card .card-header h5 {
margin: 0;
font-size: 15px;
font-weight: 700;
color: #1a2340;
}
.cm-info-cards-row {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14px;
padding: 14px 18px 8px 18px;
}
.cm-info-card {
border-radius: 16px;
padding: 12px 14px;
display: flex;
align-items: center;
gap: 10px;
box-shadow: 0 4px 16px rgba(15,35,52,0.12);
border: 1px solid rgba(148,163,184,0.25);
background: #fff;
min-height: 70px;
}
.cm-info-card-icon {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #0f172a;
font-size: 16px;
box-shadow: 0 3px 10px rgba(15,23,42,0.10);
flex-shrink: 0;
}
.cm-info-card-body {
display: flex;
flex-direction: column;
gap: 3px;
}
.cm-info-card-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.4px;
color: #64748b;
font-weight: 600;
}
.cm-info-card-value {
font-size: 14px;
font-weight: 700;
color: #0f172a;
white-space: nowrap;
}
.cm-card-container {
background: linear-gradient(135deg, #e0f2ff, #eef4ff);
}
.cm-card-container .cm-info-card-icon {
background: linear-gradient(135deg, #2563eb, #4f46e5);
color: #e5edff;
}
.cm-card-date {
background: linear-gradient(135deg, #ecfdf3, #e0fbea);
}
.cm-card-date .cm-info-card-icon {
background: linear-gradient(135deg, #16a34a, #22c55e);
color: #ecfdf3;
}
.cm-card-excel {
background: linear-gradient(135deg, #fff7ed, #fffbeb);
}
.cm-card-excel .cm-info-card-icon {
background: linear-gradient(135deg, #f97316, #fb923c);
color: #fff7ed;
}
.cm-total-cards-row {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 14px;
padding: 4px 18px 14px 18px;
}
.cm-total-card {
border-radius: 18px;
padding: 12px 18px;
display: flex;
align-items: center;
gap: 10px;
box-shadow: 0 8px 30px rgba(15,23,42,0.08);
border: 1px solid rgba(148,163,184,0.25);
}
.cm-total-icon {
width: 34px;
height: 34px;
border-radius: 999px;
display: flex;
align-items: center;
justify-content: center;
font-size: 17px;
flex-shrink: 0;
color: #0f172a;
background: #fff;
box-shadow: 0 4px 14px rgba(15,23,42,0.15);
}
.cm-total-text {
display: flex;
flex-direction: column;
gap: 3px;
}
.cm-total-label {
font-size: 11px;
font-weight: 600;
color: #4b5563;
}
.cm-total-value {
font-size: 18px;
font-weight: 800;
color: #111827;
}
.cm-total-card-ctn {
background: linear-gradient(135deg, #e0f2ff, #eef2ff);
border-left: 4px solid #3b82f6;
}
.cm-total-card-qty {
background: linear-gradient(135deg, #dcfce7, #ecfdf5);
border-left: 4px solid #22c55e;
}
.cm-total-card-cbm {
background: linear-gradient(135deg, #fef9c3, #fffbeb);
border-left: 4px solid #f59e0b;
}
.cm-total-card-kg {
background: linear-gradient(135deg, #fee2e2, #fef2f2);
border-left: 4px solid #ef4444;
}
.cm-filter-bar {
padding: 4px 20px 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.cm-row-count {
font-size: 12px;
color: #8a93a6;
font-weight: 500;
}
.cm-filter-input {
max-width: 240px;
font-size: 12px;
border-radius: 20px;
border: 1px solid #dde2ee;
padding: 6px 14px;
outline: none;
transition: border 0.2s;
}
.cm-filter-input:focus {
border-color: #4c6fff;
box-shadow: 0 0 0 3px rgba(76,111,255,0.1);
}
.cm-table-scroll-outer {
margin: 10px 14px 30px 14px;
border-radius: 14px;
border: 1.5px solid #c9a359;
box-shadow: 0 4px 14px rgba(76,111,255,0.18);
overflow-x: auto;
overflow-y: hidden;
position: relative;
}
.cm-table-wrapper {
position: relative;
min-width: 1200px;
}
.cm-table {
font-size: 12px;
width: 100%;
border-collapse: separate;
border-spacing: 0;
font-family: 'DM Sans', sans-serif;
}
.cm-table thead tr th {
position: sticky;
top: 0;
z-index: 3;
background: linear-gradient(100deg, #fde4b3 0%, #fbd48a 100%);
color: #0c0909;
font-weight: 700;
font-size: 12px;
padding: 11px 14px;
border-bottom: 2px solid rgba(255,255,255,0.15);
border-right: 1px solid rgba(255,255,255,0.18);
white-space: nowrap;
text-align: center;
letter-spacing: 0.2px;
text-shadow: 0 1px 2px rgba(0,0,0,0.35);
}
.cm-table thead tr th:first-child {
border-top-left-radius: 10px;
}
.cm-table thead tr th:last-child {
border-top-right-radius: 10px;
border-right: none;
}
.cm-table thead tr th:first-child,
.cm-table tbody tr td:first-child {
width: 46px;
min-width: 46px;
max-width: 46px;
text-align: center;
}
.cm-table tbody tr td {
padding: 8px 14px;
border-bottom: 1px solid #f0f3fb;
border-right: 1px solid #f0f3fb;
color: #2d3a55;
font-size: 12.5px;
text-align: center;
vertical-align: middle;
background: #fff;
transition: background 0.15s;
white-space: nowrap;
}
.cm-table tbody tr td:last-child {
border-right: none;
}
.cm-table tbody tr:nth-child(even) td {
background: #f8f9ff;
}
.cm-table tbody tr:hover td {
background: #edf3ff !important;
}
.cm-row-num {
color: #8a93a6;
font-size: 11px;
font-weight: 600;
}
.cm-cell-input {
width: 100%;
min-width: 90px;
background: transparent;
border: 1.5px solid transparent;
border-radius: 5px;
font-size: 12.5px;
font-family: 'DM Sans', sans-serif;
color: #2d3a55;
padding: 3px 6px;
text-align: center;
transition: border 0.15s, background 0.15s, box-shadow 0.15s;
outline: none;
}
.cm-cell-input:hover {
border-color: #c9d4f5;
background: #f5f8ff;
}
.cm-cell-input:focus {
border-color: #4c6fff;
background: #fff;
box-shadow: 0 0 0 3px rgba(76,111,255,0.12);
}
.cm-cell-readonly {
background: #f3f4ff;
cursor: not-allowed;
border-color: #e2e3ff;
}
.cm-save-btn-floating {
position: fixed;
right: 26px;
bottom: 22px;
z-index: 50;
font-size: 13px;
font-weight: 600;
border-radius: 22px;
padding: 8px 20px;
background: linear-gradient(90deg, #4c6fff, #8e54e9);
border: none;
color: #fff;
box-shadow: 0 4px 16px rgba(76,111,255,0.35);
transition: opacity 0.2s, transform 0.15s;
cursor: pointer;
}
.cm-save-btn-floating:hover {
opacity: 0.94;
transform: translateY(-1px);
}
.cm-save-btn-floating.cm-disabled {
opacity: 0.6;
cursor: default;
pointer-events: none;
}
.cm-toast {
position: fixed;
right: 26px;
bottom: 70px;
background: #0f172a;
color: #f9fafb;
font-size: 12.5px;
padding: 8px 14px;
border-radius: 10px;
box-shadow: 0 8px 24px rgba(15,23,42,0.25);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s, transform 0.2s;
transform: translateY(8px);
z-index: 60;
}
.cm-toast.cm-show {
opacity: 1;
pointer-events: auto;
transform: translateY(0);
}
.cm-empty {
padding: 30px 20px;
color: #8a93a6;
font-size: 13px;
}
.cm-table-scroll-outer::-webkit-scrollbar {
height: 6px;
}
.cm-table-scroll-outer::-webkit-scrollbar-track {
background: #f0f3fb;
border-radius: 4px;
}
.cm-table-scroll-outer::-webkit-scrollbar-thumb {
background: #c5cce8;
border-radius: 4px;
}
.cm-table-scroll-outer::-webkit-scrollbar-thumb:hover {
background: #8a99d0;
}
@media (max-width: 991px) {
.cm-total-cards-row {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 767px) {
.cm-header-card .card-body {
flex-direction: column;
align-items: flex-start;
}
.cm-info-cards-row {
grid-template-columns: 1fr;
}
.cm-table-scroll-outer {
overflow-x: auto;
}
.cm-table-wrapper {
min-width: 900px;
}
}
@media (max-width: 575px) {
.cm-total-cards-row {
grid-template-columns: 1fr;
}
}
</style>
<div class="container-fluid cm-wrapper">
<div class="card cm-header-card">
<div class="card-body">
<div>
<h4 class="cm-header-title">Container {{ $container->container_number }}</h4>
<div class="cm-header-sub">
Edit loading list directly like Excel. TT columns autocalculate from CTN, QTY, CBM, KG, PRICE.
</div>
</div>
<div class="d-flex gap-2">
<a href="{{ route('containers.index') }}" class="cm-back-btn">
<i class="bi bi-arrow-left"></i>
Back to list
</a>
<a href="{{ route('containers.download.pdf', $container->id) }}" class="cm-download-pdf">
<i class="bi bi-file-earmark-pdf"></i>
Download PDF
</a>
<a href="{{ route('containers.download.excel', $container->id) }}" class="cm-download-excel">
<i class="bi bi-file-earmark-excel"></i>
Download Excel
</a>
</div>
</div>
</div>
<div class="card cm-main-card">
<div class="card-header">
<h5>Container Information</h5>
</div>
<div class="cm-info-cards-row">
<div class="cm-info-card cm-card-container">
<div class="cm-info-card-icon">
<i class="bi bi-box-seam"></i>
</div>
<div class="cm-info-card-body">
<div class="cm-info-card-label">Container</div>
<div class="cm-info-card-value">
{{ $container->container_name ?? $container->container_number }}
</div>
</div>
</div>
<div class="cm-info-card cm-card-date">
<div class="cm-info-card-icon">
<i class="bi bi-calendar-event"></i>
</div>
<div class="cm-info-card-body">
<div class="cm-info-card-label">Date</div>
<div class="cm-info-card-value">
{{ $container->container_date ? $container->container_date->format('d-m-Y') : '' }}
</div>
</div>
</div>
<div class="cm-info-card cm-card-excel">
<div class="cm-info-card-icon">
<i class="bi bi-file-earmark-excel"></i>
</div>
<div class="cm-info-card-body">
<div class="cm-info-card-label">Excel File</div>
<div class="cm-info-card-value">
@if($container->excel_file)
<a href="{{ url($container->excel_file) }}" target="_blank" style="color:#0f172a;text-decoration:none;">
Download / View
</a>
@else
<span style="color:#b0b8cc;">Not uploaded</span>
@endif
</div>
</div>
</div>
</div>
@php
$totalCtn = 0;
$totalQty = 0;
$totalCbm = 0.0;
$totalKg = 0.0;
if(!$container->rows->isEmpty()){
foreach ($container->rows as $row) {
if (!is_array($row->data)) continue;
foreach ($row->data as $h => $v) {
$norm = strtoupper(str_replace([' ', '/', '-', '.'],'', $h));
$val = is_numeric(str_replace([','], '', $v)) ? floatval(str_replace([','], '', $v)) : 0;
if (str_contains($norm, 'TOTALCTN') || $norm === 'CTN' || str_contains($norm,'TOTALCNTR') || str_contains($norm,'TOTALCARTON')) {
$totalCtn += $val;
}
if (
str_contains($norm,'TOTALQTY') ||
str_contains($norm,'ITLQTY') ||
str_contains($norm,'TTLQTY')
) {
$totalQty += $val;
}
if (
str_contains($norm,'TOTALCBM') ||
str_contains($norm,'TTLCBM') ||
str_contains($norm,'ITLCBM')
) {
$totalCbm += $val;
}
if (
str_contains($norm,'TOTALKG') ||
str_contains($norm,'TTKG')
) {
$totalKg += $val;
}
}
}
}
@endphp
@if(!$container->rows->isEmpty())
<div class="cm-total-cards-row">
<div class="cm-total-card cm-total-card-ctn">
<div class="cm-total-icon">
<i class="bi bi-box"></i>
</div>
<div class="cm-total-text">
<div class="cm-total-label">Total CTN</div>
<div class="cm-total-value">
{{ number_format($totalCtn, 0) }}
</div>
</div>
</div>
<div class="cm-total-card cm-total-card-qty">
<div class="cm-total-icon">
<i class="bi bi-check-circle"></i>
</div>
<div class="cm-total-text">
<div class="cm-total-label">Total QTY</div>
<div class="cm-total-value">
{{ number_format($totalQty, 0) }}
</div>
</div>
</div>
<div class="cm-total-card cm-total-card-cbm">
<div class="cm-total-icon">
<i class="bi bi-border-width"></i>
</div>
<div class="cm-total-text">
<div class="cm-total-label">Total CBM</div>
<div class="cm-total-value">
{{ number_format($totalCbm, 3) }}
</div>
</div>
</div>
<div class="cm-total-card cm-total-card-kg">
<div class="cm-total-icon">
<i class="bi bi-exclamation-triangle"></i>
</div>
<div class="cm-total-text">
<div class="cm-total-label">Total KG</div>
<div class="cm-total-value">
{{ number_format($totalKg, 2) }}
</div>
</div>
</div>
</div>
@endif
@if($container->rows->isEmpty())
<div class="cm-empty">No entries found for this container.</div>
@else
@php
$allHeadings = [];
foreach ($container->rows as $row) {
if (is_array($row->data)) {
$allHeadings = array_unique(array_merge($allHeadings, array_keys($row->data)));
}
}
@endphp
<div class="cm-filter-bar">
<span class="cm-row-count">
Total rows: {{ $container->rows->count() }} Edit cells then click "Save Changes".
</span>
<input type="text" id="cmRowSearch" class="cm-filter-input"
placeholder="Quick search..." onkeyup="cmFilterRows()">
</div>
<form id="cm-edit-rows-form" action="{{ route('containers.rows.update', $container->id) }}" method="POST">
@csrf
<div class="cm-table-scroll-outer">
<div class="cm-table-wrapper">
<table class="cm-table" id="cmExcelTable">
<thead>
<tr>
<th>#</th>
@foreach($allHeadings as $heading)
<th>{{ $heading }}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach($container->rows as $index => $row)
<tr>
<td class="cm-row-num">{{ $index + 1 }}</td>
@foreach($allHeadings as $heading)
@php
$value = $row->data[$heading] ?? '';
$norm = strtoupper(str_replace([' ', '/', '-', '.'],'', $heading));
$isCtn = str_contains($norm, 'CTN');
$isTotalQty = (
str_contains($norm, 'TOTALQTY') ||
str_contains($norm, 'ITLQTY') ||
str_contains($norm, 'TTLQTY')
);
$isQty = !$isTotalQty && (
str_contains($norm, 'QTY') ||
str_contains($norm, 'PCS') ||
str_contains($norm, 'PIECES')
);
$isTotalCbm = (
str_contains($norm, 'TOTALCBM') ||
str_contains($norm, 'TTLCBM') ||
str_contains($norm, 'ITLCBM')
);
$isCbm = !$isTotalCbm && str_contains($norm, 'CBM');
$isTotalKg = (
str_contains($norm, 'TOTALKG') ||
str_contains($norm, 'TTKG')
);
$isKg = !$isTotalKg && (str_contains($norm, 'KG') || str_contains($norm, 'WEIGHT'));
$isPrice = (str_contains($norm, 'PRICE') || str_contains($norm, 'RATE'));
$isAmount = (
str_contains($norm, 'AMOUNT') ||
str_contains($norm, 'TTLAMOUNT') ||
str_contains($norm, 'TOTALAMOUNT')
);
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
$isLockedByInvoice = in_array($row->row_index, $lockedRowIndexes ?? []);
$isReadOnly = $isTotalColumn || $container->status !== 'pending' || $isLockedByInvoice;
@endphp
<td>
<input
type="text"
class="cm-cell-input {{ $isReadOnly ? 'cm-cell-readonly' : '' }}"
name="rows[{{ $row->id }}][{{ $heading }}]"
value="{{ $value }}"
data-row-id="{{ $row->id }}"
data-col-key="{{ $heading }}"
data-ctn="{{ $isCtn ? '1' : '0' }}"
data-qty="{{ $isQty ? '1' : '0' }}"
data-ttlqty="{{ $isTotalQty ? '1' : '0' }}"
data-cbm="{{ $isCbm ? '1' : '0' }}"
data-ttlcbm="{{ $isTotalCbm ? '1' : '0' }}"
data-kg="{{ $isKg ? '1' : '0' }}"
data-ttlkg="{{ $isTotalKg ? '1' : '0' }}"
data-price="{{ $isPrice ? '1' : '0' }}"
data-amount="{{ $isAmount ? '1' : '0' }}"
{{ $isReadOnly ? 'readonly' : '' }}
>
</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</form>
@endif
</div>
</div>
@if(!$container->rows->isEmpty())
<button
id="cmSaveBtnFloating"
class="cm-save-btn-floating {{ $container->status !== 'pending' ? 'cm-disabled' : '' }}"
{{ $container->status !== 'pending' ? 'disabled' : '' }}
>
Save Changes
</button>
@endif
<div id="cmToast" class="cm-toast"></div>
<script>
function cmFilterRows() {
const input = document.getElementById('cmRowSearch');
if (!input) return;
const filter = input.value.toLowerCase();
const table = document.getElementById('cmExcelTable');
if (!table) return;
const rows = table.getElementsByTagName('tr');
for (let i = 1; i < rows.length; i++) {
const cells = rows[i].getElementsByTagName('td');
let match = false;
for (let j = 0; j < cells.length; j++) {
const txt = cells[j].textContent || cells[j].innerText;
if (txt.toLowerCase().indexOf(filter) > -1) {
match = true;
break;
}
}
rows[i].style.display = match ? '' : 'none';
}
}
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('cm-edit-rows-form');
const btn = document.getElementById('cmSaveBtnFloating');
const toast = document.getElementById('cmToast');
const table = document.getElementById('cmExcelTable');
function showToast(message, isError = false) {
if (!toast) return;
toast.textContent = message;
toast.style.background = isError ? '#b91c1c' : '#0f172a';
toast.classList.add('cm-show');
setTimeout(() => toast.classList.remove('cm-show'), 2500);
}
// फक्त number काढण्यासाठी helper, पण cell मध्ये format नाही करणार
function parseNumber(str) {
if (!str) return 0;
const cleaned = String(str).replace(/,/g, '').trim();
const val = parseFloat(cleaned);
return isNaN(val) ? 0 : val;
}
function recalcRow(row) {
const inputs = row.querySelectorAll('.cm-cell-input');
let ctn = 0, qty = 0, ttlQty = 0;
let cbm = 0, ttlCbm = 0;
let kg = 0, ttlKg = 0;
let price = 0, amount = 0;
let ctnInput = null,
qtyInput = null,
ttlQtyInput = null,
cbmInput = null,
ttlCbmInput = null,
kgInput = null,
ttlKgInput = null,
priceInput = null,
amountInput = null;
inputs.forEach(inp => {
const val = parseNumber(inp.value);
if (inp.dataset.ctn === '1') {
ctn = val;
ctnInput = inp;
} else if (inp.dataset.qty === '1') {
qty = val;
qtyInput = inp;
} else if (inp.dataset.ttlqty === '1') {
ttlQty = val;
ttlQtyInput = inp;
} else if (inp.dataset.cbm === '1') {
cbm = val;
cbmInput = inp;
} else if (inp.dataset.ttlcbm === '1') {
ttlCbm = val;
ttlCbmInput = inp;
} else if (inp.dataset.kg === '1') {
kg = val;
kgInput = inp;
} else if (inp.dataset.ttlkg === '1') {
ttlKg = val;
ttlKgInput = inp;
} else if (inp.dataset.price === '1') {
price = val;
priceInput = inp;
} else if (inp.dataset.amount === '1') {
amount = val;
amountInput = inp;
}
});
// इथे आपण फक्त VALUE बदलतो, कोणतंही toFixed नाही वापरत
if (ttlQtyInput && ctnInput && qtyInput) {
const newTtlQty = ctn * qty;
ttlQtyInput.value = newTtlQty === 0 ? '' : String(newTtlQty);
ttlQty = newTtlQty;
}
if (ttlCbmInput && cbmInput && ctnInput) {
const newTtlCbm = cbm * ctn;
ttlCbmInput.value = newTtlCbm === 0 ? '' : String(newTtlCbm);
ttlCbm = newTtlCbm;
}
if (ttlKgInput && kgInput && ctnInput) {
const newTtlKg = kg * ctn;
ttlKgInput.value = newTtlKg === 0 ? '' : String(newTtlKg);
ttlKg = newTtlKg;
}
if (amountInput && priceInput && ttlQtyInput) {
const newAmount = price * ttlQty;
amountInput.value = newAmount === 0 ? '' : String(newAmount);
amount = newAmount;
}
}
if (table) {
table.addEventListener('input', function (e) {
const target = e.target;
if (!target.classList.contains('cm-cell-input')) return;
if (target.classList.contains('cm-cell-readonly') || target.hasAttribute('readonly')) {
target.blur();
return;
}
const row = target.closest('tr');
if (row) {
recalcRow(row);
}
});
}
if (form && btn) {
btn.addEventListener('click', function () {
if (btn.classList.contains('cm-disabled') || btn.hasAttribute('disabled')) {
return;
}
btn.classList.add('cm-disabled');
const formData = new FormData(form);
fetch(form.action, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value
},
body: formData
})
.then(async res => {
if (!res.ok) {
const text = await res.text();
throw new Error(text || 'Failed to save');
}
return res.json().catch(() => ({}));
})
.then(() => {
showToast('Changes saved successfully.');
})
.catch(() => {
showToast('Error while saving changes.', true);
})
.finally(() => {
btn.classList.remove('cm-disabled');
});
});
}
});
</script>
@endsection

View File

@@ -10,7 +10,7 @@
body {
font-family: 'Inter', sans-serif;
overflow-x: hidden;
overflow-x: hidden; /* Prevent horizontal scroll on body */
}
.glass-card {
@@ -22,6 +22,7 @@
overflow: hidden;
}
/* New Stats Container */
.stats-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
@@ -42,7 +43,9 @@
min-height: 70px;
}
.stat-card:hover { transform: translateY(-2px); }
.stat-card:hover {
transform: translateY(-2px);
}
.stat-card.warning {
border-left-color: #f59e0b;
@@ -54,6 +57,16 @@
background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%);
}
.stat-card.danger {
border-left-color: #ef4444;
background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
}
.stat-card.info {
border-left-color: #3b82f6;
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
}
.stat-card.secondary {
border-left-color: #8b5cf6;
background: linear-gradient(135deg, #faf5ff 0%, #f3e8ff 100%);
@@ -78,19 +91,46 @@
.stat-card.warning .stat-icon {
background: rgba(245, 158, 11, 0.1);
}
.stat-card.warning .stat-icon i { color: #f59e0b; }
.stat-card.warning .stat-icon i {
color: #f59e0b;
}
.stat-card.success .stat-icon {
background: rgba(16, 185, 129, 0.1);
}
.stat-card.success .stat-icon i { color: #10b981; }
.stat-card.success .stat-icon i {
color: #10b981;
}
.stat-card.danger .stat-icon {
background: rgba(239, 68, 68, 0.1);
}
.stat-card.danger .stat-icon i {
color: #ef4444;
}
.stat-card.info .stat-icon {
background: rgba(59, 130, 246, 0.1);
}
.stat-card.info .stat-icon i {
color: #3b82f6;
}
.stat-card.secondary .stat-icon {
background: rgba(139, 92, 246, 0.1);
}
.stat-card.secondary .stat-icon i { color: #8b5cf6; }
.stat-content { flex: 1; }
.stat-card.secondary .stat-icon i {
color: #8b5cf6;
}
.stat-content {
flex: 1;
}
.stat-value {
font-size: 22px;
@@ -107,13 +147,14 @@
line-height: 1.3;
}
/* Updated Search Container - Wider with icon on left */
.search-container {
background: rgba(255, 255, 255, 0.9);
border-radius: 10px;
padding: 6px 12px;
border: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
width: 350px;
width: 350px; /* Increased width */
display: flex;
align-items: center;
}
@@ -169,6 +210,7 @@
font-family: 'Inter', sans-serif;
}
/* Updated Table Styles - Fixed horizontal scroll */
.table-glass {
background: rgba(255, 255, 255, 0.9);
border-radius: 10px;
@@ -177,6 +219,7 @@
font-family: 'Inter', sans-serif;
}
/* Single gradient for entire header - Blue to Purple */
.table thead {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
}
@@ -189,9 +232,20 @@
border: none;
font-family: 'Inter', sans-serif;
position: relative;
background: linear-gradient(135deg, #667eea 0%);
background: linear-gradient(135deg, #667eea 0%);;
}
/* Remove individual curved borders */
.table-header:first-child {
border-top-left-radius: 0;
}
.table-header:last-child {
border-top-right-radius: 0;
}
/* Apply rounded corners to the entire header container */
.table-container thead tr:first-child th:first-child {
border-top-left-radius: 10px;
}
@@ -200,6 +254,7 @@
border-top-right-radius: 10px;
}
/* Updated Table Column Alignment */
.table > :not(caption) > * > * {
padding: 14px 12px;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
@@ -208,34 +263,43 @@
vertical-align: middle;
}
.table > :not(caption) > * > *:nth-child(2),
.table > :not(caption) > * > *:nth-child(3),
.table > :not(caption) > * > *:nth-child(4),
.table > :not(caption) > * > *:nth-child(5) {
/* Center align specific columns */
.table > :not(caption) > * > *:nth-child(2), /* Customer ID */
.table > :not(caption) > * > *:nth-child(3), /* Orders */
.table > :not(caption) > * > *:nth-child(4), /* Total */
.table > :not(caption) > * > *:nth-child(5) { /* Create Date */
text-align: center;
}
.table > :not(caption) > * > *:first-child { text-align: left; }
/* Customer Info column should remain left-aligned */
.table > :not(caption) > * > *:first-child {
text-align: left;
}
.table > :not(caption) > * > *:nth-child(6),
.table > :not(caption) > * > *:nth-child(7),
.table > :not(caption) > * > *:nth-child(8),
.table > :not(caption) > * > *:nth-child(9) {
/* Status and Actions columns should remain as is */
.table > :not(caption) > * > *:nth-child(6), /* Status */
.table > :not(caption) > * > *:nth-child(7) { /* Actions */
text-align: center;
}
/* Updated header alignment to match */
.table-header:nth-child(2),
.table-header:nth-child(3),
.table-header:nth-child(4),
.table-header:nth-child(5),
.table-header:nth-child(6),
.table-header:nth-child(7),
.table-header:nth-child(8),
.table-header:nth-child(9) {
.table-header:nth-child(5) {
text-align: center;
}
/* Customer Info header stays left */
.table-header:first-child {
text-align: Center;
}
/* Status and Actions headers stay centered */
.table-header:nth-child(6),
.table-header:nth-child(7) {
text-align: center;
}
.table-header:first-child { text-align: Center; }
.customer-avatar {
width: 40px;
@@ -310,7 +374,7 @@
.customer-info-column {
min-width: 220px;
max-width: 220px;
max-width: 220px; /* Added max-width to prevent overflow */
}
.table tbody tr {
@@ -327,25 +391,31 @@
color: #6c757d;
}
/* Remove customer-stats since we're adding columns */
/* Enhanced table styling - Fixed horizontal scroll */
.table-container {
border-radius: 10px;
overflow: hidden;
width: 100%;
width: 100%; /* Ensure container takes full width */
}
/* Fix table responsiveness */
.table-responsive {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
width: 100%;
}
/* Ensure table doesn't exceed container */
.table {
width: 100%;
max-width: 100%;
margin-bottom: 0;
table-layout: auto;
table-layout: auto; /* Changed to auto for better column distribution */
}
/* Fix for search and filter section */
.search-filter-container {
display: flex;
align-items: center;
@@ -369,10 +439,11 @@
flex-shrink: 0;
}
/* New columns styling */
.orders-column, .total-column, .customer-id-column, .create-date-column {
text-align: center;
font-weight: 500;
min-width: 80px;
min-width: 80px; /* Added minimum widths for consistency */
}
.orders-count {
@@ -387,6 +458,7 @@
font-weight: 600;
}
/* ---------- Pagination Styles ---------- */
.pagination-container {
display: flex;
justify-content: space-between;
@@ -413,10 +485,14 @@
justify-content: flex-end;
}
.pagination-img-btn {
.pagination-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
@@ -424,19 +500,20 @@
justify-content: center;
min-width: 40px;
height: 32px;
padding: 0;
}
.pagination-img-btn:hover:not(:disabled) {
.pagination-btn:hover:not(:disabled) {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-img-btn:disabled {
.pagination-btn:disabled {
background: #f8fafc;
color: #cbd5e0;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.5;
opacity: 0.6;
}
.pagination-page-btn {
@@ -474,11 +551,61 @@
flex-wrap: wrap;
}
.pagination-ellipsis {
color: #9ba5bb;
font-size: 13px;
padding: 0 4px;
}
/* Image-based pagination buttons */
.pagination-img-btn {
background: #fff;
border: 1px solid #e3eaf6;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
padding: 0;
}
.pagination-img-btn:hover:not(:disabled) {
background: #1a2951;
border-color: #1a2951;
}
.pagination-img-btn:disabled {
background: #f8fafc;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.5;
}
.pagination-img-btn img {
width: 16px;
height: 16px;
filter: brightness(0) saturate(100%) invert(26%) sepia(89%) saturate(748%) hue-rotate(201deg) brightness(93%) contrast(89%);
transition: filter 0.3s ease;
}
.pagination-img-btn:hover:not(:disabled) img {
filter: brightness(0) saturate(100%) invert(100%) sepia(100%) saturate(0%) hue-rotate(288deg) brightness(106%) contrast(101%);
}
.pagination-img-btn:disabled img {
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
}
/* Mobile responsive fixes */
@media (max-width: 1200px) {
.table > :not(caption) > * > * {
padding: 12px 8px;
font-size: 13px;
}
.customer-info-column {
min-width: 180px;
max-width: 180px;
@@ -486,42 +613,65 @@
}
@media (max-width: 992px) {
.search-container { width: 280px; }
.stats-container { grid-template-columns: repeat(2, 1fr); }
.search-container {
width: 280px;
}
.stats-container {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.stats-container { grid-template-columns: repeat(2, 1fr); }
.stats-container {
grid-template-columns: repeat(2, 1fr);
}
.search-filter-container {
flex-direction: column;
align-items: stretch;
gap: 10px;
}
.search-section { justify-content: center; }
.search-container { width: 100%; }
.search-section {
justify-content: center;
}
.search-container {
width: 100%;
}
.filter-section {
justify-content: center;
flex-wrap: wrap;
}
.pagination-container {
flex-direction: column;
gap: 10px;
align-items: stretch;
}
.pagination-controls { justify-content: center; }
.pagination-controls {
justify-content: center;
}
.table > :not(caption) > * > * {
padding: 10px 6px;
font-size: 12px;
}
.customer-info-column {
min-width: 150px;
max-width: 150px;
}
.customer-avatar {
width: 32px;
height: 32px;
font-size: 0.8rem;
}
.action-btn {
width: 26px;
height: 26px;
@@ -530,12 +680,19 @@
}
@media (max-width: 576px) {
.stats-container { grid-template-columns: 1fr; }
.table-responsive { font-size: 12px; }
.stats-container {
grid-template-columns: 1fr;
}
.table-responsive {
font-size: 12px;
}
.customer-info-column {
min-width: 120px;
max-width: 120px;
}
.premium-badge,
.regular-badge,
.status-badge {
@@ -546,11 +703,14 @@
</style>
<div class="container-fluid">
<!-- Header - Removed gradient -->
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 style="color: #2c3e50; font-weight: 700; font-family: 'Inter', sans-serif;">Customer List</h4>
</div>
<!-- Stats Cards with NEW DESIGN -->
<div class="stats-container">
<!-- Total Customers -->
<div class="stat-card">
<div class="stat-icon">
<i class="bi bi-people-fill"></i>
@@ -561,6 +721,7 @@
</div>
</div>
<!-- New This Month -->
<div class="stat-card warning">
<div class="stat-icon">
<i class="bi bi-person-plus"></i>
@@ -578,6 +739,7 @@
</div>
</div>
<!-- Active Customers -->
<div class="stat-card success">
<div class="stat-icon">
<i class="bi bi-activity"></i>
@@ -593,6 +755,7 @@
</div>
</div>
<!-- Premium Customers -->
<div class="stat-card secondary">
<div class="stat-icon">
<i class="bi bi-award-fill"></i>
@@ -609,8 +772,10 @@
</div>
</div>
<!-- Search and Filter Section -->
<div class="glass-card p-3 mb-3">
<div class="search-filter-container">
<!-- Search Section - Wider with icon on left -->
<div class="search-section">
<form method="GET" action="{{ route('admin.customers.index') }}" class="d-flex align-items-center">
<div class="search-container">
@@ -627,6 +792,7 @@
</form>
</div>
<!-- Filter Section -->
<div class="filter-section">
<a href="{{ route('admin.customers.index', ['status'=>'active', 'search'=>$search ?? '']) }}"
class="filter-btn {{ ($status ?? '') == 'active' ? 'active' : '' }}">
@@ -652,6 +818,7 @@
</div>
</div>
<!-- Customer List Table -->
<div class="table-container">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
@@ -661,9 +828,8 @@
<th class="table-header">Customer ID</th>
<th class="table-header">Orders</th>
<th class="table-header">Order Total</th>
<th class="table-header">GST Amount</th>
<th class="table-header">Total Paid</th>
<th class="table-header">Remaining</th>
<th class="table-header">Total Payable</th> {{-- NEW --}}
<th class="table-header">Remaining</th> {{-- NEW --}}
<th class="table-header">Create Date</th>
<th class="table-header">Status</th>
<th class="table-header" width="100">Actions</th>
@@ -673,119 +839,113 @@
<tbody id="customersTableBody">
@forelse($customers as $c)
@php
// 1) Orders = total invoice count
$ordersCount = $c->invoices->count();
// Invoice total (with GST)
$totalPayable = $c->invoices->sum('final_amount_with_gst');
// 2) Order Total = सर्व invoices च्या charge groups चा base total (without GST)
$orderTotal = $c->invoices->sum(function($invoice) {
return $invoice->chargeGroups->sum('total_charge');
});
// Total paid via installments
$totalPaid = $c->invoices
->flatMap(fn($inv) => $inv->installments)
->sum('amount');
// 3) GST Amount = सर्व invoices च्या gst_amount चा sum
$gstTotal = $c->invoices->sum('gst_amount');
// 3) Total Payable = customer ने किती paid केले (installments sum)
$totalPaid = $c->invoices->flatMap->installments->sum('amount');
// 4) Remaining = grand_total_with_charges - paid
$grandTotal = $c->invoices->sum(function($invoice) {
$base = $invoice->chargeGroups->sum('total_charge');
$gst = (float)($invoice->gst_amount ?? 0);
return $base + $gst;
});
$remainingAmount = max($grandTotal - $totalPaid, 0);
// Remaining amount
$remainingAmount = max($totalPayable - $totalPaid, 0);
@endphp
<tr>
<td class="customer-info-column">
<div class="d-flex align-items-start">
<div class="customer-avatar me-3">
{{ strtoupper(substr($c->customer_name,0,1)) }}
</div>
<div>
<div class="fw-bold">{{ $c->customer_name }}</div>
@if($c->customer_type == 'premium')
<span class="premium-badge">Premium Customer</span>
@else
<span class="regular-badge">Regular Customer</span>
@endif
<div class="customer-details mt-1">
{{ $c->email }}<br>
{{ $c->mobile_no }}
</div>
<tr>
<!-- Customer Info Column -->
<td class="customer-info-column">
<div class="d-flex align-items-start">
<div class="customer-avatar me-3">
{{ strtoupper(substr($c->customer_name,0,1)) }}
</div>
<div>
<div class="fw-bold">{{ $c->customer_name }}</div>
@if($c->customer_type == 'premium')
<span class="premium-badge">Premium Customer</span>
@else
<span class="regular-badge">Regular Customer</span>
@endif
<div class="customer-details mt-1">
{{ $c->email }}<br>
{{ $c->mobile_no }}
</div>
</div>
</td>
</div>
</td>
<td class="customer-id-column">
<span class="fw-bold text-primary">{{ $c->customer_id }}</span>
</td>
<!-- Customer ID -->
<td class="customer-id-column">
<span class="fw-bold text-primary">{{ $c->customer_id }}</span>
</td>
<td class="orders-column">
<span class="orders-count">{{ $ordersCount }}</span>
</td>
<!-- Orders Column -->
<td class="orders-column">
<span class="orders-count">{{ $c->orders->count() }}</span>
</td>
<td class="total-column">
<span class="total-amount">
{{ number_format($orderTotal, 2) }}
<!-- Total Column -->
<td class="total-column">
<span class="total-amount">
{{ number_format($c->orders->sum('ttl_amount'), 2) }}
</span>
</td>
<td class="total-column">
<span class="total-amount">
{{ number_format($totalPayable, 2) }}
</span>
</td>
<td class="total-column">
@if($remainingAmount > 0)
<span class="text-danger fw-bold">
{{ number_format($remainingAmount, 2) }}
</span>
</td>
<td class="total-column">
<span class="total-amount">
{{ number_format($gstTotal, 2) }}
@else
<span class="text-success fw-bold">
₹0.00
</span>
</td>
@endif
</td>
<td class="total-column">
<span class="total-amount">
{{ number_format($totalPaid, 2) }}
</span>
</td>
<td class="total-column">
@if($remainingAmount > 0)
<span class="text-danger fw-bold">
{{ number_format($remainingAmount, 2) }}
</span>
@else
<span class="text-success fw-bold">
₹0.00
</span>
@endif
</td>
<td class="create-date-column">
<span class="text-muted">{{ $c->created_at ? $c->created_at->format('d-m-Y') : '-' }}</span>
</td>
<td>
@if($c->status === 'active')
<span class="status-badge active-status">Active</span>
@else
<span class="status-badge inactive-status">Inactive</span>
@endif
</td>
<!-- Create Date -->
<td class="create-date-column">
<span class="text-muted">{{ $c->created_at ? $c->created_at->format('d-m-Y') : '-' }}</span>
</td>
<td>
<div class="d-flex justify-content-center">
<a href="{{ route('admin.customers.view', $c->id) }}"
class="action-btn" title="View">
<i class="bi bi-eye"></i>
</a>
<!-- Status -->
<td>
@if($c->status === 'active')
<span class="status-badge active-status">Active</span>
@else
<span class="status-badge inactive-status">Inactive</span>
@endif
</td>
<form action="{{ route('admin.customers.status', $c->id) }}"
method="POST" style="display:inline-block;">
@csrf
<button class="action-btn" title="Toggle Status" type="submit">
<i class="bi bi-power"></i>
</button>
</form>
</div>
</td>
</tr>
<!-- Actions -->
<td>
<div class="d-flex justify-content-center">
<a href="{{ route('admin.customers.view', $c->id) }}"
class="action-btn" title="View">
<i class="bi bi-eye"></i>
</a>
<form action="{{ route('admin.customers.status', $c->id) }}"
method="POST" style="display:inline-block;">
@csrf
<button class="action-btn" title="Toggle Status" type="submit">
<i class="bi bi-power"></i>
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="10" class="text-center py-4">
<td colspan="7" class="text-center py-4">
<i class="bi bi-people display-4 text-muted d-block mb-2"></i>
<span class="text-muted">No customers found.</span>
</td>
@@ -796,12 +956,14 @@
</div>
</div>
<!-- Pagination Controls -->
<div class="pagination-container">
<div class="pagination-info" id="pageInfo">
Showing {{ $customers->firstItem() ?? 0 }} to {{ $customers->lastItem() ?? 0 }} of {{ $customers->total() }} entries
</div>
<div class="pagination-controls">
<button class="pagination-img-btn" id="prevPageBtn" title="Previous page" {{ $customers->onFirstPage() ? 'disabled' : '' }}>
<!-- Left arrow SVG -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -815,6 +977,7 @@
@endfor
</div>
<button class="pagination-img-btn" id="nextPageBtn" title="Next page" {{ $customers->hasMorePages() ? '' : 'disabled' }}>
<!-- Right arrow SVG -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -825,6 +988,7 @@
<script>
document.addEventListener('DOMContentLoaded', function() {
// Add hover effects to table rows
const tableRows = document.querySelectorAll('.table tbody tr');
tableRows.forEach(row => {
row.addEventListener('mouseenter', function() {
@@ -836,6 +1000,7 @@
});
});
// Pagination button handlers
document.getElementById('prevPageBtn').addEventListener('click', function() {
@if(!$customers->onFirstPage())
window.location.href = '{{ $customers->previousPageUrl() }}';

View File

@@ -3,6 +3,7 @@
@section('page-title', 'Customer Details')
@section('content')
<style>
:root {
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
@@ -32,12 +33,10 @@
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
45deg,
rgba(102, 126, 234, 0.03) 0%,
rgba(118, 75, 162, 0.03) 50%,
rgba(16, 185, 129, 0.03) 100%
);
background: linear-gradient(45deg,
rgba(102, 126, 234, 0.03) 0%,
rgba(118, 75, 162, 0.03) 50%,
rgba(16, 185, 129, 0.03) 100%);
pointer-events: none;
}
@@ -82,12 +81,10 @@
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(
45deg,
transparent 0%,
rgba(255, 255, 255, 0.1) 50%,
transparent 100%
);
background: linear-gradient(45deg,
transparent 0%,
rgba(255, 255, 255, 0.1) 50%,
transparent 100%);
animation: headerShimmer 8s infinite linear;
transform: rotate(45deg);
}
@@ -336,7 +333,7 @@
font-weight: 500;
}
/* Buttons */
/* Buttons - FIXED POSITIONING */
.btn-back {
background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.3);
@@ -361,12 +358,10 @@
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.2),
transparent
);
background: linear-gradient(90deg,
transparent,
rgba(255, 255, 255, 0.2),
transparent);
transition: left 0.6s ease;
}
@@ -402,12 +397,10 @@
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.2),
transparent
);
background: linear-gradient(90deg,
transparent,
rgba(255, 255, 255, 0.2),
transparent);
transition: left 0.6s ease;
}
@@ -504,7 +497,7 @@
.animation-delay-3 { animation-delay: 0.3s; }
.animation-delay-4 { animation-delay: 0.4s; }
/* Header Button Container */
/* Header Button Container - FIXED */
.header-actions {
display: flex;
align-items: center;
@@ -518,48 +511,47 @@
padding: 25px 20px;
text-align: center;
}
.customer-avatar {
width: 80px;
height: 80px;
font-size: 2rem;
}
.page-header {
padding: 20px;
}
.stats-card {
padding: 20px;
}
.stats-value {
font-size: 1.8rem;
}
.header-actions {
justify-content: center;
margin-top: 15px;
}
.page-header .row {
text-align: center;
}
}
@media (max-width: 576px) {
.btn-back,
.btn-outline-secondary {
.btn-back, .btn-outline-secondary {
padding: 10px 20px;
font-size: 0.9rem;
}
.header-actions {
flex-direction: column;
gap: 10px;
width: 100%;
}
.header-actions a {
width: 100%;
justify-content: center;
@@ -568,7 +560,8 @@
</style>
<div class="container py-4">
{{-- HEADER --}}
{{-- HEADER - FIXED BUTTON POSITION --}}
<div class="page-header animate-fade-in">
<div class="row align-items-center">
<div class="col-md-8">
@@ -593,7 +586,7 @@
<div class="col-auto">
<div class="avatar-container">
<div class="customer-avatar">
{{ strtoupper(substr($customer->customer_name, 0, 1)) }}
{{ strtoupper(substr($customer->customer_name,0,1)) }}
</div>
<div class="avatar-status {{ $customer->status == 'active' ? 'status-active' : 'status-inactive' }}"></div>
</div>
@@ -710,28 +703,43 @@
</div>
</div>
{{-- Total Payable --}}
<!-- {{-- Total Payable --}}
<div class="stats-card amount">
<div class="stats-icon">
<i class="bi bi-wallet2"></i>
</div>
<div class="stats-value">{{ number_format($totalPayable, 2) }}</div>
<div class="stats-label">Total Payable</div>
</div>
{{-- Total Remaining --}}
<div class="stats-card marks">
<div class="stats-icon">
<i class="bi bi-exclamation-circle"></i>
</div>
<div class="stats-value">{{ number_format($totalRemaining, 2) }}</div>
<div class="stats-label">Remaining Amount</div>
</div> -->
<div class="col-md-4 animate-fade-in animation-delay-3">
<div class="stats-card marks">
<div class="stats-icon">
<i class="bi bi-wallet2"></i>
<i class="bi bi-hash"></i>
</div>
<div class="stats-value">{{ number_format($totalPayable, 2) }}</div>
<div class="stats-label">Total Payable</div>
</div>
</div>
{{-- Remaining Amount --}}
<div class="col-md-4 animate-fade-in animation-delay-3">
<div class="stats-card marks">
<div class="stats-icon">
<i class="bi bi-exclamation-circle"></i>
<i class="bi bi-hash"></i>
</div>
<div class="stats-value">{{ number_format($totalRemaining, 2) }}</div>
<div class="stats-label">Remaining Amount</div>
</div>
</div>
{{-- Mark Count --}}
<div class="col-md-4 animate-fade-in animation-delay-3">
<div class="stats-card marks">
@@ -753,7 +761,7 @@
<span class="badge bg-primary ms-2">{{ $customer->marks->count() }}</span>
</h5>
</div>
<div class="section-body">
@if($customer->marks->count() == 0)
<div class="text-center py-5">
@@ -775,23 +783,20 @@
@endif
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
document.addEventListener('DOMContentLoaded', function() {
// Add hover effects to interactive elements
const interactiveElements = document.querySelectorAll('.info-card, .stats-card, .mark-item');
interactiveElements.forEach(element => {
element.addEventListener('mouseenter', function () {
if (this.classList.contains('mark-item')) {
this.style.transform = 'translateX(5px)';
} else {
this.style.transform = 'translateY(-5px)';
}
element.addEventListener('mouseenter', function() {
this.style.transform = this.classList.contains('mark-item') ? 'translateX(5px)' : 'translateY(-5px)';
});
element.addEventListener('mouseleave', function () {
element.addEventListener('mouseleave', function() {
this.style.transform = 'translateY(0)';
});
});
@@ -801,7 +806,7 @@ document.addEventListener('DOMContentLoaded', function () {
statsValues.forEach(value => {
const originalText = value.textContent;
value.textContent = '0';
setTimeout(() => {
value.textContent = originalText;
value.style.transform = 'scale(1.1)';
@@ -812,4 +817,5 @@ document.addEventListener('DOMContentLoaded', function () {
});
});
</script>
@endsection
@endsection

File diff suppressed because it is too large Load Diff

View File

@@ -8,10 +8,10 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
/*Remove horizontal scroll bar*/
/* html, body {
html, body {
overflow-x: hidden !important;
font-family: 'Inter', sans-serif !important;
} */
}
/* Invoice Management Styles */
.invoice-management-box {
@@ -27,7 +27,7 @@
justify-content: space-between;
align-items: center;
border-radius: 17px 17px 0 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: #fceeb8ff;
min-height: 54px;
padding: 15px 26px 10px 22px;
border-bottom: 1.4px solid #e8e2cf;
@@ -37,7 +37,7 @@
.invoice-management-title {
font-size: 1.32rem;
font-weight: 800;
color: #ffffffff;
color: #2451af;
letter-spacing: .08em;
display: flex;
align-items: center;
@@ -50,50 +50,43 @@
color: #336ad3;
}
/* ===== UPDATED FILTER BAR WITH DATE RANGE ===== */
/* Tools Row Styling */
.invoice-tools-row {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 16px 24px;
border-bottom: 1px solid rgba(255,255,255,0.1);
box-shadow: 0 10px 25px -5px rgba(99, 102, 241, 0.3);
position: relative;
border-radius: 17px 17px 0 0;
top:5px;
}
.filter-bar-container {
background: linear-gradient(135deg, #f8fafc 0%, #edf2f7 100%);
padding: 25px 30px;
border-bottom: 1px solid #e2e8f0;
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
gap: 20px;
flex-wrap: wrap;
width: 100%;
}
/* Search Box Styling */
.search-box {
display: flex;
align-items: center;
background: white;
border-radius: 50px;
padding: 8px 20px;
box-shadow: 0 4px 15px rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.5);
border: 1px solid rgba(255,255,255,0.3);
flex: 2;
min-width: 250px;
border-radius: 12px;
padding: 10px 18px;
box-shadow: 0 4px 15px rgba(0,0,0,0.08);
border: 1.5px solid #e2e8f0;
min-width: 380px;
flex: 1;
max-width: 500px;
transition: all 0.3s ease;
font-family: 'Inter', sans-serif;
}
.search-box:focus-within {
box-shadow: 0 8px 20px rgba(79, 70, 229, 0.25);
border-color: #3b82f6;
box-shadow: 0 4px 20px rgba(59, 130, 246, 0.15);
transform: translateY(-2px);
border-color: white;
}
.search-box i {
color: #667eea;
color: #64748b;
margin-right: 12px;
font-size: 1.1rem;
font-size: 1.2rem;
}
.search-box input {
@@ -102,195 +95,92 @@
background: transparent;
width: 100%;
font-size: 15px;
color: #1f2937;
color: #334155;
font-family: 'Inter', sans-serif;
font-weight: 400;
}
.search-box input::placeholder {
color: #9ca3af;
color: #94a3b8;
font-family: 'Inter', sans-serif;
font-weight: 300;
}
/* Filter Selects Styling */
.filter-select-wrapper {
flex: 1;
min-width: 140px;
}
.filter-select {
width: 100%;
background: white;
border: 1px solid rgba(255,255,255,0.5);
border-radius: 50px;
padding: 10px 20px;
font-size: 14px;
color: #1f2937;
outline: none;
box-shadow: 0 4px 10px rgba(0,0,0,0.05);
font-family: 'Inter', sans-serif;
font-weight: 400;
transition: all 0.3s ease;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23667eea' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 15px center;
background-size: 16px;
}
.filter-select:hover {
background-color: white;
border-color: white;
}
.filter-select:focus {
border-color: white;
box-shadow: 0 0 0 3px rgba(255,255,255,0.2);
background-color: white;
}
/* ===== NEW: Status dropdown option colors ===== */
.filter-select option[value="paid"] {
background-color: #d1fae5;
color: #065f46;
font-weight: 500;
}
.filter-select option[value="pending"] {
background-color: #fef3c7;
color: #d97706;
font-weight: 500;
}
.filter-select option[value="overdue"] {
background-color: #e9d5ff;
color: #6b21a8;
font-weight: 500;
}
.filter-select option[value="paying"] {
background-color: #e0e7ff;
color: #4338ca;
font-weight: 500;
}
.filter-select option[value="all"] {
background-color: white;
color: #1f2937;
}
/* For Firefox compatibility */
.filter-select option {
padding: 8px;
}
/* ===== END status dropdown option colors ===== */
/* Date Range Styling */
.date-range-wrapper {
flex: 1.5;
min-width: 280px;
display: flex;
align-items: center;
gap: 8px;
/* background: rgba(255, 255, 255, 0.2); */
padding: 5px 12px;
border-radius: 50px;
backdrop-filter: blur(5px);
}
.date-input-container {
display: flex;
align-items: center;
background: white;
border-radius: 50px;
padding: 5px 12px;
flex: 1;
border: 1px solid rgba(255,255,255,0.5);
}
.date-input-container i {
color: #667eea;
margin-right: 6px;
font-size: 0.9rem;
}
.date-input {
/* .search-btn {
background: linear-gradient(90deg, #226ad6, #46b4fd 123%);
border: none;
outline: none;
background: transparent;
width: 100%;
font-size: 13px;
color: #1f2937;
font-family: 'Inter', sans-serif;
padding: 5px 0;
}
.date-input::placeholder {
color: #9ca3af;
font-size: 12px;
}
.date-separator {
border-radius: 8px;
padding: 5px 10px;
color: white;
font-weight: 600;
font-size: 14px;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
margin-left: 3px;
font-family: 'Inter', sans-serif;
white-space: nowrap;
} */
.search-btn:hover {
background: linear-gradient(90deg, #3264f8, #3acfff 140%);
box-shadow: 0 4px 15px rgba(59, 130, 246, 0.4);
transform: translateY(-1px);
}
/* Responsive Design */
@media (max-width: 1100px) {
.filter-bar-container {
gap: 12px;
}
.search-box {
min-width: 200px;
}
.date-range-wrapper {
min-width: 260px;
}
.filter-group {
display: flex;
align-items: center;
gap: 15px;
flex-wrap: wrap;
}
@media (max-width: 768px) {
.invoice-tools-row {
padding: 16px;
}
.filter-bar-container {
flex-direction: column;
align-items: stretch;
}
.search-box {
min-width: 100%;
}
.filter-select-wrapper {
min-width: 100%;
}
.date-range-wrapper {
min-width: 100%;
flex-direction: column;
background: transparent;
backdrop-filter: none;
padding: 0;
gap: 8px;
}
.date-input-container {
width: 100%;
}
.date-separator {
display: none;
}
.filter-select {
background: white;
border: 1.5px solid #e2e8f0;
border-radius: 10px;
padding: 10px 15px;
font-size: 14px;
color: #334155;
outline: none;
min-width: 160px;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
font-family: 'Inter', sans-serif;
font-weight: 400;
transition: all 0.3s ease;
}
.filter-select:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.create-invoice-btn {
background: linear-gradient(90deg, #226ad6, #46b4fd 123%);
padding: 12px 28px;
font-weight: 600;
border: none;
border-radius: 10px;
color: #fff;
font-size: 15px;
box-shadow: 0 2px 13px #dde7fa42;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
font-family: 'Inter', sans-serif;
text-decoration: none;
white-space: nowrap;
}
.create-invoice-btn:hover {
background: linear-gradient(90deg, #3264f8, #3acfff 140%);
box-shadow: 0 4px 25px #5ab8f880;
color: #fff;
text-decoration: none;
transform: translateY(-2px);
}
/* ===== END UPDATED FILTER BAR ===== */
.invoice-management-main {
background: #fff;
@@ -333,7 +223,7 @@
/* Center all table content */
.table thead tr {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: #feebbe !important;
}
.table thead th:first-child {
@@ -347,7 +237,7 @@
background: transparent !important;
border: none;
font-weight: 700;
color: #ffffffff;
color: #343535;
letter-spacing: 0.02em;
font-size: 14px;
padding: 20px 15px;
@@ -368,33 +258,25 @@
/* Soft blue background for ALL table rows */
.table-striped tbody tr {
background: #f0f8ff !important;
transition: all 0.15s ease;
border-radius: 6px;
box-shadow: 0 1px 4px rgba(0,0,0,0.05);
}
.table-striped tbody tr td {
padding: 6px 10px;
font-size: 14px;
}
.table-striped tbody tr:hover {
background: #e6f3ff !important;
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
transform: translateY(-0.5px);
}
background: #f0f8ff !important;
transition: all 0.2s ease;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.table-striped tbody tr:hover {
background: #e6f3ff !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-1px);
}
/* Remove striped pattern - all rows same soft blue */
.table-striped tbody tr:nth-of-type(odd),
.table-striped tbody tr:nth-of-type(even) {
background: #f0f8ff !important;
}
/* Center all table cells with proper spacing */
.table td {
padding: 18px 15px;
border: none;
@@ -409,7 +291,7 @@
font-weight: 400;
}
/* First and last cell rounded corners */
.table td:first-child {
padding-left: 30px;
font-weight: 600;
@@ -541,10 +423,6 @@
background: url('/images/status-bg-overdue.png') !important;
}
.badge-paying {
background: url('/images/status-bg-paying.png') !important;
}
/* Fallback colors if images don't load - ALL WITH SAME SIZE */
.badge.badge-paid {
background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important;
@@ -565,13 +443,6 @@
border-color: #8b5cf6 !important;
}
.badge.badge-paying {
background: linear-gradient(135deg, #e0e7ff, #c7d2fe) !important;
color: #4338ca !important;
border-color: #6366f1 !important;
}
/* Entry Button - Centered */
.btn-entry {
background: linear-gradient(135deg, #3492f8 55%, #1256cc 110%);
@@ -690,6 +561,37 @@
overflow-y: auto;
}
/* Date Range Picker Styles */
.date-range-container {
display: flex;
align-items: center;
gap: 10px;
}
.date-input {
background: white;
border: 1.5px solid #e2e8f0;
border-radius: 8px;
padding: 10px 12px;
font-size: 14px;
color: #334155;
outline: none;
min-width: 140px;
box-shadow: 0 2px 5px rgba(0,0,0,0.04);
font-family: 'Inter', sans-serif;
}
.date-input:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.date-separator {
color: #64748b;
font-weight: 500;
font-family: 'Inter', sans-serif;
}
/* Stats Summary - Centered */
.stats-summary {
display: grid;
@@ -1040,6 +942,17 @@
/* Responsive Design */
@media (max-width: 1200px) {
.invoice-tools-row {
flex-direction: column;
align-items: stretch;
gap: 20px;
}
.search-box {
max-width: 100%;
min-width: auto;
}
.filter-group {
justify-content: space-between;
width: 100%;
@@ -1057,6 +970,10 @@
padding: 15px 20px;
}
.invoice-tools-row {
padding: 20px;
}
.table th, .table td {
font-size: 13px;
padding: 15px 10px;
@@ -1078,6 +995,21 @@
font-size: 12px;
}
.filter-group {
flex-direction: column;
align-items: stretch;
}
.filter-select {
min-width: auto;
width: 100%;
}
.create-invoice-btn {
width: 100%;
justify-content: center;
}
.invoice-icon {
width: 32px;
height: 32px;
@@ -1093,6 +1025,15 @@
display: block;
}
.date-range-container {
flex-direction: column;
width: 100%;
}
.date-input {
width: 100%;
}
.stats-summary {
grid-template-columns: 1fr;
margin: 20px;
@@ -1130,6 +1071,11 @@
min-width: auto;
padding: 10px 15px;
}
.search-btn {
padding: 8px 15px;
font-size: 13px;
}
}
</style>
@@ -1142,58 +1088,39 @@
</span>
</div>
<!-- UPDATED FILTER BAR WITH DATE RANGE -->
<!-- TOOLS ROW - Search, Filter, Create Button -->
<div class="invoice-tools-row">
<form method="GET" action="{{ route('admin.invoices.index') }}" style="width: 100%;">
<div class="filter-bar-container">
<!-- Search Box -->
<div class="search-box">
<i class="bi bi-search"></i>
<input type="text"
id="searchInput"
name="search"
value="{{ request('search') }}"
placeholder="Search Invoices...">
</div>
<!-- Status Filter -->
<div class="filter-select-wrapper">
<select class="filter-select" id="statusFilter" name="status">
<option value="all" {{ request('status')=='all' ? 'selected' : '' }}>All Status</option>
<option value="paid" {{ request('status')=='paid' ? 'selected' : '' }}>Paid</option>
<option value="pending" {{ request('status')=='pending' ? 'selected' : '' }}>Pending</option>
<option value="overdue" {{ request('status')=='overdue' ? 'selected' : '' }}>Overdue</option>
</select>
</div>
<!-- Date Range Filter (FROM - TO) with dd-mm-yyyy format -->
<div class="date-range-wrapper">
<div class="date-input-container">
<i class="bi bi-calendar3"></i>
<input type="text"
class="date-input"
id="startDate"
name="start_date"
value="{{ request('start_date') ? date('d-m-Y', strtotime(request('start_date'))) : '' }}"
placeholder="dd-mm-yyyy"
onfocus="(this.type='date')"
onblur="if(!this.value) this.type='text'">
</div>
<span class="date-separator">to</span>
<div class="date-input-container">
<i class="bi bi-calendar3"></i>
<input type="text"
class="date-input"
id="endDate"
name="end_date"
value="{{ request('end_date') ? date('d-m-Y', strtotime(request('end_date'))) : '' }}"
placeholder="dd-mm-yyyy"
onfocus="(this.type='date')"
onblur="if(!this.value) this.type='text'">
</div>
</div>
<!-- Search Box with Button -->
<div style="display: flex; align-items: center; flex: 1; max-width: 500px; min-width: 380px;">
<div class="search-box">
<i class="bi bi-search"></i>
<input type="text" id="invoiceSearch" placeholder="Search by invoice number, customer name...">
</div>
</form>
</button>
</div>
<!-- Filter Group -->
<div class="filter-group">
<select class="filter-select" id="statusFilter">
<option value="">All Status</option>
<option value="paid">Paid</option>
<option value="pending">Pending</option>
<option value="overdue">Overdue</option>
</select>
<!-- Date Range Picker -->
<div class="date-range-container">
<input type="date" class="date-input" id="startDate">
<span class="date-separator">to</span>
<input type="date" class="date-input" id="endDate">
</div>
</div>
<!-- Create Invoice Button -->
<!-- <a href="{{ route('admin.invoices.create') }}" class="create-invoice-btn">
<i class="bi bi-plus-circle"></i> Create Invoice
</a> -->
</div>
<div class="invoice-management-main no-extra-space">
@@ -1229,110 +1156,77 @@
<div class="card-body p-0">
<div class="table-container">
<table class="table table-striped align-middle" id="invoicesTable">
<thead class="table-light">
<tr>
<th class="column-header">#</th>
<th class="column-header">Invoice Number</th>
<th class="column-header">Customer</th>
<th class="column-header">Container</th> {{-- NEW --}}
<th class="column-header">Final Amount</th>
<th class="column-header">GST Amount</th>
<th class="column-header">Total w/GST</th>
<th class="column-header">Status</th>
<th class="column-header">Invoice Date</th>
<th class="column-header">Due Date</th>
<th class="column-header">Action</th>
</tr>
</thead>
<thead class="table-light">
<tr>
<th class="column-header">#</th>
<th class="column-header">Invoice Number</th>
<th class="column-header">Customer</th>
<th class="column-header">Final Amount</th>
<th class="column-header">GST %</th>
<th class="column-header">Total w/GST</th>
<th class="column-header">Status</th>
<th class="column-header">Invoice Date</th>
<th class="column-header">Due Date</th>
<th class="column-header">Action</th>
</tr>
</thead>
<tbody id="invoicesTableBody">
@php
$totalInvoices = $invoices->count();
$sortedInvoices = $invoices->sortByDesc('created_at'); // Latest first
@endphp
@forelse($sortedInvoices as $i => $invoice)
<tr>
<td>{{ $totalInvoices - $i }}</td>
<tbody id="invoicesTableBody">
@php
$totalInvoices = $invoices->count();
$sortedInvoices = $invoices->sortByDesc('created_at'); // Latest first
@endphp
@forelse($sortedInvoices as $i => $invoice)
<tr>
<td>{{ $totalInvoices - $i }}</td>
<td>
<div class="invoice-number-cell">
<div class="invoice-icon invoice-icon-{{ (($totalInvoices - $i) % 8) + 1 }}">
<i class="bi bi-file-earmark-text"></i>
</div>
<a href="#" class="invoice-number-link open-invoice-popup" data-id="{{ $invoice->id }}">
{{ $invoice->invoice_number }}
</a>
</div>
</td>
<td>
<div class="invoice-number-cell">
<div class="invoice-icon invoice-icon-{{ (($totalInvoices - $i) % 8) + 1 }}">
<i class="bi bi-file-earmark-text"></i>
</div>
<a href="#" class="invoice-number-link open-invoice-popup" data-id="{{ $invoice->id }}">
{{ $invoice->invoice_number }}
</a>
</div>
</td>
<td class="customer-cell">
{{ $invoice->customer_name }}
</td>
<td class="customer-cell">{{ $invoice->customer_name }}</td>
{{-- NEW: Container column --}}
<td class="customer-cell">
@if($invoice->container)
{{ $invoice->container->container_number }}
{{-- जर फक्त ID हवी असेल तर:
#{{ $invoice->container->id }}
--}}
@else
@endif
</td>
<td class="amount-cell">{{ number_format($invoice->final_amount, 2) }}</td>
<td class="gst-cell">{{ $invoice->gst_percent }}%</td>
<td class="amount-cell">{{ number_format($invoice->final_amount_with_gst, 2) }}</td>
<td class="amount-cell">
{{ number_format($invoice->final_amount, 2) }}
</td>
<td>
<span class="badge badge-{{ $invoice->status }}">
@if($invoice->status == 'paid')
<i class="bi bi-check-circle-fill status-icon"></i>
@elseif($invoice->status == 'pending')
<i class="bi bi-clock-fill status-icon"></i>
@elseif($invoice->status == 'overdue')
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
@endif
{{ ucfirst($invoice->status) }}
</span>
</td>
<td class="amount-cell">
{{ number_format($invoice->gst_amount, 2) }}
</td>
<td class="amount-cell">
{{ number_format($invoice->final_amount_with_gst, 2) }}
</td>
<td>
<span class="badge badge-{{ $invoice->status }}">
@if($invoice->status == 'paid')
<i class="bi bi-check-circle-fill status-icon"></i>
@elseif($invoice->status == 'pending')
<i class="bi bi-clock-fill status-icon"></i>
@elseif($invoice->status == 'paying')
<i class="bi bi-arrow-repeat status-icon"></i> {{-- processing icon --}}
@elseif($invoice->status == 'overdue')
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
@endif
{{ ucfirst($invoice->status) }}
</span>
</td>
<td class="date-cell">
{{ $invoice->invoice_date }}
</td>
<td class="date-cell">
{{ $invoice->due_date }}
</td>
<td>
<a href="{{ route('admin.invoices.edit', $invoice->id) }}"
class="btn-entry">
<i class="bi bi-pencil"></i> Entry
</a>
</td>
</tr>
@empty
<tr>
{{-- 1 new column वाढवलाय म्हणून colspan 11 --}}
<td colspan="11" class="text-muted">No invoices found</td>
</tr>
@endforelse
</tbody>
<td class="date-cell">{{ $invoice->invoice_date }}</td>
<td class="date-cell">{{ $invoice->due_date }}</td>
<td>
<a href="{{ route('admin.invoices.edit', $invoice->id) }}"
class="btn-entry">
<i class="bi bi-pencil"></i> Entry
</a>
</td>
</tr>
@empty
<tr>
<td colspan="10" class="text-muted">No invoices found</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
@@ -1359,14 +1253,11 @@
<i class="bi bi-check-circle-fill status-icon"></i>
@elseif($invoice->status == 'pending')
<i class="bi bi-clock-fill status-icon"></i>
@elseif($invoice->status == 'paying')
<i class="bi bi-arrow-repeat status-icon"></i>
@elseif($invoice->status == 'overdue')
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
@endif
{{ ucfirst($invoice->status) }}
</span>
</div>
<div class="mobile-invoice-details">
@@ -1379,8 +1270,8 @@
<span class="mobile-detail-value">{{ number_format($invoice->final_amount, 2) }}</span>
</div>
<div class="mobile-detail-item">
<span class="mobile-detail-label">GST Amount</span>
<span class="mobile-detail-value">{{ number_format($invoice->gst_amount, 2) }}</span>
<span class="mobile-detail-label">GST</span>
<span class="mobile-detail-value">{{ $invoice->gst_percent }}%</span>
</div>
<div class="mobile-detail-item">
<span class="mobile-detail-label">Total</span>
@@ -1450,6 +1341,7 @@
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Pagination state
@@ -1496,28 +1388,18 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
// Function to parse dd-mm-yyyy to Date object
function parseDate(dateStr) {
if (!dateStr) return null;
const parts = dateStr.split('-');
if (parts.length === 3) {
// dd-mm-yyyy format
return new Date(parts[2], parts[1] - 1, parts[0]);
}
return new Date(dateStr);
}
// Search and filter functionality
const searchInput = document.getElementById('searchInput');
// Search and filter functionality for both desktop and mobile
const searchInput = document.getElementById('invoiceSearch');
const statusFilter = document.getElementById('statusFilter');
const startDate = document.getElementById('startDate');
const endDate = document.getElementById('endDate');
const startDateInput = document.getElementById('startDate');
const endDateInput = document.getElementById('endDate');
const searchButton = document.getElementById('searchButton');
function filterInvoices() {
const searchTerm = searchInput.value.toLowerCase();
const statusValue = statusFilter.value;
const startDateValue = startDate.value;
const endDateValue = endDate.value;
const startDate = startDateInput.value;
const endDate = endDateInput.value;
filteredInvoices = allInvoices.filter(invoice => {
let include = true;
@@ -1531,23 +1413,18 @@ document.addEventListener('DOMContentLoaded', function() {
}
// Status filter
if (statusValue && statusValue !== 'all' && invoice.status !== statusValue) {
if (statusValue && invoice.status !== statusValue) {
include = false;
}
// Date range filter
if (startDateValue || endDateValue) {
const invoiceDate = parseDate(invoice.invoice_date);
// Date filter
if (startDate || endDate) {
const invoiceDate = new Date(invoice.invoice_date);
const start = startDate ? new Date(startDate) : null;
const end = endDate ? new Date(endDate) : null;
if (startDateValue) {
const start = parseDate(startDateValue);
if (invoiceDate < start) include = false;
}
if (endDateValue) {
const end = parseDate(endDateValue);
if (invoiceDate > end) include = false;
}
if (start && invoiceDate < start) include = false;
if (end && invoiceDate > end) include = false;
}
return include;
@@ -1560,14 +1437,14 @@ document.addEventListener('DOMContentLoaded', function() {
// Add event listeners for filtering
searchInput.addEventListener('input', filterInvoices);
searchButton.addEventListener('click', filterInvoices);
statusFilter.addEventListener('change', filterInvoices);
startDate.addEventListener('change', filterInvoices);
endDate.addEventListener('change', filterInvoices);
startDateInput.addEventListener('change', filterInvoices);
endDateInput.addEventListener('change', filterInvoices);
// Also trigger search on Enter key in search input
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
filterInvoices();
}
});
@@ -1657,8 +1534,7 @@ document.addEventListener('DOMContentLoaded', function() {
const mobileContainer = document.getElementById('mobileInvoicesContainer');
if (filteredInvoices.length === 0) {
// 1 column वाढवल्यामुळे colspan 11
tbody.innerHTML = '<tr><td colspan="11" class="text-muted">No invoices found</td></tr>';
tbody.innerHTML = '<tr><td colspan="10" class="text-muted">No invoices found</td></tr>';
mobileContainer.innerHTML = '<div class="text-muted text-center py-4">No invoices found</div>';
return;
}
@@ -1689,19 +1565,14 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</td>
<td class="customer-cell">${invoice.customer_name}</td>
<!-- NEW: Container column -->
<td class="customer-cell">
${invoice.container ? (invoice.container.container_number ?? '—') : '—'}
</td>
<td class="amount-cell">${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
<td class="amount-cell">${parseFloat(invoice.gst_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
<td class="gst-cell">${invoice.gst_percent}%</td>
<td class="amount-cell">${parseFloat(invoice.final_amount_with_gst).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
<td>
<span class="badge badge-${invoice.status}">
${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''}
${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''}
${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''}
${invoice.status === 'paying' ? '<i class="bi bi-arrow-repeat status-icon"></i>' : ''}
${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
</span>
</td>
@@ -1735,7 +1606,6 @@ document.addEventListener('DOMContentLoaded', function() {
${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''}
${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''}
${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''}
${invoice.status === 'paying' ? '<i class="bi bi-arrow-repeat status-icon"></i>' : ''}
${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
</span>
</div>
@@ -1744,20 +1614,13 @@ document.addEventListener('DOMContentLoaded', function() {
<span class="mobile-detail-label">Customer</span>
<span class="mobile-detail-value">${invoice.customer_name}</span>
</div>
<!-- NEW: Container for mobile -->
<div class="mobile-detail-item">
<span class="mobile-detail-label">Container</span>
<span class="mobile-detail-value">
${invoice.container ? (invoice.container.container_number ?? '—') : '—'}
</span>
</div>
<div class="mobile-detail-item">
<span class="mobile-detail-label">Amount</span>
<span class="mobile-detail-value">${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</span>
</div>
<div class="mobile-detail-item">
<span class="mobile-detail-label">GST Amount</span>
<span class="mobile-detail-value">${parseFloat(invoice.gst_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</span>
<span class="mobile-detail-label">GST</span>
<span class="mobile-detail-value">${invoice.gst_percent}%</span>
</div>
<div class="mobile-detail-item">
<span class="mobile-detail-label">Total</span>

File diff suppressed because it is too large Load Diff

View File

@@ -264,24 +264,13 @@ header .bi-bell .badge {
</a>
@endcan
<!-- {{-- Shipments --}}
{{-- Shipments --}}
@can('shipment.view')
<a href="{{ route('admin.shipments') }}" class="{{ request()->routeIs('admin.shipments') ? 'active' : '' }}">
<i class="bi bi-truck"></i> Shipments
</a>
@endcan -->
@endcan
{{-- Container NEW MENU --}}
@can('container.view')
<a href="{{ route('containers.index') }}" class="{{ request()->routeIs('containers.*') ? 'active' : '' }}">
<i class="fa-solid fa-box"></i> Container
</a>
@endcan
{{-- Invoice --}}
@can('invoice.view')
<a href="{{ route('admin.invoices.index') }}" class="{{ request()->routeIs('admin.invoices.index') ? 'active' : '' }}">

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,623 +1,272 @@
<!DOCTYPE html>
<html lang="en">
<html>
<head>
<meta charset="UTF-8">
<title>Invoice #{{ $invoice->invoice_number }}</title>
<title>{{ $invoice->invoice_number }}</title>
<style>
body {
font-family: 'DejaVu Sans', sans-serif;
font-size: 10px;
color: #1a202c;
line-height: 1.4;
font-family: 'Segoe UI', 'Roboto', Arial, sans-serif;
background: #F7FBFC;
color: #1A222B;
margin: 0;
padding: 18px;
background: #fff;
}
/* ── TOP HEADER ── */
.top-header {
margin-bottom: 8px;
border-bottom: 2px solid #1a202c;
padding-bottom: 6px;
}
.header-table {
width: 100%;
border-collapse: collapse;
}
.header-table td {
vertical-align: middle;
padding: 0;
font-size: 15px;
}
/* Logo cell — fixed width, logo fits inside */
.logo-cell {
width: 60px;
.container {
max-width: 850px;
margin: 24px auto 0 auto;
background: #fff;
border-radius: 13px;
box-shadow: 0 2px 14px rgba(40,105,160,0.08);
padding: 35px 32px 18px 32px;
}
.logo-cell img {
width: 55px;
height: 55px;
display: block;
.header {
display: flex;
justify-content: space-between;
align-items: flex-start;
border-bottom: 2px solid #E6EBF0;
padding-bottom: 13px;
}
/* Company name cell */
.title-cell {
padding-left: 10px;
text-align: left;
.logo-company {
display: flex;
align-items: flex-start;
}
.company-name {
font-size: 18px;
.logo {
height: 50px;
margin-right: 13px;
}
.company-details {
margin-top: 0;
font-size: 15px;
}
.company-title {
font-size: 21px;
font-weight: bold;
color: #1a202c;
text-transform: uppercase;
letter-spacing: 2px;
}
.company-subtitle {
font-size: 11px;
font-weight: bold;
color: #2d3748;
letter-spacing: 1px;
}
.company-tagline {
font-size: 8px;
color: #718096;
margin-top: 3px;
}
/* ── INFO TABLE (Customer | Invoice side by side) ── */
.info-outer {
width: 100%;
border-collapse: collapse;
border: 1px solid #cbd5e0;
margin-bottom: 10px;
}
.info-outer td {
vertical-align: top;
padding: 10px 12px;
}
.info-left { width: 50%; }
.info-right { width: 50%; border-left: 1px solid #cbd5e0; }
.section-title {
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #718096;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 3px;
margin-bottom: 5px;
}
.info-line {
font-size: 9px;
color: #2d3748;
margin-bottom: 2px;
line-height: 1.5;
}
/* ── STATUS BADGE ── */
.badge {
display: inline;
padding: 2px 6px;
font-size: 8px;
.company-sub {
font-size: 16px;
margin-bottom: 8px;
font-weight: 500;
}
.invoice-details {
text-align: right;
min-width: 220px;
}
.invoice-title {
font-weight: bold;
text-transform: uppercase;
border-radius: 3px;
font-size: 23px;
letter-spacing: 0.5px;
margin-bottom: 2px;
}
.badge-paid { background: #c6f6d5; color: #22543d; }
.badge-pending { background: #fef3c7; color: #92400e; }
.badge-paying { background: #dbeafe; color: #1e40af; }
.badge-overdue { background: #fee2e2; color: #991b1b; }
/* ── BILL TO ── */
.bill-to-box {
border: 1px solid #cbd5e0;
padding: 8px 12px;
margin-bottom: 10px;
background: #f7fafc;
.paid-label {
margin-top: 8px;
margin-bottom: 2px;
}
.bill-to-label {
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #718096;
margin-bottom: 4px;
}
.bill-name { font-size: 12px; font-weight: bold; color: #1a202c; margin-bottom: 2px; }
.bill-sub { font-size: 10px; font-weight: bold; color: #2d3748; margin-bottom: 2px; }
.bill-detail { font-size: 9px; color: #4a5568; line-height: 1.8; }
/* ── SUMMARY GRID — 4 boxes per row ── */
.summary-wrap { margin-bottom: 12px; }
.summary-label {
font-size: 9px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #4a5568;
margin-bottom: 4px;
}
.summary-outer {
width: 100%;
border-collapse: collapse;
}
.sbox {
width: 25%;
border: 1px solid #cbd5e0;
padding: 7px 8px;
text-align: center;
vertical-align: middle;
}
.sbox-row1 { background: #ffffff; }
.sbox-row2 { background: #f7fafc; }
.sbox-lbl {
font-size: 7px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #718096;
margin-bottom: 3px;
}
.sbox-val {
font-size: 11px;
font-weight: bold;
color: #1a202c;
}
.sbox-sub {
font-size: 7px;
color: #a0aec0;
margin-top: 1px;
}
/* ── CHARGE GROUPS ── */
.cg-header {
font-size: 10px;
font-weight: bold;
color: #ffffff;
background: #2d3748;
padding: 5px 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
.cg-group-wrap {
border: 1px solid #cbd5e0;
margin-bottom: 10px;
}
.cg-sum-table {
width: 100%;
border-collapse: collapse;
font-size: 9px;
}
.cg-sum-table th {
background: #edf2f7;
padding: 5px;
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
color: #4a5568;
border: 1px solid #cbd5e0;
text-align: left;
}
.cg-sum-table td {
padding: 5px;
border: 1px solid #cbd5e0;
color: #2d3748;
font-size: 9px;
}
.cg-item-table {
width: 100%;
border-collapse: collapse;
font-size: 8px;
}
.cg-item-table th {
background: #f0fff4;
padding: 4px 5px;
font-size: 7px;
font-weight: bold;
text-transform: uppercase;
color: #276749;
border: 1px solid #c6f6d5;
text-align: left;
white-space: nowrap;
}
.cg-item-table td {
padding: 4px 5px;
border: 1px solid #e2e8f0;
color: #2d3748;
white-space: nowrap;
}
.row-even { background: #f7fafc; }
/* ── GRAND TOTAL ── */
.grand-outer {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
margin-bottom: 10px;
}
.grand-spacer { width: 60%; }
.grand-inner { width: 40%; vertical-align: top; }
.grand-table { width: 100%; border-collapse: collapse; font-size: 9px; }
.grand-table td { padding: 4px 8px; border: 1px solid #e2e8f0; }
.g-lbl { text-align: right; font-weight: bold; color: #4a5568; background: #f7fafc; }
.g-val { text-align: right; font-weight: bold; color: #1a202c; }
.g-total-lbl { text-align: right; font-size: 11px; font-weight: bold; color: #1a202c; background: #ebf8ff; border-top: 2px solid #2b6cb0; }
.g-total-val { text-align: right; font-size: 11px; font-weight: bold; color: #1a202c; background: #ebf8ff; border-top: 2px solid #2b6cb0; }
/* ── INSTALLMENTS ── */
.install-section { margin-top: 12px; }
.install-header {
font-size: 9px;
font-weight: bold;
.paid-tag {
background: #23BF47;
color: #fff;
background: #2d3748;
padding: 5px 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
.install-table { width: 100%; border-collapse: collapse; font-size: 9px; }
.install-table th {
background: #edf2f7;
padding: 5px 6px;
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
color: #4a5568;
border: 1px solid #cbd5e0;
border-radius: 8px;
padding: 4px 16px 4px 22px;
font-size: 17px;
display: inline-block;
position: relative;
}
.paid-tag:before {
content: '';
position: absolute;
left: 7px;
top: 7px;
width: 10px;
height: 10px;
background: #fff;
border-radius: 50%;
}
.paid-date {
font-size: 14px;
color: #23BF47;
margin-top: 2px;
}
.install-table td { padding: 4px 6px; border: 1px solid #e2e8f0; color: #2d3748; }
/* ── HELPERS ── */
.tr { text-align: right; }
.tc { text-align: center; }
.bold { font-weight: bold; }
.bill-section {
background: #F3F7FB;
border-radius: 11px;
padding: 20px 18px 13px 18px;
margin: 28px 0 16px 0;
box-shadow: 0 0px 0px #0000;
}
.bill-title {
font-size: 17px;
font-weight: bold;
color: #23355D;
margin-bottom: 4px;
letter-spacing: 0.3px;
}
.bill-details {
font-size: 15px;
line-height: 1.6;
}
/* ── FOOTER ── */
table {
width: 100%;
border-collapse: collapse;
margin-top: 9px;
margin-bottom: 13px;
background: #fff;
border-radius: 10px;
overflow: hidden;
}
th {
background: #F6F7F9;
padding: 10px 0;
font-size: 15px;
color: #6781A6;
font-weight: bold;
border: none;
text-align: left;
}
td {
padding: 7px 0;
color: #222;
font-size: 15px;
border: none;
text-align: left;
}
tbody tr:not(:last-child) td {
border-bottom: 1px solid #E6EBF0;
}
tbody tr:last-child td {
border-bottom: none;
}
.totals-row td {
font-weight: bold;
color: #23355D;
}
.gst-row td {
font-weight: 500;
color: #23BF47;
}
.total-row td {
font-weight: bold;
font-size: 17px;
color: #222;
}
.payment-info {
margin-top: 24px;
margin-bottom: 9px;
font-size: 15px;
}
.ref-number {
font-size: 14px;
color: #6781A6;
margin-bottom: 8px;
margin-top: 2px;
}
.footer {
margin-top: 16px;
padding-top: 8px;
border-top: 1px solid #e2e8f0;
font-size: 8px;
color: #718096;
border-top: 1.2px solid #E6EBF0;
margin-top: 25px;
padding-top: 12px;
font-size: 16px;
color: #888;
text-align: center;
}
.footer strong {
color: #222;
font-weight: 500;
}
</style>
</head>
<body>
{{-- ══ VARIABLES ══ --}}
@php
$companyName = $invoice->company_name ?: ($invoice->customer->company_name ?? null);
$custName = $invoice->customer_name;
$custAddress = $invoice->customer_address ?: ($invoice->customer->address ?? null);
$custPincode = $invoice->pincode ?: ($invoice->customer->pincode ?? null);
$custEmail = $invoice->customer_email ?: ($invoice->customer->email ?? null);
$custMobile = $invoice->customer_mobile ?: ($invoice->customer->mobile_no ?? null);
@endphp
{{-- ══ 1) HEADER Logo + Company Name ══ --}}
<div class="top-header">
<table class="header-table">
<tr>
{{-- Logo: 55x55px DomPDF साठी public_path() वापरतो --}}
<td class="logo-cell">
<img src="{{ public_path('images/kent_logo2.png') }}" alt="Logo" width="55" height="55">
</td>
{{-- Company Name + Subtitle + Tagline --}}
<td class="title-cell">
<div class="company-name">KENT</div>
<div class="company-subtitle">International Pvt. Ltd.</div>
<div class="company-tagline">
Address Line 1, City, State Pincode &nbsp;|&nbsp;
Email: info@company.com &nbsp;|&nbsp;
Phone: +91 1234567890
<div class="container">
<!-- Header Section -->
<div class="header">
<div class="logo-company">
<img src="{{ public_path('images/kent_logo2.png') }}" class="logo">
<div class="company-details">
<div class="company-title">{{ $invoice->company_name ?? 'Kent International Pvt. Ltd.' }}</div>
<div class="company-sub"></div>
{{ $invoice->company_address ?? '123 Business Park, Sector 5' }}<br>
{{ $invoice->company_city ?? 'Gurugram, Haryana 122001' }}<br>
{{ $invoice->company_country ?? 'India' }}<br>
GST: {{ $invoice->company_gst ?? 'GST123456789' }}<br>
Email: {{ $invoice->company_email ?? 'billing@kent.com' }}<br>
Phone: {{ $invoice->company_phone ?? '+91 124 123 4567' }}
</div>
</td>
</tr>
</table>
</div>
{{-- ══ 2) CUSTOMER | INVOICE ══ --}}
<table class="info-outer">
<tr>
<td class="info-left">
<div class="section-title">Customer Details</div>
@if($companyName)
<div class="info-line bold" style="font-size:11px;">{{ $companyName }}</div>
<div class="info-line">{{ $custName }}</div>
@else
<div class="info-line bold" style="font-size:11px;">{{ $custName }}</div>
@endif
@if($custAddress)<div class="info-line">{{ $custAddress }}</div>@endif
@if($custPincode)<div class="info-line">{{ $custPincode }}</div>@endif
@if($custEmail)<div class="info-line">Email: {{ $custEmail }}</div>@endif
@if($custMobile)<div class="info-line">Phone: {{ $custMobile }}</div>@endif
</td>
<td class="info-right">
<div class="section-title">Invoice Details</div>
<div class="info-line"><strong>Invoice #:</strong> {{ $invoice->invoice_number }}</div>
<div class="info-line"><strong>Date:</strong> {{ \Carbon\Carbon::parse($invoice->invoice_date)->format('d M, Y') }}</div>
<div class="info-line"><strong>Due Date:</strong> {{ \Carbon\Carbon::parse($invoice->due_date)->format('d M, Y') }}</div>
<div class="info-line">
<strong>Status:</strong>
<span class="badge badge-{{ strtolower($invoice->status) }}">{{ strtoupper($invoice->status) }}</span>
</div>
<div class="info-line"><strong>GST Type:</strong> {{ strtoupper($invoice->tax_type ?? 'GST') }}</div>
<div class="info-line"><strong>GST %:</strong> {{ number_format($invoice->gst_percent ?? 0, 2) }}%</div>
</td>
</tr>
</table>
{{-- ══ 3) BILL TO ══ --}}
<div class="bill-to-box">
<div class="bill-to-label">Bill To</div>
@if($companyName)
<div class="bill-name">{{ $companyName }}</div>
<div class="bill-sub">{{ $custName }}</div>
@else
<div class="bill-name">{{ $custName }}</div>
@endif
<div class="bill-detail">
@if($custAddress){{ $custAddress }}<br>@endif
@if($custPincode){{ $custPincode }}<br>@endif
@if($custEmail)Email: {{ $custEmail }}<br>@endif
@if($custMobile)Phone: {{ $custMobile }}@endif
</div>
</div>
{{-- ══ 4) 8 SUMMARY BOXES ══ --}}
@php
$allGroupItems = $invoice->chargeGroups->flatMap(fn($g) => $g->items);
$invoiceItemIds = $allGroupItems->pluck('invoice_item_id')->unique()->values();
$invoiceItems = $invoice->items->whereIn('id', $invoiceItemIds);
$totalMarkCount = $invoiceItems->pluck('mark_no')->filter()->unique()->count();
$totalCtn = $invoiceItems->sum('ctn');
$totalQty = $invoiceItems->sum('ttl_qty');
$totalCbm = $invoiceItems->sum('ttl_cbm');
$totalKg = $invoiceItems->sum('ttl_kg');
@endphp
<div class="summary-wrap">
<div class="summary-label">Container &amp; Shipment Summary</div>
<table class="summary-outer">
<tr>
<td class="sbox sbox-row1">
<div class="sbox-lbl">Container Name</div>
<div class="sbox-val">{{ $invoice->container->container_name ?? '—' }}</div>
</td>
<td class="sbox sbox-row1">
<div class="sbox-lbl">Container Date</div>
<div class="sbox-val">
@if($invoice->container && $invoice->container->container_date)
{{ \Carbon\Carbon::parse($invoice->container->container_date)->format('d M, Y') }}
@else@endif
<div class="invoice-details">
<div class="invoice-title">INVOICE</div>
Invoice #: {{ $invoice->invoice_number }}<br>
Issue Date: {{ date('d M Y', strtotime($invoice->invoice_date)) }}<br>
Due Date: {{ date('d M Y', strtotime($invoice->due_date)) }}
@if(strtolower($invoice->status) == 'paid')
<div class="paid-label">
<span class="paid-tag">Paid</span>
</div>
</td>
<td class="sbox sbox-row1">
<div class="sbox-lbl">Container No.</div>
<div class="sbox-val">{{ $invoice->container->container_number ?? '—' }}</div>
</td>
<td class="sbox sbox-row1">
<div class="sbox-lbl">Total Mark No.</div>
<div class="sbox-val">{{ $totalMarkCount }}</div>
<div class="sbox-sub">Unique marks</div>
</td>
</tr>
<tr>
<td class="sbox sbox-row2">
<div class="sbox-lbl">Total CTN</div>
<div class="sbox-val">{{ number_format($totalCtn, 0) }}</div>
<div class="sbox-sub">Cartons</div>
</td>
<td class="sbox sbox-row2">
<div class="sbox-lbl">Total QTY</div>
<div class="sbox-val">{{ number_format($totalQty, 0) }}</div>
<div class="sbox-sub">Pieces</div>
</td>
<td class="sbox sbox-row2">
<div class="sbox-lbl">Total CBM</div>
<div class="sbox-val">{{ number_format($totalCbm, 3) }}</div>
<div class="sbox-sub">Cubic Meter</div>
</td>
<td class="sbox sbox-row2">
<div class="sbox-lbl">Total KG</div>
<div class="sbox-val">{{ number_format($totalKg, 2) }}</div>
<div class="sbox-sub">Kilograms</div>
</td>
</tr>
</table>
</div>
{{-- ══ 5) CHARGE GROUPS ══ --}}
@if($invoice->chargeGroups && $invoice->chargeGroups->count() > 0)
<div style="margin-bottom:14px;">
<div class="cg-header">Charge Groups</div>
@foreach($invoice->chargeGroups as $group)
@php
$groupItemIds = $group->items->pluck('invoice_item_id')->toArray();
$groupInvItems = $invoice->items->whereIn('id', $groupItemIds);
@endphp
<div class="cg-group-wrap">
<table class="cg-sum-table">
<thead>
<tr>
<th style="width:22%;">Group Name</th>
<th style="width:11%;">Basis</th>
<th style="width:11%;">Basis Value</th>
<th style="width:10%;">Rate</th>
<th style="width:13%;" class="tr">Total Charge</th>
<th style="width:8%;">GST %</th>
<th style="width:9%;">Tax Type</th>
<th style="width:16%;" class="tr">Total With GST</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>{{ $group->group_name ?? 'Group '.$group->id }}</strong></td>
<td>{{ strtoupper($group->basis_type) }}</td>
<td>{{ number_format($group->basis_value, 3) }}</td>
<td>{{ number_format($group->rate, 2) }}</td>
<td class="tr">{{ number_format($group->total_charge, 2) }}</td>
<td>{{ number_format($group->gst_percent ?? 0, 2) }}%</td>
<td>{{ strtoupper($group->tax_type ?? 'GST') }}</td>
<td class="tr"><strong>{{ number_format($group->total_with_gst, 2) }}</strong></td>
</tr>
</tbody>
</table>
@if($groupInvItems->count() > 0)
<table class="cg-item-table">
<thead>
<tr>
<th style="width:3%;">#</th>
<th style="width:15%;">Description</th>
<th style="width:7%;">Mark No</th>
<th style="width:5%;" class="tc">QTY</th>
<th style="width:6%;" class="tr">TTL QTY</th>
<th style="width:6%;" class="tr">CBM</th>
<th style="width:6%;" class="tr">TTL CBM</th>
<th style="width:5%;" class="tr">KG</th>
<th style="width:6%;" class="tr">TTL KG</th>
<th style="width:7%;" class="tr">TTL Amt</th>
<th style="width:5%;" class="tr">Rate</th>
<th style="width:7%;" class="tr">Total Charge</th>
<th style="width:5%;" class="tr">GST %</th>
<th style="width:6%;" class="tr">Item GST</th>
<th style="width:6%;" class="tr">Item Total</th>
</tr>
</thead>
<tbody>
@foreach($groupInvItems as $idx => $item)
@php
$groupBasisTotal = $groupInvItems->sum(function($i) use ($group) {
return match($group->basis_type) {
'ttl_qty' => $i->ttl_qty ?? 0,
'ttl_cbm' => $i->ttl_cbm ?? 0,
'ttl_kg' => $i->ttl_kg ?? 0,
'amount' => $i->ttl_amount ?? 0,
default => $i->ttl_amount ?? 0,
};
});
$itemBasisValue = match($group->basis_type) {
'ttl_qty' => $item->ttl_qty ?? 0,
'ttl_cbm' => $item->ttl_cbm ?? 0,
'ttl_kg' => $item->ttl_kg ?? 0,
'amount' => $item->ttl_amount ?? 0,
default => $item->ttl_amount ?? 0,
};
$itemCharge = $groupBasisTotal > 0
? ($itemBasisValue / $groupBasisTotal) * $group->total_charge
: 0;
$gstPct = $group->gst_percent ?? 0;
$itemGst = $itemCharge * $gstPct / 100;
$itemTotal = $itemCharge + $itemGst;
@endphp
<tr class="{{ $idx % 2 === 1 ? 'row-even' : '' }}">
<td class="tc">{{ $idx + 1 }}</td>
<td>{{ $item->description }}</td>
<td>{{ $item->mark_no ?? '—' }}</td>
<td class="tc">{{ $item->qty ?? 0 }}</td>
<td class="tr">{{ number_format($item->ttl_qty ?? 0, 0) }}</td>
<td class="tr">{{ number_format($item->cbm ?? 0, 3) }}</td>
<td class="tr">{{ number_format($item->ttl_cbm ?? 0, 3) }}</td>
<td class="tr">{{ number_format($item->kg ?? 0, 2) }}</td>
<td class="tr">{{ number_format($item->ttl_kg ?? 0, 2) }}</td>
<td class="tr">{{ number_format($item->ttl_amount ?? 0, 2) }}</td>
<td class="tr">{{ number_format($group->rate, 2) }}</td>
<td class="tr">{{ number_format($itemCharge, 2) }}</td>
<td class="tr">{{ number_format($gstPct, 2) }}%</td>
<td class="tr">{{ number_format($itemGst, 2) }}</td>
<td class="tr"><strong>{{ number_format($itemTotal, 2) }}</strong></td>
</tr>
@endforeach
</tbody>
</table>
@endif
<div class="paid-date">
Paid on: {{ date('d M Y', strtotime($invoice->paid_date ?? now())) }}
</div>
@else
<div class="paid-date" style="color:#d00;">Status: {{ ucfirst($invoice->status) }}</div>
@endif
</div>
</div>
@endforeach
</div>
@endif
{{-- ══ GRAND TOTAL ══ --}}
<table class="grand-outer">
<tr>
<td class="grand-spacer"></td>
<td class="grand-inner">
<table class="grand-table">
<!-- Bill To Section -->
<div class="bill-section">
<div class="bill-title">Bill To</div>
<div class="bill-details">
<strong>{{ $invoice->customer_name }}</strong><br>
{{ $invoice->customer_address }}<br>
GST: {{ $invoice->customer_gst ?? '-' }}<br>
Email: {{ $invoice->customer_email }}<br>
Phone: {{ $invoice->customer_mobile }}
</div>
</div>
<!-- Items Table -->
<table>
<thead>
<tr>
<td class="g-lbl">Charge Groups Total:</td>
<td class="g-val">{{ number_format($invoice->charge_groups_total ?? 0, 2) }}</td>
<th>Description</th>
<th>Qty</th>
<th>Rate ()</th>
<th>Amount ()</th>
</tr>
</thead>
<tbody>
@foreach($invoice->items as $item)
<tr>
<td class="g-lbl">GST Amount ({{ number_format($invoice->gst_percent ?? 0, 2) }}%):</td>
<td class="g-val">{{ number_format($invoice->gst_amount ?? 0, 2) }}</td>
<td>{{ $item->description }}</td>
<td>{{ $item->qty }}</td>
<td>{{ number_format($item->price, 0) }}</td>
<td>{{ number_format($item->ttl_amount, 0) }}</td>
</tr>
<tr>
<td class="g-total-lbl">Grand Total:</td>
<td class="g-total-val">{{ number_format($invoice->grand_total_with_charges ?? 0, 2) }}</td>
@endforeach
<tr class="totals-row">
<td colspan="3" style="text-align:right;">Subtotal:</td>
<td>{{ number_format($invoice->subtotal, 0) }}</td>
</tr>
</table>
</td>
</tr>
</table>
{{-- ══ INSTALLMENTS ══ --}}
@if($invoice->installments && $invoice->installments->count() > 0)
<div class="install-section">
<div class="install-header">Payment Installments</div>
<table class="install-table">
<thead>
<tr>
<th style="width:6%;" class="tc">#</th>
<th style="width:20%;">Date</th>
<th style="width:25%;">Payment Method</th>
<th style="width:25%;">Reference No</th>
<th style="width:24%;" class="tr">Amount ()</th>
</tr>
</thead>
<tbody>
@foreach($invoice->installments as $idx => $inst)
<tr>
<td class="tc">{{ $idx + 1 }}</td>
<td>{{ \Carbon\Carbon::parse($inst->installment_date)->format('d M, Y') }}</td>
<td>{{ strtoupper($inst->payment_method) }}</td>
<td>{{ $inst->reference_no ?? '—' }}</td>
<td class="tr">{{ number_format($inst->amount, 2) }}</td>
</tr>
@endforeach
</tbody>
</table>
@php
$totalPaid = $invoice->installments->sum('amount');
$grandTotal = $invoice->grand_total_with_charges ?? 0;
$remaining = max(0, $grandTotal - $totalPaid);
@endphp
<table class="grand-outer" style="margin-top:6px;">
<tr>
<td class="grand-spacer"></td>
<td class="grand-inner">
<table class="grand-table">
<tr>
<td class="g-lbl" style="color:#059669;">Total Paid:</td>
<td class="g-val" style="color:#059669;">{{ number_format($totalPaid, 2) }}</td>
</tr>
<tr>
<td class="g-lbl" style="color:#dc2626;">Remaining:</td>
<td class="g-val" style="color:#dc2626;">{{ number_format($remaining, 2) }}</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
@endif
{{-- ══ FOOTER ══ --}}
<div class="footer">
Thank you for your business! &nbsp;|&nbsp; For queries: info@company.com &nbsp;|&nbsp; +91 1234567890
</div>
<tr class="gst-row">
<td colspan="3" style="text-align:right;">GST ({{ $invoice->gst_percent }}%):</td>
<td>{{ number_format($invoice->gst_amount, 0) }}</td>
</tr>
<tr class="total-row">
<td colspan="3" style="text-align:right;">Total:</td>
<td>{{ number_format($invoice->final_amount_with_gst, 0) }}</td>
</tr>
</tbody>
</table>
<!-- Payment Info & Reference -->
<div class="payment-info">
<strong>Payment Method:</strong> {{ $invoice->payment_method ?? 'Bank Transfer' }}
</div>
<div class="ref-number">
Reference Number: {{ $invoice->reference_no ?? "REF123456789" }}
</div>
<!-- Footer -->
<div class="footer">
Thank you for your business!<br>
For any queries, please contact us at <strong>{{ $invoice->company_email ?? 'billing@kent.com' }}</strong>
</div>
</div>
</body>
</html>
</html>

View File

@@ -1,32 +0,0 @@
<table>
<thead>
<tr>
<th>Invoice No</th>
<th>Invoice Date</th>
<th>Mark No</th>
<th>Container No</th>
<th>Container Date</th>
<th>Company</th>
<th>Customer</th>
<th>Amount</th>
<th>Amount + GST</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@foreach($invoices as $inv)
<tr>
<td>{{ $inv->invoice_number }}</td>
<td>{{ $inv->invoice_date ? \Carbon\Carbon::parse($inv->invoice_date)->format('d-m-Y') : '' }}</td>
<td>{{ $inv->mark_no }}</td>
<td>{{ $inv->container_number }}</td>
<td>{{ $inv->container_date ? \Carbon\Carbon::parse($inv->container_date)->format('d-m-Y') : '' }}</td>
<td>{{ $inv->company_name }}</td>
<td>{{ $inv->customer_name }}</td>
<td>{{ $inv->final_amount }}</td>
<td>{{ $inv->final_amount_with_gst }}</td>
<td>{{ $inv->invoice_status }}</td>
</tr>
@endforeach
</tbody>
</table>

View File

@@ -1,48 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Invoices Report</title>
<style>
body { font-family: DejaVu Sans, sans-serif; font-size: 12px; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th, td { border: 1px solid #ccc; padding: 6px 8px; text-align: left; }
th { background: #f3f4f6; }
</style>
</head>
<body>
<h3>Invoices Report</h3>
<table>
<thead>
<tr>
<th>Invoice No</th>
<th>Invoice Date</th>
<th>Mark No</th>
<th>Container No</th>
<th>Container Date</th>
<th>Company</th>
<th>Customer</th>
<th>Amount</th>
<th>Amount + GST</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@foreach($invoices as $inv)
<tr>
<td>{{ $inv->invoice_number ?? '-' }}</td>
<td>{{ $inv->invoice_date ? \Carbon\Carbon::parse($inv->invoice_date)->format('d-m-Y') : '-' }}</td>
<td>{{ $inv->mark_no ?? '-' }}</td>
<td>{{ $inv->container_number ?? '-' }}</td>
<td>{{ $inv->container_date ? \Carbon\Carbon::parse($inv->container_date)->format('d-m-Y') : '-' }}</td>
<td>{{ $inv->company_name ?? '-' }}</td>
<td>{{ $inv->customer_name ?? '-' }}</td>
<td>{{ $inv->final_amount ? number_format($inv->final_amount, 2) : '0.00' }}</td>
<td>{{ $inv->final_amount_with_gst ? number_format($inv->final_amount_with_gst, 2) : '0.00' }}</td>
<td>{{ ucfirst($inv->invoice_status ?? 'pending') }}</td>
</tr>
@endforeach
</tbody>
</table>
</body>
</html>

View File

@@ -1,252 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Invoices Report</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'DejaVu Sans', Arial, sans-serif;
font-size: 10px;
color: #1f2937;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
border-bottom: 3px solid #3b82f6;
padding-bottom: 15px;
}
.header h1 {
font-size: 24px;
color: #1f2937;
margin-bottom: 5px;
}
.header .date {
font-size: 11px;
color: #6b7280;
}
.stats-grid {
display: table;
width: 100%;
margin-bottom: 25px;
border-collapse: collapse;
}
.stats-row {
display: table-row;
}
.stat-box {
display: table-cell;
width: 25%;
padding: 15px;
text-align: center;
border: 2px solid #e5e7eb;
vertical-align: middle;
}
.stat-box.total {
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
border-left: 4px solid #3b82f6;
}
.stat-box.paid {
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
border-left: 4px solid #22c55e;
}
.stat-box.pending {
background: linear-gradient(135deg, #fffbeb 0%, #fef9c3 100%);
border-left: 4px solid #f59e0b;
}
.stat-box.overdue {
background: linear-gradient(135deg, #fff5f5 0%, #fee2e2 100%);
border-left: 4px solid #ef4444;
}
.stat-value {
font-size: 28px;
font-weight: bold;
color: #1f2937;
margin-bottom: 5px;
}
.stat-box.total .stat-value { color: #3b82f6; }
.stat-box.paid .stat-value { color: #16a34a; }
.stat-box.pending .stat-value { color: #d97706; }
.stat-box.overdue .stat-value { color: #dc2626; }
.stat-label {
font-size: 11px;
color: #6b7280;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
table.data-table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
font-size: 9px;
}
table.data-table thead {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
table.data-table th {
padding: 10px 8px;
text-align: center;
color: white;
font-weight: 600;
font-size: 9px;
border: 1px solid #5a67d8;
}
table.data-table td {
padding: 8px;
text-align: center;
border: 1px solid #e5e7eb;
font-size: 8px;
}
table.data-table tbody tr:nth-child(even) {
background: #f9fafb;
}
table.data-table tbody tr:hover {
background: #f3f4f6;
}
.status-badge {
display: inline-block;
padding: 4px 10px;
border-radius: 12px;
font-size: 8px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.status-paid {
background: linear-gradient(135deg, #10b981, #34d399);
color: white;
}
.status-pending {
background: linear-gradient(135deg, #fef3c7, #fde68a);
color: #d97706;
}
.status-overdue {
background: linear-gradient(135deg, #fef2f2, #fecaca);
color: #dc2626;
}
.footer {
margin-top: 20px;
text-align: center;
font-size: 9px;
color: #9ca3af;
border-top: 1px solid #e5e7eb;
padding-top: 10px;
}
.no-data {
text-align: center;
padding: 40px;
color: #9ca3af;
font-size: 12px;
}
</style>
</head>
<body>
<div class="header">
<h1>📊 Invoices Report</h1>
<div class="date">Generated on: {{ now()->format('d M Y, h:i A') }}</div>
</div>
<!-- Stats Boxes -->
<div class="stats-grid">
<div class="stats-row">
<div class="stat-box total">
<div class="stat-value">{{ $totalInvoices }}</div>
<div class="stat-label">Total Invoices</div>
</div>
<div class="stat-box paid">
<div class="stat-value">{{ $paidInvoices }}</div>
<div class="stat-label">Paid Invoices</div>
</div>
<div class="stat-box pending">
<div class="stat-value">{{ $pendingInvoices }}</div>
<div class="stat-label">Pending Invoices</div>
</div>
<div class="stat-box overdue">
<div class="stat-value">{{ $overdueInvoices }}</div>
<div class="stat-label">Overdue Invoices</div>
</div>
</div>
</div>
<!-- Data Table -->
@if($invoices->count() > 0)
<table class="data-table">
<thead>
<tr>
<th>Invoice No</th>
<th>Date</th>
<th>Mark No</th>
<th>Container</th>
<th>Cont. Date</th>
<th>Company</th>
<th>Customer</th>
<th>Amount</th>
<th>Amount+GST</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@foreach($invoices as $inv)
<tr>
<td>{{ $inv->invoice_number ?? '-' }}</td>
<td>{{ $inv->invoice_date ? \Carbon\Carbon::parse($inv->invoice_date)->format('d-m-Y') : '-' }}</td>
<td>{{ $inv->mark_no ?? '-' }}</td>
<td>{{ $inv->container_number ?? '-' }}</td>
<td>{{ $inv->container_date ? \Carbon\Carbon::parse($inv->container_date)->format('d-m-Y') : '-' }}</td>
<td>{{ $inv->company_name ?? '-' }}</td>
<td>{{ $inv->customer_name ?? '-' }}</td>
<td>{{ $inv->final_amount ? '₹'.number_format($inv->final_amount, 2) : '-' }}</td>
<td>{{ $inv->final_amount_with_gst ? '₹'.number_format($inv->final_amount_with_gst, 2) : '-' }}</td>
<td>
@php
$status = strtolower($inv->invoice_status ?? 'pending');
@endphp
<span class="status-badge status-{{ $status }}">{{ ucfirst($status) }}</span>
</td>
</tr>
@endforeach
</tbody>
</table>
@else
<div class="no-data">
No invoices found matching the criteria.
</div>
@endif
<div class="footer">
Kent Logistics - Invoice Management System | Page 1 of 1
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -5,421 +5,242 @@
@section('content')
<div class="container-fluid px-0 bg-transparent">
<style>
/* ── HEADER ── */
body {
background: linear-gradient(135deg, #f2f6ff, #fefefe);
font-family: "Poppins", sans-serif;
}
/* PROFILE HEADER */
.profile-header {
display: flex;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
gap: 1.5rem;
padding: 32px 36px;
background: linear-gradient(120deg, #4e73df 60%, #1cc88a 100%);
border-radius: 22px;
gap: 1rem;
padding: 36px 32px;
background: linear-gradient(120deg, #4e73df 68%, #1cc88a 100%);
border-radius: 20px;
color: white;
box-shadow: 0 10px 36px rgba(78,115,223,0.22);
margin-bottom: 28px;
box-shadow: 0 8px 28px rgba(24,40,90,0.09);
margin-bottom: 34px;
position: relative;
overflow: hidden;
}
.profile-header::before {
content:"";position:absolute;top:-60px;right:-60px;
width:220px;height:220px;
background:rgba(255,255,255,0.08);border-radius:50%;
min-height: 140px;
}
.profile-header::after {
content:"";position:absolute;bottom:-80px;right:180px;
width:180px;height:180px;
background:rgba(255,255,255,0.05);border-radius:50%;
content: "";
position: absolute;
top: -58px;
right: -85px;
width: 210px;
height: 210px;
background: rgba(255,255,255,0.14);
border-radius: 50%;
}
.avatar {
width: 98px;
height: 98px;
border-radius: 50%;
object-fit: cover;
border: 4px solid rgba(255,255,255,0.7);
box-shadow: 0 5px 20px rgba(0,0,0,0.15);
background: #fff;
z-index: 1;
}
.main-info {
display: flex;
flex-direction: column;
gap: 10px;
z-index: 1;
flex: 1;
}
.main-row {
display: flex;
flex-wrap: wrap;
gap: 25px;
}
.field-group {
min-width: 210px;
}
.label {
font-size: 0.9rem;
color: #dbe7f4;
font-weight: 500;
}
.value {
font-size: 1.1rem;
font-weight: 600;
color: #fff;
background: rgba(255,255,255,0.13);
border-radius: 8px;
padding: 6px 12px;
border: 1.5px solid rgba(255,255,255,0.18);
transition: background 0.13s;
}
.value:hover { background: rgba(255,255,255,0.22); }
/* STATS + CARDS COMMON */
.profile-stats-row, .profile-grid-row {
display: flex;
flex-wrap: wrap;
gap: 18px;
margin-bottom: 20px;
}
.stat-card, .profile-card {
background: #fff;
border-radius: 14px;
box-shadow: 0 3px 18px rgba(60,80,120,0.08);
padding: 20px;
transition: all 0.3s ease;
position: relative;
flex: 1 1 200px;
}
.stat-card:hover, .profile-card:hover {
transform: translateY(-4px);
box-shadow: 0 6px 25px rgba(50,70,120,0.18);
}
.avatar-initials {
width:100px;height:100px;border-radius:50%;
border:4px solid rgba(255,255,255,0.75);
box-shadow:0 6px 24px rgba(0,0,0,0.18);
background:#fff;z-index:1;flex-shrink:0;
display:flex;align-items:center;justify-content:center;
font-size:2.6rem;font-weight:800;color:#4e73df;
letter-spacing:-1px;
/* STATS */
.stat-card::before {
content: "";
position: absolute;
top: -20px;
right: -20px;
width: 90px;
height: 90px;
background: linear-gradient(135deg, #c4d7ff, #e9f0ff);
border-radius: 50%;
opacity: 0.25;
}
.main-info { display:flex;flex-direction:column;gap:14px;z-index:1;flex:1; }
.main-row { display:flex;flex-wrap:wrap;gap:18px;align-items:flex-start; }
.field-group { min-width:160px; }
.ph-label {
font-size:0.72rem;color:rgba(255,255,255,0.65);
font-weight:600;text-transform:uppercase;letter-spacing:0.07em;margin-bottom:3px;
}
.ph-value {
font-size:0.95rem;font-weight:600;color:#fff;
background:rgba(255,255,255,0.12);border-radius:9px;
padding:6px 13px;border:1.5px solid rgba(255,255,255,0.2);
backdrop-filter:blur(4px);
}
.type-badge {
display:inline-block;padding:4px 14px;border-radius:50px;
font-size:0.72rem;font-weight:700;letter-spacing:0.07em;text-transform:uppercase;
}
.type-admin { background:rgba(255,210,60,0.22);color:#ffe066;border:1px solid rgba(255,220,80,0.4); }
.type-staff { background:rgba(100,255,180,0.18);color:#a0ffda;border:1px solid rgba(100,255,180,0.35); }
.status-dot {
display:inline-block;width:9px;height:9px;
border-radius:50%;margin-right:5px;
}
.status-active { background:#22c55e;box-shadow:0 0 5px #22c55e; }
.status-inactive { background:#ef4444;box-shadow:0 0 5px #ef4444; }
/* ── STATS ── */
.profile-stats-row { display:flex;flex-wrap:wrap;gap:16px;margin-bottom:24px; }
.stat-card {
background:#fff;border-radius:16px;
box-shadow:0 3px 18px rgba(60,80,120,0.07);
padding:20px 22px;
transition:all 0.3s ease;
position:relative;
flex:1 1 160px;
overflow:hidden;
}
.stat-card::after {
content:"";position:absolute;
top:-25px;right:-25px;
width:80px;height:80px;
border-radius:50%;
opacity:0.35;
}
/* Per-card accent colors */
.stat-card.sc-blue { border-top:3px solid #4e73df; }
.stat-card.sc-blue::after { background:linear-gradient(135deg,#c4d7ff,#e9f0ff); }
.stat-card.sc-green { border-top:3px solid #1cc88a; }
.stat-card.sc-green::after { background:linear-gradient(135deg,#bbf7d0,#dcfce7); }
.stat-card.sc-yellow { border-top:3px solid #f6c23e; }
.stat-card.sc-yellow::after { background:linear-gradient(135deg,#fef9c3,#fef08a); }
.stat-card.sc-red { border-top:3px solid #e74a3b; }
.stat-card.sc-red::after { background:linear-gradient(135deg,#fee2e2,#fecaca); }
.stat-card.sc-purple { border-top:3px solid #8b5cf6; }
.stat-card.sc-purple::after { background:linear-gradient(135deg,#ede9fe,#ddd6fe); }
.stat-card.sc-teal { border-top:3px solid #0ea5e9; }
.stat-card.sc-teal::after { background:linear-gradient(135deg,#e0f2fe,#bae6fd); }
.stat-card:hover { transform:translateY(-5px);box-shadow:0 10px 30px rgba(78,115,223,0.15); }
.stat-icon-wrap {
width:42px;height:42px;border-radius:11px;
display:flex;align-items:center;justify-content:center;
font-size:1.25rem;margin-bottom:10px;
}
.sc-blue .stat-icon-wrap { background:#eff6ff; }
.sc-green .stat-icon-wrap { background:#f0fdf4; }
.sc-yellow .stat-icon-wrap { background:#fefce8; }
.sc-red .stat-icon-wrap { background:#fef2f2; }
.sc-purple .stat-icon-wrap { background:#faf5ff; }
.sc-teal .stat-icon-wrap { background:#f0f9ff; }
.stat-label {
font-size:0.78rem;color:#8b96ad;font-weight:600;
text-transform:uppercase;letter-spacing:0.06em;
margin-bottom:4px;display:block;
font-size: 0.9rem;
color: #8b96ad;
font-weight: 500;
}
.stat-value {
font-size:1.7rem;font-weight:800;color:#1b2330;
line-height:1;margin-bottom:6px;
font-size: 1.6rem;
font-weight: 700;
display: flex;
align-items: center;
color: #1b2330;
}
.stat-footer { font-size:0.78rem;color:#94a3b8; }
.positive { color:#16a34a; }
.negative { color:#dc2626; }
.stat-footer { font-size: 0.9rem; }
.positive { color: #23a25d; }
.negative { color: #e54141; }
.neutral { color: #9a9ea8; }
/* ── PROFESSIONAL INFO ── */
.pro-section { margin-bottom:26px; }
.pro-section-title {
font-size:0.88rem;font-weight:700;color:#1b2330;
border-left:4px solid #4e73df;padding-left:9px;margin-bottom:14px;
display:flex;align-items:center;gap:7px;
}
.pro-grid { display:flex;flex-wrap:wrap;gap:14px; }
.pro-box {
background:#fff;border-radius:14px;
padding:16px 20px;
box-shadow:0 2px 14px rgba(60,80,120,0.07);
border:1px solid #e8edf5;
transition:all 0.25s cubic-bezier(.4,0,.2,1);
flex:1 1 160px;min-width:0;
position:relative;overflow:hidden;
}
.pro-box::before {
content:"";
position:absolute;top:0;left:0;right:0;height:3px;
background:linear-gradient(90deg,#4e73df,#1cc88a);
opacity:0;transition:opacity 0.25s;
}
.pro-box:hover { transform:translateY(-4px);box-shadow:0 8px 28px rgba(78,115,223,0.15);border-color:#c7d4f8; }
.pro-box:hover::before { opacity:1; }
.box-icon { font-size:1.3rem;margin-bottom:7px;display:inline-block; }
.box-label { font-size:0.68rem;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:4px; }
.box-value { font-size:0.95rem;font-weight:700;color:#1b2330;white-space:nowrap;overflow:hidden;text-overflow:ellipsis; }
/* ── BOTTOM CARDS ── */
.profile-grid-row { display:flex;flex-wrap:wrap;gap:18px;margin-bottom:10px; }
.profile-card {
background:#fff;border-radius:16px;
box-shadow:0 3px 18px rgba(60,80,120,0.07);
padding:22px;transition:all 0.3s ease;flex:1 1 220px;
}
.profile-card:hover { transform:translateY(-4px);box-shadow:0 8px 28px rgba(78,115,223,0.14); }
/* PROFILE CARDS */
.profile-card h6 {
font-size:1rem;font-weight:700;margin-bottom:14px;color:#1b2330;
border-left:4px solid #4e73df;padding-left:9px;
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 12px;
color: #1b2330;
border-left: 4px solid #3767f4;
padding-left: 8px;
}
.activity-list,.quick-action-list { list-style:none;padding:0;margin:0; }
.activity-list li,.quick-action-list li {
padding:9px 6px;font-size:0.92rem;border-bottom:1px solid #f2f4f8;
display:flex;align-items:center;gap:8px;transition:0.2s ease;border-radius:6px;
.activity-list, .quick-action-list {
list-style: none;
padding: 0;
margin: 0;
}
.activity-list li:hover { background:#f4f7ff;padding-left:10px; }
.activity-list li, .quick-action-list li {
padding: 9px 2px;
font-size: 1rem;
border-bottom: 1px solid #f2f4f8;
display: flex;
align-items: center;
gap: 8px;
transition: 0.2s ease;
}
.activity-list li:hover { background: #f7faff; border-radius: 6px; }
.quick-action-list li a {
text-decoration:none;color:#2563eb;font-weight:600;
display:flex;align-items:center;gap:8px;transition:0.2s ease;width:100%;
text-decoration: none;
color: #2563eb;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
transition: 0.2s ease;
}
.quick-action-list li a:hover { color:#1e40af;transform:translateX(5px); }
.quick-action-list li a:hover { color: #1e40af; transform: translateX(4px); }
@media (max-width:900px) {
.profile-header,.profile-stats-row,.profile-grid-row { flex-direction:column; }
.avatar-initials { margin:0 auto; }
.main-row { justify-content:center; }
.pro-grid { flex-direction:column; }
.pro-box { flex:unset;width:100%; }
@media (max-width: 1000px) {
.profile-header, .profile-stats-row, .profile-grid-row {
flex-direction: column;
text-align: center;
}
.avatar { margin: 0 auto 15px; }
.main-row { justify-content: center; }
}
</style>
@php
$user = $user ?? Auth::guard('admin')->user();
$isAdmin = $user->type === 'admin';
$initials = strtoupper(substr($user->name ?? 'U', 0, 1));
$joiningFormatted = $user->joining_date
? \Carbon\Carbon::parse($user->joining_date)->format('d M Y')
: '—';
$isActive = ($user->status ?? 'active') === 'active';
// stats — controller मधून येतात, fallback 0
$s = $stats ?? [];
$totalContainers = $s['total_containers'] ?? 0;
$totalInvoices = $s['total_invoices'] ?? 0;
$paidInvoices = $s['paid_invoices'] ?? 0;
$pendingInvoices = $s['pending_invoices'] ?? 0;
$totalCustomers = $s['total_customers'] ?? 0;
$totalMarklist = $s['total_marklist'] ?? 0;
$activeMarklist = $s['active_marklist'] ?? 0;
@endphp
{{-- ════════ PROFILE HEADER ════════ --}}
<!-- PROFILE HEADER -->
<div class="profile-header">
<div class="avatar-initials">{{ $initials }}</div>
<img src="https://i.pravatar.cc/100" class="avatar" alt="Avatar" />
<div class="main-info">
{{-- Row 1 --}}
<div class="main-row">
<div class="field-group">
<div class="ph-label">Full Name</div>
<div class="ph-value" style="font-size:1.05rem;">{{ $user->name ?? '—' }}</div>
<div class="label">Name</div>
<div class="value">John Doe</div>
</div>
<div class="field-group">
<div class="ph-label">Account Type</div>
<div class="ph-value" style="padding:4px 10px;">
<span class="type-badge {{ $isAdmin ? 'type-admin' : 'type-staff' }}">
{{ $isAdmin ? '⭐ Admin' : '👤 Staff' }}
</span>
</div>
<div class="label">Email</div>
<div class="value">john@example.com</div>
</div>
<div class="field-group">
<div class="ph-label">Status</div>
<div class="ph-value">
<span class="status-dot {{ $isActive ? 'status-active' : 'status-inactive' }}"></span>
{{ ucfirst($user->status ?? 'active') }}
</div>
<div class="label">Mobile No</div>
<div class="value">+91 12345 54321</div>
</div>
@if($user->employee_id)
<div class="field-group">
<div class="ph-label">Employee ID</div>
<div class="ph-value" style="font-family:monospace;letter-spacing:0.05em;">{{ $user->employee_id }}</div>
</div>
@endif
</div>
{{-- Row 2 --}}
<div class="main-row">
<div class="field-group">
<div class="ph-label">Email</div>
<div class="ph-value" style="font-size:0.88rem;">{{ $user->email ?? '—' }}</div>
<div class="label">Company Name</div>
<div class="value">XYZ Infotech</div>
</div>
<div class="field-group">
<div class="ph-label">Phone</div>
<div class="ph-value">{{ $user->phone ?? '—' }}</div>
<div class="field-group" style="min-width:260px;">
<div class="label">Company Address</div>
<div class="value">XYZ Infotech, East Andheri, Mumbai 400 001</div>
</div>
@if($user->department)
<div class="field-group">
<div class="ph-label">Department</div>
<div class="ph-value">{{ $user->department }}</div>
</div>
@endif
@if($user->designation)
<div class="field-group">
<div class="ph-label">Designation</div>
<div class="ph-value">{{ $user->designation }}</div>
</div>
@endif
@if($user->address)
<div class="field-group" style="min-width:220px;">
<div class="ph-label">Address</div>
<div class="ph-value" style="font-size:0.88rem;">{{ $user->address }}</div>
</div>
@endif
</div>
</div>
</div>
{{-- ════════ PROFESSIONAL INFO ════════ --}}
<div class="pro-section">
<div class="pro-section-title">🏢 Professional Information</div>
<div class="pro-grid">
@if($user->employee_id)
<div class="pro-box">
<div class="box-icon">🪪</div>
<div class="box-label">Employee ID</div>
<div class="box-value" style="font-family:monospace;">{{ $user->employee_id }}</div>
</div>
@endif
@if($user->username)
<div class="pro-box">
<div class="box-icon">👤</div>
<div class="box-label">Username</div>
<div class="box-value">{{ $user->username }}</div>
</div>
@endif
@if($user->role)
<div class="pro-box">
<div class="box-icon">🎯</div>
<div class="box-label">Role</div>
<div class="box-value">{{ $user->role }}</div>
</div>
@endif
@if($user->department)
<div class="pro-box">
<div class="box-icon">🏬</div>
<div class="box-label">Department</div>
<div class="box-value">{{ $user->department }}</div>
</div>
@endif
@if($user->designation)
<div class="pro-box">
<div class="box-icon">📋</div>
<div class="box-label">Designation</div>
<div class="box-value">{{ $user->designation }}</div>
</div>
@endif
<div class="pro-box">
<div class="box-icon">📅</div>
<div class="box-label">Joining Date</div>
<div class="box-value">{{ $joiningFormatted }}</div>
</div>
@if($user->emergency_phone)
<div class="pro-box">
<div class="box-icon">🆘</div>
<div class="box-label">Emergency Phone</div>
<div class="box-value">{{ $user->emergency_phone }}</div>
</div>
@endif
@if($user->additional_info)
<div class="pro-box" style="flex:2 1 320px;">
<div class="box-icon">📝</div>
<div class="box-label">Additional Info</div>
<div class="box-value" style="font-size:0.88rem;font-weight:500;color:#475569;white-space:normal;">{{ $user->additional_info }}</div>
</div>
@endif
</div>
</div>
{{-- ════════ REAL STATS ════════ --}}
<!-- STATS ROW -->
<div class="profile-stats-row">
<div class="stat-card sc-blue">
<div class="stat-icon-wrap">📦</div>
<span class="stat-label">Total Containers</span>
<div class="stat-value">{{ number_format($totalContainers) }}</div>
<div class="stat-footer">All containers in system</div>
<div class="stat-card">
<span class="stat-label">Total Shipment</span>
<span class="stat-value">📦 1,250</span>
<div class="stat-footer positive">+12% vs last month</div>
</div>
<div class="stat-card sc-green">
<div class="stat-icon-wrap">🧾</div>
<span class="stat-label">Total Invoices</span>
<div class="stat-value">{{ number_format($totalInvoices) }}</div>
<div class="stat-footer positive">
{{ $paidInvoices }} paid
</div>
<div class="stat-card">
<span class="stat-label">In Transit</span>
<span class="stat-value" style="color:#2563eb;">🚚 320</span>
<div class="stat-footer neutral">65% of capacity</div>
</div>
<div class="stat-card sc-yellow">
<div class="stat-icon-wrap"></div>
<span class="stat-label">Pending Invoices</span>
<div class="stat-value" style="color:#d97706;">{{ number_format($pendingInvoices) }}</div>
<div class="stat-footer">Awaiting payment</div>
<div class="stat-card">
<span class="stat-label">At Customs</span>
<span class="stat-value" style="color:#f59e0b;">🛄 85</span>
<div class="stat-footer neutral">Avg 2.3 days processing</div>
</div>
<div class="stat-card sc-teal">
<div class="stat-icon-wrap"></div>
<span class="stat-label">Paid Invoices</span>
<div class="stat-value" style="color:#0ea5e9;">{{ number_format($paidInvoices) }}</div>
<div class="stat-footer positive">
@if($totalInvoices > 0)
{{ round(($paidInvoices / $totalInvoices) * 100, 1) }}% success rate
@else
No invoices yet
@endif
</div>
<div class="stat-card">
<span class="stat-label">Delayed</span>
<span class="stat-value" style="color:#e11d48;"> 55</span>
<div class="stat-footer negative">-8% vs last month</div>
</div>
<div class="stat-card sc-purple">
<div class="stat-icon-wrap">👥</div>
<span class="stat-label">Total Customers</span>
<div class="stat-value" style="color:#8b5cf6;">{{ number_format($totalCustomers) }}</div>
<div class="stat-footer">Registered customers</div>
<div class="stat-card">
<span class="stat-label">Delivered</span>
<span class="stat-value" style="color:#22c55e;"> 780</span>
<div class="stat-footer positive">98.2% success rate</div>
</div>
<div class="stat-card sc-red">
<div class="stat-icon-wrap">🏷️</div>
<span class="stat-label">Mark List</span>
<div class="stat-value" style="color:#e74a3b;">{{ number_format($totalMarklist) }}</div>
<div class="stat-footer positive">
{{ $activeMarklist }} active
</div>
</div>
</div>
{{-- ════════ BOTTOM CARDS ════════ --}}
<!-- BOTTOM CARDS -->
<div class="profile-grid-row">
<div class="profile-card">
<h6>Recent Activity</h6>
<ul class="activity-list">
<li>📦 Shipment #SH-2024-001 delivered <span style="color:#a1a5ad;font-size:0.85em;margin-left:auto;">5 min ago</span></li>
<li>📝 New shipment #SH-2024-002 created <span style="color:#a1a5ad;font-size:0.85em;margin-left:auto;">13 min ago</span></li>
<li> Delay reported for #SH-2024-003 <span style="color:#a1a5ad;font-size:0.85em;margin-left:auto;">1 hr ago</span></li>
<li>📦 Shipment #SH-2024-001 delivered <span style="color:#a1a5ad;font-size:0.9em;margin-left:8px;">5 min ago</span></li>
<li>📝 New shipment #SH-2024-002 <span style="color:#a1a5ad;font-size:0.9em;margin-left:8px;">13 min ago</span></li>
<li> Delay for #SH-2024-003 <span style="color:#a1a5ad;font-size:0.9em;margin-left:8px;">1 hr ago</span></li>
</ul>
</div>
<div class="profile-card">
@@ -432,6 +253,5 @@
</ul>
</div>
</div>
</div>
@endsection
@endsection

File diff suppressed because it is too large Load Diff

View File

@@ -15,44 +15,32 @@
@endphp
<style>
/* [ALL YOUR ORIGINAL CSS HERE - SAME AS BEFORE] */
@keyframes fadeInUp {0% { transform: translateY(20px); opacity: 0; }100% { transform: translateY(0); opacity: 1; }}
.card, .custom-table-wrapper { animation: fadeInUp 0.8s ease both; }
/* ✅ ROW HOVER EFFECT COMPLETELY DISABLED */
.custom-table tbody tr {
transition: none !important;
}
.custom-table tbody tr:hover {
background-color: transparent !important;
transform: none !important;
box-shadow: none !important;
}
.custom-table tbody tr { transition: all 0.25s ease-in-out; }
.custom-table tbody tr:hover { background-color: #fffbea; transform: scale(1.01); box-shadow: 0 2px 6px rgba(0,0,0,0.05); }
.priority-badge {
display: inline-flex; align-items: center; font-size: 13.5px; padding: 6px 16px; border-radius: 12px; font-weight: 600;
box-shadow: 0 1px 2px 0 rgba(230, 206, 206, 0.15); width: 90px; min-height: 28px; justify-content: center;
box-shadow: 0 1px 2px 0 rgba(130,130,130,0.15); width: 90px; min-height: 28px; justify-content: center;
color: #fff; margin: 2px 0; transition: transform 0.2s ease-in-out;
}
.priority-badge:hover { transform: scale(1.08); }
.priority-high { background: linear-gradient(135deg, #ff8a8a, #d12929); }
.priority-medium { background: linear-gradient(135deg, #ffe390, #f5b041); }
.priority-low { background: linear-gradient(135deg, #b8f0c2, #1d8660); }
.custom-table thead th {
text-align: center; font-weight: 700; color: #ffffffff; padding: 14px; font-size: 17px; letter-spacing: 0.5px;
border-bottom: 2px solid #bfbfbf; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
text-align: center; font-weight: 700; color: #000; padding: 14px; font-size: 17px; letter-spacing: 0.5px;
border-bottom: 2px solid #bfbfbf; background-color: #fde4b3;
}
.custom-table thead tr:first-child th:first-child { border-top-left-radius: 12px; }
.custom-table thead tr:first-child th:last-child { border-top-right-radius: 12px; }
.custom-table tbody tr:last-child td:first-child { border-bottom-left-radius: 12px; }
.custom-table tbody tr:last-child td:last-child { border-bottom-right-radius: 12px; }
.input-group input { border-radius: 10px 0 0 10px; border: 1px solid #ccc; box-shadow: inset 0 1px 3px rgba(0,0,0,0.05); }
.input-group .btn { border-radius: 0 10px 10px 0; transition: all 0.2s ease-in-out; }
.input-group .btn:hover { background: #ffd65a; border-color: #ffd65a; color: #000; }
.card { border-radius: 16px; border: none; box-shadow: 0 4px 10px rgba(0,0,0,0.08); background: #fff; }
.badge {
font-size: 11px !important; font-weight: 600 !important; padding: 7px 13px !important; border-radius: 20px !important;
text-transform: uppercase; letter-spacing: 0.3px; display: inline-flex !important; align-items: center; justify-content: center;
@@ -66,13 +54,8 @@
@keyframes pulse {0% { box-shadow: 0 0 8px rgba(0,0,0,0.1); }50% { box-shadow: 0 0 14px rgba(0,0,0,0.15); }100% { box-shadow: 0 0 8px rgba(0,0,0,0.1); }}
.count-badge { --bs-badge-padding-x: 0.65em; --bs-badge-padding-y: 0.35em; --bs-badge-font-size: 0.75em; --bs-badge-font-weight: 700; --bs-badge-color: #fff; --bs-badge-border-radius: var(--bs-border-radius); }
h4.fw-bold { background: linear-gradient(90deg, #000000ff 0%, #030302ff 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 800; letter-spacing: 1px; }
.custom-table th,
.custom-table td {
text-align: center;
vertical-align: middle;
}
.custom-table tbody td:last-child { text-align: center !important; vertical-align: middle !important; }
/* ===== PAGINATION STYLES ===== */
.pagination-container {
display: flex;
@@ -214,19 +197,19 @@
margin-bottom: 20px;
gap: 15px;
}
.search-form {
flex: 1;
max-width: 400px;
}
.search-input-group {
display: flex;
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
border-radius: 10px;
overflow: hidden;
}
.search-input {
flex: 1;
border: 1px solid #e2e8f0;
@@ -236,13 +219,13 @@
background-color: #fff;
transition: all 0.3s ease;
}
.search-input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.search-button {
background: #3b82f6;
border: 1px solid #3b82f6;
@@ -254,23 +237,23 @@
align-items: center;
justify-content: center;
}
.search-button:hover {
background: #2563eb;
border-color: #2563eb;
}
.search-icon {
width: 16px;
height: 16px;
}
.status-badges {
display: flex;
gap: 8px;
align-items: center;
}
.status-badge {
font-size: 12px;
font-weight: 600;
@@ -281,34 +264,25 @@
gap: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.status-badge-pending {
background: linear-gradient(135deg, #fef3c7, #fde68a);
color: #d97706;
border: 1px solid #f59e0b;
}
.status-badge-approved {
background: linear-gradient(135deg, #d1fae5, #a7f3d0);
color: #065f46;
border: 1px solid #10b981;
}
.status-badge-rejected {
background: linear-gradient(135deg, #fecaca, #fca5a5);
color: #991b1b;
border: 1px solid #ef4444;
}
/* ✅ TABLE WRAPPER REMOVE HORIZONTAL SCROLL */
.custom-table-wrapper {
overflow-x: hidden !important;
}
.custom-table {
width: 100%;
table-layout: auto;
}
/* Responsive styles */
@media (max-width: 768px) {
.pagination-container {
@@ -319,64 +293,77 @@
.pagination-controls {
justify-content: center;
}
.search-container {
flex-direction: column;
align-items: stretch;
}
.search-form {
max-width: 100%;
}
.status-badges {
justify-content: center;
}
}
/* ==============================================
PROFILE UPDATE REQUEST BUTTON BADGE FIX
============================================== */
/* PROFILE UPDATE REQUEST BUTTON BADGE FIX */
a.btn.btn-primary.position-relative {
position: relative;
margin-right: 10px;
}
/* Ensure button is positioning context */
a.btn.btn-primary.position-relative {
position: relative;
margin-right: 10px;
}
a.btn.btn-primary.position-relative .badge {
width: 30px !important;
height: 30px !important;
min-width: 30px !important;
padding: 0 !important;
font-size: 14px !important;
line-height: 30px !important;
border-radius: 50% !important;
display: inline-flex !important;
align-items: center;
justify-content: center;
animation: none !important;
box-shadow: 0 0 0 2px #ffffff;
}
/* Fix badge inside Profile Update Requests button */
a.btn.btn-primary.position-relative .badge {
width: 30px !important;
height: 30px !important;
min-width: 30px !important;
padding: 0 !important;
font-size: 14px !important;
line-height: 30px !important;
border-radius: 50% !important;
display: inline-flex !important;
align-items: center;
justify-content: center;
animation: none !important;
box-shadow: 0 0 0 2px #ffffff;
}
.custom-table th,
.custom-table td {
text-align: center;
vertical-align: middle;
}
</style>
<!-- Counts -->
<div class="d-flex justify-content-between align-items-center mb-2 mt-3">
<h4 class="fw-bold mb-0">User Requests (Total: {{ $total }})</h4>
<h4 class="fw-bold mb-0">User Requests (Total: {{ $total }})</h4>
@can('request.update_profile')
<a href="{{ route('admin.profile.requests') }}" class="btn btn-primary position-relative">
<i class="bi bi-person-lines-fill me-1"></i>
Profile Update Requests
@can('request.update_profile')
<a href="{{ route('admin.profile.requests') }}" class="btn btn-primary position-relative">
<i class="bi bi-person-lines-fill me-1"></i>
Profile Update Requests
@if($pendingProfileUpdates > 0)
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
{{ $pendingProfileUpdates }}
</span>
@endif
</a>
@endcan
</div>
@if($pendingProfileUpdates > 0)
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
{{ $pendingProfileUpdates }}
</span>
@endif
</a>
@endcan
</div>
<!-- Search + Table -->
<div class="card mb-4 shadow-sm">
<div class="card-body pb-1">
<!-- Updated Search Bar with Status Badges in the same line -->
<div class="search-container">
<form method="GET" action="" class="search-form">
<div class="search-input-group">
@@ -388,7 +375,7 @@
</button>
</div>
</form>
<div class="status-badges">
<span class="status-badge status-badge-pending">{{ $requests->where('status', 'pending')->count() }} Pending</span>
<span class="status-badge status-badge-approved">{{ $requests->where('status', 'approved')->count() }} Approved</span>
@@ -398,21 +385,9 @@
<div class="table-responsive custom-table-wrapper">
<table class="table align-middle mb-0 custom-table">
<thead>
<tr>
<th>#</th>
<th>Request ID</th>
<th>Name</th>
<th>Company</th>
<th>Email</th>
<th>Mobile</th>
<th>Address</th>
<th>Priority</th>
<th>Date</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<thead><tr>
<th>#</th><th>Request ID</th><th>Name</th><th>Company</th><th>Email</th><th>Mobile</th><th>Address</th><th>Priority</th><th>Date</th><th>Status</th><th>Actions</th>
</tr></thead>
<tbody>
@forelse($currentItems as $index => $req)
<tr>
@@ -424,41 +399,33 @@
<td>{{ $req->mobile_no }}</td>
<td>{{ Str::limit($req->address, 30) }}</td>
<td>
@if(strtolower($req->priority) == 'high')
<span class="priority-badge priority-high">High</span>
@elseif(strtolower($req->priority) == 'medium')
<span class="priority-badge priority-medium">Medium</span>
@elseif(strtolower($req->priority) == 'low')
<span class="priority-badge priority-low">Low</span>
@else
{{ $req->priority ?? 'N/A' }}
@endif
@if(strtolower($req->priority) == 'high')<span class="priority-badge priority-high">High</span>
@elseif(strtolower($req->priority) == 'medium')<span class="priority-badge priority-medium">Medium</span>
@elseif(strtolower($req->priority) == 'low')<span class="priority-badge priority-low">Low</span>
@else{{ $req->priority ?? 'N/A' }}@endif
</td>
<td>{{ $req->date }}</td>
<td>
@if($req->status == 'approved')
<span class="badge badge-approved"><i class="bi bi-check-circle-fill status-icon"></i>Approved</span>
@elseif($req->status == 'rejected')
<span class="badge badge-rejected"><i class="bi bi-x-circle-fill status-icon"></i>Rejected</span>
@else
<span class="badge badge-pending"><i class="bi bi-clock-fill status-icon"></i>Pending</span>
@endif
@if($req->status == 'approved')<span class="badge badge-approved"><i class="bi bi-check-circle-fill status-icon"></i>Approved</span>
@elseif($req->status == 'rejected')<span class="badge badge-rejected"><i class="bi bi-x-circle-fill status-icon"></i>Rejected</span>
@else<span class="badge badge-pending"><i class="bi bi-clock-fill status-icon"></i>Pending</span>@endif
</td>
<td>
@if($req->status == 'pending')
<a href="{{ route('admin.requests.approve', $req->id) }}"
class="btn btn-success btn-sm">
<a href="{{ route('admin.requests.approve', $req->id) }}"
class="btn btn-success btn-sm">
<i class="bi bi-check-circle"></i> Approve
</a>
<a href="{{ route('admin.requests.reject', $req->id) }}"
class="btn btn-danger btn-sm">
<a href="{{ route('admin.requests.reject', $req->id) }}"
class="btn btn-danger btn-sm">
<i class="bi bi-x-circle"></i> Reject
</a>
@else
<span class="text-muted">No Action</span>
@endif
</td>
</tr>
@empty
<tr><td colspan="11" class="text-center text-muted py-4">No records found.</td></tr>
@@ -537,4 +504,4 @@
</div>
</div>
</div>
@endsection
@endsection

View File

@@ -1400,6 +1400,7 @@
</tr>
</thead>
<tbody>
@forelse($availableOrders as $order)
<tr>
<td>

View File

@@ -4,12 +4,6 @@
@section('content')
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
overflow-x: hidden;
}
/* Hide scrollbar but keep scroll functionality */
html,

View File

@@ -4,12 +4,6 @@
@section('content')
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
overflow-x: hidden;
}
:root {
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);

View File

@@ -4,465 +4,506 @@
@section('content')
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
overflow-x: hidden;
}
:root {
--primary: #4361ee;
--primary-dark: #3a56d4;
--secondary: #f72585;
--success: #4cc9f0;
--warning: #f8961e;
--danger: #e63946;
--light: #f8f9fa;
--dark: #212529;
--gray: #6c757d;
--border: #e2e8f0;
--card-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--hover-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
--gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
:root {
--primary: #4361ee;
--primary-dark: #3a56d4;
--secondary: #f72585;
--success: #4cc9f0;
--warning: #f8961e;
--danger: #e63946;
--light: #f8f9fa;
--dark: #212529;
--gray: #6c757d;
--border: #e2e8f0;
--card-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--hover-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
--gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
/* Search Bar - Similar to Shipment */
.search-staff-bar {
display: flex;
align-items: center;
gap: 15px;
padding: 20px;
background: var(--gradient-primary);
border-radius: 16px;
box-shadow: var(--card-shadow);
flex-wrap: wrap;
margin-bottom: 30px;
color: white;
position: relative;
overflow: hidden;
}
.search-staff-bar::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255,255,255,0.1);
z-index: 0;
}
.search-staff-bar > * {
position: relative;
z-index: 1;
}
.search-staff-bar input,
.search-staff-bar select {
padding: 12px 16px;
border: 1px solid rgba(255,255,255,0.2);
border-radius: 10px;
flex: 1;
min-width: 150px;
background: rgba(255,255,255,0.9);
font-weight: 500;
transition: all 0.3s ease;
color: var(--dark);
}
.search-staff-bar input:focus,
.search-staff-bar select:focus {
background: white;
box-shadow: 0 0 0 3px rgba(255,255,255,0.3);
outline: none;
}
.btn-add-staff {
background: rgba(255,255,255,0.2);
backdrop-filter: blur(10px);
color: white;
border: 1px solid rgba(255,255,255,0.3);
padding: 12px 24px;
border-radius: 10px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
white-space: nowrap;
font-weight: 600;
text-decoration: none;
}
.btn-add-staff:hover {
background: rgba(255,255,255,0.3);
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}
.search-icon {
font-size: 20px;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
}
.user-icon {
font-size: 18px;
}
@media (max-width: 768px) {
/* Search Bar - Similar to Shipment */
.search-staff-bar {
flex-direction: column;
align-items: stretch;
display: flex;
align-items: center;
gap: 15px;
padding: 20px;
background: var(--gradient-primary);
border-radius: 16px;
box-shadow: var(--card-shadow);
flex-wrap: wrap;
margin-bottom: 30px;
color: white;
position: relative;
overflow: hidden;
}
.search-staff-bar input,
.search-staff-bar::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255,255,255,0.1);
z-index: 0;
}
.search-staff-bar > * {
position: relative;
z-index: 1;
}
.search-staff-bar input,
.search-staff-bar select {
width: 100%;
padding: 12px 16px;
border: 1px solid rgba(255,255,255,0.2);
border-radius: 10px;
flex: 1;
min-width: 150px;
background: rgba(255,255,255,0.9);
font-weight: 500;
transition: all 0.3s ease;
color: var(--dark);
}
}
/* Card Styles - Same as Shipment */
.card {
border: none;
border-radius: 16px;
box-shadow: var(--card-shadow);
transition: all 0.3s ease;
overflow: hidden;
}
.card:hover {
transform: translateY(-5px);
box-shadow: var(--hover-shadow);
}
.card-header {
background: var(--gradient-primary);
color: white;
border: none;
padding: 20px 25px;
border-radius: 16px 16px 0 0 !important;
}
.card-header h5 {
margin: 0;
font-weight: 700;
display: flex;
align-items: center;
gap: 10px;
}
/* Table Styles - Similar to Shipment */
.table-responsive {
border-radius: 0 0 16px 16px;
overflow-x: hidden; /* horizontal scroll remove */
}
.table {
margin: 0;
border-collapse: separate;
border-spacing: 0;
width: 100%;
padding: 0;
}
.table thead th {
background: #f8f9fa;
border: none;
padding: 16px 12px;
font-weight: 700;
color: var(--dark);
text-align: left;
vertical-align: middle;
border-bottom: 2px solid var(--border);
position: relative;
}
.table tbody tr {
transition: all 0.3s ease;
}
/* hover stable ठेवण्यासाठी */
.table tbody tr:hover {
background-color: inherit;
transform: none;
box-shadow: none;
}
.table tbody td {
padding: 14px 12px;
text-align: left;
vertical-align: middle;
border-bottom: 1px solid var(--border);
font-weight: 500;
}
.table tbody tr:last-child td {
border-bottom: none;
}
/* Status Badges */
.badge {
padding: 6px 12px !important;
border-radius: 20px !important;
font-weight: 600 !important;
font-size: 12px !important;
border: 2px solid transparent !important;
min-width: 80px !important;
text-align: center !important;
display: inline-block !important;
line-height: 1.2 !important;
}
.badge-active {
background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important;
color: #065f46 !important;
border-color: #10b981 !important;
}
.badge-inactive {
background: linear-gradient(135deg, #fecaca, #fca5a5) !important;
color: #991b1b !important;
border-color: #ef4444 !important;
}
.badge-pending {
background: linear-gradient(135deg, #fef3c7, #fde68a) !important;
color: #92400e !important;
border-color: #f59e0b !important;
}
/* Employee ID Badge */
.employee-id-badge {
font-family: 'Courier New', monospace;
background: rgba(67, 97, 238, 0.1);
padding: 4px 8px;
border-radius: 6px;
font-size: 0.85rem;
color: var(--primary);
border: 1px solid rgba(67, 97, 238, 0.2);
display: inline-block;
}
/* Action Buttons */
.action-buttons {
display: flex;
gap: 8px;
}
.btn-action {
padding: 6px 12px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
text-decoration: none;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 5px;
border: none;
cursor: pointer;
}
.btn-edit {
background: linear-gradient(135deg, #4cc9f0, #4361ee);
color: white;
}
.btn-edit:hover {
background: linear-gradient(135deg, #38bdf8, #3a56d4);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(76, 201, 240, 0.3);
}
.btn-delete {
background: linear-gradient(135deg, #f87171, #ef4444);
color: white;
}
.btn-delete:hover {
background: linear-gradient(135deg, #ef4444, #dc2626);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
}
/* Success Message */
.alert-success {
background: linear-gradient(135deg, #e6ffed, #d1f7e5);
border: 1px solid #b6f0c6;
border-left: 4px solid var(--success);
color: #0f5132;
padding: 1rem 1.25rem;
border-radius: 10px;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.75rem;
}
.alert-success:before {
content: '✓';
background: var(--success);
color: white;
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--gray);
}
.empty-state:before {
content: '👤';
font-size: 3rem;
display: block;
margin-bottom: 1rem;
opacity: 0.5;
}
/* Role Badges */
.role-badge {
padding: 4px 8px;
border-radius: 6px;
font-size: 11px;
font-weight: 600;
background: rgba(67, 97, 238, 0.1);
color: var(--primary);
border: 1px solid rgba(67, 97, 238, 0.2);
}
/* Stats Cards */
.stats-cards {
display: flex;
gap: 20px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.stat-card {
flex: 1;
min-width: 200px;
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: var(--card-shadow);
display: flex;
align-items: center;
gap: 15px;
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-3px);
box-shadow: var(--hover-shadow);
}
.stat-icon {
width: 50px;
height: 50px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.stat-icon.total {
background: linear-gradient(135deg, #e6f3ff, #c2d9ff);
color: var(--primary);
}
.stat-icon.active {
background: linear-gradient(135deg, #d1fae5, #a7f3d0);
color: #10b981;
}
.stat-content h3 {
font-size: 1.8rem;
font-weight: 700;
margin: 0;
color: var(--dark);
}
.stat-content p {
color: var(--gray);
margin: 4px 0 0 0;
font-size: 0.875rem;
}
/* Pagination - Same as Shipment */
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding: 12px 25px;
border-top: 1px solid #eef3fb;
}
.pagination-info {
font-size: 13px;
color: #9ba5bb;
font-weight: 600;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
}
.pagination-btn:hover:not(:disabled) {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-btn:disabled {
background: #f8fafc;
color: #cbd5e0;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.6;
}
.pagination-page-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 36px;
text-align: center;
}
.pagination-page-btn:hover {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-page-btn.active {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-pages {
display: flex;
gap: 4px;
align-items: center;
}
.pagination-ellipsis {
color: #9ba5bb;
font-size: 13px;
padding: 0 4px;
}
@media (max-width: 768px) {
.stats-cards {
flex-direction: column;
.search-staff-bar input:focus,
.search-staff-bar select:focus {
background: white;
box-shadow: 0 0 0 3px rgba(255,255,255,0.3);
outline: none;
}
.stat-card {
min-width: 100%;
.btn-add-staff {
background: rgba(255,255,255,0.2);
backdrop-filter: blur(10px);
color: white;
border: 1px solid rgba(255,255,255,0.3);
padding: 12px 24px;
border-radius: 10px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
white-space: nowrap;
font-weight: 600;
text-decoration: none;
}
.pagination-container {
flex-direction: column;
.btn-add-staff:hover {
background: rgba(255,255,255,0.3);
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}
.search-icon {
font-size: 20px;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
}
.user-icon {
font-size: 18px;
}
@media (max-width: 768px) {
.search-staff-bar {
flex-direction: column;
align-items: stretch;
}
.search-staff-bar input,
.search-staff-bar select {
width: 100%;
}
}
/* Card Styles - Same as Shipment */
.card {
border: none;
border-radius: 16px;
box-shadow: var(--card-shadow);
transition: all 0.3s ease;
overflow: hidden;
}
.card:hover {
transform: translateY(-5px);
box-shadow: var(--hover-shadow);
}
.card-header {
background: var(--gradient-primary);
color: white;
border: none;
padding: 20px 25px;
border-radius: 16px 16px 0 0 !important;
}
.card-header h5 {
margin: 0;
font-weight: 700;
display: flex;
align-items: center;
gap: 10px;
align-items: stretch;
}
.pagination-controls {
/* Table Styles - Similar to Shipment */
.table-responsive {
border-radius: 0 0 16px 16px;
overflow-x: auto;
}
.table {
margin: 0;
border-collapse: separate;
border-spacing: 0;
width: 100%;
padding: 0;
}
.table thead th {
background: #f8f9fa;
border: none;
padding: 16px 12px;
font-weight: 700;
color: var(--dark);
text-align: left;
vertical-align: middle;
border-bottom: 2px solid var(--border);
position: relative;
}
.table tbody tr {
transition: all 0.3s ease;
}
.table tbody tr:hover {
background-color: #f8f9ff;
transform: scale(1.01);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.table tbody td {
padding: 14px 12px;
text-align: left;
vertical-align: middle;
border-bottom: 1px solid var(--border);
font-weight: 500;
}
.table tbody tr:last-child td {
border-bottom: none;
}
/* Status Badges - Similar Style */
.badge {
padding: 6px 12px !important;
border-radius: 20px !important;
font-weight: 600 !important;
font-size: 12px !important;
border: 2px solid transparent !important;
min-width: 80px !important;
text-align: center !important;
display: inline-block !important;
line-height: 1.2 !important;
}
.badge-active {
background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important;
color: #065f46 !important;
border-color: #10b981 !important;
}
.badge-inactive {
background: linear-gradient(135deg, #fecaca, #fca5a5) !important;
color: #991b1b !important;
border-color: #ef4444 !important;
}
.badge-pending {
background: linear-gradient(135deg, #fef3c7, #fde68a) !important;
color: #92400e !important;
border-color: #f59e0b !important;
}
/* Employee ID Badge - Similar to Shipment ID */
.employee-id-badge {
font-family: 'Courier New', monospace;
background: rgba(67, 97, 238, 0.1);
padding: 4px 8px;
border-radius: 6px;
font-size: 0.85rem;
color: var(--primary);
border: 1px solid rgba(67, 97, 238, 0.2);
display: inline-block;
}
/* Action Buttons - Similar Style */
.action-buttons {
display: flex;
gap: 8px;
}
.btn-action {
padding: 6px 12px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
text-decoration: none;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 5px;
border: none;
cursor: pointer;
}
.btn-edit {
background: linear-gradient(135deg, #4cc9f0, #4361ee);
color: white;
}
.btn-edit:hover {
background: linear-gradient(135deg, #38bdf8, #3a56d4);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(76, 201, 240, 0.3);
}
.btn-delete {
background: linear-gradient(135deg, #f87171, #ef4444);
color: white;
}
.btn-delete:hover {
background: linear-gradient(135deg, #ef4444, #dc2626);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
}
/* Success Message - Similar Style */
.alert-success {
background: linear-gradient(135deg, #e6ffed, #d1f7e5);
border: 1px solid #b6f0c6;
border-left: 4px solid var(--success);
color: #0f5132;
padding: 1rem 1.25rem;
border-radius: 10px;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.75rem;
}
.alert-success:before {
content: '✓';
background: var(--success);
color: white;
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--gray);
}
.empty-state:before {
content: '👤';
font-size: 3rem;
display: block;
margin-bottom: 1rem;
opacity: 0.5;
}
/* Role Badges */
.role-badge {
padding: 4px 8px;
border-radius: 6px;
font-size: 11px;
font-weight: 600;
background: rgba(67, 97, 238, 0.1);
color: var(--primary);
border: 1px solid rgba(67, 97, 238, 0.2);
}
/* Stats Cards - Similar to Shipment Totals */
.stats-cards {
display: flex;
gap: 20px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.stat-card {
flex: 1;
min-width: 200px;
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: var(--card-shadow);
display: flex;
align-items: center;
gap: 15px;
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-3px);
box-shadow: var(--hover-shadow);
}
.stat-icon {
width: 50px;
height: 50px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.stat-icon.total {
background: linear-gradient(135deg, #e6f3ff, #c2d9ff);
color: var(--primary);
}
.stat-icon.active {
background: linear-gradient(135deg, #d1fae5, #a7f3d0);
color: #10b981;
}
.stat-content h3 {
font-size: 1.8rem;
font-weight: 700;
margin: 0;
color: var(--dark);
}
.stat-content p {
color: var(--gray);
margin: 4px 0 0 0;
font-size: 0.875rem;
}
/* Pagination - Same as Shipment */
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding: 12px 25px;
border-top: 1px solid #eef3fb;
}
.pagination-info {
font-size: 13px;
color: #9ba5bb;
font-weight: 600;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
}
.pagination-btn:hover:not(:disabled) {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-btn:disabled {
background: #f8fafc;
color: #cbd5e0;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.6;
}
.pagination-page-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 36px;
text-align: center;
}
.pagination-page-btn:hover {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-page-btn.active {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-pages {
display: flex;
gap: 4px;
align-items: center;
}
.pagination-ellipsis {
color: #9ba5bb;
font-size: 13px;
padding: 0 4px;
}
@media (max-width: 768px) {
.stats-cards {
flex-direction: column;
}
.stat-card {
min-width: 100%;
}
.pagination-container {
flex-direction: column;
gap: 10px;
align-items: stretch;
}
.pagination-controls {
justify-content: center;
}
}
}
</style>
<div class="container-fluid py-4">
@if(session('success'))
<div class="alert-success">
{{ session('success') }}
@@ -675,7 +716,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Search filter
if (searchTerm) {
const matchesSearch =
const matchesSearch =
staff.name.toLowerCase().includes(searchTerm) ||
staff.email.toLowerCase().includes(searchTerm) ||
(staff.employee_id && staff.employee_id.toLowerCase().includes(searchTerm)) ||
@@ -852,4 +893,4 @@ function renderTable() {
});
}
</script>
@endsection
@endsection

View File

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<title>Laravel Test Page</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
/* Paste the provided CSS here */
</style>
</head>
<body class="bg-light">
<div class="container mt-5">
<div class="card shadow-lg border-0 rounded-4">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Laravel Gitea Test Page</h4>
</div>
<div class="card-body">
<h5 class="text-success"> Laravel is running successfully!</h5>
<p class="mt-3">If you see this page, your Blade views and routing work perfectly.</p>
<ul class="list-group mt-4">
<li class="list-group-item"><strong>PHP Version:</strong> {{ PHP_VERSION }}</li>
<li class="list-group-item"><strong>Laravel Version:</strong> {{ app()->version() }}</li>
<li class="list-group-item"><strong>Environment:</strong> {{ app()->environment() }}</li>
</ul>
<a href="{{ url('/') }}" class="btn btn-outline-primary mt-4">Go to Home testing done</a>
</div>
</div>
</div>
</body>
</html>

View File

@@ -12,78 +12,48 @@ use App\Http\Controllers\Admin\AdminAccountController;
use App\Http\Controllers\Admin\AdminReportController;
use App\Http\Controllers\Admin\AdminStaffController;
use App\Http\Controllers\Admin\AdminChatController;
use Illuminate\Support\Facades\Broadcast;
use App\Http\Controllers\ContainerController;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Http\Request;
// ---------------------------
// Public Front Page
// ---------------------------
Route::get('/', function () {
return redirect()->route('admin.login');
return view('welcome');
});
// ==========================================
// BROADCASTING AUTH (FOR ADMIN SESSION)
// ==========================================
Broadcast::routes(['middleware' => ['web']]);
Route::post('/broadcasting/auth', function (\Illuminate\Http\Request $request) {
\Log::info('🎯 Broadcasting Auth Request', [
'channel' => $request->input('channel_name'),
'admin_check' => auth('admin')->check(),
'web_check' => auth('web')->check(),
]);
if (auth('admin')->check()) {
\Log::info('✅ Admin authenticated', ['id' => auth('admin')->id()]);
return Broadcast::auth($request);
}
if (auth('web')->check()) {
\Log::info('✅ Web user authenticated', ['id' => auth('web')->id()]);
return Broadcast::auth($request);
}
\Log::warning('❌ No authentication found');
return response()->json(['message' => 'Unauthenticated'], 403);
})->middleware('web');
// ---------------------------
// ADMIN LOGIN ROUTES
// ---------------------------
Route::prefix('admin')->group(function () {
// login routes (public)
Route::prefix('admin')->middleware('web')->group(function () {
Route::get('/login', [AdminAuthController::class, 'showLoginForm'])->name('admin.login');
Route::post('/login', [AdminAuthController::class, 'login'])->name('admin.login.submit');
Route::post('/logout', [AdminAuthController::class, 'logout'])->name('admin.logout');
});
});
Route::get('/login', function () {
return redirect()->route('admin.login');
})->name('login');
Broadcast::routes(['middleware' => ['web']]);
// ==========================================
// PROTECTED ADMIN ROUTES (session protected)
// ==========================================
Route::prefix('admin')
->middleware('auth:admin')
->middleware(['web', 'auth:admin'])
->group(function () {
// Dashboard
Route::get('/dashboard', [AdminOrderController::class, 'dashboard'])
Route::get('/dashboard', [AdminOrderController::class, 'index'])
->name('admin.dashboard');
Route::get('/reports/containers/excel', [AdminReportController::class, 'containerReportExcel'])
->name('admin.reports.containers.excel');
Route::get('/reports/containers/pdf', [AdminReportController::class, 'containerReportPdf'])
->name('admin.reports.containers.pdf');
//reports DOWNLOAD
Route::get('/reports', [AdminReportController::class, 'containerReport'])
->name('admin.reports');
Route::get('/reports', [AdminReportController::class, 'index'])->name('admin.reports');
Route::get('/chat-support', fn() => view('admin.chat_support'))
->name('admin.chat_support');
@@ -94,311 +64,294 @@ Route::prefix('admin')
Route::get('/account', fn() => view('admin.account'))
->name('admin.account');
Route::get('/profile', [AdminAuthController::class, 'profile'])->name('admin.profile');
Route::get('/profile', fn() => view('admin.profile'))
->name('admin.profile');
Route::post(
'admin/orders/upload-excel-preview',
[\App\Http\Controllers\Admin\AdminOrderController::class, 'uploadExcelPreview']
)->name('admin.orders.upload.excel.preview');
//---------------------------
// CONTAINER ROUTES
//---------------------------
Route::get('/containers', [ContainerController::class, 'index'])
->name('containers.index');
Route::get('/containers/create', [ContainerController::class, 'create'])
->name('containers.create');
Route::post('/containers', [ContainerController::class, 'store'])
->name('containers.store');
Route::get('/containers/{container}', [ContainerController::class, 'show'])
->name('containers.show');
Route::post('/containers/{container}/update-rows', [ContainerController::class, 'updateRows'])
->name('containers.rows.update');
Route::post('containers/{container}/status', [ContainerController::class, 'updateStatus'])
->name('containers.update-status');
Route::delete('/containers/{container}', [ContainerController::class, 'destroy'])
->name('containers.destroy');
Route::get('containers/{container}/download-pdf', [ContainerController::class, 'downloadPdf'])
->name('containers.download.pdf');
Route::get('/admin/containers/{container}/download-excel', [ContainerController::class, 'downloadExcel'])
->name('containers.download.excel');
// ---------------------------
// USER REQUESTS
// ---------------------------
Route::get('/requests', [UserRequestController::class, 'index'])
->name('admin.requests');
Route::get('/requests', [UserRequestController::class, 'index'])
->name('admin.requests');
Route::get('/requests/approve/{id}', [UserRequestController::class, 'approve'])
->name('admin.requests.approve');
Route::get('/requests/approve/{id}', [UserRequestController::class, 'approve'])
->name('admin.requests.approve');
Route::get('/requests/reject/{id}', [UserRequestController::class, 'reject'])
->name('admin.requests.reject');
Route::get('/requests/reject/{id}', [UserRequestController::class, 'reject'])
->name('admin.requests.reject');
// PROFILE UPDATE REQUESTS
Route::get('/profile-update-requests',
Route::get('/profile-update-requests',
[UserRequestController::class, 'profileUpdateRequests']
)->name('admin.profile.requests');
Route::get('/profile-update/approve/{id}',
Route::get('/profile-update/approve/{id}',
[UserRequestController::class, 'approveProfileUpdate']
)->name('admin.profile.approve');
Route::get('/profile-update/reject/{id}',
Route::get('/profile-update/reject/{id}',
[UserRequestController::class, 'rejectProfileUpdate']
)->name('admin.profile.reject');
// ---------------------------
// MARK LIST
// ---------------------------
Route::get('/mark-list', [AdminMarkListController::class, 'index'])
->name('admin.marklist.index');
Route::get('/mark-list', [AdminMarkListController::class, 'index'])
->name('admin.marklist.index');
Route::get('/mark-list/status/{id}', [AdminMarkListController::class, 'toggleStatus'])
->name('admin.marklist.toggle');
Route::get('/mark-list/status/{id}', [AdminMarkListController::class, 'toggleStatus'])
->name('admin.marklist.toggle');
// ---------------------------
// ORDERS (UPDATED)
// ORDERS
// ---------------------------
Route::get('/orders', [AdminOrderController::class, 'index'])
->name('admin.orders');
Route::get('/orders', [AdminOrderController::class, 'orderShow'])
->name('admin.orders');
Route::get('/orders/list', [AdminOrderController::class, 'index'])
->name('admin.orders.index');
Route::get('/orders/{id}', [AdminOrderController::class, 'show'])
->name('admin.orders.show');
Route::get('/orders/list', [AdminOrderController::class, 'index'])
->name('admin.orders.index');
Route::post('/orders/temp/add', [AdminOrderController::class, 'addTempItem'])
->name('admin.orders.temp.add');
Route::get('/orders/{id}', [AdminOrderController::class, 'show'])
->name('admin.orders.show');
Route::post('/orders/temp/delete', [AdminOrderController::class, 'deleteTempItem'])
->name('admin.orders.temp.delete');
Route::post('/orders/temp/add', [AdminOrderController::class, 'addTempItem'])
->name('admin.orders.temp.add');
Route::post('/orders/temp/reset', [AdminOrderController::class, 'resetTemp'])
->name('admin.orders.temp.reset');
Route::post('/orders/temp/delete', [AdminOrderController::class, 'deleteTempItem'])
->name('admin.orders.temp.delete');
Route::post('/orders/finish', [AdminOrderController::class, 'finishOrder'])
->name('admin.orders.finish');
Route::post('/orders/temp/reset', [AdminOrderController::class, 'resetTemp'])
->name('admin.orders.temp.reset');
Route::get('/orders/view/{id}', [AdminOrderController::class, 'popup'])
->name('admin.orders.popup');
Route::post('/orders/finish', [AdminOrderController::class, 'finishOrder'])
->name('admin.orders.finish');
Route::get('/orders/{id}/see', [AdminOrderController::class, 'see'])
->name('orders.see');
Route::get('/orders/view/{id}', [AdminOrderController::class, 'popup'])
->name('admin.orders.popup');
Route::post('/admin/orders/temp/add', [AdminOrderController::class, 'addTempItem'])
->name('admin.orders.temp.add');
// Route::get('/orders/{id}', [AdminOrderController::class, 'view'])
// ->name('admin.orders.view');
Route::get('/orders/{order:order_id}/see', [AdminOrderController::class, 'see'])
->name('admin.orders.see');
// ---------------------------
// ORDERS (FIXED ROUTES)
// ---------------------------
Route::post('/orders/{order}/item', [AdminOrderController::class, 'addItem'])
->name('admin.orders.addItem');
Route::delete('/orders/item/{id}', [AdminOrderController::class, 'deleteItem'])
->name('admin.orders.deleteItem');
Route::post('/orders/item/{id}/restore', [AdminOrderController::class, 'restoreItem'])
->name('admin.orders.restoreItem');
Route::put('/orders/item/update/{id}', [AdminOrderController::class, 'updateItem'])
// Add item to order
Route::post('/orders/{order}/item', [AdminOrderController::class, 'addItem'])
->name('admin.orders.addItem');
// Delete item from order
Route::delete('/orders/item/{id}', [AdminOrderController::class, 'deleteItem'])
->name('admin.orders.deleteItem');
// Restore deleted item
Route::post('/orders/item/{id}/restore', [AdminOrderController::class, 'restoreItem'])
->name('admin.orders.restoreItem');
// Update main order fields
Route::put('/admin/orders/item/update/{id}', [AdminOrderController::class, 'updateItem'])
->name('admin.orders.updateItem');
// Delete full order
Route::delete('/orders/{id}/delete', [AdminOrderController::class, 'destroy'])
->name('admin.orders.destroy');
Route::delete('/orders/{id}/delete', [AdminOrderController::class, 'destroy'])
->name('admin.orders.destroy');
// ---------------------------
// SHIPMENTS (FIXED ROUTES)
// ---------------------------
Route::get('/shipments', [ShipmentController::class, 'index'])
->name('admin.shipments');
Route::post('/orders/upload-excel-preview',
[AdminOrderController::class, 'uploadExcelPreview']
)->name('admin.orders.upload.excel.preview');
Route::post('/shipments', [ShipmentController::class, 'store'])
->name('admin.shipments.store');
// ---------------------------
// SHIPMENTS (FIXED ROUTES)
// ---------------------------
Route::get('/shipments', [ShipmentController::class, 'index'])
->name('admin.shipments');
Route::post('/shipments', [ShipmentController::class, 'store'])
->name('admin.shipments.store');
Route::post('/shipments/update-status', [ShipmentController::class, 'updateStatus'])
->name('admin.shipments.updateStatus');
// Get shipment orders for modal (AJAX)
Route::get('/shipments/{id}/orders', [ShipmentController::class, 'getShipmentOrders'])
->name('admin.shipments.orders');
// Get shipment details for edit (AJAX)
Route::get('/shipments/{id}', [ShipmentController::class, 'show'])
->name('admin.shipments.show');
// Shipment Update
Route::put('/shipments/{id}', [ShipmentController::class, 'update'])
->name('admin.shipments.update');
// Shipment Delete
Route::delete('/shipments/{id}', [ShipmentController::class, 'destroy'])
->name('admin.shipments.destroy');
Route::get('/shipment/dummy/{id}', [ShipmentController::class, 'dummy'])
->name('admin.shipments.dummy');
Route::post('/shipments/update-status', [ShipmentController::class, 'updateStatus'])
->name('admin.shipments.updateStatus');
Route::get('/shipments/{id}/orders', [ShipmentController::class, 'getShipmentOrders'])
->name('admin.shipments.orders');
Route::get('/shipments/{id}', [ShipmentController::class, 'show'])
->name('admin.shipments.show');
Route::put('/shipments/{id}', [ShipmentController::class, 'update'])
->name('admin.shipments.update');
Route::delete('/shipments/{id}', [ShipmentController::class, 'destroy'])
->name('admin.shipments.destroy');
Route::get('/shipment/dummy/{id}', [ShipmentController::class, 'dummy'])
->name('admin.shipments.dummy');
// web.php
Route::delete('/shipments/{shipment}/orders/{order}',
[ShipmentController::class, 'removeOrder']
)->name('admin.shipments.removeOrder');
Route::post('/shipments/{shipment}/add-orders',
[ShipmentController::class, 'addOrders']
)->name('admin.shipments.addOrders');
Route::get('/shipment/dummy/{id}', [ShipmentController::class, 'dummy'])
->name('admin.shipments.dummy');
// ---------------------------
// INVOICES
// ---------------------------
Route::get('/invoices', [AdminInvoiceController::class, 'index'])
->name('admin.invoices.index');
Route::get('/invoices', [AdminInvoiceController::class, 'index'])
->name('admin.invoices.index');
Route::get('/invoices/{id}/popup', [AdminInvoiceController::class, 'popup'])
->name('admin.invoices.popup');
Route::get('/invoices/{id}/popup', [AdminInvoiceController::class, 'popup'])
->name('admin.invoices.popup');
Route::get('/invoices/{id}/edit', [AdminInvoiceController::class, 'edit'])
->name('admin.invoices.edit');
Route::get('/invoices/{id}/edit', [AdminInvoiceController::class, 'edit'])
->name('admin.invoices.edit');
Route::post('/invoices/{id}/update', [AdminInvoiceController::class, 'update'])
->name('admin.invoices.update');
Route::post('/invoices/{id}/update', [AdminInvoiceController::class, 'update'])
->name('admin.invoices.update');
Route::post('/invoices/{invoice}/installments', [AdminInvoiceController::class, 'storeInstallment'])
Route::post('/invoices/{invoice}/installments', [AdminInvoiceController::class, 'storeInstallment'])
->name('admin.invoice.installment.store');
Route::post('/invoices/{id}/installment', [AdminInvoiceController::class, 'storeInstallment'])
->name('admin.invoice.installment.store');
Route::post('/invoices/{id}/installment', [AdminInvoiceController::class, 'storeInstallment'])
->name('admin.invoice.installment.store');
Route::get(
'/admin/invoices/{id}/download',
[AdminInvoiceController::class, 'downloadInvoice']
)->name('admin.invoices.download');
Route::delete('/installment/{id}', [AdminInvoiceController::class, 'deleteInstallment'])
->name('admin.invoice.installment.delete');
->name('admin.invoice.installment.delete');
Route::put('admin/invoices/{invoice}/items', [AdminInvoiceController::class, 'updateItems'])
->name('admin.invoices.items.update');
Route::post('/admin/invoices/{invoice}/charge-group', [AdminInvoiceController::class, 'storeChargeGroup'])
->name('admin.invoices.charge-group.store');
// जर create page वापरायचा असेल तर AdminInvoiceController मध्ये create() असावा.
// नसेल तर हा route comment / delete कर.
// Route::get('/admin/invoices/create', [AdminInvoiceController::class, 'create'])
// ->name('admin.invoices.create');
// //Add New Invoice
Route::get('/admin/invoices/create', [InvoiceController::class, 'create'])->name('admin.invoices.create');
// ---------------------------
// CUSTOMERS
// ---------------------------
Route::get('/customers', [AdminCustomerController::class, 'index'])
->name('admin.customers.index');
Route::get('/customers', [AdminCustomerController::class, 'index'])
->name('admin.customers.index');
Route::get('/customers/add', [AdminCustomerController::class, 'create'])
->name('admin.customers.add');
Route::get('/customers/add', [AdminCustomerController::class, 'create'])
->name('admin.customers.add');
Route::post('/customers/store', [AdminCustomerController::class, 'store'])
->name('admin.customers.store');
Route::post('/customers/store', [AdminCustomerController::class, 'store'])
->name('admin.customers.store');
Route::get('/customers/{id}/view', [AdminCustomerController::class, 'view'])
->name('admin.customers.view');
Route::get('/customers/{id}/view', [AdminCustomerController::class, 'view'])
->name('admin.customers.view');
Route::post('/customers/{id}/status', [AdminCustomerController::class, 'toggleStatus'])
->name('admin.customers.status');
Route::post('/customers/{id}/status', [AdminCustomerController::class, 'toggleStatus'])
->name('admin.customers.status');
// CHAT
Route::get('/chat-support', [AdminChatController::class, 'index'])
->name('admin.chat_support');
Route::get('/chat-support/{ticketId}', [AdminChatController::class, 'openChat'])
->name('admin.chat.open');
// Chat list page
Route::get('/chat-support', [AdminChatController::class, 'index'])
->name('admin.chat_support');
Route::post('/chat-support/{ticketId}/send', [AdminChatController::class, 'sendMessage'])
->name('admin.chat.send');
// Chat window (open specific user's chat)
Route::get('/chat-support/{ticketId}', [AdminChatController::class, 'openChat'])
->name('admin.chat.open');
// Admin sending message
Route::post('/chat-support/{ticketId}/send', [AdminChatController::class, 'sendMessage'])
->name('admin.chat.send');
});
// ==========================================
// ADMIN ACCOUNT (AJAX) ROUTES
// ==========================================
Route::prefix('admin/account')
->middleware(['web', 'auth:admin'])
->name('admin.account.')
->group(function () {
Route::get('/dashboard', [AdminAccountController::class, 'getDashboardData'])
->name('dashboard');
Route::get('/available-orders', [AdminAccountController::class, 'getAvailableOrders'])
->name('orders.available');
Route::post('/create-order', [AdminAccountController::class, 'accountCreateOrder'])
->name('create');
Route::post('/toggle-payment', [AdminAccountController::class, 'togglePayment'])
->name('toggle');
Route::post('/installment/create', [AdminAccountController::class, 'addInstallment'])
->name('installment.create');
Route::post('/installment/update-status', [AdminAccountController::class, 'updateInstallmentStatus'])
->name('installment.update');
Route::get('/entry/{entry_no}', [AdminAccountController::class, 'getEntryDetails'])
->name('entry.details');
// ⬇⬇ NEW ROUTES FOR EDIT + DELETE ⬇⬇
Route::post('/update-entry', [AdminAccountController::class, 'updateEntry'])
->name('update.entry');
Route::post('/delete-entry', [AdminAccountController::class, 'deleteEntry'])
->name('delete.entry');
// ===== Associated Orders Routes (EDIT MODAL साठी) =====
Route::post('/add-orders-to-entry', [AdminAccountController::class, 'addOrdersToEntry'])
->name('add.orders.to.entry');
Route::get('/entry-orders/{entry_no}', [AdminAccountController::class, 'getEntryOrders'])
->name('entry.orders');
Route::post('/remove-order-from-entry', [AdminAccountController::class, 'removeOrderFromEntry'])
->name('remove.order.from.entry');
});
// ---------------------------
// REPORTS DOWNLOAD ROUTES
// ---------------------------
Route::get('/admin/orders/download/pdf', [AdminOrderController::class, 'downloadPdf'])
->name('admin.orders.download.pdf');
Route::get('/admin/orders/download/excel', [AdminOrderController::class, 'downloadExcel'])
->name('admin.orders.download.excel');
Route::prefix('admin/account')->middleware(['web', 'auth:admin'])->name('admin.account.')->group(function () {
Route::post('/toggle-payment', [AdminAccountController::class, 'togglePayment'])->name('toggle');
});
//---------------------------
//Edit Button Route
//---------------------------
// protected admin routes
Route::middleware(['web', 'auth:admin'])
->prefix('admin')
->name('admin.')
->group(function () {
// staff resource
Route::resource('staff', AdminStaffController::class);
});
// ==========================================
// ADMIN ACCOUNT (AJAX) ROUTES
// ==========================================
Route::prefix('admin/account')
->middleware('auth:admin')
->name('admin.account.')
->group(function () {
Route::get('/dashboard', [AdminAccountController::class, 'getDashboardData'])
->name('dashboard');
Route::get('/available-orders', [AdminAccountController::class, 'getAvailableOrders'])
->name('orders.available');
Route::post('/create-order', [AdminAccountController::class, 'accountCreateOrder'])
->name('create');
Route::post('/toggle-payment', [AdminAccountController::class, 'togglePayment'])
->name('toggle');
Route::post('/installment/create', [AdminAccountController::class, 'addInstallment'])
->name('installment.create');
Route::post('/installment/update-status', [AdminAccountController::class, 'updateInstallmentStatus'])
->name('installment.update');
Route::get('/entry/{entry_no}', [AdminAccountController::class, 'getEntryDetails'])
->name('entry.details');
Route::post('/update-entry', [AdminAccountController::class, 'updateEntry'])
->name('update.entry');
Route::post('/delete-entry', [AdminAccountController::class, 'deleteEntry'])
->name('delete.entry');
Route::post('/add-orders-to-entry', [AdminAccountController::class, 'addOrdersToEntry'])
->name('add.orders.to.entry');
Route::get('/entry-orders/{entry_no}', [AdminAccountController::class, 'getEntryOrders'])
->name('entry.orders');
Route::post('/remove-order-from-entry', [AdminAccountController::class, 'removeOrderFromEntry'])
->name('remove.order.from.entry');
});
// ---------------------------
// REPORTS DOWNLOAD ROUTES
// ---------------------------
Route::get('/admin/orders/download/pdf', [AdminOrderController::class, 'downloadPdf'])
->name('admin.orders.download.pdf');
Route::get('/admin/orders/download/excel', [AdminOrderController::class, 'downloadExcel'])
->name('admin.orders.download.excel');
Route::prefix('admin/account')->middleware('auth:admin')->name('admin.account.')->group(function () {
Route::post('/toggle-payment', [AdminAccountController::class, 'togglePayment'])->name('toggle');
});
// STAFF RESOURCE
Route::middleware(['auth:admin'])
->prefix('admin')
->name('admin.')
->group(function () {
Route::resource('staff', AdminStaffController::class);
});
// Extra admin prefix group
Route::prefix('admin')->middleware('auth:admin')->group(function () {
// ... your routes
});
Route::post('/admin/broadcasting/auth', function () {
return Broadcast::auth(request());
})->middleware('auth:admin');
// INVOICE DOWNLOAD (AdminInvoiceController वापरून)
Route::get('/admin/invoices/{invoice}/download', [AdminInvoiceController::class, 'download'])
->name('admin.invoices.download');
// CONTAINER POPUP VIEW
// In admin group
Route::get('/admin/containers/{container}/popup', [\App\Http\Controllers\ContainerController::class, 'popupPopup'])
->name('containers.popup');
//Invoice pdf download route
Route::get('invoice/{id}/download-pdf', [AdminInvoiceController::class, 'downloadPdf'])
->name('admin.invoice.download.pdf');
// Route::get('invoice/{id}/download-excel', [AdminInvoiceController::class, 'downloadExcel'])
// ->name('admin.invoice.download.excel');
Route::get('invoice/{id}/share', [AdminInvoiceController::class, 'share'])
->name('admin.invoice.share');