From effd72761ee9e7ef17eba3666ace422afe613f18 Mon Sep 17 00:00:00 2001 From: theorose49 Date: Tue, 30 Jun 2026 14:01:35 +0900 Subject: [PATCH] =?UTF-8?q?fix(mail):=20=EB=8F=99=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=ED=94=BC=EB=93=9C=EB=B0=B1=C2=B7=EC=9E=90?= =?UTF-8?q?=EB=8F=99=ED=8F=B4=EB=A7=81=C2=B7=EC=97=90=EB=9F=AC=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=20+=20=EB=A9=94=EC=9D=BC=20=ED=81=B4=EB=A6=AD=20?= =?UTF-8?q?=EC=8B=9C=20Gmail=20=EC=97=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 동기화 클릭 시 즉시 '동기화 중' 표시 + 30초간 자동 폴링(syncing 동안 3s refetch), 실패 시 알림, 동기화 오류(연동 설정)도 표기 - 메일 제목 클릭 → 본인 Gmail에서 해당 메일 열기(rfc822msgid), 외부링크 아이콘 - 펼침(자세히)은 좌측 ▸ 버튼으로 분리 Co-Authored-By: Claude Opus 4.8 (1M context) --- src/pages/ProjectDetail.tsx | 40 +++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/pages/ProjectDetail.tsx b/src/pages/ProjectDetail.tsx index a0176f4..985cba7 100644 --- a/src/pages/ProjectDetail.tsx +++ b/src/pages/ProjectDetail.tsx @@ -3,7 +3,7 @@ import { useParams, Link } from "react-router-dom"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { ArrowLeft, Plus, GanttChartSquare, Columns3, CalendarDays, Trash2, Upload, Download, Lock, Pencil, - Mail, ChevronDown, ChevronRight, RefreshCw, EyeOff, Eye, + Mail, ChevronDown, ChevronRight, RefreshCw, EyeOff, Eye, ExternalLink, } from "lucide-react"; import { getProject, getProjectMembers, getContacts, getTasks, getContract, getContractFiles, @@ -499,12 +499,23 @@ function Contacts({ projectId }: { projectId: string }) { function MailTab({ projectId }: { projectId: string }) { const { nameOf } = useDirectory(); const qc = useQueryClient(); - const q = useQuery({ queryKey: ["mails", projectId], queryFn: () => getProjectMails(projectId) }); const [showHidden, setShowHidden] = useState(false); + const [polling, setPolling] = useState(false); + const q = useQuery({ + queryKey: ["mails", projectId], + queryFn: () => getProjectMails(projectId), + refetchInterval: (query) => (polling || query.state.data?.syncing ? 3000 : false), + }); const sync = useMutation({ mutationFn: () => syncProjectMails(projectId), - onSuccess: () => setTimeout(() => qc.invalidateQueries({ queryKey: ["mails", projectId] }), 2500), + onMutate: () => setPolling(true), + onError: () => { setPolling(false); alert("동기화 요청에 실패했습니다. 잠시 후 다시 시도해 주세요."); }, + onSettled: () => { + qc.invalidateQueries({ queryKey: ["mails", projectId] }); + setTimeout(() => setPolling(false), 30000); // 백필 동안 자동 폴링 후 중단 + }, }); + const busy = sync.isPending || polling || !!q.data?.syncing; if (q.isLoading) return ; const data = q.data; @@ -527,8 +538,8 @@ function MailTab({ projectId }: { projectId: string }) { @{data.domain} 와(과) 주고받은 메일 · 내가 수신·참조(CC)된 메일만

- {data.lastSyncedAt ? `마지막 동기화 ${formatDateTime(data.lastSyncedAt)}` : data.syncing ? "처음 동기화 중…" : "아직 동기화 안 됨"} - {data.error && · 일부 메일함 오류} + {busy ? "동기화 중…" : data.lastSyncedAt ? `마지막 동기화 ${formatDateTime(data.lastSyncedAt)}` : "아직 동기화 안 됨"} + {data.error && · 동기화 오류(연동 설정 확인)}

@@ -537,8 +548,8 @@ function MailTab({ projectId }: { projectId: string }) { {showHidden ? "숨긴 메일 가리기" : `숨긴 메일 보기 (${hiddenCount})`} )} - +
{visible.length === 0 ? ( @@ -553,6 +564,11 @@ function MailTab({ projectId }: { projectId: string }) { ); } +// RFC822 Message-ID로 보는 사람 본인 Gmail에서 해당 메일을 연다. +function gmailUrl(messageId: string): string { + return `https://mail.google.com/mail/u/0/#search/rfc822msgid:${encodeURIComponent(messageId)}`; +} + // "Name " 또는 "a@b" → 표시용 짧은 이름(이름 또는 @앞) function addrName(a: string): string { a = a.trim(); @@ -589,9 +605,11 @@ function MailRow({ projectId, mail, nameOf }: { projectId: string; mail: Project - +
{when}