TorbatYar/frontend/modules/workspace/features/projects.tsx
Mortezakoohjani a483b47369 feat(workspace): complete production workspace and content operations
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-12 11:54:17 +03:30

119 lines
5.4 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 Link from "next/link";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { Button, Card, CardContent, Dialog, Input, PageHeader, Select, Textarea } from "@/components/ds";
import { useCurrentHub } from "@/modules/workspace/hooks/useCurrentHub";
import { workspaceApi } from "@/modules/workspace/services/workspace-api";
import { PRIORITY, labelOf } from "@/modules/workspace/labels";
import { StatusChip } from "@/modules/workspace/components/StatusChip";
import { WorkspaceEmptyState, WorkspacePageError, WorkspacePageLoader } from "@/modules/workspace/pages/shared";
export const WorkspaceProjects = function WorkspaceProjectsPage() {
const queryClient = useQueryClient();
const { tenantId, hubId, loading } = useCurrentHub();
const [open, setOpen] = useState(false);
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [teamId, setTeamId] = useState("");
const [priority, setPriority] = useState("medium");
const projects = useQuery({
queryKey: ["workspace", tenantId, "projects", hubId],
queryFn: () => workspaceApi.projects.list(tenantId!, { work_hub_id: hubId || undefined }),
enabled: !!tenantId,
});
const teams = useQuery({
queryKey: ["workspace", tenantId, "teams", hubId],
queryFn: () => workspaceApi.teams.list(tenantId!, hubId || undefined),
enabled: !!tenantId && !!hubId,
});
const create = useMutation({
mutationFn: () =>
workspaceApi.projects.create(tenantId!, {
work_hub_id: hubId,
team_id: teamId || undefined,
name,
description,
priority,
status: "planning",
}),
onSuccess: () => {
toast.success("پروژه ایجاد شد");
setOpen(false);
setName("");
setDescription("");
void queryClient.invalidateQueries({ queryKey: ["workspace", tenantId, "projects"] });
},
onError: (err) => toast.error(err instanceof Error ? err.message : "ایجاد پروژه ناموفق بود"),
});
if (!tenantId || loading || projects.isLoading) return <WorkspacePageLoader />;
if (!hubId) return <WorkspaceEmptyState title="مرکز کار لازم است" description="ابتدا یک مرکز کار انتخاب کنید." />;
if (projects.error) return <WorkspacePageError error={projects.error} onRetry={() => projects.refetch()} />;
return (
<div className="space-y-6">
<PageHeader
title="پروژه‌ها"
description="پروژه‌های مرکز کار فعال را بسازید و پیشرفت را دنبال کنید."
actions={<Button onClick={() => setOpen(true)}>پروژه جدید</Button>}
/>
{(projects.data ?? []).length === 0 ? (
<WorkspaceEmptyState
title="پروژه‌ای نیست"
description="اولین پروژه را برای وظایف و محتوا ایجاد کنید."
action={<Button onClick={() => setOpen(true)}>ایجاد پروژه</Button>}
/>
) : (
<div className="grid gap-4 md:grid-cols-2">
{(projects.data ?? []).map((project) => (
<Card key={String(project.id)}>
<CardContent className="space-y-3 p-5">
<div className="flex items-start justify-between gap-2">
<h2 className="font-semibold text-secondary">{String(project.name || "پروژه")}</h2>
<StatusChip value={String(project.status || "")} kind="project" />
</div>
<p className="text-sm text-[var(--muted)]">{String(project.description || "بدون توضیح")}</p>
<p className="text-xs text-[var(--muted)]">اولویت: {labelOf(PRIORITY, String(project.priority || ""))}</p>
<Link href={`/workspace/projects/${project.id}`}>
<Button variant="outline" className="w-full">
جزئیات پروژه
</Button>
</Link>
</CardContent>
</Card>
))}
</div>
)}
<Dialog open={open} onClose={() => setOpen(false)} title="پروژه جدید">
<div className="space-y-3">
<Input value={name} onChange={(event) => setName(event.target.value)} placeholder="نام پروژه" />
<Textarea value={description} onChange={(event) => setDescription(event.target.value)} placeholder="توضیح" />
<Select value={teamId} onChange={(event) => setTeamId(event.target.value)}>
<option value="">بدون تیم</option>
{(teams.data ?? []).map((team) => (
<option key={String(team.id)} value={String(team.id)}>
{String(team.name)}
</option>
))}
</Select>
<Select value={priority} onChange={(event) => setPriority(event.target.value)}>
{Object.entries(PRIORITY).map(([value, label]) => (
<option key={value} value={value}>
{label}
</option>
))}
</Select>
<Button className="w-full" disabled={!name.trim() || create.isPending} onClick={() => create.mutate()}>
ایجاد
</Button>
</div>
</Dialog>
</div>
);
};
export default WorkspaceProjects;