My-Weekly-ToDo-List/src/app/api/someday-lists/route.ts
mARTin 3016293cd9 feat: add someday list tabs and side navigation arrows
Add tab system for someday lists allowing categorization (e.g. Private,
Work, Family). Lists can be assigned to tabs via tag icon dropdown in
list headers. Tabs appear in the someday label column for filtering.
Double-click tab name to rename. Tabs persist in DB via new SomedayList.tab field.

Also add hover-overlay prev/next day/week navigation arrows on left and
right sides of the weekly grid, and fix first hour label clipping.

i18n: tab translations for EN, DE, FR, ES, IT.

v1.25.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 21:15:46 +01:00

228 lines
7.1 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from "@/lib/auth";
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export async function GET(request: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
const userId = (session.user as any).id;
const lists = await prisma.somedayList.findMany({
where: { userId },
orderBy: { order: 'asc' },
include: {
tasks: {
where: { deletedAt: null },
orderBy: { order: 'asc' }
}
}
});
return NextResponse.json({ lists });
} catch (error) {
console.error('Error fetching someday lists:', error);
return NextResponse.json(
{ error: 'Failed to fetch someday lists' },
{ status: 500 }
);
}
}
export async function POST(request: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
const userId = (session.user as any).id;
const { title } = await request.json();
if (!title) {
return NextResponse.json(
{ error: 'Title is required' },
{ status: 400 }
);
}
// Get max order
const maxOrderList = await prisma.somedayList.findFirst({
where: { userId },
orderBy: { order: 'desc' }
});
const order = (maxOrderList?.order ?? -1) + 1;
const list = await prisma.somedayList.create({
data: {
userId,
title,
order
},
include: { tasks: true } // Return with empty tasks array for frontend consistency
});
return NextResponse.json({ list });
} catch (error) {
console.error('Error creating someday list:', error);
return NextResponse.json(
{ error: 'Failed to create someday list' },
{ status: 500 }
);
}
}
export async function DELETE(request: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
if (!id) {
return NextResponse.json(
{ error: 'List ID is required' },
{ status: 400 }
);
}
// Verify ownership
const list = await prisma.somedayList.findUnique({
where: { id }
});
if (!list || list.userId !== (session.user as any).id) {
return NextResponse.json(
{ error: 'List not found or unauthorized' },
{ status: 404 }
);
}
// Delete list (tasks cascade delete is not set in schema for tasks->list, check schema)
// In schema: tasks defined as `tasks Task[]`.
// We updated schema: `user User ... onDelete: Cascade`. `tasks` are separate.
// We need to verify if deleting list deletes tasks or unlinks them.
// Schema: `somedayList SomedayList? @relation...`
// If we want cascade delete tasks in the list, we should check relations.
// Prisma default is usually not cascade for optional relations unless specified.
// Let's assume we want to keep tasks or delete them? Usually delete list = delete tasks in it.
// Let's explicitly delete tasks first or rely on schema if configured.
// Schema update I did: `tasks Task[]`. `Task` has `somedayListId`.
// I didn't add `onDelete: Cascade` to the `somedayList` relation in `Task`.
// So I should clean up tasks manually or update schema.
// For now, let's delete tasks in the list.
// Soft-delete tasks in this list (they can be recovered from trash)
await prisma.task.updateMany({
where: { somedayListId: id },
data: { deletedAt: new Date(), somedayListId: null }
});
await prisma.somedayList.delete({
where: { id }
});
return NextResponse.json({ success: true });
} catch (error) {
console.error('Error deleting someday list:', error);
return NextResponse.json(
{ error: 'Failed to delete someday list' },
{ status: 500 }
);
}
}
export async function PATCH(request: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
const body = await request.json();
// Handle Reordering (Array of { id, order })
if (Array.isArray(body)) {
const updates = body.map(async (item: { id: string; order: number }) => {
// Verify ownership for each or just assume if one matches?
// Better to be safe, but for performance in batch, we might trust ID if valid.
// Let's verify ownership implicitly by where clause.
return prisma.somedayList.updateMany({
where: {
id: item.id,
userId: (session.user as any).id
},
data: { order: item.order }
});
});
await Promise.all(updates);
return NextResponse.json({ success: true });
}
// Handle Single Update (Title and/or Tab)
const { id, title, tab } = body;
if (!id) {
return NextResponse.json(
{ error: 'ID is required' },
{ status: 400 }
);
}
// Verify ownership
const existingList = await prisma.somedayList.findUnique({
where: { id }
});
if (!existingList || existingList.userId !== (session.user as any).id) {
return NextResponse.json(
{ error: 'List not found or unauthorized' },
{ status: 404 }
);
}
const data: Record<string, any> = {};
if (title !== undefined) data.title = title;
if (tab !== undefined) data.tab = tab;
const list = await prisma.somedayList.update({
where: { id },
data
});
return NextResponse.json({ list });
} catch (error) {
console.error('Error updating someday list:', error);
return NextResponse.json(
{ error: 'Failed to update someday list' },
{ status: 500 }
);
}
}