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:
parent
1b28021134
commit
06da5bdf2f
15 changed files with 1766 additions and 1229 deletions
|
|
@ -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 %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue