feat(backend): complete AI and business migration
This commit is contained in:
@@ -1,42 +1,21 @@
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
import { requireFreelancerBackend } from "@/server/web/freelancer";
|
||||
import { InvoicesClient, type InvoiceRow } from "./invoices-client";
|
||||
|
||||
export default async function InvoicesPage() {
|
||||
const supabase = await createClient();
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { data: invoicesData } = await supabase
|
||||
.from("invoices")
|
||||
.select(`
|
||||
id,
|
||||
invoice_number,
|
||||
amount,
|
||||
currency,
|
||||
status,
|
||||
issue_date,
|
||||
due_date,
|
||||
created_at,
|
||||
clients ( name ),
|
||||
projects ( name )
|
||||
`)
|
||||
.eq("user_id", user.id)
|
||||
.order("created_at", { ascending: false });
|
||||
|
||||
const invoices: InvoiceRow[] = (invoicesData || []).map((i: any) => ({
|
||||
id: i.id,
|
||||
invoice_number: i.invoice_number,
|
||||
amount: Number(i.amount),
|
||||
currency: i.currency,
|
||||
status: i.status,
|
||||
issue_date: i.issue_date,
|
||||
due_date: i.due_date,
|
||||
created_at: i.created_at,
|
||||
clientName: i.clients?.name || null,
|
||||
projectName: i.projects?.name || null,
|
||||
const { actor, service } = await requireFreelancerBackend();
|
||||
const clientNames = new Map(service.listClients(actor).map((client) => [client.id, client.name]));
|
||||
const projectNames = new Map(service.listProjects(actor).map((project) => [project.id, project.name]));
|
||||
const invoices: InvoiceRow[] = service.listInvoices(actor).map((invoice) => ({
|
||||
id: invoice.id,
|
||||
invoice_number: invoice.invoiceNumber,
|
||||
amount: invoice.amountMinor / 100,
|
||||
currency: invoice.currency,
|
||||
status: invoice.status,
|
||||
issue_date: invoice.issueDate,
|
||||
due_date: invoice.dueDate,
|
||||
created_at: invoice.createdAt.toISOString(),
|
||||
clientName: invoice.clientId ? clientNames.get(invoice.clientId) ?? null : null,
|
||||
projectName: invoice.projectId ? projectNames.get(invoice.projectId) ?? null : null,
|
||||
}));
|
||||
|
||||
return <InvoicesClient invoices={invoices} />;
|
||||
|
||||
@@ -1,40 +1,20 @@
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
import { requireFreelancerBackend } from "@/server/web/freelancer";
|
||||
import { ProposalsClient, type ProposalRow } from "./proposals-client";
|
||||
|
||||
export default async function ProposalsPage() {
|
||||
const supabase = await createClient();
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { data: proposalsData } = await supabase
|
||||
.from("proposals")
|
||||
.select(`
|
||||
id,
|
||||
title,
|
||||
amount,
|
||||
currency,
|
||||
status,
|
||||
valid_until,
|
||||
created_at,
|
||||
clients ( name ),
|
||||
projects ( name )
|
||||
`)
|
||||
.eq("user_id", user.id)
|
||||
.order("created_at", { ascending: false });
|
||||
|
||||
const proposals: ProposalRow[] = (proposalsData || []).map((p: any) => ({
|
||||
id: p.id,
|
||||
title: p.title,
|
||||
amount: Number(p.amount),
|
||||
currency: p.currency,
|
||||
status: p.status,
|
||||
valid_until: p.valid_until,
|
||||
created_at: p.created_at,
|
||||
clientName: p.clients?.name || null,
|
||||
projectName: p.projects?.name || null,
|
||||
const { actor, service } = await requireFreelancerBackend();
|
||||
const clientNames = new Map(service.listClients(actor).map((client) => [client.id, client.name]));
|
||||
const projectNames = new Map(service.listProjects(actor).map((project) => [project.id, project.name]));
|
||||
const proposals: ProposalRow[] = service.listProposals(actor).map((proposal) => ({
|
||||
id: proposal.id,
|
||||
title: proposal.title,
|
||||
amount: proposal.amountMinor / 100,
|
||||
currency: proposal.currency,
|
||||
status: proposal.status,
|
||||
valid_until: proposal.validUntil?.toISOString() ?? null,
|
||||
created_at: proposal.createdAt.toISOString(),
|
||||
clientName: proposal.clientId ? clientNames.get(proposal.clientId) ?? null : null,
|
||||
projectName: proposal.projectId ? projectNames.get(proposal.projectId) ?? null : null,
|
||||
}));
|
||||
|
||||
return <ProposalsClient proposals={proposals} />;
|
||||
|
||||
@@ -1,40 +1,18 @@
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
import { requireFreelancerBackend } from "@/server/web/freelancer";
|
||||
import { SubscriptionsClient, type SubscriptionRow } from "./subscriptions-client";
|
||||
|
||||
export default async function SubscriptionsPage() {
|
||||
const supabase = await createClient();
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { data: subscriptionsData } = await supabase
|
||||
.from("subscriptions")
|
||||
.select(`
|
||||
id,
|
||||
name,
|
||||
amount,
|
||||
currency,
|
||||
billing_cycle,
|
||||
status,
|
||||
category,
|
||||
next_billing_date,
|
||||
created_at
|
||||
`)
|
||||
.eq("user_id", user.id)
|
||||
.order("created_at", { ascending: false });
|
||||
|
||||
const subscriptions: SubscriptionRow[] = (subscriptionsData || []).map((s: any) => ({
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
amount: Number(s.amount),
|
||||
currency: s.currency,
|
||||
billing_cycle: s.billing_cycle,
|
||||
status: s.status,
|
||||
category: s.category,
|
||||
next_billing_date: s.next_billing_date,
|
||||
created_at: s.created_at,
|
||||
const { actor, service } = await requireFreelancerBackend();
|
||||
const subscriptions: SubscriptionRow[] = service.listSubscriptions(actor).map((subscription) => ({
|
||||
id: subscription.id,
|
||||
name: subscription.name,
|
||||
amount: subscription.amountMinor / 100,
|
||||
currency: subscription.currency,
|
||||
billing_cycle: subscription.billingCycle,
|
||||
status: subscription.status,
|
||||
category: subscription.category,
|
||||
next_billing_date: subscription.nextBillingDate,
|
||||
created_at: subscription.createdAt.toISOString(),
|
||||
}));
|
||||
|
||||
return <SubscriptionsClient subscriptions={subscriptions} />;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
"use server";
|
||||
|
||||
import { requireFreelancerBackend } from "@/server/web/freelancer";
|
||||
|
||||
export async function listChatSessionsAction() {
|
||||
const { actor, service } = await requireFreelancerBackend();
|
||||
return service.listChatSessions(actor).map((session) => ({
|
||||
id: session.id,
|
||||
title: session.title,
|
||||
created_at: session.createdAt.toISOString(),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function listChatMessagesAction(sessionId: string) {
|
||||
const { actor, service } = await requireFreelancerBackend();
|
||||
return service.listChatMessages(actor, sessionId).map((message) => ({
|
||||
id: message.id,
|
||||
role: message.role,
|
||||
content: message.content,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function createChatSessionAction(title: string) {
|
||||
const { actor, service } = await requireFreelancerBackend();
|
||||
const session = service.createChatSession(actor, { title });
|
||||
return {
|
||||
id: session.id,
|
||||
title: session.title,
|
||||
created_at: session.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
export async function deleteChatSessionAction(sessionId: string) {
|
||||
const { actor, service } = await requireFreelancerBackend();
|
||||
service.deleteChatSession(actor, sessionId);
|
||||
}
|
||||
@@ -1,12 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { createClient } from "@/lib/supabase/client";
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { DefaultChatTransport, type UIMessage } from "ai";
|
||||
import { Brain, Loader2, MessageSquare, Plus, Send, Trash2 } from "lucide-react";
|
||||
import { Button } from "poyraz-ui/atoms";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { toast } from "poyraz-ui/molecules";
|
||||
import {
|
||||
createChatSessionAction,
|
||||
deleteChatSessionAction,
|
||||
listChatMessagesAction,
|
||||
listChatSessionsAction,
|
||||
} from "./actions";
|
||||
|
||||
function formatMessageContent(text: string) {
|
||||
if (!text) return null;
|
||||
@@ -38,7 +43,6 @@ type ChatSession = {
|
||||
};
|
||||
|
||||
export default function AIChatPage() {
|
||||
const [supabase] = useState(() => createClient());
|
||||
const [sessions, setSessions] = useState<ChatSession[]>([]);
|
||||
const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
|
||||
const [input, setInput] = useState("");
|
||||
@@ -60,26 +64,17 @@ export default function AIChatPage() {
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchSessions() {
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
if (!user) return;
|
||||
|
||||
const { data } = await supabase
|
||||
.from("chat_sessions")
|
||||
.select("id, title, created_at")
|
||||
.eq("user_id", user.id)
|
||||
.order("created_at", { ascending: false });
|
||||
|
||||
if (data) {
|
||||
try {
|
||||
const data = await listChatSessionsAction();
|
||||
setSessions(data);
|
||||
setActiveSessionId(data[0]?.id || null);
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "Sohbetler yüklenemedi.");
|
||||
}
|
||||
}
|
||||
|
||||
void fetchSessions();
|
||||
}, [supabase]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchMessages() {
|
||||
@@ -88,23 +83,21 @@ export default function AIChatPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
const { data } = await supabase
|
||||
.from("chat_messages")
|
||||
.select("id, role, content")
|
||||
.eq("session_id", activeSessionId)
|
||||
.order("created_at", { ascending: true });
|
||||
|
||||
const formattedMessages: UIMessage[] = (data || []).map((message) => ({
|
||||
id: message.id,
|
||||
role: message.role as UIMessage["role"],
|
||||
parts: [{ type: "text", text: message.content || "" }],
|
||||
}));
|
||||
|
||||
setMessages(formattedMessages);
|
||||
try {
|
||||
const data = await listChatMessagesAction(activeSessionId);
|
||||
const formattedMessages: UIMessage[] = data.map((message) => ({
|
||||
id: message.id,
|
||||
role: message.role as UIMessage["role"],
|
||||
parts: [{ type: "text", text: message.content }],
|
||||
}));
|
||||
setMessages(formattedMessages);
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "Mesajlar yüklenemedi.");
|
||||
}
|
||||
}
|
||||
|
||||
void fetchMessages();
|
||||
}, [activeSessionId, setMessages, supabase]);
|
||||
}, [activeSessionId, setMessages]);
|
||||
|
||||
async function handleNewChat() {
|
||||
setActiveSessionId(null);
|
||||
@@ -113,7 +106,12 @@ export default function AIChatPage() {
|
||||
|
||||
async function handleDeleteSession(id: string, event: React.MouseEvent) {
|
||||
event.stopPropagation();
|
||||
await supabase.from("chat_sessions").delete().eq("id", id);
|
||||
try {
|
||||
await deleteChatSessionAction(id);
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "Sohbet silinemedi.");
|
||||
return;
|
||||
}
|
||||
|
||||
const nextSessions = sessions.filter((session) => session.id !== id);
|
||||
setSessions(nextSessions);
|
||||
@@ -134,22 +132,16 @@ export default function AIChatPage() {
|
||||
setInput("");
|
||||
|
||||
if (!sessionId) {
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
if (!user) return;
|
||||
|
||||
const { data: newSession } = await supabase
|
||||
.from("chat_sessions")
|
||||
.insert({
|
||||
user_id: user.id,
|
||||
title: currentInput.length > 32 ? `${currentInput.slice(0, 32)}...` : currentInput,
|
||||
})
|
||||
.select("id, title, created_at")
|
||||
.single();
|
||||
|
||||
if (!newSession) return;
|
||||
let newSession: ChatSession;
|
||||
try {
|
||||
newSession = await createChatSessionAction(
|
||||
currentInput.length > 32 ? `${currentInput.slice(0, 32)}...` : currentInput,
|
||||
);
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "Sohbet oluşturulamadı.");
|
||||
setInput(currentInput);
|
||||
return;
|
||||
}
|
||||
|
||||
sessionId = newSession.id;
|
||||
setActiveSessionId(sessionId);
|
||||
|
||||
Reference in New Issue
Block a user