Pdf Changes Done

This commit is contained in:
Utkarsh Khedkar
2026-03-09 10:24:44 +05:30
parent c11467068c
commit 9cc6959396
32 changed files with 3416 additions and 2188 deletions

View File

@@ -8,6 +8,7 @@ use App\Models\InvoiceItem;
use App\Models\InvoiceInstallment; use App\Models\InvoiceInstallment;
use App\Models\InvoiceChargeGroup; use App\Models\InvoiceChargeGroup;
use App\Models\InvoiceChargeGroupItem; use App\Models\InvoiceChargeGroupItem;
use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@@ -20,9 +21,9 @@ class AdminInvoiceController extends Controller
// ------------------------------------------------------------- // -------------------------------------------------------------
public function index(Request $request) public function index(Request $request)
{ {
// Container relation सह invoices load करतो
$query = Invoice::with(['items', 'customer', 'container']); $query = Invoice::with(['items', 'customer', 'container']);
// Search
if ($request->filled('search')) { if ($request->filled('search')) {
$search = $request->search; $search = $request->search;
$query->where(function ($q) use ($search) { $query->where(function ($q) use ($search) {
@@ -30,42 +31,51 @@ class AdminInvoiceController extends Controller
->orWhere('customer_name', 'like', "%{$search}%"); ->orWhere('customer_name', 'like', "%{$search}%");
}); });
} }
// Status filter
if ($request->filled('status') && $request->status !== 'all') { if ($request->filled('status') && $request->status !== 'all') {
$query->where('status', $request->status); $query->where('status', $request->status);
} }
// Date range filter (invoice_date वर)
if ($request->filled('start_date')) { if ($request->filled('start_date')) {
$query->whereDate('invoice_date', '>=', $request->start_date); $query->whereDate('invoice_date', '>=', $request->start_date);
} }
if ($request->filled('end_date')) { if ($request->filled('end_date')) {
$query->whereDate('invoice_date', '<=', $request->end_date); $query->whereDate('invoice_date', '<=', $request->end_date);
} }
// Latest first
$invoices = $query->latest()->get(); $invoices = $query->latest()->get();
return view('admin.invoice', compact('invoices')); return view('admin.invoice', compact('invoices'));
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
// POPUP VIEW (AJAX) // POPUP VIEW + CUSTOMER DATA SYNC
// ------------------------------------------------------------- // -------------------------------------------------------------
public function popup($id) public function popup($id)
{ {
$invoice = Invoice::with([ $invoice = Invoice::with([
'items', 'items',
'customer', 'chargeGroups.items',
'container',
'chargeGroups.items.item',
])->findOrFail($id); ])->findOrFail($id);
// demo update असेल तर ठेव/काढ
$invoice->update([
'customer_email' => 'test@demo.com',
'customer_address' => 'TEST ADDRESS',
'pincode' => '999999',
]);
$shipment = null; $shipment = null;
return view('admin.popup_invoice', compact('invoice', 'shipment')); // आधीच group मध्ये असलेले item ids
$groupedItemIds = $invoice->chargeGroups
->flatMap(fn($group) => $group->items->pluck('invoice_item_id'))
->unique()
->values()
->toArray();
return view('admin.popup_invoice', compact('invoice', 'shipment', 'groupedItemIds'));
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
@@ -134,10 +144,10 @@ class AdminInvoiceController extends Controller
$data['igst_percent'] = $taxPercent; $data['igst_percent'] = $taxPercent;
} }
$gstAmount = ($finalAmount * $taxPercent) / 100; $gstAmount = ($finalAmount * $taxPercent) / 100;
$data['gst_amount'] = $gstAmount; $data['gst_amount'] = $gstAmount;
$data['final_amount_with_gst'] = $finalAmount + $gstAmount; $data['final_amount_with_gst'] = $finalAmount + $gstAmount;
$data['gst_percent'] = $taxPercent; $data['gst_percent'] = $taxPercent;
Log::info('📌 Final Calculated Invoice Values', [ Log::info('📌 Final Calculated Invoice Values', [
'invoice_id' => $invoice->id, 'invoice_id' => $invoice->id,
@@ -177,7 +187,7 @@ class AdminInvoiceController extends Controller
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
// 🔹 UPDATE INVOICE ITEMS (price + ttl_amount) // UPDATE INVOICE ITEMS
// ------------------------------------------------------------- // -------------------------------------------------------------
public function updateItems(Request $request, Invoice $invoice) public function updateItems(Request $request, Invoice $invoice)
{ {
@@ -230,10 +240,16 @@ class AdminInvoiceController extends Controller
$gstAmount = $newBaseAmount * $gstPercent / 100; $gstAmount = $newBaseAmount * $gstPercent / 100;
$finalWithGst = $newBaseAmount + $gstAmount; $finalWithGst = $newBaseAmount + $gstAmount;
$invoice->final_amount = $newBaseAmount; $invoice->final_amount = $newBaseAmount;
$invoice->gst_amount = $gstAmount; $invoice->gst_amount = $gstAmount;
$invoice->final_amount_with_gst = $finalWithGst; $invoice->final_amount_with_gst = $finalWithGst;
$invoice->gst_percent = $gstPercent; $invoice->gst_percent = $gstPercent;
$invoice->save();
// ⭐ Total Charges (groups समावेत) पुन्हा कॅलक्युलेट
$chargeGroupsTotal = $invoice->chargeGroups()->sum('total_charge');
$invoice->charge_groups_total = $chargeGroupsTotal;
$invoice->grand_total_with_charges = $invoice->final_amount_with_gst + $chargeGroupsTotal;
$invoice->save(); $invoice->save();
Log::info('✅ Invoice items updated & totals recalculated', [ Log::info('✅ Invoice items updated & totals recalculated', [
@@ -245,13 +261,15 @@ class AdminInvoiceController extends Controller
'cgst_percent' => $invoice->cgst_percent, 'cgst_percent' => $invoice->cgst_percent,
'sgst_percent' => $invoice->sgst_percent, 'sgst_percent' => $invoice->sgst_percent,
'igst_percent' => $invoice->igst_percent, 'igst_percent' => $invoice->igst_percent,
'charge_groups_total' => $invoice->charge_groups_total,
'grand_total_with_charges' => $invoice->grand_total_with_charges,
]); ]);
return back()->with('success', 'Invoice items updated successfully.'); return back()->with('success', 'Invoice items updated successfully.');
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
// PDF GENERATION USING mPDF // PDF GENERATION
// ------------------------------------------------------------- // -------------------------------------------------------------
public function generateInvoicePDF($invoice) public function generateInvoicePDF($invoice)
{ {
@@ -302,7 +320,10 @@ class AdminInvoiceController extends Controller
$invoice = Invoice::findOrFail($invoice_id); $invoice = Invoice::findOrFail($invoice_id);
$paidTotal = $invoice->installments()->sum('amount'); $paidTotal = $invoice->installments()->sum('amount');
$remaining = $invoice->final_amount_with_gst - $paidTotal;
// 👇 Total Charges (grand_total_with_charges) वरून remaining
$grandTotal = $invoice->grand_total_with_charges;
$remaining = $grandTotal - $paidTotal;
if ($request->amount > $remaining) { if ($request->amount > $remaining) {
return response()->json([ return response()->json([
@@ -331,11 +352,12 @@ class AdminInvoiceController extends Controller
'installment' => $installment, 'installment' => $installment,
'totalPaid' => $newPaid, 'totalPaid' => $newPaid,
'gstAmount' => $invoice->gst_amount, 'gstAmount' => $invoice->gst_amount,
'finalAmountWithGst' => $invoice->final_amount_with_gst, 'finalAmountWithGst' => $grandTotal, // इथे grand total पाठव
'baseAmount' => $invoice->final_amount, 'baseAmount' => $invoice->final_amount,
'remaining' => max(0, $invoice->final_amount_with_gst - $newPaid), 'remaining' => max(0, $grandTotal - $newPaid),
'isCompleted' => $newPaid >= $invoice->final_amount_with_gst, 'isCompleted' => $newPaid >= $grandTotal,
]); ]);
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
@@ -348,34 +370,32 @@ class AdminInvoiceController extends Controller
$installment->delete(); $installment->delete();
$paidTotal = $invoice->installments()->sum('amount'); $paidTotal = $invoice->installments()->sum('amount');
$remaining = $invoice->final_amount_with_gst - $paidTotal; $grandTotal = $invoice->grand_total_with_charges;
$remaining = $grandTotal - $paidTotal;
if ($remaining > 0 && $invoice->status === 'paid') {
$invoice->update(['status' => 'pending']);
}
return response()->json([ return response()->json([
'status' => 'success', 'status' => 'success',
'message' => 'Installment deleted.', 'message' => 'Installment deleted.',
'totalPaid' => $paidTotal, 'totalPaid' => $paidTotal,
'gstAmount' => $invoice->gst_amount, 'gstAmount' => $invoice->gst_amount,
'finalAmountWithGst' => $invoice->final_amount_with_gst, 'finalAmountWithGst' => $grandTotal, // इथेही
'baseAmount' => $invoice->final_amount, 'baseAmount' => $invoice->final_amount,
'remaining' => $remaining, 'remaining' => $remaining,
'isZero' => $paidTotal == 0, 'isZero' => $paidTotal == 0,
]); ]);
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
// CHARGE GROUP SAVE (NEW) // CHARGE GROUP SAVE (no AJAX branch)
// ------------------------------------------------------------- // -------------------------------------------------------------
public function storeChargeGroup(Request $request, $invoiceId) public function storeChargeGroup(Request $request, $invoiceId)
{ {
$invoice = Invoice::with('items')->findOrFail($invoiceId); $invoice = Invoice::with('items')->findOrFail($invoiceId);
$data = $request->validate([ $data = $request->validate([
'group_name' => 'nullable|string|max:255', 'group_name' => 'required|string|max:255',
'basis_type' => 'required|in:ttl_qty,amount,ttl_cbm,ttl_kg', 'basis_type' => 'required|in:ttl_qty,amount,ttl_cbm,ttl_kg',
'basis_value' => 'required|numeric', 'basis_value' => 'required|numeric',
'rate' => 'required|numeric|min:0.0001', 'rate' => 'required|numeric|min:0.0001',
@@ -383,62 +403,47 @@ class AdminInvoiceController extends Controller
'item_ids' => 'required|array', 'item_ids' => 'required|array',
'item_ids.*' => 'integer|exists:invoice_items,id', 'item_ids.*' => 'integer|exists:invoice_items,id',
]); ]);
$exists = InvoiceChargeGroup::where('invoice_id', $invoice->id)
->where('group_name', $data['group_name'])
->exists();
if ($exists) {
return back()
->withErrors(['group_name' => 'This group name is already used for this invoice.'])
->withInput();
}
$group = InvoiceChargeGroup::create([ $group = InvoiceChargeGroup::create([
'invoice_id' => $invoice->id, 'invoice_id' => $invoice->id,
'group_name' => $data['group_name'] ?? null, 'group_name' => $data['group_name'],
'basis_type' => $data['basis_type'], 'basis_type' => $data['basis_type'],
'basis_value' => $data['basis_value'], 'basis_value' => $data['basis_value'],
'rate' => $data['rate'], 'rate' => $data['rate'],
'total_charge' => $data['auto_total'], 'total_charge' => $data['auto_total'],
]); ]);
foreach ($data['item_ids'] as $itemId) { foreach ($data['item_ids'] as $itemId) {
InvoiceChargeGroupItem::create([ InvoiceChargeGroupItem::create([
'group_id' => $group->id, 'group_id' => $group->id,
'invoice_item_id' => $itemId, 'invoice_item_id' => $itemId,
]); ]);
} }
if ($request->ajax()) { // ⭐ Charge groups नुसार Total Charges सेट करा
// load items with invoice item relation $chargeGroupsTotal = $invoice->chargeGroups()->sum('total_charge');
$group->load(['items.item']); $grandTotal = $invoice->final_amount_with_gst + $chargeGroupsTotal;
// prepare simple array for JS $invoice->update([
$itemsPayload = $group->items->map(function ($gi) { 'charge_groups_total' => $chargeGroupsTotal,
$it = $gi->item; 'grand_total_with_charges' => $grandTotal,
return [ ]);
'description' => $it->description,
'qty' => $it->qty,
'ttlqty' => $it->ttl_qty,
'cbm' => $it->cbm,
'ttlcbm' => $it->ttl_cbm,
'kg' => $it->kg,
'ttlkg' => $it->ttl_kg,
'amount' => $it->ttl_amount,
];
});
return response()->json([
'success' => true,
'message' => 'Charge group created successfully.',
'group' => [
'id' => $group->id,
'group_name' => $group->group_name,
'basis_type' => $group->basis_type,
'basis_value' => $group->basis_value,
'rate' => $group->rate,
'total_charge'=> $group->total_charge,
'items' => $itemsPayload,
],
]);
}
return redirect() return redirect()
->back() ->back()
->with('success', 'Charge group saved successfully.'); ->with('success', 'Charge group saved successfully.');
} }
public function download(Invoice $invoice) public function download(Invoice $invoice)
{ {
if (!$invoice->pdf_path || !Storage::exists($invoice->pdf_path)) { if (!$invoice->pdf_path || !Storage::exists($invoice->pdf_path)) {

View File

@@ -70,11 +70,13 @@ class AdminOrderController extends Controller
'invoices.final_amount_with_gst', 'invoices.final_amount_with_gst',
'invoices.status as invoice_status', 'invoices.status as invoice_status',
'invoices.mark_no', 'invoices.mark_no',
'invoices.container_id', // <<< हे नक्की घाल
'containers.container_number', 'containers.container_number',
'containers.container_date', 'containers.container_date',
DB::raw('COALESCE(invoices.company_name, mark_list.company_name) as company_name'), 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') DB::raw('COALESCE(invoices.customer_name, mark_list.customer_name) as customer_name')
) )
->when($request->filled('search'), function ($q) use ($request) { ->when($request->filled('search'), function ($q) use ($request) {
$search = trim($request->search); $search = trim($request->search);
$q->where(function ($qq) use ($search) { $q->where(function ($qq) use ($search) {

View File

@@ -10,6 +10,8 @@ use App\Models\InvoiceItem;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel; use Maatwebsite\Excel\Facades\Excel;
use Carbon\Carbon; use Carbon\Carbon;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Facades\Storage; // <-- added for Excel download
class ContainerController extends Controller class ContainerController extends Controller
{ {
@@ -241,8 +243,8 @@ class ContainerController extends Controller
} }
// ROWS CLEANING // ROWS CLEANING
$dataRows = array_slice($rows, $headerRowIndex + 1); $dataRows = array_slice($rows, $headerRowIndex + 1);
$cleanedRows = []; $cleanedRows = [];
$unmatchedRowsData = []; $unmatchedRowsData = [];
foreach ($dataRows as $offset => $row) { foreach ($dataRows as $offset => $row) {
@@ -253,7 +255,7 @@ class ContainerController extends Controller
$rowText = strtoupper(implode(' ', $trimmedRow)); $rowText = strtoupper(implode(' ', $trimmedRow));
if ( if (
stripos($rowText, 'TOTAL') !== false || stripos($rowText, 'TOTAL') !== false ||
stripos($rowText, 'TTL') !== false || stripos($rowText, 'TTL') !== false ||
stripos($rowText, 'GRAND') !== false stripos($rowText, 'GRAND') !== false
) { ) {
continue; continue;
@@ -280,10 +282,7 @@ class ContainerController extends Controller
->withInput(); ->withInput();
} }
/* // FORMULA CHECK
* FORMULA CHECK UPDATED WITH AMOUNT FIX + NUMBER SANITIZER
*/
$cleanNumber = function ($value) { $cleanNumber = function ($value) {
if (is_string($value)) { if (is_string($value)) {
$value = str_replace(',', '', trim($value)); $value = str_replace(',', '', trim($value));
@@ -311,7 +310,6 @@ class ContainerController extends Controller
$desc = $essentialColumns['desc_col'] !== null ? (string)($row[$essentialColumns['desc_col']] ?? '') : ''; $desc = $essentialColumns['desc_col'] !== null ? (string)($row[$essentialColumns['desc_col']] ?? '') : '';
$mark = $essentialColumns['itemno_col'] !== null ? (string)($row[$essentialColumns['itemno_col']] ?? '') : ''; $mark = $essentialColumns['itemno_col'] !== null ? (string)($row[$essentialColumns['itemno_col']] ?? '') : '';
// expected
$expTtlQty = $qty * $ctn; $expTtlQty = $qty * $ctn;
$expTtlCbm = $cbm * $ctn; $expTtlCbm = $cbm * $ctn;
$expTtlKg = $kg * $ctn; $expTtlKg = $kg * $ctn;
@@ -350,7 +348,6 @@ class ContainerController extends Controller
} }
if (!empty($rowErrors)) { if (!empty($rowErrors)) {
// full row data map for excel table
$rowData = []; $rowData = [];
foreach ($header as $colIndex => $headingText) { foreach ($header as $colIndex => $headingText) {
$value = $row[$colIndex] ?? null; $value = $row[$colIndex] ?? null;
@@ -368,7 +365,7 @@ class ContainerController extends Controller
} }
} }
// MARK CHECK: strict - collect ALL marks + unmatched rows // MARK CHECK
$marksFromExcel = []; $marksFromExcel = [];
foreach ($cleanedRows as $item) { foreach ($cleanedRows as $item) {
$row = $item['row']; $row = $item['row'];
@@ -397,7 +394,6 @@ class ContainerController extends Controller
$markErrors = []; $markErrors = [];
if (!empty($unmatchedMarks)) { if (!empty($unmatchedMarks)) {
foreach ($cleanedRows as $item) { foreach ($cleanedRows as $item) {
$row = $item['row']; $row = $item['row'];
$offset = $item['offset']; $offset = $item['offset'];
@@ -523,17 +519,19 @@ class ContainerController extends Controller
$snap = $markToSnapshot[$firstMark] ?? null; $snap = $markToSnapshot[$firstMark] ?? null;
$invoice = new Invoice(); $invoice = new Invoice();
$invoice->container_id = $container->id; $invoice->container_id = $container->id;
// $invoice->customer_id = $customerId; // $invoice->customer_id = $customerId;
$invoice->mark_no = $firstMark;
// इथे Mark No सेट करतो
$invoice->mark_no = $firstMark;
$invoice->invoice_number = $this->generateInvoiceNumber(); $invoice->invoice_number = $this->generateInvoiceNumber();
$invoice->invoice_date = now()->toDateString();
// NEW (add this):
$invoice->due_date = Carbon::parse($invoice->invoice_date)->addDays(10)->format('Y-m-d');
// invoice_date = container_date
$invoice->invoice_date = $container->container_date;
// due_date = container_date + 10 days
$invoice->due_date = Carbon::parse($invoice->invoice_date)
->addDays(10)
->format('Y-m-d');
if ($snap) { if ($snap) {
$invoice->customer_name = $snap['customer_name'] ?? null; $invoice->customer_name = $snap['customer_name'] ?? null;
@@ -550,8 +548,8 @@ class ContainerController extends Controller
$invoice->customer_address = null; $invoice->customer_address = null;
$invoice->pincode = null; $invoice->pincode = null;
$uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark')); $uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark'));
$invoice->notes = 'Auto-created from Container ' . $container->container_number $invoice->notes = 'Auto-created from Container ' . $container->container_number
. ' for Mark(s): ' . implode(', ', $uniqueMarks); . ' for Mark(s): ' . implode(', ', $uniqueMarks);
$invoice->pdf_path = null; $invoice->pdf_path = null;
$invoice->status = 'pending'; $invoice->status = 'pending';
@@ -627,40 +625,120 @@ class ContainerController extends Controller
->where('id', $rowId) ->where('id', $rowId)
->first(); ->first();
if (!$row) continue; if (!$row) {
continue;
}
// original update // 1) update container_rows.data
$data = $row->data ?? []; $data = $row->data ?? [];
foreach ($cols as $colHeader => $value) { foreach ($cols as $colHeader => $value) {
$data[$colHeader] = $value; $data[$colHeader] = $value;
} }
$row->update([ $row->update(['data' => $data]);
'data' => $data,
]); // 2) normalize keys
$normalizedMap = [];
foreach ($data as $key => $value) {
if ($key === null || $key === '') {
continue;
}
$normKey = strtoupper((string)$key);
$normKey = str_replace([' ', '/', '-', '.'], '', $normKey);
$normalizedMap[$normKey] = $value;
}
// helper: get first numeric value from given keys
$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;
};
// 3) read values QTY vs TTLQTY separately
$ctnKeys = ['CTN', 'CTNS'];
$qtyKeys = ['QTY', 'PCS', 'PIECES']; // per-carton qty
$ttlQtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY']; // total qty
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
$amountKeys = ['AMOUNT', 'TTLAMOUNT', 'TOTALAMOUNT'];
$ctn = $getFirstNumeric($normalizedMap, $ctnKeys);
// per carton qty
$qty = $getFirstNumeric($normalizedMap, $qtyKeys);
// total qty direct from TOTALQTY/TTLQTY/ITLQTY
$ttlQ = $getFirstNumeric($normalizedMap, $ttlQtyKeys);
// if total column is 0 then compute ctn * qty
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;
}
// 4) get description
$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;
}
}
}
// extra: update linked invoice items & invoice totals
$rowIndex = $row->row_index; $rowIndex = $row->row_index;
$ctn = (float) ($data['CTN'] ?? $data['CTNS'] ?? 0); // 5) find linked invoice_items
$qty = (float) ($data['QTY'] ?? 0);
$ttlQ = (float) ($data['TTLQTY'] ?? $data['TOTALQTY'] ?? $data['TTL/QTY'] ?? ($ctn * $qty));
$price = (float) ($data['PRICE'] ?? 0);
$cbm = (float) ($data['CBM'] ?? 0);
$ttlC = (float) ($data['TOTALCBM'] ?? $data['TTL CBM'] ?? ($cbm * $ctn));
$kg = (float) ($data['KG'] ?? 0);
$ttlK = (float) ($data['TOTALKG'] ?? $data['TTL KG'] ?? ($kg * $ctn));
$amount = (float) ($data['AMOUNT'] ?? ($price * $ttlQ));
$desc = $data['DESCRIPTION'] ?? $data['DESC'] ?? null;
$items = InvoiceItem::where('container_id', $container->id) $items = InvoiceItem::where('container_id', $container->id)
->where('container_row_index', $rowIndex) ->where('container_row_index', $rowIndex)
->get(); ->get();
if ($items->isEmpty() && $desc) {
$items = InvoiceItem::where('container_id', $container->id)
->whereNull('container_row_index')
->where('description', $desc)
->get();
}
// 6) update invoice_items + recalc invoice totals
foreach ($items as $item) { foreach ($items as $item) {
$item->description = $desc; $item->description = $desc;
$item->ctn = $ctn; $item->ctn = $ctn;
$item->qty = $qty; $item->qty = $qty; // per carton
$item->ttl_qty = $ttlQ; $item->ttl_qty = $ttlQ; // total
$item->price = $price; $item->price = $price;
$item->ttl_amount = $amount; $item->ttl_amount = $amount;
$item->cbm = $cbm; $item->cbm = $cbm;
@@ -703,33 +781,39 @@ class ContainerController extends Controller
->with('success', 'Excel rows updated successfully.'); ->with('success', 'Excel rows updated successfully.');
} }
// app/Http/Controllers/ContainerController.php public function updateStatus(Request $request, Container $container)
public function updateStatus(Request $request, Container $container) {
{ $request->validate([
$request->validate([ 'status' => 'required|in:container-ready,export-custom,international-transit,arrived-at-india,import-custom,warehouse,domestic-distribution,out-for-delivery,delivered',
'status' => 'required|in:pending,in-progress,completed,cancelled',
]);
$container->status = $request->status;
$container->save();
// जर AJAX असेल तर JSON दे
if ($request->wantsJson() || $request->ajax()) {
return response()->json([
'success' => true,
'status' => $container->status,
]); ]);
$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.');
} }
// normal form submit असेल तर redirect
return back()->with('success', 'Container status updated.');
}
public function destroy(Container $container) public function destroy(Container $container)
{ {
$container->delete(); $container->delete();
return redirect()->route('containers.index')->with('success', 'Container deleted.');
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 private function generateInvoiceNumber(): string
@@ -755,4 +839,105 @@ public function updateStatus(Request $request, Container $container)
return 'INV-' . $year . '-' . str_pad($nextSeq, 6, '0', STR_PAD_LEFT); 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.');
}
// Stored path like "containers/abc.xlsx"
$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)
{
// existing show सारखाच data वापरू
$container->load('rows');
// summary आधीपासून index() मध्ये जसा काढतोस तसाच logic reuse
$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

@@ -11,6 +11,8 @@ class InvoiceItem extends Model
protected $fillable = [ protected $fillable = [
'invoice_id', 'invoice_id',
'container_id', // Container mapping
'container_row_index', // Container row index
'description', 'description',
'ctn', 'ctn',
@@ -38,7 +40,6 @@ class InvoiceItem extends Model
return $this->belongsTo(Invoice::class); return $this->belongsTo(Invoice::class);
} }
public function chargeGroupItems() public function chargeGroupItems()
{ {
return $this->hasMany(InvoiceChargeGroupItem::class, 'invoice_item_id'); return $this->hasMany(InvoiceChargeGroupItem::class, 'invoice_item_id');
@@ -114,4 +115,3 @@ class InvoiceItem extends Model
return $basis * $rate; return $basis * $rate;
} }
} }

View File

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

View File

@@ -9,7 +9,7 @@ return new class extends Migration
public function up(): void public function up(): void
{ {
Schema::table('containers', function (Blueprint $table) { Schema::table('containers', function (Blueprint $table) {
$table->string('status', 20) $table->string('status', 21)
->default('pending') ->default('pending')
->after('container_date'); ->after('container_date');
}); });

View File

@@ -6,21 +6,24 @@ use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
/** public function up(): void
* Run the migrations.
*/
public function up()
{ {
Schema::table('invoices', function (Blueprint $table) { Schema::table('invoices', function (Blueprint $table) {
$table->date('duedate')->nullable()->after('invoicedate'); // column आधीच आहे का हे check करून, नसेल तरच add करायचा
if (!Schema::hasColumn('invoices', 'due_date')) {
$table->date('due_date')
->nullable()
->after('invoice_date');
}
}); });
} }
public function down() public function down(): void
{ {
Schema::table('invoices', function (Blueprint $table) { Schema::table('invoices', function (Blueprint $table) {
$table->dropColumn('duedate'); if (Schema::hasColumn('invoices', 'due_date')) {
$table->dropColumn('due_date');
}
}); });
} }
}; };

View File

@@ -0,0 +1,26 @@
<?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();
});
}
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,102 @@
<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

@@ -2603,11 +2603,12 @@ function renderPaymentTable(list){
<td>${escapeHtml(entry.entry_date)}</td> <td>${escapeHtml(entry.entry_date)}</td>
<td>${escapeHtml(entry.description)}</td> <td>${escapeHtml(entry.description)}</td>
<!-- Order Quantity - Clickable number without box -->
<td> <td>
<button type="button" class="entry-link" <span onclick="openEntryOrdersModal('${escapeHtml(entry.entry_no)}')"
onclick="openEntryOrdersModal('${escapeHtml(entry.entry_no)}')"> style="cursor: pointer; color: #276dea; font-weight: 600; text-decoration: underline; text-decoration-color: #ccc;">
${entry.order_quantity ?? '-'} ${entry.order_quantity ?? '-'}
</button> </span>
</td> </td>
<td>${escapeHtml(entry.region)}</td> <td>${escapeHtml(entry.region)}</td>
@@ -2680,7 +2681,6 @@ function renderPaymentTable(list){
function cycleToggle(btn) { function cycleToggle(btn) {
// वर्तमान position घेऊन पुढचा स्टेट कॅल्क्युलेट करा // वर्तमान position घेऊन पुढचा स्टेट कॅल्क्युलेट करा
let pos = parseInt(btn.dataset.pos || '0', 10); // 0 = unpaid, 1 = pending, 2 = paid let pos = parseInt(btn.dataset.pos || '0', 10); // 0 = unpaid, 1 = pending, 2 = paid

View File

@@ -98,9 +98,7 @@
gap: 10px; gap: 10px;
} }
.filter-title i { .filter-title i { color: var(--primary-color); }
color: var(--primary-color);
}
.filter-grid { .filter-grid {
display: grid; display: grid;
@@ -108,9 +106,7 @@
gap: 15px; gap: 15px;
} }
.filter-group { .filter-group { position: relative; }
position: relative;
}
.filter-group label { .filter-group label {
display: block; display: block;
@@ -139,9 +135,7 @@
box-shadow: 0 0 0 3px rgba(76, 111, 255, 0.1); box-shadow: 0 0 0 3px rgba(76, 111, 255, 0.1);
} }
.filter-input::placeholder { .filter-input::placeholder { color: #94a3b8; }
color: #94a3b8;
}
.filter-actions { .filter-actions {
display: flex; display: flex;
@@ -212,9 +206,7 @@
gap: 12px; gap: 12px;
} }
.card-header h2 i { .card-header h2 i { color: white; }
color: white;
}
.stats-badge { .stats-badge {
background: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.2);
@@ -239,9 +231,7 @@
transform: translateX(4px); transform: translateX(4px);
} }
.container-item:last-child { .container-item:last-child { border-bottom: none; }
border-bottom: none;
}
.container-header { .container-header {
display: flex; display: flex;
@@ -297,40 +287,92 @@
color: #94a3b8; color: #94a3b8;
} }
.status-badge { /* STATUS DROPDOWN (badge look) */
padding: 6px 16px; .status-dropdown {
border-radius: 20px; position: relative;
font-size: 12px; 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; font-weight: 600;
letter-spacing: 0.3px; color: var(--dark-text);
display: inline-flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
gap: 6px; gap: 6px;
} }
.status-badge i { .status-dropdown-toggle span { white-space: nowrap; }
font-size: 10px;
.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-pending { .status-dropdown-menu.open { display: block; }
background: #fef3c7;
color: #d97706; .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-in-progress { .status-option:hover { background: #eef2ff; }
background: #dbeafe;
color: #1d4ed8; .status-option .dot {
width: 8px;
height: 8px;
border-radius: 999px;
background: #9ca3af;
} }
.status-completed { .status-option.active .dot {
background: #d1fae5; background: #22c55e;
color: #065f46;
} }
.status-cancelled { /* COLOR MAPPING per status dropdown tint + main toggle text color */
background: #fee2e2; .status-option.status-container-ready { background: #eff6ff; color: #1d4ed8; }
color: #991b1b; .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 { .action-buttons {
display: flex; display: flex;
@@ -373,39 +415,7 @@
color: white; color: white;
} }
.update-form { .update-form { position: relative; }
display: flex;
align-items: center;
gap: 8px;
}
.status-select {
padding: 8px 12px;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
font-size: 13px;
color: var(--dark-text);
background: white;
min-width: 140px;
cursor: pointer;
}
.update-btn {
background: var(--primary-color);
color: white;
border: none;
padding: 8px 16px;
border-radius: var(--radius-md);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
}
.update-btn:hover {
background: #3b5de6;
}
.no-results { .no-results {
text-align: center; text-align: center;
@@ -444,16 +454,13 @@
animation: slideIn 0.3s ease; animation: slideIn 0.3s ease;
} }
.success-message i { .success-message i { font-size: 20px; }
font-size: 20px;
}
@keyframes slideIn { @keyframes slideIn {
from { opacity: 0; transform: translateY(-10px); } from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); } to { opacity: 1; transform: translateY(0); }
} }
/* 🔥 Totals section */
.totals-section { .totals-section {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
@@ -504,9 +511,7 @@
width: 100%; width: 100%;
justify-content: center; justify-content: center;
} }
.filter-grid { .filter-grid { grid-template-columns: 1fr; }
grid-template-columns: 1fr;
}
.container-header { .container-header {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
@@ -519,13 +524,8 @@
} }
@media (max-width: 576px) { @media (max-width: 576px) {
.update-form { .update-form { width: 100%; }
flex-direction: column; .status-dropdown { width: 100%; }
width: 100%;
}
.status-select, .update-btn {
width: 100%;
}
} }
</style> </style>
@@ -569,10 +569,15 @@
<label><i class="fas fa-tag"></i> Status</label> <label><i class="fas fa-tag"></i> Status</label>
<select name="status" class="filter-select"> <select name="status" class="filter-select">
<option value="">All Status</option> <option value="">All Status</option>
<option value="pending" {{ request('status') == 'pending' ? 'selected' : '' }}>Pending</option> <option value="container-ready" {{ request('status') == 'container-ready' ? 'selected' : '' }}>Container Ready</option>
<option value="in-progress" {{ request('status') == 'in-progress' ? 'selected' : '' }}>In Progress</option> <option value="export-custom" {{ request('status') == 'export-custom' ? 'selected' : '' }}>Export Custom</option>
<option value="completed" {{ request('status') == 'completed' ? 'selected' : '' }}>Completed</option> <option value="international-transit" {{ request('status') == 'international-transit' ? 'selected' : '' }}>International Transit</option>
<option value="cancelled" {{ request('status') == 'cancelled' ? 'selected' : '' }}>Cancelled</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> </select>
</div> </div>
@@ -610,22 +615,31 @@
<p>Get started by creating your first container</p> <p>Get started by creating your first container</p>
</div> </div>
@else @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) @foreach($containers as $container)
@php @php
$status = $container->status; $status = $container->status ?? 'container-ready';
$statusClass = match ($status) { $statusLabel = $labels[$status] ?? ucfirst(str_replace('-', ' ', $status));
'completed' => 'status-completed',
'in-progress' => 'status-in-progress',
'cancelled' => 'status-cancelled',
default => 'status-pending',
};
@endphp @endphp
<div class="container-item"> <div class="container-item">
<div class="container-header"> <div class="container-header">
<div class="container-info"> <div class="container-info">
<div class="container-avatar"> <div class="container-avatar">
{{ substr($container->container_name, 0, 2) }} {{ strtoupper(substr($container->container_name, 0, 2)) }}
</div> </div>
<div class="container-details"> <div class="container-details">
<h3>{{ $container->container_name }}</h3> <h3>{{ $container->container_name }}</h3>
@@ -647,51 +661,56 @@
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<span class="status-badge {{ $statusClass }}">
<i class="fas fa-circle"></i>
<span class="status-text">
{{ ucfirst(str_replace('-', ' ', $status)) }}
</span>
</span>
@can('container.update')
<a href="{{ route('containers.show', $container->id) }}" class="action-btn view-btn">
<i class="fas fa-eye"></i> View
</a>
@endcan
@can('containers.update_status') @can('containers.update_status')
<form action="{{ route('containers.update-status', $container->id) }}" <form action="{{ route('containers.update-status', $container->id) }}"
method="POST" method="POST"
class="update-form ajax-status-form" class="update-form ajax-status-form"
data-container-id="{{ $container->id }}"> data-container-id="{{ $container->id }}">
@csrf @csrf
<select name="status" class="status-select">
<option value="pending" {{ $status === 'pending' ? 'selected' : '' }}>Pending</option> @php $statusClass = 'status-' . $status; @endphp
<option value="in-progress" {{ $status === 'in-progress' ? 'selected' : '' }}>In Progress</option>
<option value="completed" {{ $status === 'completed' ? 'selected' : '' }}>Completed</option> <div class="status-dropdown">
<option value="cancelled" {{ $status === 'cancelled' ? 'selected' : '' }}>Cancelled</option> <div class="status-dropdown-toggle {{ $statusClass }}">
</select> <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> </form>
@endcan @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') @can('container.delete')
<form action="{{ route('containers.destroy', $container->id) }}" method="POST" <form action="{{ route('containers.destroy', $container->id) }}" method="POST"
onsubmit="return confirm('Are you sure you want to delete this container and all its entries?');"> class="delete-form"
@csrf data-container-id="{{ $container->id }}">
@method('DELETE') @csrf
<button type="submit" class="action-btn delete-btn"> @method('DELETE')
<i class="fas fa-trash"></i> Delete <button type="submit" class="action-btn delete-btn">
</button> <i class="fas fa-trash"></i> Delete
</form> </button>
</form>
@endcan @endcan
</div> </div>
</div> </div>
<!-- 🔥 Totals instead of first row preview -->
<div class="totals-section"> <div class="totals-section">
<div class="total-card"> <div class="total-card">
<div class="total-value">{{ number_format($container->summary['total_ctn'], 1) }}</div> <div class="total-value">{{ number_format($container->summary['total_ctn'], 1) }}</div>
@@ -716,87 +735,121 @@
</div> </div>
</div> </div>
<script> <script>
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.ajax-status-form .status-select').forEach(function (selectEl) { // STATUS DROPDOWN
selectEl.addEventListener('change', function (event) { document.querySelectorAll('.status-dropdown').forEach(function (wrapper) {
const form = event.target.closest('form'); const toggle = wrapper.querySelector('.status-dropdown-toggle');
const url = form.getAttribute('action'); const menu = wrapper.querySelector('.status-dropdown-menu');
const token = form.querySelector('input[name="_token"]').value;
const status = event.target.value;
console.log('Sending status update:', url, status); 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, { fetch(url, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-CSRF-TOKEN': token,
'X-Requested-With': 'XMLHttpRequest', 'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json',
'Accept': 'application/json', 'Accept': 'application/json',
}, },
body: JSON.stringify({ status: status }) body: formData
}) })
.then(async res => { .then(async res => {
console.log('Response status:', res.status);
let data = null; let data = null;
try { data = await res.json(); } catch (e) {} try { data = await res.json(); } catch (e) {}
console.log('Response body:', data);
if (!res.ok || !data || !data.success) { if (!res.ok || !data || !data.success) {
alert('Status update failed'); alert('Delete failed');
return; return;
} }
// 👉 UI update (badge text + color) if (item) {
const item = form.closest('.container-item'); item.style.opacity = '0';
const badge = item.querySelector('.status-badge'); item.style.transform = 'translateX(-10px)';
setTimeout(() => item.remove(), 200);
// text
// text
const pretty = data.status.replace('-', ' ');
const textEl = badge.querySelector('.status-text');
if (textEl) {
textEl.textContent =
pretty.charAt(0).toUpperCase() + pretty.slice(1);
}
// classes
badge.classList.remove(
'status-pending',
'status-in-progress',
'status-completed',
'status-cancelled'
);
if (data.status === 'pending') {
badge.classList.add('status-pending');
} else if (data.status === 'in-progress') {
badge.classList.add('status-in-progress');
} else if (data.status === 'completed') {
badge.classList.add('status-completed');
} else if (data.status === 'cancelled') {
badge.classList.add('status-cancelled');
} }
}) })
.catch(err => { .catch(() => {
console.error('Error:', err); alert('Network error while deleting container');
alert('Network error while updating status');
}); });
}); });
}); });
}); });
</script> </script>
<link rel="stylesheet" <link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
@endsection @endsection

View File

@@ -137,6 +137,10 @@
Some rows in your Excel file have formula or mark issues. Some rows in your Excel file have formula or mark issues.
See the table below, and detailed messages after the table. See the table below, and detailed messages after the table.
</div> </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>
</div> </div>
@@ -183,11 +187,9 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{-- Formula error rows --}} {{-- Formula error rows (red formula mismatch, critical) --}}
@foreach($formulaErrors as $fe) @foreach($formulaErrors as $fe)
@php @php $rowData = $fe['data'] ?? []; @endphp
$rowData = $fe['data'] ?? [];
@endphp
@if(!empty($rowData)) @if(!empty($rowData))
<tr class="table-danger"> <tr class="table-danger">
<td>{{ $fe['excel_row'] }}</td> <td>{{ $fe['excel_row'] }}</td>
@@ -199,11 +201,9 @@
@endif @endif
@endforeach @endforeach
{{-- Mark error rows --}} {{-- Mark error rows (yellow mark not found, warning) --}}
@foreach($markErrors as $me) @foreach($markErrors as $me)
@php @php $rowData = $me['data'] ?? []; @endphp
$rowData = $me['data'] ?? [];
@endphp
@if(!empty($rowData)) @if(!empty($rowData))
<tr class="table-warning"> <tr class="table-warning">
<td>{{ $me['excel_row'] }}</td> <td>{{ $me['excel_row'] }}</td>
@@ -292,7 +292,9 @@
<div class="col-md-6"> <div class="col-md-6">
<label class="cm-form-label">Container Date</label> <label class="cm-form-label">Container Date</label>
<input type="date" name="container_date" <input type="date"
name="container_date" {{-- name fix --}}
id="containerdate" {{-- JS साठी जुना id ठेवला --}}
class="form-control cm-form-control" class="form-control cm-form-control"
value="{{ old('container_date') }}"> value="{{ old('container_date') }}">
</div> </div>
@@ -322,4 +324,26 @@
</div> </div>
</div> </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 @endsection

View File

@@ -0,0 +1,202 @@
<!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;
}
/* 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 like screenshot */
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; /* इथे solid yellow */
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
{{-- 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

@@ -6,7 +6,13 @@
<style> <style>
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap'); @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap');
/* ── TOP HEADER CARD ── */ html, body {
margin: 0;
padding: 0;
width: 100%;
overflow-x: hidden;
}
.cm-header-card { .cm-header-card {
background: linear-gradient(100deg, #4c6fff 0%, #8e54e9 100%); background: linear-gradient(100deg, #4c6fff 0%, #8e54e9 100%);
border-radius: 14px; border-radius: 14px;
@@ -34,13 +40,13 @@
margin-top: 2px; margin-top: 2px;
} }
/* ── MAIN CARD ── */
.cm-main-card { .cm-main-card {
border-radius: 14px; border-radius: 14px;
border: none; border: none;
box-shadow: 0 4px 20px rgba(15,35,52,0.10); box-shadow: 0 4px 20px rgba(15,35,52,0.10);
overflow: hidden; overflow: hidden;
background: #fff; background: #fff;
position: relative;
} }
.cm-main-card .card-header { .cm-main-card .card-header {
background: #fff; background: #fff;
@@ -57,30 +63,138 @@
color: #1a2340; color: #1a2340;
} }
/* ── INFO STRIP ── */ .cm-info-cards-row {
.cm-info-strip { display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)); /* 3 cards one row */
gap: 14px;
padding: 14px 18px 8px 18px;
}
.cm-info-card {
border-radius: 16px;
padding: 12px 14px;
display: flex; display: flex;
gap: 28px; align-items: center;
padding: 12px 20px 0 20px; gap: 10px;
flex-wrap: wrap; 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-label { .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; font-size: 11px;
color: #8a93a6;
font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.4px;
margin-bottom: 2px; color: #64748b;
}
.cm-info-value {
font-size: 13px;
font-weight: 600; font-weight: 600;
color: #1a2340; }
.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;
}
/* TOTAL BOXES */
.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;
} }
/* ── FILTER BAR ── */
.cm-filter-bar { .cm-filter-bar {
padding: 12px 20px 0 20px; padding: 4px 20px 0 20px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@@ -106,59 +220,50 @@
box-shadow: 0 0 0 3px rgba(76,111,255,0.1); box-shadow: 0 0 0 3px rgba(76,111,255,0.1);
} }
/* ── TABLE SCROLL OUTER ── */
.cm-table-scroll-outer { .cm-table-scroll-outer {
margin: 10px 14px 0 14px; margin: 10px 14px 0 14px;
border-radius: 14px 14px 0 0; border-radius: 14px;
/* इथे overflow: hidden होते, ते visible केले */ border: 1.5px solid #c9a359;
overflow-x: auto; /* फक्त horizontal scroll हवा असेल तर */ box-shadow: 0 4px 14px rgba(76,111,255,0.18);
overflow-y: visible; overflow-x: auto;
border: 1.5px solid #b8920e; overflow-y: hidden;
border-bottom: none; position: relative;
box-shadow: 0 -2px 10px rgba(184,146,14,0.10);
} }
/* ── TABLE WRAPPER ── */
.cm-table-wrapper { .cm-table-wrapper {
position: relative; position: relative;
/* इथे max-height, overflow auto काढले, जे inner scroll देत होते */ min-width: 1200px;
max-height: none;
overflow: visible;
border-top: none;
} }
/* ── TABLE STYLES ── */
.cm-table { .cm-table {
font-size: 12px; font-size: 12px;
min-width: 1100px;
width: 100%; width: 100%;
border-collapse: separate; border-collapse: separate;
border-spacing: 0; border-spacing: 0;
font-family: 'DM Sans', sans-serif; font-family: 'DM Sans', sans-serif;
} }
/* Sticky header light header color */
.cm-table thead tr th { .cm-table thead tr th {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 3; z-index: 3;
background: #fde4b3; background: linear-gradient(100deg, #fde4b3 0%, #fbd48a 100%);
color: #0a0a09; color: #0c0909;
font-weight: 700; font-weight: 700;
font-size: 12px; font-size: 12px;
padding: 11px 14px; padding: 11px 14px;
border-bottom: 2px solid #fde4b3; border-bottom: 2px solid rgba(255,255,255,0.15);
border-right: 1px solid #fde4b3; border-right: 1px solid rgba(255,255,255,0.18);
white-space: nowrap; white-space: nowrap;
text-align: center; text-align: center;
letter-spacing: 0.2px; letter-spacing: 0.2px;
text-shadow: 0 1px 2px rgba(0,0,0,0.18); 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 { .cm-table thead tr th:last-child {
border-top-right-radius: 10px;
border-right: none; border-right: none;
} }
/* # column narrower */
.cm-table thead tr th:first-child, .cm-table thead tr th:first-child,
.cm-table tbody tr td:first-child { .cm-table tbody tr td:first-child {
width: 46px; width: 46px;
@@ -166,8 +271,6 @@
max-width: 46px; max-width: 46px;
text-align: center; text-align: center;
} }
/* Body rows */
.cm-table tbody tr td { .cm-table tbody tr td {
padding: 8px 14px; padding: 8px 14px;
border-bottom: 1px solid #f0f3fb; border-bottom: 1px solid #f0f3fb;
@@ -178,28 +281,23 @@
vertical-align: middle; vertical-align: middle;
background: #fff; background: #fff;
transition: background 0.15s; transition: background 0.15s;
white-space: nowrap;
} }
.cm-table tbody tr td:last-child { .cm-table tbody tr td:last-child {
border-right: none; border-right: none;
} }
/* Zebra */
.cm-table tbody tr:nth-child(even) td { .cm-table tbody tr:nth-child(even) td {
background: #f8f9ff; background: #f8f9ff;
} }
/* Hover */
.cm-table tbody tr:hover td { .cm-table tbody tr:hover td {
background: #edf3ff !important; background: #edf3ff !important;
} }
/* Row number td */
.cm-row-num { .cm-row-num {
color: #8a93a6; color: #8a93a6;
font-size: 11px; font-size: 11px;
font-weight: 600; font-weight: 600;
} }
/* Editable cell input */
.cm-cell-input { .cm-cell-input {
width: 100%; width: 100%;
min-width: 90px; min-width: 90px;
@@ -223,104 +321,279 @@
background: #fff; background: #fff;
box-shadow: 0 0 0 3px rgba(76,111,255,0.12); box-shadow: 0 0 0 3px rgba(76,111,255,0.12);
} }
.cm-cell-readonly {
background: #f3f4ff;
cursor: not-allowed;
border-color: #e2e3ff;
}
/* Save button */ .cm-save-btn-floating {
.cm-save-btn { position: fixed;
font-size: 12.5px; right: 26px;
bottom: 22px;
z-index: 50;
font-size: 13px;
font-weight: 600; font-weight: 600;
border-radius: 20px; border-radius: 22px;
padding: 7px 18px; padding: 8px 20px;
background: linear-gradient(90deg, #4c6fff, #8e54e9); background: linear-gradient(90deg, #4c6fff, #8e54e9);
border: none; border: none;
color: #fff; color: #fff;
box-shadow: 0 3px 10px rgba(76,111,255,0.22); box-shadow: 0 4px 16px rgba(76,111,255,0.35);
transition: opacity 0.2s, transform 0.15s; transition: opacity 0.2s, transform 0.15s;
cursor: pointer; cursor: pointer;
} }
.cm-save-btn:hover { .cm-save-btn-floating:hover {
opacity: 0.92; opacity: 0.94;
transform: translateY(-1px); 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);
}
/* Empty state */
.cm-empty { .cm-empty {
padding: 30px 20px; padding: 30px 20px;
color: #8a93a6; color: #8a93a6;
font-size: 13px; font-size: 13px;
} }
/* Scrollbar styling आता फक्त horizontal ला लागू होईल */ .cm-table-scroll-outer::-webkit-scrollbar {
.cm-table-scroll-outer::-webkit-scrollbar { height: 6px; } 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-track {
.cm-table-scroll-outer::-webkit-scrollbar-thumb:hover { background: #8a99d0; } 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) { @media (max-width: 767px) {
.cm-header-card .card-body { flex-direction: column; align-items: flex-start; } .cm-header-card .card-body {
.cm-info-strip { gap: 16px; } flex-direction: column;
.cm-table-scroll-outer { overflow-x: auto; } align-items: flex-start;
.cm-table { min-width: 900px; } }
.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> </style>
<div class="container-fluid cm-wrapper"> <div class="container-fluid cm-wrapper">
{{-- TOP GRADIENT HEADER --}}
<div class="card cm-header-card"> <div class="card cm-header-card">
<div class="card-body"> <div class="card-body">
<div> <div>
<h4 class="cm-header-title"> <h4 class="cm-header-title">Container {{ $container->container_number }}</h4>
Container: {{ $container->container_number }}
</h4>
<div class="cm-header-sub"> <div class="cm-header-sub">
Edit loading list directly scroll horizontally &amp; vertically like Excel. Edit loading list directly like Excel. TT columns autocalculate from CTN, QTY, CBM, KG, PRICE.
</div> </div>
</div> </div>
<a href="{{ route('containers.index') }}" class="btn btn-light btn-sm"> <div class="d-flex gap-2">
Back to list <a href="{{ route('containers.index') }}" class="btn btn-light btn-sm">Back to list</a>
</a>
<a href="{{ route('containers.download.pdf', $container->id) }}"
class="btn btn-sm btn-outline-primary">
Download PDF
</a>
<a href="{{ route('containers.download.excel', $container->id) }}"
class="btn btn-sm btn-outline-success">
Download Excel
</a>
</div>
</div> </div>
</div> </div>
{{-- MAIN CARD --}}
<div class="card cm-main-card"> <div class="card cm-main-card">
<div class="card-header"> <div class="card-header">
<h5>Container Information</h5> <h5>Container Information</h5>
@if(!$container->rows->isEmpty())
<button type="submit"
form="cm-edit-rows-form"
class="cm-save-btn">
💾 Save Changes
</button>
@endif
</div> </div>
{{-- INFO STRIP --}} {{-- 3 INFO CARDS IN SINGLE ROW --}}
<div class="cm-info-strip"> <div class="cm-info-cards-row">
<div class="cm-info-item"> <div class="cm-info-card cm-card-container">
<div class="cm-info-label">Container</div> <div class="cm-info-card-icon">
<div class="cm-info-value">{{ $container->container_name }}</div> <i class="bi bi-box-seam"></i>
</div> </div>
<div class="cm-info-item"> <div class="cm-info-card-body">
<div class="cm-info-label">Date</div> <div class="cm-info-card-label">Container</div>
<div class="cm-info-value">{{ $container->container_date?->format('d-m-Y') }}</div> <div class="cm-info-card-value">
</div> {{ $container->container_name ?? $container->container_number }}
<div class="cm-info-item">
<div class="cm-info-label">Excel File</div>
@if($container->excel_file)
<div class="cm-info-value">
<a href="{{ \Illuminate\Support\Facades\Storage::url($container->excel_file) }}"
target="_blank" style="color:#4c6fff;text-decoration:none;font-weight:600;">
📄 Download / View
</a>
</div> </div>
@else </div>
<div class="cm-info-value" style="color:#b0b8cc;">Not uploaded</div> </div>
@endif
<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>
</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()) @if($container->rows->isEmpty())
<div class="cm-empty">No entries found for this container.</div> <div class="cm-empty">No entries found for this container.</div>
@else @else
@@ -333,60 +606,114 @@
} }
@endphp @endphp
{{-- FILTER BAR --}}
<div class="cm-filter-bar"> <div class="cm-filter-bar">
<span class="cm-row-count"> <span class="cm-row-count">
Total rows: {{ $container->rows->count() }} &nbsp;&nbsp; Edit cells then click "Save Changes" Total rows: {{ $container->rows->count() }} &nbsp;&nbsp; Edit cells then click "Save Changes".
</span> </span>
<input type="text" <input type="text" id="cmRowSearch" class="cm-filter-input"
id="cmRowSearch" placeholder="Quick search..." onkeyup="cmFilterRows()">
class="cm-filter-input"
placeholder="🔍 Quick search..."
onkeyup="cmFilterRows()">
</div> </div>
{{-- EDITABLE TABLE FORM --}} <form id="cm-edit-rows-form" action="{{ route('containers.rows.update', $container->id) }}" method="POST">
<form id="cm-edit-rows-form"
action="{{ route('containers.rows.update', $container->id) }}"
method="POST">
@csrf @csrf
<div class="cm-table-scroll-outer"> <div class="cm-table-scroll-outer">
<div class="cm-table-wrapper"> <div class="cm-table-wrapper">
<table class="cm-table" id="cmExcelTable"> <table class="cm-table" id="cmExcelTable">
<thead> <thead>
<tr> <tr>
<th>#</th> <th>#</th>
@foreach($allHeadings as $heading) @foreach($allHeadings as $heading)
<th>{{ $heading }}</th> <th>{{ $heading }}</th>
@endforeach @endforeach
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach($container->rows as $index => $row) @foreach($container->rows as $index => $row)
<tr> <tr>
<td class="cm-row-num">{{ $index + 1 }}</td> <td class="cm-row-num">{{ $index + 1 }}</td>
@foreach($allHeadings as $heading) @foreach($allHeadings as $heading)
@php $value = $row->data[$heading] ?? ''; @endphp @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;
@endphp
<td> <td>
<input type="text" <input
class="cm-cell-input" type="text"
name="rows[{{ $row->id }}][{{ $heading }}]" class="cm-cell-input {{ $isTotalColumn ? 'cm-cell-readonly' : '' }}"
value="{{ $value }}"> 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' }}"
@if($isTotalColumn) readonly @endif
>
</td> </td>
@endforeach @endforeach
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</form> </form>
@endif @endif
</div> </div>
</div> </div>
@if(!$container->rows->isEmpty())
<button id="cmSaveBtnFloating" class="cm-save-btn-floating">
Save Changes
</button>
<div id="cmToast" class="cm-toast">
Changes saved successfully.
</div>
@endif
<script> <script>
function cmFilterRows() { function cmFilterRows() {
const input = document.getElementById('cmRowSearch'); const input = document.getElementById('cmRowSearch');
@@ -394,7 +721,6 @@
const filter = input.value.toLowerCase(); const filter = input.value.toLowerCase();
const table = document.getElementById('cmExcelTable'); const table = document.getElementById('cmExcelTable');
if (!table) return; if (!table) return;
const rows = table.getElementsByTagName('tr'); const rows = table.getElementsByTagName('tr');
for (let i = 1; i < rows.length; i++) { for (let i = 1; i < rows.length; i++) {
const cells = rows[i].getElementsByTagName('td'); const cells = rows[i].getElementsByTagName('td');
@@ -409,5 +735,157 @@
rows[i].style.display = match ? '' : 'none'; 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);
}
function parseNumber(str) {
if (!str) return 0;
const cleaned = String(str).replace(/,/g, '').trim();
const val = parseFloat(cleaned);
return isNaN(val) ? 0 : val;
}
function formatNumber(val, decimals) {
if (isNaN(val)) val = 0;
return val.toFixed(decimals);
}
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;
}
});
if (ttlQtyInput && ctnInput && qtyInput) {
const newTtlQty = ctn * qty;
ttlQtyInput.value = formatNumber(newTtlQty, 0);
ttlQty = newTtlQty;
}
if (ttlCbmInput && cbmInput && ctnInput) {
const newTtlCbm = cbm * ctn;
ttlCbmInput.value = formatNumber(newTtlCbm, 3);
ttlCbm = newTtlCbm;
}
if (ttlKgInput && kgInput && ctnInput) {
const newTtlKg = kg * ctn;
ttlKgInput.value = formatNumber(newTtlKg, 2);
ttlKg = newTtlKg;
}
if (amountInput && priceInput && ttlQtyInput) {
const newAmount = price * ttlQty;
amountInput.value = formatNumber(newAmount, 2);
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 () {
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> </script>
@endsection @endsection

View File

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

View File

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

View File

@@ -1298,7 +1298,7 @@ body {
</div> </div>
<!-- ORDER MANAGEMENT --> <!-- ORDER MANAGEMENT -->
<div class="order-mgmt-box"> <!-- <div class="order-mgmt-box">
<div class="order-mgmt-bar"> <div class="order-mgmt-bar">
<span class="order-mgmt-title"><i class="bi bi-table"></i> Order Management</span> <span class="order-mgmt-title"><i class="bi bi-table"></i> Order Management</span>
@can('order.create') @can('order.create')
@@ -1307,9 +1307,9 @@ body {
</button> </button>
@endcan @endcan
</div> </div>
<div class="order-mgmt-main"> <div class="order-mgmt-main"> -->
<!-- RECENT ORDERS TABLE --> <!-- RECENT ORDERS TABLE -->
<div class="card shadow-sm"> <!-- <div class="card shadow-sm">
<div class="card-header"> <div class="card-header">
<strong>Recent Orders</strong> <strong>Recent Orders</strong>
<span style="font-size:13px;color:rgba(255,255,255,0.8);margin-left:10px;"> <span style="font-size:13px;color:rgba(255,255,255,0.8);margin-left:10px;">
@@ -1421,20 +1421,20 @@ body {
@endforelse @endforelse
</tbody> </tbody>
</table> </table>
</div> </div> -->
<!-- Pagination Controls --> <!-- Pagination Controls -->
<div class="pagination-container"> <!-- <div class="pagination-container">
<div class="pagination-info" id="pageInfo">Showing 1 to 10 of {{ $orders->count() }} entries</div> <div class="pagination-info" id="pageInfo">Showing 1 to 10 of {{ $orders->count() }} entries</div>
<div class="pagination-controls"> <div class="pagination-controls">
<button class="pagination-img-btn" id="prevPageBtn" title="Previous page"> <button class="pagination-img-btn" id="prevPageBtn" title="Previous page">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/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"/> <path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>
</button> </button> -->
<div class="pagination-pages" id="paginationPages"> <!-- <div class="pagination-pages" id="paginationPages"> -->
<!-- Page numbers will be inserted here --> <!-- Page numbers will be inserted here -->
</div> <!-- </div>
<button class="pagination-img-btn" id="nextPageBtn" title="Next page"> <button class="pagination-img-btn" id="nextPageBtn" title="Next page">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/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"/> <path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -1447,7 +1447,7 @@ body {
</div> </div>
</div> </div>
</div> </div>
</div> </div> -->
<!-- CREATE ORDER MODAL --> <!-- CREATE ORDER MODAL -->
<div class="create-order-modal" id="createOrderModal"> <div class="create-order-modal" id="createOrderModal">

View File

@@ -8,10 +8,10 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
/*Remove horizontal scroll bar*/ /*Remove horizontal scroll bar*/
html, body { /* html, body {
overflow-x: hidden !important; overflow-x: hidden !important;
font-family: 'Inter', sans-serif !important; font-family: 'Inter', sans-serif !important;
} } */
/* Invoice Management Styles */ /* Invoice Management Styles */
.invoice-management-box { .invoice-management-box {
@@ -50,43 +50,50 @@
color: #336ad3; color: #336ad3;
} }
/* Tools Row Styling */ /* ===== UPDATED FILTER BAR WITH DATE RANGE ===== */
.invoice-tools-row { .invoice-tools-row {
background: linear-gradient(135deg, #f8fafc 0%, #edf2f7 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 25px 30px; padding: 16px 24px;
border-bottom: 1px solid #e2e8f0; border-bottom: 1px solid rgba(255,255,255,0.1);
display: flex; box-shadow: 0 10px 25px -5px rgba(99, 102, 241, 0.3);
justify-content: space-between; position: relative;
align-items: center; border-radius: 17px 17px 0 0;
gap: 20px; top:5px;
flex-wrap: wrap;
} }
.filter-bar-container {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
width: 100%;
}
/* Search Box Styling */
.search-box { .search-box {
display: flex; display: flex;
align-items: center; align-items: center;
background: white; background: white;
border-radius: 12px; border-radius: 50px;
padding: 10px 18px; padding: 8px 20px;
box-shadow: 0 4px 15px rgba(0,0,0,0.08); box-shadow: 0 4px 15px rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.5);
border: 1.5px solid #e2e8f0; border: 1px solid rgba(255,255,255,0.3);
min-width: 380px; flex: 2;
flex: 1; min-width: 250px;
max-width: 500px;
transition: all 0.3s ease; transition: all 0.3s ease;
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
} }
.search-box:focus-within { .search-box:focus-within {
border-color: #3b82f6; box-shadow: 0 8px 20px rgba(79, 70, 229, 0.25);
box-shadow: 0 4px 20px rgba(59, 130, 246, 0.15);
transform: translateY(-2px); transform: translateY(-2px);
border-color: white;
} }
.search-box i { .search-box i {
color: #64748b; color: #667eea;
margin-right: 12px; margin-right: 12px;
font-size: 1.2rem; font-size: 1.1rem;
} }
.search-box input { .search-box input {
@@ -95,93 +102,189 @@
background: transparent; background: transparent;
width: 100%; width: 100%;
font-size: 15px; font-size: 15px;
color: #334155; color: #1f2937;
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
font-weight: 400; font-weight: 400;
} }
.search-box input::placeholder { .search-box input::placeholder {
color: #94a3b8; color: #9ca3af;
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
font-weight: 300;
} }
/* .search-btn { /* Filter Selects Styling */
background: linear-gradient(90deg, #226ad6, #46b4fd 123%); .filter-select-wrapper {
border: none; flex: 1;
border-radius: 8px; min-width: 140px;
padding: 5px 10px;
color: white;
font-weight: 600;
font-size: 14px;
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);
}
.filter-group {
display: flex;
align-items: center;
gap: 15px;
flex-wrap: wrap;
} }
.filter-select { .filter-select {
width: 100%;
background: white; background: white;
border: 1.5px solid #e2e8f0; border: 1px solid rgba(255,255,255,0.5);
border-radius: 10px; border-radius: 50px;
padding: 10px 15px; padding: 10px 20px;
font-size: 14px; font-size: 14px;
color: #334155; color: #1f2937;
outline: none; outline: none;
min-width: 160px; box-shadow: 0 4px 10px rgba(0,0,0,0.05);
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
font-weight: 400; font-weight: 400;
transition: all 0.3s ease; 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 { .filter-select:focus {
border-color: #3b82f6; border-color: white;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); box-shadow: 0 0 0 3px rgba(255,255,255,0.2);
background-color: white;
} }
.create-invoice-btn { /* ===== NEW: Status dropdown option colors ===== */
background: linear-gradient(90deg, #226ad6, #46b4fd 123%); .filter-select option[value="paid"] {
padding: 12px 28px; background-color: #d1fae5;
font-weight: 600; color: #065f46;
border: none; font-weight: 500;
border-radius: 10px; }
color: #fff;
font-size: 15px; .filter-select option[value="pending"] {
box-shadow: 0 2px 13px #dde7fa42; background-color: #fef3c7;
transition: all 0.3s ease; color: #d97706;
display: flex; font-weight: 500;
align-items: center; }
gap: 8px;
.filter-select option[value="overdue"] {
background-color: #e9d5ff;
color: #6b21a8;
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 {
border: none;
outline: none;
background: transparent;
width: 100%;
font-size: 13px;
color: #1f2937;
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
text-decoration: none; padding: 5px 0;
white-space: nowrap;
} }
.create-invoice-btn:hover { .date-input::placeholder {
background: linear-gradient(90deg, #3264f8, #3acfff 140%); color: #9ca3af;
box-shadow: 0 4px 25px #5ab8f880; font-size: 12px;
color: #fff;
text-decoration: none;
transform: translateY(-2px);
} }
.date-separator {
color: white;
font-weight: 600;
font-size: 14px;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
/* Responsive Design */
@media (max-width: 1100px) {
.filter-bar-container {
gap: 12px;
}
.search-box {
min-width: 200px;
}
.date-range-wrapper {
min-width: 260px;
}
}
@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;
}
}
/* ===== END UPDATED FILTER BAR ===== */
.invoice-management-main { .invoice-management-main {
background: #fff; background: #fff;
border-radius: 0 0 17px 17px; border-radius: 0 0 17px 17px;
@@ -569,37 +672,6 @@
overflow-y: auto; 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: #000000ff;
font-weight: 500;
font-family: 'Inter', sans-serif;
}
/* Stats Summary - Centered */ /* Stats Summary - Centered */
.stats-summary { .stats-summary {
display: grid; display: grid;
@@ -950,17 +1022,6 @@
/* Responsive Design */ /* Responsive Design */
@media (max-width: 1200px) { @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 { .filter-group {
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
@@ -978,10 +1039,6 @@
padding: 15px 20px; padding: 15px 20px;
} }
.invoice-tools-row {
padding: 20px;
}
.table th, .table td { .table th, .table td {
font-size: 13px; font-size: 13px;
padding: 15px 10px; padding: 15px 10px;
@@ -1003,21 +1060,6 @@
font-size: 12px; 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 { .invoice-icon {
width: 32px; width: 32px;
height: 32px; height: 32px;
@@ -1033,15 +1075,6 @@
display: block; display: block;
} }
.date-range-container {
flex-direction: column;
width: 100%;
}
.date-input {
width: 100%;
}
.stats-summary { .stats-summary {
grid-template-columns: 1fr; grid-template-columns: 1fr;
margin: 20px; margin: 20px;
@@ -1079,11 +1112,6 @@
min-width: auto; min-width: auto;
padding: 10px 15px; padding: 10px 15px;
} }
.search-btn {
padding: 8px 15px;
font-size: 13px;
}
} }
</style> </style>
@@ -1096,56 +1124,58 @@
</span> </span>
</div> </div>
<!-- TOOLS ROW - Search, Filter, Create Button --> <!-- UPDATED FILTER BAR WITH DATE RANGE -->
<div class="invoice-tools-row"> <div class="invoice-tools-row">
<!-- Search Box with Button --> <form method="GET" action="{{ route('admin.invoices.index') }}" style="width: 100%;">
<form method="GET" action="{{ route('admin.invoices.index') }}"> <div class="filter-bar-container">
<div class="invoice-tools-row"> <!-- Search Box -->
<div style="display:flex; align-items:center; flex:1; max-width:500px; min-width:380px;"> <div class="search-box">
<div class="search-box"> <i class="bi bi-search"></i>
<i class="bi bi-search"></i> <input type="text"
<input type="text" id="searchInput"
id="invoiceSearch" name="search"
name="search" value="{{ request('search') }}"
value="{{ request('search') }}" placeholder="Search Invoices...">
placeholder="Search by invoice number, customer name..."> </div>
</div>
<button type="submit" id="searchButton" class="search-btn">
Search
</button>
</div>
<div class="filter-group"> <!-- Status Filter -->
<select class="filter-select" id="statusFilter" name="status"> <div class="filter-select-wrapper">
<option value="all">All Status</option> <select class="filter-select" id="statusFilter" name="status">
<option value="paid" {{ request('status')=='paid' ? 'selected' : '' }}>Paid</option> <option value="all" {{ request('status')=='all' ? 'selected' : '' }}>All Status</option>
<option value="pending" {{ request('status')=='pending' ? 'selected' : '' }}>Pending</option> <option value="paid" {{ request('status')=='paid' ? 'selected' : '' }}>Paid</option>
<option value="overdue" {{ request('status')=='overdue' ? 'selected' : '' }}>Overdue</option> <option value="pending" {{ request('status')=='pending' ? 'selected' : '' }}>Pending</option>
</select> <option value="overdue" {{ request('status')=='overdue' ? 'selected' : '' }}>Overdue</option>
</select>
</div>
<div class="date-range-container"> <!-- Date Range Filter (FROM - TO) with dd-mm-yyyy format -->
<input type="date" <div class="date-range-wrapper">
class="date-input" <div class="date-input-container">
id="startDate" <i class="bi bi-calendar3"></i>
name="start_date" <input type="text"
value="{{ request('start_date') }}"> class="date-input"
<span class="date-separator">to</span> id="startDate"
<input type="date" name="start_date"
class="date-input" value="{{ request('start_date') ? date('d-m-Y', strtotime(request('start_date'))) : '' }}"
id="endDate" placeholder="dd-mm-yyyy"
name="end_date" onfocus="(this.type='date')"
value="{{ request('end_date') }}"> onblur="if(!this.value) this.type='text'">
</div> </div>
</div> <span class="date-separator">to</span>
</div> <div class="date-input-container">
</form> <i class="bi bi-calendar3"></i>
<input type="text"
class="date-input"
id="endDate"
<!-- Create Invoice Button --> name="end_date"
<!-- <a href="{{ route('admin.invoices.create') }}" class="create-invoice-btn"> value="{{ request('end_date') ? date('d-m-Y', strtotime(request('end_date'))) : '' }}"
<i class="bi bi-plus-circle"></i> Create Invoice placeholder="dd-mm-yyyy"
</a> --> onfocus="(this.type='date')"
onblur="if(!this.value) this.type='text'">
</div>
</div>
</div>
</form>
</div> </div>
<div class="invoice-management-main no-extra-space"> <div class="invoice-management-main no-extra-space">
@@ -1181,77 +1211,107 @@
<div class="card-body p-0"> <div class="card-body p-0">
<div class="table-container"> <div class="table-container">
<table class="table table-striped align-middle" id="invoicesTable"> <table class="table table-striped align-middle" id="invoicesTable">
<thead class="table-light"> <thead class="table-light">
<tr> <tr>
<th class="column-header">#</th> <th class="column-header">#</th>
<th class="column-header">Invoice Number</th> <th class="column-header">Invoice Number</th>
<th class="column-header">Customer</th> <th class="column-header">Customer</th>
<th class="column-header">Final Amount</th> <th class="column-header">Container</th> {{-- NEW --}}
<th class="column-header">GST %</th> <th class="column-header">Final Amount</th>
<th class="column-header">Total w/GST</th> <th class="column-header">GST %</th>
<th class="column-header">Status</th> <th class="column-header">Total w/GST</th>
<th class="column-header">Invoice Date</th> <th class="column-header">Status</th>
<th class="column-header">Due Date</th> <th class="column-header">Invoice Date</th>
<th class="column-header">Action</th> <th class="column-header">Due Date</th>
</tr> <th class="column-header">Action</th>
</thead> </tr>
</thead>
<tbody id="invoicesTableBody"> <tbody id="invoicesTableBody">
@php @php
$totalInvoices = $invoices->count(); $totalInvoices = $invoices->count();
$sortedInvoices = $invoices->sortByDesc('created_at'); // Latest first $sortedInvoices = $invoices->sortByDesc('created_at'); // Latest first
@endphp @endphp
@forelse($sortedInvoices as $i => $invoice) @forelse($sortedInvoices as $i => $invoice)
<tr> <tr>
<td>{{ $totalInvoices - $i }}</td> <td>{{ $totalInvoices - $i }}</td>
<td> <td>
<div class="invoice-number-cell"> <div class="invoice-number-cell">
<div class="invoice-icon invoice-icon-{{ (($totalInvoices - $i) % 8) + 1 }}"> <div class="invoice-icon invoice-icon-{{ (($totalInvoices - $i) % 8) + 1 }}">
<i class="bi bi-file-earmark-text"></i> <i class="bi bi-file-earmark-text"></i>
</div> </div>
<a href="#" class="invoice-number-link open-invoice-popup" data-id="{{ $invoice->id }}"> <a href="#" class="invoice-number-link open-invoice-popup" data-id="{{ $invoice->id }}">
{{ $invoice->invoice_number }} {{ $invoice->invoice_number }}
</a> </a>
</div> </div>
</td> </td>
<td class="customer-cell">{{ $invoice->customer_name }}</td> <td class="customer-cell">
{{ $invoice->customer_name }}
</td>
<td class="amount-cell">{{ number_format($invoice->final_amount, 2) }}</td> {{-- NEW: Container column --}}
<td class="gst-cell">{{ $invoice->gst_percent }}%</td> <td class="customer-cell">
<td class="amount-cell">{{ number_format($invoice->final_amount_with_gst, 2) }}</td> @if($invoice->container)
{{ $invoice->container->container_number }}
{{-- जर फक्त ID हवी असेल तर:
#{{ $invoice->container->id }}
--}}
@else
@endif
</td>
<td> <td class="amount-cell">
<span class="badge badge-{{ $invoice->status }}"> {{ number_format($invoice->final_amount, 2) }}
@if($invoice->status == 'paid') </td>
<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="date-cell">{{ $invoice->invoice_date }}</td> <td class="gst-cell">
<td class="date-cell">{{ $invoice->due_date }}</td> {{ $invoice->gst_percent }}%
</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 == '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>
<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> </table>
</div> </div>
</div> </div>
@@ -1366,7 +1426,6 @@
</div> </div>
</div> </div>
</div> </div>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Pagination state // Pagination state
@@ -1413,18 +1472,28 @@ document.addEventListener('DOMContentLoaded', function() {
} }
}); });
// Search and filter functionality for both desktop and mobile // Function to parse dd-mm-yyyy to Date object
const searchInput = document.getElementById('invoiceSearch'); 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');
const statusFilter = document.getElementById('statusFilter'); const statusFilter = document.getElementById('statusFilter');
const startDateInput = document.getElementById('startDate'); const startDate = document.getElementById('startDate');
const endDateInput = document.getElementById('endDate'); const endDate = document.getElementById('endDate');
const searchButton = document.getElementById('searchButton');
function filterInvoices() { function filterInvoices() {
const searchTerm = searchInput.value.toLowerCase(); const searchTerm = searchInput.value.toLowerCase();
const statusValue = statusFilter.value; const statusValue = statusFilter.value;
const startDate = startDateInput.value; const startDateValue = startDate.value;
const endDate = endDateInput.value; const endDateValue = endDate.value;
filteredInvoices = allInvoices.filter(invoice => { filteredInvoices = allInvoices.filter(invoice => {
let include = true; let include = true;
@@ -1438,18 +1507,23 @@ document.addEventListener('DOMContentLoaded', function() {
} }
// Status filter // Status filter
if (statusValue && invoice.status !== statusValue) { if (statusValue && statusValue !== 'all' && invoice.status !== statusValue) {
include = false; include = false;
} }
// Date filter // Date range filter
if (startDate || endDate) { if (startDateValue || endDateValue) {
const invoiceDate = new Date(invoice.invoice_date); const invoiceDate = parseDate(invoice.invoice_date);
const start = startDate ? new Date(startDate) : null;
const end = endDate ? new Date(endDate) : null;
if (start && invoiceDate < start) include = false; if (startDateValue) {
if (end && invoiceDate > end) include = false; const start = parseDate(startDateValue);
if (invoiceDate < start) include = false;
}
if (endDateValue) {
const end = parseDate(endDateValue);
if (invoiceDate > end) include = false;
}
} }
return include; return include;
@@ -1462,14 +1536,14 @@ document.addEventListener('DOMContentLoaded', function() {
// Add event listeners for filtering // Add event listeners for filtering
searchInput.addEventListener('input', filterInvoices); searchInput.addEventListener('input', filterInvoices);
searchButton.addEventListener('click', filterInvoices);
statusFilter.addEventListener('change', filterInvoices); statusFilter.addEventListener('change', filterInvoices);
startDateInput.addEventListener('change', filterInvoices); startDate.addEventListener('change', filterInvoices);
endDateInput.addEventListener('change', filterInvoices); endDate.addEventListener('change', filterInvoices);
// Also trigger search on Enter key in search input // Also trigger search on Enter key in search input
searchInput.addEventListener('keypress', function(e) { searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') { if (e.key === 'Enter') {
e.preventDefault();
filterInvoices(); filterInvoices();
} }
}); });
@@ -1559,7 +1633,8 @@ document.addEventListener('DOMContentLoaded', function() {
const mobileContainer = document.getElementById('mobileInvoicesContainer'); const mobileContainer = document.getElementById('mobileInvoicesContainer');
if (filteredInvoices.length === 0) { if (filteredInvoices.length === 0) {
tbody.innerHTML = '<tr><td colspan="10" class="text-muted">No invoices found</td></tr>'; // 1 column वाढवल्यामुळे colspan 11
tbody.innerHTML = '<tr><td colspan="11" class="text-muted">No invoices found</td></tr>';
mobileContainer.innerHTML = '<div class="text-muted text-center py-4">No invoices found</div>'; mobileContainer.innerHTML = '<div class="text-muted text-center py-4">No invoices found</div>';
return; return;
} }
@@ -1590,6 +1665,10 @@ document.addEventListener('DOMContentLoaded', function() {
</div> </div>
</td> </td>
<td class="customer-cell">${invoice.customer_name}</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.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
<td class="gst-cell">${invoice.gst_percent}%</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 class="amount-cell">${parseFloat(invoice.final_amount_with_gst).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
@@ -1639,6 +1718,13 @@ document.addEventListener('DOMContentLoaded', function() {
<span class="mobile-detail-label">Customer</span> <span class="mobile-detail-label">Customer</span>
<span class="mobile-detail-value">${invoice.customer_name}</span> <span class="mobile-detail-value">${invoice.customer_name}</span>
</div> </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"> <div class="mobile-detail-item">
<span class="mobile-detail-label">Amount</span> <span class="mobile-detail-label">Amount</span>
<span class="mobile-detail-value">${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</span> <span class="mobile-detail-value">${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</span>
@@ -1683,11 +1769,6 @@ document.addEventListener('DOMContentLoaded', function() {
} }
}); });
}); });
</script> </script>
@endsection @endsection

View File

@@ -1,7 +1,9 @@
@extends('admin.layouts.app') @extends('admin.layouts.app')
@section('page-title', 'Edit Invoice') @section('page-title', 'Edit Invoice')
@section('content') @section('content')
<style> <style>
:root { :root {
@@ -460,11 +462,12 @@
</div> </div>
@php @php
$totalPaid = $invoice->installments()->sum('amount'); // आता helpers वापरू: totalPaid() आणि remainingAmount()
$remaining = $invoice->final_amount_with_gst - $totalPaid; $totalPaid = $invoice->totalPaid();
$remaining = $invoice->remainingAmount();
@endphp @endphp
{{-- Amount Breakdown --}} {{-- Amount Breakdown (items + GST + groups) --}}
<div class="amount-breakdown-compact"> <div class="amount-breakdown-compact">
<h6 class="fw-bold mb-3 text-dark"> <h6 class="fw-bold mb-3 text-dark">
<i class="fas fa-calculator me-2"></i>Amount Breakdown <i class="fas fa-calculator me-2"></i>Amount Breakdown
@@ -506,10 +509,17 @@
</span> </span>
</div> </div>
<div class="breakdown-row">
<span class="breakdown-label">Charge Groups Total</span>
<span class="breakdown-value text-info" id="chargeGroupsTotal">
{{ number_format($invoice->charge_groups_total, 2) }}
</span>
</div>
<div class="breakdown-row" style="border-top: 2px solid #e2e8f0; padding-top: 0.75rem;"> <div class="breakdown-row" style="border-top: 2px solid #e2e8f0; padding-top: 0.75rem;">
<span class="breakdown-label fw-bold">Total Invoice Amount Including GST</span> <span class="breakdown-label fw-bold">Grand Total (Items + GST + Groups)</span>
<span class="breakdown-value fw-bold text-dark" id="totalInvoiceWithGst"> <span class="breakdown-value fw-bold text-dark" id="totalInvoiceWithGst">
{{ number_format($invoice->final_amount_with_gst, 2) }} {{ number_format($invoice->grand_total_with_charges, 2) }}
</span> </span>
</div> </div>
@@ -523,18 +533,18 @@
<div class="breakdown-row" style="border-bottom: none;"> <div class="breakdown-row" style="border-bottom: none;">
<span class="breakdown-label text-danger">Remaining</span> <span class="breakdown-label text-danger">Remaining</span>
<span class="breakdown-value fw-bold text-danger" id="remainingAmount"> <span class="breakdown-value fw-bold text-danger" id="remainingAmount">
{{ number_format($remaining, 2) }} {{ number_format(max(0, $remaining), 2) }}
</span> </span>
</div> </div>
</div> </div>
{{-- Installment Summary --}} {{-- Installment Summary (top cards) --}}
<div class="summary-grid-compact"> <div class="summary-grid-compact">
<div class="summary-card-compact total"> <div class="summary-card-compact total">
<div class="summary-value-compact text-success" id="totalInvoiceWithGstCard"> <div class="summary-value-compact text-success" id="totalInvoiceWithGstCard">
{{ number_format($invoice->final_amount_with_gst, 2) }} {{ number_format($invoice->grand_total_with_charges, 2) }}
</div> </div>
<div class="summary-label-compact">Total Amount</div> <div class="summary-label-compact">Grand Total (Items + GST + Groups)</div>
</div> </div>
<div class="summary-card-compact paid"> <div class="summary-card-compact paid">
<div class="summary-value-compact text-primary"> <div class="summary-value-compact text-primary">
@@ -544,7 +554,7 @@
</div> </div>
<div class="summary-card-compact remaining"> <div class="summary-card-compact remaining">
<div class="summary-value-compact text-warning"> <div class="summary-value-compact text-warning">
{{ number_format($remaining, 2) }} {{ number_format(max(0, $remaining), 2) }}
</div> </div>
<div class="summary-label-compact">Remaining</div> <div class="summary-label-compact">Remaining</div>
</div> </div>
@@ -621,7 +631,7 @@
class="form-control-compact" class="form-control-compact"
step="0.01" step="0.01"
min="1" min="1"
max="{{ $remaining }}" max="{{ max(0, $remaining) }}"
required required
placeholder="Enter installment amount"> placeholder="Enter installment amount">
</div> </div>
@@ -784,6 +794,8 @@ document.addEventListener("DOMContentLoaded", function () {
if (document.getElementById("gstAmount")) { if (document.getElementById("gstAmount")) {
document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount); document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount);
} }
// grand total आता finalAmountWithGst नाही, पण API अजून तेच key देत आहे,
// त्यामुळे इथे फक्त card आणि breakdown value update करतो:
if (document.getElementById("totalInvoiceWithGst")) { if (document.getElementById("totalInvoiceWithGst")) {
document.getElementById("totalInvoiceWithGst").textContent = "" + formatINR(data.finalAmountWithGst); document.getElementById("totalInvoiceWithGst").textContent = "" + formatINR(data.finalAmountWithGst);
} }
@@ -876,7 +888,6 @@ document.addEventListener("DOMContentLoaded", function () {
}); });
}); });
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const invoiceDateInput = document.querySelector('input[name="invoice_date"]'); const invoiceDateInput = document.querySelector('input[name="invoice_date"]');
const dueDateInput = document.querySelector('input[name="due_date"]'); const dueDateInput = document.querySelector('input[name="due_date"]');
@@ -886,14 +897,10 @@ document.addEventListener('DOMContentLoaded', function() {
const selectedDate = new Date(this.value); const selectedDate = new Date(this.value);
if (!isNaN(selectedDate.getTime())) { if (!isNaN(selectedDate.getTime())) {
// १० दिवस पुढे नेण्यासाठी logic
selectedDate.setDate(selectedDate.getDate() + 10); selectedDate.setDate(selectedDate.getDate() + 10);
// तारीख YYYY-MM-DD format मध्ये करण्यासाठी
const year = selectedDate.getFullYear(); const year = selectedDate.getFullYear();
const month = String(selectedDate.getMonth() + 1).padStart(2, '0'); const month = String(selectedDate.getMonth() + 1).padStart(2, '0');
const day = String(selectedDate.getDate()).padStart(2, '0'); const day = String(selectedDate.getDate()).padStart(2, '0');
dueDateInput.value = `${year}-${month}-${day}`; dueDateInput.value = `${year}-${month}-${day}`;
} }
}); });

View File

@@ -65,40 +65,77 @@
.stats-container { .stats-container {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px; gap: 16px;
margin-bottom: 24px; margin-bottom: 24px;
} }
.stat-card { .stat-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: var(--shadow-sm);
border-left: 4px solid;
display: flex; display: flex;
flex-direction: column; align-items: center;
gap: 16px;
padding: 20px 22px;
border-radius: 14px;
border-left: 4px solid;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
transition: transform 0.3s ease, box-shadow 0.3s ease; transition: transform 0.3s ease, box-shadow 0.3s ease;
position: relative;
overflow: hidden;
} }
.stat-card:hover { .stat-card:hover {
transform: translateY(-5px); transform: translateY(-4px);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); box-shadow: 0 8px 20px rgba(0,0,0,0.1);
} }
.stat-card.total { border-left-color: #667eea; } .stat-card.total {
.stat-card.paid { border-left-color: #10b981; } background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
.stat-card.pending { border-left-color: #f59e0b; } border-left-color: #3b82f6;
.stat-card.overdue { border-left-color: #ef4444; } }
.stat-card.paid {
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
border-left-color: #22c55e;
}
.stat-card.pending {
background: linear-gradient(135deg, #fffbeb 0%, #fef9c3 100%);
border-left-color: #f59e0b;
}
.stat-card.overdue {
background: linear-gradient(135deg, #fff5f5 0%, #fee2e2 100%);
border-left-color: #ef4444;
}
.stat-icon-wrap {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-size: 20px;
}
.stat-card.total .stat-icon-wrap { background: rgba(59,130,246,0.12); color: #3b82f6; }
.stat-card.paid .stat-icon-wrap { background: rgba(34,197,94,0.15); color: #16a34a; }
.stat-card.pending .stat-icon-wrap{ background: rgba(245,158,11,0.15); color: #d97706; }
.stat-card.overdue .stat-icon-wrap{ background: rgba(239,68,68,0.12); color: #dc2626; }
.stat-text {
display: flex;
flex-direction: column;
gap: 4px;
}
.stat-value { .stat-value {
font-size: 28px; font-size: 26px;
font-weight: 700; font-weight: 700;
margin-bottom: 8px; line-height: 1;
color: var(--text-dark);
} }
.stat-label { .stat-label {
font-size: 14px; font-size: 13px;
color: var(--text-muted); color: var(--text-muted);
font-weight: 500; font-weight: 500;
} }
@@ -483,9 +520,9 @@
@media(max-width: 768px) { @media(max-width: 768px) {
.orders-title { font-size: 24px; } .orders-title { font-size: 24px; }
.orders-container { padding: 16px; } .orders-container { padding: 16px; }
.stats-container { grid-template-columns: 1fr; } .stats-container { grid-template-columns: 1fr 1fr; }
.stat-card { padding: 16px; } .stat-card { padding: 14px 16px; }
.stat-value { font-size: 24px; } .stat-value { font-size: 22px; }
.download-section { justify-content: stretch; } .download-section { justify-content: stretch; }
.download-btn { flex: 1; justify-content: center; } .download-btn { flex: 1; justify-content: center; }
@@ -497,6 +534,10 @@
.pagination-container { flex-direction: column; gap: 16px; } .pagination-container { flex-direction: column; gap: 16px; }
} }
@media(max-width: 480px) {
.stats-container { grid-template-columns: 1fr; }
}
</style> </style>
<div class="orders-container"> <div class="orders-container">
@@ -504,7 +545,6 @@
<i class="fas fa-file-invoice-dollar"></i> Invoices Management <i class="fas fa-file-invoice-dollar"></i> Invoices Management
</div> </div>
{{-- Stats Cards based on invoices --}}
<div class="stats-container"> <div class="stats-container">
@php @php
$totalInvoices = $invoices->count(); $totalInvoices = $invoices->count();
@@ -512,22 +552,45 @@
$pendingInvoices = $invoices->where('invoice_status', 'pending')->count(); $pendingInvoices = $invoices->where('invoice_status', 'pending')->count();
$overdueInvoices = $invoices->where('invoice_status', 'overdue')->count(); $overdueInvoices = $invoices->where('invoice_status', 'overdue')->count();
@endphp @endphp
<div class="stat-card total"> <div class="stat-card total">
<div class="stat-value">{{ $totalInvoices }}</div> <div class="stat-icon-wrap">
<div class="stat-label">Total Invoices</div> <i class="fas fa-file-invoice"></i>
</div>
<div class="stat-text">
<div class="stat-value">{{ $totalInvoices }}</div>
<div class="stat-label">Total Invoices</div>
</div>
</div> </div>
<div class="stat-card paid"> <div class="stat-card paid">
<div class="stat-value">{{ $paidInvoices }}</div> <div class="stat-icon-wrap">
<div class="stat-label">Paid Invoices</div> <i class="fas fa-check-circle"></i>
</div>
<div class="stat-text">
<div class="stat-value">{{ $paidInvoices }}</div>
<div class="stat-label">Paid Invoices</div>
</div>
</div> </div>
<div class="stat-card pending"> <div class="stat-card pending">
<div class="stat-value">{{ $pendingInvoices }}</div> <div class="stat-icon-wrap">
<div class="stat-label">Pending Invoices</div> <i class="fas fa-clock"></i>
</div>
<div class="stat-text">
<div class="stat-value">{{ $pendingInvoices }}</div>
<div class="stat-label">Pending Invoices</div>
</div>
</div> </div>
<div class="stat-card overdue"> <div class="stat-card overdue">
<div class="stat-value">{{ $overdueInvoices }}</div> <div class="stat-icon-wrap">
<div class="stat-label">Overdue Invoices</div> <i class="fas fa-exclamation-triangle"></i>
</div>
<div class="stat-text">
<div class="stat-value">{{ $overdueInvoices }}</div>
<div class="stat-label">Overdue Invoices</div>
</div>
</div> </div>
</div> </div>
@@ -570,7 +633,7 @@
<tr> <tr>
<th>Invoice No</th> <th>Invoice No</th>
<th>Invoice Date</th> <th>Invoice Date</th>
<th>Mark No</th> {{-- <th>Mark No</th> --}}
<th>Container No</th> <th>Container No</th>
<th>Container Date</th> <th>Container Date</th>
<th>Company</th> <th>Company</th>
@@ -586,10 +649,30 @@
$status = strtolower($inv->invoice_status ?? 'pending'); $status = strtolower($inv->invoice_status ?? 'pending');
@endphp @endphp
<tr> <tr>
<td>{{ $inv->invoice_number }}</td> <td>
@if($inv->invoice_number)
<a href="javascript:void(0);"
class="invoice-popup-link"
data-invoice-id="{{ $inv->id }}">
{{ $inv->invoice_number }}
</a>
@else
-
@endif
</td>
<td>{{ $inv->invoice_date ? \Carbon\Carbon::parse($inv->invoice_date)->format('d-m-Y') : '-' }}</td> <td>{{ $inv->invoice_date ? \Carbon\Carbon::parse($inv->invoice_date)->format('d-m-Y') : '-' }}</td>
<td>{{ $inv->mark_no ?? '-' }}</td> {{-- <td>{{ $inv->mark_no ?? '-' }}</td> --}}
<td>{{ $inv->container_number ?? '-' }}</td> <td>
@if(!empty($inv->container_id) && !empty($inv->container_number))
<a href="javascript:void(0);"
class="container-popup-link"
data-container-id="{{ $inv->container_id }}">
{{ $inv->container_number }}
</a>
@else
-
@endif
</td>
<td>{{ $inv->container_date ? \Carbon\Carbon::parse($inv->container_date)->format('d-m-Y') : '-' }}</td> <td>{{ $inv->container_date ? \Carbon\Carbon::parse($inv->container_date)->format('d-m-Y') : '-' }}</td>
<td>{{ $inv->company_name ?? '-' }}</td> <td>{{ $inv->company_name ?? '-' }}</td>
<td>{{ $inv->customer_name ?? '-' }}</td> <td>{{ $inv->customer_name ?? '-' }}</td>
@@ -633,6 +716,20 @@
@endif @endif
</div> </div>
<div class="modal fade" id="invoiceModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="invoiceModalTitle">Invoice Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="invoiceModalBody">
<p class="text-center text-muted">Loading...</p>
</div>
</div>
</div>
</div>
<script> <script>
let currentPage = 1; let currentPage = 1;
const itemsPerPage = 10; const itemsPerPage = 10;
@@ -680,7 +777,7 @@
const term = searchTerm.toLowerCase(); const term = searchTerm.toLowerCase();
const fields = [ const fields = [
inv.invoice_number?.toString().toLowerCase(), inv.invoice_number?.toString().toLowerCase(),
inv.mark_no?.toString().toLowerCase(), // inv.mark_no?.toString().toLowerCase(),
inv.container_number?.toString().toLowerCase(), inv.container_number?.toString().toLowerCase(),
inv.company_name?.toString().toLowerCase(), inv.company_name?.toString().toLowerCase(),
inv.customer_name?.toString().toLowerCase() inv.customer_name?.toString().toLowerCase()
@@ -711,6 +808,21 @@
document.getElementById('downloadPdf').addEventListener('click', downloadPdf); document.getElementById('downloadPdf').addEventListener('click', downloadPdf);
document.getElementById('downloadExcel').addEventListener('click', downloadExcel); document.getElementById('downloadExcel').addEventListener('click', downloadExcel);
document.addEventListener('click', function(e) {
const invLink = e.target.closest('.invoice-popup-link');
if (invLink) {
e.preventDefault();
openInvoicePopup(invLink.dataset.invoiceId);
return;
}
const contLink = e.target.closest('.container-popup-link');
if (contLink) {
e.preventDefault();
openContainerPopup(contLink.dataset.containerId);
}
});
}); });
function handleFilter() { function handleFilter() {
@@ -797,7 +909,7 @@
tbody.innerHTML = ''; tbody.innerHTML = '';
if (filteredInvoices.length === 0) { if (filteredInvoices.length === 0) {
tbody.innerHTML = '<tr><td colspan="10" class="text-center py-4 text-muted">No invoices found matching your criteria.</td></tr>'; tbody.innerHTML = '<tr><td colspan="9" class="text-center py-4 text-muted">No invoices found matching your criteria.</td></tr>';
return; return;
} }
@@ -809,10 +921,26 @@
const status = (inv.invoice_status || 'pending').toLowerCase(); const status = (inv.invoice_status || 'pending').toLowerCase();
const row = document.createElement('tr'); const row = document.createElement('tr');
row.innerHTML = ` row.innerHTML = `
<td>${inv.invoice_number || '-'}</td> <td>
${
inv.invoice_number
? `<a href="javascript:void(0);" class="invoice-popup-link" data-invoice-id="${inv.id}">
${inv.invoice_number}
</a>`
: '-'
}
</td>
<td>${inv.invoice_date ? new Date(inv.invoice_date).toLocaleDateString('en-GB') : '-'}</td> <td>${inv.invoice_date ? new Date(inv.invoice_date).toLocaleDateString('en-GB') : '-'}</td>
<td>${inv.mark_no || '-'}</td> <!-- <td>${inv.mark_no || '-'}</td> -->
<td>${inv.container_number || '-'}</td> <td>
${
inv.container_id && inv.container_number
? `<a href="javascript:void(0);" class="container-popup-link" data-container-id="${inv.container_id}">
${inv.container_number}
</a>`
: '-'
}
</td>
<td>${inv.container_date ? new Date(inv.container_date).toLocaleDateString('en-GB') : '-'}</td> <td>${inv.container_date ? new Date(inv.container_date).toLocaleDateString('en-GB') : '-'}</td>
<td>${inv.company_name || '-'}</td> <td>${inv.company_name || '-'}</td>
<td>${inv.customer_name || '-'}</td> <td>${inv.customer_name || '-'}</td>
@@ -828,6 +956,66 @@
}); });
} }
function openInvoicePopup(invoiceId) {
const modal = new bootstrap.Modal(document.getElementById('invoiceModal'));
document.getElementById('invoiceModalTitle').textContent = 'Invoice Details';
document.getElementById('invoiceModalBody').innerHTML =
"<div class='text-center py-4'>" +
"<div class='spinner-border text-primary' role='status'></div>" +
"<p class='mt-2 text-muted'>Loading invoice details...</p>" +
"</div>";
modal.show();
fetch(`/admin/invoices/${invoiceId}/popup`)
.then(res => {
if (!res.ok) throw new Error('Network error');
return res.text();
})
.then(html => {
document.getElementById('invoiceModalBody').innerHTML = html;
})
.catch(error => {
console.error(error);
document.getElementById('invoiceModalBody').innerHTML =
"<div class='text-center py-4 text-danger'>" +
"<i class='bi bi-exclamation-triangle fs-1'></i>" +
"<p class='mt-2'>Failed to load invoice details. Please try again.</p>" +
"</div>";
});
}
function openContainerPopup(containerId) {
const modal = new bootstrap.Modal(document.getElementById('invoiceModal'));
document.getElementById('invoiceModalTitle').textContent = 'Container Details';
document.getElementById('invoiceModalBody').innerHTML =
"<div class='text-center py-4'>" +
"<div class='spinner-border text-primary' role='status'></div>" +
"<p class='mt-2 text-muted'>Loading container details...</p>" +
"</div>";
modal.show();
fetch(`/admin/containers/${containerId}/popup`)
.then(res => {
if (!res.ok) throw new Error('Network error');
return res.text();
})
.then(html => {
document.getElementById('invoiceModalBody').innerHTML = html;
})
.catch(error => {
console.error(error);
document.getElementById('invoiceModalBody').innerHTML =
"<div class='text-center py-4 text-danger'>" +
"<i class='bi bi-exclamation-triangle fs-1'></i>" +
"<p class='mt-2'>Failed to load container details.</p>" +
"</div>";
});
}
function downloadPdf() { function downloadPdf() {
if (filteredInvoices.length === 0) { if (filteredInvoices.length === 0) {
showNotification('No data available to download', 'warning'); showNotification('No data available to download', 'warning');

View File

@@ -477,7 +477,7 @@
} }
/* ── SUMMARY ── */ /* ── SUMMARY ── */
.summary-wrap { /* .summary-wrap {
padding: 0 2.5rem 2rem; padding: 0 2.5rem 2rem;
} }
@@ -516,7 +516,79 @@
.summary-row .value { font-weight: 700; color: var(--primary); } .summary-row .value { font-weight: 700; color: var(--primary); }
.summary-row.total .label { font-size: 1rem; font-weight: 700; color: var(--primary); } .summary-row.total .label { font-size: 1rem; font-weight: 700; color: var(--primary); }
.summary-row.total .value { font-size: 1.15rem; color: #10b981; } .summary-row.total .value { font-size: 1.15rem; color: #10b981; }
.summary-row .value.red { color: #ef4444; } .summary-row .value.red { color: #ef4444; } */
.summary-card-compact {
margin: 1.5rem 2.5rem 1.5rem;
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
box-shadow: var(--shadow-lg);
background: var(--surface);
}
/* Header full width, curved top corners */
.summary-header-compact {
background: var(--primary);
color: #fff;
padding: 0.8rem 1.25rem;
font-weight: 700;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.5rem;
width: 100%;
border-top-left-radius: var(--radius);
border-top-right-radius: var(--radius);
}
/* Icon थोडा highlight */
.summary-header-compact i {
font-size: 0.9rem;
opacity: 0.9;
}
.summary-body-compact {
padding: 1rem 1.25rem 0.9rem;
}
.summary-row-compact {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.45rem 0;
border-bottom: 1px solid var(--border);
font-size: 0.88rem;
}
.summary-row-compact:last-child {
border-bottom: none;
}
.summary-row-compact .label {
color: var(--text-secondary);
font-weight: 500;
}
.summary-row-compact .value {
font-weight: 700;
color: var(--primary);
}
.summary-row-compact.total .label {
font-size: 0.95rem;
font-weight: 700;
}
.summary-row-compact.total .value {
font-size: 1.05rem;
color: #10b981;
}
.summary-row-compact.muted .value {
color: var(--text-muted);
}
/* ── FOOTER ── */ /* ── FOOTER ── */
.invoice-footer { .invoice-footer {
@@ -653,8 +725,8 @@
</div> </div>
<!-- ═══════════════════════════ ID BOXES ═══════════════════════════ --> <!-- ═══════════════════════════ ID BOXES ═══════════════════════════ -->
<div class="id-grid"> <!-- <div class="id-grid">
<!-- Invoice ID -->
<div class="id-box"> <div class="id-box">
<div class="id-icon-wrap id-icon-blue"> <div class="id-icon-wrap id-icon-blue">
<i class="fas fa-receipt"></i> <i class="fas fa-receipt"></i>
@@ -663,10 +735,10 @@
<div class="id-label">Invoice ID</div> <div class="id-label">Invoice ID</div>
<div class="id-value">{{ $invoice->invoice_number }}</div> <div class="id-value">{{ $invoice->invoice_number }}</div>
</div> </div>
</div> </div> -->
<!-- Container ID --> <!-- Container ID -->
<div class="id-box"> <!-- <div class="id-box">
<div class="id-icon-wrap id-icon-green"> <div class="id-icon-wrap id-icon-green">
<i class="fas fa-box"></i> <i class="fas fa-box"></i>
</div> </div>
@@ -684,25 +756,56 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div> -->
<!-- ═══════════════════════════ DATES ═══════════════════════════ --> <!-- ═══════════════════════════ DATES ═══════════════════════════ -->
<div class="date-strip"> <!-- ═══════════════════════════ ID + DATES (ONE ROW) ═══════════════════════════ -->
<!-- <div class="date-strip">
<div class="date-row"> <div class="date-row">
{{-- Container ID --}}
<div class="date-card" style="flex: 1.2;">
<div class="date-icon-wrap" style="background:#ecfeff;color:#0e7490;">
<i class="fas fa-box"></i>
</div>
<div>
<div class="date-label">Container ID</div>
<div class="date-value">
@if($invoice->container && $invoice->container->container_number)
{{ $invoice->container->container_number }}
@elseif($invoice->container_id)
{{ $invoice->container_id }}
@else
N/A
@endif
</div>
</div>
</div>
{{-- छोटा arrow --}}
<div class="date-arrow">
<i class="fas fa-arrow-right"></i>
</div>
{{-- Invoice Date --}}
<div class="date-card"> <div class="date-card">
<div class="date-icon-wrap"> <div class="date-icon-wrap">
<i class="fas fa-calendar-alt"></i> <i class="fas fa-calendar-alt"></i>
</div> </div>
<div> <div>
<div class="date-label">Invoice Date</div> <div class="date-label">Invoice Date</div>
<div class="date-value">{{ \Carbon\Carbon::parse($invoice->invoice_date)->format('M d, Y') }}</div> <div class="date-value">
{{ \Carbon\Carbon::parse($invoice->invoice_date)->format('M d, Y') }}
</div>
</div> </div>
</div> </div>
{{-- दुसरा arrow --}}
<div class="date-arrow"> <div class="date-arrow">
<i class="fas fa-arrow-right"></i> <i class="fas fa-arrow-right"></i>
</div> </div>
{{-- Due Date --}}
<div class="date-card"> <div class="date-card">
<div class="date-icon-wrap" style="background:#fff7ed;color:#f59e0b;"> <div class="date-icon-wrap" style="background:#fff7ed;color:#f59e0b;">
<i class="fas fa-clock"></i> <i class="fas fa-clock"></i>
@@ -714,8 +817,74 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
-->
<!-- ═══════════════════════════ CONTAINER + INVOICE DATE + DUE DATE (ONE ROW) ═══════════════════════════ -->
<div class="date-strip">
<div class="date-row">
{{-- Container ID --}}
<div class="date-card" style="flex: 1.2;">
<div class="date-icon-wrap" style="background:#ecfeff;color:#0e7490;">
<i class="fas fa-box"></i>
</div>
<div>
<div class="date-label">Container ID</div>
<div class="date-value">
@if($invoice->container && $invoice->container->container_number)
{{ $invoice->container->container_number }}
@elseif($invoice->container_id)
{{ $invoice->container_id }}
@else
N/A
@endif
</div>
</div>
</div>
{{-- छोटा arrow --}}
<div class="date-arrow">
<i class="fas fa-arrow-right"></i>
</div>
{{-- Invoice Date --}}
<div class="date-card">
<div class="date-icon-wrap">
<i class="fas fa-calendar-alt"></i>
</div>
<div>
<div class="date-label">Invoice Date</div>
<div class="date-value">
{{ \Carbon\Carbon::parse($invoice->invoice_date)->format('M d, Y') }}
</div>
</div>
</div>
{{-- दुसरा arrow --}}
<div class="date-arrow">
<i class="fas fa-arrow-right"></i>
</div>
{{-- Due Date --}}
<div class="date-card">
<div class="date-icon-wrap" style="background:#fff7ed;color:#f59e0b;">
<i class="fas fa-clock"></i>
</div>
<div>
<div class="date-label">Due Date</div>
<div class="date-value {{ $invoice->status == 'overdue' ? 'overdue' : '' }}">
{{ \Carbon\Carbon::parse($invoice->due_date)->format('M d, Y') }}
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════ CUSTOMER ═══════════════════════════ --> <!-- ═══════════════════════════ CUSTOMER ═══════════════════════════ -->
<div class="panel"> <div class="panel">
@@ -758,82 +927,83 @@
@endif --> @endif -->
<div class="table-responsive"> <div class="table-responsive">
<table class="invoice-table items-table"> <table class="invoice-table items-table">
<thead> <thead>
<tr> <tr>
<th class="text-center" style="width:44px;"> <th class="text-center" style="width:44px;">
<input type="checkbox" id="selectAllItems"> <input type="checkbox" id="selectAllItems">
</th> </th>
<th class="text-center" style="min-width:50px;">#</th> <th class="text-center" style="min-width:50px;">#</th>
<th style="min-width:200px;">Description</th> <th style="min-width:200px;">Description</th>
<th class="text-center" style="min-width:75px;">CTN</th> <th class="text-center" style="min-width:75px;">CTN</th>
<th class="text-center" style="min-width:75px;">QTY</th> <th class="text-center" style="min-width:75px;">QTY</th>
<th class="text-center" style="min-width:95px;">TTL/QTY</th> <th class="text-center" style="min-width:95px;">TTL/QTY</th>
<th class="text-center" style="min-width:75px;">Unit</th> <th class="text-center" style="min-width:75px;">Unit</th>
<th class="text-center" style="min-width:110px;">Price</th> <th class="text-center" style="min-width:110px;">Price</th>
<th class="text-center" style="min-width:125px;">TTL Amount</th> <th class="text-center" style="min-width:125px;">TTL Amount</th>
<th class="text-center" style="min-width:85px;">CBM</th> <th class="text-center" style="min-width:85px;">CBM</th>
<th class="text-center" style="min-width:95px;">TTL CBM</th> <th class="text-center" style="min-width:95px;">TTL CBM</th>
<th class="text-center" style="min-width:80px;">KG</th> <th class="text-center" style="min-width:80px;">KG</th>
<th class="text-center" style="min-width:95px;">TTL KG</th> <th class="text-center" style="min-width:95px;">TTL KG</th>
<th class="text-center" style="min-width:95px;">Shop No</th> <th class="text-center" style="min-width:95px;">Shop No</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach($invoice->items as $i => $item) @foreach($invoice->items as $i => $item)
@php @php
$alreadyGrouped = in_array($item->id, $groupedItemIds ?? []); $alreadyGrouped = in_array($item->id, $groupedItemIds ?? []);
@endphp @endphp
<tr class="{{ $alreadyGrouped ? 'grouped-item-row' : '' }}"> <tr class="{{ $alreadyGrouped ? 'grouped-item-row' : '' }}">
<td class="text-center"> <td class="text-center">
<input type="checkbox" <input type="checkbox"
class="item-select-checkbox" class="item-select-checkbox"
value="{{ $item->id }}" name="item_ids[]"
{{ $alreadyGrouped ? 'disabled' : '' }}> value="{{ $item->id }}"
</td> {{ $alreadyGrouped ? 'disabled' : '' }}>
<td class="text-center" style="font-weight:600;color:var(--text-muted);">{{ $i + 1 }}</td> </td>
<td class="desc-col" style="font-weight:600;color:var(--primary);">{{ $item->description }}</td> <td class="text-center" style="font-weight:600;color:var(--text-muted);">{{ $i + 1 }}</td>
<td class="text-center">{{ $item->ctn }}</td> <td class="desc-col" style="font-weight:600;color:var(--primary);">{{ $item->description }}</td>
<td class="text-center">{{ $item->qty }}</td> <td class="text-center">{{ $item->ctn }}</td>
<td class="text-center" style="font-weight:700;">{{ $item->ttl_qty }}</td> <td class="text-center">{{ $item->qty }}</td>
<td class="text-center">{{ $item->unit }}</td> <td class="text-center" style="font-weight:700;">{{ $item->ttl_qty }}</td>
<td class="text-center">{{ $item->unit }}</td>
@if($isEmbedded) @if($isEmbedded)
<td class="text-center" style="min-width:120px;"> <td class="text-center" style="min-width:120px;">
<input type="number" step="0.01" min="0" <input type="number" step="0.01" min="0"
name="items[{{ $item->id }}][price]" name="items[{{ $item->id }}][price]"
value="{{ old('items.' . $item->id . '.price', $item->price) }}" value="{{ old('items.' . $item->id . '.price', $item->price) }}"
class="form-control form-control-sm text-end"> class="form-control form-control-sm text-end">
</td> </td>
<td class="text-center" style="min-width:140px;"> <td class="text-center" style="min-width:140px;">
<input type="number" step="0.01" min="0" <input type="number" step="0.01" min="0"
name="items[{{ $item->id }}][ttl_amount]" name="items[{{ $item->id }}][ttl_amount]"
value="{{ old('items.' . $item->id . '.ttl_amount', $item->ttl_amount) }}" value="{{ old('items.' . $item->id . '.ttl_amount', $item->ttl_amount) }}"
class="form-control form-control-sm text-end"> class="form-control form-control-sm text-end">
</td> </td>
@else @else
<td class="text-center price-green">{{ number_format($item->price, 2) }}</td> <td class="text-center price-green">{{ number_format($item->price, 2) }}</td>
<td class="text-center price-blue">{{ number_format($item->ttl_amount, 2) }}</td> <td class="text-center price-blue">{{ number_format($item->ttl_amount, 2) }}</td>
@endif
<td class="text-center">{{ $item->cbm }}</td>
<td class="text-center">{{ $item->ttl_cbm }}</td>
<td class="text-center">{{ $item->kg }}</td>
<td class="text-center">{{ $item->ttl_kg }}</td>
<td class="text-center"><span class="badge-shop">{{ $item->shop_no }}</span></td>
</tr>
@endforeach
@if($invoice->items->isEmpty())
<tr>
<td colspan="15" class="text-center py-4" style="color:var(--text-muted);font-weight:600;">
<i class="fas fa-inbox me-2" style="font-size:1.3rem;opacity:.4;"></i><br>No invoice items found.
</td>
</tr>
@endif @endif
</tbody>
</table> <td class="text-center">{{ $item->cbm }}</td>
</div> <td class="text-center">{{ $item->ttl_cbm }}</td>
<td class="text-center">{{ $item->kg }}</td>
<td class="text-center">{{ $item->ttl_kg }}</td>
<td class="text-center"><span class="badge-shop">{{ $item->shop_no }}</span></td>
</tr>
@endforeach
@if($invoice->items->isEmpty())
<tr>
<td colspan="15" class="text-center py-4" style="color:var(--text-muted);font-weight:600;">
<i class="fas fa-inbox me-2" style="font-size:1.3rem;opacity:.4;"></i><br>No invoice items found.
</td>
</tr>
@endif
</tbody>
</table>
</div>
<!-- ACTION BAR --> <!-- ACTION BAR -->
<div class="action-bar"> <div class="action-bar">
@@ -1065,7 +1235,7 @@
@endif @endif
<!-- ═══════════════════════════ SUMMARY ═══════════════════════════ --> <!-- ═══════════════════════════ SUMMARY ═══════════════════════════ -->
<div class="summary-wrap"> <!-- <div class="summary-wrap">
<div class="row justify-content-end"> <div class="row justify-content-end">
<div class="col-md-5"> <div class="col-md-5">
<div class="summary-card"> <div class="summary-card">
@@ -1107,8 +1277,70 @@
</div> </div>
</div> </div>
</div> </div>
</div> -->
{{-- ===== FINAL SUMMARY (POPUP) ===== --}}
<div class="summary-card-compact mt-3">
<div class="summary-header-compact">
<i class="fas fa-calculator"></i>
<span>Final Summary</span>
</div> </div>
<div class="summary-body-compact">
@if($invoice->tax_type === 'gst')
<div class="summary-row-compact">
<span class="label">
CGST {{ $invoice->cgst_percent ?? $invoice->gst_percent / 2 }}%
</span>
<span class="value red">
{{ number_format($invoice->gst_amount / 2, 2) }}
</span>
</div>
<div class="summary-row-compact">
<span class="label">
SGST {{ $invoice->sgst_percent ?? $invoice->gst_percent / 2 }}%
</span>
<span class="value red">
{{ number_format($invoice->gst_amount / 2, 2) }}
</span>
</div>
@elseif($invoice->tax_type === 'igst')
<div class="summary-row-compact">
<span class="label">
IGST {{ $invoice->igst_percent ?? $invoice->gst_percent }}%
</span>
<span class="value red">
{{ number_format($invoice->gst_amount, 2) }}
</span>
</div>
@else
<div class="summary-row-compact">
<span class="label">
GST {{ $invoice->gst_percent }}%
</span>
<span class="value red">
{{ number_format($invoice->gst_amount, 2) }}
</span>
</div>
@endif
<div class="summary-row-compact">
<span class="label">Charge Groups Total</span>
<span class="value">
{{ number_format($invoice->charge_groups_total, 2) }}
</span>
</div>
<!-- <div class="summary-row-compact total">
<span class="label">Grand Total (Items + GST + Groups)</span>
<span class="value">
{{ number_format($invoice->grand_total_with_charges, 2) }}
</span>
</div> -->
</div>
</div>
<!-- ═══════════════════════════ FOOTER ═══════════════════════════ --> <!-- ═══════════════════════════ FOOTER ═══════════════════════════ -->
<div class="invoice-footer"> <div class="invoice-footer">
@if($invoice->pdf_path && $showActions) @if($invoice->pdf_path && $showActions)
@@ -1125,31 +1357,52 @@
</div> </div>
</div> </div>
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script> <script>
function shareInvoice() { function shareInvoice() {
const shareData = { const shareData = {
title: "Invoice {{ $invoice->invoice_number }}", title: "Invoice {{ $invoice->invoice_number }}",
text: "Sharing invoice {{ $invoice->invoice_number }}", text: "Sharing invoice {{ $invoice->invoice_number }}",
url: "{{ asset($invoice->pdf_path) }}" url: "{{ asset($invoice->pdf_path) }}"
}; };
if (navigator.share) { if (navigator.share) {
navigator.share(shareData).catch(() => {}); navigator.share(shareData).catch(() => {});
} else { } else {
navigator.clipboard.writeText(shareData.url); navigator.clipboard.writeText(shareData.url);
alert("Link copied! Sharing not supported on this browser."); alert("Link copied! Sharing not supported on this browser.");
}
} }
}
function renumberChargeGroups() {
const groupsTbody = document.querySelector(
'.cg-groups-panel table.invoice-table tbody'
);
if (!groupsTbody) return;
let index = 1;
groupsTbody.querySelectorAll('tr').forEach(row => {
if (row.classList.contains('cg-items-row')) return;
const next = row.nextElementSibling;
const isMainGroupRow = next && next.classList.contains('cg-items-row');
if (!isMainGroupRow) return;
const firstCell = row.querySelector('td:first-child');
if (firstCell) {
firstCell.textContent = index++;
}
});
}
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
const selectAll = document.getElementById('selectAllItems'); const selectAll = document.getElementById('selectAllItems');
const itemCheckboxes = document.querySelectorAll('.item-select-checkbox'); const itemCheckboxes = document.querySelectorAll('.item-select-checkbox');
const countSpan = document.getElementById('selectedItemsCount'); const countSpan = document.getElementById('selectedItemsCount');
const btnCreate = document.getElementById('btnCreateChargeGroup'); const btnCreate = document.getElementById('btnCreateChargeGroup');
const chargeGroupBox = document.getElementById('chargeGroupBox'); const chargeGroupBox = document.getElementById('chargeGroupBox');
const cgCancelBtn = document.getElementById('cgCancelBtn'); const cgCancelBtn = document.getElementById('cgCancelBtn');
const cgItemsTableBody = document.getElementById('cgItemsTableBody'); const cgItemsTableBody = document.getElementById('cgItemsTableBody');
const cgBasisSelect = document.getElementById('cgBasis'); const cgBasisSelect = document.getElementById('cgBasis');
@@ -1166,6 +1419,8 @@ document.addEventListener('DOMContentLoaded', function () {
const cgRateHidden = document.getElementById('cgRateHidden'); const cgRateHidden = document.getElementById('cgRateHidden');
const cgForm = document.getElementById('chargeGroupForm'); const cgForm = document.getElementById('chargeGroupForm');
const cgGroupName = document.getElementById('cgGroupName');
function updateSelectionState() { function updateSelectionState() {
let selectedCount = 0; let selectedCount = 0;
itemCheckboxes.forEach(cb => { itemCheckboxes.forEach(cb => {
@@ -1188,13 +1443,13 @@ document.addEventListener('DOMContentLoaded', function () {
row.querySelector(`td:nth-child(${n})`)?.textContent.trim() ?? ''; row.querySelector(`td:nth-child(${n})`)?.textContent.trim() ?? '';
items.push({ items.push({
description: cellText(3), description: cellText(3),
qty: cellText(5), qty: cellText(5),
ttlqty: cellText(6), ttlqty: cellText(6),
cbm: cellText(10), cbm: cellText(10),
ttlcbm: cellText(11), ttlcbm: cellText(11),
kg: cellText(12), kg: cellText(12),
ttlkg: cellText(13), ttlkg: cellText(13),
amount: cellText(9), amount: cellText(9),
}); });
} }
}); });
@@ -1258,8 +1513,8 @@ document.addEventListener('DOMContentLoaded', function () {
if (cgRateHidden && cgRateInput) cgRateHidden.value = cgRateInput.value || 0; if (cgRateHidden && cgRateInput) cgRateHidden.value = cgRateInput.value || 0;
if (cgAutoTotalInput) cgAutoTotalInput.value = suggested || 0; if (cgAutoTotalInput) cgAutoTotalInput.value = suggested || 0;
if (cgTotalChargeInput) { if (cgTotalChargeInput) {
cgTotalChargeInput.value = suggested ? suggested.toFixed(2) : '0'; cgTotalChargeInput.value = suggested ? suggested.toFixed(2) : '0';
} }
} }
itemCheckboxes.forEach(cb => { itemCheckboxes.forEach(cb => {
@@ -1271,7 +1526,7 @@ document.addEventListener('DOMContentLoaded', function () {
selectAll.checked = (checked > 0 && checked === total); selectAll.checked = (checked > 0 && checked === total);
selectAll.indeterminate = (checked > 0 && checked < total); selectAll.indeterminate = (checked > 0 && checked < total);
} }
if (!chargeGroupBox.classList.contains('d-none')) { if (chargeGroupBox && !chargeGroupBox.classList.contains('d-none')) {
fillChargeGroupItemsTable(); fillChargeGroupItemsTable();
refreshBasisSummaryAndSuggestion(); refreshBasisSummaryAndSuggestion();
} }
@@ -1282,7 +1537,7 @@ document.addEventListener('DOMContentLoaded', function () {
selectAll.addEventListener('change', function () { selectAll.addEventListener('change', function () {
itemCheckboxes.forEach(cb => { if (!cb.disabled) cb.checked = selectAll.checked; }); itemCheckboxes.forEach(cb => { if (!cb.disabled) cb.checked = selectAll.checked; });
updateSelectionState(); updateSelectionState();
if (!chargeGroupBox.classList.contains('d-none')) { if (chargeGroupBox && !chargeGroupBox.classList.contains('d-none')) {
fillChargeGroupItemsTable(); fillChargeGroupItemsTable();
refreshBasisSummaryAndSuggestion(); refreshBasisSummaryAndSuggestion();
} }
@@ -1312,244 +1567,65 @@ document.addEventListener('DOMContentLoaded', function () {
}); });
} }
// MAIN CHANGE: normal form submit, फक्त hidden item_ids तयार करतो
if (cgForm) { if (cgForm) {
cgForm.addEventListener('submit', function (e) { cgForm.addEventListener('submit', function () {
// Stop normal form submit (no page reload) const selectedIds = [];
e.preventDefault(); itemCheckboxes.forEach(cb => {
if (cb.checked && !cb.disabled) {
// 1) Collect selected item IDs selectedIds.push(cb.value);
const selectedIds = []; }
itemCheckboxes.forEach(cb => {
if (cb.checked && !cb.disabled) {
selectedIds.push(cb.value);
}
});
// 2) Frontend validations (same as before)
if (selectedIds.length === 0) {
alert('Please select at least one item for this charge group.');
return;
}
if (!cgBasisSelect || !cgBasisSelect.value) {
alert('Please select a basis for this charge group.');
return;
}
if (
!cgTotalChargeInput ||
!cgTotalChargeInput.value ||
parseFloat(cgTotalChargeInput.value) <= 0
) {
alert('Please enter total charges for this group.');
return;
}
// 3) Remove previously added hidden item_ids[]
const oldHidden = cgForm.querySelectorAll('input[name="item_ids[]"]');
oldHidden.forEach(el => el.remove());
// 4) Add fresh hidden item_ids[] for current selection
selectedIds.forEach(id => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'item_ids[]';
input.value = id;
cgForm.appendChild(input);
});
// 5) Build AJAX request
const url = cgForm.action;
const formData = new FormData(cgForm);
// Optional: disable save button while processing
if (cgSaveBtn) {
cgSaveBtn.disabled = true;
cgSaveBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Saving...';
}
fetch(url, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(res => {
if (!res.ok) {
throw new Error('Request failed with status ' + res.status);
}
return res.json();
})
.then(data => {
if (!data.success) {
throw new Error(data.message || 'Failed to create charge group.');
}
const group = data.group;
// 6) Append new group main row + hidden items row
const groupsTbody = document.querySelector(
'.cg-groups-panel table.invoice-table tbody'
);
if (groupsTbody && group) {
const currentMainRows = groupsTbody.querySelectorAll(
'tr:not(.cg-items-row)'
).length;
const index = currentMainRows + 1;
// Main summary row (same structure as Blade)
const mainRow = document.createElement('tr');
mainRow.innerHTML = `
<td>${index}</td>
<td style="font-weight:600;">
${group.group_name ? group.group_name : 'Group #' + group.id}
</td>
<td>
<span style="text-transform:uppercase;font-size:0.75rem;font-weight:700;color:var(--text-muted);">
${group.basis_type}
</span>
</td>
<td class="text-end" style="font-family:'JetBrains Mono',monospace;font-size:0.82rem;">
${Number(group.basis_value).toFixed(3)}
</td>
<td class="text-end" style="font-family:'JetBrains Mono',monospace;font-size:0.82rem;">
${Number(group.rate).toFixed(2)}
</td>
<td class="text-end price-blue">
${Number(group.total_charge).toFixed(2)}
</td>
<td class="text-center">
<button type="button"
class="cg-toggle-btn cg-toggle-items"
data-group-id="${group.id}">
<i class="fas fa-eye"></i> View
</button>
</td>
`;
// Hidden items row (same idea as Blade .cg-items-row)
const itemsRow = document.createElement('tr');
itemsRow.className = 'cg-items-row d-none';
itemsRow.setAttribute('data-group-id', group.id);
let itemsTableHtml = `
<div style="padding:1rem;background:#f8faff;border-radius:8px;margin:0.25rem 0;">
<div style="font-weight:700;font-size:0.8rem;margin-bottom:0.6rem;color:var(--primary);">
Items in this group
</div>
`;
if (!group.items || group.items.length === 0) {
itemsTableHtml += `
<div style="color:var(--text-muted);font-size:0.82rem;">
No items linked.
</div>
`;
} else {
itemsTableHtml += `
<div class="table-responsive">
<table class="invoice-table">
<thead>
<tr>
<th>#</th>
<th>Description</th>
<th class="text-center">QTY</th>
<th class="text-center">TTL QTY</th>
<th class="text-center">CBM</th>
<th class="text-center">TTL CBM</th>
<th class="text-center">KG</th>
<th class="text-center">TTL KG</th>
<th class="text-end">TTL Amount</th>
</tr>
</thead>
<tbody>
`;
group.items.forEach((it, idx) => {
itemsTableHtml += `
<tr>
<td>${idx + 1}</td>
<td>${it.description ?? ''}</td>
<td class="text-center">${it.qty ?? ''}</td>
<td class="text-center">${it.ttlqty ?? ''}</td>
<td class="text-center">${it.cbm ?? ''}</td>
<td class="text-center">${it.ttlcbm ?? ''}</td>
<td class="text-center">${it.kg ?? ''}</td>
<td class="text-center">${it.ttlkg ?? ''}</td>
<td class="text-end">${Number(it.amount ?? 0).toFixed(2)}</td>
</tr>
`;
}); });
itemsTableHtml += ` if (selectedIds.length === 0) {
</tbody> alert('Please select at least one item for this charge group.');
</table> // default submit रोखण्यासाठी return false
</div> return false;
`;
}
itemsTableHtml += `</div>`;
itemsRow.innerHTML = `
<td colspan="7">
${itemsTableHtml}
</td>
`;
// Append main row + items row in order
groupsTbody.appendChild(mainRow);
groupsTbody.appendChild(itemsRow);
}
// 7) Reset Charge Group UI (hide panel, uncheck items)
if (chargeGroupBox) {
chargeGroupBox.classList.add('d-none');
}
itemCheckboxes.forEach(cb => {
if (!cb.disabled) cb.checked = false;
});
if (selectAll) {
selectAll.checked = false;
selectAll.indeterminate = false;
}
updateSelectionState();
// Optional: clear form
if (cgGroupName) cgGroupName.value = '';
if (cgBasisSelect) cgBasisSelect.value = '';
if (cgRateInput) cgRateInput.value = '';
if (cgBasisValueSpan) cgBasisValueSpan.textContent = '0';
if (cgBasisLabelSpan) cgBasisLabelSpan.textContent = '';
if (cgSuggestedTotalSpan) cgSuggestedTotalSpan.textContent = '0';
if (cgTotalChargeInput) cgTotalChargeInput.value = '';
})
.catch(err => {
console.error(err);
alert('Error creating charge group. Please try again.');
})
.finally(() => {
if (cgSaveBtn) {
cgSaveBtn.disabled = false;
cgSaveBtn.innerHTML = '<i class="fas fa-save"></i> Save Charge Group';
} }
if (!cgBasisSelect || !cgBasisSelect.value) {
alert('Please select a basis for this charge group.');
return false;
}
if (!cgTotalChargeInput ||
!cgTotalChargeInput.value ||
parseFloat(cgTotalChargeInput.value) <= 0
) {
alert('Please enter total charges for this group.');
return false;
}
const oldHidden = cgForm.querySelectorAll('input[name="item_ids[]"]');
oldHidden.forEach(el => el.remove());
selectedIds.forEach(id => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'item_ids[]';
input.value = id;
cgForm.appendChild(input);
});
// इथे e.preventDefault नाही; normal submit होऊ दे
return true;
}); });
}); }
}
updateSelectionState(); updateSelectionState();
}); });
// View/Hide group items
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
document.addEventListener('click', function (e) { document.addEventListener('click', function (e) {
if (!e.target.classList.contains('cg-toggle-items') && if (!e.target.classList.contains('cg-toggle-items') &&
!e.target.closest('.cg-toggle-items')) return; !e.target.closest('.cg-toggle-items')) return;
const btn = e.target.closest('.cg-toggle-items') || e.target; const btn = e.target.closest('.cg-toggle-items') || e.target;
const groupId = btn.getAttribute('data-group-id'); const groupId = btn.getAttribute('data-group-id');
const row = document.querySelector('.cg-items-row[data-group-id="' + groupId + '"]'); const row = document.querySelector('.cg-items-row[data-group-id="' + groupId + '"]');
if (!row) return; if (!row) return;
row.classList.toggle('d-none'); row.classList.toggle('d-none');
const isHidden = row.classList.contains('d-none'); const isHidden = row.classList.contains('d-none');
btn.innerHTML = isHidden btn.innerHTML = isHidden
@@ -1557,6 +1633,25 @@ document.addEventListener('DOMContentLoaded', function () {
: '<i class="fas fa-eye-slash"></i> Hide'; : '<i class="fas fa-eye-slash"></i> Hide';
}); });
}); });
// simple select all (duplicate राहिला तरी harmless; गरज असल्यास काढू शकतो)
document.addEventListener('DOMContentLoaded', function () {
const selectAll = document.getElementById('selectAllItems');
const checkboxes = document.querySelectorAll('.item-select-checkbox');
if (selectAll) {
selectAll.addEventListener('change', function () {
checkboxes.forEach(cb => {
if (!cb.disabled) {
cb.checked = selectAll.checked;
}
});
});
}
});
</script> </script>
</body> </body>
</html> </html>

View File

@@ -577,7 +577,7 @@
<th>Container Date</th> <th>Container Date</th>
<th>Company Name</th> <th>Company Name</th>
<th>Customer Name</th> <th>Customer Name</th>
<th>Mark No</th> {{-- <th>Mark No</th> --}}
<th>Invoice No</th> <th>Invoice No</th>
<th>Invoice Date</th> <th>Invoice Date</th>
<th>Invoice Amount</th> <th>Invoice Amount</th>
@@ -609,11 +609,11 @@
{{ $r->customer_name ?? '-' }} {{ $r->customer_name ?? '-' }}
</span> </span>
</td> </td>
<td> {{-- <td>
<span class="data-highlight" title="{{ $r->mark_no }}"> <span class="data-highlight" title="{{ $r->mark_no }}">
{{ $r->mark_no ?? '-' }} {{ $r->mark_no ?? '-' }}
</span> </span>
</td> </td> --}}
<td> <td>
<span class="data-highlight" title="{{ $r->invoice_number }}"> <span class="data-highlight" title="{{ $r->invoice_number }}">
{{ $r->invoice_number }} {{ $r->invoice_number }}
@@ -640,7 +640,7 @@
</tr> </tr>
@empty @empty
<tr> <tr>
<td colspan="9"> <td colspan="8">
<div class="empty-state"> <div class="empty-state">
<div class="empty-icon"> <div class="empty-icon">
<i class="fas fa-inbox"></i> <i class="fas fa-inbox"></i>
@@ -799,7 +799,7 @@
if (filteredReports.length === 0) { if (filteredReports.length === 0) {
tbody.innerHTML = ` tbody.innerHTML = `
<tr> <tr>
<td colspan="9"> <td colspan="8">
<div class="empty-state"> <div class="empty-state">
<div class="empty-icon"> <div class="empty-icon">
<i class="fas fa-inbox"></i> <i class="fas fa-inbox"></i>
@@ -842,11 +842,11 @@
${report.customer_name || '-'} ${report.customer_name || '-'}
</span> </span>
</td> </td>
<td> <!-- <td>
<span class="data-highlight" title="${report.mark_no || ''}"> <span class="data-highlight" title="${report.mark_no || ''}">
${report.mark_no || '-'} ${report.mark_no || '-'}
</span> </span>
</td> </td> -->
<td> <td>
<span class="data-highlight" title="${report.invoice_number || ''}"> <span class="data-highlight" title="${report.invoice_number || ''}">
${report.invoice_number || '-'} ${report.invoice_number || '-'}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,36 +0,0 @@
<!-- <!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

@@ -15,7 +15,6 @@ use App\Http\Controllers\Admin\AdminChatController;
use Illuminate\Support\Facades\Broadcast; use Illuminate\Support\Facades\Broadcast;
use App\Http\Controllers\ContainerController; use App\Http\Controllers\ContainerController;
// --------------------------- // ---------------------------
// Public Front Page // Public Front Page
// --------------------------- // ---------------------------
@@ -31,9 +30,9 @@ Broadcast::routes(['middleware' => ['web']]);
Route::post('/broadcasting/auth', function (\Illuminate\Http\Request $request) { Route::post('/broadcasting/auth', function (\Illuminate\Http\Request $request) {
\Log::info('🎯 Broadcasting Auth Request', [ \Log::info('🎯 Broadcasting Auth Request', [
'channel' => $request->input('channel_name'), 'channel' => $request->input('channel_name'),
'admin_check'=> auth('admin')->check(), 'admin_check' => auth('admin')->check(),
'web_check' => auth('web')->check(), 'web_check' => auth('web')->check(),
]); ]);
if (auth('admin')->check()) { if (auth('admin')->check()) {
@@ -50,7 +49,6 @@ Route::post('/broadcasting/auth', function (\Illuminate\Http\Request $request) {
return response()->json(['message' => 'Unauthenticated'], 403); return response()->json(['message' => 'Unauthenticated'], 403);
})->middleware('web'); })->middleware('web');
// --------------------------- // ---------------------------
// ADMIN LOGIN ROUTES // ADMIN LOGIN ROUTES
// --------------------------- // ---------------------------
@@ -62,7 +60,6 @@ Route::prefix('admin')->group(function () {
Broadcast::routes(['middleware' => ['web']]); Broadcast::routes(['middleware' => ['web']]);
// ========================================== // ==========================================
// PROTECTED ADMIN ROUTES (session protected) // PROTECTED ADMIN ROUTES (session protected)
// ========================================== // ==========================================
@@ -93,37 +90,35 @@ Route::prefix('admin')
[\App\Http\Controllers\Admin\AdminOrderController::class, 'uploadExcelPreview'] [\App\Http\Controllers\Admin\AdminOrderController::class, 'uploadExcelPreview']
)->name('admin.orders.upload.excel.preview'); )->name('admin.orders.upload.excel.preview');
//---------------------------
// CONTAINER ROUTES
//---------------------------
// Index + list
//--------------------------- //---------------------------
// CONTAINER ROUTES // CONTAINER ROUTES
//--------------------------- //---------------------------
Route::get('/containers', [ContainerController::class, 'index']) Route::get('/containers', [ContainerController::class, 'index'])
->name('containers.index'); ->name('containers.index');
Route::get('/containers/create', [ContainerController::class, 'create']) Route::get('/containers/create', [ContainerController::class, 'create'])
->name('containers.create'); ->name('containers.create');
Route::post('/containers', [ContainerController::class, 'store']) Route::post('/containers', [ContainerController::class, 'store'])
->name('containers.store'); ->name('containers.store');
Route::get('/containers/{container}', [ContainerController::class, 'show']) Route::get('/containers/{container}', [ContainerController::class, 'show'])
->name('containers.show'); ->name('containers.show');
Route::post('/containers/{container}/update-rows', [ContainerController::class, 'updateRows']) Route::post('/containers/{container}/update-rows', [ContainerController::class, 'updateRows'])
->name('containers.rows.update'); ->name('containers.rows.update');
Route::post('containers/{container}/status', [ContainerController::class, 'updateStatus']) Route::post('containers/{container}/status', [ContainerController::class, 'updateStatus'])
->name('containers.update-status'); ->name('containers.update-status');
Route::delete('/containers/{container}', [ContainerController::class, 'destroy']) Route::delete('/containers/{container}', [ContainerController::class, 'destroy'])
->name('containers.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 // USER REQUESTS
// --------------------------- // ---------------------------
@@ -149,7 +144,6 @@ Route::prefix('admin')
[UserRequestController::class, 'rejectProfileUpdate'] [UserRequestController::class, 'rejectProfileUpdate']
)->name('admin.profile.reject'); )->name('admin.profile.reject');
// --------------------------- // ---------------------------
// MARK LIST // MARK LIST
// --------------------------- // ---------------------------
@@ -159,19 +153,15 @@ Route::prefix('admin')
Route::get('/mark-list/status/{id}', [AdminMarkListController::class, 'toggleStatus']) Route::get('/mark-list/status/{id}', [AdminMarkListController::class, 'toggleStatus'])
->name('admin.marklist.toggle'); ->name('admin.marklist.toggle');
// --------------------------- // ---------------------------
// ORDERS (UPDATED) // ORDERS (UPDATED)
// --------------------------- // ---------------------------
// मुख्य Orders पेज (invoice + container listing)
Route::get('/orders', [AdminOrderController::class, 'index']) Route::get('/orders', [AdminOrderController::class, 'index'])
->name('admin.orders'); ->name('admin.orders');
// जुनं list route (असल्या वापरासाठी पण index ला point)
Route::get('/orders/list', [AdminOrderController::class, 'index']) Route::get('/orders/list', [AdminOrderController::class, 'index'])
->name('admin.orders.index'); ->name('admin.orders.index');
// Order show (old single order view)
Route::get('/orders/{id}', [AdminOrderController::class, 'show']) Route::get('/orders/{id}', [AdminOrderController::class, 'show'])
->name('admin.orders.show'); ->name('admin.orders.show');
@@ -193,7 +183,6 @@ Route::prefix('admin')
Route::get('/orders/{id}/see', [AdminOrderController::class, 'see']) Route::get('/orders/{id}/see', [AdminOrderController::class, 'see'])
->name('orders.see'); ->name('orders.see');
// --------------------------- // ---------------------------
// ORDERS (FIXED ROUTES) // ORDERS (FIXED ROUTES)
// --------------------------- // ---------------------------
@@ -212,7 +201,6 @@ Route::prefix('admin')
Route::delete('/orders/{id}/delete', [AdminOrderController::class, 'destroy']) Route::delete('/orders/{id}/delete', [AdminOrderController::class, 'destroy'])
->name('admin.orders.destroy'); ->name('admin.orders.destroy');
// --------------------------- // ---------------------------
// SHIPMENTS (FIXED ROUTES) // SHIPMENTS (FIXED ROUTES)
// --------------------------- // ---------------------------
@@ -240,7 +228,6 @@ Route::prefix('admin')
Route::get('/shipment/dummy/{id}', [ShipmentController::class, 'dummy']) Route::get('/shipment/dummy/{id}', [ShipmentController::class, 'dummy'])
->name('admin.shipments.dummy'); ->name('admin.shipments.dummy');
// --------------------------- // ---------------------------
// INVOICES // INVOICES
// --------------------------- // ---------------------------
@@ -271,9 +258,10 @@ Route::prefix('admin')
Route::post('/admin/invoices/{invoice}/charge-group', [AdminInvoiceController::class, 'storeChargeGroup']) Route::post('/admin/invoices/{invoice}/charge-group', [AdminInvoiceController::class, 'storeChargeGroup'])
->name('admin.invoices.charge-group.store'); ->name('admin.invoices.charge-group.store');
Route::get('/admin/invoices/create', [InvoiceController::class, 'create']) // जर create page वापरायचा असेल तर AdminInvoiceController मध्ये create() असावा.
->name('admin.invoices.create'); // नसेल तर हा route comment / delete कर.
// Route::get('/admin/invoices/create', [AdminInvoiceController::class, 'create'])
// ->name('admin.invoices.create');
// --------------------------- // ---------------------------
// CUSTOMERS // CUSTOMERS
@@ -293,7 +281,6 @@ Route::prefix('admin')
Route::post('/customers/{id}/status', [AdminCustomerController::class, 'toggleStatus']) Route::post('/customers/{id}/status', [AdminCustomerController::class, 'toggleStatus'])
->name('admin.customers.status'); ->name('admin.customers.status');
// CHAT // CHAT
Route::get('/chat-support', [AdminChatController::class, 'index']) Route::get('/chat-support', [AdminChatController::class, 'index'])
->name('admin.chat_support'); ->name('admin.chat_support');
@@ -305,7 +292,6 @@ Route::prefix('admin')
->name('admin.chat.send'); ->name('admin.chat.send');
}); });
// ========================================== // ==========================================
// ADMIN ACCOUNT (AJAX) ROUTES // ADMIN ACCOUNT (AJAX) ROUTES
// ========================================== // ==========================================
@@ -351,7 +337,6 @@ Route::prefix('admin/account')
->name('remove.order.from.entry'); ->name('remove.order.from.entry');
}); });
// --------------------------- // ---------------------------
// REPORTS DOWNLOAD ROUTES // REPORTS DOWNLOAD ROUTES
// --------------------------- // ---------------------------
@@ -373,7 +358,7 @@ Route::middleware(['auth:admin'])
Route::resource('staff', AdminStaffController::class); Route::resource('staff', AdminStaffController::class);
}); });
// Extra admin prefix group (तसाच ठेवला) // Extra admin prefix group
Route::prefix('admin')->middleware('auth:admin')->group(function () { Route::prefix('admin')->middleware('auth:admin')->group(function () {
// ... your routes // ... your routes
}); });
@@ -382,6 +367,14 @@ Route::post('/admin/broadcasting/auth', function () {
return Broadcast::auth(request()); return Broadcast::auth(request());
})->middleware('auth:admin'); })->middleware('auth:admin');
Route::get('/admin/invoices/{invoice}/download', [InvoiceController::class, 'download']) // INVOICE DOWNLOAD (AdminInvoiceController वापरून)
Route::get('/admin/invoices/{invoice}/download', [AdminInvoiceController::class, 'download'])
->name('admin.invoices.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');