diff --git a/src/App.tsx b/src/App.tsx index fd675cb..97c7175 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,7 +21,7 @@ function App() { } /> - diff --git a/src/components/ChatPanel.tsx b/src/components/ChatPanel.tsx index b466dcc..ad35e80 100644 --- a/src/components/ChatPanel.tsx +++ b/src/components/ChatPanel.tsx @@ -38,6 +38,7 @@ function isImageMime(mime: string | null): boolean { export default function ChatPanel({ chatId, projectId }: Props) { const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); + const mentionMapRef = useRef>(new Map()); // slug → member_id const [sending, setSending] = useState(false); const [loadingOlder, setLoadingOlder] = useState(false); const [hasMore, setHasMore] = useState(true); @@ -197,11 +198,15 @@ export default function ChatPanel({ chatId, projectId }: Props) { })); setInput(""); setPendingFiles([]); + mentionMapRef.current.clear(); setSending(true); try { - // Extract @mentions from text + // Extract @mentions from text → resolve to member IDs const mentionMatches = text.match(/@(\w+)/g); - const mentions = mentionMatches ? [...new Set(mentionMatches.map(m => m.slice(1)))] : []; + const mentionSlugs = mentionMatches ? [...new Set(mentionMatches.map(m => m.slice(1)))] : []; + const mentions = mentionSlugs + .map(slug => mentionMapRef.current.get(slug)) + .filter((id): id is string => !!id); const msg = await sendMessage({ chat_id: chatId, @@ -422,6 +427,7 @@ export default function ChatPanel({ chatId, projectId }: Props) { value={input} onChange={setInput} onSubmit={handleSend} + onMentionInsert={(id, slug) => mentionMapRef.current.set(slug, id)} placeholder="Сообщение... (@mention)" disabled={sending} /> diff --git a/src/components/MentionInput.tsx b/src/components/MentionInput.tsx index 6875ed1..a8da63c 100644 --- a/src/components/MentionInput.tsx +++ b/src/components/MentionInput.tsx @@ -7,11 +7,12 @@ interface Props { value: string; onChange: (value: string) => void; onSubmit: () => void; + onMentionInsert?: (memberId: string, slug: string) => void; placeholder?: string; disabled?: boolean; } -export default function MentionInput({ projectId, value, onChange, onSubmit, placeholder, disabled }: Props) { +export default function MentionInput({ projectId, value, onChange, onSubmit, onMentionInsert, placeholder, disabled }: Props) { const [members, setMembers] = useState([]); const [showDropdown, setShowDropdown] = useState(false); const [filter, setFilter] = useState(""); @@ -37,6 +38,7 @@ export default function MentionInput({ projectId, value, onChange, onSubmit, pla const after = value.slice(inputRef.current?.selectionStart ?? value.length); const newValue = `${before}@${member.slug} ${after}`; onChange(newValue); + onMentionInsert?.(member.id, member.slug); setShowDropdown(false); setMentionStart(-1); // Focus and set cursor after mention diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 6c4e05c..b0ff608 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -6,10 +6,10 @@ import { logout } from "@/lib/auth-client"; interface Props { projects: Project[]; - activeSlug?: string; + activeId?: string; } -export default function Sidebar({ projects, activeSlug }: Props) { +export default function Sidebar({ projects, activeId }: Props) { const [open, setOpen] = useState(false); const nav = ( @@ -42,10 +42,10 @@ export default function Sidebar({ projects, activeSlug }: Props) { {projects.map((p) => ( setOpen(false)} className={`block px-3 py-2 rounded text-sm transition-colors ${ - activeSlug === p.slug + activeId === p.id ? "bg-[var(--accent)]/10 text-[var(--accent)]" : "hover:bg-white/5 text-[var(--fg)]" }`} diff --git a/src/lib/api.ts b/src/lib/api.ts index 4f73b70..bff4b10 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -9,12 +9,12 @@ function getToken(): string | null { return localStorage.getItem("tb_token"); } -export function getCurrentSlug(): string { +export function getCurrentMemberId(): string { const token = getToken(); if (!token) return "unknown"; try { const payload = JSON.parse(atob(token.split(".")[1])); - return payload.slug || payload.sub || "unknown"; + return payload.sub || "unknown"; } catch { return "unknown"; } diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index eb9aa0c..33cb88c 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -27,5 +27,5 @@ export default function HomePage() { } // Redirect to first project - return ; + return ; } \ No newline at end of file diff --git a/src/pages/ProjectPage.tsx b/src/pages/ProjectPage.tsx index edf1f49..c5f017f 100644 --- a/src/pages/ProjectPage.tsx +++ b/src/pages/ProjectPage.tsx @@ -16,7 +16,7 @@ const TABS = [ ]; export default function ProjectPage() { - const { slug } = useParams<{ slug: string }>(); + const { id: projectId } = useParams<{ id: string }>(); const [searchParams, setSearchParams] = useSearchParams(); const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(true); @@ -36,7 +36,7 @@ export default function ProjectPage() { .finally(() => setLoading(false)); }, []); - const project = projects.find((p) => p.slug === slug); + const project = projects.find((p) => p.id === projectId); const handleProjectUpdated = (updated: Project) => { setProjects((prev) => prev.map((p) => (p.id === updated.id ? updated : p))); @@ -52,7 +52,7 @@ export default function ProjectPage() { return (
- +