Extract common diff_confirmation_panel() for selection mismatch UI

Refactors 5 nearly-identical diff_panel functions into a single reusable
component. Each specific diff_panel function now delegates to the common
function with action-specific parameters.

Reduces ~300 lines of duplicated code to ~100 lines of shared logic.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-09 12:17:51 +00:00
parent b2132a8ef5
commit fc4c2a8e40

View File

@@ -20,6 +20,104 @@ from animaltrack.models.animals import Animal
from animaltrack.models.reference import Location, Species
from animaltrack.selection.validation import SelectionDiff
# =============================================================================
# Selection Diff Confirmation Panel
# =============================================================================
def diff_confirmation_panel(
diff: SelectionDiff,
filter_str: str,
resolved_ids: list[str],
roster_hash: str,
ts_utc: int,
action: Callable[..., Any] | str,
action_hidden_fields: list[tuple[str, str]],
cancel_url: str,
confirm_button_text: str,
question_text: str,
confirm_button_cls: str = ButtonT.primary,
) -> Div:
"""Create a confirmation panel for selection mismatch scenarios.
This is a reusable component for all action forms that use optimistic locking.
When the client's selection differs from the server's current state, this panel
shows what changed and asks for confirmation before proceeding.
Args:
diff: SelectionDiff with added/removed counts.
filter_str: Original filter string.
resolved_ids: Server's resolved IDs (current).
roster_hash: Server's roster hash (current).
ts_utc: Timestamp for resolution.
action: Route function or URL for confirmation submit.
action_hidden_fields: List of (name, value) tuples for action-specific fields.
cancel_url: URL for the cancel button.
confirm_button_text: Text for the confirm button.
question_text: Question shown in the alert (e.g. "Would you like to...").
confirm_button_cls: Button style class (default: ButtonT.primary).
Returns:
Div containing the diff panel with confirm button.
"""
# Build description of changes
changes = []
if diff.removed:
changes.append(f"{len(diff.removed)} animals were removed since you loaded this page")
if diff.added:
changes.append(f"{len(diff.added)} animals were added")
changes_text = ". ".join(changes) + "." if changes else "The selection has changed."
# Build confirmation form with hidden fields
resolved_id_fields = [
Hidden(name="resolved_ids", value=animal_id) for animal_id in resolved_ids
]
# Build action-specific hidden fields
action_fields = [Hidden(name=name, value=value) for name, value in action_hidden_fields]
confirm_form = Form(
*resolved_id_fields,
Hidden(name="filter", value=filter_str),
Hidden(name="roster_hash", value=roster_hash),
*action_fields,
Hidden(name="ts_utc", value=str(ts_utc)),
Hidden(name="confirmed", value="true"),
Hidden(name="nonce", value=str(ULID())),
Div(
Button(
"Cancel",
type="button",
cls=ButtonT.default,
onclick=f"window.location.href='{cancel_url}'",
),
Button(
confirm_button_text,
type="submit",
cls=confirm_button_cls,
hx_disabled_elt="this",
),
cls="flex gap-3 mt-4",
),
action=action,
method="post",
)
return Div(
Alert(
Div(
P("Selection Changed", cls="font-bold text-lg mb-2"),
P(changes_text, cls="mb-2"),
P(question_text, cls="text-sm"),
),
cls=AlertT.warning,
),
confirm_form,
cls="space-y-4",
)
# =============================================================================
# Event Datetime Picker Component
# =============================================================================
@@ -680,61 +778,17 @@ def tag_add_diff_panel(
Returns:
Div containing the diff panel with confirm button.
"""
# Build description of changes
changes = []
if diff.removed:
changes.append(f"{len(diff.removed)} animals were removed since you loaded this page")
if diff.added:
changes.append(f"{len(diff.added)} animals were added")
changes_text = ". ".join(changes) + "." if changes else "The selection has changed."
# Build confirmation form with hidden fields
resolved_id_fields = [
Hidden(name="resolved_ids", value=animal_id) for animal_id in resolved_ids
]
confirm_form = Form(
*resolved_id_fields,
Hidden(name="filter", value=filter_str),
Hidden(name="roster_hash", value=roster_hash),
Hidden(name="tag", value=tag),
Hidden(name="ts_utc", value=str(ts_utc)),
Hidden(name="confirmed", value="true"),
Hidden(name="nonce", value=str(ULID())),
Div(
Button(
"Cancel",
type="button",
cls=ButtonT.default,
onclick="window.location.href='/actions/tag-add'",
),
Button(
f"Confirm Tag ({diff.server_count} animals)",
type="submit",
cls=ButtonT.primary,
hx_disabled_elt="this",
),
cls="flex gap-3 mt-4",
),
return diff_confirmation_panel(
diff=diff,
filter_str=filter_str,
resolved_ids=resolved_ids,
roster_hash=roster_hash,
ts_utc=ts_utc,
action=action,
method="post",
)
return Div(
Alert(
Div(
P("Selection Changed", cls="font-bold text-lg mb-2"),
P(changes_text, cls="mb-2"),
P(
f"Would you like to proceed with tagging {diff.server_count} animals as '{tag}'?",
cls="text-sm",
),
),
cls=AlertT.warning,
),
confirm_form,
cls="space-y-4",
action_hidden_fields=[("tag", tag)],
cancel_url="/actions/tag-add",
confirm_button_text=f"Confirm Tag ({diff.server_count} animals)",
question_text=f"Would you like to proceed with tagging {diff.server_count} animals as '{tag}'?",
)
@@ -906,61 +960,17 @@ def tag_end_diff_panel(
Returns:
Div containing the diff panel with confirm button.
"""
# Build description of changes
changes = []
if diff.removed:
changes.append(f"{len(diff.removed)} animals were removed since you loaded this page")
if diff.added:
changes.append(f"{len(diff.added)} animals were added")
changes_text = ". ".join(changes) + "." if changes else "The selection has changed."
# Build confirmation form with hidden fields
resolved_id_fields = [
Hidden(name="resolved_ids", value=animal_id) for animal_id in resolved_ids
]
confirm_form = Form(
*resolved_id_fields,
Hidden(name="filter", value=filter_str),
Hidden(name="roster_hash", value=roster_hash),
Hidden(name="tag", value=tag),
Hidden(name="ts_utc", value=str(ts_utc)),
Hidden(name="confirmed", value="true"),
Hidden(name="nonce", value=str(ULID())),
Div(
Button(
"Cancel",
type="button",
cls=ButtonT.default,
onclick="window.location.href='/actions/tag-end'",
),
Button(
f"Confirm End Tag ({diff.server_count} animals)",
type="submit",
cls=ButtonT.primary,
hx_disabled_elt="this",
),
cls="flex gap-3 mt-4",
),
return diff_confirmation_panel(
diff=diff,
filter_str=filter_str,
resolved_ids=resolved_ids,
roster_hash=roster_hash,
ts_utc=ts_utc,
action=action,
method="post",
)
return Div(
Alert(
Div(
P("Selection Changed", cls="font-bold text-lg mb-2"),
P(changes_text, cls="mb-2"),
P(
f"Would you like to proceed with ending tag '{tag}' on {diff.server_count} animals?",
cls="text-sm",
),
),
cls=AlertT.warning,
),
confirm_form,
cls="space-y-4",
action_hidden_fields=[("tag", tag)],
cancel_url="/actions/tag-end",
confirm_button_text=f"Confirm End Tag ({diff.server_count} animals)",
question_text=f"Would you like to proceed with ending tag '{tag}' on {diff.server_count} animals?",
)
@@ -1154,63 +1164,21 @@ def attrs_diff_panel(
Returns:
Div containing the diff panel with confirm button.
"""
# Build description of changes
changes = []
if diff.removed:
changes.append(f"{len(diff.removed)} animals were removed since you loaded this page")
if diff.added:
changes.append(f"{len(diff.added)} animals were added")
changes_text = ". ".join(changes) + "." if changes else "The selection has changed."
# Build confirmation form with hidden fields
resolved_id_fields = [
Hidden(name="resolved_ids", value=animal_id) for animal_id in resolved_ids
]
confirm_form = Form(
*resolved_id_fields,
Hidden(name="filter", value=filter_str),
Hidden(name="roster_hash", value=roster_hash),
Hidden(name="sex", value=sex or ""),
Hidden(name="life_stage", value=life_stage or ""),
Hidden(name="repro_status", value=repro_status or ""),
Hidden(name="ts_utc", value=str(ts_utc)),
Hidden(name="confirmed", value="true"),
Hidden(name="nonce", value=str(ULID())),
Div(
Button(
"Cancel",
type="button",
cls=ButtonT.default,
onclick="window.location.href='/actions/attrs'",
),
Button(
f"Confirm Update ({diff.server_count} animals)",
type="submit",
cls=ButtonT.primary,
hx_disabled_elt="this",
),
cls="flex gap-3 mt-4",
),
return diff_confirmation_panel(
diff=diff,
filter_str=filter_str,
resolved_ids=resolved_ids,
roster_hash=roster_hash,
ts_utc=ts_utc,
action=action,
method="post",
)
return Div(
Alert(
Div(
P("Selection Changed", cls="font-bold text-lg mb-2"),
P(changes_text, cls="mb-2"),
P(
f"Would you like to proceed with updating {diff.server_count} animals?",
cls="text-sm",
),
),
cls=AlertT.warning,
),
confirm_form,
cls="space-y-4",
action_hidden_fields=[
("sex", sex or ""),
("life_stage", life_stage or ""),
("repro_status", repro_status or ""),
],
cancel_url="/actions/attrs",
confirm_button_text=f"Confirm Update ({diff.server_count} animals)",
question_text=f"Would you like to proceed with updating {diff.server_count} animals?",
)
@@ -1456,66 +1424,25 @@ def outcome_diff_panel(
Returns:
Div containing the diff panel with confirm button.
"""
# Build description of changes
changes = []
if diff.removed:
changes.append(f"{len(diff.removed)} animals were removed since you loaded this page")
if diff.added:
changes.append(f"{len(diff.added)} animals were added")
changes_text = ". ".join(changes) + "." if changes else "The selection has changed."
# Build confirmation form with hidden fields
resolved_id_fields = [
Hidden(name="resolved_ids", value=animal_id) for animal_id in resolved_ids
]
confirm_form = Form(
*resolved_id_fields,
Hidden(name="filter", value=filter_str),
Hidden(name="roster_hash", value=roster_hash),
Hidden(name="outcome", value=outcome),
Hidden(name="reason", value=reason or ""),
Hidden(name="yield_product_code", value=yield_product_code or ""),
Hidden(name="yield_unit", value=yield_unit or ""),
Hidden(name="yield_quantity", value=str(yield_quantity) if yield_quantity else ""),
Hidden(name="yield_weight_kg", value=str(yield_weight_kg) if yield_weight_kg else ""),
Hidden(name="ts_utc", value=str(ts_utc)),
Hidden(name="confirmed", value="true"),
Hidden(name="nonce", value=str(ULID())),
Div(
Button(
"Cancel",
type="button",
cls=ButtonT.default,
onclick="window.location.href='/actions/outcome'",
),
Button(
f"Confirm Outcome ({diff.server_count} animals)",
type="submit",
cls=ButtonT.destructive,
hx_disabled_elt="this",
),
cls="flex gap-3 mt-4",
),
return diff_confirmation_panel(
diff=diff,
filter_str=filter_str,
resolved_ids=resolved_ids,
roster_hash=roster_hash,
ts_utc=ts_utc,
action=action,
method="post",
)
return Div(
Alert(
Div(
P("Selection Changed", cls="font-bold text-lg mb-2"),
P(changes_text, cls="mb-2"),
P(
f"Would you like to proceed with recording {outcome} for {diff.server_count} animals?",
cls="text-sm",
),
),
cls=AlertT.warning,
),
confirm_form,
cls="space-y-4",
action_hidden_fields=[
("outcome", outcome),
("reason", reason or ""),
("yield_product_code", yield_product_code or ""),
("yield_unit", yield_unit or ""),
("yield_quantity", str(yield_quantity) if yield_quantity else ""),
("yield_weight_kg", str(yield_weight_kg) if yield_weight_kg else ""),
],
cancel_url="/actions/outcome",
confirm_button_text=f"Confirm Outcome ({diff.server_count} animals)",
question_text=f"Would you like to proceed with recording {outcome} for {diff.server_count} animals?",
confirm_button_cls=ButtonT.destructive,
)
@@ -1670,60 +1597,19 @@ def status_correct_diff_panel(
Returns:
Div containing the diff panel with confirm button.
"""
# Build description of changes
changes = []
if diff.removed:
changes.append(f"{len(diff.removed)} animals were removed since you loaded this page")
if diff.added:
changes.append(f"{len(diff.added)} animals were added")
changes_text = ". ".join(changes) + "." if changes else "The selection has changed."
# Build confirmation form with hidden fields
resolved_id_fields = [
Hidden(name="resolved_ids", value=animal_id) for animal_id in resolved_ids
]
confirm_form = Form(
*resolved_id_fields,
Hidden(name="filter", value=filter_str),
Hidden(name="roster_hash", value=roster_hash),
Hidden(name="new_status", value=new_status),
Hidden(name="reason", value=reason),
Hidden(name="ts_utc", value=str(ts_utc)),
Hidden(name="confirmed", value="true"),
Hidden(name="nonce", value=str(ULID())),
Div(
Button(
"Cancel",
type="button",
cls=ButtonT.default,
onclick="window.location.href='/actions/status-correct'",
),
Button(
f"Confirm Correction ({diff.server_count} animals)",
type="submit",
cls=ButtonT.destructive,
hx_disabled_elt="this",
),
cls="flex gap-3 mt-4",
),
return diff_confirmation_panel(
diff=diff,
filter_str=filter_str,
resolved_ids=resolved_ids,
roster_hash=roster_hash,
ts_utc=ts_utc,
action=action,
method="post",
)
return Div(
Alert(
Div(
P("Selection Changed", cls="font-bold text-lg mb-2"),
P(changes_text, cls="mb-2"),
P(
f"Would you like to proceed with correcting status to {new_status} for {diff.server_count} animals?",
cls="text-sm",
),
),
cls=AlertT.warning,
),
confirm_form,
cls="space-y-4",
action_hidden_fields=[
("new_status", new_status),
("reason", reason),
],
cancel_url="/actions/status-correct",
confirm_button_text=f"Confirm Correction ({diff.server_count} animals)",
question_text=f"Would you like to proceed with correcting status to {new_status} for {diff.server_count} animals?",
confirm_button_cls=ButtonT.destructive,
)