Files
neta/app/portal/projects/[id]/portal-project-client.tsx
T

229 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState } from "react";
import { format } from "date-fns";
import { tr } from "date-fns/locale";
import { Card, CardContent, Badge, Button, Textarea, Label } from "poyraz-ui/atoms";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from "poyraz-ui/molecules";
import { CheckCircle2, Clock, MessageSquare, Loader2, RefreshCw } from "lucide-react";
import toast from "react-hot-toast";
import { createRevisionRequest } from "./actions";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "poyraz-ui/molecules";
export function PortalProjectClient({ project, sections, tasks, revisions, clientId }: any) {
const [isSubmitting, setIsSubmitting] = useState(false);
const [openRevision, setOpenRevision] = useState(false);
const handleRevision = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsSubmitting(true);
const formData = new FormData(e.currentTarget);
try {
const res = await createRevisionRequest(project.id, clientId, formData);
if (res.error) throw new Error(res.error);
toast.success("Revizyon talebiniz başarıyla iletildi.");
setOpenRevision(false);
} catch (err: any) {
toast.error(err.message);
} finally {
setIsSubmitting(false);
}
};
const pendingRevisions = revisions.filter((r: any) => r.status === 'pending' || r.status === 'in_progress').length;
return (
<div className="mx-auto flex max-w-7xl flex-col gap-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
{/* Header Info */}
<div className="flex flex-col gap-4 md:flex-row md:items-start justify-between">
<div className="space-y-1">
<h1 className="text-3xl font-bold text-foreground">{project.name}</h1>
{project.description && <p className="text-muted-foreground">{project.description}</p>}
</div>
<div className="flex flex-col gap-2 md:items-end">
<div className="flex gap-2">
<Badge variant="outline" className="px-3 py-1 capitalize text-sm">{project.status}</Badge>
<Dialog open={openRevision} onOpenChange={setOpenRevision}>
<DialogTrigger asChild>
<Button className="gap-2 shrink-0">
<RefreshCw className="h-4 w-4" /> Revizyon Talep Et
</Button>
</DialogTrigger>
<DialogContent>
<form onSubmit={handleRevision}>
<DialogHeader>
<DialogTitle>Yeni Revizyon Talebi</DialogTitle>
</DialogHeader>
<div className="py-4 space-y-4">
{pendingRevisions > 0 && (
<div className="p-3 bg-amber-500/10 text-amber-600 rounded-md text-sm border border-amber-500/20">
Şu anda sonuçlanmamış {pendingRevisions} adet revizyon talebiniz var. Yeni bir tane eklemek istediğinize emin misiniz?
</div>
)}
<div className="space-y-2">
<Label>Lütfen yapılmasını istediğiniz değişiklikleri detaylıca açıklayın</Label>
<Textarea name="description" required rows={5} placeholder="Şu kısmın rengi mavi olabilir mi? Ayrıca metinleri güncelleyelim..." />
</div>
</div>
<DialogFooter>
<Button type="button" variant="ghost" onClick={() => setOpenRevision(false)}>İptal</Button>
<Button type="submit" disabled={isSubmitting}>
{isSubmitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Talebi Gönder
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</div>
{project.due_date && (
<div className="flex items-center gap-1.5 text-sm text-muted-foreground">
<Clock className="h-4 w-4" />
<span>Teslim: {format(new Date(project.due_date), 'd MMM yyyy', { locale: tr })}</span>
</div>
)}
</div>
</div>
<Tabs defaultValue="overview" className="w-full">
<TabsList className="mb-6 w-full justify-start rounded-none border-b border-border bg-transparent h-auto p-0">
<TabsTrigger value="overview" className="rounded-none data-[state=active]:border-b-2 data-[state=active]:border-primary px-6 py-3 data-[state=active]:shadow-none data-[state=active]:bg-transparent">
Genel Bakış
</TabsTrigger>
<TabsTrigger value="plan" className="rounded-none data-[state=active]:border-b-2 data-[state=active]:border-primary px-6 py-3 data-[state=active]:shadow-none data-[state=active]:bg-transparent">
Plan & Aşamalar
</TabsTrigger>
<TabsTrigger value="revisions" className="rounded-none data-[state=active]:border-b-2 data-[state=active]:border-primary px-6 py-3 data-[state=active]:shadow-none data-[state=active]:bg-transparent flex items-center gap-2">
Revizyonlar
{pendingRevisions > 0 && (
<Badge variant="secondary" className="px-1.5 py-0 min-w-5 h-5 flex items-center justify-center">{pendingRevisions}</Badge>
)}
</TabsTrigger>
</TabsList>
<TabsContent value="overview" className="mt-0">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Card>
<CardContent className="p-5 space-y-4">
<h3 className="font-semibold">İlerleme Durumu</h3>
<div className="space-y-2">
<div className="flex items-center justify-between text-3xl font-bold">
<span>%{project.progress}</span>
</div>
<div className="h-3 w-full bg-secondary rounded-full overflow-hidden">
<div
className="h-full bg-primary transition-all duration-500"
style={{ width: `${project.progress}%` }}
/>
</div>
<p className="text-sm text-muted-foreground">Projenizin anlık tamamlanma oranı.</p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-5 space-y-4">
<h3 className="font-semibold flex items-center gap-2">
<CheckCircle2 className="h-4 w-4" /> Yapılan İşler
</h3>
{tasks.length === 0 ? (
<p className="text-sm text-muted-foreground italic">Listelenecek görev bulunmuyor.</p>
) : (
<ul className="space-y-3 max-h-60 overflow-y-auto tiny-scrollbar pr-2">
{tasks.map((task: any) => (
<li key={task.id} className="text-sm flex gap-3 p-2 rounded hover:bg-muted/30 transition-colors">
{task.status === 'completed' || task.status === 'done' ? (
<CheckCircle2 className="h-4 w-4 text-emerald-500 shrink-0 mt-0.5" />
) : (
<div className="h-4 w-4 rounded-full border-2 border-muted-foreground/30 shrink-0 mt-0.5" />
)}
<div>
<span className={task.status === 'completed' || task.status === 'done' ? "text-muted-foreground" : "text-foreground font-medium"}>
{task.title}
</span>
{task.date && (
<div className="text-xs text-muted-foreground mt-1">
{format(new Date(task.date), 'd MMM yyyy', { locale: tr })}
</div>
)}
</div>
</li>
))}
</ul>
)}
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="plan" className="mt-0">
<Card>
<CardContent className="p-6 space-y-6">
<h3 className="font-semibold text-lg border-b border-border pb-2">Proje Planı & Aşamalar</h3>
{sections.length === 0 ? (
<p className="text-muted-foreground italic">Henüz bir plan yüklenmemiş.</p>
) : (
<div className="space-y-6">
{sections.map((section: any) => (
<div key={section.id} className="space-y-3 p-4 rounded-lg bg-muted/20 border border-border/50">
<div className="flex items-center justify-between">
<h4 className="font-medium text-foreground">{section.title}</h4>
<Badge variant="secondary" className="text-xs">
{section.type === 'milestone' ? 'Aşama' : section.type === 'deliverable' ? 'Teslimat' : 'Not'}
</Badge>
</div>
<p className="text-sm text-muted-foreground whitespace-pre-wrap">{section.content}</p>
</div>
))}
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="revisions" className="mt-0">
<Card>
<CardContent className="p-6 space-y-6">
<div className="flex items-center justify-between border-b border-border pb-2">
<h3 className="font-semibold text-lg">Revizyon Talepleriniz</h3>
<div className="text-sm text-muted-foreground">Kalan Hak: {project.revision_quota !== null ? project.revision_quota : 'Sınırsız'}</div>
</div>
{revisions.length === 0 ? (
<div className="flex flex-col items-center justify-center py-10 text-center space-y-3">
<MessageSquare className="h-8 w-8 text-muted-foreground/50" />
<p className="text-muted-foreground">Henüz bir revizyon talebi oluşturmadınız.</p>
<Button variant="outline" size="sm" onClick={() => setOpenRevision(true)}>Yeni Talep Oluştur</Button>
</div>
) : (
<div className="space-y-4">
{revisions.map((rev: any) => (
<div key={rev.id} className="p-4 rounded-md border border-border bg-muted/10 hover:bg-muted/30 transition-colors">
<div className="flex justify-between items-start mb-3">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Clock className="h-4 w-4" />
{format(new Date(rev.created_at), "d MMM yyyy, HH:mm", { locale: tr })}
</div>
<Badge variant={
rev.status === 'completed' ? 'default' :
rev.status === 'rejected' ? 'destructive' : 'secondary'
} className="capitalize">
{rev.status === 'pending' ? 'Bekliyor' :
rev.status === 'in_progress' ? 'İşleniyor' :
rev.status === 'completed' ? 'Tamamlandı' : 'Reddedildi'}
</Badge>
</div>
<p className="text-sm text-foreground whitespace-pre-wrap leading-relaxed">{rev.description}</p>
</div>
))}
</div>
)}
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}