add entry manually
This commit is contained in:
47
app.py
47
app.py
@@ -513,6 +513,12 @@ def logbook():
|
||||
if not is_logged_in(): return redirect(url_for('login'))
|
||||
user_id = get_user_id()
|
||||
|
||||
# Fetch activities for the manual entry dropdown
|
||||
activities = list(db.activities.find({'user_id': user_id}))
|
||||
# Ensure keys exist for template using them safely
|
||||
for a in activities:
|
||||
if 'subcategories' not in a: a['subcategories'] = []
|
||||
|
||||
# --- Statistics Logic ---
|
||||
time_range = request.args.get('range', '24h')
|
||||
now = datetime.now()
|
||||
@@ -616,7 +622,46 @@ def logbook():
|
||||
entry['duration_str'] = str(duration).split('.')[0]
|
||||
entry['is_running'] = True
|
||||
|
||||
return render_template('logbook.html', log=log, chart_data=chart_data, current_range=time_range, total_time_display=total_time_display)
|
||||
return render_template('logbook.html', log=log, chart_data=chart_data, current_range=time_range, total_time_display=total_time_display, activities=activities)
|
||||
|
||||
@app.route('/add_manual_entry', methods=['POST'])
|
||||
def add_manual_entry():
|
||||
if not is_logged_in(): return redirect(url_for('login'))
|
||||
user_id = get_user_id()
|
||||
|
||||
activity_id = request.form['activity_id']
|
||||
start_str = request.form['start_time']
|
||||
end_str = request.form['end_time']
|
||||
note = request.form.get('note', '')
|
||||
|
||||
# Check both inputs (one will be disabled in frontend, but check priority)
|
||||
subcategory = request.form.get('subcategory_select')
|
||||
if not subcategory:
|
||||
subcategory = request.form.get('subcategory', '')
|
||||
|
||||
try:
|
||||
start_time = datetime.strptime(start_str, '%Y-%m-%dT%H:%M')
|
||||
end_time = datetime.strptime(end_str, '%Y-%m-%dT%H:%M') if end_str else datetime.now()
|
||||
|
||||
# Validation: End time cannot be before start time
|
||||
if end_time < start_time:
|
||||
flash('End time cannot be before start time')
|
||||
return redirect(url_for('logbook'))
|
||||
|
||||
except ValueError:
|
||||
flash('Invalid date format')
|
||||
return redirect(url_for('logbook'))
|
||||
|
||||
db.time_entries.insert_one({
|
||||
'user_id': user_id,
|
||||
'activity_id': ObjectId(activity_id),
|
||||
'start_time': start_time,
|
||||
'end_time': end_time,
|
||||
'note': note,
|
||||
'subcategory': subcategory
|
||||
})
|
||||
|
||||
return redirect(url_for('logbook'))
|
||||
|
||||
@app.route('/logbook/<entry_id>', methods=['GET', 'POST'])
|
||||
def log_entry_detail(entry_id):
|
||||
|
||||
@@ -6,6 +6,59 @@
|
||||
<div style="flex: 1; min-width: 300px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
|
||||
<h2 style="margin: 0;">Logbook</h2>
|
||||
<button onclick="document.getElementById('manualEntryForm').style.display = document.getElementById('manualEntryForm').style.display === 'none' ? 'block' : 'none'"
|
||||
class="btn" style="padding: 5px 12px; font-weight: bold;">
|
||||
+ Add Entry
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Manual Entry Form (Hidden by default) -->
|
||||
<div id="manualEntryForm" style="display: none; margin-bottom: 2rem; background: #fff; padding: 1.5rem; border: 1px solid var(--border-dim); border-radius: var(--radius-md); box-shadow: 0 4px 6px rgba(0,0,0,0.05);">
|
||||
<h3 style="margin-top: 0; margin-bottom: 1rem; font-size: 1.1rem;">Add Log Entry Retrospectively</h3>
|
||||
<form action="{{ url_for('add_manual_entry') }}" method="POST">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem;">
|
||||
<div>
|
||||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem;">Activity</label>
|
||||
<select name="activity_id" id="activitySelect" onchange="updateSubcategories()" required style="width: 100%; padding: 8px; border: 1px solid var(--border-dim); border-radius: 4px;">
|
||||
<option value="" disabled selected>Select Activity</option>
|
||||
{% for activity in activities %}
|
||||
<option value="{{ activity._id }}" data-subcategories='{{ activity.get("subcategories", [])|tojson }}'>{{ activity.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem;">Subcategory</label>
|
||||
<!-- Text Input (Default / Fallback) -->
|
||||
<input type="text" id="subcatInput" name="subcategory" placeholder="e.g. Development" style="width: 100%; padding: 8px; border: 1px solid var(--border-dim); border-radius: 4px;">
|
||||
|
||||
<!-- Select Input (Hidden by default) -->
|
||||
<select id="subcatSelect" name="subcategory_select" disabled style="display: none; width: 100%; padding: 8px; border: 1px solid var(--border-dim); border-radius: 4px;">
|
||||
<option value="">-- Select Subcategory --</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem;">
|
||||
<div>
|
||||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem;">Start Time</label>
|
||||
<input type="datetime-local" name="start_time" required class="date-input" style="width: 100%; padding: 8px; border: 1px solid var(--border-dim); border-radius: 4px;">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem;">End Time</label>
|
||||
<input type="datetime-local" name="end_time" required class="date-input" style="width: 100%; padding: 8px; border: 1px solid var(--border-dim); border-radius: 4px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label style="display: block; margin-bottom: 0.5rem; font-size: 0.9rem;">Note</label>
|
||||
<textarea name="note" rows="2" style="width: 100%; padding: 8px; border: 1px solid var(--border-dim); border-radius: 4px; resize: vertical;"></textarea>
|
||||
</div>
|
||||
|
||||
<div style="text-align: right;">
|
||||
<button type="button" onclick="document.getElementById('manualEntryForm').style.display = 'none'" class="btn" style="background: transparent; color: #666; margin-right: 0.5rem;">Cancel</button>
|
||||
<button type="submit" class="btn">Save Entry</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Search Bar -->
|
||||
@@ -139,6 +192,63 @@
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
// Initialize datetime inputs with safe defaults if needed
|
||||
window.addEventListener('load', () => {
|
||||
const now = new Date();
|
||||
now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
|
||||
const nowStr = now.toISOString().slice(0, 16);
|
||||
|
||||
// Optionally set default values for inputs that are empty
|
||||
document.querySelectorAll('input[type="datetime-local"].date-input').forEach(input => {
|
||||
if (!input.value) input.value = nowStr;
|
||||
});
|
||||
|
||||
// Initialize subcategories check
|
||||
updateSubcategories();
|
||||
});
|
||||
|
||||
function updateSubcategories() {
|
||||
const activitySelect = document.getElementById('activitySelect');
|
||||
const subcatInput = document.getElementById('subcatInput');
|
||||
const subcatSelect = document.getElementById('subcatSelect');
|
||||
|
||||
const selectedOption = activitySelect.options[activitySelect.selectedIndex];
|
||||
let subcats = [];
|
||||
|
||||
try {
|
||||
if (selectedOption && selectedOption.dataset.subcategories) {
|
||||
subcats = JSON.parse(selectedOption.dataset.subcategories);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error parsing subcategories", e);
|
||||
}
|
||||
|
||||
if (subcats && subcats.length > 0) {
|
||||
// Show Dropdown
|
||||
subcatInput.style.display = 'none';
|
||||
subcatInput.disabled = true; // Disable so it's not sent
|
||||
|
||||
subcatSelect.style.display = 'block';
|
||||
subcatSelect.disabled = false;
|
||||
|
||||
// Clear and populate
|
||||
subcatSelect.innerHTML = '<option value="">-- Select Subcategory --</option>';
|
||||
subcats.forEach(sc => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = sc;
|
||||
opt.textContent = sc;
|
||||
subcatSelect.appendChild(opt);
|
||||
});
|
||||
} else {
|
||||
// Show Text Input
|
||||
subcatInput.style.display = 'block';
|
||||
subcatInput.disabled = false;
|
||||
|
||||
subcatSelect.style.display = 'none';
|
||||
subcatSelect.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Background Sync (Polls for new tasks or status changes)
|
||||
let lastContentHash = "";
|
||||
setInterval(() => {
|
||||
|
||||
Reference in New Issue
Block a user