All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Wrap 7 list-item/card components with React.memo to prevent unnecessary re-renders when parent components update but props remain unchanged: - TaskItem (task lists) - EventCard (calendar views) - EntryCard (knowledge base) - WorkspaceCard (workspace list) - TeamCard (team list) - DomainItem (domain list) - ConnectionCard (federation connections) All are pure components rendered inside .map() loops that depend solely on their props for rendering output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
66 lines
2.2 KiB
TypeScript
66 lines
2.2 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
import React from "react";
|
|
import type { Task } from "@mosaic/shared";
|
|
import { TaskStatus, TaskPriority } from "@mosaic/shared";
|
|
import { formatDate, isPastTarget, isApproachingTarget } from "@/lib/utils/date-format";
|
|
|
|
interface TaskItemProps {
|
|
task: Task;
|
|
}
|
|
|
|
const statusIcons: Record<TaskStatus, string> = {
|
|
[TaskStatus.NOT_STARTED]: "⚪",
|
|
[TaskStatus.IN_PROGRESS]: "🟢",
|
|
[TaskStatus.PAUSED]: "⏸️",
|
|
[TaskStatus.COMPLETED]: "✅",
|
|
[TaskStatus.ARCHIVED]: "💤",
|
|
};
|
|
|
|
const priorityLabels: Record<TaskPriority, string> = {
|
|
[TaskPriority.HIGH]: "High priority",
|
|
[TaskPriority.MEDIUM]: "Medium priority",
|
|
[TaskPriority.LOW]: "Low priority",
|
|
};
|
|
|
|
export const TaskItem = React.memo(function TaskItem({ task }: TaskItemProps): React.JSX.Element {
|
|
const statusIcon = statusIcons[task.status];
|
|
const priorityLabel = priorityLabels[task.priority];
|
|
|
|
// PDA-friendly date status
|
|
let dateStatus = "";
|
|
if (task.dueDate) {
|
|
if (isPastTarget(task.dueDate)) {
|
|
dateStatus = "Target passed";
|
|
} else if (isApproachingTarget(task.dueDate)) {
|
|
dateStatus = "Approaching target";
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="bg-white p-4 rounded-lg shadow-sm border border-gray-200 hover:shadow-md transition-shadow">
|
|
<div className="flex items-start gap-3">
|
|
<span className="text-xl flex-shrink-0" aria-label={`Status: ${task.status}`}>
|
|
{statusIcon}
|
|
</span>
|
|
<div className="flex-1 min-w-0">
|
|
<h3 className="font-semibold text-gray-900 mb-1">{task.title}</h3>
|
|
{task.description && <p className="text-sm text-gray-600 mb-2">{task.description}</p>}
|
|
<div className="flex flex-wrap items-center gap-2 text-xs">
|
|
{task.priority && (
|
|
<span className="px-2 py-1 bg-blue-100 text-blue-700 rounded-full">
|
|
{priorityLabel}
|
|
</span>
|
|
)}
|
|
{task.dueDate && <span className="text-gray-500">{formatDate(task.dueDate)}</span>}
|
|
{dateStatus && (
|
|
<span className="px-2 py-1 bg-amber-100 text-amber-700 rounded-full">
|
|
{dateStatus}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
});
|