Polish UI, harden company creation, and add error pages

This commit is contained in:
cesnimda
2026-03-23 19:34:29 +01:00
parent 8f5eab2fe4
commit fcafda6f52
38 changed files with 2293 additions and 1269 deletions
+17 -14
View File
@@ -5,6 +5,7 @@ import { Box, Button, Chip, Divider, Paper, Typography } from "@mui/material";
import { api } from "../api";
import { JobApplication } from "../types";
import { useToast } from "../toast";
import { useI18n } from "../i18n/I18nProvider";
import JobDetailsDialog from "./JobDetailsDialog";
@@ -28,6 +29,7 @@ function groupItems(items: JobApplication[]): ReminderGroups {
}
function ReminderSection({ title, items, onOpen, onSetFollowUp }: { title: string; items: JobApplication[]; onOpen: (id: number) => void; onSetFollowUp: (id: number, days: number | null) => void }) {
const { t } = useI18n();
if (items.length === 0) return null;
return (
@@ -40,17 +42,17 @@ function ReminderSection({ title, items, onOpen, onSetFollowUp }: { title: strin
{j.company?.name ?? ""} <span style={{ fontWeight: 700, opacity: 0.7 }}></span> {j.jobTitle}
</Typography>
<Box sx={{ display: "flex", gap: 1, mt: 0.5, flexWrap: "wrap" }}>
{j.needsFollowUp ? <Chip size="small" color="warning" label="Follow up" /> : null}
{j.needsFollowUp ? <Chip size="small" color="warning" label={t("remindersFollowUpLabel")} /> : null}
{j.followUpReason ? <Chip size="small" label={j.followUpReason} variant="outlined" /> : null}
{j.followUpAt ? <Chip size="small" label={`Follow-up: ${new Date(j.followUpAt).toLocaleDateString()}`} variant="outlined" /> : null}
{j.followUpAt ? <Chip size="small" label={t("remindersFollowUpDate", { date: new Date(j.followUpAt).toLocaleDateString() })} variant="outlined" /> : null}
<Chip size="small" label={j.status} variant="outlined" />
</Box>
</Box>
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap", justifyContent: "flex-end" }}>
<Button size="small" variant="outlined" onClick={() => onOpen(j.id)}>Open</Button>
<Button size="small" variant="outlined" onClick={() => onOpen(j.id)}>{t("remindersOpen")}</Button>
<Button size="small" variant="outlined" onClick={() => onSetFollowUp(j.id, 3)}>+3d</Button>
<Button size="small" variant="outlined" onClick={() => onSetFollowUp(j.id, 7)}>+7d</Button>
<Button size="small" onClick={() => onSetFollowUp(j.id, null)}>Clear</Button>
<Button size="small" onClick={() => onSetFollowUp(j.id, null)}>{t("remindersClear")}</Button>
</Box>
</Paper>
))}
@@ -60,6 +62,7 @@ function ReminderSection({ title, items, onOpen, onSetFollowUp }: { title: strin
export default function RemindersView() {
const { toast } = useToast();
const { t } = useI18n();
const [items, setItems] = useState<JobApplication[]>([]);
const [openJobId, setOpenJobId] = useState<number | null>(null);
@@ -78,32 +81,32 @@ export default function RemindersView() {
try {
const d = daysFromNow === null ? null : new Date(Date.now() + daysFromNow * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
await api.patch(`/jobapplications/${id}/followup`, { followUpAt: d });
toast(daysFromNow === null ? "Follow-up cleared." : "Follow-up set.", "success");
toast(daysFromNow === null ? t("remindersFollowUpCleared") : t("remindersFollowUpSet"), "success");
await load();
} catch {
toast("Failed to set follow-up.", "error");
toast(t("remindersFollowUpFailed"), "error");
}
};
return (
<Paper sx={{ mt: 0, p: 2 }}>
<Typography variant="h6" sx={{ mb: 1 }}>Needs Follow-up</Typography>
<Typography variant="h6" sx={{ mb: 1 }}>{t("remindersTitle")}</Typography>
<Typography variant="body2" sx={{ color: "text.secondary", mb: 2 }}>
Grouped by the most useful next action so you can fix gaps faster.
{t("remindersSubtitle")}
</Typography>
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<ReminderSection title="Missing tailored CV" items={grouped.missingCv} onOpen={setOpenJobId} onSetFollowUp={setFollowUp} />
<ReminderSection title="Missing interview prep" items={grouped.missingInterviewNotes} onOpen={setOpenJobId} onSetFollowUp={setFollowUp} />
<ReminderSection title="Follow-up due" items={grouped.overdueFollowUp} onOpen={setOpenJobId} onSetFollowUp={setFollowUp} />
<ReminderSection title="Other reminders" items={grouped.other} onOpen={setOpenJobId} onSetFollowUp={setFollowUp} />
<ReminderSection title={t("remindersMissingTailoredCv")} items={grouped.missingCv} onOpen={setOpenJobId} onSetFollowUp={setFollowUp} />
<ReminderSection title={t("remindersMissingInterviewPrep")} items={grouped.missingInterviewNotes} onOpen={setOpenJobId} onSetFollowUp={setFollowUp} />
<ReminderSection title={t("remindersFollowUpDue")} items={grouped.overdueFollowUp} onOpen={setOpenJobId} onSetFollowUp={setFollowUp} />
<ReminderSection title={t("remindersOther")} items={grouped.other} onOpen={setOpenJobId} onSetFollowUp={setFollowUp} />
{items.length === 0 ? <Typography sx={{ color: "text.secondary", textAlign: "center", py: 3 }}>Nothing to follow up right now.</Typography> : null}
{items.length === 0 ? <Typography sx={{ color: "text.secondary", textAlign: "center", py: 3 }}>{t("remindersNothing")}</Typography> : null}
</Box>
<Divider sx={{ my: 2 }} />
<Typography variant="caption" sx={{ color: "text.secondary" }}>
Tip: focus on tailored CV and interview prep first for the highest-value roles.
{t("remindersTip")}
</Typography>
<JobDetailsDialog open={openJobId !== null} jobId={openJobId} onClose={() => setOpenJobId(null)} />