Files
main_frontend/src/pages/ProjectsPage/ProjectsPage.tsx
T
2026-04-04 14:51:40 +03:00

167 lines
4.3 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 type { components } from "@shared/api/__generated__/openapi.types"
import type { JSX } from "react"
import { PlusIcon } from "lucide-react"
import { FunctionComponent, useState } from "react"
import { useRouter } from "next/navigation"
import { ProjectCard } from "@entities/ProjectCard"
import { useBreadcrumbs } from "@shared/context/BreadcrumbsContext"
import {
CreateProjectModal,
DeleteProjectModal,
EditProjectModal,
RenameProjectModal,
} from "@features/project"
import api from "@shared/api"
import { useDebounce } from "@shared/hooks/useDebounce"
import { Button } from "@shared/ui"
import { ProjectCardSkeleton } from "@shared/ui/Skeleton"
import {
ProjectsHeader,
type ProjectStatusEnum,
} from "@widgets/Projects/ProjectsHeader"
import { IProjectsPageProps } from "./ProjectsPage.d"
import styles from "./ProjectsPage.module.scss"
type ProjectRead = components["schemas"]["ProjectRead"]
export const ProjectsPage: FunctionComponent<
IProjectsPageProps
> = (): JSX.Element => {
useBreadcrumbs([{ label: "Проекты", href: "/projects" }])
const router = useRouter()
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false)
const [editProject, setEditProject] = useState<ProjectRead | null>(null)
const [renameProject, setRenameProject] = useState<ProjectRead | null>(null)
const [deleteProject, setDeleteProject] = useState<ProjectRead | null>(null)
const [search, setSearch] = useState("")
const [statusFilter, setStatusFilter] = useState<ProjectStatusEnum>("")
const debouncedSearch = useDebounce(search, 300)
const {
data: projects,
isLoading: projectsLoading,
refetch: refetchProjects,
} = api.useQuery("get", "/api/projects/", {
params: {
query: {
search: debouncedSearch || undefined,
status: statusFilter || undefined,
},
},
})
return (
<div className={styles.root} data-testid="ProjectsPage">
<div className={styles.header}>
<div className={styles.titles}>
<h1 className={styles.title}>Мои проекты</h1>
<h4 className={styles.subtitle}>
Управляйте своими последними проектами
</h4>
</div>
<div>
<Button
variant="primary"
size="lg"
onClick={() => setIsCreateModalOpen(true)}
>
<PlusIcon /> Создать проект
</Button>
</div>
</div>
<CreateProjectModal
open={isCreateModalOpen}
onOpenChange={setIsCreateModalOpen}
onCreated={async () => {
await refetchProjects()
}}
/>
{editProject && (
<EditProjectModal
open
onOpenChange={(open) => {
if (!open) setEditProject(null)
}}
project={editProject}
onUpdated={async () => {
await refetchProjects()
}}
/>
)}
{renameProject && (
<RenameProjectModal
open
onOpenChange={(open) => {
if (!open) setRenameProject(null)
}}
project={renameProject}
onRenamed={async () => {
await refetchProjects()
}}
/>
)}
{deleteProject && (
<DeleteProjectModal
open
onOpenChange={(open) => {
if (!open) setDeleteProject(null)
}}
project={deleteProject}
onDeleted={async () => {
await refetchProjects()
}}
/>
)}
<ProjectsHeader
search={search}
onSearchChange={setSearch}
statusFilter={statusFilter}
onStatusFilterChange={setStatusFilter}
/>
<div className={styles.projectList}>
{projectsLoading
? Array.from({ length: 6 }).map((_, i) => (
<ProjectCardSkeleton key={i} />
))
: projects?.map((project) => (
<ProjectCard
key={project.id}
project={project}
progress={project.status === "PROCESSING" ? 45 : 0}
currentAction={
project.status === "PROCESSING" ? "Рендеринг" : undefined
}
onClick={() => router.push(`/projects/${project.id}`)}
onEdit={() => setEditProject(project)}
onRename={() => setRenameProject(project)}
onDelete={() => setDeleteProject(project)}
/>
))}
</div>
{!projectsLoading && projects?.length === 0 && (
<div className={styles.empty}>
<p className={styles.emptyText}>
{search || statusFilter
? "Проекты не найдены"
: "У вас пока нет проектов"}
</p>
</div>
)}
</div>
)
}