Compare commits
2 Commits
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
0257b68f16 | ||
|
|
785f2564be |
@@ -5,6 +5,10 @@ namespace App\Http\Controllers\Admin;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use App\Models\Container;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\MarkList;
|
||||||
|
|
||||||
class AdminAuthController extends Controller
|
class AdminAuthController extends Controller
|
||||||
{
|
{
|
||||||
@@ -35,12 +39,11 @@ class AdminAuthController extends Controller
|
|||||||
'password' => $request->password,
|
'password' => $request->password,
|
||||||
];
|
];
|
||||||
|
|
||||||
// attempt login
|
|
||||||
if (Auth::guard('admin')->attempt($credentials)) {
|
if (Auth::guard('admin')->attempt($credentials)) {
|
||||||
$request->session()->regenerate();
|
$request->session()->regenerate();
|
||||||
$user = Auth::guard('admin')->user();
|
$user = Auth::guard('admin')->user();
|
||||||
|
return redirect()->route('admin.dashboard')
|
||||||
return redirect()->route('admin.dashboard')->with('success', 'Welcome back, ' . $user->name . '!');
|
->with('success', 'Welcome back, ' . $user->name . '!');
|
||||||
}
|
}
|
||||||
|
|
||||||
return back()->withErrors(['login' => 'Invalid login credentials.']);
|
return back()->withErrors(['login' => 'Invalid login credentials.']);
|
||||||
@@ -51,6 +54,25 @@ class AdminAuthController extends Controller
|
|||||||
Auth::guard('admin')->logout();
|
Auth::guard('admin')->logout();
|
||||||
$request->session()->invalidate();
|
$request->session()->invalidate();
|
||||||
$request->session()->regenerateToken();
|
$request->session()->regenerateToken();
|
||||||
return redirect()->route('admin.login')->with('success', 'Logged out successfully.');
|
return redirect()->route('admin.login')
|
||||||
|
->with('success', 'Logged out successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function profile()
|
||||||
|
{
|
||||||
|
$user = Auth::guard('admin')->user();
|
||||||
|
|
||||||
|
// ── Real Stats ──
|
||||||
|
$stats = [
|
||||||
|
'total_containers' => Container::count(),
|
||||||
|
'total_invoices' => Invoice::count(),
|
||||||
|
'paid_invoices' => Invoice::where('status', 'paid')->count(),
|
||||||
|
'pending_invoices' => Invoice::where('status', 'pending')->count(),
|
||||||
|
'total_customers' => User::count(),
|
||||||
|
'total_marklist' => MarkList::count(),
|
||||||
|
'active_marklist' => MarkList::where('status', 'active')->count(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('admin.profile', compact('user', 'stats'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,9 +269,23 @@ class AdminInvoiceController extends Controller
|
|||||||
$newPaid = $paidTotal + $request->amount;
|
$newPaid = $paidTotal + $request->amount;
|
||||||
$remaining = max(0, $grandTotal - $newPaid);
|
$remaining = max(0, $grandTotal - $newPaid);
|
||||||
|
|
||||||
if ($newPaid >= $grandTotal && $grandTotal > 0) {
|
|
||||||
$invoice->update(['status' => 'paid']);
|
|
||||||
}
|
// Full payment logic (जर पूर्ण भरले तर status paid करणे, नाहीतर pending राहील)
|
||||||
|
// if ($newPaid >= $grandTotal && $grandTotal > 0) {
|
||||||
|
// $invoice->update([
|
||||||
|
// 'payment_method' => $request->payment_method,
|
||||||
|
// 'reference_no' => $request->reference_no,
|
||||||
|
// 'status' => ($newPaid >= $grandTotal && $grandTotal > 0) ? 'paid' : $invoice->status,
|
||||||
|
// ]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Partial payment status logic:
|
||||||
|
$invoice->update([
|
||||||
|
'payment_method' => $request->payment_method,
|
||||||
|
'reference_no' => $request->reference_no,
|
||||||
|
'status' => ($newPaid >= $grandTotal && $grandTotal > 0) ? 'paid' : $invoice->status,
|
||||||
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
|
|||||||
@@ -28,11 +28,23 @@ class AdminOrderController extends Controller
|
|||||||
* ---------------------------*/
|
* ---------------------------*/
|
||||||
public function dashboard()
|
public function dashboard()
|
||||||
{
|
{
|
||||||
|
// ── Order counts (from Invoice Management / Orders table) ──
|
||||||
$totalOrders = Order::count();
|
$totalOrders = Order::count();
|
||||||
$pendingOrders = Order::where('status', 'pending')->count();
|
|
||||||
$totalShipments = Shipment::count();
|
// "Pending" म्हणजे delivered नसलेले सर्व orders
|
||||||
$totalItems = OrderItem::count();
|
// Order तयार होतो तेव्हा status = 'order_placed' असतो, 'pending' नाही
|
||||||
|
$deliveredStatuses = ['delivered'];
|
||||||
|
$pendingOrders = Order::whereNotIn('status', $deliveredStatuses)->count();
|
||||||
|
|
||||||
|
// ── Invoice counts ──
|
||||||
|
$totalContainers = Container::count();
|
||||||
|
$totalInvoices = Invoice::count();
|
||||||
|
$paidInvoices = Invoice::where('status', 'paid')->count();
|
||||||
|
$pendingInvoices = Invoice::where('status', 'pending')->count();
|
||||||
|
$overdueInvoices = Invoice::where('status', 'overdue')->count();
|
||||||
$totalRevenue = Invoice::sum('final_amount_with_gst');
|
$totalRevenue = Invoice::sum('final_amount_with_gst');
|
||||||
|
|
||||||
|
// ── User / Staff counts ──
|
||||||
$activeCustomers = User::where('status', 'active')->count();
|
$activeCustomers = User::where('status', 'active')->count();
|
||||||
$inactiveCustomers = User::where('status', 'inactive')->count();
|
$inactiveCustomers = User::where('status', 'inactive')->count();
|
||||||
$totalStaff = Admin::where('type', 'staff')->count();
|
$totalStaff = Admin::where('type', 'staff')->count();
|
||||||
@@ -43,8 +55,11 @@ class AdminOrderController extends Controller
|
|||||||
return view('admin.dashboard', compact(
|
return view('admin.dashboard', compact(
|
||||||
'totalOrders',
|
'totalOrders',
|
||||||
'pendingOrders',
|
'pendingOrders',
|
||||||
'totalShipments',
|
'totalContainers',
|
||||||
'totalItems',
|
'totalInvoices',
|
||||||
|
'paidInvoices',
|
||||||
|
'pendingInvoices',
|
||||||
|
'overdueInvoices',
|
||||||
'totalRevenue',
|
'totalRevenue',
|
||||||
'activeCustomers',
|
'activeCustomers',
|
||||||
'inactiveCustomers',
|
'inactiveCustomers',
|
||||||
@@ -90,11 +105,23 @@ class AdminOrderController extends Controller
|
|||||||
->when($request->filled('status'), function ($q) use ($request) {
|
->when($request->filled('status'), function ($q) use ($request) {
|
||||||
$q->where('invoices.status', $request->status);
|
$q->where('invoices.status', $request->status);
|
||||||
})
|
})
|
||||||
->orderByDesc('invoices.invoice_date') // इथे बदल
|
->orderByDesc('invoices.invoice_date')
|
||||||
->orderByDesc('invoices.id') // same-date साठी tie-breaker
|
->orderByDesc('invoices.id')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return view('admin.orders', compact('invoices'));
|
// ── Real DB counts (filter-independent) ──
|
||||||
|
$totalInvoices = \App\Models\Invoice::count();
|
||||||
|
$paidInvoices = \App\Models\Invoice::where('status', 'paid')->count();
|
||||||
|
$pendingInvoices = \App\Models\Invoice::where('status', 'pending')->count();
|
||||||
|
$overdueInvoices = \App\Models\Invoice::where('status', 'overdue')->count();
|
||||||
|
|
||||||
|
return view('admin.orders', compact(
|
||||||
|
'invoices',
|
||||||
|
'totalInvoices',
|
||||||
|
'paidInvoices',
|
||||||
|
'pendingInvoices',
|
||||||
|
'overdueInvoices'
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------
|
/* ---------------------------
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ class ContainerController extends Controller
|
|||||||
'kg_col' => null,
|
'kg_col' => null,
|
||||||
'totalkg_col' => null,
|
'totalkg_col' => null,
|
||||||
'itemno_col' => null,
|
'itemno_col' => null,
|
||||||
|
'shopno_col' => null,
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($header as $colIndex => $headingText) {
|
foreach ($header as $colIndex => $headingText) {
|
||||||
@@ -233,6 +234,11 @@ class ContainerController extends Controller
|
|||||||
strpos($normalized, 'ITEM') !== false
|
strpos($normalized, 'ITEM') !== false
|
||||||
) {
|
) {
|
||||||
$essentialColumns['itemno_col'] = $colIndex;
|
$essentialColumns['itemno_col'] = $colIndex;
|
||||||
|
} elseif (
|
||||||
|
strpos($normalized, 'SHOPNO') !== false ||
|
||||||
|
strpos($normalized, 'SHOP') !== false
|
||||||
|
) {
|
||||||
|
$essentialColumns['shopno_col'] = $colIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,32 +524,27 @@ class ContainerController extends Controller
|
|||||||
$firstMark = $rowsForCustomer[0]['mark'];
|
$firstMark = $rowsForCustomer[0]['mark'];
|
||||||
$snap = $markToSnapshot[$firstMark] ?? null;
|
$snap = $markToSnapshot[$firstMark] ?? null;
|
||||||
|
|
||||||
// ✅ Customer User model वरून fetch करा (customer_id string आहे जसे CID-2025-000001)
|
|
||||||
$customerUser = \App\Models\User::where('customer_id', $customerId)->first();
|
$customerUser = \App\Models\User::where('customer_id', $customerId)->first();
|
||||||
|
|
||||||
$invoice = new Invoice();
|
$invoice = new Invoice();
|
||||||
$invoice->container_id = $container->id;
|
$invoice->container_id = $container->id;
|
||||||
$invoice->customer_id = $customerUser->id ?? null; // ✅ integer id store करतोय
|
$invoice->customer_id = $customerUser->id ?? null;
|
||||||
$invoice->mark_no = $firstMark;
|
$invoice->mark_no = $firstMark;
|
||||||
|
|
||||||
$invoice->invoice_number = $this->generateInvoiceNumber();
|
$invoice->invoice_number = $this->generateInvoiceNumber();
|
||||||
|
|
||||||
// invoice_date = container_date
|
|
||||||
$invoice->invoice_date = $container->container_date;
|
$invoice->invoice_date = $container->container_date;
|
||||||
|
|
||||||
// due_date = container_date + 10 days
|
|
||||||
$invoice->due_date = Carbon::parse($invoice->invoice_date)
|
$invoice->due_date = Carbon::parse($invoice->invoice_date)
|
||||||
->addDays(10)
|
->addDays(10)
|
||||||
->format('Y-m-d');
|
->format('Y-m-d');
|
||||||
|
|
||||||
// ✅ Snapshot data from MarkList (backward compatibility)
|
|
||||||
if ($snap) {
|
if ($snap) {
|
||||||
$invoice->customer_name = $snap['customer_name'] ?? null;
|
$invoice->customer_name = $snap['customer_name'] ?? null;
|
||||||
$invoice->company_name = $snap['company_name'] ?? null;
|
$invoice->company_name = $snap['company_name'] ?? null;
|
||||||
$invoice->customer_mobile = $snap['mobile_no'] ?? null;
|
$invoice->customer_mobile = $snap['mobile_no'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ User model वरून email, address, pincode घ्या
|
|
||||||
if ($customerUser) {
|
if ($customerUser) {
|
||||||
$invoice->customer_email = $customerUser->email ?? null;
|
$invoice->customer_email = $customerUser->email ?? null;
|
||||||
$invoice->customer_address = $customerUser->address ?? null;
|
$invoice->customer_address = $customerUser->address ?? null;
|
||||||
@@ -569,6 +570,7 @@ class ContainerController extends Controller
|
|||||||
foreach ($rowsForCustomer as $item) {
|
foreach ($rowsForCustomer as $item) {
|
||||||
$row = $item['row'];
|
$row = $item['row'];
|
||||||
$offset = $item['offset'];
|
$offset = $item['offset'];
|
||||||
|
$mark = $item['mark']; // ✅ mark_no from Excel
|
||||||
|
|
||||||
$description = $essentialColumns['desc_col'] !== null ? ($row[$essentialColumns['desc_col']] ?? null) : null;
|
$description = $essentialColumns['desc_col'] !== null ? ($row[$essentialColumns['desc_col']] ?? null) : null;
|
||||||
$ctn = $essentialColumns['ctn_col'] !== null ? (int) ($row[$essentialColumns['ctn_col']] ?? 0) : 0;
|
$ctn = $essentialColumns['ctn_col'] !== null ? (int) ($row[$essentialColumns['ctn_col']] ?? 0) : 0;
|
||||||
@@ -582,6 +584,8 @@ class ContainerController extends Controller
|
|||||||
$kg = $essentialColumns['kg_col'] !== null ? (float) ($row[$essentialColumns['kg_col']] ?? 0) : 0;
|
$kg = $essentialColumns['kg_col'] !== null ? (float) ($row[$essentialColumns['kg_col']] ?? 0) : 0;
|
||||||
$ttlKg = $essentialColumns['totalkg_col'] !== null ? (float) ($row[$essentialColumns['totalkg_col']] ?? $kg) : $kg;
|
$ttlKg = $essentialColumns['totalkg_col'] !== null ? (float) ($row[$essentialColumns['totalkg_col']] ?? $kg) : $kg;
|
||||||
|
|
||||||
|
$shopNo = $essentialColumns['shopno_col'] !== null ? ($row[$essentialColumns['shopno_col']] ?? null) : null;
|
||||||
|
|
||||||
$rowIndex = $headerRowIndex + 1 + $offset;
|
$rowIndex = $headerRowIndex + 1 + $offset;
|
||||||
|
|
||||||
InvoiceItem::create([
|
InvoiceItem::create([
|
||||||
@@ -599,7 +603,8 @@ class ContainerController extends Controller
|
|||||||
'ttl_cbm' => $ttlCbm,
|
'ttl_cbm' => $ttlCbm,
|
||||||
'kg' => $kg,
|
'kg' => $kg,
|
||||||
'ttl_kg' => $ttlKg,
|
'ttl_kg' => $ttlKg,
|
||||||
'shop_no' => null,
|
'shop_no' => $shopNo,
|
||||||
|
'mark_no' => $mark, // ✅ save mark_no from Excel
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$totalAmount += $ttlAmount;
|
$totalAmount += $ttlAmount;
|
||||||
@@ -621,7 +626,6 @@ class ContainerController extends Controller
|
|||||||
{
|
{
|
||||||
$container->load('rows');
|
$container->load('rows');
|
||||||
|
|
||||||
// paid / paying invoices च्या row indexes collect करा
|
|
||||||
$lockedRowIndexes = \App\Models\Invoice::whereIn('invoices.status', ['paid', 'paying'])
|
$lockedRowIndexes = \App\Models\Invoice::whereIn('invoices.status', ['paid', 'paying'])
|
||||||
->where('invoices.container_id', $container->id)
|
->where('invoices.container_id', $container->id)
|
||||||
->join('invoice_items', 'invoices.id', '=', 'invoice_items.invoice_id')
|
->join('invoice_items', 'invoices.id', '=', 'invoice_items.invoice_id')
|
||||||
@@ -647,14 +651,12 @@ class ContainerController extends Controller
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(['data' => $data]);
|
$row->update(['data' => $data]);
|
||||||
|
|
||||||
// 2) normalize keys
|
|
||||||
$normalizedMap = [];
|
$normalizedMap = [];
|
||||||
foreach ($data as $key => $value) {
|
foreach ($data as $key => $value) {
|
||||||
if ($key === null || $key === '') {
|
if ($key === null || $key === '') {
|
||||||
@@ -666,7 +668,6 @@ class ContainerController extends Controller
|
|||||||
$normalizedMap[$normKey] = $value;
|
$normalizedMap[$normKey] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper: get first numeric value from given keys
|
|
||||||
$getFirstNumeric = function (array $map, array $possibleKeys) {
|
$getFirstNumeric = function (array $map, array $possibleKeys) {
|
||||||
foreach ($possibleKeys as $search) {
|
foreach ($possibleKeys as $search) {
|
||||||
$normSearch = strtoupper($search);
|
$normSearch = strtoupper($search);
|
||||||
@@ -686,23 +687,19 @@ class ContainerController extends Controller
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3) read values – QTY vs TTLQTY separately
|
|
||||||
$ctnKeys = ['CTN', 'CTNS'];
|
$ctnKeys = ['CTN', 'CTNS'];
|
||||||
$qtyKeys = ['QTY', 'PCS', 'PIECES']; // per-carton qty
|
$qtyKeys = ['QTY', 'PCS', 'PIECES'];
|
||||||
$ttlQtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY']; // total qty
|
$ttlQtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY'];
|
||||||
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
|
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
|
||||||
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
|
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
|
||||||
$amountKeys = ['AMOUNT', 'TTLAMOUNT', 'TOTALAMOUNT'];
|
$amountKeys = ['AMOUNT', 'TTLAMOUNT', 'TOTALAMOUNT'];
|
||||||
|
|
||||||
$ctn = $getFirstNumeric($normalizedMap, $ctnKeys);
|
$ctn = $getFirstNumeric($normalizedMap, $ctnKeys);
|
||||||
|
|
||||||
// per carton qty
|
|
||||||
$qty = $getFirstNumeric($normalizedMap, $qtyKeys);
|
$qty = $getFirstNumeric($normalizedMap, $qtyKeys);
|
||||||
|
|
||||||
// total qty direct from TOTALQTY/TTLQTY/ITLQTY
|
|
||||||
$ttlQ = $getFirstNumeric($normalizedMap, $ttlQtyKeys);
|
$ttlQ = $getFirstNumeric($normalizedMap, $ttlQtyKeys);
|
||||||
|
|
||||||
// if total column is 0 then compute ctn * qty
|
|
||||||
if ($ttlQ == 0 && $ctn && $qty) {
|
if ($ttlQ == 0 && $ctn && $qty) {
|
||||||
$ttlQ = $ctn * $qty;
|
$ttlQ = $ctn * $qty;
|
||||||
}
|
}
|
||||||
@@ -725,7 +722,6 @@ class ContainerController extends Controller
|
|||||||
$amount = $price * $ttlQ;
|
$amount = $price * $ttlQ;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4) get description
|
|
||||||
$desc = null;
|
$desc = null;
|
||||||
foreach (['DESCRIPTION', 'DESC'] as $dKey) {
|
foreach (['DESCRIPTION', 'DESC'] as $dKey) {
|
||||||
$normD = str_replace([' ', '/', '-', '.'], '', strtoupper($dKey));
|
$normD = str_replace([' ', '/', '-', '.'], '', strtoupper($dKey));
|
||||||
@@ -737,9 +733,31 @@ class ContainerController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$shopNo = null;
|
||||||
|
foreach (['SHOPNO', 'SHOP'] as $sKey) {
|
||||||
|
$normS = str_replace([' ', '/', '-', '.'], '', strtoupper($sKey));
|
||||||
|
foreach ($normalizedMap as $nKey => $v) {
|
||||||
|
if (strpos($nKey, $normS) !== false) {
|
||||||
|
$shopNo = is_string($v) ? trim($v) : $v;
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Get mark_no
|
||||||
|
$markNo = null;
|
||||||
|
foreach (['MARKNO', 'MARK', 'ITEMNO', 'ITEM'] as $mKey) {
|
||||||
|
$normM = str_replace([' ', '/', '-', '.'], '', strtoupper($mKey));
|
||||||
|
foreach ($normalizedMap as $nKey => $v) {
|
||||||
|
if (strpos($nKey, $normM) !== false) {
|
||||||
|
$markNo = is_string($v) ? trim($v) : $v;
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$rowIndex = $row->row_index;
|
$rowIndex = $row->row_index;
|
||||||
|
|
||||||
// 5) find linked invoice_items
|
|
||||||
$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();
|
||||||
@@ -751,18 +769,19 @@ class ContainerController extends Controller
|
|||||||
->get();
|
->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; // per carton
|
$item->qty = $qty;
|
||||||
$item->ttl_qty = $ttlQ; // total
|
$item->ttl_qty = $ttlQ;
|
||||||
$item->price = $price;
|
$item->price = $price;
|
||||||
$item->ttl_amount = $amount;
|
$item->ttl_amount = $amount;
|
||||||
$item->cbm = $cbm;
|
$item->cbm = $cbm;
|
||||||
$item->ttl_cbm = $ttlC;
|
$item->ttl_cbm = $ttlC;
|
||||||
$item->kg = $kg;
|
$item->kg = $kg;
|
||||||
$item->ttl_kg = $ttlK;
|
$item->ttl_kg = $ttlK;
|
||||||
|
$item->shop_no = $shopNo;
|
||||||
|
$item->mark_no = $markNo; // ✅ update mark_no
|
||||||
$item->save();
|
$item->save();
|
||||||
|
|
||||||
$invoice = $item->invoice;
|
$invoice = $item->invoice;
|
||||||
@@ -877,7 +896,6 @@ class ContainerController extends Controller
|
|||||||
abort(404, 'Excel file not found on record.');
|
abort(404, 'Excel file not found on record.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stored path like "containers/abc.xlsx"
|
|
||||||
$path = $container->excel_file;
|
$path = $container->excel_file;
|
||||||
|
|
||||||
if (!Storage::exists($path)) {
|
if (!Storage::exists($path)) {
|
||||||
@@ -891,10 +909,8 @@ class ContainerController extends Controller
|
|||||||
|
|
||||||
public function popupPopup(Container $container)
|
public function popupPopup(Container $container)
|
||||||
{
|
{
|
||||||
// existing show सारखाच data वापरू
|
|
||||||
$container->load('rows');
|
$container->load('rows');
|
||||||
|
|
||||||
// summary आधीपासून index() मध्ये जसा काढतोस तसाच logic reuse
|
|
||||||
$rows = $container->rows ?? collect();
|
$rows = $container->rows ?? collect();
|
||||||
|
|
||||||
$totalCtn = 0;
|
$totalCtn = 0;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class InvoiceItem extends Model
|
|||||||
'ttl_kg',
|
'ttl_kg',
|
||||||
|
|
||||||
'shop_no',
|
'shop_no',
|
||||||
|
'mark_no',
|
||||||
];
|
];
|
||||||
|
|
||||||
/****************************
|
/****************************
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('invoice_items', function (Blueprint $table) {
|
||||||
|
$table->string('mark_no')->nullable(); // after() काहीही नको
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('invoice_items', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('mark_no');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
BIN
public/invoices/invoice-INV-2026-000222.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000222.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000225.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000225.pdf
Normal file
Binary file not shown.
@@ -40,6 +40,73 @@
|
|||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* DOWNLOAD BUTTONS - NEW STYLES */
|
||||||
|
.cm-download-pdf {
|
||||||
|
background: linear-gradient(100deg, #4c6fff 0%, #8e54e9 100%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border: none !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 12.5px !important;
|
||||||
|
padding: 8px 16px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
box-shadow: 0 4px 14px rgba(76,111,255,0.4) !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 6px !important;
|
||||||
|
}
|
||||||
|
.cm-download-pdf:hover {
|
||||||
|
transform: translateY(-1px) !important;
|
||||||
|
box-shadow: 0 6px 20px rgba(76,111,255,0.5) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
opacity: 0.95 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-download-excel {
|
||||||
|
background: linear-gradient(100deg, #10b981 0%, #059669 100%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border: none !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 12.5px !important;
|
||||||
|
padding: 8px 16px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
box-shadow: 0 4px 14px rgba(16,185,129,0.4) !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 6px !important;
|
||||||
|
}
|
||||||
|
.cm-download-excel:hover {
|
||||||
|
transform: translateY(-1px) !important;
|
||||||
|
box-shadow: 0 6px 20px rgba(16,185,129,0.5) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
opacity: 0.95 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-back-btn {
|
||||||
|
background: linear-gradient(100deg, #6b7280 0%, #4b5563 100%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border: none !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 12.5px !important;
|
||||||
|
padding: 8px 16px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
box-shadow: 0 4px 14px rgba(107,114,128,0.4) !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 6px !important;
|
||||||
|
}
|
||||||
|
.cm-back-btn:hover {
|
||||||
|
transform: translateY(-1px) !important;
|
||||||
|
box-shadow: 0 6px 20px rgba(107,114,128,0.5) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
opacity: 0.95 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.cm-main-card {
|
.cm-main-card {
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -65,7 +132,7 @@
|
|||||||
|
|
||||||
.cm-info-cards-row {
|
.cm-info-cards-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr)); /* 3 cards one row */
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
padding: 14px 18px 8px 18px;
|
padding: 14px 18px 8px 18px;
|
||||||
}
|
}
|
||||||
@@ -132,7 +199,6 @@
|
|||||||
color: #fff7ed;
|
color: #fff7ed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TOTAL BOXES */
|
|
||||||
.cm-total-cards-row {
|
.cm-total-cards-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
@@ -421,6 +487,11 @@
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cm-table-scroll-outer {
|
||||||
|
margin: 10px 14px 30px 14px; /* फक्त हे बदल करा */
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="container-fluid cm-wrapper">
|
<div class="container-fluid cm-wrapper">
|
||||||
@@ -433,27 +504,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<a href="{{ route('containers.index') }}" class="btn btn-light btn-sm">Back to list</a>
|
<a href="{{ route('containers.index') }}" class="cm-back-btn">
|
||||||
|
<i class="bi bi-arrow-left"></i>
|
||||||
|
Back to list
|
||||||
|
</a>
|
||||||
|
|
||||||
<a href="{{ route('containers.download.pdf', $container->id) }}"
|
<a href="{{ route('containers.download.pdf', $container->id) }}" class="cm-download-pdf">
|
||||||
class="btn btn-sm btn-outline-primary">
|
<i class="bi bi-file-earmark-pdf"></i>
|
||||||
Download PDF
|
Download PDF
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{{ route('containers.download.excel', $container->id) }}"
|
<a href="{{ route('containers.download.excel', $container->id) }}" class="cm-download-excel">
|
||||||
class="btn btn-sm btn-outline-success">
|
<i class="bi bi-file-earmark-excel"></i>
|
||||||
Download Excel
|
Download Excel
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- बाकीचा सगळा code same आहे - काही बदल नाही -->
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- 3 INFO CARDS IN SINGLE ROW --}}
|
|
||||||
<div class="cm-info-cards-row">
|
<div class="cm-info-cards-row">
|
||||||
<div class="cm-info-card cm-card-container">
|
<div class="cm-info-card cm-card-container">
|
||||||
<div class="cm-info-card-icon">
|
<div class="cm-info-card-icon">
|
||||||
@@ -608,7 +682,7 @@
|
|||||||
|
|
||||||
<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() }} Edit cells then click "Save Changes".
|
Total rows: {{ $container->rows->count() }} Edit cells then click "Save Changes".
|
||||||
</span>
|
</span>
|
||||||
<input type="text" id="cmRowSearch" class="cm-filter-input"
|
<input type="text" id="cmRowSearch" class="cm-filter-input"
|
||||||
placeholder="Quick search..." onkeyup="cmFilterRows()">
|
placeholder="Quick search..." onkeyup="cmFilterRows()">
|
||||||
@@ -672,15 +746,10 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
|
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
|
||||||
// row index = headerRowIndex + 1 + offset — ContainerRow मध्ये row_index save आहे
|
|
||||||
$isLockedByInvoice = in_array($row->row_index, $lockedRowIndexes ?? []);
|
$isLockedByInvoice = in_array($row->row_index, $lockedRowIndexes ?? []);
|
||||||
$isReadOnly = $isTotalColumn || $container->status !== 'pending' || $isLockedByInvoice;
|
$isReadOnly = $isTotalColumn || $container->status !== 'pending' || $isLockedByInvoice;
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
@if($loop->first && $isLockedByInvoice)
|
|
||||||
{{-- पहिल्या cell मध्ये lock icon --}}
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -702,7 +771,6 @@
|
|||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -724,6 +792,8 @@
|
|||||||
</button>
|
</button>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<!-- Toast notification missing होती, add केली -->
|
||||||
|
<div id="cmToast" class="cm-toast"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function cmFilterRows() {
|
function cmFilterRows() {
|
||||||
@@ -854,7 +924,6 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
const target = e.target;
|
const target = e.target;
|
||||||
if (!target.classList.contains('cm-cell-input')) return;
|
if (!target.classList.contains('cm-cell-input')) return;
|
||||||
|
|
||||||
// readonly / non-pending cells साठी block
|
|
||||||
if (target.classList.contains('cm-cell-readonly') || target.hasAttribute('readonly')) {
|
if (target.classList.contains('cm-cell-readonly') || target.hasAttribute('readonly')) {
|
||||||
target.blur();
|
target.blur();
|
||||||
return;
|
return;
|
||||||
@@ -869,7 +938,6 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
|
|
||||||
if (form && btn) {
|
if (form && btn) {
|
||||||
btn.addEventListener('click', function () {
|
btn.addEventListener('click', function () {
|
||||||
// जर बटण आधीच disabled असेल (non-pending status किंवा processing)
|
|
||||||
if (btn.classList.contains('cm-disabled') || btn.hasAttribute('disabled')) {
|
if (btn.classList.contains('cm-disabled') || btn.hasAttribute('disabled')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,7 @@
|
|||||||
@extends('admin.layouts.app')
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
@section('page-title', 'Dashboard')
|
@section('page-title', 'Dashboard')
|
||||||
@php
|
|
||||||
use App\Models\Order;
|
|
||||||
use App\Models\OrderItem;
|
|
||||||
use App\Models\Shipment;
|
|
||||||
use App\Models\Invoice;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Models\Admin;
|
|
||||||
|
|
||||||
$totalOrders = Order::count();
|
|
||||||
$pendingOrders = Order::where('status', 'pending')->count();
|
|
||||||
$totalShipments = Shipment::count();
|
|
||||||
$totalItems = OrderItem::count();
|
|
||||||
|
|
||||||
$totalRevenue = Invoice::sum('final_amount_with_gst');
|
|
||||||
|
|
||||||
// USERS (CUSTOMERS)
|
|
||||||
$activeCustomers = User::where('status', 'active')->count();
|
|
||||||
$inactiveCustomers = User::where('status', 'inactive')->count();
|
|
||||||
|
|
||||||
// STAFF (FROM ADMINS TABLE)
|
|
||||||
$totalStaff = Admin::where('type', 'staff')->count();
|
|
||||||
|
|
||||||
$orders = Order::latest()->get();
|
|
||||||
@endphp
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<style>
|
<style>
|
||||||
/* ===== GLOBAL STYLES (From Shipment) ===== */
|
/* ===== GLOBAL STYLES (From Shipment) ===== */
|
||||||
@@ -1235,67 +1212,92 @@ body {
|
|||||||
|
|
||||||
<!-- STATS CARDS -->
|
<!-- STATS CARDS -->
|
||||||
<div class="stats-row-wrap">
|
<div class="stats-row-wrap">
|
||||||
|
|
||||||
|
{{-- Row 1: Total Containers, Active Customers, Total Invoices, Paid Invoices --}}
|
||||||
<div class="stats-row">
|
<div class="stats-row">
|
||||||
<div class="stats-card stats-card-blue">
|
<div class="stats-card stats-card-blue">
|
||||||
<span class="stats-icon">📦</span>
|
<span class="stats-icon">🚢</span>
|
||||||
<div>
|
<div>
|
||||||
<div class="stats-label">Total Shipments</div>
|
<div class="stats-label">Total Containers</div>
|
||||||
<div class="stats-value">{{ $totalShipments }}</div>
|
<div class="stats-value">{{ $totalContainers }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-card stats-card-blue">
|
<div class="stats-card stats-card-blue">
|
||||||
<span class="stats-icon">👥</span>
|
<span class="stats-icon">👥</span>
|
||||||
<div>
|
<div>
|
||||||
<div class="stats-label">Active Customers</div>
|
<div class="stats-label">Active Customers</div>
|
||||||
<div class="stats-value">{{ $activeCustomers }}</div>
|
<div class="stats-value">{{ $activeCustomers }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-card stats-card-green">
|
|
||||||
<span class="stats-icon">💰</span>
|
|
||||||
<div>
|
|
||||||
<div class="stats-label">Total Revenue</div>
|
|
||||||
<div class="stats-value">₹{{ number_format($totalRevenue, 2) }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="stats-card stats-card-red">
|
|
||||||
<span class="stats-icon">⏳</span>
|
|
||||||
<div>
|
|
||||||
<div class="stats-label">Pending Order</div>
|
|
||||||
<div class="stats-value">{{ $pendingOrders }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="stats-row">
|
|
||||||
<div class="stats-card stats-card-blue">
|
<div class="stats-card stats-card-blue">
|
||||||
<span class="stats-icon">📦</span>
|
<span class="stats-icon">🧾</span>
|
||||||
<div>
|
<div>
|
||||||
<div class="stats-label">Total Orders</div>
|
<div class="stats-label">Total Orders</div>
|
||||||
<div class="stats-value">{{ $totalOrders }}</div>
|
<div class="stats-value">{{ $totalInvoices }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stats-card stats-card-green">
|
||||||
|
<span class="stats-icon">✅</span>
|
||||||
|
<div>
|
||||||
|
<div class="stats-label">Paid Invoices</div>
|
||||||
|
<div class="stats-value">{{ $paidInvoices }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Row 2: Pending Invoices, Total Staff, Inactive Customers, Total Revenue --}}
|
||||||
|
<div class="stats-row">
|
||||||
|
<div class="stats-card stats-card-orng">
|
||||||
|
<span class="stats-icon">🕐</span>
|
||||||
|
<div>
|
||||||
|
<div class="stats-label">Pending Orders</div>
|
||||||
|
<div class="stats-value">{{ $pendingInvoices }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-card stats-card-blue">
|
<div class="stats-card stats-card-blue">
|
||||||
<span class="stats-icon">🧑💼</span>
|
<span class="stats-icon">🧑‍💼</span>
|
||||||
<div>
|
<div>
|
||||||
<div class="stats-label">Total Staff</div>
|
<div class="stats-label">Total Staff</div>
|
||||||
<div class="stats-value">{{ $totalStaff }}</div>
|
<div class="stats-value">{{ $totalStaff }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-card stats-card-blue">
|
|
||||||
<span class="stats-icon">📦</span>
|
|
||||||
<div>
|
|
||||||
<div class="stats-label">Total Items</div>
|
|
||||||
<div class="stats-value">{{ $totalItems }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="stats-card stats-card-orng">
|
<div class="stats-card stats-card-orng">
|
||||||
<span class="stats-icon">⛔</span>
|
<span class="stats-icon">⛔</span>
|
||||||
<div>
|
<div>
|
||||||
<div class="stats-label">Inactive Customers</div>
|
<div class="stats-label">Inactive Customers</div>
|
||||||
<div class="stats-value">{{ $inactiveCustomers }}</div>
|
<div class="stats-value">{{ $inactiveCustomers }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stats-card stats-card-green">
|
||||||
|
<span class="stats-icon">💰</span>
|
||||||
|
<div>
|
||||||
|
<div class="stats-label">Total Revenue</div>
|
||||||
|
<div class="stats-value">₹{{ number_format($totalRevenue, 2) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- COMMENTED OUT --}}
|
||||||
|
{{--
|
||||||
|
<div class="stats-card stats-card-red">
|
||||||
|
<div class="stats-label">Pending Orders</div>
|
||||||
|
<div class="stats-value">{{ $pendingOrders }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stats-card stats-card-red">
|
||||||
|
<div class="stats-label">Overdue Invoices</div>
|
||||||
|
<div class="stats-value">{{ $overdueInvoices }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stats-card stats-card-blue">
|
||||||
|
<div class="stats-label">Total Orders</div>
|
||||||
|
<div class="stats-value">{{ $totalOrders }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stats-card stats-card-blue">
|
||||||
|
<div class="stats-label">Delivered Orders</div>
|
||||||
|
<div class="stats-value">{{ $totalOrders - $pendingOrders }}</div>
|
||||||
|
</div>
|
||||||
|
--}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ORDER MANAGEMENT -->
|
<!-- ORDER MANAGEMENT -->
|
||||||
<!-- <div class="order-mgmt-box">
|
<!-- <div class="order-mgmt-box">
|
||||||
|
|||||||
@@ -327,14 +327,15 @@
|
|||||||
|
|
||||||
{{-- Edit Invoice Header --}}
|
{{-- Edit Invoice Header --}}
|
||||||
<div class="glass-card">
|
<div class="glass-card">
|
||||||
<div class="card-header-compact">
|
<div class="card-header-compact d-flex justify-content-between align-items-center">
|
||||||
<h4>
|
<h4>
|
||||||
<i class="fas fa-edit me-2"></i>
|
<i class="fas fa-edit me-2"></i>
|
||||||
Edit Invoice Details
|
Edit Invoice Details
|
||||||
</h4>
|
</h4>
|
||||||
|
<small id="headerUpdateMsg" class="text-light"></small>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body-compact">
|
<div class="card-body-compact">
|
||||||
<form action="{{ route('admin.invoices.update', $invoice->id) }}" method="POST">
|
<form id="invoiceHeaderForm" action="{{ route('admin.invoices.update', $invoice->id) }}" method="POST">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
<div class="form-grid-compact">
|
<div class="form-grid-compact">
|
||||||
@@ -379,13 +380,12 @@
|
|||||||
<label class="form-label-compact">
|
<label class="form-label-compact">
|
||||||
<i class="fas fa-tasks"></i> Status
|
<i class="fas fa-tasks"></i> Status
|
||||||
</label>
|
</label>
|
||||||
<select name="status" class="form-select-compact" required>
|
<select name="status" id="statusSelect" class="form-select-compact" required>
|
||||||
<option value="pending" {{ old('status', $invoice->status) === 'pending' ? 'selected' : '' }}>Pending</option>
|
<option value="pending" {{ old('status', $invoice->status) === 'pending' ? 'selected' : '' }}>Pending</option>
|
||||||
<option value="paying" {{ old('status', $invoice->status) === 'paying' ? 'selected' : '' }}>Paying</option>
|
<option value="paying" {{ old('status', $invoice->status) === 'paying' ? 'selected' : '' }}>Paying</option>
|
||||||
<option value="paid" {{ old('status', $invoice->status) === 'paid' ? 'selected' : '' }}>Paid</option>
|
<option value="paid" {{ old('status', $invoice->status) === 'paid' ? 'selected' : '' }}>Paid</option>
|
||||||
<option value="overdue" {{ old('status', $invoice->status) === 'overdue' ? 'selected' : '' }}>Overdue</option>
|
<option value="overdue" {{ old('status', $invoice->status) === 'overdue' ? 'selected' : '' }}>Overdue</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Notes --}}
|
{{-- Notes --}}
|
||||||
@@ -401,7 +401,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-end mt-3">
|
<div class="text-end mt-3">
|
||||||
<button type="submit" class="btn-success-compact btn-compact">
|
<button type="submit" id="btnHeaderSave" class="btn-success-compact btn-compact">
|
||||||
<i class="fas fa-save me-2"></i>Update Invoice
|
<i class="fas fa-save me-2"></i>Update Invoice
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -857,6 +857,72 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ Invoice header AJAX save (no page refresh)
|
||||||
|
const headerForm = document.getElementById('invoiceHeaderForm');
|
||||||
|
const headerBtn = document.getElementById('btnHeaderSave');
|
||||||
|
const headerMsg = document.getElementById('headerUpdateMsg');
|
||||||
|
|
||||||
|
if (headerForm && headerBtn) {
|
||||||
|
headerForm.addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
headerMsg.textContent = '';
|
||||||
|
headerBtn.disabled = true;
|
||||||
|
headerBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Saving...';
|
||||||
|
|
||||||
|
const formData = new FormData(headerForm);
|
||||||
|
|
||||||
|
|
||||||
|
fetch(headerForm.action, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(async res => {
|
||||||
|
let data = null;
|
||||||
|
try { data = await res.json(); } catch(e) {}
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
if (data && data.errors) {
|
||||||
|
const firstError = Object.values(data.errors)[0][0] ?? 'Validation error.';
|
||||||
|
throw new Error(firstError);
|
||||||
|
}
|
||||||
|
throw new Error(data && data.message ? data.message : 'Failed to update invoice.');
|
||||||
|
}
|
||||||
|
|
||||||
|
headerMsg.textContent = 'Invoice header updated.';
|
||||||
|
headerMsg.classList.remove('text-danger');
|
||||||
|
headerMsg.classList.add('text-light');
|
||||||
|
|
||||||
|
// popup_invoice वरचा status badge update करायचा असल्यास:
|
||||||
|
const status = document.getElementById('statusSelect')?.value;
|
||||||
|
const badge = document.querySelector('.status-badge');
|
||||||
|
if (badge && status) {
|
||||||
|
badge.classList.remove('status-paid','status-pending','status-overdue','status-default');
|
||||||
|
if (status === 'paid') badge.classList.add('status-paid');
|
||||||
|
else if (status === 'pending') badge.classList.add('status-pending');
|
||||||
|
else if (status === 'overdue') badge.classList.add('status-overdue');
|
||||||
|
else badge.classList.add('status-default');
|
||||||
|
|
||||||
|
badge.innerHTML = status.charAt(0).toUpperCase() + status.slice(1);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
headerMsg.textContent = err.message || 'Error updating invoice.';
|
||||||
|
headerMsg.classList.remove('text-light');
|
||||||
|
headerMsg.classList.add('text-warning');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
headerBtn.disabled = false;
|
||||||
|
headerBtn.innerHTML = '<i class="fas fa-save me-2"></i>Update Invoice';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -546,12 +546,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stats-container">
|
<div class="stats-container">
|
||||||
@php
|
|
||||||
$totalInvoices = $invoices->count();
|
|
||||||
$paidInvoices = $invoices->where('invoice_status', 'paid')->count();
|
|
||||||
$pendingInvoices = $invoices->where('invoice_status', 'pending')->count();
|
|
||||||
$overdueInvoices = $invoices->where('invoice_status', 'overdue')->count();
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<div class="stat-card total">
|
<div class="stat-card total">
|
||||||
<div class="stat-icon-wrap">
|
<div class="stat-icon-wrap">
|
||||||
|
|||||||
@@ -813,6 +813,7 @@
|
|||||||
</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:120px;">Mark No</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>
|
||||||
@@ -845,6 +846,7 @@
|
|||||||
<td class="desc-col" style="font-weight:600;color:var(--primary);">
|
<td class="desc-col" style="font-weight:600;color:var(--primary);">
|
||||||
{{ $item->description }}
|
{{ $item->description }}
|
||||||
</td>
|
</td>
|
||||||
|
<td class="text-center">{{ $item->mark_no }}</td>
|
||||||
<td class="text-center">{{ $item->ctn }}</td>
|
<td class="text-center">{{ $item->ctn }}</td>
|
||||||
<td class="text-center">{{ $item->qty }}</td>
|
<td class="text-center">{{ $item->qty }}</td>
|
||||||
<td class="text-center" style="font-weight:700;">{{ $item->ttl_qty }}</td>
|
<td class="text-center" style="font-weight:700;">{{ $item->ttl_qty }}</td>
|
||||||
@@ -884,7 +886,7 @@
|
|||||||
|
|
||||||
@if($invoice->items->isEmpty())
|
@if($invoice->items->isEmpty())
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="15" class="text-center py-4"
|
<td colspan="16" class="text-center py-4"
|
||||||
style="color:var(--text-muted);font-weight:600;">
|
style="color:var(--text-muted);font-weight:600;">
|
||||||
<i class="fas fa-inbox me-2"
|
<i class="fas fa-inbox me-2"
|
||||||
style="font-size:1.3rem;opacity:.4;"></i><br>
|
style="font-size:1.3rem;opacity:.4;"></i><br>
|
||||||
@@ -1253,6 +1255,7 @@
|
|||||||
<td class="text-center">{{ $it->ttl_cbm }}</td>
|
<td class="text-center">{{ $it->ttl_cbm }}</td>
|
||||||
<td class="text-center">{{ $it->kg }}</td>
|
<td class="text-center">{{ $it->kg }}</td>
|
||||||
<td class="text-center">{{ $it->ttl_kg }}</td>
|
<td class="text-center">{{ $it->ttl_kg }}</td>
|
||||||
|
<td>{{ $item->mark_no }}</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
{{ number_format($it->ttl_amount, 2) }}
|
{{ number_format($it->ttl_amount, 2) }}
|
||||||
</td>
|
</td>
|
||||||
@@ -1452,13 +1455,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(6),
|
||||||
ttlqty: cellText(6),
|
ttlqty: cellText(7),
|
||||||
cbm: cellText(10),
|
cbm: cellText(11),
|
||||||
ttlcbm: cellText(11),
|
ttlcbm: cellText(12),
|
||||||
kg: cellText(12),
|
kg: cellText(13),
|
||||||
ttlkg: cellText(13),
|
ttlkg: cellText(14),
|
||||||
amount: cellText(9),
|
amount: cellText(10),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,242 +5,421 @@
|
|||||||
@section('content')
|
@section('content')
|
||||||
<div class="container-fluid px-0 bg-transparent">
|
<div class="container-fluid px-0 bg-transparent">
|
||||||
<style>
|
<style>
|
||||||
body {
|
/* ── HEADER ── */
|
||||||
background: linear-gradient(135deg, #f2f6ff, #fefefe);
|
|
||||||
font-family: "Poppins", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* PROFILE HEADER */
|
|
||||||
.profile-header {
|
.profile-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 1rem;
|
gap: 1.5rem;
|
||||||
padding: 36px 32px;
|
padding: 32px 36px;
|
||||||
background: linear-gradient(120deg, #4e73df 68%, #1cc88a 100%);
|
background: linear-gradient(120deg, #4e73df 60%, #1cc88a 100%);
|
||||||
border-radius: 20px;
|
border-radius: 22px;
|
||||||
color: white;
|
color: white;
|
||||||
box-shadow: 0 8px 28px rgba(24,40,90,0.09);
|
box-shadow: 0 10px 36px rgba(78,115,223,0.22);
|
||||||
margin-bottom: 34px;
|
margin-bottom: 28px;
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 140px;
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.profile-header::before {
|
||||||
|
content:"";position:absolute;top:-60px;right:-60px;
|
||||||
|
width:220px;height:220px;
|
||||||
|
background:rgba(255,255,255,0.08);border-radius:50%;
|
||||||
}
|
}
|
||||||
.profile-header::after {
|
.profile-header::after {
|
||||||
content: "";
|
content:"";position:absolute;bottom:-80px;right:180px;
|
||||||
position: absolute;
|
width:180px;height:180px;
|
||||||
top: -58px;
|
background:rgba(255,255,255,0.05);border-radius:50%;
|
||||||
right: -85px;
|
|
||||||
width: 210px;
|
|
||||||
height: 210px;
|
|
||||||
background: rgba(255,255,255,0.14);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
.avatar {
|
|
||||||
width: 98px;
|
|
||||||
height: 98px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
border: 4px solid rgba(255,255,255,0.7);
|
|
||||||
box-shadow: 0 5px 20px rgba(0,0,0,0.15);
|
|
||||||
background: #fff;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
.main-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
z-index: 1;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.main-row {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 25px;
|
|
||||||
}
|
|
||||||
.field-group {
|
|
||||||
min-width: 210px;
|
|
||||||
}
|
|
||||||
.label {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #dbe7f4;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.value {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #fff;
|
|
||||||
background: rgba(255,255,255,0.13);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
border: 1.5px solid rgba(255,255,255,0.18);
|
|
||||||
transition: background 0.13s;
|
|
||||||
}
|
|
||||||
.value:hover { background: rgba(255,255,255,0.22); }
|
|
||||||
|
|
||||||
/* STATS + CARDS COMMON */
|
.avatar-initials {
|
||||||
.profile-stats-row, .profile-grid-row {
|
width:100px;height:100px;border-radius:50%;
|
||||||
display: flex;
|
border:4px solid rgba(255,255,255,0.75);
|
||||||
flex-wrap: wrap;
|
box-shadow:0 6px 24px rgba(0,0,0,0.18);
|
||||||
gap: 18px;
|
background:#fff;z-index:1;flex-shrink:0;
|
||||||
margin-bottom: 20px;
|
display:flex;align-items:center;justify-content:center;
|
||||||
|
font-size:2.6rem;font-weight:800;color:#4e73df;
|
||||||
|
letter-spacing:-1px;
|
||||||
}
|
}
|
||||||
.stat-card, .profile-card {
|
|
||||||
background: #fff;
|
.main-info { display:flex;flex-direction:column;gap:14px;z-index:1;flex:1; }
|
||||||
border-radius: 14px;
|
.main-row { display:flex;flex-wrap:wrap;gap:18px;align-items:flex-start; }
|
||||||
box-shadow: 0 3px 18px rgba(60,80,120,0.08);
|
.field-group { min-width:160px; }
|
||||||
padding: 20px;
|
|
||||||
|
.ph-label {
|
||||||
|
font-size:0.72rem;color:rgba(255,255,255,0.65);
|
||||||
|
font-weight:600;text-transform:uppercase;letter-spacing:0.07em;margin-bottom:3px;
|
||||||
|
}
|
||||||
|
.ph-value {
|
||||||
|
font-size:0.95rem;font-weight:600;color:#fff;
|
||||||
|
background:rgba(255,255,255,0.12);border-radius:9px;
|
||||||
|
padding:6px 13px;border:1.5px solid rgba(255,255,255,0.2);
|
||||||
|
backdrop-filter:blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-badge {
|
||||||
|
display:inline-block;padding:4px 14px;border-radius:50px;
|
||||||
|
font-size:0.72rem;font-weight:700;letter-spacing:0.07em;text-transform:uppercase;
|
||||||
|
}
|
||||||
|
.type-admin { background:rgba(255,210,60,0.22);color:#ffe066;border:1px solid rgba(255,220,80,0.4); }
|
||||||
|
.type-staff { background:rgba(100,255,180,0.18);color:#a0ffda;border:1px solid rgba(100,255,180,0.35); }
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
display:inline-block;width:9px;height:9px;
|
||||||
|
border-radius:50%;margin-right:5px;
|
||||||
|
}
|
||||||
|
.status-active { background:#22c55e;box-shadow:0 0 5px #22c55e; }
|
||||||
|
.status-inactive { background:#ef4444;box-shadow:0 0 5px #ef4444; }
|
||||||
|
|
||||||
|
/* ── STATS ── */
|
||||||
|
.profile-stats-row { display:flex;flex-wrap:wrap;gap:16px;margin-bottom:24px; }
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background:#fff;border-radius:16px;
|
||||||
|
box-shadow:0 3px 18px rgba(60,80,120,0.07);
|
||||||
|
padding:20px 22px;
|
||||||
transition:all 0.3s ease;
|
transition:all 0.3s ease;
|
||||||
position:relative;
|
position:relative;
|
||||||
flex: 1 1 200px;
|
flex:1 1 160px;
|
||||||
|
overflow:hidden;
|
||||||
}
|
}
|
||||||
.stat-card:hover, .profile-card:hover {
|
.stat-card::after {
|
||||||
transform: translateY(-4px);
|
content:"";position:absolute;
|
||||||
box-shadow: 0 6px 25px rgba(50,70,120,0.18);
|
top:-25px;right:-25px;
|
||||||
|
width:80px;height:80px;
|
||||||
|
border-radius:50%;
|
||||||
|
opacity:0.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* STATS */
|
/* Per-card accent colors */
|
||||||
.stat-card::before {
|
.stat-card.sc-blue { border-top:3px solid #4e73df; }
|
||||||
content: "";
|
.stat-card.sc-blue::after { background:linear-gradient(135deg,#c4d7ff,#e9f0ff); }
|
||||||
position: absolute;
|
|
||||||
top: -20px;
|
.stat-card.sc-green { border-top:3px solid #1cc88a; }
|
||||||
right: -20px;
|
.stat-card.sc-green::after { background:linear-gradient(135deg,#bbf7d0,#dcfce7); }
|
||||||
width: 90px;
|
|
||||||
height: 90px;
|
.stat-card.sc-yellow { border-top:3px solid #f6c23e; }
|
||||||
background: linear-gradient(135deg, #c4d7ff, #e9f0ff);
|
.stat-card.sc-yellow::after { background:linear-gradient(135deg,#fef9c3,#fef08a); }
|
||||||
border-radius: 50%;
|
|
||||||
opacity: 0.25;
|
.stat-card.sc-red { border-top:3px solid #e74a3b; }
|
||||||
|
.stat-card.sc-red::after { background:linear-gradient(135deg,#fee2e2,#fecaca); }
|
||||||
|
|
||||||
|
.stat-card.sc-purple { border-top:3px solid #8b5cf6; }
|
||||||
|
.stat-card.sc-purple::after { background:linear-gradient(135deg,#ede9fe,#ddd6fe); }
|
||||||
|
|
||||||
|
.stat-card.sc-teal { border-top:3px solid #0ea5e9; }
|
||||||
|
.stat-card.sc-teal::after { background:linear-gradient(135deg,#e0f2fe,#bae6fd); }
|
||||||
|
|
||||||
|
.stat-card:hover { transform:translateY(-5px);box-shadow:0 10px 30px rgba(78,115,223,0.15); }
|
||||||
|
|
||||||
|
.stat-icon-wrap {
|
||||||
|
width:42px;height:42px;border-radius:11px;
|
||||||
|
display:flex;align-items:center;justify-content:center;
|
||||||
|
font-size:1.25rem;margin-bottom:10px;
|
||||||
}
|
}
|
||||||
|
.sc-blue .stat-icon-wrap { background:#eff6ff; }
|
||||||
|
.sc-green .stat-icon-wrap { background:#f0fdf4; }
|
||||||
|
.sc-yellow .stat-icon-wrap { background:#fefce8; }
|
||||||
|
.sc-red .stat-icon-wrap { background:#fef2f2; }
|
||||||
|
.sc-purple .stat-icon-wrap { background:#faf5ff; }
|
||||||
|
.sc-teal .stat-icon-wrap { background:#f0f9ff; }
|
||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
font-size: 0.9rem;
|
font-size:0.78rem;color:#8b96ad;font-weight:600;
|
||||||
color: #8b96ad;
|
text-transform:uppercase;letter-spacing:0.06em;
|
||||||
font-weight: 500;
|
margin-bottom:4px;display:block;
|
||||||
}
|
}
|
||||||
.stat-value {
|
.stat-value {
|
||||||
font-size: 1.6rem;
|
font-size:1.7rem;font-weight:800;color:#1b2330;
|
||||||
font-weight: 700;
|
line-height:1;margin-bottom:6px;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
color: #1b2330;
|
|
||||||
}
|
}
|
||||||
.stat-footer { font-size: 0.9rem; }
|
.stat-footer { font-size:0.78rem;color:#94a3b8; }
|
||||||
.positive { color: #23a25d; }
|
.positive { color:#16a34a; }
|
||||||
.negative { color: #e54141; }
|
.negative { color:#dc2626; }
|
||||||
.neutral { color: #9a9ea8; }
|
|
||||||
|
|
||||||
/* PROFILE CARDS */
|
/* ── PROFESSIONAL INFO ── */
|
||||||
|
.pro-section { margin-bottom:26px; }
|
||||||
|
.pro-section-title {
|
||||||
|
font-size:0.88rem;font-weight:700;color:#1b2330;
|
||||||
|
border-left:4px solid #4e73df;padding-left:9px;margin-bottom:14px;
|
||||||
|
display:flex;align-items:center;gap:7px;
|
||||||
|
}
|
||||||
|
.pro-grid { display:flex;flex-wrap:wrap;gap:14px; }
|
||||||
|
.pro-box {
|
||||||
|
background:#fff;border-radius:14px;
|
||||||
|
padding:16px 20px;
|
||||||
|
box-shadow:0 2px 14px rgba(60,80,120,0.07);
|
||||||
|
border:1px solid #e8edf5;
|
||||||
|
transition:all 0.25s cubic-bezier(.4,0,.2,1);
|
||||||
|
flex:1 1 160px;min-width:0;
|
||||||
|
position:relative;overflow:hidden;
|
||||||
|
}
|
||||||
|
.pro-box::before {
|
||||||
|
content:"";
|
||||||
|
position:absolute;top:0;left:0;right:0;height:3px;
|
||||||
|
background:linear-gradient(90deg,#4e73df,#1cc88a);
|
||||||
|
opacity:0;transition:opacity 0.25s;
|
||||||
|
}
|
||||||
|
.pro-box:hover { transform:translateY(-4px);box-shadow:0 8px 28px rgba(78,115,223,0.15);border-color:#c7d4f8; }
|
||||||
|
.pro-box:hover::before { opacity:1; }
|
||||||
|
|
||||||
|
.box-icon { font-size:1.3rem;margin-bottom:7px;display:inline-block; }
|
||||||
|
.box-label { font-size:0.68rem;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:4px; }
|
||||||
|
.box-value { font-size:0.95rem;font-weight:700;color:#1b2330;white-space:nowrap;overflow:hidden;text-overflow:ellipsis; }
|
||||||
|
|
||||||
|
/* ── BOTTOM CARDS ── */
|
||||||
|
.profile-grid-row { display:flex;flex-wrap:wrap;gap:18px;margin-bottom:10px; }
|
||||||
|
.profile-card {
|
||||||
|
background:#fff;border-radius:16px;
|
||||||
|
box-shadow:0 3px 18px rgba(60,80,120,0.07);
|
||||||
|
padding:22px;transition:all 0.3s ease;flex:1 1 220px;
|
||||||
|
}
|
||||||
|
.profile-card:hover { transform:translateY(-4px);box-shadow:0 8px 28px rgba(78,115,223,0.14); }
|
||||||
.profile-card h6 {
|
.profile-card h6 {
|
||||||
font-size: 1.1rem;
|
font-size:1rem;font-weight:700;margin-bottom:14px;color:#1b2330;
|
||||||
font-weight: 600;
|
border-left:4px solid #4e73df;padding-left:9px;
|
||||||
margin-bottom: 12px;
|
|
||||||
color: #1b2330;
|
|
||||||
border-left: 4px solid #3767f4;
|
|
||||||
padding-left: 8px;
|
|
||||||
}
|
|
||||||
.activity-list, .quick-action-list {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
|
.activity-list,.quick-action-list { list-style:none;padding:0;margin:0; }
|
||||||
.activity-list li,.quick-action-list li {
|
.activity-list li,.quick-action-list li {
|
||||||
padding: 9px 2px;
|
padding:9px 6px;font-size:0.92rem;border-bottom:1px solid #f2f4f8;
|
||||||
font-size: 1rem;
|
display:flex;align-items:center;gap:8px;transition:0.2s ease;border-radius:6px;
|
||||||
border-bottom: 1px solid #f2f4f8;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
transition: 0.2s ease;
|
|
||||||
}
|
}
|
||||||
.activity-list li:hover { background: #f7faff; border-radius: 6px; }
|
.activity-list li:hover { background:#f4f7ff;padding-left:10px; }
|
||||||
.quick-action-list li a {
|
.quick-action-list li a {
|
||||||
text-decoration: none;
|
text-decoration:none;color:#2563eb;font-weight:600;
|
||||||
color: #2563eb;
|
display:flex;align-items:center;gap:8px;transition:0.2s ease;width:100%;
|
||||||
font-weight: 600;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
transition: 0.2s ease;
|
|
||||||
}
|
}
|
||||||
.quick-action-list li a:hover { color: #1e40af; transform: translateX(4px); }
|
.quick-action-list li a:hover { color:#1e40af;transform:translateX(5px); }
|
||||||
|
|
||||||
@media (max-width: 1000px) {
|
@media (max-width:900px) {
|
||||||
.profile-header, .profile-stats-row, .profile-grid-row {
|
.profile-header,.profile-stats-row,.profile-grid-row { flex-direction:column; }
|
||||||
flex-direction: column;
|
.avatar-initials { margin:0 auto; }
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.avatar { margin: 0 auto 15px; }
|
|
||||||
.main-row { justify-content:center; }
|
.main-row { justify-content:center; }
|
||||||
|
.pro-grid { flex-direction:column; }
|
||||||
|
.pro-box { flex:unset;width:100%; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- PROFILE HEADER -->
|
@php
|
||||||
|
$user = $user ?? Auth::guard('admin')->user();
|
||||||
|
$isAdmin = $user->type === 'admin';
|
||||||
|
$initials = strtoupper(substr($user->name ?? 'U', 0, 1));
|
||||||
|
$joiningFormatted = $user->joining_date
|
||||||
|
? \Carbon\Carbon::parse($user->joining_date)->format('d M Y')
|
||||||
|
: '—';
|
||||||
|
$isActive = ($user->status ?? 'active') === 'active';
|
||||||
|
|
||||||
|
// stats — controller मधून येतात, fallback 0
|
||||||
|
$s = $stats ?? [];
|
||||||
|
$totalContainers = $s['total_containers'] ?? 0;
|
||||||
|
$totalInvoices = $s['total_invoices'] ?? 0;
|
||||||
|
$paidInvoices = $s['paid_invoices'] ?? 0;
|
||||||
|
$pendingInvoices = $s['pending_invoices'] ?? 0;
|
||||||
|
$totalCustomers = $s['total_customers'] ?? 0;
|
||||||
|
$totalMarklist = $s['total_marklist'] ?? 0;
|
||||||
|
$activeMarklist = $s['active_marklist'] ?? 0;
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
{{-- ════════ PROFILE HEADER ════════ --}}
|
||||||
<div class="profile-header">
|
<div class="profile-header">
|
||||||
<img src="https://i.pravatar.cc/100" class="avatar" alt="Avatar" />
|
<div class="avatar-initials">{{ $initials }}</div>
|
||||||
|
|
||||||
<div class="main-info">
|
<div class="main-info">
|
||||||
|
{{-- Row 1 --}}
|
||||||
<div class="main-row">
|
<div class="main-row">
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
<div class="label">Name</div>
|
<div class="ph-label">Full Name</div>
|
||||||
<div class="value">John Doe</div>
|
<div class="ph-value" style="font-size:1.05rem;">{{ $user->name ?? '—' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
<div class="label">Email</div>
|
<div class="ph-label">Account Type</div>
|
||||||
<div class="value">john@example.com</div>
|
<div class="ph-value" style="padding:4px 10px;">
|
||||||
|
<span class="type-badge {{ $isAdmin ? 'type-admin' : 'type-staff' }}">
|
||||||
|
{{ $isAdmin ? '⭐ Admin' : '👤 Staff' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
<div class="label">Mobile No</div>
|
<div class="ph-label">Status</div>
|
||||||
<div class="value">+91 12345 54321</div>
|
<div class="ph-value">
|
||||||
|
<span class="status-dot {{ $isActive ? 'status-active' : 'status-inactive' }}"></span>
|
||||||
|
{{ ucfirst($user->status ?? 'active') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if($user->employee_id)
|
||||||
|
<div class="field-group">
|
||||||
|
<div class="ph-label">Employee ID</div>
|
||||||
|
<div class="ph-value" style="font-family:monospace;letter-spacing:0.05em;">{{ $user->employee_id }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Row 2 --}}
|
||||||
<div class="main-row">
|
<div class="main-row">
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
<div class="label">Company Name</div>
|
<div class="ph-label">Email</div>
|
||||||
<div class="value">XYZ Infotech</div>
|
<div class="ph-value" style="font-size:0.88rem;">{{ $user->email ?? '—' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-group" style="min-width:260px;">
|
<div class="field-group">
|
||||||
<div class="label">Company Address</div>
|
<div class="ph-label">Phone</div>
|
||||||
<div class="value">XYZ Infotech, East Andheri, Mumbai 400 001</div>
|
<div class="ph-value">{{ $user->phone ?? '—' }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if($user->department)
|
||||||
|
<div class="field-group">
|
||||||
|
<div class="ph-label">Department</div>
|
||||||
|
<div class="ph-value">{{ $user->department }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if($user->designation)
|
||||||
|
<div class="field-group">
|
||||||
|
<div class="ph-label">Designation</div>
|
||||||
|
<div class="ph-value">{{ $user->designation }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if($user->address)
|
||||||
|
<div class="field-group" style="min-width:220px;">
|
||||||
|
<div class="ph-label">Address</div>
|
||||||
|
<div class="ph-value" style="font-size:0.88rem;">{{ $user->address }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- STATS ROW -->
|
{{-- ════════ PROFESSIONAL INFO ════════ --}}
|
||||||
|
<div class="pro-section">
|
||||||
|
<div class="pro-section-title">🏢 Professional Information</div>
|
||||||
|
<div class="pro-grid">
|
||||||
|
|
||||||
|
@if($user->employee_id)
|
||||||
|
<div class="pro-box">
|
||||||
|
<div class="box-icon">🪪</div>
|
||||||
|
<div class="box-label">Employee ID</div>
|
||||||
|
<div class="box-value" style="font-family:monospace;">{{ $user->employee_id }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($user->username)
|
||||||
|
<div class="pro-box">
|
||||||
|
<div class="box-icon">👤</div>
|
||||||
|
<div class="box-label">Username</div>
|
||||||
|
<div class="box-value">{{ $user->username }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($user->role)
|
||||||
|
<div class="pro-box">
|
||||||
|
<div class="box-icon">🎯</div>
|
||||||
|
<div class="box-label">Role</div>
|
||||||
|
<div class="box-value">{{ $user->role }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($user->department)
|
||||||
|
<div class="pro-box">
|
||||||
|
<div class="box-icon">🏬</div>
|
||||||
|
<div class="box-label">Department</div>
|
||||||
|
<div class="box-value">{{ $user->department }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($user->designation)
|
||||||
|
<div class="pro-box">
|
||||||
|
<div class="box-icon">📋</div>
|
||||||
|
<div class="box-label">Designation</div>
|
||||||
|
<div class="box-value">{{ $user->designation }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="pro-box">
|
||||||
|
<div class="box-icon">📅</div>
|
||||||
|
<div class="box-label">Joining Date</div>
|
||||||
|
<div class="box-value">{{ $joiningFormatted }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($user->emergency_phone)
|
||||||
|
<div class="pro-box">
|
||||||
|
<div class="box-icon">🆘</div>
|
||||||
|
<div class="box-label">Emergency Phone</div>
|
||||||
|
<div class="box-value">{{ $user->emergency_phone }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($user->additional_info)
|
||||||
|
<div class="pro-box" style="flex:2 1 320px;">
|
||||||
|
<div class="box-icon">📝</div>
|
||||||
|
<div class="box-label">Additional Info</div>
|
||||||
|
<div class="box-value" style="font-size:0.88rem;font-weight:500;color:#475569;white-space:normal;">{{ $user->additional_info }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ════════ REAL STATS ════════ --}}
|
||||||
<div class="profile-stats-row">
|
<div class="profile-stats-row">
|
||||||
<div class="stat-card">
|
|
||||||
<span class="stat-label">Total Shipment</span>
|
<div class="stat-card sc-blue">
|
||||||
<span class="stat-value">📦 1,250</span>
|
<div class="stat-icon-wrap">📦</div>
|
||||||
<div class="stat-footer positive">+12% vs last month</div>
|
<span class="stat-label">Total Containers</span>
|
||||||
|
<div class="stat-value">{{ number_format($totalContainers) }}</div>
|
||||||
|
<div class="stat-footer">All containers in system</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
|
||||||
<span class="stat-label">In Transit</span>
|
<div class="stat-card sc-green">
|
||||||
<span class="stat-value" style="color:#2563eb;">🚚 320</span>
|
<div class="stat-icon-wrap">🧾</div>
|
||||||
<div class="stat-footer neutral">65% of capacity</div>
|
<span class="stat-label">Total Invoices</span>
|
||||||
</div>
|
<div class="stat-value">{{ number_format($totalInvoices) }}</div>
|
||||||
<div class="stat-card">
|
<div class="stat-footer positive">
|
||||||
<span class="stat-label">At Customs</span>
|
{{ $paidInvoices }} paid
|
||||||
<span class="stat-value" style="color:#f59e0b;">🛄 85</span>
|
|
||||||
<div class="stat-footer neutral">Avg 2.3 days processing</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<span class="stat-label">Delayed</span>
|
|
||||||
<span class="stat-value" style="color:#e11d48;">⏰ 55</span>
|
|
||||||
<div class="stat-footer negative">-8% vs last month</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<span class="stat-label">Delivered</span>
|
|
||||||
<span class="stat-value" style="color:#22c55e;">✅ 780</span>
|
|
||||||
<div class="stat-footer positive">98.2% success rate</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- BOTTOM CARDS -->
|
<div class="stat-card sc-yellow">
|
||||||
|
<div class="stat-icon-wrap">⏳</div>
|
||||||
|
<span class="stat-label">Pending Invoices</span>
|
||||||
|
<div class="stat-value" style="color:#d97706;">{{ number_format($pendingInvoices) }}</div>
|
||||||
|
<div class="stat-footer">Awaiting payment</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-card sc-teal">
|
||||||
|
<div class="stat-icon-wrap">✅</div>
|
||||||
|
<span class="stat-label">Paid Invoices</span>
|
||||||
|
<div class="stat-value" style="color:#0ea5e9;">{{ number_format($paidInvoices) }}</div>
|
||||||
|
<div class="stat-footer positive">
|
||||||
|
@if($totalInvoices > 0)
|
||||||
|
{{ round(($paidInvoices / $totalInvoices) * 100, 1) }}% success rate
|
||||||
|
@else
|
||||||
|
No invoices yet
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-card sc-purple">
|
||||||
|
<div class="stat-icon-wrap">👥</div>
|
||||||
|
<span class="stat-label">Total Customers</span>
|
||||||
|
<div class="stat-value" style="color:#8b5cf6;">{{ number_format($totalCustomers) }}</div>
|
||||||
|
<div class="stat-footer">Registered customers</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-card sc-red">
|
||||||
|
<div class="stat-icon-wrap">🏷️</div>
|
||||||
|
<span class="stat-label">Mark List</span>
|
||||||
|
<div class="stat-value" style="color:#e74a3b;">{{ number_format($totalMarklist) }}</div>
|
||||||
|
<div class="stat-footer positive">
|
||||||
|
{{ $activeMarklist }} active
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ════════ BOTTOM CARDS ════════ --}}
|
||||||
<div class="profile-grid-row">
|
<div class="profile-grid-row">
|
||||||
<div class="profile-card">
|
<div class="profile-card">
|
||||||
<h6>Recent Activity</h6>
|
<h6>Recent Activity</h6>
|
||||||
<ul class="activity-list">
|
<ul class="activity-list">
|
||||||
<li>📦 Shipment #SH-2024-001 delivered <span style="color:#a1a5ad;font-size:0.9em;margin-left:8px;">5 min ago</span></li>
|
<li>📦 Shipment #SH-2024-001 delivered <span style="color:#a1a5ad;font-size:0.85em;margin-left:auto;">5 min ago</span></li>
|
||||||
<li>📝 New shipment #SH-2024-002 <span style="color:#a1a5ad;font-size:0.9em;margin-left:8px;">13 min ago</span></li>
|
<li>📝 New shipment #SH-2024-002 created <span style="color:#a1a5ad;font-size:0.85em;margin-left:auto;">13 min ago</span></li>
|
||||||
<li>⏰ Delay for #SH-2024-003 <span style="color:#a1a5ad;font-size:0.9em;margin-left:8px;">1 hr ago</span></li>
|
<li>⏰ Delay reported for #SH-2024-003 <span style="color:#a1a5ad;font-size:0.85em;margin-left:auto;">1 hr ago</span></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="profile-card">
|
<div class="profile-card">
|
||||||
@@ -253,5 +432,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
@@ -82,8 +82,7 @@ Route::prefix('admin')
|
|||||||
Route::get('/account', fn() => view('admin.account'))
|
Route::get('/account', fn() => view('admin.account'))
|
||||||
->name('admin.account');
|
->name('admin.account');
|
||||||
|
|
||||||
Route::get('/profile', fn() => view('admin.profile'))
|
Route::get('/profile', [AdminAuthController::class, 'profile'])->name('admin.profile');
|
||||||
->name('admin.profile');
|
|
||||||
|
|
||||||
Route::post(
|
Route::post(
|
||||||
'admin/orders/upload-excel-preview',
|
'admin/orders/upload-excel-preview',
|
||||||
|
|||||||
Reference in New Issue
Block a user