144 lines
4.9 KiB
PHP
144 lines
4.9 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Admin;
|
|
|
|
use App\Mail\OperatorMessageMail;
|
|
use App\Models\Customer;
|
|
use App\Models\SentMail;
|
|
use App\Models\SupportRequest;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Attributes\Validate;
|
|
use Livewire\Component;
|
|
|
|
/**
|
|
* One customer, everything about them, and a box to write to them.
|
|
*
|
|
* The console had a customer LIST and nothing behind it: an operator answering
|
|
* a question had the name in one window, the mail client in another, and no
|
|
* record anywhere afterwards that an answer had been given at all. So the
|
|
* conversation happened outside the system that knows who the customer is.
|
|
*
|
|
* This page is the other half. What they bought, what they asked, what we sent
|
|
* them — and a field to write the next message, which goes out as mail from the
|
|
* support mailbox and is written into the register on its way.
|
|
*
|
|
* Reading their INCOMING mail is not here yet. That needs the mailbox itself
|
|
* polled over IMAP, which is a mail-server credential and a dependency; until
|
|
* then the requests a customer files through the portal appear here in full,
|
|
* and a mail they write instead still has to be read in the mail client.
|
|
*/
|
|
#[Layout('layouts.admin')]
|
|
class CustomerDetail extends Component
|
|
{
|
|
public Customer $customer;
|
|
|
|
/** What the operator is writing. Empty is the normal state of this page. */
|
|
#[Validate('required|string|max:200')]
|
|
public string $subject = '';
|
|
|
|
#[Validate('required|string|min:3|max:5000')]
|
|
public string $body = '';
|
|
|
|
/**
|
|
* The request being answered, if this message is an answer to one.
|
|
*
|
|
* Answering marks it answered — which is the whole reason to answer from
|
|
* here rather than from a mail client, where the request stays open for
|
|
* ever because nothing told it otherwise.
|
|
*/
|
|
public ?string $answering = null;
|
|
|
|
public function mount(string $uuid): void
|
|
{
|
|
$this->authorize('customers.manage');
|
|
|
|
$this->customer = Customer::query()->where('uuid', $uuid)->firstOrFail();
|
|
}
|
|
|
|
/**
|
|
* Start an answer to one request: subject prefilled, the request remembered.
|
|
*/
|
|
public function answer(string $uuid): void
|
|
{
|
|
$this->authorize('customers.manage');
|
|
|
|
$request = $this->customer->supportRequests()->where('uuid', $uuid)->first();
|
|
|
|
if ($request === null) {
|
|
return;
|
|
}
|
|
|
|
$this->answering = $request->uuid;
|
|
$this->subject = __('customer_message.re', ['subject' => $request->subject]);
|
|
$this->body = '';
|
|
}
|
|
|
|
public function cancelAnswer(): void
|
|
{
|
|
$this->answering = null;
|
|
$this->subject = '';
|
|
$this->body = '';
|
|
}
|
|
|
|
/**
|
|
* Send it, record it, and close the request it answers.
|
|
*
|
|
* The register row is written here rather than left to the MessageSent
|
|
* listener, because this is the one mail whose BODY is worth keeping: an
|
|
* operator typed it, and "what exactly did I write to them in March" is a
|
|
* question the mail server's log cannot answer either.
|
|
*/
|
|
public function send(): void
|
|
{
|
|
$this->authorize('customers.manage');
|
|
|
|
$data = $this->validate();
|
|
|
|
Mail::to($this->customer->email)->send(
|
|
new OperatorMessageMail($this->customer, $data['subject'], $data['body'])
|
|
);
|
|
|
|
// The listener has already written a row for the delivery — it fires on
|
|
// every mail this application sends. This adds what only the console
|
|
// knows: that a person wrote it, and what they wrote.
|
|
SentMail::query()
|
|
->where('customer_id', $this->customer->id)
|
|
->where('to', $this->customer->email)
|
|
->latest('id')
|
|
->limit(1)
|
|
->update(['from_operator' => true, 'body' => $data['body']]);
|
|
|
|
if ($this->answering !== null) {
|
|
$this->customer->supportRequests()
|
|
->where('uuid', $this->answering)
|
|
->update(['status' => 'answered', 'answered_at' => now()]);
|
|
}
|
|
|
|
$this->cancelAnswer();
|
|
$this->dispatch('notify', message: __('customer_message.sent'));
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
$this->authorize('customers.manage');
|
|
|
|
return view('livewire.admin.customer-detail', [
|
|
'requests' => $this->customer->supportRequests()
|
|
->with('instance')
|
|
->latest('id')
|
|
->get(),
|
|
// The whole conversation as it left here, newest first. Capped:
|
|
// this is a page, not an export, and a customer of three years has
|
|
// a few hundred rows nobody scrolls.
|
|
'mails' => SentMail::query()
|
|
->where('customer_id', $this->customer->id)
|
|
->latest('sent_at')
|
|
->limit(50)
|
|
->get(),
|
|
'instance' => $this->customer->instances()->latest('id')->first(),
|
|
'subscription' => $this->customer->subscriptions()->latest('id')->first(),
|
|
]);
|
|
}
|
|
}
|