Files
multica/apps/web/features/issues/components/list-row.tsx
Naiyuan Qing a2e5cbd47b refactor(issues): extract hooks from issue-detail, eliminate dual source of truth
- Remove useState<Issue> mirror anti-pattern — read directly from useIssueStore
- handleUpdateField now writes to global store (board/list sync instantly)
- handleDelete now calls removeIssue (deleted issue disappears from list)
- Extract useIssueTimeline hook (comment CRUD + WS events + reconnect)
- Extract useIssueReactions hook (issue reactions + WS events)
- Extract useIssueSubscribers hook (subscribers + WS events + rollback)
- Add useWSReconnect hook for per-component reconnect handling
- Add React.memo to BoardCardContent, DraggableBoardCard, ListRow
- Add key={id} to RichTextEditor to fix stale description on issue switch
- issue-detail.tsx: 1330 → 979 lines

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 13:09:17 +08:00

65 lines
2.0 KiB
TypeScript

"use client";
import { memo } from "react";
import Link from "next/link";
import type { Issue } from "@/shared/types";
import { ActorAvatar } from "@/components/common/actor-avatar";
import { useIssueSelectionStore } from "@/features/issues/stores/selection-store";
import { PriorityIcon } from "./priority-icon";
function formatDate(date: string): string {
return new Date(date).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
});
}
export const ListRow = memo(function ListRow({ issue }: { issue: Issue }) {
const selected = useIssueSelectionStore((s) => s.selectedIds.has(issue.id));
const toggle = useIssueSelectionStore((s) => s.toggle);
return (
<div
className={`group/row flex h-9 items-center gap-2 px-4 text-sm transition-colors hover:bg-accent/50 ${
selected ? "bg-accent/30" : ""
}`}
>
<div className="relative flex shrink-0 items-center justify-center w-4 h-4">
<PriorityIcon
priority={issue.priority}
className={selected ? "hidden" : "group-hover/row:hidden"}
/>
<input
type="checkbox"
checked={selected}
onChange={() => toggle(issue.id)}
className={`absolute inset-0 cursor-pointer accent-primary ${
selected ? "" : "hidden group-hover/row:block"
}`}
/>
</div>
<Link
href={`/issues/${issue.id}`}
className="flex flex-1 items-center gap-2 min-w-0"
>
<span className="w-16 shrink-0 text-xs text-muted-foreground">
{issue.identifier}
</span>
<span className="min-w-0 flex-1 truncate">{issue.title}</span>
{issue.due_date && (
<span className="shrink-0 text-xs text-muted-foreground">
{formatDate(issue.due_date)}
</span>
)}
{issue.assignee_type && issue.assignee_id && (
<ActorAvatar
actorType={issue.assignee_type}
actorId={issue.assignee_id}
size={20}
/>
)}
</Link>
</div>
);
});