import React, { useMemo, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { Box, Button, Chip, Divider, Paper, Stack, Typography } from "@mui/material"; import { api, getApiErrorMessage } from "../api"; import ViewStateNotice from "./ViewStateNotice"; import { JobApplication } from "../types"; import { buildWorkflowPath, getReminderGroup, getWorkflowAction } from "../jobWorkflowSignals"; import { useToast } from "../toast"; import { useI18n } from "../i18n/I18nProvider"; import { useViewResource, type ViewResourceError } from "../hooks/useViewResource"; import { PageHeader } from "../layout/PageLayout"; import { statusLabel } from "../pipeline"; type FollowUpFailure = { job: Pick; days: number | null; date: string | null; error: ViewResourceError; }; type ReminderGroups = { missingCv: JobApplication[]; missingInterviewNotes: JobApplication[]; overdueFollowUp: JobApplication[]; other: JobApplication[]; }; function groupItems(items: JobApplication[]): ReminderGroups { const groups: ReminderGroups = { missingCv: [], missingInterviewNotes: [], overdueFollowUp: [], other: [] }; items.forEach((item) => { const group = getReminderGroup(item); groups[group].push(item); }); return groups; } function ReminderSection({ title, items, disabled, onOpen, onSetFollowUp }: { title: string; items: JobApplication[]; disabled: boolean; onOpen: (job: JobApplication) => void; onSetFollowUp: (job: JobApplication, days: number | null) => void }) { const { language, t } = useI18n(); if (items.length === 0) return null; return ( {title} {items.map((j) => { const action = getWorkflowAction(j, { packageWork: t("jobTablePackageWork"), followUp: t("jobTableFollowUp"), interviewPrep: t("jobTableInterviewStage"), readiness: t("jobTableReadiness"), }); return ( {j.company?.name ?? ""} • {j.jobTitle} {j.needsFollowUp ? : null} {(j.workflowSignal?.reason ?? j.followUpReason) ? : null} {j.followUpAt ? : null} button": { maxWidth: "100%", whiteSpace: "normal", overflowWrap: "anywhere" } }}> {[3, 7].map(days => )} ); })} ); } export default function RemindersView() { const navigate = useNavigate(); const { toast } = useToast(); const { t } = useI18n(); const pending = useRef(false); const [busy, setBusy] = useState(false); const [failure, setFailure] = useState(null); const resultsRef = useRef(null); const remindersResource = useViewResource( async () => { const res = await api.get("/jobapplications/reminders", { params: { upcomingDays: 14 } }); return Array.isArray(res.data) ? res.data : []; }, { initialData: [], errorMessage: "", }, ); const items = remindersResource.data; const grouped = useMemo(() => groupItems(items), [items]); const loading = busy || remindersResource.loading || remindersResource.refreshing; const disabled = loading || !!remindersResource.error; const openJob = (job: JobApplication) => { if (pending.current || disabled) return; navigate(buildWorkflowPath(job)); }; const setFollowUp = async (job: Pick, daysFromNow: number | null, date = daysFromNow === null ? null : new Date(Date.now() + daysFromNow * 24 * 60 * 60 * 1000).toISOString().slice(0, 10)) => { if (pending.current || disabled) return; pending.current = true; resultsRef.current?.focus(); setBusy(true); setFailure(null); try { await api.patch(`/jobapplications/${job.id}/followup`, { followUpAt: date }); toast(daysFromNow === null ? t("remindersFollowUpCleared") : t("remindersFollowUpSet"), "success"); await remindersResource.reload(); } catch (error) { const status = (error as { response?: { status?: number } })?.response?.status; const unauthorized = status === 401 || status === 403; setFailure({ job: { id: job.id, jobTitle: job.jobTitle }, days: daysFromNow, date, error: { kind: unauthorized ? "unauthorized" : "error", message: getApiErrorMessage(error, ""), retryable: !unauthorized, status } }); toast(t("remindersFollowUpFailed"), "error"); } finally { pending.current = false; setBusy(false); } }; const retryRead = async () => { if (pending.current || loading) return; pending.current = true; resultsRef.current?.focus(); setBusy(true); try { await remindersResource.reload(); } finally { pending.current = false; setBusy(false); } }; return ( setFollowUp(failure.job, failure.days, failure.date) : retryRead} /> {!remindersResource.loading && remindersResource.error?.kind !== "unauthorized" ? ( {!loading && !remindersResource.error && items.length === 0 ? {t("remindersNothing")} : null} ) : null} {t("remindersTip")} ); }