2 Commits
qa ... dev

Author SHA256 Message Date
Utkarsh Khedkar
0257b68f16 Merge branch 'dev' of http://103.248.30.24:3000/kent-logistics/Kent-logistics-Laravel into dev 2026-03-13 23:06:51 +05:30
Utkarsh Khedkar
785f2564be changes 2026-03-13 23:06:19 +05:30
15 changed files with 757 additions and 338 deletions

View File

@@ -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'));
} }
} }

View File

@@ -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',

View File

@@ -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'
));
} }
/* --------------------------- /* ---------------------------

View File

@@ -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;

View File

@@ -29,6 +29,7 @@ class InvoiceItem extends Model
'ttl_kg', 'ttl_kg',
'shop_no', 'shop_no',
'mark_no',
]; ];
/**************************** /****************************

View File

@@ -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');
});
}
};

Binary file not shown.

Binary file not shown.

View File

@@ -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() }} &nbsp;&nbsp; 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;
} }

View File

@@ -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">&#x1F6A2;</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">&#x1F465;</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">&#x1F9FE;</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">&#x2705;</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">&#x1F550;</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">&#x1F9D1;&#x200D;&#x1F4BC;</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">&#x26D4;</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">&#x1F4B0;</span>
<div>
<div class="stats-label">Total Revenue</div>
<div class="stats-value">&#8377;{{ 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">

View File

@@ -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

View File

@@ -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">

View File

@@ -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),
}); });
} }
}); });

View File

@@ -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

View File

@@ -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',