fix: key drag targets by date, not weekday, in views wider than a week
Some checks are pending
Playwright Tests / test (push) Waiting to run

The drop preview and the empty-slot input were keyed by date.getDay(), which
is only unique inside a 7-day window. At 21 days every column sharing the
hovered column's weekday lit up — the mirrored drop targets, one per Tuesday.

The invisible half was worse: handleDrop resolved its target with
visibleDays.find(d => d.getDay() === dayOfWeek), so dropping on the second or
third Tuesday scheduled the task on the first one. Both now use the column's
own date; handleDragOver/handleDrop take a Date instead of a weekday number.

Task rendering was already date-based, so only drag state was affected.

v1.115.3

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-09-03 13:54:50 +02:00
parent 6f4fe219dc
commit d4ec6b0ef3
4 changed files with 35 additions and 19 deletions

View File

@ -2,6 +2,21 @@
All notable changes to this project are documented in this file. All notable changes to this project are documented in this file.
## [1.115.3] - 2026-09-03
### Fixed
- Drag & drop in views wider than 7 days: the drop target was keyed by weekday
(`date.getDay()`), which is only unique inside a 7-day window. In a 21-day view every
column sharing the weekday of the hovered one lit up as a drop target — three mirrored
highlights, one per Tuesday. Now keyed by the local date.
- Same bug, worse half: `handleDrop` resolved the target date with
`visibleDays.find(d => d.getDay() === dayOfWeek)`, so dropping a task on the second or
third Tuesday of a wide view silently scheduled it on the *first* Tuesday. The drop now
uses the column's own date. Tasks dragged in a >7-day view before this fix may sit on the
wrong date.
- The inline "click an empty slot to add a task" input had the same weekday key, so clicking
one slot opened an input in every column with that weekday.
## [1.115.2] - 2026-09-03 ## [1.115.2] - 2026-09-03
### Changed ### Changed

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "my-weekly-todo-list", "name": "my-weekly-todo-list",
"version": "1.115.2", "version": "1.115.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "my-weekly-todo-list", "name": "my-weekly-todo-list",
"version": "1.115.2", "version": "1.115.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@auth/prisma-adapter": "^2.11.1", "@auth/prisma-adapter": "^2.11.1",

View File

@ -1,6 +1,6 @@
{ {
"name": "my-weekly-todo-list", "name": "my-weekly-todo-list",
"version": "1.115.2", "version": "1.115.3",
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -1071,8 +1071,10 @@ export default function WeeklyView() {
const [slideDirection, setSlideDirection] = useState<"next" | "prev" | null>( const [slideDirection, setSlideDirection] = useState<"next" | "prev" | null>(
null, null,
); );
// Keyed by local date ("YYYY-MM-DD"), not by weekday: with more than 7 visible day
// columns a weekday matches several columns at once, which lit up every Tuesday.
const [activeSlot, setActiveSlot] = useState<{ const [activeSlot, setActiveSlot] = useState<{
day: number; dateKey: string;
slot: string; slot: string;
} | null>(null); } | null>(null);
const [newSlotTask, setNewSlotTask] = useState(""); const [newSlotTask, setNewSlotTask] = useState("");
@ -1081,7 +1083,7 @@ export default function WeeklyView() {
); );
const [currentTime, setCurrentTime] = useState(new Date()); const [currentTime, setCurrentTime] = useState(new Date());
const [dropPreview, setDropPreview] = useState<{ const [dropPreview, setDropPreview] = useState<{
day?: number; dateKey?: string;
slot?: string; slot?: string;
listId?: string; listId?: string;
slotIdx?: number; slotIdx?: number;
@ -5023,7 +5025,7 @@ export default function WeeklyView() {
const handleDragOver = ( const handleDragOver = (
e: DragEvent | React.DragEvent, e: DragEvent | React.DragEvent,
dayOfWeek?: number, date?: Date,
slot?: string, slot?: string,
) => { ) => {
// Reject someday list drags on day slots // Reject someday list drags on day slots
@ -5039,17 +5041,16 @@ export default function WeeklyView() {
e.dataTransfer.dropEffect = "move"; e.dataTransfer.dropEffect = "move";
} }
// Update drop preview if we have day and slot info // Update drop preview if we have day and slot info
if (dayOfWeek !== undefined && slot) { if (date && slot) {
setDropPreview({ day: dayOfWeek, slot }); setDropPreview({ dateKey: formatDateToISO(date), slot });
} }
}; };
const handleDrop = async (e: DragEvent, dayOfWeek: number, slot?: string) => { const handleDrop = async (e: DragEvent, date: Date, slot?: string) => {
e.preventDefault(); e.preventDefault();
if (draggedTask) { if (draggedTask) {
const visibleDays = getVisibleDays(); const targetDateObj = date;
const targetDateObj = const dayOfWeek = targetDateObj.getDay();
visibleDays.find((d) => d.getDay() === dayOfWeek) || new Date();
let targetSlot = slot; let targetSlot = slot;
@ -7652,7 +7653,7 @@ export default function WeeklyView() {
const isHourStart = minutes === "00"; const isHourStart = minutes === "00";
const slotEvents = getEventsForSlot(date, slot); const slotEvents = getEventsForSlot(date, slot);
const isActive = const isActive =
activeSlot?.day === date.getDay() && activeSlot?.dateKey === formatDateToISO(date) &&
activeSlot?.slot === slot; activeSlot?.slot === slot;
const isProtected = isSlotProtected(date, slot); const isProtected = isSlotProtected(date, slot);
const isOccupiedByTask = isSlotOccupiedByTask(date, slot, draggedTask?.id); const isOccupiedByTask = isSlotOccupiedByTask(date, slot, draggedTask?.id);
@ -7674,7 +7675,7 @@ export default function WeeklyView() {
} }
if (!isActive) { if (!isActive) {
setActiveSlot({ day: date.getDay(), slot }); setActiveSlot({ dateKey: formatDateToISO(date), slot });
setNewSlotTask(""); setNewSlotTask("");
} }
}; };
@ -7694,11 +7695,11 @@ export default function WeeklyView() {
const handleSlotDrop = (e: React.DragEvent) => { const handleSlotDrop = (e: React.DragEvent) => {
e.preventDefault(); e.preventDefault();
if (isProtected || isOccupiedByTask) return; // Don't allow dropping on protected or occupied slots if (isProtected || isOccupiedByTask) return; // Don't allow dropping on protected or occupied slots
handleDrop(e, date.getDay(), slot); handleDrop(e, date, slot);
}; };
const isDropTarget = const isDropTarget =
dropPreview?.day === date.getDay() && dropPreview?.dateKey === formatDateToISO(date) &&
dropPreview?.slot === slot; dropPreview?.slot === slot;
const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
@ -7736,7 +7737,7 @@ export default function WeeklyView() {
}} }}
onDragOver={(e) => onDragOver={(e) =>
!isProtected && !isOccupiedByTask && !isProtected && !isOccupiedByTask &&
handleDragOver(e, date.getDay(), slot) handleDragOver(e, date, slot)
} }
onDrop={handleSlotDrop} onDrop={handleSlotDrop}
> >
@ -7947,8 +7948,8 @@ export default function WeeklyView() {
</div> </div>
) : ( ) : (
<div <div
onDragOver={(e) => handleDragOver(e, date.getDay())} onDragOver={(e) => handleDragOver(e, date)}
onDrop={(e) => handleDrop(e, date.getDay())} onDrop={(e) => handleDrop(e, date)}
onDragLeave={handleDragLeave} onDragLeave={handleDragLeave}
style={{ flex: 1 }} style={{ flex: 1 }}
> >