diff --git a/app/routes/inspections.py b/app/routes/inspections.py index cacd43f..c5d313b 100644 --- a/app/routes/inspections.py +++ b/app/routes/inspections.py @@ -344,13 +344,6 @@ def execute(inspection_id): flash('Inspection submitted successfully!', 'success') return redirect(url_for('inspections.view', inspection_id=inspection_id)) - elif action == 'flag': - # Save current form state as a draft so no work is lost, - # then send the inspector to the issue creation page. - _save_draft(inspection, responses) - db.session.commit() - return redirect(url_for('inspections.flag_issue', inspection_id=inspection_id)) - else: # save draft _save_draft(inspection, responses) db.session.commit() @@ -381,6 +374,50 @@ def _save_draft(inspection, responses): db.session.commit() +# ── AJAX: save draft (used by Flag Issue button before navigating away) ─────── + +@bp.route('//save-draft', methods=['POST']) +@login_required +def save_draft_ajax(inspection_id): + """ + Accepts a JSON body of { field_id: value } and persists it as a draft. + Returns JSON { ok: true } on success so the caller can navigate to flag_issue. + """ + inspection = Inspection.query.get_or_404(inspection_id) + + if current_user.role == 'inspector' and inspection.inspector_id != current_user.id: + return jsonify({'ok': False, 'error': 'Access denied'}), 403 + + if inspection.status == 'completed': + return jsonify({'ok': False, 'error': 'Inspection already completed'}), 400 + + data = request.get_json(silent=True) or {} + responses = data.get('responses', {}) + + # Merge with any previously saved responses so photo paths are preserved + existing_responses = {} + if inspection.notes: + try: + parsed = json.loads(inspection.notes) + if isinstance(parsed, dict) and '_form_data' in parsed: + existing_responses = parsed['_form_data'] + except (json.JSONDecodeError, TypeError): + pass + + # Only overwrite keys that are present in the submitted data; + # preserve photo paths for image fields not included in the JSON payload. + merged = {**existing_responses, **responses} + + _save_responses(inspection, merged) + db.session.commit() + + current_app.logger.info( + 'INSPECTION DRAFT SAVED (flag) | inspection_id=%s | by=%s', + inspection_id, current_user.username + ) + return jsonify({'ok': True}) + + # ── View ────────────────────────────────────────────────────────────────────── @bp.route('/') diff --git a/app/templates/inspections/execute.html b/app/templates/inspections/execute.html index a026741..eef6a9b 100644 --- a/app/templates/inspections/execute.html +++ b/app/templates/inspections/execute.html @@ -214,9 +214,11 @@
{{ inspection.template.frequency|title }} -
@@ -674,5 +676,64 @@ document.getElementById('inspectionForm').addEventListener('submit', async funct alert('An error occurred. Please try again.'); } }); + +// ── Flag Issue: save draft via AJAX then navigate ──────────────────────────── +(function () { + const btn = document.getElementById('flagIssueBtn'); + if (!btn) return; + + btn.addEventListener('click', async function () { + const saveDraftUrl = btn.dataset.flagUrl; + const redirectUrl = btn.dataset.flagRedirect; + + // Collect all current form field values (non-file inputs only) + const form = document.getElementById('inspectionForm'); + const responses = {}; + form.querySelectorAll('input, textarea, select').forEach(el => { + if (!el.name || !el.name.startsWith('field_')) return; + if (el.type === 'file') return; // files can't be JSON-serialised + if (el.type === 'checkbox') { + responses[el.name.replace('field_', '')] = el.checked ? 'true' : 'false'; + } else if (el.type === 'radio') { + if (el.checked) responses[el.name.replace('field_', '')] = el.value; + } else { + responses[el.name.replace('field_', '')] = el.value; + } + }); + + // Flush signature canvases to their hidden inputs before reading values + collectSignatures(); + form.querySelectorAll('input[type="hidden"]').forEach(el => { + if (!el.name || !el.name.startsWith('field_')) return; + responses[el.name.replace('field_', '')] = el.value; + }); + + btn.disabled = true; + btn.innerHTML = ' Saving…'; + + try { + const res = await fetch(saveDraftUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': document.querySelector('input[name="csrf_token"]').value, + }, + body: JSON.stringify({ responses }), + }); + const json = await res.json(); + if (json.ok) { + window.location.href = redirectUrl; + } else { + alert('Could not save draft: ' + (json.error || 'Unknown error')); + btn.disabled = false; + btn.innerHTML = ' Flag Issue'; + } + } catch (err) { + console.error('Flag Issue draft save error:', err); + // Fall back to navigating directly without saving + window.location.href = redirectUrl; + } + }); +}()); {% endblock %} \ No newline at end of file