Refactor: Rearrange header, streamline settings, fix task hover icons, and overhaul recurring task manager
This commit is contained in:
parent
0e854b6e9b
commit
a834b440a5
@ -790,18 +790,20 @@ h3 {
|
|||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 4px;
|
padding: 2px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
color: var(--weekly-text-light);
|
color: var(--weekly-text-light);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: color 0.15s;
|
transition: all 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-action-btn:hover {
|
.task-action-btn:hover {
|
||||||
color: var(--weekly-text);
|
color: var(--weekly-text);
|
||||||
background: rgba(0,0,0,0.05);
|
background: rgba(0,0,0,0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Specific button styles */
|
/* Specific button styles */
|
||||||
|
|||||||
@ -1,96 +1,107 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Repeat, X, Trash2 } from 'lucide-react';
|
||||||
|
|
||||||
interface RecurringTasksManagerProps {
|
interface RecurringTasksManagerProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
tasks: any[];
|
tasks: any[];
|
||||||
|
onStopRecurring: (task: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RecurringTasksManager({ isOpen, onClose, tasks }: RecurringTasksManagerProps) {
|
export default function RecurringTasksManager({ isOpen, onClose, tasks, onStopRecurring }: RecurringTasksManagerProps) {
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
// Filter tasks that are recurring
|
// Filter and group recurring tasks by series signature
|
||||||
const recurringTasks = tasks.filter(t => t.isRecurring);
|
// We use title + recurrence settings as the identity of a "series"
|
||||||
|
const seriesGroups = new Map<string, any>();
|
||||||
|
|
||||||
|
tasks.filter(t => t.isRecurring).forEach(t => {
|
||||||
|
// Only include those that aren't already ended
|
||||||
|
if (t.recurrenceEndDate && new Date(t.recurrenceEndDate) < new Date()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const signature = `${t.title}-${t.recurrenceInterval || 1}-${t.recurrenceUnit || 'weeks'}-${t.recurrenceTime || ''}`;
|
||||||
|
|
||||||
|
// Keep the latest instance to represent the series
|
||||||
|
const existing = seriesGroups.get(signature);
|
||||||
|
if (!existing || (t.scheduledDate && new Date(t.scheduledDate) > new Date(existing.scheduledDate || 0))) {
|
||||||
|
seriesGroups.set(signature, t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupedTasks = Array.from(seriesGroups.values());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4" onClick={onClose}>
|
<div className="fixed inset-0 bg-black/50 z-[3000] flex items-center justify-center p-4 animate-fadeIn" onClick={onClose}>
|
||||||
<div className="bg-white rounded-lg shadow-2xl w-[800px] max-w-full h-[600px] flex flex-col" onClick={e => e.stopPropagation()}>
|
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-2xl w-[600px] max-w-full overflow-hidden flex flex-col animate-scaleIn" onClick={e => e.stopPropagation()}>
|
||||||
<div className="p-6 flex items-center justify-between border-b border-gray-100">
|
<div className="p-5 flex items-center justify-between border-b border-gray-100 dark:border-gray-800">
|
||||||
<h2 className="text-xl font-bold text-gray-800">Recurring to-dos</h2>
|
<div className="flex items-center gap-3">
|
||||||
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 font-bold text-xl">
|
<div className="p-2 bg-teal-100 dark:bg-teal-900/30 rounded-lg text-teal-600">
|
||||||
×
|
<Repeat size={20} />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-lg font-bold text-gray-800 dark:text-white">Wiederkehrende Aufgaben</h2>
|
||||||
|
</div>
|
||||||
|
<button onClick={onClose} className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-full text-gray-400 transition-colors">
|
||||||
|
<X size={20} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto p-8">
|
<div className="flex-1 overflow-y-auto p-6">
|
||||||
{recurringTasks.length === 0 ? (
|
{groupedTasks.length === 0 ? (
|
||||||
<div className="flex flex-col items-start max-w-lg">
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||||
<p className="text-gray-600 mb-6">
|
<div className="w-16 h-16 bg-gray-50 dark:bg-gray-800 rounded-full flex items-center justify-center text-gray-300 mb-4">
|
||||||
You don't have any recurring to-dos yet.
|
<Repeat size={32} />
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-500 dark:text-gray-400 max-w-xs">
|
||||||
|
Du hast momentan keine aktiven wiederkehrenden Aufgaben.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button className="bg-[#333] text-white px-4 py-2 rounded text-sm font-medium flex items-center gap-2 hover:bg-black transition-colors mb-8">
|
|
||||||
<span>+</span> Recurring to-do
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<p className="text-gray-800 font-medium mb-4">Or, type a frequency to the end of a to-do.</p>
|
|
||||||
|
|
||||||
<div className="bg-gray-100 p-8 rounded mb-8 w-full flex items-center justify-center">
|
|
||||||
<div className="bg-white p-6 shadow-sm rounded text-center w-64">
|
|
||||||
<div className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-1">OCT 5, 2023</div>
|
|
||||||
<div className="text-xl font-bold text-gray-900 uppercase tracking-wider mb-4">THURSDAY</div>
|
|
||||||
<div className="h-6 w-1 bg-black animate-pulse mx-auto"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-8 text-sm w-full">
|
|
||||||
<div>
|
|
||||||
<h4 className="italic text-gray-500 mb-2">Frequencies:</h4>
|
|
||||||
<ul className="text-gray-500 italic space-y-1">
|
|
||||||
<li>every day</li>
|
|
||||||
<li>every week</li>
|
|
||||||
<li>every other week</li>
|
|
||||||
<li>every month</li>
|
|
||||||
<li>every year</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="italic text-gray-500 mb-2">Examples:</h4>
|
|
||||||
<ul className="text-gray-500 italic space-y-1">
|
|
||||||
<li>Brush teeth every day</li>
|
|
||||||
<li>Management meeting every week</li>
|
|
||||||
<li>Piano lessons every other week</li>
|
|
||||||
<li>Pay rent every month</li>
|
|
||||||
<li>Garret's birthday every year</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-3">
|
||||||
{recurringTasks.map(task => (
|
{groupedTasks.map(task => (
|
||||||
<div key={task.id} className="flex items-center justify-between p-4 bg-gray-50 rounded border border-gray-100">
|
<div key={task.id} className="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-800/50 rounded-xl border border-gray-100 dark:border-gray-700/50 hover:border-teal-200 dark:hover:border-teal-900/50 transition-all">
|
||||||
<div>
|
<div className="flex-1 min-w-0 pr-4">
|
||||||
<div className="font-medium text-gray-900">{task.title}</div>
|
<div className="font-semibold text-gray-900 dark:text-white truncate" title={task.title}>
|
||||||
<div className="text-xs text-gray-500 mt-1">
|
{task.title}
|
||||||
Repeats every {task.recurrenceInterval} {task.recurrenceUnit}
|
</div>
|
||||||
|
<div className="flex items-center gap-2 mt-1">
|
||||||
|
<span className="text-xs px-2 py-0.5 bg-teal-50 dark:bg-teal-900/20 text-teal-600 dark:text-teal-400 rounded-md font-medium capitalize">
|
||||||
|
Alle {task.recurrenceInterval === 1 ? '' : task.recurrenceInterval} {
|
||||||
|
task.recurrenceUnit === 'days' ? 'Tage' :
|
||||||
|
task.recurrenceUnit === 'weeks' ? 'Wochen' :
|
||||||
|
task.recurrenceUnit === 'months' ? 'Monate' : 'Jahre'
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
{task.recurrenceTime && (
|
||||||
|
<span className="text-[10px] text-gray-400">
|
||||||
|
um {task.recurrenceTime} Uhr
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button className="text-red-500 text-sm hover:underline">Stop Details</button>
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (confirm(`Serie "${task.title}" wirklich beenden?`)) {
|
||||||
|
onStopRecurring(task);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2 px-3 py-2 bg-red-50 hover:bg-red-100 dark:bg-red-900/10 dark:hover:bg-red-900/20 text-red-600 dark:text-red-400 rounded-lg text-xs font-bold transition-colors group"
|
||||||
|
>
|
||||||
|
<Trash2 size={14} className="group-hover:scale-110 transition-transform" />
|
||||||
|
Serie beenden
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 border-t border-gray-100 flex justify-end">
|
<div className="p-3 bg-gray-50 dark:bg-gray-900 border-t border-gray-100 dark:border-gray-800 flex justify-center">
|
||||||
<button
|
<p className="text-[10px] text-gray-400 font-medium">
|
||||||
onClick={onClose}
|
Tipp: Du kannst Aufgaben jederzeit beim Bearbeiten wiederkehrend machen.
|
||||||
className="w-10 h-10 bg-[#333] rounded-full flex items-center justify-center text-white hover:bg-black transition-colors text-xl font-bold"
|
</p>
|
||||||
>
|
|
||||||
+
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -323,7 +323,6 @@ 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 [showSettings, setShowSettings] = useState(false);
|
const [showSettings, setShowSettings] = useState(false);
|
||||||
const [showPreferences, setShowPreferences] = useState(false);
|
|
||||||
const [showDatePicker, setShowDatePicker] = useState(false);
|
const [showDatePicker, setShowDatePicker] = useState(false);
|
||||||
const [isAddingSomedayList, setIsAddingSomedayList] = useState(false);
|
const [isAddingSomedayList, setIsAddingSomedayList] = useState(false);
|
||||||
const [newSomedayListName, setNewSomedayListName] = useState('');
|
const [newSomedayListName, setNewSomedayListName] = useState('');
|
||||||
@ -1522,16 +1521,32 @@ export default function WeeklyView() {
|
|||||||
|
|
||||||
{/* Refactored Header: Left, Center, Right */}
|
{/* Refactored Header: Left, Center, Right */}
|
||||||
<header className="flex items-center justify-between w-full px-4 py-2 border-b border-gray-200 bg-white dark:bg-gray-900 dark:border-gray-700 dark:text-white transition-colors duration-200">
|
<header className="flex items-center justify-between w-full px-4 py-2 border-b border-gray-200 bg-white dark:bg-gray-900 dark:border-gray-700 dark:text-white transition-colors duration-200">
|
||||||
{/* LEFT SECTION: Days to Show & Time Range */}
|
{/* LEFT SECTION: Slot Duration & Days to Show */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
|
{/* Slot Duration */}
|
||||||
|
{showTimeGrid && (
|
||||||
|
<div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 rounded p-1" title="Slot Duration">
|
||||||
|
<Clock size={16} className="text-gray-500 mr-1" />
|
||||||
|
{[15, 30, 60].map(duration => (
|
||||||
|
<button
|
||||||
|
key={duration}
|
||||||
|
onClick={() => setCellDuration(duration as CellDuration)}
|
||||||
|
className={`px-2 py-0.5 text-xs rounded transition-colors ${cellDuration === duration ? 'bg-white shadow-sm font-bold text-black' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700'}`}
|
||||||
|
>
|
||||||
|
{duration}m
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Days to Show */}
|
{/* Days to Show */}
|
||||||
<div className="flex items-center gap-1 bg-gray-100 rounded p-1" title="Days to show">
|
<div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 rounded p-1" title="Days to show">
|
||||||
<LayoutGrid size={16} className="text-gray-500 mr-1" />
|
<LayoutGrid size={16} className="text-gray-500 mr-1" />
|
||||||
{[1, 3, 5, 7].map(num => (
|
{[1, 3, 5, 7].map(num => (
|
||||||
<button
|
<button
|
||||||
key={num}
|
key={num}
|
||||||
onClick={() => setViewDays(num)}
|
onClick={() => setViewDays(num)}
|
||||||
className={`px-2 py-0.5 text-xs rounded transition-colors ${viewDays === num ? 'bg-white shadow-sm font-bold text-black' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-200'}`}
|
className={`px-2 py-0.5 text-xs rounded transition-colors ${viewDays === num ? 'bg-white shadow-sm font-bold text-black' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700'}`}
|
||||||
>
|
>
|
||||||
{num}
|
{num}
|
||||||
</button>
|
</button>
|
||||||
@ -1540,7 +1555,7 @@ export default function WeeklyView() {
|
|||||||
|
|
||||||
{/* Time Range */}
|
{/* Time Range */}
|
||||||
{showTimeGrid && (
|
{showTimeGrid && (
|
||||||
<div className="flex items-center gap-2 text-xs text-gray-500 bg-gray-100 rounded p-1 px-2" title="Visible Hours">
|
<div className="flex items-center gap-2 text-xs text-gray-500 bg-gray-100 dark:bg-gray-800 rounded p-1 px-2" title="Visible Hours">
|
||||||
<Clock size={16} className="text-gray-500" />
|
<Clock size={16} className="text-gray-500" />
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<input
|
<input
|
||||||
@ -1549,7 +1564,7 @@ export default function WeeklyView() {
|
|||||||
max={endHour - 1}
|
max={endHour - 1}
|
||||||
value={startHour}
|
value={startHour}
|
||||||
onChange={(e) => setStartHour(Math.max(0, Math.min(parseInt(e.target.value) || 0, endHour - 1)))}
|
onChange={(e) => setStartHour(Math.max(0, Math.min(parseInt(e.target.value) || 0, endHour - 1)))}
|
||||||
className="w-12 p-0.5 border border-gray-200 rounded text-center bg-transparent focus:outline-none focus:border-teal-500"
|
className="w-10 p-0.5 border border-gray-200 dark:border-gray-700 rounded text-center bg-transparent focus:outline-none focus:border-teal-500"
|
||||||
/>
|
/>
|
||||||
<span>-</span>
|
<span>-</span>
|
||||||
<input
|
<input
|
||||||
@ -1558,20 +1573,33 @@ export default function WeeklyView() {
|
|||||||
max="24"
|
max="24"
|
||||||
value={endHour}
|
value={endHour}
|
||||||
onChange={(e) => setEndHour(Math.max(startHour + 1, Math.min(parseInt(e.target.value) || 24, 24)))}
|
onChange={(e) => setEndHour(Math.max(startHour + 1, Math.min(parseInt(e.target.value) || 24, 24)))}
|
||||||
className="w-12 p-0.5 border border-gray-200 rounded text-center bg-transparent focus:outline-none focus:border-teal-500"
|
className="w-10 p-0.5 border border-gray-200 dark:border-gray-700 rounded text-center bg-transparent focus:outline-none focus:border-teal-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Day/Night Mode Switch */}
|
{/* View Style Toggles */}
|
||||||
<button
|
<div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 rounded p-1" title="View Style">
|
||||||
onClick={() => setDarkMode(!darkMode)}
|
<button
|
||||||
className="p-1.5 rounded-md hover:bg-gray-100 transition-colors"
|
onClick={() => setShowTimeGrid(!showTimeGrid)}
|
||||||
title={darkMode ? "Switch to Light Mode" : "Switch to Dark Mode"}
|
className={`px-2 py-0.5 text-xs rounded transition-colors ${showTimeGrid ? 'bg-white shadow-sm font-bold text-black' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700'}`}
|
||||||
>
|
>
|
||||||
{darkMode ? <Sun size={18} className="text-yellow-500" /> : <Moon size={18} className="text-gray-500" />}
|
Time Grid
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setViewStyle('grid')}
|
||||||
|
className={`px-2 py-0.5 text-xs rounded transition-colors ${!showTimeGrid && viewStyle === 'grid' ? 'bg-white shadow-sm font-bold text-black' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700'}`}
|
||||||
|
>
|
||||||
|
Grid
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setViewStyle('list')}
|
||||||
|
className={`px-2 py-0.5 text-xs rounded transition-colors ${!showTimeGrid && viewStyle === 'list' ? 'bg-white shadow-sm font-bold text-black' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700'}`}
|
||||||
|
>
|
||||||
|
List
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CENTER SECTION: Week/Year, Motto, Focus Mode */}
|
{/* CENTER SECTION: Week/Year, Motto, Focus Mode */}
|
||||||
@ -1638,27 +1666,14 @@ export default function WeeklyView() {
|
|||||||
|
|
||||||
{/* RIGHT SECTION: Navigation & Tools */}
|
{/* RIGHT SECTION: Navigation & Tools */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{/* Date Picker Toggle */}
|
{/* Day/Night Mode Switch */}
|
||||||
<div className="relative">
|
<button
|
||||||
<button
|
onClick={() => setDarkMode(!darkMode)}
|
||||||
className={`p-1.5 hover:bg-gray-100 rounded-md transition-colors ${showDatePicker ? 'text-teal-600 bg-teal-50' : 'text-gray-500 hover:text-black'}`}
|
className="p-1.5 rounded-md hover:bg-gray-100 transition-colors"
|
||||||
onClick={() => setShowDatePicker(!showDatePicker)}
|
title={darkMode ? "Switch to Light Mode" : "Switch to Dark Mode"}
|
||||||
title="Jump to date"
|
>
|
||||||
>
|
{darkMode ? <Sun size={18} className="text-yellow-500" /> : <Moon size={18} className="text-gray-500" />}
|
||||||
<Calendar size={18} />
|
</button>
|
||||||
</button>
|
|
||||||
{showDatePicker && (
|
|
||||||
<SimpleDatePicker
|
|
||||||
selected={currentWeekStart}
|
|
||||||
onSelect={(date) => {
|
|
||||||
setCurrentWeekStart(getStartOfWeek(date, weekStartDay));
|
|
||||||
setShowDatePicker(false);
|
|
||||||
}}
|
|
||||||
onClose={() => setShowDatePicker(false)}
|
|
||||||
language={language}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Navigation Controls */}
|
{/* Navigation Controls */}
|
||||||
<div className="flex items-center bg-gray-100 rounded-lg p-0.5">
|
<div className="flex items-center bg-gray-100 rounded-lg p-0.5">
|
||||||
@ -1672,16 +1687,13 @@ export default function WeeklyView() {
|
|||||||
Today
|
Today
|
||||||
</button>
|
</button>
|
||||||
<button className="p-1 hover:bg-white hover:shadow-sm rounded text-gray-500 hover:text-black transition-all" onClick={goToNextDay} title="Next Day">
|
<button className="p-1 hover:bg-white hover:shadow-sm rounded text-gray-500 hover:text-black transition-all" onClick={goToNextDay} title="Next Day">
|
||||||
<ChevronRight size={16} />
|
<ChevronLeft size={16} className="rotate-180" />
|
||||||
</button>
|
</button>
|
||||||
<button className="p-1 hover:bg-white hover:shadow-sm rounded text-gray-500 hover:text-black transition-all" onClick={goToNextWeek} title="Next Week">
|
<button className="p-1 hover:bg-white hover:shadow-sm rounded text-gray-500 hover:text-black transition-all" onClick={goToNextWeek} title="Next Week">
|
||||||
<ChevronsRight size={16} />
|
<ChevronsLeft size={16} className="rotate-180" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Divider */}
|
|
||||||
<div className="h-4 w-px bg-gray-300 mx-1"></div>
|
|
||||||
|
|
||||||
{/* Search */}
|
{/* Search */}
|
||||||
<button
|
<button
|
||||||
className="p-1.5 hover:bg-gray-100 rounded-md text-gray-500 hover:text-black transition-colors"
|
className="p-1.5 hover:bg-gray-100 rounded-md text-gray-500 hover:text-black transition-colors"
|
||||||
@ -1700,13 +1712,9 @@ export default function WeeklyView() {
|
|||||||
<Repeat size={18} />
|
<Repeat size={18} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Settings - Actually triggers User Menu in original code? No, settings was separate. */}
|
|
||||||
{/* Original code had UserMenu handling settings. And a separate sync indicator. */}
|
|
||||||
{/* The prompt asked for: Search | Settings | User Menu */}
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="p-1.5 hover:bg-gray-100 rounded-md text-gray-500 hover:text-black transition-colors"
|
className="p-1.5 hover:bg-gray-100 rounded-md text-gray-500 hover:text-black transition-colors"
|
||||||
onClick={() => setShowPreferences(true)}
|
onClick={() => setShowSettings(true)}
|
||||||
title="Settings"
|
title="Settings"
|
||||||
>
|
>
|
||||||
<Settings size={18} />
|
<Settings size={18} />
|
||||||
@ -1715,7 +1723,7 @@ export default function WeeklyView() {
|
|||||||
{/* User Menu */}
|
{/* User Menu */}
|
||||||
<UserMenu
|
<UserMenu
|
||||||
userEmail={session?.user?.email}
|
userEmail={session?.user?.email}
|
||||||
onOpenSettings={() => setShowPreferences(true)}
|
onOpenSettings={() => setShowSettings(true)}
|
||||||
trigger={
|
trigger={
|
||||||
<button className="p-1.5 hover:bg-gray-100 rounded-md text-gray-500 hover:text-black transition-colors" title="User Menu">
|
<button className="p-1.5 hover:bg-gray-100 rounded-md text-gray-500 hover:text-black transition-colors" title="User Menu">
|
||||||
<User size={18} />
|
<User size={18} />
|
||||||
@ -2533,7 +2541,38 @@ export default function WeeklyView() {
|
|||||||
<RecurringTasksManager
|
<RecurringTasksManager
|
||||||
isOpen={isRecurringTasksOpen}
|
isOpen={isRecurringTasksOpen}
|
||||||
onClose={() => setIsRecurringTasksOpen(false)}
|
onClose={() => setIsRecurringTasksOpen(false)}
|
||||||
tasks={tasks} // Pass tasks to manager
|
tasks={tasks}
|
||||||
|
onStopRecurring={async (task) => {
|
||||||
|
const idToUpdate = task.id.startsWith('virtual-') ? task.id.split('-')[1] : task.id;
|
||||||
|
const dateStr = new Date().toISOString();
|
||||||
|
|
||||||
|
// Update locally
|
||||||
|
setTasks(prev => prev.map(t => {
|
||||||
|
const isMatch = t.title === task.title &&
|
||||||
|
t.userId === task.userId &&
|
||||||
|
t.recurrenceInterval === task.recurrenceInterval &&
|
||||||
|
t.recurrenceUnit === task.recurrenceUnit;
|
||||||
|
if (isMatch) {
|
||||||
|
return { ...t, recurrenceEndDate: new Date(dateStr) };
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Update DB
|
||||||
|
try {
|
||||||
|
await fetch('/api/tasks', {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: idToUpdate,
|
||||||
|
recurrenceEndDate: dateStr
|
||||||
|
})
|
||||||
|
});
|
||||||
|
fetchTasks();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to stop recurring series:', error);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{/* Recurrence Modal */}
|
{/* Recurrence Modal */}
|
||||||
{
|
{
|
||||||
@ -2660,106 +2699,6 @@ export default function WeeklyView() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
{/* Preferences Slide-in Panel */}
|
|
||||||
<div className={`preferences-panel ${showPreferences ? 'open' : ''}`}>
|
|
||||||
<div className="preferences-panel-content">
|
|
||||||
<h3 style={{ fontSize: '1.2rem', fontWeight: 400, marginBottom: '1.5rem', color: '#fff' }}>Preferences</h3>
|
|
||||||
|
|
||||||
{/* Columns (View Days) */}
|
|
||||||
<div className="pref-row">
|
|
||||||
<span className="pref-label">Columns</span>
|
|
||||||
<div className="pref-options">
|
|
||||||
{[1, 3, 5, 7].map(n => (
|
|
||||||
<button key={n} className={`pref-option-btn ${viewDays === n ? 'active' : ''}`}
|
|
||||||
onClick={() => setViewDays(n as any)}>{n}</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Show Time Grid */}
|
|
||||||
<div className="pref-row">
|
|
||||||
<span className="pref-label">Time grid</span>
|
|
||||||
<div className="pref-options">
|
|
||||||
<button className={`pref-toggle ${showTimeGrid ? 'active' : ''}`}
|
|
||||||
onClick={() => setShowTimeGrid(!showTimeGrid)}>
|
|
||||||
{showTimeGrid ? '◉' : '○'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{showTimeGrid && (
|
|
||||||
<div className="pref-row">
|
|
||||||
<span className="pref-label">Slot duration</span>
|
|
||||||
<div className="pref-options">
|
|
||||||
{[15, 30, 60].map(n => (
|
|
||||||
<button key={n} className={`pref-option-btn ${cellDuration === n ? 'active' : ''}`}
|
|
||||||
onClick={() => setCellDuration(n as CellDuration)}>{n}m</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* View Style */}
|
|
||||||
<div className="pref-row">
|
|
||||||
<span className="pref-label">View style</span>
|
|
||||||
<div className="pref-options">
|
|
||||||
<button className={`pref-option-btn ${viewStyle === 'grid' ? 'active' : ''}`}
|
|
||||||
onClick={() => setViewStyle('grid')}>Grid</button>
|
|
||||||
<button className={`pref-option-btn ${viewStyle === 'list' ? 'active' : ''}`}
|
|
||||||
onClick={() => setViewStyle('list')}>List</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Show Someday */}
|
|
||||||
<div className="pref-row">
|
|
||||||
<span className="pref-label">Someday</span>
|
|
||||||
<div className="pref-options">
|
|
||||||
<button className={`pref-toggle ${showSomeday ? 'active' : ''}`}
|
|
||||||
onClick={() => setShowSomeday(!showSomeday)}>
|
|
||||||
{showSomeday ? '◉' : '○'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Show All Day */}
|
|
||||||
<div className="pref-row">
|
|
||||||
<span className="pref-label">All-day events</span>
|
|
||||||
<div className="pref-options">
|
|
||||||
<button className={`pref-toggle ${showAllDay ? 'active' : ''}`}
|
|
||||||
onClick={() => setShowAllDay(!showAllDay)}>
|
|
||||||
{showAllDay ? '◉' : '○'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Lines */}
|
|
||||||
<div className="pref-row">
|
|
||||||
<span className="pref-label">Protect events</span>
|
|
||||||
<div className="pref-options">
|
|
||||||
<button className={`pref-toggle ${protectEventTimes ? 'active' : ''}`}
|
|
||||||
onClick={() => setProtectEventTimes(!protectEventTimes)}>
|
|
||||||
{protectEventTimes ? '◉' : '○'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ borderTop: '1px solid rgba(255,255,255,0.1)', margin: '1rem 0' }} />
|
|
||||||
|
|
||||||
{/* Full Settings Link */}
|
|
||||||
<button
|
|
||||||
className="pref-full-settings-btn"
|
|
||||||
onClick={() => { setShowPreferences(false); setShowSettings(true); }}
|
|
||||||
>
|
|
||||||
⚙ Full Settings
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Close chevron */}
|
|
||||||
<button className="preferences-close-btn" onClick={() => setShowPreferences(false)}>
|
|
||||||
‹‹
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{showPreferences && <div className="preferences-overlay" onClick={() => setShowPreferences(false)} />}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
selectedTaskForNotes && (
|
selectedTaskForNotes && (
|
||||||
@ -3090,7 +3029,7 @@ function TaskItem({ task, isEditing, onToggle, onEdit, onUpdate, onDelete, onNot
|
|||||||
>
|
>
|
||||||
{/* Edit */}
|
{/* Edit */}
|
||||||
<button className="task-action-btn" onClick={(e) => { e.stopPropagation(); onEdit(); }} title="Edit">
|
<button className="task-action-btn" onClick={(e) => { e.stopPropagation(); onEdit(); }} title="Edit">
|
||||||
<svg viewBox="0 0 24 24" width={variant === 'minimal' ? "14" : "16"} height={variant === 'minimal' ? "14" : "16"} stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
|
<svg viewBox="0 0 24 24" width="12" height="12" stroke="currentColor" strokeWidth="2.5" fill="none" strokeLinecap="round" strokeLinejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Recurrence */}
|
{/* Recurrence */}
|
||||||
@ -3099,7 +3038,7 @@ function TaskItem({ task, isEditing, onToggle, onEdit, onUpdate, onDelete, onNot
|
|||||||
onClick={(e) => { e.stopPropagation(); onRecurrence(); }}
|
onClick={(e) => { e.stopPropagation(); onRecurrence(); }}
|
||||||
title={task.isRecurring ? "Edit recurrence" : "Make recurring"}
|
title={task.isRecurring ? "Edit recurrence" : "Make recurring"}
|
||||||
>
|
>
|
||||||
<svg viewBox="0 0 24 24" width={variant === 'minimal' ? "14" : "16"} height={variant === 'minimal' ? "14" : "16"} stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round">
|
<svg viewBox="0 0 24 24" width="12" height="12" stroke="currentColor" strokeWidth="2.5" fill="none" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<polyline points="23 4 23 10 17 10"></polyline>
|
<polyline points="23 4 23 10 17 10"></polyline>
|
||||||
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
|
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
|
||||||
</svg>
|
</svg>
|
||||||
@ -3111,7 +3050,7 @@ function TaskItem({ task, isEditing, onToggle, onEdit, onUpdate, onDelete, onNot
|
|||||||
onClick={(e) => { e.stopPropagation(); setIsNotesOpen(!isNotesOpen); }}
|
onClick={(e) => { e.stopPropagation(); setIsNotesOpen(!isNotesOpen); }}
|
||||||
title="Notes"
|
title="Notes"
|
||||||
>
|
>
|
||||||
<svg viewBox="0 0 24 24" width={variant === 'minimal' ? "14" : "16"} height={variant === 'minimal' ? "14" : "16"} stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round">
|
<svg viewBox="0 0 24 24" width="12" height="12" stroke="currentColor" strokeWidth="2.5" fill="none" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<line x1="3" y1="12" x2="21" y2="12"></line>
|
<line x1="3" y1="12" x2="21" y2="12"></line>
|
||||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
<line x1="3" y1="6" x2="21" y2="6"></line>
|
||||||
<line x1="3" y1="18" x2="21" y2="18"></line>
|
<line x1="3" y1="18" x2="21" y2="18"></line>
|
||||||
@ -3125,7 +3064,7 @@ function TaskItem({ task, isEditing, onToggle, onEdit, onUpdate, onDelete, onNot
|
|||||||
onClick={(e) => { e.stopPropagation(); onRollToggle(); }}
|
onClick={(e) => { e.stopPropagation(); onRollToggle(); }}
|
||||||
title={task.isRolling ? "Disable rolling" : "Enable rolling"}
|
title={task.isRolling ? "Disable rolling" : "Enable rolling"}
|
||||||
>
|
>
|
||||||
<svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round">
|
<svg viewBox="0 0 24 24" width="12" height="12" stroke="currentColor" strokeWidth="2.5" fill="none" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<polyline points="1 4 1 10 7 10"></polyline>
|
<polyline points="1 4 1 10 7 10"></polyline>
|
||||||
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path>
|
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path>
|
||||||
</svg>
|
</svg>
|
||||||
@ -3134,7 +3073,7 @@ function TaskItem({ task, isEditing, onToggle, onEdit, onUpdate, onDelete, onNot
|
|||||||
|
|
||||||
{/* Delete */}
|
{/* Delete */}
|
||||||
<button className="task-action-btn delete" onClick={(e) => { e.stopPropagation(); onDelete(); }} title="Delete">
|
<button className="task-action-btn delete" onClick={(e) => { e.stopPropagation(); onDelete(); }} title="Delete">
|
||||||
<svg viewBox="0 0 24 24" width={variant === 'minimal' ? "14" : "16"} height={variant === 'minimal' ? "14" : "16"} stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round">
|
<svg viewBox="0 0 24 24" width="12" height="12" stroke="currentColor" strokeWidth="2.5" fill="none" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user