Compare commits

..

4 Commits

Author SHA256 Message Date
Abhishek Mali
9a6ca49ad7 user order controller update for show view data 2026-03-13 20:19:34 +05:30
Abhishek Mali
bf2689e62d container and invoice api done small api like view invoice traking in order section is remaning 2026-03-13 17:25:58 +05:30
Utkarsh Khedkar
c25b468c77 Status Updated paying 2026-03-12 18:20:09 +05:30
Utkarsh Khedkar
5d8a746876 Status Updated 2026-03-12 18:11:43 +05:30
25 changed files with 584 additions and 277 deletions

View File

@@ -130,7 +130,7 @@ class AdminInvoiceController extends Controller
$data = $request->validate([
'invoice_date' => 'required|date',
'due_date' => 'required|date|after_or_equal:invoice_date',
'status' => 'required|in:pending,paid,overdue',
'status' => 'required|in:pending,paying,paid,overdue',
'notes' => 'nullable|string',
]);
@@ -149,8 +149,8 @@ class AdminInvoiceController extends Controller
$this->generateInvoicePDF($invoice);
return redirect()
->route('admin.invoices.index')
->with('success', 'Invoice updated & PDF generated successfully.');
->route('admin.invoices.edit', $invoice->id)
->with('success', 'Invoice updated & PDF generated successfully.');
}
// -------------------------------------------------------------

View File

@@ -11,7 +11,7 @@ use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
use Carbon\Carbon;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Facades\Storage; // <-- added for Excel download
use Illuminate\Support\Facades\Storage;
class ContainerController extends Controller
{
@@ -518,9 +518,12 @@ class ContainerController extends Controller
$firstMark = $rowsForCustomer[0]['mark'];
$snap = $markToSnapshot[$firstMark] ?? null;
// ✅ Customer User model वरून fetch करा (customer_id string आहे जसे CID-2025-000001)
$customerUser = \App\Models\User::where('customer_id', $customerId)->first();
$invoice = new Invoice();
$invoice->container_id = $container->id;
$invoice->customer_id = $customerId;
$invoice->customer_id = $customerUser->id ?? null; // ✅ integer id store करतोय
$invoice->mark_no = $firstMark;
$invoice->invoice_number = $this->generateInvoiceNumber();
@@ -533,28 +536,24 @@ class ContainerController extends Controller
->addDays(10)
->format('Y-m-d');
// Customer User model वरून fetch करा (customer_id string आहे जसे CID-2025-000001)
$customerUser = \App\Models\User::where('customer_id', $customerId)->first();
$invoice->container_id = $container->id;
$invoice->customer_id = $customerUser->id ?? null; // ✅ integer id
$invoice->mark_no = $firstMark;
if ($snap) {
$invoice->customer_name = $snap['customer_name'] ?? null;
$invoice->company_name = $snap['company_name'] ?? null;
$invoice->customer_mobile = $snap['mobile_no'] ?? null;
}
$invoice->final_amount = 0;
$invoice->gst_percent = 0;
$invoice->gst_amount = 0;
$invoice->final_amount_with_gst = 0;
// ✅ User model वरून email, address, pincode घ्या
// ✅ Snapshot data from MarkList (backward compatibility)
if ($snap) {
$invoice->customer_name = $snap['customer_name'] ?? null;
$invoice->company_name = $snap['company_name'] ?? null;
$invoice->customer_mobile = $snap['mobile_no'] ?? null;
}
// ✅ User model वरून email, address, pincode घ्या
if ($customerUser) {
$invoice->customer_email = $customerUser->email ?? null;
$invoice->customer_address = $customerUser->address ?? null;
$invoice->pincode = $customerUser->pincode ?? null;
}
$invoice->final_amount = 0;
$invoice->gst_percent = 0;
$invoice->gst_amount = 0;
$invoice->final_amount_with_gst = 0;
$uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark'));
$invoice->notes = 'Auto-created from Container ' . $container->container_number
@@ -621,7 +620,18 @@ class ContainerController extends Controller
public function show(Container $container)
{
$container->load('rows');
return view('admin.container_show', compact('container'));
// paid / paying invoices च्या row indexes collect करा
$lockedRowIndexes = \App\Models\Invoice::whereIn('invoices.status', ['paid', 'paying'])
->where('invoices.container_id', $container->id)
->join('invoice_items', 'invoices.id', '=', 'invoice_items.invoice_id')
->pluck('invoice_items.container_row_index')
->filter()
->unique()
->values()
->toArray();
return view('admin.container_show', compact('container', 'lockedRowIndexes'));
}
public function updateRows(Request $request, Container $container)
@@ -879,73 +889,71 @@ class ContainerController extends Controller
return Storage::download($path, $fileName);
}
public function popupPopup(Container $container)
{
// existing show सारखाच data वापरू
$container->load('rows');
{
// existing show सारखाच data वापरू
$container->load('rows');
// summary आधीपासून index() मध्ये जसा काढतोस तसाच logic reuse
$rows = $container->rows ?? collect();
// summary आधीपासून index() मध्ये जसा काढतोस तसाच logic reuse
$rows = $container->rows ?? collect();
$totalCtn = 0;
$totalQty = 0;
$totalCbm = 0;
$totalKg = 0;
$totalCtn = 0;
$totalQty = 0;
$totalCbm = 0;
$totalKg = 0;
$ctnKeys = ['CTN', 'CTNS'];
$qtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY', 'QTY', 'PCS', 'PIECES'];
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
$ctnKeys = ['CTN', 'CTNS'];
$qtyKeys = ['ITLQTY', 'TOTALQTY', 'TTLQTY', 'QTY', 'PCS', 'PIECES'];
$cbmKeys = ['TOTALCBM', 'TTLCBM', 'ITLCBM', 'CBM'];
$kgKeys = ['TOTALKG', 'TTKG', 'KG', 'WEIGHT'];
$getFirstNumeric = function (array $data, array $possibleKeys) {
$normalizedMap = [];
foreach ($data as $key => $value) {
if ($key === null) continue;
$normKey = strtoupper((string)$key);
$normKey = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normKey);
$normalizedMap[$normKey] = $value;
}
$getFirstNumeric = function (array $data, array $possibleKeys) {
$normalizedMap = [];
foreach ($data as $key => $value) {
if ($key === null) continue;
$normKey = strtoupper((string)$key);
$normKey = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normKey);
$normalizedMap[$normKey] = $value;
}
foreach ($possibleKeys as $search) {
$normSearch = strtoupper($search);
$normSearch = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normSearch);
foreach ($possibleKeys as $search) {
$normSearch = strtoupper($search);
$normSearch = str_replace([' ', ',', '-', '.', "\n", "\r", "\t"], '', $normSearch);
foreach ($normalizedMap as $nKey => $value) {
if (strpos($nKey, $normSearch) !== false) {
if (is_numeric($value)) {
return (float)$value;
}
if (is_string($value) && is_numeric(trim($value))) {
return (float)trim($value);
foreach ($normalizedMap as $nKey => $value) {
if (strpos($nKey, $normSearch) !== false) {
if (is_numeric($value)) {
return (float)$value;
}
if (is_string($value) && is_numeric(trim($value))) {
return (float)trim($value);
}
}
}
}
return 0;
};
foreach ($rows as $row) {
$data = $row->data ?? [];
if (!is_array($data)) continue;
$totalCtn += $getFirstNumeric($data, $ctnKeys);
$totalQty += $getFirstNumeric($data, $qtyKeys);
$totalCbm += $getFirstNumeric($data, $cbmKeys);
$totalKg += $getFirstNumeric($data, $kgKeys);
}
return 0;
};
foreach ($rows as $row) {
$data = $row->data ?? [];
if (!is_array($data)) continue;
$summary = [
'total_ctn' => round($totalCtn, 2),
'total_qty' => round($totalQty, 2),
'total_cbm' => round($totalCbm, 3),
'total_kg' => round($totalKg, 2),
];
$totalCtn += $getFirstNumeric($data, $ctnKeys);
$totalQty += $getFirstNumeric($data, $qtyKeys);
$totalCbm += $getFirstNumeric($data, $cbmKeys);
$totalKg += $getFirstNumeric($data, $kgKeys);
return view('admin.partials.container_popup_readonly', [
'container' => $container,
'summary' => $summary,
]);
}
$summary = [
'total_ctn' => round($totalCtn, 2),
'total_qty' => round($totalQty, 2),
'total_cbm' => round($totalCbm, 3),
'total_kg' => round($totalKg, 2),
];
return view('admin.partials.container_popup_readonly', [
'container' => $container,
'summary' => $summary,
]);
}
}

View File

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

View File

@@ -49,10 +49,10 @@ class Invoice extends Model
return $this->hasMany(InvoiceItem::class)->orderBy('id', 'ASC');
}
public function container()
{
return $this->belongsTo(Container::class);
}
// public function container()
// {
// return $this->belongsTo(Container::class);
// }
public function customer()
{
@@ -119,4 +119,15 @@ class Invoice extends Model
return max(0, $grand - $paid);
}
public function isLockedForEdit(): bool
{
return $this->status === 'paid';
}
public function container()
{
return $this->belongsTo(\App\Models\Container::class, 'container_id');
}
}

View File

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

View File

@@ -9,15 +9,29 @@ class AddChargeColumnsToInvoicesTable extends Migration
public function up()
{
Schema::table('invoices', function (Blueprint $table) {
$table->decimal('charge_groups_total', 15, 2)->nullable()->after('final_amount_with_gst');
$table->decimal('grand_total_with_charges', 15, 2)->nullable()->after('charge_groups_total');
if (!Schema::hasColumn('invoices', 'charge_groups_total')) {
$table->decimal('charge_groups_total', 15, 2)
->nullable()
->after('final_amount_with_gst');
}
if (!Schema::hasColumn('invoices', 'grand_total_with_charges')) {
$table->decimal('grand_total_with_charges', 15, 2)
->nullable()
->after('charge_groups_total');
}
});
}
public function down()
{
Schema::table('invoices', function (Blueprint $table) {
$table->dropColumn(['charge_groups_total', 'grand_total_with_charges']);
if (Schema::hasColumn('invoices', 'charge_groups_total')) {
$table->dropColumn('charge_groups_total');
}
if (Schema::hasColumn('invoices', 'grand_total_with_charges')) {
$table->dropColumn('grand_total_with_charges');
}
});
}
}
}

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -672,10 +672,15 @@
);
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
// row index = headerRowIndex + 1 + offset — ContainerRow मध्ये row_index save आहे
$isLockedByInvoice = in_array($row->row_index, $lockedRowIndexes ?? []);
$isReadOnly = $isTotalColumn || $container->status !== 'pending' || $isLockedByInvoice;
@endphp
// जर container pending नसेल तर सर्व fields readonly
$isReadOnly = $isTotalColumn || $container->status !== 'pending';
@endphp
@if($loop->first && $isLockedByInvoice)
{{-- पहिल्या cell मध्ये lock icon --}}
@endif
<td>
<input
type="text"

View File

@@ -169,6 +169,13 @@
color: #6b21a8;
font-weight: 500;
}
.filter-select option[value="paying"] {
background-color: #e0e7ff;
color: #4338ca;
font-weight: 500;
}
.filter-select option[value="all"] {
background-color: white;
@@ -534,6 +541,10 @@
background: url('/images/status-bg-overdue.png') !important;
}
.badge-paying {
background: url('/images/status-bg-paying.png') !important;
}
/* Fallback colors if images don't load - ALL WITH SAME SIZE */
.badge.badge-paid {
background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important;
@@ -554,6 +565,13 @@
border-color: #8b5cf6 !important;
}
.badge.badge-paying {
background: linear-gradient(135deg, #e0e7ff, #c7d2fe) !important;
color: #4338ca !important;
border-color: #6366f1 !important;
}
/* Entry Button - Centered */
.btn-entry {
background: linear-gradient(135deg, #3492f8 55%, #1256cc 110%);
@@ -1277,16 +1295,19 @@
</td>
<td>
<span class="badge badge-{{ $invoice->status }}">
@if($invoice->status == 'paid')
<i class="bi bi-check-circle-fill status-icon"></i>
@elseif($invoice->status == 'pending')
<i class="bi bi-clock-fill status-icon"></i>
@elseif($invoice->status == 'overdue')
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
@endif
{{ ucfirst($invoice->status) }}
</span>
<span class="badge badge-{{ $invoice->status }}">
@if($invoice->status == 'paid')
<i class="bi bi-check-circle-fill status-icon"></i>
@elseif($invoice->status == 'pending')
<i class="bi bi-clock-fill status-icon"></i>
@elseif($invoice->status == 'paying')
<i class="bi bi-arrow-repeat status-icon"></i> {{-- processing icon --}}
@elseif($invoice->status == 'overdue')
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
@endif
{{ ucfirst($invoice->status) }}
</span>
</td>
<td class="date-cell">
@@ -1338,11 +1359,14 @@
<i class="bi bi-check-circle-fill status-icon"></i>
@elseif($invoice->status == 'pending')
<i class="bi bi-clock-fill status-icon"></i>
@elseif($invoice->status == 'paying')
<i class="bi bi-arrow-repeat status-icon"></i>
@elseif($invoice->status == 'overdue')
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
@endif
{{ ucfirst($invoice->status) }}
</span>
</div>
<div class="mobile-invoice-details">
@@ -1677,6 +1701,7 @@ document.addEventListener('DOMContentLoaded', function() {
${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''}
${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''}
${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''}
${invoice.status === 'paying' ? '<i class="bi bi-arrow-repeat status-icon"></i>' : ''}
${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
</span>
</td>
@@ -1710,6 +1735,7 @@ document.addEventListener('DOMContentLoaded', function() {
${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''}
${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''}
${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''}
${invoice.status === 'paying' ? '<i class="bi bi-arrow-repeat status-icon"></i>' : ''}
${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
</span>
</div>

View File

@@ -381,9 +381,11 @@
</label>
<select name="status" class="form-select-compact" required>
<option value="pending" {{ old('status', $invoice->status) === 'pending' ? 'selected' : '' }}>Pending</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="overdue" {{ old('status', $invoice->status) === 'overdue' ? 'selected' : '' }}>Overdue</option>
</select>
</div>
{{-- Notes --}}

View File

@@ -640,6 +640,24 @@
/* ── DIVIDER ── */
.section-divider { height: 1px; background: var(--border); margin: 0 2.5rem; }
.pagination .page-link {
border-radius: 6px;
border: 1px solid #e5e7eb;
padding: 4px 10px;
font-size: 0.78rem;
color: #111827;
}
.pagination .page-item.active .page-link {
background-color: #111827;
border-color: #111827;
color: #ffffff;
}
.pagination .page-item.disabled .page-link {
background-color: #f3f4f6;
color: #9ca3af;
border-color: #e5e7eb;
}
</style>
</head>
<body>
@@ -781,89 +799,125 @@
@endphp
<div class="panel">
<div class="panel-header">
<i class="fas fa-list"></i> Invoice Items
<div class="panel-header">
<i class="fas fa-list"></i> Invoice Items
</div>
<div class="panel-body">
<div class="table-responsive">
<table class="invoice-table items-table">
<thead>
<tr>
<th class="text-center" style="width:44px;">
<input type="checkbox" id="selectAllItems">
</th>
<th class="text-center" style="min-width:50px;">#</th>
<th style="min-width:200px;">Description</th>
<th class="text-center" style="min-width:75px;">CTN</th>
<th class="text-center" style="min-width:75px;">QTY</th>
<th class="text-center" style="min-width:95px;">TTL/QTY</th>
<th class="text-center" style="min-width:75px;">Unit</th>
<th class="text-center" style="min-width:110px;">Price</th>
<th class="text-center" style="min-width:125px;">TTL Amount</th>
<th class="text-center" style="min-width:85px;">CBM</th>
<th class="text-center" style="min-width:95px;">TTL CBM</th>
<th class="text-center" style="min-width:80px;">KG</th>
<th class="text-center" style="min-width:95px;">TTL KG</th>
<th class="text-center" style="min-width:95px;">Shop No</th>
</tr>
</thead>
<tbody id="itemsTableBody">
@foreach($invoice->items as $i => $item)
@php
$alreadyGrouped = in_array($item->id, $groupedItemIds ?? []);
@endphp
<tr class="{{ $alreadyGrouped ? 'grouped-item-row' : '' }}">
<td class="text-center">
<input type="checkbox"
class="item-select-checkbox"
name="item_ids[]"
value="{{ $item->id }}"
{{ $alreadyGrouped ? 'disabled' : '' }}>
</td>
<td class="text-center" style="font-weight:600;color:var(--text-muted);">
{{ $i + 1 }}
</td>
<td class="desc-col" style="font-weight:600;color:var(--primary);">
{{ $item->description }}
</td>
<td class="text-center">{{ $item->ctn }}</td>
<td class="text-center">{{ $item->qty }}</td>
<td class="text-center" style="font-weight:700;">{{ $item->ttl_qty }}</td>
<td class="text-center">{{ $item->unit }}</td>
@if($isEmbedded)
<td class="text-center" style="min-width:120px;">
<input type="number" step="0.01" min="0"
name="items[{{ $item->id }}][price]"
value="{{ old('items.' . $item->id . '.price', $item->price) }}"
class="form-control form-control-sm text-end">
</td>
<td class="text-center" style="min-width:140px;">
<input type="number" step="0.01" min="0"
name="items[{{ $item->id }}][ttl_amount]"
value="{{ old('items.' . $item->id . '.ttl_amount', $item->ttl_amount) }}"
class="form-control form-control-sm text-end">
</td>
@else
<td class="text-center price-green">
{{ number_format($item->price, 2) }}
</td>
<td class="text-center price-blue">
{{ number_format($item->ttl_amount, 2) }}
</td>
@endif
<td class="text-center">{{ $item->cbm }}</td>
<td class="text-center">{{ $item->ttl_cbm }}</td>
<td class="text-center">{{ $item->kg }}</td>
<td class="text-center">{{ $item->ttl_kg }}</td>
<td class="text-center">
<span class="badge-shop">{{ $item->shop_no }}</span>
</td>
</tr>
@endforeach
@if($invoice->items->isEmpty())
<tr>
<td colspan="15" class="text-center py-4"
style="color:var(--text-muted);font-weight:600;">
<i class="fas fa-inbox me-2"
style="font-size:1.3rem;opacity:.4;"></i><br>
No invoice items found.
</td>
</tr>
@endif
</tbody>
</table>
</div>
<div class="table-responsive">
<table class="invoice-table items-table">
<thead>
<tr>
<th class="text-center" style="width:44px;">
<input type="checkbox" id="selectAllItems">
</th>
<th class="text-center" style="min-width:50px;">#</th>
<th style="min-width:200px;">Description</th>
<th class="text-center" style="min-width:75px;">CTN</th>
<th class="text-center" style="min-width:75px;">QTY</th>
<th class="text-center" style="min-width:95px;">TTL/QTY</th>
<th class="text-center" style="min-width:75px;">Unit</th>
<th class="text-center" style="min-width:110px;">Price</th>
<th class="text-center" style="min-width:125px;">TTL Amount</th>
<th class="text-center" style="min-width:85px;">CBM</th>
<th class="text-center" style="min-width:95px;">TTL CBM</th>
<th class="text-center" style="min-width:80px;">KG</th>
<th class="text-center" style="min-width:95px;">TTL KG</th>
<th class="text-center" style="min-width:95px;">Shop No</th>
</tr>
</thead>
<tbody>
@foreach($invoice->items as $i => $item)
@php
$alreadyGrouped = in_array($item->id, $groupedItemIds ?? []);
@endphp
<tr class="{{ $alreadyGrouped ? 'grouped-item-row' : '' }}">
<td class="text-center">
<input type="checkbox"
class="item-select-checkbox"
name="item_ids[]"
value="{{ $item->id }}"
{{ $alreadyGrouped ? 'disabled' : '' }}>
</td>
<td class="text-center" style="font-weight:600;color:var(--text-muted);">{{ $i + 1 }}</td>
<td class="desc-col" style="font-weight:600;color:var(--primary);">{{ $item->description }}</td>
<td class="text-center">{{ $item->ctn }}</td>
<td class="text-center">{{ $item->qty }}</td>
<td class="text-center" style="font-weight:700;">{{ $item->ttl_qty }}</td>
<td class="text-center">{{ $item->unit }}</td>
{{-- Items pagination bar --}}
<div class="d-flex justify-content-between align-items-center mt-2"
id="itemsPaginationBar"
style="font-size:0.8rem;">
<div>
Showing <span id="itemsStart">0</span> to
<span id="itemsEnd">0</span> of
<span id="itemsTotal">0</span> items
</div>
@if($isEmbedded)
<td class="text-center" style="min-width:120px;">
<input type="number" step="0.01" min="0"
name="items[{{ $item->id }}][price]"
value="{{ old('items.' . $item->id . '.price', $item->price) }}"
class="form-control form-control-sm text-end">
</td>
<td class="text-center" style="min-width:140px;">
<input type="number" step="0.01" min="0"
name="items[{{ $item->id }}][ttl_amount]"
value="{{ old('items.' . $item->id . '.ttl_amount', $item->ttl_amount) }}"
class="form-control form-control-sm text-end">
</td>
@else
<td class="text-center price-green">{{ number_format($item->price, 2) }}</td>
<td class="text-center price-blue">{{ number_format($item->ttl_amount, 2) }}</td>
@endif
<nav aria-label="Items pagination">
<ul class="pagination mb-0 pagination-sm" id="itemsPages">
{{-- JS will render buttons here --}}
</ul>
</nav>
</div>
<td class="text-center">{{ $item->cbm }}</td>
<td class="text-center">{{ $item->ttl_cbm }}</td>
<td class="text-center">{{ $item->kg }}</td>
<td class="text-center">{{ $item->ttl_kg }}</td>
<td class="text-center"><span class="badge-shop">{{ $item->shop_no }}</span></td>
</tr>
@endforeach
@if($invoice->items->isEmpty())
<tr>
<td colspan="15" class="text-center py-4" style="color:var(--text-muted);font-weight:600;">
<i class="fas fa-inbox me-2" style="font-size:1.3rem;opacity:.4;"></i><br>No invoice items found.
</td>
</tr>
@endif
</tbody>
</table>
</div>
</div>
</div>
<!-- ACTION BAR -->
<div class="action-bar">
<small>Selected items for charge group: <span id="selectedItemsCount">0</span></small>
@@ -1671,8 +1725,7 @@ document.addEventListener('DOMContentLoaded', function () {
: '<i class="fas fa-eye-slash"></i> Hide';
});
});
// simple select all (duplicate राहिला तरी harmless)
// "Select All" checkbox logic
document.addEventListener('DOMContentLoaded', function () {
const selectAll = document.getElementById('selectAllItems');
const checkboxes = document.querySelectorAll('.item-select-checkbox');
@@ -1687,6 +1740,113 @@ document.addEventListener('DOMContentLoaded', function () {
});
}
});
// Items table pagination logic edit blade
document.addEventListener('DOMContentLoaded', function () {
// ====== ITEMS TABLE PAGINATION (50 per page, numbered) ======
const itemsPerPage = 50;
const itemsTbody = document.getElementById('itemsTableBody');
const allItemRows = itemsTbody ? Array.from(itemsTbody.querySelectorAll('tr')) : [];
const itemsStart = document.getElementById('itemsStart');
const itemsEnd = document.getElementById('itemsEnd');
const itemsTotal = document.getElementById('itemsTotal');
const itemsPages = document.getElementById('itemsPages');
const itemsBar = document.getElementById('itemsPaginationBar');
let itemsCurrentPage = 1;
if (allItemRows.length) {
// data rows
const dataRows = allItemRows.filter(row => !row.querySelector('td[colspan]'));
const total = dataRows.length;
const totalPages = Math.max(1, Math.ceil(total / itemsPerPage));
if (itemsTotal) itemsTotal.textContent = total;
function renderItemsPage() {
if (!total) {
allItemRows.forEach(r => r.style.display = '');
if (itemsStart) itemsStart.textContent = 0;
if (itemsEnd) itemsEnd.textContent = 0;
if (itemsPages) itemsPages.innerHTML = '';
return;
}
const startIndex = (itemsCurrentPage - 1) * itemsPerPage;
const endIndex = Math.min(startIndex + itemsPerPage, total);
dataRows.forEach((row, idx) => {
row.style.display = (idx >= startIndex && idx < endIndex) ? '' : 'none';
});
// "no items" row hide
allItemRows.forEach(row => {
const td = row.querySelector('td[colspan]');
if (td) row.style.display = 'none';
});
if (itemsStart) itemsStart.textContent = startIndex + 1;
if (itemsEnd) itemsEnd.textContent = endIndex;
// pagination buttons: 1 2 …
if (itemsPages) {
itemsPages.innerHTML = '';
// prev
const prevLi = document.createElement('li');
prevLi.className = 'page-item' + (itemsCurrentPage === 1 ? ' disabled' : '');
prevLi.innerHTML = '<button class="page-link" type="button" aria-label="Previous"></button>';
if (itemsCurrentPage > 1) {
prevLi.querySelector('button').addEventListener('click', () => {
itemsCurrentPage--;
renderItemsPage();
});
}
itemsPages.appendChild(prevLi);
// page numbers
for (let p = 1; p <= totalPages; p++) {
const li = document.createElement('li');
li.className = 'page-item' + (p === itemsCurrentPage ? ' active' : '');
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'page-link';
btn.textContent = p;
if (p !== itemsCurrentPage) {
btn.addEventListener('click', () => {
itemsCurrentPage = p;
renderItemsPage();
});
}
li.appendChild(btn);
itemsPages.appendChild(li);
}
// next
const nextLi = document.createElement('li');
nextLi.className = 'page-item' + (itemsCurrentPage === totalPages ? ' disabled' : '');
nextLi.innerHTML = '<button class="page-link" type="button" aria-label="Next"></button>';
if (itemsCurrentPage < totalPages) {
nextLi.querySelector('button').addEventListener('click', () => {
itemsCurrentPage++;
renderItemsPage();
});
}
itemsPages.appendChild(nextLi);
}
}
renderItemsPage();
} else if (itemsBar) {
itemsBar.style.display = 'none';
}
});
</script>