"use client"; import { completeProjectRecord, createProjectRecord, updateProjectRecord, } from "@/app/(dashboard)/projects/actions"; import { PendingLink } from "@/components/ui/pending-link"; import { PendingSubmitButton } from "@/components/ui/pending-submit-button"; import { Badge, Button, Card, CardContent, Input, Label, Textarea } from "poyraz-ui/atoms"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "poyraz-ui/molecules"; import { CalendarDays, CheckCircle2, Eye, FolderKanban, ImageIcon, LayoutGrid, List, Pencil, Plus, Target, Wallet, Brain, Loader2, } from "lucide-react"; import { usePathname, useRouter } from "next/navigation"; import { useEffect, useState, type ChangeEvent } from "react"; import { StatCard } from "@/components/system/stat-card"; export type ProjectClientOption = { id: string; name: string; }; export type ProjectListItem = { id: string; client_id: string | null; clientName: 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 | null; currency: string; progress: number; cover_image_path: string | null; cover_image_alt: string | null; coverImageUrl: string | null; taskCount: number; doneTaskCount: number; }; const typeLabels = { client_project: "Müşteri projesi", side_project: "Side project", }; const statusLabels = { planning: "Planlama", active: "Aktif", paused: "Duraklatıldı", completed: "Tamamlandı", cancelled: "İptal edildi", }; const statusClasses = { planning: "border-blue-200 bg-blue-50 text-blue-700", active: "border-emerald-200 bg-emerald-50 text-emerald-700", paused: "border-amber-200 bg-amber-50 text-amber-700", completed: "border-zinc-200 bg-zinc-50 text-zinc-700", cancelled: "border-rose-200 bg-rose-50 text-rose-700", }; type ProjectsClientProps = { projects: ProjectListItem[]; clients: ProjectClientOption[]; }; export function ProjectsClient({ projects, clients }: ProjectsClientProps) { const [query, setQuery] = useState(""); const [view, setView] = useState<"grid" | "list">("grid"); const normalizedQuery = query.trim().toLowerCase(); const filteredProjects = normalizedQuery ? projects.filter((project) => [project.name, project.description, project.clientName, typeLabels[project.type]] .filter(Boolean) .some((value) => value!.toLowerCase().includes(normalizedQuery)), ) : projects; const activeCount = projects.filter((project) => project.status === "active").length; const sideProjectCount = projects.filter((project) => project.type === "side_project").length; const averageProgress = projects.length ? Math.round(projects.reduce((sum, project) => sum + project.progress, 0) / projects.length) : 0; const totalBudget = projects.reduce((sum, project) => sum + (project.budget_amount || 0), 0); return (

Projeler

Proje listesi

{filteredProjects.length} kayıt görüntüleniyor.

setQuery(event.target.value)} placeholder="Proje, müşteri veya açıklama ara" className="sm:w-80" />
{filteredProjects.length > 0 ? ( view === "grid" ? (
{filteredProjects.map((project) => ( ))}
) : (
Proje Tür Durum İlerleme İşlem
{filteredProjects.map((project) => ( ))}
) ) : ( )}
); } function ProjectCard({ project, clients, }: { project: ProjectListItem; clients: ProjectClientOption[]; }) { const router = useRouter(); const pathname = usePathname(); const [isNavigating, setIsNavigating] = useState(false); const detailHref = `/projects/${project.id}`; useEffect(() => { setIsNavigating(false); }, [pathname]); function goToProjectDetail() { setIsNavigating(true); router.push(detailHref); } function prefetchProjectDetail() { router.prefetch(detailHref); } return ( { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); goToProjectDetail(); } }} > {isNavigating ? (
) : null}

{project.name}

{project.description || "Açıklama eklenmedi."}

{statusLabels[project.status]}
{project.doneTaskCount}/{project.taskCount} görev tamamlandı
); } function ProjectCover({ project }: { project: ProjectListItem }) { if (project.coverImageUrl) { return (
{project.cover_image_alt
); } return (
Kapak görseli yok
); } function ProjectRow({ project, clients, }: { project: ProjectListItem; clients: ProjectClientOption[]; }) { return (
{project.name}
{project.clientName || "Bağımsız side project"}
{typeLabels[project.type]}
{statusLabels[project.status]}
); } function ProjectMeta({ project }: { project: ProjectListItem }) { return (
{typeLabels[project.type]}
{project.clientName || "Müşteri bağlantısı yok"}
{project.due_date ? formatDate(project.due_date) : "Deadline yok"}
{project.budget_amount ? formatCurrency(project.budget_amount) : "Bütçe yok"}
); } function ProjectActions({ project, clients, showDetail, }: { project: ProjectListItem; clients: ProjectClientOption[]; showDetail: boolean; }) { return (
event.stopPropagation()} onKeyDown={(event) => event.stopPropagation()} > {showDetail ? ( ) : null} {project.status !== "completed" ? (
} >
) : null}
); } function ProjectDialog({ mode, project, clients, iconOnly = false, }: { mode: "create" | "edit"; project?: ProjectListItem; clients: ProjectClientOption[]; iconOnly?: boolean; }) { const [open, setOpen] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [projectType, setProjectType] = useState(project?.type || "client_project"); const action = mode === "create" ? createProjectRecord : updateProjectRecord; async function handleSubmit(formData: FormData) { setIsSubmitting(true); try { await action(formData); setOpen(false); toast.success(mode === "create" ? "Proje eklendi." : "Proje güncellendi."); } catch (error) { toast.error( error instanceof Error ? error.message : "Proje kaydedilirken beklenmeyen bir hata oluştu.", ); } finally { setIsSubmitting(false); } } return (
{project ? : null} {mode === "create" ? "Yeni proje" : "Projeyi düzenle"} Müşteri projelerini ve kişisel side projectleri aynı modelde takip et.
); } function CoverImageInput({ project }: { project?: ProjectListItem }) { const inputId = `cover-${project?.id || "new"}`; const [previewUrl, setPreviewUrl] = useState(project?.coverImageUrl || ""); useEffect(() => { return () => { if (previewUrl.startsWith("blob:")) { URL.revokeObjectURL(previewUrl); } }; }, [previewUrl]); function handleFileChange(event: ChangeEvent) { const file = event.target.files?.[0]; if (!file) { setPreviewUrl(project?.coverImageUrl || ""); return; } const nextPreviewUrl = URL.createObjectURL(file); setPreviewUrl((currentPreviewUrl) => { if (currentPreviewUrl.startsWith("blob:")) { URL.revokeObjectURL(currentPreviewUrl); } return nextPreviewUrl; }); } return (
); } function ProjectFormFields({ project, clients, projectType, onProjectTypeChange, }: { project?: ProjectListItem; clients: ProjectClientOption[]; projectType: ProjectListItem["type"]; onProjectTypeChange: (value: ProjectListItem["type"]) => void; }) { return (