{{--
Edit Inquiry — the twin of inquiries/create.blade.php.
## There is no edit-inquiry wireframe
v12 draws one inquiry form, add-inquiry.html, and inquiry.html's row-edit
icon points straight at it. So this screen IS that wireframe, field for
field and section for section; the only things the pack does not decide are
the title, the trail and the submit wording, and those follow the projects
precedent (a per-record crumb, and the client's own save wording reused
because there is no second spelling of it to copy).
## The twin rule (recipe §9)
This file and inquiries/create.blade.php are twins. Every field is written
out in both, and neither includes a shared form partial. **A field added,
removed or renamed here must be mirrored in create.blade.php in the same
commit.** TwinViewParityTest renders both screens and fails when their
`name` sets or their required-attribute sets differ. It does not compare
labels, order or validation rules, so those are on whoever edits the pair.
### What this screen has that its twin does not
* it PUTs to `inquiries.update` and therefore carries a `_method` field,
where the twin POSTs to `inquiries.store`;
* its rule source is UpdateInquiryRequest, which differs from
StoreInquiryRequest in `authorize()` and in letting `hold_until` be a
date in the past — an inquiry whose two-week hold lapsed in April must
still be editable in August, and under the store rule its own edit form
could not be saved at all. That class's docblock has the full reasoning
and why the relaxation is narrower than it looks;
* its breadcrumb trail ends with the inquiry's code, where the twin's ends
"Add Inquiry"; its title is "Edit Inquiry" where the twin's is v12's
"Add New Inquiry";
* every control is prefilled from the inquiry — including the two
conditional source blocks, which are also server-rendered *open* when
the stored source calls for them, because no `change` event fires on a
freshly drawn page and the module's JS therefore never runs there;
* both unit selects are server-rendered with the project's units, with
**this inquiry's own hold ignored** (`data-ignore-inquiry` below, and
UnitOptions' `$ignoreInquiryId`) — otherwise the user would be told
their own reservation blocks them;
* it hands the cascade endpoint the same id, so a project change reloads
the options under the same rule.
## The Inquiry Conclusion section is append-only, and loads EMPTY
This is the one place the twin's markup looks like an omission and is not
(CR-6 §5.2, §9 assumption 2). The section renders unchecked and blank on
this screen exactly as it does on the create screen: anything ticked here
APPENDS new `follow_ups` rows, and the entries already recorded are never
re-editable from this form. They are read on the Follow Up page and on the
show page.
The reason is the BA's, and it is worth keeping: a form that prefilled the
journey would let a re-save silently rewrite what was recorded about the
customer last month, and there is no screen anywhere in the module that
asks for that. It also keeps the twins' field sets identical by
construction, which is why the parity test passes without an exception for
this section.
## Deliberate differences from the wireframe
The create twin's list applies here unchanged — WALK-IN on the source
select, the starred Site Visit / Follow Up Call sub-fields, the real Not
Interested panel in place of the wireframe's preview block, and the inert
Proceed to Booking button. See that file.
--}}
@extends('layouts.app')
@section('title', __('inquiries.edit_title'))
@section('breadcrumbs')
@endsection
@section('content')
@php
$inquiryTypes = \App\Enums\InquiryType::options();
$sources = \App\Enums\InquirySource::options();
$occupations = \App\Enums\Occupation::options();
$propertyTypes = \App\Enums\PropertyType::options();
$transTypes = \App\Enums\TransType::options();
// Which tab the Conclusion section opens on. Old input wins so a
// rejected submit comes back on the tab the user was looking at.
$conclusionTab = old('conclusion_tab', 'interested');
/** @var array $conclusionOld */
$conclusionOld = is_array(old('conclusion')) ? old('conclusion') : [];
// A row is ticked if old input says so. The closed list is
// FollowUpRecorder::isSelected()'s, so the screen and the server agree
// about what a ticked box looks like.
$isTicked = static fn (\App\Enums\FollowUpType $type): bool => in_array(
data_get($conclusionOld, $type->value.'.selected'),
[true, 1, '1', 'on', 'true'],
true,
);
// A sub-panel holding a server-rendered error is open on load whatever
// its checkbox says — otherwise a rejected submit hides the message it
// is complaining about, and the module's JS is not what should be
// deciding that (it never runs on a freshly drawn page).
$hasError = static fn (\App\Enums\FollowUpType $type): bool => $errors->hasAny(array_map(
static fn (string $field): string => 'conclusion.'.$type->value.'.'.$field,
$type->fields(),
));
// add-inquiry.html gives a glyph to exactly two of the nine rows.
// Faithful to the wireframe rather than to the enum, which knows an
// icon for all ten because the follow-up page draws all ten with one.
$iconRows = [\App\Enums\FollowUpType::PersonalMeeting, \App\Enums\FollowUpType::FollowUpCall];
@endphp
{{-- The module's client config, in ONE JSON data-attribute plus three
scalars — the shape nrems-unit-import.js established. The script holds
no copy of any wording, and the only url it builds is this template
with the project id substituted. --}}
{{-- ===== 1. Inquiry Date & Source ===== --}}
{{ __('inquiries.sections.date_source') }}
{{-- Shown only for REFERENCE. The hidden attribute is server-set
from the current value, so the block is in the right state on
a freshly drawn page — no `change` event fires on one, so the
module never runs there (the lesson CR-5 §15.4 recorded about
the unit-import replace warning).
**CR-6.3 — the two fields in each block are client-side REQUIRED
while their block is visible.** `data-conditional-required` marks
them and nrems-inquiry-form.js arms and disarms the rule on the
bound validator as the source changes, and once on load.
The mapping is not written down here twice: which two fields a
source requires is exactly which two fields its block CONTAINS,
and that is StoreInquiryRequest::validateSourceDetails()'s list
— REFERENCE takes reference_by + reference_contact, BROKER takes
broker_id + broker_contact_number. The Javascript reads the
blocks and holds no copy of it.
The message is rendered here from the SAME two lang pieces the
server composes its sentence from (`validation.required` with
the `inquiries.fields.*` attribute name), so the client and the
server say one sentence about one field.
The marker is deliberately NOT `data-validate-rules`, which is
nrems-client-rules.js's attribute: that module arms once on load
and never removes, which is exactly wrong for a field that is
only sometimes on screen. A separate name means the two layers
can never both claim these four fields. --}}
{{-- Written out rather than because each
option carries a `data-phone` the component has no way
to emit, and the wireframe's whole broker behaviour is
that selecting one prefills the contact field from it.
's own docblock invites this use; the
markup below is the component's, class for class. The
phone is escaped by Blade like any attribute value.
The option text is Broker::optionLabel(), not the bare
name: since CR-12 the list is active brokers PLUS the
one this inquiry already points at, and that one is
suffixed with the reason it is still here — see
InquiryController::brokerOptions(). --}}
@error('broker_id')
{{-- ===== 2. Prospective Customer Information ===== --}}
{{ __('inquiries.sections.customer') }}
{{-- ===== 3. Assigned To ===== --}}
{{ __('inquiries.sections.assignment') }}
{{-- One field in a two-column row, exactly as the wireframe draws
it: the control keeps its half-width and the row keeps its
grid. --}}
{{-- ===== 4. Inquiry Details ===== --}}
{{ __('inquiries.sections.details') }}
{{-- Interested Project is `required` on the client's decision of
2026-09-11, where v12 draws it unstarred. The asterisk is
mirrored on the create twin and the rule is one rule for
both screens — StoreInquiryRequest::rules(), which
UpdateInquiryRequest inherits rather than overrides. A
legacy inquiry stored with no project therefore has to be
given one before this form will save. --}}
{{-- Both unit selects are refilled from the cascade endpoint
when the project changes (CR-6 §3). They are server-rendered
with the project's units so the screen is correct before any
Javascript runs — empty here, because a new inquiry has no
project yet. --}}
{{-- ===== 5. Tentative Booking ===== --}}
{{ __('inquiries.sections.tentative') }}
{{-- ===== 6. Inquiry Conclusion ===== --}}
{{ __('inquiries.sections.conclusion') }}
{{-- Which tab was active is a posted value, because the server
reads a different half of the payload for each (see
FollowUpRecorder::entriesFromInquiryForm()). --}}
@break
@default
{{-- The six date-only types (1, 2, 5, 6, 7, 8). Their
date is optional — see \App\Enums\FollowUpType::requiredFields(). --}}
@endswitch
@endforeach
{{-- The Not Interested tab's real content. See "Deliberate
differences" above on why this is a panel and not the
wireframe's explanatory preview. --}}
{{-- v12's card footer: Cancel as the outline button, the inert Proceed
to Booking in the wireframe's success fill, then the primary
action, all right-aligned inside the card's bottom rule.
`.card-footer` is already the flex row, so no wrapper of its own. --}}
{{-- The conditional, unit-cascade and conclusion rules are excluded from
the client rule set: every one of them is conditional on another
control's value, and proengsoft has no vocabulary for that — it would
mark an untouched Broker select invalid on a walk-in inquiry rather
than render nothing. The server validates all of them. See
UpdateInquiryRequest::clientRuleExclusions(). --}}
@endsection
@push('styles')
@include('inquiries.partials.styles')
@endpush
@push('scripts')
{{-- The inquiry form client (CR-6 §5.2). ES5, no build step, no dependency,
and it does nothing at all on a page without
#nrems_inquiry_form_module. Every string it can put on screen comes in
from Blade through one JSON data-attribute, and every option it builds
lands through createElement/textContent — never innerHTML. --}}
@endpush