feat: kanban drag fix, project targets, calendar/simple view polish

- Fix kanban drag: remove confusing card tilt, ensure dragOver always
  calls preventDefault so drops work reliably across all browsers
- Add project drop targets bar in kanban: drag tasks onto project chips
  to assign/unassign projects
- Add "Create Project" button to kanban filter bar
- Right-align kanban filter bar
- Use ProjectIcon (FontAwesome) in kanban cards instead of raw emoji
- Calendar view: Google Calendar-inspired styling (subtle grid lines,
  smaller time labels, cleaner borders)
- Simple view: TeuxDeux-inspired styling (uppercase headers, thick
  bottom border, flat borderless task items, minimal aesthetic)

v1.47.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
mARTin 2026-03-18 00:10:38 +01:00
parent c84fd4c60e
commit 855760e39c
3 changed files with 163 additions and 19 deletions

View File

@ -1,6 +1,6 @@
{
"name": "my-weekly-todo-list",
"version": "1.46.0",
"version": "1.47.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": {

View File

@ -1718,6 +1718,7 @@ h3 {
background: var(--weekly-bg, #fff);
flex-shrink: 0;
flex-wrap: wrap;
justify-content: flex-end;
}
.kanban-filter-search {
@ -1865,7 +1866,8 @@ h3 {
.kanban-card:active {
cursor: grabbing;
transform: rotate(2deg);
opacity: 0.8;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.kanban-card-done {
@ -3782,31 +3784,132 @@ h3 {
border-right: none !important;
}
/* Calendar View (Google Calendar Style - vertical lines) */
/* Calendar View (Google Calendar Style) */
.calendar-view.time-grid-on .weekly-day-column {
border-right: 1px solid #e0e0e0;
border-right: 1px solid #dadce0;
margin: 0 !important;
}
.calendar-view .time-column {
border-right: 1px solid #dadce0;
}
.calendar-view .time-column-header {
border-bottom: 1px solid #dadce0;
}
.calendar-view .time-slot {
border-bottom: 1px solid #f1f3f4;
}
.calendar-view .time-slot:nth-child(even) {
border-bottom: 1px solid #dadce0;
}
.calendar-view .time-slot-label {
color: #70757a;
font-size: 0.65rem;
font-weight: 400;
}
.calendar-view .weekly-day-header {
border-bottom: 1px solid #dadce0;
font-weight: 500;
}
.calendar-view .time-slot-event {
border-radius: 4px;
border-left: none;
font-size: 0.8rem;
}
.dark-mode .calendar-view.time-grid-on .weekly-day-column {
border-right: 1px solid #333;
}
.dark-mode .calendar-view .time-column {
border-right: 1px solid #333;
}
.dark-mode .calendar-view .time-slot {
border-bottom: 1px solid #2a2a2a;
}
.dark-mode .calendar-view .time-slot:nth-child(even) {
border-bottom: 1px solid #333;
}
.dark-mode .calendar-view .time-slot-label {
color: #9aa0a6;
}
.dark-mode .calendar-view .weekly-day-header {
border-bottom: 1px solid #333;
}
/* Simple View (Original Style with Spacing) */
/* Simple View (TeuxDeux Style) */
.simple-view .weekly-day-column {
border-left: 0px !important;
margin: 0 10px !important;
border-right: 1px solid #e8e8e8 !important;
margin: 0 !important;
overflow-x: hidden !important;
word-wrap: break-word;
}
.simple-view .weekly-day-column:last-child {
border-right: none !important;
}
.simple-view .time-column {
border-right: 0px !important;
display: none !important;
}
.simple-view .time-slot-event {
left: 0 !important;
right: 0 !important;
}
.simple-view .weekly-day-header {
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 700;
font-size: 0.75rem;
padding: 12px 8px 8px;
border-bottom: 2px solid #222;
}
.dark-mode .simple-view .weekly-day-header {
border-bottom: 2px solid #666;
}
.dark-mode .simple-view .weekly-day-column {
border-right: 1px solid #333 !important;
}
.simple-view .task-item {
border: none !important;
border-bottom: 1px solid #eee !important;
border-radius: 0 !important;
padding: 8px 4px !important;
background: transparent !important;
box-shadow: none !important;
}
.dark-mode .simple-view .task-item {
border-bottom: 1px solid #333 !important;
}
.simple-view .task-item:hover {
background: rgba(0,0,0,0.02) !important;
}
.dark-mode .simple-view .task-item:hover {
background: rgba(255,255,255,0.03) !important;
}
/* Kanban project drop targets */
.kanban-project-targets {
display: flex;
gap: 6px;
padding: 8px 12px;
border-bottom: 1px solid var(--weekly-border, #e5e7eb);
background: var(--weekly-bg-alt, #f8f9fa);
flex-wrap: wrap;
flex-shrink: 0;
}
.kanban-project-chip {
display: flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: 16px;
font-size: 0.75rem;
border: 2px solid transparent;
cursor: default;
transition: border-color 0.15s, box-shadow 0.15s;
}
.kanban-project-chip-drag-over {
border-color: var(--weekly-accent, #6366f1);
box-shadow: 0 0 0 2px rgba(99,102,241,0.2);
}

View File

@ -5543,8 +5543,8 @@ export default function WeeklyView() {
</span>
<div style={{ display: "flex", gap: "4px" }}>
{[
{ key: "simple", icon: <CalendarDays size={13} /> },
{ key: "calendar", icon: <Calendar size={13} /> },
{ key: "simple", icon: <Calendar size={13} /> },
{ key: "calendar", icon: <CalendarDays size={13} /> },
{ key: "list", icon: <ListTodo size={13} /> },
{ key: "kanban", icon: <Kanban size={13} /> },
].map((v) => (
@ -5830,14 +5830,14 @@ export default function WeeklyView() {
className={`p-1.5 rounded transition-colors ${viewStyle === "simple" ? "bg-white shadow-sm text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
title={t.simpleView}
>
<CalendarDays size={16} />
<Calendar size={16} />
</button>
<button
onClick={() => { setViewStyle("calendar"); setShowTimeGrid(true); saveSetting("viewStyle", "calendar"); saveSetting("showTimeGrid", true); }}
className={`p-1.5 rounded transition-colors ${viewStyle === "calendar" ? "bg-white shadow-sm text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
title={t.calendarView}
>
<Calendar size={16} />
<CalendarDays size={16} />
</button>
<button
onClick={() => { setViewStyle("list"); setShowTimeGrid(false); saveSetting("viewStyle", "list"); saveSetting("showTimeGrid", false); }}
@ -6296,7 +6296,7 @@ export default function WeeklyView() {
)}
{task.project && (
<div className="kanban-card-project" style={{ color: task.project.color || "#888" }}>
{task.project.icon || "📁"} {task.project.name}
<ProjectIcon icon={task.project.icon} size={12} color={task.project.color || "#888"} /> {task.project.name}
</div>
)}
{task.somedayListId && (() => {
@ -6308,11 +6308,9 @@ export default function WeeklyView() {
const kanbanColumnDrop = (stageId: string | null) => ({
onDragOver: (e: React.DragEvent) => {
if (e.dataTransfer.types.includes("text/kanban-task")) {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
e.currentTarget.classList.add("kanban-column-drag-over");
}
},
onDragLeave: (e: React.DragEvent) => {
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
@ -6320,15 +6318,36 @@ export default function WeeklyView() {
}
},
onDrop: async (e: React.DragEvent) => {
e.preventDefault();
e.currentTarget.classList.remove("kanban-column-drag-over");
const taskId = e.dataTransfer.getData("text/kanban-task");
if (taskId) {
e.preventDefault();
await updateTaskFields(taskId, { kanbanStage: stageId });
}
},
});
const kanbanProjectDrop = (projectId: string | null) => ({
onDragOver: (e: React.DragEvent) => {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
e.currentTarget.classList.add("kanban-project-chip-drag-over");
},
onDragLeave: (e: React.DragEvent) => {
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
e.currentTarget.classList.remove("kanban-project-chip-drag-over");
}
},
onDrop: async (e: React.DragEvent) => {
e.preventDefault();
e.currentTarget.classList.remove("kanban-project-chip-drag-over");
const taskId = e.dataTransfer.getData("text/kanban-task");
if (taskId) {
await updateTaskFields(taskId, { projectId });
}
},
});
const hasActiveFilters = kanbanSearch || kanbanFilterProject || kanbanFilterList || kanbanFilterWeek;
return (
@ -6388,7 +6407,29 @@ export default function WeeklyView() {
<X size={14} />
</button>
)}
<button
onClick={() => setShowProjectsSidebar(true)}
className="kanban-filter-select"
style={{ display: "flex", alignItems: "center", gap: "4px", cursor: "pointer", fontWeight: 500 }}
>
<Plus size={12} /> {t.projects || "Projects"}
</button>
</div>
{/* Project drop targets — drag tasks here to assign */}
{projects.length > 0 && (
<div className="kanban-project-targets">
{projects.map(p => (
<div key={p.id} className="kanban-project-chip" style={{ background: (p.color || "#6366f1") + "18", color: p.color || "#6366f1" }} {...kanbanProjectDrop(p.id)}>
<ProjectIcon icon={p.icon} size={12} color={p.color || "#6366f1"} />
<span>{p.name}</span>
</div>
))}
<div className="kanban-project-chip" style={{ background: "#f3f4f6", color: "#9ca3af" }} {...kanbanProjectDrop(null)}>
<X size={12} />
<span>{language === "de" ? "Kein Projekt" : "No project"}</span>
</div>
</div>
)}
<div className="kanban-board">
{kanbanStages.map((stage) => {
const stageTasks = filteredKanbanTasks.filter(t => (t.kanbanStage || null) === stage.id);