Compare commits

..

1 Commits

Author SHA1 Message Date
246abee37b Adding Billed To field in Manage Collection. 2025-11-19 11:23:38 +05:30
70 changed files with 1205 additions and 3346 deletions

View File

@ -89,7 +89,7 @@
); );
--bs-root-font-size: 16px; --bs-root-font-size: 16px;
--bs-body-font-family: var(--bs-font-sans-serif); --bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 0.85rem; --bs-body-font-size: 0.875rem;
--bs-body-font-weight: 400; --bs-body-font-weight: 400;
--bs-body-line-height: 1.375; --bs-body-line-height: 1.375;
--bs-body-color: #646e78; --bs-body-color: #646e78;
@ -9060,7 +9060,7 @@ img[data-app-light-img][data-app-dark-img] {
} }
.table th { .table th {
color: var(--bs-heading-color); color: var(--bs-heading-color);
font-size: 0.8025rem; font-size: 0.8125rem;
letter-spacing: 0.2px; letter-spacing: 0.2px;
text-transform: uppercase; text-transform: uppercase;
} }
@ -20345,7 +20345,7 @@ li:not(:first-child) .dropdown-item,
} }
.fs-6 { .fs-6 {
font-size: 0.8375rem !important; font-size: 0.9375rem !important;
} }
.fs-tiny { .fs-tiny {
@ -32560,7 +32560,9 @@ body:not(.modal-open) .layout-content-navbar .layout-navbar {
.bg-blue { .bg-blue {
background-color:var(--bs-blue) background-color:var(--bs-blue)
} }
.text-blue{
color:var(--bs-blue)
}
.bg-indigo { .bg-indigo {
background-color:var(--bs-indigo) background-color:var(--bs-indigo)
} }
@ -32572,10 +32574,4 @@ body:not(.modal-open) .layout-content-navbar .layout-navbar {
} }
.text-red{ .text-red{
color:var(--bs-red) color:var(--bs-red)
}
.text-blue{
color:var(--bs-blue)
}
.text-green{
color:var(--bs-green)
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

View File

@ -1,6 +1,6 @@
import React, { useEffect, useMemo } from "react"; import React, { useEffect, useMemo } from "react";
import { useExpenseAllTransactionsList, useExpenseTransactions } from "../../hooks/useExpense"; import { useExpenseTransactions } from "../../hooks/useExpense";
import Error from "../common/Error"; import Error from "../common/Error";
import { formatUTCToLocalTime } from "../../utils/dateUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils";
import Loader, { SpinnerLoader } from "../common/Loader"; import Loader, { SpinnerLoader } from "../common/Loader";
@ -11,10 +11,11 @@ import { employee } from "../../data/masters";
import { useAdvancePaymentContext } from "../../pages/AdvancePayment/AdvancePaymentPage"; import { useAdvancePaymentContext } from "../../pages/AdvancePayment/AdvancePaymentPage";
import { formatFigure } from "../../utils/appUtils"; import { formatFigure } from "../../utils/appUtils";
const AdvancePaymentList = ({ employeeId, searchString }) => { const AdvancePaymentList = ({ employeeId }) => {
const { setBalance } = useAdvancePaymentContext(); const { setBalance } = useAdvancePaymentContext();
const { data, isError, isLoading, error, isFetching } = const { data, isError, isLoading, error, isFetching } =
useExpenseTransactions(employeeId, { enabled: !!employeeId }); useExpenseTransactions(employeeId, { enabled: !!employeeId });
const records = Array.isArray(data) ? data : []; const records = Array.isArray(data) ? data : [];
let currentBalance = 0; let currentBalance = 0;
@ -84,7 +85,7 @@ const AdvancePaymentList = ({ employeeId, searchString }) => {
key: "date", key: "date",
label: ( label: (
<> <>
Date Date
</> </>
), ),
align: "text-start", align: "text-start",

View File

@ -1,100 +0,0 @@
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) => (
<div className="d-flex align-items-center gap-2" onClick={() => navigate(`/advance-payment/${r.id}`)}
style={{ cursor: "pointer" }}>
<Avatar firstName={r.firstName} lastName={r.lastName} />
<span className="fw-medium">
{r.firstName} {r.lastName}
</span>
</div>
),
},
{
key: "jobRoleName",
label: "Job Role",
align: "text-start",
customRender: (r) => (
<span className="fw-semibold">
{r.jobRoleName}
</span>
),
},
{
key: "balanceAmount",
label: "Balance (₹)",
align: "text-end",
customRender: (r) => (
<span className="fw-semibold fs-6">
{formatFigure(r.balanceAmount, {
// type: "currency",
currency: "INR",
})}
</span>
),
},
];
if (isLoading) return <p className="text-center py-4">Loading...</p>;
if (isError) return <p className="text-center py-4 text-danger">{error.message}</p>;
return (
<div className="card-datatable" id="payment-request-table">
<div className="mx-2">
<table className="table border-top dataTable text-nowrap align-middle">
<thead>
<tr>
{columns.map((col) => (
<th key={col.key} className={`sorting ${col.align}`}>
{col.label}
</th>
))}
</tr>
</thead>
<tbody>
{rows.length > 0 ? (
rows.map((row) => (
<tr key={row.id} className="align-middle" style={{ height: "40px" }}>
{columns.map((col) => (
<td key={col.key} className={`d-table-cell ${col.align} py-3`}>
{col.customRender
? col.customRender(row)
: col.getValue(row)}
</td>
))}
</tr>
))
) : (
<tr>
<td colSpan={columns.length} className="text-center border-0 py-3">
No Employees Found
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
)
}
export default AdvancePaymentList1;

View File

@ -2,19 +2,15 @@ import React, { useState } from "react";
import HorizontalBarChart from "../Charts/HorizontalBarChart"; import HorizontalBarChart from "../Charts/HorizontalBarChart";
import { useProjects } from "../../hooks/useProjects"; import { useProjects } from "../../hooks/useProjects";
import { ITEMS_PER_PAGE } from "../../utils/constants"; import { ITEMS_PER_PAGE } from "../../utils/constants";
import { useProjectCompletionStatus } from "../../hooks/useDashboard_Data";
const ProjectCompletionChart = () => { const ProjectCompletionChart = () => {
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const { const { data: projects, isLoading: loading, isError, error } = useProjects(50,currentPage);
data: projects, // Bar chart logic
isLoading: loading, const projectNames = projects?.data?.map((p) => p.name) || [];
isError,
error,
} = useProjectCompletionStatus();
const projectNames = projects?.map((p) => p.name) || [];
const projectProgress = const projectProgress =
projects?.map((p) => { projects?.data?.map((p) => {
const completed = p.completedWork || 0; const completed = p.completedWork || 0;
const planned = p.plannedWork || 1; const planned = p.plannedWork || 1;
const percent = planned ? (completed / planned) * 100 : 0; const percent = planned ? (completed / planned) * 100 : 0;

View File

@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { defaultExpense, ExpenseSchema } from "./ExpenseSchema"; import { defaultExpense, ExpenseSchema } from "./ExpenseSchema";
import { formatFileSize, localToUtc } from "../../utils/appUtils"; import { formatFileSize, localToUtc } from "../../utils/appUtils";
import { useProjectName } from "../../hooks/useProjects"; import { useProjectName } from "../../hooks/useProjects";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { changeMaster } from "../../slices/localVariablesSlice"; import { changeMaster } from "../../slices/localVariablesSlice";
import useMaster, { import useMaster, {
@ -32,11 +32,6 @@ import Label from "../common/Label";
import EmployeeSearchInput from "../common/EmployeeSearchInput"; import EmployeeSearchInput from "../common/EmployeeSearchInput";
import Filelist from "./Filelist"; import Filelist from "./Filelist";
import { DEFAULT_CURRENCY } from "../../utils/constants"; import { DEFAULT_CURRENCY } from "../../utils/constants";
import SelectEmployeeServerSide, {
SelectProjectField,
} from "../common/Forms/SelectFieldServerSide";
import { useAllocationServiceProjectTeam } from "../../hooks/useServiceProject";
import { AppFormController } from "../../hooks/appHooks/useAppForm";
const ManageExpense = ({ closeModal, expenseToEdit = null }) => { const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
const { const {
@ -45,7 +40,6 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
error: ExpenseErrorLoad, error: ExpenseErrorLoad,
} = useExpense(expenseToEdit); } = useExpense(expenseToEdit);
const [expenseCategory, setExpenseCategory] = useState(); const [expenseCategory, setExpenseCategory] = useState();
const [selectedEmployees, setSelectedEmployees] = useState([]);
const dispatch = useDispatch(); const dispatch = useDispatch();
const { const {
expenseCategories, expenseCategories,
@ -89,11 +83,11 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
loading: StatusLoadding, loading: StatusLoadding,
error: stausError, error: stausError,
} = useExpenseStatus(); } = useExpenseStatus();
// const { const {
// data: employees, data: employees,
// isLoading: EmpLoading, isLoading: EmpLoading,
// isError: isEmployeeError, isError: isEmployeeError,
// } = useEmployeesNameByProject(selectedproject); } = useEmployeesNameByProject(selectedproject);
const files = watch("billAttachments"); const files = watch("billAttachments");
const onFileChange = async (e) => { const onFileChange = async (e) => {
@ -156,14 +150,6 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
} }
}; };
const { mutate: AllocationTeam, isPending1 } =
useAllocationServiceProjectTeam(() => {
setSelectedEmployees([]);
setSeletingEmp({
employee: null,
isOpen: false,
});
});
useEffect(() => { useEffect(() => {
if (expenseToEdit && data) { if (expenseToEdit && data) {
reset({ reset({
@ -194,7 +180,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
: [], : [],
}); });
} }
}, [data, reset]); }, [data, reset, employees]);
const { mutate: ExpenseUpdate, isPending } = useUpdateExpense(() => const { mutate: ExpenseUpdate, isPending } = useUpdateExpense(() =>
handleClose() handleClose()
); );
@ -237,7 +223,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
</h5> </h5>
<form id="expenseForm" onSubmit={handleSubmit(onSubmit)}> <form id="expenseForm" onSubmit={handleSubmit(onSubmit)}>
<div className="row my-2 text-start"> <div className="row my-2 text-start">
{/* <div className="col-md-6"> <div className="col-md-6">
<Label className="form-label" required> <Label className="form-label" required>
Select Project Select Project
</Label> </Label>
@ -259,23 +245,6 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
{errors.projectId && ( {errors.projectId && (
<small className="danger-text">{errors.projectId.message}</small> <small className="danger-text">{errors.projectId.message}</small>
)} )}
</div> */}
<div className="col-12 col-md-6 mb-2">
<SelectProjectField
label="Project"
required
placeholder="Select Project"
value={watch("projectId")}
onChange={(val) =>
setValue("projectId", val, {
shouldDirty: true,
shouldValidate: true,
})
}
/>
{errors.projectId && (
<small className="danger-text">{errors.projectId.message}</small>
)}
</div> </div>
<div className="col-md-6"> <div className="col-md-6">
@ -338,28 +307,14 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
)} )}
</div> </div>
<div className="col-12 col-md-6 text-start"> <div className="col-12 col-md-6 text-start">
{/* <Label className="form-label" required> <Label className="form-label" required>
Paid By{" "} Paid By{" "}
</Label> */} </Label>
{/* <EmployeeSearchInput <EmployeeSearchInput
control={control} control={control}
name="paidById" name="paidById"
projectId={null} projectId={null}
forAll={true} forAll={expenseToEdit ? true : false}
/> */}
<AppFormController
name="paidById"
control={control}
render={({ field }) => (
<SelectEmployeeServerSide
label="Paid By" required
value={field.value}
onChange={field.onChange}
isFullObject={false} // because using ID
/>
)}
/> />
</div> </div>
</div> </div>
@ -468,9 +423,10 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
<small className="danger-text">{errors.gstNumber.message}</small> <small className="danger-text">{errors.gstNumber.message}</small>
)} )}
</div> </div>
</div> </div>
<div className="row"> <div className="row">
<div className="col-md-6 text-start "> <div className="col-md-6 text-start ">
<Label htmlFor="currencyId" className="form-label" required> <Label htmlFor="currencyId" className="form-label" required>
Select Currency Select Currency
</Label> </Label>
@ -496,26 +452,24 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
<small className="danger-text">{errors.currencyId.message}</small> <small className="danger-text">{errors.currencyId.message}</small>
)} )}
</div> </div>
{expenseCategory?.noOfPersonsRequired && ( {expenseCategory?.noOfPersonsRequired && (
<div className="col-md-6 text-start"> <div className="col-md-6 text-start">
<Label className="form-label" required> <Label className="form-label" required>No. of Persons</Label>
No. of Persons <input
</Label> type="number"
<input id="noOfPersons"
type="number" className="form-control form-control-sm"
id="noOfPersons" {...register("noOfPersons")}
className="form-control form-control-sm" inputMode="numeric"
{...register("noOfPersons")} />
inputMode="numeric" {errors.noOfPersons && (
/> <small className="danger-text">
{errors.noOfPersons && ( {errors.noOfPersons.message}
<small className="danger-text"> </small>
{errors.noOfPersons.message} )}
</small> </div>
)} )}
</div> </div>
)}
</div>
<div className="row my-2 text-start"> <div className="row my-2 text-start">
<div className="col-md-12"> <div className="col-md-12">

View File

@ -1,80 +1,54 @@
import { useState } from "react"; import { useState } from "react";
const PreviewDocument = ({ imageUrl }) => { const PreviewDocument = ({ imageUrl }) => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [rotation, setRotation] = useState(0); const [rotation, setRotation] = useState(0);
const [scale, setScale] = useState(1);
const zoomIn = () => setScale((prev) => Math.min(prev + 0.2, 3));
const zoomOut = () => setScale((prev) => Math.max(prev - 0.2, 0.4));
const resetAll = () => {
setRotation(0);
setScale(1);
};
return ( return (
<> <>
<div className="d-flex justify-content-start gap-3 mb-2"> <div className="d-flex justify-content-start">
<i <i
className="bx bx-rotate-right cursor-pointer fs-4" className="bx bx-rotate-right cursor-pointer"
title="Rotate"
onClick={() => setRotation((prev) => prev + 90)} onClick={() => setRotation((prev) => prev + 90)}
></i> ></i>
</div>
<div
className="position-relative d-flex flex-column justify-content-center align-items-center"
style={{ minHeight: "80vh" }}
>
{loading && (
<div className="text-secondary text-center mb-2">Loading...</div>
)}
<i <div className="mb-3 d-flex justify-content-center align-items-center">
className="bx bx-zoom-in cursor-pointer fs-4" <img
title="Zoom In" src={imageUrl}
onClick={zoomIn} alt="Full View"
></i> className="img-fluid"
style={{
<i maxHeight: "80vh",
className="bx bx-zoom-out cursor-pointer fs-4" objectFit: "contain",
title="Zoom Out" display: loading ? "none" : "block",
onClick={zoomOut} transform: `rotate(${rotation}deg)`,
></i> transition: "transform 0.3s ease",
}}
onLoad={() => setLoading(false)}
/>
</div> </div>
<div <div className="position-absolute bottom-0 start-0 justify-content-center gap-2">
className="position-relative d-flex flex-column justify-content-center align-items-center overflow-hidden" <button
style={{ minHeight: "80vh" }} className="btn btn-outline-secondary"
> onClick={() => setRotation(0)}
{loading && ( title="Reset Rotation"
<div className="text-secondary text-center mb-2"> >
Loading... <i className="bx bx-reset"></i> Reset
</div> </button>
)}
<div className="mb-3 d-flex justify-content-center align-items-center">
<img
src={imageUrl}
alt="Full View"
className="img-fluid"
style={{
maxHeight: "80vh",
objectFit: "contain",
display: loading ? "none" : "block",
transform: `rotate(${rotation}deg) scale(${scale})`,
transition: "transform 0.3s ease",
cursor: "grab",
}}
onLoad={() => setLoading(false)}
/>
</div>
<div className="position-absolute bottom-0 start-0 m-2">
<button
className="btn btn-outline-secondary"
onClick={resetAll}
>
<i className="bx bx-reset"></i> Reset
</button>
</div>
</div> </div>
</> </div>
</>
); );
}; };
export default PreviewDocument; export default PreviewDocument;

View File

@ -50,11 +50,8 @@ const Header = () => {
const isRecurringExpense = /^\/recurring-payment$/.test(pathname); const isRecurringExpense = /^\/recurring-payment$/.test(pathname);
const isAdvancePayment = /^\/advance-payment$/.test(pathname); const isAdvancePayment = /^\/advance-payment$/.test(pathname);
const isServiceProjectPage = /^\/service-projects\/[0-9a-fA-F-]{36}$/.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 = [ const allowedProjectStatusIds = [
"603e994b-a27f-4e5d-a251-f3d69b0498ba", "603e994b-a27f-4e5d-a251-f3d69b0498ba",

View File

@ -25,19 +25,22 @@ const Sidebar = () => {
/> />
</span> */} </span> */}
<small className="app-brand-link fw-bold navbar-brand text-green fs-6"> <a
href="/"
className="app-brand-link fw-bold navbar-brand text-green fs-6"
>
<span className="app-brand-logo demo"> <span className="app-brand-logo demo">
<img src="/img/brand/marco.png" width="50" /> <img src="/img/brand/marco.png" width="50" />
</span> </span>
<span className="text-blue">OnField</span> <span className="text-blue">OnField</span>
<span>Work</span> <span>Work</span>
<span className="text-dark">.com</span> <span className="text-dark">.com</span>
</small> </a>
</Link> </Link>
<small className="layout-menu-toggle menu-link text-large ms-auto"> <a className="layout-menu-toggle menu-link text-large ms-auto">
<i className="bx bx-chevron-left bx-sm d-flex align-items-center justify-content-center"></i> <i className="bx bx-chevron-left bx-sm d-flex align-items-center justify-content-center"></i>
</small> </a>
</div> </div>
<div className="menu-inner-shadow"></div> <div className="menu-inner-shadow"></div>
@ -58,7 +61,7 @@ const Sidebar = () => {
</> </>
)} )}
{data && {data &&
data?.data?.map((section) => ( data?.data.map((section) => (
<React.Fragment <React.Fragment
key={section.id || section.header || section.items[0]?.id} key={section.id || section.header || section.items[0]?.id}
> >

View File

@ -40,6 +40,7 @@ const ActionPaymentRequest = ({ requestId }) => {
error: PaymentModeError, error: PaymentModeError,
} = usePaymentMode(); } = usePaymentMode();
console.log("Kartik", data)
const IsReview = useHasUserPermission(REVIEW_EXPENSE); const IsReview = useHasUserPermission(REVIEW_EXPENSE);
const [imageLoaded, setImageLoaded] = useState({}); const [imageLoaded, setImageLoaded] = useState({});

View File

@ -29,7 +29,6 @@ import Filelist from "../Expenses/Filelist";
import InputSuggestions from "../common/InputSuggestion"; import InputSuggestions from "../common/InputSuggestion";
import { useProfile } from "../../hooks/useProfile"; import { useProfile } from "../../hooks/useProfile";
import { blockUI } from "../../utils/blockUI"; import { blockUI } from "../../utils/blockUI";
import { SelectProjectField } from "../common/Forms/SelectFieldServerSide";
function ManagePaymentRequest({ closeModal, requestToEdit = null }) { function ManagePaymentRequest({ closeModal, requestToEdit = null }) {
const { const {
@ -235,10 +234,10 @@ function ManagePaymentRequest({ closeModal, requestToEdit = null }) {
{/* Project and Category */} {/* Project and Category */}
<div className="row my-2 text-start"> <div className="row my-2 text-start">
<div className="col-md-6"> <div className="col-md-6">
{/* <Label className="form-label" required> <Label className="form-label" required>
Select Project Select Project
</Label> */} </Label>
{/* <select <select
className="form-select form-select-sm" className="form-select form-select-sm"
{...register("projectId")} {...register("projectId")}
disabled={ disabled={
@ -255,23 +254,7 @@ function ManagePaymentRequest({ closeModal, requestToEdit = null }) {
</option> </option>
)) ))
)} )}
</select> */} </select>
<SelectProjectField
label="Project"
required
placeholder="Select Project"
value={watch("projectId")}
onChange={(val) =>
setValue("projectId", val, {
shouldDirty: true,
shouldValidate: true,
})
}
disabled={
data?.recurringPayment?.isVariable && !isDraft && !isProcessed
}
/>
{errors.projectId && ( {errors.projectId && (
<small className="danger-text">{errors.projectId.message}</small> <small className="danger-text">{errors.projectId.message}</small>
)} )}

View File

@ -85,7 +85,7 @@ const PaymentRequestList = ({ filters, filterData, removeFilterChip, clearFilter
key: "paymentRequestUID", key: "paymentRequestUID",
label: "Request ID", label: "Request ID",
align: "text-start mx-2", align: "text-start mx-2",
getValue: (e) => <div className="d-flex"><span>{e.paymentRequestUID || "N/A"}</span> {e.isAdvancePayment && <span class="ms-1 badge bg-label-warning text-xxs" >Adv</span>}</div>, getValue: (e) => e.paymentRequestUID || "N/A",
}, },
{ {
key: "title", key: "title",

View File

@ -148,7 +148,7 @@ const ViewPaymentRequest = ({ requestId }) => {
<div className="col-12 col-sm-6 "> <div className="col-12 col-sm-6 ">
<div className="row "> <div className="row ">
<div className="col-12 d-flex justify-content-between mb-6"> <div className="col-12 d-flex justify-content-between mb-6">
<div className="d-flex align-items-center"><span className="fw-semibold">PR No : </span><span className="fw-semibold ms-2"> {data?.paymentRequestUID}</span> {data.isAdvancePayment && <span class="ms-1 badge bg-label-warning text-xs" >Advance</span>}</div> <div className="d-flex align-items-center"><span className="fw-semibold">PR No : </span><span className="fw-semibold ms-2"> {data?.paymentRequestUID}</span></div>
<span <span
className={`badge bg-label-${getColorNameFromHex(data?.expenseStatus?.color) || "secondary" className={`badge bg-label-${getColorNameFromHex(data?.expenseStatus?.color) || "secondary"
}`} }`}

View File

@ -82,6 +82,7 @@ const EditActivityModal = ({
useEffect(() => { useEffect(() => {
if (!workItem) return; if (!workItem) return;
console.log(workItem)
reset({ reset({
activityID: String( activityID: String(
workItem?.workItem?.activityId || workItem?.activityMaster?.id workItem?.workItem?.activityId || workItem?.activityMaster?.id

View File

@ -8,12 +8,7 @@ const ProjectCardView = ({ data, currentPage, totalPages, paginate }) => {
return ( return (
<div className="row page-min-h"> <div className="row page-min-h">
{data?.length === 0 && ( {data?.length === 0 && (
<div <p className="text-center text-muted">No projects found.</p>
className="col-12 d-flex justify-content-center align-items-center"
style={{ minHeight: "250px" }}
>
<p className="text-center text-muted m-0">No Infra projects found.</p>
</div>
)} )}
{data?.map((project) => ( {data?.map((project) => (

View File

@ -126,8 +126,8 @@ const ProjectListView = ({ data, currentPage, totalPages, paginate }) => {
return ( return (
<div className="card page-min-h py-4 px-6 shadow-sm"> <div className="card page-min-h py-4 px-6 shadow-sm">
<div className="table-responsive text-nowrap"> <div className="table-responsive text-nowrap page-min-h">
<table className="table table-hover align-middle m-0"> <table className="table table-hover align-middle m-0">
<thead className="border-bottom "> <thead className="border-bottom ">
<tr> <tr>
{projectColumns.map((col) => ( {projectColumns.map((col) => (
@ -143,94 +143,77 @@ const ProjectListView = ({ data, currentPage, totalPages, paginate }) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{data?.length > 0 ? ( {data?.map((project) => (
data?.map((project) => ( <tr key={project.id}>
<tr key={project.id}> {projectColumns.map((col) => (
{projectColumns.map((col) => (
<td
key={col.key}
colSpan={col.colSpan}
className={`${col.className} py-5`}
style={{ paddingTop: "20px", paddingBottom: "20px" }}
>
{col.getValue
? col.getValue(project)
: project[col.key] || "N/A"}
</td>
))}
<td <td
className={`mx-2 ${canManageProject ? "d-sm-table-cell" : "d-none" key={col.key}
}`} colSpan={col.colSpan}
className={`${col.className} py-5`}
style={{ paddingTop: "20px", paddingBottom: "20px" }}
> >
<div className="dropdown z-2"> {col.getValue
<button ? col.getValue(project)
type="button" : project[col.key] || "N/A"}
className="btn btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i
className="bx bx-dots-vertical-rounded bx-sm text-muted"
data-bs-toggle="tooltip"
data-bs-offset="0,8"
data-bs-placement="top"
data-bs-custom-class="tooltip-dark"
title="More Action"
></i>
</button>
<ul className="dropdown-menu dropdown-menu-end">
<li>
<a
aria-label="click to View details"
className="dropdown-item cursor-pointer"
>
<i className="bx bx-detail me-2"></i>
<span className="align-left">View details</span>
</a>
</li>
<li>
<a
className="dropdown-item cursor-pointer"
onClick={() =>
setMangeProject({
isOpen: true,
Project: project.id,
})
}
>
<i className="bx bx-pencil me-2"></i>
<span className="align-left">Modify</span>
</a>
</li>
<li onClick={() => handleViewActivities(project.id)}>
<a className="dropdown-item cursor-pointer">
<i className="bx bx-task me-2"></i>
<span className="align-left">Activities</span>
</a>
</li>
</ul>
</div>
</td> </td>
</tr> ))}
))
) : (
<tr
className="no-hover"
style={{
pointerEvents: "none",
backgroundColor: "transparent",
}}
>
<td <td
colSpan={projectColumns.length + 1} className={`mx-2 ${
className="text-center align-middle" canManageProject ? "d-sm-table-cell" : "d-none"
style={{ height: "300px", borderBottom: "none" }} }`}
> >
No Infra projects available <div className="dropdown z-2">
<button
type="button"
className="btn btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i
className="bx bx-dots-vertical-rounded bx-sm text-muted"
data-bs-toggle="tooltip"
data-bs-offset="0,8"
data-bs-placement="top"
data-bs-custom-class="tooltip-dark"
title="More Action"
></i>
</button>
<ul className="dropdown-menu dropdown-menu-end">
<li>
<a
aria-label="click to View details"
className="dropdown-item cursor-pointer"
>
<i className="bx bx-detail me-2"></i>
<span className="align-left">View details</span>
</a>
</li>
<li>
<a
className="dropdown-item cursor-pointer"
onClick={() =>
setMangeProject({
isOpen: true,
Project: project.id,
})
}
>
<i className="bx bx-pencil me-2"></i>
<span className="align-left">Modify</span>
</a>
</li>
<li onClick={() => handleViewActivities(project.id)}>
<a className="dropdown-item cursor-pointer">
<i className="bx bx-task me-2"></i>
<span className="align-left">Activities</span>
</a>
</li>
</ul>
</div>
</td> </td>
</tr> </tr>
)} ))}
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -10,13 +10,11 @@ import {
import useMaster, { useServices } from "../../../hooks/masterHook/useMaster"; import useMaster, { useServices } from "../../../hooks/masterHook/useMaster";
import showToast from "../../../services/toastService"; import showToast from "../../../services/toastService";
import { useOrganizationEmployees } from "../../../hooks/useOrganization"; import { useOrganizationEmployees } from "../../../hooks/useOrganization";
import { useDispatch } from "react-redux";
import { changeMaster } from "../../../slices/localVariablesSlice";
const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => { const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
const debounceSearchTerm = useDebounce(searchTerm, 500); const debounceSearchTerm = useDebounce(searchTerm, 500);
const dispatch = useDispatch();
const { const {
data: employeesData = [], data: employeesData = [],
isLoading, isLoading,
@ -47,7 +45,6 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
}); });
useEffect(() => { useEffect(() => {
dispatch(changeMaster("Job Role"));
if (employeesData?.data?.length > 0) { if (employeesData?.data?.length > 0) {
const available = employeesData.data.filter((emp) => { const available = employeesData.data.filter((emp) => {
const projEmp = projectEmployees.find((pe) => pe.employeeId === emp.id); const projEmp = projectEmployees.find((pe) => pe.employeeId === emp.id);
@ -122,7 +119,7 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
status: true, status: true,
})); }));
handleAssignEmployee({ payload, actionType: "assign" }); handleAssignEmployee({ payload,actionType:"assign"} );
setEmployees((prev) => setEmployees((prev) =>
prev.map((emp) => ({ prev.map((emp) => ({
@ -135,26 +132,26 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
); );
}; };
if (isLoading) { if (isLoading) {
return (<div className="page-min-h d-flex justify-content-center align-items-center "><p className="text-muted">Loading employees...</p></div>); return ( <div className="page-min-h d-flex justify-content-center align-items-center "><p className="text-muted">Loading employees...</p></div>) ;
} }
if (isError) { if (isError) {
return ( return (
<div className="page-min-h d-flex justify-content-center align-items-center "> <div className="page-min-h d-flex justify-content-center align-items-center ">
{error?.status === 400 ? ( {error?.status === 400 ? (
<p className="m-0">Enter employee you want to find.</p> <p className="m-0">Enter employee you want to find.</p>
) : ( ) : (
<p className="m-0 dange-text">Something went wrong. Please try again later.</p> <p className="m-0 dange-text">Something went wrong. Please try again later.</p>
)} )}
</div> </div>
); );
} }
if (employees.length === 0) { if (employees.length === 0) {
return (<div className="page-min-h d-flex justify-content-center align-items-center "><p className="text-muted">No available employees to assign.</p></div>); return(<div className="page-min-h d-flex justify-content-center align-items-center "><p className="text-muted">No available employees to assign.</p></div>) ;
} }
return ( return (
@ -186,8 +183,9 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
onChange={(e) => onChange={(e) =>
handleSelectChange(index, "serviceId", e.target.value) handleSelectChange(index, "serviceId", e.target.value)
} }
className={`form-select form-select-sm w-auto border-none rounded-0 py-1 px-auto ${emp.errors.serviceId ? "is-invalid" : "" className={`form-select form-select-sm w-auto border-none rounded-0 py-1 px-auto ${
}`} emp.errors.serviceId ? "is-invalid" : ""
}`}
> >
<option value="">Select Service</option> <option value="">Select Service</option>
{services?.map((s) => ( {services?.map((s) => (
@ -207,8 +205,9 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
onChange={(e) => onChange={(e) =>
handleSelectChange(index, "jobRole", e.target.value) handleSelectChange(index, "jobRole", e.target.value)
} }
className={`form-select form-select-sm w-auto border-none rounded-0 py-1 px-auto ${emp.errors.jobRole ? "is-invalid" : "" className={`form-select form-select-sm w-auto border-none rounded-0 py-1 px-auto ${
}`} emp.errors.jobRole ? "is-invalid" : ""
}`}
> >
<option value="">Select Job Role</option> <option value="">Select Job Role</option>
{jobRoles?.map((r) => ( {jobRoles?.map((r) => (

View File

@ -27,7 +27,6 @@ import InputSuggestions from "../common/InputSuggestion";
import { useEmployeesName } from "../../hooks/useEmployees"; import { useEmployeesName } from "../../hooks/useEmployees";
import PmsEmployeeInputTag from "../common/PmsEmployeeInputTag"; import PmsEmployeeInputTag from "../common/PmsEmployeeInputTag";
import HoverPopup from "../common/HoverPopup"; import HoverPopup from "../common/HoverPopup";
import { SelectProjectField } from "../common/Forms/SelectFieldServerSide";
const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => { const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
const { const {
@ -132,7 +131,7 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
} }
}, [currencyData, requestToEdit, setValue]); }, [currencyData, requestToEdit, setValue]);
const StrikeDate = watch("strikeDate"); const StrikeDate = watch("strikeDate")
const onSubmit = (fromdata) => { const onSubmit = (fromdata) => {
let payload = { let payload = {
@ -164,7 +163,10 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
{/* Project and Category */} {/* Project and Category */}
<div className="row my-2 text-start"> <div className="row my-2 text-start">
<div className="col-md-6"> <div className="col-md-6">
{/* <select <Label className="form-label" required>
Select Project
</Label>
<select
className="form-select form-select-sm" className="form-select form-select-sm"
{...register("projectId")} {...register("projectId")}
> >
@ -178,19 +180,7 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
</option> </option>
)) ))
)} )}
</select> */} </select>
<SelectProjectField
label="Select Project"
required
placeholder="Select Project"
value={watch("projectId")}
onChange={(val) =>
setValue("projectId", val, {
shouldDirty: true,
shouldValidate: true,
})
}
/>
{errors.projectId && ( {errors.projectId && (
<small className="danger-text">{errors.projectId.message}</small> <small className="danger-text">{errors.projectId.message}</small>
)} )}
@ -245,7 +235,7 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
</div> </div>
<div className="col-md-6 mt-2"> <div className="col-md-6 mt-2">
<div className="d-flex justify-content-start align-items-center text-nowrap gap-2"> <div className="d-flex justify-content-start align-items-center gap-2">
<Label htmlFor="isVariable" className="form-label mb-0" required> <Label htmlFor="isVariable" className="form-label mb-0" required>
Payment Type Payment Type
</Label> </Label>
@ -253,16 +243,13 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
title="Payment Type" title="Payment Type"
id="payment_type" id="payment_type"
content={ content={
<div className=" w-50"> <p>
<p> Choose whether the payment amount varies or remains fixed each cycle.
Choose whether the payment amount varies or remains fixed <br />
each cycle. <strong>Is Variable:</strong> Amount changes per cycle.
<br /> <br />
<strong>Is Variable:</strong> Amount changes per cycle. <strong>Fixed:</strong> Amount stays constant.
<br /> </p>
<strong>Fixed:</strong> Amount stays constant.
</p>
</div>
} }
> >
<i className="bx bx-info-circle bx-sm text-muted cursor-pointer"></i> <i className="bx bx-info-circle bx-sm text-muted cursor-pointer"></i>
@ -283,10 +270,7 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
checked={field.value === true} checked={field.value === true}
onChange={() => field.onChange(true)} onChange={() => field.onChange(true)}
/> />
<Label <Label htmlFor="isVariableTrue" className="form-check-label">
htmlFor="isVariableTrue"
className="form-check-label"
>
Is Variable Is Variable
</Label> </Label>
</div> </div>
@ -299,10 +283,7 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
checked={field.value === false} checked={field.value === false}
onChange={() => field.onChange(false)} onChange={() => field.onChange(false)}
/> />
<Label <Label htmlFor="isVariableFalse" className="form-check-label">
htmlFor="isVariableFalse"
className="form-check-label"
>
Fixed Fixed
</Label> </Label>
</div> </div>
@ -314,6 +295,7 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
<small className="danger-text">{errors.isVariable.message}</small> <small className="danger-text">{errors.isVariable.message}</small>
)} )}
</div> </div>
</div> </div>
{/* Date and Amount */} {/* Date and Amount */}
@ -409,12 +391,11 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
id="frequency" id="frequency"
content={ content={
<p> <p>
Defines how often payments or billing occur, such as Defines how often payments or billing occur, such as monthly, quarterly, or annually.
monthly, quarterly, or annually.
</p> </p>
} }
> >
<i className="bx bx-info-circle bx-xs text-muted cursor-pointer"></i> <i className="bx bx-info-circle bx-sm text-muted cursor-pointer"></i>
</HoverPopup> </HoverPopup>
</div> </div>
@ -463,13 +444,10 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
{/* Payment Buffer Days and End Date */} {/* Payment Buffer Days and End Date */}
<div className="row my-2 text-start"> <div className="row my-2 text-start">
<div className="col-md-6"> <div className="col-md-6">
<div className="d-flex justify-content-start align-items-center text-nowrap gap-2"> <div className="d-flex justify-content-start align-items-center gap-2">
<Label <Label htmlFor="paymentBufferDays" className="form-label mb-0" required>
htmlFor="paymentBufferDays"
className="form-label mb-0 "
required
>
Payment Buffer Days Payment Buffer Days
</Label> </Label>
<HoverPopup <HoverPopup
@ -477,12 +455,11 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
id="payment_buffer_days" id="payment_buffer_days"
content={ content={
<p> <p>
Number of extra days allowed after the due date before Number of extra days allowed after the due date before payment is considered late.
payment is considered late.
</p> </p>
} }
> >
<i className="bx bx-info-circle bx-xs text-muted cursor-pointer"></i> <i className="bx bx-info-circle bx-sm text-muted cursor-pointer"></i>
</HoverPopup> </HoverPopup>
</div> </div>
@ -503,8 +480,9 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
)} )}
</div> </div>
<div className="col-md-6"> <div className="col-md-6">
<div className="d-flex justify-content-start align-items-center text-nowrap gap-2"> <div className="d-flex justify-content-start align-items-center gap-2">
<Label htmlFor="endDate" className="form-label mb-0" required> <Label htmlFor="endDate" className="form-label mb-0" required>
End Date End Date
</Label> </Label>
@ -517,7 +495,7 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
</p> </p>
} }
> >
<i className="bx bx-info-circle bx-xs text-muted cursor-pointer"></i> <i className="bx bx-info-circle bx-sm text-muted cursor-pointer"></i>
</HoverPopup> </HoverPopup>
</div> </div>
@ -532,8 +510,10 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
<small className="danger-text">{errors.endDate.message}</small> <small className="danger-text">{errors.endDate.message}</small>
)} )}
</div> </div>
</div> </div>
<div className="row my-2 text-start"> <div className="row my-2 text-start">
<div className="col-md-6"> <div className="col-md-6">
<Label htmlFor="notifyTo" className="form-label" required> <Label htmlFor="notifyTo" className="form-label" required>
@ -592,8 +572,8 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
{createPending || isPending {createPending || isPending
? "Please wait...." ? "Please wait...."
: requestToEdit : requestToEdit
? "Update" ? "Update"
: "Save as Draft"} : "Save as Draft"}
</button> </button>
</div> </div>
</form> </form>

View File

@ -166,7 +166,7 @@ const RecurringExpenseList = ({ search, filterStatuses }) => {
} }
); );
}; };
console.log("Tanish",filteredData)
return ( return (
<> <>
{IsDeleteModalOpen && ( {IsDeleteModalOpen && (

View File

@ -1,16 +1,17 @@
import SelectField from "../../common/Forms/SelectField"; import SelectField from "../common/Forms/SelectField";
import Error from "../../common/Error"; import { useJobStatus } from "../../hooks/masterHook/useMaster";
import { SpinnerLoader } from "../common/Loader";
import Error from "../common/Error";
import { z } from "zod"; import { z } from "zod";
import { import {
AppFormController, AppFormController,
AppFormProvider, AppFormProvider,
useAppForm, useAppForm,
} from "../../../hooks/appHooks/useAppForm"; } from "../../hooks/appHooks/useAppForm";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { closePopup } from "../../../slices/localVariablesSlice"; import { closePopup } from "../../slices/localVariablesSlice";
import { useUpdateServiceProjectJob } from "../../../hooks/useServiceProject"; import { useUpdateServiceProjectJob } from "../../hooks/useServiceProject";
import { useJobStatus } from "../../../hooks/masterHook/useMaster";
export const ChangeStatusSchema = z.object({ export const ChangeStatusSchema = z.object({
statusId: z.string().min(1, { message: "Please select status" }), statusId: z.string().min(1, { message: "Please select status" }),
@ -31,7 +32,7 @@ const ChangeStatus = ({ statusId, projectId, jobId, popUpId }) => {
} = methods; } = methods;
const { mutate: UpdateStatus, isPending } = useUpdateServiceProjectJob(() => { const { mutate: UpdateStatus, isPending } = useUpdateServiceProjectJob(() => {
handleClose(); // handleClose();
}); });
const onSubmit = (formData) => { const onSubmit = (formData) => {
const payload = const payload =
@ -52,11 +53,6 @@ const ChangeStatus = ({ statusId, projectId, jobId, popUpId }) => {
}; };
return ( return (
<AppFormProvider {...methods}> <AppFormProvider {...methods}>
<div className="d-flex mb-2">
<span className="fs-6 fw-medium">
Change Status
</span>
</div>
<form className="row text-start" onSubmit={handleSubmit(onSubmit)}> <form className="row text-start" onSubmit={handleSubmit(onSubmit)}>
<div className="mb-2"> <div className="mb-2">
<AppFormController <AppFormController

View File

@ -1,16 +1,16 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import Avatar from "../../common/Avatar"; import Avatar from "../common/Avatar";
import { useAppForm } from "../../../hooks/appHooks/useAppForm"; import { useAppForm } from "../../hooks/appHooks/useAppForm";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { JobCommentSchema } from "../ServiceProjectSchema"; import { JobCommentSchema } from "./ServiceProjectSchema";
import { import {
useAddCommentJob, useAddCommentJob,
useJobComments, useJobComments,
} from "../../../hooks/useServiceProject"; } from "../../hooks/useServiceProject";
import { ITEMS_PER_PAGE } from "../../../utils/constants"; import { ITEMS_PER_PAGE } from "../../utils/constants";
import { formatUTCToLocalTime } from "../../../utils/dateUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils";
import Filelist from "../../Expenses/Filelist"; import Filelist from "../Expenses/Filelist";
import { formatFileSize, getIconByFileType } from "../../../utils/appUtils"; import { formatFileSize, getIconByFileType } from "../../utils/appUtils";
const JobComments = ({ data }) => { const JobComments = ({ data }) => {
const { const {
@ -150,7 +150,7 @@ const JobComments = ({ data }) => {
type="submit" type="submit"
disabled={!watch("comment")?.trim() || isPending} disabled={!watch("comment")?.trim() || isPending}
> >
Send Submit
</button> </button>
</div> </div>
</form> </form>
@ -161,48 +161,46 @@ const JobComments = ({ data }) => {
const user = item?.createdBy; const user = item?.createdBy;
return ( return (
<div className="d-flex align-items-start mt-2 mx-0 px-0"> <div
<Avatar key={item.id}
size="xs" className="list-group-item border-0 border-bottom p-0"
firstName={user?.firstName} >
lastName={user?.lastName} <div className="d-flex align-items-start mt-2 mx-0 px-0">
/> <Avatar
firstName={user?.firstName}
<div className="w-100"> lastName={user?.lastName}
<div className="d-flex flex-row align-items-center gap-3 w-100"> />
<span className="fw-semibold"> <div className="">
{user?.firstName} {user?.lastName} <div className="d-flex flex-row gap-3">
</span> <span className="fw-semibold">
{user?.firstName} {user?.lastName}
<span className="text-secondary"> </span>
<em>{formatUTCToLocalTime(item?.createdAt, true)}</em> <span className="text-secondary">
</span> <em>{formatUTCToLocalTime(item?.createdAt, true)}</em>
</span>
</div> </div>
<div className="text-muted text-secondary">
<div className="text-muted text-secondary"> {user?.jobRoleName}
{user?.jobRoleName} </div>
</div> <div className="text-wrap">
<p className="mb-1 mt-2 text-muted">{item.comment}</p>
<div className="text-wrap"> <div className="d-flex flex-wrap jusify-content-end gap-2 gap-sm-6 ">
<p className="mb-1 mt-2 text-muted">{item.comment}</p> {item.attachments?.map((file) => (
<div className="d-flex align-items-center">
<div className="d-flex flex-wrap jusify-content-end gap-2 gap-sm-6"> <i
{item.attachments?.map((file) => ( className={`bx bx-xxl ${getIconByFileType(
<div className="d-flex align-items-center"> file?.contentType
<i )} fs-3`}
className={`bx bx-xxl ${getIconByFileType( ></i>
file?.contentType <div className="d-flex flex-column">
)} fs-3`} <p className="m-0">{file.fileName}</p>
></i> <small className="text-secondary">
<div className="d-flex flex-column"> {formatFileSize(file.fileSize)}
<p className="m-0">{file.fileName}</p> </small>
<small className="text-secondary"> </div>
{formatFileSize(file.fileSize)}
</small>
</div> </div>
</div> ))}
))} </div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,49 +4,24 @@ import {
getJobStatusBadge, getJobStatusBadge,
getNextBadgeColor, getNextBadgeColor,
} from "../../utils/appUtils"; } from "../../utils/appUtils";
import { useServiceProjectJobs, useUpdateServiceProjectJob } from "../../hooks/useServiceProject"; import { useServiceProjectJobs } from "../../hooks/useServiceProject";
import { ITEMS_PER_PAGE, JOBS_STATUS_IDS } from "../../utils/constants"; import { ITEMS_PER_PAGE } from "../../utils/constants";
import EmployeeAvatarGroup from "../common/EmployeeAvatarGroup"; import EmployeeAvatarGroup from "../common/EmployeeAvatarGroup";
import { formatUTCToLocalTime } from "../../utils/dateUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils";
import { SpinnerLoader } from "../common/Loader"; import { SpinnerLoader } from "../common/Loader";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import ProjectPage from "../../pages/project/ProjectPage"; import ProjectPage from "../../pages/project/ProjectPage";
import { useServiceProjectJobContext } from "./Jobs"; import { useServiceProjectJobContext } from "./Jobs";
import ConfirmModal from "../common/ConfirmModal";
const JobList = ({ isArchive }) => { const JobList = () => {
const { setSelectedJob, setManageJob } = useServiceProjectJobContext(); const { setSelectedJob, setManageJob } = useServiceProjectJobContext();
const { mutate: UpdateJob,isPending } = useUpdateServiceProjectJob(() => {
});
const { projectId } = useParams(); const { projectId } = useParams();
const { data, isLoading, isError, error } = useServiceProjectJobs( const { data, isLoading, isError, error } = useServiceProjectJobs(
ITEMS_PER_PAGE, ITEMS_PER_PAGE,
1, 1,
true, 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 = [ const jobGrid = [
{ {
@ -60,17 +35,12 @@ const JobList = ({ isArchive }) => {
label: "Title", label: "Title",
getValue: (e) => ( getValue: (e) => (
<span <span
className={`fw-semibold text-truncate d-inline-block ${!isArchive ? "cursor-pointer" : ""}`} className="fw-semibold text-truncate d-inline-block cursor-pointer"
style={{ style={{
maxWidth: "100%", maxWidth: "100%",
width: "210px", width: "210px",
}} }}
onClick={() => { onClick={() => setSelectedJob({ showCanvas: true, job: e?.id })}
if (!isArchive) {
setSelectedJob({ showCanvas: true, job: e?.id });
}
}}
title={e?.title}
> >
{e?.title} {e?.title}
</span> </span>
@ -79,7 +49,6 @@ const JobList = ({ isArchive }) => {
className: "text-start", className: "text-start",
}, },
{ {
key: "dueDate", key: "dueDate",
label: "Due On", label: "Due On",
@ -117,155 +86,92 @@ const JobList = ({ isArchive }) => {
}, },
]; ];
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 ( return (
<> <div className="dataTables_wrapper dt-bootstrap5 no-footer table-responsive">
{isArchiveModalOpen && ( <table
<ConfirmModal className="datatables-users table border-top dataTable no-footer dtr-column text-nowrap table-responsive"
isOpen={isArchiveModalOpen} aria-describedby="DataTables_Table_0_info"
type={isArchiveAction ? "archive" : "Un-archive"} >
header={isArchiveAction ? "Archive Job" : "Restore Job"} <thead>
message={ <tr>
isArchiveAction {jobGrid.map((col) => (
? "Are you sure you want to archive this job?" <th
: "Are you sure you want to restore this job?" key={col.key}
} className={`${col.align || "text-center"} ${
onSubmit={handleArchive} col.className || ""
onClose={() => setIsArchiveModalOpen(false)} }`}
loading={isPending} scope="col"
/> >
)} <div className={col.className}>{col.label}</div>
<div className="dataTables_wrapper dt-bootstrap5 no-footer table-responsive">
<table
className="datatables-users table border-top dataTable no-footer dtr-column text-nowrap table-responsive"
aria-describedby="DataTables_Table_0_info"
>
<thead>
<tr>
{jobGrid.map((col) => (
<th
key={col.key}
className={`${col.align || "text-center"} ${col.className || ""
}`}
scope="col"
>
<div className={col.className}>{col.label}</div>
</th>
))}
<th className="sorting_disabled text-center" aria-label="Actions">
Actions
</th> </th>
</tr> ))}
</thead> <th className="sorting_disabled text-center" aria-label="Actions">
Actions
</th>
</tr>
</thead>
<tbody> <tbody>
{Array.isArray(data?.data) && data.data.length > 0 ? ( {Array.isArray(data?.data) && data.data.length > 0 ? (
data.data.map((row, i) => ( data.data.map((row, i) => (
<tr key={i} className="text-start"> <tr key={i} className="text-start">
{jobGrid.map((col) => ( {jobGrid.map((col) => (
<td <td
key={col.key} key={col.key}
className={col.className} className={col.className}
// onClick={() => onClick={() =>
// setSelectedJob({ showCanvas: true, job: row?.id }) setSelectedJob({ showCanvas: true, job: row?.id })
// } }
onClick={() => { >
if (!isArchive) { {col.getValue(row)}
setSelectedJob({ showCanvas: true, job: e?.id });
}
}}
>
{col.getValue(row)}
</td>
))}
<td>
<div className="dropdown text-center">
<button
className="btn btn-icon dropdown-toggle hide-arrow"
data-bs-toggle="dropdown"
>
<i className="bx bx-dots-vertical-rounded bx-md"></i>
</button>
<div className="dropdown-menu dropdown-menu-end">
{!isArchive && (
<>
<button
className="dropdown-item py-1"
onClick={() =>
setSelectedJob({ showCanvas: true, job: row?.id })
}
>
<i className="bx bx-detail bx-sm"></i> View
</button>
<button
className="dropdown-item py-1"
onClick={() =>
setManageJob({ isOpen: true, jobId: row?.id })
}
>
<i className="bx bx-edit bx-sm"></i> Edit
</button>
</>
)}
{isArchive && (
<button
className="dropdown-item py-1"
onClick={() => {
setArchiveJobId(row.id);
setIsArchiveAction(false);
setIsArchiveModalOpen(true);
}}
>
<i className="bx bx-reset bx-sm"></i> Restore
</button>
)}
{!isArchive && canArchive(row?.status?.id) && (
<button
className="dropdown-item py-1"
onClick={() => {
setArchiveJobId(row.id);
setIsArchiveAction(true);
setIsArchiveModalOpen(true);
}}
>
<i className="bx bx-archive bx-sm"></i> Archive
</button>
)}
</div>
</div>
</td> </td>
</tr> ))}
)) <td>
) : ( <div className="dropdown text-center">
<tr style={{ height: "200px" }}> <button
<td className="btn btn-icon dropdown-toggle hide-arrow"
colSpan={jobGrid.length + 1} data-bs-toggle="dropdown"
className="text-center border-0 align-middle" >
> <i className="bx bx-dots-vertical-rounded bx-md"></i>
{isLoading ? <SpinnerLoader /> : "Not Found Jobs."} </button>
<div className="dropdown-menu dropdown-menu-end">
<button
className="dropdown-item py-1"
onClick={() =>
setSelectedJob({ showCanvas: true, job: row?.id })
}
>
<i className="bx bx-detail bx-sm"></i> View
</button>
<>
<button
className="dropdown-item py-1"
onClick={() =>
setManageJob({ isOpen: true, jobId: row?.id })
}
>
<i className="bx bx-edit bx-sm"></i> Edit
</button>
</>
</div>
</div>
</td> </td>
</tr> </tr>
)} ))
</tbody> ) : (
</table> <tr style={{ height: "200px" }}>
</div> <td
</> colSpan={jobGrid.length + 1}
className="text-center border-0 align-middle"
>
{isLoading ? <SpinnerLoader /> : "Not Found Jobs."}
</td>
</tr>
)}
</tbody>
</table>
</div>
); );
}; };

View File

@ -1,7 +1,6 @@
import React from "react"; import React from "react";
import Avatar from "../../common/Avatar"; import Avatar from "../common/Avatar";
import { formatUTCToLocalTime } from "../../../utils/dateUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils";
const JobStatusLog = ({ data }) => { const JobStatusLog = ({ data }) => {
return ( return (

View File

@ -5,8 +5,8 @@ import { useServiceProjects } from "../../hooks/useServiceProject";
import { ITEMS_PER_PAGE } from "../../utils/constants"; import { ITEMS_PER_PAGE } from "../../utils/constants";
import OffcanvasComponent from "../common/OffcanvasComponent"; import OffcanvasComponent from "../common/OffcanvasComponent";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import ManageJob from "./ServiceProjectJob/ManageJob"; import ManageJob from "./ManageJob";
import ManageJobTicket from "./ServiceProjectJob/ManageJobTicket"; import ManageJobTicket from "./ManageJobTicket";
import GlobalModel from "../common/GlobalModel"; import GlobalModel from "../common/GlobalModel";
import PreviewDocument from "../Expenses/PreviewDocument"; import PreviewDocument from "../Expenses/PreviewDocument";
@ -21,8 +21,6 @@ export const useServiceProjectJobContext = () => {
}; };
const Jobs = () => { const Jobs = () => {
const [manageJob, setManageJob] = useState({ isOpen: false, jobId: null }); const [manageJob, setManageJob] = useState({ isOpen: false, jobId: null });
const [showArchive, setShowArchive] = useState(false);
const [showCanvas, setShowCanvas] = useState(false); const [showCanvas, setShowCanvas] = useState(false);
const [selectedProject, setSelectedProject] = useState(null); const [selectedProject, setSelectedProject] = useState(null);
const [selectJob, setSelectedJob] = useState({ const [selectJob, setSelectedJob] = useState({
@ -60,34 +58,21 @@ const Jobs = () => {
<ManageJob Job={manageJob.jobId} /> <ManageJob Job={manageJob.jobId} />
</OffcanvasComponent> </OffcanvasComponent>
<div className="card page-min-h my-2 px-7 pb-4"> <div className="card page-min-h my-2 px-7 pb-4">
<div className="row align-items-center py-4"> <div className="row">
<div className="col-12 py-2 d-flex justify-content-end ">
{/* LEFT — Tabs */} <div className="px-2">
<div className="col-12 col-md-6 text-start"> <button
<button className="btn btn-sm btn-primary"
type="button" onClick={() => setManageJob({ isOpen: true, jobId: null })}
className={`btn btn-sm ${showArchive ? "btn-secondary" : "btn-outline-secondary"}`} >
onClick={() => setShowArchive(!showArchive)} <i className="bx bx-plus-circle bx-md me-2"></i>New Job
> </button>
<i className="bx bx-archive bx-sm me-1 mt-1"></i> Archived </div>
</button>
</div> </div>
{/* RIGHT — New Job button */} <JobList filterByProject={selectedProject} />
<div className="col-12 col-md-6 d-flex justify-content-md-end mt-2 mt-md-0">
<button
className="btn btn-sm btn-primary"
onClick={() => setManageJob({ isOpen: true, jobId: null })}
>
<i className="bx bx-plus-circle bx-md me-2"></i>New Job
</button>
</div>
</div> </div>
{/* Job List */}
<JobList filterByProject={selectedProject} isArchive={showArchive} />
</div> </div>
</JonContext.Provider> </JonContext.Provider>
</> </>
); );

View File

@ -1,32 +1,30 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import Breadcrumb from "../../common/Breadcrumb"; import Breadcrumb from "../common/Breadcrumb";
import Label from "../../common/Label"; import Label from "../common/Label";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { defaultJobValue, jobSchema } from "../ServiceProjectSchema"; import { defaultJobValue, jobSchema } from "./ServiceProjectSchema";
import { import {
useBranches,
useCreateServiceProjectJob, useCreateServiceProjectJob,
useJobTags, useJobTags,
useServiceProjectJobDetails, useServiceProjectJobDetails,
useServiceProjects, useServiceProjects,
useUpdateServiceProjectJob, useUpdateServiceProjectJob,
} from "../../../hooks/useServiceProject"; } from "../../hooks/useServiceProject";
import { ITEMS_PER_PAGE } from "../../../utils/constants"; import { ITEMS_PER_PAGE } from "../../utils/constants";
import DatePicker from "../../common/DatePicker"; import DatePicker from "../common/DatePicker";
import PmsEmployeeInputTag from "../../common/PmsEmployeeInputTag"; import PmsEmployeeInputTag from "../common/PmsEmployeeInputTag";
import TagInput from "../../common/TagInput"; import TagInput from "../common/TagInput";
import { localToUtc } from "../../../utils/appUtils"; import { localToUtc } from "../../utils/appUtils";
import SelectField from "../../common/Forms/SelectField"; import SelectField from "../common/Forms/SelectField";
import { import {
AppFormController, AppFormController,
AppFormProvider, AppFormProvider,
useAppForm, useAppForm,
} from "../../../hooks/appHooks/useAppForm"; } from "../../hooks/appHooks/useAppForm";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { useJobStatus } from "../../../hooks/masterHook/useMaster"; import { useJobStatus } from "../../hooks/masterHook/useMaster";
import { useServiceProjectJobContext } from "../Jobs"; import { useServiceProjectJobContext } from "./Jobs";
import { SelectFieldSearch } from "../../common/Forms/SelectFieldServerSide";
const ManageJob = ({ Job }) => { const ManageJob = ({ Job }) => {
const { setManageJob, setSelectedJob } = useServiceProjectJobContext(); const { setManageJob, setSelectedJob } = useServiceProjectJobContext();
@ -42,7 +40,6 @@ const ManageJob = ({ Job }) => {
watch, watch,
handleSubmit, handleSubmit,
reset, reset,
setValue,
formState: { errors }, formState: { errors },
} = methods; } = methods;
@ -165,7 +162,6 @@ const ManageJob = ({ Job }) => {
dueDate: JobData.dueDate ?? null, dueDate: JobData.dueDate ?? null,
tags: JobData.tags ?? [], tags: JobData.tags ?? [],
statusId: JobData.status.id, statusId: JobData.status.id,
projectBranchId : JobData?.projectBranch?.id
}); });
}, [JobData, Job, projectId]); }, [JobData, Job, projectId]);
return ( return (
@ -203,7 +199,7 @@ const ManageJob = ({ Job }) => {
/> />
</div> </div>
<div className="col-12 col-md-6 mb-2 mb-md-4"> <div className="col-12 col-md-6 mb-2 mb-md-4">
<Label>Select Employee</Label> <Label required>Select Employee</Label>
<PmsEmployeeInputTag <PmsEmployeeInputTag
control={control} control={control}
name="assignees" name="assignees"
@ -242,20 +238,7 @@ const ManageJob = ({ Job }) => {
name="tags" name="tags"
label="Tag" label="Tag"
placeholder="Enter Tag" placeholder="Enter Tag"
/> required
</div>
<div className="col-12 col-md-6 mb-2 mb-md-4">
<SelectFieldSearch
label="Select Branch"
placeholder="Select Branch"
value={watch("projectBranchId")}
onChange={(val) => setValue("projectBranchId", val)}
valueKey="id"
labelKey="branchName"
hookParams={[projectId, true, 10, 1]}
useFetchHook={useBranches}
isMultiple={false}
disabled={Job}
/> />
</div> </div>
<div className="col-12"> <div className="col-12">

View File

@ -1,27 +1,25 @@
import React, { useEffect, useRef } from "react"; import React, { useEffect } from "react";
import { useServiceProjectJobDetails } from "../../../hooks/useServiceProject"; import { useServiceProjectJobDetails } from "../../hooks/useServiceProject";
import { SpinnerLoader } from "../../common/Loader"; import { SpinnerLoader } from "../common/Loader";
import Error from "../../common/Error"; import Error from "../common/Error";
import { formatUTCToLocalTime } from "../../../utils/dateUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils";
import Avatar from "../../common/Avatar"; import Avatar from "../common/Avatar";
import EmployeeAvatarGroup from "../../common/EmployeeAvatarGroup"; import EmployeeAvatarGroup from "../common/EmployeeAvatarGroup";
import { daysLeft, getJobStatusBadge } from "../../../utils/appUtils"; import JobStatusLog from "./JobStatusLog";
import HoverPopup from "../../common/HoverPopup"; import JobComments from "./JobComments";
import { daysLeft, getJobStatusBadge } from "../../utils/appUtils";
import HoverPopup from "../common/HoverPopup";
import ChangeStatus from "./ChangeStatus"; import ChangeStatus from "./ChangeStatus";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { STATUS_JOB_CLOSED } from "../../../utils/constants"; import { STATUS_JOB_CLOSED } from "../../utils/constants";
import Tooltip from "../../common/Tooltip"; import Tooltip from "../common/Tooltip";
import BranchDetails from "../ServiceProjectBranch/BranchDetails";
import { JobDetailsSkeleton } from "../ServiceProjectSeketon";
import JobComments from "./JobComments";
import JobStatusLog from "./JobStatusLog";
const ManageJobTicket = ({ Job }) => { const ManageJobTicket = ({ Job }) => {
const { projectId } = useParams(); const { projectId } = useParams();
const { data, isLoading, isError, error } = useServiceProjectJobDetails( const { data, isLoading, isError, error } = useServiceProjectJobDetails(
Job?.job Job?.job
); );
const drawerRef = useRef();
const tabsData = [ const tabsData = [
{ {
id: "comment", id: "comment",
@ -39,7 +37,7 @@ const ManageJobTicket = ({ Job }) => {
}, },
]; ];
if (isLoading) return <JobDetailsSkeleton />; if (isLoading) return <SpinnerLoader />;
if (isError) if (isError)
return ( return (
<div> <div>
@ -47,11 +45,7 @@ const ManageJobTicket = ({ Job }) => {
</div> </div>
); );
return ( return (
<div <div className="row text-start">
className=" text-start position-relative"
ref={drawerRef}
style={{ overflow: "visible" }}
>
<div className="col-12"> <div className="col-12">
<h6 className="fs-5 fw-semibold">{data?.title}</h6> <h6 className="fs-5 fw-semibold">{data?.title}</h6>
<div className="d-flex justify-content-between align-items-start flex-wrap mb-2"> <div className="d-flex justify-content-between align-items-start flex-wrap mb-2">
@ -60,16 +54,16 @@ const ManageJobTicket = ({ Job }) => {
{data?.jobTicketUId || "N/A"} {data?.jobTicketUId || "N/A"}
</p> </p>
<div className="d-flex flex-column align-items-end gap-3 mb-3"> <div className="d-flex flex-column align-items-end gap-3 mb-3">
<div className="d-flex flex-row gap-2 position-relative"> <div className="d-flex flex-row gap-2">
<span className={`badge ${getJobStatusBadge(data?.status?.id)}`}> <span className={`badge ${getJobStatusBadge(data?.status?.id)}`}>
{data?.status?.displayName} {data?.status?.displayName}
</span> </span>
{STATUS_JOB_CLOSED !== data?.status?.id && ( {STATUS_JOB_CLOSED !== data?.status?.id && (
<HoverPopup <HoverPopup
id="STATUS_CHANEG" id="STATUS_CHANEG"
title="Change Status"
Mode="click" Mode="click"
className="" className=""
align="right"
content={ content={
<ChangeStatus <ChangeStatus
statusId={data?.status?.id} statusId={data?.status?.id}
@ -96,33 +90,23 @@ const ManageJobTicket = ({ Job }) => {
<p>{data?.description || "N/A"}</p> <p>{data?.description || "N/A"}</p>
</div> </div>
<div className="d-flex justify-content-between align-items-center mb-4"> <div className="d-flex justify-content-between mb-4">
<div className="d-flex flex-row gap-1 text-secondry"> <div className="d-flex flex-row gap-1 fw-medium">
<i className="bx bx-calendar"></i>{" "} <i className="bx bx-calendar"></i>{" "}
<span> <span>
Created Date : {formatUTCToLocalTime(data?.createdAt, true)} Created Date : {formatUTCToLocalTime(data?.createdAt, true)}
</span> </span>
</div> </div>
<div className="d-flex flex-row gap-2 text-wraps">
{data?.tags?.map((tag, ind) => (
<span
key={`${ind}0-${tag?.name}`}
className="badge bg-label-primary"
>
{tag?.name}
</span>
))}
</div>
</div> </div>
<div className="d-flex justify-content-md-between "> <div className="d-flex justify-content-md-between ">
<div className="d-flex flex-row gap-5"> <div className="d-flex flex-row gap-5">
<span className="text-secondry"> <span className="fw-medium">
<i className="bx bx-calendar"></i> Start Date :{" "} <i className="bx bx-calendar"></i> Start Date :{" "}
{formatUTCToLocalTime(data?.startDate)} {formatUTCToLocalTime(data?.startDate)}
</span>{" "} </span>{" "}
<i className="bx bx-right-arrow-alt"></i>{" "} <i className="bx bx-right-arrow-alt"></i>{" "}
<span className="text-secondry"> <span className="fw-medium">
<i className="bx bx-calendar"></i> Due on :{" "} <i className="bx bx-calendar"></i> Due on :{" "}
{formatUTCToLocalTime(data?.startDate)} {formatUTCToLocalTime(data?.startDate)}
</span> </span>
@ -132,7 +116,7 @@ const ManageJobTicket = ({ Job }) => {
const { days, color } = daysLeft(data?.startDate, data?.dueDate); const { days, color } = daysLeft(data?.startDate, data?.dueDate);
return ( return (
<span> <span>
<span className="text-secondry me-1">Days Left:</span> <span className="fw-medium me-1">Days Left:</span>
<span className={`badge bg-${color}`}> <span className={`badge bg-${color}`}>
{days !== null ? `${days} days` : "N/A"} {days !== null ? `${days} days` : "N/A"}
</span> </span>
@ -140,82 +124,61 @@ const ManageJobTicket = ({ Job }) => {
); );
})()} })()}
</div> </div>
{data?.projectBranch && ( <div className="d-block mt-4 mb-3">
<div className="d-flex gap-3 my-2 position-relative" ref={drawerRef} > <div className="row align-items-start align-items-md-start gap-2 mb-1">
<span className="text-secondary"> <div className="col-12 col-md-auto">
<i className="bx bx-buildings"></i> Branch Name: <small className="fs-6 fw-medium">Created By</small>
</span> </div>
<div className="col d-flex flex-row align-items-center ">
<HoverPopup
id="BRANCH_DETAILS"
Mode="click"
align="auto"
boundaryRef={drawerRef}
content={<BranchDetails branch={data?.projectBranch?.id} />}
>
<span className="text-decoration-underline cursor-pointer">
{data?.projectBranch?.branchName}
</span>
</HoverPopup>
</div>
)}
<div className="border-top my-1">
<p className="m-0 py-1">
<i className="bx bx-group"></i> Peoples
</p>
{/* Created By */}
<div className="d-flex justify-content-between align-items-start w-100">
<p className="text-secondary m-0 me-3">Created By</p>
<div className="flex-grow-1 d-flex align-items-center gap-2">
<Avatar <Avatar
size="xs" size="xs"
firstName={data?.createdBy?.firstName} firstName={data?.createdBy?.firstName}
lastName={data?.createdBy?.lastName} lastName={data?.createdBy?.lastName}
/> />{" "}
<div className="d-flex flex-column"> <div className="d-flex flex-row align-items-center">
<p className="m-0 text-truncate"> <p className="m-0">{`${data?.createdBy?.firstName} ${data?.createdBy?.lastName}`}</p>
{data?.createdBy?.firstName} {data?.createdBy?.lastName} <small className="text-secondary ms-1">
</p> ({data?.createdBy?.jobRoleName})
<small className="text-secondary text-xs">
{data?.createdBy?.jobRoleName}
</small> </small>
</div> </div>
</div> </div>
</div> </div>
{/* Assigned To */} {data?.assignees?.length > 0 && (
<div className="d-flex flex-column flex-md-row align-items-start w-100 mt-2"> <div className="row align-items-start align-items-md-start gap-2">
<p className="text-secondary m-0 me-3">Assigned To</p> <div className="col-12 col-md-auto">
<small className="fs-6 fw-medium">Assigned To</small>
</div>
<div className="flex-grow-1"> <div className="col">
<div className="d-flex flex-wrap gap-3"> <div className="row gap-4">
{data?.assignees?.map((emp) => ( {data?.assignees?.map((emp) => (
<div key={emp.id} className="d-flex align-items-center"> <div
<Avatar key={emp.id}
size="xs" className="col-6 col-sm-6 col-md-4 col-lg-4"
firstName={emp.firstName} >
lastName={emp.lastName} <div className="d-flex align-items-center gap-2">
/> <Avatar
size="xs"
firstName={emp.firstName}
lastName={emp.lastName}
/>
<div className="d-flex flex-column ms-2 text-truncate"> <div className="d-flex flex-column">
<span className="text-truncate"> <span className=" text-truncate">
{emp.firstName} {emp.lastName} {emp.firstName} {emp.lastName}
</span> </span>
<small className="text-secondary text-xs text-truncate"> <small className="text-secondary text-truncate">
{emp.jobRoleName} {emp.jobRoleName}
</small> </small>
</div>
</div>
</div> </div>
</div> ))}
))} </div>
</div> </div>
</div> </div>
</div> )}
</div> </div>
</div> </div>

View File

@ -192,8 +192,8 @@ const ManageServiceProject = ({ serviceProjectId, onClose }) => {
{...register("statusId")} {...register("statusId")}
> >
<option>Select Service</option> <option>Select Service</option>
{PROJECT_STATUS?.map((status) => ( {PROJECT_STATUS.map((status) => (
<option key={status.id} value={status.id}>{status.label}</option> <option value={status.id}>{status.label}</option>
))} ))}
</select> </select>
{errors?.statusId && ( {errors?.statusId && (

View File

@ -1,95 +0,0 @@
import React from 'react'
import { formatUTCToLocalTime } from '../../utils/dateUtils'
const ServiceProfile = ({data,setIsOpenModal}) => {
return (
<div className="card mb-4 h-100">
<div className="card-header text-start">
<h5 className="card-action-title mb-0 ps-1">
{" "}
<i className="fa fa-building rounded-circle text-primary"></i>
<span className="ms-2 fw-bold">Project Profile</span>
</h5>
</div>
<div className="card-body">
<ul className="list-unstyled my-3 ps-0 text-start">
<li className="d-flex mb-3">
<div className="d-flex align-items-start" style={{ minWidth: "150px" }}>
<i className="bx bx-cog"></i>
<span className="fw-medium mx-2 text-nowrap">Name:</span>
</div>
<div className="flex-grow-1 text-start text-wrap">
{data.name}
</div>
</li>
<li className="d-flex mb-3">
<div className="d-flex align-items-center" style={{ width: '150px' }}>
<i className="bx bx-fingerprint"></i>
<span className="fw-medium mx-2">Nick Name:</span>
</div>
<span>{data.shortName}</span>
</li>
<li className="d-flex mb-3">
<div className="d-flex align-items-center" style={{ width: '150px' }}>
<i className="bx bx-check"></i>
<span className="fw-medium mx-2">Assign Date:</span>
</div>
<span>
{data.assignedDate ? formatUTCToLocalTime(data.assignedDate) : "NA"}
</span>
</li>
<li className="d-flex mb-3">
<div className="d-flex align-items-center" style={{ width: '150px' }}>
<i className="bx bx-trophy"></i>
<span className="fw-medium mx-2">Status:</span>
</div>
<span>{data?.status.status}</span>
</li>
<li className="d-flex mb-3">
<div className="d-flex align-items-center" style={{ width: '150px' }}>
<i className="bx bx-user"></i>
<span className="fw-medium mx-2">Contact:</span>
</div>
<span>{data.contactName}</span>
</li>
<li className="d-flex mb-3">
{/* Label section with icon */}
<div className="d-flex align-items-start" style={{ minWidth: "150px" }}>
<i className="bx bx-flag mt-1"></i>
<span className="fw-medium mx-2 text-nowrap">Address:</span>
</div>
{/* Content section that wraps nicely */}
<div className="flex-grow-1 text-start text-wrap">
{data.address}
</div>
</li>
<li className="d-flex justify-content-center mt-4">
<a className="d-flex justify-content-center mt-4">
<button
type="button"
className="btn btn-sm btn-primary"
data-bs-toggle="modal"
data-bs-target="#edit-project-modal"
onClick={() => setIsOpenModal(true)}
>
Modify Details
</button>
</a>
</li>
</ul>
</div>
</div>
)
}
export default ServiceProfile

View File

@ -1,86 +0,0 @@
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 <BranchDetailsSkeleton />;
if (isError) return <Error error={error} />;
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 (
<div className="w-100">
<div className="d-flex mb-2 align-items-center">
<i className="bx bx-buildings bx-sm me-2 text-primary"></i>
<span className="fw-semibold">Branch Details</span>
</div>
<DetailRow label="Branch Name" value={data?.branchName} />
<DetailRow label="Type" value={data?.branchType} />
<DetailRow label="Address" value={data?.address} />
{/* Contact persons */}
{contactInfo.map((person, index) => (
<div key={index} className="mb-2">
<div className="fw-medium text-primary">{person.contactPerson}</div>
<DetailRow label="Role" value={person.designation} />
<DetailRow label="Emails" value={person.contactEmails.join(", ")} />
<DetailRow label="Phone" value={person.contactNumbers.join(", ")} />
</div>
))}
{/* Map Link */}
{googleMapUrl && (
<div className="mt-2">
<a
href={googleMapUrl}
target="_blank"
rel="noopener noreferrer"
className="text-primary text-decoration-underline"
>
View on Google Maps
</a>
<button
className="btn btn-xs btn-secondry border ms-2"
onClick={handleCopy}
>
<i className={`bx bx-xs me-1 ${copied ? "bxs-copy-alt":"bx-copy-alt"} `} ></i> {copied ? "Copied!" : "Copy"}
</button>
</div>
)}
</div>
);
};
const DetailRow = ({ label, value }) => (
<div className="d-flex mb-1">
<div className="text-secondary" style={{ width: "90px", flexShrink: 0 }}>
{label}:
</div>
<div className="" style={{ wordBreak: "break-word" }}>
{value || "N/A"}
</div>
</div>
);
export default BranchDetails;

View File

@ -1,358 +0,0 @@
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 (
<div className="container p-3">
<h5 className="m-0">
{BranchToEdit ? "Update Branch" : "Create Branch"}
</h5>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="row my-2 text-start">
<div className="col-md-6">
<Label htmlFor="branchName" className="form-label" required>
Branch Name
</Label>
<input
type="text"
id="branchName"
className="form-control form-control-sm"
{...register("branchName")}
placeholder="Enter Branch"
/>
{errors.branchName && (
<small className="danger-text">{errors.branchName.message}</small>
)}
</div>
<div className="col-md-6">
<Label htmlFor="branchType" className="form-label" required>
Branch Type
</Label>
<InputSuggestions
organizationList={branchTypes}
value={watch("branchType") || ""}
onChange={(val) =>
setValue("branchType", val, { shouldValidate: true })
}
error={errors.branchType?.message}
/>
</div>
</div>
<div className="row my-2 text-start">
<div className="col-md-6">
<Label htmlFor="googleMapUrl" className="form-label">
Google Map URL
</Label>
<input
type="text"
id="googleMapUrl"
className="form-control form-control-sm"
{...register("googleMapUrl")}
/>
{errors.googleMapUrl && (
<small className="danger-text">
{errors.googleMapUrl.message}
</small>
)}
</div>
</div>
<div className="row my-2 text-start">
<div className="col-12">
<Label className="form-label" required>
Contact Persons
</Label>
{contacts.map((item, index) => (
<div key={index} className="border rounded p-2 mb-3">
<div className="d-flex justify-content-end py-1">
{" "}
<div className="col-md-1 d-flex align-items-center">
<i
className="bx bx-trash text-danger cursor-pointer"
onClick={() =>
setContacts(contacts.filter((_, i) => i !== index))
}
></i>
</div>
</div>
<div className="row mb-2">
<div className=" col-md-6">
<Label className="form-label">Contact Name</Label>
<input
type="text"
placeholder="Contact Name"
className="form-control form-control-sm"
value={item.contactPerson}
onChange={(e) => {
const list = [...contacts];
list[index].contactPerson = e.target.value;
setContacts(list);
}}
/>
</div>
<div className="col-md-6">
<Label className="form-label">Designation</Label>
<input
type="text"
placeholder="Designation"
className="form-control form-control-sm"
value={item.designation}
onChange={(e) => {
const list = [...contacts];
list[index].designation = e.target.value;
setContacts(list);
}}
/>
</div>
</div>
{/* Numbers Section */}
<Label className="form-label">Contact Numbers</Label>
{item.contactNumbers.map((num, numIndex) => (
<div
key={numIndex}
className="d-flex gap-2 mb-2 align-items-center"
>
<input
type="text"
placeholder="Number"
className="form-control form-control-sm"
maxLength={10}
value={num}
onChange={(e) => {
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 ? (
<i
className="bx bx-plus-circle text-primary cursor-pointer fs-5"
onClick={() => {
const list = [...contacts];
list[index].contactNumbers.push("");
setContacts(list);
}}
></i>
) : (
<i
className="bx bx-minus-circle text-danger cursor-pointer fs-5"
onClick={() => {
const list = [...contacts];
list[index].contactNumbers.splice(numIndex, 1);
setContacts(list);
}}
></i>
)}
</div>
))}
<hr />
{/* Emails Section */}
<Label className="form-label">Contact Emails</Label>
{item.contactEmails.map((email, emailIndex) => (
<div
key={emailIndex}
className="d-flex gap-2 mb-2 align-items-center"
>
<input
type="email"
placeholder="Email"
className="form-control form-control-sm"
value={email}
onChange={(e) => {
const list = [...contacts];
list[index].contactEmails[emailIndex] = e.target.value;
setContacts(list);
}}
/>
{/* Show PLUS only on the last row */}
{emailIndex === item.contactEmails.length - 1 ? (
<i
className="bx bx-plus-circle text-primary cursor-pointer fs-5"
onClick={() => {
const list = [...contacts];
list[index].contactEmails.push("");
setContacts(list);
}}
></i>
) : (
<i
className="bx bx-minus-circle text-danger cursor-pointer fs-5"
onClick={() => {
const list = [...contacts];
list[index].contactEmails.splice(emailIndex, 1);
setContacts(list);
}}
></i>
)}
</div>
))}
</div>
))}
<button
type="button"
className="btn btn-sm btn-primary mt-2"
onClick={() =>
setContacts([
...contacts,
{
contactPerson: "",
designation: "",
contactEmails: [""], // important
contactNumbers: [""], // important
},
])
}
>
<i className="bx bx-plus"></i> Add Contact
</button>
</div>
</div>
<div className="row my-2 text-start">
<div className="col-12">
<Label htmlFor="address" className="form-label" required>
Address
</Label>
<textarea
id="address"
className="form-control form-control-sm"
{...register("address")}
/>
{errors.address && (
<small className="danger-text">{errors.address.message}</small>
)}
</div>
</div>
<div className="d-flex justify-content-end gap-3">
<button
type="reset"
onClick={handleClose}
className="btn btn-label-secondary btn-sm mt-3"
>
Cancel
</button>
<button type="submit" className="btn btn-primary btn-sm mt-3">
{isPending ? "Please wait..." : "Submit"}
</button>
</div>
</form>
</div>
);
};
export default ManageBranch;

View File

@ -1,283 +0,0 @@
import React, { useState } from "react";
import GlobalModel from "../../common/GlobalModel";
import ManageBranch from "./ManageBranch";
import { useBranches, useDeleteBranch } from "../../../hooks/useServiceProject";
import { ITEMS_PER_PAGE } from "../../../utils/constants";
import { useDebounce } from "../../../utils/appUtils";
import { useParams } from "react-router-dom";
import Pagination from "../../common/Pagination";
import ConfirmModal from "../../common/ConfirmModal";
import { SpinnerLoader } from "../../common/Loader";
const ServiceBranch = () => {
const { projectId } = useParams();
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [showInactive, setShowInactive] = useState(false);
const [manageState, setManageState] = useState({
IsOpen: false,
branchId: null,
});
const { mutate: DeleteBranch, isPending } = useDeleteBranch();
const [deletingId, setDeletingId] = useState(null);
const [search, setSearch] = useState("");
const [currentPage, setCurrentPage] = useState(1);
const debouncedSearch = useDebounce(search, 500);
const { data, isLoading, isError, error } = useBranches(
projectId,
!showInactive,
ITEMS_PER_PAGE - 12,
currentPage,
debouncedSearch
);
const paginate = (page) => {
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
setCurrentPage(page);
}
};
const columns = [
{
key: "branchName",
label: "Name",
align: "text-start",
getValue: (e) => e?.branchName || "N/A",
},
];
const handleDelete = (id) => {
setDeletingId(id);
DeleteBranch(
{ id, isActive: showInactive },
{
onSettled: () => {
setDeletingId(null);
setIsDeleteModalOpen(false);
},
}
);
};
return (
<>
{IsDeleteModalOpen && (
<ConfirmModal
isOpen={IsDeleteModalOpen}
type={!showInactive ? "delete" : "undo"}
header={!showInactive ? "Delete Branch" : "Restore Branch"}
message={
!showInactive
? "Are you sure you want delete?"
: "Are you sure you want restore?"
}
onSubmit={handleDelete}
onClose={() => setIsDeleteModalOpen(false)}
loading={isPending}
paramData={deletingId}
/>
)}
<div className="card h-100 table-responsive px-sm-4">
<div className="card-datatable" id="payment-request-table">
{/* Header Section */}
<div className="row align-items-center justify-content-between mt-3 mx-1">
<div className="col-md-4 col-sm-12 ms-n3 text-start ">
<h5 className="mb-0">
<i className="bx bx-buildings text-primary"></i>
<span className="ms-2 fw-bold">Branchs</span>
</h5>
</div>
{/* Flex container for toggle + button */}
<div className="col-md-8 col-sm-12 text-end">
<div className="d-flex justify-content-end gap-2">
<div className="form-check form-switch d-inline-flex align-items-center">
<input
type="checkbox"
className="form-check-input mt-1"
id="inactiveEmployeesCheckbox"
checked={showInactive}
onChange={() => setShowInactive(!showInactive)}
/>
<label
htmlFor="inactiveEmployeesCheckbox"
className="ms-2 text-xs"
>
{!showInactive ? "Show Deleted" : "Hide Deleted"}
</label>
</div>
<div className="d-flex justify-content-end">
<button
className="btn btn-sm btn-primary"
type="button"
onClick={() =>
setManageState({
IsOpen: true,
branchId: null,
})
}
>
<i className="bx bx-sm bx-plus-circle me-2"></i>
Add Branch
</button>
</div>
</div>
</div>
</div>
<div className="mx-2 mt-3">
<table className="table border-top text-nowrap align-middle table-borderless">
<thead>
<tr>
{columns.map((col) => (
<th key={col.key} className={col.align}>
{col.label}
</th>
))}
<th className="text-center">Action</th>
</tr>
</thead>
<tbody>
{isLoading && (
<tr>
<td
colSpan={columns.length + 1}
className="text-center py-5"
style={{
height: "200px",
verticalAlign: "middle",
}}
>
<div className="d-flex justify-content-center align-items-center w-100 h-100">
<SpinnerLoader />
</div>
</td>
</tr>
)}
{isError && (
<tr>
<td
colSpan={columns.length + 1}
className="text-center text-danger py-5"
>
{error?.message || "Error loading branches"}
</td>
</tr>
)}
{!isLoading &&
!isError &&
data?.data?.length > 0 &&
data.data.map((branch) => (
<tr key={branch.id} style={{ height: "35px" }}>
{columns.map((col) => (
<td key={col.key} className={`${col.align} py-3`}>
{col.getValue(branch)}
</td>
))}
<td className="text-center">
<div className="dropdown z-2">
<button
type="button"
className="btn btn-xs btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0 m-0"
data-bs-toggle="dropdown"
>
<i className="bx bx-dots-vertical-rounded text-muted p-0"></i>
</button>
<ul className="dropdown-menu dropdown-menu-end w-auto">
{!showInactive ? (
<>
<li
onClick={() =>
setManageState({
IsOpen: true,
branchId: branch.id,
})
}
>
<a className="dropdown-item px-2 cursor-pointer py-1">
<i className="bx bx-edit text-primary bx-xs me-2"></i>
Modify
</a>
</li>
<li
onClick={() => {
setIsDeleteModalOpen(true);
setDeletingId(branch.id);
}}
>
<a className="dropdown-item px-2 cursor-pointer py-1">
<i className="bx bx-trash text-danger bx-xs me-2"></i>
Delete
</a>
</li>
</>
) : (
<li
onClick={() => {
setIsDeleteModalOpen(true);
setDeletingId(branch.id);
}}
>
<a className="dropdown-item px-2 cursor-pointer py-1">
<i className="bx bx-undo text-danger me-2"></i>
Restore
</a>
</li>
)}
</ul>
</div>
</td>
</tr>
))}
{!isLoading &&
!isError &&
(!data?.data || data.data.length === 0) && (
<tr>
<td
colSpan={columns.length + 1}
className="text-center py-12"
>
No Branch Found
</td>
</tr>
)}
</tbody>
</table>
{data?.data?.length > 0 && (
<Pagination
currentPage={currentPage}
totalPages={data.totalPages}
onPageChange={paginate}
/>
)}
</div>
{manageState.IsOpen && (
<GlobalModel
isOpen
size="md"
closeModal={() =>
setManageState({ IsOpen: false, branchId: null })
}
>
<ManageBranch
key={manageState.branchId ?? "new"}
BranchToEdit={manageState.branchId}
closeModal={() =>
setManageState({ IsOpen: false, branchId: null })
}
/>
</GlobalModel>
)}
</div>
</div>
</>
);
};
export default ServiceBranch;

View File

@ -1,80 +0,0 @@
import React from "react";
import { useAppForm } from "../../../hooks/appHooks/useAppForm";
import { zodResolver } from "@hookform/resolvers/zod";
import { JobCommentSchema } from "../ServiceProjectSchema";
import Avatar from "../../common/Avatar";
import Filelist from "../../Expenses/Filelist";
const UpdateJobComment = () => {
const {
register,
handleSubmit,
watch,
reset,
setValue,
formState: { errors },
} = useAppForm({
resolver: zodResolver(JobCommentSchema),
defaultValues: { comment: "", attachments: [] },
});
const onSubmit = () => {};
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="d-flex">
<Avatar firstName={"A"} lastName={"D"} />
<div className="flex-grow-1">
<textarea
className="form-control"
rows={3}
placeholder="Write your comment..."
{...register("comment")}
></textarea>
{errors?.comment && (
<small className="danger-text">{errors?.comment?.message}</small>
)}
</div>
</div>
{/* <div className="flex-grow-1 ms-10 mt-2">
{files?.length > 0 && (
<Filelist files={} removeFile={removeFile} />
)}
</div> */}
<div className="d-flex justify-content-end gap-2 align-items-center text-end mt-3 ms-10 ms-md-0">
<div
onClick={() => document.getElementById("attachments").click()}
className="cursor-pointer"
style={{ whiteSpace: "nowrap" }}
>
<input
type="file"
accept=".pdf,.jpg,.jpeg,.png"
id="attachments"
multiple
className="d-none"
{...register("attachments")}
onChange={(e) => {
onFileChange(e);
e.target.value = "";
}}
/>
<i className="bx bx-sm bx-paperclip mb-1 me-1"></i>
Add Attachment
</div>
<button
className="btn btn-primary btn-sm px-1 py-1"
type="submit"
disabled={!watch("comment")?.trim() || isPending}
>
Submit
</button>
</div>
</form>
</div>
);
};
export default UpdateJobComment;

View File

@ -4,27 +4,18 @@ import { useServiceProject } from "../../hooks/useServiceProject";
import { formatUTCToLocalTime } from "../../utils/dateUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils";
import ManageServiceProject from "./ManageServiceProject"; import ManageServiceProject from "./ManageServiceProject";
import GlobalModel from "../common/GlobalModel"; import GlobalModel from "../common/GlobalModel";
import { SpinnerLoader } from "../common/Loader";
import ServiceBranch from "./ServiceProjectBranch/ServiceBranch";
import ServiceProfile from "./ServiceProfile";
const ServiceProjectProfile = () => { const ServiceProjectProfile = () => {
const { projectId } = useParams(); const { projectId } = useParams();
const [IsOpenModal, setIsOpenModal] = useState(false); const [IsOpenModal, setIsOpenModal] = useState(false);
const { data, isLoading, isError, error } = useServiceProject(projectId); const { data, isLoading, isError, error } = useServiceProject(projectId);
if (isLoading) if (isLoading) {
return ( return <div className="">Loadng.</div>;
<div className="py-8"> }
<SpinnerLoader />
</div>
);
return ( return (
<> <>
{IsOpenModal && ( {IsOpenModal && (
<GlobalModel <GlobalModel isOpen={IsOpenModal} closeModal={() => setIsOpenModal(false)}>
isOpen={IsOpenModal}
closeModal={() => setIsOpenModal(false)}
>
<ManageServiceProject <ManageServiceProject
serviceProjectId={projectId} serviceProjectId={projectId}
onClose={() => setIsOpenModal(false)} onClose={() => setIsOpenModal(false)}
@ -33,13 +24,98 @@ const ServiceProjectProfile = () => {
)} )}
<div className="row py-2"> <div className="row py-2">
<div className="col-md-6 col-lg-5 order-2 mb-6"> <div className="col-md-6 col-lg-4 order-2 mb-6">
<ServiceProfile data={data} setIsOpenModal={setIsOpenModal}/> <div className="card mb-4">
<div className="card-header text-start">
<h5 className="card-action-title mb-0 ps-1">
{" "}
<i className="fa fa-building rounded-circle text-primary"></i>
<span className="ms-2 fw-bold">Project Profile</span>
</h5>
</div>
<div className="card-body">
<ul className="list-unstyled my-3 ps-0 text-start">
<li className="d-flex mb-3">
<div className="d-flex align-items-start" style={{ minWidth: "150px" }}>
<i className="bx bx-cog"></i>
<span className="fw-medium mx-2 text-nowrap">Name:</span>
</div>
{/* Content section that wraps nicely */}
<div className="flex-grow-1 text-start text-wrap">
{data.name}
</div>
</li>
<li className="d-flex mb-3">
<div className="d-flex align-items-center" style={{ width: '150px' }}>
<i className="bx bx-fingerprint"></i>
<span className="fw-medium mx-2">Nick Name:</span>
</div>
<span>{data.shortName}</span>
</li>
<li className="d-flex mb-3">
<div className="d-flex align-items-center" style={{ width: '150px' }}>
<i className="bx bx-check"></i>
<span className="fw-medium mx-2">Assign Date:</span>
</div>
<span>
{data.assignedDate ? formatUTCToLocalTime(data.assignedDate) : "NA"}
</span>
</li>
<li className="d-flex mb-3">
<div className="d-flex align-items-center" style={{ width: '150px' }}>
<i className="bx bx-trophy"></i>
<span className="fw-medium mx-2">Status:</span>
</div>
<span>{data?.status.status}</span>
</li>
<li className="d-flex mb-3">
<div className="d-flex align-items-center" style={{ width: '150px' }}>
<i className="bx bx-user"></i>
<span className="fw-medium mx-2">Contact:</span>
</div>
<span>{data.contactName}</span>
</li>
<li className="d-flex mb-3">
{/* Label section with icon */}
<div className="d-flex align-items-start" style={{ minWidth: "150px" }}>
<i className="bx bx-flag mt-1"></i>
<span className="fw-medium mx-2 text-nowrap">Address:</span>
</div>
{/* Content section that wraps nicely */}
<div className="flex-grow-1 text-start text-wrap">
{data.address}
</div>
</li>
<li className="d-flex justify-content-center mt-4"> {/* Added mt-4 for some top margin */}
<a className="d-flex justify-content-center mt-4"> {/* Added mt-4 for some top margin */}
<button
type="button"
className="btn btn-sm btn-primary"
data-bs-toggle="modal"
data-bs-target="#edit-project-modal"
onClick={() => setIsOpenModal(true)}
>
Modify Details
</button>
</a>
</li>
</ul>
</div>
</div>
</div> </div>
<div className="col-md-6 col-lg-7 order-2 mb-6">
<ServiceBranch />
</div>
</div> </div>
</> </>
); );

View File

@ -50,10 +50,6 @@ export const defaultProjectValues = {
//#endregion //#endregion
//#region JobSchema //#region JobSchema
export const TagSchema = z.object({ export const TagSchema = z.object({
@ -74,8 +70,6 @@ export const jobSchema = z.object({
tags: z.array(TagSchema).optional().default([]), tags: z.array(TagSchema).optional().default([]),
statusId: z.string().optional().nullable(), statusId: z.string().optional().nullable(),
projectBranchId: z.string().optional().nullable(),
}); });
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
@ -115,52 +109,6 @@ export const defaultJobValue = {
startDate: null, startDate: null,
dueDate: null, dueDate: null,
tags: [], tags: [],
branchId: null,
}; };
//#endregion //#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

View File

@ -1,138 +0,0 @@
import React from "react";
const SkeletonLine = ({ height = 18, width = "100%", className = "" }) => (
<div
className={`skeleton ${className}`}
style={{
height,
width,
borderRadius: "4px",
}}
></div>
);
export const BranchDetailsSkeleton = () => {
return (
<div className="w-100">
<div className="d-flex mb-3">
<SkeletonLine height={22} width="280px" />
</div>
<div className="row mb-2">
<div className="col-4">
<SkeletonLine height={16} width="70%" />
</div>
<div className="col-8">
<SkeletonLine height={16} />
</div>
</div>
<div className="row mb-2">
<div className="col-4">
<SkeletonLine height={16} width="70%" />
</div>
<div className="col-8">
<SkeletonLine height={16} />
</div>
</div>
<div className="row mb-2">
<div className="col-4">
<SkeletonLine height={16} width="70%" />
</div>
<div className="col-8">
<SkeletonLine height={16} />
</div>
</div>
<div className="row mb-2">
<div className="col-4">
<SkeletonLine height={16} width="70%" />
</div>
<div className="col-8">
<SkeletonLine height={16} width="90%" />
</div>
</div>
<div className="row mb-2">
<div className="col-4">
<SkeletonLine height={16} width="70%" />
</div>
<div className="col-8 d-flex gap-2 align-items-center">
<SkeletonLine height={16} width="60%" />
<SkeletonLine height={16} width="20px" />
</div>
</div>
</div>
);
};
export const JobDetailsSkeleton = () => {
return (
<div className="row text-start">
<div className="col-12">
{/* Title */}
<SkeletonLine height={24} width="50%" />
{/* Job ID + Status */}
<div className="d-flex justify-content-between align-items-start flex-wrap mb-3 mt-2">
<SkeletonLine height={18} width="30%" />
<div className="d-flex flex-row gap-2">
<SkeletonLine height={22} width="70px" />
<SkeletonLine height={22} width="22px" />
</div>
</div>
{/* Description */}
<SkeletonLine height={40} width="100%" />
{/* Created Date */}
<div className="d-flex my-3">
<SkeletonLine height={16} width="40%" />
</div>
{/* Start / Due Date */}
<div className="d-flex justify-content-between mb-4">
<SkeletonLine height={16} width="50%" />
<SkeletonLine height={22} width="70px" />
</div>
{/* Branch Name */}
<div className="d-flex flex-row gap-3 my-2">
<SkeletonLine height={16} width="30%" />
<SkeletonLine height={16} width="40%" />
</div>
{/* Created By */}
<div className="row align-items-center my-3">
<div className="col-12 col-md-auto mb-2">
<SkeletonLine height={16} width="80px" />
</div>
<div className="col d-flex align-items-center gap-2">
<SkeletonLine height={30} width="30px" /> {/* Avatar */}
<SkeletonLine height={16} width="40%" />
</div>
</div>
{/* Assigned To */}
<div className="row mt-2">
<div className="col-12 col-md-auto mb-2">
<SkeletonLine height={16} width="90px" />
</div>
</div>
{/* Tabs */}
<div className="mt-4">
<div className="d-flex gap-3 mb-3">
<SkeletonLine height={35} width="80px" />
<SkeletonLine height={35} width="80px" />
<SkeletonLine height={35} width="80px" />
</div>
<SkeletonLine height={150} width="100%" />
</div>
</div>
</div>
);
};

View File

@ -26,6 +26,13 @@ const ServiceProjectCard = ({ project, isCore = true }) => {
const ManageProject = useHasUserPermission(MANAGE_PROJECT); const ManageProject = useHasUserPermission(MANAGE_PROJECT);
const { setMangeProject, setManageServiceProject } = useProjectContext(); const { setMangeProject, setManageServiceProject } = useProjectContext();
const getProgress = (planned, completed) => {
return (completed * 100) / planned + "%";
};
const getProgressInNumber = (planned, completed) => {
return (completed * 100) / planned;
};
const handleClose = () => setShowModal(false); const handleClose = () => setShowModal(false);
const handleViewProject = () => { const handleViewProject = () => {
@ -36,6 +43,10 @@ const ServiceProjectCard = ({ project, isCore = true }) => {
navigate(`/service-projects/${project.id}`); navigate(`/service-projects/${project.id}`);
} }
}; };
const handleViewActivities = () => {
dispatch(setProjectId(project.id));
navigate(`/activities/records?project=${project.id}`);
};
const handleManage = () => { const handleManage = () => {
if (isCore) { if (isCore) {
setMangeProject({ setMangeProject({
@ -57,8 +68,6 @@ const ServiceProjectCard = ({ project, isCore = true }) => {
DeleteProject(projectId, false); DeleteProject(projectId, false);
}; };
return ( return (
<> <>
<ConfirmModal <ConfirmModal
@ -89,7 +98,7 @@ const ServiceProjectCard = ({ project, isCore = true }) => {
> >
{project?.shortName ? project?.shortName : project?.name} {project?.shortName ? project?.shortName : project?.name}
</h5> </h5>
<div className="client-info text-body text-start"> <div className="client-info text-body">
<span>{project?.shortName ? project?.name : ""}</span> <span>{project?.shortName ? project?.name : ""}</span>
</div> </div>
</div> </div>
@ -129,6 +138,14 @@ const ServiceProjectCard = ({ project, isCore = true }) => {
<span className="align-left">Modify</span> <span className="align-left">Modify</span>
</a> </a>
</li> </li>
{isCore && (
<li onClick={handleViewActivities}>
<a className="dropdown-item">
<i className="bx bx-task me-2"></i>
<span className="align-left">Activities</span>
</a>
</li>
)}
{!isCore && ( {!isCore && (
<li <li
onClick={() => onClick={() =>

View File

@ -1,209 +0,0 @@
import React, { useState } from "react";
import { MANAGE_PROJECT, PROJECT_STATUS } from "../../../utils/constants";
import { useProjects } from "../../../hooks/useProjects";
import { formatNumber, formatUTCToLocalTime } from "../../../utils/dateUtils";
import ProgressBar from "../../common/ProgressBar";
import {
getProjectStatusColor,
getProjectStatusName,
} from "../../../utils/projectStatus";
import { useDispatch } from "react-redux";
import { setProjectId } from "../../../slices/localVariablesSlice";
import { useNavigate } from "react-router-dom";
import { useHasUserPermission } from "../../../hooks/useHasUserPermission";
import { useProjectContext } from "../../../pages/project/ProjectPage";
import usePagination from "../../../hooks/usePagination";
import Pagination from "../../common/Pagination";
const ServiceProjectList = ({
data,
currentPage,
totalPages,
paginate,
isCore = true,
}) => {
const dispatch = useDispatch();
const navigate = useNavigate();
const { setMangeProject, setManageServiceProject } = useProjectContext();
const handleClose = () => setShowModal(false);
// check Permissions
const canManageProject = useHasUserPermission(MANAGE_PROJECT);
const projectColumns = [
{
key: "projectName",
label: "Project Name",
className: "text-start py-3",
getValue: (p) => (
<div
className="text-primary cursor-pointer fw-bold py-3"
onClick={() => {
dispatch(setProjectId(p.id));
navigate(`/service-projects/${p.id}`);
}}
>
{p.shortName ? `${p.name} (${p.shortName})` : p.name}
</div>
),
},
{
key: "client.contactPerson",
label: "Contact Person",
className: "text-start small",
getValue: (p) => p.client?.contactPerson || "N/A",
},
{
key: "assignedDate",
label: "Assign Date",
className: "text-center small",
getValue: (p) => formatUTCToLocalTime(p.assignedDate),
},
{
key: "status",
label: "Status",
className: "text-center small",
getValue: (p) => (
<span className={`badge ${getProjectStatusColor(p.status?.id)}`}>
{p.status?.status}
</span>
),
},
];
const handleViewProject = (p) => {
if (isCore) {
dispatch(setProjectId(p.id));
navigate(`/projects/details`);
} else {
navigate(`/service-projects/${p.id}`);
}
};
const handleManage = (p) => {
if (isCore) {
setMangeProject({
isOpen: true,
Project: p.id,
});
} else {
setManageServiceProject({
isOpen: true,
project: p.id,
});
}
};
return (
<div>
<div className="card page-min-h py-4 px-6 shadow-sm">
<div className="table-responsive text-nowrap page-min-h">
<table className="table table-hover align-middle m-0">
<thead className="border-bottom ">
<tr>
{projectColumns.map((col) => (
<th
key={col.key}
colSpan={col.colSpan}
className={`${col.className} table_header_border`}
>
{col.label}
</th>
))}
<th className="text-center py-3">Action</th>
</tr>
</thead>
<tbody>
{data?.length > 0 ? (
data.map((project) => (
<tr key={project.id}>
{projectColumns.map((col) => (
<td
key={col.key}
colSpan={col.colSpan}
className={`${col.className} py-5`}
style={{ paddingTop: "20px", paddingBottom: "20px" }}
>
{col.getValue
? col.getValue(project)
: project[col.key] || "N/A"}
</td>
))}
<td
className={`mx-2 ${
canManageProject ? "d-sm-table-cell" : "d-none"
}`}
>
<div className="dropdown z-2">
<button
type="button"
className="btn btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i
className="bx bx-dots-vertical-rounded bx-sm text-muted"
data-bs-toggle="tooltip"
data-bs-offset="0,8"
data-bs-placement="top"
data-bs-custom-class="tooltip-dark"
title="More Action"
></i>
</button>
<ul className="dropdown-menu dropdown-menu-end">
<li>
<a
aria-label="click to View details"
className="dropdown-item"
onClick={() => handleViewProject(project)}
>
<i className="bx bx-detail me-2"></i>
<span className="align-left">View details</span>
</a>
</li>
<li>
<a
className="dropdown-item"
onClick={() => handleManage(project)}
>
<i className="bx bx-pencil me-2"></i>
<span className="align-left">Modify</span>
</a>
</li>
</ul>
</div>
</td>
</tr>
))
) : (
<tr
className="no-hover"
style={{
pointerEvents: "none",
backgroundColor: "transparent",
}}
>
<td
colSpan={projectColumns.length + 1}
className="text-center align-middle"
style={{ height: "300px", borderBottom: "none" }}
>
No Service projects available
</td>
</tr>
)}
</tbody>
</table>
</div>
<Pagination
currentPage={currentPage}
totalPages={totalPages}
paginate={paginate}
/>
</div>
</div>
);
};
export default ServiceProjectList;

View File

@ -162,7 +162,7 @@ const ServiceProjectTeamAllocation = () => {
</div> </div>
<div className="col-12 d-flex flex-row gap-2 flex-wrap"> <div className="col-12 d-flex flex-row gap-2 flex-wrap">
{selectedEmployees.map((e) => ( {selectedEmployees.map((e) => (
<EmployeeChip key={`${e.id}-emp`} handleRemove={handleRemove} employee={e} /> <EmployeeChip handleRemove={handleRemove} employee={e} />
))} ))}
</div> </div>
</> </>

View File

@ -104,7 +104,8 @@ const SubScriptionHistory = ({ tenantId }) => {
</button> </button>
<button <button
className="dropdown-item py-1" className="dropdown-item py-1"
onClick={() =>{} onClick={() =>
console.log("Download clicked for", item.id)
} }
> >
<i className="bx bx-cloud-download bx-sm"></i> Download <i className="bx bx-cloud-download bx-sm"></i> Download

View File

@ -93,9 +93,11 @@ const TenantForm = () => {
}; };
const onSubmitTenant = (data) => { const onSubmitTenant = (data) => {
console.log("Tenant Data:", data);
}; };
const onSubmitSubScription = (data) => { const onSubmitSubScription = (data) => {
console.log("Subscription Data:", data);
}; };
const newTenantConfig = [ const newTenantConfig = [

View File

@ -27,7 +27,7 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => {
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
const searchDebounce = useDebounce(searchString, 500); const searchDebounce = useDebounce(searchString, 500);
const { data, isLoading, isError, error } = useCollections( const { data, isLoading, isError, error } = useCollections(
selectedProject, selectedProject,
searchDebounce, searchDebounce,
@ -40,6 +40,7 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => {
); );
const { setProcessedPayment, setAddPayment, setViewCollection } = const { setProcessedPayment, setAddPayment, setViewCollection } =
useCollectionContext(); useCollectionContext();
const paginate = (page) => { const paginate = (page) => {
if (page >= 1 && page <= (data?.totalPages ?? 1)) { if (page >= 1 && page <= (data?.totalPages ?? 1)) {
setCurrentPage(page); setCurrentPage(page);
@ -112,16 +113,6 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => {
), ),
align: "text-center", align: "text-center",
}, },
{
key: "status",
label: "Status",
getValue: (col) => (
<span className={`badge bg-label-${col?.isActive ? "primary" : "danger"}`}>
{col?.isActive ? "Active" : "Inactive"}
</span>
),
align: "text-center",
},
{ {
key: "amount", key: "amount",
label: "Total Amount", label: "Total Amount",
@ -138,7 +129,6 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => {
), ),
align: "text-end", align: "text-end",
}, },
{ {
key: "balance", key: "balance",
label: "Balance", label: "Balance",

View File

@ -25,6 +25,7 @@ const ViewCollection = ({ onClose }) => {
if (isLoading) return <CollectionDetailsSkeleton />; if (isLoading) return <CollectionDetailsSkeleton />;
if (isError) return <div>{error.message}</div>; if (isError) return <div>{error.message}</div>;
return ( return (
<div className="container p-3"> <div className="container p-3">
<p className="fs-5 fw-semibold">Collection Details</p> <p className="fs-5 fw-semibold">Collection Details</p>
@ -42,8 +43,9 @@ const ViewCollection = ({ onClose }) => {
<div> <div>
{" "} {" "}
<span <span
className={`badge bg-label-${data?.isActive ? "primary" : "danger" className={`badge bg-label-${
}`} data?.isActive ? "primary" : "danger"
}`}
> >
{data?.isActive ? "Active" : "Inactive"} {data?.isActive ? "Active" : "Inactive"}
</span> </span>
@ -212,8 +214,9 @@ const ViewCollection = ({ onClose }) => {
<ul className="nav nav-tabs" role="tablist"> <ul className="nav nav-tabs" role="tablist">
<li className="nav-item"> <li className="nav-item">
<button <button
className={`nav-link ${activeTab === "payments" ? "active" : "" className={`nav-link ${
}`} activeTab === "payments" ? "active" : ""
}`}
onClick={() => setActiveTab("payments")} onClick={() => setActiveTab("payments")}
type="button" type="button"
> >
@ -222,8 +225,9 @@ const ViewCollection = ({ onClose }) => {
</li> </li>
<li className="nav-item"> <li className="nav-item">
<button <button
className={`nav-link ${activeTab === "details" ? "active" : "" className={`nav-link ${
}`} activeTab === "details" ? "active" : ""
}`}
onClick={() => setActiveTab("details")} onClick={() => setActiveTab("details")}
type="button" type="button"
> >

View File

@ -1,44 +1,45 @@
import React from "react"; import React from 'react'
export const EmployeeChip = ({ handleRemove, employee }) => { export const EmployeeChip = ({handleRemove,employee}) => {
return ( return(
<span <span
key={employee?.id} key={employee?.id}
className="tagify__tag d-inline-flex align-items-center me-1 mb-1" className="tagify__tag d-inline-flex align-items-center me-1 mb-1"
role="listitem" role="listitem"
> >
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
{employee?.photo ? ( {employee?.photo ? (
<span className="tagify__tag__avatar-wrap me-1"> <span className="tagify__tag__avatar-wrap me-1">
<img <img
src={employee?.avataremployeerl || "/defaemployeelt-avatar.png"} src={employee?.avataremployeerl || "/defaemployeelt-avatar.png"}
alt={`${employee?.firstName || ""} ${employee?.lastName || ""}`} alt={`${employee?.firstName || ""} ${employee?.lastName || ""}`}
style={{ width: 12, height: 12, objectFit: "cover" }} style={{ width: 12, height: 12, objectFit: "cover" }}
/> />
</span> </span>
) : ( ) : (
<div className="avatar avatar-xs me-2"> <div className="avatar avatar-xs me-2">
<span className="avatar-initial roemployeended-circle bg-label-secondary"> <span className="avatar-initial roemployeended-circle bg-label-secondary">
{employee?.firstName?.[0] || ""} {employee?.firstName?.[0] || ""}
{employee?.lastName?.[0] || ""} {employee?.lastName?.[0] || ""}
</span> </span>
</div> </div>
)} )}
<div className="d-flex flex-colemployeemn"> <div className="d-flex flex-colemployeemn">
<span className="tagify__tag-text"> <span className="tagify__tag-text">
{employee?.firstName} {employee?.lastName} {employee?.firstName} {employee?.lastName}
</span> </span>
</div> </div>
</div> </div>
<bemployeetton
type="bemployeetton"
className="tagify__tag__removeBtn border-none"
onClick={() => handleRemove(employee?.id)}
aria-label={`Remove ${employee?.firstName}`}
title="Remove"
/>
</span>
)
}
<bemployeetton
type="bemployeetton"
className="tagify__tag__removeBtn border-none"
onClick={() => handleRemove(employee?.id)}
aria-label={`Remove ${employee?.firstName}`}
title="Remove"
/>
</span>
);
};

View File

@ -17,13 +17,9 @@ const ConfirmModal = ({
case "delete": case "delete":
return <i className="bx bx-x-circle text-danger" style={{ fontSize: "60px" }}></i>; return <i className="bx bx-x-circle text-danger" style={{ fontSize: "60px" }}></i>;
case "success": case "success":
return <i className="bx bx-archive-in text-warning" style={{ fontSize: "60px" }}></i>; return <i className="bx bx-check-circle text-success" style={{ fontSize: "60px" }}></i>;
case "archive": case "warning":
return <i className="bx bx-archive-in text-warning" style={{ fontSize: "60px" }}></i>; return <i className="bx bx-error-circle text-warning" style={{ fontSize: "60px" }}></i>;
case "Un-archive":
return <i className="bx bx-archive-out text-warning" style={{ fontSize: "60px" }}></i>;
case "undo":
return <i className="bx bx-undo text-info" style={{ fontSize: "50px" }}></i>;
default: default:
return null; return null;
} }

View File

@ -7,7 +7,6 @@ import Avatar from "./Avatar";
const EmployeeSearchInput = ({ const EmployeeSearchInput = ({
control, control,
name, name,
size = "sm",
projectId, projectId,
placeholder, placeholder,
forAll, forAll,
@ -47,7 +46,7 @@ const EmployeeSearchInput = ({
<input <input
type="text" type="text"
ref={ref} ref={ref}
className={`form-control form-control-sm-${size}`} className={`form-control form-control-sm`}
placeholder={placeholder} placeholder={placeholder}
value={search} value={search}
onChange={(e) => { onChange={(e) => {

View File

@ -2,18 +2,15 @@ import React, { useEffect, useRef, useState } from "react";
import Label from "../Label"; import Label from "../Label";
const InputSuggessionField = ({ const InputSuggessionField = ({
suggesstionList = [], organizationList = [],
value, value,
onChange, onChange,
error, error,
disabled = false, disabled=false
label = "Label",
placeholder = "Please Enter",
required = false,
isLoading = false,
}) => { }) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const dropdownRef = useRef(null); const dropdownRef = useRef(null);
useEffect(() => { useEffect(() => {
const handleClickOutside = (event) => { const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
@ -24,12 +21,12 @@ const InputSuggessionField = ({
return () => document.removeEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside);
}, []); }, []);
const selectedOption = suggesstionList.find((opt) => opt === value); const selectedOption = options.find((opt) => opt[valueKey] === value);
const displayText = selectedOption ? selectedOption : placeholder; const displayText = selectedOption ? selectedOption[labelKey] : placeholder;
const handleSelect = (option) => { const handleSelect = (option) => {
onChange(option); onChange(option[valueKey]);
setOpen(false); setOpen(false);
}; };
@ -60,7 +57,7 @@ const InputSuggessionField = ({
{open && !isLoading && ( {open && !isLoading && (
<ul <ul
className="dropdown-menu w-100 shadow-sm show animate__fadeIn h-64 overflow-auto rounded overflow-x-hidden" className="dropdown-menu w-100 shadow-sm show animate__fadeIn"
style={{ style={{
position: "absolute", position: "absolute",
top: "100%", top: "100%",
@ -71,14 +68,16 @@ const InputSuggessionField = ({
overflow: "hidden", overflow: "hidden",
}} }}
> >
{suggesstionList.map((option, i) => ( {options.map((option, i) => (
<li key={i}> <li key={i}>
<button <button
type="button" type="button"
className={`dropdown-item ${option === value ? "active" : ""}`} className={`dropdown-item ${
option[valueKey] === value ? "active" : ""
}`}
onClick={() => handleSelect(option)} onClick={() => handleSelect(option)}
> >
{option} {option[labelKey]}
</button> </button>
</li> </li>
))} ))}

View File

@ -3,7 +3,6 @@ import Label from "../Label";
import { useDebounce } from "../../../utils/appUtils"; import { useDebounce } from "../../../utils/appUtils";
import { useEmployeesName } from "../../../hooks/useEmployees"; import { useEmployeesName } from "../../../hooks/useEmployees";
import { useProjectBothName } from "../../../hooks/useProjects"; import { useProjectBothName } from "../../../hooks/useProjects";
import EmployeeRepository from "../../../repositories/EmployeeRepository";
const SelectEmployeeServerSide = ({ const SelectEmployeeServerSide = ({
label = "Select", label = "Select",
@ -19,7 +18,6 @@ const SelectEmployeeServerSide = ({
}) => { }) => {
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const debounce = useDebounce(searchText, 300); const debounce = useDebounce(searchText, 300);
const [forcedSelected, setForcedSelected] = useState(null);
const { data, isLoading } = useEmployeesName( const { data, isLoading } = useEmployeesName(
projectId, projectId,
@ -36,361 +34,6 @@ const SelectEmployeeServerSide = ({
return `${emp.firstName || ""} ${emp.lastName || ""}`.trim(); return `${emp.firstName || ""} ${emp.lastName || ""}`.trim();
}; };
let selectedSingle = null;
if (!isMultiple) {
if (isFullObject && value) selectedSingle = value;
else if (!isFullObject && value)
selectedSingle =
options.find((o) => o[valueKey] === value) || forcedSelected;
}
let selectedList = [];
if (isMultiple && Array.isArray(value)) {
if (isFullObject) selectedList = value;
else {
selectedList = options.filter((opt) => value.includes(opt[valueKey]));
}
}
const displayText = !isMultiple
? getDisplayName(selectedSingle) || placeholder
: selectedList.length > 0
? selectedList.map((e) => getDisplayName(e)).join(", ")
: placeholder;
useEffect(() => {
const handleClickOutside = (e) => {
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
setOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
const handleSelect = (option) => {
if (!isMultiple) {
if (isFullObject) onChange(option);
else onChange(option[valueKey]);
setOpen(false);
} else {
let updated = [];
const exists = selectedList.some((e) => e[valueKey] === option[valueKey]);
updated = exists
? selectedList.filter((e) => e[valueKey] !== option[valueKey])
: [...selectedList, option];
if (isFullObject) onChange(updated);
else onChange(updated.map((x) => x[valueKey]));
}
};
useEffect(() => {
if (!value || isFullObject) return;
const exists = options.some((o) => o[valueKey] === value);
if (exists) return;
const loadSingleEmployee = async () => {
try {
const emp = await EmployeeRepository.getEmployeeName(
null,
null,
true,
value
);
setForcedSelected(emp.data[0]);
} catch (err) {
console.error("Failed to load selected employee", err);
}
};
loadSingleEmployee();
}, [value, options, isFullObject, valueKey]);
return (
<div className="mb-3 position-relative" ref={dropdownRef}>
{label && (
<Label className="form-label" required={required}>
{label}
</Label>
)}
{/* MAIN BUTTON */}
<button
type="button"
className={`select2-icons form-select d-flex align-items-center justify-content-between ${
open ? "show" : ""
}`}
onClick={() => setOpen((prev) => !prev)}
>
<span className={`text-truncate ${!displayText ? "text-muted" : ""}`}>
{displayText}
</span>
</button>
{open && (
<ul
className="dropdown-menu w-100 shadow-sm show animate__fadeIn h-64 overflow-auto rounded"
style={{
position: "absolute",
top: "100%",
left: 0,
zIndex: 1050,
marginTop: "4px",
borderRadius: "0.375rem",
padding: 0,
}}
>
<li className="p-1 sticky-top bg-white" style={{ zIndex: 10 }}>
<input
type="search"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
className="form-control form-control-sm"
placeholder="Search..."
/>
</li>
{isLoading && (
<li className="dropdown-item text-muted text-center">Loading...</li>
)}
{!isLoading && options.length === 0 && (
<li className="dropdown-item text-muted text-center">
No results found
</li>
)}
{!isLoading &&
options.map((option) => {
const isActive = isMultiple
? selectedList.some((x) => x[valueKey] === option[valueKey])
: selectedSingle &&
selectedSingle[valueKey] === option[valueKey];
return (
<li key={option[valueKey]} className="px-1 rounded">
<button
type="button"
className={`dropdown-item rounded ${
isActive ? "active" : ""
}`}
onClick={() => handleSelect(option)}
>
{getDisplayName(option)}
</button>
</li>
);
})}
</ul>
)}
</div>
);
};
export default SelectEmployeeServerSide;
export const SelectProjectField = ({
label = "Select",
placeholder = "Select Project",
required = false,
value = null,
onChange,
valueKey = "id",
isFullObject = false,
isMultiple = false,
isAllProject = false,
disabled
}) => {
const [searchText, setSearchText] = useState("");
const debounce = useDebounce(searchText, 300);
const { data, isLoading } = useProjectBothName(debounce);
const options = data ?? [];
const [open, setOpen] = useState(false);
const dropdownRef = useRef(null);
const getDisplayName = (project) => {
if (!project) return "";
return `${project.name || ""}`.trim();
};
let selectedSingle = null;
if (!isMultiple) {
if (isFullObject && value) selectedSingle = value;
else if (!isFullObject && value)
selectedSingle = options.find((o) => o[valueKey] === value);
}
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);
}, []);
/** -----------------------------
* 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 (
<div className="mb-3 position-relative" ref={dropdownRef}>
{label && (
<Label className="form-label" required={required}>
{label}
</Label>
)}
{/* MAIN BUTTON */}
<button
type="button"
className={`select2-icons form-select d-flex align-items-center justify-content-between ${
open ? "show" : ""
}`}
onClick={() => setOpen((prev) => !prev)}
disabled={disabled}
>
<span className={`text-truncate ${!displayText ? "text-muted" : ""}`}>
{displayText}
</span>
</button>
{/* DROPDOWN */}
{open && (
<ul
className="dropdown-menu w-100 shadow-sm show animate__fadeIn h-64 overflow-auto rounded"
style={{
position: "absolute",
top: "100%",
left: 0,
zIndex: 1050,
marginTop: "2px",
borderRadius: "0.375rem",
overflow: "hidden",
}}
>
<div className="p-1">
<input
type="search"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
className="form-control form-control-sm"
placeholder="Search..."
/>
</div>
{isLoading && (
<li className="dropdown-item text-muted text-center">Loading...</li>
)}
{!isLoading && options.length === 0 && (
<li className="dropdown-item text-muted text-center">
No results found
</li>
)}
{!isLoading &&
options.map((option) => {
const isActive = isMultiple
? selectedList.some((x) => x[valueKey] === option[valueKey])
: selectedSingle &&
selectedSingle[valueKey] === option[valueKey];
return (
<li key={option[valueKey]} className="px-1 rounded w-full">
<button
type="button"
className={`dropdown-item rounded d-block text-truncate w-100 ${
isActive ? "active" : ""
}`}
onClick={() => handleSelect(option)}
>
{getDisplayName(option)}
</button>
</li>
);
})}
</ul>
)}
</div>
);
};
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) * SELECTED OPTION (SINGLE)
* ----------------------------- */ * ----------------------------- */
@ -406,6 +49,7 @@ export const SelectFieldSearch = ({
* SELECTED OPTION (MULTIPLE) * SELECTED OPTION (MULTIPLE)
* ----------------------------- */ * ----------------------------- */
let selectedList = []; let selectedList = [];
if (isMultiple && Array.isArray(value)) { if (isMultiple && Array.isArray(value)) {
if (isFullObject) selectedList = value; if (isFullObject) selectedList = value;
else { else {
@ -434,36 +78,6 @@ export const SelectFieldSearch = ({
return () => document.removeEventListener("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 * HANDLE SELECT
* ----------------------------- */ * ----------------------------- */
@ -502,10 +116,9 @@ export const SelectFieldSearch = ({
{/* MAIN BUTTON */} {/* MAIN BUTTON */}
<button <button
type="button" type="button"
className={`select2-icons form-select d-flex align-items-center justify-content-between ${ className={`select2-icons form-select d-flex align-items-center justify-content-between ${
open ? "show" : "" open ? "show" : ""
}`} }`}
disabled={disabled}
onClick={() => setOpen((prev) => !prev)} onClick={() => setOpen((prev) => !prev)}
> >
<span className={`text-truncate ${!displayText ? "text-muted" : ""}`}> <span className={`text-truncate ${!displayText ? "text-muted" : ""}`}>
@ -516,13 +129,190 @@ export const SelectFieldSearch = ({
{/* DROPDOWN */} {/* DROPDOWN */}
{open && ( {open && (
<ul <ul
className="dropdown-menu w-100 shadow-sm show animate__fadeIn h-64 overflow-auto rounded overflow-x-hidden" className="dropdown-menu w-100 shadow-sm show animate__fadeIn h-64 overflow-auto rounded"
style={{ style={{
position: "absolute", position: "absolute",
top: "100%", top: "100%",
left: 0, left: 0,
zIndex: 1050, zIndex: 1050,
marginTop: "2px", marginTop: "4px",
borderRadius: "0.375rem",
overflow: "hidden",
}}
>
<div className="p-1">
<input
type="search"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
className="form-control form-control-sm"
placeholder="Search..."
/>
</div>
{isLoading && (
<li className="dropdown-item text-muted text-center">Loading...</li>
)}
{!isLoading && options.length === 0 && (
<li className="dropdown-item text-muted text-center">
No results found
</li>
)}
{!isLoading &&
options.map((option) => {
const isActive = isMultiple
? selectedList.some((x) => x[valueKey] === option[valueKey])
: selectedSingle &&
selectedSingle[valueKey] === option[valueKey];
return (
<li key={option[valueKey]}>
<button
type="button"
className={`dropdown-item ${isActive ? "active" : ""}`}
onClick={() => handleSelect(option)}
>
{getDisplayName(option)}
</button>
</li>
);
})}
</ul>
)}
</div>
);
};
export default SelectEmployeeServerSide;
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 } = useProjectBothName(debounce);
const options = data ?? [];
const [open, setOpen] = useState(false);
const dropdownRef = useRef(null);
const getDisplayName = (project) => {
if (!project) return "";
return `${project.name || ""}`.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);
}, []);
/** -----------------------------
* 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 (
<div className="mb-3 position-relative" ref={dropdownRef}>
{label && (
<Label className="form-label" required={required}>
{label}
</Label>
)}
{/* MAIN BUTTON */}
<button
type="button"
className={`select2-icons form-select d-flex align-items-center justify-content-between ${
open ? "show" : ""
}`}
onClick={() => setOpen((prev) => !prev)}
>
<span className={`text-truncate ${!displayText ? "text-muted" : ""}`}>
{displayText}
</span>
</button>
{/* DROPDOWN */}
{open && (
<ul
className="dropdown-menu w-100 shadow-sm show animate__fadeIn h-64 overflow-auto rounded"
style={{
position: "absolute",
top: "100%",
left: 0,
zIndex: 1050,
marginTop: "2px",
borderRadius: "0.375rem", borderRadius: "0.375rem",
overflow: "hidden", overflow: "hidden",
}} }}
@ -534,7 +324,6 @@ export const SelectFieldSearch = ({
onChange={(e) => setSearchText(e.target.value)} onChange={(e) => setSearchText(e.target.value)}
className="form-control form-control-sm" className="form-control form-control-sm"
placeholder="Search..." placeholder="Search..."
disabled={disabled}
/> />
</div> </div>

View File

@ -28,6 +28,7 @@ const CommentEditor = () => {
const [value, setValue] = useState(""); const [value, setValue] = useState("");
const handleSubmit = () => { const handleSubmit = () => {
console.log("Comment:", value);
// Submit or handle content // Submit or handle content
}; };

View File

@ -1,4 +1,4 @@
import React, { useEffect, useRef } from "react"; import React, { useEffect, useMemo, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { import {
closePopup, closePopup,
@ -6,10 +6,6 @@ import {
togglePopup, togglePopup,
} from "../../slices/localVariablesSlice"; } from "../../slices/localVariablesSlice";
/**
* align: "auto" | "left" | "right"
* boundaryRef: optional ref to the drawer/container element to use as boundary
*/
const HoverPopup = ({ const HoverPopup = ({
id, id,
title, title,
@ -17,8 +13,6 @@ const HoverPopup = ({
children, children,
className = "", className = "",
Mode = "hover", Mode = "hover",
align = "auto",
boundaryRef = null,
}) => { }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const visible = useSelector((s) => s.localVariables.popups[id] || false); const visible = useSelector((s) => s.localVariables.popups[id] || false);
@ -29,9 +23,11 @@ const HoverPopup = ({
const handleMouseEnter = () => { const handleMouseEnter = () => {
if (Mode === "hover") dispatch(openPopup(id)); if (Mode === "hover") dispatch(openPopup(id));
}; };
const handleMouseLeave = () => { const handleMouseLeave = () => {
if (Mode === "hover") dispatch(closePopup(id)); if (Mode === "hover") dispatch(closePopup(id));
}; };
const handleClick = (e) => { const handleClick = (e) => {
if (Mode === "click") { if (Mode === "click") {
e.stopPropagation(); e.stopPropagation();
@ -39,159 +35,74 @@ const HoverPopup = ({
} }
}; };
// Close on outside click when using click mode
useEffect(() => { useEffect(() => {
if (Mode !== "click" || !visible) return; if (Mode !== "click" || !visible) return;
const handler = (e) => { const handleOutside = (e) => {
if ( if (
popupRef.current && !popupRef.current?.contains(e.target) &&
!popupRef.current.contains(e.target) && !triggerRef.current?.contains(e.target)
triggerRef.current &&
!triggerRef.current.contains(e.target)
) { ) {
dispatch(closePopup(id)); 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(() => { useEffect(() => {
if (!visible || !popupRef.current || !triggerRef.current) return; if (!visible || !popupRef.current) return;
// run in next frame so DOM/layout settles const popup = popupRef.current;
requestAnimationFrame(() => { const rect = popup.getBoundingClientRect();
const popup = popupRef.current;
// choose boundary: provided boundaryRef or nearest positioned parent (popup.parentElement) popup.style.left = "50%";
const boundaryEl = popup.style.right = "auto";
(boundaryRef && boundaryRef.current) || popup.parentElement; popup.style.transform = "translateX(-50%)";
if (!boundaryEl) return;
const boundaryRect = boundaryEl.getBoundingClientRect(); if (rect.right > window.innerWidth) {
const triggerRect = triggerRef.current.getBoundingClientRect(); popup.style.left = "auto";
popup.style.right = "0";
popup.style.transform = "none";
}
// reset styles first if (rect.left < 0) {
popup.style.left = ""; popup.style.left = "0";
popup.style.right = ""; popup.style.right = "auto";
popup.style.transform = ""; popup.style.transform = "none";
popup.style.top = ""; }
}, [visible]);
const popupRect = popup.getBoundingClientRect(); return (
const parentRect = boundaryRect; // alias <div className="d-inline-block position-relative">
// Convert trigger center to parent coordinates
const triggerCenterX =
triggerRect.left + triggerRect.width / 2 - parentRect.left;
// 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 (
<div
className="d-inline-block position-relative" // <-- ADD THIS !!
style={{
maxWidth: "calc(700px - 100px)",
width: "100%",
wordWrap: "break-word",
overflow: "visible", // also make sure popup isn't clipped
}}
>
<div
className="d-inline-block"
ref={triggerRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleClick}
style={{ cursor: "pointer" }}
>
{children}
</div>
{visible && (
<div <div
ref={popupRef} ref={triggerRef}
className={`hover-popup bg-white border rounded shadow-sm p-3 position-absolute mt-2 ${className}`} onMouseEnter={handleMouseEnter}
style={{ onMouseLeave={handleMouseLeave}
zIndex: 2000, onClick={handleClick}
top: "100%", style={{ cursor: "pointer" }}
width: "max-content",
minWidth: "120px",
}}
onClick={(e) => e.stopPropagation()}
> >
{title && <h6 className="fw-semibold mb-2">{title}</h6>} {children}
<div>{content}</div>
</div> </div>
)}
</div>
);
{visible && (
<div
ref={popupRef}
className={`bg-white border rounded shadow-sm p-3 w-max position-absolute top-100 mt-2 ${className}`}
style={{
zIndex: 2000,
left: "50%",
transform: "translateX(-50%)",
}}
onClick={(e) => e.stopPropagation()}
>
{title && <h6 className="fw-semibold mb-2">{title}</h6>}
<div>{content}</div>
</div>
)}
</div>
);
}; };
export default HoverPopup; export default HoverPopup;

View File

@ -5,13 +5,14 @@ const InputSuggestions = ({
value, value,
onChange, onChange,
error, error,
disabled = false, disabled=false
}) => { }) => {
const [filteredList, setFilteredList] = useState([]); const [filteredList, setFilteredList] = useState([]);
const [showSuggestions, setShowSuggestions] = useState(false); const [showSuggestions, setShowSuggestions] = useState(false);
const handleInputChange = (e) => { const handleInputChange = (e) => {
const val = e.target.value; const val = e.target.value;
onChange(val); onChange(val);
const matches = organizationList.filter((org) => const matches = organizationList.filter((org) =>
org.toLowerCase().includes(val.toLowerCase()) org.toLowerCase().includes(val.toLowerCase())
); );
@ -25,7 +26,7 @@ const InputSuggestions = ({
}; };
return ( return (
<div className="mb-3 position-relative"> <div className="position-relative">
<input <input
className="form-control form-control-sm" className="form-control form-control-sm"
value={value} value={value}
@ -38,19 +39,19 @@ const InputSuggestions = ({
/> />
{showSuggestions && filteredList.length > 0 && ( {showSuggestions && filteredList.length > 0 && (
<ul <ul
className="dropdown-menu w-100 shadow-sm show animate__fadeIn" className="list-group shadow-sm position-absolute w-100 bg-white border zindex-tooltip"
style={{ style={{
maxHeight: "180px", maxHeight: "180px",
overflowY: "auto", overflowY: "auto",
marginTop: "2px", marginTop: "2px",
zIndex: 1000, zIndex: 1000,
borderRadius: "0px", borderRadius:"0px"
}} }}
> >
{filteredList.map((org) => ( {filteredList.map((org) => (
<li <li
key={org} key={org}
className="ropdown-item" className="list-group-item list-group-item-action border-none "
style={{ style={{
cursor: "pointer", cursor: "pointer",
padding: "5px 12px", padding: "5px 12px",
@ -58,15 +59,17 @@ const InputSuggestions = ({
transition: "background-color 0.2s", transition: "background-color 0.2s",
}} }}
onMouseDown={() => handleSelectSuggestion(org)} onMouseDown={() => handleSelectSuggestion(org)}
className={`dropdown-item ${ onMouseEnter={(e) =>
org === value ? "active" : "" (e.currentTarget.style.backgroundColor = "#f8f9fa")
}`} }
onMouseLeave={(e) =>
(e.currentTarget.style.backgroundColor = "transparent")
}
> >
{org} {org}
</li> </li>
))} ))}
</ul> </ul>
)} )}
{error && <small className="danger-text">{error}</small>} {error && <small className="danger-text">{error}</small>}

View File

@ -2,6 +2,7 @@ import { useState, useEffect } from "react";
import GlobalRepository from "../repositories/GlobalRepository"; import GlobalRepository from "../repositories/GlobalRepository";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
export const useDashboard_Data = ({ days, FromDate, projectId }) => { export const useDashboard_Data = ({ days, FromDate, projectId }) => {
const [dashboard_data, setDashboard_Data] = useState([]); const [dashboard_data, setDashboard_Data] = useState([]);
const [isLineChartLoading, setLoading] = useState(false); const [isLineChartLoading, setLoading] = useState(false);
@ -17,13 +18,11 @@ export const useDashboard_Data = ({ days, FromDate, projectId }) => {
try { try {
const payload = { const payload = {
days, days,
FromDate: FromDate || "", FromDate: FromDate || '',
projectId: projectId || null, projectId: projectId || null,
}; };
const response = await GlobalRepository.getDashboardProgressionData( const response = await GlobalRepository.getDashboardProgressionData(payload);
payload
);
setDashboard_Data(response.data); setDashboard_Data(response.data);
} catch (err) { } catch (err) {
setError("Failed to fetch dashboard data."); setError("Failed to fetch dashboard data.");
@ -39,6 +38,123 @@ export const useDashboard_Data = ({ days, FromDate, projectId }) => {
return { dashboard_data, loading: isLineChartLoading, error }; 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) => { export const useAttendanceOverviewData = (projectId, days) => {
const [attendanceOverviewData, setAttendanceOverviewData] = useState([]); const [attendanceOverviewData, setAttendanceOverviewData] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -51,10 +167,7 @@ export const useAttendanceOverviewData = (projectId, days) => {
setError(""); setError("");
try { try {
const response = await GlobalRepository.getAttendanceOverview( const response = await GlobalRepository.getAttendanceOverview(projectId, days);
projectId,
days
);
setAttendanceOverviewData(response.data); setAttendanceOverviewData(response.data);
} catch (err) { } catch (err) {
setError("Failed to fetch attendance overview data."); setError("Failed to fetch attendance overview data.");
@ -69,6 +182,7 @@ export const useAttendanceOverviewData = (projectId, days) => {
return { attendanceOverviewData, loading, error }; return { attendanceOverviewData, loading, error };
}; };
// -------------------Query---------------------------- // -------------------Query----------------------------
// export const useDashboard_Data = (days, FromDate, projectId)=>{ // export const useDashboard_Data = (days, FromDate, projectId)=>{
@ -85,47 +199,39 @@ 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) => { export const useDashboard_AttendanceData = (date, projectId) => {
return useQuery({ return useQuery({
queryKey: ["dashboardAttendances", date, projectId], queryKey: ["dashboardAttendances", date, projectId],
queryFn: async () => { queryFn: async () => {
const resp = await await GlobalRepository.getDashboardAttendanceData(
date, const resp = await await GlobalRepository.getDashboardAttendanceData(date, projectId)
projectId
);
return resp.data; return resp.data;
}, }
}); })
}; }
export const useDashboardTeamsCardData = (projectId) => { export const useDashboardTeamsCardData = (projectId) => {
return useQuery({ return useQuery({
queryKey: ["dashboardTeams", projectId], queryKey: ["dashboardTeams", projectId],
queryFn: async () => { queryFn: async () => {
const resp = await GlobalRepository.getDashboardTeamsCardData(projectId);
const resp = await GlobalRepository.getDashboardTeamsCardData(projectId)
return resp.data; return resp.data;
}, }
}); })
}; }
export const useDashboardTasksCardData = (projectId) => { export const useDashboardTasksCardData = (projectId) => {
return useQuery({ return useQuery({
queryKey: ["dashboardTasks", projectId], queryKey: ["dashboardTasks", projectId],
queryFn: async () => { queryFn: async () => {
const resp = await GlobalRepository.getDashboardTasksCardData(projectId);
const resp = await GlobalRepository.getDashboardTasksCardData(projectId)
return resp.data; return resp.data;
}, }
}); })
}; }
// export const useAttendanceOverviewData = (projectId, days) => { // export const useAttendanceOverviewData = (projectId, days) => {
// return useQuery({ // return useQuery({
// queryKey:["dashboardAttendanceOverView",projectId], // queryKey:["dashboardAttendanceOverView",projectId],
@ -141,30 +247,29 @@ export const useDashboardProjectsCardData = () => {
return useQuery({ return useQuery({
queryKey: ["dashboardProjects"], queryKey: ["dashboardProjects"],
queryFn: async () => { queryFn: async () => {
const resp = await GlobalRepository.getDashboardProjectsCardData(); const resp = await GlobalRepository.getDashboardProjectsCardData();
return resp.data; return resp.data;
}, }
}); })
}; }
export const useExpenseAnalysis = (projectId, startDate, endDate) => { export const useExpenseAnalysis = (projectId, startDate, endDate) => {
const hasBothDates = !!startDate && !!endDate; const hasBothDates = !!startDate && !!endDate;
const noDatesSelected = !startDate && !endDate; const noDatesSelected = !startDate && !endDate;
const shouldFetch = noDatesSelected || hasBothDates; const shouldFetch =
noDatesSelected ||
hasBothDates;
return useQuery({ return useQuery({
queryKey: ["expenseAnalysis", projectId, startDate, endDate], queryKey: ["expenseAnalysis", projectId, startDate, endDate],
queryFn: async () => { queryFn: async () => {
const resp = await GlobalRepository.getExpenseData( const resp = await GlobalRepository.getExpenseData(projectId, startDate, endDate);
projectId,
startDate,
endDate
);
return resp.data; return resp.data;
}, },
enabled: shouldFetch, enabled: shouldFetch,
refetchOnWindowFocus: true, // refetch when you come back refetchOnWindowFocus: true, // refetch when you come back
refetchOnMount: "always", // always refetch on remount refetchOnMount: "always", // always refetch on remount
staleTime: 0, staleTime: 0,
}); });
}; };
@ -175,20 +280,17 @@ export const useExpenseStatus = (projectId) => {
queryFn: async () => { queryFn: async () => {
const resp = await GlobalRepository.getExpenseStatus(projectId); const resp = await GlobalRepository.getExpenseStatus(projectId);
return resp.data; return resp.data;
}, }
}); })
}; }
export const useExpenseDataByProject = (projectId, categoryId, months) => { export const useExpenseDataByProject = (projectId, categoryId, months) => {
return useQuery({ return useQuery({
queryKey: ["expenseByProject", projectId, categoryId, months], queryKey: ["expenseByProject", projectId, categoryId, months],
queryFn: async () => { queryFn: async () => {
const resp = await GlobalRepository.getExpenseDataByProject( const resp = await GlobalRepository.getExpenseDataByProject(projectId, categoryId, months);
projectId,
categoryId,
months
);
return resp.data; return resp.data;
}, },
}); });
}; };

View File

@ -231,7 +231,7 @@ export const useEmployeesName = (projectId, search, allEmployee) => {
queryFn: async () => queryFn: async () =>
await EmployeeRepository.getEmployeeName(projectId, search, allEmployee), await EmployeeRepository.getEmployeeName(projectId, search, allEmployee),
staleTime: 5 * 60 * 1000, staleTime: 5 * 60 * 1000, // Optional: cache for 5 minutes
}); });
}; };

View File

@ -438,15 +438,6 @@ export const useExpenseTransactions = (employeeId)=>{
keepPreviousData:true, keepPreviousData:true,
}) })
} }
export const useExpenseAllTransactionsList = (searchString) => {
return useQuery({
queryKey: ["transaction", searchString],
queryFn: async () => {
const resp = await ExpenseRepository.getAllTranctionList(searchString);
return resp.data;
},
});
};
//#endregion //#endregion
// ---------------------------Put Post Recurring Expense--------------------------------------- // ---------------------------Put Post Recurring Expense---------------------------------------

View File

@ -20,15 +20,14 @@ export const useCurrentService = () => {
// ------------------------------Query------------------- // ------------------------------Query-------------------
export const useProjects = (pageSize, pageNumber,searchString) => { export const useProjects = (pageSize, pageNumber) => {
const loggedUser = useSelector((store) => store.globalVariables.loginUser); const loggedUser = useSelector((store) => store.globalVariables.loginUser);
return useQuery({ return useQuery({
queryKey: ["ProjectsList", pageSize, pageNumber,searchString], queryKey: ["ProjectsList", pageSize, pageNumber],
queryFn: async () => { queryFn: async () => {
const response = await ProjectRepository.getProjectList( const response = await ProjectRepository.getProjectList(
pageSize, pageSize,
pageNumber, pageNumber
searchString,
); );
return response?.data; return response?.data;
}, },
@ -412,6 +411,7 @@ export const useUpdateProject = (onSuccessCallback) => {
}, },
onError: (error) => { onError: (error) => {
console.log(error);
showToast(error?.message || "Error while updating project", "error"); showToast(error?.message || "Error while updating project", "error");
}, },
}); });

View File

@ -8,14 +8,13 @@ import { ServiceProjectRepository } from "../repositories/ServiceProjectReposito
import showToast from "../services/toastService"; import showToast from "../services/toastService";
//#region Service Project //#region Service Project
export const useServiceProjects = (pageSize, pageNumber, searchString) => { export const useServiceProjects = (pageSize, pageNumber) => {
return useQuery({ return useQuery({
queryKey: ["serviceProjects", pageSize, pageNumber, searchString], queryKey: ["serviceProjects", pageSize, pageNumber],
queryFn: async () => { queryFn: async () => {
const response = await ServiceProjectRepository.GetServiceProjects( const response = await ServiceProjectRepository.GetServiceProjects(
pageSize, pageSize,
pageNumber, pageNumber
searchString
); );
return response.data; return response.data;
}, },
@ -155,25 +154,16 @@ export const useServiceProjectJobs = (
pageSize, pageSize,
pageNumber, pageNumber,
isActive = true, isActive = true,
project, project
isArchive
) => { ) => {
return useQuery({ return useQuery({
queryKey: [ queryKey: ["serviceProjectJobs", pageSize, pageNumber, isActive, project],
"serviceProjectJobs",
pageSize,
pageNumber,
isActive,
project,
isArchive,
],
queryFn: async () => { queryFn: async () => {
const resp = await ServiceProjectRepository.GetJobList( const resp = await ServiceProjectRepository.GetJobList(
pageSize, pageSize,
pageNumber, pageNumber,
isActive, isActive,
project, project
isArchive
); );
return resp.data; return resp.data;
}, },
@ -191,7 +181,7 @@ export const useJobComments = (jobId, pageSize, pageNumber) => {
); );
return resp.data; return resp.data;
}, },
enabled: !!jobId, enabled:!!jobId,
initialPageParam: pageNumber, initialPageParam: pageNumber,
@ -268,25 +258,18 @@ export const useCreateServiceProjectJob = (onSuccessCallback) => {
export const useUpdateServiceProjectJob = (onSuccessCallback) => { export const useUpdateServiceProjectJob = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async ({ id, payload, isArchiveAction = false }) => { mutationFn: async ({ id, payload }) => {
// Call the repository patch
const resp = await ServiceProjectRepository.UpdateJob(id, payload); const resp = await ServiceProjectRepository.UpdateJob(id, payload);
return { resp, isArchiveAction }; return resp;
}, },
onSuccess: () => {
onSuccess: ({ isArchiveAction }) => {
queryClient.invalidateQueries({ queryKey: ["serviceProjectJobs"] }); queryClient.invalidateQueries({ queryKey: ["serviceProjectJobs"] });
queryClient.invalidateQueries({ queryKey: ["service-job"] }); queryClient.invalidateQueries({ queryKey: ["service-job"] });
if (onSuccessCallback) onSuccessCallback(); if (onSuccessCallback) onSuccessCallback();
showToast("Job Updated successfully", "success");
if (isArchiveAction) {
showToast("Job archived successfully", "success");
} else {
showToast("Job restored successfully", "success");
}
}, },
onError: (error) => { onError: (error) => {
showToast( showToast(
error?.response?.data?.message || error?.response?.data?.message ||
@ -299,125 +282,3 @@ export const useUpdateServiceProjectJob = (onSuccessCallback) => {
}; };
//#endregion //#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"
);
},
});
};

View File

@ -13,8 +13,6 @@ import Label from "../../components/common/Label";
import AdvancePaymentList from "../../components/AdvancePayment/AdvancePaymentList"; import AdvancePaymentList from "../../components/AdvancePayment/AdvancePaymentList";
import { employee } from "../../data/masters"; import { employee } from "../../data/masters";
import { formatFigure } from "../../utils/appUtils"; import { formatFigure } from "../../utils/appUtils";
import { useParams } from "react-router-dom";
import { useExpenseTransactions } from "../../hooks/useExpense";
export const AdvancePaymentContext = createContext(); export const AdvancePaymentContext = createContext();
export const useAdvancePaymentContext = () => { export const useAdvancePaymentContext = () => {
@ -27,32 +25,14 @@ export const useAdvancePaymentContext = () => {
return context; return context;
}; };
const AdvancePaymentPage = () => { 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 [balance, setBalance] = useState(null);
const { control, reset, watch } = useForm({ const { control, reset, watch } = useForm({
defaultValues: { defaultValues: {
employeeId: employeeId || "", employeeId: "",
searchString: "",
}, },
}); });
const selectedEmployeeId = employeeId || watch("employeeId"); const selectedEmployeeId = watch("employeeId");
const searchString = watch("searchString");
useEffect(() => { useEffect(() => {
const selectedEmpoyee = sessionStorage.getItem("transaction-empId"); const selectedEmpoyee = sessionStorage.getItem("transaction-empId");
reset({ reset({
@ -67,20 +47,30 @@ const AdvancePaymentPage = () => {
data={[ data={[
{ label: "Home", link: "/dashboard" }, { label: "Home", link: "/dashboard" },
{ label: "Finance", link: "/advance-payment" }, { label: "Finance", link: "/advance-payment" },
{ label: "Advance Payment", link: "/advance-payment" }, { label: "Advance Payment" },
employeeName && { label: employeeName, link: "" }, ]}
].filter(Boolean)}
/> />
<div className="card px-4 py-2 page-min-h "> <div className="card px-4 py-2 page-min-h ">
<div className="row py-1 justify-content-end"> <div className="row py-1">
<div className="col-12 col-md-4">
<div className="d-block text-start">
<EmployeeSearchInput
control={control}
name="employeeId"
projectId={null}
forAll={true}
placeholder={"Enter Employee Name"}
/>
</div>
</div>
<div className="col-md-8 d-flex align-items-center justify-content-end"> <div className="col-md-8 d-flex align-items-center justify-content-end">
{balance ? ( {balance ? (
<> <>
<label className="fs-5 fw-semibold">Current Balance : </label> <label className="fs-5 fw-semibold">Current Balance : </label>
<span <span
className={`${balance > 0 ? "text-success" : "text-danger" className={`${
} fs-5 fw-bold ms-1`} balance > 0 ? "text-success" : "text-danger"
} fs-5 fw-bold ms-1`}
> >
{balance > 0 ? ( {balance > 0 ? (
<i className="bx bx-plus b-sm"></i> <i className="bx bx-plus b-sm"></i>
@ -98,7 +88,7 @@ const AdvancePaymentPage = () => {
)} )}
</div> </div>
</div> </div>
<AdvancePaymentList employeeId={selectedEmployeeId} searchString={searchString} /> <AdvancePaymentList employeeId={selectedEmployeeId} />
</div> </div>
</div> </div>
</AdvancePaymentContext.Provider> </AdvancePaymentContext.Provider>

View File

@ -1,34 +0,0 @@
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 (
<div className="container-fluid">
<Breadcrumb
data={[
{ label: "Home", link: "/dashboard" },
{ label: "Finance", link: "/advance-payment" },
{ label: "Advance Payment" },
]}
/>
<div className="card px-4 py-2 page-min-h">
<div className="row py-1">
<AdvancePaymentList1 searchString={searchString} />
</div>
</div>
</div>
)
}
export default AdvancePaymentPage1

View File

@ -20,7 +20,7 @@ const LandingPage = () => {
<div className="row w-100"> <div className="row w-100">
<div className="col-md-auto d-flex justify-content-between align-items-center"> <div className="col-md-auto d-flex justify-content-between align-items-center">
<img <img
src="/img/brand/marco-250x250.png" src="/img/brand/ofw-500x500.png"
style={{ width: "40px" }} style={{ width: "40px" }}
className="me-2" className="me-2"
></img> ></img>
@ -401,15 +401,15 @@ const LandingPage = () => {
</p> </p>
<h5> Our Mission</h5>{" "} <h5> Our Mission</h5>{" "}
<p> <p>
At <OfwLabel></OfwLabel>, our mission is to empower organizations At <OfwLabel></OfwLabel>
to manage their field operations effortlessly helping teams stay , our mission is to empower organizations to manage their field
organized, accountable, and productive, no matter where they are. operations effortlessly helping teams stay organized,
We aim to eliminate manual processes, data silos, and accountable, and productive, no matter where they are. We aim to
communication gaps that often slow down projects and increase eliminate manual processes, data silos, and communication gaps
operational costs. <br /> What We Do We provide a comprehensive that often slow down projects and increase operational costs.{" "}
suite of tools designed to handle every critical aspect of field <br /> What We Do We provide a comprehensive suite of tools
management from workforce tracking to expense control and designed to handle every critical aspect of field management
reporting. With from workforce tracking to expense control and reporting. With
<OfwLabel></OfwLabel>, you can: <OfwLabel></OfwLabel>, you can:
</p> </p>
<ul> <ul>

View File

@ -11,18 +11,14 @@ import GlobalModel from "../../components/common/GlobalModel";
import ManageServiceProject from "../../components/ServiceProject/ManageServiceProject"; import ManageServiceProject from "../../components/ServiceProject/ManageServiceProject";
import { SpinnerLoader } from "../../components/common/Loader"; import { SpinnerLoader } from "../../components/common/Loader";
import ServiceProjectCard from "../../components/ServiceProject/ServiceProjectTeam/ServiceProjectCard"; import ServiceProjectCard from "../../components/ServiceProject/ServiceProjectTeam/ServiceProjectCard";
import ServiceProjectList from "../../components/ServiceProject/ServiceProjectTeam/ServiceProjectList";
import { useDebounce } from "../../utils/appUtils";
const ServiceProjectDisplay = ({ listView, selectedStatuses, searchTerm }) => { const ServiceProjectDisplay = ({ listView ,selectedStatuses }) => {
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const { manageServiceProject, setManageServiceProject } = useProjectContext(); const { manageServiceProject, setManageServiceProject } = useProjectContext();
const debouncedSearch = useDebounce(searchTerm, 500);
const { data, isLoading, isError, error } = useServiceProjects( const { data, isLoading, isError, error } = useServiceProjects(
ITEMS_PER_PAGE, ITEMS_PER_PAGE,
currentPage, currentPage
debouncedSearch
); );
const paginate = (page) => { const paginate = (page) => {
if (page >= 1 && page <= (data?.totalPages ?? 1)) { if (page >= 1 && page <= (data?.totalPages ?? 1)) {
@ -51,20 +47,15 @@ const ServiceProjectDisplay = ({ listView, selectedStatuses, searchTerm }) => {
</div> </div>
); );
return ( return (
<div className=""> <div className="row">
<div className="row">
{listView ? ( {listView ? (
<ServiceProjectList data={filteredProjects} <p>List</p>
currentPage={currentPage} ) : (
totalPages={data?.totalPages}
paginate={paginate}
isCore={false} />
) : filteredProjects?.length > 0 ? (
filteredProjects?.map((project) => ( filteredProjects?.map((project) => (
<ServiceProjectCard key={project.id} project={project} isCore={false} /> <ServiceProjectCard key={project.id} project={project} isCore={false} />
)) ))
):(<div className="d-flex justify-content-center align-items-center page-min-h "><p>No Service projects available</p></div>)} )}
<div className="col-12 d-flex justify-content-start mt-3"> <div className="col-12 d-flex justify-content-start mt-3">
<Pagination <Pagination
@ -91,7 +82,6 @@ const ServiceProjectDisplay = ({ listView, selectedStatuses, searchTerm }) => {
</GlobalModel> </GlobalModel>
)} )}
</div> </div>
</div>
); );
}; };

View File

@ -116,60 +116,56 @@ const CollectionPage = () => {
/> />
<div className="card my-3 py-2 px-sm-4 px-2"> <div className="card my-3 py-2 px-sm-4 px-2">
<div className="row align-items-center gap-sm-2 gap-md-0 mx-0"> <div className="row align-items-center mx-0">
<div className="col-12 col-md-4 d-flex flex-column flex-md-row flex-wrap align-items-start"> {/* Left side: Date Picker + Show Pending (stacked on mobile) */}
<div className="d-inline-flex border rounded-pill overflow-hidden shadow-none"> <div className="col-12 col-md-6 d-flex flex-column flex-md-row flex-wrap align-items-start align-md-items-center gap-2 gap-md-3 mb-3 mb-md-0">
<button <FormProvider {...methods}>
type="button" <DateRangePicker1 howManyDay={180} startField="fromDate"
className={`btn px-2 py-1 rounded-0 text-tiny ${ endField="toDate" />
!showPending ? "btn-primary text-white" : "" </FormProvider>
}`}
onClick={() => setShowPending(false)} <div className="form-check form-switch d-flex align-items-center mt-1">
<input
type="checkbox"
className="form-check-input"
role="switch"
id="inactiveEmployeesCheckbox"
checked={showPending}
onChange={(e) => setShowPending(e.target.checked)}
/>
<label
className="form-check-label ms-2"
htmlFor="inactiveEmployeesCheckbox"
> >
Show All Show Completed Collections
</button> </label>
<button
type="button"
className={`btn px-2 py-1 rounded-0 text-tiny ${
showPending ? "btn-primary text-white" : ""
}`}
onClick={() => setShowPending(true)}
>
Pending
</button>
</div> </div>
</div> </div>
<div className="col-12 col-sm-8 d-block d-sm-flex justify-content-end ga-2 align-items-center gap-2"> {/* Right side: Search + Add Button */}
<div className="col-12 col-sm-6 d-flex justify-content-end align-items-center gap-2">
<input <input
type="search" type="search"
value={searchText} value={searchText}
onChange={(e) => setSearchText(e.target.value)} onChange={(e) => setSearchText(e.target.value)}
placeholder="Search Collection" placeholder="Search Collection"
className="form-control form-control-sm mt-2 mt-sm-0" className="form-control form-control-sm w-auto"
/> />
<div className="d-flex justify-content-between justify-content-sm-between mt-2 mt-sm-0">
<FormProvider {...methods} className="me-3">
<DateRangePicker1
howManyDay={180}
startField="fromDate"
endField="toDate"
/>
</FormProvider>
{(canCreate || isAdmin) && ( {(canCreate || isAdmin) && (
<button <button
className="btn btn-sm btn-primary ms-sm-2" className="btn btn-sm btn-primary"
type="button" type="button"
onClick={() => onClick={() =>
setCollection({ isOpen: true, invoiceId: null }) setCollection({ isOpen: true, invoiceId: null })
} }
> >
<i className="bx bx-plus-circle me-2"></i> <i className="bx bx-plus-circle me-2"></i>
<span className="d-none d-md-inline-block">Collection</span> <span className="d-none d-md-inline-block">
</button> Add New Collection
)} </span>
</div> </button>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import React, { createContext, useContext, useEffect, useRef, useState } from "react"; import React, { createContext, useContext, useEffect, useState } from "react";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import { import {
ITEMS_PER_PAGE, ITEMS_PER_PAGE,
@ -36,17 +36,15 @@ const ProjectPage = () => {
isOpen: false, isOpen: false,
project: null, project: null,
}); });
const dropdownRef = useRef(null);
const [projectList, setProjectList] = useState([]); const [projectList, setProjectList] = useState([]);
const [listView, setListView] = useState(false); const [listView, setListView] = useState(false);
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [coreProjects, setCoreProjects] = useState(() => { const [coreProjects, setCoreProjects] = useState(() => {
const storedValue = sessionStorage.getItem("whichProjectDisplay"); const storedValue = sessionStorage.getItem('whichProjectDisplay');
return storedValue === "true"; return storedValue === 'true';
}); });
const HasManageProject = useHasUserPermission(MANAGE_PROJECT); const HasManageProject = useHasUserPermission(MANAGE_PROJECT);
const [currentPage, setCurrentPage] = useState(1);
const [open, setOpen] = useState(false);
const [selectedStatuses, setSelectedStatuses] = useState( const [selectedStatuses, setSelectedStatuses] = useState(
PROJECT_STATUS.map((s) => s.id) PROJECT_STATUS.map((s) => s.id)
@ -66,20 +64,12 @@ const ProjectPage = () => {
manageServiceProject, manageServiceProject,
}; };
const handleToggleProject = (value) => { const handleToggleProject = (value) => {
setCoreProjects(value); setCoreProjects(value);
sessionStorage.setItem("whichProjectDisplay", String(value)); sessionStorage.setItem("whichProjectDisplay", String(value));
}; };
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
return ( return (
<ProjectContext.Provider value={contextDispatcher}> <ProjectContext.Provider value={contextDispatcher}>
@ -115,11 +105,14 @@ const ProjectPage = () => {
> >
Infra Project Infra Project
</button> </button>
</div> </div>
</div> </div>
{/* RIGHT SIDE — SEARCH + CARD/LIST + DROPDOWN */} {/* RIGHT SIDE — SEARCH + CARD/LIST + DROPDOWN */}
<div className="d-flex flex-wrap align-items-center justify-content-end"> <div className="d-flex flex-wrap align-items-center justify-content-end">
{/* Search */} {/* Search */}
<div className="me-2" style={{ minWidth: "200px" }}> <div className="me-2" style={{ minWidth: "200px" }}>
<input <input
@ -138,8 +131,7 @@ const ProjectPage = () => {
<div className="d-flex gap-2"> <div className="d-flex gap-2">
<button <button
type="button" type="button"
className={`btn btn-sm p-1 ${!listView ? "btn-primary" : "btn-outline-primary" className={`btn btn-sm p-1 ${!listView ? "btn-primary" : "btn-outline-primary"}`}
}`}
onClick={() => setListView(false)} onClick={() => setListView(false)}
title="Card View" title="Card View"
> >
@ -148,8 +140,7 @@ const ProjectPage = () => {
<button <button
type="button" type="button"
className={`btn btn-sm p-1 ${listView ? "btn-primary" : "btn-outline-primary" className={`btn btn-sm p-1 ${listView ? "btn-primary" : "btn-outline-primary"}`}
}`}
onClick={() => setListView(true)} onClick={() => setListView(true)}
title="List View" title="List View"
> >
@ -158,74 +149,41 @@ const ProjectPage = () => {
</div> </div>
{/* Dropdown Filter */} {/* Dropdown Filter */}
<div className="dropdown me-2 position-relative"> <div className="dropdown me-2">
<div <a
className="cursor-pointer p-1" className="dropdown-toggle hide-arrow cursor-pointer p-1"
onClick={() => setOpen(!open)} data-bs-toggle="dropdown"
aria-expanded="false"
title="Filter"
> >
<i <i className="bx bx-slider-alt fs-5"></i>
className={`bx bx-slider-alt fs-5 ${selectedStatuses.length !== PROJECT_STATUS.length ? "text-primary" : "" </a>
}`}
></i>
{selectedStatuses.length !== PROJECT_STATUS.length && ( <ul className="dropdown-menu p-2 text-capitalize">
<span className="badge bg-warning text-white rounded-pill position-absolute" {PROJECT_STATUS.map(({ id, label }) => (
style={{ <li key={id}>
top: "-4px", <div className="form-check">
right: "-4px", <input
fontSize: "0.6rem", className="form-check-input"
padding: "2px 5px", type="checkbox"
}} checked={selectedStatuses.includes(id)}
> onChange={() => handleStatusChange(id)}
{PROJECT_STATUS.length - selectedStatuses.length} />
</span> <label className="form-check-label">{label}</label>
)} </div>
</div> </li>
))}
{open && ( </ul>
<ul
ref={dropdownRef}
className="dropdown-menu show p-2 text-capitalize"
onMouseDown={(e) => e.stopPropagation()} // IMPORTANT
>
{PROJECT_STATUS.map(({ id, label }) => (
<li key={id}>
<div className="form-check">
<input
className="form-check-input"
type="checkbox"
checked={selectedStatuses.includes(id)}
onClick={(e) => e.stopPropagation()} // IMPORTANT
onChange={() => handleStatusChange(id)}
/>
<label
className="form-check-label"
onClick={(e) => e.stopPropagation()} // OPTIONAL
>
{label}
</label>
</div>
</li>
))}
</ul>
)}
</div> </div>
{HasManageProject && ( {HasManageProject && (
<button <button
type="button" type="button"
className="btn btn-primary btn-sm d-flex align-items-center my-2" className="btn btn-primary btn-sm d-flex align-items-center my-2"
onClick={ onClick={() =>
() => coreProjects
coreProjects ? setMangeProject({ isOpen: true, Project: null }) // Organization Project Infra
? setMangeProject({ isOpen: true, Project: null }) // Organization Project Infra : setManageServiceProject({ isOpen: true, Project: null }) // Service Project
: setManageServiceProject({
isOpen: true,
Project: null,
}) // Service Project
} }
> >
<i className="bx bx-plus-circle me-2"></i> <i className="bx bx-plus-circle me-2"></i>
@ -237,22 +195,11 @@ const ProjectPage = () => {
</div> </div>
</div> </div>
{coreProjects ? ( {coreProjects ? <ProjectsDisplay listView={listView}
<ProjectsDisplay searchTerm={searchTerm}
listView={listView} selectedStatuses={selectedStatuses}
searchTerm={searchTerm} handleStatusChange={handleStatusChange} /> : <ServiceProjectDisplay listView={listView}
selectedStatuses={selectedStatuses} selectedStatuses={selectedStatuses} />}
handleStatusChange={handleStatusChange}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
/>
) : (
<ServiceProjectDisplay
listView={listView}
searchTerm={searchTerm}
selectedStatuses={selectedStatuses}
/>
)}
</div> </div>
</ProjectContext.Provider> </ProjectContext.Provider>
); );

View File

@ -10,7 +10,6 @@ import { useServiceProjects } from "../../hooks/useServiceProject";
import { ITEMS_PER_PAGE, PROJECT_STATUS } from "../../utils/constants"; import { ITEMS_PER_PAGE, PROJECT_STATUS } from "../../utils/constants";
import usePagination from "../../hooks/usePagination"; import usePagination from "../../hooks/usePagination";
import ManageProjectInfo from "../../components/Project/ManageProjectInfo"; import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
import { useDebounce } from "../../utils/appUtils";
const ProjectsDisplay = ({ const ProjectsDisplay = ({
listView, listView,
@ -27,8 +26,8 @@ const ProjectsDisplay = ({
} = useProjectContext(); } = useProjectContext();
const [projectList, setProjectList] = useState([]); const [projectList, setProjectList] = useState([]);
const debouncedSearch = useDebounce(searchTerm, 500);
const { data, isLoading, isError, error } = useProjects(ITEMS_PER_PAGE, 1, debouncedSearch); const { data, isLoading, isError, error } = useProjects(ITEMS_PER_PAGE, 1);
const filteredProjects = const filteredProjects =
data?.data?.filter((project) => { data?.data?.filter((project) => {
@ -99,7 +98,7 @@ const ProjectsDisplay = ({
); );
return ( return (
<div className=""> <div className="row">
{listView ? ( {listView ? (
<ProjectListView <ProjectListView
data={projectList} data={projectList}

View File

@ -11,13 +11,12 @@ const EmployeeRepository = {
// deleteEmployee: ( id ) => api.delete( `/users/${ id }` ), // deleteEmployee: ( id ) => api.delete( `/users/${ id }` ),
getEmployeeProfile: (id) => api.get(`/api/employee/profile/get/${id}`), getEmployeeProfile: (id) => api.get(`/api/employee/profile/get/${id}`),
deleteEmployee: (id, active) => api.delete(`/api/employee/${id}?active=${active}`), deleteEmployee: (id, active) => api.delete(`/api/employee/${id}?active=${active}`),
getEmployeeName: (projectId, search, allEmployee,employeeId) => { getEmployeeName: (projectId, search, allEmployee) => {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (projectId) params.append("projectId", projectId); if (projectId) params.append("projectId", projectId);
if (search) params.append("searchString", search); if (search) params.append("searchString", search);
if (allEmployee) params.append("allEmployee", allEmployee); if (allEmployee) params.append("allEmployee", allEmployee)
if (employeeId) params.append("employeeId", employeeId);
const query = params.toString(); const query = params.toString();
return api.get(`/api/Employee/basic${query ? `?${query}` : ""}`); return api.get(`/api/Employee/basic${query ? `?${query}` : ""}`);

View File

@ -44,13 +44,13 @@ const ExpenseRepository = {
DeletePaymentRequest: () => api.get("delete here come"), DeletePaymentRequest: () => api.get("delete here come"),
CreatePaymentRequestExpense: (data) => CreatePaymentRequestExpense: (data) =>
api.post("/api/Expense/payment-request/expense/create", 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 //#endregion
//#region Recurring Expense //#region Recurring Expense
GetRecurringExpenseList: (pageSize, pageNumber, filter, isActive, searchString) => { GetRecurringExpenseList:(pageSize, pageNumber, filter,isActive, searchString) => {
const payloadJsonString = JSON.stringify(filter); const payloadJsonString = JSON.stringify(filter);
return api.get( return api.get(
`/api/expense/get/recurring-payment/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&isActive=${isActive}&searchString=${searchString}` `/api/expense/get/recurring-payment/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&isActive=${isActive}&searchString=${searchString}`
@ -70,11 +70,9 @@ const ExpenseRepository = {
//#region Advance Payment //#region Advance Payment
GetTranctionList: (employeeId) => GetTranctionList: (employeeId) =>
api.get(`/api/Expense/get/transactions/${employeeId}`), api.get(`/api/Expense/get/transactions/${employeeId}`),
getAllTranctionList: (searchString) =>
api.get(`/api/Expense/get/advance-payment/employee/list?searchString=${searchString}`),
//#endregion //#endregion
}; };
export default ExpenseRepository; export default ExpenseRepository;

View File

@ -18,8 +18,6 @@ const GlobalRepository = {
return api.get(`/api/Dashboard/Progression?${params.toString()}`); return api.get(`/api/Dashboard/Progression?${params.toString()}`);
}, },
getProjectCompletionStatus:()=>api.get(`/api/Dashboard/project-completion-status`),
getDashboardAttendanceData: (date, projectId) => { getDashboardAttendanceData: (date, projectId) => {

View File

@ -1,9 +1,8 @@
import { api } from "../utils/axiosClient"; import { api } from "../utils/axiosClient";
const ProjectRepository = { const ProjectRepository = {
getProjectList: (pageSize, pageNumber) =>
getProjectList: (pageSize, pageNumber,searchString) => api.get(`/api/project/list?pageSize=${pageSize}&pageNumber=${pageNumber}`),
api.get(`/api/project/list?pageSize=${pageSize}&pageNumber=${pageNumber}&searchString=${searchString}`),
getProjectByprojectId: (projetid) => getProjectByprojectId: (projetid) =>
api.get(`/api/project/details/${projetid}`), api.get(`/api/project/details/${projetid}`),

View File

@ -1,12 +1,10 @@
import { isAction } from "@reduxjs/toolkit";
import { api } from "../utils/axiosClient"; import { api } from "../utils/axiosClient";
export const ServiceProjectRepository = { export const ServiceProjectRepository = {
//#region Service Project
CreateServiceProject: (data) => api.post("/api/ServiceProject/create", data), CreateServiceProject: (data) => api.post("/api/ServiceProject/create", data),
GetServiceProjects: (pageSize, pageNumber,searchString) => GetServiceProjects: (pageSize, pageNumber) =>
api.get( api.get(
`/api/ServiceProject/list?pageSize=${pageSize}&pageNumber=${pageNumber}&searchString=${searchString}` `/api/ServiceProject/list?pageSize=${pageSize}&pageNumber=${pageNumber}`
), ),
GetServiceProject: (id) => api.get(`/api/ServiceProject/details/${id}`), GetServiceProject: (id) => api.get(`/api/ServiceProject/details/${id}`),
UpdateServiceProject: (id, data) => UpdateServiceProject: (id, data) =>
@ -19,14 +17,12 @@ export const ServiceProjectRepository = {
api.get( api.get(
`/api/ServiceProject/get/allocation/list?projectId=${projectId}&isActive=${isActive} ` `/api/ServiceProject/get/allocation/list?projectId=${projectId}&isActive=${isActive} `
), ),
//#endregion
//#region Job //#region Job
CreateJob: (data) => api.post(`/api/ServiceProject/job/create`, data), CreateJob: (data) => api.post(`/api/ServiceProject/job/create`, data),
GetJobList: (pageSize, pageNumber, isActive, projectId,isArchive) => GetJobList: (pageSize, pageNumber, isActive, projectId) =>
api.get( api.get(
`/api/ServiceProject/job/list?pageSize=${pageSize}&pageNumber=${pageNumber}&isActive=${isActive}&projectId=${projectId}&isArchive=${isArchive}` `/api/ServiceProject/job/list?pageSize=${pageSize}&pageNumber=${pageNumber}&isActive=${isActive}&projectId=${projectId}`
), ),
GetJobDetails: (id) => api.get(`/api/ServiceProject/job/details/${id}`), GetJobDetails: (id) => api.get(`/api/ServiceProject/job/details/${id}`),
AddComment: (data) => api.post("/api/ServiceProject/job/add/comment", data), AddComment: (data) => api.post("/api/ServiceProject/job/add/comment", data),
@ -39,22 +35,4 @@ export const ServiceProjectRepository = {
api.patch(`/api/ServiceProject/job/edit/${id}`, patchData, { api.patch(`/api/ServiceProject/job/edit/${id}`, patchData, {
"Content-Type": "application/json-patch+json", "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`),
}; };

View File

@ -60,8 +60,7 @@ import PaymentRequestPage from "../pages/PaymentRequest/PaymentRequestPage";
import RecurringExpensePage from "../pages/RecurringExpense/RecurringExpensePage"; import RecurringExpensePage from "../pages/RecurringExpense/RecurringExpensePage";
import AdvancePaymentPage from "../pages/AdvancePayment/AdvancePaymentPage"; import AdvancePaymentPage from "../pages/AdvancePayment/AdvancePaymentPage";
import ServiceProjectDetail from "../pages/ServiceProject/ServiceProjectDetail"; import ServiceProjectDetail from "../pages/ServiceProject/ServiceProjectDetail";
import ManageJob from "../components/ServiceProject/ServiceProjectJob/ManageJob"; import ManageJob from "../components/ServiceProject/ManageJob";
import AdvancePaymentPage1 from "../pages/AdvancePayment/AdvancePaymentPage1";
const router = createBrowserRouter( const router = createBrowserRouter(
[ [
{ {
@ -117,8 +116,7 @@ const router = createBrowserRouter(
{ path: "/expenses", element: <ExpensePage /> }, { path: "/expenses", element: <ExpensePage /> },
{ path: "/payment-request", element: <PaymentRequestPage /> }, { path: "/payment-request", element: <PaymentRequestPage /> },
{ path: "/recurring-payment", element: <RecurringExpensePage /> }, { path: "/recurring-payment", element: <RecurringExpensePage /> },
{ path: "/advance-payment", element: <AdvancePaymentPage1 /> }, { path: "/advance-payment", element: <AdvancePaymentPage /> },
{ path: "/advance-payment/:employeeId", element: <AdvancePaymentPage /> },
{ path: "/collection", element: <CollectionPage /> }, { path: "/collection", element: <CollectionPage /> },
{ path: "/masters", element: <MasterPage /> }, { path: "/masters", element: <MasterPage /> },
{ path: "/tenants", element: <TenantPage /> }, { path: "/tenants", element: <TenantPage /> },

View File

@ -150,9 +150,6 @@ export function startSignalR(loggedUser) {
queryClient.invalidateQueries(["serviceProjects"]); queryClient.invalidateQueries(["serviceProjects"]);
queryClient.invalidateQueries(["serviceProject"]); queryClient.invalidateQueries(["serviceProject"]);
} }
if (keyword === "Project_Branch") {
queryClient.invalidateQueries(["branches"]);
}
if (keyword === "Service_Project_Allocation") { if (keyword === "Service_Project_Allocation") {
queryClient.invalidateQueries(["serviceProjectTeam"]); queryClient.invalidateQueries(["serviceProjectTeam"]);

View File

@ -210,35 +210,4 @@ export const PAYEE_RECURRING_EXPENSE = [
//#region Service Project and Jobs //#region Service Project and Jobs
export const STATUS_JOB_CLOSED = "3ddeefb5-ae3c-4e10-a922-35e0a452bb69" export const STATUS_JOB_CLOSED = "3ddeefb5-ae3c-4e10-a922-35e0a452bb69"
//#endregion //#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",
},
];