feat: measurement workflow improvements and recipe update-in-place
- Auto-advance to next task after completing all subtask measurements - 1s pause between measurements to show pass/fail/warning result - Colored marker strip (green/red/amber) based on measurement status - Replace duplicate measurements instead of appending (fixes progress bar) - Add Task column and Date/Time column to measurement summary table - Enrich summary with task_info for each measurement - Update-in-place for recipe versions without measurements (no copy-on-write) - Dark theme improvements and navbar cleanup - Server config: ignore extra env vars Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -164,7 +164,8 @@
|
||||
<table class="tmf-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">#</th>
|
||||
<th class="text-center">{{ _('Data/Ora') }}</th>
|
||||
<th>{{ _('Task') }}</th>
|
||||
<th>{{ _('Descrizione') }}</th>
|
||||
<th class="text-right">{{ _('Nominale') }}</th>
|
||||
<th class="text-right">{{ _('Valore') }}</th>
|
||||
@@ -174,10 +175,13 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for m in measurements|sort(attribute='subtask.marker_number') %}
|
||||
{% for m in measurements|sort(attribute='task_info.order_index,subtask.marker_number') %}
|
||||
<tr>
|
||||
<td class="text-center font-mono font-medium" style="color: var(--text-primary);">
|
||||
{{ m.subtask.marker_number }}
|
||||
<td class="text-center font-mono text-xs" style="color: var(--text-secondary);">
|
||||
{{ m.measured_at[:16]|replace('T', ' ') if m.measured_at else '-' }}
|
||||
</td>
|
||||
<td style="color: var(--text-secondary);" class="text-sm">
|
||||
{{ m.task_info.title if m.task_info else '-' }}
|
||||
</td>
|
||||
<td style="color: var(--text-primary);">
|
||||
{{ m.subtask.description }}
|
||||
@@ -203,14 +207,14 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if m.method == 'manual' %}
|
||||
{% if m.input_method == 'manual' %}
|
||||
<div class="inline-flex items-center gap-1" style="color: var(--text-secondary);">
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/>
|
||||
</svg>
|
||||
<span class="text-sm">{{ _('Manuale') }}</span>
|
||||
</div>
|
||||
{% elif m.method == 'caliper' %}
|
||||
{% elif m.input_method == 'usb_caliper' %}
|
||||
<div class="inline-flex items-center gap-1" style="color: var(--text-secondary);">
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
{% block content %}
|
||||
<script>
|
||||
window.__taskAnnotations = {{ task.annotations_json|default('null')|tojson }};
|
||||
window.__allTaskIds = {{ all_task_ids|tojson }};
|
||||
</script>
|
||||
<div class="min-h-[calc(100vh-3.5rem)] flex flex-col"
|
||||
x-data="taskExecute()"
|
||||
@@ -210,23 +211,43 @@
|
||||
class="shrink-0 flex items-center gap-1.5 px-3 py-1.5 rounded-lg border text-xs font-medium transition-all duration-200"
|
||||
:class="idx === currentIndex
|
||||
? 'bg-primary text-white border-primary shadow-md marker-active-pulse'
|
||||
: isMeasured(st.id)
|
||||
: getMeasurementStatus(st.id) === 'pass'
|
||||
? 'bg-measure-pass/10 text-measure-pass border-measure-pass/30 hover:bg-measure-pass/20'
|
||||
: 'bg-[var(--bg-card)] text-[var(--text-secondary)] border-[var(--border-color)] hover:border-primary/50 hover:text-primary'
|
||||
: getMeasurementStatus(st.id) === 'fail'
|
||||
? 'bg-red-50 dark:bg-red-900/20 text-measure-fail border-measure-fail/30 hover:bg-red-100 dark:hover:bg-red-900/30'
|
||||
: getMeasurementStatus(st.id) === 'warning'
|
||||
? 'bg-amber-50 dark:bg-amber-900/20 text-measure-warning border-measure-warning/30 hover:bg-amber-100 dark:hover:bg-amber-900/30'
|
||||
: 'bg-[var(--bg-card)] text-[var(--text-secondary)] border-[var(--border-color)] hover:border-primary/50 hover:text-primary'
|
||||
">
|
||||
<span class="inline-flex items-center justify-center w-5 h-5 rounded-full text-[10px] font-bold"
|
||||
:class="idx === currentIndex
|
||||
? 'bg-white/20'
|
||||
: isMeasured(st.id)
|
||||
: getMeasurementStatus(st.id) === 'pass'
|
||||
? 'bg-measure-pass/20'
|
||||
: 'bg-[var(--bg-secondary)]'
|
||||
: getMeasurementStatus(st.id) === 'fail'
|
||||
? 'bg-measure-fail/20'
|
||||
: getMeasurementStatus(st.id) === 'warning'
|
||||
? 'bg-measure-warning/20'
|
||||
: 'bg-[var(--bg-secondary)]'
|
||||
"
|
||||
x-text="st.marker_number"></span>
|
||||
<span x-text="st.description" class="truncate max-w-[100px]"></span>
|
||||
{# Check icon if measured #}
|
||||
<svg x-show="isMeasured(st.id)" class="w-3.5 h-3.5 text-measure-pass shrink-0" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
{# Status icon: pass=check, fail=X, warning=! #}
|
||||
<template x-if="getMeasurementStatus(st.id) === 'pass'">
|
||||
<svg class="w-3.5 h-3.5 text-measure-pass shrink-0" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="getMeasurementStatus(st.id) === 'fail'">
|
||||
<svg class="w-3.5 h-3.5 text-measure-fail shrink-0" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="getMeasurementStatus(st.id) === 'warning'">
|
||||
<svg class="w-3.5 h-3.5 text-measure-warning shrink-0" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/>
|
||||
</svg>
|
||||
</template>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
@@ -628,6 +649,11 @@ function taskExecute() {
|
||||
return m ? m.value : null;
|
||||
},
|
||||
|
||||
getMeasurementStatus(subtaskId) {
|
||||
const m = this.measurements.find(m => m.subtask_id === subtaskId);
|
||||
return m ? m.pass_fail : null;
|
||||
},
|
||||
|
||||
// ---- Handle numpad confirm ----
|
||||
async handleMeasurement(value, inputMethod) {
|
||||
if (!this.currentSubtask || this.saving) return;
|
||||
@@ -652,10 +678,8 @@ function taskExecute() {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
subtask_id: this.currentSubtask.id,
|
||||
task_id: this.task.id,
|
||||
version_id: this.task.version_id,
|
||||
value: value,
|
||||
pass_fail: pf,
|
||||
deviation: dev,
|
||||
lot_number: this.lotNumber,
|
||||
serial_number: this.serialNumber,
|
||||
input_method: inputMethod || 'manual',
|
||||
@@ -670,20 +694,32 @@ function taskExecute() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Record measurement locally
|
||||
this.measurements.push({
|
||||
subtask_id: this.currentSubtask.id,
|
||||
value: value,
|
||||
pass_fail: pf,
|
||||
deviation: dev,
|
||||
});
|
||||
// Record measurement locally (replace if already measured)
|
||||
const existingIdx = this.measurements.findIndex(m => m.subtask_id === this.currentSubtask.id);
|
||||
const mEntry = { subtask_id: this.currentSubtask.id, value, pass_fail: pf, deviation: dev };
|
||||
if (existingIdx !== -1) {
|
||||
this.measurements.splice(existingIdx, 1, mEntry);
|
||||
} else {
|
||||
this.measurements.push(mEntry);
|
||||
}
|
||||
|
||||
this.saving = false;
|
||||
|
||||
// Pause 1s to let user see the result (pass/fail/warning)
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
|
||||
// Check if all done
|
||||
if (this.completedCount >= this.totalSubtasks) {
|
||||
// Show completion overlay
|
||||
this.showCompletionOverlay = true;
|
||||
// Auto-advance to next task, or show overlay on last task
|
||||
const taskIds = window.__allTaskIds || [];
|
||||
const currentIdx = taskIds.indexOf(this.task.id);
|
||||
if (currentIdx >= 0 && currentIdx < taskIds.length - 1) {
|
||||
// Navigate to next task
|
||||
window.location.href = '{{ url_for("measure.task_execute", task_id=0) }}'.replace('/0', '/' + taskIds[currentIdx + 1]);
|
||||
} else {
|
||||
// Last task (or unknown): show completion overlay
|
||||
this.showCompletionOverlay = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user