Files
thrillwiki_django_no_react/backend/templates/components/history_panel.html

173 lines
8.3 KiB
HTML

{% comment %}
History Panel Component
=======================
A reusable history panel component for displaying object change history and FSM transitions.
Purpose:
Displays both regular history records and FSM (Finite State Machine) transition
history for parks, rides, and other entities with historical tracking.
Usage Examples:
Basic history:
{% include 'components/history_panel.html' with history=history %}
With FSM toggle (for moderators):
{% include 'components/history_panel.html' with history=history show_fsm_toggle=True fsm_history_url=fsm_url model_type='park' object_id=park.id can_view_fsm=perms.parks.change_park %}
Ride history:
{% include 'components/history_panel.html' with history=history show_fsm_toggle=True fsm_history_url=fsm_url model_type='ride' object_id=ride.id can_view_fsm=perms.rides.change_ride %}
Parameters:
Required:
- history: QuerySet or list of history records
Optional (FSM):
- show_fsm_toggle: Show toggle button for FSM history (default: False)
- fsm_history_url: URL for loading FSM transition history via HTMX
- model_type: Model type for FSM history (e.g., 'park', 'ride')
- object_id: Object ID for FSM history
- can_view_fsm: Whether user can view FSM history (default: False)
Optional (styling):
- title: Panel title (default: 'History')
- panel_class: Additional CSS classes for panel
- max_height: Maximum height for scrollable area (default: 'max-h-96')
- collapsed: Start collapsed (default: False)
Dependencies:
- Tailwind CSS for styling
- Alpine.js for interactivity
- HTMX (optional, for FSM history lazy loading)
- Font Awesome icons
Accessibility:
- Uses heading structure for panel title
- Toggle button has accessible label
- History items use semantic structure
{% endcomment %}
{% with title=title|default:'History' show_fsm_toggle=show_fsm_toggle|default:False can_view_fsm=can_view_fsm|default:False max_height=max_height|default:'max-h-96' collapsed=collapsed|default:False %}
<div class="p-6 mb-6 bg-white rounded-lg shadow dark:bg-gray-800 {{ panel_class }}"
x-data="{ showFsmHistory: false {% if collapsed %}, showHistory: false{% endif %} }">
{# Header with optional FSM toggle #}
<div class="flex items-center justify-between mb-4">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
{% if collapsed %}
<button type="button"
@click="showHistory = !showHistory"
class="flex items-center gap-2 hover:text-gray-700 dark:hover:text-gray-300">
<i class="fas fa-chevron-right transition-transform"
:class="{ 'rotate-90': showHistory }"
aria-hidden="true"></i>
{{ title }}
</button>
{% else %}
{{ title }}
{% endif %}
</h2>
{% if show_fsm_toggle and can_view_fsm %}
<button type="button"
@click="showFsmHistory = !showFsmHistory"
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500"
aria-expanded="showFsmHistory"
aria-controls="{{ model_type }}-fsm-history-container">
<i class="mr-2 fas fa-history" aria-hidden="true"></i>
<span x-text="showFsmHistory ? 'Hide Transitions' : 'Show Transitions'"></span>
</button>
{% endif %}
</div>
{# Collapsible wrapper #}
<div {% if collapsed %}x-show="showHistory" x-cloak x-transition{% endif %}>
{# FSM Transition History (Moderators Only) #}
{% if show_fsm_toggle and can_view_fsm and fsm_history_url %}
<div x-show="showFsmHistory" x-cloak x-transition class="mb-4">
<div id="{{ model_type }}-fsm-history-container"
x-show="showFsmHistory"
x-init="$watch('showFsmHistory', value => { if(value && !$el.dataset.loaded) { htmx.trigger($el, 'load-history'); $el.dataset.loaded = 'true'; } })"
hx-get="{{ fsm_history_url }}{% if model_type and object_id %}?model_type={{ model_type }}&object_id={{ object_id }}{% endif %}"
hx-trigger="load-history"
hx-target="this"
hx-swap="innerHTML"
hx-indicator="#{{ model_type }}-fsm-loading"
class="p-4 rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800">
{# Loading State #}
<div id="{{ model_type }}-fsm-loading" class="htmx-indicator flex items-center justify-center py-4">
<i class="mr-2 text-blue-500 fas fa-spinner fa-spin" aria-hidden="true"></i>
<span class="text-sm text-gray-600 dark:text-gray-400">Loading transitions...</span>
</div>
</div>
</div>
{% endif %}
{# Regular History #}
<div class="space-y-4 overflow-y-auto {{ max_height }}">
{% for record in history %}
<div class="p-4 rounded-lg bg-gray-50 dark:bg-gray-700/50">
{# Timestamp and user #}
<div class="mb-1 text-sm text-gray-500 dark:text-gray-400">
{# Support both simple_history and pghistory formats #}
{% if record.history_date %}
{{ record.history_date|date:"M d, Y H:i" }}
{% if record.history_user %}
by {{ record.history_user.username }}
{% endif %}
{% elif record.pgh_created_at %}
{{ record.pgh_created_at|date:"M d, Y H:i" }}
{% if record.pgh_context.user %}
by {{ record.pgh_context.user }}
{% endif %}
{% if record.pgh_label %}
- {{ record.pgh_label }}
{% endif %}
{% endif %}
</div>
{# Changes #}
{% if record.diff_against_previous %}
<div class="mt-2 space-y-2">
{# Support both dictionary and method formats #}
{% if record.get_display_changes %}
{% for field, change in record.get_display_changes.items %}
{% if field != "updated_at" %}
<div class="text-sm">
<span class="font-medium text-gray-700 dark:text-gray-300">{{ field }}:</span>
<span class="text-red-600 dark:text-red-400">{{ change.old|default:"—" }}</span>
<span class="mx-1 text-gray-400"></span>
<span class="text-green-600 dark:text-green-400">{{ change.new|default:"—" }}</span>
</div>
{% endif %}
{% endfor %}
{% else %}
{% for field, changes in record.diff_against_previous.items %}
{% if field != "updated_at" %}
<div class="text-sm">
<span class="font-medium text-gray-700 dark:text-gray-300">{{ field|title }}:</span>
<span class="text-red-600 dark:text-red-400">{{ changes.old|default:"—" }}</span>
<span class="mx-1 text-gray-400"></span>
<span class="text-green-600 dark:text-green-400">{{ changes.new|default:"—" }}</span>
</div>
{% endif %}
{% endfor %}
{% endif %}
</div>
{% endif %}
</div>
{% empty %}
<p class="text-gray-500 dark:text-gray-400 text-center py-4">
<i class="fas fa-history mr-2" aria-hidden="true"></i>
No history available.
</p>
{% endfor %}
</div>
</div>
</div>
{% endwith %}