@php
// Create DateTime objects in Asia/Kolkata timezone
// $sent = new DateTime($mail->sent_date, new DateTimeZone('Asia/Kolkata'));
$sent = new DateTime($mail->sent_date); // Let it parse the actual timezone
$sent->setTimezone(new DateTimeZone('Asia/Kolkata'));
// $replied = new DateTime($mail->sent_date_s, new DateTimeZone('Asia/Kolkata'));
$replied = new DateTime($mail->sent_date_s); // Let it parse the actual timezone
$replied->setTimezone(new DateTimeZone('Asia/Kolkata'));
@endphp
{{-- Kept for DataTables' benefit: it is column 0 and the listing's
own order. The cards do not draw it. --}}
| {{ $loop->iteration }} |
{{-- {{ $mail->email_id }} | --}}
@php
// Every per-row link is scoped to the mailbox the mail actually
// sits in, not the mailbox in the page URL. The two differ on the
// assignment listings: those span mailboxes on purpose (mail
// assigned to an employee is delivered to whichever mailbox it was
// addressed to), so a row here can belong to a different user than
// $employee. Linking with $employee->id sent Reply/Open/Summary
// looking for the mail under an owner that does not have it, and
// they found nothing.
$mailboxId = $mail->user_id ?: ($employee->id ?? null);
// The mail's own page — the subject links to it, so reading a
// mail costs no button in the Actions column.
$mailUrl = route('mails.show', [
'emial_id' => $mail->email_id,
'is_sent' => '0',
'user_id' => $mailboxId,
]);
@endphp
@php
// Plenty of mail arrives with no name at all, or with the
// address as the name. Either way the address stands in for
// the name and the second line is dropped, because printing
// the same address twice tells the reader nothing.
$senderEmail = trim((string) $mail->sender_email);
$senderName = trim((string) $mail->sender_name);
$displayName = $senderName !== '' ? $senderName : $senderEmail;
$showAddress = strcasecmp($displayName, $senderEmail) !== 0;
@endphp
{{ mb_substr($displayName, 0, 1) }}
{{ $displayName }}
@if ($isOwnSentMail)
Sent
@endif
@if ($showAddress)
{{ $senderEmail }}
@endif
|
{{ $mail->subject === 'No Subject' ? '(No subject)' : $mail->subject }}
|
{{-- {{ $mail->attachment_status == 1 ? 'Yes' : 'No' }} | --}}
{{ $sent->format('d M Y') }}
{{ $sent->format('h:i A') }}
|
{{-- Next Action --}}
@php
$actionWord = strtolower(trim((string) ($mail->next_action ?? '')));
$actionDef = $nextActionByCode[$actionWord] ?? null;
// Anything other than "ai" was worked out from this
// mail's other labels rather than recommended for it,
// which the pill says so as not to overclaim.
$actionInferred =
strtolower(trim((string) ($mail->next_action_source ?? ''))) !== 'ai';
// Sort by urgency: the vocabulary order is the ranking,
// and mail with no action at all sorts last.
$actionRank = $actionDef ? $actionDef['rank'] : 999;
@endphp
@if ($actionDef)
Action{{ $actionDef['label'] }}
@else
Action—
@endif
|
{{-- Lead --}}
@php
$isLead = (int) ($mail->is_potential_lead ?? 0) === 1;
$leadWord = strtolower(trim((string) ($mail->lead_type ?? '')));
$leadDef = $leadByCode[$leadWord] ?? null;
// Anything other than "ai" was worked out from this mail's
// other labels rather than detected in it, which the pill
// says so as not to overclaim.
$leadInferred =
strtolower(trim((string) ($mail->lead_source ?? ''))) !== 'ai';
// Null is not zero: an inferred lead was never scored, and
// showing it as 0 would read as "the model was sure it is
// not one".
$leadScore = $mail->lead_score ?? null;
$leadScore = $leadScore === null || $leadScore === '' ? null : (int) $leadScore;
// How urgently this one needs answering: its type's band,
// dropped one step when the classifier only just cleared
// that type's floor. Derived here rather than read off the
// row, so an admin who re-bands a type sees this column
// change on the next page load.
$leadBand = \App\Models\LeadType::priorityFor($mail);
$leadBandDef = $leadBand === null ? null : $leadPriorityDefs[$leadBand];
// Sort by band first and certainty within it, so the column
// ranks by what to do next rather than by how sure the model
// was. Mail that is not a lead sorts last whatever else it
// carries.
$leadOrder = $isLead
? \App\Models\LeadType::priorityOrderValue($leadBand, $leadScore)
: 0;
$leadTitle = $isLead
? trim(
($leadInferred
? 'Inferred from this mail’s existing labels — it has not been through the lead classifier. '
: '') .
(trim((string) ($mail->lead_reason ?? '')) ?:
($leadDef['description'] ?? 'Looks like a sales opportunity.')),
)
: 'Not a sales opportunity.';
@endphp
@if ($isLead)
@if ($leadBandDef)
{{-- Urgency before kind, because it is the reason to
look at the column at all. The tooltip says how the
band was arrived at: a band that cannot be explained
is a band nobody trusts twice. --}}
@php
$ownBand = $leadDef['priority'] ?? null;
$wasDemoted = $ownBand !== null && $ownBand !== $leadBand;
$bandWhy = $wasDemoted
? ($leadDef['label'] ?? 'This type') .
' is a ' .
strtolower($leadPriorityDefs[$ownBand]['label']) .
'-priority type, but ' .
$leadScore .
' only just clears its floor of ' .
($leadDef['min_score'] ?? 0) .
', so it is shown one band lower.'
: $leadBandDef['description'];
@endphp
Priority{{ $leadBandDef['label'] }}
@endif
Lead{{ $leadDef['label'] ?? 'Lead' }}
@if ($leadScore !== null)
{{-- The classifier's own certainty. Shown beside the
type because "Pricing Request, 40" and "Pricing
Request, 95" are not the same claim. --}}
{{ $leadScore }}
@endif
@else
Lead—
@endif
|
{{-- Assigned --}}
@php
$assignedDeptId = (int) ($mail->assigned_department_id ?? 0);
$assignedDeptDef = $assignedByDept[$assignedDeptId] ?? null;
$assignedToId = (int) ($mail->assigned_user_id ?? 0);
$assignedToName = $assigneeNames[$assignedToId] ?? null;
// A person decided this one, and it will not be changed
// back by the rule or by mail:assign-existing. Worth
// saying: it is the difference between "the mapping sent
// this here" and "somebody put this here".
$assignedManually =
strtolower(trim((string) ($mail->assignment_source ?? ''))) ===
\App\Models\Department::SOURCE_MANUAL;
// Unowned sorts first: the reason to sort this column is
// to find what nobody has picked up.
$assignedOrder = $assignedDeptId > 0 ? 1 : 0;
@endphp
@if ($assignedDeptId > 0)
@php
$assignedTeamLabel =
$assignedDeptDef['label'] ?? 'Deleted team';
$assignedWhy = $assignedManually
? 'Assigned by hand' .
($assignedToName ? ' to ' . $assignedToName : '') .
' — this overrides the routing rule and is not changed back automatically.'
: 'Routed automatically to ' .
$assignedTeamLabel .
($assignedToName
? ', whose head is ' . $assignedToName . '.'
: '. No head is named for this team, so it belongs to the team rather than to a person.');
@endphp
{{ $assignedManually ? 'Owner' : 'Team' }}{{ $assignedTeamLabel }}
@if ($assignedToName)
{{-- The person, beside the team. "Support" and
"Support / Pankaj" are different situations:
one is a queue, the other is somebody's desk. --}}
{{ $assignedToName }}
@endif
@else
Team—
@endif
|
@if ($type == 'Reply pending mail')
@php
$now = new DateTime('now', new DateTimeZone('Asia/Kolkata'));
$interval = $sent->diff($now);
// Difference formatting
$diffParts = [];
if ($interval->y) {
$diffParts[] = "{$interval->y} years";
}
if ($interval->m) {
$diffParts[] = "{$interval->m} months";
}
if ($interval->d) {
$diffParts[] = "{$interval->d} days";
}
if ($interval->h) {
$diffParts[] = "{$interval->h} hours";
}
if ($interval->i) {
$diffParts[] = "{$interval->i} minutes";
}
if ($interval->s) {
$diffParts[] = "{$interval->s} seconds";
}
// Full text kept for the tooltip
$diffText = implode(' ', $diffParts) . ' ago';
// Compact label — the two most significant units only.
$units = [
['y', 'yr'],
['m', 'mo'],
['d', 'd'],
['h', 'h'],
['i', 'm'],
];
$shortParts = [];
foreach ($units as [$prop, $suffix]) {
if ($interval->$prop && count($shortParts) < 2) {
$shortParts[] = $interval->$prop . $suffix;
}
}
$shortText = $shortParts ? implode(' ', $shortParts) . ' ago' : 'Just now';
// Severity: the longer a reply is outstanding, the louder the pill.
$totalDays = (int) $sent->diff($now)->days;
if ($totalDays >= 30) {
$pill = 'emi-pill--red';
} elseif ($totalDays >= 7) {
$pill = 'emi-pill--amber';
} elseif ($totalDays >= 1) {
$pill = 'emi-pill--blue';
} else {
$pill = 'emi-pill--green';
}
@endphp
Waiting{{ $shortText }}
|
@endif
@if ($type == 'Replied Mail')
{{-- On a card this sits among the pills, so it says what the
date is rather than relying on a column header. --}}
Replied
{{ $replied->format('d M Y, h:i A') }}
|
@php
$interval = $sent->diff($replied);
$diffParts = [];
if ($interval->y) {
$diffParts[] = "{$interval->y} years";
}
if ($interval->m) {
$diffParts[] = "{$interval->m} months";
}
if ($interval->d) {
$diffParts[] = "{$interval->d} days";
}
if ($interval->h) {
$diffParts[] = "{$interval->h} hours";
}
if ($interval->i) {
$diffParts[] = "{$interval->i} minutes";
}
if ($interval->s) {
$diffParts[] = "{$interval->s} seconds";
}
$diffText = implode(' ', $diffParts);
// Color logic
if ($interval->y >= 1) {
$color = 'text-secondary';
} elseif ($interval->m >= 1) {
$color = 'text-warning';
} elseif ($interval->d >= 2) {
$color = 'text-danger';
} elseif ($interval->d == 1) {
$color = 'text-primary';
} else {
$color = 'text-success';
}
@endphp
{{-- {{ $diffText }} | --}}
@endif
@if ($type == 'Read Mail' || $type == 'Un-Read Mail' || $type == 'All Mail')
@if ($type == 'All Mail')
{{-- Sentiment --}}
@php
$getting_spam = strpos($mail->label_ids, 'SPAM') !== false ? 1 : 0;
if ($mail->is_promotion == 1 || $getting_spam == 1) {
$sentimentText = 'Mass mail';
$sentimentPill = 'emi-pill--violet';
} else {
$sentimentDef =
$sentimentByCode[strtolower(trim((string) $mail->is_spam_value))] ?? null;
// Mail classified under a label that has since been
// renamed or deactivated still shows the word it was
// given, in a neutral pill.
$sentimentText = $sentimentDef['label'] ?? ucfirst((string) $mail->is_spam_value);
$sentimentPill = $sentimentDef['pill'] ?? 'emi-pill--slate';
}
@endphp
Sentiment{{ $sentimentText !== '' ? $sentimentText : '—' }}
{{-- {{ $mail->is_promotion == 1
? ($getting_spam == 1
? 'Spam & Promotion'
: 'Promotion')
: ($getting_spam == 1
? 'Spam'
: ucfirst($mail->is_spam_value)) }} --}}
{{-- {{ $mail->is_promotion == 1 ? $getting_spam . 'Promotion ' : ucfirst($mail->is_spam_value) }} --}}
|
{{-- Category --}}
@php
$categoryWord = strtolower(trim((string) ($mail->is_category_value ?? '')));
$categoryDef = $categoryByCode[$categoryWord] ?? null;
@endphp
@if ($categoryDef)
Category{{ $categoryDef['label'] }}
@elseif ($categoryWord !== '')
{{-- Classified under a code since renamed, retired,
or belonging to another account's vocabulary.
The word it was given is still worth showing. --}}
Category{{ ucfirst(str_replace('_', ' ', $categoryWord)) }}
@else
Category—
@endif
|
{{-- Flags. Only what is true about the mail is drawn: an
unread pill, a promotion/spam label, and whether the
mail is waiting on somebody. A plain read mail shows a
dash rather than three "No" pills. Sorted so the rows
carrying the most flags come first. --}}
@php
$getting_spam = strpos($mail->label_ids, 'SPAM') !== false ? 1 : 0;
$flagScore =
($mail->message_status == 1 ? 0 : 4) +
($mail->is_promotion == 1 || $getting_spam == 1 ? 2 : 0) +
($mail->action_required == 1 ? 1 : 0);
@endphp
@if ($mail->message_status != 1)
Unread
@endif
@if ($mail->is_promotion == 1)
Promotion
@elseif ($getting_spam == 1)
Spam
@endif
@if ($mail->action_required == 1)
{{-- "Awaiting reply", not "Action": the next-action
pill on the same card is already labelled
Action, and these two say different things. --}}
Awaiting reply
@endif
{{-- Nothing to say about a read, unlabelled mail with
nothing outstanding, so the card says nothing —
a column needed a dash to fill, a card does not. --}}
|
@endif
{{--
@php
switch ($mail->is_spam) {
case 0:
$statusText = 'Negative';
break;
case 1:
$statusText = 'Positive';
break;
case 2:
$statusText = 'Reply Mail';
break;
case 3:
$statusText = 'Neutral';
break;
case 4:
$statusText = 'Promotion';
break;
case 5:
$statusText = 'Sales';
break;
case 6:
$statusText = 'AMC';
break;
default:
$statusText = 'Unknown';
break;
}
@endphp
{{ $statusText }}
| --}}
{{-- @elseif ($type == 'send-mail')
ok | --}}
@endif
{{-- {{ $mail->is_spam }}1 | --}}
{{-- Actions. Five buttons in a table column sat off the right
edge of the window; on a card they sit at the end of the
mail they belong to. Summary and Reply stay out here,
opening the mail moved onto the subject, and the rest are
in the menu. --}}
@php
$followUp = $followUpsByMessage[$mail->id] ?? null;
$followUpLate = $followUp && $followUp->isOverdue();
$followUpOpen = $followUp && $followUp->isOpen();
// Track is only meaningful once a reply has gone out on a
// mail that was waiting for one.
$trackUrl =
$mail->sent_mail_id != -1 && $mail->action_required == 1
? route('mails.show', [
'emial_id' => $mail->sent_mail_id,
'is_sent' => '1',
'user_id' => $mailboxId,
])
: null;
@endphp
{{-- A mail already carrying a follow-up says when it is due,
red once it is late, and links to the queue. --}}
@if ($followUpOpen)
{{ $followUp->dueLabel() }}
@endif
{{-- Email Summary: the whole conversation this mail belongs to,
in five sections. Opens in a modal so the list is not lost;
the modal offers the full page. --}}
Summary
{{-- Reply is the one link that does NOT take $mailboxId. Its
user_id means "reply as this account", not "read from this
mailbox": a mail assigned to an employee is answered by that
employee, from their own address, even though it was
delivered elsewhere. replyMail() resolves the mail's real
mailbox itself for the purpose of reading it back. --}}
Reply
|
@endforeach