diff --git a/package.json b/package.json index 7e5179a..9086089 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.54.1", + "version": "1.55.0", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "main": "index.js", "scripts": { diff --git a/src/app/globals.css b/src/app/globals.css index 37f1ebb..44357fb 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1972,6 +1972,258 @@ h3 { } } +/* Kanban Detail Modal */ +.kanban-detail-overlay { + position: fixed; + inset: 0; + z-index: 2000; + background: rgba(0,0,0,0.4); + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + animation: kanbanFadeIn 0.15s ease; +} +@keyframes kanbanFadeIn { + from { opacity: 0; } + to { opacity: 1; } +} +.kanban-detail-modal { + background: var(--weekly-bg, #fff); + border-radius: 12px; + width: 100%; + max-width: 560px; + max-height: 85vh; + display: flex; + flex-direction: column; + box-shadow: 0 20px 60px rgba(0,0,0,0.3); + animation: kanbanSlideUp 0.2s ease; + overflow: hidden; +} +@keyframes kanbanSlideUp { + from { transform: translateY(20px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } +} +.kanban-detail-header { + display: flex; + align-items: center; + gap: 8px; + padding: 16px 20px; + border-bottom: 1px solid var(--weekly-border, #e5e7eb); +} +.kanban-detail-title-input { + flex: 1; + font-size: 1.1rem; + font-weight: 600; + border: none; + background: transparent; + color: var(--weekly-text, #111); + outline: none; + font-family: inherit; +} +.kanban-detail-title-input:focus { + border-bottom: 2px solid var(--weekly-accent, #6366f1); +} +.kanban-detail-close { + background: none; + border: none; + cursor: pointer; + color: var(--weekly-text-muted, #9ca3af); + padding: 4px; + border-radius: 6px; + display: flex; +} +.kanban-detail-close:hover { + background: var(--weekly-hover, rgba(0,0,0,0.05)); + color: var(--weekly-text, #111); +} +.kanban-detail-body { + padding: 16px 20px; + overflow-y: auto; + flex: 1; + display: flex; + flex-direction: column; + gap: 16px; +} +.kanban-detail-meta { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; +} +.kanban-detail-field { + display: flex; + flex-direction: column; + gap: 4px; +} +.kanban-detail-field label { + font-size: 0.7rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--weekly-text-muted, #9ca3af); + display: flex; + align-items: center; + gap: 4px; +} +.kanban-detail-select, +.kanban-detail-date { + padding: 6px 8px; + border: 1px solid var(--weekly-border, #e5e7eb); + border-radius: 6px; + background: var(--weekly-bg, #fff); + color: var(--weekly-text, #111); + font-size: 0.8rem; + font-family: inherit; + cursor: pointer; +} +.kanban-detail-select:focus, +.kanban-detail-date:focus { + outline: none; + border-color: var(--weekly-accent, #6366f1); +} +.kanban-detail-section { + display: flex; + flex-direction: column; + gap: 6px; +} +.kanban-detail-section > label { + font-size: 0.7rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--weekly-text-muted, #9ca3af); + display: flex; + align-items: center; + gap: 4px; +} +.kanban-detail-notes { + padding: 10px 12px; + border: 1px solid var(--weekly-border, #e5e7eb); + border-radius: 8px; + background: var(--weekly-bg, #fff); + color: var(--weekly-text, #111); + font-size: 0.85rem; + font-family: inherit; + resize: vertical; + min-height: 80px; + line-height: 1.5; +} +.kanban-detail-notes:focus { + outline: none; + border-color: var(--weekly-accent, #6366f1); +} +.kanban-detail-subtasks { + display: flex; + flex-direction: column; + gap: 4px; +} +.kanban-detail-subtask { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 0; +} +.kanban-detail-subtask input[type="checkbox"] { + width: 15px; + height: 15px; + cursor: pointer; + flex-shrink: 0; +} +.kanban-detail-subtask-title { + flex: 1; + border: none; + background: transparent; + color: var(--weekly-text, #111); + font-size: 0.85rem; + font-family: inherit; + outline: none; + padding: 2px 4px; + border-radius: 4px; +} +.kanban-detail-subtask-title:focus { + background: var(--weekly-hover, rgba(0,0,0,0.03)); +} +.kanban-detail-subtask-title.completed { + text-decoration: line-through; + opacity: 0.5; +} +.kanban-detail-subtask-delete { + background: none; + border: none; + cursor: pointer; + color: var(--weekly-text-muted, #d1d5db); + padding: 2px; + border-radius: 4px; + display: flex; + opacity: 0; + transition: opacity 0.1s; +} +.kanban-detail-subtask:hover .kanban-detail-subtask-delete { + opacity: 1; +} +.kanban-detail-subtask-delete:hover { + color: #ef4444; + background: rgba(239,68,68,0.08); +} +.kanban-detail-subtask-add { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 0; + color: var(--weekly-text-muted, #9ca3af); +} +.kanban-detail-subtask-input { + flex: 1; + border: none; + background: transparent; + color: var(--weekly-text, #111); + font-size: 0.85rem; + font-family: inherit; + outline: none; + padding: 2px 4px; +} +.kanban-detail-subtask-input::placeholder { + color: var(--weekly-text-muted, #d1d5db); +} +.kanban-detail-footer { + padding: 12px 20px; + border-top: 1px solid var(--weekly-border, #e5e7eb); + display: flex; + justify-content: flex-end; +} +.kanban-detail-delete { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 14px; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 0.8rem; + font-family: inherit; + color: #ef4444; + background: rgba(239,68,68,0.08); +} +.kanban-detail-delete:hover { + background: rgba(239,68,68,0.15); +} + +@media (max-width: 600px) { + .kanban-detail-modal { + max-width: 100%; + max-height: 95vh; + border-radius: 12px 12px 0 0; + margin-top: auto; + } + .kanban-detail-overlay { + align-items: flex-end; + padding: 0; + } + .kanban-detail-meta { + grid-template-columns: 1fr; + } +} + .preferences-overlay { position: fixed; inset: 0; diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index 9cdd627..c48f4dc 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -2077,6 +2077,7 @@ export default function WeeklyView() { const [kanbanDeleteStageId, setKanbanDeleteStageId] = useState(null); const [kanbanAddingStageId, setKanbanAddingStageId] = useState(null); const [kanbanNewTaskTitle, setKanbanNewTaskTitle] = useState(""); + const [kanbanDetailTask, setKanbanDetailTask] = useState(null); const [protectEventTimes, setProtectEventTimes] = useState(false); const [unlockedEvents, setUnlockedEvents] = useState>(new Set()); @@ -6456,10 +6457,10 @@ export default function WeeklyView() { e.dataTransfer.effectAllowed = "move"; }} onClick={(e) => { - // Don't open notes if clicking checkbox, contentEditable, or buttons + // Don't open detail if clicking checkbox, contentEditable, or buttons const target = e.target as HTMLElement; if (target.tagName === "INPUT" || target.contentEditable === "true" || target.closest("button")) return; - setSelectedTaskForNotes(task); + setKanbanDetailTask(task); }} style={{ cursor: "pointer" }} > @@ -8845,6 +8846,210 @@ export default function WeeklyView() { /> ) } + + {/* Kanban Detail Modal */} + {kanbanDetailTask && (() => { + const task = kanbanDetailTask; + // Re-resolve the task from state to get latest data + const liveTask = findTaskAnywhere(task.id) || task; + return ( +
setKanbanDetailTask(null)}> +
e.stopPropagation()}> + {/* Header */} +
+
+ { toggleTask(liveTask.id); setKanbanDetailTask({ ...liveTask, completed: !liveTask.completed }); }} + style={{ width: "18px", height: "18px", cursor: "pointer", accentColor: "var(--weekly-accent, #6366f1)" }} + /> + { + const val = e.target.value.trim(); + if (val && val !== liveTask.title) { + updateTask(liveTask.id, val); + setKanbanDetailTask({ ...liveTask, title: val }); + } + }} + onKeyDown={(e) => { if (e.key === "Enter") (e.target as HTMLInputElement).blur(); }} + className="kanban-detail-title-input" + /> +
+ +
+ +
+ {/* Meta row: project, stage, date */} +
+ {/* Project picker */} +
+ + +
+ + {/* Kanban stage picker */} +
+ + +
+ + {/* Due date */} +
+ + { + const val = e.target.value || null; + updateTaskFields(liveTask.id, { scheduledDate: val }); + setKanbanDetailTask({ ...liveTask, scheduledDate: val }); + }} + className="kanban-detail-date" + /> +
+ + {/* Someday list */} +
+ + +
+
+ + {/* Notes */} +
+ +