Skip to content

Commit f0f4db6

Browse files
feat: quick reschedule chips + close modal on task completion
1 parent c3dd016 commit f0f4db6

File tree

1 file changed

+81
-1
lines changed

1 file changed

+81
-1
lines changed

src/components/TaskDetailModal.tsx

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState, useEffect } from 'react';
2-
import { X, Calendar, Flag, Folder, Check } from 'lucide-react';
2+
import { X, Calendar, Flag, Folder, Check, Sunrise, CalendarPlus, Sofa, Briefcase, Tent, CalendarFold } from 'lucide-react';
33
import { TickTickTask, TickTickProject } from '@/lib/ticktick';
44
import { parseISO } from 'date-fns';
55
import { TagCombobox } from './TagCombobox';
@@ -111,6 +111,68 @@ export function TaskDetailModal({ isOpen, onClose, task, onUpdate, onDelete, pro
111111
}
112112
};
113113

114+
// Quick reschedule helpers
115+
const toDateStr = (d: Date): string => {
116+
const year = d.getFullYear();
117+
const month = String(d.getMonth() + 1).padStart(2, '0');
118+
const day = String(d.getDate()).padStart(2, '0');
119+
return `${year}-${month}-${day}T12:00:00+0000`;
120+
};
121+
122+
const offsetDate = (days: number): string => {
123+
const d = new Date();
124+
d.setDate(d.getDate() + days);
125+
return toDateStr(d);
126+
};
127+
128+
const getThisSaturday = (): string => {
129+
const dow = new Date().getDay(); // 0=Sun,6=Sat
130+
const offset = dow === 6 ? 7 : dow === 0 ? 6 : 6 - dow;
131+
return offsetDate(offset);
132+
};
133+
134+
const getNextMonday = (): string => {
135+
const dow = new Date().getDay();
136+
const offset = dow === 1 ? 7 : (8 - dow) % 7;
137+
return offsetDate(offset);
138+
};
139+
140+
const getNextSaturday = (): string => {
141+
const dow = new Date().getDay();
142+
const offset = (dow === 6 ? 7 : dow === 0 ? 6 : 6 - dow) + 7;
143+
return offsetDate(offset);
144+
};
145+
146+
const getNextMonthFirst = (): string => {
147+
const today = new Date();
148+
return toDateStr(new Date(today.getFullYear(), today.getMonth() + 1, 1));
149+
};
150+
151+
const handleQuickReschedule = (newDateStr: string) => {
152+
const updates: Partial<TickTickTask> = {};
153+
if (title !== task.title) updates.title = title;
154+
if (content !== (task.content || '')) updates.content = content;
155+
if (priority !== (task.priority || 0)) updates.priority = priority;
156+
if (projectId !== task.projectId) updates.projectId = projectId;
157+
const originalTags = task.tags || [];
158+
if (JSON.stringify(tags) !== JSON.stringify(originalTags)) updates.tags = tags;
159+
updates.startDate = newDateStr;
160+
updates.dueDate = newDateStr;
161+
onUpdate(task.id, updates);
162+
onClose();
163+
};
164+
165+
const RESCHEDULE_OPTIONS = [
166+
{ label: 'Tomorrow', icon: Sunrise, date: () => offsetDate(1) },
167+
{ label: 'In 3 days', icon: CalendarPlus, date: () => offsetDate(3) },
168+
{ label: 'This Sat', icon: Sofa, date: getThisSaturday },
169+
{ label: 'Next Mon', icon: Briefcase, date: getNextMonday },
170+
{ label: 'Next Sat', icon: Tent, date: getNextSaturday },
171+
{ label: 'In 15 days', icon: CalendarPlus, date: () => offsetDate(15) },
172+
{ label: 'Next month', icon: CalendarFold, date: getNextMonthFirst },
173+
{ label: 'In 30 days', icon: CalendarPlus, date: () => offsetDate(30) },
174+
];
175+
114176
// Tag management
115177
const addTag = (tag: string) => {
116178
const trimmed = tag.trim().toLowerCase();
@@ -147,6 +209,7 @@ export function TaskDetailModal({ isOpen, onClose, task, onUpdate, onDelete, pro
147209
onClick={() => {
148210
const newStatus = task.status === 2 ? 0 : 2;
149211
onUpdate(task.id, { status: newStatus });
212+
if (newStatus === 2) onClose();
150213
}}
151214
className={`w-5 h-5 rounded border flex items-center justify-center transition-colors ${task.status === 2
152215
? 'bg-green-500 border-green-500 text-white'
@@ -216,6 +279,23 @@ export function TaskDetailModal({ isOpen, onClose, task, onUpdate, onDelete, pro
216279
</div>
217280
</div>
218281

282+
{/* Quick Reschedule */}
283+
<div className="mb-4">
284+
<label className="block text-xs font-bold text-gray-400 dark:text-gray-500 uppercase tracking-widest mb-2">Reschedule</label>
285+
<div className="flex flex-wrap gap-2">
286+
{RESCHEDULE_OPTIONS.map(({ label, icon: Icon, date }) => (
287+
<button
288+
key={label}
289+
onClick={() => handleQuickReschedule(date())}
290+
className="flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-md bg-gray-50 dark:bg-gray-800 text-gray-600 dark:text-gray-300 hover:bg-blue-50 dark:hover:bg-blue-900/30 hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
291+
>
292+
<Icon size={12} />
293+
{label}
294+
</button>
295+
))}
296+
</div>
297+
</div>
298+
219299
{/* Tags Section */}
220300
<div className="mb-6">
221301
<label className="block text-xs font-bold text-gray-400 uppercase tracking-widest mb-2">Tags</label>

0 commit comments

Comments
 (0)