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})`}
)}
- }
- onClick={() => sync.mutate()} disabled={sync.isPending}>동기화
+ }
+ onClick={() => sync.mutate()} disabled={sync.isPending}>{busy ? "동기화 중" : "동기화"}
{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}