Complete S03 runtime closure and S04 control loop
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import {
|
||||
Box,
|
||||
@@ -22,6 +23,7 @@ import AutoGraphIcon from "@mui/icons-material/AutoGraph";
|
||||
import { api } from "../api";
|
||||
import { getUserKeyFromToken } from "../themePrefs";
|
||||
import { useI18n } from "../i18n/I18nProvider";
|
||||
import { buildJobWorkspacePath, JOB_DETAILS_TABS } from "../jobWorkspaceRoute";
|
||||
|
||||
interface JobStats {
|
||||
total: number;
|
||||
@@ -34,8 +36,12 @@ interface JobStats {
|
||||
|
||||
type ReminderJob = {
|
||||
id: number;
|
||||
jobTitle: string;
|
||||
status: string;
|
||||
followUpAt?: string | null;
|
||||
tailoredCvText?: string | null;
|
||||
followUpReason?: string | null;
|
||||
company?: { name?: string | null };
|
||||
};
|
||||
|
||||
type AnalyticsPoint = { month: string; applied: number; responses: number };
|
||||
@@ -127,6 +133,7 @@ function SectionCard({ children, sx = {} }: { children: React.ReactNode; sx?: an
|
||||
|
||||
export default function DashboardView() {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useI18n();
|
||||
const [stats, setStats] = useState<JobStats | null>(null);
|
||||
const [overview, setOverview] = useState<OverviewAnalytics | null>(null);
|
||||
@@ -206,6 +213,15 @@ export default function DashboardView() {
|
||||
const totalApplied = appliedValues.reduce((sum, value) => sum + value, 0);
|
||||
const totalResponses = responseValues.reduce((sum, value) => sum + value, 0);
|
||||
const responseRate = totalApplied > 0 ? Math.round((totalResponses / totalApplied) * 100) : 0;
|
||||
const priorityJobs = reminderJobs.slice(0, 5);
|
||||
const openReminderJob = (job: ReminderJob) => {
|
||||
const reason = (job.followUpReason ?? '').toLowerCase();
|
||||
if (reason.includes('tailored cv')) {
|
||||
navigate(buildJobWorkspacePath(job.id, { tab: JOB_DETAILS_TABS.tailoredCv }));
|
||||
return;
|
||||
}
|
||||
navigate(buildJobWorkspacePath(job.id, { tab: JOB_DETAILS_TABS.followUp, followMode: 'waiting-update' }));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
@@ -370,6 +386,31 @@ export default function DashboardView() {
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", xl: "1.15fr 0.85fr" }, gap: 2, mt: 2 }}>
|
||||
<SectionCard>
|
||||
<Typography variant="h6" sx={{ fontWeight: 950, mb: 1 }}>{t("remindersTitle")}</Typography>
|
||||
<Typography variant="body2" sx={{ color: "text.secondary", mb: 1.5 }}>{t("remindersSubtitle")}</Typography>
|
||||
{priorityJobs.length === 0 ? (
|
||||
<Typography sx={{ color: "text.secondary" }}>{t("remindersNothing")}</Typography>
|
||||
) : (
|
||||
<Stack spacing={1.1}>
|
||||
{priorityJobs.map((job) => (
|
||||
<Box key={job.id} sx={{ p: 1.5, borderRadius: 3, border: "1px solid", borderColor: "divider", backgroundColor: alpha(theme.palette.primary.main, 0.03), display: "flex", justifyContent: "space-between", gap: 2, alignItems: "center", flexWrap: "wrap" }}>
|
||||
<Box>
|
||||
<Typography sx={{ fontWeight: 900 }}>{job.company?.name ?? t("jobTableCompany")} • {job.jobTitle}</Typography>
|
||||
<Typography variant="body2" sx={{ color: "text.secondary" }}>{job.followUpReason ?? t("remindersFollowUpLabel")}</Typography>
|
||||
</Box>
|
||||
<Button variant="outlined" onClick={() => openReminderJob(job)}>
|
||||
{job.followUpReason?.toLowerCase().includes('tailored cv') ? t("jobDetailsTabTailoredCv") : t("jobTableFollowUp")}
|
||||
</Button>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
<Box sx={{ mt: 1.5 }}>
|
||||
<Button variant="text" onClick={() => navigate('/reminders')}>{t("reminders")}</Button>
|
||||
</Box>
|
||||
</SectionCard>
|
||||
|
||||
{prefs.companies ? (
|
||||
<SectionCard>
|
||||
<Typography variant="h6" sx={{ fontWeight: 950, mb: 1 }}>{t("dashboardTopCompaniesByActivity")}</Typography>
|
||||
|
||||
Reference in New Issue
Block a user