fix: restore project icons and persist someday tab assignments across reconnects
Project icons (faHeart, faBriefcase etc.) were rendering as literal text in GridTaskBlock because the lookup skipped the legacy "faX" → "x" normalization that ProjectIcon does. Added the same normalization before allIcons.find(). Someday list tabs now survive provider disconnects and reconnects: - customTabs (empty tabs) saved to viewSettings in DB instead of localStorage only - assignListToTab saves a title→tab mapping in viewSettings.somedayTabPrefs - Profile load merges DB customTabs with localStorage for migration - Import route reads somedayTabPrefs and auto-applies tab when creating/ relinking a SomedayList by title match v1.81.20
This commit is contained in:
parent
0371532e7d
commit
f94f83af73
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"name": "my-weekly-todo-list",
|
||||||
"version": "1.81.19",
|
"version": "1.81.20",
|
||||||
"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": {
|
||||||
|
|||||||
@ -40,13 +40,17 @@ export async function POST(req: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { email: session.user.email }
|
where: { email: session.user.email },
|
||||||
|
select: { id: true, viewSettings: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore tab assignments from stored preferences (survives disconnect/reconnect)
|
||||||
|
const somedayTabPrefs: Record<string, string> = (user.viewSettings as any)?.somedayTabPrefs || {};
|
||||||
|
|
||||||
// Normalize sourceLists
|
// Normalize sourceLists
|
||||||
let lists: SourceList[] = [];
|
let lists: SourceList[] = [];
|
||||||
if (Array.isArray(sourceLists)) {
|
if (Array.isArray(sourceLists)) {
|
||||||
@ -241,6 +245,7 @@ export async function POST(req: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!somedayList) {
|
if (!somedayList) {
|
||||||
|
const restoredTab = somedayTabPrefs[listTitle] || null;
|
||||||
somedayList = await prisma.somedayList.create({
|
somedayList = await prisma.somedayList.create({
|
||||||
data: {
|
data: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
@ -249,21 +254,25 @@ export async function POST(req: NextRequest) {
|
|||||||
externalId: sourceInfo?.id ?? null,
|
externalId: sourceInfo?.id ?? null,
|
||||||
externalProvider: sourceInfo ? provider : null,
|
externalProvider: sourceInfo ? provider : null,
|
||||||
lastSyncedAt: new Date(),
|
lastSyncedAt: new Date(),
|
||||||
|
...(restoredTab && { tab: restoredTab }),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
listsCreated++;
|
listsCreated++;
|
||||||
console.log(`[IMPORT] Created SomedayList "${listTitle}" (${somedayList.id}) with externalId=${sourceInfo?.id}`);
|
console.log(`[IMPORT] Created SomedayList "${listTitle}" (${somedayList.id}) with externalId=${sourceInfo?.id}${restoredTab ? `, tab=${restoredTab}` : ''}`);
|
||||||
} else if (sourceInfo && !somedayList.externalId) {
|
} else if (sourceInfo && !somedayList.externalId) {
|
||||||
// Update existing list with external link if missing
|
// Update existing list with external link if missing;
|
||||||
|
// also restore tab if it got lost and we have a preference saved
|
||||||
|
const restoredTab = !somedayList.tab && somedayTabPrefs[listTitle] ? somedayTabPrefs[listTitle] : undefined;
|
||||||
somedayList = await prisma.somedayList.update({
|
somedayList = await prisma.somedayList.update({
|
||||||
where: { id: somedayList.id },
|
where: { id: somedayList.id },
|
||||||
data: {
|
data: {
|
||||||
externalId: sourceInfo.id,
|
externalId: sourceInfo.id,
|
||||||
externalProvider: provider,
|
externalProvider: provider,
|
||||||
lastSyncedAt: new Date(),
|
lastSyncedAt: new Date(),
|
||||||
|
...(restoredTab && { tab: restoredTab }),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log(`[IMPORT] Linked SomedayList "${listTitle}" to external ${sourceInfo.id}`);
|
console.log(`[IMPORT] Linked SomedayList "${listTitle}" to external ${sourceInfo.id}${restoredTab ? `, restored tab=${restoredTab}` : ''}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// First pass: create/update all tasks (parents first via sorting)
|
// First pass: create/update all tasks (parents first via sorting)
|
||||||
|
|||||||
@ -307,7 +307,11 @@ export function GridTaskBlock({
|
|||||||
)}
|
)}
|
||||||
<span style={task.completed && showTaskCheckboxes ? { opacity: 0.5, flex: 1, display: "flex", alignItems: "center", flexWrap: "wrap", rowGap: "2px" } : { flex: 1, display: "flex", alignItems: "center", flexWrap: "wrap", rowGap: "2px" }}>
|
<span style={task.completed && showTaskCheckboxes ? { opacity: 0.5, flex: 1, display: "flex", alignItems: "center", flexWrap: "wrap", rowGap: "2px" } : { flex: 1, display: "flex", alignItems: "center", flexWrap: "wrap", rowGap: "2px" }}>
|
||||||
<span style={{ marginRight: "4px" }}>{showProjectIcons && task.project && (() => {
|
<span style={{ marginRight: "4px" }}>{showProjectIcons && task.project && (() => {
|
||||||
const found = allIcons.find(i => i.name === task.project!.icon);
|
const rawIcon = task.project!.icon || "";
|
||||||
|
const normalised = rawIcon.startsWith("fa") && rawIcon.length > 2 && rawIcon[2] === rawIcon[2].toUpperCase()
|
||||||
|
? rawIcon.slice(2, 3).toLowerCase() + rawIcon.slice(3)
|
||||||
|
: rawIcon;
|
||||||
|
const found = allIcons.find(i => i.name === normalised || i.name === rawIcon);
|
||||||
const iconStyle = { marginRight: "4px", verticalAlign: "middle" } as const;
|
const iconStyle = { marginRight: "4px", verticalAlign: "middle" } as const;
|
||||||
if (found) {
|
if (found) {
|
||||||
return found.type === "fa"
|
return found.type === "fa"
|
||||||
|
|||||||
@ -705,6 +705,15 @@ export default function WeeklyView() {
|
|||||||
setCustomTabs(tabs);
|
setCustomTabs(tabs);
|
||||||
const email = session?.user?.email;
|
const email = session?.user?.email;
|
||||||
if (email) localStorage.setItem(`weekly_custom_tabs_${email}`, JSON.stringify(tabs));
|
if (email) localStorage.setItem(`weekly_custom_tabs_${email}`, JSON.stringify(tabs));
|
||||||
|
// Also persist to DB so tabs survive on other devices and reconnects
|
||||||
|
const updated = { ...(viewSettingsRef.current as any), somedayCustomTabs: tabs };
|
||||||
|
viewSettingsRef.current = updated;
|
||||||
|
setViewSettings(updated);
|
||||||
|
fetch("/api/user/profile", {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ viewSettings: updated }),
|
||||||
|
}).catch(e => console.error("[tabs] Failed to save custom tabs to DB:", e));
|
||||||
};
|
};
|
||||||
|
|
||||||
const somedayTabs = useMemo(() => {
|
const somedayTabs = useMemo(() => {
|
||||||
@ -722,6 +731,20 @@ export default function WeeklyView() {
|
|||||||
|
|
||||||
const assignListToTab = async (listId: string, tab: string | null) => {
|
const assignListToTab = async (listId: string, tab: string | null) => {
|
||||||
setSomedayLists(prev => prev.map(l => l.id === listId ? { ...l, tab } : l));
|
setSomedayLists(prev => prev.map(l => l.id === listId ? { ...l, tab } : l));
|
||||||
|
// Persist title→tab preference so it survives reconnects
|
||||||
|
const list = somedayLists.find(l => l.id === listId);
|
||||||
|
if (list) {
|
||||||
|
const prefs: Record<string, string> = { ...((viewSettingsRef.current as any).somedayTabPrefs || {}) };
|
||||||
|
if (tab) { prefs[list.title] = tab; } else { delete prefs[list.title]; }
|
||||||
|
const updated = { ...(viewSettingsRef.current as any), somedayTabPrefs: prefs };
|
||||||
|
viewSettingsRef.current = updated;
|
||||||
|
setViewSettings(updated);
|
||||||
|
fetch("/api/user/profile", {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ viewSettings: updated }),
|
||||||
|
}).catch(e => console.error("[tabs] Failed to save tab pref:", e));
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await fetch("/api/someday-lists", {
|
await fetch("/api/someday-lists", {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
@ -1288,7 +1311,17 @@ export default function WeeklyView() {
|
|||||||
if (profileData.hourLabelFormat) setHourLabelFormat(profileData.hourLabelFormat);
|
if (profileData.hourLabelFormat) setHourLabelFormat(profileData.hourLabelFormat);
|
||||||
if (profileData.showSubHourSlots !== undefined) setShowSubHourSlots(profileData.showSubHourSlots);
|
if (profileData.showSubHourSlots !== undefined) setShowSubHourSlots(profileData.showSubHourSlots);
|
||||||
if (profileData.allDayPosition) setAllDayPosition(profileData.allDayPosition);
|
if (profileData.allDayPosition) setAllDayPosition(profileData.allDayPosition);
|
||||||
if (profileData.viewSettings) setViewSettings(profileData.viewSettings);
|
if (profileData.viewSettings) {
|
||||||
|
setViewSettings(profileData.viewSettings);
|
||||||
|
// Load customTabs from DB (cross-device, survives reconnects)
|
||||||
|
const dbTabs = (profileData.viewSettings as any).somedayCustomTabs;
|
||||||
|
if (Array.isArray(dbTabs) && dbTabs.length > 0) {
|
||||||
|
setCustomTabs(prev => {
|
||||||
|
const merged = new Set([...dbTabs, ...prev]);
|
||||||
|
return Array.from(merged);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Show onboarding wizard for new users
|
// Show onboarding wizard for new users
|
||||||
if (profileData.hasCompletedOnboarding === false) {
|
if (profileData.hasCompletedOnboarding === false) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user