Phase 1: Responsive tables and Tailwind CSS standardization

- Added responsive table styles with mobile card layouts to base.html
- Converted all templates from Bootstrap/plain CSS to Tailwind CSS
- Implemented responsive tables with horizontal scroll on desktop
- Added mobile card layouts for better readability on small screens
- Standardized form layouts with Tailwind responsive grid
- Updated all 15 templates for consistent styling:
  * base.html (responsive table CSS)
  * index.html (responsive tables + mobile cards)
  * students.html (Bootstrap -> Tailwind)
  * student_detail.html (Bootstrap -> Tailwind)
  * add_student.html (Bootstrap -> Tailwind)
  * edit_student.html (Bootstrap -> Tailwind)
  * import_students.html (Bootstrap -> Tailwind)
  * loan_tablet.html (Bootstrap -> Tailwind)
  * history.html (plain CSS -> Tailwind)
  * non_loanable_devices.html (plain CSS -> Tailwind)
  * add_non_loanable_device.html (plain CSS -> Tailwind)
  * edit_non_loanable_device.html (plain CSS -> Tailwind)
  * add_tablet.html (plain CSS -> Tailwind)
  * add_user.html (plain CSS -> Tailwind)
  * project_management.html (plain CSS -> Tailwind)
This commit is contained in:
ijuanes 2026-06-24 15:53:55 +01:00
parent 1b28021134
commit 06da5bdf2f
15 changed files with 1766 additions and 1229 deletions

View file

@ -3,179 +3,175 @@
{% block title %}{{ gettext('Student Details') }} - {{ student.full_name }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('list_students') }}">{{ gettext('Students') }}</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ student.full_name }}</li>
</ol>
</nav>
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="page-title mb-0">{{ student.full_name }}</h1>
<div class="btn-group" role="group">
<a href="{{ url_for('edit_student', student_id=student.id) }}" class="btn btn-warning">
<i class="bi bi-pencil"></i> {{ gettext('Edit') }}
</a>
<a href="{{ url_for('delete_student', student_id=student.id) }}" class="btn btn-danger"
onclick="return confirm('{{ gettext('Are you sure you want to delete this student?') }}')">
<i class="bi bi-trash"></i> {{ gettext('Delete') }}
</a>
</div>
<div class="space-y-6">
{# Breadcrumb #}
<nav class="flex" aria-label="Breadcrumb">
<ol class="flex items-center space-x-2">
<li>
<a href="{{ url_for('list_students') }}" class="text-cobalt-600 hover:text-cobalt-800">{{ gettext('Students') }}</a>
</li>
<li>
<svg class="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
</svg>
</li>
<li>
<span class="text-gray-500">{{ student.full_name }}</span>
</li>
</ol>
</nav>
{# Student Header #}
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div>
<h1 class="text-2xl font-bold text-gray-900">{{ student.full_name }}</h1>
<p class="text-gray-600 mt-1">{{ gettext('Student Details') }}</p>
</div>
<div class="flex gap-2">
<a href="{{ url_for('edit_student', student_id=student.id) }}"
class="inline-flex items-center gap-2 px-4 py-2 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600 transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
{{ gettext('Edit') }}
</a>
<a href="{{ url_for('delete_student', student_id=student.id) }}"
onclick="return confirm('{{ gettext('Are you sure you want to delete this student?') }}')"
class="inline-flex items-center gap-2 px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
</svg>
{{ gettext('Delete') }}
</a>
</div>
</div>
{# Student Information and Assigned Devices #}
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
{# Student Information Card #}
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="bg-cobalt-600 px-6 py-4">
<h2 class="text-lg font-semibold text-white">{{ gettext('Student Information') }}</h2>
</div>
<div class="row">
<!-- Student Information Card -->
<div class="col-12 col-lg-6 mb-4">
<div class="card h-100">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">{{ gettext('Student Information') }}</h5>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-4">{{ gettext('ID') }}:</dt>
<dd class="col-sm-8">{{ student.id }}</dd>
<dt class="col-sm-4">{{ gettext('CIAL Code') }}:</dt>
<dd class="col-sm-8">{{ student.cial_code }}</dd>
<dt class="col-sm-4">{{ gettext('NIF/NIE/Passport') }}:</dt>
<dd class="col-sm-8">{{ student.nif_nie_passport or '-' }}</dd>
<dt class="col-sm-4">{{ gettext('Registration Number') }}:</dt>
<dd class="col-sm-8">{{ student.registration_number or '-' }}</dd>
<dt class="col-sm-4">{{ gettext('File Number') }}:</dt>
<dd class="col-sm-8">{{ student.file_number or '-' }}</dd>
<dt class="col-sm-4">{{ gettext('Order Number') }}:</dt>
<dd class="col-sm-8">{{ student.order_number or '-' }}</dd>
<dt class="col-sm-4">{{ gettext('Full Name') }}:</dt>
<dd class="col-sm-8">{{ student.full_name }}</dd>
<dt class="col-sm-4">{{ gettext('First Name') }}:</dt>
<dd class="col-sm-8">{{ student.first_name }}</dd>
<dt class="col-sm-4">{{ gettext('Last Name') }}:</dt>
<dd class="col-sm-8">{{ student.last_name }}</dd>
<dt class="col-sm-4">{{ gettext('Birth Date') }}:</dt>
<dd class="col-sm-8">{{ student.birth_date or '-' }}</dd>
<dt class="col-sm-4">{{ gettext('Gender') }}:</dt>
<dd class="col-sm-8">{{ student.gender or '-' }}</dd>
<dt class="col-sm-4">{{ gettext('Study Group') }}:</dt>
<dd class="col-sm-8">{{ student.study_group or '-' }}</dd>
<dt class="col-sm-4">{{ gettext('Created') }}:</dt>
<dd class="col-sm-8">{{ student.created_at }}</dd>
<dt class="col-sm-4">{{ gettext('Updated') }}:</dt>
<dd class="col-sm-8">{{ student.updated_at }}</dd>
</dl>
</div>
<div class="p-6">
<dl class="space-y-4">
<div class="flex flex-col sm:flex-row sm:items-center">
<dt class="text-sm font-medium text-gray-500 sm:w-1/3">{{ gettext('ID') }}:</dt>
<dd class="text-sm text-gray-900 sm:w-2/3">{{ student.id }}</dd>
</div>
</div>
<!-- Assigned Devices Card -->
<div class="col-12 col-lg-6 mb-4">
<div class="card h-100">
<div class="card-header bg-info text-white">
<h5 class="mb-0">{{ gettext('Assigned Devices') }}</h5>
</div>
<div class="card-body">
<!-- Assigned Tablets -->
<h6>{{ gettext('Tablets') }}</h6>
{% if assigned_tablets %}
<ul class="list-group list-group-flush mb-3">
{% for tablet in assigned_tablets %}
<li class="list-group-item">
<strong>{{ tablet.brand }} {{ tablet.model }}</strong><br>
<small class="text-muted">{{ gettext('Serial:') }} {{ tablet.serial_number }}</small><br>
<small class="text-muted">{{ gettext('Status:') }} {{ tablet.status }}</small>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted">{{ gettext('No tablets assigned') }}</p>
{% endif %}
<!-- Assigned Non-Loanable Devices -->
<h6>{{ gettext('Other Devices') }}</h6>
{% if assigned_devices %}
<ul class="list-group list-group-flush">
{% for device in assigned_devices %}
<li class="list-group-item">
<strong>{{ device.brand }} {{ device.model }}</strong><br>
<small class="text-muted">{{ gettext('Type:') }} {{ device.device_type }}</small><br>
<small class="text-muted">{{ gettext('Serial:') }} {{ device.serial_number }}</small>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted">{{ gettext('No other devices assigned') }}</p>
{% endif %}
</div>
<div class="flex flex-col sm:flex-row sm:items-center">
<dt class="text-sm font-medium text-gray-500 sm:w-1/3">{{ gettext('CIAL Code') }}:</dt>
<dd class="text-sm text-gray-900 sm:w-2/3">
<code class="bg-gray-100 px-2 py-1 rounded">{{ student.cial_code }}</code>
</dd>
</div>
</div>
<div class="flex flex-col sm:flex-row sm:items-center">
<dt class="text-sm font-medium text-gray-500 sm:w-1/3">{{ gettext('NIF/NIE/Passport') }}:</dt>
<dd class="text-sm text-gray-900 sm:w-2/3">{{ student.nif_nie_passport or '-' }}</dd>
</div>
<div class="flex flex-col sm:flex-row sm:items-center">
<dt class="text-sm font-medium text-gray-500 sm:w-1/3">{{ gettext('Registration Number') }}:</dt>
<dd class="text-sm text-gray-900 sm:w-2/3">{{ student.registration_number or '-' }}</dd>
</div>
<div class="flex flex-col sm:flex-row sm:items-center">
<dt class="text-sm font-medium text-gray-500 sm:w-1/3">{{ gettext('File Number') }}:</dt>
<dd class="text-sm text-gray-900 sm:w-2/3">{{ student.file_number or '-' }}</dd>
</div>
<div class="flex flex-col sm:flex-row sm:items-center">
<dt class="text-sm font-medium text-gray-500 sm:w-1/3">{{ gettext('Order Number') }}:</dt>
<dd class="text-sm text-gray-900 sm:w-2/3">{{ student.order_number or '-' }}</dd>
</div>
<div class="flex flex-col sm:flex-row sm:items-center">
<dt class="text-sm font-medium text-gray-500 sm:w-1/3">{{ gettext('Full Name') }}:</dt>
<dd class="text-sm text-gray-900 sm:w-2/3">{{ student.full_name }}</dd>
</div>
<div class="flex flex-col sm:flex-row sm:items-center">
<dt class="text-sm font-medium text-gray-500 sm:w-1/3">{{ gettext('First Name') }}:</dt>
<dd class="text-sm text-gray-900 sm:w-2/3">{{ student.first_name }}</dd>
</div>
<div class="flex flex-col sm:flex-row sm:items-center">
<dt class="text-sm font-medium text-gray-500 sm:w-1/3">{{ gettext('Last Name') }}:</dt>
<dd class="text-sm text-gray-900 sm:w-2/3">{{ student.last_name }}</dd>
</div>
<div class="flex flex-col sm:flex-row sm:items-center">
<dt class="text-sm font-medium text-gray-500 sm:w-1/3">{{ gettext('Birth Date') }}:</dt>
<dd class="text-sm text-gray-900 sm:w-2/3">{{ student.birth_date or '-' }}</dd>
</div>
<div class="flex flex-col sm:flex-row sm:items-center">
<dt class="text-sm font-medium text-gray-500 sm:w-1/3">{{ gettext('Gender') }}:</dt>
<dd class="text-sm text-gray-900 sm:w-2/3">{{ student.gender or '-' }}</dd>
</div>
<div class="flex flex-col sm:flex-row sm:items-center">
<dt class="text-sm font-medium text-gray-500 sm:w-1/3">{{ gettext('Study Group') }}:</dt>
<dd class="text-sm text-gray-900 sm:w-2/3">{{ student.study_group or '-' }}</dd>
</div>
<div class="flex flex-col sm:flex-row sm:items-center">
<dt class="text-sm font-medium text-gray-500 sm:w-1/3">{{ gettext('Created') }}:</dt>
<dd class="text-sm text-gray-900 sm:w-2/3">{{ student.created_at }}</dd>
</div>
<div class="flex flex-col sm:flex-row sm:items-center">
<dt class="text-sm font-medium text-gray-500 sm:w-1/3">{{ gettext('Updated') }}:</dt>
<dd class="text-sm text-gray-900 sm:w-2/3">{{ student.updated_at }}</dd>
</div>
</dl>
</div>
<!-- Loan History Card -->
<div class="col-12 mb-4">
<div class="card">
<div class="card-header bg-success text-white">
<h5 class="mb-0">{{ gettext('Loan History') }}</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0">
<thead class="table-light">
<tr>
<th>{{ gettext('ID') }}</th>
<th>{{ gettext('Tablet') }}</th>
<th>{{ gettext('Loan Date') }}</th>
<th>{{ gettext('Return Date') }}</th>
<th>{{ gettext('Status') }}</th>
</tr>
</thead>
<tbody>
{% if loans %}
{% for loan in loans %}
<tr>
<td>{{ loan.id }}</td>
<td>{{ loan.brand }} {{ loan.model }} ({{ loan.serial_number }})</td>
<td>{{ loan.loan_date }}</td>
<td>{{ loan.return_date or '-' }}</td>
<td>
<span class="badge bg-{{ 'success' if loan.status == 'returned' else 'primary' }}">
{{ loan.status }}
</span>
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="5" class="text-center text-muted">
{{ gettext('No loan history') }}
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
{# Assigned Devices Card #}
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="bg-green-600 px-6 py-4">
<h2 class="text-lg font-semibold text-white">{{ gettext('Assigned Devices') }}</h2>
</div>
<div class="p-6">
{# Assigned Tablets #}
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wider mb-3">{{ gettext('Tablets') }}</h3>
{% if assigned_tablets %}
<div class="space-y-3 mb-6">
{% for tablet in assigned_tablets %}
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div>
<div class="font-medium text-gray-900">{{ tablet.brand }} {{ tablet.model }}</div>
<div class="text-sm text-gray-500">
{{ gettext('Serial:') }} {{ tablet.serial_number }}
</div>
<div class="text-sm text-gray-500">
{{ gettext('Status:') }}
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium
{% if tablet.status == 'available' %}bg-green-100 text-green-800
{% elif tablet.status == 'loaned' %}bg-yellow-100 text-yellow-800
{% else %}bg-gray-100 text-gray-800{% endif %}">
{{ tablet.status }}
</span>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="text-center">
<a href="{{ url_for('list_students') }}" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> {{ gettext('Back to Students') }}
</a>
{% else %}
<p class="text-gray-500 text-sm mb-6">{{ gettext('No tablets assigned.') }}</p>
{% endif %}
{# Assigned Non-Loanable Devices #}
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wider mb-3">{{ gettext('Non-Loanable Devices') }}</h3>
{% if assigned_devices %}
<div class="space-y-3">
{% for device in assigned_devices %}
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div>
<div class="font-medium text-gray-900">{{ device.brand }} {{ device.model }}</div>
<div class="text-sm text-gray-500">
{{ gettext('Serial:') }} {{ device.serial_number }}
</div>
<div class="text-sm text-gray-500">
{{ gettext('Type:') }} {{ device.device_type }}
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-gray-500 text-sm">{{ gettext('No non-loanable devices assigned.') }}</p>
{% endif %}
</div>
</div>
</div>