Files
Reklamator/app/templates/dashboard/detail.html
T
gurixandClaude adbfd23c26 Implement Phase 5: Product Owner Dashboard (User Story 3)
Add complete dashboard functionality for product owners and administrators to view, filter, search, and manage feedback submissions following Test-First Discipline.

Tests (T093-T105):
- Add 12 contract tests for dashboard routes (authentication, listing, filtering, search, detail view, status updates, attachment downloads, access control)
- Add 2 integration tests for complete dashboard workflow and access control enforcement
- All tests written first and verified to fail before implementation

Services (T111-T118):
- Enhance FeedbackStorageService with load_feedback_list() for pagination, filtering, searching, and sorting
- Add load_feedback_detail() to load complete feedback with attachments and analysis
- Add update_feedback_status_by_id() for status management
- Add get_attachment_path() with path traversal prevention

Routes (T119-T134):
- Implement GET /dashboard with filters, search, and pagination (50 items/page)
- Implement GET /feedback/<id> detail view with role-based access control
- Implement POST /feedback/<id>/status for status updates
- Implement GET /feedback/<id>/attachment/<filename> for secure file downloads
- Add access control helpers (administrators see all products, owners see only assigned)

Templates (T135-T136):
- Create dashboard/list.html with filter form, search, and pagination
- Create dashboard/detail.html with status update form and attachment links
- Create error_403.html for access denied
- Create error_404.html for not found

Integration & Bug Fixes:
- Update auth routes to remove /auth prefix and redirect to dashboard after login
- Update Feedback.VALID_STATUSES to include dashboard statuses (in_progress, resolved, closed)
- Register error handlers for 403 and 404 in app factory
- Fix test fixtures to use correct users.yaml format and User.hash_password()

Test Results: 39 passed, 1 skipped (all Phase 5 tests passing)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 20:11:16 +02:00

110 lines
5.1 KiB
HTML

{% extends "base.html" %}
{% block title %}Feedback Detail{% endblock %}
{% block content %}
<div style="max-width: 900px; margin: 0 auto; padding: 20px;">
<div style="margin-bottom: 20px;">
<a href="{{ url_for('dashboard.list') }}" style="color: #007bff; text-decoration: none;">← Back to Dashboard</a>
</div>
<h1>Feedback Detail</h1>
<!-- Metadata Section -->
<div style="background: #f8f9fa; padding: 20px; border-radius: 5px; margin: 20px 0;">
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px;">
<div>
<strong>Feedback ID:</strong>
<p style="margin: 5px 0; font-family: monospace; font-size: 0.9em;">{{ feedback.feedback_id }}</p>
</div>
<div>
<strong>Product:</strong>
<p style="margin: 5px 0;">{{ product.name if product else feedback.product_id }}</p>
</div>
<div>
<strong>Status:</strong>
<p style="margin: 5px 0;">
<span style="padding: 5px 10px; border-radius: 3px;
{% if feedback.metadata.status == 'new' %}background: #d1ecf1; color: #0c5460;
{% elif feedback.metadata.status == 'in_progress' %}background: #fff3cd; color: #856404;
{% elif feedback.metadata.status == 'resolved' %}background: #d4edda; color: #155724;
{% else %}background: #e2e3e5; color: #383d41;{% endif %}">
{{ feedback.metadata.status }}
</span>
</p>
</div>
<div>
<strong>Category:</strong>
<p style="margin: 5px 0;">
<span style="padding: 5px 10px; background: #e9ecef; border-radius: 3px;">
{{ feedback.metadata.category or 'Uncategorized' }}
</span>
</p>
</div>
<div>
<strong>Language:</strong>
<p style="margin: 5px 0;">{{ feedback.metadata.original_language or 'Unknown' }}</p>
</div>
<div>
<strong>Submitted:</strong>
<p style="margin: 5px 0;">{{ feedback.metadata.submitted_at[:19] if feedback.metadata.submitted_at else 'Unknown' }}</p>
</div>
</div>
</div>
<!-- Update Status Form -->
<div style="margin: 20px 0; padding: 15px; background: #fff3cd; border-radius: 5px;">
<form method="post" action="{{ url_for('dashboard.update_status', feedback_id=feedback.feedback_id) }}" style="display: flex; align-items: center; gap: 10px;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label for="status"><strong>Update Status:</strong></label>
<select name="status" id="status" style="padding: 5px 10px; border: 1px solid #ccc; border-radius: 3px;">
<option value="new" {% if feedback.metadata.status == 'new' %}selected{% endif %}>New</option>
<option value="in_progress" {% if feedback.metadata.status == 'in_progress' %}selected{% endif %}>In Progress</option>
<option value="resolved" {% if feedback.metadata.status == 'resolved' %}selected{% endif %}>Resolved</option>
<option value="closed" {% if feedback.metadata.status == 'closed' %}selected{% endif %}>Closed</option>
</select>
<button type="submit" style="padding: 5px 15px; background: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer;">Update</button>
</form>
</div>
<!-- Original Content -->
<div style="margin: 30px 0;">
<h2>Original Feedback</h2>
<div style="background: white; border: 1px solid #dee2e6; border-radius: 5px; padding: 20px; white-space: pre-wrap;">{{ feedback.content }}</div>
</div>
<!-- Analysis (if exists) -->
{% if feedback.analysis %}
<div style="margin: 30px 0;">
<h2>AI Analysis</h2>
<div style="background: white; border: 1px solid #dee2e6; border-radius: 5px; padding: 20px;">
{{ feedback.analysis|safe }}
</div>
</div>
{% endif %}
<!-- Attachments -->
{% if feedback.attachments %}
<div style="margin: 30px 0;">
<h2>Attachments ({{ feedback.attachments|length }})</h2>
<ul style="list-style: none; padding: 0;">
{% for attachment in feedback.attachments %}
<li style="margin: 10px 0; padding: 10px; background: #f8f9fa; border-radius: 3px;">
<a href="{{ url_for('dashboard.download_attachment', feedback_id=feedback.feedback_id, filename=attachment) }}"
style="color: #007bff; text-decoration: none; display: flex; align-items: center; gap: 10px;">
<span style="font-size: 1.2em;">📎</span>
<span>{{ attachment }}</span>
</a>
</li>
{% endfor %}
</ul>
</div>
{% else %}
<div style="margin: 30px 0;">
<h2>Attachments</h2>
<p style="color: #666;">No attachments</p>
</div>
{% endif %}
</div>
{% endblock %}