feat(ui): redesign job workspace sections
This commit is contained in:
@@ -43,6 +43,7 @@ import {
|
||||
} from "../types";
|
||||
import { useDialogActions } from "../dialogs";
|
||||
import { useI18n } from "../i18n/I18nProvider";
|
||||
import ViewStateNotice from "./ViewStateNotice";
|
||||
|
||||
function parseRawEmail(raw: string): { subject?: string; date?: string; from?: string; to?: string; body: string } {
|
||||
const lines = raw.replace(/\r\n/g, "\n").split("\n");
|
||||
@@ -127,6 +128,8 @@ export default function Correspondence({ jobId, jobContext }: { jobId: number; j
|
||||
return label;
|
||||
};
|
||||
const [messages, setMessages] = useState<CorrespondenceMessage[]>([]);
|
||||
const [messagesLoading, setMessagesLoading] = useState(true);
|
||||
const [messagesError, setMessagesError] = useState<string | null>(null);
|
||||
const [from, setFrom] = useState<"Me" | "Company">("Me");
|
||||
const [text, setText] = useState("");
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -154,9 +157,18 @@ export default function Correspondence({ jobId, jobContext }: { jobId: number; j
|
||||
const autoRefreshKeyRef = useRef<string | null>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
const res = await api.get<CorrespondenceMessage[]>(`/correspondence/${jobId}`);
|
||||
setMessages(res.data);
|
||||
}, [jobId]);
|
||||
setMessagesLoading(true);
|
||||
try {
|
||||
const res = await api.get<CorrespondenceMessage[]>(`/correspondence/${jobId}`);
|
||||
setMessages(res.data);
|
||||
setMessagesError(null);
|
||||
} catch (error) {
|
||||
setMessagesError(getApiErrorMessage(error, t("correspondenceLoadFailed")));
|
||||
throw error; // Existing mutation callers still handle their own operation failure.
|
||||
} finally {
|
||||
setMessagesLoading(false);
|
||||
}
|
||||
}, [jobId, t]);
|
||||
|
||||
const loadGmailStatus = useCallback(async () => {
|
||||
try {
|
||||
@@ -242,7 +254,7 @@ export default function Correspondence({ jobId, jobContext }: { jobId: number; j
|
||||
}, [gmailQuery, gmailStatus?.connected, importOpen, importTab, jobId, linkedThreadIds.length, load, loadGmailMatches, loadGmailStatus, t, toast]);
|
||||
|
||||
useEffect(() => {
|
||||
void load();
|
||||
void load().catch(() => undefined);
|
||||
}, [load]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -460,8 +472,9 @@ export default function Correspondence({ jobId, jobContext }: { jobId: number; j
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Paper ref={scrollRef} sx={{ p: 1.5, maxHeight: 360, overflowY: "auto", background: theme.palette.mode === "dark" ? "rgba(15,23,42,0.45)" : "rgba(255,255,255,0.75)", backdropFilter: "blur(8px)" }}>
|
||||
{messages.length === 0 ? (
|
||||
<ViewStateNotice title={t("correspondenceLoadFailed")} loading={messagesLoading} error={messagesError ? { kind: "error", message: messagesError, retryable: true } : null} onRetry={() => load().catch(() => undefined)} compact />
|
||||
<Paper variant="outlined" ref={scrollRef} sx={{ p: 1.5, maxHeight: 360, overflowY: "auto", bgcolor: "background.paper", borderRadius: 1 }}>
|
||||
{messages.length === 0 && !messagesLoading && !messagesError ? (
|
||||
<Typography sx={{ color: "text.secondary", py: 2, textAlign: "center" }}>{t("correspondenceNoMessages")}</Typography>
|
||||
) : (
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
||||
@@ -470,7 +483,7 @@ export default function Correspondence({ jobId, jobContext }: { jobId: number; j
|
||||
const accent = isMe ? theme.palette.primary.main : theme.palette.warning.main;
|
||||
return (
|
||||
<Box key={m.id} sx={{ display: "flex", justifyContent: isMe ? "flex-end" : "flex-start" }}>
|
||||
<Box sx={{ maxWidth: "80%", borderRadius: 3, p: 1.25, border: `1px solid ${alpha(accent, theme.palette.mode === "dark" ? 0.32 : 0.22)}`, background: alpha(accent, theme.palette.mode === "dark" ? 0.14 : 0.1), color: "text.primary" }}>
|
||||
<Box sx={{ maxWidth: { xs: "100%", sm: "85%" }, minWidth: 0, overflowWrap: "anywhere", borderRadius: 1, p: 1.25, border: "1px solid", borderColor: "divider", borderLeft: `3px solid ${accent}`, bgcolor: "background.default", color: "text.primary", "& .MuiChip-root": { maxWidth: "100%", height: "auto", minHeight: 24 }, "& .MuiChip-label": { whiteSpace: "normal", overflowWrap: "anywhere" } }}>
|
||||
{m.subject ? <Typography sx={{ fontWeight: 800, mb: 0.5 }}>{m.subject}</Typography> : null}
|
||||
<Typography sx={{ whiteSpace: "pre-wrap", lineHeight: 1.35 }}>{m.content}</Typography>
|
||||
{(m.externalThreadId || m.externalFrom || m.externalTo || m.externalLabelsJson || m.attachmentMetadataJson) ? (
|
||||
@@ -498,7 +511,7 @@ export default function Correspondence({ jobId, jobContext }: { jobId: number; j
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
<Box sx={{ mt: 1.5, p: 1.5, borderRadius: 3, border: "none", boxShadow: "0px 1px 2px 0px rgba(15,23,42,0.04), 0px 8px 24px -12px rgba(15,23,42,0.12)", backgroundColor: "background.default" }}>
|
||||
<Box sx={{ mt: 1.5, p: 1.5, borderRadius: 1, border: 1, borderColor: "divider", bgcolor: "background.default", "& .MuiChip-root": { maxWidth: "100%", height: "auto", minHeight: 24 }, "& .MuiChip-label": { whiteSpace: "normal", overflowWrap: "anywhere" } }}>
|
||||
<Typography variant="overline">{t("correspondenceLinkedContinuity")}</Typography>
|
||||
<Typography variant="body2" sx={{ color: "text.secondary", mb: 1.25 }}>
|
||||
{t("correspondenceLinkedContinuityHelp")}
|
||||
@@ -548,7 +561,7 @@ export default function Correspondence({ jobId, jobContext }: { jobId: number; j
|
||||
|
||||
<Button variant="outlined" size="small" onClick={() => setImportOpen(true)}>{t("correspondenceImportEmail")}</Button>
|
||||
|
||||
<TextField label={t("correspondenceLogNoteOrMessage")} value={text} onChange={(e) => setText(e.target.value)} multiline minRows={3} sx={{ flex: "1 1 320px" }} helperText={t("correspondenceCharacters", { count: text.length })} />
|
||||
<TextField label={t("correspondenceLogNoteOrMessage")} value={text} onChange={(e) => setText(e.target.value)} multiline minRows={3} sx={{ flex: { xs: "1 1 100%", sm: "1 1 320px" }, minWidth: 0 }} helperText={t("correspondenceCharacters", { count: text.length })} />
|
||||
|
||||
<Button variant="contained" onClick={send} disabled={!canSend}>{t("correspondenceAdd")}</Button>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user