diff --git a/src/components/common/Forms/SelectFieldServerSide.jsx b/src/components/common/Forms/SelectFieldServerSide.jsx index d60be88b..8968d6d5 100644 --- a/src/components/common/Forms/SelectFieldServerSide.jsx +++ b/src/components/common/Forms/SelectFieldServerSide.jsx @@ -202,7 +202,7 @@ export const SelectProjectField = ({ isAllProject = false, disabled, className, - errors + errors, }) => { const [searchText, setSearchText] = useState(""); const debounce = useDebounce(searchText, 300); @@ -304,12 +304,11 @@ export const SelectProjectField = ({ {displayText} - - {errors?.projectId && ( -
{errors.projectId.message}
- )} - {/* DROPDOWN */} + {errors?.projectId && ( +
{errors.projectId.message}
+ )} + {open && ( )} @@ -372,7 +373,7 @@ export const SelectProjectField = ({ export const SelectFieldSearch = ({ label = "Select", - placeholder = "Select ", + placeholder = "Select", required = false, value = null, onChange, @@ -383,7 +384,7 @@ export const SelectFieldSearch = ({ isMultiple = false, hookParams, useFetchHook, - errors=null, + errors = null, }) => { const [searchText, setSearchText] = useState(""); const debounce = useDebounce(searchText, 300); @@ -393,108 +394,75 @@ export const SelectFieldSearch = ({ const [open, setOpen] = useState(false); const dropdownRef = useRef(null); - const getDisplayName = (entity) => { - if (!entity) return ""; - return `${entity[labelKey] || ""}`.trim(); - }; + const getDisplayName = (entity) => + entity ? `${entity[labelKey] || ""}`.trim() : ""; - /** ----------------------------- - * SELECTED OPTION (SINGLE) - * ----------------------------- */ + /** ----------------------------- SELECTED OPTION ----------------------------- */ let selectedSingle = null; - if (!isMultiple) { if (isFullObject && value) selectedSingle = value; else if (!isFullObject && value) selectedSingle = options.find((o) => o[valueKey] === value); } - /** ----------------------------- - * SELECTED OPTION (MULTIPLE) - * ----------------------------- */ let selectedList = []; if (isMultiple && Array.isArray(value)) { - if (isFullObject) selectedList = value; - else { - selectedList = options.filter((opt) => value.includes(opt[valueKey])); - } + selectedList = isFullObject + ? value + : options.filter((opt) => value.includes(opt[valueKey])); } - /** Main button label */ const displayText = !isMultiple ? getDisplayName(selectedSingle) || placeholder - : selectedList.length > 0 + : selectedList.length ? selectedList.map((e) => getDisplayName(e)).join(", ") : placeholder; - /** ----------------------------- - * HANDLE OUTSIDE CLICK - * ----------------------------- */ + /** ----------------------------- OUTSIDE CLICK ----------------------------- */ useEffect(() => { const handleClickOutside = (e) => { - if (dropdownRef.current && !dropdownRef.current.contains(e.target)) { + if (dropdownRef.current && !dropdownRef.current.contains(e.target)) setOpen(false); - } }; - document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); - // MERGED OPTIONS TO ENSURE SELECTED VALUE APPEARS EVEN IF NOT IN SEARCH RESULT - const [mergedOptions, setMergedOptions] = useState([]); + /** ----------------------------- MERGED OPTIONS ----------------------------- */ + const [mergedOptions, setMergedOptions] = useState(options); useEffect(() => { let finalList = [...options]; - if (!isMultiple && value && !isFullObject) { - // already selected option inside options? - const exists = options.some((o) => o[valueKey] === value); - - // if selected item not found, try to get from props (value) as fallback - if (!exists && typeof value === "object") { - finalList.unshift(value); - } + if (!isMultiple && value && !isFullObject && typeof value === "object") { + const exists = options.some((o) => o[valueKey] === value[valueKey]); + if (!exists) finalList.unshift(value); } if (isMultiple && Array.isArray(value)) { value.forEach((val) => { const id = isFullObject ? val[valueKey] : val; const exists = options.some((o) => o[valueKey] === id); - - if (!exists && typeof val === "object") { - finalList.unshift(val); - } + if (!exists && typeof val === "object") finalList.unshift(val); }); } - setMergedOptions(finalList); - }, [options, value]); + // Only update if different to avoid infinite loop + const oldKeys = mergedOptions.map((o) => o[valueKey]).join(","); + const newKeys = finalList.map((o) => o[valueKey]).join(","); + if (oldKeys !== newKeys) setMergedOptions(finalList); + }, [options, value, isMultiple, isFullObject, valueKey, mergedOptions]); - /** ----------------------------- - * HANDLE SELECT - * ----------------------------- */ + /** ----------------------------- HANDLE SELECT ----------------------------- */ const handleSelect = (option) => { if (!isMultiple) { - // SINGLE SELECT - if (isFullObject) onChange(option); - else onChange(option[valueKey]); + onChange(isFullObject ? option : option[valueKey]); } else { - // MULTIPLE SELECT - let updated = []; - const exists = selectedList.some((e) => e[valueKey] === option[valueKey]); - - if (exists) { - // remove - updated = selectedList.filter((e) => e[valueKey] !== option[valueKey]); - } else { - // add - updated = [...selectedList, option]; - } - - if (isFullObject) onChange(updated); - else onChange(updated.map((x) => x[valueKey])); + const updated = exists + ? selectedList.filter((e) => e[valueKey] !== option[valueKey]) + : [...selectedList, option]; + onChange(isFullObject ? updated : updated.map((x) => x[valueKey])); } }; @@ -506,10 +474,9 @@ export const SelectFieldSearch = ({ )} - {/* MAIN BUTTON */} - {errors && ( -
{errors.message}
- )} - {/* DROPDOWN */} + {errors &&
{errors.message}
} + {open && ( )} diff --git a/src/components/purchase/ManagePurchase.jsx b/src/components/purchase/ManagePurchase.jsx index 86643b05..82a1940f 100644 --- a/src/components/purchase/ManagePurchase.jsx +++ b/src/components/purchase/ManagePurchase.jsx @@ -9,29 +9,30 @@ import { import PurchasePartyDetails from "./PurchasePartyDetails"; import PurchaseTransportDetails from "./PurchaseTransportDetails"; import PurchasePaymentDetails from "./PurchasePaymentDetails"; +import { useCreatePurchaseInvoice } from "../../hooks/usePurchase"; -const ManagePurchase = () => { +const ManagePurchase = ({ onClose }) => { const [activeTab, setActiveTab] = useState(0); const [completedTabs, setCompletedTabs] = useState([]); const stepsConfig = [ { - name: "Contact Info", + name: "Party Details", icon: "bx bx-user bx-md", - subtitle: "Provide Contact Details", + subtitle: "Supplier & project information", component: , }, { - name: "Organization", - icon: "bx bx-buildings bx-md", - subtitle: "Organization Details", - component: , + name: "Invoice & Transport", + icon: "bx bx-receipt bx-md", + subtitle: "Invoice, eWay bill & transport info", + component: , }, { - name: "Subscription", - icon: "bx bx-star bx-md", - subtitle: "Payment & Financials", - component: , + name: "Payment Details", + icon: "bx bx-credit-card bx-md", + subtitle: "Amount, tax & due date", + component: , }, ]; @@ -55,8 +56,14 @@ const ManagePurchase = () => { setActiveTab((prev) => Math.max(prev - 1, 0)); }; + const { mutate: CreateInvoice, isPending } = useCreatePurchaseInvoice(() => { + onClose?.(); + }); + const onSubmit = (formData) => { console.log("PURCHASE DATA:", formData); + let payload = formData; + CreateInvoice(payload); }; return ( @@ -88,9 +95,7 @@ const ManagePurchase = () => { {step.name} - - {step.subtitle} - + {step.subtitle} @@ -112,26 +117,27 @@ const ManagePurchase = () => {
- - {activeTab < stepsConfig.length - 1 ? ( - - ) : ( - - )} + + {activeTab < stepsConfig.length - 1 ? ( + + ) : ( + + )} +
diff --git a/src/components/purchase/PurchasePartyDetails.jsx b/src/components/purchase/PurchasePartyDetails.jsx index 4870d38a..3746ba16 100644 --- a/src/components/purchase/PurchasePartyDetails.jsx +++ b/src/components/purchase/PurchasePartyDetails.jsx @@ -6,7 +6,7 @@ import { SelectFieldSearch, SelectProjectField, } from "../common/Forms/SelectFieldServerSide"; -import { useOrganization, useOrganizationsList } from "../../hooks/useOrganization"; +import { useGlobaleOrganizations, useOrganization, useOrganizationsList } from "../../hooks/useOrganization"; import { ITEMS_PER_PAGE } from "../../utils/constants"; const PurchasePartyDetails = () => { @@ -82,7 +82,7 @@ const PurchasePartyDetails = () => { onChange={field.onChange} valueKey="id" labelKey="name" - useFetchHook={useOrganizationsList} + useFetchHook={useGlobaleOrganizations} hookParams={[ITEMS_PER_PAGE,1]} errors={errors?.organizationId} /> @@ -93,15 +93,25 @@ const PurchasePartyDetails = () => { {/* Supplier */}
- + - ( + + )} /> {errors?.supplierId && ( diff --git a/src/components/purchase/PurchasePaymentDetails.jsx b/src/components/purchase/PurchasePaymentDetails.jsx index c626ac8b..6cfaa6c3 100644 --- a/src/components/purchase/PurchasePaymentDetails.jsx +++ b/src/components/purchase/PurchasePaymentDetails.jsx @@ -1,20 +1,24 @@ -import React from "react"; +import React, { useEffect } from "react"; import Label from "../common/Label"; import { useAppFormContext } from "../../hooks/appHooks/useAppForm"; import DatePicker from "../common/DatePicker"; +import { useInvoiceAttachmentTypes } from "../../hooks/masterHook/useMaster"; const PurchasePaymentDetails = () => { + const { data, isLoading, error, isError } = useInvoiceAttachmentTypes(); + console.log(data); const { register, watch, - setValue,control, + setValue, + control, formState: { errors }, } = useAppFormContext(); const baseAmount = watch("baseAmount"); const taxAmount = watch("taxAmount"); - React.useEffect(() => { + useEffect(() => { const base = parseFloat(baseAmount) || 0; const tax = parseFloat(taxAmount) || 0; @@ -25,7 +29,6 @@ const PurchasePaymentDetails = () => { return (
- {/* Base Amount */}
- {/* Tax Amount */}
- {/* Total Amount */}
- {/* Transport Charges */}
@@ -104,12 +104,14 @@ const PurchasePaymentDetails = () => { )}
- {/* Payment Due Date */}
- - + {errors?.paymentDueDate && (
diff --git a/src/hooks/masterHook/useMaster.js b/src/hooks/masterHook/useMaster.js index ca5de298..b4157b69 100644 --- a/src/hooks/masterHook/useMaster.js +++ b/src/hooks/masterHook/useMaster.js @@ -10,16 +10,25 @@ import { } from "@tanstack/react-query"; import showToast from "../../services/toastService"; - -export const useRecurringStatus = ()=>{ +export const useInvoiceAttachmentTypes = () => { return useQuery({ - queryKey:["RecurringStatus"], - queryFn:async()=>{ + queryKey: ["invoiceAttachmentType"], + queryFn: async () => { + const resp = await MasterRespository.getInvoiceAttachmentTypes(); + return resp.data; + }, + }); +}; + +export const useRecurringStatus = () => { + return useQuery({ + queryKey: ["RecurringStatus"], + queryFn: async () => { const resp = await MasterRespository.getRecurringStatus(); - return resp.data - } - }) -} + return resp.data; + }, + }); +}; export const useCurrencies = () => { return useQuery({ queryKey: ["currencies"], @@ -31,10 +40,10 @@ export const useCurrencies = () => { }; export const usePaymentAjustmentHead = (isActive) => { - return useQuery({ - queryKey: ["paymentType",isActive], - queryFn: async () => await MasterRespository.getPaymentAdjustmentHead(isActive), - + return useQuery({ + queryKey: ["paymentType", isActive], + queryFn: async () => + await MasterRespository.getPaymentAdjustmentHead(isActive), }); }; @@ -296,26 +305,26 @@ export const useOrganizationType = () => { }); }; -export const useJobStatus=(statusId,projectId)=>{ +export const useJobStatus = (statusId, projectId) => { return useQuery({ - queryKey:["Job_Staus",statusId,projectId], - queryFn:async()=>{ - const resp = await MasterRespository.getJobStatus(statusId,projectId); + queryKey: ["Job_Staus", statusId, projectId], + queryFn: async () => { + const resp = await MasterRespository.getJobStatus(statusId, projectId); return resp.data; }, - enabled:!!statusId && !!projectId - }) -} + enabled: !!statusId && !!projectId, + }); +}; -export const useTeamRole=()=>{ +export const useTeamRole = () => { return useQuery({ - queryKey:["Team_Role"], - queryFn:async()=>{ + queryKey: ["Team_Role"], + queryFn: async () => { const resp = await MasterRespository.getTeamRole(); return resp.data; - } - }) -} + }, + }); +}; //#region ==Get Masters== const fetchMasterData = async (masterType) => { switch (masterType) { @@ -405,8 +414,6 @@ const useMaster = () => { export default useMaster; //#endregion - - // ================================Mutation==================================== //#region Job Role @@ -456,10 +463,6 @@ export const useCreateJobRole = (onSuccessCallback) => { }; //#endregion Job Role - - - - //#region Application Role export const useCreateApplicationRole = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -505,10 +508,6 @@ export const useUpdateApplicationRole = (onSuccessCallback) => { }; //#endregion - - - - //#region Create work Category export const useCreateWorkCategory = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -554,11 +553,6 @@ export const useUpdateWorkCategory = (onSuccessCallback) => { }; //#endregion - - - - - //#region Contact Category export const useCreateContactCategory = (onSuccessCallback) => { @@ -609,10 +603,6 @@ export const useUpdateContactCategory = (onSuccessCallback) => { //#endregion - - - - //#region Contact Tag export const useCreateContactTag = (onSuccessCallback) => { @@ -659,10 +649,6 @@ export const useUpdateContactTag = (onSuccessCallback) => { }; //#endregion - - - - //#region Expense Category export const useCreateExpenseCategory = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -689,7 +675,10 @@ export const useUpdateExpenseCategory = (onSuccessCallback) => { return useMutation({ mutationFn: async ({ id, payload }) => { - const response = await MasterRespository.updateExpenseCategory(id, payload); + const response = await MasterRespository.updateExpenseCategory( + id, + payload + ); return response.data; }, onSuccess: (data, variables) => { @@ -708,11 +697,6 @@ export const useUpdateExpenseCategory = (onSuccessCallback) => { //#endregion - - - - - //#region Payment Mode export const useCreatePaymentMode = (onSuccessCallback) => { @@ -759,10 +743,6 @@ export const useUpdatePaymentMode = (onSuccessCallback) => { //#endregion - - - - // Services------------------------------- // export const useCreateService = (onSuccessCallback) => { @@ -844,10 +824,6 @@ export const useUpdateService = (onSuccessCallback) => { //#endregion - - - - //#region Activity Grouph export const useCreateActivityGroup = (onSuccessCallback) => { @@ -912,10 +888,6 @@ export const useUpdateActivityGroup = (onSuccessCallback) => { //#endregion - - - - //#region Activities export const useCreateActivity = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -970,11 +942,6 @@ export const useUpdateActivity = (onSuccessCallback) => { //#endregion - - - - - //#region Expense Status export const useCreateExpenseStatus = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -1018,10 +985,6 @@ export const useUpdateExpenseStatus = (onSuccessCallback) => { }; //#endregion - - - - //#region Document-Category export const useCreateDocumentCatgory = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -1068,11 +1031,6 @@ export const useUpdateDocumentCategory = (onSuccessCallback) => { }; //#endregion - - - - - //#region Document-Type export const useCreateDocumentType = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -1117,10 +1075,6 @@ export const useUpdateDocumentType = (onSuccessCallback) => { }; //#endregion - - - - //#region Payment Adjustment Head export const useCreatePaymentAjustmentHead = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -1147,7 +1101,10 @@ export const useUpdatePaymentAjustmentHead = (onSuccessCallback) => { return useMutation({ mutationFn: async ({ id, payload }) => { - const resp = await MasterRespository.updatePaymentAjustmentHead(id, payload); + const resp = await MasterRespository.updatePaymentAjustmentHead( + id, + payload + ); return resp.data; }, onSuccess: (data) => { @@ -1164,10 +1121,6 @@ export const useUpdatePaymentAjustmentHead = (onSuccessCallback) => { }; //#endregion - - - - //#region ==Delete Master== export const useDeleteMasterItem = () => { const queryClient = useQueryClient(); @@ -1203,8 +1156,6 @@ export const useDeleteMasterItem = () => { //#endregion - - export const useDeleteServiceGroup = () => { const queryClient = useQueryClient(); diff --git a/src/hooks/useOrganization.js b/src/hooks/useOrganization.js index 1da12f21..028f6d72 100644 --- a/src/hooks/useOrganization.js +++ b/src/hooks/useOrganization.js @@ -24,7 +24,9 @@ export const useOrganizationModal = () => { dispatch( openOrgModal({ isOpen: true, - orgData: options.hasOwnProperty("orgData") ? options.orgData : orgData, + orgData: options.hasOwnProperty("orgData") + ? options.orgData + : orgData, startStep: options.startStep ?? startStep ?? 1, prevStep: options.prevStep ?? prevStep ?? 1, flowType: options.flowType ?? flowType ?? "default", @@ -37,16 +39,30 @@ export const useOrganizationModal = () => { // ================================Query============================================================= -export const useOrganization=(id)=>{ -return useQuery({ - queryKey:["organization",id], - queryFn:async()=> { - const resp = await await OrganizationRepository.getOrganizaion(id); - return resp.data - }, - enabled:!!id -}) -} +export const useGlobaleOrganizations = (pageSize, pageNumber, searchString) => { + return useQuery({ + queryKey: ["global_organization",pageSize, pageNumber, searchString], + queryFn: async () => { + const resp = await OrganizationRepository.getGlobalOrganization( + pageSize, + pageNumber, + searchString + ); + return resp.data; + }, + }); +}; + +export const useOrganization = (id) => { + return useQuery({ + queryKey: ["organization", id], + queryFn: async () => { + const resp = await await OrganizationRepository.getOrganizaion(id); + return resp.data; + }, + enabled: !!id, + }); +}; export const useOrganizationBySPRID = (sprid) => { return useQuery({ queryKey: ["organization by", sprid], @@ -101,7 +117,7 @@ export const useOrganizationEmployees = ( organizationId, searchString ), - enabled: !!projectId , + enabled: !!projectId, }); }; @@ -138,10 +154,10 @@ export const useAssignOrgToProject = (onSuccessCallback) => { useClient.invalidateQueries({ queryKey: ["projectAssignedOrganiztions"], }); - useClient.invalidateQueries({ + useClient.invalidateQueries({ queryKey: ["projectAssignedOrganiztionsName"], }); - + useClient.invalidateQueries({ queryKey: ["projectAssignedServices", projectId], }); @@ -181,12 +197,14 @@ export const useAssignOrgToTenant = (onSuccessCallback) => { export const useUpdateOrganization = (onSuccessCallback) => { const useClient = useQueryClient(); return useMutation({ - mutationFn: async ({orgId,payload}) => - await OrganizationRepository.updateOrganizaion(orgId,payload), + mutationFn: async ({ orgId, payload }) => + await OrganizationRepository.updateOrganizaion(orgId, payload), onSuccess: (_, variables) => { useClient.invalidateQueries({ queryKey: ["organizationList"] }); - useClient.invalidateQueries({ queryKey: ["projectAssignedOrganiztionsName"] }); - + useClient.invalidateQueries({ + queryKey: ["projectAssignedOrganiztionsName"], + }); + showToast("Organization Updated successfully", "success"); if (onSuccessCallback) onSuccessCallback(); }, diff --git a/src/hooks/usePurchase.jsx b/src/hooks/usePurchase.jsx new file mode 100644 index 00000000..749e720d --- /dev/null +++ b/src/hooks/usePurchase.jsx @@ -0,0 +1,25 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { PurchaseRepository } from "../repositories/PurchaseRepository"; + + + +export const useCreatePurchaseInvoice = (onSuccessCallback) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (payload) => + await PurchaseRepository.CreatePurchase(payload), + onSuccess: (data, variables) => { + showToast("Purchase Invoice Created successfully", "success"); + if (onSuccessCallback) onSuccessCallback(); + }, + onError: (error) => { + showToast( + error?.response?.data?.message || + error.message || + "Failed to create invoice", + "error" + ); + }, + }); +}; diff --git a/src/repositories/MastersRepository.jsx b/src/repositories/MastersRepository.jsx index 72b3e2a0..65ad4339 100644 --- a/src/repositories/MastersRepository.jsx +++ b/src/repositories/MastersRepository.jsx @@ -157,4 +157,7 @@ export const MasterRespository = { ), getTeamRole: () => api.get(`/api/Master/team-roles/list`), + + + getInvoiceAttachmentTypes:()=>api.get("/api/Master/invoice-attachment-type/list") }; diff --git a/src/repositories/OrganizationRespository.jsx b/src/repositories/OrganizationRespository.jsx index 722423a9..ab9121ff 100644 --- a/src/repositories/OrganizationRespository.jsx +++ b/src/repositories/OrganizationRespository.jsx @@ -2,8 +2,9 @@ import { api } from "../utils/axiosClient"; const OrganizationRepository = { createOrganization: (data) => api.post("/api/Organization/create", data), - updateOrganizaion:(id,data)=>api.put(`/api/Organization/edit/${id}`,data), - getOrganizaion:(id)=>api.get(`/api/Organization/details/${id}`), + updateOrganizaion: (id, data) => + api.put(`/api/Organization/edit/${id}`, data), + getOrganizaion: (id) => api.get(`/api/Organization/details/${id}`), getOrganizationList: (pageSize, pageNumber, active, sprid, searchString) => { return api.get( `/api/Organization/list?pageSize=${pageSize}&pageNumber=${pageNumber}&active=${active}&${ @@ -39,6 +40,11 @@ const OrganizationRepository = { return api.get(url); }, + + getGlobalOrganization: (pageSize, pageNumber, searchString) => + api.get( + `/api/Organization/list/basic?pageSize=${pageSize}&pageNumber=${pageNumber}&searchString=${searchString}` + ), }; export default OrganizationRepository; diff --git a/src/repositories/PurchaseRepository.jsx b/src/repositories/PurchaseRepository.jsx new file mode 100644 index 00000000..eb50340f --- /dev/null +++ b/src/repositories/PurchaseRepository.jsx @@ -0,0 +1,5 @@ +import { api } from "../utils/axiosClient"; + +export const PurchaseRepository = { + CreatePurchase: (data) => api.post("/api/PurchaseInvoice/create", data), +};