Refresh dashboard, adopt MUI X, and improve AI follow-ups

This commit is contained in:
cesnimda
2026-03-23 21:23:15 +01:00
parent 7293582376
commit 66d924e880
9 changed files with 684 additions and 251 deletions
@@ -16,6 +16,7 @@ import {
Typography,
Chip,
} from "@mui/material";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { api } from "../api";
import { Company, JobApplication } from "../types";
@@ -40,6 +41,17 @@ function toDateInputValue(isoLike?: string): string {
return d.toISOString().slice(0, 10);
}
function parsePickerDate(value?: string | null): Date | null {
if (!value) return null;
const parsed = new Date(value);
return Number.isNaN(+parsed) ? null : parsed;
}
function toPickerIso(value: Date | null): string {
if (!value || Number.isNaN(+value)) return "";
return value.toISOString().slice(0, 10);
}
function parseTags(raw: any): string[] {
if (!raw) return [];
if (Array.isArray(raw)) return raw.filter((x) => typeof x === "string");
@@ -172,7 +184,7 @@ export default function EditJobDialog({ open, jobId, onClose, onSaved }: Props)
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 2, mt: 1 }}>
<Autocomplete options={companies} getOptionLabel={(c) => c.name} value={company} onChange={(_, v) => setCompany(v)} renderInput={(params) => <TextField {...params} label={t("company")} />} />
<TextField label={t("editJobJobTitle")} value={jobTitle} onChange={(e) => setJobTitle(e.target.value)} />
<TextField label={t("editJobAppliedOn")} type="date" value={dateApplied} onChange={(e) => setDateApplied(e.target.value)} InputLabelProps={{ shrink: true }} />
<DatePicker label={t("editJobAppliedOn")} value={parsePickerDate(dateApplied)} onChange={(value) => setDateApplied(toPickerIso(value))} slotProps={{ textField: { fullWidth: true } }} />
<TextField label={t("addJobModalJobUrl")} value={jobUrl} onChange={(e) => setJobUrl(e.target.value)} />
</Box>
</Paper>
@@ -183,11 +195,11 @@ export default function EditJobDialog({ open, jobId, onClose, onSaved }: Props)
<TextField select label={t("editJobCurrentStatus")} value={status} onChange={(e) => setStatus(e.target.value)}>
{STATUS_OPTIONS.map((s) => <MenuItem key={s} value={s}>{s}</MenuItem>)}
</TextField>
<TextField label={t("editJobStatusChangedOn")} type="date" value={statusChangedAt} onChange={(e) => setStatusChangedAt(e.target.value)} InputLabelProps={{ shrink: true }} helperText={status === initialStatus ? t("editJobStatusChangedHelpIdle") : t("editJobStatusChangedHelpActive")} />
<DatePicker label={t("editJobStatusChangedOn")} value={parsePickerDate(statusChangedAt)} onChange={(value) => setStatusChangedAt(toPickerIso(value))} slotProps={{ textField: { fullWidth: true, helperText: status === initialStatus ? t("editJobStatusChangedHelpIdle") : t("editJobStatusChangedHelpActive") } }} />
<Box sx={{ display: "flex", alignItems: "center" }}><FormControlLabel control={<Checkbox checked={responseReceived} onChange={(e) => setResponseReceived(e.target.checked)} />} label={t("editJobReplyReceived")} /></Box>
<TextField label={t("editJobReplyReceivedOn")} type="date" disabled={!responseReceived} value={responseDate} onChange={(e) => setResponseDate(e.target.value)} InputLabelProps={{ shrink: true }} />
<DatePicker label={t("editJobReplyReceivedOn")} disabled={!responseReceived} value={parsePickerDate(responseDate)} onChange={(value) => setResponseDate(toPickerIso(value))} slotProps={{ textField: { fullWidth: true } }} />
<TextField label={t("editJobNextAction")} value={nextAction} onChange={(e) => setNextAction(e.target.value)} />
<TextField label={t("editJobFollowUpOn")} type="date" value={followUpAt} onChange={(e) => setFollowUpAt(e.target.value)} InputLabelProps={{ shrink: true }} />
<DatePicker label={t("editJobFollowUpOn")} value={parsePickerDate(followUpAt)} onChange={(value) => setFollowUpAt(toPickerIso(value))} slotProps={{ textField: { fullWidth: true } }} />
</Box>
</Paper>
@@ -196,7 +208,7 @@ export default function EditJobDialog({ open, jobId, onClose, onSaved }: Props)
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 2, mt: 1 }}>
<TextField label={t("location")} value={location} onChange={(e) => setLocation(e.target.value)} />
<TextField label={t("addJobModalSalary")} value={salary} onChange={(e) => setSalary(e.target.value)} />
<TextField label={t("editJobDeadline")} type="date" value={deadline} onChange={(e) => setDeadline(e.target.value)} InputLabelProps={{ shrink: true }} />
<DatePicker label={t("editJobDeadline")} value={parsePickerDate(deadline)} onChange={(value) => setDeadline(toPickerIso(value))} slotProps={{ textField: { fullWidth: true } }} />
<TextField label={t("editJobDescriptionLanguage")} value={descriptionLanguage} onChange={(e) => setDescriptionLanguage(e.target.value)} />
<Box sx={{ gridColumn: "1 / -1" }}><TagsInput value={tags} onChange={setTags} /></Box>
<TextField label={t("editJobNotes")} value={notes} onChange={(e) => setNotes(e.target.value)} multiline rows={4} helperText={t("correspondenceCharacters", { count: notes.length })} sx={{ gridColumn: "1 / -1" }} />