135 lines
4.6 KiB
JavaScript
135 lines
4.6 KiB
JavaScript
import { useState } from 'react'
|
||
import styles from '@/lib/styles'
|
||
import { cn } from '@/lib/utils'
|
||
import { X } from 'lucide-react'
|
||
|
||
|
||
export function TaskButton({ taskData }) {
|
||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||
|
||
const openModal = () => setIsModalOpen(true)
|
||
const closeModal = () => setIsModalOpen(false)
|
||
|
||
return (
|
||
<>
|
||
<div
|
||
key={taskData?.id || 1}
|
||
className={cn(
|
||
styles.card.task,
|
||
"w-[110px] cursor-pointer hover:opacity-80 transition-opacity"
|
||
)}
|
||
onClick={openModal}
|
||
>
|
||
{taskData?.title || "Sample task card"}
|
||
</div>
|
||
|
||
{isModalOpen && (
|
||
<div
|
||
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4"
|
||
onClick={closeModal}
|
||
>
|
||
<div
|
||
className="relative w-fit h-fit"
|
||
onClick={(e) => e.stopPropagation()}
|
||
>
|
||
{/* Кнопка закрытия */}
|
||
<button
|
||
onClick={closeModal}
|
||
className="absolute -top-4 -right-4 z-10 bg-red-500 hover:bg-red-600 text-white rounded-full p-2 transition-colors"
|
||
aria-label="Close modal"
|
||
>
|
||
<X size={20} />
|
||
</button>
|
||
|
||
<CardComponent
|
||
status={taskData?.status}
|
||
time_spent={taskData?.time_spent}
|
||
description={taskData?.description}
|
||
title={taskData?.title}
|
||
priority={taskData?.priority}
|
||
due_date={taskData?.due_date}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</>
|
||
)
|
||
}
|
||
|
||
export function CardComponent({
|
||
status = "todo",
|
||
time_spent = 0,
|
||
description = "No description",
|
||
title = "Task",
|
||
priority = "medium",
|
||
due_date = null
|
||
}) {
|
||
const formatTimeSpent = (minutes) => {
|
||
if (!minutes || minutes === 0) return "0 min";
|
||
const hours = Math.floor(minutes / 60);
|
||
const mins = minutes % 60;
|
||
if (hours > 0) {
|
||
return `${hours}h ${mins > 0 ? `${mins}m` : ''}`;
|
||
}
|
||
return `${mins}m`;
|
||
};
|
||
|
||
const formatDate = (dateString) => {
|
||
if (!dateString) return "No due date";
|
||
const date = new Date(dateString);
|
||
return date.toLocaleDateString('en-US', {
|
||
month: 'short',
|
||
day: 'numeric',
|
||
year: 'numeric'
|
||
});
|
||
};
|
||
|
||
const priorityBadgeStyle = {
|
||
low: styles.badge.low,
|
||
medium: styles.badge.medium,
|
||
high: styles.badge.high,
|
||
critical: styles.badge.critical,
|
||
};
|
||
|
||
const statusLabels = {
|
||
open: "Open",
|
||
closed: "Closed",
|
||
in_progress: "In Progress",
|
||
todo: "To Do"
|
||
};
|
||
|
||
return (
|
||
<div className={cn(styles.card.filled, "flex flex-col w-full min-w-[400px] max-w-[600px] h-[400px] overflow-hidden")}>
|
||
<div className="flex items-end flex-1 -mt-6 -mx-6 bg-center bg-cover bg-[url('/images/a.jpg')]">
|
||
<h1 className={cn(styles.text.h1, "pl-6")}>{title}</h1>
|
||
</div>
|
||
<div className="flex-1 flex flex-col gap-3 z-10 mt-6">
|
||
<div className="flex items-center gap-2 flex-wrap">
|
||
<span className={cn(priorityBadgeStyle[priority])}>
|
||
{priority.charAt(0).toUpperCase() + priority.slice(1)}
|
||
</span>
|
||
<span className={cn(styles.text.smallMuted)}>
|
||
Status: {statusLabels[status]}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-4 text-sm">
|
||
<span className={cn(styles.text.smallMuted)}>
|
||
⏱️ {formatTimeSpent(time_spent)}
|
||
</span>
|
||
<span className={cn(styles.text.smallMuted)}>
|
||
📅 {formatDate(due_date)}
|
||
</span>
|
||
</div>
|
||
|
||
<p className={cn(styles.text.smallMuted, "flex-1 line-clamp-3 overflow-auto")}>
|
||
{description}
|
||
</p>
|
||
|
||
<button className={cn(styles.button.primaryNeon, "w-[110px]")}>
|
||
<span className={styles.text.neonBlue}>Complete</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)
|
||
} |