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
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:
parent
6f4fe219dc
commit
d4ec6b0ef3
15
CHANGELOG.md
15
CHANGELOG.md
@ -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
4
package-lock.json
generated
@ -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",
|
||||||
|
|||||||
@ -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": {
|
||||||
|
|||||||
@ -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 }}
|
||||||
>
|
>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user