Add confirmation for deletion

This commit is contained in:
cesnimda
2026-03-21 21:17:05 +01:00
parent 5ed5b340a5
commit 5b96465eaa
4 changed files with 128 additions and 9 deletions
@@ -235,6 +235,7 @@ export default function Correspondence({ jobId }: { jobId: number }) {
const deleteMessage = async (messageId: number) => {
if (!window.confirm("Remove this correspondence message?")) return;
try {
await api.delete(`/correspondence/${messageId}`);
await load();
@@ -16,6 +16,7 @@ import {
import { api } from "../api";
import { JobApplication } from "../types";
import { useToast } from "../toast";
import Correspondence from "./Correspondence";
import Attachments from "./Attachments";
@@ -50,6 +51,7 @@ function statusChipColor(status: string): "default" | "primary" | "warning" | "e
}
export default function JobDetailsDialog({ open, jobId, onClose }: Props) {
const { toast } = useToast();
const [job, setJob] = useState<JobApplication | null>(null);
const [tab, setTab] = useState(0);
const [history, setHistory] = useState<{ id: number; type: string; oldValue?: string; newValue?: string; note?: string; at: string }[]>([]);
@@ -57,6 +59,7 @@ export default function JobDetailsDialog({ open, jobId, onClose }: Props) {
const [followUpDraft, setFollowUpDraft] = useState<FollowUpDraft | null>(null);
const [loadingDraft, setLoadingDraft] = useState(false);
const [sendingDraft, setSendingDraft] = useState(false);
const [refreshingAi, setRefreshingAi] = useState(false);
const [draftRecipient, setDraftRecipient] = useState("");
const [draftSubject, setDraftSubject] = useState("");
const [draftBody, setDraftBody] = useState("");
@@ -182,12 +185,33 @@ export default function JobDetailsDialog({ open, jobId, onClose }: Props) {
<Typography sx={{ whiteSpace: "pre-wrap" }}>{job.translatedDescription}</Typography>
</Box>
) : null}
{job?.fullSummary || job?.shortSummary ? (
<Box sx={{ gridColumn: "1 / -1", mt: 1 }}>
<Typography variant="overline">Summary</Typography>
<Typography sx={{ whiteSpace: "pre-wrap" }}>{job?.fullSummary ?? job?.shortSummary}</Typography>
<Box sx={{ gridColumn: "1 / -1", mt: 1 }}>
<Box sx={{ display: "flex", justifyContent: "space-between", gap: 1, alignItems: "center", flexWrap: "wrap", mb: 0.5 }}>
<Typography variant="overline">Summary and skills</Typography>
<Button
size="small"
variant="outlined"
disabled={refreshingAi}
onClick={async () => {
if (!jobId) return;
if (!window.confirm("Overwrite the current summary and skills with a freshly generated version?")) return;
setRefreshingAi(true);
try {
const res = await api.post<JobApplication>(`/jobapplications/${jobId}/refresh-ai`);
setJob(res.data);
toast("Summary and skills refreshed.", "success");
} catch (error: any) {
toast(error?.response?.data || "Failed to refresh summary and skills.", "error");
} finally {
setRefreshingAi(false);
}
}}
>
{refreshingAi ? "Refreshing..." : "Refresh summary and skills"}
</Button>
</Box>
) : null}
<Typography sx={{ whiteSpace: "pre-wrap" }}>{job?.fullSummary ?? job?.shortSummary ?? "No summary yet."}</Typography>
</Box>
<Box sx={{ gridColumn: "1 / -1" }}>
<Typography variant="overline">Notes</Typography>
<Typography sx={{ whiteSpace: "pre-wrap" }}>{job?.notes ?? ""}</Typography>
+16 -4
View File
@@ -204,10 +204,20 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
setSelectedIds((prev) => checked ? [...prev, id] : prev.filter((x) => x !== id));
};
const softDelete = async (id: number) => {
const confirmDelete = (jobsToDelete: JobApplication[]) => {
if (jobsToDelete.length === 0) return false;
if (jobsToDelete.length === 1) {
const job = jobsToDelete[0];
return window.confirm(`Move "${job.jobTitle}" at ${job.company?.name ?? "this company"} to trash?`);
}
return window.confirm(`Move ${jobsToDelete.length} selected jobs to trash?`);
};
const softDelete = async (job: JobApplication) => {
if (!confirmDelete([job])) return;
try {
await api.delete(`/jobapplications/${id}`);
toast("Job moved to trash.", "success", { label: "Undo", onClick: () => { void restore(id); } });
await api.delete(`/jobapplications/${job.id}`);
toast("Job moved to trash.", "success", { label: "Undo", onClick: () => { void restore(job.id); } });
setReloadToken((t) => t + 1);
} catch {
toast("Failed to delete job.", "error");
@@ -236,6 +246,8 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
const runBulkAction = async (action: "delete" | "restore" | "status", value?: string) => {
if (selectedIds.length === 0) return;
const selectedJobs = jobs.filter((job) => selectedIds.includes(job.id));
if (action === "delete" && !confirmDelete(selectedJobs)) return;
try {
await Promise.all(selectedIds.map((id) => {
if (action === "delete") return api.delete(`/jobapplications/${id}`);
@@ -354,7 +366,7 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
<Tooltip title="Edit"><IconButton size="small" onClick={() => setEditJobId(job.id)}><EditOutlinedIcon fontSize="small" /></IconButton></Tooltip>
<Tooltip title="Quick status"><IconButton size="small" onClick={(e) => { setStatusJobId(job.id); setStatusAnchor(e.currentTarget); }}><MoreHorizIcon fontSize="small" /></IconButton></Tooltip>
<Tooltip title="Open"><IconButton size="small" onClick={() => setDetailsJobId(job.id)}><LaunchIcon fontSize="small" /></IconButton></Tooltip>
{(mode === "trash" || (includeDeleted && job.isDeleted)) ? <Tooltip title="Restore"><IconButton size="small" onClick={() => void restore(job.id)}><RestoreFromTrashOutlinedIcon fontSize="small" /></IconButton></Tooltip> : <Tooltip title="Soft delete"><IconButton size="small" onClick={() => void softDelete(job.id)}><DeleteOutlineIcon fontSize="small" /></IconButton></Tooltip>}
{(mode === "trash" || (includeDeleted && job.isDeleted)) ? <Tooltip title="Restore"><IconButton size="small" onClick={() => void restore(job.id)}><RestoreFromTrashOutlinedIcon fontSize="small" /></IconButton></Tooltip> : <Tooltip title="Soft delete"><IconButton size="small" onClick={() => void softDelete(job)}><DeleteOutlineIcon fontSize="small" /></IconButton></Tooltip>}
</Box>
</TableCell>
</TableRow>