139 lines
4.7 KiB
PHP
139 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Admin;
|
|
|
|
use App\Models\Customer;
|
|
use App\Models\InboundMail;
|
|
use App\Services\Mail\IngestInboundMail;
|
|
use App\Services\Mail\InboundMailbox;
|
|
use App\Services\Mail\InboundMailStatus;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Attributes\Url;
|
|
use Livewire\Component;
|
|
|
|
/**
|
|
* What customers wrote to us.
|
|
*
|
|
* The half that was missing: outgoing mail has a register, portal requests have
|
|
* a page, and a customer who simply replied by mail was invisible here — the
|
|
* question sat in a mail client on somebody's desktop and the console, which
|
|
* knows who they are and what they bought, knew nothing about it.
|
|
*
|
|
* Unassigned mail is shown FIRST and never hidden. A message from an address
|
|
* nobody recognises is a new enquiry or a customer writing from a private
|
|
* account, and it is exactly the one that must not be missed.
|
|
*
|
|
* Attachments are names and sizes. The files are left on the mail server on
|
|
* purpose — storing whatever a stranger chooses to send would make this a
|
|
* malware store with a console in front of it.
|
|
*/
|
|
#[Layout('layouts.admin')]
|
|
class Inbox extends Component
|
|
{
|
|
/** Show what has been dealt with as well. */
|
|
#[Url(except: false)]
|
|
public bool $archived = false;
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->authorize('customers.manage');
|
|
}
|
|
|
|
/**
|
|
* Look now rather than at the next tick.
|
|
*
|
|
* The scheduler polls every couple of minutes; this is for the operator on
|
|
* the phone with somebody who says "ich habe es gerade geschickt".
|
|
*/
|
|
public function fetchNow(IngestInboundMail $ingest): void
|
|
{
|
|
$this->authorize('customers.manage');
|
|
|
|
$taken = $ingest();
|
|
|
|
$this->dispatch('notify', message: $taken === 0
|
|
? __('inbox.nothing_new')
|
|
: trans_choice('inbox.fetched', $taken, ['count' => $taken]));
|
|
}
|
|
|
|
/** Put one away. It stays in the mailbox; only this list forgets it. */
|
|
public function archive(string $uuid): void
|
|
{
|
|
$this->authorize('customers.manage');
|
|
|
|
InboundMail::query()->where('uuid', $uuid)->update([
|
|
'archived_at' => now(),
|
|
'read_at' => now(),
|
|
]);
|
|
}
|
|
|
|
public function unarchive(string $uuid): void
|
|
{
|
|
$this->authorize('customers.manage');
|
|
|
|
InboundMail::query()->where('uuid', $uuid)->update(['archived_at' => null]);
|
|
}
|
|
|
|
/**
|
|
* Attach a mail nobody could place to a customer.
|
|
*
|
|
* The address decides automatically and this is the manual override: a
|
|
* customer who writes from their private account is one an operator
|
|
* recognises and the matcher never will.
|
|
*/
|
|
public function assign(string $uuid, string $customerUuid): void
|
|
{
|
|
$this->authorize('customers.manage');
|
|
|
|
$mail = InboundMail::query()->where('uuid', $uuid)->first();
|
|
|
|
if ($mail === null) {
|
|
return;
|
|
}
|
|
|
|
if ($customerUuid === '') {
|
|
$mail->update(['customer_id' => null]);
|
|
|
|
return;
|
|
}
|
|
|
|
$customer = Customer::query()->where('uuid', $customerUuid)->first();
|
|
|
|
if ($customer !== null) {
|
|
$mail->update(['customer_id' => $customer->id]);
|
|
$this->dispatch('notify', message: __('inbox.assigned', ['name' => $customer->name]));
|
|
}
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
$this->authorize('customers.manage');
|
|
|
|
$mails = InboundMail::query()
|
|
->with('customer')
|
|
->when(! $this->archived, fn ($q) => $q->open())
|
|
->when($this->archived, fn ($q) => $q->whereNotNull('archived_at'))
|
|
// Unassigned first: the mail nobody has placed is the one that gets
|
|
// forgotten, and sorting it under the rest is how it stays that way.
|
|
->orderByRaw('customer_id IS NULL DESC')
|
|
->latest('received_at')
|
|
->limit(100)
|
|
->get();
|
|
|
|
return view('livewire.admin.inbox', [
|
|
'mails' => $mails,
|
|
'customers' => Customer::query()->whereNull('closed_at')->orderBy('name')->get(['uuid', 'name', 'email']),
|
|
'openCount' => InboundMail::query()->open()->count(),
|
|
// Said on the page rather than left as an empty list nobody can
|
|
// explain: a mailbox that was never configured looks exactly like a
|
|
// mailbox with no mail in it.
|
|
'configured' => app(InboundMailbox::class)->isConfigured(),
|
|
// The same line the settings page shows. This is where somebody
|
|
// notices nothing is arriving, so this is where "last reached" has
|
|
// to be readable — otherwise an empty inbox and a broken mailbox
|
|
// look identical.
|
|
'status' => app(InboundMailStatus::class)->last(),
|
|
]);
|
|
}
|
|
}
|