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

227 lines
11 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-6">
{sections.length === 0 ? (
<div className="py-10 text-center border rounded-lg border-dashed text-muted-foreground">
Henüz bir plan yüklenmemiş.
</div>
) : (
<div className="space-y-4">
{sections.map((section: any) => (
<Card key={section.id}>
<CardContent className="p-5 space-y-3">
<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>
</CardContent>
</Card>
))}
</div>
)}
</TabsContent>
<TabsContent value="revisions" className="mt-6">
<div className="flex items-center justify-end mb-4">
<div className="text-sm font-medium text-muted-foreground bg-muted/50 px-3 py-1.5 rounded-md">
Kalan Hak: <span className="text-foreground ml-1">{project.revision_quota !== null ? project.revision_quota : 'Sınırsız'}</span>
</div>
</div>
{revisions.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-center space-y-3 border rounded-lg border-dashed text-muted-foreground">
<MessageSquare className="h-8 w-8 text-muted-foreground/50" />
<p>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) => (
<Card key={rev.id} className="transition-colors hover:border-primary/30">
<CardContent className="p-5">
<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>
</CardContent>
</Card>
))}
</div>
)}
</TabsContent>
</Tabs>
</div>
);
}