Initial commit: Tablet lending system with user-tablet many-to-many relationship
- Database schema: tablets, users, loans (junction table) - CLI app: minimal_app.py - Web apps: app.py (Flask), simple_app.py (http.server) - Features: loan management, search, user loans view, project notes - Git structure: CONTRIBUTING.md, .gitignore Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
commit
8a8d2f02fa
25 changed files with 2885 additions and 0 deletions
30
templates/add_tablet.html
Normal file
30
templates/add_tablet.html
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Add New Tablet</h2>
|
||||
<form method="POST" action="/add_tablet">
|
||||
<div class="form-group">
|
||||
<label for="brand">Brand:</label>
|
||||
<input type="text" id="brand" name="brand" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="model">Model:</label>
|
||||
<input type="text" id="model" name="model" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="serial_number">Serial Number:</label>
|
||||
<input type="text" id="serial_number" name="serial_number" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notes">Notes:</label>
|
||||
<textarea id="notes" name="notes"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn">Add Tablet</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
30
templates/add_user.html
Normal file
30
templates/add_user.html
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Add New User</h2>
|
||||
<form method="POST" action="/add_user">
|
||||
<div class="form-group">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name" name="name" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">Email:</label>
|
||||
<input type="email" id="email" name="email">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="phone">Phone:</label>
|
||||
<input type="text" id="phone" name="phone">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="identification">Identification:</label>
|
||||
<input type="text" id="identification" name="identification" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn">Add User</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
153
templates/base.html
Normal file
153
templates/base.html
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tablet Management System</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
.nav {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.nav a {
|
||||
padding: 8px 16px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.nav a:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.section h2 {
|
||||
color: #4CAF50;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
}
|
||||
th, td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
th {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.btn {
|
||||
padding: 6px 12px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.btn:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.btn-danger {
|
||||
background-color: #f44336;
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background-color: #CC00CC;
|
||||
}
|
||||
.flash-message {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.flash-success {
|
||||
background-color: #dff0d8;
|
||||
color: #3c763d;
|
||||
border: 1px solid #d6e9c6;
|
||||
}
|
||||
.flash-error {
|
||||
background-color: #f2dede;
|
||||
color: #a94442;
|
||||
border: 1px solid #ebccd1;
|
||||
}
|
||||
form {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
textarea {
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Tablet Management System</h1>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="flash-message flash-{{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<div class="nav">
|
||||
<a href="/">Home</a>
|
||||
<a href="/add_tablet">Add Tablet</a>
|
||||
<a href="/add_user">Add User</a>
|
||||
<a href="/loan_tablet">Loan Tablet</a>
|
||||
<a href="/history">Loan History</a>
|
||||
<a href="/user_loans">User Loans</a>
|
||||
<a href="/project_management">Project Management</a>
|
||||
</div>
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
35
templates/history.html
Normal file
35
templates/history.html
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="section">
|
||||
<h2>Loan History</h2>
|
||||
{% if loans %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tablet</th>
|
||||
<th>Serial Number</th>
|
||||
<th>Borrower</th>
|
||||
<th>Loan Date</th>
|
||||
<th>Return Date</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for loan in loans %}
|
||||
<tr>
|
||||
<td>{{ loan.brand }} {{ loan.model }}</td>
|
||||
<td>{{ loan.serial_number }}</td>
|
||||
<td>{{ loan.name }}</td>
|
||||
<td>{{ loan.loan_date }}</td>
|
||||
<td>{{ loan.return_date or '-' }}</td>
|
||||
<td>{{ loan.status }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No loan history available.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
63
templates/index.html
Normal file
63
templates/index.html
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="section">
|
||||
<h2>Available Tablets</h2>
|
||||
{% if available_tablets %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Brand</th>
|
||||
<th>Model</th>
|
||||
<th>Serial Number</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for tablet in available_tablets %}
|
||||
<tr>
|
||||
<td>{{ tablet.brand }}</td>
|
||||
<td>{{ tablet.model }}</td>
|
||||
<td>{{ tablet.serial_number }}</td>
|
||||
<td>{{ tablet.notes or '-' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No available tablets.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Active Loans</h2>
|
||||
{% if active_loans %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tablet</th>
|
||||
<th>Serial Number</th>
|
||||
<th>Borrower</th>
|
||||
<th>Loan Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for loan in active_loans %}
|
||||
<tr>
|
||||
<td>{{ loan.brand }} {{ loan.model }}</td>
|
||||
<td>{{ loan.serial_number }}</td>
|
||||
<td>{{ loan.name }}</td>
|
||||
<td>{{ loan.loan_date }}</td>
|
||||
<td>
|
||||
<a href="/return_tablet/{{ loan.id }}" class="btn btn-danger">Return</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No active loans.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
78
templates/loan_tablet.html
Normal file
78
templates/loan_tablet.html
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Loan Tablet</h2>
|
||||
<form method="POST" action="/loan_tablet">
|
||||
<!-- Tablet Selection with Search -->
|
||||
<div class="form-group">
|
||||
<label for="tablet_id">Tablet:</label>
|
||||
<input type="text" id="tablet_search" class="search-input"
|
||||
placeholder="Search tablets (brand, model, or serial)..."
|
||||
onkeyup="filterDropdown('tablet_search', 'tablet_id')">
|
||||
<select id="tablet_id" name="tablet_id" required size="5">
|
||||
<option value="">Select a tablet</option>
|
||||
{% for tablet in available_tablets %}
|
||||
<option value="{{ tablet.id }}"
|
||||
data-search="{{ tablet.brand|lower }} {{ tablet.model|lower }} {{ tablet.serial_number|lower }}">
|
||||
{{ tablet.brand }} {{ tablet.model }} ({{ tablet.serial_number }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- User Selection with Search -->
|
||||
<div class="form-group">
|
||||
<label for="user_id">User:</label>
|
||||
<input type="text" id="user_search" class="search-input"
|
||||
placeholder="Search users (name or ID)..."
|
||||
onkeyup="filterDropdown('user_search', 'user_id')">
|
||||
<select id="user_id" name="user_id" required size="5">
|
||||
<option value="">Select a user</option>
|
||||
{% for user in users %}
|
||||
<option value="{{ user.id }}"
|
||||
data-search="{{ user.name|lower }} {{ user.identification|lower }}">
|
||||
{{ user.name }} ({{ user.identification }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn">Loan Tablet</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function filterDropdown(searchId, selectId) {
|
||||
const search = document.getElementById(searchId).value.toLowerCase();
|
||||
const select = document.getElementById(selectId);
|
||||
const options = select.options;
|
||||
|
||||
for (let i = 1; i < options.length; i++) {
|
||||
const searchText = options[i].getAttribute('data-search') || '';
|
||||
options[i].style.display = searchText.includes(search) ? '' : 'none';
|
||||
}
|
||||
|
||||
// Show first visible option if search is empty, otherwise keep selection
|
||||
if (search === '') {
|
||||
select.selectedIndex = 0;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
select[size] {
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
196
templates/project_management.html
Normal file
196
templates/project_management.html
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Project Management - Development Notes</h2>
|
||||
|
||||
<div class="project-management-container">
|
||||
<div class="editor-section">
|
||||
<div class="editor-controls">
|
||||
<button onclick="saveNotes()" class="btn">Save Notes</button>
|
||||
<button onclick="loadNotes()" class="btn">Reload</button>
|
||||
<span id="status" style="margin-left: 15px; font-style: italic;"></span>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="editor-col">
|
||||
<h3>Editor</h3>
|
||||
<textarea id="markdown-editor" class="markdown-editor"
|
||||
placeholder="Write your markdown notes here..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="editor-col">
|
||||
<h3>Preview</h3>
|
||||
<div id="markdown-preview" class="markdown-preview"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script>
|
||||
const NOTES_FILE = 'notes_development/project_notes.md';
|
||||
|
||||
// Initialize: load and render notes
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadNotes();
|
||||
|
||||
// Set up live preview
|
||||
const editor = document.getElementById('markdown-editor');
|
||||
const preview = document.getElementById('markdown-preview');
|
||||
|
||||
editor.addEventListener('input', function() {
|
||||
preview.innerHTML = marked.parse(editor.value);
|
||||
});
|
||||
});
|
||||
|
||||
function loadNotes() {
|
||||
fetch('/get_notes/' + NOTES_FILE)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('File not found, using default');
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then(content => {
|
||||
document.getElementById('markdown-editor').value = content;
|
||||
document.getElementById('markdown-preview').innerHTML = marked.parse(content);
|
||||
document.getElementById('status').textContent = 'Loaded successfully';
|
||||
setTimeout(() => {
|
||||
document.getElementById('status').textContent = '';
|
||||
}, 2000);
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('status').textContent = 'Using default content';
|
||||
setTimeout(() => {
|
||||
document.getElementById('status').textContent = '';
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
function saveNotes() {
|
||||
const content = document.getElementById('markdown-editor').value;
|
||||
const statusEl = document.getElementById('status');
|
||||
|
||||
fetch('/save_notes', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ content: content, filename: NOTES_FILE })
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
statusEl.textContent = 'Saved successfully!';
|
||||
statusEl.style.color = '#4CAF50';
|
||||
} else {
|
||||
statusEl.textContent = 'Error saving';
|
||||
statusEl.style.color = '#f44336';
|
||||
}
|
||||
setTimeout(() => {
|
||||
statusEl.textContent = '';
|
||||
statusEl.style.color = '';
|
||||
}, 2000);
|
||||
})
|
||||
.catch(error => {
|
||||
statusEl.textContent = 'Error: ' + error.message;
|
||||
statusEl.style.color = '#f44336';
|
||||
setTimeout(() => {
|
||||
statusEl.textContent = '';
|
||||
statusEl.style.color = '';
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-save every 30 seconds
|
||||
setInterval(() => {
|
||||
saveNotes();
|
||||
}, 30000);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.project-management-container {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.editor-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.editor-row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.editor-col {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.markdown-editor {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.markdown-preview {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: white;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.markdown-preview h1,
|
||||
.markdown-preview h2,
|
||||
.markdown-preview h3,
|
||||
.markdown-preview h4,
|
||||
.markdown-preview h5,
|
||||
.markdown-preview h6 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.markdown-preview pre {
|
||||
background-color: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.markdown-preview code {
|
||||
font-family: monospace;
|
||||
background-color: #f5f5f5;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.markdown-preview table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.markdown-preview th,
|
||||
.markdown-preview td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.editor-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
157
templates/user_loans.html
Normal file
157
templates/user_loans.html
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
{% 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>
|
||||
|
||||
<div class="user-loans-container">
|
||||
{% for user_data in users_with_loans %}
|
||||
{% set user = user_data.user %}
|
||||
{% set loans = user_data.loans %}
|
||||
|
||||
<div class="user-card">
|
||||
<div class="user-header">
|
||||
<h3>{{ user.name }} ({{ user.identification }})</h3>
|
||||
<span class="loan-count">{{ loans|length }} tablet{{ 's' if loans|length != 1 else '' }} loaned</span>
|
||||
</div>
|
||||
|
||||
{% if loans %}
|
||||
<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 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>
|
||||
{% else %}
|
||||
<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>
|
||||
|
||||
<style>
|
||||
.relationship-note {
|
||||
background-color: #e8f5e9;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
border-left: 4px solid #4CAF50;
|
||||
}
|
||||
|
||||
.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-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: #e8f5e9;
|
||||
}
|
||||
|
||||
.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: #4CAF50;
|
||||
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 %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue