{{-- The Follow Up History timeline (CR-6 §5.3, §5.5). ONE partial for two screens. view-inquiry.html draws it in the right-hand card and follow-up.html draws it under the option list, and the two wireframes' markup differs only in the class names of the inner text (`.fu-title`/`.fu-meta`/`.fu-note` versus `.fu-info-title`/`.fu-info-meta`/`.fu-info-note`) and in the dot's connector (`.fu-line` versus `.fu-dot-line`). Those are two names for one thing, so the view-inquiry set is used on both — it is the richer of the two (it is the only one that draws the `.fu-action-detail` block and the badge strip) and CR-6 §5.5 asks for the two screens to share a partial by name. Props (passed via @include): $inquiry the inquiry, for the pinned hold entry below $entries its follow-ups, newest first, with `creator`, `assignee` and `completedBy` already loaded — this partial runs no queries of its own, so a caller that forgot to eager-load gets an N+1 that the show page's query-count test catches rather than a silent one ## The two CR-10 states Every entry now reads as one of three things, and the third is the one CR-10 added: pending the task has not been done (App\Models\FollowUp::isPending(), the mirror of the scope the dashboard queries with). A `badge badge-warning` "Pending" and — for a viewer the policy lets act — the same ✓ the dashboard card offers, through the shared partials/task-action.blade.php. completed `completed_at` is set: a meta line saying when and by whom, plus the conclusion if one was recorded. "By whom" is omitted rather than guessed when `completed_by` is null, which it legitimately is for the booking-confirm auto-complete and for a user since deleted. neither a document entry saved WITH a date, and every Not Interested. Those were never tasks, so they render exactly as they did before CR-10 and gain no badge, no button and no meta line. **The Pending badge and its ✓ stay visible whatever the inquiry's status is** (CR-10 §3.5). Not Interested drops a task off the DASHBOARD and writes nothing; the journey is still the journey, and a timeline that hid entries on a closed inquiry would be hiding the reason it closed. ## The pinned Tentative Booking entry CR-6 §5.3 asks the timeline to carry "unit + hold-until for holds", and view-inquiry.html draws exactly such an entry (its Entry 4). But a hold is NOT a `follow_ups` row — §5.4 says the hold modal "updates the two inquiry columns only", and FollowUpType has no case for one. The two statements only reconcile one way: the hold is rendered from the inquiry's own columns, as a pinned entry at the top of the list, and it appears only while the inquiry actually holds a unit. That is an interpretation, and it is written down here rather than resolved silently. It reads correctly: a live hold *is* the most recent thing that happened to the inquiry, and the alternative — the CR's own detail chip having nothing to attach to — would have been a requirement quietly dropped. ## Escaping Every value here is a user-supplied string (a location somebody typed, a reason somebody typed, a customer's assignee's name) and every one of them goes out through `{{ }}`. There is no `{!! !!}` in this file and no value reaches a script: nothing here is a rawColumn, so Blade's escaping is the whole of the defence and it is sufficient. --}} @php /** @var \App\Models\Inquiry $inquiry */ /** @var iterable<\App\Models\FollowUp> $entries */ $dateFormat = config('nrems.date_format'); $hasHold = $inquiry->holdsAUnit(); $isEmpty = $hasHold === false && count($entries) === 0; // Wire every entry back to the inquiry it already belongs to, BEFORE // anything asks the gate about it. // // FollowUpPolicy::complete() reads `$followUp->inquiry` for the two // inquiry-column branches of its row scope, and an entry loaded through // `$inquiry->followUps` does not carry the INVERSE relation. Without this // line an ordinary in-scope viewer — an inquiry's own creator, who holds no // `follow_ups.manage` and so cannot short-circuit the policy at its first // branch — would fire one `select * from inquiries` per pending entry, and // the "runs no queries" promise above would be false for exactly the user // this feature was written for. An Admin never noticed, because the // permission check answers before `inquiry` is ever touched. // // setRelation() is pure memory: it hands over the model this partial was // already given rather than fetching one. It is done here rather than in // the two controllers precisely because it is not an eager load — there is // nothing to load — and because a third caller cannot then forget it. foreach ($entries as $entry) { $entry->setRelation('inquiry', $inquiry); } @endphp
@if ($hasHold) {{-- The pinned hold. See the header block for why it is not a row of `follow_ups` and why it is here anyway. --}}
{{ __('inquiries.history.tentative_title') }}
{{ __('inquiries.history.hold_detail', [ 'unit' => $inquiry->tentativeUnit?->unit_number ?? '—', 'date' => $inquiry->hold_until?->format($dateFormat) ?? '—', ]) }}
@endif @foreach ($entries as $entry)
{{ $entry->type->label() }} @if ($entry->creator !== null) {{ __('inquiries.history.by', ['name' => $entry->creator->name]) }} @endif
@php // "20 Apr 2026 · 11:00 AM", with either half omitted when it // was not recorded — six of the ten types have no time at // all, and every type's date is optional on some screen. $meta = array_filter([ $entry->date?->format($dateFormat), $entry->timeLabel(), ]); @endphp @if ($meta !== [])
{{ implode(' · ', $meta) }}
@endif @if (filled($entry->reason))
{{ $entry->reason }}
@endif {{-- The conclusion somebody recorded when they closed this task — or the words "Booking Confirm", written by the auto-complete of CR-10 §3.4. Escaped like every other user-typed value on this page. --}} @if (filled($entry->conclusion))
{{ $entry->conclusion }}
@endif @if (filled($entry->location))
{{ $entry->location }}
@endif {{-- "Completed on 02 Sep 2026 · 4:15 PM by Neha Desai" — the client's "show date and time on which date this is completed and who this task mark completed". The "by" half is dropped when `completed_by` is null; see the header. --}} @if ($entry->completed_at !== null)
@if ($entry->completedBy !== null) {{ __('inquiries.history.completed_by', [ 'date' => $entry->completed_at->format($dateFormat), 'time' => $entry->completed_at->format('g:i A'), 'name' => $entry->completedBy->name, ]) }} @else {{ __('inquiries.history.completed', [ 'date' => $entry->completed_at->format($dateFormat), 'time' => $entry->completed_at->format('g:i A'), ]) }} @endif
@endif @if ($entry->assignee !== null || $entry->isPending())
@if ($entry->assignee !== null) {{ __('inquiries.history.assigned', ['name' => $entry->assignee->name]) }} @endif @if ($entry->isPending()) {{ __('inquiries.history.pending') }} @include('inquiries.partials.task-action', ['task' => $entry]) @endif
@endif
@endforeach @if ($isEmpty)

{{ __('inquiries.history.empty') }}

@endif