diff --git a/duty_teller/handlers/group_duty_pin.py b/duty_teller/handlers/group_duty_pin.py index 3750e09..032dfcc 100644 --- a/duty_teller/handlers/group_duty_pin.py +++ b/duty_teller/handlers/group_duty_pin.py @@ -266,14 +266,18 @@ async def pin_duty_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No ) pinned = True except (BadRequest, Forbidden) as e: - logger.warning("Failed to pin message for pin_duty chat_id=%s: %s", chat_id, e) + logger.warning( + "Failed to pin message for pin_duty chat_id=%s: %s", chat_id, e + ) await loop.run_in_executor(None, _sync_save_pin, chat_id, msg.message_id) next_end = await loop.run_in_executor(None, _get_next_shift_end_sync) await _schedule_next_update(context.application, chat_id, next_end) if pinned: await update.message.reply_text(t(lang, "pin_duty.pinned")) else: - await update.message.reply_text(t(lang, "pin_duty.could_not_pin_make_admin")) + await update.message.reply_text( + t(lang, "pin_duty.could_not_pin_make_admin") + ) return try: await context.bot.pin_chat_message( diff --git a/tests/test_handlers_group_duty_pin.py b/tests/test_handlers_group_duty_pin.py index 9ba63d8..c4e007f 100644 --- a/tests/test_handlers_group_duty_pin.py +++ b/tests/test_handlers_group_duty_pin.py @@ -328,11 +328,17 @@ async def test_pin_duty_cmd_no_message_id_creates_sends_pins_saves_schedules_rep with patch("duty_teller.handlers.group_duty_pin.get_lang", return_value="en"): with patch.object(mod, "_sync_get_message_id", return_value=None): - with patch.object(mod, "_get_duty_message_text_sync", return_value="Duty text"): + with patch.object( + mod, "_get_duty_message_text_sync", return_value="Duty text" + ): with patch.object(mod, "_sync_save_pin") as mock_save: - with patch.object(mod, "_get_next_shift_end_sync", return_value=None): + with patch.object( + mod, "_get_next_shift_end_sync", return_value=None + ): with patch.object(mod, "_schedule_next_update", AsyncMock()): - with patch("duty_teller.handlers.group_duty_pin.t") as mock_t: + with patch( + "duty_teller.handlers.group_duty_pin.t" + ) as mock_t: mock_t.return_value = "Pinned" await mod.pin_duty_cmd(update, context) context.bot.send_message.assert_called_once_with(chat_id=100, text="Duty text") @@ -363,7 +369,9 @@ async def test_pin_duty_cmd_no_message_id_send_message_raises_replies_failed(): with patch.object(mod, "_sync_get_message_id", return_value=None): with patch.object(mod, "_get_duty_message_text_sync", return_value="Duty"): with patch.object(mod, "_sync_save_pin") as mock_save: - with patch.object(mod, "_schedule_next_update", AsyncMock()) as mock_schedule: + with patch.object( + mod, "_schedule_next_update", AsyncMock() + ) as mock_schedule: with patch("duty_teller.handlers.group_duty_pin.t") as mock_t: mock_t.return_value = "Failed" await mod.pin_duty_cmd(update, context) @@ -398,9 +406,13 @@ async def test_pin_duty_cmd_no_message_id_pin_raises_saves_and_replies_could_not with patch.object(mod, "_sync_get_message_id", return_value=None): with patch.object(mod, "_get_duty_message_text_sync", return_value="Duty"): with patch.object(mod, "_sync_save_pin") as mock_save: - with patch.object(mod, "_get_next_shift_end_sync", return_value=None): + with patch.object( + mod, "_get_next_shift_end_sync", return_value=None + ): with patch.object(mod, "_schedule_next_update", AsyncMock()): - with patch("duty_teller.handlers.group_duty_pin.t") as mock_t: + with patch( + "duty_teller.handlers.group_duty_pin.t" + ) as mock_t: mock_t.return_value = "Make me admin to pin" await mod.pin_duty_cmd(update, context) context.bot.send_message.assert_called_once_with(chat_id=100, text="Duty") diff --git a/webapp/js/hints.js b/webapp/js/hints.js index ce6f98f..a1731ec 100644 --- a/webapp/js/hints.js +++ b/webapp/js/hints.js @@ -126,9 +126,14 @@ function buildDutyItemTimePrefix(item, idx, total, hintDay, sep, fromLabel, toLa timePrefix = toLabel + sep + endHHMM; } } else if (idx > 0) { - if (startHHMM) timePrefix = fromLabel + sep + startHHMM; - if (endHHMM && endSameDay && endHHMM !== startHHMM) { - timePrefix += (timePrefix ? " " : "") + toLabel + sep + endHHMM; + if (startSameDay && startHHMM) { + timePrefix = fromLabel + sep + startHHMM; + if (endHHMM && endSameDay && endHHMM !== startHHMM) { + timePrefix += " " + toLabel + sep + endHHMM; + } + } else if (endHHMM) { + /* Continuation from previous day — only end time */ + timePrefix = toLabel + sep + endHHMM; } } return timePrefix; diff --git a/webapp/js/hints.test.js b/webapp/js/hints.test.js index 744afb1..e09ad0f 100644 --- a/webapp/js/hints.test.js +++ b/webapp/js/hints.test.js @@ -82,6 +82,33 @@ describe("getDutyMarkerRows", () => { expect(rows[0].timePrefix).toContain("06:00"); }); + it("second duty continuation from previous day shows only end time (to HH:MM)", () => { + const hintDay = "2025-02-23"; + const duties = [ + { + full_name: "A", + start_at: "2025-02-23T00:00:00", + end_at: "2025-02-23T09:00:00", + }, + { + full_name: "B", + start_at: "2025-02-22T09:00:00", + end_at: "2025-02-23T09:00:00", + }, + ]; + const rows = getDutyMarkerRows(duties, hintDay, SEP, FROM, TO); + expect(rows).toHaveLength(2); + expect(rows[0].fullName).toBe("A"); + expect(rows[0].timePrefix).toContain(FROM); + expect(rows[0].timePrefix).toContain("00:00"); + expect(rows[0].timePrefix).toContain(TO); + expect(rows[0].timePrefix).toContain("09:00"); + expect(rows[1].fullName).toBe("B"); + expect(rows[1].timePrefix).not.toContain(FROM); + expect(rows[1].timePrefix).toContain(TO); + expect(rows[1].timePrefix).toContain("09:00"); + }); + it("multiple duties in one day — correct order when input is pre-sorted", () => { const hintDay = "2025-02-25"; const duties = [