import React, { useMemo } from "react";
import { Box, Chip, Typography, useTheme } from "@mui/material";
import { alpha } from "@mui/material/styles";
import WorkOutlineIcon from "@mui/icons-material/WorkOutline";
import MarkEmailReadIcon from "@mui/icons-material/MarkEmailRead";
import ForumIcon from "@mui/icons-material/Forum";
import EmojiEventsIcon from "@mui/icons-material/EmojiEvents";
import CloseIcon from "@mui/icons-material/Close";
import { JobApplication } from "../types";
type HistoryEvent = {
id: number;
type: string;
oldValue?: string;
newValue?: string;
note?: string;
at: string;
};
type FlowItem = {
key: string;
label: string;
at: Date;
color: string;
icon: React.ReactNode;
};
function normalizeStatus(status?: string): string {
if (!status) return "Applied";
if (status === "Interviewing") return "Interview";
return status;
}
function parseResponseDate(value?: string): Date | null {
if (!value) return null;
const parts = value.split(":");
const raw = parts.length > 1 ? parts.slice(1).join(":") : "";
const d = new Date(raw);
return Number.isNaN(+d) ? null : d;
}
function firstStatusChange(history: HistoryEvent[], status: string): Date | null {
const match = history.find((item) => item.type === "StatusChanged" && normalizeStatus(item.newValue) === status);
if (!match) return null;
const d = new Date(match.at);
return Number.isNaN(+d) ? null : d;
}
function firstResponse(history: HistoryEvent[], job: JobApplication): Date | null {
if (job.responseDate) {
const d = new Date(job.responseDate);
if (!Number.isNaN(+d)) return d;
}
const explicit = history.find((item) => item.type === "ReplyReceived");
if (explicit) {
const d = new Date(explicit.at);
if (!Number.isNaN(+d)) return d;
}
const updated = history.find((item) => item.type === "ResponseUpdated");
if (!updated) return null;
return parseResponseDate(updated.newValue) ?? new Date(updated.at);
}
export default function JobFlowBar({ job, history = [] }: { job: JobApplication | null; history?: HistoryEvent[] }) {
const theme = useTheme();
const items = useMemo(() => {
if (!job) return [] as FlowItem[];
const appliedAt = new Date(job.dateApplied);
const replyAt = firstResponse(history, job);
const interviewAt = firstStatusChange(history, "Interview") || (normalizeStatus(job.status) === "Interview" ? replyAt ?? appliedAt : null);
const offerAt = firstStatusChange(history, "Offer") || (normalizeStatus(job.status) === "Offer" ? replyAt ?? interviewAt ?? appliedAt : null);
const rejectedLabel = normalizeStatus(job.status) === "Ghosted" ? "Ghosted" : "Rejected";
const rejectedAt = firstStatusChange(history, "Rejected")
|| firstStatusChange(history, "Ghosted")
|| ((normalizeStatus(job.status) === "Rejected" || normalizeStatus(job.status) === "Ghosted") ? replyAt ?? appliedAt : null);
const next: FlowItem[] = [
{
key: "applied",
label: "Applied",
at: appliedAt,
color: theme.palette.info.main,
icon: ,
},
];
if (replyAt) {
next.push({
key: "reply",
label: "Reply",
at: replyAt,
color: theme.palette.success.main,
icon: ,
});
}
if (interviewAt) {
next.push({
key: "interview",
label: "Interview",
at: interviewAt,
color: theme.palette.primary.main,
icon: ,
});
}
if (offerAt) {
next.push({
key: "offer",
label: "Offer",
at: offerAt,
color: theme.palette.success.dark,
icon: ,
});
} else if (rejectedAt) {
next.push({
key: normalizeStatus(job.status).toLowerCase(),
label: rejectedLabel,
at: rejectedAt,
color: theme.palette.error.main,
icon: ,
});
}
return next
.filter((item) => !Number.isNaN(+item.at))
.sort((a, b) => +a.at - +b.at)
.filter((item, index, arr) => index === arr.findIndex((other) => other.key === item.key));
}, [history, job, theme.palette.error.main, theme.palette.info.main, theme.palette.primary.main, theme.palette.success.dark, theme.palette.success.main]);
if (!job || items.length === 0) return null;
return (
Flow
{items.map((item, index) => (
{index < items.length - 1 ? (
{"->"}
) : null}
))}
);
}