Resolve merge conflicts
This commit is contained in:
@@ -6,10 +6,11 @@ use App\Models\Order;
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
class OrdersExport implements FromCollection, WithHeadings
|
class OrdersExport implements FromCollection, WithHeadings
|
||||||
{
|
{
|
||||||
protected $request;
|
protected Request $request;
|
||||||
|
|
||||||
public function __construct(Request $request)
|
public function __construct(Request $request)
|
||||||
{
|
{
|
||||||
@@ -18,61 +19,99 @@ class OrdersExport implements FromCollection, WithHeadings
|
|||||||
|
|
||||||
private function buildQuery()
|
private function buildQuery()
|
||||||
{
|
{
|
||||||
$query = Order::with(['markList', 'invoice', 'shipments']);
|
$query = Order::query()->with([
|
||||||
|
'markList',
|
||||||
|
'invoice',
|
||||||
|
'shipments',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// SEARCH
|
||||||
if ($this->request->filled('search')) {
|
if ($this->request->filled('search')) {
|
||||||
$search = $this->request->search;
|
$search = trim($this->request->search);
|
||||||
$query->where(function($q) use ($search) {
|
|
||||||
$q->where('order_id', 'like', "%{$search}%")
|
$query->where(function ($q) use ($search) {
|
||||||
->orWhereHas('markList', function($q2) use ($search) {
|
$q->where('orders.order_id', 'like', "%{$search}%")
|
||||||
|
->orWhereHas('markList', function ($q2) use ($search) {
|
||||||
$q2->where('company_name', 'like', "%{$search}%")
|
$q2->where('company_name', 'like', "%{$search}%")
|
||||||
->orWhere('customer_id', 'like', "%{$search}%");
|
->orWhere('customer_id', 'like', "%{$search}%")
|
||||||
|
->orWhere('origin', 'like', "%{$search}%")
|
||||||
|
->orWhere('destination', 'like', "%{$search}%");
|
||||||
})
|
})
|
||||||
->orWhereHas('invoice', function($q3) use ($search) {
|
->orWhereHas('invoice', function ($q3) use ($search) {
|
||||||
$q3->where('invoice_number', 'like', "%{$search}%");
|
$q3->where('invoice_number', 'like', "%{$search}%");
|
||||||
|
})
|
||||||
|
->orWhereHas('shipments', function ($q4) use ($search) {
|
||||||
|
// ✅ FIXED
|
||||||
|
$q4->where('shipments.shipment_id', 'like', "%{$search}%");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// INVOICE STATUS
|
||||||
|
// INVOICE STATUS (FIXED)
|
||||||
if ($this->request->filled('status')) {
|
if ($this->request->filled('status')) {
|
||||||
$query->whereHas('invoice', function($q) {
|
$query->where(function ($q) {
|
||||||
$q->where('status', $this->request->status);
|
$q->whereHas('invoice', function ($q2) {
|
||||||
|
$q2->where('status', $this->request->status);
|
||||||
|
})
|
||||||
|
->orWhereDoesntHave('invoice');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// SHIPMENT STATUS (FIXED)
|
||||||
if ($this->request->filled('shipment')) {
|
if ($this->request->filled('shipment')) {
|
||||||
$query->whereHas('shipments', function($q) {
|
$query->where(function ($q) {
|
||||||
$q->where('status', $this->request->shipment);
|
$q->whereHas('shipments', function ($q2) {
|
||||||
|
$q2->where('status', $this->request->shipment);
|
||||||
|
})
|
||||||
|
->orWhereDoesntHave('shipments');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return $query->latest('id');
|
|
||||||
|
// DATE RANGE
|
||||||
|
if ($this->request->filled('from_date')) {
|
||||||
|
$query->whereDate('orders.created_at', '>=', $this->request->from_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->filled('to_date')) {
|
||||||
|
$query->whereDate('orders.created_at', '<=', $this->request->to_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->latest('orders.id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function collection()
|
public function collection()
|
||||||
{
|
{
|
||||||
$orders = $this->buildQuery()->get();
|
return $this->buildQuery()->get()->map(function ($order) {
|
||||||
|
|
||||||
// Map to simple array rows suitable for Excel
|
|
||||||
return $orders->map(function($order) {
|
|
||||||
$mark = $order->markList;
|
$mark = $order->markList;
|
||||||
$invoice = $order->invoice;
|
$invoice = $order->invoice;
|
||||||
$shipment = $order->shipments->first() ?? null;
|
$shipment = $order->shipments->first();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'Order ID' => $order->order_id,
|
'Order ID' => $order->order_id ?? '-',
|
||||||
'Shipment ID' => $shipment->shipment_id ?? '-',
|
'Shipment ID' => $shipment?->shipment_id ?? '-',
|
||||||
'Customer ID' => $mark->customer_id ?? '-',
|
'Customer ID' => $mark?->customer_id ?? '-',
|
||||||
'Company' => $mark->company_name ?? '-',
|
'Company' => $mark?->company_name ?? '-',
|
||||||
'Origin' => $mark->origin ?? $order->origin ?? '-',
|
'Origin' => $mark?->origin ?? $order->origin ?? '-',
|
||||||
'Destination' => $mark->destination ?? $order->destination ?? '-',
|
'Destination' => $mark?->destination ?? $order->destination ?? '-',
|
||||||
'Order Date' => $order->created_at ? $order->created_at->format('d-m-Y') : '-',
|
'Order Date' => $order->created_at
|
||||||
'Invoice No' => $invoice->invoice_number ?? '-',
|
? $order->created_at->format('d-m-Y')
|
||||||
'Invoice Date' => $invoice?->invoice_date ? \Carbon\Carbon::parse($invoice->invoice_date)->format('d-m-Y') : '-',
|
: '-',
|
||||||
'Amount' => $invoice?->final_amount ? number_format($invoice->final_amount, 2) : '-',
|
'Invoice No' => $invoice?->invoice_number ?? '-',
|
||||||
'Amount + GST' => $invoice?->final_amount_with_gst ? number_format($invoice->final_amount_with_gst, 2) : '-',
|
'Invoice Date' => $invoice?->invoice_date
|
||||||
'Invoice Status' => $invoice->status ? ucfirst($invoice->status) : 'Pending',
|
? Carbon::parse($invoice->invoice_date)->format('d-m-Y')
|
||||||
'Shipment Status' => $shipment?->status ? ucfirst(str_replace('_', ' ', $shipment->status)) : 'Pending',
|
: '-',
|
||||||
|
'Amount' => $invoice?->final_amount !== null
|
||||||
|
? number_format($invoice->final_amount, 2)
|
||||||
|
: '0.00',
|
||||||
|
'Amount + GST' => $invoice?->final_amount_with_gst !== null
|
||||||
|
? number_format($invoice->final_amount_with_gst, 2)
|
||||||
|
: '0.00',
|
||||||
|
'Invoice Status' => ucfirst($invoice?->status ?? 'pending'),
|
||||||
|
'Shipment Status' => ucfirst(str_replace('_', ' ', $shipment?->status ?? 'pending')),
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,22 @@
|
|||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
<<<<<<< HEAD
|
||||||
use App\Models\SupportTicket;
|
use App\Models\SupportTicket;
|
||||||
use App\Models\ChatMessage;
|
use App\Models\ChatMessage;
|
||||||
use App\Events\NewChatMessage;
|
use App\Events\NewChatMessage;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
=======
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\SupportTicket;
|
||||||
|
use App\Models\ChatMessage;
|
||||||
|
use App\Events\NewChatMessage;
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
|
|
||||||
class AdminChatController extends Controller
|
class AdminChatController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
<<<<<<< HEAD
|
||||||
* Page 1: List all customer chat tickets
|
* Page 1: List all customer chat tickets
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
@@ -23,11 +31,31 @@ class AdminChatController extends Controller
|
|||||||
|
|
||||||
return view('admin.chat_support', compact('tickets'));
|
return view('admin.chat_support', compact('tickets'));
|
||||||
}
|
}
|
||||||
|
=======
|
||||||
|
* Page 1: List all active user chats
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$tickets = SupportTicket::with('user')
|
||||||
|
->withCount([
|
||||||
|
'messages as unread_count' => function ($q) {
|
||||||
|
$q->where('sender_type', \App\Models\User::class)
|
||||||
|
->where('read_by_admin', false);
|
||||||
|
}
|
||||||
|
])
|
||||||
|
->orderBy('updated_at', 'desc')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return view('admin.chat_support', compact('tickets'));
|
||||||
|
}
|
||||||
|
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page 2: Open chat window for a specific user
|
* Page 2: Open chat window for a specific user
|
||||||
*/
|
*/
|
||||||
public function openChat($ticketId)
|
public function openChat($ticketId)
|
||||||
|
<<<<<<< HEAD
|
||||||
{
|
{
|
||||||
$ticket = SupportTicket::with('user')->findOrFail($ticketId);
|
$ticket = SupportTicket::with('user')->findOrFail($ticketId);
|
||||||
$messages = ChatMessage::where('ticket_id', $ticketId)
|
$messages = ChatMessage::where('ticket_id', $ticketId)
|
||||||
@@ -40,6 +68,28 @@ class AdminChatController extends Controller
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Admin sends a message to the user (FIXED - LIVE CHAT)
|
* Admin sends a message to the user (FIXED - LIVE CHAT)
|
||||||
|
=======
|
||||||
|
{
|
||||||
|
$ticket = SupportTicket::with('user')->findOrFail($ticketId);
|
||||||
|
|
||||||
|
// ✅ MARK USER MESSAGES AS READ FOR ADMIN
|
||||||
|
ChatMessage::where('ticket_id', $ticketId)
|
||||||
|
->where('sender_type', \App\Models\User::class)
|
||||||
|
->where('read_by_admin', false)
|
||||||
|
->update(['read_by_admin' => true]);
|
||||||
|
|
||||||
|
$messages = ChatMessage::where('ticket_id', $ticketId)
|
||||||
|
->orderBy('created_at', 'asc')
|
||||||
|
->with('sender')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return view('admin.chat_window', compact('ticket', 'messages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin sends a message to the user
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
*/
|
*/
|
||||||
public function sendMessage(Request $request, $ticketId)
|
public function sendMessage(Request $request, $ticketId)
|
||||||
{
|
{
|
||||||
@@ -56,6 +106,12 @@ class AdminChatController extends Controller
|
|||||||
'sender_id' => $admin->id,
|
'sender_id' => $admin->id,
|
||||||
'sender_type' => \App\Models\Admin::class,
|
'sender_type' => \App\Models\Admin::class,
|
||||||
'message' => $request->message,
|
'message' => $request->message,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
|
||||||
|
'read_by_admin' => true,
|
||||||
|
'read_by_user' => false,
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
];
|
];
|
||||||
|
|
||||||
// File Upload
|
// File Upload
|
||||||
@@ -69,14 +125,28 @@ class AdminChatController extends Controller
|
|||||||
$message = ChatMessage::create($data);
|
$message = ChatMessage::create($data);
|
||||||
$message->load('sender');
|
$message->load('sender');
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
\Log::info("DEBUG: ChatController sendMessage called", [
|
\Log::info("DEBUG: ChatController sendMessage called", [
|
||||||
|
=======
|
||||||
|
\Log::info("DEBUG: ChatController sendMessage called", [
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
'ticket_id' => $ticketId,
|
'ticket_id' => $ticketId,
|
||||||
'payload' => $request->all()
|
'payload' => $request->all()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
// 🔥 LIVE CHAT - Queue bypass (100% working)
|
// 🔥 LIVE CHAT - Queue bypass (100% working)
|
||||||
broadcast(new NewChatMessage($message))->toOthers();
|
broadcast(new NewChatMessage($message))->toOthers();
|
||||||
|
|
||||||
|
=======
|
||||||
|
// Broadcast real-time
|
||||||
|
broadcast(new NewChatMessage($message));
|
||||||
|
|
||||||
|
\Log::info("DEBUG: ChatController sendMessage called 79", [
|
||||||
|
'ticket_id' => $ticketId,
|
||||||
|
'payload' => $request->all()
|
||||||
|
]);
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => $message
|
'message' => $message
|
||||||
|
|||||||
@@ -19,9 +19,12 @@ class AdminCustomerController extends Controller
|
|||||||
$search = $request->search;
|
$search = $request->search;
|
||||||
$status = $request->status;
|
$status = $request->status;
|
||||||
|
|
||||||
$query = User::with(['marks', 'orders'])->orderBy('id', 'desc');
|
$query = User::with([
|
||||||
|
'marks',
|
||||||
|
'orders',
|
||||||
|
'invoices.installments' // 🔥 IMPORTANT
|
||||||
|
])->orderBy('id', 'desc');
|
||||||
|
|
||||||
// SEARCH FILTER
|
|
||||||
if (!empty($search)) {
|
if (!empty($search)) {
|
||||||
$query->where(function ($q) use ($search) {
|
$query->where(function ($q) use ($search) {
|
||||||
$q->where('customer_name', 'like', "%$search%")
|
$q->where('customer_name', 'like', "%$search%")
|
||||||
@@ -31,20 +34,22 @@ class AdminCustomerController extends Controller
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// STATUS FILTER
|
|
||||||
if (!empty($status) && in_array($status, ['active', 'inactive'])) {
|
if (!empty($status) && in_array($status, ['active', 'inactive'])) {
|
||||||
$query->where('status', $status);
|
$query->where('status', $status);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all customers for statistics (without pagination)
|
|
||||||
$allCustomers = $query->get();
|
$allCustomers = $query->get();
|
||||||
|
|
||||||
// Get paginated customers for the table (10 per page)
|
|
||||||
$customers = $query->paginate(10);
|
$customers = $query->paginate(10);
|
||||||
|
|
||||||
return view('admin.customers', compact('customers', 'allCustomers', 'search', 'status'));
|
return view('admin.customers', compact(
|
||||||
|
'customers',
|
||||||
|
'allCustomers',
|
||||||
|
'search',
|
||||||
|
'status'
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
// SHOW ADD CUSTOMER FORM
|
// SHOW ADD CUSTOMER FORM
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
@@ -106,20 +111,36 @@ class AdminCustomerController extends Controller
|
|||||||
// VIEW CUSTOMER FULL DETAILS
|
// VIEW CUSTOMER FULL DETAILS
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
public function view($id)
|
public function view($id)
|
||||||
{
|
{
|
||||||
$customer = User::with(['marks', 'orders'])->findOrFail($id);
|
$customer = User::with([
|
||||||
|
'marks',
|
||||||
|
'orders',
|
||||||
|
'invoices.installments'
|
||||||
|
])->findOrFail($id);
|
||||||
|
|
||||||
|
// Orders
|
||||||
$totalOrders = $customer->orders->count();
|
$totalOrders = $customer->orders->count();
|
||||||
$totalAmount = $customer->orders->sum('ttl_amount');
|
$totalOrderAmount = $customer->orders->sum('ttl_amount');
|
||||||
$recentOrders = $customer->orders()->latest()->take(5)->get();
|
|
||||||
|
// Invoices (PAYABLE)
|
||||||
|
$totalPayable = $customer->invoices->sum('final_amount_with_gst');
|
||||||
|
|
||||||
|
// Paid via installments
|
||||||
|
$totalPaid = $customer->invoiceInstallments->sum('amount');
|
||||||
|
|
||||||
|
// Remaining
|
||||||
|
$totalRemaining = max($totalPayable - $totalPaid, 0);
|
||||||
|
|
||||||
return view('admin.customers_view', compact(
|
return view('admin.customers_view', compact(
|
||||||
'customer',
|
'customer',
|
||||||
'totalOrders',
|
'totalOrders',
|
||||||
'totalAmount',
|
'totalOrderAmount',
|
||||||
'recentOrders'
|
'totalPayable',
|
||||||
|
'totalPaid',
|
||||||
|
'totalRemaining'
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
// TOGGLE STATUS ACTIVE / INACTIVE
|
// TOGGLE STATUS ACTIVE / INACTIVE
|
||||||
|
|||||||
@@ -43,7 +43,14 @@ class AdminInvoiceController extends Controller
|
|||||||
$invoice = Invoice::with(['items', 'customer', 'container'])->findOrFail($id);
|
$invoice = Invoice::with(['items', 'customer', 'container'])->findOrFail($id);
|
||||||
$shipment = null;
|
$shipment = null;
|
||||||
|
|
||||||
return view('admin.invoice_edit', compact('invoice', 'shipment'));
|
// ADD THIS SECTION: Calculate customer's total due across all invoices
|
||||||
|
$customerTotalDue = Invoice::where('customer_id', $invoice->customer_id)
|
||||||
|
->where('status', '!=', 'cancelled')
|
||||||
|
->where('status', '!=', 'void')
|
||||||
|
->sum('final_amount_with_gst');
|
||||||
|
|
||||||
|
// Pass the new variable to the view
|
||||||
|
return view('admin.invoice_edit', compact('invoice', 'shipment', 'customerTotalDue'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
@@ -250,6 +257,17 @@ class AdminInvoiceController extends Controller
|
|||||||
$invoice->update(['pdf_path' => 'invoices/' . $fileName]);
|
$invoice->update(['pdf_path' => 'invoices/' . $fileName]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function downloadInvoice($id)
|
||||||
|
{
|
||||||
|
$invoice = Invoice::findOrFail($id);
|
||||||
|
|
||||||
|
// ALWAYS regenerate to reflect latest HTML/CSS
|
||||||
|
$this->generateInvoicePDF($invoice);
|
||||||
|
$invoice->refresh();
|
||||||
|
|
||||||
|
return response()->download(public_path($invoice->pdf_path));
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// INSTALLMENTS (ADD)
|
// INSTALLMENTS (ADD)
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
@@ -285,6 +303,8 @@ class AdminInvoiceController extends Controller
|
|||||||
|
|
||||||
if ($newPaid >= $invoice->final_amount_with_gst) {
|
if ($newPaid >= $invoice->final_amount_with_gst) {
|
||||||
$invoice->update(['status' => 'paid']);
|
$invoice->update(['status' => 'paid']);
|
||||||
|
|
||||||
|
$this->generateInvoicePDF($invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -315,6 +335,8 @@ class AdminInvoiceController extends Controller
|
|||||||
|
|
||||||
if ($remaining > 0 && $invoice->status === 'paid') {
|
if ($remaining > 0 && $invoice->status === 'paid') {
|
||||||
$invoice->update(['status' => 'pending']);
|
$invoice->update(['status' => 'pending']);
|
||||||
|
|
||||||
|
$this->generateInvoicePDF($invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
|
|||||||
@@ -10,47 +10,33 @@ use App\Models\MarkList;
|
|||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Models\InvoiceItem;
|
use App\Models\InvoiceItem;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use PDF; // barryvdh/laravel-dompdf facade
|
use PDF;
|
||||||
use Maatwebsite\Excel\Facades\Excel;
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
use App\Exports\OrdersExport;
|
use App\Exports\OrdersExport;
|
||||||
|
use App\Imports\OrderItemsPreviewImport;
|
||||||
|
|
||||||
|
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
|
||||||
class AdminOrderController extends Controller
|
class AdminOrderController extends Controller
|
||||||
{
|
{
|
||||||
// ---------------------------
|
/* ---------------------------
|
||||||
// LIST / DASHBOARD
|
* LIST / DASHBOARD
|
||||||
// ---------------------------
|
* ---------------------------*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
// raw list for admin dashboard (simple)
|
|
||||||
$orders = Order::latest()->get();
|
$orders = Order::latest()->get();
|
||||||
$markList = MarkList::where('status', 'active')->get();
|
$markList = MarkList::where('status', 'active')->get();
|
||||||
|
|
||||||
return view('admin.dashboard', compact('orders', 'markList'));
|
return view('admin.dashboard', compact('orders', 'markList'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/* ---------------------------
|
||||||
* Orders list (detailed)
|
* CREATE NEW ORDER (simple page)
|
||||||
*/
|
* ---------------------------*/
|
||||||
// public function orderShow()
|
|
||||||
// {
|
|
||||||
// $orders = Order::with(['markList', 'shipments', 'invoice'])
|
|
||||||
// ->latest('id')
|
|
||||||
// ->get();
|
|
||||||
|
|
||||||
// return view('admin.orders', compact('orders'));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// CREATE NEW ORDER (simple DB flow)
|
|
||||||
// ---------------------------
|
|
||||||
/**
|
|
||||||
* Show create form (you can place create UI on separate view or dashboard)
|
|
||||||
*/
|
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
// return a dedicated create view - create it at resources/views/admin/orders_create.blade.php
|
|
||||||
// If you prefer create UI on dashboard, change this to redirect route('admin.orders.index') etc.
|
|
||||||
$markList = MarkList::where('status', 'active')->get();
|
$markList = MarkList::where('status', 'active')->get();
|
||||||
return view('admin.orders_create', compact('markList'));
|
return view('admin.orders_create', compact('markList'));
|
||||||
}
|
}
|
||||||
@@ -109,38 +95,14 @@ class AdminOrderController extends Controller
|
|||||||
return view('admin.orders_show', compact('order', 'user'));
|
return view('admin.orders_show', compact('order', 'user'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// public function popup($id)
|
public function popup($id)
|
||||||
// {
|
|
||||||
// $order = Order::with(['items', 'markList'])->findOrFail($id);
|
|
||||||
// $user = $this->getCustomerFromOrder($order);
|
|
||||||
|
|
||||||
// return view('admin.popup', compact('order', 'user'));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// ORDER ITEM MANAGEMENT (DB)
|
|
||||||
// ---------------------------
|
|
||||||
/**
|
|
||||||
* Add an item to an existing order
|
|
||||||
*/
|
|
||||||
public function addItem(Request $request, $orderId)
|
|
||||||
{
|
{
|
||||||
$order = Order::findOrFail($orderId);
|
$order = Order::with(['items', 'markList'])->findOrFail($id);
|
||||||
|
|
||||||
$data = $request->validate([
|
$user = null;
|
||||||
'description' => 'required|string',
|
if ($order->markList && $order->markList->customer_id) {
|
||||||
'ctn' => 'nullable|numeric',
|
$user = User::where('customer_id', $order->markList->customer_id)->first();
|
||||||
'qty' => 'nullable|numeric',
|
}
|
||||||
'ttl_qty' => 'nullable|numeric',
|
|
||||||
'unit' => 'nullable|string',
|
|
||||||
'price' => 'nullable|numeric',
|
|
||||||
'ttl_amount' => 'nullable|numeric',
|
|
||||||
'cbm' => 'nullable|numeric',
|
|
||||||
'ttl_cbm' => 'nullable|numeric',
|
|
||||||
'kg' => 'nullable|numeric',
|
|
||||||
'ttl_kg' => 'nullable|numeric',
|
|
||||||
'shop_no' => 'nullable|string',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$data['order_id'] = $order->id;
|
$data['order_id'] = $order->id;
|
||||||
|
|
||||||
@@ -153,27 +115,60 @@ class AdminOrderController extends Controller
|
|||||||
return redirect()->back()->with('success', 'Item added and totals updated.');
|
return redirect()->back()->with('success', 'Item added and totals updated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/* ---------------------------
|
||||||
* Soft-delete an order item and recalc totals
|
* ORDER ITEM MANAGEMENT (existing orders)
|
||||||
*/
|
* ---------------------------*/
|
||||||
|
public function addItem(Request $request, $orderId)
|
||||||
|
{
|
||||||
|
$order = Order::findOrFail($orderId);
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'description' => 'required|string',
|
||||||
|
'ctn' => 'nullable|numeric',
|
||||||
|
'qty' => 'nullable|numeric',
|
||||||
|
'unit' => 'nullable|string',
|
||||||
|
'price' => 'nullable|numeric',
|
||||||
|
'cbm' => 'nullable|numeric',
|
||||||
|
'kg' => 'nullable|numeric',
|
||||||
|
'shop_no' => 'nullable|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ✅ BACKEND CALCULATION
|
||||||
|
$ctn = (float) ($data['ctn'] ?? 0);
|
||||||
|
$qty = (float) ($data['qty'] ?? 0);
|
||||||
|
$price = (float) ($data['price'] ?? 0);
|
||||||
|
$cbm = (float) ($data['cbm'] ?? 0);
|
||||||
|
$kg = (float) ($data['kg'] ?? 0);
|
||||||
|
|
||||||
|
$data['ttl_qty'] = $ctn * $qty;
|
||||||
|
$data['ttl_amount'] = $data['ttl_qty'] * $price;
|
||||||
|
$data['ttl_cbm'] = $cbm * $ctn;
|
||||||
|
$data['ttl_kg'] = $ctn * $kg;
|
||||||
|
|
||||||
|
$data['order_id'] = $order->id;
|
||||||
|
|
||||||
|
OrderItem::create($data);
|
||||||
|
|
||||||
|
$this->recalcTotals($order);
|
||||||
|
$this->updateInvoiceFromOrder($order);
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Item added and totals updated.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function deleteItem($id)
|
public function deleteItem($id)
|
||||||
{
|
{
|
||||||
$item = OrderItem::findOrFail($id);
|
$item = OrderItem::findOrFail($id);
|
||||||
$order = $item->order;
|
$order = $item->order;
|
||||||
|
|
||||||
$item->delete(); // soft delete
|
$item->delete();
|
||||||
|
|
||||||
// recalc totals
|
|
||||||
$this->recalcTotals($order);
|
$this->recalcTotals($order);
|
||||||
$this->updateInvoiceFromOrder($order);
|
$this->updateInvoiceFromOrder($order);
|
||||||
|
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Item deleted and totals updated.');
|
return redirect()->back()->with('success', 'Item deleted and totals updated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Restore soft-deleted item and recalc totals
|
|
||||||
*/
|
|
||||||
public function restoreItem($id)
|
public function restoreItem($id)
|
||||||
{
|
{
|
||||||
$item = OrderItem::withTrashed()->findOrFail($id);
|
$item = OrderItem::withTrashed()->findOrFail($id);
|
||||||
@@ -181,17 +176,15 @@ class AdminOrderController extends Controller
|
|||||||
|
|
||||||
$item->restore();
|
$item->restore();
|
||||||
|
|
||||||
// recalc totals
|
|
||||||
$this->recalcTotals($order);
|
$this->recalcTotals($order);
|
||||||
$this->updateInvoiceFromOrder($order);
|
$this->updateInvoiceFromOrder($order);
|
||||||
|
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Item restored and totals updated.');
|
return redirect()->back()->with('success', 'Item restored and totals updated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------
|
/* ---------------------------
|
||||||
// ORDER CRUD: update / destroy
|
* ORDER CRUD: update / destroy
|
||||||
// ---------------------------
|
* ---------------------------*/
|
||||||
public function update(Request $request, $id)
|
public function update(Request $request, $id)
|
||||||
{
|
{
|
||||||
$order = Order::findOrFail($id);
|
$order = Order::findOrFail($id);
|
||||||
@@ -208,46 +201,35 @@ class AdminOrderController extends Controller
|
|||||||
'destination' => $data['destination'] ?? null,
|
'destination' => $data['destination'] ?? null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// optionally recalc totals (not necessary unless you change item-level fields here)
|
|
||||||
$this->recalcTotals($order);
|
$this->recalcTotals($order);
|
||||||
|
|
||||||
return redirect()->route('admin.orders.show', $order->id)
|
return redirect()->route('admin.orders.show', $order->id)
|
||||||
->with('success', 'Order updated successfully.');
|
->with('success', 'Order updated successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Soft-delete whole order and its items (soft-delete items first then order)
|
|
||||||
*/
|
|
||||||
public function destroy($id)
|
public function destroy($id)
|
||||||
{
|
{
|
||||||
$order = Order::findOrFail($id);
|
$order = Order::findOrFail($id);
|
||||||
|
|
||||||
// soft-delete items first (so they show up in onlyTrashed for restore)
|
|
||||||
OrderItem::where('order_id', $order->id)->delete();
|
OrderItem::where('order_id', $order->id)->delete();
|
||||||
|
|
||||||
// then soft-delete order
|
|
||||||
$order->delete();
|
$order->delete();
|
||||||
|
|
||||||
return redirect()->route('admin.orders.index')
|
return redirect()->route('admin.orders.index')
|
||||||
->with('success', 'Order deleted successfully.');
|
->with('success', 'Order deleted successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------
|
/* ---------------------------
|
||||||
// HELPERS
|
* HELPERS
|
||||||
// ---------------------------
|
* ---------------------------*/
|
||||||
/**
|
|
||||||
* Recalculate totals for the order from current (non-deleted) items
|
|
||||||
*/
|
|
||||||
private function recalcTotals(Order $order)
|
private function recalcTotals(Order $order)
|
||||||
{
|
{
|
||||||
// make sure we re-query live items (non-deleted)
|
|
||||||
$items = $order->items()->get();
|
$items = $order->items()->get();
|
||||||
|
|
||||||
$order->update([
|
$order->update([
|
||||||
'ctn' => (int) $items->sum(fn($i) => (int) ($i->ctn ?? 0)),
|
'ctn' => (int) $items->sum(fn($i) => (int) ($i->ctn ?? 0)),
|
||||||
'qty' => (int) $items->sum(fn($i) => (int) ($i->qty ?? 0)),
|
'qty' => (int) $items->sum(fn($i) => (int) ($i->qty ?? 0)),
|
||||||
'ttl_qty' => (int) $items->sum(fn($i) => (int) ($i->ttl_qty ?? 0)),
|
'ttl_qty' => (int) $items->sum(fn($i) => (int) ($i->ttl_qty ?? 0)),
|
||||||
'ttl_amount'=> (float) $items->sum(fn($i) => (float) ($i->ttl_amount ?? 0)),
|
'ttl_amount' => (float) $items->sum(fn($i) => (float) ($i->ttl_amount ?? 0)),
|
||||||
'cbm' => (float) $items->sum(fn($i) => (float) ($i->cbm ?? 0)),
|
'cbm' => (float) $items->sum(fn($i) => (float) ($i->cbm ?? 0)),
|
||||||
'ttl_cbm' => (float) $items->sum(fn($i) => (float) ($i->ttl_cbm ?? 0)),
|
'ttl_cbm' => (float) $items->sum(fn($i) => (float) ($i->ttl_cbm ?? 0)),
|
||||||
'kg' => (float) $items->sum(fn($i) => (float) ($i->kg ?? 0)),
|
'kg' => (float) $items->sum(fn($i) => (float) ($i->kg ?? 0)),
|
||||||
@@ -255,9 +237,6 @@ class AdminOrderController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate order id (keeps old format)
|
|
||||||
*/
|
|
||||||
private function generateOrderId()
|
private function generateOrderId()
|
||||||
{
|
{
|
||||||
$year = date('y');
|
$year = date('y');
|
||||||
@@ -269,9 +248,9 @@ class AdminOrderController extends Controller
|
|||||||
return $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT);
|
return $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------
|
/* ---------------------------
|
||||||
// INVOICE CREATION (optional helper used by store/finish)
|
* INVOICE CREATION HELPERS
|
||||||
// ---------------------------
|
* ---------------------------*/
|
||||||
private function createInvoice(Order $order)
|
private function createInvoice(Order $order)
|
||||||
{
|
{
|
||||||
$invoiceNumber = $this->generateInvoiceNumber();
|
$invoiceNumber = $this->generateInvoiceNumber();
|
||||||
@@ -302,7 +281,6 @@ class AdminOrderController extends Controller
|
|||||||
'pdf_path' => null,
|
'pdf_path' => null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// clone order items into invoice items
|
|
||||||
foreach ($order->items as $item) {
|
foreach ($order->items as $item) {
|
||||||
InvoiceItem::create([
|
InvoiceItem::create([
|
||||||
'invoice_id' => $invoice->id,
|
'invoice_id' => $invoice->id,
|
||||||
@@ -350,198 +328,277 @@ class AdminOrderController extends Controller
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function popup($id)
|
/* ---------------------------
|
||||||
|
* SEE (detailed)
|
||||||
|
* ---------------------------*/
|
||||||
|
public function see($id)
|
||||||
{
|
{
|
||||||
// Load order with items + markList
|
$order = Order::with([
|
||||||
$order = Order::with(['items', 'markList'])->findOrFail($id);
|
'markList',
|
||||||
|
'items',
|
||||||
|
'invoice.items',
|
||||||
|
'shipments' => function ($q) use ($id) {
|
||||||
|
$q->whereHas('orders', function ($oq) use ($id) {
|
||||||
|
$oq->where('orders.id', $id)
|
||||||
|
->whereNull('orders.deleted_at');
|
||||||
|
})->with([
|
||||||
|
'orders' => function ($oq) use ($id) {
|
||||||
|
$oq->where('orders.id', $id)
|
||||||
|
->whereNull('orders.deleted_at')
|
||||||
|
->with('items');
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
])->findOrFail($id);
|
||||||
|
|
||||||
// Fetch user based on markList customer_id (same as show method)
|
$orderData = [
|
||||||
$user = null;
|
'order_id' => $order->order_id,
|
||||||
if ($order->markList && $order->markList->customer_id) {
|
'status' => $order->status,
|
||||||
$user = \App\Models\User::where('customer_id', $order->markList->customer_id)->first();
|
'totals' => [
|
||||||
|
'ctn' => $order->ctn,
|
||||||
|
'qty' => $order->qty,
|
||||||
|
'ttl_qty' => $order->ttl_qty,
|
||||||
|
'cbm' => $order->cbm,
|
||||||
|
'ttl_cbm' => $order->ttl_cbm,
|
||||||
|
'kg' => $order->kg,
|
||||||
|
'ttl_kg' => $order->ttl_kg,
|
||||||
|
'amount' => $order->ttl_amount,
|
||||||
|
],
|
||||||
|
'items' => $order->items,
|
||||||
|
];
|
||||||
|
|
||||||
|
$shipmentsData = [];
|
||||||
|
foreach ($order->shipments as $shipment) {
|
||||||
|
$shipmentOrders = [];
|
||||||
|
$totals = [
|
||||||
|
'ctn' => 0, 'qty' => 0, 'ttl_qty' => 0,
|
||||||
|
'cbm' => 0, 'ttl_cbm' => 0,
|
||||||
|
'kg' => 0, 'ttl_kg' => 0,
|
||||||
|
'amount' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($shipment->orders as $shipOrder) {
|
||||||
|
foreach ($shipOrder->items as $item) {
|
||||||
|
$shipmentOrders[] = [
|
||||||
|
'order_id' => $shipOrder->order_id,
|
||||||
|
'origin' => $shipOrder->origin,
|
||||||
|
'destination' => $shipOrder->destination,
|
||||||
|
'description' => $item->description,
|
||||||
|
'ctn' => $item->ctn,
|
||||||
|
'qty' => $item->qty,
|
||||||
|
'ttl_qty' => $item->ttl_qty,
|
||||||
|
'amount' => $item->ttl_amount,
|
||||||
|
];
|
||||||
|
|
||||||
|
$totals['ctn'] += $item->ctn;
|
||||||
|
$totals['qty'] += $item->qty;
|
||||||
|
$totals['ttl_qty'] += $item->ttl_qty;
|
||||||
|
$totals['cbm'] += $item->cbm;
|
||||||
|
$totals['ttl_cbm'] += $item->ttl_cbm;
|
||||||
|
$totals['kg'] += $item->kg;
|
||||||
|
$totals['ttl_kg'] += $item->ttl_kg;
|
||||||
|
$totals['amount'] += $item->ttl_amount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('admin.popup', compact('order', 'user'));
|
if (empty($shipmentOrders)) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$shipmentsData[] = [
|
||||||
|
'shipment_id' => $shipment->shipment_id,
|
||||||
|
'status' => $shipment->status,
|
||||||
public function resetTemp()
|
'date' => $shipment->shipment_date,
|
||||||
{
|
'total_orders' => 1,
|
||||||
session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']);
|
'orders' => $shipmentOrders,
|
||||||
|
'totals' => $totals,
|
||||||
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
|
];
|
||||||
->with('success', 'Order reset successfully.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$invoiceData = null;
|
||||||
|
if ($order->invoice) {
|
||||||
|
$invoice = $order->invoice;
|
||||||
|
$invoiceData = [
|
||||||
|
'invoice_no' => $invoice->invoice_number,
|
||||||
|
'status' => $invoice->status,
|
||||||
|
'invoice_date' => $invoice->invoice_date,
|
||||||
|
'due_date' => $invoice->due_date,
|
||||||
|
'customer' => [
|
||||||
|
'name' => $invoice->customer_name,
|
||||||
|
'mobile' => $invoice->customer_mobile,
|
||||||
|
'email' => $invoice->customer_email,
|
||||||
|
'address' => $invoice->customer_address,
|
||||||
|
'pincode' => $invoice->pincode,
|
||||||
|
],
|
||||||
|
'items' => $invoice->items,
|
||||||
|
'summary' => [
|
||||||
|
'amount' => $invoice->final_amount,
|
||||||
|
'cgst' => 0,
|
||||||
|
'sgst' => 0,
|
||||||
|
'total' => $invoice->final_amount_with_gst,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin.see_order', compact(
|
||||||
|
'order',
|
||||||
|
'orderData',
|
||||||
|
'shipmentsData',
|
||||||
|
'invoiceData'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------
|
||||||
|
* FILTERED LIST + EXPORTS
|
||||||
|
* ---------------------------*/
|
||||||
public function orderShow()
|
public function orderShow()
|
||||||
{
|
{
|
||||||
$orders = Order::with([
|
$orders = Order::with([
|
||||||
'markList', // company, customer, origin, destination, date
|
'markList',
|
||||||
'shipments', // shipment_id, shipment_date, status
|
'shipments',
|
||||||
'invoice' // invoice number, dates, amounts, status
|
'invoice'
|
||||||
])
|
])->latest('id')->get();
|
||||||
->latest('id') // show latest orders first
|
|
||||||
->get();
|
|
||||||
|
|
||||||
return view('admin.orders', compact('orders'));
|
return view('admin.orders', compact('orders'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// inside AdminOrderController
|
|
||||||
|
|
||||||
private function buildOrdersQueryFromRequest(Request $request)
|
private function buildOrdersQueryFromRequest(Request $request)
|
||||||
{
|
{
|
||||||
$query = Order::with(['markList', 'invoice', 'shipments']);
|
$query = Order::query()
|
||||||
|
->with(['markList', 'invoice', 'shipments']);
|
||||||
|
|
||||||
// Search across order_id, markList.company_name, markList.customer_id, invoice.invoice_number
|
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
$search = $request->search;
|
$search = trim($request->search);
|
||||||
$query->where(function($q) use ($search) {
|
|
||||||
$q->where('order_id', 'like', "%{$search}%")
|
$query->where(function ($q) use ($search) {
|
||||||
->orWhereHas('markList', function($q2) use ($search) {
|
$q->where('orders.order_id', 'like', "%{$search}%")
|
||||||
|
->orWhereHas('markList', function ($q2) use ($search) {
|
||||||
$q2->where('company_name', 'like', "%{$search}%")
|
$q2->where('company_name', 'like', "%{$search}%")
|
||||||
->orWhere('customer_id', 'like', "%{$search}%");
|
->orWhere('customer_id', 'like', "%{$search}%")
|
||||||
|
->orWhere('origin', 'like', "%{$search}%")
|
||||||
|
->orWhere('destination', 'like', "%{$search}%");
|
||||||
})
|
})
|
||||||
->orWhereHas('invoice', function($q3) use ($search) {
|
->orWhereHas('invoice', function ($q3) use ($search) {
|
||||||
$q3->where('invoice_number', 'like', "%{$search}%");
|
$q3->where('invoice_number', 'like', "%{$search}%");
|
||||||
|
})
|
||||||
|
->orWhereHas('shipments', function ($q4) use ($search) {
|
||||||
|
$q4->where('shipments.shipment_id', 'like', "%{$search}%");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invoice status filter
|
|
||||||
if ($request->filled('status')) {
|
if ($request->filled('status')) {
|
||||||
$query->whereHas('invoice', function($q) use ($request) {
|
$query->where(function ($q) use ($request) {
|
||||||
$q->where('status', $request->status);
|
$q->whereHas('invoice', function ($q2) use ($request) {
|
||||||
|
$q2->where('status', $request->status);
|
||||||
|
})->orWhereDoesntHave('invoice');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shipment status filter
|
|
||||||
if ($request->filled('shipment')) {
|
if ($request->filled('shipment')) {
|
||||||
$query->whereHas('shipments', function($q) use ($request) {
|
$query->where(function ($q) use ($request) {
|
||||||
$q->where('status', $request->shipment);
|
$q->whereHas('shipments', function ($q2) use ($request) {
|
||||||
|
$q2->where('status', $request->shipment);
|
||||||
|
})->orWhereDoesntHave('shipments');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// optional ordering
|
if ($request->filled('from_date')) {
|
||||||
$query->latest('id');
|
$query->whereDate('orders.created_at', '>=', $request->from_date);
|
||||||
|
}
|
||||||
|
|
||||||
return $query;
|
if ($request->filled('to_date')) {
|
||||||
|
$query->whereDate('orders.created_at', '<=', $request->to_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->latest('orders.id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function downloadPdf(Request $request)
|
public function downloadPdf(Request $request)
|
||||||
{
|
{
|
||||||
// Build same filtered query used for table
|
$orders = $this->buildOrdersQueryFromRequest($request)->get();
|
||||||
$query = $this->buildOrdersQueryFromRequest($request);
|
|
||||||
|
|
||||||
$orders = $query->get();
|
|
||||||
|
|
||||||
// optional: pass filters to view for header
|
|
||||||
$filters = [
|
$filters = [
|
||||||
'search' => $request->search ?? null,
|
'search' => $request->search,
|
||||||
'status' => $request->status ?? null,
|
'status' => $request->status,
|
||||||
'shipment' => $request->shipment ?? null,
|
'shipment' => $request->shipment,
|
||||||
|
'from' => $request->from_date,
|
||||||
|
'to' => $request->to_date,
|
||||||
];
|
];
|
||||||
|
|
||||||
$pdf = PDF::loadView('admin.orders.pdf', compact('orders', 'filters'))
|
$pdf = PDF::loadView('admin.orders.pdf', compact('orders', 'filters'))
|
||||||
->setPaper('a4', 'landscape'); // adjust if needed
|
->setPaper('a4', 'landscape');
|
||||||
|
|
||||||
$fileName = 'orders-report'
|
return $pdf->download(
|
||||||
. ($filters['status'] ? "-{$filters['status']}" : '')
|
'orders-report-' . now()->format('Y-m-d') . '.pdf'
|
||||||
. '-' . date('Y-m-d') . '.pdf';
|
);
|
||||||
|
|
||||||
return $pdf->download($fileName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function downloadExcel(Request $request)
|
public function downloadExcel(Request $request)
|
||||||
{
|
{
|
||||||
// pass request to OrdersExport which will build Filtered query internally
|
return Excel::download(
|
||||||
return Excel::download(new OrdersExport($request), 'orders-report-' . date('Y-m-d') . '.xlsx');
|
new OrdersExport($request),
|
||||||
|
'orders-report-' . now()->format('Y-m-d') . '.xlsx'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------
|
||||||
|
* NEW: Create Order + Invoice directly from popup
|
||||||
|
* route: admin.orders.temp.add (Create New Order form)
|
||||||
|
* --------------------------------------------------*/
|
||||||
public function addTempItem(Request $request)
|
public function addTempItem(Request $request)
|
||||||
{
|
{
|
||||||
// Validate item fields
|
// 1) order-level fields
|
||||||
$item = $request->validate([
|
|
||||||
'mark_no' => 'required',
|
|
||||||
'origin' => 'required',
|
|
||||||
'destination' => 'required',
|
|
||||||
'description' => 'required|string',
|
|
||||||
'ctn' => 'nullable|numeric',
|
|
||||||
'qty' => 'nullable|numeric',
|
|
||||||
'ttl_qty' => 'nullable|numeric',
|
|
||||||
'unit' => 'nullable|string',
|
|
||||||
'price' => 'nullable|numeric',
|
|
||||||
'ttl_amount' => 'nullable|numeric',
|
|
||||||
'cbm' => 'nullable|numeric',
|
|
||||||
'ttl_cbm' => 'nullable|numeric',
|
|
||||||
'kg' => 'nullable|numeric',
|
|
||||||
'ttl_kg' => 'nullable|numeric',
|
|
||||||
'shop_no' => 'nullable|string',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// ❌ Prevent changing mark_no once first item added
|
|
||||||
if (session()->has('mark_no') && session('mark_no') != $request->mark_no) {
|
|
||||||
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
|
|
||||||
->with('error', 'You must finish or clear the current order before changing Mark No.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save mark, origin, destination ONLY ONCE
|
|
||||||
if (!session()->has('mark_no')) {
|
|
||||||
session([
|
|
||||||
'mark_no' => $request->mark_no,
|
|
||||||
'origin' => $request->origin,
|
|
||||||
'destination' => $request->destination
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ❌ DO NOT overwrite these values again
|
|
||||||
// session(['mark_no' => $request->mark_no]);
|
|
||||||
// session(['origin' => $request->origin]);
|
|
||||||
// session(['destination' => $request->destination]);
|
|
||||||
|
|
||||||
// Add new sub-item into session
|
|
||||||
session()->push('temp_order_items', $item);
|
|
||||||
|
|
||||||
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
|
|
||||||
|
|
||||||
->with('success', 'Item added.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// STEP 3 : FINISH ORDER
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
public function finishOrder(Request $request)
|
|
||||||
{
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'mark_no' => 'required',
|
'mark_no' => 'required',
|
||||||
'origin' => 'required',
|
'origin' => 'required',
|
||||||
'destination' => 'required',
|
'destination' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$items = session('temp_order_items', []);
|
// 2) multi-row items
|
||||||
|
$items = $request->validate([
|
||||||
|
'items' => 'required|array',
|
||||||
|
'items.*.description' => 'required|string',
|
||||||
|
'items.*.ctn' => 'nullable|numeric',
|
||||||
|
'items.*.qty' => 'nullable|numeric',
|
||||||
|
|
||||||
|
'items.*.unit' => 'nullable|string',
|
||||||
|
'items.*.price' => 'nullable|numeric',
|
||||||
|
|
||||||
|
'items.*.cbm' => 'nullable|numeric',
|
||||||
|
|
||||||
|
'items.*.kg' => 'nullable|numeric',
|
||||||
|
|
||||||
|
'items.*.shop_no' => 'nullable|string',
|
||||||
|
])['items'];
|
||||||
|
|
||||||
|
// रिकामे rows काढा
|
||||||
|
$items = array_filter($items, function ($row) {
|
||||||
|
return trim($row['description'] ?? '') !== '';
|
||||||
|
});
|
||||||
|
|
||||||
if (empty($items)) {
|
if (empty($items)) {
|
||||||
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
|
return back()->with('error', 'Add at least one item.');
|
||||||
->with('error', 'Add at least one item before finishing.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =======================
|
// ✅ BACKEND CALCULATION (DO NOT TRUST FRONTEND)
|
||||||
// GENERATE ORDER ID
|
foreach ($items as &$item) {
|
||||||
// =======================
|
|
||||||
$year = date('y');
|
|
||||||
$prefix = "KNT-$year-";
|
|
||||||
|
|
||||||
$lastOrder = Order::latest('id')->first();
|
$ctn = (float) ($item['ctn'] ?? 0);
|
||||||
$nextNumber = $lastOrder ? intval(substr($lastOrder->order_id, -8)) + 1 : 1;
|
$qty = (float) ($item['qty'] ?? 0);
|
||||||
|
$price = (float) ($item['price'] ?? 0);
|
||||||
|
$cbm = (float) ($item['cbm'] ?? 0);
|
||||||
|
$kg = (float) ($item['kg'] ?? 0);
|
||||||
|
|
||||||
$orderId = $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT);
|
// Calculated fields
|
||||||
|
$item['ttl_qty'] = $ctn * $qty;
|
||||||
|
$item['ttl_amount'] = $item['ttl_qty'] * $price;
|
||||||
|
$item['ttl_cbm'] = $cbm * $ctn;
|
||||||
|
$item['ttl_kg'] = $ctn * $kg;
|
||||||
|
}
|
||||||
|
unset($item); // VERY IMPORTANT
|
||||||
|
|
||||||
// =======================
|
|
||||||
// TOTAL SUMS
|
// 3) totals
|
||||||
// =======================
|
|
||||||
$total_ctn = array_sum(array_column($items, 'ctn'));
|
$total_ctn = array_sum(array_column($items, 'ctn'));
|
||||||
$total_qty = array_sum(array_column($items, 'qty'));
|
$total_qty = array_sum(array_column($items, 'qty'));
|
||||||
$total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
|
$total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
|
||||||
@@ -551,14 +608,15 @@ class AdminOrderController extends Controller
|
|||||||
$total_kg = array_sum(array_column($items, 'kg'));
|
$total_kg = array_sum(array_column($items, 'kg'));
|
||||||
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
|
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
|
||||||
|
|
||||||
// =======================
|
// 4) order id generate
|
||||||
// CREATE ORDER
|
$orderId = $this->generateOrderId();
|
||||||
// =======================
|
|
||||||
|
// 5) order create
|
||||||
$order = Order::create([
|
$order = Order::create([
|
||||||
'order_id' => $orderId,
|
'order_id' => $orderId,
|
||||||
'mark_no' => $request->mark_no,
|
'mark_no' => $request->mark_no,
|
||||||
'origin' => $request->origin,
|
'origin' => $request->origin,
|
||||||
'destination' => $request->destination,
|
'destination'=> $request->destination,
|
||||||
'ctn' => $total_ctn,
|
'ctn' => $total_ctn,
|
||||||
'qty' => $total_qty,
|
'qty' => $total_qty,
|
||||||
'ttl_qty' => $total_ttl_qty,
|
'ttl_qty' => $total_ttl_qty,
|
||||||
@@ -567,14 +625,14 @@ class AdminOrderController extends Controller
|
|||||||
'ttl_cbm' => $total_ttl_cbm,
|
'ttl_cbm' => $total_ttl_cbm,
|
||||||
'kg' => $total_kg,
|
'kg' => $total_kg,
|
||||||
'ttl_kg' => $total_ttl_kg,
|
'ttl_kg' => $total_ttl_kg,
|
||||||
'status' => 'pending',
|
'status' => 'order_placed',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// SAVE ORDER ITEMS
|
// 6) order items
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
OrderItem::create([
|
OrderItem::create([
|
||||||
'order_id' => $order->id,
|
'order_id' => $order->id,
|
||||||
'description' => $item['description'],
|
'description'=> $item['description'],
|
||||||
'ctn' => $item['ctn'],
|
'ctn' => $item['ctn'],
|
||||||
'qty' => $item['qty'],
|
'qty' => $item['qty'],
|
||||||
'ttl_qty' => $item['ttl_qty'],
|
'ttl_qty' => $item['ttl_qty'],
|
||||||
@@ -589,9 +647,8 @@ class AdminOrderController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =======================
|
// 7) invoice number
|
||||||
// INVOICE CREATION START
|
$invoiceNumber = $this->generateInvoiceNumber();
|
||||||
// =======================
|
|
||||||
|
|
||||||
// 1. Auto-generate invoice number
|
// 1. Auto-generate invoice number
|
||||||
// $lastInvoice = \App\Models\Invoice::latest()->first();
|
// $lastInvoice = \App\Models\Invoice::latest()->first();
|
||||||
@@ -670,34 +727,52 @@ class AdminOrderController extends Controller
|
|||||||
->with('success', 'Order + Invoice created successfully.');
|
->with('success', 'Order + Invoice created successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------
|
/* ---------------------------
|
||||||
// ORDER CRUD: update / destroy
|
* UPDATE ORDER ITEM (existing orders)
|
||||||
// ---------------------------
|
* ---------------------------*/
|
||||||
public function updateItem(Request $request, $id)
|
public function updateItem(Request $request, $id)
|
||||||
{
|
{
|
||||||
$item = OrderItem::findOrFail($id);
|
$item = OrderItem::findOrFail($id);
|
||||||
$order = $item->order;
|
$order = $item->order;
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'description' => 'required|string',
|
||||||
|
'ctn' => 'nullable|numeric',
|
||||||
|
'qty' => 'nullable|numeric',
|
||||||
|
'unit' => 'nullable|string',
|
||||||
|
'price' => 'nullable|numeric',
|
||||||
|
'cbm' => 'nullable|numeric',
|
||||||
|
'kg' => 'nullable|numeric',
|
||||||
|
'shop_no' => 'nullable|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ✅ BACKEND CALCULATION
|
||||||
|
$ctn = (float) ($request->ctn ?? 0);
|
||||||
|
$qty = (float) ($request->qty ?? 0);
|
||||||
|
$price = (float) ($request->price ?? 0);
|
||||||
|
$cbm = (float) ($request->cbm ?? 0);
|
||||||
|
$kg = (float) ($request->kg ?? 0);
|
||||||
|
|
||||||
$item->update([
|
$item->update([
|
||||||
'description' => $request->description,
|
'description' => $request->description,
|
||||||
'ctn' => $request->ctn,
|
'ctn' => $ctn,
|
||||||
'qty' => $request->qty,
|
'qty' => $qty,
|
||||||
'ttl_qty' => $request->ttl_qty,
|
'ttl_qty' => $ctn * $qty,
|
||||||
'unit' => $request->unit,
|
'unit' => $request->unit,
|
||||||
'price' => $request->price,
|
'price' => $price,
|
||||||
'ttl_amount' => $request->ttl_amount,
|
'ttl_amount' => ($ctn * $qty) * $price,
|
||||||
'cbm' => $request->cbm,
|
'cbm' => $cbm,
|
||||||
'ttl_cbm' => $request->ttl_cbm,
|
'ttl_cbm' => $cbm * $ctn,
|
||||||
'kg' => $request->kg,
|
'kg' => $kg,
|
||||||
'ttl_kg' => $request->ttl_kg,
|
'ttl_kg' => $ctn * $kg,
|
||||||
'shop_no' => $request->shop_no,
|
'shop_no' => $request->shop_no,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->recalcTotals($order);
|
$this->recalcTotals($order);
|
||||||
$this->updateInvoiceFromOrder($order); // <-- NEW
|
$this->updateInvoiceFromOrder($order);
|
||||||
|
|
||||||
return back()->with('success', 'Item updated successfully');
|
return back()->with('success', 'Item updated successfully');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private function updateInvoiceFromOrder(Order $order)
|
private function updateInvoiceFromOrder(Order $order)
|
||||||
@@ -705,20 +780,17 @@ class AdminOrderController extends Controller
|
|||||||
$invoice = Invoice::where('order_id', $order->id)->first();
|
$invoice = Invoice::where('order_id', $order->id)->first();
|
||||||
|
|
||||||
if (!$invoice) {
|
if (!$invoice) {
|
||||||
return; // No invoice exists (should not happen normally)
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update invoice totals
|
|
||||||
$invoice->final_amount = $order->ttl_amount;
|
$invoice->final_amount = $order->ttl_amount;
|
||||||
$invoice->gst_percent = 0;
|
$invoice->gst_percent = 0;
|
||||||
$invoice->gst_amount = 0;
|
$invoice->gst_amount = 0;
|
||||||
$invoice->final_amount_with_gst = $order->ttl_amount;
|
$invoice->final_amount_with_gst = $order->ttl_amount;
|
||||||
$invoice->save();
|
$invoice->save();
|
||||||
|
|
||||||
// Delete old invoice items
|
|
||||||
InvoiceItem::where('invoice_id', $invoice->id)->delete();
|
InvoiceItem::where('invoice_id', $invoice->id)->delete();
|
||||||
|
|
||||||
// Re-create invoice items from updated order items
|
|
||||||
foreach ($order->items as $item) {
|
foreach ($order->items as $item) {
|
||||||
InvoiceItem::create([
|
InvoiceItem::create([
|
||||||
'invoice_id' => $invoice->id,
|
'invoice_id' => $invoice->id,
|
||||||
@@ -738,4 +810,35 @@ class AdminOrderController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function uploadExcelPreview(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$request->validate([
|
||||||
|
'excel' => 'required|file|mimes:xlsx,xls'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$import = new OrderItemsPreviewImport();
|
||||||
|
Excel::import($import, $request->file('excel'));
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'items' => $import->rows
|
||||||
|
]);
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Invalid Excel file format'
|
||||||
|
], 422);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\Log::error($e);
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Server error'
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -55,6 +55,7 @@ class AdminStaffController extends Controller
|
|||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 1️⃣ Create staff WITHOUT employee_id (ID not available yet)
|
||||||
$admin = Admin::create([
|
$admin = Admin::create([
|
||||||
'name' => $request->name,
|
'name' => $request->name,
|
||||||
'email' => $request->email,
|
'email' => $request->email,
|
||||||
@@ -69,23 +70,33 @@ class AdminStaffController extends Controller
|
|||||||
'status' => $request->status,
|
'status' => $request->status,
|
||||||
'additional_info' => $request->additional_info,
|
'additional_info' => $request->additional_info,
|
||||||
|
|
||||||
'username' => $request->username,
|
// username may be NULL here
|
||||||
|
'username' => $request->username ?: null,
|
||||||
'password' => Hash::make($request->password),
|
'password' => Hash::make($request->password),
|
||||||
'type' => 'staff',
|
'type' => 'staff',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Generate EMPLOYEE ID using admin ID (safe)
|
// 2️⃣ Generate EMPLOYEE ID
|
||||||
$employeeId = 'EMP' . str_pad($admin->id, 4, '0', STR_PAD_LEFT);
|
$employeeId = 'EMP' . str_pad($admin->id, 4, '0', STR_PAD_LEFT);
|
||||||
$admin->update(['employee_id' => $employeeId]);
|
|
||||||
|
|
||||||
// Assign permissions (if any)
|
// 3️⃣ Auto-generate username if left blank
|
||||||
|
$username = $request->username ?: strtolower($employeeId);
|
||||||
|
|
||||||
|
// 4️⃣ Update employee_id + username together
|
||||||
|
$admin->update([
|
||||||
|
'employee_id' => $employeeId,
|
||||||
|
'username' => $username,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 5️⃣ Assign permissions (if any)
|
||||||
if ($request->permissions) {
|
if ($request->permissions) {
|
||||||
$admin->givePermissionTo($request->permissions);
|
$admin->givePermissionTo($request->permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
DB::commit();
|
DB::commit();
|
||||||
|
|
||||||
return redirect()->route('admin.staff.index')
|
return redirect()
|
||||||
|
->route('admin.staff.index')
|
||||||
->with('success', 'Staff created successfully.');
|
->with('success', 'Staff created successfully.');
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
@@ -94,6 +105,7 @@ class AdminStaffController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function edit($id)
|
public function edit($id)
|
||||||
{
|
{
|
||||||
$staff = Admin::where('type', 'staff')->findOrFail($id);
|
$staff = Admin::where('type', 'staff')->findOrFail($id);
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ class ShipmentController extends Controller
|
|||||||
$usedOrderIds = ShipmentItem::pluck('order_id')->toArray();
|
$usedOrderIds = ShipmentItem::pluck('order_id')->toArray();
|
||||||
|
|
||||||
// 2) Load available orders (not used in any shipment)
|
// 2) Load available orders (not used in any shipment)
|
||||||
$availableOrders = Order::whereNotIn('id', $usedOrderIds)->get();
|
$availableOrders = Order::whereNotIn('id', $usedOrderIds)
|
||||||
|
->where('status', '!=', 'order_placed')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 3) Load all shipments for listing
|
// 3) Load all shipments for listing
|
||||||
$shipments = Shipment::latest()->get();
|
$shipments = Shipment::latest()->get();
|
||||||
@@ -65,6 +69,16 @@ class ShipmentController extends Controller
|
|||||||
// CALCULATE TOTALS
|
// CALCULATE TOTALS
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
$orders = Order::whereIn('id', $request->order_ids)->get();
|
$orders = Order::whereIn('id', $request->order_ids)->get();
|
||||||
|
foreach ($orders as $order) {
|
||||||
|
if ($order->status === 'order_placed') {
|
||||||
|
return back()->with(
|
||||||
|
'error',
|
||||||
|
"Order {$order->order_id} is not ready for shipment"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$total_ctn = $orders->sum('ctn');
|
$total_ctn = $orders->sum('ctn');
|
||||||
$total_qty = $orders->sum('qty');
|
$total_qty = $orders->sum('qty');
|
||||||
@@ -82,7 +96,7 @@ class ShipmentController extends Controller
|
|||||||
'shipment_id' => $newShipmentId,
|
'shipment_id' => $newShipmentId,
|
||||||
'origin' => $request->origin,
|
'origin' => $request->origin,
|
||||||
'destination' => $request->destination,
|
'destination' => $request->destination,
|
||||||
'status' => Shipment::STATUS_PENDING,
|
'status' => Shipment::STATUS_SHIPMENT_READY,
|
||||||
'shipment_date' => $request->shipment_date,
|
'shipment_date' => $request->shipment_date,
|
||||||
|
|
||||||
'total_ctn' => $total_ctn,
|
'total_ctn' => $total_ctn,
|
||||||
@@ -135,28 +149,34 @@ class ShipmentController extends Controller
|
|||||||
* Update Shipment status from action button
|
* Update Shipment status from action button
|
||||||
*/
|
*/
|
||||||
public function updateStatus(Request $request)
|
public function updateStatus(Request $request)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'shipment_id' => 'required|exists:shipments,id',
|
'shipment_id' => 'required|exists:shipments,id',
|
||||||
'status' => 'required|string'
|
'status' => 'required|string'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 1) Update shipment status
|
|
||||||
$shipment = Shipment::findOrFail($request->shipment_id);
|
$shipment = Shipment::findOrFail($request->shipment_id);
|
||||||
$shipment->status = $request->status;
|
$shipment->status = $request->status;
|
||||||
$shipment->save();
|
$shipment->save();
|
||||||
|
|
||||||
// 2) Update ALL related orders' status
|
// ✅ Sync shipment status to orders ONLY after shipment exists
|
||||||
foreach ($shipment->orders as $order) {
|
foreach ($shipment->orders as $order) {
|
||||||
$order->status = $shipment->status; // status is string: pending, in_transit, dispatched, delivered
|
|
||||||
|
// Prevent rollback or overwrite
|
||||||
|
if ($order->status === 'delivered') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$order->status = $shipment->status;
|
||||||
$order->save();
|
$order->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->back()->with(
|
return redirect()->back()->with(
|
||||||
'success',
|
'success',
|
||||||
"Shipment status updated to {$shipment->statusLabel()} and related orders updated."
|
"Shipment status updated to {$shipment->statusLabel()}."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update shipment details
|
* Update shipment details
|
||||||
@@ -224,5 +244,95 @@ class ShipmentController extends Controller
|
|||||||
|
|
||||||
return view('admin.view_shipment', compact('shipment', 'dummyData'));
|
return view('admin.view_shipment', compact('shipment', 'dummyData'));
|
||||||
}
|
}
|
||||||
|
// App\Models\Shipment.php
|
||||||
|
|
||||||
|
public function orders()
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(\App\Models\Order::class, 'shipment_items', 'shipment_id', 'order_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeOrder(Shipment $shipment, Order $order)
|
||||||
|
{
|
||||||
|
// Remove row from pivot table shipment_items
|
||||||
|
ShipmentItem::where('shipment_id', $shipment->id)
|
||||||
|
->where('order_id', $order->id)
|
||||||
|
->delete(); // removes link shipment <-> order [web:41][web:45]
|
||||||
|
|
||||||
|
// Recalculate totals on this shipment (optional but recommended)
|
||||||
|
$orders = Order::whereIn(
|
||||||
|
'id',
|
||||||
|
ShipmentItem::where('shipment_id', $shipment->id)->pluck('order_id')
|
||||||
|
)->get();
|
||||||
|
|
||||||
|
$shipment->total_ctn = $orders->sum('ctn');
|
||||||
|
$shipment->total_qty = $orders->sum('qty');
|
||||||
|
$shipment->total_ttl_qty = $orders->sum('ttl_qty');
|
||||||
|
$shipment->total_cbm = $orders->sum('cbm');
|
||||||
|
$shipment->total_ttl_cbm = $orders->sum('ttl_cbm');
|
||||||
|
$shipment->total_kg = $orders->sum('kg');
|
||||||
|
$shipment->total_ttl_kg = $orders->sum('ttl_kg');
|
||||||
|
$shipment->total_amount = $orders->sum('ttl_amount');
|
||||||
|
$shipment->save();
|
||||||
|
|
||||||
|
// Redirect back to preview page where your blade is loaded
|
||||||
|
return redirect()
|
||||||
|
->route('admin.shipments.dummy', $shipment->id)
|
||||||
|
->with('success', 'Order removed from shipment successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addOrders(Request $request, Shipment $shipment)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'order_ids' => 'required|array|min:1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$orders = Order::whereIn('id', $request->order_ids)->get();
|
||||||
|
|
||||||
|
foreach ($orders as $order) {
|
||||||
|
|
||||||
|
if ($order->status === 'order_placed') {
|
||||||
|
return back()->with(
|
||||||
|
'error',
|
||||||
|
"Order {$order->order_id} is not ready for shipment"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Prevent duplicates
|
||||||
|
if (ShipmentItem::where('order_id', $order->id)->exists()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShipmentItem::create([
|
||||||
|
'shipment_id' => $shipment->id,
|
||||||
|
'order_id' => $order->id,
|
||||||
|
'order_ctn' => $order->ctn,
|
||||||
|
'order_qty' => $order->qty,
|
||||||
|
'order_ttl_qty' => $order->ttl_qty,
|
||||||
|
'order_ttl_amount' => $order->ttl_amount,
|
||||||
|
'order_ttl_kg' => $order->ttl_kg,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recalculate totals
|
||||||
|
$orderIds = ShipmentItem::where('shipment_id', $shipment->id)->pluck('order_id');
|
||||||
|
$allOrders = Order::whereIn('id', $orderIds)->get();
|
||||||
|
|
||||||
|
$shipment->update([
|
||||||
|
'total_ctn' => $allOrders->sum('ctn'),
|
||||||
|
'total_qty' => $allOrders->sum('qty'),
|
||||||
|
'total_ttl_qty' => $allOrders->sum('ttl_qty'),
|
||||||
|
'total_cbm' => $allOrders->sum('cbm'),
|
||||||
|
'total_ttl_cbm' => $allOrders->sum('ttl_cbm'),
|
||||||
|
'total_kg' => $allOrders->sum('kg'),
|
||||||
|
'total_ttl_kg' => $allOrders->sum('ttl_kg'),
|
||||||
|
'total_amount' => $allOrders->sum('ttl_amount'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->route('admin.shipments.dummy', $shipment->id)
|
||||||
|
->with('success', 'Orders added to shipment successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,12 @@ class UserRequestController extends Controller
|
|||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$requests = CustomerRequest::orderBy('id', 'desc')->get();
|
$requests = CustomerRequest::orderBy('id', 'desc')->get();
|
||||||
return view('admin.requests', compact('requests'));
|
$pendingProfileUpdates = \App\Models\UpdateRequest::where('status', 'pending')->count();
|
||||||
|
|
||||||
|
return view('admin.requests', compact(
|
||||||
|
'requests',
|
||||||
|
'pendingProfileUpdates'
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Approve user request
|
// Approve user request
|
||||||
|
|||||||
@@ -6,76 +6,48 @@ use Illuminate\Http\Request;
|
|||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth;
|
use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class UserAuthController extends Controller
|
class UserAuthController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
public function refreshToken()
|
public function refreshToken()
|
||||||
{
|
{
|
||||||
\Log::info('🔄 refreshToken() called');
|
Log::info('🔄 [JWT-REFRESH] called');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get current token
|
$newToken = JWTAuth::parseToken()->refresh();
|
||||||
$currentToken = JWTAuth::getToken();
|
|
||||||
|
|
||||||
if (!$currentToken) {
|
Log::info('✅ [JWT-REFRESH] Token refreshed');
|
||||||
\Log::warning('⚠ No token provided in refreshToken()');
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Token not provided',
|
|
||||||
], 401);
|
|
||||||
}
|
|
||||||
|
|
||||||
\Log::info('📥 Current Token:', ['token' => (string) $currentToken]);
|
|
||||||
|
|
||||||
// Try refreshing token
|
|
||||||
$newToken = JWTAuth::refresh($currentToken);
|
|
||||||
|
|
||||||
\Log::info('✅ Token refreshed successfully', ['new_token' => $newToken]);
|
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'token' => $newToken,
|
'token' => $newToken,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
} catch (\Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
|
} catch (\PHPOpenSourceSaver\JWTAuth\Exceptions\TokenExpiredException $e) {
|
||||||
\Log::error('❌ TokenExpiredException in refreshToken()', [
|
Log::warning('⛔ [JWT-REFRESH] Refresh TTL expired');
|
||||||
'message' => $e->getMessage(),
|
|
||||||
]);
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Token expired, cannot refresh.',
|
|
||||||
], 401);
|
|
||||||
|
|
||||||
} catch (\Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
|
|
||||||
\Log::error('❌ TokenInvalidException in refreshToken()', [
|
|
||||||
'message' => $e->getMessage(),
|
|
||||||
]);
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Invalid token.',
|
'message' => 'Refresh expired. Please login again.',
|
||||||
], 401);
|
|
||||||
|
|
||||||
} catch (\Tymon\JWTAuth\Exceptions\JWTException $e) {
|
|
||||||
\Log::error('❌ JWTException in refreshToken()', [
|
|
||||||
'message' => $e->getMessage(),
|
|
||||||
]);
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Could not refresh token.',
|
|
||||||
], 401);
|
], 401);
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Log::error('❌ General Exception in refreshToken()', [
|
Log::error('🔥 [JWT-REFRESH] Exception', [
|
||||||
'message' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
'trace' => $e->getTraceAsString(),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Unexpected error while refreshing token.',
|
'message' => 'Unable to refresh token.',
|
||||||
], 500);
|
], 401);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User Login
|
* User Login
|
||||||
|
|||||||
@@ -67,6 +67,12 @@ class ChatController extends Controller
|
|||||||
'sender_id' => auth()->id(),
|
'sender_id' => auth()->id(),
|
||||||
'sender_type' => \App\Models\User::class,
|
'sender_type' => \App\Models\User::class,
|
||||||
'message' => $request->message,
|
'message' => $request->message,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
'client_id' => $request->client_id, // ✅ ADD
|
||||||
|
'read_by_admin' => false,
|
||||||
|
'read_by_user' => true,
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
];
|
];
|
||||||
|
|
||||||
// Handle file upload
|
// Handle file upload
|
||||||
@@ -83,7 +89,11 @@ class ChatController extends Controller
|
|||||||
$message->load('sender');
|
$message->load('sender');
|
||||||
|
|
||||||
// Fire real-time event
|
// Fire real-time event
|
||||||
|
<<<<<<< HEAD
|
||||||
broadcast(new NewChatMessage($message))->toOthers();
|
broadcast(new NewChatMessage($message))->toOthers();
|
||||||
|
=======
|
||||||
|
broadcast(new NewChatMessage($message));
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
|
|||||||
@@ -289,6 +289,44 @@ public function invoiceDetails($invoice_id)
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function confirmOrder($order_id)
|
||||||
|
{
|
||||||
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
|
if (! $user) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Unauthorized'
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
$order = $user->orders()
|
||||||
|
->where('order_id', $order_id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
$order->status = 'order_confirmed';
|
||||||
|
$order->save();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Order confirmed successfully'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class JwtRefreshMiddleware
|
|||||||
{
|
{
|
||||||
public function handle($request, Closure $next)
|
public function handle($request, Closure $next)
|
||||||
{
|
{
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JWTAuth::parseToken()->authenticate();
|
JWTAuth::parseToken()->authenticate();
|
||||||
} catch (TokenExpiredException $e) {
|
} catch (TokenExpiredException $e) {
|
||||||
|
|||||||
26
app/Imports/OrderItemsPreviewImport.php
Normal file
26
app/Imports/OrderItemsPreviewImport.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Imports;
|
||||||
|
|
||||||
|
use Maatwebsite\Excel\Concerns\ToCollection;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
class OrderItemsPreviewImport implements ToCollection
|
||||||
|
{
|
||||||
|
public array $rows = [];
|
||||||
|
|
||||||
|
public function collection(Collection $collection)
|
||||||
|
{
|
||||||
|
$header = $collection->first()->map(fn ($h) => strtolower(trim($h)))->toArray();
|
||||||
|
|
||||||
|
foreach ($collection->skip(1) as $row) {
|
||||||
|
$item = [];
|
||||||
|
foreach ($header as $i => $key) {
|
||||||
|
$item[$key] = $row[$i] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($item['description'])) {
|
||||||
|
$this->rows[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ class Admin extends Authenticatable
|
|||||||
'name', 'email', 'password', 'username',
|
'name', 'email', 'password', 'username',
|
||||||
'phone', 'emergency_phone', 'address',
|
'phone', 'emergency_phone', 'address',
|
||||||
'role', 'department', 'designation', 'joining_date',
|
'role', 'department', 'designation', 'joining_date',
|
||||||
'status', 'additional_info', 'type', // admin/staff indicator
|
'status', 'additional_info', 'type','employee_id', // admin/staff indicator
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class ChatMessage extends Model
|
|||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
|
<<<<<<< HEAD
|
||||||
'ticket_id',
|
'ticket_id',
|
||||||
'sender_id',
|
'sender_id',
|
||||||
'sender_type', // user OR admin
|
'sender_type', // user OR admin
|
||||||
@@ -17,6 +18,19 @@ class ChatMessage extends Model
|
|||||||
'file_path',
|
'file_path',
|
||||||
'file_type',
|
'file_type',
|
||||||
];
|
];
|
||||||
|
=======
|
||||||
|
'ticket_id',
|
||||||
|
'sender_id',
|
||||||
|
'sender_type',
|
||||||
|
'message',
|
||||||
|
'file_path',
|
||||||
|
'file_type',
|
||||||
|
'read_by_admin',
|
||||||
|
'read_by_user',
|
||||||
|
'client_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ticket this message belongs to.
|
* The ticket this message belongs to.
|
||||||
|
|||||||
@@ -64,5 +64,25 @@ class Order extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const STATUS_LABELS = [
|
||||||
|
'order_placed' => 'Order Placed',
|
||||||
|
'order_confirmed' => 'Order Confirmed',
|
||||||
|
'supplier_warehouse' => 'Supplier Warehouse',
|
||||||
|
'consolidate_warehouse'=> 'Consolidate Warehouse',
|
||||||
|
'export_custom' => 'Export Custom',
|
||||||
|
'international_transit'=> 'International Transit',
|
||||||
|
'arrived_india' => 'Arrived at India',
|
||||||
|
'import_custom' => 'Import Custom',
|
||||||
|
'warehouse' => 'Warehouse',
|
||||||
|
'domestic_distribution'=> 'Domestic Distribution',
|
||||||
|
'out_for_delivery' => 'Out for Delivery',
|
||||||
|
'delivered' => 'Delivered',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function getStatusLabelAttribute()
|
||||||
|
{
|
||||||
|
return self::STATUS_LABELS[$this->status]
|
||||||
|
?? ucfirst(str_replace('_', ' ', $this->status));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,25 +45,6 @@ class Shipment extends Model
|
|||||||
return $this->belongsToMany(Order::class, 'shipment_items', 'shipment_id', 'order_id');
|
return $this->belongsToMany(Order::class, 'shipment_items', 'shipment_id', 'order_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// STATUS CONSTANTS
|
|
||||||
// ---------------------------
|
|
||||||
|
|
||||||
const STATUS_PENDING = 'pending';
|
|
||||||
const STATUS_IN_TRANSIT = 'in_transit';
|
|
||||||
const STATUS_DISPATCHED = 'dispatched';
|
|
||||||
const STATUS_DELIVERED = 'delivered';
|
|
||||||
|
|
||||||
public static function statusOptions()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
self::STATUS_PENDING => 'Pending',
|
|
||||||
self::STATUS_IN_TRANSIT => 'In Transit',
|
|
||||||
self::STATUS_DISPATCHED => 'Dispatched',
|
|
||||||
self::STATUS_DELIVERED => 'Delivered',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// HELPERS
|
// HELPERS
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
@@ -73,8 +54,38 @@ class Shipment extends Model
|
|||||||
return $this->items()->count();
|
return $this->items()->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// STATUS CONSTANTS (LOGISTICS FLOW)
|
||||||
|
// ---------------------------
|
||||||
|
const STATUS_SHIPMENT_READY = 'shipment_ready';
|
||||||
|
const STATUS_EXPORT_CUSTOM = 'export_custom';
|
||||||
|
const STATUS_INTERNATIONAL_TRANSIT= 'international_transit';
|
||||||
|
const STATUS_ARRIVED_INDIA = 'arrived_india';
|
||||||
|
const STATUS_IMPORT_CUSTOM = 'import_custom';
|
||||||
|
const STATUS_WAREHOUSE = 'warehouse';
|
||||||
|
const STATUS_DOMESTIC_DISTRIBUTION= 'domestic_distribution';
|
||||||
|
const STATUS_OUT_FOR_DELIVERY = 'out_for_delivery';
|
||||||
|
const STATUS_DELIVERED = 'delivered';
|
||||||
|
|
||||||
|
public static function statusOptions()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
self::STATUS_SHIPMENT_READY => 'Shipment Ready',
|
||||||
|
self::STATUS_EXPORT_CUSTOM => 'Export Custom',
|
||||||
|
self::STATUS_INTERNATIONAL_TRANSIT => 'International Transit',
|
||||||
|
self::STATUS_ARRIVED_INDIA => 'Arrived at India',
|
||||||
|
self::STATUS_IMPORT_CUSTOM => 'Import Custom',
|
||||||
|
self::STATUS_WAREHOUSE => 'Warehouse',
|
||||||
|
self::STATUS_DOMESTIC_DISTRIBUTION => 'Domestic Distribution',
|
||||||
|
self::STATUS_OUT_FOR_DELIVERY => 'Out for Delivery',
|
||||||
|
self::STATUS_DELIVERED => 'Delivered',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function statusLabel()
|
public function statusLabel()
|
||||||
{
|
{
|
||||||
return self::statusOptions()[$this->status] ?? ucfirst($this->status);
|
return self::statusOptions()[$this->status]
|
||||||
|
?? ucfirst(str_replace('_', ' ', $this->status));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,19 @@ public function invoices()
|
|||||||
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'id');
|
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// App\Models\User.php
|
||||||
|
|
||||||
|
|
||||||
|
public function invoiceInstallments()
|
||||||
|
{
|
||||||
|
return $this->hasManyThrough(
|
||||||
|
InvoiceInstallment::class,
|
||||||
|
Invoice::class,
|
||||||
|
'customer_id', // FK on invoices
|
||||||
|
'invoice_id' // FK on installments
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
22
app/Providers/BroadcastServiceProvider.php
Normal file
22
app/Providers/BroadcastServiceProvider.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Broadcast;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
class BroadcastServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
Broadcast::routes([
|
||||||
|
'middleware' => ['web'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 👇 FORCE admin guard for broadcasting
|
||||||
|
Auth::shouldUse('admin');
|
||||||
|
|
||||||
|
require base_path('routes/channels.php');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,13 +6,18 @@ use Illuminate\Foundation\Configuration\Middleware;
|
|||||||
|
|
||||||
return Application::configure(basePath: dirname(__DIR__))
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
->withRouting(
|
->withRouting(
|
||||||
web: __DIR__.'/../routes/web.php',
|
web: [
|
||||||
|
__DIR__.'/../routes/web.php',
|
||||||
|
__DIR__.'/../routes/channels.php',
|
||||||
|
],
|
||||||
|
api: __DIR__.'/../routes/api.php',
|
||||||
commands: __DIR__.'/../routes/console.php',
|
commands: __DIR__.'/../routes/console.php',
|
||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
//
|
//
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions) {
|
||||||
//
|
//
|
||||||
})->create();
|
})
|
||||||
|
->create();
|
||||||
|
|||||||
@@ -4,4 +4,6 @@ return [
|
|||||||
App\Providers\AppServiceProvider::class,
|
App\Providers\AppServiceProvider::class,
|
||||||
App\Providers\RouteServiceProvider::class,
|
App\Providers\RouteServiceProvider::class,
|
||||||
App\Providers\AuthServiceProvider::class,
|
App\Providers\AuthServiceProvider::class,
|
||||||
|
App\Providers\BroadcastServiceProvider::class,
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
63
composer.lock
generated
63
composer.lock
generated
@@ -2880,6 +2880,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "maennchen/zipstream-php",
|
"name": "maennchen/zipstream-php",
|
||||||
|
<<<<<<< HEAD
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -2890,21 +2891,45 @@
|
|||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/682f1098a8fddbaf43edac2306a691c7ad508ec5",
|
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/682f1098a8fddbaf43edac2306a691c7ad508ec5",
|
||||||
"reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5",
|
"reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5",
|
||||||
|
=======
|
||||||
|
"version": "3.1.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/maennchen/ZipStream-PHP.git",
|
||||||
|
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f",
|
||||||
|
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f",
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-mbstring": "*",
|
"ext-mbstring": "*",
|
||||||
"ext-zlib": "*",
|
"ext-zlib": "*",
|
||||||
|
<<<<<<< HEAD
|
||||||
"php-64bit": "^8.3"
|
"php-64bit": "^8.3"
|
||||||
|
=======
|
||||||
|
"php-64bit": "^8.2"
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"brianium/paratest": "^7.7",
|
"brianium/paratest": "^7.7",
|
||||||
"ext-zip": "*",
|
"ext-zip": "*",
|
||||||
|
<<<<<<< HEAD
|
||||||
"friendsofphp/php-cs-fixer": "^3.86",
|
"friendsofphp/php-cs-fixer": "^3.86",
|
||||||
"guzzlehttp/guzzle": "^7.5",
|
"guzzlehttp/guzzle": "^7.5",
|
||||||
"mikey179/vfsstream": "^1.6",
|
"mikey179/vfsstream": "^1.6",
|
||||||
"php-coveralls/php-coveralls": "^2.5",
|
"php-coveralls/php-coveralls": "^2.5",
|
||||||
"phpunit/phpunit": "^12.0",
|
"phpunit/phpunit": "^12.0",
|
||||||
|
=======
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.16",
|
||||||
|
"guzzlehttp/guzzle": "^7.5",
|
||||||
|
"mikey179/vfsstream": "^1.6",
|
||||||
|
"php-coveralls/php-coveralls": "^2.5",
|
||||||
|
"phpunit/phpunit": "^11.0",
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
"vimeo/psalm": "^6.0"
|
"vimeo/psalm": "^6.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
@@ -2946,7 +2971,11 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
|
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
|
||||||
|
<<<<<<< HEAD
|
||||||
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.1"
|
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.1"
|
||||||
|
=======
|
||||||
|
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2"
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -2954,7 +2983,11 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
<<<<<<< HEAD
|
||||||
"time": "2025-12-10T09:58:31+00:00"
|
"time": "2025-12-10T09:58:31+00:00"
|
||||||
|
=======
|
||||||
|
"time": "2025-01-27T12:07:53+00:00"
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "markbaker/complex",
|
"name": "markbaker/complex",
|
||||||
@@ -4181,6 +4214,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpoffice/phpspreadsheet",
|
"name": "phpoffice/phpspreadsheet",
|
||||||
|
<<<<<<< HEAD
|
||||||
"version": "1.30.2",
|
"version": "1.30.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -4191,6 +4225,18 @@
|
|||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/09cdde5e2f078b9a3358dd217e2c8cb4dac84be2",
|
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/09cdde5e2f078b9a3358dd217e2c8cb4dac84be2",
|
||||||
"reference": "09cdde5e2f078b9a3358dd217e2c8cb4dac84be2",
|
"reference": "09cdde5e2f078b9a3358dd217e2c8cb4dac84be2",
|
||||||
|
=======
|
||||||
|
"version": "1.30.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
|
||||||
|
"reference": "fa8257a579ec623473eabfe49731de5967306c4c"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fa8257a579ec623473eabfe49731de5967306c4c",
|
||||||
|
"reference": "fa8257a579ec623473eabfe49731de5967306c4c",
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -4213,11 +4259,19 @@
|
|||||||
"markbaker/complex": "^3.0",
|
"markbaker/complex": "^3.0",
|
||||||
"markbaker/matrix": "^3.0",
|
"markbaker/matrix": "^3.0",
|
||||||
"php": ">=7.4.0 <8.5.0",
|
"php": ">=7.4.0 <8.5.0",
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
"psr/http-client": "^1.0",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
|
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
|
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
|
||||||
|
<<<<<<< HEAD
|
||||||
"doctrine/instantiator": "^1.5",
|
"doctrine/instantiator": "^1.5",
|
||||||
|
=======
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
"dompdf/dompdf": "^1.0 || ^2.0 || ^3.0",
|
"dompdf/dompdf": "^1.0 || ^2.0 || ^3.0",
|
||||||
"friendsofphp/php-cs-fixer": "^3.2",
|
"friendsofphp/php-cs-fixer": "^3.2",
|
||||||
"mitoteam/jpgraph": "^10.3",
|
"mitoteam/jpgraph": "^10.3",
|
||||||
@@ -4264,9 +4318,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Adrien Crivelli"
|
"name": "Adrien Crivelli"
|
||||||
|
<<<<<<< HEAD
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Owen Leibman"
|
"name": "Owen Leibman"
|
||||||
|
=======
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
|
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
|
||||||
@@ -4283,9 +4340,15 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
|
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
|
||||||
|
<<<<<<< HEAD
|
||||||
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.2"
|
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.2"
|
||||||
},
|
},
|
||||||
"time": "2026-01-11T05:58:24+00:00"
|
"time": "2026-01-11T05:58:24+00:00"
|
||||||
|
=======
|
||||||
|
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.1"
|
||||||
|
},
|
||||||
|
"time": "2025-10-26T16:01:04+00:00"
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpoption/phpoption",
|
"name": "phpoption/phpoption",
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ return [
|
|||||||
'model' => App\Models\Staff::class,
|
'model' => App\Models\Staff::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'ttl' => (int) env('JWT_TTL', 60),
|
'ttl' => (int) env('JWT_TTL', 1440),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@@ -108,7 +108,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'refresh_ttl' => (int) env('JWT_REFRESH_TTL', 20160),
|
'refresh_ttl' => (int) env('JWT_REFRESH_TTL', 64800),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('chat_messages', function (Blueprint $table) {
|
||||||
|
$table->boolean('read_by_admin')->default(false)->after('file_type');
|
||||||
|
$table->boolean('read_by_user')->default(false)->after('read_by_admin');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('chat_messages', function (Blueprint $table) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('chat_messages', function (Blueprint $table) {
|
||||||
|
$table->string('client_id')
|
||||||
|
->nullable()
|
||||||
|
->after('sender_type')
|
||||||
|
->index();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('chat_messages', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('client_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
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.
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.
|
Before Width: | Height: | Size: 48 KiB |
BIN
public/profile_upload/profile_1766120292.jpg
Normal file
BIN
public/profile_upload/profile_1766120292.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
7
resources/js/bootstrap.js
vendored
7
resources/js/bootstrap.js
vendored
@@ -1,4 +1,9 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
window.axios = axios;
|
window.axios = axios;
|
||||||
|
|
||||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
axios.defaults.withCredentials = true;
|
||||||
|
axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
|
||||||
|
axios.defaults.headers.common["X-CSRF-TOKEN"] = document.querySelector(
|
||||||
|
'meta[name="csrf-token"]'
|
||||||
|
).content;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
<style>
|
<style>
|
||||||
/* ---------- Base ---------- */
|
/* ---------- Base ---------- */
|
||||||
|
|
||||||
:root{
|
:root{
|
||||||
--primary-1:#1a2951;
|
--primary-1:#1a2951;
|
||||||
--primary-2:#243a72;
|
--primary-2:#243a72;
|
||||||
@@ -37,7 +38,7 @@ body {
|
|||||||
/* header */
|
/* header */
|
||||||
.account-header {
|
.account-header {
|
||||||
margin-bottom: 18px;
|
margin-bottom: 18px;
|
||||||
background: linear-gradient(90deg,var(--primary-1),var(--primary-2));
|
background:linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
padding: 22px 26px;
|
padding: 22px 26px;
|
||||||
border-radius: var(--rounded);
|
border-radius: var(--rounded);
|
||||||
box-shadow: 0 6px 18px rgba(34,50,90,0.12);
|
box-shadow: 0 6px 18px rgba(34,50,90,0.12);
|
||||||
@@ -48,7 +49,7 @@ body {
|
|||||||
|
|
||||||
/* top actions row */
|
/* top actions row */
|
||||||
.top-actions {
|
.top-actions {
|
||||||
display:flex; align-items:center; justify-content:space-between;
|
align-items:center; justify-content:space-between;
|
||||||
gap:12px; margin:16px 0 20px 0; flex-wrap:wrap;
|
gap:12px; margin:16px 0 20px 0; flex-wrap:wrap;
|
||||||
}
|
}
|
||||||
.top-actions .left {
|
.top-actions .left {
|
||||||
@@ -62,12 +63,12 @@ body {
|
|||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
display:inline-flex; align-items:center; justify-content:center; gap:8px;
|
display:inline-flex; align-items:center; justify-content:center; gap:8px;
|
||||||
background: linear-gradient(90deg,var(--primary-1),var(--primary-2));
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
color:#fff; border:none; padding:10px 16px; border-radius:10px; font-weight:600;
|
color:#fff; border:none; padding:10px 16px; border-radius:10px; font-weight:600;
|
||||||
cursor:pointer; transition: transform .15s ease, box-shadow .15s;
|
cursor:pointer; transition: transform .15s ease, box-shadow .15s;
|
||||||
}
|
}
|
||||||
.btn.ghost { background: transparent; color:var(--primary-1); border:1.5px solid #dbe4f5; box-shadow:none; }
|
.btn.ghost { background: transparent; color:var(--primary-1); border:1.5px solid #dbe4f5; box-shadow:none; }
|
||||||
.btn:hover{ transform: translateY(-3px); box-shadow: 0 8px 26px rgba(36,58,114,0.12); }
|
.btn:hover{ transform: translateY(-3px); box-shadow: 0 8px 26px rgba(227, 229, 234, 0.12); }
|
||||||
|
|
||||||
/* account panels */
|
/* account panels */
|
||||||
.account-panels {
|
.account-panels {
|
||||||
@@ -89,12 +90,12 @@ body {
|
|||||||
background: var(--card-bg);
|
background: var(--card-bg);
|
||||||
border-radius:12px;
|
border-radius:12px;
|
||||||
box-shadow:0 8px 20px rgba(25,40,80,0.06);
|
box-shadow:0 8px 20px rgba(25,40,80,0.06);
|
||||||
padding:22px;
|
padding:20px; /* 005 */
|
||||||
box-sizing:border-box;
|
box-sizing:border-box;
|
||||||
overflow-x:auto;
|
overflow-x:auto;
|
||||||
transition: transform .12s, box-shadow .12s;
|
transition: transform .12s, box-shadow .12s;
|
||||||
min-height: 520px;
|
min-height: 520px;
|
||||||
display: flex;
|
/* display: flex; */ /* 005 */
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -205,8 +206,8 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
.toggle-switch-btn {
|
.toggle-switch-btn {
|
||||||
appearance:none;
|
appearance:none;
|
||||||
-webkit-appearance:none;
|
-webkit-appearance:none;
|
||||||
width:60px;
|
width:64px;
|
||||||
height:24px;
|
height:26.5px; /* 005 */
|
||||||
background:#f25b5b;
|
background:#f25b5b;
|
||||||
border:2px solid #f25b5b;
|
border:2px solid #f25b5b;
|
||||||
border-radius:999px;
|
border-radius:999px;
|
||||||
@@ -313,7 +314,7 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
border-top: 1px solid #eef3fb;
|
border-top: 1px solid #eef3fb;
|
||||||
margin-right:550px;
|
/* margin-right:550px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination-info {
|
.pagination-info {
|
||||||
@@ -326,7 +327,8 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-right:-1050px;
|
position: absolute;
|
||||||
|
right: 16px; /* 005 */
|
||||||
|
|
||||||
}
|
}
|
||||||
.pagination-controls1 {
|
.pagination-controls1 {
|
||||||
@@ -341,7 +343,7 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-right:-550px;
|
margin-right:8=500px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,7 +459,7 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
|
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Entry Details Modal (existing) ---------- */
|
/* ---------- Entry Details Modal (Enhanced) ---------- */
|
||||||
.modal-fade1 {
|
.modal-fade1 {
|
||||||
position:fixed;
|
position:fixed;
|
||||||
inset:0;
|
inset:0;
|
||||||
@@ -479,6 +481,9 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
max-height:92vh;
|
max-height:92vh;
|
||||||
overflow:auto;
|
overflow:auto;
|
||||||
min-height: 500px;
|
min-height: 500px;
|
||||||
|
}
|
||||||
|
#entryOrdersModal {
|
||||||
|
z-index: 1300;
|
||||||
}
|
}
|
||||||
#entryOrdersModal {
|
#entryOrdersModal {
|
||||||
z-index: 1300;
|
z-index: 1300;
|
||||||
@@ -488,7 +493,7 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
.entry-summary-cards {
|
.entry-summary-cards {
|
||||||
display:flex;
|
display:flex;
|
||||||
gap:16px;
|
gap:16px;
|
||||||
margin-bottom:20px;
|
margin-top:25px;
|
||||||
flex-wrap:wrap;
|
flex-wrap:wrap;
|
||||||
}
|
}
|
||||||
.entry-summary-card {
|
.entry-summary-card {
|
||||||
@@ -503,6 +508,184 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
.entry-summary-label{ font-size:12px; color:var(--muted); }
|
.entry-summary-label{ font-size:12px; color:var(--muted); }
|
||||||
.entry-summary-value{ font-size:18px; font-weight:700; color:#253047; margin-top:6px; }
|
.entry-summary-value{ font-size:18px; font-weight:700; color:#253047; margin-top:6px; }
|
||||||
|
|
||||||
|
/* Enhanced dropdown */
|
||||||
|
.installment-status-dropdown {
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 40px 10px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1.5px solid #e3eaf6;
|
||||||
|
background: white;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background-image: url('data:image/svg+xml;charset=US-ASCII,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="%236b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 12px center;
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installment-status-dropdown:hover {
|
||||||
|
border-color: #c2d1f0;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installment-status-dropdown:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: 0 0 0 3px rgba(39, 109, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status-specific dropdown options */
|
||||||
|
.installment-status-dropdown option {
|
||||||
|
padding: 12px !important;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installment-status-dropdown option[value="Pending"] {
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installment-status-dropdown option[value="Loading"] {
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installment-status-dropdown option[value="Packed"] {
|
||||||
|
color: #8b5cf6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installment-status-dropdown option[value="Dispatched"] {
|
||||||
|
color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installment-status-dropdown option[value="Delivered"] {
|
||||||
|
color: #0c6b2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Entry Orders Modal (Enhanced) ---------- */
|
||||||
|
.entry-orders-modal .modal-box1 {
|
||||||
|
padding: 0 !important;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 20px 60px rgba(18, 30, 60, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-orders-header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
padding: 24px 30px;
|
||||||
|
color: white;
|
||||||
|
border-radius: 16px 16px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-orders-header h2 {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-orders-header .subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-orders-content {
|
||||||
|
padding: 30px;
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Orders table in modal */
|
||||||
|
.orders-table-container {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid #eef3fb;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-table th {
|
||||||
|
background: linear-gradient(90deg, #f8fbff, #f5f9ff);
|
||||||
|
padding: 14px 16px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-1);
|
||||||
|
border-bottom: 2px solid #eef3fb;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-table td {
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-bottom: 1px solid #f1f6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-table tr:hover {
|
||||||
|
background: #fbfdff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-table tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Order ID with badge style */
|
||||||
|
.order-id-badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: linear-gradient(90deg, #f0f7ff, #e6f0ff);
|
||||||
|
color: var(--primary-1);
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
border: 1px solid #dbe4f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Summary row */
|
||||||
|
.orders-summary-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 20px;
|
||||||
|
background: linear-gradient(90deg, #e6ebf5 0%, #f9fbff 100%);
|
||||||
|
|
||||||
|
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
border: 1px solid #eef3fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-summary-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-summary-value {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--primary-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-summary-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-top: 4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
/* installment modal */
|
/* installment modal */
|
||||||
#installmentModal .modal-box1 { max-width:720px; min-width:380px; }
|
#installmentModal .modal-box1 { max-width:720px; min-width:380px; }
|
||||||
|
|
||||||
@@ -563,7 +746,7 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
|
|
||||||
/* Combined filters row styling */
|
/* Combined filters row styling */
|
||||||
.combined-filters-row {
|
.combined-filters-row {
|
||||||
display: flex;
|
display: ruby; /* 005 */
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
@@ -576,8 +759,8 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
}
|
}
|
||||||
|
|
||||||
.right{
|
.right{
|
||||||
margin-left:auto;
|
/* margin-left:auto;
|
||||||
margin-top:-16px;
|
margin-top:-16px; */ /* 005 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-group1 {
|
.filter-group1 {
|
||||||
@@ -827,7 +1010,10 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
background: linear-gradient(90deg, #f9fbff, #f7faff);
|
background:linear-gradient(90deg, #e6ebf5 0%, #f9fbff 100%);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid #eef3fb;
|
border: 1px solid #eef3fb;
|
||||||
}
|
}
|
||||||
@@ -953,6 +1139,15 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.combined-top-row .btn:hover {
|
||||||
|
color: #ffffff !important;
|
||||||
|
background-color: inherit !important;
|
||||||
|
border-color: inherit !important;
|
||||||
|
transform: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.remove-order-btn:hover {
|
.remove-order-btn:hover {
|
||||||
background: #d42c3f;
|
background: #d42c3f;
|
||||||
}
|
}
|
||||||
@@ -1021,6 +1216,192 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
/* नवीन responsive styles */
|
||||||
|
@media (max-width:980px){
|
||||||
|
.account-container {
|
||||||
|
padding: 20px !important;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-card {
|
||||||
|
min-width:100% !important;
|
||||||
|
padding: 18px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-row input{
|
||||||
|
width:220px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: stretch;
|
||||||
|
margin-right: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combined-filters-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group1,
|
||||||
|
.filter-group2,
|
||||||
|
.filter-group3,
|
||||||
|
.filter-group4 {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:768px){
|
||||||
|
body {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-container {
|
||||||
|
padding: 15px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-header {
|
||||||
|
padding: 18px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-actions .left {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-panels {
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-row input {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pagination adjustments for mobile */
|
||||||
|
.pagination-controls,
|
||||||
|
.pagination-controls1,
|
||||||
|
.pagination-controls2 {
|
||||||
|
margin-right: 0 !important;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container {
|
||||||
|
margin-right: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive modals */
|
||||||
|
|
||||||
|
|
||||||
|
.entry-details-modal .modal-box1,
|
||||||
|
.entry-orders-modal .modal-box1 {
|
||||||
|
margin: 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-details-header,
|
||||||
|
.entry-orders-header {
|
||||||
|
padding: 18px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-details-content,
|
||||||
|
.entry-orders-content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-summary-cards {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-summary-value {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-summary-row {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zoom out specific fix */
|
||||||
|
@media screen and (max-width: 1200px) {
|
||||||
|
.account-container {
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-block {
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extra small screens */
|
||||||
|
@media screen and (max-width: 576px) {
|
||||||
|
.account-container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-header h2 {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-card {
|
||||||
|
padding: 15px;
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-info {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-summary-cards {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent horizontal scroll on very small screens */
|
||||||
|
@media screen and (max-width: 400px) {
|
||||||
|
body {
|
||||||
|
min-width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-container {
|
||||||
|
padding: 10px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-responsive {
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix for sidebar gap on zoom out */
|
||||||
|
html, body {
|
||||||
|
max-width: 100vw;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure all containers are properly constrained */
|
||||||
|
.container, .container-fluid, .account-container {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="account-container">
|
<div class="account-container">
|
||||||
@@ -1166,8 +1547,8 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
<!-- CREATE ORDER POPUP MODAL -->
|
<!-- CREATE ORDER POPUP MODAL -->
|
||||||
<div class="create-order-modal" id="createOrderModal">
|
<div class="create-order-modal" id="createOrderModal">
|
||||||
<div class="modal-box">
|
<div class="modal-box">
|
||||||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:16px;">
|
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:16px; background:linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding:20px; border-radius:12px; color:white; margin-top:-20px; margin-left:-20px; margin-right:-15px; margin-top:-15px;">
|
||||||
<div style="font-size:20px; font-weight:800; color:var(--primary-1)">Create New Installment</div>
|
<div style="font-size:20px; font-weight:800;">Create New Installment</div>
|
||||||
<button class="btn ghost" id="closeCreateModal" title="Close create form">✕</button>
|
<button class="btn ghost" id="closeCreateModal" title="Close create form">✕</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1258,37 +1639,64 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ENTRY DETAILS MODAL -->
|
<!-- ENTRY DETAILS MODAL (Enhanced) -->
|
||||||
<div class="modal-fade1" id="entryDetailsModal">
|
<div class="modal-fade1 entry-details-modal" id="entryDetailsModal">
|
||||||
<div class="modal-box1 entry-details-modal">
|
<div class="modal-box1">
|
||||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
<div class="entry-details-header" >
|
||||||
<div>
|
<h2 >Entry Details</h2>
|
||||||
<h2 style="margin:0;font-size:20px;color:#223256;font-weight:800">Entry Details — <span id="entryDetailsId">-</span></h2>
|
<div class="subtitle">
|
||||||
<div style="font-size:13px;color:var(--muted)">Complete view of all installments for this entry.</div>
|
<span id="entryDetailsId">Loading...</span>
|
||||||
|
<span>• Complete view of all installments for this entry</span>
|
||||||
</div>
|
</div>
|
||||||
<div><button class="btn ghost" onclick="closeEntryDetailsModal()">Close</button></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="entry-details-content">
|
||||||
<div class="entry-summary-cards">
|
<div class="entry-summary-cards">
|
||||||
<div class="entry-summary-card">
|
<div class="entry-summary-card">
|
||||||
<div class="entry-summary-label">Original Amount</div>
|
<div class="entry-summary-label">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>
|
||||||
|
</svg>
|
||||||
|
Original Amount
|
||||||
|
</div>
|
||||||
<div class="entry-summary-value" id="originalAmount">-</div>
|
<div class="entry-summary-value" id="originalAmount">-</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="entry-summary-card">
|
<div class="entry-summary-card">
|
||||||
<div class="entry-summary-label">Total Processed</div>
|
<div class="entry-summary-label">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
|
||||||
|
</svg>
|
||||||
|
Total Processed
|
||||||
|
</div>
|
||||||
<div class="entry-summary-value" id="totalProcessed">-</div>
|
<div class="entry-summary-value" id="totalProcessed">-</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="entry-summary-card">
|
<div class="entry-summary-card">
|
||||||
<div class="entry-summary-label">Pending Balance</div>
|
<div class="entry-summary-label">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>
|
||||||
|
</svg>
|
||||||
|
Pending Balance
|
||||||
|
</div>
|
||||||
<div class="entry-summary-value" id="pendingBalance">-</div>
|
<div class="entry-summary-value" id="pendingBalance">-</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="entry-summary-card">
|
<div class="entry-summary-card">
|
||||||
<div class="entry-summary-label">Total Installments</div>
|
<div class="entry-summary-label">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
||||||
|
</svg>
|
||||||
|
Total Installments
|
||||||
|
</div>
|
||||||
<div class="entry-summary-value" id="totalInstallments">-</div>
|
<div class="entry-summary-value" id="totalInstallments">-</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="entry-installments-table" style="width:100%; border-collapse:collapse;">
|
<div style="margin-top: 30px;">
|
||||||
|
<h3 style="font-size: 18px; font-weight: 700; color: var(--primary-1); margin-bottom: 16px;">
|
||||||
|
Installment History
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="orders-table-container">
|
||||||
|
<table class="entry-installments-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Installment</th>
|
<th>Installment</th>
|
||||||
@@ -1303,36 +1711,40 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
<tr><td colspan="6" class="empty-state">No installments yet</td></tr>
|
<tr><td colspan="6" class="empty-state">No installments yet</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style="display:flex; justify-content: flex-end; gap:12px; margin-top:16px;">
|
<div style="padding: 20px 30px; border-top: 1px solid #eef3fb; display: flex; justify-content: space-between; align-items: center;">
|
||||||
<button type="button" class="btn ghost" onclick="closeEntryDetailsModal()">Close</button>
|
<button type="button" class="btn ghost" onclick="closeEntryDetailsModal()">
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
@can('account.add_installment')
|
@can('account.add_installment')
|
||||||
<button type="button" class="btn" id="addInstallmentFromDetails">+ Add New Installment</button>
|
<button type="button" class="btn" id="addInstallmentFromDetails">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 6px;">
|
||||||
|
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
|
||||||
|
</svg>
|
||||||
|
Add New Installment
|
||||||
|
</button>
|
||||||
@endcan
|
@endcan
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ENTRY ORDERS MODAL -->
|
<!-- ENTRY ORDERS MODAL (Enhanced) -->
|
||||||
<div class="modal-fade1" id="entryOrdersModal">
|
<div class="modal-fade1 entry-orders-modal" id="entryOrdersModal">
|
||||||
<div class="modal-box1" style="max-width: 1000px;">
|
<div class="modal-box1">
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;">
|
<div class="entry-orders-header">
|
||||||
<div>
|
<h2>Entry Orders</h2>
|
||||||
<h2 style="margin:0;font-size:20px;color:#223256;font-weight:800;">
|
<div class="subtitle">
|
||||||
Entry Orders
|
<span id="entryOrdersEntryNo-span">Loading...</span>
|
||||||
<span id="entryOrdersEntryNo-span" style="font-size:14px;color:#6b7280;margin-left:8px;"></span>
|
<span>• All orders associated with this entry</span>
|
||||||
</h2>
|
|
||||||
<div style="font-size:13px;color:#6f7b8f;">
|
|
||||||
All orders associated with this entry.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn ghost" type="button" onclick="closeEntryOrdersModal()">
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="entry-orders-content">
|
||||||
<div class="orders-table-container">
|
<div class="orders-table-container">
|
||||||
<table class="orders-table" style="width:100%;border-collapse:collapse;font-size:13px;">
|
<table class="orders-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Order ID</th>
|
<th>Order ID</th>
|
||||||
@@ -1346,23 +1758,40 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody id="entryOrdersTableBody">
|
<tbody id="entryOrdersTableBody">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7" class="empty-state">No orders associated with this entry</td>
|
<td colspan="7" class="empty-state">Loading orders...</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="orders-summary-row">
|
||||||
|
<div class="orders-summary-item">
|
||||||
|
<div class="orders-summary-value" id="ordersTotalCount">0</div>
|
||||||
|
<div class="orders-summary-label">Total Orders</div>
|
||||||
|
</div>
|
||||||
|
<div class="orders-summary-item">
|
||||||
|
<div class="orders-summary-value" id="ordersTotalQuantity">0</div>
|
||||||
|
<div class="orders-summary-label">Total Quantity</div>
|
||||||
|
</div>
|
||||||
|
<div class="orders-summary-item">
|
||||||
|
<div class="orders-summary-value" id="ordersTotalAmount">₹0</div>
|
||||||
|
<div class="orders-summary-label">Total Amount</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="padding: 20px 30px; border-top: 1px solid #eef3fb; display: flex; justify-content: flex-end;">
|
||||||
|
<button class="btn ghost" type="button" onclick="closeEntryOrdersModal()">
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Installment Modal -->
|
<!-- Installment Modal -->
|
||||||
<div class="modal-fade1" id="installmentModal">
|
<div class="modal-fade1" id="installmentModal">
|
||||||
<div class="modal-box1" style="max-width:720px;">
|
<div class="modal-box1" style="max-width:720px;">
|
||||||
<div style="display:flex;align-items:center; justify-content:space-between; margin-bottom:12px;">
|
<div style="display:flex;align-items:center; justify-content:space-between; margin-bottom:12px; background:linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding:16px; border-radius:8px; color:white; margin-top:-15px; margin-left:-20px; margin-right:-15px;">
|
||||||
<div style="font-size:18px;font-weight:800;color:#243a72;">+ Add New Installment</div>
|
<div style="font-size:18px;font-weight:800;color:#243a72;">+ Add New Installment</div>
|
||||||
<button class="btn ghost" onclick="closeInstallmentModal()">✕</button>
|
<button class="btn ghost" onclick="closeInstallmentModal()">✕</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -2628,10 +3057,15 @@ async function submitEditEntry(e) {
|
|||||||
|
|
||||||
|
|
||||||
function openEntryOrdersModal(entryNo) {
|
function openEntryOrdersModal(entryNo) {
|
||||||
// header la entry no show kar
|
// Set entry number in header
|
||||||
document.getElementById('entryOrdersEntryNo-span').textContent = `(${entryNo})`;
|
document.getElementById('entryOrdersEntryNo-span').textContent = `(${entryNo})`;
|
||||||
|
|
||||||
// table clean / loading state
|
// Reset summary values
|
||||||
|
document.getElementById('ordersTotalCount').textContent = '0';
|
||||||
|
document.getElementById('ordersTotalQuantity').textContent = '0';
|
||||||
|
document.getElementById('ordersTotalAmount').textContent = '₹0';
|
||||||
|
|
||||||
|
// table loading state
|
||||||
const tbody = document.getElementById('entryOrdersTableBody');
|
const tbody = document.getElementById('entryOrdersTableBody');
|
||||||
tbody.innerHTML = `
|
tbody.innerHTML = `
|
||||||
<tr>
|
<tr>
|
||||||
@@ -2664,6 +3098,10 @@ function openEntryOrdersModal(entryNo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
let totalQuantity = 0;
|
||||||
|
let totalAmount = 0;
|
||||||
|
|
||||||
orders.forEach(order => {
|
orders.forEach(order => {
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
|
|
||||||
@@ -2673,27 +3111,31 @@ function openEntryOrdersModal(entryNo) {
|
|||||||
? escapeHtml(idString)
|
? escapeHtml(idString)
|
||||||
: 'KNT-25-' + String(numericId).padStart(8, '0');
|
: 'KNT-25-' + String(numericId).padStart(8, '0');
|
||||||
|
|
||||||
// इथे वेगवेगळी शक्य keys try कर
|
// Calculate values for summary
|
||||||
const amountValue =
|
const quantity = Number(order.qty || order.order_qty || 0);
|
||||||
order.ttl_amount ??
|
const amountValue = Number(order.ttl_amount ?? order.ttlamount ?? order.total_amount ?? order.order_amount ?? order.amount ?? 0);
|
||||||
order.ttlamount ??
|
|
||||||
order.total_amount ??
|
totalQuantity += quantity;
|
||||||
order.order_amount ??
|
totalAmount += amountValue;
|
||||||
order.amount ??
|
|
||||||
0;
|
|
||||||
|
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td>${formattedId}</td>
|
<td>
|
||||||
|
<span class="order-id-badge">${formattedId}</span>
|
||||||
|
</td>
|
||||||
<td>${escapeHtml(order.markno ?? order.mark_no ?? '')}</td>
|
<td>${escapeHtml(order.markno ?? order.mark_no ?? '')}</td>
|
||||||
<td>${escapeHtml(order.origin ?? '')}</td>
|
<td>${escapeHtml(order.origin ?? '')}</td>
|
||||||
<td>${escapeHtml(order.destination ?? '')}</td>
|
<td>${escapeHtml(order.destination ?? '')}</td>
|
||||||
<td>${escapeHtml(order.ctn ?? '')}</td>
|
<td>${escapeHtml(order.ctn ?? '')}</td>
|
||||||
<td>${escapeHtml(order.qty ?? '')}</td>
|
<td><strong>${quantity}</strong></td>
|
||||||
<td>${formatCurrency(amountValue)}</td>
|
<td><strong>${formatCurrency(amountValue)}</strong></td>
|
||||||
`;
|
`;
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update summary
|
||||||
|
document.getElementById('ordersTotalCount').textContent = orders.length;
|
||||||
|
document.getElementById('ordersTotalQuantity').textContent = totalQuantity.toLocaleString();
|
||||||
|
document.getElementById('ordersTotalAmount').textContent = formatCurrency(totalAmount);
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -2841,7 +3283,7 @@ function handleSearch(){
|
|||||||
updatePaginationControls();
|
updatePaginationControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Entry details & installments ---------- */
|
/* ---------- Entry details & installments (Enhanced) ---------- */
|
||||||
async function openEntryDetailsModal(entryNo) {
|
async function openEntryDetailsModal(entryNo) {
|
||||||
try {
|
try {
|
||||||
const res = await jsonFetch('/admin/account/entry/' + encodeURIComponent(entryNo));
|
const res = await jsonFetch('/admin/account/entry/' + encodeURIComponent(entryNo));
|
||||||
@@ -2851,58 +3293,109 @@ async function openEntryDetailsModal(entryNo) {
|
|||||||
const entry = res.entry;
|
const entry = res.entry;
|
||||||
currentEntry = entry;
|
currentEntry = entry;
|
||||||
|
|
||||||
|
// Set header info
|
||||||
document.getElementById('entryDetailsId').textContent = entry.entry_no;
|
document.getElementById('entryDetailsId').textContent = entry.entry_no;
|
||||||
document.getElementById('originalAmount').textContent = formatCurrency(entry.amount);
|
|
||||||
|
|
||||||
const totalProcessed = Number(entry.amount) - Number(entry.pending_amount);
|
// Calculate values
|
||||||
|
const originalAmount = Number(entry.amount || 0);
|
||||||
|
const pendingAmount = Number(entry.pending_amount || 0);
|
||||||
|
const totalProcessed = originalAmount - pendingAmount;
|
||||||
|
|
||||||
|
// Update summary cards
|
||||||
|
document.getElementById('originalAmount').textContent = formatCurrency(originalAmount);
|
||||||
document.getElementById('totalProcessed').textContent = formatCurrency(totalProcessed);
|
document.getElementById('totalProcessed').textContent = formatCurrency(totalProcessed);
|
||||||
|
document.getElementById('pendingBalance').textContent = formatCurrency(pendingAmount);
|
||||||
|
document.getElementById('totalInstallments').textContent = entry.installments?.length || 0;
|
||||||
|
|
||||||
document.getElementById('pendingBalance').textContent = formatCurrency(entry.pending_amount);
|
// Render installments table
|
||||||
document.getElementById('totalInstallments').textContent = entry.installments.length;
|
|
||||||
|
|
||||||
const tbody = document.getElementById('installmentsTableBody');
|
const tbody = document.getElementById('installmentsTableBody');
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
if (!entry.installments || entry.installments.length === 0) {
|
||||||
|
tbody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="empty-state">
|
||||||
|
<div style="padding: 30px; text-align: center;">
|
||||||
|
<div style="font-size: 48px; color: #eef3fb; margin-bottom: 16px;">📋</div>
|
||||||
|
<div style="font-size: 16px; color: #6f7b8f; font-weight: 500;">
|
||||||
|
No installments found for this entry
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 14px; color: #9ba5bb; margin-top: 8px;">
|
||||||
|
Add your first installment to get started
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
entry.installments.forEach((ins, idx) => {
|
entry.installments.forEach((ins, idx) => {
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
tr.innerHTML = `
|
|
||||||
<td>${idx === 0 ? 'Original Entry' : 'Installment ' + idx}</td>
|
|
||||||
<td>${escapeHtml(ins.proc_date)}</td>
|
|
||||||
<td>${escapeHtml(ins.description)}</td>
|
|
||||||
<td>${escapeHtml(ins.region)}</td>
|
|
||||||
<td>${formatCurrency(ins.amount)}</td>
|
|
||||||
|
|
||||||
|
// Determine installment label
|
||||||
|
let installmentLabel = 'Original Entry';
|
||||||
|
if (idx > 0) {
|
||||||
|
installmentLabel = `Installment ${idx}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status dropdown options with colors
|
||||||
|
const statusOptions = [
|
||||||
|
{ value: 'Pending', label: '⏳ Pending', color: '#f59e0b' },
|
||||||
|
{ value: 'Loading', label: '📦 Loading', color: '#3b82f6' },
|
||||||
|
{ value: 'Packed', label: '📦 Packed', color: '#8b5cf6' },
|
||||||
|
{ value: 'Dispatched', label: '🚚 Dispatched', color: '#10b981' },
|
||||||
|
{ value: 'Delivered', label: '✅ Delivered', color: '#0c6b2e' }
|
||||||
|
];
|
||||||
|
|
||||||
|
let statusOptionsHtml = '';
|
||||||
|
statusOptions.forEach(opt => {
|
||||||
|
const selected = ins.status === opt.value ? 'selected' : '';
|
||||||
|
statusOptionsHtml += `<option value="${opt.value}" ${selected} style="color: ${opt.color}; font-weight: 500;">${opt.label}</option>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<div style="width: 32px; height: 32px; border-radius: 8px; background: linear-gradient(135deg, #f0f7ff, #e6f0ff); display: flex; align-items: center; justify-content: center; font-weight: 700; color: var(--primary-1);">
|
||||||
|
${idx + 1}
|
||||||
|
</div>
|
||||||
|
<span style="font-weight: 600;">${installmentLabel}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div style="display: flex; align-items: center; gap: 6px;">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
|
||||||
|
<line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/>
|
||||||
|
<line x1="3" y1="10" x2="21" y2="10"/>
|
||||||
|
</svg>
|
||||||
|
${escapeHtml(ins.proc_date || ins.date || '')}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>${escapeHtml(ins.description || '')}</td>
|
||||||
|
<td>
|
||||||
|
<span style="padding: 4px 8px; background: #f0f7ff; border-radius: 6px; font-size: 12px; font-weight: 600; color: var(--primary-1);">
|
||||||
|
${escapeHtml(ins.region || '')}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span style="font-weight: 700; color: var(--primary-1);">
|
||||||
|
${formatCurrency(ins.amount || 0)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<select class="installment-status-dropdown"
|
<select class="installment-status-dropdown"
|
||||||
data-id="${ins.id}"
|
data-id="${ins.id}"
|
||||||
onchange="updateInstallmentStatus(${ins.id}, this.value)">
|
onchange="updateInstallmentStatus(${ins.id}, this.value)"
|
||||||
<option value="Pending" ${ins.status === 'Pending' ? 'selected' : ''}
|
title="Update installment status">
|
||||||
style="color: #f59e0b; font-weight: 500; padding: 10px;">
|
${statusOptionsHtml}
|
||||||
⏳ Pending
|
|
||||||
</option>
|
|
||||||
<option value="Loading" ${ins.status === 'Loading' ? 'selected' : ''}
|
|
||||||
style="color: #3b82f6; font-weight: 500; padding: 10px;">
|
|
||||||
📦 Loading
|
|
||||||
</option>
|
|
||||||
<option value="Packed" ${ins.status === 'Packed' ? 'selected' : ''}
|
|
||||||
style="color: #8b5cf6; font-weight: 500; padding: 10px;">
|
|
||||||
📦 Packed
|
|
||||||
</option>
|
|
||||||
<option value="Dispatched" ${ins.status === 'Dispatched' ? 'selected' : ''}
|
|
||||||
style="color: #10b981; font-weight: 500; padding: 10px;">
|
|
||||||
🚚 Dispatched
|
|
||||||
</option>
|
|
||||||
<option value="Delivered" ${ins.status === 'Delivered' ? 'selected' : ''}
|
|
||||||
style="color: #0c6b2e; font-weight: 500; padding: 10px;">
|
|
||||||
✅ Delivered
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show modal
|
||||||
document.getElementById('entryDetailsModal').classList.add('modal-open');
|
document.getElementById('entryDetailsModal').classList.add('modal-open');
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -92,3 +92,78 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
@section('scripts')
|
||||||
|
<script>
|
||||||
|
// -------------------------------
|
||||||
|
// WAIT FOR ECHO READY (DEFINE IT)
|
||||||
|
// -------------------------------
|
||||||
|
function waitForEcho(callback, retries = 40) {
|
||||||
|
if (window.Echo) {
|
||||||
|
console.log('%c[ECHO] Ready (Admin List)', 'color: green; font-weight: bold;');
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retries <= 0) {
|
||||||
|
console.error('[ECHO] Failed to initialize');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => waitForEcho(callback, retries - 1), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// LISTEN FOR REALTIME MESSAGES
|
||||||
|
// -------------------------------
|
||||||
|
waitForEcho(() => {
|
||||||
|
console.log('[ADMIN LIST] Listening for new messages...');
|
||||||
|
|
||||||
|
const globalBox = document.getElementById('globalNewMessageBox');
|
||||||
|
const globalCount = document.getElementById('globalNewMessageCount');
|
||||||
|
let totalNewMessages = 0;
|
||||||
|
|
||||||
|
window.Echo.private('admin.chat')
|
||||||
|
.listen('.NewChatMessage', (event) => {
|
||||||
|
|
||||||
|
// only USER → ADMIN messages
|
||||||
|
if (event.sender_type !== 'App\\Models\\User') return;
|
||||||
|
|
||||||
|
const ticketId = event.ticket_id;
|
||||||
|
|
||||||
|
// 1) UPDATE PER-TICKET BADGE
|
||||||
|
const badge = document.getElementById(`badge-${ticketId}`);
|
||||||
|
if (badge) {
|
||||||
|
let count = parseInt(badge.innerText || 0);
|
||||||
|
badge.innerText = count + 1;
|
||||||
|
badge.classList.remove('d-none');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) UPDATE GLOBAL NEW MESSAGE COUNTER
|
||||||
|
totalNewMessages++;
|
||||||
|
if (globalBox && globalCount) {
|
||||||
|
globalBox.classList.remove('d-none');
|
||||||
|
globalCount.innerText = totalNewMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) त्या ticket ला यादीत सर्वात वर आणा आणि स्क्रोल करा
|
||||||
|
const listContainer = document.querySelector('.tickets-list');
|
||||||
|
const ticketEl = document.querySelector(`.ticket-item[data-ticket-id="${ticketId}"]`);
|
||||||
|
|
||||||
|
if (listContainer && ticketEl) {
|
||||||
|
if (ticketEl.previousElementSibling) {
|
||||||
|
listContainer.insertBefore(ticketEl, listContainer.firstElementChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
const topOffset = ticketEl.offsetTop;
|
||||||
|
window.scrollTo({
|
||||||
|
top: listContainer.offsetTop + topOffset - 20,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[ADMIN LIST] Badge/global counter updated for ticket', ticketId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endsection
|
||||||
|
|||||||
@@ -197,5 +197,4 @@ waitForEcho(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
@extends('admin.layouts.app')
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
@section('page-title', 'Customers')
|
@section('page-title', 'Customers')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Import Inter font */
|
/* Import Inter font */
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||||
|
|
||||||
@@ -700,9 +700,9 @@
|
|||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<!-- Header - Removed gradient -->
|
<!-- Header - Removed gradient -->
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
<h4 style="color: #2c3e50; font-weight: 700; font-family: 'Inter', sans-serif;">Customer List</h4>
|
<h4 style="color: #2c3e50; font-weight: 700; font-family: 'Inter', sans-serif;">Customer List</h4>
|
||||||
@@ -827,14 +827,30 @@
|
|||||||
<th class="table-header">Customer Info</th>
|
<th class="table-header">Customer Info</th>
|
||||||
<th class="table-header">Customer ID</th>
|
<th class="table-header">Customer ID</th>
|
||||||
<th class="table-header">Orders</th>
|
<th class="table-header">Orders</th>
|
||||||
<th class="table-header">Total</th>
|
<th class="table-header">Order Total</th>
|
||||||
|
<th class="table-header">Total Payable</th> {{-- NEW --}}
|
||||||
|
<th class="table-header">Remaining</th> {{-- NEW --}}
|
||||||
|
<th class="table-header">Paid Amount</th>
|
||||||
<th class="table-header">Create Date</th>
|
<th class="table-header">Create Date</th>
|
||||||
<th class="table-header">Status</th>
|
<th class="table-header">Status</th>
|
||||||
<th class="table-header" width="100">Actions</th>
|
<th class="table-header" width="100">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody id="customersTableBody">
|
<tbody id="customersTableBody">
|
||||||
@forelse($customers as $c)
|
@forelse($customers as $c)
|
||||||
|
@php
|
||||||
|
// Invoice total (with GST)
|
||||||
|
$totalPayable = $c->invoices->sum('final_amount_with_gst');
|
||||||
|
|
||||||
|
// Total paid via installments
|
||||||
|
$totalPaid = $c->invoices
|
||||||
|
->flatMap(fn($inv) => $inv->installments)
|
||||||
|
->sum('amount');
|
||||||
|
|
||||||
|
// Remaining amount
|
||||||
|
$remainingAmount = max($totalPayable - $totalPaid, 0);
|
||||||
|
@endphp
|
||||||
<tr>
|
<tr>
|
||||||
<!-- Customer Info Column -->
|
<!-- Customer Info Column -->
|
||||||
<td class="customer-info-column">
|
<td class="customer-info-column">
|
||||||
@@ -869,9 +885,36 @@
|
|||||||
|
|
||||||
<!-- Total Column -->
|
<!-- Total Column -->
|
||||||
<td class="total-column">
|
<td class="total-column">
|
||||||
<span class="total-amount">₹{{ number_format($c->orders->sum('ttl_amount'), 2) }}</span>
|
<span class="total-amount">
|
||||||
|
₹{{ number_format($c->orders->sum('ttl_amount'), 2) }}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
<td class="total-column">
|
||||||
|
<span class="total-amount">
|
||||||
|
₹{{ number_format($totalPayable, 2) }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="total-column">
|
||||||
|
@if($remainingAmount > 0)
|
||||||
|
<span class="text-danger fw-bold">
|
||||||
|
₹{{ number_format($remainingAmount, 2) }}
|
||||||
|
</span>
|
||||||
|
@else
|
||||||
|
<span class="text-success fw-bold">
|
||||||
|
₹0.00
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="total-column">
|
||||||
|
<span class="fw-bold text-success">
|
||||||
|
₹{{ number_format($totalPaid, 2) }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
<!-- Create Date -->
|
<!-- Create Date -->
|
||||||
<td class="create-date-column">
|
<td class="create-date-column">
|
||||||
<span class="text-muted">{{ $c->created_at ? $c->created_at->format('d-m-Y') : '-' }}</span>
|
<span class="text-muted">{{ $c->created_at ? $c->created_at->format('d-m-Y') : '-' }}</span>
|
||||||
@@ -945,9 +988,9 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Add hover effects to table rows
|
// Add hover effects to table rows
|
||||||
const tableRows = document.querySelectorAll('.table tbody tr');
|
const tableRows = document.querySelectorAll('.table tbody tr');
|
||||||
@@ -974,6 +1017,6 @@
|
|||||||
@endif
|
@endif
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -698,11 +698,48 @@
|
|||||||
<div class="stats-icon">
|
<div class="stats-icon">
|
||||||
<i class="bi bi-currency-rupee"></i>
|
<i class="bi bi-currency-rupee"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-value">₹{{ number_format($totalAmount, 2) }}</div>
|
<div class="stats-value">₹{{ number_format($totalOrderAmount, 2) }}</div>
|
||||||
<div class="stats-label">Total Amount Spent</div>
|
<div class="stats-label">Total Amount Spent</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- {{-- Total Payable --}}
|
||||||
|
<div class="stats-card amount">
|
||||||
|
<div class="stats-icon">
|
||||||
|
<i class="bi bi-wallet2"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stats-value">₹{{ number_format($totalPayable, 2) }}</div>
|
||||||
|
<div class="stats-label">Total Payable</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Total Remaining --}}
|
||||||
|
<div class="stats-card marks">
|
||||||
|
<div class="stats-icon">
|
||||||
|
<i class="bi bi-exclamation-circle"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stats-value">₹{{ number_format($totalRemaining, 2) }}</div>
|
||||||
|
<div class="stats-label">Remaining Amount</div>
|
||||||
|
</div> -->
|
||||||
|
<div class="col-md-4 animate-fade-in animation-delay-3">
|
||||||
|
<div class="stats-card marks">
|
||||||
|
<div class="stats-icon">
|
||||||
|
<i class="bi bi-hash"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stats-value">₹{{ number_format($totalPayable, 2) }}</div>
|
||||||
|
<div class="stats-label">Total Payable</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 animate-fade-in animation-delay-3">
|
||||||
|
<div class="stats-card marks">
|
||||||
|
<div class="stats-icon">
|
||||||
|
<i class="bi bi-hash"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stats-value">₹{{ number_format($totalRemaining, 2) }}</div>
|
||||||
|
<div class="stats-label">Remaining Amount</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{{-- Mark Count --}}
|
{{-- Mark Count --}}
|
||||||
<div class="col-md-4 animate-fade-in animation-delay-3">
|
<div class="col-md-4 animate-fade-in animation-delay-3">
|
||||||
<div class="stats-card marks">
|
<div class="stats-card marks">
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 17px 17px 0 0;
|
border-radius: 17px 17px 0 0;
|
||||||
background: #fceeb8ff;
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
min-height: 54px;
|
min-height: 54px;
|
||||||
padding: 15px 26px 10px 22px;
|
padding: 15px 26px 10px 22px;
|
||||||
border-bottom: 1.4px solid #e8e2cf;
|
border-bottom: 1.4px solid #e8e2cf;
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
.invoice-management-title {
|
.invoice-management-title {
|
||||||
font-size: 1.32rem;
|
font-size: 1.32rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: #2451af;
|
color: #ffffffff;
|
||||||
letter-spacing: .08em;
|
letter-spacing: .08em;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
|
|
||||||
/* Center all table content */
|
/* Center all table content */
|
||||||
.table thead tr {
|
.table thead tr {
|
||||||
background: #feebbe !important;
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.table thead th:first-child {
|
.table thead th:first-child {
|
||||||
@@ -237,7 +237,7 @@
|
|||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
border: none;
|
border: none;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #343535;
|
color: #ffffffff;
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.02em;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 20px 15px;
|
padding: 20px 15px;
|
||||||
@@ -259,24 +259,32 @@
|
|||||||
/* Soft blue background for ALL table rows */
|
/* Soft blue background for ALL table rows */
|
||||||
.table-striped tbody tr {
|
.table-striped tbody tr {
|
||||||
background: #f0f8ff !important;
|
background: #f0f8ff !important;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.15s ease;
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-striped tbody tr:hover {
|
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.table-striped tbody tr td {
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-striped tbody tr:hover {
|
||||||
background: #e6f3ff !important;
|
background: #e6f3ff !important;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-0.5px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Remove striped pattern - all rows same soft blue */
|
|
||||||
.table-striped tbody tr:nth-of-type(odd),
|
.table-striped tbody tr:nth-of-type(odd),
|
||||||
.table-striped tbody tr:nth-of-type(even) {
|
.table-striped tbody tr:nth-of-type(even) {
|
||||||
background: #f0f8ff !important;
|
background: #f0f8ff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Center all table cells with proper spacing */
|
|
||||||
.table td {
|
.table td {
|
||||||
padding: 18px 15px;
|
padding: 18px 15px;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -291,7 +299,7 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* First and last cell rounded corners */
|
|
||||||
.table td:first-child {
|
.table td:first-child {
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -587,7 +595,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.date-separator {
|
.date-separator {
|
||||||
color: #64748b;
|
color: #000000ff;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-family: 'Inter', sans-serif;
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,401 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------
|
||||||
|
INVOICE PREVIEW RESPONSIVE FIXES
|
||||||
|
-------------------------------------------------- */
|
||||||
|
.invoice-preview-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-preview-wrapper * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override any fixed width styles that might be in popup_invoice */
|
||||||
|
#invoicePreview,
|
||||||
|
.invoice-container,
|
||||||
|
.invoice-wrapper {
|
||||||
|
max-width: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive table fixes for invoice */
|
||||||
|
.invoice-preview-wrapper table {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
table-layout: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-preview-wrapper .table-responsive {
|
||||||
|
overflow-x: auto !important;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure all elements scale properly */
|
||||||
|
.invoice-preview-wrapper .row,
|
||||||
|
.invoice-preview-wrapper .col,
|
||||||
|
.invoice-preview-wrapper [class*="col-"] {
|
||||||
|
flex: 1 1 auto !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force responsive behavior for print-style elements */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.invoice-preview-wrapper {
|
||||||
|
font-size: 95%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.invoice-preview-wrapper {
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.invoice-preview-wrapper {
|
||||||
|
font-size: 85%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-preview-wrapper table th,
|
||||||
|
.invoice-preview-wrapper table td {
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.invoice-preview-wrapper {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-preview-wrapper .d-flex {
|
||||||
|
flex-direction: column !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-preview-wrapper .text-end,
|
||||||
|
.invoice-preview-wrapper .text-start {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent any fixed pixel widths */
|
||||||
|
.invoice-preview-wrapper [style*="width:"]:not([style*="width:100%"]):not([style*="width:auto"]) {
|
||||||
|
width: auto !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-preview-wrapper [style*="min-width"] {
|
||||||
|
min-width: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------
|
||||||
|
COMPACT CARD DESIGN
|
||||||
|
-------------------------------------------------- */
|
||||||
|
.glass-card {
|
||||||
|
background: var(--glass-bg);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
box-shadow: var(--shadow-strong);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
animation: fadeUp 0.6s ease;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-card::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(45deg,
|
||||||
|
rgba(102, 126, 234, 0.03),
|
||||||
|
rgba(118, 75, 162, 0.03) 50%,
|
||||||
|
rgba(16, 185, 129, 0.03));
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------
|
||||||
|
COMPACT CARD HEADER
|
||||||
|
-------------------------------------------------- */
|
||||||
|
.card-header-compact {
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
color: #fff;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border: none;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-compact h4 {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------
|
||||||
|
COMPACT CARD BODY
|
||||||
|
-------------------------------------------------- */
|
||||||
|
.card-body-compact {
|
||||||
|
padding: 1.5rem;
|
||||||
|
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------
|
||||||
|
COMPACT FORM ELEMENTS
|
||||||
|
-------------------------------------------------- */
|
||||||
|
.form-grid-compact {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group-compact {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-compact {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control-compact, .form-select-compact {
|
||||||
|
border: 2px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: white;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control-compact:focus, .form-select-compact:focus {
|
||||||
|
border-color: #667eea;
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------
|
||||||
|
COMPACT BUTTONS
|
||||||
|
-------------------------------------------------- */
|
||||||
|
.btn-compact {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success-compact {
|
||||||
|
background: var(--success-gradient);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success-compact:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary-compact {
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary-compact:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger-compact {
|
||||||
|
background: var(--danger-gradient);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger-compact:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(239, 68, 68, 0.4);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info-compact {
|
||||||
|
background: linear-gradient(135deg, #06b6d4 0%, #0ea5e9 100%);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 12px rgba(6, 182, 212, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info-compact:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(6, 182, 212, 0.4);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning-compact {
|
||||||
|
background: var(--warning-gradient);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning-compact:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(245, 158, 11, 0.4);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------
|
||||||
|
COMPACT SUMMARY CARDS
|
||||||
|
-------------------------------------------------- */
|
||||||
|
.summary-grid-compact {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card-compact {
|
||||||
|
background: white;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: var(--shadow-soft);
|
||||||
|
border-left: 4px solid;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card-compact.total { border-left-color: #10b981; }
|
||||||
|
.summary-card-compact.paid { border-left-color: #3b82f6; }
|
||||||
|
.summary-card-compact.remaining { border-left-color: #f59e0b; }
|
||||||
|
|
||||||
|
.summary-value-compact {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-label-compact {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #64748b;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------
|
||||||
|
COMPACT AMOUNT BREAKDOWN
|
||||||
|
-------------------------------------------------- */
|
||||||
|
.amount-breakdown-compact {
|
||||||
|
background: white;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: var(--shadow-soft);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breakdown-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breakdown-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breakdown-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breakdown-value {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------
|
||||||
|
COMPACT TABLE
|
||||||
|
-------------------------------------------------- */
|
||||||
|
.table-compact {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-compact thead th {
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
color: white;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-compact tbody tr {
|
||||||
|
background: white;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-compact tbody tr:hover {
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-compact tbody td {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------
|
||||||
|
BADGE STYLES
|
||||||
|
-------------------------------------------------- */
|
||||||
|
.badge-compact {
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------
|
||||||
|
HEADER ACTION BUTTONS
|
||||||
|
-------------------------------------------------- */
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------
|
||||||
|
RESPONSIVE DESIGN
|
||||||
|
-------------------------------------------------- */
|
||||||
|
@media (max-width: 768px) {
|
||||||
.glass-card {
|
.glass-card {
|
||||||
background: var(--glass-bg);
|
background: var(--glass-bg);
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
@@ -309,16 +704,59 @@
|
|||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
.invoice-preview-wrapper {
|
||||||
|
max-width: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-preview-wrapper * {
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-card {
|
||||||
|
box-shadow: none !important;
|
||||||
|
border: 1px solid #000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-compact {
|
||||||
|
background: #000 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="container-fluid py-3">
|
<div class="container-fluid py-3">
|
||||||
{{-- Invoice Preview / Overview --}}
|
{{-- Invoice Preview / Overview --}}
|
||||||
<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-file-invoice me-2"></i>
|
<i class="fas fa-file-invoice me-2"></i>
|
||||||
Invoice Overview
|
Invoice Overview
|
||||||
</h4>
|
</h4>
|
||||||
|
<div class="header-actions">
|
||||||
|
<!-- Share Invoice -->
|
||||||
|
<button class="btn-info-compact btn-compact" id="shareInvoiceBtn">
|
||||||
|
<i class="fas fa-share-alt me-2"></i>Share Invoice
|
||||||
|
</button>
|
||||||
|
<!-- Download Invoice -->
|
||||||
|
<a href="{{ route('admin.invoices.download', $invoice->id) }}"
|
||||||
|
class="btn-warning-compact btn-compact" target="_blank">
|
||||||
|
<i class="fas fa-download me-2"></i>Download Invoice
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body-compact">
|
<div class="card-body-compact">
|
||||||
{{-- इथे popup_invoice मधूनच items table (editable) येईल --}}
|
{{-- इथे popup_invoice मधूनच items table (editable) येईल --}}
|
||||||
@@ -459,6 +897,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
@php
|
@php
|
||||||
$totalPaid = $invoice->installments()->sum('amount');
|
$totalPaid = $invoice->installments()->sum('amount');
|
||||||
$remaining = $invoice->final_amount_with_gst - $totalPaid;
|
$remaining = $invoice->final_amount_with_gst - $totalPaid;
|
||||||
@@ -551,6 +990,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Installment Management --}}
|
{{-- Installment Management --}}
|
||||||
|
=======
|
||||||
|
<!-- Installment Management -->
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
<div class="glass-card">
|
<div class="glass-card">
|
||||||
<div class="card-header-compact d-flex justify-content-between align-items-center">
|
<div class="card-header-compact d-flex justify-content-between align-items-center">
|
||||||
<h4 class="mb-0">
|
<h4 class="mb-0">
|
||||||
@@ -643,12 +1085,7 @@
|
|||||||
<h6 class="fw-bold mb-2 text-dark">
|
<h6 class="fw-bold mb-2 text-dark">
|
||||||
<i class="fas fa-history me-2"></i>Installment History
|
<i class="fas fa-history me-2"></i>Installment History
|
||||||
</h6>
|
</h6>
|
||||||
|
@if($invoice->installments->count() > 0)
|
||||||
<div id="noInstallmentsMsg"
|
|
||||||
class="{{ $invoice->installments->count() ? 'd-none' : '' }} text-center text-muted fw-bold py-4">
|
|
||||||
No installments found. Click Add Installment to create one.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table-compact">
|
<table class="table-compact">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -691,6 +1128,11 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@else
|
||||||
|
<div id="noInstallmentsMsg" class="text-center text-muted fw-bold py-4">
|
||||||
|
No installments found. Click "Add Installment" to create one.
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -717,6 +1159,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
if (submitForm && submitBtn) {
|
if (submitForm && submitBtn) {
|
||||||
submitForm.addEventListener("submit", function (e) {
|
submitForm.addEventListener("submit", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -825,12 +1268,131 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
const id = row.getAttribute("data-id");
|
const id = row.getAttribute("data-id");
|
||||||
|
|
||||||
fetch("{{ url('admin/installment') }}/" + id, {
|
fetch("{{ url('admin/installment') }}/" + id, {
|
||||||
|
=======
|
||||||
|
if (submitForm) {
|
||||||
|
submitForm.addEventListener("submit", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Processing...';
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
|
||||||
|
fetch("{{ route('admin.invoice.installment.store', $invoice->id) }}", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"X-CSRF-TOKEN": submitForm.querySelector("input[name=_token]").value,
|
||||||
|
"Accept": "application/json"
|
||||||
|
},
|
||||||
|
body: new FormData(submitForm)
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
|
||||||
|
if (data.status === "error") {
|
||||||
|
alert(data.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const table = document.querySelector("#installmentTable");
|
||||||
|
const noInstallmentsMsg = document.getElementById("noInstallmentsMsg");
|
||||||
|
|
||||||
|
if (noInstallmentsMsg) {
|
||||||
|
noInstallmentsMsg.classList.add("d-none");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!table) {
|
||||||
|
// Create table if it doesn't exist
|
||||||
|
const tableHTML = `
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table-compact">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Payment Method</th>
|
||||||
|
<th>Reference No</th>
|
||||||
|
<th>Amount</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="installmentTable">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
const parent = submitForm.closest('.card-body-compact');
|
||||||
|
parent.insertAdjacentHTML('beforeend', tableHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTable = document.querySelector("#installmentTable");
|
||||||
|
const index = newTable.rows.length + 1;
|
||||||
|
|
||||||
|
newTable.insertAdjacentHTML("beforeend", `
|
||||||
|
<tr data-id="${data.installment.id}">
|
||||||
|
<td class="fw-bold text-muted">${index}</td>
|
||||||
|
<td>${data.installment.installment_date}</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge-compact bg-primary bg-opacity-10 text-primary">
|
||||||
|
${data.installment.payment_method.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>${data.installment.reference_no || '-'}</td>
|
||||||
|
<td class="fw-bold text-success">${formatINR(data.installment.amount)}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn-danger-compact btn-compact btn-sm deleteInstallment">
|
||||||
|
<i class="fas fa-trash me-1"></i>Delete
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Update all displayed values
|
||||||
|
if (document.getElementById("paidAmount")) document.getElementById("paidAmount").textContent = formatINR(data.totalPaid);
|
||||||
|
if (document.getElementById("remainingAmount")) document.getElementById("remainingAmount").textContent = formatINR(data.remaining);
|
||||||
|
|
||||||
|
// Update summary cards
|
||||||
|
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
|
||||||
|
if (paidCard) paidCard.textContent = formatINR(data.totalPaid);
|
||||||
|
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
|
||||||
|
if (remainingCard) remainingCard.textContent = formatINR(data.remaining);
|
||||||
|
|
||||||
|
submitForm.reset();
|
||||||
|
|
||||||
|
// If fully paid, disable/add display logic
|
||||||
|
if (data.isCompleted) {
|
||||||
|
toggleBtn?.remove();
|
||||||
|
formBox.classList.add("d-none");
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(data.message);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
alert("Something went wrong. Please try again.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete Installment
|
||||||
|
document.addEventListener("click", function (e) {
|
||||||
|
if (!e.target.classList.contains("deleteInstallment")) return;
|
||||||
|
|
||||||
|
if (!confirm("Are you sure you want to delete this installment?")) return;
|
||||||
|
|
||||||
|
const row = e.target.closest("tr");
|
||||||
|
const id = row.getAttribute("data-id");
|
||||||
|
|
||||||
|
fetch("{{ url('/admin/installment') }}/" + id, {
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').getAttribute("content"),
|
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').getAttribute("content"),
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
<<<<<<< HEAD
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.status === "success") {
|
if (data.status === "success") {
|
||||||
@@ -858,6 +1420,49 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
|
|
||||||
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
|
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
|
||||||
if (remainingCard) remainingCard.textContent = "₹" + formatINR(data.remaining);
|
if (remainingCard) remainingCard.textContent = "₹" + formatINR(data.remaining);
|
||||||
|
=======
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === "success") {
|
||||||
|
row.style.opacity = "0";
|
||||||
|
setTimeout(() => {
|
||||||
|
row.remove();
|
||||||
|
|
||||||
|
// Update row numbers
|
||||||
|
const rows = document.querySelectorAll("#installmentTable tr");
|
||||||
|
rows.forEach((row, index) => {
|
||||||
|
row.querySelector("td:first-child").textContent = index + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show no installments message if empty
|
||||||
|
if (rows.length === 0) {
|
||||||
|
const noInstallmentsMsg = document.getElementById("noInstallmentsMsg");
|
||||||
|
if (noInstallmentsMsg) {
|
||||||
|
noInstallmentsMsg.classList.remove("d-none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
// Update all displayed values
|
||||||
|
if (document.getElementById("paidAmount")) document.getElementById("paidAmount").textContent = formatINR(data.totalPaid);
|
||||||
|
if (document.getElementById("remainingAmount")) document.getElementById("remainingAmount").textContent = formatINR(data.remaining);
|
||||||
|
|
||||||
|
// Update summary cards
|
||||||
|
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
|
||||||
|
if (paidCard) paidCard.textContent = formatINR(data.totalPaid);
|
||||||
|
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
|
||||||
|
if (remainingCard) remainingCard.textContent = formatINR(data.remaining);
|
||||||
|
|
||||||
|
// Show add installment button if there's remaining amount
|
||||||
|
if (data.remaining > 0 && !document.getElementById("toggleInstallmentForm")) {
|
||||||
|
const header = document.querySelector(".glass-card .card-header-compact");
|
||||||
|
header.innerHTML += `
|
||||||
|
<button id="toggleInstallmentForm" class="btn-primary-compact btn-compact">
|
||||||
|
<i class="fas fa-plus-circle me-2"></i>Add Installment
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
|
|
||||||
if (data.isZero) {
|
if (data.isZero) {
|
||||||
document.getElementById("noInstallmentsMsg")?.classList.remove("d-none");
|
document.getElementById("noInstallmentsMsg")?.classList.remove("d-none");
|
||||||
@@ -874,4 +1479,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<<<<<<< HEAD
|
||||||
@endsection
|
@endsection
|
||||||
|
=======
|
||||||
|
@endsection
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
|
|||||||
@@ -199,7 +199,7 @@
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 10px 18px !important;
|
padding: 10px 18px !important;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 48px;
|
height: 65px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
@@ -219,6 +219,32 @@
|
|||||||
font-size: 1.06rem;
|
font-size: 1.06rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ================================
|
||||||
|
HEADER NOTIFICATION BADGE FIX
|
||||||
|
================================ */
|
||||||
|
|
||||||
|
/* Target ONLY badge inside bell icon */
|
||||||
|
header .bi-bell {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override broken global badge styles */
|
||||||
|
header .bi-bell .badge {
|
||||||
|
width: 22px !important;
|
||||||
|
height: 22px !important;
|
||||||
|
min-width: 22px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
line-height: 22px !important;
|
||||||
|
border-radius: 50% !important;
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
animation: none !important;
|
||||||
|
box-shadow: 0 0 0 2px #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
@@ -295,12 +321,12 @@
|
|||||||
</a>
|
</a>
|
||||||
@endcan
|
@endcan
|
||||||
|
|
||||||
{{-- Profile Update Requests --}}
|
<!-- {{-- Profile Update Requests --}}
|
||||||
@can('request.update_profile')
|
@can('request.update_profile')
|
||||||
<a href="{{ route('admin.profile.requests') }}" class="{{ request()->routeIs('admin.profile.requests') ? 'active' : '' }}">
|
<a href="{{ route('admin.profile.requests') }}">
|
||||||
<i class="bi bi-person-lines-fill"></i> Profile Update Requests
|
<i class="bi bi-person-lines-fill"></i> Profile Update Requests
|
||||||
</a>
|
</a>
|
||||||
@endcan
|
@endcan -->
|
||||||
|
|
||||||
{{-- Staff --}}
|
{{-- Staff --}}
|
||||||
@can('staff.view')
|
@can('staff.view')
|
||||||
@@ -371,7 +397,7 @@
|
|||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
@vite(['resources/js/app.js'])
|
@vite(['resources/js/app.js'])
|
||||||
@yield('scripts')
|
@yield('scripts') <!-- ⭐ REQUIRED FOR CHAT TO WORK -->
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const headerToggle = document.getElementById('headerToggle');
|
const headerToggle = document.getElementById('headerToggle');
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
@extends('admin.layouts.app')
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
@section('page-title', 'Orders')
|
@section('page-title', 'Orders')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<style>
|
<style>
|
||||||
/* ===== GLOBAL RESPONSIVE STYLES ===== */
|
/* ===== GLOBAL RESPONSIVE STYLES ===== */
|
||||||
html, body {
|
html, body {
|
||||||
overflow-x: hidden !important;
|
overflow-x: hidden !important;
|
||||||
@@ -553,6 +553,29 @@
|
|||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.date-range {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-input {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
background: white;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.filter-bar {
|
.filter-bar {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -563,38 +586,60 @@
|
|||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-select {
|
.filter-select,
|
||||||
|
.date-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.date-range {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
.pagination-container {
|
.pagination-container {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="orders-container">
|
<div class="orders-container">
|
||||||
<div class="orders-title">
|
<div class="orders-title">
|
||||||
<i class="fas fa-box-open"></i> Orders Management
|
<i class="fas fa-box-open"></i> Orders Management
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Stats Cards -->
|
<!-- Stats Cards -->
|
||||||
<div class="stats-container">
|
<div class="stats-container">
|
||||||
|
@php
|
||||||
|
$totalOrders = $orders->count();
|
||||||
|
$paidInvoices = $orders->filter(function($order) {
|
||||||
|
$status = $order->invoice?->status ?? 'pending';
|
||||||
|
return strtolower($status) === 'paid';
|
||||||
|
})->count();
|
||||||
|
$pendingInvoices = $orders->filter(function($order) {
|
||||||
|
$status = $order->invoice?->status ?? 'pending';
|
||||||
|
return strtolower($status) === 'pending';
|
||||||
|
})->count();
|
||||||
|
$overdueInvoices = $orders->filter(function($order) {
|
||||||
|
$status = $order->invoice?->status ?? 'pending';
|
||||||
|
return strtolower($status) === 'overdue';
|
||||||
|
})->count();
|
||||||
|
@endphp
|
||||||
|
|
||||||
<div class="stat-card total">
|
<div class="stat-card total">
|
||||||
<div class="stat-value">{{ $orders->count() }}</div>
|
<div class="stat-value">{{ $totalOrders }}</div>
|
||||||
<div class="stat-label">Total Orders</div>
|
<div class="stat-label">Total Orders</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card paid">
|
<div class="stat-card paid">
|
||||||
<div class="stat-value">{{ $orders->where('invoice.status', 'paid')->count() }}</div>
|
<div class="stat-value">{{ $paidInvoices }}</div>
|
||||||
<div class="stat-label">Paid Invoices</div>
|
<div class="stat-label">Paid Invoices</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card pending">
|
<div class="stat-card pending">
|
||||||
<div class="stat-value">{{ $orders->where('invoice.status', 'pending')->count() }}</div>
|
<div class="stat-value">{{ $pendingInvoices }}</div>
|
||||||
<div class="stat-label">Pending Invoices</div>
|
<div class="stat-label">Pending Invoices</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card overdue">
|
<div class="stat-card overdue">
|
||||||
<div class="stat-value">{{ $orders->where('invoice.status', 'overdue')->count() }}</div>
|
<div class="stat-value">{{ $overdueInvoices }}</div>
|
||||||
<div class="stat-label">Overdue Invoices</div>
|
<div class="stat-label">Overdue Invoices</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -617,12 +662,14 @@
|
|||||||
<i class="fas fa-search search-icon"></i>
|
<i class="fas fa-search search-icon"></i>
|
||||||
<input type="text" class="search-input" placeholder="Search orders..." id="searchInput">
|
<input type="text" class="search-input" placeholder="Search orders..." id="searchInput">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<select class="filter-select" id="statusFilter">
|
<select class="filter-select" id="statusFilter">
|
||||||
<option value="">All Status</option>
|
<option value="">All Invoice Status</option>
|
||||||
<option value="paid">Paid</option>
|
<option value="paid">Paid</option>
|
||||||
<option value="pending">Pending</option>
|
<option value="pending">Pending</option>
|
||||||
<option value="overdue">Overdue</option>
|
<option value="overdue">Overdue</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select class="filter-select" id="shipmentFilter">
|
<select class="filter-select" id="shipmentFilter">
|
||||||
<option value="">All Shipments</option>
|
<option value="">All Shipments</option>
|
||||||
<option value="pending">Pending</option>
|
<option value="pending">Pending</option>
|
||||||
@@ -630,6 +677,13 @@
|
|||||||
<option value="dispatched">Dispatched</option>
|
<option value="dispatched">Dispatched</option>
|
||||||
<option value="delivered">Delivered</option>
|
<option value="delivered">Delivered</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<div class="date-range">
|
||||||
|
<span class="date-label">From:</span>
|
||||||
|
<input type="date" class="date-input" id="fromDate">
|
||||||
|
<span class="date-label">To:</span>
|
||||||
|
<input type="date" class="date-input" id="toDate">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(isset($orders) && $orders->count() > 0)
|
@if(isset($orders) && $orders->count() > 0)
|
||||||
@@ -660,20 +714,23 @@
|
|||||||
$mark = $order->markList ?? null;
|
$mark = $order->markList ?? null;
|
||||||
$invoice = $order->invoice ?? null;
|
$invoice = $order->invoice ?? null;
|
||||||
$shipment = $order->shipments->first() ?? null;
|
$shipment = $order->shipments->first() ?? null;
|
||||||
$invoiceStatus = strtolower($invoice->status ?? '');
|
|
||||||
$shipmentStatus = strtolower(str_replace('_', ' ', $shipment->status ?? ''));
|
// Normalized status values for consistent CSS classes
|
||||||
|
$invoiceStatus = strtolower($invoice?->status ?? 'pending');
|
||||||
|
$shipmentStatus = strtolower($shipment?->status ?? 'pending');
|
||||||
|
$shipmentStatusForCss = str_replace([' ', '-'], '_', $shipmentStatus);
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $order->order_id ?? '-' }}</td>
|
<td>{{ $order->order_id ?? '-' }}</td>
|
||||||
<td>{{ $shipment->shipment_id ?? '-' }}</td>
|
<td>{{ $shipment?->shipment_id ?? '-' }}</td>
|
||||||
<td>{{ $mark->customer_id ?? '-' }}</td>
|
<td>{{ $mark?->customer_id ?? '-' }}</td>
|
||||||
<td>{{ $mark->company_name ?? '-' }}</td>
|
<td>{{ $mark?->company_name ?? '-' }}</td>
|
||||||
<td>{{ $mark->origin ?? $order->origin ?? '-' }}</td>
|
<td>{{ $mark?->origin ?? $order->origin ?? '-' }}</td>
|
||||||
<td>{{ $mark->destination ?? $order->destination ?? '-' }}</td>
|
<td>{{ $mark?->destination ?? $order->destination ?? '-' }}</td>
|
||||||
<td>{{ $order->created_at ? $order->created_at->format('d-m-Y') : '-' }}</td>
|
<td>{{ $order->created_at ? $order->created_at->format('d-m-Y') : '-' }}</td>
|
||||||
|
|
||||||
<td>{{ $invoice->invoice_number ?? '-' }}</td>
|
<td>{{ $invoice?->invoice_number ?? '-' }}</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
{{ $invoice?->invoice_date ? date('d-m-Y', strtotime($invoice->invoice_date)) : '-' }}
|
{{ $invoice?->invoice_date ? date('d-m-Y', strtotime($invoice->invoice_date)) : '-' }}
|
||||||
@@ -688,27 +745,19 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
@if($invoice?->status)
|
|
||||||
<span class="status-badge status-{{ $invoiceStatus }}">
|
<span class="status-badge status-{{ $invoiceStatus }}">
|
||||||
{{ ucfirst($invoice->status) }}
|
{{ ucfirst($invoiceStatus) }}
|
||||||
</span>
|
</span>
|
||||||
@else
|
|
||||||
<span class="status-badge status-pending">Pending</span>
|
|
||||||
@endif
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
@if($shipment?->status)
|
<span class="status-badge ship-{{ $shipmentStatusForCss }}">
|
||||||
<span class="status-badge ship-{{ str_replace(' ', '_', $shipmentStatus) }}">
|
|
||||||
{{ ucfirst($shipmentStatus) }}
|
{{ ucfirst($shipmentStatus) }}
|
||||||
</span>
|
</span>
|
||||||
@else
|
|
||||||
<span class="status-badge ship-pending">Pending</span>
|
|
||||||
@endif
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="{{ route('admin.orders.show', $order->id) }}" title="View Details">
|
<a href="{{ route('admin.orders.see', $order->id) }}" title="View Details">
|
||||||
<i class="fas fa-eye action-btn"></i>
|
<i class="fas fa-eye action-btn"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -744,17 +793,14 @@
|
|||||||
<p class="text-muted">There are currently no orders in the system.</p>
|
<p class="text-muted">There are currently no orders in the system.</p>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Pagination state
|
// Pagination state
|
||||||
let currentPage = 1;
|
let currentPage = 1;
|
||||||
const itemsPerPage = 10;
|
const itemsPerPage = 10;
|
||||||
let allOrders = @json($orders);
|
let allOrders = {!! $orders->toJson() !!};
|
||||||
let filteredOrders = [...allOrders];
|
let filteredOrders = [...allOrders];
|
||||||
console.log('ORDERS SAMPLE:', allOrders[0]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Status icon helper functions
|
// Status icon helper functions
|
||||||
function getInvoiceStatusIcon(status) {
|
function getInvoiceStatusIcon(status) {
|
||||||
@@ -785,26 +831,96 @@ console.log('ORDERS SAMPLE:', allOrders[0]);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Date validation
|
||||||
|
function isValidDate(dateString) {
|
||||||
|
if (!dateString) return false;
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date instanceof Date && !isNaN(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date comparison helper - FIXED to properly handle order date
|
||||||
|
function isDateBetween(dateToCheck, fromDate, toDate) {
|
||||||
|
if (!dateToCheck) return true; // If no date to check, don't filter it out
|
||||||
|
|
||||||
|
// Parse the date to check (order date)
|
||||||
|
const checkDate = new Date(dateToCheck);
|
||||||
|
if (!isValidDate(checkDate)) return true;
|
||||||
|
|
||||||
|
// Set time to beginning of day for comparison
|
||||||
|
checkDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
// Check from date
|
||||||
|
if (fromDate && isValidDate(fromDate)) {
|
||||||
|
const from = new Date(fromDate);
|
||||||
|
from.setHours(0, 0, 0, 0);
|
||||||
|
if (checkDate < from) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to date
|
||||||
|
if (toDate && isValidDate(toDate)) {
|
||||||
|
const to = new Date(toDate);
|
||||||
|
to.setHours(23, 59, 59, 999);
|
||||||
|
if (checkDate > to) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to check if an order matches all filters
|
||||||
|
function orderMatchesFilters(order, searchTerm, statusFilter, shipmentFilter, fromDate, toDate) {
|
||||||
|
// Search term matching across multiple fields
|
||||||
|
let matchesSearch = true;
|
||||||
|
if (searchTerm) {
|
||||||
|
const searchFields = [
|
||||||
|
order.order_id?.toString().toLowerCase(),
|
||||||
|
order.shipments?.[0]?.shipment_id?.toString().toLowerCase(),
|
||||||
|
order.mark_list?.customer_id?.toString().toLowerCase(),
|
||||||
|
order.mark_list?.company_name?.toString().toLowerCase(),
|
||||||
|
order.invoice?.invoice_number?.toString().toLowerCase(),
|
||||||
|
order.mark_list?.origin?.toString().toLowerCase(),
|
||||||
|
order.mark_list?.destination?.toString().toLowerCase()
|
||||||
|
];
|
||||||
|
matchesSearch = searchFields.some(field => field && field.includes(searchTerm.toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoice status filter with safe access and normalization
|
||||||
|
const invoiceStatus = (order.invoice?.status || 'pending').toLowerCase();
|
||||||
|
const matchesStatus = !statusFilter || invoiceStatus === statusFilter;
|
||||||
|
|
||||||
|
// Shipment status filter with safe access and normalization
|
||||||
|
const shipmentStatus = (order.shipments?.[0]?.status || 'pending').toLowerCase();
|
||||||
|
const matchesShipment = !shipmentFilter || shipmentStatus === shipmentFilter;
|
||||||
|
|
||||||
|
// Date range filter - using order date (created_at)
|
||||||
|
const orderDate = order.created_at;
|
||||||
|
const matchesDate = isDateBetween(orderDate, fromDate, toDate);
|
||||||
|
|
||||||
|
return matchesSearch && matchesStatus && matchesShipment && matchesDate;
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize pagination and filters
|
// Initialize pagination and filters
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Set today as default "to" date
|
||||||
renderTable();
|
renderTable();
|
||||||
updatePaginationControls();
|
updatePaginationControls();
|
||||||
|
|
||||||
// Bind pagination events
|
// Pagination events
|
||||||
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
|
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
|
||||||
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
|
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
|
||||||
|
|
||||||
// Bind filter events
|
// Filter events
|
||||||
document.getElementById('searchInput').addEventListener('input', handleSearch);
|
document.getElementById('searchInput').addEventListener('input', handleFilter);
|
||||||
document.getElementById('statusFilter').addEventListener('change', handleFilter);
|
document.getElementById('statusFilter').addEventListener('change', handleFilter);
|
||||||
document.getElementById('shipmentFilter').addEventListener('change', handleFilter);
|
document.getElementById('shipmentFilter').addEventListener('change', handleFilter);
|
||||||
|
document.getElementById('fromDate').addEventListener('change', handleFilter);
|
||||||
|
document.getElementById('toDate').addEventListener('change', handleFilter);
|
||||||
|
|
||||||
// Bind download events
|
// Download buttons
|
||||||
document.getElementById('downloadPdf').addEventListener('click', downloadPdf);
|
document.getElementById('downloadPdf').addEventListener('click', downloadPdf);
|
||||||
document.getElementById('downloadExcel').addEventListener('click', downloadExcel);
|
document.getElementById('downloadExcel').addEventListener('click', downloadExcel);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Download Functions
|
// Download Functions with ALL filter parameters
|
||||||
function downloadPdf() {
|
function downloadPdf() {
|
||||||
if (filteredOrders.length === 0) {
|
if (filteredOrders.length === 0) {
|
||||||
showNotification('No data available to download', 'warning');
|
showNotification('No data available to download', 'warning');
|
||||||
@@ -813,25 +929,26 @@ console.log('ORDERS SAMPLE:', allOrders[0]);
|
|||||||
|
|
||||||
showNotification('Preparing PDF download...', 'info');
|
showNotification('Preparing PDF download...', 'info');
|
||||||
|
|
||||||
// Get current filters for the download
|
// Get all current filters
|
||||||
const searchTerm = document.getElementById('searchInput').value;
|
const searchTerm = document.getElementById('searchInput').value;
|
||||||
const statusFilter = document.getElementById('statusFilter').value;
|
const statusFilter = document.getElementById('statusFilter').value;
|
||||||
const shipmentFilter = document.getElementById('shipmentFilter').value;
|
const shipmentFilter = document.getElementById('shipmentFilter').value;
|
||||||
|
const fromDate = document.getElementById('fromDate').value;
|
||||||
|
const toDate = document.getElementById('toDate').value;
|
||||||
|
|
||||||
// Create download URL with filters
|
// Create download URL with all filters - ALWAYS include all filters
|
||||||
let downloadUrl = "{{ route('admin.orders.download.pdf') }}";
|
let downloadUrl = "{{ route('admin.orders.download.pdf') }}";
|
||||||
let params = new URLSearchParams();
|
let params = new URLSearchParams();
|
||||||
|
|
||||||
if (searchTerm) params.append('search', searchTerm);
|
// Always append all parameters, even if empty
|
||||||
if (statusFilter) params.append('status', statusFilter);
|
params.append('search', searchTerm || '');
|
||||||
if (shipmentFilter) params.append('shipment', shipmentFilter);
|
params.append('status', statusFilter || '');
|
||||||
|
params.append('shipment', shipmentFilter || '');
|
||||||
|
params.append('from_date', fromDate || '');
|
||||||
|
params.append('to_date', toDate || '');
|
||||||
|
|
||||||
if (params.toString()) {
|
// Trigger download with all parameters
|
||||||
downloadUrl += '?' + params.toString();
|
window.location.href = downloadUrl + '?' + params.toString();
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger download
|
|
||||||
window.location.href = downloadUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadExcel() {
|
function downloadExcel() {
|
||||||
@@ -842,120 +959,49 @@ console.log('ORDERS SAMPLE:', allOrders[0]);
|
|||||||
|
|
||||||
showNotification('Preparing Excel download...', 'info');
|
showNotification('Preparing Excel download...', 'info');
|
||||||
|
|
||||||
// Get current filters for the download
|
// Get all current filters
|
||||||
const searchTerm = document.getElementById('searchInput').value;
|
const searchTerm = document.getElementById('searchInput').value;
|
||||||
const statusFilter = document.getElementById('statusFilter').value;
|
const statusFilter = document.getElementById('statusFilter').value;
|
||||||
const shipmentFilter = document.getElementById('shipmentFilter').value;
|
const shipmentFilter = document.getElementById('shipmentFilter').value;
|
||||||
|
const fromDate = document.getElementById('fromDate').value;
|
||||||
|
const toDate = document.getElementById('toDate').value;
|
||||||
|
|
||||||
// Create download URL with filters
|
// Create download URL with all filters - ALWAYS include all filters
|
||||||
let downloadUrl = "{{ route('admin.orders.download.excel') }}";
|
let downloadUrl = "{{ route('admin.orders.download.excel') }}";
|
||||||
let params = new URLSearchParams();
|
let params = new URLSearchParams();
|
||||||
|
|
||||||
if (searchTerm) params.append('search', searchTerm);
|
// Always append all parameters, even if empty
|
||||||
if (statusFilter) params.append('status', statusFilter);
|
params.append('search', searchTerm || '');
|
||||||
if (shipmentFilter) params.append('shipment', shipmentFilter);
|
params.append('status', statusFilter || '');
|
||||||
|
params.append('shipment', shipmentFilter || '');
|
||||||
|
params.append('from_date', fromDate || '');
|
||||||
|
params.append('to_date', toDate || '');
|
||||||
|
|
||||||
if (params.toString()) {
|
// Trigger download with all parameters
|
||||||
downloadUrl += '?' + params.toString();
|
window.location.href = downloadUrl + '?' + params.toString();
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger download
|
|
||||||
window.location.href = downloadUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notification function
|
|
||||||
function showNotification(message, type = 'info') {
|
|
||||||
// Remove existing notification
|
|
||||||
const existingNotification = document.querySelector('.download-notification');
|
|
||||||
if (existingNotification) {
|
|
||||||
existingNotification.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
const notification = document.createElement('div');
|
|
||||||
notification.className = `download-notification alert alert-${type}`;
|
|
||||||
notification.style.cssText = `
|
|
||||||
position: fixed;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
z-index: 10000;
|
|
||||||
padding: 12px 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: white;
|
|
||||||
font-weight: 500;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
||||||
animation: slideIn 0.3s ease;
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (type === 'info') {
|
|
||||||
notification.style.background = 'linear-gradient(135deg, #3b82f6, #1d4ed8)';
|
|
||||||
} else if (type === 'warning') {
|
|
||||||
notification.style.background = 'linear-gradient(135deg, #f59e0b, #d97706)';
|
|
||||||
} else if (type === 'success') {
|
|
||||||
notification.style.background = 'linear-gradient(135deg, #10b981, #059669)';
|
|
||||||
}
|
|
||||||
|
|
||||||
notification.textContent = message;
|
|
||||||
document.body.appendChild(notification);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
notification.style.animation = 'slideOut 0.3s ease';
|
|
||||||
setTimeout(() => notification.remove(), 300);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add CSS for notifications
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.textContent = `
|
|
||||||
@keyframes slideIn {
|
|
||||||
from { transform: translateX(100%); opacity: 0; }
|
|
||||||
to { transform: translateX(0); opacity: 1; }
|
|
||||||
}
|
|
||||||
@keyframes slideOut {
|
|
||||||
from { transform: translateX(0); opacity: 1; }
|
|
||||||
to { transform: translateX(100%); opacity: 0; }
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
|
|
||||||
// Search functionality
|
|
||||||
function handleSearch(e) {
|
|
||||||
const searchTerm = e.target.value.toLowerCase();
|
|
||||||
filterOrders();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter functionality
|
// Filter functionality
|
||||||
function handleFilter() {
|
function handleFilter() {
|
||||||
filterOrders();
|
const searchTerm = document.getElementById('searchInput').value;
|
||||||
}
|
|
||||||
|
|
||||||
function filterOrders() {
|
|
||||||
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
|
||||||
const statusFilter = document.getElementById('statusFilter').value;
|
const statusFilter = document.getElementById('statusFilter').value;
|
||||||
const shipmentFilter = document.getElementById('shipmentFilter').value;
|
const shipmentFilter = document.getElementById('shipmentFilter').value;
|
||||||
|
const fromDate = document.getElementById('fromDate').value;
|
||||||
|
const toDate = document.getElementById('toDate').value;
|
||||||
|
|
||||||
filteredOrders = allOrders.filter(order => {
|
// Apply all filters
|
||||||
const matchesSearch = !searchTerm ||
|
filteredOrders = allOrders.filter(order =>
|
||||||
order.order_id?.toLowerCase().includes(searchTerm) ||
|
orderMatchesFilters(order, searchTerm, statusFilter, shipmentFilter, fromDate, toDate)
|
||||||
order.mark_list?.company_name?.toLowerCase().includes(searchTerm) ||
|
);
|
||||||
order.invoice?.invoice_number?.toLowerCase().includes(searchTerm);
|
|
||||||
|
|
||||||
|
|
||||||
const matchesStatus = !statusFilter ||
|
|
||||||
(order.invoice?.status && order.invoice.status.toLowerCase() === statusFilter);
|
|
||||||
|
|
||||||
const matchesShipment = !shipmentFilter ||
|
|
||||||
(order.shipments?.[0]?.status && order.shipments[0].status.toLowerCase() === shipmentFilter);
|
|
||||||
|
|
||||||
return matchesSearch && matchesStatus && matchesShipment;
|
|
||||||
});
|
|
||||||
|
|
||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
renderTable();
|
renderTable();
|
||||||
updatePaginationControls();
|
updatePaginationControls();
|
||||||
|
|
||||||
// Update download buttons state
|
// Update download buttons state
|
||||||
document.getElementById('downloadPdf').disabled = filteredOrders.length === 0;
|
const hasResults = filteredOrders.length > 0;
|
||||||
document.getElementById('downloadExcel').disabled = filteredOrders.length === 0;
|
document.getElementById('downloadPdf').disabled = !hasResults;
|
||||||
|
document.getElementById('downloadExcel').disabled = !hasResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pagination Functions
|
// Pagination Functions
|
||||||
@@ -1056,8 +1102,11 @@ console.log('ORDERS SAMPLE:', allOrders[0]);
|
|||||||
const mark = order.mark_list || null;
|
const mark = order.mark_list || null;
|
||||||
const invoice = order.invoice || null;
|
const invoice = order.invoice || null;
|
||||||
const shipment = order.shipments?.[0] || null;
|
const shipment = order.shipments?.[0] || null;
|
||||||
const invoiceStatus = (invoice?.status || '').toLowerCase();
|
|
||||||
const shipmentStatus = (shipment?.status || '').toLowerCase().replace('_', ' ');
|
// Normalized status values matching Blade logic
|
||||||
|
const invoiceStatus = (invoice?.status || 'pending').toLowerCase();
|
||||||
|
const shipmentStatus = (shipment?.status || 'pending').toLowerCase();
|
||||||
|
const shipmentStatusForCss = shipmentStatus.replace(/[ -]/g, '_');
|
||||||
|
|
||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
@@ -1073,19 +1122,13 @@ console.log('ORDERS SAMPLE:', allOrders[0]);
|
|||||||
<td>${invoice?.final_amount ? '₹' + Number(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '-'}</td>
|
<td>${invoice?.final_amount ? '₹' + Number(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '-'}</td>
|
||||||
<td>${invoice?.final_amount_with_gst ? '₹' + Number(invoice.final_amount_with_gst).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '-'}</td>
|
<td>${invoice?.final_amount_with_gst ? '₹' + Number(invoice.final_amount_with_gst).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '-'}</td>
|
||||||
<td>
|
<td>
|
||||||
${invoice?.status
|
<span class="status-badge status-${invoiceStatus}">${getInvoiceStatusIcon(invoiceStatus)}${invoiceStatus.charAt(0).toUpperCase() + invoiceStatus.slice(1)}</span>
|
||||||
? `<span class="status-badge status-${invoiceStatus}">${getInvoiceStatusIcon(invoiceStatus)}${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}</span>`
|
|
||||||
: '<span class="status-badge status-pending"><i class="fas fa-clock status-icon"></i>Pending</span>'
|
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
${shipment?.status
|
<span class="status-badge ship-${shipmentStatusForCss}">${getShipmentStatusIcon(shipmentStatusForCss)}${shipmentStatus.charAt(0).toUpperCase() + shipmentStatus.slice(1)}</span>
|
||||||
? `<span class="status-badge ship-${shipmentStatus.replace(' ', '_')}">${getShipmentStatusIcon(shipmentStatus.replace(' ', '_'))}${shipment.status.charAt(0).toUpperCase() + shipment.status.slice(1)}</span>`
|
|
||||||
: '<span class="status-badge ship-pending"><i class="fas fa-clock status-icon"></i>Pending</span>'
|
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="/admin/orders/${order.id}" title="View Details">
|
<a href="/admin/orders/${order.id}/see" title="View Details">
|
||||||
<i class="fas fa-eye action-btn"></i>
|
<i class="fas fa-eye action-btn"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -1093,6 +1136,60 @@ console.log('ORDERS SAMPLE:', allOrders[0]);
|
|||||||
tbody.appendChild(row);
|
tbody.appendChild(row);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
|
||||||
@endsection
|
// Notification function
|
||||||
|
function showNotification(message, type = 'info') {
|
||||||
|
// Remove existing notification
|
||||||
|
const existingNotification = document.querySelector('.download-notification');
|
||||||
|
if (existingNotification) {
|
||||||
|
existingNotification.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const notification = document.createElement('div');
|
||||||
|
notification.className = `download-notification alert alert-${type}`;
|
||||||
|
notification.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 10000;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
|
animation: slideIn 0.3s ease;
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (type === 'info') {
|
||||||
|
notification.style.background = 'linear-gradient(135deg, #3b82f6, #1d4ed8)';
|
||||||
|
} else if (type === 'warning') {
|
||||||
|
notification.style.background = 'linear-gradient(135deg, #f59e0b, #d97706)';
|
||||||
|
} else if (type === 'success') {
|
||||||
|
notification.style.background = 'linear-gradient(135deg, #10b981, #059669)';
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.textContent = message;
|
||||||
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.style.animation = 'slideOut 0.3s ease';
|
||||||
|
setTimeout(() => notification.remove(), 300);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add CSS for notifications
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
@keyframes slideIn {
|
||||||
|
from { transform: translateX(100%); opacity: 0; }
|
||||||
|
to { transform: translateX(0); opacity: 1; }
|
||||||
|
}
|
||||||
|
@keyframes slideOut {
|
||||||
|
from { transform: translateX(0); opacity: 1; }
|
||||||
|
to { transform: translateX(100%); opacity: 0; }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
@endsection
|
||||||
@@ -11,17 +11,18 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h3>Orders Report</h3>
|
|
||||||
|
|
||||||
@if(!empty($filters))
|
<h3>Orders Report</h3>
|
||||||
|
|
||||||
|
@if(!empty($filters))
|
||||||
<p>
|
<p>
|
||||||
@if($filters['search']) Search: <strong>{{ $filters['search'] }}</strong> @endif
|
@if(!empty($filters['search'])) Search: <strong>{{ $filters['search'] }}</strong> @endif
|
||||||
@if($filters['status']) | Status: <strong>{{ ucfirst($filters['status']) }}</strong> @endif
|
@if(!empty($filters['status'])) | Status: <strong>{{ ucfirst($filters['status']) }}</strong> @endif
|
||||||
@if($filters['shipment']) | Shipment: <strong>{{ ucfirst($filters['shipment']) }}</strong> @endif
|
@if(!empty($filters['shipment'])) | Shipment: <strong>{{ ucfirst($filters['shipment']) }}</strong> @endif
|
||||||
</p>
|
</p>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Order ID</th>
|
<th>Order ID</th>
|
||||||
@@ -42,29 +43,32 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
@forelse($orders as $order)
|
@forelse($orders as $order)
|
||||||
@php
|
@php
|
||||||
$mark = $order->markList ?? null;
|
$mark = $order->markList;
|
||||||
$invoice = $order->invoice ?? null;
|
$invoice = $order->invoice;
|
||||||
$shipment = $order->shipments->first() ?? null;
|
$shipment = $order->shipments->first();
|
||||||
@endphp
|
@endphp
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $order->order_id }}</td>
|
<td>{{ $order->order_id }}</td>
|
||||||
<td>{{ $shipment->shipment_id ?? '-' }}</td>
|
<td>{{ $shipment?->shipment_id ?? '-' }}</td>
|
||||||
<td>{{ $mark->customer_id ?? '-' }}</td>
|
<td>{{ $mark?->customer_id ?? '-' }}</td>
|
||||||
<td>{{ $mark->company_name ?? '-' }}</td>
|
<td>{{ $mark?->company_name ?? '-' }}</td>
|
||||||
<td>{{ $mark->origin ?? $order->origin ?? '-' }}</td>
|
<td>{{ $mark?->origin ?? $order->origin ?? '-' }}</td>
|
||||||
<td>{{ $mark->destination ?? $order->destination ?? '-' }}</td>
|
<td>{{ $mark?->destination ?? $order->destination ?? '-' }}</td>
|
||||||
<td>{{ $order->created_at ? $order->created_at->format('d-m-Y') : '-' }}</td>
|
<td>{{ $order->created_at?->format('d-m-Y') ?? '-' }}</td>
|
||||||
<td>{{ $invoice->invoice_number ?? '-' }}</td>
|
<td>{{ $invoice?->invoice_number ?? '-' }}</td>
|
||||||
<td>{{ $invoice?->invoice_date ? \Carbon\Carbon::parse($invoice->invoice_date)->format('d-m-Y') : '-' }}</td>
|
<td>{{ $invoice?->invoice_date ? \Carbon\Carbon::parse($invoice->invoice_date)->format('d-m-Y') : '-' }}</td>
|
||||||
<td>{{ $invoice?->final_amount ? number_format($invoice->final_amount, 2) : '-' }}</td>
|
<td>{{ $invoice?->final_amount !== null ? number_format($invoice->final_amount, 2) : '-' }}</td>
|
||||||
<td>{{ $invoice?->final_amount_with_gst ? number_format($invoice->final_amount_with_gst, 2) : '-' }}</td>
|
<td>{{ $invoice?->final_amount_with_gst !== null ? number_format($invoice->final_amount_with_gst, 2) : '-' }}</td>
|
||||||
<td>{{ $invoice->status ? ucfirst($invoice->status) : 'Pending' }}</td>
|
<td>{{ ucfirst($invoice?->status ?? 'Pending') }}</td>
|
||||||
<td>{{ $shipment?->status ? ucfirst(str_replace('_',' ',$shipment->status)) : 'Pending' }}</td>
|
<td>{{ ucfirst(str_replace('_',' ', $shipment?->status ?? 'Pending')) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr><td colspan="13" style="text-align:center">No orders found</td></tr>
|
<tr>
|
||||||
|
<td colspan="13" style="text-align:center">No orders found</td>
|
||||||
|
</tr>
|
||||||
@endforelse
|
@endforelse
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -13,35 +13,41 @@
|
|||||||
<div class="d-flex justify-content-between align-items-start">
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
<div>
|
<div>
|
||||||
<h4 class="fw-bold mb-0">Order Details</h4>
|
<h4 class="fw-bold mb-0">Order Details</h4>
|
||||||
|
@php
|
||||||
|
$status = strtolower($order->status ?? '');
|
||||||
|
@endphp
|
||||||
|
|
||||||
<small class="text-muted">Detailed view of this shipment order</small>
|
<small class="text-muted">Detailed view of this shipment order</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- ADD ITEM --}}
|
{{-- ADD ITEM --}}
|
||||||
@can('order.create')
|
@can('order.create')
|
||||||
|
@if($status === 'pending')
|
||||||
<button class="btn btn-add-item" data-bs-toggle="modal" data-bs-target="#addItemModal">
|
<button class="btn btn-add-item" data-bs-toggle="modal" data-bs-target="#addItemModal">
|
||||||
<i class="fas fa-plus-circle me-2"></i>Add New Item
|
<i class="fas fa-plus-circle me-2"></i>Add New Item
|
||||||
</button>
|
</button>
|
||||||
|
@endif
|
||||||
@endcan
|
@endcan
|
||||||
|
|
||||||
|
|
||||||
<a href="{{ route('admin.dashboard') }}" class="btn-close"></a>
|
<a href="{{ route('admin.dashboard') }}" class="btn-close"></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- {{-- ACTION BUTTONS --}}
|
<!-- {{-- ACTION BUTTONS --}}
|
||||||
|
<div class="mt-3 d-flex gap-2">-->
|
||||||
|
|
||||||
|
|
||||||
<div class="mt-3 d-flex gap-2">
|
<div class="mt-3 d-flex gap-2">
|
||||||
|
{{-- Edit Order --}}
|
||||||
|
@if($status === 'pending')
|
||||||
|
<button class="btn btn-edit-order"
|
||||||
{{-- EDIT ORDER --}} -->
|
onclick="document.getElementById('editOrderForm').style.display='block'">
|
||||||
<!-- @if($order->status === 'pending')
|
|
||||||
<button class="btn btn-edit-order" onclick="document.getElementById('editOrderForm').style.display='block'">
|
|
||||||
<i class="fas fa-edit me-2"></i>Edit Order
|
<i class="fas fa-edit me-2"></i>Edit Order
|
||||||
</button>
|
</button>
|
||||||
@else
|
@endif
|
||||||
<button class="btn btn-edit-order" disabled><i class="fas fa-edit me-2"></i>Edit Order</button>
|
|
||||||
@endif -->
|
|
||||||
|
|
||||||
<!-- {{-- DELETE ORDER --}}
|
{{-- Delete Order --}}
|
||||||
@if($order->status === 'pending')
|
@if($status === 'pending')
|
||||||
<form action="{{ route('admin.orders.destroy', $order->id) }}"
|
<form action="{{ route('admin.orders.destroy', $order->id) }}"
|
||||||
method="POST"
|
method="POST"
|
||||||
onsubmit="return confirm('Delete this entire order?')">
|
onsubmit="return confirm('Delete this entire order?')">
|
||||||
@@ -51,9 +57,10 @@
|
|||||||
<i class="fas fa-trash-alt me-2"></i>Delete Order
|
<i class="fas fa-trash-alt me-2"></i>Delete Order
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@endif -->
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- </div> -->
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
@@ -191,11 +198,10 @@
|
|||||||
<td>{{ $item->shop_no }}</td>
|
<td>{{ $item->shop_no }}</td>
|
||||||
|
|
||||||
<td class="d-flex justify-content-center gap-2">
|
<td class="d-flex justify-content-center gap-2">
|
||||||
|
@if($status === 'pending')
|
||||||
{{-- EDIT BUTTON --}}
|
{{-- EDIT BUTTON --}}
|
||||||
@can('order.edit')
|
@can('order.edit')
|
||||||
<button
|
<button type="button"
|
||||||
type="button"
|
|
||||||
class="btn btn-sm btn-edit-item"
|
class="btn btn-sm btn-edit-item"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#editItemModal{{ $item->id }}">
|
data-bs-target="#editItemModal{{ $item->id }}">
|
||||||
@@ -203,20 +209,22 @@
|
|||||||
</button>
|
</button>
|
||||||
@endcan
|
@endcan
|
||||||
|
|
||||||
@can('order.delete')
|
|
||||||
{{-- DELETE BUTTON --}}
|
{{-- DELETE BUTTON --}}
|
||||||
|
@can('order.delete')
|
||||||
<form action="{{ route('admin.orders.deleteItem', $item->id) }}"
|
<form action="{{ route('admin.orders.deleteItem', $item->id) }}"
|
||||||
method="POST"
|
method="POST"
|
||||||
onsubmit="return confirm('Delete this item?')">
|
onsubmit="return confirm('Delete this item?')">
|
||||||
@csrf
|
@csrf
|
||||||
@method('DELETE')
|
@method('DELETE')
|
||||||
<button class="btn btn-sm btn-delete-item">
|
<button type="submit" class="btn btn-sm btn-delete-item">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@endcan
|
@endcan
|
||||||
|
@endif
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -617,7 +625,7 @@ function fillFormFromDeleted(item) {
|
|||||||
box-shadow: 0 4px 15px 0 rgba(102, 126, 234, 0.3);
|
box-shadow: 0 4px 15px 0 rgba(102, 126, 234, 0.3);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-right: -800px;
|
margin-right: -650px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-add-item:hover {
|
.btn-add-item:hover {
|
||||||
|
|||||||
@@ -11,59 +11,67 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
.container {
|
|
||||||
|
.container {
|
||||||
max-width: 850px;
|
max-width: 850px;
|
||||||
margin: 24px auto 0 auto;
|
margin: 24px auto 0 auto;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 13px;
|
border-radius: 13px;
|
||||||
box-shadow: 0 2px 14px rgba(40,105,160,0.08);
|
box-shadow: 0 2px 14px rgba(40,105,160,0.08);
|
||||||
padding: 35px 32px 18px 32px;
|
padding: 35px 32px 18px 32px;
|
||||||
}
|
}
|
||||||
.header {
|
|
||||||
display: flex;
|
/* ================= HEADER FIX ================= */
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
.header {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden; /* clears floats */
|
||||||
border-bottom: 2px solid #E6EBF0;
|
border-bottom: 2px solid #E6EBF0;
|
||||||
padding-bottom: 13px;
|
padding-bottom: 13px;
|
||||||
}
|
}
|
||||||
.logo-company {
|
|
||||||
display: flex;
|
/* LEFT SIDE */
|
||||||
align-items: flex-start;
|
.logo-company {
|
||||||
}
|
float: left;
|
||||||
.logo {
|
width: 65%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RIGHT SIDE */
|
||||||
|
.invoice-details {
|
||||||
|
float: right;
|
||||||
|
width: 35%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logo */
|
||||||
|
.logo {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
margin-right: 13px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
.company-details {
|
|
||||||
margin-top: 0;
|
/* Company text */
|
||||||
|
.company-details {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
line-height: 1.55;
|
||||||
.company-title {
|
}
|
||||||
|
|
||||||
|
.company-title {
|
||||||
font-size: 21px;
|
font-size: 21px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
.company-sub {
|
|
||||||
font-size: 16px;
|
/* Invoice */
|
||||||
margin-bottom: 8px;
|
.invoice-title {
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.invoice-details {
|
|
||||||
text-align: right;
|
|
||||||
min-width: 220px;
|
|
||||||
}
|
|
||||||
.invoice-title {
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 23px;
|
font-size: 23px;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
.paid-label {
|
|
||||||
margin-top: 8px;
|
/* Paid / Status */
|
||||||
margin-bottom: 2px;
|
.paid-tag {
|
||||||
}
|
|
||||||
.paid-tag {
|
|
||||||
background: #23BF47;
|
background: #23BF47;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -72,8 +80,9 @@
|
|||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.paid-tag:before {
|
|
||||||
|
.paid-tag:before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 7px;
|
left: 7px;
|
||||||
@@ -82,99 +91,80 @@
|
|||||||
height: 10px;
|
height: 10px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
.paid-date {
|
|
||||||
|
.paid-date {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #23BF47;
|
color: #23BF47;
|
||||||
margin-top: 2px;
|
margin-top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bill-section {
|
/* ================= REST ================= */
|
||||||
|
|
||||||
|
.bill-section {
|
||||||
background: #F3F7FB;
|
background: #F3F7FB;
|
||||||
border-radius: 11px;
|
border-radius: 11px;
|
||||||
padding: 20px 18px 13px 18px;
|
padding: 20px 18px 13px 18px;
|
||||||
margin: 28px 0 16px 0;
|
margin: 28px 0 16px 0;
|
||||||
box-shadow: 0 0px 0px #0000;
|
}
|
||||||
}
|
|
||||||
.bill-title {
|
.bill-title {
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #23355D;
|
color: #23355D;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
letter-spacing: 0.3px;
|
}
|
||||||
}
|
|
||||||
.bill-details {
|
.bill-details {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
margin-top: 9px;
|
margin-top: 9px;
|
||||||
margin-bottom: 13px;
|
margin-bottom: 13px;
|
||||||
background: #fff;
|
}
|
||||||
border-radius: 10px;
|
|
||||||
overflow: hidden;
|
th {
|
||||||
}
|
|
||||||
th {
|
|
||||||
background: #F6F7F9;
|
background: #F6F7F9;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
color: #6781A6;
|
color: #6781A6;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border: none;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
td {
|
|
||||||
|
td {
|
||||||
padding: 7px 0;
|
padding: 7px 0;
|
||||||
color: #222;
|
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
border: none;
|
color: #222;
|
||||||
text-align: left;
|
}
|
||||||
}
|
|
||||||
tbody tr:not(:last-child) td {
|
tbody tr:not(:last-child) td {
|
||||||
border-bottom: 1px solid #E6EBF0;
|
border-bottom: 1px solid #E6EBF0;
|
||||||
}
|
}
|
||||||
tbody tr:last-child td {
|
|
||||||
border-bottom: none;
|
.footer {
|
||||||
}
|
|
||||||
.totals-row td {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #23355D;
|
|
||||||
}
|
|
||||||
.gst-row td {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #23BF47;
|
|
||||||
}
|
|
||||||
.total-row td {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 17px;
|
|
||||||
color: #222;
|
|
||||||
}
|
|
||||||
.payment-info {
|
|
||||||
margin-top: 24px;
|
|
||||||
margin-bottom: 9px;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
.ref-number {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #6781A6;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
border-top: 1.2px solid #E6EBF0;
|
border-top: 1.2px solid #E6EBF0;
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #888;
|
color: #888;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.footer strong {
|
.gst-row td {
|
||||||
color: #222;
|
color: #23BF47;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.total-row td {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 17px;
|
||||||
|
border-top: 2px solid #E6EBF0;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -241,18 +231,47 @@
|
|||||||
<td>{{ number_format($item->ttl_amount, 0) }}</td>
|
<td>{{ number_format($item->ttl_amount, 0) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
{{-- SUBTOTAL --}}
|
||||||
<tr class="totals-row">
|
<tr class="totals-row">
|
||||||
<td colspan="3" style="text-align:right;">Subtotal:</td>
|
<td colspan="3" style="text-align:right;">total</td>
|
||||||
<td>{{ number_format($invoice->subtotal, 0) }}</td>
|
<td style="text-align:right;">
|
||||||
|
₹ {{ number_format($invoice->final_amount, 2) }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
{{-- TAX --}}
|
||||||
|
@if($invoice->tax_type === 'gst' && $invoice->gst_amount > 0)
|
||||||
|
|
||||||
<tr class="gst-row">
|
<tr class="gst-row">
|
||||||
<td colspan="3" style="text-align:right;">GST ({{ $invoice->gst_percent }}%):</td>
|
<td colspan="3" style="text-align:right;">
|
||||||
<td>{{ number_format($invoice->gst_amount, 0) }}</td>
|
GST ({{ $invoice->gst_percent }}%)
|
||||||
|
</td>
|
||||||
|
<td style="text-align:right;">
|
||||||
|
₹ {{ number_format($invoice->gst_amount, 2) }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
@elseif($invoice->tax_type === 'igst' && $invoice->gst_amount > 0)
|
||||||
|
|
||||||
|
<tr class="gst-row">
|
||||||
|
<td colspan="3" style="text-align:right;">
|
||||||
|
IGST ({{ $invoice->gst_percent }}%)
|
||||||
|
</td>
|
||||||
|
<td style="text-align:right;">
|
||||||
|
₹ {{ number_format($invoice->gst_amount, 2) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- TOTAL --}}
|
||||||
<tr class="total-row">
|
<tr class="total-row">
|
||||||
<td colspan="3" style="text-align:right;">Total:</td>
|
<td colspan="3" style="text-align:right;">Total Amount</td>
|
||||||
<td>{{ number_format($invoice->final_amount_with_gst, 0) }}</td>
|
<td style="text-align:right;">
|
||||||
|
₹ {{ number_format($invoice->final_amount_with_gst, 2) }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<!-- Payment Info & Reference -->
|
<!-- Payment Info & Reference -->
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
/* ALL YOUR EXISTING CSS STAYS HERE - EXCEPT GST TOTALS SECTION REMOVED */
|
||||||
:root {
|
:root {
|
||||||
--primary: #2c3e50;
|
--primary: #2c3e50;
|
||||||
--secondary: #3498db;
|
--secondary: #3498db;
|
||||||
@@ -69,6 +70,9 @@
|
|||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.id-box:hover {
|
.id-box:hover {
|
||||||
@@ -83,11 +87,10 @@
|
|||||||
.id-box-secondary {
|
.id-box-secondary {
|
||||||
border-left: 4px solid var(--success);
|
border-left: 4px solid var(--success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.id-icon {
|
.id-icon {
|
||||||
width: 36px;
|
width: 48px;
|
||||||
height: 36px;
|
height: 48px;
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -96,15 +99,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.id-icon-primary {
|
.id-icon-primary {
|
||||||
background: rgba(52, 152, 219, 0.1);
|
background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
|
||||||
color: var(--secondary);
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.id-icon-secondary {
|
.id-icon-secondary {
|
||||||
background: rgba(39, 174, 96, 0.1);
|
background: linear-gradient(135deg, #27ae60 0%, #219653 100%);
|
||||||
color: var(--success);
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.id-label {
|
.id-label {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
@@ -119,6 +121,8 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
word-break: break-word;
|
||||||
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-container {
|
.date-container {
|
||||||
@@ -173,8 +177,7 @@
|
|||||||
.date-connector {
|
.date-connector {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
gap: 4px;
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-connector i {
|
.date-connector i {
|
||||||
@@ -183,6 +186,7 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
color: var(--secondary);
|
color: var(--secondary);
|
||||||
border: 2px solid #e9ecef;
|
border: 2px solid #e9ecef;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
@@ -221,14 +225,16 @@
|
|||||||
background-color: rgba(52, 152, 219, 0.03);
|
background-color: rgba(52, 152, 219, 0.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary-card {
|
/* Simplified Summary Section */
|
||||||
background: var(--light);
|
.summary-container {
|
||||||
border-left: 4px solid var(--secondary);
|
margin: 2rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary-header {
|
.amount-row {
|
||||||
background: var(--primary);
|
display: flex;
|
||||||
color: #ffffff;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-primary {
|
.text-primary {
|
||||||
@@ -276,6 +282,66 @@
|
|||||||
.id-box {
|
.id-box {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.date-status-row {
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-header .col-md-6.text-end {
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-badge {
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-container {
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.date-status-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-separator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-badge {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-box {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-responsive {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-container {
|
||||||
|
box-shadow: none;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -630,6 +696,7 @@
|
|||||||
alert("Link copied! Sharing not supported on this browser.");
|
alert("Link copied! Sharing not supported on this browser.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,75 +5,149 @@
|
|||||||
@section('content')
|
@section('content')
|
||||||
<div class="container-fluid px-0">
|
<div class="container-fluid px-0">
|
||||||
|
|
||||||
@php
|
@php
|
||||||
$perPage = 5;
|
$perPage = 5;
|
||||||
$currentPage = request()->get('page', 1);
|
$currentPage = request()->get('page', 1);
|
||||||
$currentPage = max(1, (int)$currentPage);
|
$currentPage = max(1, (int)$currentPage);
|
||||||
$total = $requests->count();
|
$total = $requests->count();
|
||||||
$totalPages = ceil($total / $perPage);
|
|
||||||
$currentItems = $requests->slice(($currentPage - 1) * $perPage, $perPage);
|
$currentItems = $requests->slice(($currentPage - 1) * $perPage, $perPage);
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.old-value { color: #6b7280; font-weight: 600; }
|
/* ===== Card Wrapper ===== */
|
||||||
.new-value { color: #111827; font-weight: 700; }
|
.request-card {
|
||||||
.changed { background: #fef3c7; padding: 6px; border-radius: 6px; }
|
background: #ffffff;
|
||||||
.box { padding: 10px 14px; border-radius: 8px; background: #f8fafc; margin-bottom: 10px; }
|
border-radius: 14px;
|
||||||
.diff-label { font-size: 13px; font-weight: 700; }
|
padding: 18px;
|
||||||
.actions { display: flex; gap: 10px; }
|
margin-bottom: 18px;
|
||||||
</style>
|
box-shadow: 0 6px 20px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
<h4 class="fw-bold my-3">Profile Update Requests ({{ $total }})</h4>
|
/* ===== Header Row ===== */
|
||||||
|
.request-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 60px 1.5fr 1fr 1.2fr 1fr;
|
||||||
|
gap: 14px;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
<div class="card mb-4 shadow-sm">
|
.request-header strong {
|
||||||
<div class="card-body pb-1">
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
<div class="table-responsive custom-table-wrapper">
|
/* ===== Badges ===== */
|
||||||
<table class="table align-middle mb-0 custom-table">
|
.badge {
|
||||||
<thead>
|
padding: 6px 14px;
|
||||||
<tr>
|
font-size: 12px;
|
||||||
<th>#</th>
|
border-radius: 999px;
|
||||||
<th>User</th>
|
font-weight: 700;
|
||||||
<th>Requested Changes</th>
|
}
|
||||||
<th>Status</th>
|
|
||||||
<th>Requested At</th>
|
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
.badge-pending {
|
||||||
@foreach($currentItems as $index => $req)
|
background: #fff7ed;
|
||||||
@php
|
color: #c2410c;
|
||||||
|
border: 1px solid #fed7aa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-approved {
|
||||||
|
background: #ecfdf5;
|
||||||
|
color: #047857;
|
||||||
|
border: 1px solid #a7f3d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-rejected {
|
||||||
|
background: #fef2f2;
|
||||||
|
color: #b91c1c;
|
||||||
|
border: 1px solid #fecaca;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Action Buttons ===== */
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions .btn {
|
||||||
|
padding: 6px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Detail Grid ===== */
|
||||||
|
.detail-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 14px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Detail Box ===== */
|
||||||
|
.detail-box {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-box.changed {
|
||||||
|
background: linear-gradient(145deg, #fff7ed, #ffedd5);
|
||||||
|
border-left: 4px solid #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.old {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #020617;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Responsive ===== */
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.request-header {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h4 class="fw-bold my-3">Profile Update Requests ({{ $total }})</h4>
|
||||||
|
|
||||||
|
@foreach($currentItems as $index => $req)
|
||||||
|
@php
|
||||||
$user = $req->user;
|
$user = $req->user;
|
||||||
// FIX: Convert string to array
|
|
||||||
$newData = is_array($req->data) ? $req->data : json_decode($req->data, true);
|
$newData = is_array($req->data) ? $req->data : json_decode($req->data, true);
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<tr>
|
<div class="request-card">
|
||||||
<td><strong>{{ ($currentPage - 1) * $perPage + $index + 1 }}</strong></td>
|
|
||||||
|
|
||||||
<td>
|
<!-- HEADER -->
|
||||||
|
<div class="request-header">
|
||||||
|
<strong>#{{ ($currentPage - 1) * $perPage + $index + 1 }}</strong>
|
||||||
|
|
||||||
|
<div>
|
||||||
<strong>{{ $user->customer_name }}</strong><br>
|
<strong>{{ $user->customer_name }}</strong><br>
|
||||||
<small>{{ $user->email }}</small><br>
|
<small>{{ $user->email }}</small><br>
|
||||||
<small>ID: {{ $user->customer_id }}</small>
|
<small>ID: {{ $user->customer_id }}</small>
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
@foreach($newData as $key => $newValue)
|
|
||||||
@php
|
|
||||||
$oldValue = $user->$key ?? '—';
|
|
||||||
$changed = $oldValue != $newValue;
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<div class="box {{ $changed ? 'changed' : '' }}">
|
|
||||||
<span class="diff-label">{{ ucfirst(str_replace('_',' ', $key)) }}:</span><br>
|
|
||||||
<span class="old-value">Old: {{ $oldValue }}</span><br>
|
|
||||||
<span class="new-value">New: {{ $newValue ?? '—' }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>
|
<div>
|
||||||
@if($req->status == 'pending')
|
@if($req->status == 'pending')
|
||||||
<span class="badge badge-pending">Pending</span>
|
<span class="badge badge-pending">Pending</span>
|
||||||
@elseif($req->status == 'approved')
|
@elseif($req->status == 'approved')
|
||||||
@@ -81,31 +155,57 @@
|
|||||||
@else
|
@else
|
||||||
<span class="badge badge-rejected">Rejected</span>
|
<span class="badge badge-rejected">Rejected</span>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</div>
|
||||||
|
|
||||||
<td>{{ $req->created_at->format('d M Y, h:i A') }}</td>
|
<div>{{ $req->created_at->format('d M Y, h:i A') }}</div>
|
||||||
|
|
||||||
<td class="actions">
|
<div class="actions">
|
||||||
@if($req->status == 'pending')
|
@if($req->status == 'pending')
|
||||||
<a href="{{ route('admin.profile.approve', $req->id) }}" class="btn btn-success btn-sm">
|
<a href="{{ route('admin.profile.approve', $req->id) }}" class="btn btn-success btn-sm">
|
||||||
<i class="bi bi-check-circle"></i> Approve
|
✔ Approve
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{{ route('admin.profile.reject', $req->id) }}" class="btn btn-danger btn-sm">
|
<a href="{{ route('admin.profile.reject', $req->id) }}" class="btn btn-danger btn-sm">
|
||||||
<i class="bi bi-x-circle"></i> Reject
|
✖ Reject
|
||||||
</a>
|
</a>
|
||||||
@else
|
@else
|
||||||
<span class="text-muted">Completed</span>
|
<span class="text-muted">Completed</span>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</tr>
|
<!-- DETAILS ROW 1 -->
|
||||||
|
<div class="detail-grid">
|
||||||
|
@foreach(['customer_name','company_name','email'] as $field)
|
||||||
|
@php
|
||||||
|
$old = $user->$field ?? '—';
|
||||||
|
$new = $newData[$field] ?? $old;
|
||||||
|
@endphp
|
||||||
|
<div class="detail-box {{ $old != $new ? 'changed' : '' }}">
|
||||||
|
<div class="detail-label">{{ ucfirst(str_replace('_',' ', $field)) }}</div>
|
||||||
|
<div class="old">Old: {{ $old }}</div>
|
||||||
|
<div class="new">New: {{ $new }}</div>
|
||||||
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- DETAILS ROW 2 -->
|
||||||
|
<div class="detail-grid">
|
||||||
|
@foreach(['mobile_no','address','pincode'] as $field)
|
||||||
|
|
||||||
|
@php
|
||||||
|
$old = $user->$field ?? '—';
|
||||||
|
$new = $newData[$field] ?? $old;
|
||||||
|
@endphp
|
||||||
|
<div class="detail-box {{ $old != $new ? 'changed' : '' }}">
|
||||||
|
<div class="detail-label">{{ ucfirst(str_replace('_',' ', $field)) }}</div>
|
||||||
|
<div class="old">Old: {{ $old }}</div>
|
||||||
|
<div class="new">New: {{ $new }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -912,10 +912,6 @@
|
|||||||
</label>
|
</label>
|
||||||
<select class="filter-control" id="companyFilter">
|
<select class="filter-control" id="companyFilter">
|
||||||
<option value="" selected>All Companies</option>
|
<option value="" selected>All Companies</option>
|
||||||
<option value="ABC Corporation">ABC Corporation</option>
|
|
||||||
<option value="XYZ Enterprises">XYZ Enterprises</option>
|
|
||||||
<option value="Global Traders">Global Traders</option>
|
|
||||||
<option value="Tech Solutions Ltd">Tech Solutions Ltd</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
@@ -1101,6 +1097,7 @@
|
|||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Initialize statistics and pagination
|
// Initialize statistics and pagination
|
||||||
|
populateCompanyFilter(allReports);
|
||||||
updateStatistics();
|
updateStatistics();
|
||||||
renderTable();
|
renderTable();
|
||||||
updatePaginationControls();
|
updatePaginationControls();
|
||||||
@@ -1403,5 +1400,33 @@
|
|||||||
// Initial filter application
|
// Initial filter application
|
||||||
filterTable();
|
filterTable();
|
||||||
});
|
});
|
||||||
|
function populateCompanyFilter(reports) {
|
||||||
|
const companySelect = document.getElementById('companyFilter');
|
||||||
|
if (!companySelect) return;
|
||||||
|
|
||||||
|
// Collect unique company names
|
||||||
|
const companies = new Set();
|
||||||
|
|
||||||
|
reports.forEach(r => {
|
||||||
|
if (r.company_name && r.company_name.trim() !== '') {
|
||||||
|
companies.add(r.company_name.trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort alphabetically
|
||||||
|
const sortedCompanies = Array.from(companies).sort();
|
||||||
|
|
||||||
|
// Remove existing options except "All Companies"
|
||||||
|
companySelect.innerHTML = '<option value="">All Companies</option>';
|
||||||
|
|
||||||
|
// Append options
|
||||||
|
sortedCompanies.forEach(company => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = company;
|
||||||
|
option.textContent = company;
|
||||||
|
companySelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
.custom-table tbody tr:hover { background-color: #fffbea; transform: scale(1.01); box-shadow: 0 2px 6px rgba(0,0,0,0.05); }
|
.custom-table tbody tr:hover { background-color: #fffbea; transform: scale(1.01); box-shadow: 0 2px 6px rgba(0,0,0,0.05); }
|
||||||
.priority-badge {
|
.priority-badge {
|
||||||
display: inline-flex; align-items: center; font-size: 13.5px; padding: 6px 16px; border-radius: 12px; font-weight: 600;
|
display: inline-flex; align-items: center; font-size: 13.5px; padding: 6px 16px; border-radius: 12px; font-weight: 600;
|
||||||
box-shadow: 0 1px 2px 0 rgba(130,130,130,0.15); width: 90px; min-height: 28px; justify-content: center;
|
box-shadow: 0 1px 2px 0 rgba(230, 206, 206, 0.15); width: 90px; min-height: 28px; justify-content: center;
|
||||||
color: #fff; margin: 2px 0; transition: transform 0.2s ease-in-out;
|
color: #fff; margin: 2px 0; transition: transform 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
.priority-badge:hover { transform: scale(1.08); }
|
.priority-badge:hover { transform: scale(1.08); }
|
||||||
@@ -30,8 +30,8 @@
|
|||||||
.priority-medium { background: linear-gradient(135deg, #ffe390, #f5b041); }
|
.priority-medium { background: linear-gradient(135deg, #ffe390, #f5b041); }
|
||||||
.priority-low { background: linear-gradient(135deg, #b8f0c2, #1d8660); }
|
.priority-low { background: linear-gradient(135deg, #b8f0c2, #1d8660); }
|
||||||
.custom-table thead th {
|
.custom-table thead th {
|
||||||
text-align: center; font-weight: 700; color: #000; padding: 14px; font-size: 17px; letter-spacing: 0.5px;
|
text-align: center; font-weight: 700; color: #ffffffff; padding: 14px; font-size: 17px; letter-spacing: 0.5px;
|
||||||
border-bottom: 2px solid #bfbfbf; background-color: #fde4b3;
|
border-bottom: 2px solid #bfbfbf; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);;
|
||||||
}
|
}
|
||||||
.custom-table thead tr:first-child th:first-child { border-top-left-radius: 12px; }
|
.custom-table thead tr:first-child th:first-child { border-top-left-radius: 12px; }
|
||||||
.custom-table thead tr:first-child th:last-child { border-top-right-radius: 12px; }
|
.custom-table thead tr:first-child th:last-child { border-top-right-radius: 12px; }
|
||||||
@@ -307,13 +307,58 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* ==============================================
|
||||||
|
PROFILE UPDATE REQUEST BUTTON BADGE FIX
|
||||||
|
============================================== */
|
||||||
|
|
||||||
|
/* Ensure button is positioning context */
|
||||||
|
a.btn.btn-primary.position-relative {
|
||||||
|
position: relative;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix badge inside Profile Update Requests button */
|
||||||
|
a.btn.btn-primary.position-relative .badge {
|
||||||
|
width: 30px !important;
|
||||||
|
height: 30px !important;
|
||||||
|
min-width: 30px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
line-height: 30px !important;
|
||||||
|
border-radius: 50% !important;
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
animation: none !important;
|
||||||
|
box-shadow: 0 0 0 2px #ffffff;
|
||||||
|
|
||||||
|
}
|
||||||
|
.custom-table th,
|
||||||
|
.custom-table td {
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Counts -->
|
<!-- Counts -->
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2 mt-3">
|
<div class="d-flex justify-content-between align-items-center mb-2 mt-3">
|
||||||
<h4 class="fw-bold mb-0">User Requests (Total: {{ $total }})</h4>
|
<h4 class="fw-bold mb-0">User Requests (Total: {{ $total }})</h4>
|
||||||
|
|
||||||
</div>
|
@can('request.update_profile')
|
||||||
|
<a href="{{ route('admin.profile.requests') }}" class="btn btn-primary position-relative">
|
||||||
|
<i class="bi bi-person-lines-fill me-1"></i>
|
||||||
|
Profile Update Requests
|
||||||
|
|
||||||
|
@if($pendingProfileUpdates > 0)
|
||||||
|
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
|
||||||
|
{{ $pendingProfileUpdates }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Search + Table -->
|
<!-- Search + Table -->
|
||||||
<div class="card mb-4 shadow-sm">
|
<div class="card mb-4 shadow-sm">
|
||||||
|
|||||||
1388
resources/views/admin/see_order.blade.php
Normal file
1388
resources/views/admin/see_order.blade.php
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,30 +1,581 @@
|
|||||||
@extends('admin.layouts.app')
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
@section('page-title', 'Account Dashboard')
|
@section('page-title', 'Staff Management Dashboard')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<style>
|
<style>
|
||||||
.top-bar { display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem; }
|
:root {
|
||||||
.card { background:#fff; border:1px solid #e4e4e4; border-radius:6px; padding:1rem; box-shadow:0 1px 3px rgba(0,0,0,.03); }
|
--primary: #4361ee;
|
||||||
table { width:100%; border-collapse:collapse; }
|
--primary-dark: #3a56d4;
|
||||||
th, td { padding:.6rem .75rem; border-bottom:1px solid #f1f1f1; text-align:left; }
|
--secondary: #f72585;
|
||||||
.btn { padding:.45rem .75rem; border-radius:4px; border:1px solid #ccc; background:#f7f7f7; cursor:pointer; }
|
--success: #4cc9f0;
|
||||||
.btn.primary { background:#0b74de; color:#fff; border-color:#0b74de; }
|
--warning: #f8961e;
|
||||||
.actions a { margin-right:.5rem; color:#0b74de; text-decoration:none; }
|
--danger: #e63946;
|
||||||
.muted { color:#666; font-size:.95rem; }
|
--light: #f8f9fa;
|
||||||
|
--dark: #212529;
|
||||||
|
--gray: #6c757d;
|
||||||
|
--border: #e2e8f0;
|
||||||
|
--card-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
|
--hover-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||||
|
--gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search Bar - Similar to Shipment */
|
||||||
|
.search-staff-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
padding: 20px;
|
||||||
|
background: var(--gradient-primary);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
color: white;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-staff-bar::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-staff-bar > * {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-staff-bar input,
|
||||||
|
.search-staff-bar select {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid rgba(255,255,255,0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 150px;
|
||||||
|
background: rgba(255,255,255,0.9);
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-staff-bar input:focus,
|
||||||
|
.search-staff-bar select:focus {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 0 0 3px rgba(255,255,255,0.3);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-staff {
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
color: white;
|
||||||
|
border: 1px solid rgba(255,255,255,0.3);
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-staff:hover {
|
||||||
|
background: rgba(255,255,255,0.3);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.search-staff-bar {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.search-staff-bar input,
|
||||||
|
.search-staff-bar select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card Styles - Same as Shipment */
|
||||||
|
.card {
|
||||||
|
border: none;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: var(--hover-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background: var(--gradient-primary);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 20px 25px;
|
||||||
|
border-radius: 16px 16px 0 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header h5 {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 700;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table Styles - Similar to Shipment */
|
||||||
|
.table-responsive {
|
||||||
|
border-radius: 0 0 16px 16px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
margin: 0;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: none;
|
||||||
|
padding: 16px 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--dark);
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-bottom: 2px solid var(--border);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody tr {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody tr:hover {
|
||||||
|
background-color: #f8f9ff;
|
||||||
|
transform: scale(1.01);
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody td {
|
||||||
|
padding: 14px 12px;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Badges - Similar Style */
|
||||||
|
.badge {
|
||||||
|
padding: 6px 12px !important;
|
||||||
|
border-radius: 20px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
border: 2px solid transparent !important;
|
||||||
|
min-width: 80px !important;
|
||||||
|
text-align: center !important;
|
||||||
|
display: inline-block !important;
|
||||||
|
line-height: 1.2 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-active {
|
||||||
|
background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important;
|
||||||
|
color: #065f46 !important;
|
||||||
|
border-color: #10b981 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-inactive {
|
||||||
|
background: linear-gradient(135deg, #fecaca, #fca5a5) !important;
|
||||||
|
color: #991b1b !important;
|
||||||
|
border-color: #ef4444 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-pending {
|
||||||
|
background: linear-gradient(135deg, #fef3c7, #fde68a) !important;
|
||||||
|
color: #92400e !important;
|
||||||
|
border-color: #f59e0b !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Employee ID Badge - Similar to Shipment ID */
|
||||||
|
.employee-id-badge {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
background: rgba(67, 97, 238, 0.1);
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--primary);
|
||||||
|
border: 1px solid rgba(67, 97, 238, 0.2);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Action Buttons - Similar Style */
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit {
|
||||||
|
background: linear-gradient(135deg, #4cc9f0, #4361ee);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit:hover {
|
||||||
|
background: linear-gradient(135deg, #38bdf8, #3a56d4);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(76, 201, 240, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete {
|
||||||
|
background: linear-gradient(135deg, #f87171, #ef4444);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete:hover {
|
||||||
|
background: linear-gradient(135deg, #ef4444, #dc2626);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Success Message - Similar Style */
|
||||||
|
.alert-success {
|
||||||
|
background: linear-gradient(135deg, #e6ffed, #d1f7e5);
|
||||||
|
border: 1px solid #b6f0c6;
|
||||||
|
border-left: 4px solid var(--success);
|
||||||
|
color: #0f5132;
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success:before {
|
||||||
|
content: '✓';
|
||||||
|
background: var(--success);
|
||||||
|
color: white;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empty State */
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem 1rem;
|
||||||
|
color: var(--gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state:before {
|
||||||
|
content: '👤';
|
||||||
|
font-size: 3rem;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Role Badges */
|
||||||
|
.role-badge {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
background: rgba(67, 97, 238, 0.1);
|
||||||
|
color: var(--primary);
|
||||||
|
border: 1px solid rgba(67, 97, 238, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stats Cards - Similar to Shipment Totals */
|
||||||
|
.stats-cards {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 200px;
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: var(--hover-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon.total {
|
||||||
|
background: linear-gradient(135deg, #e6f3ff, #c2d9ff);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon.active {
|
||||||
|
background: linear-gradient(135deg, #d1fae5, #a7f3d0);
|
||||||
|
color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-content h3 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-content p {
|
||||||
|
color: var(--gray);
|
||||||
|
margin: 4px 0 0 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pagination - Same as Shipment */
|
||||||
|
.pagination-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 12px 25px;
|
||||||
|
border-top: 1px solid #eef3fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-info {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #9ba5bb;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e3eaf6;
|
||||||
|
color: #1a2951;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 40px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn:hover:not(:disabled) {
|
||||||
|
background: #1a2951;
|
||||||
|
color: white;
|
||||||
|
border-color: #1a2951;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn:disabled {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #cbd5e0;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-page-btn {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e3eaf6;
|
||||||
|
color: #1a2951;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
min-width: 36px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-page-btn:hover {
|
||||||
|
background: #1a2951;
|
||||||
|
color: white;
|
||||||
|
border-color: #1a2951;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-page-btn.active {
|
||||||
|
background: #1a2951;
|
||||||
|
color: white;
|
||||||
|
border-color: #1a2951;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-pages {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-ellipsis {
|
||||||
|
color: #9ba5bb;
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.stats-cards {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-controls {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="top-bar">
|
<div class="container-fluid py-4">
|
||||||
<h2>Staff</h2>
|
|
||||||
<a href="{{ route('admin.staff.create') }}" class="btn primary">Add Staff</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
@if(session('success'))
|
@if(session('success'))
|
||||||
<div style="padding:.5rem; background:#e6ffed; border:1px solid #b6f0c6; margin-bottom:1rem;">{{ session('success') }}</div>
|
<div class="alert-success">
|
||||||
|
{{ session('success') }}
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<table>
|
{{-- Stats Cards --}}
|
||||||
|
<div class="stats-cards">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-icon total">
|
||||||
|
👥
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<h3>{{ $staff->count() }}</h3>
|
||||||
|
<p>Total Staff</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-icon active">
|
||||||
|
✅
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<h3>{{ $staff->where('status', 'active')->count() }}</h3>
|
||||||
|
<p>Active Staff</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-icon" style="background: linear-gradient(135deg, #f3e8ff, #e9d5ff); color: #8b5cf6;">
|
||||||
|
👑
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<h3>{{ $staff->unique('role')->count() }}</h3>
|
||||||
|
<p>Unique Roles</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Search Bar --}}
|
||||||
|
<div class="search-staff-bar">
|
||||||
|
<span class="search-icon">🔍</span>
|
||||||
|
<input type="text" id="searchInput" placeholder="Search by name, email, or employee ID...">
|
||||||
|
<div class="status-filter-container">
|
||||||
|
<select id="statusFilter" class="status-filter-select">
|
||||||
|
<option value="all">All Status</option>
|
||||||
|
<option value="active">Active</option>
|
||||||
|
<option value="inactive">Inactive</option>
|
||||||
|
<option value="pending">Pending</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<select id="roleFilter">
|
||||||
|
<option value="all">All Roles</option>
|
||||||
|
@foreach($staff->unique('role')->pluck('role') as $role)
|
||||||
|
@if($role)
|
||||||
|
<option value="{{ $role }}">{{ ucfirst($role) }}</option>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
<a href="{{ route('admin.staff.create') }}" class="btn-add-staff">
|
||||||
|
<span class="user-icon">➕</span>
|
||||||
|
Add Staff
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Staff Table --}}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0"><i class="bi bi-people me-2"></i> Staff Management</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>#</th>
|
<th>#</th>
|
||||||
@@ -37,29 +588,309 @@
|
|||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
|
||||||
|
<tbody id="staffTableBody">
|
||||||
|
@php
|
||||||
|
$totalStaff = count($staff);
|
||||||
|
@endphp
|
||||||
@forelse($staff as $s)
|
@forelse($staff as $s)
|
||||||
<tr>
|
<tr class="staff-row" data-status="{{ $s->status }}" data-role="{{ $s->role ?? '' }}">
|
||||||
<td>{{ $s->id }}</td>
|
<td class="fw-bold">{{ $totalStaff - $loop->index }}</td>
|
||||||
<td class="muted">{{ $s->employee_id }}</td>
|
<td>
|
||||||
<td>{{ $s->name }}</td>
|
<span class="employee-id-badge">{{ $s->employee_id }}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<div style="width: 36px; height: 36px; border-radius: 50%; background: linear-gradient(135deg, #667eea, #764ba2); display: flex; align-items: center; justify-content: center; color: white; font-weight: bold;">
|
||||||
|
{{ strtoupper(substr($s->name, 0, 1)) }}
|
||||||
|
</div>
|
||||||
|
<span class="fw-medium">{{ $s->name }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td>{{ $s->email }}</td>
|
<td>{{ $s->email }}</td>
|
||||||
<td>{{ $s->phone }}</td>
|
<td>{{ $s->phone ?? '-' }}</td>
|
||||||
<td>{{ $s->role ?? '-' }}</td>
|
<td>
|
||||||
<td>{{ ucfirst($s->status) }}</td>
|
@if($s->role)
|
||||||
<td class="actions">
|
<span class="role-badge">{{ $s->role }}</span>
|
||||||
<a href="{{ route('admin.staff.edit', $s->id) }}">Edit</a>
|
@else
|
||||||
<form action="{{ route('admin.staff.destroy', $s->id) }}" method="POST" style="display:inline" onsubmit="return confirm('Delete this staff?')">
|
<span class="text-muted">-</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge badge-{{ $s->status }}">
|
||||||
|
{{ ucfirst($s->status) }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="action-buttons">
|
||||||
|
<a href="{{ route('admin.staff.edit', $s->id) }}" class="btn-action btn-edit">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
Edit
|
||||||
|
</a>
|
||||||
|
<form action="{{ route('admin.staff.destroy', $s->id) }}" method="POST" style="display:inline" onsubmit="return confirm('Are you sure you want to delete this staff member?')">
|
||||||
@csrf
|
@csrf
|
||||||
@method('DELETE')
|
@method('DELETE')
|
||||||
<button class="btn" type="submit">Delete</button>
|
<button type="submit" class="btn-action btn-delete">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr><td colspan="8" class="muted">No staff found.</td></tr>
|
<tr>
|
||||||
|
<td colspan="8" class="text-center py-5 text-muted">
|
||||||
|
<div class="empty-state">
|
||||||
|
No staff members found
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
@endforelse
|
@endforelse
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Pagination --}}
|
||||||
|
<div class="pagination-container">
|
||||||
|
<div class="pagination-info" id="pageInfo">
|
||||||
|
Showing 1 to {{ $staff->count() }} of {{ $staff->count() }} entries
|
||||||
|
</div>
|
||||||
|
<div class="pagination-controls">
|
||||||
|
<button class="pagination-btn" id="prevPageBtn" title="Previous page" disabled>
|
||||||
|
<i class="bi bi-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
<div class="pagination-pages" id="paginationPages">
|
||||||
|
<button class="pagination-page-btn active">1</button>
|
||||||
|
</div>
|
||||||
|
<button class="pagination-btn" id="nextPageBtn" title="Next page" disabled>
|
||||||
|
<i class="bi bi-chevron-right"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Pagination state
|
||||||
|
let currentPage = 1;
|
||||||
|
const itemsPerPage = 10;
|
||||||
|
let allStaff = @json($staff);
|
||||||
|
let filteredStaff = [...allStaff];
|
||||||
|
|
||||||
|
// Initialize on page load
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
renderTable();
|
||||||
|
updatePaginationControls();
|
||||||
|
|
||||||
|
// Bind pagination events
|
||||||
|
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
|
||||||
|
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
|
||||||
|
|
||||||
|
// Filter functionality
|
||||||
|
const statusFilter = document.getElementById('statusFilter');
|
||||||
|
const searchInput = document.getElementById('searchInput');
|
||||||
|
const roleFilter = document.getElementById('roleFilter');
|
||||||
|
|
||||||
|
function filterStaff() {
|
||||||
|
const selectedStatus = statusFilter.value;
|
||||||
|
const searchTerm = searchInput.value.toLowerCase();
|
||||||
|
const selectedRole = roleFilter.value;
|
||||||
|
|
||||||
|
filteredStaff = allStaff.filter(staff => {
|
||||||
|
let include = true;
|
||||||
|
|
||||||
|
// Status filter
|
||||||
|
if (selectedStatus !== 'all' && staff.status !== selectedStatus) {
|
||||||
|
include = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Role filter
|
||||||
|
if (selectedRole !== 'all') {
|
||||||
|
const staffRole = staff.role || '';
|
||||||
|
if (staffRole.toLowerCase() !== selectedRole.toLowerCase()) {
|
||||||
|
include = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search filter
|
||||||
|
if (searchTerm) {
|
||||||
|
const matchesSearch =
|
||||||
|
staff.name.toLowerCase().includes(searchTerm) ||
|
||||||
|
staff.email.toLowerCase().includes(searchTerm) ||
|
||||||
|
(staff.employee_id && staff.employee_id.toLowerCase().includes(searchTerm)) ||
|
||||||
|
(staff.phone && staff.phone.toLowerCase().includes(searchTerm));
|
||||||
|
if (!matchesSearch) include = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return include;
|
||||||
|
});
|
||||||
|
|
||||||
|
currentPage = 1;
|
||||||
|
renderTable();
|
||||||
|
updatePaginationControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listeners for filters
|
||||||
|
statusFilter.addEventListener('change', filterStaff);
|
||||||
|
searchInput.addEventListener('input', filterStaff);
|
||||||
|
roleFilter.addEventListener('change', filterStaff);
|
||||||
|
|
||||||
|
// Initialize filter
|
||||||
|
filterStaff();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pagination Functions
|
||||||
|
function goToPreviousPage() {
|
||||||
|
if (currentPage > 1) {
|
||||||
|
currentPage--;
|
||||||
|
renderTable();
|
||||||
|
updatePaginationControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToNextPage() {
|
||||||
|
const totalPages = Math.ceil(filteredStaff.length / itemsPerPage);
|
||||||
|
if (currentPage < totalPages) {
|
||||||
|
currentPage++;
|
||||||
|
renderTable();
|
||||||
|
updatePaginationControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePaginationControls() {
|
||||||
|
const totalPages = Math.ceil(filteredStaff.length / itemsPerPage);
|
||||||
|
const prevBtn = document.getElementById('prevPageBtn');
|
||||||
|
const nextBtn = document.getElementById('nextPageBtn');
|
||||||
|
const pageInfo = document.getElementById('pageInfo');
|
||||||
|
const paginationPages = document.getElementById('paginationPages');
|
||||||
|
|
||||||
|
prevBtn.disabled = currentPage === 1;
|
||||||
|
nextBtn.disabled = currentPage === totalPages || totalPages === 0;
|
||||||
|
|
||||||
|
// Update page info text
|
||||||
|
const startIndex = (currentPage - 1) * itemsPerPage + 1;
|
||||||
|
const endIndex = Math.min(currentPage * itemsPerPage, filteredStaff.length);
|
||||||
|
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${filteredStaff.length} entries`;
|
||||||
|
|
||||||
|
// Generate page numbers
|
||||||
|
paginationPages.innerHTML = '';
|
||||||
|
|
||||||
|
if (totalPages <= 7) {
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
addPageButton(i, paginationPages);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addPageButton(1, paginationPages);
|
||||||
|
|
||||||
|
if (currentPage > 3) {
|
||||||
|
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = Math.max(2, currentPage - 1);
|
||||||
|
const end = Math.min(totalPages - 1, currentPage + 1);
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
addPageButton(i, paginationPages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPage < totalPages - 2) {
|
||||||
|
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
addPageButton(totalPages, paginationPages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPageButton(pageNumber, container) {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.className = 'pagination-page-btn';
|
||||||
|
if (pageNumber === currentPage) {
|
||||||
|
button.classList.add('active');
|
||||||
|
}
|
||||||
|
button.textContent = pageNumber;
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
currentPage = pageNumber;
|
||||||
|
renderTable();
|
||||||
|
updatePaginationControls();
|
||||||
|
});
|
||||||
|
container.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render Table
|
||||||
|
function renderTable() {
|
||||||
|
const tbody = document.getElementById('staffTableBody');
|
||||||
|
|
||||||
|
if (filteredStaff.length === 0) {
|
||||||
|
tbody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="8" class="text-center py-5 text-muted">
|
||||||
|
<div class="empty-state">
|
||||||
|
No staff members found matching your criteria
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||||
|
const endIndex = startIndex + itemsPerPage;
|
||||||
|
const paginatedItems = filteredStaff.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
const sortedItems = [...paginatedItems].sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
||||||
|
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
sortedItems.forEach((staff, index) => {
|
||||||
|
const displayIndex = filteredStaff.length - (startIndex + index);
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.className = 'staff-row';
|
||||||
|
row.setAttribute('data-status', staff.status);
|
||||||
|
row.setAttribute('data-role', staff.role || '');
|
||||||
|
|
||||||
|
row.innerHTML = `
|
||||||
|
<td class="fw-bold">${displayIndex}</td>
|
||||||
|
<td>
|
||||||
|
<span class="employee-id-badge">${staff.employee_id}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<div style="width: 36px; height: 36px; border-radius: 50%; background: linear-gradient(135deg, #667eea, #764ba2); display: flex; align-items: center; justify-content: center; color: white; font-weight: bold;">
|
||||||
|
${staff.name ? staff.name.charAt(0).toUpperCase() : '?'}
|
||||||
|
</div>
|
||||||
|
<span class="fw-medium">${staff.name}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>${staff.email}</td>
|
||||||
|
<td>${staff.phone || '-'}</td>
|
||||||
|
<td>
|
||||||
|
${staff.role ? `<span class="role-badge">${staff.role}</span>` : '<span class="text-muted">-</span>'}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge badge-${staff.status}">
|
||||||
|
${staff.status.charAt(0).toUpperCase() + staff.status.slice(1)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="action-buttons">
|
||||||
|
<a href="/admin/staff/${staff.id}/edit" class="btn-action btn-edit">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
Edit
|
||||||
|
</a>
|
||||||
|
<form action="/admin/staff/${staff.id}" method="POST" style="display:inline" onsubmit="return confirm('Are you sure you want to delete this staff member?')">
|
||||||
|
<input type="hidden" name="_token" value="{{ csrf_token() }}">
|
||||||
|
<input type="hidden" name="_method" value="DELETE">
|
||||||
|
<button type="submit" class="btn-action btn-delete">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -12,12 +12,15 @@ use App\Http\Controllers\MarkListController;
|
|||||||
use App\Http\Controllers\User\UserOrderController;
|
use App\Http\Controllers\User\UserOrderController;
|
||||||
use App\Http\Controllers\User\UserProfileController;
|
use App\Http\Controllers\User\UserProfileController;
|
||||||
use App\Http\Controllers\User\ChatController;
|
use App\Http\Controllers\User\ChatController;
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Broadcast::routes(['middleware' => ['auth:api']]);
|
Broadcast::routes(['middleware' => ['auth:api']]);
|
||||||
|
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
|
|
||||||
//user send request
|
//user send request
|
||||||
Route::post('/signup-request', [RequestController::class, 'usersignup']);
|
Route::post('/signup-request', [RequestController::class, 'usersignup']);
|
||||||
@@ -26,9 +29,10 @@ Route::post('/signup-request', [RequestController::class, 'usersignup']);
|
|||||||
Route::post('/user/login', [UserAuthController::class, 'login']);
|
Route::post('/user/login', [UserAuthController::class, 'login']);
|
||||||
|
|
||||||
|
|
||||||
|
Route::post('/auth/refresh', [UserAuthController::class, 'refreshToken']);
|
||||||
|
|
||||||
|
|
||||||
Route::middleware(['auth:api'])->group(function () {
|
Route::middleware(['auth:api'])->group(function () {
|
||||||
//Route::post('/user/refresh', [UserAuthController::class, 'refreshToken']);
|
|
||||||
|
|
||||||
Route::post('/user/logout', [UserAuthController::class, 'logout']);
|
Route::post('/user/logout', [UserAuthController::class, 'logout']);
|
||||||
|
|
||||||
@@ -43,9 +47,11 @@ Route::middleware(['auth:api'])->group(function () {
|
|||||||
Route::get('/user/order/{order_id}/shipment', [UserOrderController::class, 'orderShipment']);
|
Route::get('/user/order/{order_id}/shipment', [UserOrderController::class, 'orderShipment']);
|
||||||
Route::get('/user/order/{order_id}/invoice', [UserOrderController::class, 'orderInvoice']);
|
Route::get('/user/order/{order_id}/invoice', [UserOrderController::class, 'orderInvoice']);
|
||||||
Route::get('/user/order/{order_id}/track', [UserOrderController::class, 'trackOrder']);
|
Route::get('/user/order/{order_id}/track', [UserOrderController::class, 'trackOrder']);
|
||||||
Route::get('/user/invoice/{invoice_id}/details', [UserOrderController::class, 'invoiceDetails']);
|
Route::post('/user/orders/{order_id}/confirm', [UserOrderController::class, 'confirmOrder']);
|
||||||
|
|
||||||
|
|
||||||
// Invoice List
|
// Invoice List
|
||||||
|
Route::get('/user/invoice/{invoice_id}/details', [UserOrderController::class, 'invoiceDetails']);
|
||||||
Route::get('/user/invoices', [UserOrderController::class, 'allInvoices']);
|
Route::get('/user/invoices', [UserOrderController::class, 'allInvoices']);
|
||||||
Route::get('/user/invoice/{invoice_id}/installments', [UserOrderController::class, 'invoiceInstallmentsById']);
|
Route::get('/user/invoice/{invoice_id}/installments', [UserOrderController::class, 'invoiceInstallmentsById']);
|
||||||
|
|
||||||
@@ -58,7 +64,11 @@ Route::middleware(['auth:api'])->group(function () {
|
|||||||
|
|
||||||
// Route::post('/user/profile/update', [UserProfileController::class, 'updateProfile']);
|
// Route::post('/user/profile/update', [UserProfileController::class, 'updateProfile']);
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
// ===========================
|
// ===========================
|
||||||
|
=======
|
||||||
|
// ===========================
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
// CHAT SUPPORT ROUTES
|
// CHAT SUPPORT ROUTES
|
||||||
// ===========================
|
// ===========================
|
||||||
Route::get('/user/chat/start', [ChatController::class, 'startChat']);
|
Route::get('/user/chat/start', [ChatController::class, 'startChat']);
|
||||||
@@ -69,6 +79,7 @@ Route::middleware(['auth:api'])->group(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
Route::post('/broadcasting/auth', function (Request $request) {
|
Route::post('/broadcasting/auth', function (Request $request) {
|
||||||
if (!auth()->check()) {
|
if (!auth()->check()) {
|
||||||
return response()->json(['message' => 'Unauthenticated'], 401);
|
return response()->json(['message' => 'Unauthenticated'], 401);
|
||||||
@@ -76,3 +87,30 @@ Route::post('/broadcasting/auth', function (Request $request) {
|
|||||||
|
|
||||||
return Broadcast::auth($request);
|
return Broadcast::auth($request);
|
||||||
})->middleware('auth:api');
|
})->middleware('auth:api');
|
||||||
|
=======
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Route::post('/broadcasting/auth', function (Request $request) {
|
||||||
|
|
||||||
|
$user = auth('api')->user(); // JWT user (Flutter)
|
||||||
|
|
||||||
|
if (! $user) {
|
||||||
|
\Log::warning('BROADCAST AUTH FAILED - NO USER');
|
||||||
|
return response()->json(['message' => 'Unauthorized'], 401);
|
||||||
|
}
|
||||||
|
\Log::info('BROADCAST AUTH OK', [
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'channel' => $request->channel_name,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Broadcast::auth(
|
||||||
|
$request->setUserResolver(fn () => $user)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
>>>>>>> 8b6d3d5fadadda310ef45ec03c879b900bff4cb025f45d1bb5d188761d53e043
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ use App\Http\Controllers\Admin\AdminAccountController;
|
|||||||
use App\Http\Controllers\Admin\AdminReportController;
|
use App\Http\Controllers\Admin\AdminReportController;
|
||||||
use App\Http\Controllers\Admin\AdminStaffController;
|
use App\Http\Controllers\Admin\AdminStaffController;
|
||||||
use App\Http\Controllers\Admin\AdminChatController;
|
use App\Http\Controllers\Admin\AdminChatController;
|
||||||
use Illuminate\Support\Facades\Broadcast;
|
use Illuminate\Session\Middleware\StartSession;
|
||||||
use App\Http\Controllers\ContainerController;
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
@@ -62,13 +63,18 @@ Route::post('/broadcasting/auth', function (\Illuminate\Http\Request $request) {
|
|||||||
// ADMIN LOGIN ROUTES
|
// ADMIN LOGIN ROUTES
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// login routes (public)
|
// login routes (public)
|
||||||
Route::prefix('admin')->group(function () {
|
Route::prefix('admin')->middleware('web')->group(function () {
|
||||||
Route::get('/login', [AdminAuthController::class, 'showLoginForm'])->name('admin.login');
|
Route::get('/login', [AdminAuthController::class, 'showLoginForm'])->name('admin.login');
|
||||||
Route::post('/login', [AdminAuthController::class, 'login'])->name('admin.login.submit');
|
Route::post('/login', [AdminAuthController::class, 'login'])->name('admin.login.submit');
|
||||||
Route::post('/logout', [AdminAuthController::class, 'logout'])->name('admin.logout');
|
Route::post('/logout', [AdminAuthController::class, 'logout'])->name('admin.logout');
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Route::get('/login', function () {
|
||||||
|
return redirect()->route('admin.login');
|
||||||
|
})->name('login');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Broadcast::routes([
|
//Broadcast::routes([
|
||||||
//'middleware' => ['web', 'auth:admin'],
|
//'middleware' => ['web', 'auth:admin'],
|
||||||
@@ -79,7 +85,7 @@ Broadcast::routes(['middleware' => ['web']]);
|
|||||||
// PROTECTED ADMIN ROUTES (session protected)
|
// PROTECTED ADMIN ROUTES (session protected)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
Route::prefix('admin')
|
Route::prefix('admin')
|
||||||
->middleware('auth:admin')
|
->middleware(['web', 'auth:admin'])
|
||||||
->group(function () {
|
->group(function () {
|
||||||
|
|
||||||
// Dashboard
|
// Dashboard
|
||||||
@@ -193,6 +199,14 @@ Route::prefix('admin')
|
|||||||
Route::get('/orders/view/{id}', [AdminOrderController::class, 'popup'])
|
Route::get('/orders/view/{id}', [AdminOrderController::class, 'popup'])
|
||||||
->name('admin.orders.popup');
|
->name('admin.orders.popup');
|
||||||
|
|
||||||
|
Route::post('/admin/orders/temp/add', [AdminOrderController::class, 'addTempItem'])
|
||||||
|
->name('admin.orders.temp.add');
|
||||||
|
|
||||||
|
|
||||||
|
// Route::get('/orders/{id}', [AdminOrderController::class, 'view'])
|
||||||
|
// ->name('admin.orders.view');
|
||||||
|
Route::get('/orders/{order:order_id}/see', [AdminOrderController::class, 'see'])
|
||||||
|
->name('admin.orders.see');
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// ORDERS (FIXED ROUTES)
|
// ORDERS (FIXED ROUTES)
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
@@ -212,6 +226,12 @@ Route::prefix('admin')
|
|||||||
Route::delete('/orders/{id}/delete', [AdminOrderController::class, 'destroy'])
|
Route::delete('/orders/{id}/delete', [AdminOrderController::class, 'destroy'])
|
||||||
->name('admin.orders.destroy');
|
->name('admin.orders.destroy');
|
||||||
|
|
||||||
|
|
||||||
|
Route::post('/orders/upload-excel-preview',
|
||||||
|
[AdminOrderController::class, 'uploadExcelPreview']
|
||||||
|
)->name('admin.orders.upload.excel.preview');
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// SHIPMENTS (FIXED ROUTES)
|
// SHIPMENTS (FIXED ROUTES)
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
@@ -243,6 +263,16 @@ Route::prefix('admin')
|
|||||||
Route::get('/shipment/dummy/{id}', [ShipmentController::class, 'dummy'])
|
Route::get('/shipment/dummy/{id}', [ShipmentController::class, 'dummy'])
|
||||||
->name('admin.shipments.dummy');
|
->name('admin.shipments.dummy');
|
||||||
|
|
||||||
|
// web.php
|
||||||
|
Route::delete('/shipments/{shipment}/orders/{order}',
|
||||||
|
[ShipmentController::class, 'removeOrder']
|
||||||
|
)->name('admin.shipments.removeOrder');
|
||||||
|
|
||||||
|
Route::post('/shipments/{shipment}/add-orders',
|
||||||
|
[ShipmentController::class, 'addOrders']
|
||||||
|
)->name('admin.shipments.addOrders');
|
||||||
|
Route::get('/shipment/dummy/{id}', [ShipmentController::class, 'dummy'])
|
||||||
|
->name('admin.shipments.dummy');
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// INVOICES
|
// INVOICES
|
||||||
@@ -265,6 +295,10 @@ Route::prefix('admin')
|
|||||||
Route::post('/invoices/{id}/installment', [AdminInvoiceController::class, 'storeInstallment'])
|
Route::post('/invoices/{id}/installment', [AdminInvoiceController::class, 'storeInstallment'])
|
||||||
->name('admin.invoice.installment.store');
|
->name('admin.invoice.installment.store');
|
||||||
|
|
||||||
|
Route::get(
|
||||||
|
'/admin/invoices/{id}/download',
|
||||||
|
[AdminInvoiceController::class, 'downloadInvoice']
|
||||||
|
)->name('admin.invoices.download');
|
||||||
|
|
||||||
|
|
||||||
Route::delete('/installment/{id}', [AdminInvoiceController::class, 'deleteInstallment'])
|
Route::delete('/installment/{id}', [AdminInvoiceController::class, 'deleteInstallment'])
|
||||||
@@ -276,7 +310,7 @@ Route::prefix('admin')
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Add New Invoice
|
// //Add New Invoice
|
||||||
Route::get('/admin/invoices/create', [InvoiceController::class, 'create'])->name('admin.invoices.create');
|
Route::get('/admin/invoices/create', [InvoiceController::class, 'create'])->name('admin.invoices.create');
|
||||||
|
|
||||||
|
|
||||||
@@ -316,7 +350,7 @@ Route::prefix('admin')
|
|||||||
// ADMIN ACCOUNT (AJAX) ROUTES
|
// ADMIN ACCOUNT (AJAX) ROUTES
|
||||||
// ==========================================
|
// ==========================================
|
||||||
Route::prefix('admin/account')
|
Route::prefix('admin/account')
|
||||||
->middleware('auth:admin')
|
->middleware(['web', 'auth:admin'])
|
||||||
->name('admin.account.')
|
->name('admin.account.')
|
||||||
->group(function () {
|
->group(function () {
|
||||||
|
|
||||||
@@ -375,7 +409,7 @@ Route::prefix('admin')
|
|||||||
->name('admin.orders.download.excel');
|
->name('admin.orders.download.excel');
|
||||||
|
|
||||||
|
|
||||||
Route::prefix('admin/account')->middleware('auth:admin')->name('admin.account.')->group(function () {
|
Route::prefix('admin/account')->middleware(['web', 'auth:admin'])->name('admin.account.')->group(function () {
|
||||||
Route::post('/toggle-payment', [AdminAccountController::class, 'togglePayment'])->name('toggle');
|
Route::post('/toggle-payment', [AdminAccountController::class, 'togglePayment'])->name('toggle');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -383,7 +417,7 @@ Route::prefix('admin')
|
|||||||
//Edit Button Route
|
//Edit Button Route
|
||||||
//---------------------------
|
//---------------------------
|
||||||
// protected admin routes
|
// protected admin routes
|
||||||
Route::middleware(['auth:admin'])
|
Route::middleware(['web', 'auth:admin'])
|
||||||
->prefix('admin')
|
->prefix('admin')
|
||||||
->name('admin.')
|
->name('admin.')
|
||||||
->group(function () {
|
->group(function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user