First Commit
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TextField,
|
||||
Typography,
|
||||
IconButton,
|
||||
} from "@mui/material";
|
||||
|
||||
import { api } from "../api";
|
||||
import { Company } from "../types";
|
||||
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
|
||||
import { useToast } from "../toast";
|
||||
|
||||
export default function CompaniesTable() {
|
||||
const { toast } = useToast();
|
||||
const [companies, setCompanies] = useState<Company[]>([]);
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<Company | null>(null);
|
||||
|
||||
const [recruiterName, setRecruiterName] = useState("");
|
||||
const [recruiterEmail, setRecruiterEmail] = useState("");
|
||||
const [recruiterLinkedIn, setRecruiterLinkedIn] = useState("");
|
||||
const [pipelineStage, setPipelineStage] = useState("");
|
||||
const [lastContactedAt, setLastContactedAt] = useState("");
|
||||
const [nextContactAt, setNextContactAt] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
api.get<Company[]>("/companies").then((r) => setCompanies(r.data));
|
||||
}, []);
|
||||
|
||||
const openEdit = (c: Company) => {
|
||||
setEditing(c);
|
||||
setRecruiterName(c.recruiterName ?? "");
|
||||
setRecruiterEmail(c.recruiterEmail ?? "");
|
||||
setRecruiterLinkedIn(c.recruiterLinkedIn ?? "");
|
||||
setPipelineStage(c.pipelineStage ?? "");
|
||||
setLastContactedAt((c.lastContactedAt ?? "").slice(0, 10));
|
||||
setNextContactAt((c.nextContactAt ?? "").slice(0, 10));
|
||||
setEditOpen(true);
|
||||
};
|
||||
|
||||
const canSave = useMemo(() => !!editing?.id, [editing]);
|
||||
|
||||
const save = async () => {
|
||||
if (!editing?.id) return;
|
||||
try {
|
||||
const res = await api.put<Company>(`/companies/${editing.id}`, {
|
||||
name: editing.name,
|
||||
location: editing.location ?? null,
|
||||
source: editing.source ?? null,
|
||||
recruiterName: recruiterName.trim() || null,
|
||||
recruiterEmail: recruiterEmail.trim() || null,
|
||||
recruiterLinkedIn: recruiterLinkedIn.trim() || null,
|
||||
pipelineStage: pipelineStage.trim() || null,
|
||||
lastContactedAt: lastContactedAt || null,
|
||||
nextContactAt: nextContactAt || null,
|
||||
});
|
||||
|
||||
setCompanies((prev) => prev.map((x) => (x.id === res.data.id ? res.data : x)));
|
||||
toast("Company updated.", "success");
|
||||
setEditOpen(false);
|
||||
setEditing(null);
|
||||
} catch {
|
||||
toast("Failed to update company.", "error");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper sx={{ mt: 0 }}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Location</TableCell>
|
||||
<TableCell>Source</TableCell>
|
||||
<TableCell>Pipeline</TableCell>
|
||||
<TableCell>Recruiter</TableCell>
|
||||
<TableCell>Next Contact</TableCell>
|
||||
<TableCell width={1} align="right" />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{companies.map((c) => (
|
||||
<TableRow key={c.id}>
|
||||
<TableCell>{c.name}</TableCell>
|
||||
<TableCell>{c.location ?? ""}</TableCell>
|
||||
<TableCell>{c.source ?? ""}</TableCell>
|
||||
<TableCell>{c.pipelineStage ?? ""}</TableCell>
|
||||
<TableCell>
|
||||
{c.recruiterName ?? ""}
|
||||
{c.recruiterEmail ? ` (${c.recruiterEmail})` : ""}
|
||||
</TableCell>
|
||||
<TableCell>{c.nextContactAt ? new Date(c.nextContactAt).toLocaleDateString() : ""}</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton size="small" onClick={() => openEdit(c)}>
|
||||
<EditOutlinedIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{companies.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7}>
|
||||
<Typography sx={{ py: 2, textAlign: "center" }}>
|
||||
No companies yet.
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<Dialog open={editOpen} onClose={() => setEditOpen(false)} fullWidth maxWidth="sm">
|
||||
<DialogTitle>Edit Company</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 2, mt: 1 }}>
|
||||
<TextField
|
||||
label="Name"
|
||||
value={editing?.name ?? ""}
|
||||
onChange={(e) => setEditing((p) => (p ? { ...p, name: e.target.value } : p))}
|
||||
sx={{ gridColumn: "1 / -1" }}
|
||||
/>
|
||||
<TextField
|
||||
label="Location"
|
||||
value={editing?.location ?? ""}
|
||||
onChange={(e) => setEditing((p) => (p ? { ...p, location: e.target.value } : p))}
|
||||
/>
|
||||
<TextField
|
||||
label="Source"
|
||||
value={editing?.source ?? ""}
|
||||
onChange={(e) => setEditing((p) => (p ? { ...p, source: e.target.value } : p))}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label="Pipeline stage"
|
||||
value={pipelineStage}
|
||||
onChange={(e) => setPipelineStage(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
label="Recruiter name"
|
||||
value={recruiterName}
|
||||
onChange={(e) => setRecruiterName(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
label="Recruiter email"
|
||||
value={recruiterEmail}
|
||||
onChange={(e) => setRecruiterEmail(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
label="Recruiter LinkedIn"
|
||||
value={recruiterLinkedIn}
|
||||
onChange={(e) => setRecruiterLinkedIn(e.target.value)}
|
||||
sx={{ gridColumn: "1 / -1" }}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label="Last contacted"
|
||||
type="date"
|
||||
value={lastContactedAt}
|
||||
onChange={(e) => setLastContactedAt(e.target.value)}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
<TextField
|
||||
label="Next contact"
|
||||
type="date"
|
||||
value={nextContactAt}
|
||||
onChange={(e) => setNextContactAt(e.target.value)}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setEditOpen(false)}>Cancel</Button>
|
||||
<Button variant="contained" onClick={save} disabled={!canSave}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user