Files
TimeTracker/app/templates/tasks/edit.html
T
Dries Peeters 463704f054 feat(ui): refresh shared layout patterns and responsive screens
Unify buttons, cards, headers, toasts, and form treatments across the app so screens feel consistent and are easier to scan on desktop and mobile. Update the broader template set to use the shared UI primitives and responsive spacing patterns introduced in this refresh.
2026-03-06 22:15:06 +01:00

681 lines
34 KiB
HTML

{% extends "base.html" %}
{% from 'components/ui.html' import page_header %}
{% block title %}{{ _('Edit Task') }} - {{ task.name }} - Time Tracker{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/toastui-editor.min.css">
<link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/theme/toastui-editor-dark.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/classic.min.css">
{% endblock %}
{% block content %}
{% set actions %}
<a href="{{ url_for('tasks.view_task', task_id=task.id) }}" class="btn btn-secondary"><i class="fas fa-arrow-left"></i> {{ _('Back to Task') }}</a>
{% endset %}
{{ page_header('fas fa-edit', _('Edit Task'), subtitle_text=_('Update task details and settings for "%(task)s"', task=task.name), actions_html=actions, breadcrumbs=[{'text': _('Tasks'), 'url': url_for('tasks.list_tasks')}, {'text': task.name, 'url': url_for('tasks.view_task', task_id=task.id)}, {'text': _('Edit')}]) }}
<!-- Edit Task Form -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="lg:col-span-2">
<div class="bg-card-light dark:bg-card-dark rounded-xl border border-border-light dark:border-border-dark shadow-sm">
<div class="border-b border-border-light dark:border-border-dark p-4">
<h6 class="font-semibold flex items-center gap-2"><i class="fas fa-edit text-yellow-600"></i>{{ _('Task Information') }}</h6>
</div>
<div class="p-4">
<form method="POST" id="editTaskForm" novalidate data-validate-form>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<!-- Task Name -->
<div class="mb-4">
<label for="name" class="form-label">
<i class="fas fa-tag mr-2 text-primary"></i>{{ _('Task Name') }} <span class="text-red-500">*</span>
</label>
<input type="text" class="form-input" id="name" name="name"
value="{{ task.name }}" placeholder="{{ _('Enter a descriptive task name') }}" required>
<p class="text-xs text-text-muted-light dark:text-text-muted-dark mt-1">{{ _('Choose a clear, descriptive name that explains what needs to be done') }}</p>
</div>
<!-- Description -->
<div class="mb-4">
<label for="description" class="form-label">
<span><i class="fas fa-align-left mr-2 text-primary"></i>{{ _('Description') }}</span>
</label>
<div class="markdown-editor-wrapper">
<textarea class="form-input hidden" id="description" name="description" rows="12" placeholder="{{ _('Provide detailed information about the task, requirements, and any specific instructions...') }}">{{ task.description or '' }}</textarea>
<div id="description_editor"></div>
</div>
<p class="text-xs text-text-muted-light dark:text-text-muted-dark mt-1">{{ _('Optional: Add context, requirements, or specific instructions for the task') }}</p>
</div>
<!-- Project Selection -->
<div class="mb-4">
<label for="project_id" class="form-label">
<i class="fas fa-project-diagram mr-2 text-primary"></i>{{ _('Project') }} <span class="text-red-500">*</span>
</label>
<select class="form-input" id="project_id" name="project_id" required>
<option value="">{{ _('Select a project') }}</option>
{% for project in projects %}
<option value="{{ project.id }}" {% if task.project_id == project.id %}selected{% endif %}>
{{ project.name }}{% if project.code_display %} [{{ project.code_display }}]{% endif %} ({{ project.client }})
</option>
{% endfor %}
</select>
<p class="text-xs text-text-muted-light dark:text-text-muted-dark mt-1">{{ _('Select the project this task belongs to') }}</p>
</div>
<!-- Priority and Status -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-2">
<div>
<label for="priority" class="form-label">{{ _('Priority') }}</label>
<select id="priority" name="priority" class="form-input">
<option value="low" {% if task.priority == 'low' %}selected{% endif %}>{{ _('Low') }}</option>
<option value="medium" {% if task.priority == 'medium' %}selected{% endif %}>{{ _('Medium') }}</option>
<option value="high" {% if task.priority == 'high' %}selected{% endif %}>{{ _('High') }}</option>
<option value="urgent" {% if task.priority == 'urgent' %}selected{% endif %}>{{ _('Urgent') }}</option>
</select>
</div>
<div>
<label for="status" class="form-label">{{ _('Status') }}</label>
<select id="status" name="status" class="form-input">
<option value="todo" {% if task.status == 'todo' %}selected{% endif %}>{{ _('To Do') }}</option>
<option value="in_progress" {% if task.status == 'in_progress' %}selected{% endif %}>{{ _('In Progress') }}</option>
<option value="review" {% if task.status == 'review' %}selected{% endif %}>{{ _('Review') }}</option>
<option value="done" {% if task.status == 'done' %}selected{% endif %}>{{ _('Done') }}</option>
<option value="cancelled" {% if task.status == 'cancelled' %}selected{% endif %}>{{ _('Cancelled') }}</option>
</select>
</div>
</div>
<div class="flex items-center gap-2 mb-4 text-xs">
<span class="text-text-muted-light dark:text-text-muted-dark">{{ _('Preview') }}:</span>
<span id="priorityPreview" class="priority-badge priority-{{ task.priority }}">
{{ task.priority_display }}
</span>
<span id="statusPreview" class="status-badge status-{{ task.status }}">
<span class="whitespace-nowrap">{{ task.status_display }}</span>
</span>
</div>
<!-- Due Date and Estimated Hours -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label for="due_date" class="form-label">{{ _('Due Date') }}</label>
<input type="date" id="due_date" name="due_date" value="{{ task.due_date.strftime('%Y-%m-%d') if task.due_date else '' }}" class="form-input">
<p class="text-xs text-text-muted-light dark:text-text-muted-dark mt-1">{{ _('Optional: Set a deadline for this task') }}</p>
</div>
<div>
<label for="estimated_hours" class="form-label">{{ _('Estimated Hours') }}</label>
<input type="number" id="estimated_hours" name="estimated_hours" value="{{ task.estimated_hours or '' }}" step="0.5" min="0" placeholder="0.0" class="form-input">
<p class="text-xs text-text-muted-light dark:text-text-muted-dark mt-1">{{ _('Optional: Estimate how long this task will take') }}</p>
</div>
</div>
<!-- Assignment -->
<div class="mb-4">
<label for="assigned_to" class="form-label">{{ _('Assign To') }}</label>
<select id="assigned_to" name="assigned_to" class="form-input">
<option value="">{{ _('Unassigned') }}</option>
{% for user in users %}
<option value="{{ user.id }}" {% if task.assigned_to == user.id %}selected{% endif %}>{{ user.display_name }}</option>
{% endfor %}
</select>
<p class="text-xs text-text-muted-light dark:text-text-muted-dark mt-1">{{ _('Optional: Assign this task to a team member') }}</p>
</div>
<!-- Tags -->
<div class="mb-4">
<label for="tags" class="form-label">
<i class="fas fa-tags mr-2 text-primary"></i>{{ _('Tags') }}
</label>
<input type="text" class="form-input" id="tags" name="tags"
value="{{ task.tags or '' }}" placeholder="{{ _('e.g. bug, frontend, urgent') }}" maxlength="500">
<p class="text-xs text-text-muted-light dark:text-text-muted-dark mt-1">{{ _('Comma-separated tags for categorization') }}</p>
</div>
<!-- Gantt color -->
<div class="mb-4">
<label for="color" class="form-label">{{ _('Gantt color') }}</label>
<div class="gantt-color-picker flex items-center gap-3 mt-1">
<div class="gantt-color-picker-swatch rounded border border-gray-300 dark:border-gray-600 overflow-hidden flex-shrink-0" style="width:2.5rem;height:2.5rem;min-width:2.5rem;min-height:2.5rem;"></div>
<input type="text" name="color" id="color" value="{{ request.form.get('color', task.color or '') }}" class="form-input w-28 font-mono text-sm" maxlength="7" placeholder="#3b82f6" data-color-input>
</div>
<p class="text-xs text-text-muted-light dark:text-text-muted-dark mt-1">{{ _('Optional: Color for this task on the Gantt chart. If unset, the project color is used. Pick or enter a hex code (e.g. #3b82f6).') }}</p>
</div>
<!-- Form Actions -->
<div class="mt-6 flex justify-end gap-3 border-t border-border-light dark:border-border-dark pt-4">
<a href="{{ url_for('tasks.view_task', task_id=task.id) }}" class="btn btn-secondary">{{ _('Cancel') }}</a>
<button type="submit" class="btn btn-primary">{{ _('Update Task') }}</button>
</div>
</form>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="lg:col-span-1 space-y-4">
<!-- Progress -->
<div class="bg-card-light dark:bg-card-dark rounded-xl border border-border-light dark:border-border-dark shadow-sm">
<div class="border-b border-border-light dark:border-border-dark p-4">
<h6 class="font-semibold flex items-center gap-2"><i class="fas fa-chart-line text-primary"></i>{{ _('Progress') }}</h6>
</div>
<div class="p-4" data-testid="task-edit-progress">
<div class="flex items-center justify-between text-sm mb-2">
<span class="text-text-muted-light dark:text-text-muted-dark">{{ _('Estimate used') }}</span>
<span class="font-medium">{{ task.progress_percentage }}%</span>
</div>
<div class="h-2 rounded bg-border-light dark:bg-border-dark overflow-hidden">
<div class="h-2 bg-primary rounded" style="width: {{ task.progress_percentage }}%"></div>
</div>
<div class="mt-3 text-xs text-text-muted-light dark:text-text-muted-dark">
<span>{{ _('Actual') }}: {{ task.total_hours }} {{ _('h') }}</span>
{% if task.estimated_hours %}
<span class="ml-2">· {{ _('Estimated') }}: {{ task.estimated_hours }} {{ _('h') }}</span>
{% endif %}
{% if task.total_billable_hours %}
<span class="ml-2">· {{ _('Billable') }}: {{ task.total_billable_hours }} {{ _('h') }}</span>
{% endif %}
</div>
</div>
</div>
<!-- Current Task Info -->
<div class="bg-card-light dark:bg-card-dark rounded-xl border border-border-light dark:border-border-dark shadow-sm mb-4">
<div class="border-b border-border-light dark:border-border-dark p-4">
<h6 class="font-semibold flex items-center gap-2"><i class="fas fa-info-circle text-sky-600"></i>{{ _('Current Task Info') }}</h6>
</div>
<div class="p-4">
<div class="task-info-item mb-3">
<small class="text-text-muted-light dark:text-text-muted-dark block mb-1">{{ _('Current Status') }}</small>
<span class="status-badge status-{{ task.status }}">
{{ task.status_display }}
</span>
</div>
<div class="task-info-item mb-3">
<small class="text-text-muted-light dark:text-text-muted-dark block mb-1">{{ _('Current Priority') }}</small>
<span class="priority-badge priority-{{ task.priority }}">
{{ task.priority_display }}
</span>
</div>
<div class="task-info-item mb-3">
<small class="text-text-muted-light dark:text-text-muted-dark block mb-1">{{ _('Project') }}</small>
<div class="flex items-center">
<div class="rounded-full flex items-center justify-center mr-2 bg-sky-500/10" style="width: 24px; height: 24px;">
<i class="fas fa-project-diagram text-sky-600 fa-xs"></i>
</div>
<span>{{ task.project.name }}</span>
</div>
</div>
{% if task.assigned_user %}
<div class="task-info-item mb-3">
<small class="text-text-muted-light dark:text-text-muted-dark block mb-1">{{ _('Currently Assigned To') }}</small>
<div class="flex items-center">
<div class="rounded-full flex items-center justify-center mr-2 bg-cyan-500/10" style="width: 24px; height:24px;">
<i class="fas fa-user text-cyan-600 fa-xs"></i>
</div>
<span>{{ task.assigned_user.display_name }}</span>
</div>
</div>
{% endif %}
{% if task.due_date %}
<div class="task-info-item mb-3">
<small class="text-text-muted-light dark:text-text-muted-dark block mb-1">{{ _('Current Due Date') }}</small>
<div class="flex items-center">
<div class="rounded-full flex items-center justify-center mr-2 {% if task.is_overdue %}bg-rose-500/10{% else %}bg-slate-500/10{% endif %}" style="width: 24px; height: 24px;">
<i class="fas fa-calendar {% if task.is_overdue %}text-rose-600 dark:text-rose-400{% else %}text-slate-500 dark:text-slate-400{% endif %} fa-xs"></i>
</div>
<span class="{% if task.is_overdue %}text-rose-600 dark:text-rose-400 font-semibold{% endif %}">
{{ task.due_date|format_date }}
</span>
</div>
</div>
{% endif %}
{% if task.estimated_hours %}
<div class="task-info-item mb-3">
<small class="text-text-muted-light dark:text-text-muted-dark block mb-1">{{ _('Current Estimate') }}</small>
<div class="flex items-center">
<div class="rounded-full flex items-center justify-center mr-2 bg-amber-500/10" style="width: 24px; height: 24px;">
<i class="fas fa-clock text-amber-600 dark:text-amber-400 fa-xs"></i>
</div>
<span>{{ task.estimated_hours }} {{ _('hours') }}</span>
</div>
</div>
{% endif %}
{% if task.total_hours > 0 %}
<div class="task-info-item mb-3">
<small class="text-text-muted-light dark:text-text-muted-dark block mb-1">{{ _('Actual Hours') }}</small>
<div class="flex items-center">
<div class="rounded-full flex items-center justify-center mr-2 bg-emerald-500/10" style="width: 24px; height: 24px;">
<i class="fas fa-stopwatch text-emerald-600 fa-xs"></i>
</div>
<span>{{ task.total_hours }} {{ _('hours') }}</span>
</div>
</div>
{% endif %}
<!-- Meta -->
<div class="task-info-item">
<div class="grid grid-cols-2 gap-2 text-xs text-text-muted-light dark:text-text-muted-dark">
<div><span class="block">{{ _('Created') }}</span><span class="text-text-light dark:text-text-dark">{{ task.created_at|user_datetime if task.created_at else '-' }}</span></div>
<div><span class="block">{{ _('Updated') }}</span><span class="text-text-light dark:text-text-dark">{{ task.updated_at|user_datetime if task.updated_at else '-' }}</span></div>
<div><span class="block">{{ _('Started') }}</span><span class="text-text-light dark:text-text-dark">{{ task.started_at|user_datetime if task.started_at else '-' }}</span></div>
<div><span class="block">{{ _('Completed') }}</span><span class="text-text-light dark:text-text-dark">{{ task.completed_at|user_datetime if task.completed_at else '-' }}</span></div>
</div>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="bg-card-light dark:bg-card-dark rounded-xl border border-border-light dark:border-border-dark shadow-sm mb-4">
<div class="border-b border-border-light dark:border-border-dark p-4">
<h6 class="font-semibold flex items-center gap-2"><i class="fas fa-bolt text-yellow-600"></i>{{ _('Quick Actions') }}</h6>
</div>
<div class="p-4">
<div class="grid grid-cols-1 gap-2">
<a href="{{ url_for('tasks.view_task', task_id=task.id) }}" class="inline-flex items-center justify-center px-3 py-2 text-sm rounded-md border border-primary text-primary hover:bg-primary/10">
<i class="fas fa-eye mr-2"></i>{{ _('View Task') }}
</a>
{% if task.status == 'todo' or task.status == 'in_progress' %}
<a href="{{ url_for('timer.start_timer_for_project', project_id=task.project_id, task_id=task.id) }}" class="inline-flex items-center justify-center px-3 py-2 text-sm rounded-md bg-emerald-600 text-white hover:bg-emerald-700">
<i class="fas fa-play mr-2"></i>{{ _('Start Timer') }}
</a>
{% endif %}
<a href="{{ url_for('tasks.list_tasks') }}" class="inline-flex items-center justify-center px-3 py-2 text-sm rounded-md border border-border-light dark:border-border-dark hover:bg-background-light dark:hover:bg-background-dark">
<i class="fas fa-list mr-2"></i>{{ _('Back to Tasks') }}
</a>
</div>
</div>
</div>
<!-- Edit Tips -->
<div class="bg-card-light dark:bg-card-dark rounded-xl border border-border-light dark:border-border-dark shadow-sm" data-testid="task-edit-tips">
<div class="border-b border-border-light dark:border-border-dark p-4">
<h6 class="font-semibold flex items-center gap-2"><i class="fas fa-lightbulb text-yellow-600"></i>{{ _('Edit Tips') }}</h6>
</div>
<div class="p-4">
<div class="tip-item mb-3">
<div class="flex items-start">
<div class="rounded-full flex items-center justify-center mr-2" style="width: 24px; height: 24px;">
<i class="fas fa-info text-sky-600 dark:text-sky-400 fa-xs"></i>
</div>
<div>
<small class="font-semibold block">{{ _('Status Changes') }}</small>
<small class="text-text-muted-light dark:text-text-muted-dark">{{ _('Changing status may affect time tracking and progress calculations') }}</small>
</div>
</div>
</div>
<div class="tip-item mb-3">
<div class="flex items-start">
<div class="rounded-full flex items-center justify-center mr-2" style="width: 24px; height: 24px;">
<i class="fas fa-exclamation-triangle text-amber-600 dark:text-amber-400 fa-xs"></i>
</div>
<div>
<small class="font-semibold block">{{ _('Due Date Updates') }}</small>
<small class="text-text-muted-light dark:text-text-muted-dark">{{ _('Consider team workload when adjusting deadlines') }}</small>
</div>
</div>
</div>
<div class="tip-item">
<div class="flex items-start">
<div class="rounded-full flex items-center justify-center mr-2" style="width: 24px; height: 24px;">
<i class="fas fa-check text-emerald-600 dark:text-emerald-400 fa-xs"></i>
</div>
<div>
<small class="font-semibold block">{{ _('Assignment Changes') }}</small>
<small class="text-text-muted-light dark:text-text-muted-dark">{{ _('Notify team members when reassigning tasks') }}</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
/* Priority Badges */
.priority-badge {
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.priority-low {
background-color: #dcfce7;
color: #166534;
}
.priority-medium {
background-color: #fef3c7;
color: #92400e;
}
.priority-high {
background-color: #fed7aa;
color: #c2410c;
}
.priority-urgent {
background-color: #fee2e2;
color: #991b1b;
}
/* Status Badges */
.status-badge {
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status-todo {
background-color: #e2e8f0;
color: #475569;
}
.status-in_progress {
background-color: #fef3c7;
color: #92400e;
}
.status-review {
background-color: #dbeafe;
color: #1e40af;
}
.status-done {
background-color: #dcfce7;
color: #166534;
}
.status-cancelled {
background-color: #fee2e2;
color: #991b1b;
}
/* Task Info Items */
.task-info-item {
padding: 0.75rem;
border-radius: 8px;
background-color: #f8fafc;
transition: all 0.2s ease;
}
.task-info-item:hover {
background-color: #f1f5f9;
transform: translateX(4px);
}
/* Tip Items */
.tip-item {
padding: 0.75rem;
border-radius: 8px;
background-color: #f8fafc;
transition: all 0.2s ease;
}
.tip-item:hover {
background-color: #f1f5f9;
transform: translateX(4px);
}
/* Dark mode for task info blocks to match view page */
.dark .task-info-item,
.dark .tip-item {
background-color: #0f172a;
}
.dark .task-info-item:hover,
.dark .tip-item:hover {
background-color: #0b1220;
}
/* Form Styling */
.form-control:focus,
.form-select:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.2rem rgba(59, 130, 246, 0.25);
}
.form-control-lg {
min-height: 56px;
}
.form-select-lg {
min-height: 56px;
}
/* Enhanced markdown editor styles are now centralized in base.css */
/* Mobile Optimizations */
@media (max-width: 768px) {
.card-header {
padding: 1rem 1rem 0.75rem 1rem;
}
.card-body {
padding: 0.75rem 1rem;
}
.task-info-item:hover,
.tip-item:hover {
transform: none;
}
}
/* Hover Effects */
.btn:hover {
transform: none;
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
}
.task-info-item:hover,
.tip-item:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
</style>
<!-- Toast UI Editor -->
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
<script>
// Form validation and enhancement
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('editTaskForm');
const nameInput = document.getElementById('name');
const descriptionInput = document.getElementById('description');
const prioritySelect = document.getElementById('priority');
const statusSelect = document.getElementById('status');
const priorityPreview = document.getElementById('priorityPreview');
const statusPreview = document.getElementById('statusPreview');
let mdEditor = null;
// Initialize Toast UI Editor
if (descriptionInput && window.toastui && window.toastui.Editor) {
const theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
mdEditor = new toastui.Editor({
el: document.getElementById('description_editor'),
height: '380px',
initialEditType: 'wysiwyg',
previewStyle: 'vertical',
usageStatistics: false,
hideModeSwitch: false,
placeholder: '{{ _('Provide detailed information about the task, requirements, and any specific instructions...') }}',
theme: theme,
toolbarItems: [
['heading', 'bold', 'italic', 'strike'],
['hr', 'quote'],
['ul', 'ol', 'task'],
['link', 'code', 'codeblock', 'table'],
['image'],
['scrollSync']
],
initialValue: descriptionInput.value || ''
});
// Apply theme changes dynamically if supported
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'attributes' && mutation.attributeName === 'class' && mdEditor) {
const nextTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
try {
if (typeof mdEditor.setTheme === 'function') {
mdEditor.setTheme(nextTheme);
}
} catch (e) {}
}
});
});
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
// Image upload hook
mdEditor.removeHook && mdEditor.removeHook('addImageBlobHook');
mdEditor.addHook && mdEditor.addHook('addImageBlobHook', async (blob, callback) => {
try {
const formData = new FormData();
formData.append('image', blob, blob.name || 'upload.png');
const res = await fetch('{{ url_for('api.upload_editor_image') }}', {
method: 'POST',
body: formData
});
const data = await res.json();
if (data && data.url) {
callback(data.url, blob.name || 'image');
} else {
console.warn('Upload failed', data);
}
} catch (e) { console.error('Image upload error', e); }
});
// Autosave to localStorage
const autosaveKey = 'tt-task-edit-description-{{ task.id }}';
mdEditor.on && mdEditor.on('change', () => {
try { localStorage.setItem(autosaveKey, mdEditor.getMarkdown()); } catch (e) {}
});
// Restore if textarea is empty (rare on edit), otherwise keep DB value
if (!descriptionInput.value) {
const cached = localStorage.getItem(autosaveKey);
if (cached) { try { mdEditor.setMarkdown(cached); } catch(e) {} }
}
} else {
// Fallback when Toast UI Editor fails to load (e.g. CDN blocked, CSP, offline)
if (descriptionInput) {
descriptionInput.classList.remove('hidden');
descriptionInput.style.minHeight = '200px';
}
const editorEl = document.getElementById('description_editor');
if (editorEl) { editorEl.style.display = 'none'; }
}
// Form submission enhancement
form.addEventListener('submit', function(e) {
const name = nameInput.value.trim();
if (!name) {
e.preventDefault();
nameInput.focus();
nameInput.classList.add('is-invalid');
return false;
}
// Sync markdown content back to hidden textarea
if (mdEditor && descriptionInput) {
try { descriptionInput.value = mdEditor.getMarkdown(); } catch (err) {}
}
// Show loading state
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.innerHTML;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>{{ _('Updating...') }}';
submitBtn.disabled = true;
// Re-enable after a delay (in case of validation errors)
setTimeout(() => {
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
}, 5000);
});
// Real-time validation feedback
nameInput.addEventListener('input', function() {
if (this.value.trim()) {
this.classList.remove('is-invalid');
this.classList.add('is-valid');
} else {
this.classList.remove('is-valid');
}
});
// Highlight current values
const currentStatus = '{{ task.status }}';
const currentPriority = '{{ task.priority }}';
// Add visual indicators for current values
if (statusSelect) {
statusSelect.addEventListener('change', function() {
this.classList.remove('border-green-500', 'border-yellow-500');
if (this.value === currentStatus) {
this.classList.add('border-green-500');
} else {
this.classList.add('border-yellow-500');
}
});
}
if (prioritySelect) {
prioritySelect.addEventListener('change', function() {
this.classList.remove('border-green-500', 'border-yellow-500');
if (this.value === currentPriority) {
this.classList.add('border-green-500');
} else {
this.classList.add('border-yellow-500');
}
});
}
// Live badge previews
function updateBadge(element, value, type) {
if (!element) return;
const map = type === 'priority' ? {
low: { text: '{{ _('Low') }}', class: 'priority-low' },
medium: { text: '{{ _('Medium') }}', class: 'priority-medium' },
high: { text: '{{ _('High') }}', class: 'priority-high' },
urgent: { text: '{{ _('Urgent') }}', class: 'priority-urgent' },
} : {
todo: { text: '{{ _('To Do') }}', class: 'status-todo' },
in_progress: { text: '{{ _('In Progress') }}', class: 'status-in_progress' },
review: { text: '{{ _('Review') }}', class: 'status-review' },
done: { text: '{{ _('Done') }}', class: 'status-done' },
cancelled: { text: '{{ _('Cancelled') }}', class: 'status-cancelled' },
};
const def = map[value] || Object.values(map)[0];
element.className = element.className.split(' ').filter(c => !c.startsWith(type === 'priority' ? 'priority-' : 'status-')).join(' ').trim();
element.classList.add(def.class);
element.textContent = def.text;
}
function handlePreviewChange(){
updateBadge(priorityPreview, prioritySelect?.value || currentPriority, 'priority');
updateBadge(statusPreview, statusSelect?.value || currentStatus, 'status');
}
prioritySelect && prioritySelect.addEventListener('change', handlePreviewChange);
statusSelect && statusSelect.addEventListener('change', handlePreviewChange);
handlePreviewChange();
});
</script>
{% endblock %}
{% block scripts_extra %}
<script src="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.min.js"></script>
<script src="{{ url_for('static', filename='js/gantt-color-picker.js') }}"></script>
{% endblock %}