Localize kanban, auth, backup, and admin utilities
This commit is contained in:
@@ -233,7 +233,7 @@ export default function Correspondence({ jobId }: { jobId: number }) {
|
||||
await load();
|
||||
toast(t("correspondenceLogEmail"), "success");
|
||||
} catch (error) {
|
||||
toast(getApiErrorMessage(error, "Failed to import email."), "error");
|
||||
toast(getApiErrorMessage(error, t("addJobModalImportFailed")), "error");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -241,9 +241,9 @@ export default function Correspondence({ jobId }: { jobId: number }) {
|
||||
try {
|
||||
const res = await api.get<{ url: string }>("/gmail/connect-url");
|
||||
const popup = window.open(res.data.url, "jobtracker-gmail-connect", "width=620,height=760,resizable=yes,scrollbars=yes");
|
||||
if (!popup) toast("Your browser blocked the Gmail popup.", "error");
|
||||
if (!popup) toast(t("correspondenceBlockedPopup"), "error");
|
||||
} catch (error) {
|
||||
toast(getApiErrorMessage(error, "Failed to start Gmail connection."), "error");
|
||||
toast(getApiErrorMessage(error, t("correspondenceStartGmailFailed")), "error");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -254,18 +254,18 @@ export default function Correspondence({ jobId }: { jobId: number }) {
|
||||
setGmailMessages([]);
|
||||
toast(t("googleUnlinked"), "success");
|
||||
} catch (error) {
|
||||
toast(getApiErrorMessage(error, "Failed to disconnect Gmail."), "error");
|
||||
toast(getApiErrorMessage(error, t("correspondenceDisconnectFailed")), "error");
|
||||
}
|
||||
};
|
||||
|
||||
const deleteMessage = async (messageId: number) => {
|
||||
if (!(await confirmAction("Remove this correspondence message?", { title: "Delete message", confirmLabel: t("jobTableDeleteSelected"), destructive: true }))) return;
|
||||
if (!(await confirmAction(t("correspondenceDeleteConfirm"), { title: t("correspondenceDeleteTitle"), confirmLabel: t("adminUsersDelete"), destructive: true }))) return;
|
||||
try {
|
||||
await api.delete(`/correspondence/${messageId}`);
|
||||
await load();
|
||||
toast("Message removed.", "success");
|
||||
toast(t("correspondenceDeleted"), "success");
|
||||
} catch (error) {
|
||||
toast(getApiErrorMessage(error, "Failed to remove message."), "error");
|
||||
toast(getApiErrorMessage(error, t("correspondenceDeleteFailed")), "error");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -276,7 +276,7 @@ export default function Correspondence({ jobId }: { jobId: number }) {
|
||||
await load();
|
||||
toast(t("correspondenceImportEmail"), "success");
|
||||
} catch (error: any) {
|
||||
toast(getApiErrorMessage(error, "Failed to import Gmail message."), "error");
|
||||
toast(getApiErrorMessage(error, t("correspondenceImportGmailFailed")), "error");
|
||||
} finally {
|
||||
setImportingMessageId(null);
|
||||
}
|
||||
@@ -287,9 +287,9 @@ export default function Correspondence({ jobId }: { jobId: number }) {
|
||||
setImportingThreadId(threadId);
|
||||
const res = await api.post<{ imported: number; skipped: number; threadId?: string }>("/gmail/import-thread", { jobApplicationId: jobId, threadId, messageIds });
|
||||
await load();
|
||||
toast(`Imported ${res.data.imported} messages${res.data.skipped ? `, skipped ${res.data.skipped} duplicates` : ""}.`, "success");
|
||||
toast(t("correspondenceImportThreadResult", { imported: res.data.imported, skippedText: res.data.skipped ? t("correspondenceImportThreadSkipped", { count: res.data.skipped }) : "" }), "success");
|
||||
} catch (error: any) {
|
||||
toast(getApiErrorMessage(error, "Failed to import Gmail thread."), "error");
|
||||
toast(getApiErrorMessage(error, t("correspondenceImportThreadFailed")), "error");
|
||||
} finally {
|
||||
setImportingThreadId(null);
|
||||
}
|
||||
@@ -299,7 +299,7 @@ export default function Correspondence({ jobId }: { jobId: number }) {
|
||||
<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 ? (
|
||||
<Typography sx={{ color: "text.secondary", py: 2, textAlign: "center" }}>No messages yet.</Typography>
|
||||
<Typography sx={{ color: "text.secondary", py: 2, textAlign: "center" }}>{t("correspondenceNoMessages")}</Typography>
|
||||
) : (
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
||||
{messages.map((m) => {
|
||||
@@ -384,12 +384,12 @@ export default function Correspondence({ jobId }: { jobId: number }) {
|
||||
<TextField label={t("correspondenceSearchGmail")} value={gmailQuery} onChange={(e) => setGmailQuery(e.target.value)} placeholder={t("correspondenceSearchGmailPlaceholder")} size="small" fullWidth />
|
||||
<Button variant="outlined" onClick={() => void loadGmailMessages()} disabled={gmailMessagesLoading}>{t("correspondenceSearch")}</Button>
|
||||
</Box>
|
||||
{gmailStatus.lastSyncedAt ? <Chip label={`Last synced ${new Date(gmailStatus.lastSyncedAt).toLocaleString()}`} size="small" /> : null}
|
||||
{gmailStatus.lastSyncedAt ? <Chip label={t("correspondenceLastSynced", { date: new Date(gmailStatus.lastSyncedAt).toLocaleString() })} size="small" /> : null}
|
||||
<Paper variant="outlined" sx={{ maxHeight: 420, overflowY: "auto" }}>
|
||||
{gmailMessagesLoading ? (
|
||||
<Box sx={{ py: 5, display: "flex", justifyContent: "center" }}><CircularProgress size={28} /></Box>
|
||||
) : groupedByThread.length === 0 ? (
|
||||
<Typography sx={{ color: "text.secondary", p: 2 }}>No Gmail messages found.</Typography>
|
||||
<Typography sx={{ color: "text.secondary", p: 2 }}>{t("correspondenceNoGmailMessages")}</Typography>
|
||||
) : (
|
||||
<List disablePadding>
|
||||
{groupedByThread.map(({ threadId, items }, threadIndex) => (
|
||||
@@ -398,11 +398,11 @@ export default function Correspondence({ jobId }: { jobId: number }) {
|
||||
<Box sx={{ p: 1.5, backgroundColor: alpha(theme.palette.primary.main, 0.04) }}>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", gap: 2, flexWrap: "wrap", alignItems: "center", mb: 1 }}>
|
||||
<Box>
|
||||
<Typography sx={{ fontWeight: 800 }}>{items[0]?.subject || "(No subject)"}</Typography>
|
||||
<Typography variant="body2" sx={{ color: "text.secondary" }}>{items.length} message{items.length === 1 ? "" : "s"} in thread</Typography>
|
||||
<Typography sx={{ fontWeight: 800 }}>{items[0]?.subject || t("correspondenceNoSubject")}</Typography>
|
||||
<Typography variant="body2" sx={{ color: "text.secondary" }}>{t("correspondenceMessagesInThread", { count: items.length })}</Typography>
|
||||
</Box>
|
||||
<Button startIcon={<MailOutlineIcon />} variant="outlined" size="small" disabled={importingThreadId === threadId} onClick={() => void importGmailThread(threadId, items.map((x) => x.id))}>
|
||||
{importingThreadId === threadId ? "Importing..." : "Import thread"}
|
||||
{importingThreadId === threadId ? t("correspondenceImporting") : t("correspondenceImportThread")}
|
||||
</Button>
|
||||
</Box>
|
||||
{items.map((message, index) => (
|
||||
@@ -410,11 +410,11 @@ export default function Correspondence({ jobId }: { jobId: number }) {
|
||||
{index > 0 ? <Divider sx={{ my: 1 }} /> : null}
|
||||
<ListItemButton sx={{ alignItems: "flex-start", px: 0, py: 1 }}>
|
||||
<ListItemText
|
||||
primary={<Box sx={{ display: "flex", justifyContent: "space-between", gap: 2, alignItems: "center", flexWrap: "wrap" }}><Typography sx={{ fontWeight: 700 }}>{message.subject || "(No subject)"}</Typography><Typography variant="caption" sx={{ color: "text.secondary" }}>{message.date ? new Date(message.date).toLocaleString() : ""}</Typography></Box>}
|
||||
secondary={<Box sx={{ mt: 0.5 }}><Typography variant="body2" sx={{ color: "text.primary" }}>From: {message.from || t("correspondenceUnknown")}</Typography><Typography variant="body2" sx={{ color: "text.secondary", mt: 0.25 }}>{message.snippet}</Typography></Box>}
|
||||
primary={<Box sx={{ display: "flex", justifyContent: "space-between", gap: 2, alignItems: "center", flexWrap: "wrap" }}><Typography sx={{ fontWeight: 700 }}>{message.subject || t("correspondenceNoSubject")}</Typography><Typography variant="caption" sx={{ color: "text.secondary" }}>{message.date ? new Date(message.date).toLocaleString() : ""}</Typography></Box>}
|
||||
secondary={<Box sx={{ mt: 0.5 }}><Typography variant="body2" sx={{ color: "text.primary" }}>{t("correspondenceFromLabel", { value: message.from || t("correspondenceUnknown") })}</Typography><Typography variant="body2" sx={{ color: "text.secondary", mt: 0.25 }}>{message.snippet}</Typography></Box>}
|
||||
/>
|
||||
<Button variant="contained" size="small" disabled={importingMessageId === message.id} onClick={() => void importGmailMessage(message.id)}>
|
||||
{importingMessageId === message.id ? "Importing..." : "Import"}
|
||||
{importingMessageId === message.id ? t("correspondenceImporting") : t("correspondenceImportEmail")}
|
||||
</Button>
|
||||
</ListItemButton>
|
||||
</React.Fragment>
|
||||
|
||||
Reference in New Issue
Block a user