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

144 lines
3.8 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 { IProjectCardProps } from "./ProjectCard.d"
import type { JSX } from "react"
import { Image as ImageIcon, MoreHorizontal } from "lucide-react"
import { FunctionComponent } from "react"
import cs from "classnames"
import { formatRelativeTime } from "@shared/lib/dates"
import { Card } from "@shared/ui/Card"
import { CircularProgress } from "@shared/ui/CircularProgress"
import {
Dropdown,
DropdownContent,
DropdownItem,
DropdownTrigger,
} from "@shared/ui/Dropdown"
import styles from "./ProjectCard.module.scss"
export const ProjectCard: FunctionComponent<IProjectCardProps> = ({
project,
className,
progress = 0,
currentAction,
imageUrl,
onClick,
onEdit,
onRename,
onDelete,
}): JSX.Element => {
const { name, updated_at, status } = project
const temporaryStatuses = new Set(["PROCESSING", "RENDERING", "UPLOADING"])
const isCompleted = status === "DONE"
const isProcessing = temporaryStatuses.has(status)
const isDraft = status === "DRAFT"
const isFailed = status === "FAILED"
const shouldShowProgress = isProcessing
const getStatusClass = () => {
if (isCompleted) return styles.statusGenerated
if (isProcessing) return styles.statusProcessing
if (isDraft) return styles.statusDraft
if (isFailed) return styles.statusFailed
return styles.statusDraft
}
const getStatusLabel = () => {
if (isCompleted) return "Завершено"
if (isProcessing) return "В процессе"
if (isDraft) return "Черновик"
if (isFailed) return "Ошибка"
return status
}
const displayAction = currentAction || (isProcessing ? "В процессе" : "")
return (
<Card className={cs(styles.root, className)} onClick={onClick}>
<div className={styles.hero}>
{imageUrl ? (
<img src={imageUrl} alt={name} loading="lazy" />
) : (
<div
className={styles.placeholder}
data-color-index={name.charCodeAt(0) % 4}
>
<ImageIcon />
</div>
)}
<div className={styles.statusBadge}>
<div className={cs(styles.status, getStatusClass())}>
<span className={styles.statusDot} />
{getStatusLabel()}
</div>
</div>
{shouldShowProgress && (
<div className={styles.progressOverlay}>
<div className={styles.progressCircle}>
<CircularProgress
percentage={Math.min(100, Math.max(0, progress))}
color="var(--purple-500)"
bgClassName={styles.progressBg}
valueClassName={styles.progressValue}
/>
<span className={styles.percentage}>{Math.round(progress)}%</span>
</div>
{displayAction && (
<span className={styles.actionName} title={displayAction}>
{displayAction}
</span>
)}
</div>
)}
</div>
<div className={styles.content}>
<div className={styles.info}>
<div className={styles.infoHeader}>
<h3 className={styles.title} title={name}>
{name}
</h3>
<div
className={styles.menuTrigger}
onClick={(e) => e.stopPropagation()}
>
<Dropdown>
<DropdownTrigger asChild>
<button type="button" aria-label="Действия проекта">
<MoreHorizontal size={16} />
</button>
</DropdownTrigger>
<DropdownContent align="end">
<DropdownItem onSelect={() => onEdit?.()}>
Изменить
</DropdownItem>
<DropdownItem onSelect={() => onRename?.()}>
Переименовать
</DropdownItem>
<DropdownItem
className="text-red-500"
onSelect={() => onDelete?.()}
>
Удалить
</DropdownItem>
</DropdownContent>
</Dropdown>
</div>
</div>
<span className={styles.date}>
Создано {formatRelativeTime(updated_at)}
</span>
</div>
</div>
</Card>
)
}