marco.pms.web/src/pages/Directory/DirectoryPage.jsx

425 lines
15 KiB
JavaScript

import {
useState,
Suspense,
lazy,
createContext,
useContext,
useEffect,
} from "react";
import Breadcrumb from "../../components/common/Breadcrumb";
import { useFab } from "../../Context/FabContext";
import {
useBucketList,
useBuckets,
useDeleteBucket,
} from "../../hooks/useDirectory";
import ManageBucket1 from "../../components/Directory/ManageBucket1";
import ManageContact from "../../components/Directory/ManageContact";
import BucketList from "../../components/Directory/BucketList";
import { MainDirectoryPageSkeleton } from "../../components/Directory/DirectoryPageSkeleton";
import ContactProfile from "../../components/Directory/ContactProfile";
import GlobalModel from "../../components/common/GlobalModel";
import ConfirmModal from "../../components/common/ConfirmModal";
import { useSelectedProject } from "../../slices/apiDataManager";
import {
exportToCSV,
exportToExcel,
exportToPDF,
exportToPDF1,
printTable,
} from "../../utils/tableExportUtils";
const NotesPage = lazy(() => import("./NotesPage"));
const ContactsPage = lazy(() => import("./ContactsPage"));
export const DirectoryContext = createContext();
export const useDirectoryContext = () => {
const context = useContext(DirectoryContext);
if (!context) {
return (
<div className="container-fluid">
<p>Your Action is out of context</p>
</div>
);
}
return context;
};
export default function DirectoryPage({ IsPage = true, projectId = null }) {
const [searchContact, setsearchContact] = useState("");
const [searchNote, setSearchNote] = useState("");
const [activeTab, setActiveTab] = useState("notes");
const { setActions } = useFab();
const [gridView, setGridView] = useState(true);
const [isOpenBucket, setOpenBucket] = useState(false);
const [isManageContact, setManageContact] = useState({
isOpen: false,
contactId: null,
});
const [deleteBucket, setDeleteBucket] = useState({
isOpen: false,
bucketId: null,
});
const [showActive, setShowActive] = useState(true);
const [contactOpen, setContactOpen] = useState({
contact: null,
Open: false,
});
const [notesData, setNotesData] = useState([]);
const [ContactData, setContactData] = useState([]);
const handleExport = (type) => {
let exportData = activeTab === "notes" ? notesData : ContactData;
if (!exportData?.length) return;
switch (type) {
case "csv":
exportToCSV(exportData, activeTab === "notes" ? "Notes" : "Contacts");
break;
case "excel":
exportToExcel(exportData, activeTab === "notes" ? "Notes" : "Contacts");
break;
case "pdf":
if (activeTab === "notes") {
exportToPDF1(exportData, "Notes");
} else {
// Columns for Contacts PDF
const columns = [
"Email",
"Phone",
"Organization",
"Category",
"Tags",
];
// Sanitize and trim long text to avoid PDF overflow
const sanitizedData = exportData.map((item) => ({
Email: (item.Email || "").slice(0, 40),
Phone: (item.Phone || "").slice(0, 20),
Organization: (item.Organization || "").slice(0, 30),
Category: (item.Category || "").slice(0, 20),
Tags: (item.Tags || "").slice(0, 40),
}));
// Export with proper spacing
exportToPDF(sanitizedData, "Contacts", columns, {
columnWidths: [200, 120, 180, 120, 200], // Adjust widths per column
fontSizeHeader: 12,
fontSizeRow: 10,
rowHeight: 25,
});
}
break;
default:
console.warn("Unsupported export type");
}
};
const { data, isLoading, isError, error } = useBucketList();
const handleTabClick = (tab, e) => {
e.preventDefault();
setActiveTab(tab);
};
useEffect(() => {
const actions = [];
if (IsPage) {
actions.push({
label: "Manage Bucket",
icon: "fa-solid fa-bucket fs-5",
color: "primary",
onClick: () => setOpenBucket(true),
});
}
if (data?.length > 0) {
actions.push({
label: "New Contact",
icon: "bx bx-plus-circle",
color: "warning",
onClick: () => setManageContact({ isOpen: true, contactId: null }),
});
}
setActions(actions);
return () => setActions([]);
}, [IsPage, data]);
const contextValues = {
showActive,
gridView,
data,
setManageContact,
setContactOpen,
setDeleteBucket,
};
const { mutate: DeleteBucket, isPending: Deleting } = useDeleteBucket(() => {
setDeleteBucket({ isOpen: false, bucketId: null });
});
const handleDelete = (bucketId) => {
DeleteBucket(bucketId);
};
if (isLoading) return <MainDirectoryPageSkeleton />;
if (isError) return <div>{error.message}</div>;
return (
<>
<DirectoryContext.Provider value={contextValues}>
<div className={`${IsPage ? "container-fluid" : ""}`}>
{IsPage && (
<Breadcrumb
data={[
{ label: "Home", link: "/dashboard" },
{ label: "Directory", link: null },
]}
></Breadcrumb>
)}
<div className="card ">
<div className="d-flex-row px-2">
<div className="d-flex justify-content-between align-items-center mb-1">
<ul className="nav nav-tabs">
<li className="nav-item cursor-pointer">
<a
className={`nav-link ${
activeTab === "notes" ? "active" : ""
} fs-6`}
onClick={(e) => handleTabClick("notes", e)}
>
<i className="bx bx-notepad bx-sm me-1_5"></i>
<span className="d-none d-md-inline">Notes</span>
</a>
</li>
<li className="nav-item cursor-pointer">
<a
className={`nav-link ${
activeTab === "contacts" ? "active" : ""
} fs-6`}
onClick={(e) => handleTabClick("contacts", e)}
>
<i className="bx bxs-contact bx-sm me-1_5"></i>
<span className="d-none d-md-inline">Contacts</span>
</a>
</li>
</ul>
<div className="mb-1 px-2 py-3">
<div className="d-flex align-items-center justify-content-between">
<div className="d-flex align-items-center gap-3">
{activeTab === "notes" && (
<input
type="search"
className="form-control form-control-sm"
placeholder="Search notes..."
value={searchNote}
onChange={(e) => setSearchNote(e.target.value)}
/>
)}
{activeTab === "contacts" && (
<div className="d-flex align-items-center gap-3">
<div className="d-flex gap-2 align-items-center">
<input
type="search"
className="form-control form-control-sm"
placeholder="Search contacts..."
value={searchContact}
onChange={(e) => setsearchContact(e.target.value)}
/>
<div className="d-none d-md-flex gap-2">
{" "}
<button
className={`btn btn-sm p-1 ${
gridView
? " btn-primary"
: " btn-outline-primary"
}`}
onClick={() => setGridView(true)}
>
<i className="bx bx-grid-alt"></i>
</button>
<button
className={`btn btn-sm p-1 ${
!gridView
? "btn-primary"
: "btn-outline-primary"
}`}
onClick={() => setGridView(false)}
>
<i className="bx bx-list-ul"></i>
</button>
</div>
</div>
</div>
)}
</div>
<div className="dropdown z-2">
<button
type="button"
className="btn btn-icon p-0 m-0"
data-bs-toggle="dropdown"
aria-expanded="false"
title="More Actions"
>
<i className="bx bx-dots-vertical-rounded text-muted bx-md"></i>
</button>
<ul className="dropdown-menu dropdown-menu-end shadow-sm ">
{activeTab === "contacts" && (
<li className="dropdown-item d-flex align-items-center">
<div className="form-check form-switch mb-0">
<input
type="checkbox"
className="form-check-input"
role="switch"
id="inactiveContactsSwitch"
checked={showActive}
onChange={(e) =>
setShowActive(e.target.checked)
}
/>
</div>
<span>
{showActive
? "Active Contacts"
: "Inactive Contacts"}
</span>
</li>
)}
<li>
<button
className="dropdown-item d-flex align-items-center gap-2"
onClick={() => handleExport("csv")}
>
<i className="bx bx-file"></i>
<span>Export to CSV</span>
</button>
</li>
<li>
<button
className="dropdown-item d-flex align-items-center gap-2"
onClick={() => handleExport("excel")}
>
<i className="bx bx-spreadsheet"></i>
<span>Export to Excel</span>
</button>
</li>
<li>
<button
className="dropdown-item d-flex align-items-center gap-2"
onClick={() => handleExport("pdf")}
>
<i className="bx bxs-file-pdf"></i>
<span>Export to PDF</span>
</button>
</li>
<li className={`d-block d-md-none ${activeTab === "contacts" ? "d-block":"d-none"}`}>
<span
className={`dropdown-item ${
gridView ? " text-primary" : ""
}`}
onClick={() => setGridView(true)}
>
<i className="bx bx-grid-alt"></i> Card View
</span>
</li>
<li className={` d-block d-md-none ${activeTab === "contacts" ? "d-block":"d-none"}`}>
<span
className={`dropdown-item ${
!gridView ? "text-primary" : ""
}`}
onClick={() => setGridView(false)}
>
<i className="bx bx-list-ul"></i> List View
</span>
</li>
{/* Divider */}
{activeTab === "contacts" && (
<li>
<hr className="dropdown-divider" />
</li>
)}
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<Suspense fallback={<MainDirectoryPageSkeleton />}>
{activeTab === "notes" && (
<NotesPage
projectId={projectId}
searchText={searchNote}
onExport={setNotesData}
/>
)}
{activeTab === "contacts" && (
<ContactsPage
projectId={projectId}
searchText={searchContact}
onExport={setContactData}
/>
)}
</Suspense>
</div>
{isOpenBucket && (
<GlobalModel
size="lg"
isOpen={isOpenBucket}
closeModal={() => setOpenBucket(false)}
>
<ManageBucket1 closeModal={() => setOpenBucket(false)} />
</GlobalModel>
)}
{contactOpen.Open && (
<GlobalModel
size="xl"
isOpen={contactOpen.Open}
closeModal={() => setContactOpen({ contact: null, Open: false })}
>
<ContactProfile contactId={contactOpen.contact} />
</GlobalModel>
)}
{isManageContact.isOpen && (
<GlobalModel
size="lg"
isOpen={isManageContact}
closeModal={() =>
setManageContact({ isOpen: false, contactId: null })
}
>
<ManageContact
contactId={isManageContact.contactId}
closeModal={() =>
setManageContact({ isOpen: false, contactId: null })
}
/>
</GlobalModel>
)}
{deleteBucket.isOpen && (
<ConfirmModal
isOpen={deleteBucket.isOpen}
type="delete"
header="Delete Bucket"
message="Are you sure you want delete?"
onSubmit={handleDelete}
onClose={() => setDeleteBucket({ isOpen: false, bucketId: null })}
loading={Deleting}
paramData={deleteBucket.bucketId}
/>
)}
</div>
</DirectoryContext.Provider>
</>
);
}