feat: implement portal shell layout with user authentication, sidebar navigation, and progress tracking

This commit is contained in:
Poyraz Avsever
2026-06-08 13:56:06 +03:00
parent 47b9f728e6
commit d41267c29b
17 changed files with 582 additions and 130 deletions
+5 -1
View File
@@ -20,6 +20,8 @@ type ProjectRow = {
budget_amount: number | string | null;
currency: string;
progress: number;
progress_type: "manual" | "auto" | null;
revision_quota: number | null;
cover_image_path: string | null;
cover_image_alt: string | null;
clients: { name: string } | { name: string }[] | null;
@@ -66,7 +68,7 @@ export default async function ProjectDetailPage({
supabase
.from("projects")
.select(
"id, client_id, name, type, description, status, start_date, due_date, budget_amount, currency, progress, cover_image_path, cover_image_alt, clients(name)",
"id, client_id, name, type, description, status, start_date, due_date, budget_amount, currency, progress, progress_type, revision_quota, cover_image_path, cover_image_alt, clients(name)",
)
.eq("id", id)
.eq("user_id", user.id)
@@ -120,6 +122,8 @@ export default async function ProjectDetailPage({
projectData.budget_amount === null ? null : Number(projectData.budget_amount),
currency: projectData.currency,
progress: Number(projectData.progress || 0),
progress_type: projectData.progress_type === "auto" ? "auto" : "manual",
revision_quota: Number(projectData.revision_quota || 0),
cover_image_alt: projectData.cover_image_alt,
coverImageUrl,
};
@@ -38,6 +38,7 @@ import {
Palette,
Pencil,
Plus,
Settings2,
Target,
Trash2,
Wallet,
@@ -58,6 +59,8 @@ export type ProjectDetail = {
budget_amount: number | null;
currency: string;
progress: number;
progress_type: "manual" | "auto";
revision_quota: number;
cover_image_alt: string | null;
coverImageUrl: string | null;
};
@@ -215,6 +218,7 @@ export function ProjectDetailClient({
</div>
<div className="flex gap-2">
<ProjectSettingsDialog project={project} />
<SectionDialog projectId={project.id} mode="create" defaultCategory="overview" />
{project.status !== "completed" ? (
<form action={completeProjectRecord}>
@@ -806,6 +810,101 @@ function ProjectTaskKanban({
);
}
function ProjectSettingsDialog({ project }: { project: ProjectDetail }) {
const [open, setOpen] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [progressType, setProgressType] = useState<"manual" | "auto">(project.progress_type);
const [progress, setProgress] = useState(project.progress);
const [revisionQuota, setRevisionQuota] = useState(project.revision_quota);
async function handleSubmit() {
setIsSubmitting(true);
try {
const { updateProjectSettings } = await import("@/app/(dashboard)/projects/actions");
await updateProjectSettings(project.id, progressType, progress, revisionQuota);
setOpen(false);
} catch (error) {
console.error(error);
} finally {
setIsSubmitting(false);
}
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline" className="h-9 gap-2 px-3">
<Settings2 className="h-4 w-4" />
<span className="hidden sm:inline">Ayarlar</span>
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<form action={handleSubmit} className="space-y-5">
<DialogHeader>
<DialogTitle>Proje ayarları</DialogTitle>
<DialogDescription>
İlerleme hesaplama yöntemi ve revizyon kotasını belirle.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<div className="grid gap-2">
<Label>İlerleme Hesaplama</Label>
<Select value={progressType} onValueChange={(val: "manual" | "auto") => setProgressType(val)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="manual">Manuel (Elle girilir)</SelectItem>
<SelectItem value="auto">Otomatik (Görevlere göre)</SelectItem>
</SelectContent>
</Select>
</div>
{progressType === "manual" && (
<div className="grid gap-2">
<Label>İlerleme Durumu (%)</Label>
<div className="flex items-center gap-4">
<input
type="range"
min="0"
max="100"
value={progress}
onChange={(e) => setProgress(Number(e.target.value))}
className="flex-1 accent-primary"
/>
<span className="w-12 text-right text-sm font-medium">{progress}%</span>
</div>
</div>
)}
{progressType === "auto" && (
<p className="text-xs text-muted-foreground">İlerleme yüzdesi "Görevler" sekmesindeki görevlerin tamamlanma durumuna göre otomatik hesaplanacaktır.</p>
)}
<div className="grid gap-2">
<Label>Müşteri Revizyon Kotası</Label>
<Input
type="number"
min="0"
value={revisionQuota}
onChange={(e) => setRevisionQuota(Number(e.target.value))}
/>
<p className="text-xs text-muted-foreground">Müşterinin portal üzerinden talep edebileceği toplam revizyon hakkı.</p>
</div>
</div>
<DialogFooter>
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Kaydediliyor..." : "Kaydet"}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}
function ProjectTaskDialog({
projectId,
clientId,