feat(backend): migrate freelancer and portal runtimes
This commit is contained in:
@@ -1,139 +1,48 @@
|
||||
import {
|
||||
ProjectsClient,
|
||||
type ProjectClientOption,
|
||||
type ProjectListItem,
|
||||
} from "@/app/(dashboard)/projects/projects-client";
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
import { createServiceRoleClient } from "@/lib/supabase/admin";
|
||||
|
||||
type ProjectRow = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
client_id: string | null;
|
||||
name: string;
|
||||
type: "client_project" | "side_project";
|
||||
description: string | null;
|
||||
status: "planning" | "active" | "paused" | "completed" | "cancelled";
|
||||
start_date: string | null;
|
||||
due_date: string | null;
|
||||
budget_amount: number | string | null;
|
||||
currency: string;
|
||||
progress: number;
|
||||
cover_image_path: string | null;
|
||||
cover_image_alt: string | null;
|
||||
clients: { name: string } | { name: string }[] | null;
|
||||
};
|
||||
|
||||
type TaskRow = {
|
||||
project_id: string | null;
|
||||
status: string | null;
|
||||
};
|
||||
import { ProjectsClient, type ProjectClientOption, type ProjectListItem } from "@/app/(dashboard)/projects/projects-client";
|
||||
import { requireFreelancerBackend } from "@/server/web/freelancer";
|
||||
|
||||
export default async function ProjectsPage() {
|
||||
const supabase = await createClient();
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
const { actor, service } = await requireFreelancerBackend();
|
||||
const projectRows = service.listProjects(actor);
|
||||
const clientRows = service.listClients(actor);
|
||||
const taskRows = service.listTasks(actor);
|
||||
const clientNames = new Map(clientRows.map((client) => [client.id, client.name]));
|
||||
const taskStats = new Map<string, { total: number; done: number }>();
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
for (const task of taskRows) {
|
||||
if (!task.projectId || task.status === "cancelled") continue;
|
||||
const stats = taskStats.get(task.projectId) ?? { total: 0, done: 0 };
|
||||
stats.total += 1;
|
||||
if (task.status === "done") stats.done += 1;
|
||||
taskStats.set(task.projectId, stats);
|
||||
}
|
||||
|
||||
const [{ data: projectRows }, { data: clientRows }, { data: taskRows }] =
|
||||
await Promise.all([
|
||||
supabase
|
||||
.from("projects")
|
||||
.select(
|
||||
"id, user_id, client_id, name, type, description, status, start_date, due_date, budget_amount, currency, progress, cover_image_path, cover_image_alt, clients(name)",
|
||||
)
|
||||
.eq("user_id", user.id)
|
||||
.order("created_at", { ascending: false }),
|
||||
supabase
|
||||
.from("clients")
|
||||
.select("id, name")
|
||||
.eq("user_id", user.id)
|
||||
.neq("status", "archived")
|
||||
.order("name", { ascending: true }),
|
||||
supabase.from("tasks").select("project_id, status").eq("user_id", user.id),
|
||||
]);
|
||||
|
||||
const taskStats = countTasksByProject((taskRows || []) as TaskRow[]);
|
||||
const clients = (clientRows || []) as ProjectClientOption[];
|
||||
const signedUrls = await createProjectImageUrls(
|
||||
((projectRows || []) as unknown as ProjectRow[])
|
||||
.map((project) => project.cover_image_path)
|
||||
.filter(Boolean) as string[],
|
||||
);
|
||||
|
||||
const projects: ProjectListItem[] = ((projectRows || []) as unknown as ProjectRow[]).map((project) => {
|
||||
const stats = taskStats.get(project.id) || { total: 0, done: 0 };
|
||||
|
||||
const projects: ProjectListItem[] = projectRows.map((project) => {
|
||||
const stats = taskStats.get(project.id) ?? { total: 0, done: 0 };
|
||||
return {
|
||||
id: project.id,
|
||||
client_id: project.client_id,
|
||||
clientName: getClientName(project.clients),
|
||||
client_id: project.clientId,
|
||||
clientName: project.clientId ? clientNames.get(project.clientId) ?? null : null,
|
||||
name: project.name,
|
||||
type: project.type,
|
||||
description: project.description,
|
||||
status: project.status,
|
||||
start_date: project.start_date,
|
||||
due_date: project.due_date,
|
||||
budget_amount: project.budget_amount === null ? null : Number(project.budget_amount),
|
||||
start_date: project.startDate,
|
||||
due_date: project.dueDate,
|
||||
budget_amount: project.budgetAmountMinor == null ? null : project.budgetAmountMinor / 100,
|
||||
currency: project.currency,
|
||||
progress: project.progress,
|
||||
cover_image_path: project.cover_image_path,
|
||||
cover_image_alt: project.cover_image_alt,
|
||||
coverImageUrl: project.cover_image_path ? signedUrls.get(project.cover_image_path) || null : null,
|
||||
cover_image_path: project.legacyCoverImagePath,
|
||||
cover_image_alt: project.coverImageAlt,
|
||||
coverImageUrl: project.legacyCoverImagePath,
|
||||
taskCount: stats.total,
|
||||
doneTaskCount: stats.done,
|
||||
};
|
||||
});
|
||||
const clients: ProjectClientOption[] = clientRows
|
||||
.filter((client) => client.status !== "archived")
|
||||
.sort((a, b) => a.name.localeCompare(b.name, "tr"))
|
||||
.map(({ id, name }) => ({ id, name }));
|
||||
|
||||
return <ProjectsClient projects={projects} clients={clients} />;
|
||||
}
|
||||
|
||||
async function createProjectImageUrls(
|
||||
paths: string[],
|
||||
) {
|
||||
const admin = createServiceRoleClient();
|
||||
const urls = new Map<string, string>();
|
||||
const uniquePaths = Array.from(new Set(paths));
|
||||
|
||||
await Promise.all(
|
||||
uniquePaths.map(async (path) => {
|
||||
const { data } = await admin.storage
|
||||
.from("project-assets")
|
||||
.createSignedUrl(path, 60 * 15);
|
||||
|
||||
if (data?.signedUrl) {
|
||||
urls.set(path, data.signedUrl);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
function getClientName(client: ProjectRow["clients"]) {
|
||||
if (!client) return null;
|
||||
return Array.isArray(client) ? client[0]?.name || null : client.name;
|
||||
}
|
||||
|
||||
function countTasksByProject(tasks: TaskRow[]) {
|
||||
const statsByProject = new Map<string, { total: number; done: number }>();
|
||||
|
||||
for (const task of tasks) {
|
||||
if (!task.project_id) continue;
|
||||
|
||||
const current = statsByProject.get(task.project_id) || { total: 0, done: 0 };
|
||||
current.total += 1;
|
||||
|
||||
if (task.status === "done") {
|
||||
current.done += 1;
|
||||
}
|
||||
|
||||
statsByProject.set(task.project_id, current);
|
||||
}
|
||||
|
||||
return statsByProject;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user