diff --git a/src/components/DailyProgressRport/TaskReportList.jsx b/src/components/DailyProgressRport/TaskReportList.jsx index bcb4232b..f812937c 100644 --- a/src/components/DailyProgressRport/TaskReportList.jsx +++ b/src/components/DailyProgressRport/TaskReportList.jsx @@ -205,7 +205,7 @@ const TaskReportList = () => { id="total_pending_task" title="Total Pending Task" content={ -
+
This shows the total pending tasks for each activity on that date.
} diff --git a/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx b/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx index d6bff3e3..d2647649 100644 --- a/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx +++ b/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx @@ -69,7 +69,7 @@ const ManageJobTicket = ({ Job }) => { id="STATUS_CHANEG" Mode="click" className="" - align="right" + align="left" content={ { })()}
{data?.projectBranch && ( -
+
Branch Name: @@ -149,7 +149,8 @@ const ManageJobTicket = ({ Job }) => { } > diff --git a/src/components/common/HoverPopup.jsx b/src/components/common/HoverPopup.jsx index 232ced5d..9c5f8e13 100644 --- a/src/components/common/HoverPopup.jsx +++ b/src/components/common/HoverPopup.jsx @@ -6,10 +6,6 @@ import { togglePopup, } from "../../slices/localVariablesSlice"; -/** - * align: "auto" | "left" | "right" - * boundaryRef: optional ref to the drawer/container element to use as boundary - */ const HoverPopup = ({ id, title, @@ -17,7 +13,9 @@ const HoverPopup = ({ children, className = "", Mode = "hover", - align = "auto", + align = "auto", // <-- dynamic placement + minWidth = "250px", + maxWidth = "350px", boundaryRef = null, }) => { const dispatch = useDispatch(); @@ -26,20 +24,15 @@ const HoverPopup = ({ const triggerRef = useRef(null); const popupRef = useRef(null); - const handleMouseEnter = () => { - if (Mode === "hover") dispatch(openPopup(id)); - }; - const handleMouseLeave = () => { - if (Mode === "hover") dispatch(closePopup(id)); - }; + const handleMouseEnter = () => Mode === "hover" && dispatch(openPopup(id)); + const handleMouseLeave = () => Mode === "hover" && dispatch(closePopup(id)); const handleClick = (e) => { - if (Mode === "click") { - e.stopPropagation(); - dispatch(togglePopup(id)); - } + if (Mode !== "click") return; + e.stopPropagation(); + dispatch(togglePopup(id)); }; - // Close on outside click when using click mode + // Close popup when clicking outside (click mode) useEffect(() => { if (Mode !== "click" || !visible) return; @@ -56,40 +49,68 @@ const HoverPopup = ({ document.addEventListener("click", handler); return () => document.removeEventListener("click", handler); - }, [Mode, visible, dispatch, id]); + }, [visible, Mode, id, dispatch]); + // ---------- DYNAMIC POSITIONING LOGIC ---------- useEffect(() => { if (!visible || !popupRef.current || !triggerRef.current) return; requestAnimationFrame(() => { const popup = popupRef.current; - const boundaryEl = (boundaryRef && boundaryRef.current) || popup.parentElement; - if (!boundaryEl) return; + const trigger = triggerRef.current; + + const boundaryEl = + (boundaryRef && boundaryRef.current) || popup.parentElement; const boundaryRect = boundaryEl.getBoundingClientRect(); - const triggerRect = triggerRef.current.getBoundingClientRect(); - popup.style.left = ""; - popup.style.right = ""; - popup.style.transform = ""; - popup.style.top = ""; - + const triggerRect = trigger.getBoundingClientRect(); const popupRect = popup.getBoundingClientRect(); - const triggerCenterX = triggerRect.left + triggerRect.width / 2 - boundaryRect.left; - let left = triggerCenterX - popupRect.width / 2; - // Clamp to boundaries - left = Math.max(0, Math.min(left, boundaryRect.width - popupRect.width)); + let left; + + // AUTO ALIGN (smart) + if (align === "auto") { + const center = + triggerRect.left + + triggerRect.width / 2 - + boundaryRect.left - + popupRect.width / 2; + + left = Math.max( + 0, + Math.min(center, boundaryRect.width - popupRect.width) + ); + } + + // LEFT ALIGN + else if (align === "left") { + left = triggerRect.left - boundaryRect.left; + if (left + popupRect.width > boundaryRect.width) { + left = boundaryRect.width - popupRect.width; // clamp right + } + } + + // RIGHT ALIGN + else if (align === "right") { + left = + triggerRect.left + + triggerRect.width - + boundaryRect.left - + popupRect.width; + + if (left < 0) left = 0; // clamp left + } + popup.style.left = `${left}px`; + popup.style.top = `100%`; }); }, [visible, align, boundaryRef]); - return ( -
-
+
e.stopPropagation()} > {title &&
{title}
} -
{content}
+ {content}
)}
); - }; export default HoverPopup; diff --git a/src/components/purchase/ManagePurchase.jsx b/src/components/purchase/ManagePurchase.jsx index 262238bf..fb757b54 100644 --- a/src/components/purchase/ManagePurchase.jsx +++ b/src/components/purchase/ManagePurchase.jsx @@ -28,21 +28,21 @@ const ManagePurchase = ({ onClose, purchaseId }) => { const stepsConfig = useMemo( () => [ { - name: "Party Details", + name: "Vendor & Project Details", icon: "bx bx-user bx-md", - subtitle: "Supplier & project information", + subtitle: "Vendor information and project association", component: , }, { - name: "Invoice & Transport", + name: "Invoice & Logistics", icon: "bx bx-receipt bx-md", - subtitle: "Invoice, eWay bill & transport info", + subtitle: "Invoice, e-Way bill, and logistics information", component: , }, { - name: "Payment Details", + name: "Invoice & Tax Amount", icon: "bx bx-credit-card bx-md", - subtitle: "Amount, tax & due date", + subtitle: "Payment terms, tax breakdown, and due dates", component: , }, ], @@ -127,8 +127,6 @@ const ManagePurchase = ({ onClose, purchaseId }) => { const onSubmit = useCallback( (formData) => { - if (activeTab !== 2) return; - if (purchaseId) { const payload = generatePatchOps(formData); updatePurchase({ purchaseId, payload }); @@ -136,7 +134,7 @@ const ManagePurchase = ({ onClose, purchaseId }) => { CreateInvoice(formData); } }, - [activeTab, purchaseId, generatePatchOps, updatePurchase, CreateInvoice] + [purchaseId, generatePatchOps, updatePurchase, CreateInvoice] ); return (
@@ -184,50 +182,53 @@ const ManagePurchase = ({ onClose, purchaseId }) => { {/* --- Form Content --- */}
-
{ - if (activeTab !== 2) { - e.preventDefault(); - e.stopPropagation(); - } - }} - onSubmit={purchaseOrder.handleSubmit(onSubmit)} - > - {stepsConfig[activeTab].component} + {activeTab !== 2 && ( +
+ {stepsConfig[activeTab].component} - {/* Buttons */} -
- +
+ -
- {activeTab < stepsConfig.length - 1 ? ( - - ) : ( - - )} +
- + )} + {activeTab === 2 && ( +
+ {stepsConfig[2].component} + +
+ + + +
+
+ )}
diff --git a/src/components/purchase/PurchaseActions.jsx b/src/components/purchase/PurchaseActions.jsx new file mode 100644 index 00000000..6cb82e66 --- /dev/null +++ b/src/components/purchase/PurchaseActions.jsx @@ -0,0 +1,102 @@ +export const getPurchaseActions = ({ + item, + isActive, + canDelete, + canAddChallan, + setViewPurchase, + setManagePurchase, + setDeletingId, + setIsDeleteModalOpen, + setChallan, + setAddPayment, +}) => { + const actions = []; + + // VIEW + actions.push({ + key: "view", + label: "View", + icon: "bx bx-show", + show: true, + onClick: () => + setViewPurchase({ + isOpen: true, + purchaseId: item.id, + }), + }); + + if (!isActive) { + // EDIT + actions.push({ + key: "edit", + label: "Edit", + icon: "bx bx-edit", + show: true, + onClick: () => + setManagePurchase({ + isOpen: true, + purchaseId: item.id, + }), + }); + + // DELETE + actions.push({ + key: "delete", + label: "Delete", + icon: "bx bx-trash", + show: canDelete, + onClick: () => { + setDeletingId(item.id); + setIsDeleteModalOpen(true); + }, + }); + + // ADD CHALLAN + actions.push({ + key: "challan", + label: "Add Delivery Challan", + icon: "bx bx-file bx-plus", + show: canAddChallan, + onClick: () => + setChallan({ + isOpen: true, + purchaseId: item.id, + }), + }); + + // ADD PAYMENT + actions.push({ + key: "payment", + label: "Add Payment", + icon: "bx bx-wallet", + show: true, + onClick: () => + setAddPayment({ + isOpen: true, + purchaseId: item.id, + }), + }); + } else { + // RESTORE + actions.push({ + key: "restore", + label: "Restore", + icon: "bx bx-undo", + show: true, + onClick: () => { + setDeletingId(item.id); + setIsDeleteModalOpen(true); + }, + }); + } + + return actions.filter((a) => a.show); +}; + +export const DropdownItem = ({ icon, label, onClick }) => ( +
  • + + {label} + +
  • +); diff --git a/src/components/purchase/PurchaseList.jsx b/src/components/purchase/PurchaseList.jsx index 39ed3352..5d7715b1 100644 --- a/src/components/purchase/PurchaseList.jsx +++ b/src/components/purchase/PurchaseList.jsx @@ -1,21 +1,40 @@ import React, { useState } from "react"; -import { usePurchasesList } from "../../hooks/usePurchase"; -import { ITEMS_PER_PAGE } from "../../utils/constants"; +import { + useDeletePurchaseInvoice, + usePurchasesList, +} from "../../hooks/usePurchase"; +import { + ADD_DELIVERY_CHALLAN, + DELETEPURCHASE_INVOICE, + ITEMS_PER_PAGE, +} from "../../utils/constants"; import Pagination from "../common/Pagination"; import { PurchaseColumn } from "./Purchasetable"; import { SpinnerLoader } from "../common/Loader"; import { useDebounce } from "../../utils/appUtils"; import { usePurchaseContext } from "../../pages/purchase/PurchasePage"; +import ConfirmModal from "../common/ConfirmModal"; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; +import { DropdownItem, getPurchaseActions } from "./PurchaseActions"; -const PurchaseList = ({ searchString }) => { - const { setViewPurchase, setManagePurchase, setChallan } = +const PurchaseList = ({ searchString, isActive }) => { + const { setViewPurchase, setManagePurchase, setChallan, setAddPayment } = usePurchaseContext(); const [currentPage, setCurrentPage] = useState(1); + const { mutate: DeletePurchaseInvoice, isPending } = + useDeletePurchaseInvoice(); + const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [deletingId, setDeletingId] = useState(null); + + const canAddChallan = useHasUserPermission(ADD_DELIVERY_CHALLAN); + const canDelete = useHasUserPermission(DELETEPURCHASE_INVOICE); + const debounceSearch = useDebounce(searchString, 300); const { data, isLoading } = usePurchasesList( ITEMS_PER_PAGE, currentPage, - true, + // true, + !isActive, {}, debounceSearch ); @@ -28,129 +47,141 @@ const PurchaseList = ({ searchString }) => { const visibleColumns = PurchaseColumn.filter((col) => !col.hidden); + const handleDeleteRestore = (id) => { + DeletePurchaseInvoice( + { id, isActive: isActive }, // delete if active, restore if deleted + { + onSettled: () => { + setDeletingId(null); + setIsDeleteModalOpen(false); + }, + } + ); + }; + return ( -
    -
    - - - - {visibleColumns.map((col) => ( - - ))} - - - - - - {/* LOADING */} - {isLoading && ( + <> + {IsDeleteModalOpen && ( + setIsDeleteModalOpen(false)} + loading={isPending} + paramData={deletingId} + /> + )} +
    +
    +
    -
    {col.label}
    -
    Action
    + - + {visibleColumns.map((col) => ( + + ))} + - )} - {!isLoading && data?.data?.length === 0 && ( - - - - )} + - {!isLoading && - data?.data?.map((item, index) => ( - - {visibleColumns.map((col) => ( - - ))} - + {/* LOADING */} + {isLoading && ( + + - ))} - -
    -
    - -
    -
    +
    {col.label}
    +
    Action
    - No Data Found -
    - {col.render ? col.render(item) : item[col.key] || "NA"} - -
    +
    +
    -
    + )} - {data?.data?.length > 0 && ( - - )} -
    + {!isLoading && data?.data?.length === 0 && ( + + + No Data Found + + + )} + + {!isLoading && + data?.data?.map((item, index) => ( + + {visibleColumns.map((col) => ( + + {col.render ? col.render(item) : item[col.key] || "NA"} + + ))} + +
    + +
      + {getPurchaseActions({ + item, + isActive, + canDelete, + canAddChallan, + setViewPurchase, + setManagePurchase, + setDeletingId, + setIsDeleteModalOpen, + setChallan, + setAddPayment, + }).map((action) => ( + + ))} +
    +
    + + + ))} + + +
    + + {data?.data?.length > 0 && ( + + )} +
    + ); }; diff --git a/src/components/purchase/PurchasePartyDetails.jsx b/src/components/purchase/PurchasePartyDetails.jsx index fd776961..221c33e9 100644 --- a/src/components/purchase/PurchasePartyDetails.jsx +++ b/src/components/purchase/PurchasePartyDetails.jsx @@ -36,9 +36,7 @@ const PurchasePartyDetails = () => { @@ -73,15 +71,16 @@ const PurchasePartyDetails = () => { control={control} render={({ field }) => ( )} /> @@ -109,7 +108,7 @@ const PurchasePartyDetails = () => {
    - +