Files
stack/apps/web/src/components/federation/FederatedTaskCard.tsx
Jason Woltje a8c8af21e5 fix(#92): use PDA-friendly language (Target instead of Due)
Critical PDA-friendly design compliance fix.

Changed forbidden "Due:" to approved "Target:" throughout FederatedTaskCard
component and tests, per DESIGN-PRINCIPLES.md requirements.

Changes:
- FederatedTaskCard.tsx: Changed "Due: {dueDate}" to "Target: {dueDate}"
- FederatedTaskCard.test.tsx: Updated all test expectations from "Due:" to "Target:"
- Updated test names to reflect "target date" terminology

All 11 tests passing.

This ensures full compliance with PDA-friendly language guidelines:
|  NEVER |  ALWAYS   |
| DUE      | Target date |

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-03 14:24:24 -06:00

114 lines
3.6 KiB
TypeScript

/**
* FederatedTaskCard Component
* Displays a task from a federated instance with provenance indicator
*/
import { TaskStatus, TaskPriority } from "@mosaic/shared";
import type { FederatedTask } from "./types";
import { ProvenanceIndicator } from "./ProvenanceIndicator";
interface FederatedTaskCardProps {
federatedTask: FederatedTask;
compact?: boolean;
onClick?: () => void;
}
/**
* Get PDA-friendly status text and color
*/
function getStatusDisplay(status: TaskStatus): { text: string; colorClass: string } {
switch (status) {
case TaskStatus.NOT_STARTED:
return { text: "Not Started", colorClass: "bg-gray-100 text-gray-700" };
case TaskStatus.IN_PROGRESS:
return { text: "In Progress", colorClass: "bg-blue-100 text-blue-700" };
case TaskStatus.COMPLETED:
return { text: "Completed", colorClass: "bg-green-100 text-green-700" };
case TaskStatus.PAUSED:
return { text: "Paused", colorClass: "bg-yellow-100 text-yellow-700" };
case TaskStatus.ARCHIVED:
return { text: "Archived", colorClass: "bg-gray-100 text-gray-600" };
default:
return { text: "Unknown", colorClass: "bg-gray-100 text-gray-700" };
}
}
/**
* Get priority text and color
*/
function getPriorityDisplay(priority: TaskPriority): { text: string; colorClass: string } {
switch (priority) {
case TaskPriority.LOW:
return { text: "Low", colorClass: "text-gray-600" };
case TaskPriority.MEDIUM:
return { text: "Medium", colorClass: "text-blue-600" };
case TaskPriority.HIGH:
return { text: "High", colorClass: "text-orange-600" };
default:
return { text: "Unknown", colorClass: "text-gray-600" };
}
}
/**
* Format date for display
*/
function formatDate(date: Date | null): string | null {
if (!date) {
return null;
}
return new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "short",
day: "numeric",
}).format(new Date(date));
}
export function FederatedTaskCard({
federatedTask,
compact = false,
onClick,
}: FederatedTaskCardProps): React.JSX.Element {
const { task, provenance } = federatedTask;
const status = getStatusDisplay(task.status);
const priority = getPriorityDisplay(task.priority);
const dueDate = formatDate(task.dueDate);
const paddingClass = compact ? "p-3" : "p-4";
const clickableClass = onClick ? "cursor-pointer hover:border-gray-300" : "";
return (
<div
className={`border border-gray-200 rounded-lg ${paddingClass} ${clickableClass} transition-colors`}
onClick={onClick}
>
{/* Header with title and provenance */}
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
<h3 className="text-base font-medium text-gray-900">{task.title}</h3>
{task.description && <p className="text-sm text-gray-600 mt-1">{task.description}</p>}
</div>
<ProvenanceIndicator
instanceId={provenance.instanceId}
instanceName={provenance.instanceName}
instanceUrl={provenance.instanceUrl}
compact={compact}
/>
</div>
{/* Metadata row */}
<div className="flex items-center gap-3 text-sm flex-wrap">
{/* Status badge */}
<span className={`px-2 py-1 rounded-full text-xs font-medium ${status.colorClass}`}>
{status.text}
</span>
{/* Priority */}
<span className={`text-xs font-medium ${priority.colorClass}`}>{priority.text}</span>
{/* Target date */}
{dueDate && <span className="text-xs text-gray-600">Target: {dueDate}</span>}
</div>
</div>
);
}