feat: add setting to show task checkboxes in weekly view
- Add showTaskCheckboxes field to User model in Prisma schema - Create database migration for the new field - Update user profile API to handle the new setting - Implement checkbox rendering in GridTaskBlock controlled by user preference - Add CSS styles for task checkboxes
This commit is contained in:
parent
f0672e5c68
commit
dddde43935
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable: Add showTaskCheckboxes setting
|
||||||
|
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "showTaskCheckboxes" BOOLEAN NOT NULL DEFAULT false;
|
||||||
@ -37,6 +37,7 @@ model User {
|
|||||||
showSomeday Boolean @default(true)
|
showSomeday Boolean @default(true)
|
||||||
showAllDayEvents Boolean @default(true)
|
showAllDayEvents Boolean @default(true)
|
||||||
showSchedule Boolean @default(true)
|
showSchedule Boolean @default(true)
|
||||||
|
showTaskCheckboxes Boolean @default(false)
|
||||||
cellDuration Int @default(30)
|
cellDuration Int @default(30)
|
||||||
viewStyle String @default("grid")
|
viewStyle String @default("grid")
|
||||||
viewDays Int @default(7)
|
viewDays Int @default(7)
|
||||||
|
|||||||
@ -30,6 +30,7 @@ export async function GET(request: NextRequest) {
|
|||||||
showSomeday: true,
|
showSomeday: true,
|
||||||
showAllDayEvents: true,
|
showAllDayEvents: true,
|
||||||
showSchedule: true,
|
showSchedule: true,
|
||||||
|
showTaskCheckboxes: true,
|
||||||
cellDuration: true,
|
cellDuration: true,
|
||||||
viewStyle: true,
|
viewStyle: true,
|
||||||
viewDays: true,
|
viewDays: true,
|
||||||
@ -115,7 +116,8 @@ export async function PATCH(request: NextRequest) {
|
|||||||
dateLayout,
|
dateLayout,
|
||||||
hourLabelFormat, showSubHourSlots, allDayPosition,
|
hourLabelFormat, showSubHourSlots, allDayPosition,
|
||||||
cwFontFamily, cwFontSize, cwFontWeight, cwColor,
|
cwFontFamily, cwFontSize, cwFontWeight, cwColor,
|
||||||
yearFontFamily, yearFontSize, yearFontWeight, yearColor
|
yearFontFamily, yearFontSize, yearFontWeight, yearColor,
|
||||||
|
showTaskCheckboxes
|
||||||
} = body;
|
} = body;
|
||||||
|
|
||||||
const updateData: any = {
|
const updateData: any = {
|
||||||
@ -136,6 +138,7 @@ export async function PATCH(request: NextRequest) {
|
|||||||
...(showSomeday !== undefined && { showSomeday }),
|
...(showSomeday !== undefined && { showSomeday }),
|
||||||
...(showAllDayEvents !== undefined && { showAllDayEvents }),
|
...(showAllDayEvents !== undefined && { showAllDayEvents }),
|
||||||
...(showSchedule !== undefined && { showSchedule }),
|
...(showSchedule !== undefined && { showSchedule }),
|
||||||
|
...(showTaskCheckboxes !== undefined && { showTaskCheckboxes }),
|
||||||
...(cellDuration !== undefined && !isNaN(cellDuration) && { cellDuration }),
|
...(cellDuration !== undefined && !isNaN(cellDuration) && { cellDuration }),
|
||||||
...(viewStyle !== undefined && { viewStyle }),
|
...(viewStyle !== undefined && { viewStyle }),
|
||||||
...(viewDays !== undefined && !isNaN(viewDays) && { viewDays }),
|
...(viewDays !== undefined && !isNaN(viewDays) && { viewDays }),
|
||||||
@ -210,6 +213,7 @@ export async function PATCH(request: NextRequest) {
|
|||||||
showSomeday: true,
|
showSomeday: true,
|
||||||
showAllDayEvents: true,
|
showAllDayEvents: true,
|
||||||
showSchedule: true,
|
showSchedule: true,
|
||||||
|
showTaskCheckboxes: true,
|
||||||
cellDuration: true,
|
cellDuration: true,
|
||||||
viewStyle: true,
|
viewStyle: true,
|
||||||
viewDays: true,
|
viewDays: true,
|
||||||
|
|||||||
@ -1183,8 +1183,8 @@ h3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.weekly-someday.expanded {
|
.weekly-someday.expanded {
|
||||||
max-height: 400px;
|
max-height: none;
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.weekly-someday-bar {
|
.weekly-someday-bar {
|
||||||
@ -1308,6 +1308,7 @@ h3 {
|
|||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
flex: 0 0 280px; /* Fixed width for horizontal scrolling */
|
flex: 0 0 280px; /* Fixed width for horizontal scrolling */
|
||||||
width: 280px;
|
width: 280px;
|
||||||
|
transition: transform 0.2s ease, opacity 0.2s ease;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@ -29,6 +29,7 @@ interface GridTaskBlockProps {
|
|||||||
deleteSubTask: (id: string) => void;
|
deleteSubTask: (id: string) => void;
|
||||||
onSetEditingTaskId?: (id: string | null) => void;
|
onSetEditingTaskId?: (id: string | null) => void;
|
||||||
workingHoursStart: number;
|
workingHoursStart: number;
|
||||||
|
showTaskCheckboxes?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GridTaskBlock({
|
export function GridTaskBlock({
|
||||||
@ -57,7 +58,8 @@ export function GridTaskBlock({
|
|||||||
updateSubTask,
|
updateSubTask,
|
||||||
deleteSubTask,
|
deleteSubTask,
|
||||||
onSetEditingTaskId,
|
onSetEditingTaskId,
|
||||||
workingHoursStart
|
workingHoursStart,
|
||||||
|
showTaskCheckboxes
|
||||||
}: GridTaskBlockProps) {
|
}: GridTaskBlockProps) {
|
||||||
const [isNotesOpen, setIsNotesOpen] = useState(false);
|
const [isNotesOpen, setIsNotesOpen] = useState(false);
|
||||||
const [notesValue, setNotesValue] = useState(task.markdownContent || "");
|
const [notesValue, setNotesValue] = useState(task.markdownContent || "");
|
||||||
@ -166,7 +168,7 @@ export function GridTaskBlock({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`time-slot-task ${task.completed ? "completed" : ""} ${draggedTask?.id === task.id ? "dragging" : ""}`}
|
className={`time-slot-task ${task.completed && !showTaskCheckboxes ? "completed" : ""} ${draggedTask?.id === task.id ? "dragging" : ""}`}
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: `${topOffset}px`,
|
top: `${topOffset}px`,
|
||||||
@ -226,13 +228,22 @@ export function GridTaskBlock({
|
|||||||
whiteSpace: "normal",
|
whiteSpace: "normal",
|
||||||
wordBreak: "break-word",
|
wordBreak: "break-word",
|
||||||
flex: 1,
|
flex: 1,
|
||||||
fontSize: "0.8rem",
|
|
||||||
}}
|
}}
|
||||||
onDoubleClick={(e) => {
|
onDoubleClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setEditingTaskId(task.id);
|
setEditingTaskId(task.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{showTaskCheckboxes && (
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={task.completed}
|
||||||
|
onChange={(e) => { e.stopPropagation(); toggleTask(task.id); }}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="task-checkbox flex-shrink-0"
|
||||||
|
style={{ width: "12px", height: "12px", margin: 0, cursor: "pointer", position: "relative", top: "3px", left: "-2px", accentColor: "#FFF" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{task.externalProvider && (
|
{task.externalProvider && (
|
||||||
!task.externalId ||
|
!task.externalId ||
|
||||||
!task.lastSyncedAt ||
|
!task.lastSyncedAt ||
|
||||||
@ -254,10 +265,12 @@ export function GridTaskBlock({
|
|||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
<span style={task.completed && showTaskCheckboxes ? { opacity: 0.5, flex: 1 } : { flex: 1 }}>{task.title}</span>
|
||||||
{task.markdownContent && (
|
{task.markdownContent && (
|
||||||
<span
|
<span
|
||||||
className="task-note-icon cursor-pointer flex-shrink-0 mt-[2px]"
|
className="task-note-icon cursor-pointer flex-shrink-0 mt-[2px]"
|
||||||
onClick={(e) => { e.stopPropagation(); setIsNotesOpen(!isNotesOpen); }}
|
onClick={(e) => { e.stopPropagation(); setIsNotesOpen(!isNotesOpen); }}
|
||||||
|
style={{ marginLeft: "auto" }}
|
||||||
>
|
>
|
||||||
<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||||
@ -267,7 +280,6 @@ export function GridTaskBlock({
|
|||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{task.title}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -360,6 +372,12 @@ export function GridTaskBlock({
|
|||||||
<button className="notes-toolbar-btn" onClick={() => insertMarkdown("*", "*")} title="Italic">i</button>
|
<button className="notes-toolbar-btn" onClick={() => insertMarkdown("*", "*")} title="Italic">i</button>
|
||||||
<button className="notes-toolbar-btn" onClick={() => insertMarkdown("- ")} title="List">☑</button>
|
<button className="notes-toolbar-btn" onClick={() => insertMarkdown("- ")} title="List">☑</button>
|
||||||
<span style={{ marginLeft: "auto", fontSize: "0.75rem", color: "#999" }}>Markdown</span>
|
<span style={{ marginLeft: "auto", fontSize: "0.75rem", color: "#999" }}>Markdown</span>
|
||||||
|
<button
|
||||||
|
className="notes-toolbar-btn"
|
||||||
|
onClick={() => setIsNotesOpen(false)}
|
||||||
|
title="Close"
|
||||||
|
style={{ marginLeft: "8px", fontSize: "1rem", lineHeight: 1 }}
|
||||||
|
>×</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
ref={notesRef}
|
ref={notesRef}
|
||||||
|
|||||||
@ -214,6 +214,7 @@ const translations: Record<string, any> = {
|
|||||||
goalScopeDay: "Per Day",
|
goalScopeDay: "Per Day",
|
||||||
goalFallback: "Goal Fallback Type",
|
goalFallback: "Goal Fallback Type",
|
||||||
defaultGoal: "Custom Default Goal",
|
defaultGoal: "Custom Default Goal",
|
||||||
|
showTaskCheckboxes: "Show Checkboxes on Tasks",
|
||||||
showSomeday: "Show Someday Section",
|
showSomeday: "Show Someday Section",
|
||||||
showAllDay: "Show All-Day Section",
|
showAllDay: "Show All-Day Section",
|
||||||
allDayPosition: "All-Day Events Position",
|
allDayPosition: "All-Day Events Position",
|
||||||
@ -278,6 +279,7 @@ const translations: Record<string, any> = {
|
|||||||
goalScopeDay: "Pro Tag",
|
goalScopeDay: "Pro Tag",
|
||||||
goalFallback: "Ziel-Fallback-Typ",
|
goalFallback: "Ziel-Fallback-Typ",
|
||||||
defaultGoal: "Benutzerdefiniertes Standardziel",
|
defaultGoal: "Benutzerdefiniertes Standardziel",
|
||||||
|
showTaskCheckboxes: "Checkboxen bei Aufgaben anzeigen",
|
||||||
showSomeday: "Irgendwann-Bereich anzeigen",
|
showSomeday: "Irgendwann-Bereich anzeigen",
|
||||||
showAllDay: "Ganztägige Ereignisse anzeigen",
|
showAllDay: "Ganztägige Ereignisse anzeigen",
|
||||||
allDayPosition: "Position ganztägiger Ereignisse",
|
allDayPosition: "Position ganztägiger Ereignisse",
|
||||||
@ -504,6 +506,8 @@ export default function WeeklyView() {
|
|||||||
const [somedayLists, setSomedayLists] = useState<SomedayList[]>([]);
|
const [somedayLists, setSomedayLists] = useState<SomedayList[]>([]);
|
||||||
const [editingTaskId, setEditingTaskId] = useState<string | null>(null);
|
const [editingTaskId, setEditingTaskId] = useState<string | null>(null);
|
||||||
const [draggingListId, setDraggingListId] = useState<string | null>(null);
|
const [draggingListId, setDraggingListId] = useState<string | null>(null);
|
||||||
|
const [dropTargetListIndex, setDropTargetListIndex] = useState<number | null>(null);
|
||||||
|
const isDragFromHandle = useRef(false);
|
||||||
|
|
||||||
// Undo/Redo state
|
// Undo/Redo state
|
||||||
const undoStackRef = useRef<{ tasks: Task[]; somedayLists: SomedayList[] }[]>([]);
|
const undoStackRef = useRef<{ tasks: Task[]; somedayLists: SomedayList[] }[]>([]);
|
||||||
@ -608,6 +612,7 @@ export default function WeeklyView() {
|
|||||||
yearFontWeight?: string;
|
yearFontWeight?: string;
|
||||||
yearColor?: string;
|
yearColor?: string;
|
||||||
dayHeaderGap?: string;
|
dayHeaderGap?: string;
|
||||||
|
showTaskCheckboxes?: boolean;
|
||||||
quoteSourceUrls?: string[];
|
quoteSourceUrls?: string[];
|
||||||
}>({
|
}>({
|
||||||
name: session?.user?.name || "",
|
name: session?.user?.name || "",
|
||||||
@ -807,6 +812,7 @@ export default function WeeklyView() {
|
|||||||
const dayColumnsRef = useRef<HTMLDivElement[]>([]);
|
const dayColumnsRef = useRef<HTMLDivElement[]>([]);
|
||||||
const isScrollSyncing = useRef(false);
|
const isScrollSyncing = useRef(false);
|
||||||
const dayHeaderRef = useRef<HTMLElement>(null);
|
const dayHeaderRef = useRef<HTMLElement>(null);
|
||||||
|
const somedayGridRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Scroll sync handler
|
// Scroll sync handler
|
||||||
const handleTimeColumnScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
const handleTimeColumnScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
||||||
@ -1243,11 +1249,18 @@ export default function WeeklyView() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleSomedayWheel = (e: React.WheelEvent) => {
|
useEffect(() => {
|
||||||
if (e.currentTarget) {
|
const el = somedayGridRef.current;
|
||||||
e.currentTarget.scrollLeft += e.deltaY;
|
if (!el) return;
|
||||||
}
|
const handler = (e: WheelEvent) => {
|
||||||
};
|
if (e.deltaY !== 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
el.scrollLeft += e.deltaY;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
el.addEventListener("wheel", handler, { passive: false });
|
||||||
|
return () => el.removeEventListener("wheel", handler);
|
||||||
|
}, [showSomeday, somedayExpanded]);
|
||||||
const saveSetting = async (key: string, value: any) => {
|
const saveSetting = async (key: string, value: any) => {
|
||||||
try {
|
try {
|
||||||
await fetch("/api/user/profile", {
|
await fetch("/api/user/profile", {
|
||||||
@ -1432,6 +1445,8 @@ export default function WeeklyView() {
|
|||||||
id: l.id,
|
id: l.id,
|
||||||
title: l.title,
|
title: l.title,
|
||||||
tasks: l.tasks || [], // Tasks will be overwritten/populated by fetchTasks
|
tasks: l.tasks || [], // Tasks will be overwritten/populated by fetchTasks
|
||||||
|
externalId: l.externalId || null,
|
||||||
|
externalProvider: l.externalProvider || null,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
return data.lists;
|
return data.lists;
|
||||||
@ -1457,6 +1472,7 @@ export default function WeeklyView() {
|
|||||||
id: l.id,
|
id: l.id,
|
||||||
title: l.title,
|
title: l.title,
|
||||||
tasks: [],
|
tasks: [],
|
||||||
|
externalId: l.externalId || null,
|
||||||
externalProvider: l.externalProvider || null,
|
externalProvider: l.externalProvider || null,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -4110,6 +4126,7 @@ export default function WeeklyView() {
|
|||||||
deleteSubTask={deleteSubTask}
|
deleteSubTask={deleteSubTask}
|
||||||
onSetEditingTaskId={setEditingTaskId}
|
onSetEditingTaskId={setEditingTaskId}
|
||||||
workingHoursStart={workingHoursStart}
|
workingHoursStart={workingHoursStart}
|
||||||
|
showTaskCheckboxes={profile.showTaskCheckboxes}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{visibleSlots.map((slot) => {
|
{visibleSlots.map((slot) => {
|
||||||
@ -4346,6 +4363,7 @@ export default function WeeklyView() {
|
|||||||
onUpdateSubTask={updateSubTask}
|
onUpdateSubTask={updateSubTask}
|
||||||
editingTaskId={editingTaskId}
|
editingTaskId={editingTaskId}
|
||||||
onSetEditingTaskId={setEditingTaskId}
|
onSetEditingTaskId={setEditingTaskId}
|
||||||
|
showTaskCheckboxes={profile.showTaskCheckboxes}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -4430,6 +4448,7 @@ export default function WeeklyView() {
|
|||||||
onUpdateSubTask={updateSubTask}
|
onUpdateSubTask={updateSubTask}
|
||||||
editingTaskId={editingTaskId}
|
editingTaskId={editingTaskId}
|
||||||
onSetEditingTaskId={setEditingTaskId}
|
onSetEditingTaskId={setEditingTaskId}
|
||||||
|
showTaskCheckboxes={profile.showTaskCheckboxes}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
@ -4591,17 +4610,29 @@ export default function WeeklyView() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
{somedayExpanded && (
|
{somedayExpanded && (
|
||||||
<div
|
<div
|
||||||
|
ref={somedayGridRef}
|
||||||
className={`weekly-someday-lists-grid cols-${Math.min(7, Math.max(1, viewDays))}`}
|
className={`weekly-someday-lists-grid cols-${Math.min(7, Math.max(1, viewDays))}`}
|
||||||
onWheel={handleSomedayWheel}
|
|
||||||
>
|
>
|
||||||
{(somedayLists.length > 0
|
{(() => {
|
||||||
? somedayLists
|
const baseLists = somedayLists.length > 0
|
||||||
: [{ id: "default", title: "LISTE", tasks: [] }]
|
? somedayLists
|
||||||
)
|
: [{ id: "default", title: "LISTE", tasks: [] as Task[] }];
|
||||||
.slice(0, Math.max(somedayLists.length, viewDays))
|
const sliced = baseLists.slice(0, Math.max(somedayLists.length, viewDays));
|
||||||
|
// Compute visual order during drag
|
||||||
|
if (draggingListId && dropTargetListIndex !== null) {
|
||||||
|
const dragIdx = sliced.findIndex(l => l.id === draggingListId);
|
||||||
|
if (dragIdx !== -1 && dragIdx !== dropTargetListIndex) {
|
||||||
|
const reordered = [...sliced];
|
||||||
|
const [moved] = reordered.splice(dragIdx, 1);
|
||||||
|
reordered.splice(dropTargetListIndex, 0, moved);
|
||||||
|
return reordered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sliced;
|
||||||
|
})()
|
||||||
.map((list) => (
|
.map((list) => (
|
||||||
<div
|
<div
|
||||||
key={list.id}
|
key={list.id}
|
||||||
@ -4638,29 +4669,46 @@ export default function WeeklyView() {
|
|||||||
if (target.closest(".weekly-task-item")) {
|
if (target.closest(".weekly-task-item")) {
|
||||||
return; // Let the TaskItem handle its own drag
|
return; // Let the TaskItem handle its own drag
|
||||||
}
|
}
|
||||||
// Only allow list drag if clicking the handle
|
// Only allow list drag if started from the handle
|
||||||
if (!target.closest(".someday-drag-handle")) {
|
if (!isDragFromHandle.current) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
isDragFromHandle.current = false;
|
||||||
setDraggingListId(list.id);
|
setDraggingListId(list.id);
|
||||||
e.dataTransfer.setData("text/list-id", list.id);
|
e.dataTransfer.setData("text/list-id", list.id);
|
||||||
e.dataTransfer.effectAllowed = "move";
|
e.dataTransfer.effectAllowed = "move";
|
||||||
}}
|
}}
|
||||||
onDragEnd={() => setDraggingListId(null)}
|
onDragEnd={() => { setDraggingListId(null); setDropTargetListIndex(null); }}
|
||||||
onDragOver={(e) => {
|
onDragOver={(e) => {
|
||||||
e.preventDefault(); // Allow drop
|
e.preventDefault();
|
||||||
e.dataTransfer.dropEffect = "move";
|
e.dataTransfer.dropEffect = "move";
|
||||||
|
if (!draggingListId) return;
|
||||||
|
const container = somedayGridRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
const children = Array.from(container.children) as HTMLElement[];
|
||||||
|
let targetIdx = children.length - 1;
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const rect = children[i].getBoundingClientRect();
|
||||||
|
if (e.clientX < rect.left + rect.width / 2) {
|
||||||
|
targetIdx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setDropTargetListIndex(targetIdx);
|
||||||
}}
|
}}
|
||||||
onDrop={async (e) => {
|
onDrop={async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setDraggingListId(null);
|
const droppedListId =
|
||||||
const draggedListId =
|
|
||||||
e.dataTransfer.getData("text/list-id");
|
e.dataTransfer.getData("text/list-id");
|
||||||
const draggedTaskId =
|
const draggedTaskId =
|
||||||
e.dataTransfer.getData("text/plain");
|
e.dataTransfer.getData("text/plain");
|
||||||
|
|
||||||
if (draggedListId === list.id) return;
|
if (droppedListId === list.id && !draggedTaskId) {
|
||||||
|
setDraggingListId(null);
|
||||||
|
setDropTargetListIndex(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if a calendar task is being dropped into this someday list
|
// Check if a calendar task is being dropped into this someday list
|
||||||
if (
|
if (
|
||||||
@ -4781,25 +4829,28 @@ export default function WeeklyView() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reorder logic (list drag)
|
// Reorder logic (list drag) - apply the visual order
|
||||||
if (!draggedListId) return;
|
if (!droppedListId || dropTargetListIndex === null) {
|
||||||
|
setDraggingListId(null);
|
||||||
|
setDropTargetListIndex(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const draggedIndex = somedayLists.findIndex(
|
const draggedIndex = somedayLists.findIndex(
|
||||||
(l) => l.id === draggedListId,
|
(l) => l.id === droppedListId,
|
||||||
);
|
);
|
||||||
const targetIndex = somedayLists.findIndex(
|
if (draggedIndex === -1) {
|
||||||
(l) => l.id === list.id,
|
setDraggingListId(null);
|
||||||
);
|
setDropTargetListIndex(null);
|
||||||
|
return;
|
||||||
if (draggedIndex === -1 || targetIndex === -1) return;
|
}
|
||||||
|
|
||||||
const newLists = [...somedayLists];
|
const newLists = [...somedayLists];
|
||||||
const [draggedItem] = newLists.splice(
|
const [draggedItem] = newLists.splice(draggedIndex, 1);
|
||||||
draggedIndex,
|
newLists.splice(dropTargetListIndex, 0, draggedItem);
|
||||||
1,
|
|
||||||
);
|
|
||||||
newLists.splice(targetIndex, 0, draggedItem);
|
|
||||||
|
|
||||||
setSomedayLists(newLists);
|
setSomedayLists(newLists);
|
||||||
|
setDraggingListId(null);
|
||||||
|
setDropTargetListIndex(null);
|
||||||
|
|
||||||
// Persist order
|
// Persist order
|
||||||
const orderUpdates = newLists.map((l, index) => ({
|
const orderUpdates = newLists.map((l, index) => ({
|
||||||
@ -4828,6 +4879,8 @@ export default function WeeklyView() {
|
|||||||
<div
|
<div
|
||||||
className="someday-drag-handle"
|
className="someday-drag-handle"
|
||||||
title="Drag to reorder"
|
title="Drag to reorder"
|
||||||
|
onMouseDown={() => { isDragFromHandle.current = true; }}
|
||||||
|
onMouseUp={() => { isDragFromHandle.current = false; }}
|
||||||
>
|
>
|
||||||
<GripVertical size={14} />
|
<GripVertical size={14} />
|
||||||
</div>
|
</div>
|
||||||
@ -4949,6 +5002,7 @@ export default function WeeklyView() {
|
|||||||
onUpdateSubTask={updateSubTask}
|
onUpdateSubTask={updateSubTask}
|
||||||
editingTaskId={editingTaskId}
|
editingTaskId={editingTaskId}
|
||||||
onSetEditingTaskId={setEditingTaskId}
|
onSetEditingTaskId={setEditingTaskId}
|
||||||
|
showTaskCheckboxes={profile.showTaskCheckboxes}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<SomedayAddTask
|
<SomedayAddTask
|
||||||
@ -5628,6 +5682,7 @@ interface TaskItemProps {
|
|||||||
editingTaskId?: string | null;
|
editingTaskId?: string | null;
|
||||||
onSetEditingTaskId?: (id: string | null) => void;
|
onSetEditingTaskId?: (id: string | null) => void;
|
||||||
isSubTask?: boolean;
|
isSubTask?: boolean;
|
||||||
|
showTaskCheckboxes?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TaskItem({
|
function TaskItem({
|
||||||
@ -5651,6 +5706,7 @@ function TaskItem({
|
|||||||
editingTaskId,
|
editingTaskId,
|
||||||
onSetEditingTaskId,
|
onSetEditingTaskId,
|
||||||
isSubTask = false,
|
isSubTask = false,
|
||||||
|
showTaskCheckboxes = false,
|
||||||
}: TaskItemProps) {
|
}: TaskItemProps) {
|
||||||
const [editValue, setEditValue] = useState(task.title);
|
const [editValue, setEditValue] = useState(task.title);
|
||||||
const [isNotesOpen, setIsNotesOpen] = useState(false);
|
const [isNotesOpen, setIsNotesOpen] = useState(false);
|
||||||
@ -5726,7 +5782,7 @@ function TaskItem({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
className={`weekly-task-item ${variant} ${task.completed ? "completed" : ""} ${isSomeday ? "relative mx-2" : ""}`}
|
className={`weekly-task-item ${variant} ${task.completed && !showTaskCheckboxes ? "completed" : ""} ${isSomeday ? "relative mx-2" : ""}`}
|
||||||
draggable={!isEditing && !isNotesOpen} // Disable drag when editing
|
draggable={!isEditing && !isNotesOpen} // Disable drag when editing
|
||||||
onDragStart={(e) => onDragStart(e as unknown as DragEvent, task)}
|
onDragStart={(e) => onDragStart(e as unknown as DragEvent, task)}
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
@ -5807,18 +5863,28 @@ function TaskItem({
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<span
|
<span
|
||||||
className={`weekly-task-text flex-1 ${task.completed ? "completed" : ""}`}
|
className={`weekly-task-text flex-1 ${task.completed && !showTaskCheckboxes ? "completed" : ""}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (variant === "default") onToggle();
|
if (variant === "default" && !showTaskCheckboxes) onToggle();
|
||||||
// For minimal/someday, parent onClick handles edit
|
// For minimal/someday, parent onClick handles edit
|
||||||
}}
|
}}
|
||||||
onDoubleClick={variant === "default" ? onEdit : undefined}
|
onDoubleClick={variant === "default" ? onEdit : undefined}
|
||||||
style={
|
style={
|
||||||
variant === "minimal" || isSomeday
|
variant === "minimal" || isSomeday
|
||||||
? { fontSize: "0.9375rem", display: "flex", alignItems: "center", gap: "4px" }
|
? { fontSize: "0.9375rem", display: "flex", alignItems: "center", gap: "4px", ...(task.completed && showTaskCheckboxes ? { opacity: 0.5 } : {}) }
|
||||||
: { display: "flex", alignItems: "center", gap: "6px" }
|
: { display: "flex", alignItems: "center", gap: "6px", ...(task.completed && showTaskCheckboxes ? { opacity: 0.5 } : {}) }
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{showTaskCheckboxes && (
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={task.completed}
|
||||||
|
onChange={(e) => { e.stopPropagation(); onToggle(); }}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="task-checkbox flex-shrink-0"
|
||||||
|
style={{ width: "14px", height: "14px", margin: 0, cursor: "pointer", position: "relative", top: "4px", left: "-2px", accentColor: "#333" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{needsSync && (
|
{needsSync && (
|
||||||
<span title="Needs to be synced" className="text-yellow-500 flex-shrink-0">
|
<span title="Needs to be synced" className="text-yellow-500 flex-shrink-0">
|
||||||
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
||||||
@ -6281,6 +6347,7 @@ interface SettingsSidebarProps {
|
|||||||
yearFontWeight?: string;
|
yearFontWeight?: string;
|
||||||
yearColor?: string;
|
yearColor?: string;
|
||||||
dayHeaderGap?: string;
|
dayHeaderGap?: string;
|
||||||
|
showTaskCheckboxes?: boolean;
|
||||||
quoteSourceUrls: string[];
|
quoteSourceUrls: string[];
|
||||||
}) => void;
|
}) => void;
|
||||||
quoteSourceUrls?: string[];
|
quoteSourceUrls?: string[];
|
||||||
@ -6636,6 +6703,7 @@ function SettingsSidebar({
|
|||||||
yearFontWeight?: string;
|
yearFontWeight?: string;
|
||||||
yearColor?: string;
|
yearColor?: string;
|
||||||
dayHeaderGap?: string;
|
dayHeaderGap?: string;
|
||||||
|
showTaskCheckboxes?: boolean;
|
||||||
quoteSourceUrls?: string[];
|
quoteSourceUrls?: string[];
|
||||||
}>({
|
}>({
|
||||||
name: "",
|
name: "",
|
||||||
@ -6772,6 +6840,7 @@ function SettingsSidebar({
|
|||||||
yearFontSize: profile.yearFontSize,
|
yearFontSize: profile.yearFontSize,
|
||||||
yearFontWeight: profile.yearFontWeight,
|
yearFontWeight: profile.yearFontWeight,
|
||||||
dayHeaderGap: profile.dayHeaderGap,
|
dayHeaderGap: profile.dayHeaderGap,
|
||||||
|
showTaskCheckboxes: profile.showTaskCheckboxes,
|
||||||
} as any);
|
} as any);
|
||||||
}, [profile, showTimeGrid, cellDuration, viewStyle, fontSize, showNextTask, showSomeday, showAllDay, showSchedule]);
|
}, [profile, showTimeGrid, cellDuration, viewStyle, fontSize, showNextTask, showSomeday, showAllDay, showSchedule]);
|
||||||
|
|
||||||
@ -6846,6 +6915,7 @@ function SettingsSidebar({
|
|||||||
hourLabelFormat: data.user.hourLabelFormat || "short",
|
hourLabelFormat: data.user.hourLabelFormat || "short",
|
||||||
showSubHourSlots: data.user.showSubHourSlots !== undefined ? data.user.showSubHourSlots : true,
|
showSubHourSlots: data.user.showSubHourSlots !== undefined ? data.user.showSubHourSlots : true,
|
||||||
allDayPosition: data.user.allDayPosition || "below",
|
allDayPosition: data.user.allDayPosition || "below",
|
||||||
|
showTaskCheckboxes: data.user.showTaskCheckboxes || false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data.user.showTimeGrid !== undefined)
|
if (data.user.showTimeGrid !== undefined)
|
||||||
@ -7297,6 +7367,25 @@ function SettingsSidebar({
|
|||||||
{t.runningList}
|
{t.runningList}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
style={{ display: "flex", alignItems: "center", gap: "8px" }}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="showTaskCheckboxes"
|
||||||
|
checked={profile.showTaskCheckboxes || false}
|
||||||
|
onChange={(e) =>
|
||||||
|
setProfile({ ...profile, showTaskCheckboxes: e.target.checked })
|
||||||
|
}
|
||||||
|
style={{ width: "16px", height: "16px" }}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="showTaskCheckboxes"
|
||||||
|
style={{ fontSize: "0.9rem", fontWeight: 600 }}
|
||||||
|
>
|
||||||
|
{t.showTaskCheckboxes}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{ display: "flex", alignItems: "center", gap: "8px" }}
|
style={{ display: "flex", alignItems: "center", gap: "8px" }}
|
||||||
>
|
>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user