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:
mARTin 2026-04-03 11:11:11 +02:00
parent 0371532e7d
commit f94f83af73
4 changed files with 53 additions and 7 deletions

View File

@ -1,6 +1,6 @@
{
"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",
"main": "index.js",
"scripts": {

View File

@ -40,13 +40,17 @@ export async function POST(req: NextRequest) {
}
const user = await prisma.user.findUnique({
where: { email: session.user.email }
where: { email: session.user.email },
select: { id: true, viewSettings: true },
});
if (!user) {
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
let lists: SourceList[] = [];
if (Array.isArray(sourceLists)) {
@ -241,6 +245,7 @@ export async function POST(req: NextRequest) {
}
if (!somedayList) {
const restoredTab = somedayTabPrefs[listTitle] || null;
somedayList = await prisma.somedayList.create({
data: {
userId: user.id,
@ -249,21 +254,25 @@ export async function POST(req: NextRequest) {
externalId: sourceInfo?.id ?? null,
externalProvider: sourceInfo ? provider : null,
lastSyncedAt: new Date(),
...(restoredTab && { tab: restoredTab }),
}
});
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) {
// 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({
where: { id: somedayList.id },
data: {
externalId: sourceInfo.id,
externalProvider: provider,
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)

View File

@ -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={{ 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;
if (found) {
return found.type === "fa"

View File

@ -705,6 +705,15 @@ export default function WeeklyView() {
setCustomTabs(tabs);
const email = session?.user?.email;
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(() => {
@ -722,6 +731,20 @@ export default function WeeklyView() {
const assignListToTab = async (listId: string, tab: string | null) => {
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 {
await fetch("/api/someday-lists", {
method: "PATCH",
@ -1288,7 +1311,17 @@ export default function WeeklyView() {
if (profileData.hourLabelFormat) setHourLabelFormat(profileData.hourLabelFormat);
if (profileData.showSubHourSlots !== undefined) setShowSubHourSlots(profileData.showSubHourSlots);
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
if (profileData.hasCompletedOnboarding === false) {