diff --git a/job-tracker-ui/src/App.test.tsx b/job-tracker-ui/src/App.test.tsx
index 61f66c5..4fa0853 100644
--- a/job-tracker-ui/src/App.test.tsx
+++ b/job-tracker-ui/src/App.test.tsx
@@ -1,3 +1,5 @@
test('test harness is configured', () => {
expect(true).toBe(true);
});
+
+export {};
diff --git a/job-tracker-ui/src/components/AddJobModal.tsx b/job-tracker-ui/src/components/AddJobModal.tsx
index 18ef049..22ffb2e 100644
--- a/job-tracker-ui/src/components/AddJobModal.tsx
+++ b/job-tracker-ui/src/components/AddJobModal.tsx
@@ -394,10 +394,7 @@ export default function AddJobModal({ open, onClose, onCreated }: Props) {
- setSaveAndAddAnother(e.target.checked)} />}
- label="Save and add another"
- />
+ setSaveAndAddAnother(e.target.checked)} />} label="Save and add another" />
-
-
-
-
-
- );
-}
diff --git a/job-tracker-ui/src/components/DashboardView.tsx b/job-tracker-ui/src/components/DashboardView.tsx
index 86a8771..2e6ee9e 100644
--- a/job-tracker-ui/src/components/DashboardView.tsx
+++ b/job-tracker-ui/src/components/DashboardView.tsx
@@ -28,6 +28,12 @@ interface JobStats {
averageDaysSinceApplied: number;
}
+type ReminderJob = {
+ id: number;
+ tailoredCvText?: string | null;
+ followUpReason?: string | null;
+};
+
type AnalyticsPoint = { month: string; applied: number; responses: number };
type TagPoint = { tag: string; count: number };
type SummarizerMetrics = {
@@ -126,12 +132,14 @@ export default function DashboardView() {
const [analytics, setAnalytics] = useState([]);
const [tags, setTags] = useState([]);
const [summarizerMetrics, setSummarizerMetrics] = useState(null);
+ const [reminderJobs, setReminderJobs] = useState([]);
const [prefs, setPrefs] = useState(() => loadPrefs());
const [prefsAnchor, setPrefsAnchor] = useState(null);
useEffect(() => {
api.get("/jobapplications/stats").then((r) => setStats(r.data));
api.get("/jobapplications/analytics-overview").then((r) => setOverview(r.data)).catch(() => setOverview(null));
+ api.get("/jobapplications/reminders", { params: { upcomingDays: 14 } }).then((r) => setReminderJobs(Array.isArray(r.data) ? r.data : [])).catch(() => setReminderJobs([]));
}, []);
useEffect(() => {
@@ -430,16 +438,3 @@ export default function DashboardView() {
);
}
-
-
-
-cent summarizer errors recorded."}
-
-
- ) : null}
-
- );
-}
-
-
-
diff --git a/job-tracker-ui/src/components/JobTable.tsx b/job-tracker-ui/src/components/JobTable.tsx
index e826bf5..b878918 100644
--- a/job-tracker-ui/src/components/JobTable.tsx
+++ b/job-tracker-ui/src/components/JobTable.tsx
@@ -66,6 +66,7 @@ interface JobApplication {
followUpReason?: string | null;
shortSummary?: string | null;
fullSummary?: string | null;
+ tailoredCvText?: string | null;
}
interface PagedResult {
@@ -140,6 +141,7 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
const [locationFilter, setLocationFilter] = useState("");
const debouncedLocation = useDebouncedValue(locationFilter, 250);
const [needsFollowUpOnly, setNeedsFollowUpOnly] = useState(false);
+ const [readinessFilter, setReadinessFilter] = useState<"all" | "needs-work" | "interview">("all");
const { companies } = useCompanies();
const [companyFilterId, setCompanyFilterId] = useState("All");
const [detailsJobId, setDetailsJobId] = useState(null);
@@ -196,10 +198,16 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
setExpanded((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));
};
- const selectedAllOnPage = jobs.length > 0 && jobs.every((job) => selectedIds.includes(job.id));
+ const filteredJobs = useMemo(() => {
+ if (readinessFilter === "all") return jobs;
+ if (readinessFilter === "interview") return jobs.filter((job) => job.status === "Interview" || job.status === "Interviewing");
+ return jobs.filter((job) => !job.tailoredCvText || !job.notes);
+ }, [jobs, readinessFilter]);
+
+ const selectedAllOnPage = filteredJobs.length > 0 && filteredJobs.every((job) => selectedIds.includes(job.id));
const toggleSelectAll = (checked: boolean) => {
- setSelectedIds(checked ? jobs.map((job) => job.id) : []);
+ setSelectedIds(checked ? filteredJobs.map((job) => job.id) : []);
};
const toggleSelected = (id: number, checked: boolean) => {
@@ -274,15 +282,7 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
return (
- { setSearch(e.target.value); setPage(0); }}
- placeholder="Title, company, notes, messages"
- size="small"
- InputProps={{ startAdornment: }}
- sx={{ minWidth: 320, flex: "1 1 320px" }}
- />
+ { setSearch(e.target.value); setPage(0); }} placeholder="Title, company, notes, messages" size="small" InputProps={{ startAdornment: }} sx={{ minWidth: 320, flex: "1 1 320px" }} />
Status
@@ -303,6 +303,16 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
{mode === "jobs" ? { setNeedsFollowUpOnly(e.target.checked); setPage(0); }} />} label="Needs follow-up" /> : null}
+ {mode === "jobs" ? (
+
+ Readiness
+
+
+ ) : null}
{mode === "jobs" ? { setIncludeDeleted(e.target.checked); setPage(0); }} />} label="Show deleted" /> : null}
{ setSearch(p.q ?? ""); setStatusFilter(p.status ?? "All"); setCompanyFilterId(p.companyId ?? "All"); setLocationFilter(p.location ?? ""); setNeedsFollowUpOnly(Boolean(p.needsFollowUp)); setPage(0); }} />
setColumnsAnchor(e.currentTarget)}>
@@ -391,7 +401,7 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
);
})}
- {jobs.length === 0 ? No jobs found. : null}
+ {filteredJobs.length === 0 ? No jobs found. : null}
setPage(next)} rowsPerPage={pageSize} onRowsPerPageChange={(e) => { onPageSizeChange(Number(e.target.value) as 15 | 20 | 25); setPage(0); }} rowsPerPageOptions={[15, 20, 25]} />
diff --git a/job-tracker-ui/src/layout/AppShell.tsx b/job-tracker-ui/src/layout/AppShell.tsx
index feb00d7..9c80dc3 100644
--- a/job-tracker-ui/src/layout/AppShell.tsx
+++ b/job-tracker-ui/src/layout/AppShell.tsx
@@ -31,6 +31,7 @@ export type NavItem = {
icon: React.ReactNode;
hidden?: boolean;
section?: string;
+ badgeCount?: number;
};
function initialsFrom(s?: string) {
diff --git a/job-tracker-ui/src/pages/AdminSystemPage.tsx b/job-tracker-ui/src/pages/AdminSystemPage.tsx
index 332b81c..5a4a8eb 100644
--- a/job-tracker-ui/src/pages/AdminSystemPage.tsx
+++ b/job-tracker-ui/src/pages/AdminSystemPage.tsx
@@ -54,9 +54,14 @@ function formatBytes(bytes?: number | null) {
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
+function displayMetadata(value?: string | null) {
+ return value && value.trim().length > 0 ? value : "-";
+}
+
export default function AdminSystemPage() {
const [status, setStatus] = useState(null);
const [loading, setLoading] = useState(false);
+ const [runningProbe, setRunningProbe] = useState(false);
const [error, setError] = useState(null);
const load = async () => {
@@ -193,13 +198,3 @@ export default function AdminSystemPage() {
);
}
-.summarizer.lastError} : null}
-
-
- );
-}
-tus.summarizer.lastError} : null}
-
-
- );
-}
diff --git a/job-tracker-ui/src/pages/AdminUsersPage.tsx b/job-tracker-ui/src/pages/AdminUsersPage.tsx
index 21148e2..ce3889a 100644
--- a/job-tracker-ui/src/pages/AdminUsersPage.tsx
+++ b/job-tracker-ui/src/pages/AdminUsersPage.tsx
@@ -30,6 +30,7 @@ type UserDto = {
export default function AdminUsersPage() {
const { toast } = useToast();
+ const { confirmAction } = useDialogActions();
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
@@ -122,21 +123,9 @@ export default function AdminUsersPage() {
Send a quick delivery check using the configured SMTP settings. Leave the recipient blank to use your admin email.
- setTestEmailTo(e.target.value)}
- placeholder="Uses your admin email if left blank"
- />
+ setTestEmailTo(e.target.value)} placeholder="Uses your admin email if left blank" />
setTestEmailSubject(e.target.value)} />
- setTestEmailMessage(e.target.value)}
- sx={{ gridColumn: { xs: "1 / -1", md: "1 / -1" } }}
- />
+ setTestEmailMessage(e.target.value)} sx={{ gridColumn: { xs: "1 / -1", md: "1 / -1" } }} />
-
-
- );
- })}
-
{!loading && users.length === 0 ? (
diff --git a/job-tracker-ui/src/types.ts b/job-tracker-ui/src/types.ts
index 8e4edd6..0c867bd 100644
--- a/job-tracker-ui/src/types.ts
+++ b/job-tracker-ui/src/types.ts
@@ -32,9 +32,12 @@ export interface JobApplication {
deadline?: string;
notes?: string;
coverLetterText?: string;
+ recruiterMessageDraft?: string | null;
jobUrl?: string;
shortSummary?: string;
fullSummary?: string | null;
+ tailoredCvText?: string | null;
+ tailoredCvUpdatedAt?: string | null;
hasResume?: boolean;
hasCoverLetter?: boolean;