- Add mobile-first responsive CSS to base.html - Wrap all tables in .table-container for horizontal scrolling on mobile - Add breakpoints for phones (576px), tablets (768px), desktops (992px, 1200px) - Improve mobile navigation (vertical stack) and form layouts (full-width) - Add print styles for clean printing - Update project_management.html editor for mobile - Document all changes in docs/RESPONSIVE_CSS.md No backend or JavaScript changes - pure CSS solution. For 5 internal technical users, this is simpler and more appropriate than HTMX or SPA approaches. Solves mobile accessibility issue with minimal changes.
279 lines
10 KiB
HTML
279 lines
10 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<h2>User Loans</h2>
|
|
<p class="relationship-note">
|
|
<strong>Relationship:</strong> One user can loan multiple tablets (one-to-many).
|
|
This page demonstrates the many-to-many relationship between users and tablets via the loans table.
|
|
</p>
|
|
|
|
<!-- Search Box -->
|
|
<div class="search-container">
|
|
<input type="text" id="searchInput" placeholder="Search by user name, identification, tablet brand, model, or serial number..."
|
|
onkeyup="filterUsers()">
|
|
<button onclick="clearSearch()" class="btn btn-clear">Clear</button>
|
|
</div>
|
|
|
|
<div class="user-loans-container" id="userLoansContainer">
|
|
{% for user_data in users_with_loans %}
|
|
{% set user = user_data.user %}
|
|
{% set loans = user_data.loans %}
|
|
{% set active_loans = loans|selectattr('status', 'equalto', 'active')|list %}
|
|
{% set returned_loans = loans|selectattr('status', 'equalto', 'returned')|list %}
|
|
|
|
<div class="user-card"
|
|
data-search="{{ user.name|lower }} {{ user.identification|lower }} {% for loan in loans %}{{ loan.brand|lower }} {{ loan.model|lower }} {{ loan.serial_number|lower }} {% endfor %}">
|
|
<div class="user-header">
|
|
<h3>{{ user.name }} ({{ user.identification }})</h3>
|
|
<span class="loan-count">
|
|
{{ active_loans|length }} active, {{ returned_loans|length }} past
|
|
</span>
|
|
</div>
|
|
|
|
{% if active_loans %}
|
|
<div class="loans-section current-loans">
|
|
<h4>Current Loans</h4>
|
|
<div class="table-container">
|
|
<table class="loans-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Tablet</th>
|
|
<th>Serial Number</th>
|
|
<th>Loan Date</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for loan in active_loans %}
|
|
<tr class="loan-row status-{{ loan.status }}">
|
|
<td>{{ loan.brand }} {{ loan.model }}</td>
|
|
<td>{{ loan.serial_number }}</td>
|
|
<td>{{ loan.loan_date }}</td>
|
|
<td>
|
|
<span class="status-badge status-{{ loan.status }}">
|
|
{{ loan.status }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if returned_loans %}
|
|
<div class="loans-section past-loans">
|
|
<h4>
|
|
<button class="expand-toggle" onclick="togglePastLoans(this)">
|
|
▶ Past Loans ({{ returned_loans|length }})
|
|
</button>
|
|
</h4>
|
|
<div class="past-loans-content" style="display: none;">
|
|
<div class="table-container">
|
|
<table class="loans-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Tablet</th>
|
|
<th>Serial Number</th>
|
|
<th>Loan Date</th>
|
|
<th>Return Date</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for loan in returned_loans %}
|
|
<tr class="loan-row status-{{ loan.status }}">
|
|
<td>{{ loan.brand }} {{ loan.model }}</td>
|
|
<td>{{ loan.serial_number }}</td>
|
|
<td>{{ loan.loan_date }}</td>
|
|
<td>{{ loan.return_date or '-' }}</td>
|
|
<td>
|
|
<span class="status-badge status-{{ loan.status }}">
|
|
{{ loan.status }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if not active_loans and not returned_loans %}
|
|
<p class="no-loans">No loans recorded for this user.</p>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
|
|
{% if users_with_loans|length == 0 %}
|
|
<p class="no-data">No users found. Add users and loan tablets to see data here.</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<script>
|
|
function filterUsers() {
|
|
const search = document.getElementById('searchInput').value.toLowerCase();
|
|
const cards = document.querySelectorAll('.user-card');
|
|
|
|
cards.forEach(card => {
|
|
const searchText = card.getAttribute('data-search') || '';
|
|
card.style.display = searchText.includes(search) ? '' : 'none';
|
|
});
|
|
}
|
|
|
|
function clearSearch() {
|
|
document.getElementById('searchInput').value = '';
|
|
filterUsers();
|
|
}
|
|
|
|
function togglePastLoans(button) {
|
|
const content = button.closest('.loans-section').querySelector('.past-loans-content');
|
|
const isExpanded = content.style.display !== 'none';
|
|
|
|
content.style.display = isExpanded ? 'none' : 'block';
|
|
const count = button.textContent.match(/\d+/)[0];
|
|
button.textContent = isExpanded ? '▶ Past Loans (' + count + ')' : '▼ Past Loans (' + count + ') ';
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.relationship-note {
|
|
background-color: #e8f5e9;
|
|
padding: 12px;
|
|
border-radius: 4px;
|
|
margin-bottom: 20px;
|
|
border-left: 4px solid #4CAF50;
|
|
}
|
|
|
|
.search-container {
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
.search-container input {
|
|
flex: 1;
|
|
padding: 10px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.btn-clear {
|
|
background-color: #999;
|
|
}
|
|
|
|
.user-loans-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20px;
|
|
}
|
|
|
|
.user-card {
|
|
background-color: white;
|
|
border: 1px solid #ddd;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
}
|
|
|
|
.user-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 15px;
|
|
padding-bottom: 10px;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.user-header h3 {
|
|
margin: 0;
|
|
color: #333;
|
|
}
|
|
|
|
.loan-count {
|
|
color: #666;
|
|
font-style: italic;
|
|
}
|
|
|
|
.loans-section {
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.loans-section h4 {
|
|
margin: 0 0 10px 0;
|
|
color: #555;
|
|
}
|
|
|
|
.expand-toggle {
|
|
background: none;
|
|
border: none;
|
|
color: #4CAF50;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
padding: 0;
|
|
}
|
|
|
|
.expand-toggle:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.loans-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.loans-table th,
|
|
.loans-table td {
|
|
padding: 10px;
|
|
text-align: left;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.loans-table th {
|
|
background-color: #f5f5f5;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.loan-row.status-active {
|
|
background-color: #fff8e1;
|
|
}
|
|
|
|
.loan-row.status-returned {
|
|
background-color: #f5f5f5;
|
|
}
|
|
|
|
.status-badge {
|
|
padding: 4px 8px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
text-transform: capitalize;
|
|
}
|
|
|
|
.status-badge.status-active {
|
|
background-color: #ffc107;
|
|
color: #000;
|
|
}
|
|
|
|
.status-badge.status-returned {
|
|
background-color: #9E9E9E;
|
|
color: white;
|
|
}
|
|
|
|
.no-loans {
|
|
color: #999;
|
|
font-style: italic;
|
|
text-align: center;
|
|
padding: 20px;
|
|
}
|
|
|
|
.no-data {
|
|
text-align: center;
|
|
padding: 40px;
|
|
color: #999;
|
|
}
|
|
</style>
|
|
{% endblock %}
|