From 9fd39bd7d6e6d221ff46c0161b88387d0ce2a036 Mon Sep 17 00:00:00 2001 From: Niek Rabelink Date: Mon, 2 Nov 2026 09:56:24 +0100 Subject: [PATCH] Add customer contact inquiry system, automated receipt emails, admin communication inbox, and customer profile management --- app/Http/Controllers/AccountController.php | 9 +- .../Admin/CommunicationController.php | 139 +++++++++ .../Controllers/Admin/CustomerController.php | 176 +++++++++++ app/Http/Controllers/ContactController.php | 103 +++++++ app/Mail/ContactInquiryConfirmationMail.php | 33 ++ app/Mail/ContactInquiryReceivedMail.php | 35 +++ app/Mail/ContactInquiryReplyMail.php | 40 +++ app/Models/Communication.php | 58 ++++ app/Models/CommunicationReply.php | 33 ++ app/Models/User.php | 10 + ..._02_084554_create_communications_table.php | 49 +++ resources/views/account/index.blade.php | 95 +++++- .../admin/communications/index.blade.php | 222 ++++++++++++++ .../views/admin/communications/show.blade.php | 282 ++++++++++++++++++ resources/views/admin/content/index.blade.php | 2 +- .../views/admin/customers/index.blade.php | 150 ++++++++++ .../views/admin/customers/show.blade.php | 239 +++++++++++++++ resources/views/admin/orders/show.blade.php | 11 +- .../views/admin/products/create.blade.php | 2 +- resources/views/admin/products/edit.blade.php | 4 +- resources/views/contact/index.blade.php | 252 ++++++++++++++++ .../contact-inquiry-confirmation.blade.php | 67 +++++ .../emails/contact-inquiry-received.blade.php | 81 +++++ .../emails/contact-inquiry-reply.blade.php | 49 +++ resources/views/layouts/admin.blade.php | 29 ++ resources/views/layouts/app.blade.php | 12 + routes/web.php | 18 ++ tests/Feature/CustomerCommunicationTest.php | 234 +++++++++++++++ 28 files changed, 2426 insertions(+), 8 deletions(-) create mode 100644 app/Http/Controllers/Admin/CommunicationController.php create mode 100644 app/Http/Controllers/Admin/CustomerController.php create mode 100644 app/Http/Controllers/ContactController.php create mode 100644 app/Mail/ContactInquiryConfirmationMail.php create mode 100644 app/Mail/ContactInquiryReceivedMail.php create mode 100644 app/Mail/ContactInquiryReplyMail.php create mode 100644 app/Models/Communication.php create mode 100644 app/Models/CommunicationReply.php create mode 100644 database/migrations/2026_11_02_084554_create_communications_table.php create mode 100644 resources/views/admin/communications/index.blade.php create mode 100644 resources/views/admin/communications/show.blade.php create mode 100644 resources/views/admin/customers/index.blade.php create mode 100644 resources/views/admin/customers/show.blade.php create mode 100644 resources/views/contact/index.blade.php create mode 100644 resources/views/emails/contact-inquiry-confirmation.blade.php create mode 100644 resources/views/emails/contact-inquiry-received.blade.php create mode 100644 resources/views/emails/contact-inquiry-reply.blade.php create mode 100644 tests/Feature/CustomerCommunicationTest.php diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 427430c..a7759dc 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Models\Communication; use App\Models\Order; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -25,7 +26,13 @@ class AccountController extends Controller ->latest() ->get(); - return view('account.index', compact('user', 'orders')); + $communications = Communication::where('user_id', $user->id) + ->orWhere('email', strtolower($user->email)) + ->with('replies') + ->latest() + ->get(); + + return view('account.index', compact('user', 'orders', 'communications')); } /** diff --git a/app/Http/Controllers/Admin/CommunicationController.php b/app/Http/Controllers/Admin/CommunicationController.php new file mode 100644 index 0000000..021e87d --- /dev/null +++ b/app/Http/Controllers/Admin/CommunicationController.php @@ -0,0 +1,139 @@ +latest(); + + // Status filter + if ($request->filled('status') && $request->input('status') !== 'all') { + $query->where('status', $request->input('status')); + } + + // Search query + if ($request->filled('q')) { + $search = trim($request->input('q')); + $query->where(function ($q) use ($search) { + $q->where('name', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%") + ->orWhere('subject', 'like', "%{$search}%") + ->orWhere('message', 'like', "%{$search}%"); + }); + } + + $communications = $query->paginate(20)->withQueryString(); + + $counts = [ + 'all' => Communication::count(), + 'new' => Communication::where('status', 'new')->count(), + 'in_progress' => Communication::where('status', 'in_progress')->count(), + 'resolved' => Communication::where('status', 'resolved')->count(), + 'closed' => Communication::where('status', 'closed')->count(), + ]; + + return view('admin.communications.index', [ + 'communications' => $communications, + 'counts' => $counts, + 'currentStatus' => $request->input('status', 'all'), + 'search' => $request->input('q', ''), + ]); + } + + /** + * Display the specified communication thread. + */ + public function show(Communication $communication): View + { + $communication->load(['user.orders', 'order.items', 'replies.user']); + + // Mark as in_progress if still new when viewed by admin + if ($communication->status === 'new') { + $communication->update(['status' => 'in_progress']); + } + + return view('admin.communications.show', [ + 'communication' => $communication, + ]); + } + + /** + * Update the status and/or admin notes for this communication. + */ + public function updateStatus(Request $request, Communication $communication): RedirectResponse + { + $validated = $request->validate([ + 'status' => 'required|string|in:new,in_progress,resolved,closed', + 'admin_notes' => 'nullable|string|max:5000', + ]); + + $communication->update($validated); + + return back()->with('success', 'Status van het bericht is bijgewerkt.'); + } + + /** + * Send a reply to the customer from the admin dashboard. + */ + public function sendReply(Request $request, Communication $communication): RedirectResponse + { + $validated = $request->validate([ + 'message' => 'required|string|min:5|max:5000', + ], [ + 'message.required' => 'Vul alstublieft een antwoordbericht in.', + 'message.min' => 'Het antwoordbericht moet minimaal 5 tekens bevatten.', + ]); + + $reply = CommunicationReply::create([ + 'communication_id' => $communication->id, + 'user_id' => Auth::id(), + 'is_admin_reply' => true, + 'message' => trim($validated['message']), + ]); + + $communication->update([ + 'status' => 'resolved', + 'last_replied_at' => now(), + ]); + + // Send email to customer from info@jouwdriedee.nl + try { + Mail::to($communication->email)->send(new ContactInquiryReplyMail($communication, $reply)); + } catch (\Throwable $e) { + Log::error('Fout bij verzenden van admin reactie naar klant: '.$e->getMessage(), [ + 'communication_id' => $communication->id, + 'email' => $communication->email, + ]); + + return back()->with('warning', 'Het antwoord is opgeslagen in de database, maar er trad een fout op bij het verzenden van de e-mail: '.$e->getMessage()); + } + + return back()->with('success', 'Jouw antwoord is verzonden naar '.$communication->email.' en toegevoegd aan het communicatiedossier.'); + } + + /** + * Remove the specified communication from storage. + */ + public function destroy(Communication $communication): RedirectResponse + { + $communication->delete(); + + return redirect()->route('admin.communications.index')->with('success', 'Klantbericht is verwijderd.'); + } +} diff --git a/app/Http/Controllers/Admin/CustomerController.php b/app/Http/Controllers/Admin/CustomerController.php new file mode 100644 index 0000000..bd609a2 --- /dev/null +++ b/app/Http/Controllers/Admin/CustomerController.php @@ -0,0 +1,176 @@ +input('q', '')); + + // Query registered non-admin users + $usersQuery = User::where('is_admin', false); + if ($search !== '') { + $usersQuery->where(function ($q) use ($search) { + $q->where('name', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%"); + }); + } + $registeredUsers = $usersQuery->withCount(['orders', 'communications'])->latest()->get(); + + // Also discover guest customers who placed orders or sent inquiries without an account + $registeredEmails = User::pluck('email')->map(fn ($e) => strtolower($e))->toArray(); + + $guestOrderEmails = Order::whereNull('user_id') + ->select('customer_email', 'customer_name', 'customer_phone', 'created_at') + ->latest() + ->get() + ->groupBy(fn ($item) => strtolower($item->customer_email)); + + $guestInquiryEmails = Communication::whereNull('user_id') + ->select('email', 'name', 'phone', 'created_at') + ->latest() + ->get() + ->groupBy(fn ($item) => strtolower($item->email)); + + $guestCustomers = collect(); + $allGuestEmails = $guestOrderEmails->keys()->merge($guestInquiryEmails->keys())->unique(); + + foreach ($allGuestEmails as $email) { + if (in_array(strtolower($email), $registeredEmails)) { + continue; + } + + if ($search !== '' && ! str_contains(strtolower($email), strtolower($search))) { + continue; + } + + $orderGroup = $guestOrderEmails->get($email, collect()); + $inquiryGroup = $guestInquiryEmails->get($email, collect()); + + $latestOrder = $orderGroup->first(); + $latestInquiry = $inquiryGroup->first(); + + $name = $latestOrder ? $latestOrder->customer_name : ($latestInquiry ? $latestInquiry->name : 'Gast'); + $phone = $latestOrder ? $latestOrder->customer_phone : ($latestInquiry ? $latestInquiry->phone : null); + $createdAt = $latestOrder ? $latestOrder->created_at : ($latestInquiry ? $latestInquiry->created_at : now()); + + $guestCustomers->push((object) [ + 'id' => null, + 'is_registered' => false, + 'name' => $name, + 'email' => $email, + 'phone' => $phone, + 'orders_count' => $orderGroup->count(), + 'communications_count' => $inquiryGroup->count(), + 'created_at' => $createdAt, + ]); + } + + // Map registered users into unified customer object + $registeredCustomers = $registeredUsers->map(function ($u) { + return (object) [ + 'id' => $u->id, + 'is_registered' => true, + 'name' => $u->name, + 'email' => $u->email, + 'phone' => null, + 'orders_count' => $u->orders_count, + 'communications_count' => $u->communications_count, + 'created_at' => $u->created_at, + ]; + }); + + $allCustomers = $registeredCustomers->concat($guestCustomers)->sortByDesc('created_at')->values(); + + // Manual pagination for the unified list + $perPage = 20; + $page = LengthAwarePaginator::resolveCurrentPage('page'); + $paginatedCustomers = new LengthAwarePaginator( + $allCustomers->forPage($page, $perPage), + $allCustomers->count(), + $perPage, + $page, + ['path' => route('admin.customers.index'), 'query' => $request->query()] + ); + + return view('admin.customers.index', [ + 'customers' => $paginatedCustomers, + 'totalRegistered' => $registeredUsers->count(), + 'totalGuests' => $guestCustomers->count(), + 'search' => $search, + ]); + } + + /** + * Display a specific customer's profile, including their full communication dossier and orders. + */ + public function show(string $identifier): View + { + $user = null; + $email = null; + + if (is_numeric($identifier)) { + $user = User::find($identifier); + if ($user) { + $email = strtolower($user->email); + } + } else { + $email = strtolower(urldecode($identifier)); + $user = User::where('email', $email)->first(); + } + + if (! $user && ! $email) { + abort(404, 'Klant niet gevonden.'); + } + + // Fetch all communications for this customer (by user_id OR by email) + $communicationsQuery = Communication::with(['replies', 'order'])->latest(); + if ($user) { + $communicationsQuery->where(function ($q) use ($user, $email) { + $q->where('user_id', $user->id) + ->orWhere('email', $email); + }); + } else { + $communicationsQuery->where('email', $email); + } + $communications = $communicationsQuery->get(); + + // Fetch all orders for this customer (by user_id OR by email) + $ordersQuery = Order::with('items')->latest(); + if ($user) { + $ordersQuery->where(function ($q) use ($user, $email) { + $q->where('user_id', $user->id) + ->orWhere('customer_email', $email); + }); + } else { + $ordersQuery->where('customer_email', $email); + } + $orders = $ordersQuery->get(); + + $customerName = $user ? $user->name : ($orders->first()?->customer_name ?? $communications->first()?->name ?? 'Klant'); + $customerPhone = $orders->first()?->customer_phone ?? $communications->first()?->phone ?? null; + $totalSpent = $orders->sum('total'); + + return view('admin.customers.show', [ + 'user' => $user, + 'email' => $email, + 'customerName' => $customerName, + 'customerPhone' => $customerPhone, + 'totalSpent' => $totalSpent, + 'communications' => $communications, + 'orders' => $orders, + ]); + } +} diff --git a/app/Http/Controllers/ContactController.php b/app/Http/Controllers/ContactController.php new file mode 100644 index 0000000..6985f0b --- /dev/null +++ b/app/Http/Controllers/ContactController.php @@ -0,0 +1,103 @@ +orders()->latest()->take(10)->get() : collect(); + + return view('contact.index', [ + 'user' => $user, + 'userOrders' => $userOrders, + ]); + } + + /** + * Handle the submission of a question / contact inquiry. + */ + public function store(Request $request): RedirectResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:150', + 'email' => 'required|email|max:150', + 'phone' => 'nullable|string|max:40', + 'subject' => 'required|string|max:200', + 'order_number' => 'nullable|string|max:50', + 'message' => 'required|string|min:10|max:5000', + ], [ + 'name.required' => 'Vul alstublieft jouw naam in.', + 'email.required' => 'Vul alstublieft een geldig e-mailadres in.', + 'email.email' => 'Het ingevoerde e-mailadres is niet geldig.', + 'subject.required' => 'Vul alstublieft het onderwerp van jouw vraag in.', + 'message.required' => 'Vul alstublieft jouw bericht of vraag in.', + 'message.min' => 'Het bericht moet minimaal 10 tekens bevatten.', + ]); + + $orderId = null; + if (! empty($validated['order_number'])) { + $cleanNumber = ltrim(trim($validated['order_number']), '#'); + $foundOrder = Order::where('order_number', $cleanNumber)->first(); + if ($foundOrder) { + $orderId = $foundOrder->id; + } + } + + $communication = Communication::create([ + 'user_id' => Auth::id(), + 'order_id' => $orderId, + 'name' => trim($validated['name']), + 'email' => strtolower(trim($validated['email'])), + 'phone' => ! empty($validated['phone']) ? trim($validated['phone']) : null, + 'subject' => trim($validated['subject']), + 'message' => trim($validated['message']), + 'status' => 'new', + 'ip_address' => $request->ip(), + 'user_agent' => substr((string) $request->userAgent(), 0, 500), + ]); + + // 1. Send incoming mail to webshop management (info@jouwdrieedee.nl / info@jouwdriedee.nl) + try { + $shopInbox = Setting::get('contact_email', 'info@jouwdrieedee.nl'); + // Ensure delivery to both info@jouwdrieedee.nl and fallback info@jouwdriedee.nl + $recipients = array_unique([$shopInbox, 'info@jouwdrieedee.nl', 'info@jouwdriedee.nl']); + Mail::to($recipients)->send(new ContactInquiryReceivedMail($communication)); + } catch (\Throwable $e) { + Log::error('Fout bij versturen van klantvraag naar webshop beheer: '.$e->getMessage(), [ + 'communication_id' => $communication->id, + ]); + } + + // 2. Send afschrift receipt from (noreply@jouwdriedee.nl) to the customer + try { + Mail::to($communication->email)->send(new ContactInquiryConfirmationMail($communication)); + } catch (\Throwable $e) { + Log::error('Fout bij versturen van afschrift naar klant: '.$e->getMessage(), [ + 'communication_id' => $communication->id, + 'email' => $communication->email, + ]); + } + + return redirect()->route('contact.index')->with( + 'success', + 'Bedankt voor je vraag! We hebben jouw bericht ontvangen. Er is een afschrift verzonden naar '.$communication->email.'.' + ); + } +} diff --git a/app/Mail/ContactInquiryConfirmationMail.php b/app/Mail/ContactInquiryConfirmationMail.php new file mode 100644 index 0000000..146bda1 --- /dev/null +++ b/app/Mail/ContactInquiryConfirmationMail.php @@ -0,0 +1,33 @@ +communication->subject, + ); + } + + public function content(): Content + { + return new Content( + view: 'emails.contact-inquiry-confirmation', + ); + } +} diff --git a/app/Mail/ContactInquiryReceivedMail.php b/app/Mail/ContactInquiryReceivedMail.php new file mode 100644 index 0000000..e5c4a12 --- /dev/null +++ b/app/Mail/ContactInquiryReceivedMail.php @@ -0,0 +1,35 @@ +communication->id.': '.$this->communication->subject, + replyTo: [ + new Address($this->communication->email, $this->communication->name), + ], + ); + } + + public function content(): Content + { + return new Content( + view: 'emails.contact-inquiry-received', + ); + } +} diff --git a/app/Mail/ContactInquiryReplyMail.php b/app/Mail/ContactInquiryReplyMail.php new file mode 100644 index 0000000..c3e289e --- /dev/null +++ b/app/Mail/ContactInquiryReplyMail.php @@ -0,0 +1,40 @@ +communication->subject.' - Jouwdriedee.nl', + ); + } + + public function content(): Content + { + return new Content( + view: 'emails.contact-inquiry-reply', + ); + } +} diff --git a/app/Models/Communication.php b/app/Models/Communication.php new file mode 100644 index 0000000..a34f119 --- /dev/null +++ b/app/Models/Communication.php @@ -0,0 +1,58 @@ + 'datetime', + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function order(): BelongsTo + { + return $this->belongsTo(Order::class); + } + + public function replies(): HasMany + { + return $this->hasMany(CommunicationReply::class)->oldest(); + } + + public function getStatusLabelAttribute(): string + { + return match ($this->status) { + 'new' => 'Nieuw', + 'in_progress' => 'In behandeling', + 'resolved' => 'Beantwoord', + 'closed' => 'Gesloten', + default => ucfirst($this->status), + }; + } +} diff --git a/app/Models/CommunicationReply.php b/app/Models/CommunicationReply.php new file mode 100644 index 0000000..07b8eab --- /dev/null +++ b/app/Models/CommunicationReply.php @@ -0,0 +1,33 @@ + 'boolean', + ]; + + public function communication(): BelongsTo + { + return $this->belongsTo(Communication::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index d0167ee..d68c454 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -48,4 +48,14 @@ class User extends Authenticatable { return $this->hasMany(Order::class); } + + /** + * Communications / questions submitted by this user. + * + * @return HasMany + */ + public function communications(): HasMany + { + return $this->hasMany(Communication::class); + } } diff --git a/database/migrations/2026_11_02_084554_create_communications_table.php b/database/migrations/2026_11_02_084554_create_communications_table.php new file mode 100644 index 0000000..8a52de3 --- /dev/null +++ b/database/migrations/2026_11_02_084554_create_communications_table.php @@ -0,0 +1,49 @@ +id(); + $table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete(); + $table->foreignId('order_id')->nullable()->constrained('orders')->nullOnDelete(); + $table->string('name'); + $table->string('email')->index(); + $table->string('phone')->nullable(); + $table->string('subject'); + $table->text('message'); + $table->string('status')->default('new')->index(); // new, in_progress, resolved, closed + $table->text('admin_notes')->nullable(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->timestamp('last_replied_at')->nullable(); + $table->timestamps(); + }); + + Schema::create('communication_replies', function (Blueprint $table) { + $table->id(); + $table->foreignId('communication_id')->constrained('communications')->cascadeOnDelete(); + $table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete(); + $table->boolean('is_admin_reply')->default(true); + $table->text('message'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('communication_replies'); + Schema::dropIfExists('communications'); + } +}; diff --git a/resources/views/account/index.blade.php b/resources/views/account/index.blade.php index 5045811..4919607 100644 --- a/resources/views/account/index.blade.php +++ b/resources/views/account/index.blade.php @@ -56,11 +56,24 @@ > Mijn Bestellingen - + {{ $orders->count() }} + + + + + + + + @if(session('success')) +
+ + {{ session('success') }} +
+ @endif + @if(session('warning')) +
+ + {{ session('warning') }} +
+ @endif + +
+ + +
+ + +
+
+
+
+ {{ strtoupper(substr($communication->name, 0, 1)) }} +
+
+
+ {{ $communication->name }} + <{{ $communication->email }}> +
+ Oorspronkelijk bericht +
+
+ + {{ $communication->created_at->format('d-m-Y H:i:s') }} + +
+ + +
+{{ $communication->message }} +
+ + @if($communication->ip_address) +
+ IP-adres: {{ $communication->ip_address }} + • + Afschrift verstuurd naar: {{ $communication->email }} +
+ @endif +
+ + + @if($communication->replies->isNotEmpty()) +
+

+ + Eerdere Reacties ({{ $communication->replies->count() }}) +

+ + @foreach($communication->replies as $reply) +
+
+
+
+ {{ $reply->user ? strtoupper(substr($reply->user->name, 0, 1)) : 'J3D' }} +
+
+ {{ $reply->user?->name ?? 'Jouwdriedee.nl Beheer' }} + Verzonden via info@jouwdriedee.nl +
+
+ + {{ $reply->created_at->format('d-m-Y H:i') }} + +
+ +
+{{ $reply->message }} +
+
+ @endforeach +
+ @endif + + +
+
+

+ + Beantwoord deze vraag direct +

+

+ Jouw antwoord wordt als e-mail verzonden vanuit info@jouwdriedee.nl naar {{ $communication->email }}. +

+
+ +
+ @csrf + +
+ +
+ +
+
+ + Status wordt na verzending automatisch 'Beantwoord' +
+ + +
+
+
+ +
+ + +
+ + +
+

Status & Interne Notities

+ +
+ @csrf + +
+ + +
+ +
+ + +
+ + +
+
+ + +
+
+

Klantgegevens

+ + Volledig Profiel → + +
+ +
+ + + + + @if($communication->phone) + + @endif + +
+ Account status + @if($communication->user) + + + Geregistreerd account (#{{ $communication->user->id }}) + + @else + Gastbezoeker (geen account) + @endif +
+
+ + +
+ + + @if($communication->order) +
+
+

Betreft Bestelling

+ + Order details → + +
+ +
+
+ Ordernummer + #{{ $communication->order->order_number }} +
+
+ Orderdatum + {{ $communication->order->created_at->format('d-m-Y H:i') }} +
+
+ Bedrag & Status + {{ $communication->order->formatted_total }} + {{ $communication->order->status }} +
+
+
+ @endif + +
+ +
+ + +@endsection diff --git a/resources/views/admin/content/index.blade.php b/resources/views/admin/content/index.blade.php index a40f164..60ed561 100644 --- a/resources/views/admin/content/index.blade.php +++ b/resources/views/admin/content/index.blade.php @@ -8,7 +8,7 @@
- Content Management + Shop Content & Stijl CMS

Layout & Teksten

Pas teksten, titels, USP's, kostprijstarieven en instellingen van de webshop aan. diff --git a/resources/views/admin/customers/index.blade.php b/resources/views/admin/customers/index.blade.php new file mode 100644 index 0000000..c491bef --- /dev/null +++ b/resources/views/admin/customers/index.blade.php @@ -0,0 +1,150 @@ +@extends('layouts.admin') + +@section('title', 'Klanten & Profielen | Jouwdriedee Beheer') + +@section('content') +

+ + +
+
+
+ + Relatiebeheer & Accounts +
+

Klanten & Profielen

+

+ Overzicht van geregistreerde klanten ({{ $totalRegistered }}) en gastkopers ({{ $totalGuests }}). +

+
+ + +
+ + +
+
+
+ + +
+ @if($search) + + Wissen + + @endif +
+
+ + +
+
+ + + + + + + + + + + + + @forelse($customers as $customer) + + + + + + + + + + + + + + + + + + + + + + @empty + + + + @endforelse + +
KlantTypeBestellingenCommunicatie / VragenActief sindsProfiel
+
+
+ {{ strtoupper(substr($customer->name, 0, 1)) }} +
+
+ + {{ $customer->name }} + +
+ {{ $customer->email }} +
+
+
+
+ @if($customer->is_registered) + + Geregistreerd account + + @else + + Gastkoper + + @endif + + + {{ $customer->orders_count }} order(s) + + + @if($customer->communications_count > 0) + + + {{ $customer->communications_count }} bericht(en) + + @else + 0 berichten + @endif + + {{ $customer->created_at instanceof \Carbon\Carbon ? $customer->created_at->format('d-m-Y') : date('d-m-Y', strtotime((string)$customer->created_at)) }} + + + Bekijk profiel + + +
+ Geen klanten gevonden die voldoen aan de zoekopdracht. +
+
+ + @if($customers->hasPages()) +
+ {{ $customers->links() }} +
+ @endif +
+ +
+@endsection diff --git a/resources/views/admin/customers/show.blade.php b/resources/views/admin/customers/show.blade.php new file mode 100644 index 0000000..8dceb18 --- /dev/null +++ b/resources/views/admin/customers/show.blade.php @@ -0,0 +1,239 @@ +@extends('layouts.admin') + +@section('title', 'Klantprofiel: ' . $customerName . ' | Jouwdriedee Beheer') + +@section('content') +
+ + +
+
+ + + Terug naar Klantenoverzicht + +
+

{{ $customerName }}

+ @if($user) + + Geregistreerd account (#{{ $user->id }}) + + @else + + Gastkoper + + @endif +
+

+ {{ $email }} @if($customerPhone) • {{ $customerPhone }} @endif +

+
+ + +
+ + +
+ +
+
+ Communicatie Dossier + {{ $communications->count() }} vragen / berichten +
+
+ +
+
+ +
+
+ Bestellingen + {{ $orders->count() }} orders +
+
+ +
+
+ +
+
+ Totale Omzet + € {{ number_format($totalSpent, 2, ',', '.') }} +
+
+ +
+
+ +
+ + +
+ + + +
+ + +
+ @if($communications->isEmpty()) +
+
+ +
+

Geen communicatie gevonden

+

+ Deze klant heeft nog geen vragen gesteld via het contactformulier. +

+
+ @else +
+ @foreach($communications as $comm) +
+ + +
+
+ #{{ $comm->id }} +

{{ $comm->subject }}

+
+ +
+ + {{ $comm->status_label }} + + + {{ $comm->created_at->format('d-m-Y H:i') }} + +
+
+ + +
+ {{ $comm->message }} +
+ + +
+
+ @if($comm->order) + Betreft: #{{ $comm->order->order_number }} + • + @endif + {{ $comm->replies->count() }} antwoord(en) verzonden +
+ + + Open Bericht & Beantwoord + + +
+ +
+ @endforeach +
+ @endif +
+ + +
+ @if($orders->isEmpty()) +
+
+ +
+

Nog geen bestellingen

+

+ Deze klant heeft nog geen geplaatste orders in het systeem. +

+
+ @else +
+ + + + + + + + + + + + + @foreach($orders as $order) + + + + + + + + + @endforeach + +
Order #DatumItemsTotaalStatusDetails
+ {{ $order->order_number }} + + {{ $order->created_at->format('d-m-Y H:i') }} + + {{ $order->items->count() }} prints + + {{ $order->formatted_total }} + + + {{ $order->status_label }} + + + + Order bekijken + + +
+
+ @endif +
+ +
+@endsection diff --git a/resources/views/admin/orders/show.blade.php b/resources/views/admin/orders/show.blade.php index 0f05b35..d93fdff 100644 --- a/resources/views/admin/orders/show.blade.php +++ b/resources/views/admin/orders/show.blade.php @@ -88,12 +88,19 @@
-

Klantgegevens

+
+

Klantgegevens

+ + Klantprofiel → + +
Naam - {{ $order->customer_name }} + + {{ $order->customer_name }} +
E-mailadres diff --git a/resources/views/admin/products/create.blade.php b/resources/views/admin/products/create.blade.php index 023b668..0fe9d17 100644 --- a/resources/views/admin/products/create.blade.php +++ b/resources/views/admin/products/create.blade.php @@ -338,7 +338,7 @@
-

Afbeeldingen

+

Productafbeeldingen & Fotogalerij

diff --git a/resources/views/admin/products/edit.blade.php b/resources/views/admin/products/edit.blade.php index 6a0f52c..4a5344b 100644 --- a/resources/views/admin/products/edit.blade.php +++ b/resources/views/admin/products/edit.blade.php @@ -347,7 +347,7 @@

-

Afbeeldingen

+

Productafbeeldingen & Fotogalerij

@@ -394,7 +394,7 @@

- Hoofdafbeelding + Hoofdafbeelding (Cover)