This commit is contained in:
gsinghpal
2026-05-10 10:25:12 -04:00
parent 6c6a59ceef
commit 6b7b44264a
59 changed files with 2461 additions and 324 deletions

View File

@@ -20,16 +20,39 @@
<span class="ms-2">Loading prompts...</span>
</div>
<!-- Empty state -->
<div t-elif="!state.rows.length" class="o_fp_ri_empty">
<!-- Instructions block — recipe-author HTML + image gallery shown
above the prompt cards so the operator reads context BEFORE
entering values. Hidden when neither is authored. -->
<div t-if="!state.loading and (state.instructionsHtml or state.instructionImages.length)"
class="o_fp_ri_instructions">
<div t-if="state.instructionsHtml"
class="o_fp_ri_instructions_text"
t-out="state.instructionsHtml"/>
<div t-if="state.instructionImages.length"
class="o_fp_ri_instructions_gallery">
<t t-foreach="state.instructionImages" t-as="img" t-key="img.id">
<a t-att-href="img.url"
target="_blank"
class="o_fp_ri_instructions_thumb"
t-att-title="img.name">
<img t-att-src="img.url" t-att-alt="img.name"/>
</a>
</t>
</div>
</div>
<!-- Empty state. Independent t-if (not t-elif) so the
instructions block above doesn't break the chain — the
cards / empty branch must only depend on loading + rows. -->
<div t-if="!state.loading and !state.rows.length" class="o_fp_ri_empty">
<p>No measurement prompts on this step.</p>
<button class="btn btn-secondary" t-on-click="addAdHocRow">
<i class="fa fa-plus me-1"/> Add a measurement
</button>
</div>
<!-- Cards -->
<div t-else="" class="o_fp_ri_cards">
<!-- Cards. Same fix — independent t-if. -->
<div t-if="!state.loading and state.rows.length" class="o_fp_ri_cards">
<t t-foreach="state.rows" t-as="row" t-key="row_index">
<div class="o_fp_ri_card"
t-att-class="{ 'o_fp_ri_card_required': row.required }">
@@ -53,7 +76,7 @@
<div class="o_fp_ri_meta">
<span class="o_fp_ri_pill o_fp_ri_pill_type"
t-esc="row.input_type"/>
t-esc="inputTypeLabel(row)"/>
<span t-if="row.target_unit"
class="o_fp_ri_pill o_fp_ri_pill_unit"
t-esc="row.target_unit"/>
@@ -67,14 +90,19 @@
</button>
</div>
<!-- Target range hint (if recipe author set one) -->
<div t-if="(row.target_min or row.target_max) and isNumeric(row)"
<!-- Target range hint (any prompt with a target_min /
target_max — numeric, pass_fail, etc.). Renders
as a small "Target: 0.005 0.007 in" pill so the
operator can see the spec before they enter
readings. -->
<div t-if="row.target_min or row.target_max"
class="o_fp_ri_target">
Target:
<strong>
<t t-if="row.target_min" t-esc="row.target_min"/><t t-if="row.target_min and row.target_max"></t><t t-if="row.target_max" t-esc="row.target_max"/>
<i class="fa fa-bullseye me-1"/>
<span class="o_fp_ri_target_label">Target</span>
<strong class="o_fp_ri_target_value">
<t t-if="row.target_min" t-esc="row.target_min"/><t t-if="row.target_min and row.target_max"> </t><t t-if="row.target_max" t-esc="row.target_max"/>
</strong>
<span t-if="row.target_unit" class="ms-1 text-muted" t-esc="row.target_unit"/>
<span t-if="row.target_unit" class="o_fp_ri_target_unit" t-esc="row.target_unit"/>
</div>
<!-- Hint text from recipe author -->
@@ -83,8 +111,9 @@
<!-- Card body — live input widget per type -->
<div class="o_fp_ri_card_body">
<!-- Numeric (number, temperature, thickness, time_seconds, ph) -->
<div t-if="isNumeric(row)" class="o_fp_ri_numeric">
<!-- Numeric — single value (no range defined) -->
<div t-if="isNumeric(row) and !hasRangeEntry(row)"
class="o_fp_ri_numeric">
<input type="number"
class="o_fp_ri_input o_fp_ri_input_numeric"
step="any"
@@ -97,7 +126,109 @@
t-esc="hint.text"/>
</div>
<!-- Boolean / pass-fail toggle -->
<!-- Numeric — dual entry (recipe author defined a
min and max target → operator records both
observed extremes from their measurements).
Constrained to numeric so it doesn't duplicate
the pass_fail+range branch above. -->
<div t-if="isNumeric(row) and hasRangeEntry(row)" class="o_fp_ri_dual">
<label class="o_fp_ri_dual_field">
<span class="o_fp_ri_dual_label">Min Reading</span>
<input type="number"
class="o_fp_ri_input o_fp_ri_input_numeric"
step="any"
t-model.number="row.value_min"
t-att-placeholder="row.target_min or '0.00'"/>
</label>
<label class="o_fp_ri_dual_field">
<span class="o_fp_ri_dual_label">Max Reading</span>
<input type="number"
class="o_fp_ri_input o_fp_ri_input_numeric"
step="any"
t-model.number="row.value_max"
t-att-placeholder="row.target_max or '0.00'"/>
</label>
<t t-set="dhint" t-value="dualRangeHint(row)"/>
<span t-if="dhint"
class="o_fp_ri_range_hint o_fp_ri_dual_hint"
t-att-class="'o_fp_ri_range_' + dhint.kind"
t-esc="dhint.text"/>
</div>
<!-- Pass / Fail with range — operator records min
+ max measurements first, system suggests the
verdict, then operator confirms with PASS/FAIL.
This branch fires when the recipe author
defined target_min / target_max on a pass_fail
prompt (e.g. Bore inspection: 0.005-0.007 in). -->
<t t-if="isPassFail(row) and hasRangeEntry(row)">
<div class="o_fp_ri_dual">
<label class="o_fp_ri_dual_field">
<span class="o_fp_ri_dual_label">Min Reading</span>
<input type="number"
class="o_fp_ri_input o_fp_ri_input_numeric"
step="any"
t-model.number="row.value_min"
t-att-placeholder="row.target_min or '0.00'"/>
</label>
<label class="o_fp_ri_dual_field">
<span class="o_fp_ri_dual_label">Max Reading</span>
<input type="number"
class="o_fp_ri_input o_fp_ri_input_numeric"
step="any"
t-model.number="row.value_max"
t-att-placeholder="row.target_max or '0.00'"/>
</label>
<t t-set="dhint" t-value="dualRangeHint(row)"/>
<span t-if="dhint"
class="o_fp_ri_range_hint o_fp_ri_dual_hint"
t-att-class="'o_fp_ri_range_' + dhint.kind"
t-esc="dhint.text"/>
</div>
<t t-set="sugg" t-value="suggestedPassFail(row)"/>
<div t-if="sugg" class="o_fp_ri_pf_suggest"
t-att-class="'o_fp_ri_pf_suggest_' + sugg">
<i t-att-class="sugg === 'pass' ? 'fa fa-check-circle me-1' : 'fa fa-exclamation-triangle me-1'"/>
Readings suggest <strong t-esc="sugg.toUpperCase()"/> — confirm below.
</div>
<div class="o_fp_ri_passfail">
<button type="button"
class="o_fp_ri_pf_btn o_fp_ri_pf_pass"
t-att-class="{ 'o_fp_ri_pf_active': isPassActive(row) }"
t-on-click="() => this.onPass(row)">
<i class="fa fa-check me-2"/> PASS
</button>
<button type="button"
class="o_fp_ri_pf_btn o_fp_ri_pf_fail"
t-att-class="{ 'o_fp_ri_pf_active': isFailActive(row) }"
t-on-click="() => this.onFail(row)">
<i class="fa fa-times me-2"/> FAIL
</button>
</div>
</t>
<!-- Pass / Fail without range — distinct two-button
widget so the operator sees the OUTCOME, not a
generic toggle. Active button fills with green
(PASS) or red (FAIL); the inactive one stays
outlined. -->
<div t-if="isPassFail(row) and !hasRangeEntry(row)"
class="o_fp_ri_passfail">
<button type="button"
class="o_fp_ri_pf_btn o_fp_ri_pf_pass"
t-att-class="{ 'o_fp_ri_pf_active': isPassActive(row) }"
t-on-click="() => this.onPass(row)">
<i class="fa fa-check me-2"/> PASS
</button>
<button type="button"
class="o_fp_ri_pf_btn o_fp_ri_pf_fail"
t-att-class="{ 'o_fp_ri_pf_active': isFailActive(row) }"
t-on-click="() => this.onFail(row)">
<i class="fa fa-times me-2"/> FAIL
</button>
</div>
<!-- Generic boolean toggle (Yes / No) -->
<label t-if="isBoolean(row)" class="o_fp_ri_toggle">
<input type="checkbox" t-model="row.value_boolean"/>
<span class="o_fp_ri_toggle_track">
@@ -114,14 +245,36 @@
t-model="row.value_date"/>
<!-- Selection (uses recipe author's selection_options) -->
<select t-if="isSelection(row)"
class="o_fp_ri_input o_fp_ri_input_select"
t-model="row.value_text">
<option value="">— choose —</option>
<t t-foreach="selectionOptions(row)" t-as="opt" t-key="opt">
<option t-att-value="opt" t-esc="opt"/>
</t>
</select>
<t t-if="isSelection(row)">
<t t-set="opts" t-value="selectionOptions(row)"/>
<select t-if="opts.length"
class="o_fp_ri_input o_fp_ri_input_select"
t-model="row.value_text">
<option value="">— choose —</option>
<t t-foreach="opts" t-as="opt" t-key="opt">
<option t-att-value="opt" t-esc="opt"/>
</t>
</select>
<div t-else="" class="o_fp_ri_select_empty">
<i class="fa fa-info-circle me-1"/>
No options configured for this prompt — type a value below.
<input type="text"
class="o_fp_ri_input o_fp_ri_input_text mt-2"
t-model="row.value_text"
placeholder="Enter value…"/>
</div>
</t>
<!-- Signature — distinct affordance so the operator
knows initials are required (not free text). -->
<div t-if="isSignature(row)" class="o_fp_ri_signature">
<i class="fa fa-pencil-square-o o_fp_ri_signature_icon"/>
<input type="text"
class="o_fp_ri_input o_fp_ri_input_signature"
t-model="row.value_text"
placeholder="Type your initials (e.g. JD)"
maxlength="10"/>
</div>
<!-- Photo upload -->
<div t-if="isPhoto(row)" class="o_fp_ri_photo">