diff --git a/public/assets/vendor/css/core.css b/public/assets/vendor/css/core.css index f440443e..e1d620fe 100644 --- a/public/assets/vendor/css/core.css +++ b/public/assets/vendor/css/core.css @@ -32560,9 +32560,7 @@ body:not(.modal-open) .layout-content-navbar .layout-navbar { .bg-blue { background-color:var(--bs-blue) } -.text-blue{ - color:var(--bs-blue) -} + .bg-indigo { background-color:var(--bs-indigo) } @@ -32574,4 +32572,10 @@ body:not(.modal-open) .layout-content-navbar .layout-navbar { } .text-red{ color:var(--bs-red) +} +.text-blue{ + color:var(--bs-blue) +} +.text-green{ + color:var(--bs-green) } \ No newline at end of file diff --git a/public/img/brand/marco-250x250.png b/public/img/brand/marco-250x250.png new file mode 100644 index 00000000..ca14f303 Binary files /dev/null and b/public/img/brand/marco-250x250.png differ diff --git a/src/components/AdvancePayment/AdvancePaymentList.jsx b/src/components/AdvancePayment/AdvancePaymentList.jsx index e9113b48..c6e9d004 100644 --- a/src/components/AdvancePayment/AdvancePaymentList.jsx +++ b/src/components/AdvancePayment/AdvancePaymentList.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo } from "react"; -import { useExpenseTransactions } from "../../hooks/useExpense"; +import { useExpenseAllTransactionsList, useExpenseTransactions } from "../../hooks/useExpense"; import Error from "../common/Error"; import { formatUTCToLocalTime } from "../../utils/dateUtils"; import Loader, { SpinnerLoader } from "../common/Loader"; @@ -11,11 +11,10 @@ import { employee } from "../../data/masters"; import { useAdvancePaymentContext } from "../../pages/AdvancePayment/AdvancePaymentPage"; import { formatFigure } from "../../utils/appUtils"; -const AdvancePaymentList = ({ employeeId }) => { - const { setBalance } = useAdvancePaymentContext(); +const AdvancePaymentList = ({ employeeId, searchString }) => { + const { setBalance } = useAdvancePaymentContext(); const { data, isError, isLoading, error, isFetching } = useExpenseTransactions(employeeId, { enabled: !!employeeId }); - const records = Array.isArray(data) ? data : []; let currentBalance = 0; @@ -85,7 +84,7 @@ const AdvancePaymentList = ({ employeeId }) => { key: "date", label: ( <> - Date + Date ), align: "text-start", diff --git a/src/components/AdvancePayment/AdvancePaymentList1.jsx b/src/components/AdvancePayment/AdvancePaymentList1.jsx new file mode 100644 index 00000000..e17cd1b1 --- /dev/null +++ b/src/components/AdvancePayment/AdvancePaymentList1.jsx @@ -0,0 +1,100 @@ +import React from 'react' +import Avatar from "../../components/common/Avatar"; // <-- ADD THIS +import { useExpenseAllTransactionsList } from '../../hooks/useExpense'; +import { useNavigate } from 'react-router-dom'; +import { formatFigure } from '../../utils/appUtils'; + +const AdvancePaymentList1 = ({ searchString }) => { + + const { data, isError, isLoading, error } = + useExpenseAllTransactionsList(searchString); + + const rows = data || []; + const navigate = useNavigate(); + + const columns = [ + { + key: "employee", + label: "Employee Name", + align: "text-start", + customRender: (r) => ( +
navigate(`/advance-payment/${r.id}`)} + style={{ cursor: "pointer" }}> + + + + {r.firstName} {r.lastName} + +
+ ), + }, + { + key: "jobRoleName", + label: "Job Role", + align: "text-start", + customRender: (r) => ( + + {r.jobRoleName} + + ), + }, + { + key: "balanceAmount", + label: "Balance (₹)", + align: "text-end", + customRender: (r) => ( + + {formatFigure(r.balanceAmount, { + // type: "currency", + currency: "INR", + })} + + ), + }, + ]; + + if (isLoading) return

Loading...

; + if (isError) return

{error.message}

; + + return ( +
+
+ + + + {columns.map((col) => ( + + ))} + + + + + {rows.length > 0 ? ( + rows.map((row) => ( + + {columns.map((col) => ( + + ))} + + )) + ) : ( + + + + )} + +
+ {col.label} +
+ {col.customRender + ? col.customRender(row) + : col.getValue(row)} +
+ No Employees Found +
+
+
+ ) +} + +export default AdvancePaymentList1; diff --git a/src/components/Dashboard/ProjectCompletionChart.jsx b/src/components/Dashboard/ProjectCompletionChart.jsx index b8c656b8..f1f63936 100644 --- a/src/components/Dashboard/ProjectCompletionChart.jsx +++ b/src/components/Dashboard/ProjectCompletionChart.jsx @@ -2,15 +2,19 @@ import React, { useState } from "react"; import HorizontalBarChart from "../Charts/HorizontalBarChart"; import { useProjects } from "../../hooks/useProjects"; import { ITEMS_PER_PAGE } from "../../utils/constants"; +import { useProjectCompletionStatus } from "../../hooks/useDashboard_Data"; const ProjectCompletionChart = () => { - const [currentPage, setCurrentPage] = useState(1); - const { data: projects, isLoading: loading, isError, error } = useProjects(50,currentPage); - // Bar chart logic - const projectNames = projects?.data?.map((p) => p.name) || []; + const { + data: projects, + isLoading: loading, + isError, + error, + } = useProjectCompletionStatus(); + const projectNames = projects?.map((p) => p.name) || []; const projectProgress = - projects?.data?.map((p) => { + projects?.map((p) => { const completed = p.completedWork || 0; const planned = p.plannedWork || 1; const percent = planned ? (completed / planned) * 100 : 0; diff --git a/src/components/Expenses/ManageExpense.jsx b/src/components/Expenses/ManageExpense.jsx index d595421d..724899c7 100644 --- a/src/components/Expenses/ManageExpense.jsx +++ b/src/components/Expenses/ManageExpense.jsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { defaultExpense, ExpenseSchema } from "./ExpenseSchema"; import { formatFileSize, localToUtc } from "../../utils/appUtils"; -import { useProjectName } from "../../hooks/useProjects"; +import { useProjectName } from "../../hooks/useProjects"; import { useDispatch, useSelector } from "react-redux"; import { changeMaster } from "../../slices/localVariablesSlice"; import useMaster, { @@ -32,6 +32,8 @@ import Label from "../common/Label"; import EmployeeSearchInput from "../common/EmployeeSearchInput"; import Filelist from "./Filelist"; import { DEFAULT_CURRENCY } from "../../utils/constants"; +import SelectEmployeeServerSide, { SelectProjectField } from "../common/Forms/SelectFieldServerSide"; +import { useAllocationServiceProjectTeam } from "../../hooks/useServiceProject"; const ManageExpense = ({ closeModal, expenseToEdit = null }) => { const { @@ -40,6 +42,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { error: ExpenseErrorLoad, } = useExpense(expenseToEdit); const [expenseCategory, setExpenseCategory] = useState(); + const [selectedEmployees, setSelectedEmployees] = useState([]); const dispatch = useDispatch(); const { expenseCategories, @@ -83,11 +86,11 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { loading: StatusLoadding, error: stausError, } = useExpenseStatus(); - const { - data: employees, - isLoading: EmpLoading, - isError: isEmployeeError, - } = useEmployeesNameByProject(selectedproject); + // const { + // data: employees, + // isLoading: EmpLoading, + // isError: isEmployeeError, + // } = useEmployeesNameByProject(selectedproject); const files = watch("billAttachments"); const onFileChange = async (e) => { @@ -150,6 +153,15 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { } }; + const { mutate: AllocationTeam, isPending1 } = useAllocationServiceProjectTeam( + () => { + setSelectedEmployees([]); + setSeletingEmp({ + employee: null, + isOpen: false, + }); + } + ); useEffect(() => { if (expenseToEdit && data) { reset({ @@ -168,19 +180,19 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { currencyId: data.currency.id || DEFAULT_CURRENCY, billAttachments: data.documents ? data.documents.map((doc) => ({ - fileName: doc.fileName, - base64Data: null, - contentType: doc.contentType, - documentId: doc.documentId, - fileSize: 0, - description: "", - preSignedUrl: doc.preSignedUrl, - isActive: doc.isActive ?? true, - })) + fileName: doc.fileName, + base64Data: null, + contentType: doc.contentType, + documentId: doc.documentId, + fileSize: 0, + description: "", + preSignedUrl: doc.preSignedUrl, + isActive: doc.isActive ?? true, + })) : [], - }); + }); } - }, [data, reset, employees]); + }, [data, reset]); const { mutate: ExpenseUpdate, isPending } = useUpdateExpense(() => handleClose() ); @@ -223,7 +235,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
-
+ {/*
@@ -245,6 +257,24 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { {errors.projectId && ( {errors.projectId.message} )} +
*/} +
+ + setValue("projectId", val, { + shouldDirty: true, + shouldValidate: true, + }) + } + /> + {errors.projectId && ( + {errors.projectId.message} + )}
@@ -314,9 +344,9 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { control={control} name="paidById" projectId={null} - forAll={expenseToEdit ? true : false} + forAll={true} /> -
+
@@ -423,10 +453,10 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { {errors.gstNumber.message} )}
- + -
-
+
+
@@ -452,24 +482,24 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { {errors.currencyId.message} )}
- {expenseCategory?.noOfPersonsRequired && ( -
- - - {errors.noOfPersons && ( - - {errors.noOfPersons.message} - - )} -
- )} -
+ {expenseCategory?.noOfPersonsRequired && ( +
+ + + {errors.noOfPersons && ( + + {errors.noOfPersons.message} + + )} +
+ )} +
@@ -540,7 +570,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { (fileError?.fileSize?.message || fileError?.contentType?.message || fileError?.base64Data?.message, - fileError?.documentId?.message) + fileError?.documentId?.message) }
))} @@ -565,8 +595,8 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { {isPending || createPending ? "Please Wait..." : expenseToEdit - ? "Update" - : "Save as Draft"} + ? "Update" + : "Save as Draft"}
diff --git a/src/components/Layout/Header.jsx b/src/components/Layout/Header.jsx index 0437539e..0dca90e5 100644 --- a/src/components/Layout/Header.jsx +++ b/src/components/Layout/Header.jsx @@ -50,8 +50,11 @@ const Header = () => { const isRecurringExpense = /^\/recurring-payment$/.test(pathname); const isAdvancePayment = /^\/advance-payment$/.test(pathname); const isServiceProjectPage = /^\/service-projects\/[0-9a-fA-F-]{36}$/.test(pathname); + const isAdvancePayment1 = + /^\/advance-payment(\/[0-9a-fA-F-]{36})?$/.test(pathname); - return !(isDirectoryPath || isProfilePage || isExpensePage || isPaymentRequest || isRecurringExpense || isAdvancePayment ||isServiceProjectPage); + + return !(isDirectoryPath || isProfilePage || isExpensePage || isPaymentRequest || isRecurringExpense || isAdvancePayment ||isServiceProjectPage || isAdvancePayment1); }; const allowedProjectStatusIds = [ "603e994b-a27f-4e5d-a251-f3d69b0498ba", diff --git a/src/components/Layout/Sidebar.jsx b/src/components/Layout/Sidebar.jsx index 1f4b63b8..4fa7cd9f 100644 --- a/src/components/Layout/Sidebar.jsx +++ b/src/components/Layout/Sidebar.jsx @@ -25,8 +25,7 @@ const Sidebar = () => { /> */} - @@ -35,12 +34,13 @@ const Sidebar = () => { OnField Work .com - + - + + - +
diff --git a/src/components/PaymentRequest/ActionPaymentRequest.jsx b/src/components/PaymentRequest/ActionPaymentRequest.jsx index 5f3cc1d9..52222b55 100644 --- a/src/components/PaymentRequest/ActionPaymentRequest.jsx +++ b/src/components/PaymentRequest/ActionPaymentRequest.jsx @@ -40,7 +40,6 @@ const ActionPaymentRequest = ({ requestId }) => { error: PaymentModeError, } = usePaymentMode(); - console.log("Kartik", data) const IsReview = useHasUserPermission(REVIEW_EXPENSE); const [imageLoaded, setImageLoaded] = useState({}); diff --git a/src/components/PaymentRequest/PaymentRequestList.jsx b/src/components/PaymentRequest/PaymentRequestList.jsx index e533634f..0c9edfd5 100644 --- a/src/components/PaymentRequest/PaymentRequestList.jsx +++ b/src/components/PaymentRequest/PaymentRequestList.jsx @@ -85,7 +85,7 @@ const PaymentRequestList = ({ filters, filterData, removeFilterChip, clearFilter key: "paymentRequestUID", label: "Request ID", align: "text-start mx-2", - getValue: (e) => e.paymentRequestUID || "N/A", + getValue: (e) =>
{e.paymentRequestUID || "N/A"} {e.isAdvancePayment && Adv}
, }, { key: "title", diff --git a/src/components/PaymentRequest/ViewPaymentRequest.jsx b/src/components/PaymentRequest/ViewPaymentRequest.jsx index d8e936fe..7bf122db 100644 --- a/src/components/PaymentRequest/ViewPaymentRequest.jsx +++ b/src/components/PaymentRequest/ViewPaymentRequest.jsx @@ -148,7 +148,7 @@ const ViewPaymentRequest = ({ requestId }) => {
-
PR No : {data?.paymentRequestUID}
+
PR No : {data?.paymentRequestUID} {data.isAdvancePayment && Advance}
{ if (!workItem) return; - console.log(workItem) reset({ activityID: String( workItem?.workItem?.activityId || workItem?.activityMaster?.id diff --git a/src/components/RecurringExpense/RecurringExpenseList.jsx b/src/components/RecurringExpense/RecurringExpenseList.jsx index 338a3f38..a4c45fba 100644 --- a/src/components/RecurringExpense/RecurringExpenseList.jsx +++ b/src/components/RecurringExpense/RecurringExpenseList.jsx @@ -166,7 +166,7 @@ const RecurringExpenseList = ({ search, filterStatuses }) => { } ); }; - +console.log("Tanish",filteredData) return ( <> {IsDeleteModalOpen && ( diff --git a/src/components/ServiceProject/JobList.jsx b/src/components/ServiceProject/JobList.jsx index 8a7f1639..84066253 100644 --- a/src/components/ServiceProject/JobList.jsx +++ b/src/components/ServiceProject/JobList.jsx @@ -4,24 +4,49 @@ import { getJobStatusBadge, getNextBadgeColor, } from "../../utils/appUtils"; -import { useServiceProjectJobs } from "../../hooks/useServiceProject"; -import { ITEMS_PER_PAGE } from "../../utils/constants"; +import { useServiceProjectJobs, useUpdateServiceProjectJob } from "../../hooks/useServiceProject"; +import { ITEMS_PER_PAGE, JOBS_STATUS_IDS } from "../../utils/constants"; import EmployeeAvatarGroup from "../common/EmployeeAvatarGroup"; import { formatUTCToLocalTime } from "../../utils/dateUtils"; import { SpinnerLoader } from "../common/Loader"; import { useParams } from "react-router-dom"; import ProjectPage from "../../pages/project/ProjectPage"; import { useServiceProjectJobContext } from "./Jobs"; +import ConfirmModal from "../common/ConfirmModal"; -const JobList = () => { +const JobList = ({ isArchive }) => { const { setSelectedJob, setManageJob } = useServiceProjectJobContext(); + const { mutate: UpdateJob,isPending } = useUpdateServiceProjectJob(() => { + }); const { projectId } = useParams(); const { data, isLoading, isError, error } = useServiceProjectJobs( ITEMS_PER_PAGE, 1, true, - projectId + projectId, + isArchive ); + const [isArchiveModalOpen, setIsArchiveModalOpen] = useState(false); + const [archiveJobId, setArchiveJobId] = useState(null); + const [isArchiveAction, setIsArchiveAction] = useState(true); + + const handleArchive = () => { + const payload = [ + { + op: "replace", + path: "/isArchive", + value: isArchiveAction, + }, + ]; + UpdateJob({ + id: archiveJobId, + payload, + isArchiveAction, + }); + + setIsArchiveModalOpen(false); + setArchiveJobId(null); + }; const jobGrid = [ { @@ -35,12 +60,17 @@ const JobList = () => { label: "Title", getValue: (e) => ( setSelectedJob({ showCanvas: true, job: e?.id })} + onClick={() => { + if (!isArchive) { + setSelectedJob({ showCanvas: true, job: e?.id }); + } + }} + title={e?.title} > {e?.title} @@ -49,6 +79,7 @@ const JobList = () => { className: "text-start", }, + { key: "dueDate", label: "Due On", @@ -86,92 +117,155 @@ const JobList = () => { }, ]; + const canArchive = (statusId) => { + const closedId = JOBS_STATUS_IDS.find((s) => s.label === "Closed")?.id; + const reviewDoneId = JOBS_STATUS_IDS.find((s) => s.label === "Review Done")?.id; + + return statusId === closedId || statusId === reviewDoneId; + }; + + return ( -
- - - - {jobGrid.map((col) => ( -
-
{col.label}
+ <> + {isArchiveModalOpen && ( + setIsArchiveModalOpen(false)} + loading={isPending} + /> + )} + + +
+ + + + {jobGrid.map((col) => ( + + ))} + - ))} - - - + + - - {Array.isArray(data?.data) && data.data.length > 0 ? ( - data.data.map((row, i) => ( - - {jobGrid.map((col) => ( - - ))} - + {Array.isArray(data?.data) && data.data.length > 0 ? ( + data.data.map((row, i) => ( + + {jobGrid.map((col) => ( + + ))} + + + )) + ) : ( + + - )) - ) : ( - - - - )} - -
+
{col.label}
+
+ Actions - Actions -
- setSelectedJob({ showCanvas: true, job: row?.id }) - } - > - {col.getValue(row)} - -
- -
-
+ // setSelectedJob({ showCanvas: true, job: row?.id }) + // } + onClick={() => { + if (!isArchive) { + setSelectedJob({ showCanvas: true, job: e?.id }); } - > - View - + }} + + > + {col.getValue(row)} + +
+ +
+ + {!isArchive && ( + <> + + + + + )} + + {isArchive && ( + + )} + + {!isArchive && canArchive(row?.status?.id) && ( + + )} + +
- <> - -
- +
+ {isLoading ? : "Not Found Jobs."}
- {isLoading ? : "Not Found Jobs."} -
-
+ )} + +
+
+ ); }; diff --git a/src/components/ServiceProject/Jobs.jsx b/src/components/ServiceProject/Jobs.jsx index d6339174..832e90e6 100644 --- a/src/components/ServiceProject/Jobs.jsx +++ b/src/components/ServiceProject/Jobs.jsx @@ -5,8 +5,8 @@ import { useServiceProjects } from "../../hooks/useServiceProject"; import { ITEMS_PER_PAGE } from "../../utils/constants"; import OffcanvasComponent from "../common/OffcanvasComponent"; import showToast from "../../services/toastService"; -import ManageJob from "./ManageJob"; -import ManageJobTicket from "./ManageJobTicket"; +import ManageJob from "./ServiceProjectJob/ManageJob"; +import ManageJobTicket from "./ServiceProjectJob/ManageJobTicket"; import GlobalModel from "../common/GlobalModel"; import PreviewDocument from "../Expenses/PreviewDocument"; @@ -21,6 +21,8 @@ export const useServiceProjectJobContext = () => { }; const Jobs = () => { const [manageJob, setManageJob] = useState({ isOpen: false, jobId: null }); + const [showArchive, setShowArchive] = useState(false); + const [showCanvas, setShowCanvas] = useState(false); const [selectedProject, setSelectedProject] = useState(null); const [selectJob, setSelectedJob] = useState({ @@ -58,21 +60,43 @@ const Jobs = () => {
-
-
-
- -
+
+ + {/* LEFT — Tabs */} +
+
- + {/* RIGHT — New Job button */} +
+ +
+ + {/* Job List */} +
+ ); diff --git a/src/components/ServiceProject/ServiceProfile.jsx b/src/components/ServiceProject/ServiceProfile.jsx new file mode 100644 index 00000000..30864106 --- /dev/null +++ b/src/components/ServiceProject/ServiceProfile.jsx @@ -0,0 +1,95 @@ +import React from 'react' +import { formatUTCToLocalTime } from '../../utils/dateUtils' + +const ServiceProfile = ({data,setIsOpenModal}) => { + return ( +
+
+
+ {" "} + + Project Profile +
+
+
+
    + +
  • +
    + + Name: +
    + +
    + {data.name} +
    +
  • +
  • +
    + + Nick Name: +
    + {data.shortName} +
  • +
  • +
    + + Assign Date: +
    + + {data.assignedDate ? formatUTCToLocalTime(data.assignedDate) : "NA"} + +
  • + +
  • +
    + + Status: +
    + {data?.status.status} +
  • +
  • +
    + + Contact: +
    + {data.contactName} +
  • +
  • + {/* Label section with icon */} +
    + + Address: +
    + + {/* Content section that wraps nicely */} +
    + {data.address} +
    +
  • + + + +
  • + + + + + +
  • +
+ +
+
+ ) +} + +export default ServiceProfile diff --git a/src/components/ServiceProject/ServiceProjectBranch/BranchDetails.jsx b/src/components/ServiceProject/ServiceProjectBranch/BranchDetails.jsx new file mode 100644 index 00000000..363fa19f --- /dev/null +++ b/src/components/ServiceProject/ServiceProjectBranch/BranchDetails.jsx @@ -0,0 +1,86 @@ +import React, { useState } from "react"; +import { useBranchDetails } from "../../../hooks/useServiceProject"; +import { SpinnerLoader } from "../../common/Loader"; +import Error from "../../common/Error"; +import { BranchDetailsSkeleton } from "../ServiceProjectSeketon"; + +const BranchDetails = ({ branch }) => { + const [copied, setCopied] = useState(false); + const { data, isLoading, isError, error } = useBranchDetails(branch); + + if (isLoading) return ; + if (isError) return ; + + let contactInfo = []; + try { + contactInfo = JSON.parse(data?.contactInformation || "[]"); + } catch (e) {} + + const googleMapUrl = data?.googleMapUrl || data?.locationLink; + + const handleCopy = async () => { + if (!googleMapUrl) return; + await navigator.clipboard.writeText(googleMapUrl); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+
+ + Branch Details +
+ + + + + + + + {/* Contact persons */} + {contactInfo.map((person, index) => ( +
+
{person.contactPerson}
+ + + +
+ ))} + + {/* Map Link */} + {googleMapUrl && ( +
+ + View on Google Maps + + + +
+ )} +
+ ); +}; + +const DetailRow = ({ label, value }) => ( +
+
+ {label}: +
+
+ {value || "N/A"} +
+
+); + +export default BranchDetails; diff --git a/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx b/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx new file mode 100644 index 00000000..7b51d616 --- /dev/null +++ b/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx @@ -0,0 +1,358 @@ +import React, { useEffect } from "react"; +import { useProjectName } from "../../../hooks/useProjects"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import Label from "../../common/Label"; +import { + useBranchDetails, + useBranchTypes, + useCreateBranch, + useServiceProjects, + useUpdateBranch, +} from "../../../hooks/useServiceProject"; +import { useAppForm } from "../../../hooks/appHooks/useAppForm"; +import { useParams } from "react-router-dom"; +import { BranchSchema, defaultBranches } from "../ServiceProjectSchema"; +import InputSuggessionField from "../../common/Forms/InputSuggesstionField"; +import InputSuggestions from "../../common/InputSuggestion"; + +const ManageBranch = ({ closeModal, BranchToEdit = null }) => { + const { + data, + isLoading, + isError, + error: requestError, + } = useBranchDetails(BranchToEdit); + const { data: branchTypes } = useBranchTypes(); + const [contacts, setContacts] = React.useState([ + { + contactPerson: "", + designation: "", + contactEmails: [""], + contactNumbers: [""], + }, + ]); + + const { projectId } = useParams(); + const schema = BranchSchema(); + const { + register, + control, + watch, + handleSubmit, + setValue, + reset, + formState: { errors }, + } = useAppForm({ + resolver: zodResolver(schema), + defaultValues: { + ...defaultBranches, + projectId: projectId || "", + }, + }); + + const handleClose = () => { + reset(); + closeModal(); + }; + + useEffect(() => { + if (BranchToEdit && data) { + reset({ + branchName: data.branchName || "", + projectId: data.project?.id || projectId || "", + address: data.address || "", + branchType: data.branchType || "", + googleMapUrl: data.googleMapUrl || "", + }); + + if (data.contactInformation) { + try { + setContacts(JSON.parse(data.contactInformation)); + } catch { + setContacts([]); + } + } + } + }, [data, reset]); + + const { mutate: CreateServiceBranch, isPending: createPending } = + useCreateBranch(() => { + handleClose(); + }); + const { mutate: ServiceBranchUpdate, isPending } = useUpdateBranch(() => + handleClose() + ); + + const onSubmit = (formdata) => { + let payload = { + ...data, + ...formdata, + projectId, + contactInformation: JSON.stringify(contacts), // ← important + }; + + if (BranchToEdit) { + ServiceBranchUpdate({ id: data.id, payload }); + } else { + CreateServiceBranch(payload); + } + }; + + return ( +
+
+ {BranchToEdit ? "Update Branch" : "Create Branch"} +
+
+
+
+ + + {errors.branchName && ( + {errors.branchName.message} + )} +
+
+ + + + setValue("branchType", val, { shouldValidate: true }) + } + error={errors.branchType?.message} + /> + + +
+
+ +
+
+ + + {errors.googleMapUrl && ( + + {errors.googleMapUrl.message} + + )} +
+
+ +
+
+ + + {contacts.map((item, index) => ( +
+
+ {" "} +
+ + setContacts(contacts.filter((_, i) => i !== index)) + } + > +
+
+
+
+ + { + const list = [...contacts]; + list[index].contactPerson = e.target.value; + setContacts(list); + }} + /> +
+ +
+ + { + const list = [...contacts]; + list[index].designation = e.target.value; + setContacts(list); + }} + /> +
+
+ + {/* Numbers Section */} + + + {item.contactNumbers.map((num, numIndex) => ( +
+ { + const value = e.target.value.replace(/\D/g, ""); // remove non-digit characters + const list = [...contacts]; + list[index].contactNumbers[numIndex] = value; + setContacts(list); + }} + /> + + {/* Show PLUS only on last row */} + {numIndex === item.contactNumbers.length - 1 ? ( + { + const list = [...contacts]; + list[index].contactNumbers.push(""); + setContacts(list); + }} + > + ) : ( + { + const list = [...contacts]; + list[index].contactNumbers.splice(numIndex, 1); + setContacts(list); + }} + > + )} +
+ ))} + +
+ + {/* Emails Section */} + + + {item.contactEmails.map((email, emailIndex) => ( +
+ { + const list = [...contacts]; + list[index].contactEmails[emailIndex] = e.target.value; + setContacts(list); + }} + /> + + {/* Show PLUS only on the last row */} + {emailIndex === item.contactEmails.length - 1 ? ( + { + const list = [...contacts]; + list[index].contactEmails.push(""); + setContacts(list); + }} + > + ) : ( + { + const list = [...contacts]; + list[index].contactEmails.splice(emailIndex, 1); + setContacts(list); + }} + > + )} +
+ ))} +
+ ))} + + +
+
+ +
+
+ + + + {errors?.comment && ( + {errors?.comment?.message} + )} +
+
+ {/*
+ {files?.length > 0 && ( + + )} +
*/} +
+
document.getElementById("attachments").click()} + className="cursor-pointer" + style={{ whiteSpace: "nowrap" }} + > + { + onFileChange(e); + e.target.value = ""; + }} + /> + + Add Attachment +
+ + +
+
+
+ ); +}; + +export default UpdateJobComment; diff --git a/src/components/ServiceProject/ServiceProjectProfile.jsx b/src/components/ServiceProject/ServiceProjectProfile.jsx index dfda4880..6ef359ff 100644 --- a/src/components/ServiceProject/ServiceProjectProfile.jsx +++ b/src/components/ServiceProject/ServiceProjectProfile.jsx @@ -4,18 +4,27 @@ import { useServiceProject } from "../../hooks/useServiceProject"; import { formatUTCToLocalTime } from "../../utils/dateUtils"; import ManageServiceProject from "./ManageServiceProject"; import GlobalModel from "../common/GlobalModel"; +import { SpinnerLoader } from "../common/Loader"; +import ServiceBranch from "./ServiceProjectBranch/ServiceBranch"; +import ServiceProfile from "./ServiceProfile"; const ServiceProjectProfile = () => { const { projectId } = useParams(); const [IsOpenModal, setIsOpenModal] = useState(false); const { data, isLoading, isError, error } = useServiceProject(projectId); - if (isLoading) { - return
Loadng.
; - } + if (isLoading) + return ( +
+ +
+ ); return ( <> {IsOpenModal && ( - setIsOpenModal(false)}> + setIsOpenModal(false)} + > setIsOpenModal(false)} @@ -24,98 +33,13 @@ const ServiceProjectProfile = () => { )}
-
-
-
-
- {" "} - - Project Profile -
-
-
-
    - -
  • -
    - - Name: -
    - - {/* Content section that wraps nicely */} -
    - {data.name} -
    -
  • -
  • -
    - - Nick Name: -
    - {data.shortName} -
  • -
  • -
    - - Assign Date: -
    - - {data.assignedDate ? formatUTCToLocalTime(data.assignedDate) : "NA"} - -
  • - -
  • -
    - - Status: -
    - {data?.status.status} -
  • -
  • -
    - - Contact: -
    - {data.contactName} -
  • -
  • - {/* Label section with icon */} -
    - - Address: -
    - - {/* Content section that wraps nicely */} -
    - {data.address} -
    -
  • - - - -
  • {/* Added mt-4 for some top margin */} - - {/* Added mt-4 for some top margin */} - - - - -
  • -
- -
-
+
+
- +
+ +
); diff --git a/src/components/ServiceProject/ServiceProjectSchema.jsx b/src/components/ServiceProject/ServiceProjectSchema.jsx index 5a5727b7..589f1497 100644 --- a/src/components/ServiceProject/ServiceProjectSchema.jsx +++ b/src/components/ServiceProject/ServiceProjectSchema.jsx @@ -50,6 +50,10 @@ export const defaultProjectValues = { //#endregion + + + + //#region JobSchema export const TagSchema = z.object({ @@ -70,6 +74,8 @@ export const jobSchema = z.object({ tags: z.array(TagSchema).optional().default([]), statusId: z.string().optional().nullable(), + + projectBranchId: z.string().optional().nullable(), }); const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB @@ -109,6 +115,52 @@ export const defaultJobValue = { startDate: null, dueDate: null, tags: [], + branchId: null, }; //#endregion + + + + + +//#region Branch + +export const BranchSchema = () => + z.object({ + projectId: z + .string() + .trim() + .min(1, { message: "Project is required" }), + + branchName: z + .string() + .trim() + .min(1, { message: "Branch Name is required" }), + + contactInformation: z.string().optional(), + address: z + .string() + .trim() + .min(1, { message: "Address is required" }), + + branchType: z + .string() + .trim() + .min(1, { message: "Branch Type is required" }), + + googleMapUrl: z + .string() + }); + +export const defaultBranches = { + branchName: "", + projectId: "", + contactInformation: "", + address: "", + branchType: "", + googleMapUrl: "", +}; + + +//#endregion \ No newline at end of file diff --git a/src/components/ServiceProject/ServiceProjectSeketon.jsx b/src/components/ServiceProject/ServiceProjectSeketon.jsx new file mode 100644 index 00000000..b4b3a93e --- /dev/null +++ b/src/components/ServiceProject/ServiceProjectSeketon.jsx @@ -0,0 +1,138 @@ +import React from "react"; + +const SkeletonLine = ({ height = 18, width = "100%", className = "" }) => ( +
+); + +export const BranchDetailsSkeleton = () => { + return ( +
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+
+ ); +}; + +export const JobDetailsSkeleton = () => { + return ( +
+
+ {/* Title */} + + + {/* Job ID + Status */} +
+ +
+ + +
+
+ + {/* Description */} + + + {/* Created Date */} +
+ +
+ + {/* Start / Due Date */} +
+ + +
+ + {/* Branch Name */} +
+ + +
+ + {/* Created By */} +
+
+ +
+ +
+ {/* Avatar */} + +
+
+ + {/* Assigned To */} +
+
+ +
+
+ + {/* Tabs */} +
+
+ + + +
+ + +
+
+
+ ); +}; diff --git a/src/components/Tenant/SubScriptionHistory.jsx b/src/components/Tenant/SubScriptionHistory.jsx index a587a84e..3972f3d8 100644 --- a/src/components/Tenant/SubScriptionHistory.jsx +++ b/src/components/Tenant/SubScriptionHistory.jsx @@ -104,8 +104,7 @@ const SubScriptionHistory = ({ tenantId }) => { + + {open && !isLoading && ( +
    + {suggesstionList.map((option, i) => ( +
  • + +
  • + ))} +
+ )} +
+ ); +}; + +export default InputSuggessionField; diff --git a/src/components/common/Forms/SelectFieldServerSide.jsx b/src/components/common/Forms/SelectFieldServerSide.jsx index f7aa70eb..7befef92 100644 --- a/src/components/common/Forms/SelectFieldServerSide.jsx +++ b/src/components/common/Forms/SelectFieldServerSide.jsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from "react"; import Label from "../Label"; import { useDebounce } from "../../../utils/appUtils"; import { useEmployeesName } from "../../../hooks/useEmployees"; +import { useProjectBothName } from "../../../hooks/useProjects"; const SelectEmployeeServerSide = ({ label = "Select", @@ -154,7 +155,9 @@ const SelectEmployeeServerSide = ({ )} {!isLoading && options.length === 0 && ( -
  • No results found
  • +
  • + No results found +
  • )} {!isLoading && @@ -183,24 +186,29 @@ const SelectEmployeeServerSide = ({ }; export default SelectEmployeeServerSide; - -export const SelectProjectField = ()=>{ - const [searchText, setSearchText] = useState(""); +export const SelectProjectField = ({ + label = "Select", + placeholder = "Select Project", + required = false, + value = null, + onChange, + valueKey = "id", + isFullObject = false, + isMultiple = false, + isAllProject = false, +}) => { + const [searchText, setSearchText] = useState(""); const debounce = useDebounce(searchText, 300); - const { data, isLoading } = useEmployeesName( - projectId, - debounce, - isAllEmployee - ); + const { data, isLoading } = useProjectBothName(debounce); - const options = data?.data ?? []; + const options = data ?? []; const [open, setOpen] = useState(false); const dropdownRef = useRef(null); - const getDisplayName = (emp) => { - if (!emp) return ""; - return `${emp.firstName || ""} ${emp.lastName || ""}`.trim(); + const getDisplayName = (project) => { + if (!project) return ""; + return `${project.name || ""}`.trim(); }; /** ----------------------------- @@ -304,7 +312,7 @@ export const SelectProjectField = ()=>{ top: "100%", left: 0, zIndex: 1050, - marginTop: "4px", + marginTop: "2px", borderRadius: "0.375rem", overflow: "hidden", }} @@ -324,7 +332,9 @@ export const SelectProjectField = ()=>{ )} {!isLoading && options.length === 0 && ( -
  • No results found
  • +
  • + No results found +
  • )} {!isLoading && @@ -350,4 +360,213 @@ export const SelectProjectField = ()=>{ )}
    ); -}; \ No newline at end of file +}; + +export const SelectFieldSearch = ({ + label = "Select", + placeholder = "Select ", + required = false, + value = null, + onChange, + valueKey = "id", + labelKey = "name", + disabled = false, + isFullObject = false, + isMultiple = false, + hookParams, + useFetchHook, +}) => { + const [searchText, setSearchText] = useState(""); + const debounce = useDebounce(searchText, 300); + + const { data, isLoading } = useFetchHook(...hookParams, debounce); + const options = data?.data ?? []; + const [open, setOpen] = useState(false); + const dropdownRef = useRef(null); + + const getDisplayName = (entity) => { + if (!entity) return ""; + return `${entity[labelKey] || ""}`.trim(); + }; + + /** ----------------------------- + * SELECTED OPTION (SINGLE) + * ----------------------------- */ + 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])); + } + } + + /** Main button label */ + const displayText = !isMultiple + ? getDisplayName(selectedSingle) || placeholder + : selectedList.length > 0 + ? selectedList.map((e) => getDisplayName(e)).join(", ") + : placeholder; + + /** ----------------------------- + * HANDLE OUTSIDE CLICK + * ----------------------------- */ + useEffect(() => { + const handleClickOutside = (e) => { + 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([]); + + 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 && 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); + } + }); + } + + setMergedOptions(finalList); + }, [options, value]); + + /** ----------------------------- + * HANDLE SELECT + * ----------------------------- */ + const handleSelect = (option) => { + if (!isMultiple) { + // SINGLE SELECT + if (isFullObject) onChange(option); + else onChange(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])); + } + }; + + return ( +
    + {label && ( + + )} + + {/* MAIN BUTTON */} + + + {/* DROPDOWN */} + {open && ( +
      +
      + setSearchText(e.target.value)} + className="form-control form-control-sm" + placeholder="Search..." + disabled={disabled} + /> +
      + + {isLoading && ( +
    • Loading...
    • + )} + + {!isLoading && options.length === 0 && ( +
    • + No results found +
    • + )} + + {!isLoading && + options.map((option) => { + const isActive = isMultiple + ? selectedList.some((x) => x[valueKey] === option[valueKey]) + : selectedSingle && + selectedSingle[valueKey] === option[valueKey]; + + return ( +
    • + +
    • + ); + })} +
    + )} +
    + ); +}; diff --git a/src/components/common/GlobalModal/CommentEditor.jsx b/src/components/common/GlobalModal/CommentEditor.jsx index 68d4e353..5d4dc626 100644 --- a/src/components/common/GlobalModal/CommentEditor.jsx +++ b/src/components/common/GlobalModal/CommentEditor.jsx @@ -28,7 +28,6 @@ const CommentEditor = () => { const [value, setValue] = useState(""); const handleSubmit = () => { - console.log("Comment:", value); // Submit or handle content }; diff --git a/src/components/common/HoverPopup.jsx b/src/components/common/HoverPopup.jsx index e1c2dd4d..1063745b 100644 --- a/src/components/common/HoverPopup.jsx +++ b/src/components/common/HoverPopup.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef, useState } from "react"; +import React, { useEffect, useRef } from "react"; import { useDispatch, useSelector } from "react-redux"; import { closePopup, @@ -6,6 +6,10 @@ 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, @@ -13,6 +17,8 @@ const HoverPopup = ({ children, className = "", Mode = "hover", + align = "auto", + boundaryRef = null, }) => { const dispatch = useDispatch(); const visible = useSelector((s) => s.localVariables.popups[id] || false); @@ -23,11 +29,9 @@ const HoverPopup = ({ const handleMouseEnter = () => { if (Mode === "hover") dispatch(openPopup(id)); }; - const handleMouseLeave = () => { if (Mode === "hover") dispatch(closePopup(id)); }; - const handleClick = (e) => { if (Mode === "click") { e.stopPropagation(); @@ -35,74 +39,159 @@ const HoverPopup = ({ } }; + // Close on outside click when using click mode useEffect(() => { if (Mode !== "click" || !visible) return; - const handleOutside = (e) => { + const handler = (e) => { if ( - !popupRef.current?.contains(e.target) && - !triggerRef.current?.contains(e.target) + popupRef.current && + !popupRef.current.contains(e.target) && + triggerRef.current && + !triggerRef.current.contains(e.target) ) { dispatch(closePopup(id)); } }; - document.addEventListener("click", handleOutside); - return () => document.removeEventListener("click", handleOutside); - }, [visible, Mode, id]); + document.addEventListener("click", handler); + return () => document.removeEventListener("click", handler); + }, [Mode, visible, dispatch, id]); + + // Positioning effect: respects align prop and stays inside boundary (drawer) useEffect(() => { - if (!visible || !popupRef.current) return; + if (!visible || !popupRef.current || !triggerRef.current) return; - const popup = popupRef.current; - const rect = popup.getBoundingClientRect(); + // run in next frame so DOM/layout settles + requestAnimationFrame(() => { + const popup = popupRef.current; - popup.style.left = "50%"; - popup.style.right = "auto"; - popup.style.transform = "translateX(-50%)"; + // choose boundary: provided boundaryRef or nearest positioned parent (popup.parentElement) + const boundaryEl = + (boundaryRef && boundaryRef.current) || popup.parentElement; + if (!boundaryEl) return; - if (rect.right > window.innerWidth) { - popup.style.left = "auto"; - popup.style.right = "0"; - popup.style.transform = "none"; - } + const boundaryRect = boundaryEl.getBoundingClientRect(); + const triggerRect = triggerRef.current.getBoundingClientRect(); - if (rect.left < 0) { - popup.style.left = "0"; - popup.style.right = "auto"; - popup.style.transform = "none"; - } - }, [visible]); + // reset styles first + popup.style.left = ""; + popup.style.right = ""; + popup.style.transform = ""; + popup.style.top = ""; - return ( -
    -
    - {children} -
    + const popupRect = popup.getBoundingClientRect(); + const parentRect = boundaryRect; // alias - {visible && ( -
    e.stopPropagation()} - > - {title &&
    {title}
    } + // Convert trigger center to parent coordinates + const triggerCenterX = + triggerRect.left + triggerRect.width / 2 - parentRect.left; -
    {content}
    -
    - )} + // preferred left so popup center aligns to trigger center: + const preferredLeft = triggerCenterX - popupRect.width / 2; + + // Helpers to set styles in parent's coordinate system: + const setLeft = (leftPx) => { + popup.style.left = `${leftPx}px`; + popup.style.right = "auto"; + popup.style.transform = "none"; + }; + const setRight = (rightPx) => { + popup.style.left = "auto"; + popup.style.right = `${rightPx}px`; + popup.style.transform = "none"; + }; + + // If user forced align: + if (align === "left") { + // align popup's left to parent's left (0) + setLeft(0); + return; + } + if (align === "right") { + // align popup's right to parent's right (0) + setRight(0); + return; + } + if (align === "center") { + popup.style.left = "50%"; + popup.style.right = "auto"; + popup.style.transform = "translateX(-50%)"; + return; + } + + // align === "auto": try preferred centered position, but flip fully if overflow + // clamp preferredLeft to boundaries so it doesn't render partially outside + const leftIfCentered = Math.max( + 0, + Math.min(preferredLeft, parentRect.width - popupRect.width) + ); + + // if centered fits, use it + if (leftIfCentered === preferredLeft) { + setLeft(leftIfCentered); + return; + } + + // if centering would overflow right -> stick popup fully to left (left=0) + if (preferredLeft > parentRect.width - popupRect.width) { + // place popup so its right aligns to parent's right + // i.e., left = parent width - popup width + setLeft(parentRect.width - popupRect.width); + return; + } + + // if centering would overflow left -> stick popup fully to left=0 + if (preferredLeft < 0) { + setLeft(0); + return; + } + + // fallback center + setLeft(leftIfCentered); + }); + }, [visible, align, boundaryRef]); + + return ( +
    +
    + {children}
    - ); + + {visible && ( +
    e.stopPropagation()} + > + {title &&
    {title}
    } +
    {content}
    +
    + )} +
    +); + }; export default HoverPopup; diff --git a/src/components/common/InputSuggestion.jsx b/src/components/common/InputSuggestion.jsx index 5f6a8c52..0a79fa35 100644 --- a/src/components/common/InputSuggestion.jsx +++ b/src/components/common/InputSuggestion.jsx @@ -5,14 +5,13 @@ const InputSuggestions = ({ value, onChange, error, - disabled=false + disabled = false, }) => { const [filteredList, setFilteredList] = useState([]); const [showSuggestions, setShowSuggestions] = useState(false); const handleInputChange = (e) => { const val = e.target.value; onChange(val); - const matches = organizationList.filter((org) => org.toLowerCase().includes(val.toLowerCase()) ); @@ -26,7 +25,7 @@ const InputSuggestions = ({ }; return ( -
    +
    {showSuggestions && filteredList.length > 0 && (
      {filteredList.map((org) => (
    • handleSelectSuggestion(org)} - onMouseEnter={(e) => - (e.currentTarget.style.backgroundColor = "#f8f9fa") - } - onMouseLeave={(e) => - (e.currentTarget.style.backgroundColor = "transparent") - } + className={`dropdown-item ${ + org === value ? "active" : "" + }`} > {org}
    • ))}
    + )} {error && {error}} diff --git a/src/hooks/useDashboard_Data.jsx b/src/hooks/useDashboard_Data.jsx index 5f8f4319..1a9d724a 100644 --- a/src/hooks/useDashboard_Data.jsx +++ b/src/hooks/useDashboard_Data.jsx @@ -2,7 +2,6 @@ import { useState, useEffect } from "react"; import GlobalRepository from "../repositories/GlobalRepository"; import { useQuery } from "@tanstack/react-query"; - export const useDashboard_Data = ({ days, FromDate, projectId }) => { const [dashboard_data, setDashboard_Data] = useState([]); const [isLineChartLoading, setLoading] = useState(false); @@ -18,11 +17,13 @@ export const useDashboard_Data = ({ days, FromDate, projectId }) => { try { const payload = { days, - FromDate: FromDate || '', + FromDate: FromDate || "", projectId: projectId || null, }; - const response = await GlobalRepository.getDashboardProgressionData(payload); + const response = await GlobalRepository.getDashboardProgressionData( + payload + ); setDashboard_Data(response.data); } catch (err) { setError("Failed to fetch dashboard data."); @@ -38,123 +39,6 @@ export const useDashboard_Data = ({ days, FromDate, projectId }) => { return { dashboard_data, loading: isLineChartLoading, error }; }; - -// export const useDashboard_AttendanceData = (date, projectId) => { -// const [dashboard_Attendancedata, setDashboard_AttendanceData] = useState([]); -// const [isLineChartLoading, setLoading] = useState(false); -// const [error, setError] = useState(""); - -// useEffect(() => { -// const fetchData = async () => { -// setLoading(true); -// setError(""); - -// try { -// const response = await GlobalRepository.getDashboardAttendanceData(date, projectId); // date in 2nd param -// setDashboard_AttendanceData(response.data); -// } catch (err) { -// setError("Failed to fetch dashboard data."); -// console.error(err); -// } finally { -// setLoading(false); -// } -// }; - -// if (date && projectId !== null) { -// fetchData(); -// } -// }, [date, projectId]); - -// return { dashboard_Attendancedata, isLineChartLoading: isLineChartLoading, error }; -// }; - - -// 🔹 Dashboard Projects Card Data Hook -// export const useDashboardProjectsCardData = () => { -// const [projectsCardData, setProjectsData] = useState([]); -// const [loading, setLoading] = useState(false); -// const [error, setError] = useState(""); - -// useEffect(() => { -// const fetchProjectsData = async () => { -// setLoading(true); -// setError(""); - -// try { -// const response = await GlobalRepository.getDashboardProjectsCardData(); -// setProjectsData(response.data); -// } catch (err) { -// setError("Failed to fetch projects card data."); -// console.error(err); -// } finally { -// setLoading(false); -// } -// }; - -// fetchProjectsData(); -// }, []); - -// return { projectsCardData, loading, error }; -// }; - -// 🔹 Dashboard Teams Card Data Hook -// export const useDashboardTeamsCardData = (projectId) => { -// const [teamsCardData, setTeamsData] = useState({}); -// const [loading, setLoading] = useState(false); -// const [error, setError] = useState(""); - -// useEffect(() => { -// const fetchTeamsData = async () => { -// setLoading(true); -// setError(""); - -// try { -// const response = await GlobalRepository.getDashboardTeamsCardData(projectId); -// setTeamsData(response.data || {}); -// } catch (err) { -// setError("Failed to fetch teams card data."); -// console.error("Error fetching teams card data:", err); -// setTeamsData({}); -// } finally { -// setLoading(false); -// } -// }; - -// fetchTeamsData(); -// }, [projectId]); - -// return { teamsCardData, loading, error }; -// }; - -// export const useDashboardTasksCardData = (projectId) => { -// const [tasksCardData, setTasksData] = useState({}); -// const [loading, setLoading] = useState(false); -// const [error, setError] = useState(""); - -// useEffect(() => { -// const fetchTasksData = async () => { -// setLoading(true); -// setError(""); - -// try { -// const response = await GlobalRepository.getDashboardTasksCardData(projectId); -// setTasksData(response.data); -// } catch (err) { -// setError("Failed to fetch tasks card data."); -// console.error(err); -// setTasksData({}); -// } finally { -// setLoading(false); -// } -// }; - -// fetchTasksData(); -// }, [projectId]); - -// return { tasksCardData, loading, error }; -// }; - - export const useAttendanceOverviewData = (projectId, days) => { const [attendanceOverviewData, setAttendanceOverviewData] = useState([]); const [loading, setLoading] = useState(false); @@ -167,7 +51,10 @@ export const useAttendanceOverviewData = (projectId, days) => { setError(""); try { - const response = await GlobalRepository.getAttendanceOverview(projectId, days); + const response = await GlobalRepository.getAttendanceOverview( + projectId, + days + ); setAttendanceOverviewData(response.data); } catch (err) { setError("Failed to fetch attendance overview data."); @@ -182,7 +69,6 @@ export const useAttendanceOverviewData = (projectId, days) => { return { attendanceOverviewData, loading, error }; }; - // -------------------Query---------------------------- // export const useDashboard_Data = (days, FromDate, projectId)=>{ @@ -199,39 +85,47 @@ export const useAttendanceOverviewData = (projectId, days) => { // } // }) // } - +export const useProjectCompletionStatus = () => { + return useQuery({ + queryKey: ["projectCompletionStatus"], + queryFn: async () => { + const resp = await await GlobalRepository.getProjectCompletionStatus(); + return resp.data; + }, + }); +}; export const useDashboard_AttendanceData = (date, projectId) => { return useQuery({ queryKey: ["dashboardAttendances", date, projectId], queryFn: async () => { - - const resp = await await GlobalRepository.getDashboardAttendanceData(date, projectId) + const resp = await await GlobalRepository.getDashboardAttendanceData( + date, + projectId + ); return resp.data; - } - }) -} + }, + }); +}; export const useDashboardTeamsCardData = (projectId) => { return useQuery({ queryKey: ["dashboardTeams", projectId], queryFn: async () => { - - const resp = await GlobalRepository.getDashboardTeamsCardData(projectId) + const resp = await GlobalRepository.getDashboardTeamsCardData(projectId); return resp.data; - } - }) -} + }, + }); +}; export const useDashboardTasksCardData = (projectId) => { return useQuery({ queryKey: ["dashboardTasks", projectId], queryFn: async () => { - - const resp = await GlobalRepository.getDashboardTasksCardData(projectId) + const resp = await GlobalRepository.getDashboardTasksCardData(projectId); return resp.data; - } - }) -} + }, + }); +}; // export const useAttendanceOverviewData = (projectId, days) => { // return useQuery({ // queryKey:["dashboardAttendanceOverView",projectId], @@ -247,29 +141,30 @@ export const useDashboardProjectsCardData = () => { return useQuery({ queryKey: ["dashboardProjects"], queryFn: async () => { - const resp = await GlobalRepository.getDashboardProjectsCardData(); return resp.data; - } - }) -} + }, + }); +}; export const useExpenseAnalysis = (projectId, startDate, endDate) => { const hasBothDates = !!startDate && !!endDate; const noDatesSelected = !startDate && !endDate; - const shouldFetch = - noDatesSelected || - hasBothDates; + const shouldFetch = noDatesSelected || hasBothDates; return useQuery({ queryKey: ["expenseAnalysis", projectId, startDate, endDate], queryFn: async () => { - const resp = await GlobalRepository.getExpenseData(projectId, startDate, endDate); + const resp = await GlobalRepository.getExpenseData( + projectId, + startDate, + endDate + ); return resp.data; }, enabled: shouldFetch, - refetchOnWindowFocus: true, // refetch when you come back - refetchOnMount: "always", // always refetch on remount + refetchOnWindowFocus: true, // refetch when you come back + refetchOnMount: "always", // always refetch on remount staleTime: 0, }); }; @@ -280,17 +175,20 @@ export const useExpenseStatus = (projectId) => { queryFn: async () => { const resp = await GlobalRepository.getExpenseStatus(projectId); return resp.data; - } - }) -} + }, + }); +}; export const useExpenseDataByProject = (projectId, categoryId, months) => { return useQuery({ queryKey: ["expenseByProject", projectId, categoryId, months], queryFn: async () => { - const resp = await GlobalRepository.getExpenseDataByProject(projectId, categoryId, months); + const resp = await GlobalRepository.getExpenseDataByProject( + projectId, + categoryId, + months + ); return resp.data; }, - }); -}; \ No newline at end of file +}; diff --git a/src/hooks/useExpense.js b/src/hooks/useExpense.js index bc5c1da4..539f0afd 100644 --- a/src/hooks/useExpense.js +++ b/src/hooks/useExpense.js @@ -438,6 +438,15 @@ export const useExpenseTransactions = (employeeId)=>{ keepPreviousData:true, }) } +export const useExpenseAllTransactionsList = (searchString) => { + return useQuery({ + queryKey: ["transaction", searchString], + queryFn: async () => { + const resp = await ExpenseRepository.getAllTranctionList(searchString); + return resp.data; + }, + }); +}; //#endregion // ---------------------------Put Post Recurring Expense--------------------------------------- diff --git a/src/hooks/useProjects.js b/src/hooks/useProjects.js index fcb715b6..75467399 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -20,12 +20,15 @@ export const useCurrentService = () => { // ------------------------------Query------------------- -export const useProjects = (pageSize,pageNumber) => { +export const useProjects = (pageSize, pageNumber) => { const loggedUser = useSelector((store) => store.globalVariables.loginUser); return useQuery({ - queryKey: ["ProjectsList",pageSize,pageNumber], + queryKey: ["ProjectsList", pageSize, pageNumber], queryFn: async () => { - const response = await ProjectRepository.getProjectList(pageSize,pageNumber); + const response = await ProjectRepository.getProjectList( + pageSize, + pageNumber + ); return response?.data; }, enabled: !!loggedUser, @@ -153,7 +156,7 @@ export const useProjectsAllocationByEmployee = (employeeId) => { return { projectList, loading: isLoading, error, refetch }; }; -export const useProjectName = (provideAll=false) => { +export const useProjectName = (provideAll = false) => { const { data = [], isLoading, @@ -161,7 +164,7 @@ export const useProjectName = (provideAll=false) => { refetch, isError, } = useQuery({ - queryKey: ["basicProjectNameList",provideAll], + queryKey: ["basicProjectNameList", provideAll], queryFn: async () => { const res = await ProjectRepository.projectNameList(provideAll); return res.data || res; @@ -179,6 +182,19 @@ export const useProjectName = (provideAll=false) => { }; }; +export const useProjectBothName = (searchString) => { + return useQuery({ + queryKey: ["basic_bothProject", searchString], + queryFn: async () => { + const res = await ProjectRepository.projectNameListAll(searchString); + return res.data || res; + }, + onError: (error) => { + showToast(error.message || "Error while Fetching project Name", "error"); + }, + }); +}; + export const useProjectInfra = (projectId, serviceId) => { const { data: projectInfra, @@ -337,7 +353,7 @@ export const useEmployeeForTaskAssign = ( // -- -------------Mutation------------------------------- -export const useCreateProject = ( onSuccessCallback ) => { +export const useCreateProject = (onSuccessCallback) => { const queryClient = useQueryClient(); return useMutation({ @@ -368,7 +384,7 @@ export const useCreateProject = ( onSuccessCallback ) => { }); }; -export const useUpdateProject = ( onSuccessCallback ) => { +export const useUpdateProject = (onSuccessCallback) => { const queryClient = useQueryClient(); const { mutate, isPending, isSuccess, isError } = useMutation({ mutationFn: async ({ projectId, payload }) => { @@ -395,7 +411,6 @@ export const useUpdateProject = ( onSuccessCallback ) => { }, onError: (error) => { - console.log(error); showToast(error?.message || "Error while updating project", "error"); }, }); diff --git a/src/hooks/useServiceProject.jsx b/src/hooks/useServiceProject.jsx index 1e480118..82bc5713 100644 --- a/src/hooks/useServiceProject.jsx +++ b/src/hooks/useServiceProject.jsx @@ -59,8 +59,8 @@ export const useCreateServiceProject = (onSuccessCallback) => { onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to delete task", + error.message || + "Failed to delete task", "error" ); }, @@ -84,8 +84,8 @@ export const useUpdateServiceProject = (onSuccessCallback) => { onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to update project", + error.message || + "Failed to update project", "error" ); }, @@ -110,8 +110,8 @@ export const useActiveInActiveServiceProject = (onSuccessCallback) => { onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to update project", + error.message || + "Failed to update project", "error" ); }, @@ -138,8 +138,8 @@ export const useAllocationServiceProjectTeam = (onSuccessCallback) => { onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to update project", + error.message || + "Failed to update project", "error" ); }, @@ -148,22 +148,29 @@ export const useAllocationServiceProjectTeam = (onSuccessCallback) => { //#endregion + + + + + //#region Service Jobs export const useServiceProjectJobs = ( pageSize, pageNumber, isActive = true, - project + project, + isArchive ) => { return useQuery({ - queryKey: ["serviceProjectJobs", pageSize, pageNumber, isActive, project], + queryKey: ["serviceProjectJobs", pageSize, pageNumber, isActive, project, isArchive], queryFn: async () => { const resp = await ServiceProjectRepository.GetJobList( pageSize, pageNumber, isActive, - project + project, + isArchive ); return resp.data; }, @@ -181,7 +188,7 @@ export const useJobComments = (jobId, pageSize, pageNumber) => { ); return resp.data; }, - enabled:!!jobId, + enabled: !!jobId, initialPageParam: pageNumber, @@ -223,8 +230,8 @@ export const useAddCommentJob = (onSuccessCallback) => { onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to update project", + error.message || + "Failed to update project", "error" ); }, @@ -247,8 +254,8 @@ export const useCreateServiceProjectJob = (onSuccessCallback) => { onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to update project", + error.message || + "Failed to update project", "error" ); }, @@ -258,27 +265,162 @@ export const useCreateServiceProjectJob = (onSuccessCallback) => { export const useUpdateServiceProjectJob = (onSuccessCallback) => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ id, payload }) => { - // Call the repository patch + mutationFn: async ({ id, payload, isArchiveAction = false }) => { const resp = await ServiceProjectRepository.UpdateJob(id, payload); - return resp; + return { resp, isArchiveAction }; }, - onSuccess: () => { + + onSuccess: ({ isArchiveAction }) => { queryClient.invalidateQueries({ queryKey: ["serviceProjectJobs"] }); queryClient.invalidateQueries({ queryKey: ["service-job"] }); + if (onSuccessCallback) onSuccessCallback(); - showToast("Job Updated successfully", "success"); + + if (isArchiveAction) { + showToast("Job archived successfully", "success"); + } else { + showToast("Job restored successfully", "success"); + } }, + + onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to update project", + error.message || + "Failed to update project", "error" ); }, }); }; + //#endregion + + + + + + + +//#region Branch +export const useBranches = ( + projectId, + isActive, + pageSize, + pageNumber, + searchString +) => { + return useQuery({ + queryKey: [ + "branches", + projectId, + isActive, + pageSize, + pageNumber, + searchString, + ], + queryFn: async () => { + const resp = await ServiceProjectRepository.GetBranchList( + projectId, + isActive, + pageSize, + pageNumber, + searchString + ); + return resp.data; + }, + enabled: !!projectId, + }); +}; + +export const useBranchTypes = () => { + return useQuery({ + queryKey: ["branch_Type"], + queryFn: async () => { + const resp = await ServiceProjectRepository.GetBranchTypeList(); + return resp.data; + }, + }) +} + +export const useBranchDetails = (id) => { + return useQuery({ + queryKey: ["branch", id], + queryFn: async () => { + const resp = await ServiceProjectRepository.GetBranchDetail(id); + return resp.data; + }, + enabled: !!id + }) +} + +export const useCreateBranch = (onSuccessCallBack) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (payload) => { + await ServiceProjectRepository.CreateBranch(payload); + }, + + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: ["branches"] }); + showToast("Branches Created Successfully", "success"); + if (onSuccessCallBack) onSuccessCallBack(); + }, + onError: (error) => { + showToast( + error.message || "Something went wrong please try again !", + "error" + ); + }, + }); +}; + +export const useUpdateBranch = (onSuccessCallBack) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ id, payload }) => + await ServiceProjectRepository.UpdateBranch(id, payload), + + onSuccess: (_, variables) => { + // remove old single-branch cache + queryClient.removeQueries({ queryKey: ["branch", variables.id] }); + + // refresh list + queryClient.invalidateQueries({ queryKey: ["branches"] }); + + showToast("Branch updated successfully", "success"); + onSuccessCallBack?.(); + }, + + onError: () => { + showToast("Something went wrong. Please try again later.", "error"); + }, + }); +}; + + +export const useDeleteBranch = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ id, isActive }) => + await ServiceProjectRepository.DeleteBranch(id, isActive), + + onSuccess: (_, variable) => { + queryClient.invalidateQueries({ queryKey: ["branches"] }); + showToast(`Branch ${variable.isActive ? "restored" : "deleted"} successfully`, "success"); + }, + + onError: (error) => { + showToast( + error?.response?.data?.message || + error.message || + "Failed to delete branch", + "error" + ); + }, + }); +}; diff --git a/src/pages/AdvancePayment/AdvancePaymentPage.jsx b/src/pages/AdvancePayment/AdvancePaymentPage.jsx index 9af4a6c9..f95f199a 100644 --- a/src/pages/AdvancePayment/AdvancePaymentPage.jsx +++ b/src/pages/AdvancePayment/AdvancePaymentPage.jsx @@ -13,6 +13,8 @@ import Label from "../../components/common/Label"; import AdvancePaymentList from "../../components/AdvancePayment/AdvancePaymentList"; import { employee } from "../../data/masters"; import { formatFigure } from "../../utils/appUtils"; +import { useParams } from "react-router-dom"; +import { useExpenseTransactions } from "../../hooks/useExpense"; export const AdvancePaymentContext = createContext(); export const useAdvancePaymentContext = () => { @@ -25,14 +27,32 @@ export const useAdvancePaymentContext = () => { return context; }; const AdvancePaymentPage = () => { + const { employeeId } = useParams(); + + const { data: transactionData } = useExpenseTransactions(employeeId, { + enabled: !!employeeId + }); + + const employeeName = useMemo(() => { + if (Array.isArray(transactionData) && transactionData.length > 0) { + const emp = transactionData[0].employee; + if (emp) return `${emp.firstName} ${emp.lastName}`; + } + return ""; + }, [transactionData]); + const [balance, setBalance] = useState(null); const { control, reset, watch } = useForm({ defaultValues: { - employeeId: "", + employeeId: employeeId || "", + searchString: "", }, }); - const selectedEmployeeId = watch("employeeId"); + const selectedEmployeeId = employeeId || watch("employeeId"); + + const searchString = watch("searchString"); + useEffect(() => { const selectedEmpoyee = sessionStorage.getItem("transaction-empId"); reset({ @@ -47,30 +67,20 @@ const AdvancePaymentPage = () => { data={[ { label: "Home", link: "/dashboard" }, { label: "Finance", link: "/advance-payment" }, - { label: "Advance Payment" }, - ]} + { label: "Advance Payment", link: "/advance-payment" }, + employeeName && { label: employeeName, link: "" }, + ].filter(Boolean)} /> +
    -
    -
    -
    - -
    -
    +
    {balance ? ( <> 0 ? "text-success" : "text-danger" - } fs-5 fw-bold ms-1`} + className={`${balance > 0 ? "text-success" : "text-danger" + } fs-5 fw-bold ms-1`} > {balance > 0 ? ( @@ -88,7 +98,7 @@ const AdvancePaymentPage = () => { )}
    - +
    diff --git a/src/pages/AdvancePayment/AdvancePaymentPage1.jsx b/src/pages/AdvancePayment/AdvancePaymentPage1.jsx new file mode 100644 index 00000000..4ba3721b --- /dev/null +++ b/src/pages/AdvancePayment/AdvancePaymentPage1.jsx @@ -0,0 +1,34 @@ +import React from 'react' +import Breadcrumb from '../../components/common/Breadcrumb' +import AdvancePaymentList1 from '../../components/AdvancePayment/AdvancePaymentList1' +import { useForm } from 'react-hook-form'; +import EmployeeSearchInput from '../../components/common/EmployeeSearchInput'; + +const AdvancePaymentPage1 = () => { + const { control, reset, watch } = useForm({ + defaultValues: { + searchString: "", + }, + }); + const searchString = watch("searchString"); + + return ( +
    + +
    +
    + +
    +
    + +
    + ) +} + +export default AdvancePaymentPage1 diff --git a/src/pages/Home/LandingPage.jsx b/src/pages/Home/LandingPage.jsx index b72b1523..4062d3ba 100644 --- a/src/pages/Home/LandingPage.jsx +++ b/src/pages/Home/LandingPage.jsx @@ -20,7 +20,7 @@ const LandingPage = () => {
    @@ -401,15 +401,15 @@ const LandingPage = () => {

    Our Mission
    {" "}

    - At - , our mission is to empower organizations to manage their field - operations effortlessly — helping teams stay organized, - accountable, and productive, no matter where they are. We aim to - eliminate manual processes, data silos, and communication gaps - that often slow down projects and increase operational costs.{" "} -
    What We Do We provide a comprehensive suite of tools - designed to handle every critical aspect of field management — - from workforce tracking to expense control and reporting. With + At , our mission is to empower organizations + to manage their field operations effortlessly — helping teams stay + organized, accountable, and productive, no matter where they are. + We aim to eliminate manual processes, data silos, and + communication gaps that often slow down projects and increase + operational costs.
    What We Do We provide a comprehensive + suite of tools designed to handle every critical aspect of field + management — from workforce tracking to expense control and + reporting. With , you can:

      diff --git a/src/pages/collections/CollectionPage.jsx b/src/pages/collections/CollectionPage.jsx index 0dbe446c..5469be62 100644 --- a/src/pages/collections/CollectionPage.jsx +++ b/src/pages/collections/CollectionPage.jsx @@ -118,32 +118,33 @@ const CollectionPage = () => {
      {/* Left side: Date Picker + Show Pending (stacked on mobile) */} -
      - - - - -
      - setShowPending(e.target.checked)} - /> -
      {/* Right side: Search + Add Button */}
      + + + api.get("delete here come"), CreatePaymentRequestExpense: (data) => api.post("/api/Expense/payment-request/expense/create", data), - GetPayee:()=>api.get('/api/Expense/payment-request/payee'), + GetPayee: () => api.get('/api/Expense/payment-request/payee'), //#endregion //#region Recurring Expense - GetRecurringExpenseList:(pageSize, pageNumber, filter,isActive, searchString) => { + GetRecurringExpenseList: (pageSize, pageNumber, filter, isActive, searchString) => { const payloadJsonString = JSON.stringify(filter); return api.get( `/api/expense/get/recurring-payment/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&isActive=${isActive}&searchString=${searchString}` @@ -70,9 +70,11 @@ const ExpenseRepository = { //#region Advance Payment GetTranctionList: (employeeId) => api.get(`/api/Expense/get/transactions/${employeeId}`), + getAllTranctionList: (searchString) => + api.get(`/api/Expense/get/advance-payment/employee/list?searchString=${searchString}`), //#endregion - + }; export default ExpenseRepository; diff --git a/src/repositories/GlobalRepository.jsx b/src/repositories/GlobalRepository.jsx index ec90d25d..2516020a 100644 --- a/src/repositories/GlobalRepository.jsx +++ b/src/repositories/GlobalRepository.jsx @@ -18,6 +18,8 @@ const GlobalRepository = { return api.get(`/api/Dashboard/Progression?${params.toString()}`); }, + getProjectCompletionStatus:()=>api.get(`/api/Dashboard/project-completion-status`), + getDashboardAttendanceData: (date, projectId) => { diff --git a/src/repositories/ProjectRepository.jsx b/src/repositories/ProjectRepository.jsx index 1220d085..9894feae 100644 --- a/src/repositories/ProjectRepository.jsx +++ b/src/repositories/ProjectRepository.jsx @@ -1,17 +1,25 @@ import { api } from "../utils/axiosClient"; const ProjectRepository = { - getProjectList: (pageSize,pageNumber) => api.get(`/api/project/list?pageSize=${pageSize}&pageNumber=${pageNumber}`), + + getProjectList: (pageSize, pageNumber) => + api.get(`/api/project/list?pageSize=${pageSize}&pageNumber=${pageNumber}`), getProjectByprojectId: (projetid) => api.get(`/api/project/details/${projetid}`), - getProjectAllocation: (projectId, serviceId, organizationId, employeeStatus) => { + getProjectAllocation: ( + projectId, + serviceId, + organizationId, + employeeStatus + ) => { let url = `/api/project/allocation/${projectId}`; const params = []; if (organizationId) params.push(`organizationId=${organizationId}`); if (serviceId) params.push(`serviceId=${serviceId}`); - if (employeeStatus !== undefined) params.push(`includeInactive=${employeeStatus}`); + if (employeeStatus !== undefined) + params.push(`includeInactive=${employeeStatus}`); if (params.length > 0) { url += `?${params.join("&")}`; @@ -20,7 +28,6 @@ const ProjectRepository = { return api.get(url); }, - getEmployeesByProject: (projectId) => api.get(`/api/Project/employees/get/${projectId}`), @@ -40,10 +47,13 @@ const ProjectRepository = { api.get(`/api/project/allocation-histery/${id}`), updateProjectsByEmployee: (id, data) => api.post(`/api/project/assign-projects/${id}`, data), - projectNameList: (provideAll) => api.get(`/api/project/list/basic?provideAll=${provideAll}`), + projectNameList: (provideAll) => + api.get(`/api/project/list/basic?provideAll=${provideAll}`), + projectNameListAll: (searchString) => + api.get(`/api/project/list/basic/all?searchString=${searchString}`), getProjectDetails: (id) => api.get(`/api/project/details/${id}`), - + getProjectInfraByproject: (projectId, serviceId) => { let url = `/api/project/infra-details/${projectId}`; if (serviceId) { @@ -85,7 +95,7 @@ const ProjectRepository = { api.get(`/api/Project/get/assigned/services/${projectId}`), getProjectAssignedOrganizations: (projectId) => api.get(`/api/Project/get/assigned/organization/${projectId}`), - getProjectAssignedOrganizationsName: (projectId) => + getProjectAssignedOrganizationsName: (projectId) => api.get(`/api/Project/get/assigned/organization/dropdown/${projectId}`), getEmployeeForTaskAssign: (projectId, serviceId, organizationId) => { diff --git a/src/repositories/ServiceProjectRepository.jsx b/src/repositories/ServiceProjectRepository.jsx index 94ef3c8e..f043bcc0 100644 --- a/src/repositories/ServiceProjectRepository.jsx +++ b/src/repositories/ServiceProjectRepository.jsx @@ -1,6 +1,8 @@ +import { isAction } from "@reduxjs/toolkit"; import { api } from "../utils/axiosClient"; export const ServiceProjectRepository = { + //#region Service Project CreateServiceProject: (data) => api.post("/api/ServiceProject/create", data), GetServiceProjects: (pageSize, pageNumber) => api.get( @@ -17,12 +19,14 @@ export const ServiceProjectRepository = { api.get( `/api/ServiceProject/get/allocation/list?projectId=${projectId}&isActive=${isActive} ` ), + //#endregion + //#region Job CreateJob: (data) => api.post(`/api/ServiceProject/job/create`, data), - GetJobList: (pageSize, pageNumber, isActive, projectId) => + GetJobList: (pageSize, pageNumber, isActive, projectId,isArchive) => api.get( - `/api/ServiceProject/job/list?pageSize=${pageSize}&pageNumber=${pageNumber}&isActive=${isActive}&projectId=${projectId}` + `/api/ServiceProject/job/list?pageSize=${pageSize}&pageNumber=${pageNumber}&isActive=${isActive}&projectId=${projectId}&isArchive=${isArchive}` ), GetJobDetails: (id) => api.get(`/api/ServiceProject/job/details/${id}`), AddComment: (data) => api.post("/api/ServiceProject/job/add/comment", data), @@ -35,4 +39,22 @@ export const ServiceProjectRepository = { api.patch(`/api/ServiceProject/job/edit/${id}`, patchData, { "Content-Type": "application/json-patch+json", }), + //#endregion + + //#region Project Branch + CreateBranch: (data) => api.post(`/api/ServiceProject/branch/create`, data), + UpdateBranch: (id, data) => + api.put(`/api/ServiceProject/branch/edit/${id}`, data), + + GetBranchList: (projectId, isActive, pageSize, pageNumber, searchString) => { + return api.get( + `/api/ServiceProject/branch/list/${projectId}?isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&searchString=${searchString}` + ); + }, + + GetBranchDetail: (id) => api.get(`/api/ServiceProject/branch/details/${id}`), + DeleteBranch: (id, isActive = false) => + api.delete(`/api/ServiceProject/branch/delete/${id}?isActive=${isActive}`), + + GetBranchTypeList: () => api.get(`/api/serviceproject/branch-type/list`), }; diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index 1c95ec64..dc556a6e 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -60,7 +60,8 @@ import PaymentRequestPage from "../pages/PaymentRequest/PaymentRequestPage"; import RecurringExpensePage from "../pages/RecurringExpense/RecurringExpensePage"; import AdvancePaymentPage from "../pages/AdvancePayment/AdvancePaymentPage"; import ServiceProjectDetail from "../pages/ServiceProject/ServiceProjectDetail"; -import ManageJob from "../components/ServiceProject/ManageJob"; +import ManageJob from "../components/ServiceProject/ServiceProjectJob/ManageJob"; +import AdvancePaymentPage1 from "../pages/AdvancePayment/AdvancePaymentPage1"; const router = createBrowserRouter( [ { @@ -116,7 +117,8 @@ const router = createBrowserRouter( { path: "/expenses", element: }, { path: "/payment-request", element: }, { path: "/recurring-payment", element: }, - { path: "/advance-payment", element: }, + { path: "/advance-payment", element: }, + { path: "/advance-payment/:employeeId", element: }, { path: "/collection", element: }, { path: "/masters", element: }, { path: "/tenants", element: }, diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx index 2375b010..1ca271ec 100644 --- a/src/utils/constants.jsx +++ b/src/utils/constants.jsx @@ -210,4 +210,35 @@ export const PAYEE_RECURRING_EXPENSE = [ //#region Service Project and Jobs export const STATUS_JOB_CLOSED = "3ddeefb5-ae3c-4e10-a922-35e0a452bb69" -//#endregion \ No newline at end of file +//#endregion + +export const JOBS_STATUS_IDS = [ + { + id: "32d76a02-8f44-4aa0-9b66-c3716c45a918", + label: "New", + }, + { + id: "cfa1886d-055f-4ded-84c6-42a2a8a14a66", + label: "Assigned", + }, + { + id: "5a6873a5-fed7-4745-a52f-8f61bf3bd72d", + label: "In Progress", + }, + { + id: "aab71020-2fb8-44d9-9430-c9a7e9bf33b0", + label: "Work Done", + }, + { + id: "ed10ab57-dbaa-4ca5-8ecd-56745dcbdbd7", + label: "Review Done", + }, + { + id: "3ddeefb5-ae3c-4e10-a922-35e0a452bb69", + label: "Closed", + }, + { + id: "75a0c8b8-9c6a-41af-80bf-b35bab722eb2", + label: "On Hold", + }, +]; \ No newline at end of file