fixed PmsGrid header

This commit is contained in:
pramod.mahajan 2025-10-31 09:57:28 +05:30
parent 7329319417
commit 73f437e911
5 changed files with 79 additions and 45 deletions

View File

@ -480,6 +480,24 @@ th {
border-style: solid; border-style: solid;
border-width: 0; border-width: 0;
} }
.vs-th {
position: relative;
border: none;
background-color: #f8f9fa;
padding: 0.75rem 1rem;
vertical-align: middle;
}
.vs-th::after {
content: "";
position: absolute;
left: 0;
top: 6px;
bottom: 6px;
width: 1px;
background-color: #dee2e6;
}
label { label {
display: inline-block; display: inline-block;

View File

@ -26,14 +26,18 @@ import { useNavigate } from "react-router-dom";
const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
const [deletingId, setDeletingId] = useState(null); const [deletingId, setDeletingId] = useState(null);
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const { setViewExpense, setManageExpenseModal, filterData, removeFilterChip } = useExpenseContext(); const {
setViewExpense,
setManageExpenseModal,
filterData,
removeFilterChip,
} = useExpenseContext();
const IsExpenseEditable = useHasUserPermission(); const IsExpenseEditable = useHasUserPermission();
const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE); const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const debouncedSearch = useDebounce(searchText, 500); const debouncedSearch = useDebounce(searchText, 500);
const navigate = useNavigate(); const navigate = useNavigate();
const { mutate: DeleteExpense, isPending } = useDeleteExpense(); const { mutate: DeleteExpense, isPending } = useDeleteExpense();
const { data, isLoading, isError, isInitialLoading, error } = useExpenseList( const { data, isLoading, isError, isInitialLoading, error } = useExpenseList(
ITEMS_PER_PAGE, ITEMS_PER_PAGE,
@ -80,8 +84,9 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
displayField = "Status"; displayField = "Status";
break; break;
case "submittedBy": case "submittedBy":
key = `${item?.createdBy?.firstName ?? ""} ${item.createdBy?.lastName ?? "" key = `${item?.createdBy?.firstName ?? ""} ${
}`.trim(); item.createdBy?.lastName ?? ""
}`.trim();
displayField = "Submitted By"; displayField = "Submitted By";
break; break;
case "project": case "project":
@ -139,11 +144,14 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
label: "Submitted By", label: "Submitted By",
align: "text-start", align: "text-start",
getValue: (e) => getValue: (e) =>
`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? "" `${e.createdBy?.firstName ?? ""} ${
}`.trim() || "N/A", e.createdBy?.lastName ?? ""
}`.trim() || "N/A",
customRender: (e) => ( customRender: (e) => (
<div className="d-flex align-items-center cursor-pointer" <div
onClick={() => navigate(`/employee/${e.createdBy?.id}`)}> className="d-flex align-items-center cursor-pointer"
onClick={() => navigate(`/employee/${e.createdBy?.id}`)}
>
<Avatar <Avatar
size="xs" size="xs"
classAvatar="m-0" classAvatar="m-0"
@ -151,8 +159,9 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
lastName={e.createdBy?.lastName} lastName={e.createdBy?.lastName}
/> />
<span className="text-truncate"> <span className="text-truncate">
{`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? "" {`${e.createdBy?.firstName ?? ""} ${
}`.trim() || "N/A"} e.createdBy?.lastName ?? ""
}`.trim() || "N/A"}
</span> </span>
</div> </div>
), ),
@ -176,10 +185,9 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
align: "text-center", align: "text-center",
getValue: (e) => ( getValue: (e) => (
<span <span
className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary" className={`badge bg-label-${
}`} getColorNameFromHex(e?.status?.color) || "secondary"
className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary" }`}
}`}
> >
{e.status?.name || "Unknown"} {e.status?.name || "Unknown"}
</span> </span>

View File

@ -107,8 +107,8 @@ const router = createBrowserRouter(
{ path: "/tenant/self", element: <SelfTenantDetails /> }, { path: "/tenant/self", element: <SelfTenantDetails /> },
{ path: "/organizations", element: <OrganizationPage /> }, { path: "/organizations", element: <OrganizationPage /> },
{ path: "/help/support", element: <Support /> }, { path: "/help/support", element: <Support /> },
{ path: "/help/docs", element: <Documentation /> }, { path: "/help/docs", element: <DemoBOQGrid /> },
{ path: "/help/connect", element: <DemoBOQGrid /> },
], ],
}, },
], ],

View File

@ -190,7 +190,6 @@ const boqColumns = [
*/ */
export default function DemoBOQGrid() { export default function DemoBOQGrid() {
useEffect(() => { useEffect(() => {
// 🔥 Initialize Bootstrap popovers after first render
initPopover(); initPopover();
}, []); }, []);
const wrapperRef = useRef() const wrapperRef = useRef()
@ -204,15 +203,7 @@ useEffect(() => {
}, []); }, []);
return ( return (
<div className="container-fluid py-3"> <div className="container-fluid py-3">
<button
type="button"
className="btn btn-outline-primary"
data-bs-toggle="popover"
data-bs-placement="right"
data-bs-content="This is a popover via CDN!"
>
Click me
</button>
<div className="card p-3"> <div className="card p-3">
<PmsGrid <PmsGrid

View File

@ -135,16 +135,20 @@ export default function PmsGrid({
return ( return (
<div className="pms-grid"> <div className="pms-grid">
<div className="d-flex justify-content-between mb-2"> <div className="row mb-2">
<div className="d-flex gap-2 align-items-center"> <div className="col-8">
{features.search && ( <div className="d-flex flex-row gap-2 gap-2 ">
<div>
{features.search && (
<input <input
type="search"
className="form-control form-control-sm" className="form-control form-control-sm"
placeholder="Search..." placeholder="Search..."
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
/> />
)} )}
</div>
{features.export && ( {features.export && (
<button <button
className="btn btn-sm btn-outline-secondary" className="btn btn-sm btn-outline-secondary"
@ -159,8 +163,9 @@ export default function PmsGrid({
</button> </button>
)} )}
</div> </div>
</div>
<div className="d-flex gap-2"> <div className="col-4 ">
<div className="d-flex justify-content-end gap-2">
{features.columnVisibility && ( {features.columnVisibility && (
<ColumnVisibilityPanel <ColumnVisibilityPanel
columns={colState} columns={colState}
@ -186,15 +191,16 @@ export default function PmsGrid({
</select> </select>
)} )}
</div> </div>
</div>
</div> </div>
<div <div
ref={wrapperRef} ref={wrapperRef}
className="grid-wrapper" className="grid-wrapper text-nowrap"
style={{ maxHeight: features.maxHeight || "60vh" }} style={{ maxHeight: features.maxHeight || "60vh" }}
> >
<table className="table table-sm table-bordered mb-0"> <table className="table table-sm mb-0">
<thead <thead
className="table-light" className="bg-light-secondary"
style={{ position: "sticky", top: 0, zIndex: 3 }} style={{ position: "sticky", top: 0, zIndex: 3 }}
> >
<tr> <tr>
@ -202,7 +208,7 @@ export default function PmsGrid({
<th style={{ width: 32 }} className="text-center"> <th style={{ width: 32 }} className="text-center">
<input <input
type="checkbox" type="checkbox"
className="form-check-input" className="form-check-input mx-3"
checked={ checked={
currentRows.length > 0 && currentRows.length > 0 &&
currentRows.every((r) => selected.has(r[rowKey])) currentRows.every((r) => selected.has(r[rowKey]))
@ -232,19 +238,25 @@ export default function PmsGrid({
onDragStart={(e) => onDragStart(e, col.key)} onDragStart={(e) => onDragStart(e, col.key)}
onDragOver={(e) => e.preventDefault()} onDragOver={(e) => e.preventDefault()}
onDrop={(e) => onDrop(e, col.key)} onDrop={(e) => onDrop(e, col.key)}
className={`pms-col-header ${col.pinned ? "pinned" : ""}`} className={`pms-col-header vs-th ${
col.pinned ? "pinned" : ""
}`}
style={style} style={style}
> >
<div className="d-flex align-items-center justify-content-between"> <div className="d-flex align-items-center justify-content-between px-1">
<div <div
onClick={() => col.sortable && changeSort(col.key)} onClick={() => col.sortable && changeSort(col.key)}
style={{ cursor: col.sortable ? "pointer" : "default" }} style={{ cursor: col.sortable ? "pointer" : "default" }}
> >
<strong>{col.title}</strong> <strong>{col.title}</strong>
{sortBy.key === col.key && ( {sortBy.key === col.key && (
<small className="ms-2 text-muted"> <i
[{sortBy.dir}] className={`bx bx-sm ${
</small> sortBy.dir === "asc"
? "bxs-chevron-up"
: "bxs-chevron-down"
}`}
></i>
)} )}
</div> </div>
<div className="d-flex align-items-center gap-1"> <div className="d-flex align-items-center gap-1">
@ -270,7 +282,7 @@ export default function PmsGrid({
})} })}
{features.actions && ( {features.actions && (
<th <th
className="text-center sticky-action-column" className="text-center sticky-action-column vs-th bg-white"
style={{ position: "sticky", right: 0, zIndex: 5 }} style={{ position: "sticky", right: 0, zIndex: 5 }}
> >
Actions Actions
@ -357,12 +369,12 @@ export default function PmsGrid({
function renderRow(row) { function renderRow(row) {
return ( return (
<React.Fragment key={row[rowKey]}> <React.Fragment key={row[rowKey]}>
<tr> <tr className={`${selected.has(row[rowKey]) ? "bg-light" : ""}`}>
{features.selection && ( {features.selection && (
<td className="text-center p-2"> <td className="text-center p-2">
<input <input
type="checkbox" type="checkbox"
className="form-check-input" className="form-check-input"
checked={selected.has(row[rowKey])} checked={selected.has(row[rowKey])}
onChange={() => toggleSelect(row[rowKey])} onChange={() => toggleSelect(row[rowKey])}
/> />
@ -379,13 +391,17 @@ export default function PmsGrid({
if (col.pinned === "right") if (col.pinned === "right")
style.right = `${getRightOffset(colState, col.key)}px`; style.right = `${getRightOffset(colState, col.key)}px`;
return ( return (
<td key={col.key} style={style}> <td
key={col.key}
style={style}
className={col.pinned ? "bg-white" : ""}
>
{col.render ? col.render(row) : row[col.key] ?? ""} {col.render ? col.render(row) : row[col.key] ?? ""}
</td> </td>
); );
})} })}
{features.actions && ( {features.actions && (
<td className="text-center sticky-action-column"> <td className="text-center sticky-action-column bg-white">
{features.actions(row, toggleExpand)} {features.actions(row, toggleExpand)}
</td> </td>
)} )}
@ -461,3 +477,4 @@ function ColumnVisibilityPanel({ columns, onToggle }) {
</div> </div>
); );
} }