Polish UI, harden company creation, and add error pages
This commit is contained in:
@@ -19,13 +19,15 @@ import {
|
||||
IconButton,
|
||||
} from "@mui/material";
|
||||
|
||||
import { api } from "../api";
|
||||
import { api, getApiErrorMessage } from "../api";
|
||||
import { Company } from "../types";
|
||||
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
|
||||
import { useToast } from "../toast";
|
||||
import { useI18n } from "../i18n/I18nProvider";
|
||||
|
||||
export default function CompaniesTable() {
|
||||
const { toast } = useToast();
|
||||
const { t } = useI18n();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const [companies, setCompanies] = useState<Company[]>([]);
|
||||
@@ -40,8 +42,8 @@ export default function CompaniesTable() {
|
||||
const [nextContactAt, setNextContactAt] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
api.get<Company[]>("/companies").then((r) => setCompanies(r.data));
|
||||
}, []);
|
||||
api.get<Company[]>("/companies").then((r) => setCompanies(r.data)).catch((error) => toast(getApiErrorMessage(error, t("companiesUpdateFailed")), "error"));
|
||||
}, [t, toast]);
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
@@ -83,11 +85,11 @@ export default function CompaniesTable() {
|
||||
});
|
||||
|
||||
setCompanies((prev) => prev.map((x) => (x.id === res.data.id ? res.data : x)));
|
||||
toast("Company updated.", "success");
|
||||
toast(t("companiesUpdated"), "success");
|
||||
setEditOpen(false);
|
||||
setEditing(null);
|
||||
} catch {
|
||||
toast("Failed to update company.", "error");
|
||||
} catch (error) {
|
||||
toast(getApiErrorMessage(error, t("companiesUpdateFailed")), "error");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,12 +98,12 @@ export default function CompaniesTable() {
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Location</TableCell>
|
||||
<TableCell>Source</TableCell>
|
||||
<TableCell>Pipeline</TableCell>
|
||||
<TableCell>Recruiter</TableCell>
|
||||
<TableCell>Next Contact</TableCell>
|
||||
<TableCell>{t("companiesName")}</TableCell>
|
||||
<TableCell>{t("companiesLocation")}</TableCell>
|
||||
<TableCell>{t("companiesSource")}</TableCell>
|
||||
<TableCell>{t("companiesPipeline")}</TableCell>
|
||||
<TableCell>{t("companiesRecruiter")}</TableCell>
|
||||
<TableCell>{t("companiesNextContact")}</TableCell>
|
||||
<TableCell width={1} align="right" />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
@@ -128,7 +130,7 @@ export default function CompaniesTable() {
|
||||
<TableRow>
|
||||
<TableCell colSpan={7}>
|
||||
<Typography sx={{ py: 2, textAlign: "center" }}>
|
||||
No companies yet.
|
||||
{t("companiesEmpty")}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -137,57 +139,57 @@ export default function CompaniesTable() {
|
||||
</Table>
|
||||
|
||||
<Dialog open={editOpen} onClose={() => setEditOpen(false)} fullWidth maxWidth="sm">
|
||||
<DialogTitle>Edit Company</DialogTitle>
|
||||
<DialogTitle>{t("companiesEdit")}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 2, mt: 1 }}>
|
||||
<TextField
|
||||
label="Name"
|
||||
label={t("companiesName")}
|
||||
value={editing?.name ?? ""}
|
||||
onChange={(e) => setEditing((p) => (p ? { ...p, name: e.target.value } : p))}
|
||||
sx={{ gridColumn: "1 / -1" }}
|
||||
/>
|
||||
<TextField
|
||||
label="Location"
|
||||
label={t("companiesLocation")}
|
||||
value={editing?.location ?? ""}
|
||||
onChange={(e) => setEditing((p) => (p ? { ...p, location: e.target.value } : p))}
|
||||
/>
|
||||
<TextField
|
||||
label="Source"
|
||||
label={t("companiesSource")}
|
||||
value={editing?.source ?? ""}
|
||||
onChange={(e) => setEditing((p) => (p ? { ...p, source: e.target.value } : p))}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label="Pipeline stage"
|
||||
label={t("companiesPipelineStage")}
|
||||
value={pipelineStage}
|
||||
onChange={(e) => setPipelineStage(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
label="Recruiter name"
|
||||
label={t("companiesRecruiterName")}
|
||||
value={recruiterName}
|
||||
onChange={(e) => setRecruiterName(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
label="Recruiter email"
|
||||
label={t("companiesRecruiterEmail")}
|
||||
value={recruiterEmail}
|
||||
onChange={(e) => setRecruiterEmail(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
label="Recruiter LinkedIn"
|
||||
label={t("companiesRecruiterLinkedIn")}
|
||||
value={recruiterLinkedIn}
|
||||
onChange={(e) => setRecruiterLinkedIn(e.target.value)}
|
||||
sx={{ gridColumn: "1 / -1" }}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label="Last contacted"
|
||||
label={t("companiesLastContacted")}
|
||||
type="date"
|
||||
value={lastContactedAt}
|
||||
onChange={(e) => setLastContactedAt(e.target.value)}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
<TextField
|
||||
label="Next contact"
|
||||
label={t("companiesNextContactField")}
|
||||
type="date"
|
||||
value={nextContactAt}
|
||||
onChange={(e) => setNextContactAt(e.target.value)}
|
||||
@@ -196,9 +198,9 @@ export default function CompaniesTable() {
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setEditOpen(false)}>Cancel</Button>
|
||||
<Button onClick={() => setEditOpen(false)}>{t("cancel")}</Button>
|
||||
<Button variant="contained" onClick={save} disabled={!canSave}>
|
||||
Save
|
||||
{t("save")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user