Files
TimeTrack/templates/task_modal.html
Jens Luedicke 9a79778ad6 Squashed commit of the following:
commit 1eeea9f83ad9230a5c1f7a75662770eaab0df837
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 21:15:41 2025 +0200

    Disable resuming of old time entries.

commit 3e3ec2f01cb7943622b819a19179388078ae1315
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 20:59:19 2025 +0200

    Refactor db migrations.

commit 15a51a569da36c6b7c9e01ab17b6fdbdee6ad994
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 19:58:04 2025 +0200

    Apply new style for Time Tracking view.

commit 77e5278b303e060d2b03853b06277f8aa567ae68
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 18:06:04 2025 +0200

    Allow direct registrations as a Company.

commit 188a8772757cbef374243d3a5f29e4440ddecabe
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 18:04:45 2025 +0200

    Add email invitation feature.

commit d9ebaa02aa01b518960a20dccdd5a327d82f30c6
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 17:12:32 2025 +0200

    Apply common style for Company, User, Team management pages.

commit 81149caf4d8fc6317e2ab1b4f022b32fc5aa6d22
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 16:44:32 2025 +0200

    Move export functions to own module.

commit 1a26e19338e73f8849c671471dd15cc3c1b1fe82
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 15:51:15 2025 +0200

    Split up models.py.

commit 61f1ccd10f721b0ff4dc1eccf30c7a1ee13f204d
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 12:05:28 2025 +0200

    Move utility function into own modules.

commit 84b341ed35e2c5387819a8b9f9d41eca900ae79f
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 11:44:24 2025 +0200

    Refactor auth functions use.

commit 923e311e3da5b26d85845c2832b73b7b17c48adb
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 11:35:52 2025 +0200

    Refactor route nameing and fix bugs along the way.

commit f0a5c4419c340e62a2615c60b2a9de28204d2995
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 10:34:33 2025 +0200

    Fix URL endpoints in announcement template.

commit b74d74542a1c8dc350749e4788a9464d067a88b5
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 09:25:53 2025 +0200

    Move announcements to own module.

commit 9563a28021ac46c82c04fe4649b394dbf96f92c7
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 09:16:30 2025 +0200

    Combine Company view and edit templates.

commit 6687c373e681d54e4deab6b2582fed5cea9aadf6
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 08:17:42 2025 +0200

    Move Users, Company and System Administration to own modules.

commit 8b7894a2e3eb84bb059f546648b6b9536fea724e
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 07:40:57 2025 +0200

    Move Teams and Projects to own modules.

commit d11bf059d99839ecf1f5d7020b8c8c8a2454c00b
Author: Jens Luedicke <jens@luedicke.me>
Date:   Mon Jul 7 07:09:33 2025 +0200

    Move Tasks and Sprints to own modules.
2025-07-07 21:16:36 +02:00

741 lines
20 KiB
HTML

<!-- Task Detail Modal -->
<div id="task-modal" class="modal task-modal" style="display: none;">
<div class="modal-content task-modal-content">
<div class="modal-header">
<h2 id="modal-title">Task Details</h2>
<span class="close" onclick="closeTaskModal()">&times;</span>
</div>
<div class="modal-body">
<form id="task-form">
<input type="hidden" id="task-id">
<!-- Basic Information -->
<div class="form-section">
<h3>📝 Basic Information</h3>
<div class="form-row">
<div class="form-group">
<label for="task-name">Task Name *</label>
<input type="text" id="task-name" required>
</div>
<div class="form-group">
<label for="task-priority">Priority</label>
<select id="task-priority">
<option value="LOW">Low</option>
<option value="MEDIUM">Medium</option>
<option value="HIGH">High</option>
<option value="URGENT">Urgent</option>
</select>
</div>
</div>
<div class="form-group">
<label for="task-description">Description</label>
<textarea id="task-description" rows="2"></textarea>
</div>
<div class="form-group">
<label for="task-status">Status</label>
<select id="task-status">
<option value="TODO">To Do</option>
<option value="IN_PROGRESS">In Progress</option>
<option value="IN_REVIEW">In Review</option>
<option value="DONE">Done</option>
<option value="CANCELLED">Cancelled</option>
<option value="ARCHIVED">Archived</option>
</select>
</div>
</div>
<!-- Assignment & Planning -->
<div class="form-section">
<h3>👥 Assignment & Planning</h3>
<div class="form-row">
<div class="form-group">
<label for="task-project">Project</label>
<select id="task-project">
<option value="">Select Project</option>
{% for project in available_projects %}
<option value="{{ project.id }}">{{ project.code }} - {{ project.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="task-assignee">Assigned To</label>
<select id="task-assignee">
<option value="">Unassigned</option>
{% for user in team_members %}
<option value="{{ user.id }}">{{ user.username }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="task-sprint">Sprint</label>
<select id="task-sprint">
<option value="">No Sprint</option>
<!-- Sprint options will be populated dynamically -->
</select>
</div>
<div class="form-group">
<label for="task-estimated-hours">Estimated Hours</label>
<input type="number" id="task-estimated-hours" min="0" step="0.5">
</div>
</div>
<div class="form-group">
<label for="task-due-date">Due Date</label>
<div class="hybrid-date-input">
<input type="date" id="task-due-date-native" class="date-input-native">
<input type="text" id="task-due-date" class="date-input-formatted" placeholder="{{ "YYYY-MM-DD" if (g.user.preferences.date_format if g.user.preferences else "ISO") == "ISO" else "MM/DD/YYYY" if (g.user.preferences.date_format if g.user.preferences else "ISO") == "US" else "DD/MM/YYYY" if (g.user.preferences.date_format if g.user.preferences else "ISO") in ["EU", "UK"] else "Mon, Dec 25, 2024" }}">
<button type="button" class="calendar-picker-btn" onclick="openCalendarPicker('task-due-date')" title="Open calendar">📅</button>
</div>
<div class="date-error" id="task-due-date-error" style="display: none; color: #dc3545; font-size: 0.8rem; margin-top: 0.25rem;"></div>
</div>
</div>
<!-- Dependencies -->
<div class="form-section">
<h3>🔗 Dependencies</h3>
<div class="dependencies-grid">
<!-- Blocked By -->
<div class="dependency-column">
<h4>🚫 Blocked By</h4>
<p class="dependency-help">Tasks that must be completed before this task can start</p>
<div id="blocked-by-container" class="dependency-list">
<!-- Blocked by tasks will be populated here -->
</div>
<div class="add-dependency-form">
<input type="text" id="blocked-by-input" placeholder="TSK-001" class="dependency-input">
<button type="button" class="btn btn-sm btn-secondary" onclick="addBlockedBy()">Add</button>
</div>
</div>
<!-- Blocks -->
<div class="dependency-column">
<h4>🔒 Blocks</h4>
<p class="dependency-help">Tasks that cannot start until this task is completed</p>
<div id="blocks-container" class="dependency-list">
<!-- Blocks tasks will be populated here -->
</div>
<div class="add-dependency-form">
<input type="text" id="blocks-input" placeholder="TSK-002" class="dependency-input">
<button type="button" class="btn btn-sm btn-secondary" onclick="addBlocks()">Add</button>
</div>
</div>
</div>
</div>
<!-- Subtasks -->
<div class="form-section">
<h3>📋 Subtasks</h3>
<div id="subtasks-container">
<!-- Subtasks will be populated here -->
</div>
<button type="button" class="btn btn-sm btn-secondary" onclick="addSubtask()">+ Add Subtask</button>
</div>
</form>
<!-- Comments Section (outside form) -->
<div class="form-section" id="comments-section" style="display: none;">
<h3>💬 Comments</h3>
<div id="comments-container">
<!-- Comments will be populated here -->
</div>
<div class="comment-form">
<textarea id="new-comment" placeholder="Add a comment..." rows="2"></textarea>
<div class="comment-form-actions">
<select id="comment-visibility" class="comment-visibility-select" style="display: none;">
<option value="COMPANY">🏢 Company</option>
<option value="TEAM">👥 Team Only</option>
</select>
<button type="button" class="btn btn-sm btn-primary" onclick="addComment()">Post Comment</button>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeTaskModal()">Cancel</button>
<button type="button" class="btn btn-primary" onclick="saveTask()">Save Task</button>
<button type="button" class="btn btn-danger" onclick="deleteTask()" id="delete-task-btn" style="display: none;">Delete Task</button>
</div>
</div>
</div>
<!-- Task Modal JavaScript Functions -->
<script>
// Hybrid Date Input Functions for task modal
function setupHybridDateInput(inputId) {
const formattedInput = document.getElementById(inputId);
const nativeInput = document.getElementById(inputId + '-native');
if (!formattedInput || !nativeInput) return;
// Sync from native input to formatted input
nativeInput.addEventListener('change', function() {
if (this.value) {
formattedInput.value = formatDateForInput(this.value);
// Trigger change event on formatted input
formattedInput.dispatchEvent(new Event('change'));
}
});
// Sync from formatted input to native input
formattedInput.addEventListener('change', function() {
const isoDate = parseUserDate(this.value);
if (isoDate) {
nativeInput.value = isoDate;
} else {
nativeInput.value = '';
}
});
// Clear both inputs when formatted input is cleared
formattedInput.addEventListener('input', function() {
if (this.value === '') {
nativeInput.value = '';
}
});
}
function openCalendarPicker(inputId) {
const nativeInput = document.getElementById(inputId + '-native');
if (nativeInput) {
// Try multiple methods to open the date picker
nativeInput.focus();
// For modern browsers
if (nativeInput.showPicker) {
try {
nativeInput.showPicker();
} catch (e) {
// Fallback to click if showPicker fails
nativeInput.click();
}
} else {
// Fallback for older browsers
nativeInput.click();
}
}
}
// Initialize hybrid date inputs when DOM is ready
document.addEventListener('DOMContentLoaded', function() {
// Setup hybrid date inputs for task modal
setupHybridDateInput('task-due-date');
});
</script>
<!-- Task Modal Styles -->
<style>
/* Task Modal Specific Styles - Compact Design */
.task-modal .modal-content {
width: 95%;
max-width: 900px;
max-height: 90vh;
overflow-y: auto;
}
.task-modal .modal-header {
padding: 0.75rem 1rem;
}
.task-modal .modal-header h2 {
font-size: 1.25rem;
margin: 0;
}
.task-modal-content .modal-body {
padding: 1rem;
}
.form-section {
margin-bottom: 1.25rem;
padding-bottom: 1rem;
border-bottom: 1px solid #e9ecef;
}
.form-section:last-of-type {
border-bottom: none;
margin-bottom: 0;
}
.form-section h3 {
margin: 0 0 0.75rem 0;
color: #495057;
font-size: 1rem;
font-weight: 600;
padding-bottom: 0.25rem;
border-bottom: 2px solid #f8f9fa;
}
/* Dependencies Grid - Compact */
.dependencies-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.dependency-column {
background: #f8f9fa;
border-radius: 6px;
padding: 0.75rem;
}
.dependency-column h4 {
margin: 0 0 0.25rem 0;
color: #495057;
font-size: 0.9rem;
font-weight: 600;
}
.dependency-help {
font-size: 0.75rem;
color: #6c757d;
margin: 0 0 0.5rem 0;
line-height: 1.3;
}
.dependency-list {
min-height: 40px;
margin-bottom: 0.5rem;
}
.dependency-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.4rem 0.5rem;
background: white;
border: 1px solid #dee2e6;
border-radius: 4px;
margin-bottom: 0.3rem;
font-size: 0.85rem;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.dependency-task-info {
display: flex;
align-items: center;
gap: 0.5rem;
flex: 1;
}
.dependency-task-number {
font-family: 'Courier New', monospace;
font-size: 0.75rem;
font-weight: 600;
color: #007bff;
background: #e3f2fd;
padding: 0.2rem 0.4rem;
border-radius: 4px;
flex-shrink: 0;
}
.dependency-task-title {
color: #333;
font-weight: 500;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dependency-remove-btn {
background: #dc3545;
color: white;
border: none;
border-radius: 4px;
padding: 0.25rem 0.5rem;
cursor: pointer;
font-size: 0.75rem;
transition: background-color 0.2s;
flex-shrink: 0;
}
.dependency-remove-btn:hover {
background: #c82333;
}
.add-dependency-form {
display: flex;
gap: 0.3rem;
}
.dependency-input {
flex: 1;
padding: 0.3rem 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.8rem;
font-family: 'Courier New', monospace;
background: white;
}
.dependency-input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.1);
}
.dependency-input::placeholder {
color: #adb5bd;
font-style: italic;
}
/* Subtasks Section - Compact */
.subtask-item {
display: flex;
align-items: center;
gap: 0.3rem;
margin-bottom: 0.3rem;
padding: 0.3rem 0.5rem;
background: #f8f9fa;
border-radius: 4px;
}
.subtask-item input[type="text"] {
flex: 1;
margin: 0;
padding: 0.3rem 0.5rem;
font-size: 0.85rem;
}
.subtask-item button {
padding: 0.2rem 0.4rem;
font-size: 0.7rem;
}
/* Hybrid Date Input Styles */
.hybrid-date-input {
position: relative;
display: flex;
align-items: center;
gap: 0.25rem;
}
.date-input-native {
position: absolute;
left: 0;
top: 0;
width: calc(100% - 35px);
height: 100%;
opacity: 0;
cursor: pointer;
z-index: 2;
pointer-events: auto;
}
.date-input-formatted {
flex: 1;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
background: white;
position: relative;
z-index: 1;
}
.calendar-picker-btn {
background: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
padding: 0.5rem;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
z-index: 3;
position: relative;
}
.calendar-picker-btn:hover {
background: #e9ecef;
}
/* Compact Form Styles */
.form-group {
margin-bottom: 0.75rem;
}
.form-group label {
display: block;
margin-bottom: 0.25rem;
font-size: 0.85rem;
font-weight: 500;
color: #495057;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 0.4rem 0.6rem;
font-size: 0.85rem;
border: 1px solid #ced4da;
border-radius: 4px;
transition: border-color 0.15s ease-in-out;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #80bdff;
box-shadow: 0 0 0 0.1rem rgba(0, 123, 255, 0.25);
}
.form-group textarea {
resize: vertical;
min-height: 50px;
}
.form-row {
display: flex;
gap: 0.75rem;
margin-bottom: 0.75rem;
}
.form-row .form-group {
flex: 1;
margin-bottom: 0;
}
/* Compact button styles */
.btn-sm {
padding: 0.25rem 0.5rem;
font-size: 0.8rem;
}
.modal-footer {
padding: 0.75rem 1rem;
gap: 0.5rem;
}
.modal-footer .btn {
padding: 0.5rem 1rem;
font-size: 0.9rem;
}
/* Mobile Responsiveness */
@media (max-width: 768px) {
.task-modal .modal-content {
width: 98%;
margin: 1% auto;
max-height: 98vh;
}
.dependencies-grid {
grid-template-columns: 1fr;
gap: 1rem;
}
.form-row {
flex-direction: column;
}
.dependency-task-title {
font-size: 0.85rem;
}
}
@media (max-width: 480px) {
.task-modal-content .modal-body {
padding: 1rem;
}
.form-section {
margin-bottom: 1.5rem;
padding-bottom: 1rem;
}
.dependency-item {
padding: 0.5rem;
}
.dependency-task-number {
font-size: 0.7rem;
padding: 0.15rem 0.3rem;
}
}
/* Comments Section Styles */
#comments-container {
max-height: 300px;
overflow-y: auto;
margin-bottom: 1rem;
}
.no-comments {
text-align: center;
color: #6c757d;
font-style: italic;
padding: 2rem;
}
.comment-edited {
font-size: 0.75rem;
color: #6c757d;
font-style: italic;
}
.comment-item {
background: #f8f9fa;
border-radius: 8px;
padding: 0.75rem;
margin-bottom: 0.5rem;
border: 1px solid #e9ecef;
}
.comment-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.comment-author-info {
display: flex;
align-items: center;
gap: 0.5rem;
}
.comment-author-avatar {
width: 28px;
height: 28px;
border-radius: 50%;
object-fit: cover;
}
.comment-author-details {
display: flex;
flex-direction: column;
}
.comment-author {
font-weight: 600;
font-size: 0.85rem;
color: #333;
}
.comment-time {
font-size: 0.75rem;
color: #6c757d;
}
.comment-visibility-badge {
font-size: 0.7rem;
padding: 0.2rem 0.4rem;
border-radius: 4px;
background: #e9ecef;
color: #6c757d;
}
.comment-visibility-badge.team {
background: #fff3cd;
color: #856404;
}
.comment-content {
font-size: 0.85rem;
line-height: 1.5;
color: #495057;
white-space: pre-wrap;
}
.comment-actions {
display: flex;
gap: 0.5rem;
margin-top: 0.5rem;
}
.comment-action {
font-size: 0.75rem;
color: #6c757d;
background: none;
border: none;
cursor: pointer;
padding: 0.2rem 0.4rem;
border-radius: 4px;
transition: background-color 0.2s;
}
.comment-action:hover {
background: #e9ecef;
}
.comment-form {
background: #f8f9fa;
border-radius: 8px;
padding: 0.75rem;
border: 1px solid #e9ecef;
}
#new-comment {
width: 100%;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
resize: vertical;
min-height: 60px;
font-size: 0.85rem;
}
.comment-form-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 0.5rem;
}
.comment-visibility-select {
padding: 0.25rem 0.5rem;
font-size: 0.8rem;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
}
/* Edit comment form */
.comment-edit-form {
margin-top: 0.5rem;
display: none;
}
.comment-edit-textarea {
width: 100%;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
resize: vertical;
min-height: 60px;
font-size: 0.85rem;
margin-bottom: 0.5rem;
}
.comment-edit-actions {
display: flex;
gap: 0.5rem;
}
/* Reply form */
.comment-reply-form {
margin-top: 0.5rem;
padding-left: 2rem;
display: none;
}
.comment-replies {
margin-top: 0.5rem;
padding-left: 2rem;
}
.comment-reply {
background: white;
border-radius: 6px;
padding: 0.5rem;
margin-bottom: 0.3rem;
border: 1px solid #e9ecef;
}
</style>