center empty state messages in employee table with spacing

This commit is contained in:
Pramod Mahajan 2025-05-28 00:05:27 +05:30
parent 24ffb82616
commit ed489842c6
2 changed files with 409 additions and 167 deletions

View File

@ -0,0 +1,147 @@
import React, { useState } from "react";
import { useSortableData } from "../../hooks/useSortableData";
const EmployeeList = ({ employees }) => {
const [selectedIds, setSelectedIds] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
const handleCheckboxChange = (id) => {
setSelectedIds((prev) =>
prev.includes(id) ? prev?.filter((empId) => empId !== id) : [...prev, id]
);
};
const getSelectedEmployees = () => {
console.log("Selected Employee IDs:", selectedIds);
};
const {
items: sortedEmployees,
requestSort,
sortConfig,
} = useSortableData(employees, {
key: (e) => `${e?.firstName} ${e?.lastName}`,
direction: "asc",
});
const getSortIcon = () => {
if (!sortConfig) return null;
return sortConfig.direction === "asc" ? (
<i className="bx bx-caret-up text-secondary"></i>
) : (
<i className="bx bx-caret-down text-secondary"></i>
);
};
const filteredEmployees = sortedEmployees?.filter((employee) => {
const fullName =
`${employee?.firstName} ${employee?.lastName}`?.toLowerCase();
// const email = employee.email.toLowerCase();
// const role = employee.jobRole.toLowerCase();
const term = searchTerm?.toLowerCase();
return fullName.includes(term);
// email.includes(term) ||
// role.includes(term)
});
return (
<>
<div className="d-flex justify-content-between mt-2">
<p className="m-0 fs-6 fw-normal">Add Employee</p>
<div className="px-1">
<input
type="search"
className="form-control form-control-sm"
placeholder="Search Employee..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</div>
<div className="table-responsive px-1 my-1 px-sm-0">
<table className="table align-middle mb-0">
<thead className="table-light">
<tr>
<th
onClick={() =>
requestSort((e) => `${e.firstName} ${e.lastName}`)
}
className="text-start cursor-pointer"
>
<span className="ps-2">Name {getSortIcon()}</span>
</th>
<th className="text-start">Role</th>
<th scope="col">Status</th>
<th className="text-start">Bucket</th>
</tr>
</thead>
<tbody>
{employees.length === 0 ? (
<tr>
<td colSpan={4} >
<div className="d-flex justify-content-center align-items-center py-5">
No Employee Available
</div>
</td>
</tr>
) : filteredEmployees.length === 0 ? (
<tr className="my-4">
<td colSpan={4}>
<div className="d-flex justify-content-center align-items-center py-5">
No Matchinng Employee Found.
</div>
</td>
</tr>
) : (
filteredEmployees?.map((employee) => (
<tr key={employee.id}>
<td>
<div className="d-flex align-items-start text-start">
<input
className="form-check-input me-3 mt-1"
type="checkbox"
checked={selectedIds.includes(employee.id)}
onChange={() => handleCheckboxChange(employee.id)}
/>
<div>
<p className="fw-semibold mb-0">
{`${employee.firstName} ${employee.lastName}`}
</p>
<small className="text-muted">{employee.email}</small>
</div>
</div>
</td>
<td className="text-start">
<small className="text-muted">{employee.jobRole}</small>
</td>
<td>
<span
className={`badge rounded-pill px-3 py-1 ${
employee.isActive
? "bg-success-subtle text-success"
: "bg-danger-subtle text-danger"
}`}
>
{employee.isActive ? "Active" : "Inactive"}
</span>
</td>
<td className="text-start">
<small className="text-muted">
<i className="fa fa-hashtag" aria-hidden="true"></i>{" "}
{employee.jobRole}
</small>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</>
);
};
export default EmployeeList;

View File

@ -9,31 +9,36 @@ import { DirectoryRepository } from "../../repositories/DirectoryRepository";
import { cacheData, getCachedData } from "../../slices/apiDataManager"; import { cacheData, getCachedData } from "../../slices/apiDataManager";
import { useBuckets } from "../../hooks/useDirectory"; import { useBuckets } from "../../hooks/useDirectory";
import EmployeeList from "./EmployeeList"; import EmployeeList from "./EmployeeList";
import {useAllEmployees, useEmployees} from "../../hooks/useEmployees"; import { useAllEmployees, useEmployees } from "../../hooks/useEmployees";
import {useSortableData} from "../../hooks/useSortableData"; import { useSortableData } from "../../hooks/useSortableData";
import ConfirmModal from "../common/ConfirmModal";
const ManageBucket = () => const ManageBucket = () => {
{ const [bucketList, setBucketList] = useState([]);
const [ bucketList, setBucketList ] = useState( [] ); const { employeesList } = useAllEmployees(false);
const {employeesList} = useAllEmployees(false) const { buckets, loading } = useBuckets();
const { buckets } = useBuckets();
const [action_bucket, setAction_bucket] = useState(false); const [action_bucket, setAction_bucket] = useState(false);
const [isSubmitting, setSubmitting] = useState(false); const [isSubmitting, setSubmitting] = useState(false);
const [ selected_bucket, select_bucket ] = useState( null ); const [selected_bucket, select_bucket] = useState(null);
const { items: sortedBuckteList, requestSort, sortConfig} = useSortableData(bucketList,{ const [deleteBucket, setDeleteBucket] = useState(null);
const [searchTerm, setSearchTerm] = useState("");
const {
items: sortedBuckteList,
requestSort,
sortConfig,
} = useSortableData(bucketList, {
key: (e) => `${e.name}`, key: (e) => `${e.name}`,
direction: 'asc', direction: "asc",
} ) });
const getSortIcon = () => { const getSortIcon = () => {
if (!sortConfig) return null; if (!sortConfig) return null;
return sortConfig.direction === 'asc' ? ( return sortConfig.direction === "asc" ? (
<i className="bx bx-caret-up text-secondary"></i> <i className="bx bx-caret-up text-secondary"></i>
) : ( ) : (
<i className="bx bx-caret-down text-secondary"></i> <i className="bx bx-caret-down text-secondary"></i>
); );
}; };
const { const {
register, register,
handleSubmit, handleSubmit,
@ -45,188 +50,278 @@ const ManageBucket = () =>
name: "", name: "",
description: "", description: "",
}, },
} ); });
const onSubmit = async (data) => {
setSubmitting(true);
try {
let response;
if ( selected_bucket ) const onSubmit = async (data) => {
{ setSubmitting(true);
let payload ={...data, id:selected_bucket.id} try {
response = await DirectoryRepository.UpdateBuckets(selected_bucket.id, payload); let response;
const cache_buckets = getCachedData("buckets") || [];
const updatedBuckets = cache_buckets.map((bucket) => if (selected_bucket) {
bucket.id === selected_bucket.id ? response?.data : bucket let payload = { ...data, id: selected_bucket.id };
); response = await DirectoryRepository.UpdateBuckets(
cacheData( "buckets", updatedBuckets ); selected_bucket.id,
setBucketList(updatedBuckets); payload
showToast("Bucket Updated Successfully", "success"); );
} else { const cache_buckets = getCachedData("buckets") || [];
response = await DirectoryRepository.CreateBuckets(data); const updatedBuckets = cache_buckets.map((bucket) =>
const cache_buckets = getCachedData("buckets") || []; bucket.id === selected_bucket.id ? response?.data : bucket
const updatedBuckets = [...cache_buckets, response?.data]; );
cacheData( "buckets", updatedBuckets ); cacheData("buckets", updatedBuckets);
setBucketList(updatedBuckets); setBucketList(updatedBuckets);
showToast("Bucket Created Successfully", "success"); showToast("Bucket Updated Successfully", "success");
} else {
response = await DirectoryRepository.CreateBuckets(data);
const cache_buckets = getCachedData("buckets") || [];
const updatedBuckets = [...cache_buckets, response?.data];
cacheData("buckets", updatedBuckets);
setBucketList(updatedBuckets);
showToast("Bucket Created Successfully", "success");
}
handleBack();
} catch (error) {
const message =
error?.response?.data?.message ||
error?.message ||
"Error occurred during API call";
showToast(message, "error");
} }
};
handleBack(); const handleDeleteContact = async () => {
} catch (error) { try {
const message = // delete api calling here
error?.response?.data?.message || showToast("Bucket deleted successfully", "success");
error?.message || setDeleteBucket(null);
"Error occurred during API call"; } catch (error) {
showToast(message, "error"); const message =
} error?.response?.data?.message ||
}; error?.message ||
"Error occurred during API call.";
showToast(message, "error");
}
};
useEffect(() => { useEffect(() => {
reset({ reset({
name: selected_bucket?.name || "", name: selected_bucket?.name || "",
description: selected_bucket?.description || "", description: selected_bucket?.description || "",
}); });
}, [ selected_bucket ] ); }, [selected_bucket]);
useEffect(() => {
setBucketList(buckets);
}, [buckets]);
useEffect( () =>
{
setBucketList( buckets )
}, [ buckets ] )
const handleBack = () => { const handleBack = () => {
select_bucket(null); select_bucket(null);
setAction_bucket(false); setAction_bucket(false);
setSubmitting(false); setSubmitting(false);
}; };
const sortedBucktesList = sortedBuckteList?.filter((bucket) => {
const term = searchTerm?.toLowerCase();
const name = bucket.name?.toLowerCase();
return name?.includes(term);
});
return ( return (
<div className="container m-0 p-0" style={{ minHeight: "200px" }}> <>
<div className="d-flex justify-content-center"> {deleteBucket && (
<p className="fs-6 fw-semibold m-0">Manage Buckets</p> <div
</div> className={`modal fade ${deleteBucket ? "show" : ""}`}
<div className="d-flex justify-content-between px-2 px-sm-0 mt-5 mt-sm-1 align-items-center"> tabIndex="-1"
<i role="dialog"
className={`fa-solid fa-arrow-left fs-5 cursor-pointer ${ style={{
action_bucket ? "" : "d-none" display: deleteBucket ? "block" : "none",
}`} backgroundColor: deleteBucket ? "rgba(0,0,0,0.5)" : "transparent",
onClick={handleBack} }}
></i>
<button
type="button"
className={`btn btn-sm btn-primary ms-auto ${
action_bucket ? "d-none" : ""
}`}
onClick={() => setAction_bucket(true)}
> >
<i className="bx bx-plus-circle me-2"></i> <ConfirmModal
Add Bucket type={"delete"}
</button> header={"Delete Bucket"}
</div> message={"Are you sure you want delete?"}
<div> onSubmit={handleDeleteContact}
{!action_bucket ? ( onClose={() => setDeleteBucket(null)}
<div className="table-responsive text-nowrap pt-1 px-2 px-sm-0"> // loading={IsDeleting}
<table className="table px-2"> />
<thead className="p-0 table-light"> </div>
<tr className="p-0"> )}
<th colSpan={2} className="cursor-pointer" onClick={() => requestSort((e) => `${e.name} `)}>
<div className="d-flex justify-content-start align-items-center gap-1 mx-2">
<span>Name {getSortIcon()}</span>
</div>
</th>
<th className="text-start d-none d-sm-table-cell">
<div className="d-flex align-items-center justify-content-center gap-1">
<span>Description</span>
</div>
</th>
<th>
<div className="d-flex align-items-center justify-content-center gap-1">
<span>Action</span>
</div>
</th>
</tr>
</thead>
<tbody className="table-border-bottom-0 overflow-auto"> <div className="container m-0 p-0" style={{ minHeight: "200px" }}>
{sortedBuckteList.map((bucket) => ( <div className="d-flex justify-content-center">
<tr key={bucket.id}> <p className="fs-6 fw-semibold m-0">Manage Buckets</p>
</div>
<td colSpan={2} className="text-start text-wrap" > <div className="d-flex justify-content-between px-2 px-sm-0 mt-5 mt-sm-1 align-items-center">
<i className="bx bx-right-arrow-alt me-1"></i> {bucket.name} {action_bucket ? (
</td> <i
<td className="text-start d-none d-sm-table-cell text-wrap" style={{width:"60%"}}> className={`fa-solid fa-arrow-left fs-5 cursor-pointer`}
{bucket.description} onClick={handleBack}
</td> ></i>
<td className="justify-content-center"> ) : (
<div className="d-flex justify-content-center align-items-center gap-2"> <div>
<i
className="bx bx-edit bx-sm text-primary cursor-pointer"
onClick={() => {
select_bucket(bucket);
setAction_bucket(true);
}}
></i>
<i className="bx bx-trash bx-sm text-danger cursor-pointer"></i>
<i className="bx bx-user-plus cursor-pointer"></i>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<>
<form onSubmit={handleSubmit(onSubmit)} className="px-2 px-sm-0">
<div className="">
<label className="form-label">Bucket Name</label>
<input <input
type="search"
className="form-control form-control-sm" className="form-control form-control-sm"
{...register("name")} placeholder="Search Bucket ..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/> />
{errors.name && (
<small className="danger-text">{errors.name.message}</small>
)}
</div> </div>
<div className=""> )}
<label className="form-label">Bucket Discription</label>
<textarea <button
className="form-control form-control-sm" type="button"
rows="3" className={`btn btn-sm btn-primary ms-auto ${
{...register("description")} action_bucket ? "d-none" : ""
/> }`}
{errors.description && ( onClick={() => setAction_bucket(true)}
<small className="danger-text"> >
{errors.description.message} <i className="bx bx-plus-circle me-2"></i>
</small> Add Bucket
)} </button>
</div> </div>
<div className="mt-2 d-flex justify-content-center gap-3"> <div>
<button {!action_bucket ? (
type="reset" <div className="table-responsive text-nowrap pt-1 px-2 px-sm-0">
className="btn btn-sm btn-secondary" <table className="table px-2">
disabled={isSubmitting} <thead className="p-0 table-light">
> <tr className="p-0">
Cancel <th
</button> colSpan={2}
<button className="cursor-pointer"
type="submit" onClick={() => requestSort((e) => `${e.name} `)}
className="btn btn-sm btn-primary" >
disabled={isSubmitting} <div className="d-flex justify-content-start align-items-center gap-1 mx-2">
> <span>Name {getSortIcon()}</span>
{isSubmitting ? "Please wait..." : "Submit"} </div>
</button> </th>
<th className="text-start d-none d-sm-table-cell">
<div className="d-flex align-items-center justify-content-center gap-1">
<span>Description</span>
</div>
</th>
<th>
<div className="d-flex align-items-center justify-content-center gap-1">
<span>Action</span>
</div>
</th>
</tr>
</thead>
<tbody className="table-border-bottom-0 overflow-auto">
{loading && (
<tr className="mt-10">
<td colSpan={4}>
{" "}
<div className="d-flex justify-content-center align-items-center py-5">
Loading...
</div>
</td>
</tr>
)}
{!loading && buckets.length == 0 && (
<tr>
<td colSpan={4}>
<div className="d-flex justify-content-center align-items-center py-5">
Bucket Not Available.
</div>
</td>
</tr>
)}
{!loading && sortedBucktesList.length == 0 && (
<tr>
<td className="text-center py-4 h-25" colSpan={4}>
<div className="d-flex justify-content-center align-items-center py-5">
No Matching Bucket Found.
</div>
</td>
</tr>
)}
{sortedBucktesList.map((bucket) => (
<tr key={bucket.id}>
<td colSpan={2} className="text-start text-wrap">
<i className="bx bx-right-arrow-alt me-1"></i>{" "}
{bucket.name}
</td>
<td
className="text-start d-none d-sm-table-cell text-wrap"
style={{ width: "60%" }}
>
{bucket.description}
</td>
<td className="justify-content-center">
<div className="d-flex justify-content-center align-items-center gap-2">
<i
className="bx bx-edit bx-sm text-primary cursor-pointer"
onClick={() => {
select_bucket(bucket);
setAction_bucket(true);
}}
></i>
<i
className="bx bx-trash bx-sm text-danger cursor-pointer"
onClick={() => setDeleteBucket(bucket?.id)}
></i>
<i className="bx bx-user-plus cursor-pointer"></i>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div> </div>
) : (
<>
<form onSubmit={handleSubmit(onSubmit)} className="px-2 px-sm-0">
<div className="">
<label className="form-label">Bucket Name</label>
<input
className="form-control form-control-sm"
{...register("name")}
/>
{errors.name && (
<small className="danger-text">{errors.name.message}</small>
)}
</div>
<div className="">
<label className="form-label">Bucket Discription</label>
<textarea
className="form-control form-control-sm"
rows="3"
{...register("description")}
/>
{errors.description && (
<small className="danger-text">
{errors.description.message}
</small>
)}
</div>
<div className="mt-2 d-flex justify-content-center gap-3">
<button
type="reset"
className="btn btn-sm btn-secondary"
disabled={isSubmitting}
>
Cancel
</button>
<button
type="submit"
className="btn btn-sm btn-primary"
disabled={isSubmitting}
>
{isSubmitting ? "Please wait..." : "Submit"}
</button>
</div>
</form> </form>
<EmployeeList employees={ employeesList} /> <EmployeeList employees={employeesList} />
</> </>
)} )}
</div>
</div> </div>
</div> </>
); );
}; };