diff --git a/app/Http/Controllers/Admin/AdminCustomerController.php b/app/Http/Controllers/Admin/AdminCustomerController.php new file mode 100644 index 0000000..393db9a --- /dev/null +++ b/app/Http/Controllers/Admin/AdminCustomerController.php @@ -0,0 +1,134 @@ +search; + $status = $request->status; + + $query = User::with(['marks', 'orders'])->orderBy('id', 'desc'); + + // SEARCH FILTER + if (!empty($search)) { + $query->where(function ($q) use ($search) { + $q->where('customer_name', 'like', "%$search%") + ->orWhere('email', 'like', "%$search%") + ->orWhere('mobile_no', 'like', "%$search%") + ->orWhere('customer_id', 'like', "%$search%"); + }); + } + + // STATUS FILTER + if (!empty($status) && in_array($status, ['active', 'inactive'])) { + $query->where('status', $status); + } + + $customers = $query->get(); + + return view('admin.customers', compact('customers', 'search', 'status')); + } + + // --------------------------------------------------------- + // SHOW ADD CUSTOMER FORM + // --------------------------------------------------------- + public function create() + { + return view('admin.customers_add'); + } + + // --------------------------------------------------------- + // STORE NEW CUSTOMER + // --------------------------------------------------------- + public function store(Request $request) + { + $request->validate([ + 'customer_name' => 'required|string|max:255', + 'company_name' => 'nullable|string|max:255', + 'designation' => 'nullable|string|max:255', + 'email' => 'required|email|unique:users,email', + 'mobile_no' => 'required|string|max:20', + 'address' => 'nullable|string', + 'pincode' => 'nullable|string|max:10', + 'customer_type' => 'required|in:regular,premium', + 'status' => 'required|in:active,inactive', + ]); + + // AUTO GENERATE CUSTOMER ID + $year = date('Y'); + $prefix = "CID-$year-"; + + $lastCustomer = User::whereYear('created_at', $year) + ->orderBy('id', 'DESC') + ->first(); + + $next = $lastCustomer ? intval(substr($lastCustomer->customer_id, -6)) + 1 : 1; + $customerId = $prefix . str_pad($next, 6, '0', STR_PAD_LEFT); + + // CREATE CUSTOMER + User::create([ + 'customer_id' => $customerId, + 'customer_name' => $request->customer_name, + 'company_name' => $request->company_name, + 'designation' => $request->designation, + 'email' => $request->email, + 'mobile_no' => $request->mobile_no, + 'address' => $request->address, + 'pincode' => $request->pincode, + 'date' => date('Y-m-d'), + 'customer_type' => $request->customer_type, + 'status' => $request->status, + 'password' => Hash::make('123456'), // DEFAULT PASSWORD + ]); + + return redirect() + ->route('admin.customers.index') + ->with('success', 'Customer added successfully!'); + } + + // --------------------------------------------------------- + // VIEW CUSTOMER FULL DETAILS + // --------------------------------------------------------- + public function view($id) + { + $customer = User::with(['marks', 'orders'])->findOrFail($id); + + $totalOrders = $customer->orders->count(); + $totalAmount = $customer->orders->sum('ttl_amount'); + $recentOrders = $customer->orders()->latest()->take(5)->get(); + + return view('admin.customers_view', compact( + 'customer', + 'totalOrders', + 'totalAmount', + 'recentOrders' + )); + } + + // --------------------------------------------------------- + // TOGGLE STATUS ACTIVE / INACTIVE + // --------------------------------------------------------- + public function toggleStatus($id) + { + $customer = User::findOrFail($id); + + $customer->status = $customer->status === 'active' + ? 'inactive' + : 'active'; + + $customer->save(); + + return back()->with('success', 'Customer status updated.'); + } + +} diff --git a/app/Models/User.php b/app/Models/User.php index d9f952d..b5df7a5 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -15,7 +15,7 @@ class User extends Authenticatable implements JWTSubject * The attributes that are mass assignable. */ protected $fillable = [ - 'customer_id', // CID-2025-000001 format + 'customer_id', 'customer_name', 'company_name', 'designation', @@ -25,10 +25,15 @@ class User extends Authenticatable implements JWTSubject 'pincode', 'date', 'password', + + // newly added customer fields + 'status', // active / inactive + 'customer_type', // premium / regular + 'profile_image', // optional image ]; /** - * The attributes that should be hidden for arrays. + * Attributes that should be hidden. */ protected $hidden = [ 'password', @@ -36,7 +41,7 @@ class User extends Authenticatable implements JWTSubject ]; /** - * The attributes that should be cast. + * Attribute casting. */ protected function casts(): array { @@ -47,7 +52,30 @@ class User extends Authenticatable implements JWTSubject } /** - * JWT Identifier. + * Relationship: User → MarkList (Many) + */ + public function marks() + { + return $this->hasMany(\App\Models\MarkList::class, 'customer_id', 'customer_id'); + } + + /** + * Relationship: User → Orders (Through MarkList) + */ + public function orders() + { + return $this->hasManyThrough( + \App\Models\Order::class, + \App\Models\MarkList::class, + 'customer_id', // MarkList.customer_id + 'mark_no', // Orders.mark_no + 'customer_id', // Users.customer_id + 'mark_no' // MarkList.mark_no + ); + } + + /** + * JWT Identifier */ public function getJWTIdentifier() { @@ -55,7 +83,7 @@ class User extends Authenticatable implements JWTSubject } /** - * JWT Custom Claims. + * JWT Custom Claims */ public function getJWTCustomClaims() { diff --git a/database/migrations/2025_11_17_071001_add_customer_fields_to_users_table.php b/database/migrations/2025_11_17_071001_add_customer_fields_to_users_table.php new file mode 100644 index 0000000..574464e --- /dev/null +++ b/database/migrations/2025_11_17_071001_add_customer_fields_to_users_table.php @@ -0,0 +1,31 @@ +enum('status', ['active', 'inactive'])->default('active')->after('pincode'); + + // premium / regular + $table->enum('customer_type', ['regular', 'premium'])->default('regular')->after('status'); + + // optional: profile image path + $table->string('profile_image')->nullable()->after('customer_type'); + }); + } + + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn(['status', 'customer_type', 'profile_image']); + }); + } + +}; diff --git a/public/invoices/invoice-INV-2025-000005.pdf b/public/invoices/invoice-INV-2025-000005.pdf new file mode 100644 index 0000000..f30024c Binary files /dev/null and b/public/invoices/invoice-INV-2025-000005.pdf differ diff --git a/public/invoices/invoice-INV-2025-000006.pdf b/public/invoices/invoice-INV-2025-000006.pdf new file mode 100644 index 0000000..f1d586a Binary files /dev/null and b/public/invoices/invoice-INV-2025-000006.pdf differ diff --git a/public/invoices/invoice-INV-2025-000007.pdf b/public/invoices/invoice-INV-2025-000007.pdf new file mode 100644 index 0000000..4425ec2 Binary files /dev/null and b/public/invoices/invoice-INV-2025-000007.pdf differ diff --git a/public/invoices/invoice-INV-2025-000008.pdf b/public/invoices/invoice-INV-2025-000008.pdf new file mode 100644 index 0000000..8c6af73 Binary files /dev/null and b/public/invoices/invoice-INV-2025-000008.pdf differ diff --git a/resources/views/admin/customers.blade.php b/resources/views/admin/customers.blade.php index 127e252..f5a787e 100644 --- a/resources/views/admin/customers.blade.php +++ b/resources/views/admin/customers.blade.php @@ -1,12 +1,420 @@ @extends('admin.layouts.app') -@section('page-title', 'Dashboard') +@section('page-title', 'Customers') @section('content') -
Here you can manage all system modules.
-| Customer Info | +Customer ID | +Create Date | +Status | +Actions | +
|---|---|---|---|---|
|
+
+
+
+ {{ strtoupper(substr($c->customer_name,0,1)) }}
+
+
+
+ {{ $c->customer_name }}
+ @if($c->customer_type == 'premium')
+ Premium Customer
+ @else
+ Regular Customer
+ @endif
+
+ {{ $c->email }}
+ + {{ $c->mobile_no }} +
+ {{ $c->orders->count() }} orders • ₹{{ number_format($c->orders->sum('ttl_amount'), 2) }} total
+
+ |
+
+
+ + {{ $c->customer_id }} + | + + ++ {{ $c->created_at ? $c->created_at->format('d-m-Y') : '-' }} + | + + ++ @if($c->status === 'active') + Active + @else + Inactive + @endif + | + + +
+
+
+
+
+
+
+
+ |
+
| + + No customers found. + | +||||
Complete customer information and analytics
++ + {{ $customer->company_name ?? 'No company specified' }} +
+{{ $customer->customer_id }}
+ No mark numbers found for this customer.
+| # | -Order ID | -Mark No | -Origin | -Destination | -Total CTN | -Total QTY | -Total TTL/QTY | -Total Amount (₹) | -Total CBM | -Total TTL CBM | -Total KG | -Total TTL KG | -Status | -Date | -Action | -||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{ $order->id }} | -- - {{ $order->order_id }} - - | +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | +Order ID | +Mark No | +Origin | +Destination | +Total CTN | +Total QTY | +Total TTL/QTY | +Total Amount (₹) | +Total CBM | +Total TTL CBM | +Total KG | +Total TTL KG | +Status | +Date | +Action | +
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{ $order->id }} | ++ + {{ $order->order_id }} + + | -{{ $order->mark_no }} | -{{ $order->origin }} | -{{ $order->destination }} | -{{ $order->ctn }} | -{{ $order->qty }} | -{{ $order->ttl_qty }} | -₹{{ number_format($order->ttl_amount, 2) }} | -{{ $order->cbm }} | -{{ $order->ttl_cbm }} | -{{ $order->kg }} | -{{ $order->ttl_kg }} | -- {{ ucfirst($order->status) }} - | -{{ $order->created_at->format('d-m-Y') }} | -- - View - - | -
| No orders found | -|||||||||||||||
Loading...
"; - - @endsection \ No newline at end of file diff --git a/resources/views/admin/invoice.blade.php b/resources/views/admin/invoice.blade.php index 85f36a6..9bcc41c 100644 --- a/resources/views/admin/invoice.blade.php +++ b/resources/views/admin/invoice.blade.php @@ -173,7 +173,7 @@ width: 100%; min-width: 1100px; border-collapse: separate; - border-spacing: 0; + border-spacing: 0 8px; /* Add gap between rows */ margin-bottom: 0; } @@ -189,7 +189,6 @@ border-top-right-radius: 17px; } - .table thead th { background: transparent !important; border: none; @@ -197,7 +196,7 @@ color: #343535; letter-spacing: 0.02em; font-size: 13px; - padding: 12px 8px; + padding: 16px 12px; white-space: nowrap; position: relative; border-bottom: 2px solid #e8e2cf; @@ -206,50 +205,58 @@ } .table thead th:first-child { - padding-left: 20px; + padding-left: 25px; } .table thead th:last-child { - padding-right: 20px; + padding-right: 25px; } +/* Soft blue background for ALL table rows */ .table-striped tbody tr { - background: #fff; + background: #f0f8ff !important; /* Soft blue background for all rows */ transition: all 0.2s ease; - border-bottom: 1px solid #f1f3f4; + border-radius: 12px; /* Rounded corners for each row */ + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); /* Subtle shadow for separation */ } .table-striped tbody tr:hover { - background: #f8fafc !important; -} - -.table-striped tbody tr:nth-of-type(odd) { - background: #f9fafc; + background: #e6f3ff !important; /* Slightly darker blue on hover */ + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); /* Enhanced shadow on hover */ + transform: translateY(-1px); /* Lift effect on hover */ } +/* Remove striped pattern - all rows same soft blue */ +.table-striped tbody tr:nth-of-type(odd), .table-striped tbody tr:nth-of-type(even) { - background: #ffffff; + background: #f0f8ff !important; /* Same soft blue for all rows */ } -/* Center all table cells */ +/* Center all table cells with proper spacing */ .table td { - padding: 10px 8px; + padding: 16px 12px; /* Increased vertical padding */ border: none; vertical-align: middle; white-space: nowrap; font-size: 13px; color: #4a5568; - border-bottom: 1px solid #f1f3f4; text-align: center !important; /* Center all cell content */ + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; } +/* First and last cell rounded corners */ .table td:first-child { - padding-left: 20px; + padding-left: 25px; font-weight: 600; color: #2d3748; + border-top-left-radius: 12px; + border-bottom-left-radius: 12px; } .table td:last-child { - padding-right: 20px; + padding-right: 25px; + border-top-right-radius: 12px; + border-bottom-right-radius: 12px; } /* Invoice Number with Curved Boxes */ @@ -257,13 +264,13 @@ display: flex; align-items: center; justify-content: center; /* Center the invoice number */ - gap: 8px; - padding: 6px 0; + gap: 12px; + padding: 8px 0; } .invoice-icon { - width: 32px; - height: 32px; + width: 36px; + height: 36px; border-radius: 8px; /* Curved border radius */ display: flex; align-items: center; @@ -324,31 +331,67 @@ text-decoration: none; } -/* Badge Styling - Centered */ +/* Badge Styling - Centered with custom backgrounds and icons */ .badge { - font-size: 11px; - font-weight: 600; - padding: 4px 10px; - border-radius: 8px; + font-size: 11px !important; + font-weight: 600 !important; + padding: 6px 12px 6px 8px !important; + border-radius: 20px !important; text-transform: uppercase; letter-spacing: 0.3px; - display: inline-block; - text-align: center; + display: inline-flex !important; + align-items: center; + justify-content: center; + background-size: cover !important; + background-position: center !important; + color: #fff !important; + text-shadow: 0 1px 2px rgba(0,0,0,0.3); + border: 2px solid transparent !important; + min-width: 40px; + box-sizing: border-box; + line-height: 1.2; + gap: 6px; } -.bg-success { - background: linear-gradient(135deg, #18ce77 60%, #08ac52 110%) !important; - color: #fff !important; +/* Status icons */ +.status-icon { + font-size: 12px; + display: flex; + align-items: center; + justify-content: center; } -.bg-warning { - background: linear-gradient(135deg, #ffb23c 53%, #e17800 110%) !important; - color: #fff !important; +/* Custom status badge backgrounds with icons */ +.badge-paid { + background: url('/images/status-bg-paid.png') !important; } -.bg-danger { - background: linear-gradient(135deg, #ff5a4e 65%, #d90010 110%) !important; - color: #fff !important; +.badge-pending { + background: url('/images/status-bg-pending.png') !important; +} + +.badge-overdue { + background: url('/images/status-bg-overdue.png') !important; +} + +/* Fallback colors if images don't load - ALL WITH SAME SIZE */ +.badge.badge-paid { + background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important; + color: #065f46 !important; + border-color: #10b981 !important; + width: 98px; +} + +.badge.badge-pending { + background: linear-gradient(135deg, #fef3c7, #fde68a) !important; + color: #d97706 !important; + border-color: #f59e0b !important; +} + +.badge.badge-overdue { + background: linear-gradient(135deg, #e9d5ff, #c4b5fd) !important; + color: #6b21a8 !important; + border-color: #8b5cf6 !important; } /* Action Button - Centered */ @@ -356,7 +399,7 @@ background: linear-gradient(135deg, #3492f8 55%, #1256cc 110%); border: none; border-radius: 6px; - padding: 5px 10px; + padding: 6px 12px; font-weight: 600; font-size: 12px; transition: all 0.3s ease; @@ -461,102 +504,33 @@ overflow-y: auto; } -/* Responsive Design */ -@media (max-width: 1200px) { - .invoice-tools-row { - flex-direction: column; - align-items: stretch; - gap: 15px; - } - - .search-box { - max-width: 100%; - min-width: auto; - } - - .filter-group { - justify-content: space-between; - width: 100%; - } +/* Date Range Picker Styles */ +.date-range-container { + display: flex; + align-items: center; + gap: 8px; } -@media (max-width: 768px) { - .invoice-management-bar { - padding: 12px 20px; - } - - .invoice-tools-row { - padding: 15px; - } - - .table th, .table td { - font-size: 12px; - padding: 8px 6px; - } - - .badge { - font-size: 10px; - padding: 3px 8px; - } - - .btn-primary { - padding: 4px 8px; - font-size: 11px; - } - - .filter-group { - flex-direction: column; - align-items: stretch; - } - - .filter-select { - min-width: auto; - width: 100%; - } - - .create-invoice-btn { - width: 100%; - justify-content: center; - } - - .invoice-icon { - width: 28px; - height: 28px; - font-size: 12px; - } -} - -/* Empty State - Centered */ -.text-muted { - color: #8a9bb9 !important; - font-style: italic; - padding: 30px !important; - text-align: center; +.date-input { + background: white; + border: 1px solid #e2e8f0; + border-radius: 8px; + padding: 8px 12px; font-size: 14px; + color: #334155; + outline: none; + min-width: 130px; + box-shadow: 0 2px 5px rgba(0,0,0,0.04); } -/* Card Styling */ -.card { - border-radius: 0 0 17px 17px; - border: none; - margin-bottom: 0 !important; - box-shadow: none; +.date-input:focus { + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } -.card-header { - background: #f8f9fa !important; - border-bottom: 1px solid #e9ecef; - border-radius: 0 !important; - padding: 15px 25px; - text-align: center; -} - -.card-header h4 { - margin: 0; - color: #2451af; - font-weight: 700; - font-size: 1.3rem; - text-align: center; +.date-separator { + color: #64748b; + font-weight: 500; } /* Stats Summary - Centered */ @@ -622,6 +596,262 @@ .table tbody tr td .btn-primary { margin: 0 auto; } + +/* Empty State - Centered */ +.text-muted { + color: #8a9bb9 !important; + font-style: italic; + padding: 30px !important; + text-align: center; + font-size: 14px; +} + +/* Card Styling */ +.card { + border-radius: 0 0 17px 17px; + border: none; + margin-bottom: 0 !important; + box-shadow: none; +} + +.card-header { + background: #f8f9fa !important; + border-bottom: 1px solid #e9ecef; + border-radius: 0 !important; + padding: 15px 25px; + text-align: center; +} + +.card-header h4 { + margin: 0; + color: #2451af; + font-weight: 700; + font-size: 1.3rem; + text-align: center; +} + +/* Mobile Responsive Styles */ +.mobile-invoice-card { + display: none; + background: #f0f8ff; + border-radius: 12px; + padding: 15px; + margin-bottom: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + border-left: 4px solid #667eea; +} + +.mobile-invoice-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + padding-bottom: 8px; + border-bottom: 1px solid #e2e8f0; +} + +.mobile-invoice-number { + display: flex; + align-items: center; + gap: 8px; +} + +.mobile-invoice-icon { + width: 32px; + height: 32px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 12px; + font-weight: bold; +} + +.mobile-invoice-number-text { + font-weight: 600; + color: #2469d6; + font-size: 14px; +} + +.mobile-invoice-status { + font-size: 10px !important; + padding: 4px 8px 4px 6px !important; + min-width: 70px; +} + +.mobile-invoice-details { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + margin-bottom: 12px; +} + +.mobile-detail-item { + display: flex; + flex-direction: column; +} + +.mobile-detail-label { + font-size: 11px; + color: #718096; + margin-bottom: 2px; +} + +.mobile-detail-value { + font-size: 13px; + font-weight: 500; + color: #2d3748; +} + +.mobile-invoice-actions { + display: flex; + justify-content: space-between; + gap: 8px; +} + +.mobile-action-btn { + flex: 1; + text-align: center; + padding: 6px 10px; + font-size: 12px; + border-radius: 6px; + text-decoration: none; + display: flex; + align-items: center; + justify-content: center; + gap: 4px; +} + +.mobile-action-btn.view { + background: #f0f8ff; + color: #2469d6; + border: 1px solid #2469d6; +} + +.mobile-action-btn.edit { + background: linear-gradient(135deg, #3492f8 55%, #1256cc 110%); + color: white; + border: none; +} + +/* Responsive Design */ +@media (max-width: 1200px) { + .invoice-tools-row { + flex-direction: column; + align-items: stretch; + gap: 15px; + } + + .search-box { + max-width: 100%; + min-width: auto; + } + + .filter-group { + justify-content: space-between; + width: 100%; + } +} + +@media (max-width: 992px) { + .stats-summary { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 768px) { + .invoice-management-bar { + padding: 12px 20px; + } + + .invoice-tools-row { + padding: 15px; + } + + .table th, .table td { + font-size: 12px; + padding: 12px 8px; + } + + .badge { + font-size: 10px !important; + padding: 5px 10px 5px 6px !important; + min-width: 70px; + gap: 4px; + } + + .status-icon { + font-size: 10px; + } + + .btn-primary { + padding: 4px 8px; + font-size: 11px; + } + + .filter-group { + flex-direction: column; + align-items: stretch; + } + + .filter-select { + min-width: auto; + width: 100%; + } + + .create-invoice-btn { + width: 100%; + justify-content: center; + } + + .invoice-icon { + width: 28px; + height: 28px; + font-size: 12px; + } + + /* Show mobile cards and hide table on small screens */ + .desktop-table { + display: none; + } + + .mobile-invoice-card { + display: block; + } + + .date-range-container { + flex-direction: column; + width: 100%; + } + + .date-input { + width: 100%; + } + + .stats-summary { + grid-template-columns: 1fr; + margin: 15px; + } +} + +@media (max-width: 576px) { + .invoice-management-title { + font-size: 1.1rem; + } + + .card-header h4 { + font-size: 1.1rem; + } + + .mobile-invoice-details { + grid-template-columns: 1fr; + } + + .mobile-invoice-actions { + flex-direction: column; + } +}
- | # | -Description | -CTN | -QTY | -TTL/QTY | -Unit | -Price | -TTL Amount | -CBM | -TTL CBM | -KG | -TTL KG | -Shop No | -
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{ $i + 1 }} | -{{ $item->description }} | -{{ $item->ctn }} | -{{ $item->qty }} | -{{ $item->ttl_qty }} | -{{ $item->unit }} | -{{ number_format($item->price, 2) }} | -{{ number_format($item->ttl_amount, 2) }} | -{{ $item->cbm }} | -{{ $item->ttl_cbm }} | -{{ $item->kg }} | -{{ $item->ttl_kg }} | -{{ $item->shop_no }} | -
+ | Description | +Qty | +Rate (₹) | +Amount (₹) | +
|---|---|---|---|
| {{ $item->description }} | +{{ $item->qty }} | +{{ number_format($item->price, 0) }} | +{{ number_format($item->ttl_amount, 0) }} | +
| Subtotal: | +{{ number_format($invoice->subtotal, 0) }} | +||
| GST ({{ $invoice->gst_percent }}%): | +{{ number_format($invoice->gst_amount, 0) }} | +||
| Total: | +{{ number_format($invoice->final_amount_with_gst, 0) }} | +||