🚀 Release: CorePlan v1.0.1 Community Edition
This commit is contained in:
+75
-16
@@ -8,7 +8,10 @@
|
||||
:root { --bg-color: #12141c; --surface-color: #1e212b; --surface-hover: #2a2e3d; --text-main: #ffffff; --text-muted: #8b92a5; --accent-blue: #3b82f6; --accent-red: #ef4444; --accent-green: #10b981; --accent-orange: #f59e0b; }
|
||||
body { background-color: var(--bg-color); color: var(--text-main); font-family: -apple-system, sans-serif; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; margin: 0; padding: 20px; box-sizing: border-box; }
|
||||
.container { background-color: var(--surface-color); padding: 2.5rem; border-radius: 12px; box-shadow: 0 10px 25px rgba(0,0,0,0.5); width: 100%; max-width: 350px; margin-top: 10vh; }
|
||||
.dashboard-container { max-width: 1000px; padding: 2rem; margin-top: 0; }
|
||||
|
||||
/* NEU: Maximale Breite deutlich erhöht für das neue Grid */
|
||||
.dashboard-container { max-width: 1400px; width: 98%; padding: 2rem; margin-top: 0; }
|
||||
|
||||
h1, h2, h3 { margin-top: 0; } h1 { font-size: 1.5rem; text-align: center; }
|
||||
.form-group { margin-bottom: 1.5rem; } label { display: block; margin-bottom: 0.5rem; color: var(--text-muted); font-size: 0.9rem; }
|
||||
input, select, textarea { width: 100%; box-sizing: border-box; padding: 0.75rem; background-color: var(--bg-color); border: 1px solid var(--surface-hover); color: white; border-radius: 6px; outline: none; transition: border 0.2s; font-family: inherit; }
|
||||
@@ -109,26 +112,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NEUES LAYOUT: Links (Deadlines+Projekte oben, Kalender unten), Rechts (Aufgaben) -->
|
||||
<div style="display: flex; gap: 2rem; align-items: stretch; flex-wrap: wrap;">
|
||||
|
||||
<!-- LINKE SPALTE (Deadlines, Projekte, Kalender) -->
|
||||
<div style="flex: 1.5; min-width: 400px; display: flex; flex-direction: column; gap: 1.5rem;">
|
||||
|
||||
<!-- LINKE SPALTE -->
|
||||
<div style="flex: 1.2; min-width: 450px; display: flex; flex-direction: column; gap: 1.5rem;">
|
||||
<!-- OBERE REIHE: Deadlines & Projekte -->
|
||||
<div style="display: flex; gap: 1.5rem; flex-wrap: wrap; align-items: stretch;">
|
||||
<div class="deadline-box" style="flex: 1; min-width: 200px;">
|
||||
<div class="deadline-box" style="flex: 1; min-width: 200px; margin: 0;">
|
||||
<h3 style="margin-top: 0; margin-bottom: 1rem; color: var(--accent-orange);">🚨 Deadlines (7 Tage)</h3>
|
||||
<div id="upcomingDeadlinesBox" style="display: flex; flex-direction: column; gap: 5px;"></div>
|
||||
</div>
|
||||
|
||||
<div style="flex: 1; min-width: 200px; display: flex; flex-direction: column;">
|
||||
<h3 style="margin-top: 0; margin-bottom: 1rem;">Meine Projekte</h3>
|
||||
<div id="myProjectsContainer" style="margin-top: 0; flex-grow: 1; display: flex; flex-direction: column; gap: 10px; background: var(--surface-hover); padding: 1rem; border-radius: 8px;"></div>
|
||||
<div style="background: var(--surface-hover); border-radius: 8px; padding: 1.5rem; flex-grow: 1; display: flex; flex-direction: column;">
|
||||
<h3 style="margin-top: 0; margin-bottom: 1rem;">Meine Projekte</h3>
|
||||
<div id="myProjectsContainer" style="margin-top: 0; flex-grow: 1; display: flex; flex-direction: column; gap: 10px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- UNTERE REIHE: Kalender -->
|
||||
<div class="deadline-box" style="border-left-color: var(--accent-blue);">
|
||||
<!-- UNTERE REIHE: Kalender (über volle Breite der linken Spalte) -->
|
||||
<div class="deadline-box" style="margin: 0; border-left-color: var(--accent-blue);">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:0.5rem;">
|
||||
<button onclick="changeCalMonth(-1)" style="width:auto; padding:5px 10px; background:var(--surface-color); color:var(--text-main);"><</button>
|
||||
<h3 id="calMonthYear" style="margin:0; color: var(--accent-blue);">Monat Jahr</h3>
|
||||
@@ -144,10 +149,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RECHTE SPALTE (Aufgaben) -->
|
||||
<!-- RECHTE SPALTE -->
|
||||
<div style="flex: 1; min-width: 350px; display: flex; flex-direction: column;">
|
||||
<h3 style="margin-top: 0; margin-bottom: 1rem;">Meine Aufgaben</h3>
|
||||
<div id="myTasksContainer" style="background: var(--surface-hover); border-radius: 8px; padding: 1rem; flex-grow: 1; max-height: calc(100vh - 220px); overflow-y: auto;"></div>
|
||||
<div style="background: var(--surface-hover); border-radius: 8px; padding: 1.5rem; flex-grow: 1; display: flex; flex-direction: column; max-height: calc(100vh - 180px);">
|
||||
<h3 style="margin-top: 0; margin-bottom: 1rem;">Meine Aufgaben</h3>
|
||||
<div id="myTasksContainer" style="overflow-y: auto; padding-right: 5px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -254,6 +261,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- COMMENTS MODAL (Neu: Task Notizen) -->
|
||||
<div id="commentsModal" class="modal-overlay hidden" style="z-index: 1070; background: rgba(0,0,0,0.85);">
|
||||
<div class="modal-content" style="max-width: 500px;">
|
||||
<div class="modal-header"><h3 id="commentsTaskTitle" style="margin:0; font-size:1.1rem; color:var(--accent-blue);">Notizen</h3><button class="btn-close" onclick="document.getElementById('commentsModal').classList.add('hidden')">✖</button></div>
|
||||
<div id="commentsList" style="max-height: 300px; overflow-y: auto; margin-bottom: 1rem; display:flex; flex-direction:column; gap:10px;"></div>
|
||||
<form id="commentAddForm" style="display:flex; gap:10px; background: rgba(0,0,0,0.2); padding: 10px; border-radius: 8px;">
|
||||
<input type="hidden" id="commentTaskId">
|
||||
<input type="text" id="newCommentText" placeholder="Kurzes Update oder Notiz..." required style="flex:1;">
|
||||
<button type="submit" style="width:auto; padding: 0.5rem 1rem;">Senden</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EDIT TASK MODAL -->
|
||||
<div id="editTaskModal" class="modal-overlay hidden" style="z-index: 1050; background: rgba(0,0,0,0.85);">
|
||||
<div class="modal-content" style="max-width: 500px;">
|
||||
@@ -376,8 +396,18 @@
|
||||
<ul style="list-style:none; padding:0 8px 8px 8px; margin:0;">`;
|
||||
|
||||
groupedTasks[pTitle][prio].forEach(t => {
|
||||
let dlHtml = t.deadline ? `<span style="color:var(--accent-orange); font-size:0.75rem;">⏳ ${t.deadline}</span>` : '';
|
||||
taskHtml += `<li style="padding: 10px; border-radius: 6px; background: rgba(0,0,0,0.2); margin-bottom: 5px; display:flex; gap:10px; align-items:flex-start;"><input type="checkbox" class="task-checkbox" onclick="event.preventDefault(); toggleTaskGlobal(${t.id}, ${t.project_id}, 0)"><div><div style="font-weight:bold; margin-bottom: 4px; font-size: 0.9rem;">${t.title}</div><div style="font-size:0.8rem; color:var(--text-muted);">${dlHtml}</div></div></li>`;
|
||||
let dlHtml = t.deadline ? `<span style="color:var(--accent-orange);">⏳ ${t.deadline}</span>` : '';
|
||||
const safeTitle = t.title.replace(/'/g, "\\'").replace(/"/g, '"');
|
||||
taskHtml += `<li style="padding: 10px; border-radius: 6px; background: rgba(0,0,0,0.2); margin-bottom: 5px; display:flex; gap:10px; align-items:flex-start;">
|
||||
<input type="checkbox" class="task-checkbox" onclick="event.preventDefault(); toggleTaskGlobal(${t.id}, ${t.project_id}, 0)">
|
||||
<div>
|
||||
<div style="font-weight:bold; margin-bottom: 4px; font-size: 0.9rem;">${t.title}</div>
|
||||
<div style="font-size:0.8rem; color:var(--text-muted); display:flex; gap:15px; align-items:center;">
|
||||
${dlHtml}
|
||||
<span onclick="openComments(${t.id}, '${safeTitle}')" style="cursor:pointer; color:var(--accent-blue); display:flex; align-items:center; gap:4px;" title="Task Notizen öffnen">💬 Notizen</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>`;
|
||||
});
|
||||
taskHtml += `</ul></details>`;
|
||||
}
|
||||
@@ -426,7 +456,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Kalender Klick Logik
|
||||
function openDayView(dateStr) {
|
||||
let dayTasks = cachedTasks.filter(t => t.deadline === dateStr);
|
||||
document.getElementById('dayViewTitle').innerText = `Aufgaben: ${dateStr}`;
|
||||
@@ -446,6 +475,36 @@
|
||||
document.getElementById('dayViewModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// --- COMMENTS (Notizen) ---
|
||||
function openComments(taskId, title) {
|
||||
document.getElementById('commentTaskId').value = taskId;
|
||||
document.getElementById('commentsTaskTitle').innerText = 'Notizen: ' + title;
|
||||
document.getElementById('commentsModal').classList.remove('hidden');
|
||||
loadComments(taskId);
|
||||
}
|
||||
function loadComments(taskId) {
|
||||
fetch('/api/tasks/comments?task_id=' + taskId).then(res=>res.json()).then(data=>{
|
||||
let html = '';
|
||||
if(data.success) {
|
||||
if(data.data.length === 0) html = '<div style="color:var(--text-muted); font-size:0.9rem; text-align:center; margin-top:20px;">Noch keine Notizen vorhanden.</div>';
|
||||
data.data.forEach(c => {
|
||||
html += `<div style="background:rgba(255,255,255,0.05); padding:10px; border-radius:6px; font-size:0.85rem;"><div style="display:flex; justify-content:space-between; margin-bottom:5px; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 3px;"><strong style="color:var(--accent-blue);">🙋 ${c.username}</strong><span style="color:var(--text-muted); font-size:0.75rem;">${c.created_at}</span></div><div style="line-height:1.4;">${c.comment}</div></div>`;
|
||||
});
|
||||
}
|
||||
const cl = document.getElementById('commentsList');
|
||||
cl.innerHTML = html; cl.scrollTop = cl.scrollHeight;
|
||||
});
|
||||
}
|
||||
document.getElementById('commentAddForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
let taskId = document.getElementById('commentTaskId').value;
|
||||
let comment = document.getElementById('newCommentText').value;
|
||||
let fd = new FormData(); fd.append('task_id', taskId); fd.append('comment', comment);
|
||||
fetch('/api/tasks/comment', {method:'POST', body:fd}).then(res=>res.json()).then(data=>{
|
||||
if(data.success) { document.getElementById('newCommentText').value = ''; loadComments(taskId); }
|
||||
});
|
||||
});
|
||||
|
||||
function toggleTaskGlobal(taskId, projectId, state) { let comment = prompt("Kurzer Kommentar zur Erledigung:", ""); if (comment === null) return; const formData = new FormData(); formData.append('task_id', taskId); formData.append('project_id', projectId); formData.append('comment', comment); fetch('/api/tasks/toggle', { method: 'POST', body: formData }).then(res=>res.json()).then(data=>{ if(data.success) loadMyArea(); }); }
|
||||
|
||||
// --- PROJEKTE ---
|
||||
@@ -507,7 +566,7 @@
|
||||
if (isDone && t.completion_comment) completedText += `<div style="color: var(--text-muted); margin-top:4px; font-style:italic;">💬 "${t.completion_comment}"</div>`;
|
||||
let assignHtml = t.assigned_username ? `<span style="color: var(--accent-blue);">🙋 Zuweisung: ${t.assigned_username}</span>` : '';
|
||||
const safeTitle = t.title.replace(/'/g, "\\'").replace(/"/g, '"'); const safePhase = t.phase.replace(/'/g, "\\'").replace(/"/g, '"');
|
||||
let actionsHtml = !isClosed ? `<div class="task-actions"><button class="btn-icon" onclick="openEditTodoModal(${t.id}, '${safeTitle}', '${safePhase}', ${t.priority}, '${t.deadline || ''}', '${t.assigned_to || ''}')" title="Bearbeiten">✏️</button><button class="btn-icon del" onclick="deleteTodo(${t.id}, '${safeTitle}')" title="Löschen">🗑️</button></div>` : '';
|
||||
let actionsHtml = !isClosed ? `<div class="task-actions"><button class="btn-icon" onclick="openComments(${t.id}, '${safeTitle}')" title="Notizen">💬</button><button class="btn-icon" onclick="openEditTodoModal(${t.id}, '${safeTitle}', '${safePhase}', ${t.priority}, '${t.deadline || ''}', '${t.assigned_to || ''}')" title="Bearbeiten">✏️</button><button class="btn-icon del" onclick="deleteTodo(${t.id}, '${safeTitle}')" title="Löschen">🗑️</button></div>` : '';
|
||||
list.innerHTML += `<div class="task-item ${isDone ? 'done' : ''}"><input type="checkbox" class="task-checkbox" ${isDone ? 'checked' : ''} ${disabledAttr} onclick="event.preventDefault(); toggleTask(${t.id}, ${t.is_completed})"><div class="task-details"><div style="display: flex; align-items: center; gap: 10px;"><span class="task-text" style="font-size: 1.05rem;">${t.title}</span>${prioHtml}</div><div class="task-meta">${tTimelineHtml} ${assignHtml}</div><div>${completedText}</div></div>${actionsHtml}</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ try {
|
||||
try { $pdo->exec("ALTER TABLE tasks ADD COLUMN completion_comment TEXT"); } catch (PDOException $e) {}
|
||||
try { $pdo->exec("ALTER TABLE tasks ADD COLUMN assigned_to INTEGER"); } catch (PDOException $e) {}
|
||||
|
||||
// NEU: Auto-Migration für Task Notizen
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS task_comments (id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER NOT NULL, username TEXT, comment TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)");
|
||||
|
||||
$stmt = $pdo->query("SELECT COUNT(*) FROM users");
|
||||
if ($stmt->fetchColumn() == 0) {
|
||||
$hash = password_hash('admin', PASSWORD_DEFAULT);
|
||||
|
||||
@@ -188,4 +188,33 @@ function handleGetMyArea() {
|
||||
|
||||
echo json_encode(['success' => true, 'projects' => $myProjects, 'tasks' => $myTasks]); exit;
|
||||
}
|
||||
|
||||
// NEU: Task Notizen laden
|
||||
function handleGetTaskComments() {
|
||||
global $pdo; @session_start();
|
||||
if (!isset($_SESSION['user_id'])) { http_response_code(401); exit; }
|
||||
$taskId = $_GET['task_id'] ?? 0;
|
||||
$stmt = $pdo->prepare("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at ASC");
|
||||
$stmt->execute([$taskId]);
|
||||
echo json_encode(['success' => true, 'data' => $stmt->fetchAll()]); exit;
|
||||
}
|
||||
|
||||
// NEU: Task Notiz hinzufügen
|
||||
function handleAddTaskComment() {
|
||||
global $pdo; @session_start();
|
||||
if (!isset($_SESSION['user_id'])) { http_response_code(401); exit; }
|
||||
$taskId = $_POST['task_id'] ?? 0;
|
||||
$comment = trim($_POST['comment'] ?? '');
|
||||
|
||||
if ($comment !== '') {
|
||||
$pdo->prepare("INSERT INTO task_comments (task_id, username, comment) VALUES (?, ?, ?)")->execute([$taskId, $_SESSION['username'], $comment]);
|
||||
|
||||
$stmt = $pdo->prepare("SELECT project_id FROM tasks WHERE id = ?");
|
||||
$stmt->execute([$taskId]);
|
||||
if ($t = $stmt->fetch()) {
|
||||
$pdo->prepare("UPDATE projects SET last_activity = CURRENT_TIMESTAMP WHERE id = ?")->execute([$t['project_id']]);
|
||||
}
|
||||
}
|
||||
echo json_encode(['success' => true]); exit;
|
||||
}
|
||||
?>
|
||||
@@ -28,6 +28,8 @@ if ($path === '/' || $path === '') { header('Content-Type: text/html'); readfile
|
||||
} elseif ($path === '/api/tasks/toggle' && $method === 'POST') { handleToggleTask();
|
||||
} elseif ($path === '/api/tasks/edit' && $method === 'POST') { handleEditTask();
|
||||
} elseif ($path === '/api/tasks/delete' && $method === 'POST') { handleDeleteTask();
|
||||
} elseif ($path === '/api/tasks/comments' && $method === 'GET') { handleGetTaskComments(); // NEU
|
||||
} elseif ($path === '/api/tasks/comment' && $method === 'POST') { handleAddTaskComment(); // NEU
|
||||
} elseif ($path === '/api/myarea' && $method === 'GET') { handleGetMyArea();
|
||||
} elseif ($path === '/api/me' && $method === 'GET') {
|
||||
@session_start(); if (isset($_SESSION['user_id'])) { echo json_encode(['success' => true, 'username' => $_SESSION['username'], 'role' => $_SESSION['role'], 'user_id' => $_SESSION['user_id']]); } else { http_response_code(401); echo json_encode(['success' => false, 'error' => 'Not authenticated']); } exit;
|
||||
|
||||
Reference in New Issue
Block a user