feat: optimize mobile UX with simplified header and quick actions
Replace crowded 7-element mobile header with clean 3-element layout: hamburger menu (left), tappable date display (center), search (right). All touch targets are 44px+ per Apple HIG. Move navigation, quick actions, view controls, and utilities into a new mobile quick actions panel at the top of the settings sidebar. Add safe area insets for notch and home indicator on iOS devices. v1.34.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
de483ed64e
commit
d28a02c5b5
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"name": "my-weekly-todo-list",
|
||||||
"version": "1.33.8",
|
"version": "1.34.0",
|
||||||
"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": {
|
||||||
|
|||||||
@ -3847,65 +3847,99 @@ h3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* --- Mobile overflow menu --- */
|
/* --- Mobile overflow menu --- */
|
||||||
.mobile-overflow-menu {
|
/* --- Mobile Header (simplified 3-element layout) --- */
|
||||||
position: absolute;
|
.mobile-header {
|
||||||
right: 0;
|
|
||||||
top: 100%;
|
|
||||||
margin-top: 4px;
|
|
||||||
background: white;
|
|
||||||
border: 1px solid #e5e7eb;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 8px 30px rgba(0,0,0,0.15);
|
|
||||||
z-index: 1000;
|
|
||||||
padding: 8px;
|
|
||||||
min-width: 220px;
|
|
||||||
max-height: 70vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
.dark .mobile-overflow-menu {
|
|
||||||
background: #1f2937;
|
|
||||||
border-color: #374151;
|
|
||||||
}
|
|
||||||
.mobile-overflow-menu button,
|
|
||||||
.mobile-overflow-menu .mobile-menu-item {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
justify-content: space-between;
|
||||||
|
padding: 4px 12px;
|
||||||
|
padding-top: calc(4px + env(safe-area-inset-top, 0px));
|
||||||
|
min-height: 48px;
|
||||||
|
gap: 8px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.mobile-header-btn {
|
||||||
|
min-width: 44px;
|
||||||
|
min-height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.mobile-header-btn:active {
|
||||||
|
background: rgba(0, 0, 0, 0.06);
|
||||||
|
transform: scale(0.92);
|
||||||
|
}
|
||||||
|
.dark-mode .mobile-header-btn:active {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
.mobile-header-date {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
min-height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.mobile-header-date:active {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Mobile Quick Actions in Sidebar --- */
|
||||||
|
.mobile-quick-nav-btn {
|
||||||
|
min-width: 44px;
|
||||||
|
min-height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: none;
|
||||||
|
background: var(--weekly-settings-toggle-bg, #f3f4f6);
|
||||||
|
color: var(--weekly-text, #333);
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.mobile-quick-nav-btn:active {
|
||||||
|
background: var(--weekly-teal, #0ea5e9);
|
||||||
|
color: #fff;
|
||||||
|
transform: scale(0.92);
|
||||||
|
}
|
||||||
|
.mobile-quick-action-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-height: 48px;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
font-size: 0.85rem;
|
font-size: 0.95rem;
|
||||||
color: #374151;
|
color: var(--weekly-text, #374151);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
.dark .mobile-overflow-menu button,
|
.mobile-quick-action-btn:active {
|
||||||
.dark .mobile-overflow-menu .mobile-menu-item {
|
background: var(--weekly-settings-toggle-bg, #f3f4f6);
|
||||||
color: #e5e7eb;
|
|
||||||
}
|
|
||||||
.mobile-overflow-menu button:active,
|
|
||||||
.mobile-overflow-menu .mobile-menu-item:active {
|
|
||||||
background: #f3f4f6;
|
|
||||||
}
|
|
||||||
.dark .mobile-overflow-menu button:active,
|
|
||||||
.dark .mobile-overflow-menu .mobile-menu-item:active {
|
|
||||||
background: #374151;
|
|
||||||
}
|
|
||||||
.mobile-menu-divider {
|
|
||||||
height: 1px;
|
|
||||||
background: #e5e7eb;
|
|
||||||
margin: 4px 8px;
|
|
||||||
}
|
|
||||||
.dark .mobile-menu-divider {
|
|
||||||
background: #374151;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Floating Action Button --- */
|
/* --- Floating Action Button --- */
|
||||||
.mobile-fab {
|
.mobile-fab {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 24px;
|
bottom: calc(24px + env(safe-area-inset-bottom, 0px));
|
||||||
right: 24px;
|
right: 24px;
|
||||||
width: 56px;
|
width: 56px;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import FocusModeOverlay from "./FocusModeOverlay";
|
|||||||
import {
|
import {
|
||||||
LayoutGrid,
|
LayoutGrid,
|
||||||
Calendar,
|
Calendar,
|
||||||
|
ChevronDown,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
ChevronsLeft,
|
ChevronsLeft,
|
||||||
@ -1737,10 +1738,10 @@ export default function WeeklyView() {
|
|||||||
|
|
||||||
// Mobile detection
|
// Mobile detection
|
||||||
const [isMobile, setIsMobile] = useState(false);
|
const [isMobile, setIsMobile] = useState(false);
|
||||||
const [showMobileMenu, setShowMobileMenu] = useState(false);
|
|
||||||
const [showMobileFabSheet, setShowMobileFabSheet] = useState(false);
|
const [showMobileFabSheet, setShowMobileFabSheet] = useState(false);
|
||||||
const [fabTaskTitle, setFabTaskTitle] = useState("");
|
const [fabTaskTitle, setFabTaskTitle] = useState("");
|
||||||
const mobileMenuRef = useRef<HTMLDivElement>(null);
|
|
||||||
const fabTextareaRef = useRef<HTMLTextAreaElement>(null);
|
const fabTextareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
// Moved state definitions to the top
|
// Moved state definitions to the top
|
||||||
@ -5533,153 +5534,51 @@ export default function WeeklyView() {
|
|||||||
|
|
||||||
{/* Mobile Header */}
|
{/* Mobile Header */}
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<header className="flex items-center justify-between w-full px-3 py-2 border-b border-gray-200 bg-white dark:bg-gray-900 dark:border-gray-700 dark:text-white" style={{ minHeight: "48px" }}>
|
<header className="mobile-header" style={{ background: darkMode ? "#111827" : "#ffffff", borderBottom: `1px solid ${darkMode ? "#374151" : "#e5e7eb"}` }}>
|
||||||
{/* Left: Navigation */}
|
{/* Left: Menu button */}
|
||||||
<div className="flex items-center bg-gray-100 dark:bg-gray-800 rounded-lg p-0.5">
|
<button className="mobile-header-btn" onClick={() => setShowSettings(true)} title="Menu" style={{ color: darkMode ? "#e5e7eb" : "#6b7280" }}>
|
||||||
<button className="p-1.5 rounded text-gray-500 active:bg-white active:shadow-sm" onClick={goToPrevWeek} title="Previous Week">
|
<Menu size={22} />
|
||||||
<ChevronsLeft size={16} />
|
</button>
|
||||||
</button>
|
|
||||||
<button className="p-1.5 rounded text-gray-500 active:bg-white active:shadow-sm" onClick={goToPrevDay} title="Previous Day">
|
|
||||||
<ChevronLeft size={16} />
|
|
||||||
</button>
|
|
||||||
<button className="w-7 h-7 flex items-center justify-center text-gray-600 dark:text-gray-300 rounded active:bg-white active:shadow-sm" onClick={goToToday} title="Today">
|
|
||||||
<span style={{ fontSize: "8px" }}>●</span>
|
|
||||||
</button>
|
|
||||||
<button className="p-1.5 rounded text-gray-500 active:bg-white active:shadow-sm" onClick={goToNextDay} title="Next Day">
|
|
||||||
<ChevronLeft size={16} className="rotate-180" />
|
|
||||||
</button>
|
|
||||||
<button className="p-1.5 rounded text-gray-500 active:bg-white active:shadow-sm" onClick={goToNextWeek} title="Next Week">
|
|
||||||
<ChevronsLeft size={16} className="rotate-180" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Center: Week info */}
|
{/* Center: Date display (tap to jump to date) */}
|
||||||
<div className="flex items-center gap-1 text-sm" style={{
|
<button className="mobile-header-date" onClick={() => setShowDatePicker(true)} style={{
|
||||||
fontFamily: profile.cwFontFamily || "Inter",
|
fontFamily: profile.cwFontFamily || "Inter",
|
||||||
|
fontSize: profile.cwFontSize || "0.9rem",
|
||||||
fontWeight: Number(profile.cwFontWeight || "700"),
|
fontWeight: Number(profile.cwFontWeight || "700"),
|
||||||
color: adjustColorForDarkMode(profile.cwColor || (darkMode ? "#e5e7eb" : "#333333"), darkMode),
|
color: adjustColorForDarkMode(profile.cwColor || (darkMode ? "#e5e7eb" : "#333333"), darkMode),
|
||||||
}}>
|
}}>
|
||||||
{(isLoading || isSyncing || syncStatus === "syncing") ? (
|
{(isLoading || isSyncing || syncStatus === "syncing") && (
|
||||||
<div className="weekly-spinner" title="Syncing..."></div>
|
<div className="weekly-spinner" title="Syncing..." style={{ width: 14, height: 14 }}></div>
|
||||||
) : syncError ? (
|
)}
|
||||||
<AlertCircle size={14} className="text-red-500" />
|
{syncError && <AlertCircle size={14} className="text-red-500" />}
|
||||||
) : null}
|
<span>
|
||||||
<div className="flex flex-col items-center justify-center leading-tight" style={{ whiteSpace: "nowrap", fontSize: profile.cwFontSize || "0.8rem" }}>
|
{profile.headerDisplay === "none" ? "" :
|
||||||
{profile.headerDisplay === "none" ? (
|
profile.headerDisplay === "date" ? (() => {
|
||||||
null
|
const today = new Date();
|
||||||
) : profile.headerDisplay === "date" ? (
|
const isTodayInWeek = getVisibleDays().some(d =>
|
||||||
<span>{(() => {
|
d.getDate() === today.getDate() &&
|
||||||
const today = new Date();
|
d.getMonth() === today.getMonth() &&
|
||||||
const isTodayInWeek = getVisibleDays().some(d =>
|
d.getFullYear() === today.getFullYear()
|
||||||
d.getDate() === today.getDate() &&
|
);
|
||||||
d.getMonth() === today.getMonth() &&
|
const refDate = isTodayInWeek ? today : getCWReferenceDate(getVisibleDays());
|
||||||
d.getFullYear() === today.getFullYear()
|
return refDate.toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||||
);
|
})() :
|
||||||
const refDate = isTodayInWeek ? today : getCWReferenceDate(getVisibleDays());
|
profile.headerDisplay === "month_year" ?
|
||||||
return refDate.toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' });
|
getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: 'long', year: 'numeric' }) :
|
||||||
})()}</span>
|
profile.headerDisplay === "custom" ?
|
||||||
) : profile.headerDisplay === "month_year" ? (
|
formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), language, t) :
|
||||||
<span>{getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: 'long', year: 'numeric' })}</span>
|
profile.headerDisplay === "month" ?
|
||||||
) : profile.headerDisplay === "custom" ? (
|
getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: "long" }) :
|
||||||
<span>{formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), language, t)}</span>
|
`KW ${getWeekNumber(getCWReferenceDate(getVisibleDays())).toString().padStart(2, "0")} | ${getCWReferenceDate(getVisibleDays()).getFullYear()}`
|
||||||
) : (
|
}
|
||||||
<>
|
</span>
|
||||||
<span>KW{getWeekNumber(getCWReferenceDate(getVisibleDays())).toString().padStart(2, "0")}</span>
|
<ChevronDown size={12} style={{ opacity: 0.5 }} />
|
||||||
<span style={{
|
</button>
|
||||||
fontFamily: profile.yearFontFamily || "Inter",
|
|
||||||
fontWeight: Number(profile.yearFontWeight || "700"),
|
|
||||||
color: adjustColorForDarkMode(profile.yearColor || "#333333", darkMode),
|
|
||||||
}}>{getCWReferenceDate(getVisibleDays()).getFullYear()}</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right: Settings + Overflow */}
|
{/* Right: Search */}
|
||||||
<div className="flex items-center gap-1" ref={mobileMenuRef}>
|
<button className="mobile-header-btn" onClick={() => setIsSearchOpen(true)} title="Search" style={{ color: darkMode ? "#e5e7eb" : "#6b7280" }}>
|
||||||
<button className="p-2 rounded-md text-gray-500 active:bg-gray-100" onClick={() => setShowSettings(true)} title="Settings">
|
<Search size={20} />
|
||||||
<Settings size={18} />
|
</button>
|
||||||
</button>
|
|
||||||
<div className="relative">
|
|
||||||
<button className="p-2 rounded-md text-gray-500 active:bg-gray-100" onClick={() => setShowMobileMenu(!showMobileMenu)} title="More">
|
|
||||||
<MoreVertical size={18} />
|
|
||||||
</button>
|
|
||||||
{showMobileMenu && (
|
|
||||||
<div className="mobile-overflow-menu" onClick={() => setShowMobileMenu(false)}>
|
|
||||||
<button onClick={() => { setShowDatePicker(true); }}>
|
|
||||||
<Calendar size={16} /> Jump to date
|
|
||||||
</button>
|
|
||||||
<button onClick={() => setIsSearchOpen(true)}>
|
|
||||||
<Search size={16} /> Search
|
|
||||||
</button>
|
|
||||||
<div className="mobile-menu-divider" />
|
|
||||||
<button onClick={() => {
|
|
||||||
const now = new Date();
|
|
||||||
setCalendarEventModal({ isOpen: true, event: undefined, initialDate: now, initialStartTime: `${String(now.getHours()).padStart(2, "0")}:00` });
|
|
||||||
}}>
|
|
||||||
<Plus size={16} /> Add Calendar Event
|
|
||||||
</button>
|
|
||||||
<button onClick={() => setIsRecurringTasksOpen(true)}>
|
|
||||||
<Repeat size={16} /> Recurring Tasks
|
|
||||||
</button>
|
|
||||||
<div className="mobile-menu-divider" />
|
|
||||||
<button onClick={() => { const newVal = !showNextTask; setShowNextTask(newVal); saveSetting("showNextTask", newVal); }}>
|
|
||||||
{showNextTask ? <Play size={16} className="text-teal-600" /> : <Target size={16} />}
|
|
||||||
{showNextTask ? "Showing Next Task" : "Showing Goal"}
|
|
||||||
</button>
|
|
||||||
<button onClick={() => setShowFocusMode(true)}>
|
|
||||||
<Zap size={16} /> Focus Mode
|
|
||||||
</button>
|
|
||||||
<button onClick={() => setDarkMode(!darkMode)}>
|
|
||||||
{darkMode ? <Sun size={16} className="text-yellow-500" /> : <Moon size={16} />}
|
|
||||||
{darkMode ? "Light Mode" : "Dark Mode"}
|
|
||||||
</button>
|
|
||||||
<div className="mobile-menu-divider" />
|
|
||||||
<div className="mobile-menu-item" style={{ flexDirection: "column", alignItems: "flex-start", gap: "6px" }}>
|
|
||||||
<span style={{ fontSize: "0.75rem", color: "#9ca3af" }}>Days to show</span>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
{[1, 3, 5, 7].map((num) => (
|
|
||||||
<button
|
|
||||||
key={num}
|
|
||||||
onClick={(e) => { e.stopPropagation(); setViewDays(num); savedViewDaysRef.current = num; saveSetting("viewDays", num); setShowMobileMenu(false); }}
|
|
||||||
className={`px-3 py-1 text-xs rounded ${viewDays === num ? "bg-sky-500 text-white font-bold" : "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300"}`}
|
|
||||||
>
|
|
||||||
{num}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{showTimeGrid && (
|
|
||||||
<div className="mobile-menu-item" style={{ flexDirection: "column", alignItems: "flex-start", gap: "6px" }}>
|
|
||||||
<span style={{ fontSize: "0.75rem", color: "#9ca3af" }}>Slot duration</span>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
{[15, 30, 60].map((d) => (
|
|
||||||
<button
|
|
||||||
key={d}
|
|
||||||
onClick={(e) => { e.stopPropagation(); setCellDuration(d as CellDuration); saveSetting("cellDuration", d); setShowMobileMenu(false); }}
|
|
||||||
className={`px-3 py-1 text-xs rounded ${cellDuration === d ? "bg-sky-500 text-white font-bold" : "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300"}`}
|
|
||||||
>
|
|
||||||
{d}m
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="mobile-menu-divider" />
|
|
||||||
<button onClick={handleUndo} disabled={undoCount === 0} style={undoCount === 0 ? { opacity: 0.3 } : {}}>
|
|
||||||
<Undo2 size={16} /> Undo
|
|
||||||
</button>
|
|
||||||
<button onClick={handleRedo} disabled={redoCount === 0} style={redoCount === 0 ? { opacity: 0.3 } : {}}>
|
|
||||||
<Redo2 size={16} /> Redo
|
|
||||||
</button>
|
|
||||||
<div className="mobile-menu-divider" />
|
|
||||||
<button onClick={() => { fetchCalendarEvents(true); fetchTasks(); }}>
|
|
||||||
<RefreshCcw size={16} /> Refresh
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -8204,6 +8103,35 @@ export default function WeeklyView() {
|
|||||||
saveKanbanStages={saveKanbanStages}
|
saveKanbanStages={saveKanbanStages}
|
||||||
profile={profile}
|
profile={profile}
|
||||||
setProfile={setProfile}
|
setProfile={setProfile}
|
||||||
|
isMobile={isMobile}
|
||||||
|
mobileActions={isMobile ? {
|
||||||
|
goToPrevWeek,
|
||||||
|
goToPrevDay,
|
||||||
|
goToToday,
|
||||||
|
goToNextDay,
|
||||||
|
goToNextWeek,
|
||||||
|
onJumpToDate: () => setShowDatePicker(true),
|
||||||
|
onAddCalendarEvent: () => {
|
||||||
|
const now = new Date();
|
||||||
|
setCalendarEventModal({ isOpen: true, event: undefined, initialDate: now, initialStartTime: `${String(now.getHours()).padStart(2, "0")}:00` });
|
||||||
|
},
|
||||||
|
onRecurringTasks: () => setIsRecurringTasksOpen(true),
|
||||||
|
onToggleNextTask: () => { const newVal = !showNextTask; setShowNextTask(newVal); saveSetting("showNextTask", newVal); },
|
||||||
|
onFocusMode: () => setShowFocusMode(true),
|
||||||
|
onToggleDarkMode: () => setDarkMode(!darkMode),
|
||||||
|
onUndo: handleUndo,
|
||||||
|
onRedo: handleRedo,
|
||||||
|
onRefresh: () => { fetchCalendarEvents(true); fetchTasks(); },
|
||||||
|
darkMode,
|
||||||
|
showNextTask,
|
||||||
|
undoCount,
|
||||||
|
redoCount,
|
||||||
|
viewDays,
|
||||||
|
onViewDaysChange: (num: number) => { setViewDays(num); savedViewDaysRef.current = num; saveSetting("viewDays", num); },
|
||||||
|
showTimeGrid,
|
||||||
|
cellDuration,
|
||||||
|
onCellDurationChange: (d: CellDuration) => { setCellDuration(d); saveSetting("cellDuration", d); },
|
||||||
|
} : undefined}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -9480,6 +9408,33 @@ interface SettingsSidebarProps {
|
|||||||
onProjectsChanged: () => void;
|
onProjectsChanged: () => void;
|
||||||
kanbanStages: KanbanStage[];
|
kanbanStages: KanbanStage[];
|
||||||
saveKanbanStages: (stages: KanbanStage[]) => Promise<void>;
|
saveKanbanStages: (stages: KanbanStage[]) => Promise<void>;
|
||||||
|
// Mobile quick actions
|
||||||
|
isMobile?: boolean;
|
||||||
|
mobileActions?: {
|
||||||
|
goToPrevWeek: () => void;
|
||||||
|
goToPrevDay: () => void;
|
||||||
|
goToToday: () => void;
|
||||||
|
goToNextDay: () => void;
|
||||||
|
goToNextWeek: () => void;
|
||||||
|
onJumpToDate: () => void;
|
||||||
|
onAddCalendarEvent: () => void;
|
||||||
|
onRecurringTasks: () => void;
|
||||||
|
onToggleNextTask: () => void;
|
||||||
|
onFocusMode: () => void;
|
||||||
|
onToggleDarkMode: () => void;
|
||||||
|
onUndo: () => void;
|
||||||
|
onRedo: () => void;
|
||||||
|
onRefresh: () => void;
|
||||||
|
darkMode: boolean;
|
||||||
|
showNextTask: boolean;
|
||||||
|
undoCount: number;
|
||||||
|
redoCount: number;
|
||||||
|
viewDays: number;
|
||||||
|
onViewDaysChange: (days: number) => void;
|
||||||
|
showTimeGrid: boolean;
|
||||||
|
cellDuration: CellDuration;
|
||||||
|
onCellDurationChange: (d: CellDuration) => void;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
// Notes Sidebar Component
|
// Notes Sidebar Component
|
||||||
interface NotesSidebarProps {
|
interface NotesSidebarProps {
|
||||||
@ -9702,6 +9657,8 @@ function SettingsSidebar({
|
|||||||
saveKanbanStages,
|
saveKanbanStages,
|
||||||
profile,
|
profile,
|
||||||
setProfile,
|
setProfile,
|
||||||
|
isMobile: isMobileSidebar,
|
||||||
|
mobileActions,
|
||||||
}: SettingsSidebarProps) {
|
}: SettingsSidebarProps) {
|
||||||
const [activeTab, setActiveTab] = useState<
|
const [activeTab, setActiveTab] = useState<
|
||||||
"calendar" | "general" | "account" | "styling" | "motivation" | "about" | "localisation"
|
"calendar" | "general" | "account" | "styling" | "motivation" | "about" | "localisation"
|
||||||
@ -10162,6 +10119,120 @@ function SettingsSidebar({
|
|||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
{/* Mobile Quick Actions Panel */}
|
||||||
|
{isMobileSidebar && mobileActions && (
|
||||||
|
<div className="mobile-quick-actions" style={{
|
||||||
|
padding: "12px 16px",
|
||||||
|
borderBottom: "1px solid var(--weekly-border, #eee)",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "8px",
|
||||||
|
}}>
|
||||||
|
{/* Navigation Row */}
|
||||||
|
<div style={{ display: "flex", justifyContent: "center", gap: "4px", padding: "4px 0" }}>
|
||||||
|
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.goToPrevWeek(); handleClose(); }} title="Previous Week">
|
||||||
|
<ChevronsLeft size={18} />
|
||||||
|
</button>
|
||||||
|
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.goToPrevDay(); handleClose(); }} title="Previous Day">
|
||||||
|
<ChevronLeft size={18} />
|
||||||
|
</button>
|
||||||
|
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.goToToday(); handleClose(); }} title="Today"
|
||||||
|
style={{ fontWeight: 700, fontSize: "0.75rem", minWidth: 64 }}>
|
||||||
|
{t.today || "Today"}
|
||||||
|
</button>
|
||||||
|
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.goToNextDay(); handleClose(); }} title="Next Day">
|
||||||
|
<ChevronRight size={18} />
|
||||||
|
</button>
|
||||||
|
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.goToNextWeek(); handleClose(); }} title="Next Week">
|
||||||
|
<ChevronsRight size={18} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Action Buttons */}
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "2px" }}>
|
||||||
|
<button className="mobile-quick-action-btn" onClick={() => { mobileActions.onJumpToDate(); handleClose(); }}>
|
||||||
|
<Calendar size={18} /> <span>{t.jumpToDate || "Jump to date"}</span>
|
||||||
|
</button>
|
||||||
|
<button className="mobile-quick-action-btn" onClick={() => { mobileActions.onAddCalendarEvent(); handleClose(); }}>
|
||||||
|
<Plus size={18} /> <span>{t.addCalendarEvent || "Add Calendar Event"}</span>
|
||||||
|
</button>
|
||||||
|
<button className="mobile-quick-action-btn" onClick={() => { mobileActions.onRecurringTasks(); handleClose(); }}>
|
||||||
|
<Repeat size={18} /> <span>{t.recurringTasks || "Recurring Tasks"}</span>
|
||||||
|
</button>
|
||||||
|
<button className="mobile-quick-action-btn" onClick={() => { mobileActions.onToggleNextTask(); }}>
|
||||||
|
{mobileActions.showNextTask ? <Play size={18} className="text-teal-600" /> : <Target size={18} />}
|
||||||
|
<span>{mobileActions.showNextTask ? (t.showingNextTask || "Showing Next Task") : (t.showingGoal || "Showing Goal")}</span>
|
||||||
|
</button>
|
||||||
|
<button className="mobile-quick-action-btn" onClick={() => { mobileActions.onFocusMode(); handleClose(); }}>
|
||||||
|
<Zap size={18} /> <span>{t.focusMode || "Focus Mode"}</span>
|
||||||
|
</button>
|
||||||
|
<button className="mobile-quick-action-btn" onClick={() => { mobileActions.onToggleDarkMode(); }}>
|
||||||
|
{mobileActions.darkMode ? <Sun size={18} className="text-yellow-500" /> : <Moon size={18} />}
|
||||||
|
<span>{mobileActions.darkMode ? (t.lightMode || "Light Mode") : (t.darkMode || "Dark Mode")}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* View Controls */}
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "8px", padding: "4px 0" }}>
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||||
|
<span style={{ fontSize: "0.75rem", color: "var(--weekly-text-light, #9ca3af)", minWidth: "40px" }}>{t.days || "Days"}</span>
|
||||||
|
<div style={{ display: "flex", gap: "4px", flex: 1 }}>
|
||||||
|
{[1, 3, 5, 7].map((num) => (
|
||||||
|
<button
|
||||||
|
key={num}
|
||||||
|
onClick={() => { mobileActions.onViewDaysChange(num); }}
|
||||||
|
style={{
|
||||||
|
flex: 1, padding: "6px 0", borderRadius: "6px", border: "none", cursor: "pointer",
|
||||||
|
fontSize: "0.8rem", fontWeight: mobileActions.viewDays === num ? 700 : 400,
|
||||||
|
background: mobileActions.viewDays === num ? "#0ea5e9" : "var(--weekly-settings-toggle-bg, #f3f4f6)",
|
||||||
|
color: mobileActions.viewDays === num ? "#fff" : "var(--weekly-text, #333)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{num}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{mobileActions.showTimeGrid && (
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||||
|
<span style={{ fontSize: "0.75rem", color: "var(--weekly-text-light, #9ca3af)", minWidth: "40px" }}>{t.slot || "Slot"}</span>
|
||||||
|
<div style={{ display: "flex", gap: "4px", flex: 1 }}>
|
||||||
|
{[15, 30, 60].map((d) => (
|
||||||
|
<button
|
||||||
|
key={d}
|
||||||
|
onClick={() => { mobileActions.onCellDurationChange(d as CellDuration); }}
|
||||||
|
style={{
|
||||||
|
flex: 1, padding: "6px 0", borderRadius: "6px", border: "none", cursor: "pointer",
|
||||||
|
fontSize: "0.8rem", fontWeight: mobileActions.cellDuration === d ? 700 : 400,
|
||||||
|
background: mobileActions.cellDuration === d ? "#0ea5e9" : "var(--weekly-settings-toggle-bg, #f3f4f6)",
|
||||||
|
color: mobileActions.cellDuration === d ? "#fff" : "var(--weekly-text, #333)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{d}m
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Utility Row */}
|
||||||
|
<div style={{ display: "flex", justifyContent: "center", gap: "8px", padding: "4px 0", borderTop: "1px solid var(--weekly-border, #eee)", paddingTop: "8px" }}>
|
||||||
|
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.onUndo(); }} disabled={mobileActions.undoCount === 0}
|
||||||
|
style={{ opacity: mobileActions.undoCount === 0 ? 0.3 : 1 }} title="Undo">
|
||||||
|
<Undo2 size={16} />
|
||||||
|
</button>
|
||||||
|
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.onRedo(); }} disabled={mobileActions.redoCount === 0}
|
||||||
|
style={{ opacity: mobileActions.redoCount === 0 ? 0.3 : 1 }} title="Redo">
|
||||||
|
<Redo2 size={16} />
|
||||||
|
</button>
|
||||||
|
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.onRefresh(); handleClose(); }} title="Refresh">
|
||||||
|
<RefreshCcw size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="weekly-settings-tabs"
|
className="weekly-settings-tabs"
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user