import React, { useCallback, useEffect, useRef, useState } from "react"; import { Alert, Box, Button, CircularProgress, Paper, Stack, Typography } from "@mui/material"; import { api, getApiErrorMessage } from "../api"; import { useAccountPlan } from "../accountPlan"; import type { FocusPlanResponse, StatusSuggestion, StrategySnapshotOperationResponse, UserOperation } from "../types"; import { useToast } from "../toast"; import { DraftCard, ListCard, TwoColumnSection } from "./JobDetailsPanels"; const terminal = (status: UserOperation["status"]) => ["succeeded", "failed", "cancelled"].includes(status); export function ApplicationStatusSuggestion({ jobId, onApplied }: { jobId: number; onApplied: () => void }) { const [suggestion, setSuggestion] = useState(null); const [busy, setBusy] = useState(false); const { toast } = useToast(); useEffect(() => { let active = true; api.get(`/jobapplications/${jobId}/status-suggestion`) .then(({ data }) => { if (active) setSuggestion(data.hasSuggestion ? data : null); }) .catch(() => { if (active) setSuggestion(null); }); return () => { active = false; }; }, [jobId]); if (!suggestion?.suggestedStatus) return null; const apply = async () => { setBusy(true); try { await api.patch(`/jobapplications/${jobId}/status`, { status: suggestion.suggestedStatus }); setSuggestion(null); onApplied(); toast("Application status updated from the latest message.", "success"); } catch (error) { toast(getApiErrorMessage(error, "Could not apply the suggested status."), "error"); } finally { setBusy(false); } }; return ( void apply()}>Apply {suggestion.suggestedStatus}} > A recent recruiter message suggests moving this application from {suggestion.currentStatus || "its current stage"} to {suggestion.suggestedStatus}. ); } export function ApplicationStrategySnapshot({ jobId }: { jobId: number }) { const { canUseAi } = useAccountPlan(); const { toast } = useToast(); const [plan, setPlan] = useState(null); const [operation, setOperation] = useState(null); const [loading, setLoading] = useState(true); const announced = useRef(null); const loadPlan = useCallback(async () => { try { const { data } = await api.get(`/jobapplications/${jobId}/focus-plan`); setPlan(data); } catch { setPlan(null); } }, [jobId]); useEffect(() => { let active = true; setLoading(true); Promise.all([ loadPlan(), api.get(`/jobapplications/${jobId}/focus-plan/operation`) .then(({ data }) => { if (active) setOperation(data); }) .catch(() => { if (active) setOperation(null); }), ]).finally(() => { if (active) setLoading(false); }); return () => { active = false; }; }, [jobId, loadPlan]); useEffect(() => { if (!operation || terminal(operation.status)) return; const timer = window.setTimeout(() => { api.get(`/operations/${operation.id}`) .then(({ data }) => setOperation(data)) .catch(() => undefined); }, 1000); return () => window.clearTimeout(timer); }, [operation]); useEffect(() => { if (!operation || !terminal(operation.status)) return; const key = `${operation.id}:${operation.status}`; if (announced.current === key) return; announced.current = key; if (operation.status === "succeeded") { void loadPlan(); toast("Strategy snapshot completed.", "success"); } else if (operation.status === "failed") toast("Strategy snapshot failed. You can retry safely.", "error"); else toast("Strategy snapshot cancelled.", "info"); }, [loadPlan, operation, toast]); const generate = async () => { setLoading(true); try { const { data } = await api.post(`/jobapplications/${jobId}/focus-plan/operations`, { attachmentIds: null }); announced.current = null; setOperation(data.operation); toast(data.created ? "Strategy snapshot queued." : "Strategy snapshot is already queued.", "info"); } catch (error) { toast(getApiErrorMessage(error, "Could not queue the strategy snapshot."), "error"); } finally { setLoading(false); } }; const mutateOperation = async (action: "cancel" | "retry") => { if (!operation) return; try { announced.current = null; const { data } = await api.post(`/operations/${operation.id}/${action}`); setOperation(data); } catch (error) { toast(getApiErrorMessage(error, `Could not ${action} the strategy snapshot.`), "error"); } }; const working = !!operation && !terminal(operation.status); return ( Strategy snapshot An on-demand plan grounded in this advert and your saved career data. {operation && operation.status !== "succeeded" ? ( {operation.canCancel ? : null} {operation.canRetry ? : null} }> {operation.progressStage || operation.status.replaceAll("_", " ")}{operation.progressPercent != null ? ` ยท ${operation.progressPercent}%` : ""} ) : null} {loading && !plan ? : plan ? ( ) : No strategy snapshot yet. Generate one when you want AI-assisted planning.} ); }