added activity view
This commit is contained in:
202
FitnessSync/backend/templates/bike_setups.html
Normal file
202
FitnessSync/backend/templates/bike_setups.html
Normal file
@@ -0,0 +1,202 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Bike Setups</h2>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addSetupModal">
|
||||
<i class="bi bi-plus-lg"></i> Add Setup
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle" id="setupsTable">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Frame</th>
|
||||
<th>Chainring</th>
|
||||
<th>Rear Cog</th>
|
||||
<th>Gear Ratio</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="setupsTableBody">
|
||||
<!-- Populated by JS -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Modal -->
|
||||
<div class="modal fade" id="addSetupModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="setupModalLabel">Add Bike Setup</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="setupForm">
|
||||
<input type="hidden" id="setupId" name="id">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Name (Optional)</label>
|
||||
<input type="text" class="form-control" id="name" name="name" placeholder="e.g. Track Bike">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="frame" class="form-label">Frame</label>
|
||||
<input type="text" class="form-control" id="frame" name="frame" required placeholder="e.g. Dolan Pre Cursa">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="chainring" class="form-label">Chainring</label>
|
||||
<input type="number" class="form-control" id="chainring" name="chainring" required min="1" placeholder="Teeth">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="rearCog" class="form-label">Rear Cog</label>
|
||||
<input type="number" class="form-control" id="rearCog" name="rear_cog" required min="1" placeholder="Teeth">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveSetup()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
let currentSetups = [];
|
||||
const setupModal = new bootstrap.Modal(document.getElementById('addSetupModal'));
|
||||
|
||||
document.addEventListener('DOMContentLoaded', loadSetups);
|
||||
|
||||
async function loadSetups() {
|
||||
try {
|
||||
const response = await fetch('/api/bike-setups/');
|
||||
if (!response.ok) throw new Error('Failed to load setups');
|
||||
currentSetups = await response.json();
|
||||
renderTable();
|
||||
} catch (error) {
|
||||
showToast(error.message, 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
function renderTable() {
|
||||
const tbody = document.getElementById('setupsTableBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
currentSetups.forEach(setup => {
|
||||
const ratio = (setup.chainring / setup.rear_cog).toFixed(2);
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td>${setup.name || '-'}</td>
|
||||
<td>${setup.frame}</td>
|
||||
<td>${setup.chainring}t</td>
|
||||
<td>${setup.rear_cog}t</td>
|
||||
<td>${ratio}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary me-1" onclick="editSetup(${setup.id})">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="deleteSetup(${setup.id})">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
document.getElementById('setupForm').reset();
|
||||
document.getElementById('setupId').value = '';
|
||||
document.getElementById('setupModalLabel').textContent = 'Add Bike Setup';
|
||||
}
|
||||
|
||||
// Hook into modal show event to reset form if adding
|
||||
document.getElementById('addSetupModal').addEventListener('show.bs.modal', function (event) {
|
||||
if (!event.relatedTarget || event.relatedTarget.getAttribute('data-bs-target')) {
|
||||
// If triggered by button (not manual show for edit), reset
|
||||
// Actually better to just check if we set an ID
|
||||
}
|
||||
});
|
||||
|
||||
// Reset on close
|
||||
document.getElementById('addSetupModal').addEventListener('hidden.bs.modal', resetForm);
|
||||
|
||||
|
||||
function editSetup(id) {
|
||||
const setup = currentSetups.find(s => s.id === id);
|
||||
if (!setup) return;
|
||||
|
||||
document.getElementById('setupId').value = setup.id;
|
||||
document.getElementById('name').value = setup.name || '';
|
||||
document.getElementById('frame').value = setup.frame;
|
||||
document.getElementById('chainring').value = setup.chainring;
|
||||
document.getElementById('rearCog').value = setup.rear_cog;
|
||||
|
||||
document.getElementById('setupModalLabel').textContent = 'Edit Bike Setup';
|
||||
setupModal.show();
|
||||
}
|
||||
|
||||
async function saveSetup() {
|
||||
const id = document.getElementById('setupId').value;
|
||||
const data = {
|
||||
name: document.getElementById('name').value || null,
|
||||
frame: document.getElementById('frame').value,
|
||||
chainring: parseInt(document.getElementById('chainring').value),
|
||||
rear_cog: parseInt(document.getElementById('rearCog').value)
|
||||
};
|
||||
|
||||
if (!data.frame || !data.chainring || !data.rear_cog) {
|
||||
showToast('Please fill in all required fields', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const method = id ? 'PUT' : 'POST';
|
||||
const url = id ? `/api/bike-setups/${id}` : '/api/bike-setups/';
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to save setup');
|
||||
|
||||
showToast('Setup saved successfully', 'success');
|
||||
setupModal.hide();
|
||||
loadSetups();
|
||||
} catch (error) {
|
||||
showToast(error.message, 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSetup(id) {
|
||||
if (!confirm('Are you sure you want to delete this setup?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/bike-setups/${id}`, { method: 'DELETE' });
|
||||
if (!response.ok) throw new Error('Failed to delete setup');
|
||||
|
||||
showToast('Setup deleted', 'success');
|
||||
loadSetups();
|
||||
} catch (error) {
|
||||
showToast(error.message, 'danger');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user