add tasks driectly from tracker
This commit is contained in:
42
app.py
42
app.py
@@ -148,11 +148,15 @@ def add_activity():
|
||||
name = request.form['name']
|
||||
color = request.form.get('color', '#3498db')
|
||||
|
||||
# Parse subcategories
|
||||
subcats_str = request.form.get('subcategories_data', '')
|
||||
subcategories = [s.strip() for s in subcats_str.split(',') if s.strip()]
|
||||
|
||||
activity_id = db.activities.insert_one({
|
||||
'user_id': get_user_id(),
|
||||
'name': name,
|
||||
'color': color,
|
||||
'subcategories': []
|
||||
'subcategories': subcategories
|
||||
}).inserted_id
|
||||
|
||||
# Add optional default tasks (Revised from list input)
|
||||
@@ -427,6 +431,42 @@ def create_task():
|
||||
db.tasks.insert_one(task_doc)
|
||||
return redirect(url_for('tasks'))
|
||||
|
||||
@app.route('/create_task_quick', methods=['POST'])
|
||||
def create_task_quick():
|
||||
if not is_logged_in(): return jsonify({'error': 'auth'}), 401
|
||||
|
||||
name = request.form['name']
|
||||
activity_id = request.form.get('activity_id')
|
||||
subcategory = request.form.get('subcategory', '')
|
||||
|
||||
# Check if there is an active time entry to link immediately
|
||||
current_entry = db.time_entries.find_one({
|
||||
'user_id': get_user_id(),
|
||||
'end_time': None
|
||||
})
|
||||
|
||||
task_doc = {
|
||||
'user_id': get_user_id(),
|
||||
'name': name,
|
||||
'status': 'open',
|
||||
'is_template': False,
|
||||
'source': 'manual-quick',
|
||||
'created_at': datetime.now(),
|
||||
'comments': [],
|
||||
'subcategory': subcategory,
|
||||
'activity_id': ObjectId(activity_id) if activity_id else None,
|
||||
# If we have an active entry matching this activity, link it for this session
|
||||
'time_entry_id': current_entry['_id'] if current_entry and str(current_entry['activity_id']) == activity_id else None
|
||||
}
|
||||
|
||||
new_id = db.tasks.insert_one(task_doc).inserted_id
|
||||
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'task_id': str(new_id),
|
||||
'name': name
|
||||
})
|
||||
|
||||
@app.route('/task/<task_id>', methods=['GET', 'POST'])
|
||||
def task_detail(task_id):
|
||||
if not is_logged_in(): return redirect(url_for('login'))
|
||||
|
||||
@@ -184,25 +184,32 @@
|
||||
<div class="timer-display" id="timer" style="margin: 1rem 0 2rem 0;">00:00:00</div>
|
||||
|
||||
<!-- Tasks List (Redesigned) -->
|
||||
{% if tasks %}
|
||||
<div style="margin-bottom: 2rem;">
|
||||
<h4 style="font-size: 0.8rem; text-transform: uppercase; color: var(--text-secondary); letter-spacing: 0.05em; border-bottom: 1px solid var(--border-dim); padding-bottom: 5px;">Active Tasks</h4>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-dim); padding-bottom: 5px; margin-bottom: 10px;">
|
||||
<h4 style="font-size: 0.8rem; text-transform: uppercase; color: var(--text-secondary); letter-spacing: 0.05em; margin: 0;">Active Tasks</h4>
|
||||
<!-- Quick Add Button -->
|
||||
<button type="button" onclick="openQuickTaskModal()" style="background: transparent; border: 1px solid var(--border-dim); cursor: pointer; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; color: var(--text-secondary); margin-left: 10px;" title="Add Task">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="checklist-container" id="activeTasksList">
|
||||
{% for task in tasks %}
|
||||
<div class="checklist-item {% if task.status == 'completed' %}completed{% endif %}" id="task-item-{{ task._id }}">
|
||||
<input type="checkbox" id="task_{{ task._id }}"
|
||||
{% if task.status == 'completed' %}checked{% endif %}
|
||||
onchange="toggleTask('{{ task._id }}', this)">
|
||||
<label for="task_{{ task._id }}">
|
||||
<a href="{{ url_for('task_detail', task_id=task._id) }}" style="text-decoration: none; color: inherit;">
|
||||
{{ task.name }}
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if tasks %}
|
||||
{% for task in tasks %}
|
||||
<div class="checklist-item {% if task.status == 'completed' %}completed{% endif %}" id="task-item-{{ task._id }}">
|
||||
<input type="checkbox" id="task_{{ task._id }}"
|
||||
{% if task.status == 'completed' %}checked{% endif %}
|
||||
onchange="toggleTask('{{ task._id }}', this)">
|
||||
<label for="task_{{ task._id }}">
|
||||
<a href="{{ url_for('task_detail', task_id=task._id) }}" style="text-decoration: none; color: inherit;">
|
||||
{{ task.name }}
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form action="{{ url_for('stop_timer') }}" method="POST">
|
||||
<button type="submit" class="btn btn-danger" style="width: 100%; justify-content: center; padding: 12px;">Stop Activity</button>
|
||||
@@ -214,6 +221,10 @@
|
||||
|
||||
<!-- Scripts (Timer, Tasks, Modal) -->
|
||||
<script>
|
||||
// Global vars - MUST be declared before any functions that use them
|
||||
let currentActivityId = "{{ current_entry.activity_id if current_entry else '' }}";
|
||||
let currentSubcategory = "{{ current_entry.subcategory if current_entry else '' }}";
|
||||
|
||||
// Timer Logic
|
||||
let startTime = {% if current_entry %}{{ current_entry.start_time.timestamp() * 1000 }}{% else %}null{% endif %};
|
||||
let timerInterval;
|
||||
@@ -283,17 +294,30 @@
|
||||
document.getElementById('startModal').classList.remove('show');
|
||||
}
|
||||
|
||||
function closeQuickTaskModal() {
|
||||
document.getElementById('quickTaskModal').classList.remove('show');
|
||||
document.getElementById('quickTaskName').value = ''; // Clean up
|
||||
}
|
||||
|
||||
// Close on backdrop click
|
||||
document.getElementById('startModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) closeStartModal();
|
||||
});
|
||||
|
||||
document.getElementById('quickTaskModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) closeQuickTaskModal();
|
||||
});
|
||||
|
||||
// Start Activity Immediate Function
|
||||
function startActivityImmediate(element) {
|
||||
const id = element.getAttribute('data-id');
|
||||
const name = element.getAttribute('data-name');
|
||||
const color = element.getAttribute('data-color');
|
||||
const subcats = JSON.parse(element.getAttribute('data-subcategories') || '[]');
|
||||
|
||||
// Save ID globally for quick add
|
||||
currentActivityId = id;
|
||||
|
||||
// 1. Start Background Timer
|
||||
fetch('/start_timer_bg/' + id, { method: 'POST' })
|
||||
.then(res => res.json())
|
||||
@@ -321,6 +345,14 @@
|
||||
// Reset Note/Subcat display
|
||||
document.getElementById('activeNote').style.display = 'none';
|
||||
document.getElementById('activeSubcatContainer').style.display = 'none';
|
||||
document.getElementById('activeSubcatDisplay').innerText = ''; // Reset text
|
||||
currentSubcategory = ""; // Reset global
|
||||
|
||||
// Clear tasks list visually on new start (will define via sync or reload if templates exist)
|
||||
// Ideally we'd fetch templates here, but sync will catch them in 2s.
|
||||
// For smoother UX, we could inject template names if passed from backend.
|
||||
document.getElementById('activeTasksList').innerHTML = '';
|
||||
localTaskIds = []; // Reset local known tasks
|
||||
|
||||
// 3. Open Modal for Details
|
||||
openDetailsModal(name, subcats);
|
||||
@@ -328,6 +360,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Hook into form update to set these values when modal is submitted/skipped
|
||||
function openDetailsModal(name, subcats) {
|
||||
document.getElementById('modalTitle').innerText = name + " Started";
|
||||
|
||||
@@ -353,6 +386,58 @@
|
||||
document.querySelector('#startForm input[name="note"]').focus();
|
||||
}
|
||||
|
||||
// Open Quick Task Modal
|
||||
function openQuickTaskModal() {
|
||||
if(!currentActivityId) {
|
||||
alert("No active activity context found. Please start an activity first.");
|
||||
return;
|
||||
}
|
||||
document.getElementById('quickTaskModal').classList.add('show');
|
||||
document.getElementById('quickTaskName').focus();
|
||||
}
|
||||
|
||||
// Quick Add Form Submit
|
||||
function submitQuickTask(e) {
|
||||
e.preventDefault();
|
||||
const input = document.getElementById('quickTaskName');
|
||||
const val = input.value.trim();
|
||||
if(!val) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('name', val);
|
||||
formData.append('activity_id', currentActivityId);
|
||||
formData.append('subcategory', currentSubcategory);
|
||||
|
||||
fetch('/create_task_quick', { method: 'POST', body: formData })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if(data.status === 'success') {
|
||||
input.value = '';
|
||||
closeQuickTaskModal();
|
||||
|
||||
// Add to DOM
|
||||
const list = document.getElementById('activeTasksList');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'checklist-item';
|
||||
div.id = 'task-item-' + data.task_id;
|
||||
div.innerHTML = `
|
||||
<input type="checkbox" id="task_${data.task_id}"
|
||||
onchange="toggleTask('${data.task_id}', this)">
|
||||
<label for="task_${data.task_id}">
|
||||
<a href="/task/${data.task_id}" style="text-decoration: none; color: inherit;">
|
||||
${data.name}
|
||||
</a>
|
||||
</label>
|
||||
`;
|
||||
list.insertBefore(div, list.firstChild); // Add to top
|
||||
|
||||
// Register ID so sync doesn't reload
|
||||
localTaskIds.push(data.task_id);
|
||||
console.log("Quick task added. Sync updated.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// New Task List Logic for Activity Creation form (Reusing existing script)
|
||||
let newActivityTasks = [];
|
||||
function addNewTask() {
|
||||
@@ -395,15 +480,15 @@
|
||||
<div id="startModal" class="modal-backdrop">
|
||||
<div class="modal-card">
|
||||
<h3 id="modalTitle" style="margin-bottom: 1.5rem;">Activity Started</h3>
|
||||
<form id="startForm" action="{{ url_for('update_active_entry') }}" method="POST">
|
||||
<div id="subcategoryContainer" style="display: none; margin-bottom: 1rem;">
|
||||
<form id="startForm" onsubmit="submitDetailsForm(event)">
|
||||
<div id="subcategoryContainer" style="display: none; margin-bottom: 1rem;">
|
||||
<label>Subcategory / Context</label>
|
||||
<select name="subcategory" id="modalSubcatSelect" style="width: 100%; padding: 0.5rem;">
|
||||
<option value="">-- None --</option>
|
||||
</select>
|
||||
</div>
|
||||
<label>Note (Optional)</label>
|
||||
<input type="text" name="note" placeholder="What are you working on?" autocomplete="off">
|
||||
<input type="text" name="note" id="modalNote" placeholder="What are you working on?" autocomplete="off">
|
||||
<div style="margin-top: 2rem; display: flex; justify-content: space-between;">
|
||||
<button type="button" class="btn" style="background: transparent; color: var(--text-secondary); border: 1px solid var(--border-dim);" onclick="closeStartModal()">Skip</button>
|
||||
<button type="submit" class="btn" style="background: #27ae60;">Update Details</button>
|
||||
@@ -412,6 +497,53 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Task Modal (For adding task to active session) -->
|
||||
<div id="quickTaskModal" class="modal-backdrop">
|
||||
<div class="modal-card">
|
||||
<h3 style="margin-bottom: 1.5rem;">Add Task</h3>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 1rem; font-size: 0.9rem;">
|
||||
This task will be linked to the current activity.
|
||||
</p>
|
||||
<form onsubmit="submitQuickTask(event)">
|
||||
<label>Task Name</label>
|
||||
<input type="text" id="quickTaskName" required placeholder="What needs to be done?" autocomplete="off">
|
||||
|
||||
<div style="margin-top: 2rem; display: flex; justify-content: flex-end; gap: 10px;">
|
||||
<button type="button" class="btn" style="background: transparent; color: var(--text-secondary); border: 1px solid var(--border-dim);" onclick="closeQuickTaskModal()">Cancel</button>
|
||||
<button type="submit" class="btn" style="background: #27ae60;">Add Task</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Replace standard form submit for details so page doesn't reload and lose state
|
||||
function submitDetailsForm(e) {
|
||||
e.preventDefault();
|
||||
const sub = document.getElementById('modalSubcatSelect').value;
|
||||
const note = document.getElementById('modalNote').value;
|
||||
|
||||
currentSubcategory = sub; // Update global for quick add task
|
||||
|
||||
// Update UI immediately
|
||||
if(sub) {
|
||||
document.getElementById('activeSubcatContainer').style.display = 'block';
|
||||
document.getElementById('activeSubcatDisplay').innerText = sub;
|
||||
}
|
||||
if(note) {
|
||||
document.getElementById('activeNote').style.display = 'block';
|
||||
document.getElementById('activeNote').innerText = '"' + note + '"';
|
||||
}
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('subcategory', sub);
|
||||
fd.append('note', note);
|
||||
|
||||
fetch('/update_active_entry', { method: 'POST', body: fd });
|
||||
closeStartModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Live Sync Script (Optimized for Animation) -->
|
||||
<script>
|
||||
// Generate initial state hashes based on what Jinja rendered
|
||||
@@ -431,8 +563,9 @@
|
||||
// Don't poll if page is hidden to save battery/data
|
||||
if (document.hidden) return;
|
||||
|
||||
// Don't poll or reload if the Start Modal is open (User is interacting)
|
||||
if (document.getElementById('startModal').classList.contains('show')) return;
|
||||
// Don't poll or reload if ANY Modal is open (User is interacting)
|
||||
if (document.getElementById('startModal').classList.contains('show') ||
|
||||
document.getElementById('quickTaskModal').classList.contains('show')) return;
|
||||
|
||||
fetch('/api/sync_check')
|
||||
.then(response => {
|
||||
|
||||
Reference in New Issue
Block a user