feat: implement project detail page with planning sections, tasks, and finance management

feat: add project planning section CRUD operations and integrate with Supabase
feat: enhance project card interactions with navigation to project details
fix: update task completion logic to revalidate project paths
This commit is contained in:
poyrazavsever
2026-06-05 09:39:22 +03:00
parent a341f1f69f
commit 4c46a70212
5 changed files with 1122 additions and 9 deletions
+110
View File
@@ -6,6 +6,18 @@ import { revalidatePath } from "next/cache";
const PROJECT_TYPES = ["client_project", "side_project"] as const;
const PROJECT_STATUSES = ["planning", "active", "paused", "completed", "cancelled"] as const;
const PLANNING_SECTION_CATEGORIES = [
"overview",
"problem",
"goal",
"audience",
"scope",
"design_system",
"color_palette",
"typography",
"assets",
"notes",
] as const;
const PROJECT_ASSETS_BUCKET = "project-assets";
function cleanText(value: FormDataEntryValue | null) {
@@ -27,6 +39,15 @@ function readProjectStatus(value: FormDataEntryValue | null) {
: "planning";
}
function readPlanningSectionCategory(value: FormDataEntryValue | null) {
const category = typeof value === "string" ? value : "overview";
return PLANNING_SECTION_CATEGORIES.includes(
category as (typeof PLANNING_SECTION_CATEGORIES)[number],
)
? category
: "overview";
}
function readNumber(value: FormDataEntryValue | null) {
const number = Number(typeof value === "string" ? value.replace(",", ".") : value);
return Number.isFinite(number) ? number : null;
@@ -209,4 +230,93 @@ export async function completeProjectRecord(formData: FormData) {
}
revalidatePath("/projects");
revalidatePath(`/projects/${id}`);
}
function readPlanningSectionPayload(formData: FormData) {
return {
project_id: cleanText(formData.get("project_id")),
category: readPlanningSectionCategory(formData.get("category")),
title: cleanText(formData.get("title")),
content: cleanText(formData.get("content")),
sort_order: Math.round(readNumber(formData.get("sort_order")) ?? 0),
};
}
export async function createProjectPlanningSectionRecord(formData: FormData) {
const { supabase, userId } = await getCurrentUserId();
const payload = readPlanningSectionPayload(formData);
if (!payload.project_id || !payload.title) {
throw new Error("Planlama alanı eklemek için proje ve başlık zorunludur.");
}
const { error } = await supabase.from("project_planning_sections").insert({
user_id: userId,
project_id: payload.project_id,
category: payload.category,
title: payload.title,
content: payload.content,
sort_order: payload.sort_order,
});
if (error) {
throw new Error(`Planlama alanı eklenemedi: ${error.message}`);
}
revalidatePath("/projects");
revalidatePath(`/projects/${payload.project_id}`);
}
export async function updateProjectPlanningSectionRecord(formData: FormData) {
const { supabase, userId } = await getCurrentUserId();
const id = cleanText(formData.get("id"));
const payload = readPlanningSectionPayload(formData);
if (!id || !payload.project_id || !payload.title) {
throw new Error("Planlama alanını güncellemek için kayıt kimliği, proje ve başlık zorunludur.");
}
const { error } = await supabase
.from("project_planning_sections")
.update({
category: payload.category,
title: payload.title,
content: payload.content,
sort_order: payload.sort_order,
})
.eq("id", id)
.eq("project_id", payload.project_id)
.eq("user_id", userId);
if (error) {
throw new Error(`Planlama alanı güncellenemedi: ${error.message}`);
}
revalidatePath("/projects");
revalidatePath(`/projects/${payload.project_id}`);
}
export async function deleteProjectPlanningSectionRecord(formData: FormData) {
const { supabase, userId } = await getCurrentUserId();
const id = cleanText(formData.get("id"));
const projectId = cleanText(formData.get("project_id"));
if (!id || !projectId) {
throw new Error("Silinecek planlama alanı bulunamadı.");
}
const { error } = await supabase
.from("project_planning_sections")
.delete()
.eq("id", id)
.eq("project_id", projectId)
.eq("user_id", userId);
if (error) {
throw new Error(`Planlama alanı silinemedi: ${error.message}`);
}
revalidatePath("/projects");
revalidatePath(`/projects/${projectId}`);
}