mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-23 09:11:09 -05:00
Add standardized HTMX conventions, interaction patterns, and migration guide for ThrillWiki UX
This commit is contained in:
172
backend/templates/components/history_panel.html
Normal file
172
backend/templates/components/history_panel.html
Normal file
@@ -0,0 +1,172 @@
|
||||
{% 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 %}
|
||||
Reference in New Issue
Block a user