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

@ -1,213 +1,122 @@
{% 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 class="max-w-6xl mx-auto space-y-6">
{# Page Header #}
<div>
<h1 class="text-2xl font-bold text-gray-900">{{ _('Project Management - Development Notes') }}</h1>
<p class="text-gray-600 mt-1">{{ _('Internal development notes and project documentation') }}</p>
</div>
{# Editor Section #}
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="bg-cobalt-600 px-6 py-4 flex justify-between items-center">
<h2 class="text-lg font-semibold text-white">{{ _('Editor') }}</h2>
<div class="flex gap-2">
<button onclick="saveNotes()"
class="inline-flex items-center gap-2 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-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="M5 13l4 4L19 7"></path>
</svg>
{{ _('Save Notes') }}
</button>
<button onclick="loadNotes()"
class="inline-flex items-center gap-2 px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 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="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
{{ _('Reload') }}
</button>
</div>
<div class="editor-row">
<div class="editor-col">
<h3>{{ _('Editor') }}</h3>
<textarea id="markdown-editor" class="markdown-editor"
</div>
<div class="p-6">
<div id="status" class="text-sm text-gray-500 italic mb-4"></div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div>
<h3 class="text-lg font-semibold text-gray-800 mb-3">{{ _('Editor') }}</h3>
<textarea id="markdown-editor" rows="20"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-cobalt-500 focus:border-cobalt-500 font-mono text-sm"
placeholder="{{ _('Write your markdown notes here...') }}"></textarea>
</div>
<div class="editor-col">
<h3>{{ _('Preview') }}</h3>
<div id="markdown-preview" class="markdown-preview"></div>
<div>
<h3 class="text-lg font-semibold text-gray-800 mb-3">{{ _('Preview') }}</h3>
<div id="markdown-preview"
class="w-full min-h-[400px] p-4 border border-gray-300 rounded-md bg-gray-50 overflow-y-auto prose prose-sm max-w-none"></div>
</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';
<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();
// 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);
});
// 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 })
})
});
function loadNotes() {
fetch('/get_notes/' + NOTES_FILE)
.then(response => {
if (response.ok) {
statusEl.textContent = '{{ _("Saved successfully!") }}';
statusEl.style.color = '#4CAF50';
} else {
statusEl.textContent = '{{ _("Error saving") }}';
statusEl.style.color = '#f44336';
if (!response.ok) {
throw new Error('File not found, using default');
}
setTimeout(() => {
statusEl.textContent = '';
statusEl.style.color = '';
}, 2000);
return response.json();
})
.then(data => {
document.getElementById('markdown-editor').value = data.content;
document.getElementById('markdown-preview').innerHTML = marked.parse(data.content);
showStatus('{{ _("Notes loaded successfully") }}');
})
.catch(error => {
statusEl.textContent = '{{ _("Error") }}: ' + error.message;
statusEl.style.color = '#f44336';
setTimeout(() => {
statusEl.textContent = '';
statusEl.style.color = '';
}, 2000);
console.error('Error loading notes:', error);
showStatus('{{ _("Error loading notes") }}', true);
});
}
// 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;
}
/* ============================================
RESPONSIVE DESIGN FOR PROJECT MANAGEMENT
============================================ */
@media (max-width: 768px) {
.editor-row {
flex-direction: column;
}
function saveNotes() {
const content = document.getElementById('markdown-editor').value;
fetch('/save_notes/' + NOTES_FILE, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ content: content })
})
.then(response => response.json())
.then(data => {
if (data.success) {
showStatus('{{ _("Notes saved successfully") }}');
} else {
showStatus('{{ _("Error saving notes") }}: ' + data.error, true);
}
.markdown-editor,
.markdown-preview {
height: 350px;
}
}
})
.catch(error => {
console.error('Error saving notes:', error);
showStatus('{{ _("Error saving notes") }}', true);
});
}
function showStatus(message, isError = false) {
const status = document.getElementById('status');
status.textContent = message;
status.className = 'text-sm italic ' + (isError ? 'text-red-600' : 'text-green-600');
@media (max-width: 480px) {
.editor-controls {
flex-direction: column;
gap: 0.5rem;
}
.editor-controls button {
width: 100%;
}
}
</style>
// Clear status after 3 seconds
setTimeout(() => {
status.textContent = '';
status.className = 'text-sm text-gray-500 italic';
}, 3000);
}
</script>
{% endblock %}