{{-- 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. --}}
source?->value) !== \App\Enums\InquirySource::Reference->value) hidden @endif>
{{ __('inquiries.sections.reference_details') }}
{{-- Shown only for BROKER. --}}
source?->value) !== \App\Enums\InquirySource::Broker->value) hidden @endif>
{{ __('inquiries.sections.broker_details') }}
{{-- 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')
{{ $message }}
@enderror
{{-- ===== 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()). --}} {{-- 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