// Csoport chat — projekt-szintű üzenetváltás, file attach, "Kérdés" funkció (email triggerrel) const Chat = ({ user, onClose, onToast }) => { const [, force] = React.useReducer(x => x + 1, 0); const [text, setText] = React.useState(''); const [attachments, setAttachments] = React.useState([]); const [askUser, setAskUser] = React.useState(null); const [showAskPicker, setShowAskPicker] = React.useState(false); const fileRef = React.useRef(null); const scrollRef = React.useRef(null); const [reactionPickerFor, setReactionPickerFor] = React.useState(null); const REACTION_SET = [ { emoji: '👍', label: 'Like' }, { emoji: '👎', label: 'Dislike' }, { emoji: '😄', label: 'Happy' }, { emoji: '❤️', label: 'Love' }, { emoji: '😢', label: 'Cry' }, { emoji: '🫡', label: 'Salute' }, { emoji: '🙏', label: 'Thank' }, { emoji: '🚀', label: 'Rocket' }, ]; const toggleReaction = async (msg, emoji) => { // Optimistic local toggle so the UI feels instant. if (!msg.reactions) msg.reactions = {}; const list = msg.reactions[emoji] || []; const idx = list.indexOf(user.id); if (idx >= 0) { list.splice(idx, 1); if (list.length === 0) delete msg.reactions[emoji]; else msg.reactions[emoji] = list; } else { msg.reactions[emoji] = [...list, user.id]; } setReactionPickerFor(null); force(); try { await window.api.messages.reaction({ message_id: msg.id, emoji }); await window.refreshData(); } catch (err) { onToast(window.apiErrorMessage(err)); } }; // Auto-scroll a végére React.useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [window.MESSAGES.length]); // Olvasott jelölés: amikor megnyílik, kérjük a szerveren is, hogy // jegyezze meg a jelenlegi időpontot (törli az unread badge-et). React.useEffect(() => { window.CHAT_LAST_READ = new Date().toISOString(); (async () => { try { await window.api.messages.read(); } catch (e) { /* offline ok */ } })(); return () => { window.CHAT_LAST_READ = new Date().toISOString(); }; }, []); const projectMembers = window.USERS; const sendMessage = async () => { if (!text.trim() && attachments.length === 0) return; const body = text.trim(); const askedId = askUser ? askUser.id : null; const askedEmail = askUser?.email; const files = attachments.map(a => a._file).filter(Boolean); // Optimistic local push so the bubble shows up immediately. const optimistic = { id: (window.MESSAGES.length ? Math.max(...window.MESSAGES.map(m => m.id)) : 0) + 1000000, authorId: user.id, body, createdAt: new Date().toISOString(), askedUserId: askedId, attachments: attachments.map(a => ({ name: a.name, type: a.type, size: a.size, url: a.url })), reactions: {}, _pending: true, }; window.MESSAGES.push(optimistic); setText(''); setAttachments([]); setAskUser(null); force(); try { const fd = new FormData(); fd.append('body', body); if (askedId) fd.append('asked_user_id', String(askedId)); files.forEach(f => fd.append('attachments[]', f)); await window.api.messages.create(fd); // Refresh from server so the optimistic placeholder is replaced with the // canonical record (with proper id, server timestamps, attachment URLs). await window.refreshData(); if (askedEmail) onToast(`✉ Email küldve: ${askedEmail}`); } catch (err) { // Roll back the optimistic message. const i = window.MESSAGES.findIndex(m => m.id === optimistic.id); if (i >= 0) window.MESSAGES.splice(i, 1); force(); onToast('Hiba: ' + window.apiErrorMessage(err)); } }; const onFilePick = (e) => { const files = Array.from(e.target.files || []); const next = files.map(f => ({ _file: f, name: f.name, type: f.type.startsWith('image/') ? 'image' : 'file', size: (f.size / 1024 / 1024).toFixed(1) + ' MB', url: f.type.startsWith('image/') ? URL.createObjectURL(f) : null, })); setAttachments([...attachments, ...next]); e.target.value = ''; }; const removeAttachment = (i) => { setAttachments(attachments.filter((_, idx) => idx !== i)); }; // Csoportosítás nap szerint const grouped = []; let lastDay = null; window.MESSAGES.forEach(m => { const d = m.createdAt.slice(0, 10); if (d !== lastDay) { grouped.push({ kind: 'day', day: d }); lastDay = d; } grouped.push({ kind: 'msg', msg: m }); }); return (