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:
parent
c84fd4c60e
commit
855760e39c
@ -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": {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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");
|
||||
}
|
||||
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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user