diff --git a/app/(dashboard)/business/invoices/invoices-client.tsx b/app/(dashboard)/business/invoices/invoices-client.tsx
index 6b39516..d78f284 100644
--- a/app/(dashboard)/business/invoices/invoices-client.tsx
+++ b/app/(dashboard)/business/invoices/invoices-client.tsx
@@ -3,7 +3,7 @@
import { useState } from "react";
import { format } from "date-fns";
import { tr } from "date-fns/locale";
-import { Receipt, Plus, MoreHorizontal, FileEdit, Trash2, Send, Download, CheckCircle2 } from "lucide-react";
+import { Plus, MoreHorizontal, FileEdit, Trash2, Send, Download, CheckCircle2 } from "lucide-react";
import { Button, Card, CardContent, Badge } from "poyraz-ui/atoms";
import {
DropdownMenu,
diff --git a/app/(dashboard)/business/proposals/proposals-client.tsx b/app/(dashboard)/business/proposals/proposals-client.tsx
index 083cf20..b620c7e 100644
--- a/app/(dashboard)/business/proposals/proposals-client.tsx
+++ b/app/(dashboard)/business/proposals/proposals-client.tsx
@@ -3,7 +3,7 @@
import { useState } from "react";
import { format } from "date-fns";
import { tr } from "date-fns/locale";
-import { FileText, Plus, MoreHorizontal, FileEdit, Trash2, Mail, CheckCircle2, XCircle } from "lucide-react";
+import { Plus, MoreHorizontal, FileEdit, Trash2, Mail, CheckCircle2, XCircle } from "lucide-react";
import { Button, Card, CardContent, Badge } from "poyraz-ui/atoms";
import {
DropdownMenu,
diff --git a/app/(dashboard)/clients/clients-client.tsx b/app/(dashboard)/clients/clients-client.tsx
index ed4e37e..37e0034 100644
--- a/app/(dashboard)/clients/clients-client.tsx
+++ b/app/(dashboard)/clients/clients-client.tsx
@@ -1,7 +1,6 @@
"use client";
import {
- archiveClientRecord,
createClientRecord,
updateClientRecord,
updateClientPipelineStage,
@@ -28,10 +27,7 @@ import {
toast,
} from "poyraz-ui/molecules";
import {
- Archive,
- ExternalLink,
Mail,
- PauseCircle,
Pencil,
Phone,
Plus,
@@ -45,7 +41,6 @@ import Link from "next/link";
import { useState } from "react";
import { format, isPast, isToday } from "date-fns";
import { tr } from "date-fns/locale";
-import { useEffect } from "react";
import { cn } from "@/lib/utils";
import { StatCard } from "@/components/system/stat-card";
@@ -68,19 +63,13 @@ export type ClientListItem = {
client_value_score: number;
};
-const statusLabels = {
- active: "Aktif",
- paused: "Duraklatıldı",
- archived: "Arşivlendi",
-};
+type ClientPipelineStage = ClientListItem["pipeline_stage"];
-const statusClasses = {
- active: "border-emerald-200 bg-emerald-50 text-emerald-700",
- paused: "border-amber-200 bg-amber-50 text-amber-700",
- archived: "border-zinc-200 bg-zinc-50 text-zinc-600",
-};
-
-const pipelineStages = [
+const pipelineStages: Array<{
+ id: ClientPipelineStage;
+ label: string;
+ color: string;
+}> = [
{ id: "lead", label: "Potansiyel (Lead)", color: "border-slate-200 bg-slate-50 text-slate-700" },
{ id: "contacted", label: "İletişime Geçildi", color: "border-blue-200 bg-blue-50 text-blue-700" },
{ id: "proposal_sent", label: "Teklif İletildi", color: "border-amber-200 bg-amber-50 text-amber-700" },
@@ -92,26 +81,24 @@ type ClientsClientProps = {
clients: ClientListItem[];
totalRevenue: number;
activeCount: number;
- pausedCount: number;
- archivedCount: number;
};
export function ClientsClient({
clients,
totalRevenue,
activeCount,
- pausedCount,
- archivedCount,
}: ClientsClientProps) {
const [query, setQuery] = useState("");
const normalizedQuery = query.trim().toLowerCase();
const [draggedClientId, setDraggedClientId] = useState
(null);
- const [localClients, setLocalClients] = useState(clients);
-
- useEffect(() => {
- setLocalClients(clients);
- }, [clients]);
+ const [pipelineOverrides, setPipelineOverrides] = useState<
+ Partial>
+ >({});
+ const localClients = clients.map((client) => ({
+ ...client,
+ pipeline_stage: pipelineOverrides[client.id] ?? client.pipeline_stage,
+ }));
function handleDragStart(event: React.DragEvent, clientId: string) {
setDraggedClientId(clientId);
@@ -119,7 +106,7 @@ export function ClientsClient({
event.dataTransfer.setData("text/plain", clientId);
}
- async function handleDrop(newStage: string) {
+ async function handleDrop(newStage: ClientPipelineStage) {
if (!draggedClientId) return;
const clientId = draggedClientId;
@@ -128,15 +115,17 @@ export function ClientsClient({
const client = localClients.find(c => c.id === clientId);
if (!client || client.pipeline_stage === newStage) return;
- setLocalClients(prev =>
- prev.map(c => c.id === clientId ? { ...c, pipeline_stage: newStage as any } : c)
- );
+ const previousStage = client.pipeline_stage;
+ setPipelineOverrides((current) => ({ ...current, [clientId]: newStage }));
try {
- await updateClientPipelineStage(clientId, newStage as any);
+ await updateClientPipelineStage(clientId, newStage);
toast.success("Müşteri aşaması güncellendi.");
} catch (error) {
- setLocalClients(clients);
+ setPipelineOverrides((current) => ({
+ ...current,
+ [clientId]: previousStage,
+ }));
toast.error(
error instanceof Error
? error.message
diff --git a/app/(dashboard)/clients/page.tsx b/app/(dashboard)/clients/page.tsx
index 721cee8..9780bba 100644
--- a/app/(dashboard)/clients/page.tsx
+++ b/app/(dashboard)/clients/page.tsx
@@ -57,8 +57,6 @@ export default async function ClientsPage() {
clients={clients}
totalRevenue={clients.reduce((sum, client) => sum + client.revenueTotal, 0)}
activeCount={clients.filter((client) => client.status === "active").length}
- pausedCount={clients.filter((client) => client.status === "paused").length}
- archivedCount={clients.filter((client) => client.status === "archived").length}
/>
);
}
diff --git a/app/(dashboard)/dashboard-client.tsx b/app/(dashboard)/dashboard-client.tsx
index 9d2470c..4e91863 100644
--- a/app/(dashboard)/dashboard-client.tsx
+++ b/app/(dashboard)/dashboard-client.tsx
@@ -125,14 +125,14 @@ export function DashboardClient({ data }: DashboardClientProps) {
{label}
- {payload.map((entry: any, index: number) => (
+ {payload.map((entry, index) => (
{entry.name === 'income' ? 'Gelir' : 'Gider'}
- {formatCurrency(entry.value)}
+ {formatCurrency(Number(entry.value ?? 0))}
))}
diff --git a/app/(dashboard)/finance/finance-client.tsx b/app/(dashboard)/finance/finance-client.tsx
index 80b9102..866172d 100644
--- a/app/(dashboard)/finance/finance-client.tsx
+++ b/app/(dashboard)/finance/finance-client.tsx
@@ -644,8 +644,10 @@ function AIFinanceDialog() {
throw new Error(data.error || "Bilinmeyen bir hata oluştu.");
}
setResult(data.text);
- } catch (err: any) {
- setResult("Hata: " + err.message);
+ } catch (error) {
+ setResult(
+ `Hata: ${error instanceof Error ? error.message : "Bilinmeyen bir hata oluştu."}`,
+ );
} finally {
setLoading(false);
}
diff --git a/app/(dashboard)/projects/[id]/page.tsx b/app/(dashboard)/projects/[id]/page.tsx
index 1bdc742..eaa509d 100644
--- a/app/(dashboard)/projects/[id]/page.tsx
+++ b/app/(dashboard)/projects/[id]/page.tsx
@@ -5,6 +5,7 @@ import {
type ProjectDetailTaskItem,
type ProjectFinanceItem,
type ProjectPlanningSectionItem,
+ type ProjectRevisionItem,
} from "@/app/(dashboard)/projects/[id]/project-detail-client";
import { DomainError } from "@/server/domain/errors";
import { requireFreelancerBackend } from "@/server/web/freelancer";
@@ -18,7 +19,7 @@ export default async function ProjectDetailPage({ params }: { params: Promise<{
sections: ProjectPlanningSectionItem[];
tasks: ProjectDetailTaskItem[];
financeTransactions: ProjectFinanceItem[];
- revisions: Array
>;
+ revisions: ProjectRevisionItem[];
};
try {
const row = service.getProject(actor, id);
diff --git a/app/(dashboard)/projects/[id]/project-detail-client.tsx b/app/(dashboard)/projects/[id]/project-detail-client.tsx
index b983579..84d0d4a 100644
--- a/app/(dashboard)/projects/[id]/project-detail-client.tsx
+++ b/app/(dashboard)/projects/[id]/project-detail-client.tsx
@@ -46,7 +46,8 @@ import {
Trash2,
Wallet,
} from "lucide-react";
-import { useEffect, useState, useTransition, type DragEvent } from "react";
+import Image from "next/image";
+import { useState, useTransition, type DragEvent } from "react";
export type ProjectDetail = {
id: string;
@@ -105,12 +106,20 @@ export type ProjectFinanceItem = {
category: string | null;
};
+export type ProjectRevisionItem = {
+ id: string;
+ description: string;
+ status: "pending" | "in_progress" | "completed" | "rejected";
+ created_at: string;
+ requested_by: string;
+};
+
type ProjectDetailClientProps = {
project: ProjectDetail;
sections: ProjectPlanningSectionItem[];
tasks: ProjectDetailTaskItem[];
financeTransactions: ProjectFinanceItem[];
- revisions: any[];
+ revisions: ProjectRevisionItem[];
};
const typeLabels = {
@@ -239,11 +248,14 @@ export function ProjectDetailClient({
{project.coverImageUrl ? (
-
-
![]()
+
) : (
@@ -339,16 +351,29 @@ export function ProjectDetailClient({
);
}
-function RevisionsPanel({ projectId, revisions }: { projectId: string; revisions: any[] }) {
+function RevisionsPanel({
+ projectId,
+ revisions,
+}: {
+ projectId: string;
+ revisions: ProjectRevisionItem[];
+}) {
const [isUpdating, setIsUpdating] = useState(false);
- async function handleStatusChange(id: string, status: string) {
+ async function handleStatusChange(
+ id: string,
+ status: ProjectRevisionItem["status"],
+ ) {
setIsUpdating(true);
try {
const { updateRevisionStatus } = await import("@/app/(dashboard)/projects/actions");
await updateRevisionStatus(id, projectId, status);
- } catch (err: any) {
- console.error(err);
+ } catch (error) {
+ toast.error(
+ error instanceof Error
+ ? error.message
+ : "Revizyon durumu güncellenemedi.",
+ );
} finally {
setIsUpdating(false);
}
@@ -370,7 +395,12 @@ function RevisionsPanel({ projectId, revisions }: { projectId: string; revisions
)}
{progressType === "auto" && (
- İlerleme yüzdesi "Görevler" sekmesindeki görevlerin tamamlanma durumuna göre otomatik hesaplanacaktır.
+ İlerleme yüzdesi "Görevler" sekmesindeki görevlerin tamamlanma durumuna göre otomatik hesaplanacaktır.
)}
diff --git a/app/(dashboard)/projects/projects-client.tsx b/app/(dashboard)/projects/projects-client.tsx
index 7b777bb..871bdd7 100644
--- a/app/(dashboard)/projects/projects-client.tsx
+++ b/app/(dashboard)/projects/projects-client.tsx
@@ -38,8 +38,9 @@ import {
Brain,
Loader2,
} from "lucide-react";
-import { usePathname, useRouter } from "next/navigation";
-import { useEffect, useState, type ChangeEvent } from "react";
+import Image from "next/image";
+import { useRouter } from "next/navigation";
+import { useEffect, useState, useTransition, type ChangeEvent } from "react";
import { StatCard } from "@/components/system/stat-card";
export type ProjectClientOption = {
@@ -215,17 +216,13 @@ function ProjectCard({
clients: ProjectClientOption[];
}) {
const router = useRouter();
- const pathname = usePathname();
- const [isNavigating, setIsNavigating] = useState(false);
+ const [isNavigating, startNavigation] = useTransition();
const detailHref = `/projects/${project.id}`;
- useEffect(() => {
- setIsNavigating(false);
- }, [pathname]);
-
function goToProjectDetail() {
- setIsNavigating(true);
- router.push(detailHref);
+ startNavigation(() => {
+ router.push(detailHref);
+ });
}
function prefetchProjectDetail() {
@@ -287,11 +284,14 @@ function ProjectCard({
function ProjectCover({ project }: { project: ProjectListItem }) {
if (project.coverImageUrl) {
return (
-
-
![]()
+
);
@@ -515,10 +515,13 @@ function CoverImageInput({ project }: { project?: ProjectListItem }) {
className="group relative flex aspect-16/7 cursor-pointer items-center justify-center overflow-hidden rounded-sm border border-dashed border-border bg-muted/20 transition-colors hover:border-primary/50 hover:bg-primary/5"
>
{previewUrl ? (
-

) : (
diff --git a/app/(dashboard)/tasks/tasks-client.tsx b/app/(dashboard)/tasks/tasks-client.tsx
index 3443862..6fbac1e 100644
--- a/app/(dashboard)/tasks/tasks-client.tsx
+++ b/app/(dashboard)/tasks/tasks-client.tsx
@@ -31,7 +31,7 @@ import {
Plus,
Trash2,
} from "lucide-react";
-import { useEffect, useState, useTransition, type DragEvent } from "react";
+import { useState, useTransition, type DragEvent } from "react";
export type TaskRelationOption = {
id: string;
@@ -82,29 +82,37 @@ type TasksClientProps = {
};
export function TasksClient({ tasks, clients, projects }: TasksClientProps) {
- const [localTasks, setLocalTasks] = useState(tasks);
+ const [statusOverrides, setStatusOverrides] = useState<
+ Partial>
+ >({});
+ const [deletedTaskIds, setDeletedTaskIds] = useState>(new Set());
const [query, setQuery] = useState("");
const [projectFilter, setProjectFilter] = useState("__all");
const [view, setView] = useState<"list" | "kanban">("list");
const [pendingTaskIds, setPendingTaskIds] = useState>(new Set());
const [, startTransition] = useTransition();
-
- useEffect(() => {
- setLocalTasks(tasks);
- }, [tasks]);
+ const localTasks = tasks
+ .filter((task) => !deletedTaskIds.has(task.id))
+ .map((task) => ({
+ ...task,
+ status: statusOverrides[task.id] ?? task.status,
+ }));
function handleTaskStatusChange(taskId: string, status: TaskListItem["status"]) {
- const previousTasks = localTasks;
+ const previousStatus = localTasks.find((task) => task.id === taskId)?.status;
setPendingTask(taskId, true);
- setLocalTasks((currentTasks) =>
- currentTasks.map((task) => (task.id === taskId ? { ...task, status } : task)),
- );
+ setStatusOverrides((current) => ({ ...current, [taskId]: status }));
startTransition(() => {
void updateTaskStatusRecord(taskId, status)
.catch((error) => {
- setLocalTasks(previousTasks);
+ setStatusOverrides((current) => {
+ const next = { ...current };
+ if (previousStatus) next[taskId] = previousStatus;
+ else delete next[taskId];
+ return next;
+ });
toast.error(
error instanceof Error
? error.message
@@ -118,7 +126,6 @@ export function TasksClient({ tasks, clients, projects }: TasksClientProps) {
}
function handleTaskDelete(taskId: string) {
- const previousTasks = localTasks;
const task = localTasks.find((item) => item.id === taskId);
const formData = new FormData();
formData.set("id", taskId);
@@ -128,12 +135,16 @@ export function TasksClient({ tasks, clients, projects }: TasksClientProps) {
}
setPendingTask(taskId, true);
- setLocalTasks((currentTasks) => currentTasks.filter((item) => item.id !== taskId));
+ setDeletedTaskIds((current) => new Set(current).add(taskId));
startTransition(() => {
void deleteTaskRecord(formData)
.catch((error) => {
- setLocalTasks(previousTasks);
+ setDeletedTaskIds((current) => {
+ const next = new Set(current);
+ next.delete(taskId);
+ return next;
+ });
toast.error(
error instanceof Error ? error.message : "Görev silinemedi.",
);