diff --git a/src/frontend/flask_app/blueprints/measure.py b/src/frontend/flask_app/blueprints/measure.py index 1eef4d5..997ae79 100644 --- a/src/frontend/flask_app/blueprints/measure.py +++ b/src/frontend/flask_app/blueprints/measure.py @@ -135,11 +135,15 @@ def task_execute(task_id: int): # Load all task IDs for this recipe (ordered) for auto-advance recipe_id = task_resp.get("recipe_id") all_task_ids = [] + measurement_interval_minutes = None if recipe_id: tasks_resp = api_client.get(f"/api/recipes/{recipe_id}/tasks") if isinstance(tasks_resp, list): sorted_tasks = sorted(tasks_resp, key=lambda t: t.get("order_index", 0)) all_task_ids = [t["id"] for t in sorted_tasks] + recipe_resp = api_client.get(f"/api/recipes/{recipe_id}") + if not recipe_resp.get("error"): + measurement_interval_minutes = recipe_resp.get("measurement_interval_minutes") return render_template( "measure/task_execute.html", @@ -147,6 +151,7 @@ def task_execute(task_id: int): lot_number=lot_number, serial_number=serial_number, all_task_ids=all_task_ids, + measurement_interval_minutes=measurement_interval_minutes, ) diff --git a/src/frontend/flask_app/templates/measure/task_execute.html b/src/frontend/flask_app/templates/measure/task_execute.html index 1b6010d..9d9c5be 100644 --- a/src/frontend/flask_app/templates/measure/task_execute.html +++ b/src/frontend/flask_app/templates/measure/task_execute.html @@ -406,6 +406,28 @@ + {# ================================================================ + MEASUREMENT TIMER BANNER + ================================================================ #} +
+
+ + + + + {{ _('Prossima misurazione tra') }} + + + + ({{ _('Ciclo') }} #) + +
+
+ {# ================================================================ FOOTER — Progress bar + navigation ================================================================ #} @@ -629,6 +651,13 @@ function taskExecute() { errorMessage: '', showCompletionOverlay: false, + // ---- Measurement timer ---- + measurementIntervalMinutes: {{ measurement_interval_minutes|tojson if measurement_interval_minutes else 'null' }}, + timerActive: false, + timerRemaining: 0, + _timerInterval: null, + cycleCount: 0, + // ---- Cycle & workflow state ---- cycleConfirmed: false, showSupervisorModal: false, @@ -852,6 +881,68 @@ function taskExecute() { confirmCycle() { this.cycleConfirmed = true; this.showCompletionOverlay = false; + this.cycleCount++; + + // Start measurement timer if recipe has an interval + if (this.measurementIntervalMinutes && this.measurementIntervalMinutes > 0) { + this.startMeasurementTimer(); + } + }, + + // ---- Measurement timer ---- + startMeasurementTimer() { + this.stopMeasurementTimer(); + this.timerRemaining = this.measurementIntervalMinutes * 60; + this.timerActive = true; + var self = this; + this._timerInterval = setInterval(function () { + self.timerRemaining--; + if (self.timerRemaining <= 0) { + self.onTimerExpired(); + } + }, 1000); + }, + + stopMeasurementTimer() { + if (this._timerInterval) { + clearInterval(this._timerInterval); + this._timerInterval = null; + } + this.timerActive = false; + }, + + onTimerExpired() { + this.stopMeasurementTimer(); + this.playBuzzer(); + // Reset for new measurement cycle + this.cycleConfirmed = false; + this.measurements = []; + this.currentIndex = 0; + this.currentValue = null; + }, + + playBuzzer() { + try { + var ctx = new (window.AudioContext || window.webkitAudioContext)(); + // 3 short beeps + [0, 0.25, 0.5].forEach(function (delay) { + var osc = ctx.createOscillator(); + var gain = ctx.createGain(); + osc.connect(gain); + gain.connect(ctx.destination); + osc.frequency.value = 880; + osc.type = 'square'; + gain.gain.value = 0.3; + osc.start(ctx.currentTime + delay); + osc.stop(ctx.currentTime + delay + 0.15); + }); + } catch (_) {} + }, + + get timerDisplay() { + var m = Math.floor(this.timerRemaining / 60); + var s = this.timerRemaining % 60; + return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s; }, // ---- Navigate to next task (Completato) ---- diff --git a/src/frontend/flask_app/translations/en/LC_MESSAGES/messages.po b/src/frontend/flask_app/translations/en/LC_MESSAGES/messages.po index 141b85b..3b4fd97 100644 --- a/src/frontend/flask_app/translations/en/LC_MESSAGES/messages.po +++ b/src/frontend/flask_app/translations/en/LC_MESSAGES/messages.po @@ -759,6 +759,12 @@ msgstr "seconds due to inactivity." msgid "Continua a lavorare" msgstr "Continue working" +msgid "Prossima misurazione tra" +msgstr "Next measurement in" + +msgid "Ciclo" +msgstr "Cycle" + msgid "Intervallo misura (minuti)" msgstr "Measurement interval (minutes)" diff --git a/src/frontend/flask_app/translations/it/LC_MESSAGES/messages.po b/src/frontend/flask_app/translations/it/LC_MESSAGES/messages.po index b100668..f2afef3 100644 --- a/src/frontend/flask_app/translations/it/LC_MESSAGES/messages.po +++ b/src/frontend/flask_app/translations/it/LC_MESSAGES/messages.po @@ -789,6 +789,12 @@ msgstr "secondi per inattività." msgid "Continua a lavorare" msgstr "Continua a lavorare" +msgid "Prossima misurazione tra" +msgstr "Prossima misurazione tra" + +msgid "Ciclo" +msgstr "Ciclo" + msgid "Intervallo misura (minuti)" msgstr "Intervallo misura (minuti)"