fix: restore sync spinner, fix task hover menu, and add custom motivational quote sources
- Reverted RefreshCcw icon to original CSS loading spinner - Fixed GridTaskBlock overflow to restore task actions menu visibility - Implemented motivationalQuote state and fetcher with custom API source setting - Removed redundant Goal of the Week setting from panel
This commit is contained in:
parent
f9d578ddf1
commit
6fed420375
@ -180,7 +180,7 @@ export function GridTaskBlock({
|
|||||||
boxShadow: (isNotesOpen || isSubTasksOpen || isResizing) ? "0 1px 3px rgba(0,0,0,0.05)" : "none",
|
boxShadow: (isNotesOpen || isSubTasksOpen || isResizing) ? "0 1px 3px rgba(0,0,0,0.05)" : "none",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
overflow: isNotesOpen || isSubTasksOpen ? "visible" : "hidden",
|
overflow: "visible",
|
||||||
}}
|
}}
|
||||||
draggable={!editingTaskId && !isResizing}
|
draggable={!editingTaskId && !isResizing}
|
||||||
onDragStart={(e) => handleDragStart(e, task)}
|
onDragStart={(e) => handleDragStart(e, task)}
|
||||||
|
|||||||
@ -539,6 +539,7 @@ export default function WeeklyView() {
|
|||||||
todayHighlightColor?: string;
|
todayHighlightColor?: string;
|
||||||
pastDayColor?: string;
|
pastDayColor?: string;
|
||||||
goalFallbackType?: "quote" | "next_todo" | "default";
|
goalFallbackType?: "quote" | "next_todo" | "default";
|
||||||
|
quoteSourceUrl?: string;
|
||||||
goalDefaultSentence?: string;
|
goalDefaultSentence?: string;
|
||||||
goalFontFamily?: string;
|
goalFontFamily?: string;
|
||||||
goalFontSize?: string;
|
goalFontSize?: string;
|
||||||
@ -607,7 +608,9 @@ export default function WeeklyView() {
|
|||||||
weekendColorSat: "#666666",
|
weekendColorSat: "#666666",
|
||||||
weekendColorSun: "#dc2626",
|
weekendColorSun: "#dc2626",
|
||||||
pastDayColor: "#a6a6a7",
|
pastDayColor: "#a6a6a7",
|
||||||
|
quoteSourceUrl: "https://recite.vercel.app/api/random",
|
||||||
});
|
});
|
||||||
|
const [motivationalQuote, setMotivationalQuote] = useState("");
|
||||||
const [showSummary, setShowSummary] = useState(false);
|
const [showSummary, setShowSummary] = useState(false);
|
||||||
|
|
||||||
const [isAddingSomedayList, setIsAddingSomedayList] = useState(false);
|
const [isAddingSomedayList, setIsAddingSomedayList] = useState(false);
|
||||||
@ -940,14 +943,43 @@ export default function WeeklyView() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchMotivationalQuote = useCallback(async () => {
|
||||||
|
if (profile.goalFallbackType !== "quote") return;
|
||||||
|
try {
|
||||||
|
const url = profile.quoteSourceUrl || "https://recite.vercel.app/api/random";
|
||||||
|
const res = await fetch(url);
|
||||||
|
if (!res.ok) throw new Error("Failed to fetch quote");
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
// Handle different JSON formats (array or object)
|
||||||
|
let quoteText = "";
|
||||||
|
if (Array.isArray(data) && data.length > 0) {
|
||||||
|
const item = data[0];
|
||||||
|
quoteText = item.quote || item.text || item.content || (typeof item === 'string' ? item : "");
|
||||||
|
if (item.author) quoteText += ` - ${item.author}`;
|
||||||
|
} else if (data && typeof data === 'object') {
|
||||||
|
quoteText = data.quote || data.text || data.content || "";
|
||||||
|
if (data.author) quoteText += ` - ${data.author}`;
|
||||||
|
} else if (typeof data === 'string') {
|
||||||
|
quoteText = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quoteText) setMotivationalQuote(quoteText);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching motivational quote:", error);
|
||||||
|
setMotivationalQuote("Stay focused and productive.");
|
||||||
|
}
|
||||||
|
}, [profile.goalFallbackType, profile.quoteSourceUrl]);
|
||||||
|
|
||||||
// Fetch tasks on mount
|
// Fetch tasks on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (session) {
|
if (session) {
|
||||||
fetchTasks();
|
fetchTasks();
|
||||||
fetchConnections();
|
fetchConnections();
|
||||||
fetchCalendarEvents();
|
fetchCalendarEvents();
|
||||||
|
fetchMotivationalQuote();
|
||||||
}
|
}
|
||||||
}, [session]);
|
}, [session, fetchMotivationalQuote]);
|
||||||
|
|
||||||
// Periodic pull-sync from Google Tasks (every 2 minutes)
|
// Periodic pull-sync from Google Tasks (every 2 minutes)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -1104,18 +1136,21 @@ export default function WeeklyView() {
|
|||||||
setGoal(newGoal);
|
setGoal(newGoal);
|
||||||
try {
|
try {
|
||||||
await fetch("/api/goal", {
|
await fetch("/api/goal", {
|
||||||
method: "PUT",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
weekStart: goalDateKey,
|
weekStart: goalDateKey,
|
||||||
text: newGoal,
|
goal: newGoal,
|
||||||
|
scope: profile.goalScope || "week",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error("Failed to save goal:", err);
|
console.error("Error saving goal:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleSomedayWheel = (e: React.WheelEvent) => {
|
const handleSomedayWheel = (e: React.WheelEvent) => {
|
||||||
if (e.currentTarget) {
|
if (e.currentTarget) {
|
||||||
e.currentTarget.scrollLeft += e.deltaY;
|
e.currentTarget.scrollLeft += e.deltaY;
|
||||||
@ -3277,10 +3312,10 @@ export default function WeeklyView() {
|
|||||||
{/* Week & Year */}
|
{/* Week & Year */}
|
||||||
<div className="whitespace-nowrap flex items-center gap-2">
|
<div className="whitespace-nowrap flex items-center gap-2">
|
||||||
{(isLoading || isSyncing || syncStatus === "syncing") && (
|
{(isLoading || isSyncing || syncStatus === "syncing") && (
|
||||||
<RefreshCcw
|
<div
|
||||||
className="animate-spin text-blue-600 mr-2"
|
className="animate-spin rounded-full h-4 w-4 border-2 border-gray-300 border-t-blue-600 mr-2"
|
||||||
size={18}
|
title="Syncing..."
|
||||||
/>
|
></div>
|
||||||
)}
|
)}
|
||||||
<span style={{
|
<span style={{
|
||||||
fontFamily: profile.cwFontFamily || "Inter",
|
fontFamily: profile.cwFontFamily || "Inter",
|
||||||
@ -3364,9 +3399,10 @@ export default function WeeklyView() {
|
|||||||
return a.order - b.order;
|
return a.order - b.order;
|
||||||
});
|
});
|
||||||
const nextTask = todaysTasks[0];
|
const nextTask = todaysTasks[0];
|
||||||
return nextTask ? `Do this now: ${nextTask.title}` : goal;
|
const nextTaskText = nextTask ? `Do this now: ${nextTask.title}` : (goal || (profile.goalFallbackType === "quote" ? motivationalQuote : goal));
|
||||||
|
return nextTaskText;
|
||||||
})()
|
})()
|
||||||
: goal}
|
: (goal || (profile.goalFallbackType === "quote" ? motivationalQuote : goal))}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="text-gray-300 mx-2">-</span>
|
<span className="text-gray-300 mx-2">-</span>
|
||||||
@ -4145,7 +4181,7 @@ export default function WeeklyView() {
|
|||||||
onEdit={() => setEditingTaskId(task.id)}
|
onEdit={() => setEditingTaskId(task.id)}
|
||||||
onUpdate={(newTitle) => updateTask(task.id, newTitle)}
|
onUpdate={(newTitle) => updateTask(task.id, newTitle)}
|
||||||
onDelete={() => deleteTask(task.id)}
|
onDelete={() => deleteTask(task.id)}
|
||||||
onNotes={(notes) => updateTaskNotes(task.id, notes)}
|
onNotes={() => setSelectedTaskForNotes(task)}
|
||||||
onRollToggle={() => toggleTaskRolling(task.id)}
|
onRollToggle={() => toggleTaskRolling(task.id)}
|
||||||
onRecurrence={() =>
|
onRecurrence={() =>
|
||||||
setSelectedTaskForRecurrence(task)
|
setSelectedTaskForRecurrence(task)
|
||||||
@ -6274,6 +6310,7 @@ function SettingsSidebar({
|
|||||||
todayHighlightColor?: string;
|
todayHighlightColor?: string;
|
||||||
pastDayColor?: string;
|
pastDayColor?: string;
|
||||||
goalFallbackType?: "quote" | "next_todo" | "default";
|
goalFallbackType?: "quote" | "next_todo" | "default";
|
||||||
|
quoteSourceUrl?: string;
|
||||||
goalDefaultSentence?: string;
|
goalDefaultSentence?: string;
|
||||||
goalFontFamily?: string;
|
goalFontFamily?: string;
|
||||||
goalFontSize?: string;
|
goalFontSize?: string;
|
||||||
@ -6316,6 +6353,8 @@ function SettingsSidebar({
|
|||||||
hourLabelFormat: "short",
|
hourLabelFormat: "short",
|
||||||
showSubHourSlots: true,
|
showSubHourSlots: true,
|
||||||
allDayPosition: "below",
|
allDayPosition: "below",
|
||||||
|
goalFallbackType: "quote",
|
||||||
|
quoteSourceUrl: "https://recite.vercel.app/api/random",
|
||||||
headlineFont: "Inter",
|
headlineFont: "Inter",
|
||||||
headlineFontSize: "1.25rem",
|
headlineFontSize: "1.25rem",
|
||||||
headlineFontWeight: "900",
|
headlineFontWeight: "900",
|
||||||
@ -8833,45 +8872,7 @@ function SettingsSidebar({
|
|||||||
<div
|
<div
|
||||||
style={{ display: "flex", flexDirection: "column", gap: "24px" }}
|
style={{ display: "flex", flexDirection: "column", gap: "24px" }}
|
||||||
>
|
>
|
||||||
{/* Goal of the Week */}
|
{/* Replaced Goal of the Week settings block */}
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
background: "var(--weekly-settings-item-bg)",
|
|
||||||
padding: "20px",
|
|
||||||
borderRadius: "12px",
|
|
||||||
border: "1px solid var(--weekly-border)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
style={{
|
|
||||||
display: "block",
|
|
||||||
fontSize: "0.9rem",
|
|
||||||
fontWeight: 600,
|
|
||||||
marginBottom: "8px",
|
|
||||||
color: "var(--weekly-settings-title)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t.goalOfWeek}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={goal}
|
|
||||||
onChange={(e) => setGoal(e.target.value)}
|
|
||||||
onBlur={() => saveGoal(goal)}
|
|
||||||
onKeyDown={(e) => e.key === "Enter" && e.currentTarget.blur()}
|
|
||||||
className="weekly-input"
|
|
||||||
placeholder={t.goalOfWeek}
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
padding: "12px",
|
|
||||||
fontSize: "1rem",
|
|
||||||
borderRadius: "6px",
|
|
||||||
border: "1px solid var(--weekly-border)",
|
|
||||||
background: "var(--weekly-bg)",
|
|
||||||
color: "var(--weekly-settings-text)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/* "Do This Now" Toggle */}
|
{/* "Do This Now" Toggle */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -9110,6 +9111,43 @@ function SettingsSidebar({
|
|||||||
<option value="default">Standardtext</option>
|
<option value="default">Standardtext</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
{profile.goalFallbackType === "quote" && (
|
||||||
|
<div style={{ marginTop: "12px" }}>
|
||||||
|
<label
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
marginBottom: "4px",
|
||||||
|
color: "var(--weekly-settings-label)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
API-Datenquelle (URL)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={profile.quoteSourceUrl || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setProfile((p) => ({
|
||||||
|
...p,
|
||||||
|
quoteSourceUrl: e.target.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
className="weekly-input"
|
||||||
|
placeholder="https://recite.vercel.app/api/random"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
padding: "10px",
|
||||||
|
fontSize: "0.95rem",
|
||||||
|
borderRadius: "6px",
|
||||||
|
border: "1px solid var(--weekly-border)",
|
||||||
|
background: "var(--weekly-bg)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p style={{ fontSize: "0.75rem", color: "var(--weekly-text-light)", marginTop: "4px" }}>
|
||||||
|
URL that returns a JSON list or object of quotes (e.g. {'[{"quote":"...","author":"..."}]'} or {'{"quote":"..."}'}).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{profile.goalFallbackType === "default" && (
|
{profile.goalFallbackType === "default" && (
|
||||||
<div style={{ marginTop: "12px" }}>
|
<div style={{ marginTop: "12px" }}>
|
||||||
<label
|
<label
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user