119 lines
5.4 KiB
TypeScript
119 lines
5.4 KiB
TypeScript
"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;
|