This commit is contained in:
npalOmega 2026-01-13 12:03:46 -06:00
parent ff2eacaba4
commit 3f3dcc70f6
6 changed files with 244 additions and 128 deletions

View file

@ -305,3 +305,57 @@ body {
.contactTable tbody tr:nth-child(even) { .contactTable tbody tr:nth-child(even) {
background: #fbfbfb; background: #fbfbfb;
} }
.controlsTop {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
}
.leftControls {
display: flex;
align-items: center;
}
.filter {
display: flex;
align-items: center;
}
.filter select {
height: 34px;
border: 1px solid #ccc;
border-right: none;
border-radius: 4px 0 0 4px;
padding: 6px 8px;
background: #fff;
}
.filter input {
height: 34px;
width: 260px;
border: 1px solid #ccc;
border-left: none;
border-right: none;
padding: 6px 10px;
}
.searchBtn {
height: 34px;
padding: 0 14px;
border: 1px solid #0073aa;
background-color: #0073aa;
color: #fff;
border-radius: 0 4px 4px 0;
cursor: pointer;
}
.searchBtn:hover {
background-color: #005f8d;
}
.rightControls {
display: flex;
align-items: center;
}

View file

@ -1,52 +1,100 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { fetchAccountsPaged, deleteAccount } from "./api"; import { fetchAccountsPaged, deleteAccount } from "./api";
import { useNavigate } from "react-router";
import "./styles.css";
export default function AccountList() { export default function AccountList() {
const [data, setData] = useState([]); const navigate = useNavigate();
const [rows, setRows] = useState([]);
const [search, setSearch] = useState("");
const [loading, setLoading] = useState(true);
const load = async () => {
setLoading(true);
const res = await fetchAccountsPaged({ search });
setRows(res?.rows ?? []);
setLoading(false);
};
useEffect(() => { useEffect(() => {
load(); load();
}, []); }, [search]);
const load = async () => { const handleDelete = async (id) => {
const res = await fetchAccountsPaged({}); if (!window.confirm("Delete this account?")) return;
setData(res.rows); await deleteAccount(id);
load();
}; };
return ( return (
<div> <section className="contactsPage">
<h2>Accounts</h2> <div className="pageTitle">Accounts</div>
<button onClick={() => (window.location.href = "/accounts/new")}> <div className="card controlsCard">
+ Add Account <div className="controlsTop">
</button> <div className="leftControls">
<div className="filter">
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Enter search criteria here..."
/>
<button className="searchBtn">Search!</button>
</div>
</div>
<table className="table"> <div className="rightControls">
<thead> <button
<tr> className="iconSmall"
<th>Name</th> title="New Account"
<th>Industry</th> onClick={() => navigate("/accounts/new")}
<th>Phone</th> >
<th></th> +
</tr> </button>
</thead> </div>
</div>
<tbody> {loading ? (
{data.map((x) => ( <div style={{ padding: 12 }}>Loading accounts...</div>
<tr key={x.id}> ) : (
<td>{x.name}</td> <table className="contactTable">
<td>{x.industry}</td> <thead>
<td>{x.phone}</td> <tr>
<td> <th>Name</th>
<button onClick={() => (window.location.href = `/accounts/${x.id}/edit`)}> <th>Industry</th>
Edit <th>Phone</th>
</button> <th></th>
<button onClick={() => deleteAccount(x.id)}>Delete</button> </tr>
</td> </thead>
</tr>
))} <tbody>
</tbody> {rows.map((x) => (
</table> <tr key={x.id}>
</div> <td>{x.name}</td>
<td>{x.industry}</td>
<td>{x.phoneNumber}</td>
<td className="iconActions">
<button
className="iconBtn"
title="Edit"
onClick={() => navigate(`/accounts/${x.id}/edit`)}
>
✏️
</button>
<button
className="iconBtnDanger"
title="Delete"
onClick={() => handleDelete(x.id)}
>
🗑
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</section>
); );
} }

View file

@ -12,23 +12,25 @@ export const fetchAccountsPaged = async ({
)}&page=${page}&pageSize=${pageSize}` )}&page=${page}&pageSize=${pageSize}`
); );
const rows = (res?.data || res?.Data || []).map((x) => ({ const data = res?.data || res?.Data || [];
id: x.Id,
name: x.Name ?? x.AccountName ?? "", const rows = data.map((x) => ({
number: x.AccountNumber ?? "", id: x.id ?? x.Id,
industry: x.Industry ?? "", name: x.name ?? x.Name ?? x.AccountName ?? "",
website: x.Website ?? "", number: x.number ?? x.AccountNumber ?? "",
primaryContact: x.PrimaryContact ?? "", industry: x.industry ?? x.Industry ?? "",
phoneNumber: x.Phone ?? x.PhoneNumber ?? "", website: x.website ?? x.Website ?? "",
owner: x.Owner ?? "", primaryContact: x.primaryContact ?? x.PrimaryContact ?? "",
phoneNumber: x.phoneNumber ?? x.Phone ?? x.PhoneNumber ?? "",
owner: x.owner ?? x.Owner ?? "",
})); }));
return { return {
rows, rows,
page: res?.PageNumber ?? 1, page: res?.page ?? res?.PageNumber ?? page,
pageSize: res?.PageSize ?? pageSize, pageSize: res?.pageSize ?? res?.PageSize ?? pageSize,
totalCount: res?.TotalCount ?? rows.length, totalCount: res?.totalCount ?? res?.TotalCount ?? rows.length,
totalPages: res?.TotalPages ?? 1, totalPages: res?.totalPages ?? res?.TotalPages ?? 1,
}; };
}; };

View file

@ -5,20 +5,20 @@ import { API_URL } from '../../../constants';
export const fetchContactsPaged = async ({ search = "", page = 1, pageSize = 12 }) => { export const fetchContactsPaged = async ({ search = "", page = 1, pageSize = 12 }) => {
const res = await get(`${API_URL}/Contact/GetContactList?search=${encodeURIComponent(search)}&page=${page}&pageSize=${pageSize}`); const res = await get(`${API_URL}/Contact/GetContactList?search=${encodeURIComponent(search)}&page=${page}&pageSize=${pageSize}`);
const rows = (res?.data || res?.Data || []).map(x => ({ const rows = (res?.data || res?.Data || []).map(x => ({
id: x.Id, id: x.id,
name: x.Name, name: x.name,
account: x.Account ?? "", account: x.account ?? "",
title: x.Title ?? "", title: x.title ?? "",
email: x.email ?? x.Email ?? "", email: x.email ?? "",
phoneNumber: x.phone ?? x.PhoneNumber ?? "", phoneNumber: x.phone ?? "",
owner: x.Owner ?? "", owner: x.owner ?? "",
})); }));
return { return {
rows, rows,
page: res?.PageNumber ?? 1, page: res?.pageNumber ?? 1,
pageSize: res?.PageSize ?? pageSize, pageSize: res?.pageSize ?? pageSize,
totalCount: res?.TotalCount ?? rows.length, totalCount: res?.totalCount ?? rows.length,
totalPages: res?.TotalPages ?? 1, totalPages: res?.totalPages ?? 1,
}; };
}; };

View file

@ -1,18 +1,18 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { fetchEmployeesPaged, deleteEmployee } from "./api"; import { fetchEmployeesPaged, deleteEmployee } from "./api";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { toast } from "react-toastify"; import "./styles.css";
export default function EmployeeList() { export default function EmployeeList() {
const navigate = useNavigate(); const navigate = useNavigate();
const [loading, setLoading] = useState(true);
const [rows, setRows] = useState([]); const [rows, setRows] = useState([]);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [loading, setLoading] = useState(true);
const load = async () => { const load = async () => {
setLoading(true); setLoading(true);
const res = await fetchEmployeesPaged({ search }); const res = await fetchEmployeesPaged({ search });
setRows(res.rows); setRows(res?.rows ?? []);
setLoading(false); setLoading(false);
}; };
@ -23,70 +23,82 @@ export default function EmployeeList() {
const handleDelete = async (id) => { const handleDelete = async (id) => {
if (!window.confirm("Delete this employee?")) return; if (!window.confirm("Delete this employee?")) return;
await deleteEmployee(id); await deleteEmployee(id);
toast.success("Employee deleted");
load(); load();
}; };
return ( return (
<div className="pageWrapper"> <section className="contactsPage">
<div className="pageHeader"> <div className="pageTitle">Employees</div>
<h2>Employees</h2>
<button onClick={() => navigate("/employees/new")} className="btnPrimary">
+ New Employee
</button>
</div>
<input <div className="card controlsCard">
className="searchBoxList" <div className="controlsTop">
placeholder="Search employees..." <div className="leftControls">
value={search} <div className="filter">
onChange={(e) => setSearch(e.target.value)} <input
/> value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Enter search criteria here..."
/>
<button className="searchBtn">Search!</button>
</div>
</div>
{loading ? ( <div className="rightControls">
<p>Loading employees...</p> <button
) : ( className="iconSmall"
<table className="dataTable"> title="New Employee"
<thead> onClick={() => navigate("/employees/new")}
<tr> >
<th>Name</th> +
<th>Job Title</th> </button>
<th>Email</th> </div>
<th>Phone</th> </div>
<th>Department</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody> {loading ? (
{rows.map((r) => ( <div style={{ padding: 12 }}>Loading employees...</div>
<tr key={r.id}> ) : (
<td>{r.name}</td> <table className="contactTable">
<td>{r.jobTitle}</td> <thead>
<td>{r.email}</td> <tr>
<td>{r.phone}</td> <th>Name</th>
<td>{r.department}</td> <th>Job Title</th>
<td>{r.status}</td> <th>Email</th>
<td> <th>Phone</th>
<button <th>Status</th>
className="btnSmall" <th></th>
onClick={() => navigate(`/employees/${r.id}`)}
>
Edit
</button>
<button
className="btnSmallDanger"
onClick={() => handleDelete(r.id)}
>
Del
</button>
</td>
</tr> </tr>
))} </thead>
</tbody>
</table> <tbody>
)} {rows.map((r) => (
</div> <tr key={r.id}>
<td>{r.name}</td>
<td>{r.jobTitle}</td>
<td>{r.email}</td>
<td>{r.phone}</td>
<td>{r.status}</td>
<td className="iconActions">
<button
className="iconBtn"
title="Edit"
onClick={() => navigate(`/employees/${r.id}`)}
>
✏️
</button>
<button
className="iconBtnDanger"
title="Delete"
onClick={() => handleDelete(r.id)}
>
🗑
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</section>
); );
} }

View file

@ -12,14 +12,14 @@ export const fetchEmployeesPaged = async ({
)}&page=${page}&pageSize=${pageSize}` )}&page=${page}&pageSize=${pageSize}`
); );
const rows = (res?.data || res?.Data || []).map((x) => ({ const rows = (res || []).map((x) => ({
id: x.Id, id: x.id,
name: `${x.FirstName ?? ""} ${x.LastName ?? ""}`.trim(), name: `${x.name ?? ""} ${x.name ?? ""}`.trim(),
jobTitle: x.JobTitle ?? "", jobTitle: x.jobTitle ?? "",
email: x.Email ?? "", email: x.email ?? "",
phone: x.Phone ?? "", phone: x.phone ?? "",
department: x.Department ?? "", department: x.department ?? "",
status: x.Status ?? "", status: x.status ?? "",
})); }));
return { return {