mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-27 21:33:41 +02:00
- 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>
65 lines
2.0 KiB
TypeScript
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>
|
|
);
|
|
});
|