feat: implement project management and portal client detail views with dashboard shell and layout components
This commit is contained in:
@@ -96,7 +96,7 @@ export function ClientDetailClient({ client, activities }: { client: ClientDetai
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto flex max-w-5xl flex-col gap-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
<div className="mx-auto flex max-w-7xl flex-col gap-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||||
{/* Header Info */}
|
{/* Header Info */}
|
||||||
<div className="flex flex-col gap-4 md:flex-row md:items-start justify-between">
|
<div className="flex flex-col gap-4 md:flex-row md:items-start justify-between">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
|
|||||||
@@ -250,8 +250,8 @@ export function ClientsClient({
|
|||||||
|
|
||||||
<TabsContent value="list" className="mt-0">
|
<TabsContent value="list" className="mt-0">
|
||||||
{filteredClients.length > 0 ? (
|
{filteredClients.length > 0 ? (
|
||||||
<div className="overflow-hidden rounded-sm border border-border">
|
<div className="overflow-hidden">
|
||||||
<div className="hidden grid-cols-[1.5fr_1fr_1fr_1fr_0.8fr_0.8fr] gap-4 border-b border-border bg-muted/40 px-4 py-3 text-xs font-medium uppercase text-muted-foreground lg:grid">
|
<div className="hidden grid-cols-[1.5fr_1fr_1fr_1fr_0.8fr_0.8fr] gap-4 border-b border-border px-4 py-3 text-xs font-medium uppercase text-muted-foreground lg:grid">
|
||||||
<span>Müşteri</span>
|
<span>Müşteri</span>
|
||||||
<span>İletişim</span>
|
<span>İletişim</span>
|
||||||
<span>Aşama</span>
|
<span>Aşama</span>
|
||||||
|
|||||||
@@ -297,14 +297,14 @@ export function ProjectDetailClient({
|
|||||||
<TabButton active={activeTab === "finance"} onClick={() => setActiveTab("finance")}>
|
<TabButton active={activeTab === "finance"} onClick={() => setActiveTab("finance")}>
|
||||||
Finans
|
Finans
|
||||||
</TabButton>
|
</TabButton>
|
||||||
{revisions.length > 0 && (
|
|
||||||
<TabButton active={activeTab === "revisions"} onClick={() => setActiveTab("revisions")}>
|
<TabButton active={activeTab === "revisions"} onClick={() => setActiveTab("revisions")}>
|
||||||
Revizyonlar
|
Revizyonlar
|
||||||
|
{revisions.filter(r => r.status === 'pending').length > 0 && (
|
||||||
<Badge variant="secondary" className="ml-2 px-1 py-0 h-4 text-[10px]">
|
<Badge variant="secondary" className="ml-2 px-1 py-0 h-4 text-[10px]">
|
||||||
{revisions.filter(r => r.status === 'pending').length}
|
{revisions.filter(r => r.status === 'pending').length}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TabButton>
|
|
||||||
)}
|
)}
|
||||||
|
</TabButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{activeTab === "planning" ? (
|
{activeTab === "planning" ? (
|
||||||
|
|||||||
@@ -659,14 +659,24 @@ function ProjectFormFields({
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor={`progress-${project?.id || "new"}`}>İlerleme (%)</Label>
|
<Label htmlFor={`progress-${project?.id || "new"}`}>İlerleme (%)</Label>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
<Input
|
<Input
|
||||||
id={`progress-${project?.id || "new"}`}
|
id={`progress-${project?.id || "new"}`}
|
||||||
name="progress"
|
name="progress"
|
||||||
type="number"
|
type="range"
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
defaultValue={project?.progress ?? 0}
|
defaultValue={project?.progress ?? 0}
|
||||||
|
className="flex-1 cursor-pointer accent-primary"
|
||||||
|
onChange={(e) => {
|
||||||
|
const el = document.getElementById(`progress-val-${project?.id || "new"}`);
|
||||||
|
if (el) el.textContent = `%${e.target.value}`;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
<span id={`progress-val-${project?.id || "new"}`} className="w-10 text-sm font-medium text-right">
|
||||||
|
%{project?.progress ?? 0}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { CheckCircle2, Clock, MessageSquare, Loader2, RefreshCw } from "lucide-r
|
|||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { createRevisionRequest } from "./actions";
|
import { createRevisionRequest } from "./actions";
|
||||||
|
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "poyraz-ui/molecules";
|
||||||
|
|
||||||
export function PortalProjectClient({ project, sections, tasks, revisions, clientId }: any) {
|
export function PortalProjectClient({ project, sections, tasks, revisions, clientId }: any) {
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [openRevision, setOpenRevision] = useState(false);
|
const [openRevision, setOpenRevision] = useState(false);
|
||||||
@@ -32,7 +34,7 @@ export function PortalProjectClient({ project, sections, tasks, revisions, clien
|
|||||||
const pendingRevisions = revisions.filter((r: any) => r.status === 'pending' || r.status === 'in_progress').length;
|
const pendingRevisions = revisions.filter((r: any) => r.status === 'pending' || r.status === 'in_progress').length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto flex max-w-7xl flex-col gap-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
<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 */}
|
{/* Header Info */}
|
||||||
<div className="flex flex-col gap-4 md:flex-row md:items-start justify-between">
|
<div className="flex flex-col gap-4 md:flex-row md:items-start justify-between">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -84,70 +86,29 @@ export function PortalProjectClient({ project, sections, tasks, revisions, clien
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<Tabs defaultValue="overview" className="w-full">
|
||||||
<div className="md:col-span-2 space-y-6">
|
<TabsList className="mb-6 w-full justify-start rounded-none border-b border-border bg-transparent h-auto p-0">
|
||||||
{/* Project Planning / Milestones */}
|
<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">
|
||||||
<Card>
|
Genel Bakış
|
||||||
<CardContent className="p-5 space-y-6">
|
</TabsTrigger>
|
||||||
<h3 className="font-semibold text-lg border-b border-border pb-2">Proje Planı & Aşamalar</h3>
|
<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">
|
||||||
{sections.length === 0 ? (
|
Plan & Aşamalar
|
||||||
<p className="text-muted-foreground italic">Henüz bir plan yüklenmemiş.</p>
|
</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">
|
||||||
<div className="space-y-4">
|
Revizyonlar
|
||||||
{sections.map((section: any) => (
|
{pendingRevisions > 0 && (
|
||||||
<div key={section.id} className="space-y-2">
|
<Badge variant="secondary" className="px-1.5 py-0 min-w-5 h-5 flex items-center justify-center">{pendingRevisions}</Badge>
|
||||||
<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' ? 'Milestone' : section.type === 'deliverable' ? 'Teslimat' : 'Not'}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-muted-foreground whitespace-pre-wrap">{section.content}</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</TabsTrigger>
|
||||||
</Card>
|
</TabsList>
|
||||||
|
|
||||||
{/* Revisions */}
|
<TabsContent value="overview" className="mt-0">
|
||||||
{revisions.length > 0 && (
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<Card>
|
|
||||||
<CardContent className="p-5 space-y-6">
|
|
||||||
<h3 className="font-semibold text-lg border-b border-border pb-2">Revizyon Talepleriniz</h3>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{revisions.map((rev: any) => (
|
|
||||||
<div key={rev.id} className="p-4 rounded-md border border-border bg-muted/20">
|
|
||||||
<div className="flex justify-between items-start mb-2">
|
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
||||||
<Clock className="h-3 w-3" />
|
|
||||||
{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'
|
|
||||||
}>
|
|
||||||
{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">{rev.description}</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Sidebar */}
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="p-5 space-y-4">
|
<CardContent className="p-5 space-y-4">
|
||||||
<h3 className="font-semibold">İlerleme Durumu</h3>
|
<h3 className="font-semibold">İlerleme Durumu</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between text-2xl font-bold">
|
<div className="flex items-center justify-between text-3xl font-bold">
|
||||||
<span>%{project.progress}</span>
|
<span>%{project.progress}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-3 w-full bg-secondary rounded-full overflow-hidden">
|
<div className="h-3 w-full bg-secondary rounded-full overflow-hidden">
|
||||||
@@ -156,6 +117,7 @@ export function PortalProjectClient({ project, sections, tasks, revisions, clien
|
|||||||
style={{ width: `${project.progress}%` }}
|
style={{ width: `${project.progress}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground">Projenizin anlık tamamlanma oranı.</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -163,25 +125,25 @@ export function PortalProjectClient({ project, sections, tasks, revisions, clien
|
|||||||
<Card>
|
<Card>
|
||||||
<CardContent className="p-5 space-y-4">
|
<CardContent className="p-5 space-y-4">
|
||||||
<h3 className="font-semibold flex items-center gap-2">
|
<h3 className="font-semibold flex items-center gap-2">
|
||||||
<CheckCircle2 className="h-4 w-4" /> Tamamlanan İşler
|
<CheckCircle2 className="h-4 w-4" /> Yapılan İşler
|
||||||
</h3>
|
</h3>
|
||||||
{tasks.length === 0 ? (
|
{tasks.length === 0 ? (
|
||||||
<p className="text-sm text-muted-foreground italic">Listelenecek açık görev bulunmuyor.</p>
|
<p className="text-sm text-muted-foreground italic">Listelenecek görev bulunmuyor.</p>
|
||||||
) : (
|
) : (
|
||||||
<ul className="space-y-3">
|
<ul className="space-y-3 max-h-60 overflow-y-auto tiny-scrollbar pr-2">
|
||||||
{tasks.map((task: any) => (
|
{tasks.map((task: any) => (
|
||||||
<li key={task.id} className="text-sm flex gap-2">
|
<li key={task.id} className="text-sm flex gap-3 p-2 rounded hover:bg-muted/30 transition-colors">
|
||||||
{task.status === 'completed' ? (
|
{task.status === 'completed' || task.status === 'done' ? (
|
||||||
<CheckCircle2 className="h-4 w-4 text-emerald-500 shrink-0 mt-0.5" />
|
<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 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>
|
<div>
|
||||||
<span className={task.status === 'completed' ? "line-through text-muted-foreground" : "text-foreground"}>
|
<span className={task.status === 'completed' || task.status === 'done' ? "text-muted-foreground" : "text-foreground font-medium"}>
|
||||||
{task.title}
|
{task.title}
|
||||||
</span>
|
</span>
|
||||||
{task.date && (
|
{task.date && (
|
||||||
<div className="text-xs text-muted-foreground mt-0.5">
|
<div className="text-xs text-muted-foreground mt-1">
|
||||||
{format(new Date(task.date), 'd MMM yyyy', { locale: tr })}
|
{format(new Date(task.date), 'd MMM yyyy', { locale: tr })}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -193,7 +155,74 @@ export function PortalProjectClient({ project, sections, tasks, revisions, clien
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</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>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { signOut } from "@/app/login/actions";
|
import { signOut } from "@/app/login/actions";
|
||||||
import { Button } from "poyraz-ui/atoms";
|
import { Button, Card, CardContent } from "poyraz-ui/atoms";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -120,39 +120,7 @@ function AppSidebar({
|
|||||||
variant="bordered"
|
variant="bordered"
|
||||||
className="flex h-dvh max-h-dvh flex-col overflow-hidden rounded-none border-0"
|
className="flex h-dvh max-h-dvh flex-col overflow-hidden rounded-none border-0"
|
||||||
>
|
>
|
||||||
<SidebarHeader className="shrink-0 border-b-0 px-4 py-6">
|
<SidebarContent className="tiny-scrollbar min-h-0 flex-1 overflow-y-auto px-6 py-6 mt-4">
|
||||||
<div className="flex flex-col gap-3 rounded-xl bg-primary/5 border border-primary/10 p-4 relative overflow-hidden">
|
|
||||||
<div className="flex items-center gap-2 relative z-10">
|
|
||||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary/20 text-primary">
|
|
||||||
<Activity className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">Genel Durum</span>
|
|
||||||
<span className="text-sm font-semibold text-foreground">Proje İlerlemesi</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1.5 mt-1 relative z-10">
|
|
||||||
<div className="flex items-center justify-between text-xs font-medium">
|
|
||||||
<span className="text-primary">%{progress} Tamamlandı</span>
|
|
||||||
</div>
|
|
||||||
<div className="h-2 w-full bg-primary/10 rounded-full overflow-hidden">
|
|
||||||
<div
|
|
||||||
className="h-full bg-primary transition-all duration-700 ease-out relative"
|
|
||||||
style={{ width: `${progress}%` }}
|
|
||||||
>
|
|
||||||
<div className="absolute top-0 right-0 bottom-0 left-0 bg-white/20 animate-pulse" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="absolute -right-4 -top-4 w-16 h-16 bg-primary/5 rounded-full blur-xl" />
|
|
||||||
</div>
|
|
||||||
</SidebarHeader>
|
|
||||||
|
|
||||||
<SidebarSeparator className="mx-0 my-0 w-full" />
|
|
||||||
|
|
||||||
<SidebarContent className="tiny-scrollbar min-h-0 flex-1 overflow-y-auto px-6 py-6">
|
|
||||||
{portalSidebarData.map((group, groupIndex) => (
|
{portalSidebarData.map((group, groupIndex) => (
|
||||||
<div key={group.title}>
|
<div key={group.title}>
|
||||||
<SidebarSection title={group.title} defaultOpen className="mb-0">
|
<SidebarSection title={group.title} defaultOpen className="mb-0">
|
||||||
@@ -194,7 +162,27 @@ function AppSidebar({
|
|||||||
|
|
||||||
<SidebarSeparator className="mx-0 my-0 w-full" />
|
<SidebarSeparator className="mx-0 my-0 w-full" />
|
||||||
|
|
||||||
<SidebarFooter className="shrink-0 border-t-0 px-6 py-5">
|
<SidebarFooter className="shrink-0 border-t-0 px-6 py-5 flex flex-col gap-5">
|
||||||
|
<Card className="bg-primary/5 border-primary/10 shadow-none overflow-hidden relative">
|
||||||
|
<CardContent className="p-4 space-y-3">
|
||||||
|
<div className="text-sm font-semibold text-foreground relative z-10">Proje İlerlemesi</div>
|
||||||
|
<div className="space-y-1.5 mt-1 relative z-10">
|
||||||
|
<div className="flex items-center justify-between text-xs font-medium">
|
||||||
|
<span className="text-primary">%{progress} Tamamlandı</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-2 w-full bg-primary/10 rounded-full overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="h-full bg-primary transition-all duration-700 ease-out relative"
|
||||||
|
style={{ width: `${progress}%` }}
|
||||||
|
>
|
||||||
|
<div className="absolute top-0 right-0 bottom-0 left-0 bg-white/20 animate-pulse" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute -right-4 -top-4 w-16 h-16 bg-primary/5 rounded-full blur-xl" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user