"use client"; import { getDocumentIntlLocale } from "@/lib/i18n/browser"; import { useTranslations } from "@/components/i18n/i18n-provider"; import { completeProjectRecord, createProjectRecord, updateProjectRecord, } from "@/app/(dashboard)/projects/actions"; import { LocalizedFields, type LocalizedFieldLocale, type LocalizedFieldValues } from "@/components/i18n/localized-fields"; import { PendingLink } from "@/components/ui/pending-link"; import { PendingSubmitButton } from "@/components/ui/pending-submit-button"; import { contentTranslationRegistry } from "@/lib/i18n/content"; import { Badge, Button, Card, CardContent, Input, Label } 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 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 = { 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; translations?: LocalizedFieldValues; }; const typeLabels = (t: any) => ({ client_project: t("projects.types.client"), side_project: t("projects.types.side"), }); const statusLabels = (t: any) => ({ planning: t("projects.status.planning"), active: t("projects.status.active"), paused: t("projects.status.paused"), completed: t("projects.status.completed"), cancelled: t("projects.status.cancelled"), }); 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[]; localization: { defaultLocale: string; locales: LocalizedFieldLocale[]; }; }; export function ProjectsClient({ projects, clients, localization }: ProjectsClientProps) { const t = useTranslations(); const [query, setQuery] = useState(""); const [view, setView] = useState<"grid" | "list">("grid"); const normalizedQuery = query.trim().toLowerCase(); const types = typeLabels(t); const filteredProjects = normalizedQuery ? projects.filter((project) => [project.name, project.description, project.clientName, types[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 (

{t("projects.title")}

{t("projects.list.title")}

{t("projects.list.count", { count: filteredProjects.length })}

setQuery(event.target.value)} placeholder={t("projects.list.search")} className="sm:w-80" />
{filteredProjects.length > 0 ? ( view === "grid" ? (
{filteredProjects.map((project) => ( ))}
) : (
{t("projects.list.columns.project")} {t("projects.list.columns.type")} {t("projects.list.columns.status")} {t("projects.list.columns.budgetDeadline")} İşlemler
{filteredProjects.map((project) => ( ))}
) ) : ( )}
); } function ProjectCard({ project, clients, localization, }: { project: ProjectListItem; clients: ProjectClientOption[]; localization: ProjectsClientProps["localization"]; }) { const t = useTranslations(); const router = useRouter(); const [isNavigating, startNavigation] = useTransition(); const detailHref = `/projects/${project.id}`; function goToProjectDetail() { startNavigation(() => { router.push(detailHref); }); } function prefetchProjectDetail() { router.prefetch(detailHref); } return ( { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); goToProjectDetail(); } }} > {isNavigating ? (
) : null}

{project.name}

{project.description || t("projects.card.noDescription")}

{statusLabels(t)[project.status]}
{t("projects.card.taskProgress", { done: project.doneTaskCount, total: project.taskCount })}
); } function ProjectCover({ project }: { project: ProjectListItem }) { const t = useTranslations(); if (project.coverImageUrl) { return (
{project.cover_image_alt
); } return (
{t("projects.card.noCover")}
); } function ProjectRow({ project, clients, localization, }: { project: ProjectListItem; clients: ProjectClientOption[]; localization: ProjectsClientProps["localization"]; }) { const t = useTranslations(); return (
{project.name}
{project.clientName || t("projects.card.noClient")}
{typeLabels(t)[project.type]}
{statusLabels(t)[project.status]}
); } function ProjectMeta({ project }: { project: ProjectListItem }) { const t = useTranslations(); return (
{typeLabels(t)[project.type]}
{project.clientName || t("projects.card.noClient")}
{project.due_date ? formatDate(project.due_date) : t("projects.card.noDeadline")}
{project.budget_amount ? formatCurrency(project.budget_amount) : t("projects.card.noBudget")}
); } function ProjectActions({ project, clients, localization, showDetail, }: { project: ProjectListItem; clients: ProjectClientOption[]; localization: ProjectsClientProps["localization"]; showDetail: boolean; }) { const t = useTranslations(); return (
event.stopPropagation()} onKeyDown={(event) => event.stopPropagation()} > {showDetail ? ( ) : null} {project.status !== "completed" ? (
} >
) : null}
); } function ProjectDialog({ mode, project, clients, localization, iconOnly = false, }: { mode: "create" | "edit"; project?: ProjectListItem; clients: ProjectClientOption[]; localization: ProjectsClientProps["localization"]; iconOnly?: boolean; }) { const t = useTranslations(); 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" ? t("projects.messages.created") : t("projects.messages.updated")); } catch (error) { toast.error( error instanceof Error ? error.message : t("projects.errors.saveFailed"), ); } finally { setIsSubmitting(false); } } return (
{project ? : null} {mode === "create" ? t("projects.form.createTitle") : t("projects.form.editTitle")} {t("projects.form.description")}
); } function CoverImageInput({ project }: { project?: ProjectListItem }) { const t = useTranslations(); 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, localization, projectType, onProjectTypeChange, }: { project?: ProjectListItem; clients: ProjectClientOption[]; localization: ProjectsClientProps["localization"]; projectType: ProjectListItem["type"]; onProjectTypeChange: (value: ProjectListItem["type"]) => void; }) { const t = useTranslations(); return (
({ ...f, label: (t as any)(`projects.fields.${f.name}`) || f.label, placeholder: f.placeholder ? (t as any)(`projects.placeholders.${f.name}`) || f.placeholder : undefined }))} values={project?.translations} fallbackValues={{ name: project?.name, description: project?.description, coverImageAlt: project?.cover_image_alt, }} />
{ const el = document.getElementById(`progress-val-${project?.id || "new"}`); if (el) el.textContent = `%${e.target.value}`; }} /> %{project?.progress ?? 0}
); } function ProgressBar({ progress, compact = false }: { progress: number; compact?: boolean }) { const t = useTranslations(); return (
{!compact ? (
{t("projects.card.progress")} {progress}%
) : null}
); } function EmptyState({ hasQuery }: { hasQuery: boolean }) { const t = useTranslations(); return (
{t("projects.empty.title")}
{t("projects.empty.description")}
); } function formatDate(value: string) { return new Intl.DateTimeFormat(getDocumentIntlLocale(), { day: "2-digit", month: "short", year: "numeric", }).format(new Date(`${value}T00:00:00`)); } function formatCurrency(value: number) { return new Intl.NumberFormat(getDocumentIntlLocale(), { style: "currency", currency: "USD", maximumFractionDigits: 0, }).format(value); } function AIProjectRiskDialog({ projectId }: { projectId?: string }) { const t = useTranslations(); const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const handleAnalyze = async () => { setLoading(true); setResult(null); try { const res = await fetch("/api/project-risk", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ projectId }), }); const data = await res.json(); if (!res.ok) { throw new Error(data.error || "Bilinmeyen bir hata oluştu."); } setResult(data.text); } catch (err) { setResult( "Hata: " + (err instanceof Error ? err.message : "Bilinmeyen bir hata oluştu."), ); } finally { setLoading(false); } }; return ( Proje Risk Analizi Yapay zeka, projelerinizin ilerleme durumunu ve bitiş tarihlerini kontrol ederek riskleri tahmin eder.
{!result && !loading && (
)} {loading && (

Projeler analiz ediliyor...

)} {result && (
{result}
)}
{result && ( )}
); }