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:
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user