dga_feedback-2.0.0/templates/feedback-admin.html.twig
templates/feedback-admin.html.twig
{#
/**
* @file
* Template for the DGA Feedback admin submissions page.
*
* Available variables:
* - overall_stats: Overall statistics (yes_percentage, total_count)
* - usefulness_distribution: Array of yes/no counts
* - url_statistics: Array of statistics grouped by URL
* - unique_url_count: Total count of unique URLs
* - submissions: Array of submission data
* - total_count: Total number of submissions
* - current_page: Current page number
* - limit: Items per page
* - filters: Current filter values
* - sort_by: Current sort field
* - sort_direction: Current sort direction (ASC/DESC)
* - url_stats_limit: Limit for URL statistics
* - url_stats_order_by: Order field for URL statistics
* - url_stats_order_direction: Order direction for URL statistics
* - top_feedback_page: Top feedback page statistics
* - most_feedback_page: Most feedback page statistics
* - recent_activity: Recent activity (7 days, 30 days)
* - useful_percentage: Percentage of useful feedback (yes)
* - user_type_stats: Statistics by user type (anonymous vs authenticated)
*/
#}
<div class="dga-feedback-admin-wrapper">
<div
class="dga-feedback-admin">
{# Overall Statistics Section #}
<div class="dga-feedback-stats-section">
<div class="section-header">
<h2 class="section-title">{{ 'Overall Statistics'|t }}</h2>
{% if filters is not empty %}
<div class="stats-note">
<span class="note-icon">ℹ️</span>
<span class="note-text">{{ 'Showing all-time statistics (not filtered)'|t }}</span>
</div>
{% endif %}
</div>
<div
class="stats-grid">
{# Primary Stats - Large Cards #}
<div class="stat-card stat-card-primary stat-card-large">
<div class="icon">
<img src="/modules/custom/dga_feedback/images/icons/chart.svg" alt="average-icon">
</div>
<div class="stat-content">
<div class="stat-label">{{ 'Total Submissions'|t }}</div>
<div class="stat-value">{{ overall_stats.total_count|number_format }}</div>
<div class="stat-trend">
<span class="trend-text">{{ recent_activity.last_7_days.count|default(0) }}
{{ 'in last 7 days'|t }}</span>
</div>
</div>
</div>
<div class="stat-card stat-card-success stat-card-large">
<div class="icon">
<img src="/modules/custom/dga_feedback/images/icons/positive.svg" alt="average-icon">
</div>
<div class="stat-content">
<div class="stat-label">{{ 'Useful Percentage'|t }}</div>
<div class="stat-value">{{ overall_stats.yes_percentage|number_format(1) }}%</div>
<div class="stat-progress">
<div class="progress-bar">
<div class="progress-fill" style="width: {{ overall_stats.yes_percentage }}%"></div>
</div>
<span class="progress-text">{{ overall_stats.yes_percentage|round }}%
{{ 'said Yes'|t }}</span>
</div>
</div>
</div>
<div class="stat-card stat-card-info stat-card-large">
<div class="icon">
<img src="/modules/custom/dga_feedback/images/icons/page.svg" alt="average-icon">
</div>
<div class="stat-content">
<div class="stat-label">{{ 'Total Pages'|t }}</div>
<div class="stat-value">{{ unique_url_count|number_format }}</div>
<div class="stat-subtitle">{{ 'Pages with feedback'|t }}</div>
</div>
</div>
<div class="stat-card stat-card-warning stat-card-large">
<div class="icon">
<img src="/modules/custom/dga_feedback/images/icons/average.svg" alt="average-icon">
</div>
<div class="stat-content">
<div class="stat-label">{{ 'Average per Page'|t }}</div>
<div class="stat-value">{{ unique_url_count > 0 ? (overall_stats.total_count / unique_url_count)|number_format(1) : '0.0' }}</div>
<div class="stat-subtitle">{{ 'Submissions per page'|t }}</div>
</div>
</div>
{# Advanced Stats - Medium Cards #}
<div class="stat-card stat-card-advanced stat-card-positive">
<div class="icon">
<img src="/modules/custom/dga_feedback/images/icons/positive.svg" alt="average-icon">
</div>
<div class="stat-content">
<div class="stat-label">{{ 'Yes Responses'|t }}</div>
<div class="stat-value">{{ usefulness_distribution.yes|number_format }}</div>
<div class="stat-gauge">
<div class="gauge-bar">
{% set yes_percentage = overall_stats.total_count > 0 ? (usefulness_distribution.yes / overall_stats.total_count * 100)|round : 0 %}
<div class="gauge-fill gauge-positive" style="width: {{ yes_percentage }}%"></div>
</div>
<span class="gauge-label">{{ 'Useful'|t }}</span>
</div>
</div>
</div>
<div class="stat-card stat-card-advanced stat-card-negative">
<div class="icon cancel">
<img src="/modules/custom/dga_feedback/images/icons/cancel.svg" alt="average-icon">
</div>
<div class="stat-content">
<div class="stat-label">{{ 'No Responses'|t }}</div>
<div class="stat-value">{{ usefulness_distribution.no|number_format }}</div>
<div class="stat-gauge">
<div class="gauge-bar">
{% set no_percentage = overall_stats.total_count > 0 ? (usefulness_distribution.no / overall_stats.total_count * 100)|round : 0 %}
<div class="gauge-fill gauge-negative" style="width: {{ no_percentage }}%"></div>
</div>
<span class="gauge-label">{{ 'Not Useful'|t }}</span>
</div>
</div>
</div>
<div class="stat-card stat-card-advanced stat-card-recent">
<div class="icon">
<img src="/modules/custom/dga_feedback/images/icons/clock.svg" alt="average-icon">
</div>
<div class="stat-content">
<div class="stat-label">{{ 'Last 7 Days'|t }}</div>
<div class="stat-value">{{ recent_activity.last_7_days.count|default(0)|number_format }}</div>
<div class="stat-subtitle">
{% if recent_activity.last_7_days.count > 0 %}
{{ recent_activity.last_7_days.useful_percentage|default(0)|number_format(1) }}%
{{ 'useful'|t }}
{% else %}
{{ 'No recent activity'|t }}
{% endif %}
</div>
</div>
</div>
{% if top_feedback_page %}
<div class="stat-card stat-card-advanced stat-card-top-rated">
<div class="icon">
<img src="/modules/custom/dga_feedback/images/icons/top-rated.svg" alt="average-icon">
</div>
<div class="stat-content">
<div class="stat-label">{{ 'Most Useful Page'|t }}</div>
<div class="stat-value">{{ top_feedback_page.yes_percentage|number_format(1) }}%</div>
<div class="stat-url">
<a href="{{ top_feedback_page.url }}" target="_blank" class="url-link-small" title="{{ top_feedback_page.url }}">
{{ top_feedback_page.url|length > 30 ? top_feedback_page.url|slice(0, 30) ~ '...' : top_feedback_page.url }}
</a>
<span class="url-count">{{ top_feedback_page.count }}
{{ 'submissions'|t }}</span>
</div>
</div>
</div>
{% endif %}
{% if most_feedback_page %}
<div class="stat-card stat-card-advanced stat-card-most-reviewed">
<div class="icon">
<img src="/modules/custom/dga_feedback/images/icons/fire.svg" alt="average-icon">
</div>
<div class="stat-content">
<div class="stat-label">{{ 'Most Feedback'|t }}</div>
<div class="stat-value">{{ most_feedback_page.count|number_format }}</div>
<div class="stat-url">
<a href="{{ most_feedback_page.url }}" target="_blank" class="url-link-small" title="{{ most_feedback_page.url }}">
{{ most_feedback_page.url|length > 30 ? most_feedback_page.url|slice(0, 30) ~ '...' : most_feedback_page.url }}
</a>
<span class="url-count">{{ most_feedback_page.yes_percentage|number_format(1) }}%
{{ 'useful'|t }}</span>
</div>
</div>
</div>
{% endif %}
<div class="stat-card stat-card-advanced stat-card-users">
<div class="icon">
<img src="/modules/custom/dga_feedback/images/icons/people.svg" alt="average-icon">
</div>
<div class="stat-content">
<div class="stat-label">{{ 'User Type'|t }}</div>
<div class="stat-value-split">
<div class="split-item">
<span class="split-label">{{ 'Anonymous'|t }}</span>
<span class="split-value">{{ user_type_stats.anonymous|default(0)|number_format }}</span>
</div>
<div class="split-item">
<span class="split-label">{{ 'Authenticated'|t }}</span>
<span class="split-value">{{ user_type_stats.authenticated|default(0)|number_format }}</span>
</div>
</div>
<div class="stat-chart-mini">
{% set total_users = user_type_stats.anonymous|default(0) + user_type_stats.authenticated|default(0) %}
{% if total_users > 0 %}
{% set anon_percent = (user_type_stats.anonymous|default(0) / total_users * 100)|round %}
{% set auth_percent = (user_type_stats.authenticated|default(0) / total_users * 100)|round %}
<div class="mini-bar">
<div class="mini-bar-segment mini-bar-anon" style="width: {{ anon_percent }}%"></div>
<div class="mini-bar-segment mini-bar-auth" style="width: {{ auth_percent }}%"></div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{# Filters Section #}
<div class="dga-feedback-filters-section">
<div class="section-header">
<h2 class="section-title">{{ 'Filters & Search'|t }}</h2>
<button type="button" class="button button-secondary" id="toggle-filters" aria-expanded="true">
<span class="toggle-text">{{ 'Hide Filters'|t }}</span>
</button>
</div>
<div class="filters-content" id="filters-content">
<form method="get" action="" class="filters-form" id="feedback-filters-form">
<div class="filters-grid">
<div class="filter-group">
<label for="filter-url" class="filter-label">{{ 'URL'|t }}</label>
<input type="text" id="filter-url" name="url" value="{{ filters.url|default('') }}" placeholder="{{ 'Filter by URL...'|t }}" class="form-text">
</div>
<div class="filter-group">
<label for="filter-is-useful" class="filter-label">{{ 'Useful'|t }}</label>
<select id="filter-is-useful" name="is_useful" class="form-select">
<option value="">{{ 'All'|t }}</option>
<option value="yes" {% if filters.is_useful == 'yes' %} selected {% endif %}>{{ 'Yes'|t }}</option>
<option value="no" {% if filters.is_useful == 'no' %} selected {% endif %}>{{ 'No'|t }}</option>
</select>
</div>
<div class="filter-group">
<label for="filter-entity-type" class="filter-label">{{ 'Entity Type'|t }}</label>
<input type="text" id="filter-entity-type" name="entity_type" value="{{ filters.entity_type|default('') }}" placeholder="{{ 'e.g., node'|t }}" class="form-text">
</div>
<div class="filter-group">
<label for="filter-entity-id" class="filter-label">{{ 'Entity ID'|t }}</label>
<input type="number" id="filter-entity-id" name="entity_id" value="{{ filters.entity_id|default('') }}" placeholder="{{ 'e.g., 123'|t }}" class="form-text">
</div>
<div class="filter-group">
<label for="filter-date-from" class="filter-label">{{ 'Date From'|t }}</label>
<input type="date" id="filter-date-from" name="date_from" value="{% if filters.date_from %}{{ filters.date_from|date('Y-m-d', 'UTC') }}{% endif %}" class="form-date">
</div>
<div class="filter-group">
<label for="filter-date-to" class="filter-label">{{ 'Date To'|t }}</label>
<input type="date" id="filter-date-to" name="date_to" value="{% if filters.date_to %}{{ filters.date_to|date('Y-m-d', 'UTC') }}{% endif %}" class="form-date">
</div>
</div>
<div class="filters-actions">
<button type="submit" class="button button-primary">{{ 'Apply Filters'|t }}</button>
<a href="?" class="button button-secondary">{{ 'Clear Filters'|t }}</a>
</div>
</form>
</div>
</div>
{# Submissions Table Section #}
<div class="dga-feedback-submissions-section">
<div class="section-header">
<h2 class="section-title">{{ 'All Submissions'|t }}
<span class="submissions-count">({{ total_count|number_format }})</span>
</h2>
</div>
{% if submissions is empty %}
<div class="empty-state">
<p class="empty-message">{{ 'No submissions found.'|t }}</p>
</div>
{% else %}
{# Bulk Operations #}
<div class="bulk-operations-wrapper" id="bulk-operations-wrapper" style="display: none;">
<div class="bulk-operations">
<div class="bulk-operations-info">
<span class="bulk-operations-count" id="bulk-operations-count">0</span>
<span class="bulk-operations-text">{{ 'selected'|t }}</span>
</div>
<div class="bulk-operations-actions">
<select id="bulk-action-select" class="form-select">
<option value="">{{ '- Select action -'|t }}</option>
<option value="delete">{{ 'Delete selected'|t }}</option>
</select>
<button type="button" id="bulk-action-submit" class="button button--primary" disabled>{{ 'Apply to selected items'|t }}</button>
</div>
</div>
</div>
<div class="table-wrapper">
<table class="submissions-table sticky-enabled" id="submissions-table">
<thead>
<tr>
<th class="select-all">
<input type="checkbox" id="select-all-checkbox" class="form-checkbox" title="{{ 'Select all rows in this table'|t }}"/>
</th>
<th class="sortable" data-sort="id">
{{ 'ID'|t }}
{% if sort_by == 'id' %}
<span class="sort-indicator">{{ sort_direction == 'ASC' ? '↑' : '↓' }}</span>
{% endif %}
</th>
<th class="sortable" data-sort="is_useful">
{{ 'Useful'|t }}
{% if sort_by == 'is_useful' %}
<span class="sort-indicator">{{ sort_direction == 'ASC' ? '↑' : '↓' }}</span>
{% endif %}
</th>
<th>{{ 'Reasons'|t }}</th>
<th>{{ 'Feedback'|t }}</th>
<th>{{ 'Gender'|t }}</th>
<th class="sortable" data-sort="url">
{{ 'URL'|t }}
{% if sort_by == 'url' %}
<span class="sort-indicator">{{ sort_direction == 'ASC' ? '↑' : '↓' }}</span>
{% endif %}
</th>
<th class="sortable" data-sort="user_id">
{{ 'User'|t }}
{% if sort_by == 'user_id' %}
<span class="sort-indicator">{{ sort_direction == 'ASC' ? '↑' : '↓' }}</span>
{% endif %}
</th>
<th class="sortable" data-sort="created">
{{ 'Submitted'|t }}
{% if sort_by == 'created' %}
<span class="sort-indicator">{{ sort_direction == 'ASC' ? '↑' : '↓' }}</span>
{% endif %}
</th>
<th>{{ 'Operations'|t }}</th>
</tr>
</thead>
<tbody>
{% for submission in submissions %}
<tr>
<td class="select-checkbox">
<input type="checkbox" name="submissions[]" value="{{ submission.id|default(0) }}" class="form-checkbox submission-checkbox"/>
</td>
<td class="cell-id">{{ submission.id|default(0) }}</td>
<td class="cell-useful">
{% if submission.is_useful == 'yes' %}
<span class="useful-display useful-yes">
{{ 'Yes'|t }}</span>
{% else %}
<span class="useful-display useful-no">
{{ 'No'|t }}</span>
{% endif %}
</td>
<td class="cell-reasons">
{% if submission.reasons %}
<ul class="reasons-list">
{% for reason in submission.reasons %}
<li>{{ reason|striptags|escape|t }}</li>
{% endfor %}
</ul>
{% else %}
<em class="no-reasons">{{ 'None'|t }}</em>
{% endif %}
</td>
<td class="cell-feedback">
{% if submission.feedback %}
<div class="feedback-text" title="{{ submission.feedback|striptags|escape }}">
{{ submission.feedback|striptags|escape|length > 80 ? submission.feedback|striptags|escape|slice(0, 80) ~ '...' : submission.feedback|striptags|escape }}
</div>
{% else %}
<em class="no-feedback">{{ 'No feedback'|t }}</em>
{% endif %}
</td>
<td class="cell-gender">
{% if submission.gender == 'male' %}
{{ 'Male'|t }}
{% elseif submission.gender == 'female' %}
{{ 'Female'|t }}
{% else %}
{{ submission.gender|default('—')|capitalize }}
{% endif %}
</td>
<td class="cell-url">
<a href="{{ submission.url|default('/') }}" target="_blank" class="url-link" title="{{ submission.url|default('/') }}">
{{ submission.url|default('/')|length > 40 ? submission.url|default('/')|slice(0, 40) ~ '...' : submission.url|default('/') }}
</a>
</td>
<td class="cell-user">
{% if submission.user_id and submission.user_id is not null %}
{{ 'User'|t }}
#{{ submission.user_id }}
{% else %}
<span class="anonymous-badge">{{ 'Anonymous'|t }}</span>
{% endif %}
</td>
<td class="cell-date">{{ submission.created_formatted|default('') }}</td>
<td class="cell-operations">
<div class="dropbutton-wrapper dropbutton-multiple">
<div class="dropbutton-widget">
<ul class="dropbutton">
<li class="dropbutton-action">
<a href="{{ path('dga_feedback.admin.edit', {'id': submission.id}) }}">{{ 'Edit'|t }}</a>
</li>
<li class="dropbutton-toggle">
<button type="button" class="dropbutton__toggle" aria-label="{{ 'List additional actions'|t }}" aria-expanded="false">
<span class="dropbutton__arrow">
<span class="visually-hidden">{{ 'List additional actions'|t }}</span>
</span>
</button>
</li>
</ul>
<ul class="dropbutton dropbutton-multiple dropbutton-secondary">
<li class="dropbutton-action">
<a href="{{ path('dga_feedback.admin.delete', {'id': submission.id}) }}">{{ 'Delete'|t }}</a>
</li>
</ul>
</div>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{# Pagination #}
{% if total_count > limit %}
<div class="pager-wrapper">
{{ pager }}
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
