|
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
|
|
Paper,
|
|
|
|
|
TextField,
|
|
|
|
|
Typography,
|
|
|
|
|
Chip,
|
|
|
|
|
} from "@mui/material";
|
|
|
|
|
|
|
|
|
|
import { api } from "../api";
|
|
|
|
@@ -83,41 +84,36 @@ export default function EditJobDialog({ open, jobId, onClose, onSaved }: Props)
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!open || !jobId) return;
|
|
|
|
|
setLoading(true);
|
|
|
|
|
api
|
|
|
|
|
.get<JobApplication>(`/jobapplications/${jobId}`)
|
|
|
|
|
.then((r) => {
|
|
|
|
|
const j = r.data;
|
|
|
|
|
setCompany(j.company ?? null);
|
|
|
|
|
setJobTitle(j.jobTitle ?? "");
|
|
|
|
|
setStatus(j.status ?? "Applied");
|
|
|
|
|
setInitialStatus(j.status ?? "Applied");
|
|
|
|
|
setStatusChangedAt(new Date().toISOString().slice(0, 10));
|
|
|
|
|
setDateApplied(toDateInputValue(j.dateApplied));
|
|
|
|
|
setLocation(j.location ?? "");
|
|
|
|
|
setSalary(j.salary ?? "");
|
|
|
|
|
setNextAction((j as any).nextAction ?? "");
|
|
|
|
|
setFollowUpAt((j as any).followUpAt ? toDateInputValue((j as any).followUpAt) : "");
|
|
|
|
|
setJobUrl(j.jobUrl ?? "");
|
|
|
|
|
setNotes(j.notes ?? "");
|
|
|
|
|
setDescription((j as any).description ?? "");
|
|
|
|
|
setTranslatedDescription((j as any).translatedDescription ?? "");
|
|
|
|
|
setDescriptionLanguage((j as any).descriptionLanguage ?? "");
|
|
|
|
|
setTags(parseTags((j as any).tags));
|
|
|
|
|
setDeadline((j as any).deadline ? toDateInputValue((j as any).deadline) : "");
|
|
|
|
|
setCoverLetterText(j.coverLetterText ?? "");
|
|
|
|
|
setResponseReceived(Boolean(j.responseReceived));
|
|
|
|
|
setResponseDate(j.responseDate ? toDateInputValue(j.responseDate) : "");
|
|
|
|
|
setHasResume(Boolean((j as any).hasResume));
|
|
|
|
|
setHasCoverLetter(Boolean((j as any).hasCoverLetter));
|
|
|
|
|
setHasPortfolio(Boolean((j as any).hasPortfolio));
|
|
|
|
|
setHasOtherAttachment(Boolean((j as any).hasOtherAttachment));
|
|
|
|
|
})
|
|
|
|
|
.finally(() => setLoading(false));
|
|
|
|
|
api.get<JobApplication>(`/jobapplications/${jobId}`).then((r) => {
|
|
|
|
|
const j = r.data;
|
|
|
|
|
setCompany(j.company ?? null);
|
|
|
|
|
setJobTitle(j.jobTitle ?? "");
|
|
|
|
|
setStatus(j.status ?? "Applied");
|
|
|
|
|
setInitialStatus(j.status ?? "Applied");
|
|
|
|
|
setStatusChangedAt(new Date().toISOString().slice(0, 10));
|
|
|
|
|
setDateApplied(toDateInputValue(j.dateApplied));
|
|
|
|
|
setLocation(j.location ?? "");
|
|
|
|
|
setSalary(j.salary ?? "");
|
|
|
|
|
setNextAction((j as any).nextAction ?? "");
|
|
|
|
|
setFollowUpAt((j as any).followUpAt ? toDateInputValue((j as any).followUpAt) : "");
|
|
|
|
|
setJobUrl(j.jobUrl ?? "");
|
|
|
|
|
setNotes(j.notes ?? "");
|
|
|
|
|
setDescription((j as any).description ?? "");
|
|
|
|
|
setTranslatedDescription((j as any).translatedDescription ?? "");
|
|
|
|
|
setDescriptionLanguage((j as any).descriptionLanguage ?? "");
|
|
|
|
|
setTags(parseTags((j as any).tags));
|
|
|
|
|
setDeadline((j as any).deadline ? toDateInputValue((j as any).deadline) : "");
|
|
|
|
|
setCoverLetterText(j.coverLetterText ?? "");
|
|
|
|
|
setResponseReceived(Boolean(j.responseReceived));
|
|
|
|
|
setResponseDate(j.responseDate ? toDateInputValue(j.responseDate) : "");
|
|
|
|
|
setHasResume(Boolean((j as any).hasResume));
|
|
|
|
|
setHasCoverLetter(Boolean((j as any).hasCoverLetter));
|
|
|
|
|
setHasPortfolio(Boolean((j as any).hasPortfolio));
|
|
|
|
|
setHasOtherAttachment(Boolean((j as any).hasOtherAttachment));
|
|
|
|
|
}).finally(() => setLoading(false));
|
|
|
|
|
}, [open, jobId]);
|
|
|
|
|
|
|
|
|
|
const canSave = useMemo(() => {
|
|
|
|
|
return !!company?.id && jobTitle.trim().length > 0 && !loading;
|
|
|
|
|
}, [company, jobTitle, loading]);
|
|
|
|
|
const canSave = useMemo(() => !!company?.id && jobTitle.trim().length > 0 && !loading, [company, jobTitle, loading]);
|
|
|
|
|
|
|
|
|
|
const save = async () => {
|
|
|
|
|
if (!jobId || !company?.id) return;
|
|
|
|
@@ -146,6 +142,7 @@ export default function EditJobDialog({ open, jobId, onClose, onSaved }: Props)
|
|
|
|
|
deadline: deadline || null,
|
|
|
|
|
coverLetterText: coverLetterText || null,
|
|
|
|
|
dateApplied: dateApplied || null,
|
|
|
|
|
jobUrl: jobUrl.trim() || null,
|
|
|
|
|
});
|
|
|
|
|
toast("Saved.", "success");
|
|
|
|
|
onSaved();
|
|
|
|
@@ -161,95 +158,59 @@ export default function EditJobDialog({ open, jobId, onClose, onSaved }: Props)
|
|
|
|
|
<Dialog open={open} onClose={onClose} fullWidth maxWidth="md">
|
|
|
|
|
<DialogTitle>Edit job</DialogTitle>
|
|
|
|
|
<DialogContent>
|
|
|
|
|
<Box sx={{ mt: 1, mb: 2, p: 1.5, borderRadius: 3, border: "1px solid", borderColor: "divider", backgroundColor: "background.paper" }}>
|
|
|
|
|
<Typography variant="body2" sx={{ color: "text.secondary" }}>
|
|
|
|
|
Update job details, timeline status, documents, and notes from one editing workspace.
|
|
|
|
|
</Typography>
|
|
|
|
|
</Box>
|
|
|
|
|
|
|
|
|
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 2, mt: 1 }}>
|
|
|
|
|
<Paper variant="outlined" sx={{ p: 2 }}>
|
|
|
|
|
<Typography variant="overline" sx={{ color: "text.secondary" }}>Application details</Typography>
|
|
|
|
|
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 2, mt: 1 }}>
|
|
|
|
|
<Autocomplete
|
|
|
|
|
options={companies}
|
|
|
|
|
getOptionLabel={(c) => c.name}
|
|
|
|
|
value={company}
|
|
|
|
|
onChange={(_, v) => setCompany(v)}
|
|
|
|
|
renderInput={(params) => <TextField {...params} label="Company" />}
|
|
|
|
|
/>
|
|
|
|
|
<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="Company" />} />
|
|
|
|
|
<TextField label="Job title" value={jobTitle} onChange={(e) => setJobTitle(e.target.value)} />
|
|
|
|
|
<TextField
|
|
|
|
|
label="Applied on"
|
|
|
|
|
type="date"
|
|
|
|
|
value={dateApplied}
|
|
|
|
|
onChange={(e) => setDateApplied(e.target.value)}
|
|
|
|
|
InputLabelProps={{ shrink: true }}
|
|
|
|
|
/>
|
|
|
|
|
<TextField label="Applied on" type="date" value={dateApplied} onChange={(e) => setDateApplied(e.target.value)} InputLabelProps={{ shrink: true }} />
|
|
|
|
|
<TextField label="Job URL" value={jobUrl} onChange={(e) => setJobUrl(e.target.value)} />
|
|
|
|
|
</Box>
|
|
|
|
|
</Paper>
|
|
|
|
|
|
|
|
|
|
<Paper variant="outlined" sx={{ p: 2 }}>
|
|
|
|
|
<Typography variant="overline" sx={{ color: "text.secondary" }}>Status update</Typography>
|
|
|
|
|
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 2, mt: 1 }}>
|
|
|
|
|
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr 1fr" }, gap: 2, mt: 1 }}>
|
|
|
|
|
<TextField select label="Current status" value={status} onChange={(e) => setStatus(e.target.value)}>
|
|
|
|
|
{STATUS_OPTIONS.map((s) => (
|
|
|
|
|
<MenuItem key={s} value={s}>{s}</MenuItem>
|
|
|
|
|
))}
|
|
|
|
|
{STATUS_OPTIONS.map((s) => <MenuItem key={s} value={s}>{s}</MenuItem>)}
|
|
|
|
|
</TextField>
|
|
|
|
|
<TextField
|
|
|
|
|
label="Status changed on"
|
|
|
|
|
type="date"
|
|
|
|
|
value={statusChangedAt}
|
|
|
|
|
onChange={(e) => setStatusChangedAt(e.target.value)}
|
|
|
|
|
InputLabelProps={{ shrink: true }}
|
|
|
|
|
helperText={status === initialStatus ? "Only used when you change the status." : "This date will be recorded in the timeline."}
|
|
|
|
|
/>
|
|
|
|
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
|
|
|
|
<FormControlLabel
|
|
|
|
|
control={<Checkbox checked={responseReceived} onChange={(e) => setResponseReceived(e.target.checked)} />}
|
|
|
|
|
label="Reply received"
|
|
|
|
|
/>
|
|
|
|
|
</Box>
|
|
|
|
|
<TextField
|
|
|
|
|
label="Reply received on"
|
|
|
|
|
type="date"
|
|
|
|
|
disabled={!responseReceived}
|
|
|
|
|
value={responseDate}
|
|
|
|
|
onChange={(e) => setResponseDate(e.target.value)}
|
|
|
|
|
InputLabelProps={{ shrink: true }}
|
|
|
|
|
/>
|
|
|
|
|
<TextField label="Status changed on" type="date" value={statusChangedAt} onChange={(e) => setStatusChangedAt(e.target.value)} InputLabelProps={{ shrink: true }} helperText={status === initialStatus ? "Only used when you change the status." : "This date will be recorded in the timeline."} />
|
|
|
|
|
<Box sx={{ display: "flex", alignItems: "center" }}><FormControlLabel control={<Checkbox checked={responseReceived} onChange={(e) => setResponseReceived(e.target.checked)} />} label="Reply received" /></Box>
|
|
|
|
|
<TextField label="Reply received on" type="date" disabled={!responseReceived} value={responseDate} onChange={(e) => setResponseDate(e.target.value)} InputLabelProps={{ shrink: true }} />
|
|
|
|
|
<TextField label="Next action" value={nextAction} onChange={(e) => setNextAction(e.target.value)} />
|
|
|
|
|
<TextField
|
|
|
|
|
label="Follow up on"
|
|
|
|
|
type="date"
|
|
|
|
|
value={followUpAt}
|
|
|
|
|
onChange={(e) => setFollowUpAt(e.target.value)}
|
|
|
|
|
InputLabelProps={{ shrink: true }}
|
|
|
|
|
/>
|
|
|
|
|
<TextField label="Follow up on" type="date" value={followUpAt} onChange={(e) => setFollowUpAt(e.target.value)} InputLabelProps={{ shrink: true }} />
|
|
|
|
|
</Box>
|
|
|
|
|
</Paper>
|
|
|
|
|
|
|
|
|
|
<Paper variant="outlined" sx={{ p: 2 }}>
|
|
|
|
|
<Typography variant="overline" sx={{ color: "text.secondary" }}>Role details</Typography>
|
|
|
|
|
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 2, mt: 1 }}>
|
|
|
|
|
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 2, mt: 1 }}>
|
|
|
|
|
<TextField label="Location" value={location} onChange={(e) => setLocation(e.target.value)} />
|
|
|
|
|
<TextField label="Salary" value={salary} onChange={(e) => setSalary(e.target.value)} />
|
|
|
|
|
<TextField
|
|
|
|
|
label="Deadline"
|
|
|
|
|
type="date"
|
|
|
|
|
value={deadline}
|
|
|
|
|
onChange={(e) => setDeadline(e.target.value)}
|
|
|
|
|
InputLabelProps={{ shrink: true }}
|
|
|
|
|
/>
|
|
|
|
|
<TextField label="Deadline" type="date" value={deadline} onChange={(e) => setDeadline(e.target.value)} InputLabelProps={{ shrink: true }} />
|
|
|
|
|
<TextField label="Description language" value={descriptionLanguage} onChange={(e) => setDescriptionLanguage(e.target.value)} />
|
|
|
|
|
<Box sx={{ gridColumn: "1 / -1" }}>
|
|
|
|
|
<TagsInput value={tags} onChange={setTags} />
|
|
|
|
|
</Box>
|
|
|
|
|
<TextField label="Notes" value={notes} onChange={(e) => setNotes(e.target.value)} multiline rows={4} sx={{ gridColumn: "1 / -1" }} />
|
|
|
|
|
<TextField label="Description (original)" value={description} onChange={(e) => setDescription(e.target.value)} multiline rows={6} sx={{ gridColumn: "1 / -1" }} />
|
|
|
|
|
<TextField label="Translated description" value={translatedDescription} onChange={(e) => setTranslatedDescription(e.target.value)} multiline rows={6} sx={{ gridColumn: "1 / -1" }} />
|
|
|
|
|
<TextField label="Cover letter" value={coverLetterText} onChange={(e) => setCoverLetterText(e.target.value)} multiline rows={6} sx={{ gridColumn: "1 / -1" }} />
|
|
|
|
|
<Box sx={{ gridColumn: "1 / -1" }}><TagsInput value={tags} onChange={setTags} /></Box>
|
|
|
|
|
<TextField label="Notes" value={notes} onChange={(e) => setNotes(e.target.value)} multiline rows={4} helperText={`${notes.length} characters`} sx={{ gridColumn: "1 / -1" }} />
|
|
|
|
|
<TextField label="Description (original)" value={description} onChange={(e) => setDescription(e.target.value)} multiline rows={6} helperText={`${description.length} characters`} sx={{ gridColumn: "1 / -1" }} />
|
|
|
|
|
<TextField label="Translated description" value={translatedDescription} onChange={(e) => setTranslatedDescription(e.target.value)} multiline rows={6} helperText={`${translatedDescription.length} characters`} sx={{ gridColumn: "1 / -1" }} />
|
|
|
|
|
<TextField label="Cover letter" value={coverLetterText} onChange={(e) => setCoverLetterText(e.target.value)} multiline rows={6} helperText={`${coverLetterText.length} characters`} sx={{ gridColumn: "1 / -1" }} />
|
|
|
|
|
</Box>
|
|
|
|
|
</Paper>
|
|
|
|
|
|
|
|
|
|
<Paper variant="outlined" sx={{ p: 2 }}>
|
|
|
|
|
<Typography variant="overline" sx={{ color: "text.secondary" }}>Attachments checklist</Typography>
|
|
|
|
|
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap", mt: 1, mb: 1.5 }}>
|
|
|
|
|
<Chip size="small" label={hasResume ? "Resume ready" : "Resume missing"} color={hasResume ? "success" : "default"} variant={hasResume ? "filled" : "outlined"} />
|
|
|
|
|
<Chip size="small" label={hasCoverLetter ? "Cover letter ready" : "Cover letter missing"} color={hasCoverLetter ? "success" : "default"} variant={hasCoverLetter ? "filled" : "outlined"} />
|
|
|
|
|
<Chip size="small" label={hasPortfolio ? "Portfolio ready" : "Portfolio optional"} color={hasPortfolio ? "success" : "default"} variant={hasPortfolio ? "filled" : "outlined"} />
|
|
|
|
|
</Box>
|
|
|
|
|
<Box sx={{ display: "flex", gap: 2, flexWrap: "wrap", mt: 1 }}>
|
|
|
|
|
<FormControlLabel control={<Checkbox checked={hasResume} onChange={(e) => setHasResume(e.target.checked)} />} label="Resume" />
|
|
|
|
|
<FormControlLabel control={<Checkbox checked={hasCoverLetter} onChange={(e) => setHasCoverLetter(e.target.checked)} />} label="Cover letter" />
|
|
|
|
|