Polish UI, harden company creation, and add error pages

This commit is contained in:
cesnimda
2026-03-23 19:34:29 +01:00
parent 8f5eab2fe4
commit fcafda6f52
38 changed files with 2293 additions and 1269 deletions
@@ -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>