/* 검토·피드백 문서 탭 3개: (기획안|디자인) · 아티클 · 기타 - main/etc: 파일명 클릭 → 피그마식 주석 화면(변환 PDF or 이미지). 변환 실패 파일은 다운로드만. - article : 파일명 클릭 → 즉시 다운로드. LGE 포함 전체 업로드 가능. - main 탭에 미리보기 가능한 파일이 1개뿐이면 목록 생략하고 바로 주석 화면. */ /* 문서 뷰어 공용 — 작성자 역할별 대표색(LGE 빨강 / CNX·ADMIN 파랑) + 파일 크기 표기(최소 1KB, 0도 빈칸 안 됨). */ const annoColor = (role) => (role === 'LGE' ? ROLE_BADGE.LGE : ROLE_BADGE.CNX); const fmtDocSize = (b) => (b > 1048576 ? (b / 1048576).toFixed(1) + 'MB' : Math.max(1, Math.round(b / 1024)) + 'KB'); /* 아티클 게시글 1개 — 접힘 행 + 펼침(원본 다운로드 · 댓글 스레드 · 작성창). 단일 열림은 부모가 제어. */ function ArticlePost({ doc, user, open, onToggle, onChanged }) { const isStaff = user.role === 'CNX' || user.role === 'ADMIN'; const [comments, setComments] = useSE(null); const [body, setBody] = useSE(''); const [files, setFiles] = useSE([]); // 첨부 예정 File 객체 배열 const [busy, setBusy] = useSE(false); const reload = () => window.api.getDocComments(doc.id).then(setComments).catch(() => setComments([])); useEE(() => { if (open) reload(); }, [open, doc.id]); const pickFiles = (e) => { const picked = Array.from(e.target.files || []); e.target.value = ''; setFiles((prev) => [...prev, ...picked]); }; const rmFile = (i) => setFiles((prev) => prev.filter((_, k) => k !== i)); const canSend = (body.trim() || files.length > 0) && !busy; const submit = async () => { if (!canSend) return; setBusy(true); // POST 응답이 곧 새 댓글(shapeComment 형태)이라 목록 재조회 없이 로컬에 붙인다. onChanged는 접힌 글 뱃지(commentCount) 갱신용. try { const c = await window.api.addDocComment(doc.id, body.trim(), files); setComments((prev) => [...(prev || []), c]); setBody(''); setFiles([]); onChanged && onChanged(); } catch (err) { alert('등록 실패: ' + (err.message || err)); } finally { setBusy(false); } }; const delComment = async (c) => { if (!confirm('이 댓글을 삭제할까요?')) return; try { await window.api.deleteDocComment(c.id); setComments((prev) => (prev || []).filter((x) => x.id !== c.id)); onChanged && onChanged(); } catch (err) { alert('삭제 실패: ' + (err.message || err)); } }; // 글(아티클 파일) 삭제 — 업로더 본인 or 스태프. onChanged로 부모 목록 재조회 → 이 글이 목록에서 사라짐. const delPost = async () => { if (!confirm(`'${doc.originalName}' 글을 삭제할까요? (댓글·첨부도 함께 접근 불가가 됩니다)`)) return; try { await window.api.deleteActionDoc(doc.id); onChanged && onChanged(); } catch (err) { alert('삭제 실패: ' + (err.message || err)); } }; const canDel = (author) => (author && author.id === user.id) || isStaff; const count = open && comments ? comments.length : (doc.commentCount || 0); return (
{doc.originalName} {doc.uploader.name} · {String(doc.createdAt).slice(0, 10)} · {fmtDocSize(doc.sizeBytes)} {count > 0 && 댓글 {count}} {open ? '▲' : '▼'}
{open && (
{canDel(doc.uploader) && }
{comments === null ?
불러오는 중…
: comments.length === 0 ?
아직 댓글이 없습니다.
:
{comments.map((c) => { const rc = annoColor(c.author.role); return (
{c.author.name} {c.author.role} {String(c.createdAt).slice(0, 10)} {canDel(c.author) && }
{c.body &&
{c.body}
} {c.files.length > 0 && (
{c.files.map((f) => ( ))}
)}
); })}
}