import Link from "next/link"; import { createPortal } from "react-dom"; import type { CSSProperties } from "react"; import { useState } from "react"; import TaskReadOnlyPreviewModal from "@/components/tasks/TaskReadOnlyPreviewModal"; import type { MessageLinkedTask, TaskPriority } from "@/types/api"; type TaskLinkPillTask = Pick< MessageLinkedTask, | "id" | "title" | "board_id" | "board_name" | "column_color" | "priority" | "priority_display" | "can_open" > & { link_id?: number }; type TaskLinkPillProps = { task: TaskLinkPillTask; tone?: "default" | "onAccent"; maxTitleClassName?: string; className?: string; }; const fallbackColumnColor = "#38bdf8"; const priorityColors: Record = { low: "#22c55e", medium: "#38bdf8", high: "#f59e0b", critical: "#ef4444", }; const priorityLabels: Record = { low: "Низкий", medium: "Средний", high: "Высокий", critical: "Критический", }; const priorityTitleShiftClasses: Record = { low: "group-hover/task-pill:translate-x-10 group-focus-visible/task-pill:translate-x-10", medium: "group-hover/task-pill:translate-x-12 group-focus-visible/task-pill:translate-x-12", high: "group-hover/task-pill:translate-x-12 group-focus-visible/task-pill:translate-x-12", critical: "group-hover/task-pill:translate-x-16 group-focus-visible/task-pill:translate-x-16", }; function normalizeHexColor(value?: string | null): string { const color = String(value || "").trim(); if (/^#[0-9a-f]{6}$/i.test(color)) return color; if (/^#[0-9a-f]{3}$/i.test(color)) { const [, r, g, b] = color; return `#${r}${r}${g}${g}${b}${b}`; } return fallbackColumnColor; } function hexToRgb(color: string): { red: number; green: number; blue: number } { const normalized = normalizeHexColor(color).slice(1); return { red: parseInt(normalized.slice(0, 2), 16), green: parseInt(normalized.slice(2, 4), 16), blue: parseInt(normalized.slice(4, 6), 16), }; } function getReadableTextColor(color: string): string { const { red, green, blue } = hexToRgb(color); const luminance = (0.299 * red + 0.587 * green + 0.114 * blue) / 255; return luminance > 0.62 ? "#07111f" : "#ffffff"; } export default function TaskLinkPill({ task, tone = "default", maxTitleClassName = "max-w-44", className = "", }: TaskLinkPillProps) { const [previewOpen, setPreviewOpen] = useState(false); const columnColor = normalizeHexColor(task.column_color); const priority = task.priority || "medium"; const priorityColor = priorityColors[priority]; const priorityLabel = task.priority_display || priorityLabels[priority]; const priorityTitleShiftClass = priorityTitleShiftClasses[priority]; const textColor = getReadableTextColor(columnColor); const isOnAccent = tone === "onAccent"; const title = `${task.board_name ? `${task.board_name}: ` : ""}${task.title}, ${priorityLabel}`; const style: CSSProperties = { backgroundColor: columnColor, boxShadow: isOnAccent ? "inset 0 1px 0 rgba(255, 255, 255, 0.18)" : undefined, color: textColor, }; const href = `/tasks?board=${task.board_id}&task=${task.id}`; const canOpen = task.can_open !== false; const pillClassName = `group/task-pill inline-flex min-w-0 max-w-full items-center gap-1.5 overflow-hidden rounded-full px-2 py-1 text-[11px] font-medium shadow-sm transition ${ canOpen ? "hover:brightness-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/45" : "cursor-pointer hover:brightness-105 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/45" } ${className}`; const content = ( <> {task.title} ); if (!canOpen) { return ( <> {task.link_id && previewOpen && typeof document !== "undefined" ? createPortal( setPreviewOpen(false)} />, document.body, ) : null} ); } return ( {content} ); }