mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-19 12:50:11 -05:00
463704f054
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.
504 lines
24 KiB
HTML
504 lines
24 KiB
HTML
{% extends "base.html" %}
|
|
{% from 'components/ui.html' import page_header, form_group, form_select, form_textarea, form_checkbox, form_date, form_section, form_actions %}
|
|
|
|
{% block title %}{{ _('Create Task') }} - 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.list_tasks') }}" class="btn btn-secondary"><i class="fas fa-arrow-left"></i> {{ _('Back to Tasks') }}</a>
|
|
{% endset %}
|
|
{{ page_header('fas fa-plus-circle', _('Create Task'), subtitle_text=_('Add a new task to your project to break down work into manageable components'), actions_html=actions, breadcrumbs=[{'text': _('Tasks'), 'url': url_for('tasks.list_tasks')}, {'text': _('Create Task')}]) }}
|
|
|
|
<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 p-6 rounded-xl border border-border-light dark:border-border-dark shadow-sm">
|
|
<form method="POST" id="createTaskForm" novalidate data-validate-form>
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<input type="hidden" name="next" value="{{ request.args.get('next', '') }}">
|
|
<!-- Task Name -->
|
|
<div class="mb-4">
|
|
<label for="name" class="form-label">{{ _('Task Name') }} *</label>
|
|
<input type="text" id="name" name="name" value="{{ request.form.get('name', '') }}" placeholder="{{ _('Enter a descriptive task name') }}" required class="form-input">
|
|
<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">
|
|
<div class="flex items-center justify-between">
|
|
<label for="description" class="form-label">{{ _('Description') }}</label>
|
|
<small class="text-text-muted-light dark:text-text-muted-dark">{{ _('Supports Markdown') }}</small>
|
|
</div>
|
|
<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...') }}">{{ request.form.get('description', '') }}</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">{{ _('Project') }} *</label>
|
|
<select id="project_id" name="project_id" required class="form-input">
|
|
<option value="">{{ _('Select a project') }}</option>
|
|
{% for project in projects %}
|
|
<option value="{{ project.id }}" {% if request.form.get('project_id')|int == project.id or request.args.get('project_id')|int == project.id %}selected{% endif %}>{{ project.name }} ({{ 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 request.form.get('priority') == 'low' %}selected{% endif %}>{{ _('Low') }}</option>
|
|
<option value="medium" {% if request.form.get('priority') == 'medium' or not request.form.get('priority') %}selected{% endif %}>{{ _('Medium') }}</option>
|
|
<option value="high" {% if request.form.get('priority') == 'high' %}selected{% endif %}>{{ _('High') }}</option>
|
|
<option value="urgent" {% if request.form.get('priority') == 'urgent' %}selected{% endif %}>{{ _('Urgent') }}</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="status" class="form-label">{{ _('Initial Status') }}</label>
|
|
<select id="status" name="status" class="form-input">
|
|
{% set initial_status = request.form.get('status') or request.args.get('status') or 'todo' %}
|
|
<option value="todo" {% if initial_status == 'todo' %}selected{% endif %}>{{ _('To Do') }}</option>
|
|
<option value="in_progress" {% if initial_status == 'in_progress' %}selected{% endif %}>{{ _('In Progress') }}</option>
|
|
<option value="review" {% if initial_status == 'review' %}selected{% endif %}>{{ _('Review') }}</option>
|
|
<option value="done" {% if initial_status == 'done' %}selected{% endif %}>{{ _('Done') }}</option>
|
|
<option value="cancelled" {% if initial_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-{{ request.form.get('priority', 'medium') }}">
|
|
{% if request.form.get('priority') == 'low' %}{{ _('Low') }}{% elif request.form.get('priority') == 'high' %}{{ _('High') }}{% elif request.form.get('priority') == 'urgent' %}{{ _('Urgent') }}{% else %}{{ _('Medium') }}{% endif %}
|
|
</span>
|
|
<span id="statusPreview" class="status-badge status-{{ request.form.get('status') or request.args.get('status') or 'todo' }}">
|
|
{% set status_preview = request.form.get('status') or request.args.get('status') or 'todo' %}
|
|
{% if status_preview == 'in_progress' %}{{ _('In Progress') }}{% elif status_preview == 'review' %}{{ _('Review') }}{% elif status_preview == 'done' %}{{ _('Done') }}{% elif status_preview == 'cancelled' %}{{ _('Cancelled') }}{% else %}{{ _('To Do') }}{% endif %}
|
|
</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="{{ request.form.get('due_date', '') }}" 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="{{ request.form.get('estimated_hours', '') }}" 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 request.form.get('assigned_to')|int == 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="{{ request.form.get('tags', '') }}" 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', '') }}" 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.list_tasks') }}" class="btn btn-secondary">{{ _('Cancel') }}</a>
|
|
<button type="submit" class="btn btn-primary">{{ _('Create Task') }}</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="lg:col-span-1">
|
|
<div class="bg-card-light dark:bg-card-dark p-6 rounded-xl border border-border-light dark:border-border-dark shadow-sm space-y-4 text-sm" data-testid="task-create-tips">
|
|
<h3 class="text-lg font-semibold">{{ _('Task Creation Tips') }}</h3>
|
|
<ul class="space-y-2" role="list">
|
|
<li class="tip-item flex items-start gap-3">
|
|
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-sky-500/10 text-sky-600 dark:text-sky-400" aria-hidden="true">
|
|
<i class="fas fa-bullseye text-[13px]"></i>
|
|
</span>
|
|
<div>
|
|
<strong class="block">{{ _('Clear Naming') }}</strong>
|
|
<span class="text-text-muted-light dark:text-text-muted-dark">{{ _('Use action verbs and be specific about what needs to be done') }}</span>
|
|
</div>
|
|
</li>
|
|
<li class="tip-item flex items-start gap-3">
|
|
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400" aria-hidden="true">
|
|
<i class="fas fa-hourglass-half text-[13px]"></i>
|
|
</span>
|
|
<div>
|
|
<strong class="block">{{ _('Realistic Estimates') }}</strong>
|
|
<span class="text-text-muted-light dark:text-text-muted-dark">{{ _('Consider complexity and dependencies when estimating time') }}</span>
|
|
</div>
|
|
</li>
|
|
<li class="tip-item flex items-start gap-3">
|
|
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-indigo-500/10 text-indigo-600 dark:text-indigo-400" aria-hidden="true">
|
|
<i class="fas fa-calendar-alt text-[13px]"></i>
|
|
</span>
|
|
<div>
|
|
<strong class="block">{{ _('Set Deadlines') }}</strong>
|
|
<span class="text-text-muted-light dark:text-text-muted-dark">{{ _('Due dates help prioritize work and track progress') }}</span>
|
|
</div>
|
|
</li>
|
|
<li class="tip-item flex items-start gap-3">
|
|
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-rose-500/10 text-rose-600 dark:text-rose-400" aria-hidden="true">
|
|
<i class="fas fa-flag text-[13px]"></i>
|
|
</span>
|
|
<div>
|
|
<strong class="block">{{ _('Priority Matters') }}</strong>
|
|
<span class="text-text-muted-light dark:text-text-muted-dark">{{ _("Use priority levels to help team members focus on what's most important") }}</span>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</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;
|
|
}
|
|
|
|
/* 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 tip items */
|
|
.dark .tip-item {
|
|
background-color: #0f172a; /* slate-900 */
|
|
}
|
|
.dark .tip-item:hover {
|
|
background-color: #0b1220; /* slightly lighter hover */
|
|
}
|
|
|
|
/* Priority and Status Guide Items */
|
|
.priority-guide-item,
|
|
.status-guide-item {
|
|
padding: 0.5rem;
|
|
border-radius: 6px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.priority-guide-item:hover,
|
|
.status-guide-item:hover {
|
|
background-color: #f8fafc;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Mobile Optimizations */
|
|
@media (max-width: 768px) {
|
|
.card-header {
|
|
padding: 1rem 1rem 0.75rem 1rem;
|
|
}
|
|
|
|
.card-body {
|
|
padding: 0.75rem 1rem;
|
|
}
|
|
|
|
.tip-item:hover {
|
|
transform: none;
|
|
}
|
|
|
|
.priority-guide-item:hover,
|
|
.status-guide-item:hover {
|
|
background-color: transparent;
|
|
}
|
|
}
|
|
|
|
/* Hover Effects */
|
|
.btn:hover {
|
|
transform: none;
|
|
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
|
|
}
|
|
|
|
.tip-item:hover {
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
/* Enhanced markdown editor styles are now centralized in base.css */
|
|
</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('createTaskForm');
|
|
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-create-description';
|
|
if (!descriptionInput.value) {
|
|
const cached = localStorage.getItem(autosaveKey);
|
|
if (cached) { try { mdEditor.setMarkdown(cached); } catch(e) {} }
|
|
}
|
|
mdEditor.on && mdEditor.on('change', () => {
|
|
try { localStorage.setItem(autosaveKey, mdEditor.getMarkdown()); } 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>Creating...';
|
|
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');
|
|
}
|
|
});
|
|
// 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];
|
|
// Reset classes preserving base class
|
|
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 || 'medium', 'priority');
|
|
updateBadge(statusPreview, statusSelect?.value || 'todo', '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 %}
|