Add create task modal

This commit is contained in:
2026-06-26 18:07:11 +02:00
parent 1de4169303
commit 13cec807a2
4 changed files with 73 additions and 51 deletions
+56 -30
View File
@@ -1,5 +1,11 @@
const states = ["Backlog", "In Progress", "Review", "Done"]; const states = ["Backlog", "In Progress", "Review", "Done"];
const priorities = ["Low", "Normal", "High", "Urgent"]; const priorities = ["Low", "Normal", "Medium", "High", "Urgent"];
const assignees = [
{ value: "bao", label: "Bao" },
{ value: "maxi", label: "Maxi" },
{ value: "luna", label: "Luna" },
{ value: "iris", label: "Iris" }
];
const app = document.querySelector("#app"); const app = document.querySelector("#app");
const state = { const state = {
@@ -132,10 +138,9 @@ function boardView() {
<span class="status-pill"><span class="dot ${connected ? "ok" : ""}"></span>OpenClaw ${connected ? "verbunden" : "pruefen"}</span> <span class="status-pill"><span class="dot ${connected ? "ok" : ""}"></span>OpenClaw ${connected ? "verbunden" : "pruefen"}</span>
</div> </div>
</header> </header>
<form class="quick-create" id="quick-create"> <div class="quick-create">
<input name="title" autocomplete="off" maxlength="180" placeholder="New task title"> <button class="primary" type="button" id="open-create-task">Create task</button>
<button type="submit">Create task</button> </div>
</form>
<section class="kanban"> <section class="kanban">
${states.map((name) => column(name)).join("")} ${states.map((name) => column(name)).join("")}
</section> </section>
@@ -186,23 +191,23 @@ function taskDialog() {
<label class="field">Titel <label class="field">Titel
<input name="title" required> <input name="title" required>
</label> </label>
<label class="field">Details <label class="field">Beschreibung
<textarea name="detail"></textarea> <textarea name="detail"></textarea>
</label> </label>
<div class="grid"> <div class="grid">
<label class="field">Status <label class="field status-field">Status
<select name="state">${states.map((x) => `<option>${x}</option>`).join("")}</select> <select name="state">${states.map((x) => `<option>${x}</option>`).join("")}</select>
</label> </label>
<label class="field">Prioritaet <label class="field">Priorität
<select name="priority">${priorities.map((x) => `<option>${x}</option>`).join("")}</select> <select name="priority">${priorities.map((x) => `<option>${x}</option>`).join("")}</select>
</label> </label>
</div> </div>
<label class="field">Assignee <label class="field">Zuweisen an
<input name="assignee" value="${escapeHtml(state.settings?.defaultAssignee || "luna")}"> <select name="assignee">${assigneeOptions(state.settings?.defaultAssignee || "luna")}</select>
</label> </label>
<div class="dialog-actions"> <div class="dialog-actions">
<button class="ghost" type="button" id="cancel-task">Abbrechen</button> <button class="ghost" type="button" id="cancel-task">Abbrechen</button>
<button class="primary" type="submit">Speichern</button> <button class="primary" type="submit" id="save-task">Speichern</button>
</div> </div>
</form> </form>
</dialog> </dialog>
@@ -214,41 +219,36 @@ function bindBoard() {
const form = document.querySelector("#task-form"); const form = document.querySelector("#task-form");
const dialogTitle = document.querySelector("#dialog-title"); const dialogTitle = document.querySelector("#dialog-title");
const dialogMeta = document.querySelector("#dialog-meta"); const dialogMeta = document.querySelector("#dialog-meta");
const saveTaskButton = document.querySelector("#save-task");
let draggedTaskId = null; let draggedTaskId = null;
const resetDialog = () => { const resetDialog = () => {
form.reset(); form.reset();
form.elements.id.value = ""; form.elements.id.value = "";
form.elements.assignee.value = state.settings?.defaultAssignee || "luna"; form.elements.state.value = "Backlog";
form.elements.priority.value = "Medium";
form.elements.assignee.value = normalizeAssignee(state.settings?.defaultAssignee || "luna");
form.classList.add("task-form-create");
dialogTitle.textContent = "Neue Aufgabe"; dialogTitle.textContent = "Neue Aufgabe";
dialogMeta.textContent = ""; dialogMeta.textContent = "Erstelle eine neue Aufgabe fuer das Mission-Control-Board.";
saveTaskButton.textContent = "Aufgabe erstellen";
}; };
const openTaskDialog = (task) => { const openTaskDialog = (task) => {
form.classList.remove("task-form-create");
form.elements.id.value = task.id; form.elements.id.value = task.id;
form.elements.title.value = task.title; form.elements.title.value = task.title;
form.elements.detail.value = task.detail || ""; form.elements.detail.value = task.detail || "";
form.elements.state.value = task.state; form.elements.state.value = task.state;
form.elements.priority.value = task.priority; form.elements.priority.value = task.priority;
form.elements.assignee.value = task.assignee || ""; ensureAssigneeOption(form.elements.assignee, task.assignee || state.settings?.defaultAssignee || "luna");
form.elements.assignee.value = normalizeAssignee(task.assignee || state.settings?.defaultAssignee || "luna");
dialogTitle.textContent = task.title; dialogTitle.textContent = task.title;
dialogMeta.textContent = `Status ${task.state} · Aktualisiert ${formatDate(task.updatedAt)} · Erstellt ${formatDate(task.createdAt)}`; dialogMeta.textContent = `Status ${task.state} · Aktualisiert ${formatDate(task.updatedAt)} · Erstellt ${formatDate(task.createdAt)}`;
saveTaskButton.textContent = "Speichern";
dialog.showModal(); dialog.showModal();
}; };
document.querySelector("#quick-create").addEventListener("submit", async (event) => { document.querySelector("#open-create-task").addEventListener("click", () => {
event.preventDefault(); resetDialog();
const formData = new FormData(event.currentTarget); dialog.showModal();
const title = String(formData.get("title") || "").trim();
if (!title) return;
await api("/api/tasks", {
method: "POST",
body: JSON.stringify({
title,
state: "Backlog",
priority: "Normal",
assignee: state.settings?.defaultAssignee || "luna"
})
});
await loadTasks();
renderApp();
}); });
document.querySelector("#cancel-task").addEventListener("click", () => dialog.close()); document.querySelector("#cancel-task").addEventListener("click", () => dialog.close());
dialog.addEventListener("click", (event) => { dialog.addEventListener("click", (event) => {
@@ -388,4 +388,30 @@ function formatDate(value) {
return new Date(value).toLocaleString("de-DE", { dateStyle: "short", timeStyle: "short" }); return new Date(value).toLocaleString("de-DE", { dateStyle: "short", timeStyle: "short" });
} }
function normalizeAssignee(value) {
return String(value || "").trim().toLowerCase();
}
function assigneeLabel(value) {
const normalized = normalizeAssignee(value);
const known = assignees.find((x) => x.value === normalized);
if (known) return known.label;
return normalized ? normalized.charAt(0).toUpperCase() + normalized.slice(1) : "Luna";
}
function assigneeOptions(selected) {
const normalized = normalizeAssignee(selected);
const options = [...assignees];
if (normalized && !options.some((x) => x.value === normalized)) {
options.push({ value: normalized, label: assigneeLabel(normalized) });
}
return options.map((x) => `<option value="${escapeHtml(x.value)}" ${x.value === normalized ? "selected" : ""}>${escapeHtml(x.label)}</option>`).join("");
}
function ensureAssigneeOption(select, value) {
const normalized = normalizeAssignee(value);
if (!normalized || Array.from(select.options).some((option) => option.value === normalized)) return;
select.add(new Option(assigneeLabel(normalized), normalized));
}
init(); init();
+2 -2
View File
@@ -4,10 +4,10 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Maxi Mission Control</title> <title>Maxi Mission Control</title>
<link rel="stylesheet" href="/styles.css?v=20260626-nexus-board"> <link rel="stylesheet" href="/styles.css?v=20260626-create-modal">
</head> </head>
<body> <body>
<main id="app"></main> <main id="app"></main>
<script type="module" src="/app.js?v=20260626-nexus-board"></script> <script type="module" src="/app.js?v=20260626-create-modal"></script>
</body> </body>
</html> </html>
+14 -18
View File
@@ -255,25 +255,13 @@ input:focus, textarea:focus, select:focus {
} }
.quick-create { .quick-create {
display: grid; display: flex;
grid-template-columns: minmax(180px, 360px) max-content; justify-content: flex-start;
gap: 8px;
align-items: stretch;
margin-bottom: 12px; margin-bottom: 12px;
} }
.quick-create input {
font-size: 10px;
}
.quick-create button { .quick-create button {
min-width: 100px; min-width: 112px;
padding: 0 14px;
border: 1px solid #443d7c;
border-radius: 8px;
background: #211d39;
color: #c1b8ff;
font-size: 9px;
} }
.kanban { .kanban {
@@ -467,9 +455,9 @@ input:focus, textarea:focus, select:focus {
dialog { dialog {
width: min(560px, calc(100vw - 32px)); width: min(560px, calc(100vw - 32px));
padding: 22px; padding: 24px;
border: 1px solid var(--line); border: 1px solid var(--line);
border-radius: 10px; border-radius: 8px;
background: linear-gradient(145deg, rgba(18, 21, 29, 0.98), rgba(10, 12, 18, 0.98)); background: linear-gradient(145deg, rgba(18, 21, 29, 0.98), rgba(10, 12, 18, 0.98));
color: var(--text); color: var(--text);
box-shadow: var(--shadow); box-shadow: var(--shadow);
@@ -482,7 +470,7 @@ dialog::backdrop {
dialog h2 { dialog h2 {
margin: 0; margin: 0;
font-size: 18px; font-size: 20px;
} }
.dialog-actions { .dialog-actions {
@@ -499,6 +487,14 @@ dialog h2 {
font-size: 10px; font-size: 10px;
} }
.task-form-create .status-field {
display: none;
}
.task-form-create .grid {
grid-template-columns: 1fr;
}
@media (max-width: 980px) { @media (max-width: 980px) {
.app-shell { .app-shell {
grid-template-columns: 1fr; grid-template-columns: 1fr;
+1 -1
View File
@@ -20,7 +20,7 @@ const openClawBaseUrl = (process.env.OPENCLAW_BASE_URL || "http://openclaw-gatew
const openClawToken = process.env.OPENCLAW_GATEWAY_TOKEN || ""; const openClawToken = process.env.OPENCLAW_GATEWAY_TOKEN || "";
const states = ["Backlog", "In Progress", "Review", "Done"]; const states = ["Backlog", "In Progress", "Review", "Done"];
const priorities = ["Low", "Normal", "High", "Urgent"]; const priorities = ["Low", "Normal", "Medium", "High", "Urgent"];
let db; let db;
function mustEnv(name) { function mustEnv(name) {