Merge branch 'Project_Branch_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Weidget_Dashoard_Jobs
This commit is contained in:
commit
1eeff79cf7
@ -32,8 +32,11 @@ 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 SelectEmployeeServerSide, {
|
||||||
|
SelectProjectField,
|
||||||
|
} from "../common/Forms/SelectFieldServerSide";
|
||||||
import { useAllocationServiceProjectTeam } from "../../hooks/useServiceProject";
|
import { useAllocationServiceProjectTeam } from "../../hooks/useServiceProject";
|
||||||
|
import { AppFormController } from "../../hooks/appHooks/useAppForm";
|
||||||
|
|
||||||
const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
||||||
const {
|
const {
|
||||||
@ -153,15 +156,14 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutate: AllocationTeam, isPending1 } = useAllocationServiceProjectTeam(
|
const { mutate: AllocationTeam, isPending1 } =
|
||||||
() => {
|
useAllocationServiceProjectTeam(() => {
|
||||||
setSelectedEmployees([]);
|
setSelectedEmployees([]);
|
||||||
setSeletingEmp({
|
setSeletingEmp({
|
||||||
employee: null,
|
employee: null,
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (expenseToEdit && data) {
|
if (expenseToEdit && data) {
|
||||||
reset({
|
reset({
|
||||||
@ -180,17 +182,17 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
currencyId: data.currency.id || DEFAULT_CURRENCY,
|
currencyId: data.currency.id || DEFAULT_CURRENCY,
|
||||||
billAttachments: data.documents
|
billAttachments: data.documents
|
||||||
? data.documents.map((doc) => ({
|
? data.documents.map((doc) => ({
|
||||||
fileName: doc.fileName,
|
fileName: doc.fileName,
|
||||||
base64Data: null,
|
base64Data: null,
|
||||||
contentType: doc.contentType,
|
contentType: doc.contentType,
|
||||||
documentId: doc.documentId,
|
documentId: doc.documentId,
|
||||||
fileSize: 0,
|
fileSize: 0,
|
||||||
description: "",
|
description: "",
|
||||||
preSignedUrl: doc.preSignedUrl,
|
preSignedUrl: doc.preSignedUrl,
|
||||||
isActive: doc.isActive ?? true,
|
isActive: doc.isActive ?? true,
|
||||||
}))
|
}))
|
||||||
: [],
|
: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [data, reset]);
|
}, [data, reset]);
|
||||||
const { mutate: ExpenseUpdate, isPending } = useUpdateExpense(() =>
|
const { mutate: ExpenseUpdate, isPending } = useUpdateExpense(() =>
|
||||||
@ -264,7 +266,6 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
required
|
required
|
||||||
placeholder="Select Project"
|
placeholder="Select Project"
|
||||||
value={watch("projectId")}
|
value={watch("projectId")}
|
||||||
|
|
||||||
onChange={(val) =>
|
onChange={(val) =>
|
||||||
setValue("projectId", val, {
|
setValue("projectId", val, {
|
||||||
shouldDirty: true,
|
shouldDirty: true,
|
||||||
@ -337,16 +338,30 @@ 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={true}
|
||||||
|
/> */}
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
<div className="row my-2 text-start">
|
<div className="row my-2 text-start">
|
||||||
@ -453,7 +468,6 @@ 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 ">
|
||||||
@ -484,7 +498,9 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
</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>No. of Persons</Label>
|
<Label className="form-label" required>
|
||||||
|
No. of Persons
|
||||||
|
</Label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
id="noOfPersons"
|
id="noOfPersons"
|
||||||
@ -570,7 +586,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
(fileError?.fileSize?.message ||
|
(fileError?.fileSize?.message ||
|
||||||
fileError?.contentType?.message ||
|
fileError?.contentType?.message ||
|
||||||
fileError?.base64Data?.message,
|
fileError?.base64Data?.message,
|
||||||
fileError?.documentId?.message)
|
fileError?.documentId?.message)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -595,8 +611,8 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
{isPending || createPending
|
{isPending || createPending
|
||||||
? "Please Wait..."
|
? "Please Wait..."
|
||||||
: expenseToEdit
|
: expenseToEdit
|
||||||
? "Update"
|
? "Update"
|
||||||
: "Save as Draft"}
|
: "Save as Draft"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -29,6 +29,7 @@ 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 {
|
||||||
@ -234,10 +235,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={
|
||||||
@ -254,7 +255,23 @@ 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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -8,7 +8,12 @@ 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 && (
|
||||||
<p className="text-center text-muted">No projects found.</p>
|
<div
|
||||||
|
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) => (
|
||||||
|
|||||||
@ -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 page-min-h">
|
<div className="table-responsive text-nowrap">
|
||||||
<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,77 +143,94 @@ const ProjectListView = ({ data, currentPage, totalPages, paginate }) => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{data?.map((project) => (
|
{data?.length > 0 ? (
|
||||||
<tr key={project.id}>
|
data?.map((project) => (
|
||||||
{projectColumns.map((col) => (
|
<tr key={project.id}>
|
||||||
<td
|
{projectColumns.map((col) => (
|
||||||
key={col.key}
|
<td
|
||||||
colSpan={col.colSpan}
|
key={col.key}
|
||||||
className={`${col.className} py-5`}
|
colSpan={col.colSpan}
|
||||||
style={{ paddingTop: "20px", paddingBottom: "20px" }}
|
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
|
{col.getValue
|
||||||
className="bx bx-dots-vertical-rounded bx-sm text-muted"
|
? col.getValue(project)
|
||||||
data-bs-toggle="tooltip"
|
: project[col.key] || "N/A"}
|
||||||
data-bs-offset="0,8"
|
</td>
|
||||||
data-bs-placement="top"
|
))}
|
||||||
data-bs-custom-class="tooltip-dark"
|
<td
|
||||||
title="More Action"
|
className={`mx-2 ${canManageProject ? "d-sm-table-cell" : "d-none"
|
||||||
></i>
|
}`}
|
||||||
</button>
|
>
|
||||||
<ul className="dropdown-menu dropdown-menu-end">
|
<div className="dropdown z-2">
|
||||||
<li>
|
<button
|
||||||
<a
|
type="button"
|
||||||
aria-label="click to View details"
|
className="btn btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0"
|
||||||
className="dropdown-item cursor-pointer"
|
data-bs-toggle="dropdown"
|
||||||
>
|
aria-expanded="false"
|
||||||
<i className="bx bx-detail me-2"></i>
|
>
|
||||||
<span className="align-left">View details</span>
|
<i
|
||||||
</a>
|
className="bx bx-dots-vertical-rounded bx-sm text-muted"
|
||||||
</li>
|
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>
|
<li>
|
||||||
<a
|
<a
|
||||||
className="dropdown-item cursor-pointer"
|
className="dropdown-item cursor-pointer"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setMangeProject({
|
setMangeProject({
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
Project: project.id,
|
Project: project.id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<i className="bx bx-pencil me-2"></i>
|
<i className="bx bx-pencil me-2"></i>
|
||||||
<span className="align-left">Modify</span>
|
<span className="align-left">Modify</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li onClick={() => handleViewActivities(project.id)}>
|
<li onClick={() => handleViewActivities(project.id)}>
|
||||||
<a className="dropdown-item cursor-pointer">
|
<a className="dropdown-item cursor-pointer">
|
||||||
<i className="bx bx-task me-2"></i>
|
<i className="bx bx-task me-2"></i>
|
||||||
<span className="align-left">Activities</span>
|
<span className="align-left">Activities</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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 Infra projects available
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -27,6 +27,7 @@ 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 {
|
||||||
@ -131,7 +132,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 = {
|
||||||
@ -163,10 +164,7 @@ 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">
|
||||||
<Label className="form-label" required>
|
{/* <select
|
||||||
Select Project
|
|
||||||
</Label>
|
|
||||||
<select
|
|
||||||
className="form-select form-select-sm"
|
className="form-select form-select-sm"
|
||||||
{...register("projectId")}
|
{...register("projectId")}
|
||||||
>
|
>
|
||||||
@ -180,7 +178,19 @@ 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>
|
||||||
)}
|
)}
|
||||||
@ -235,7 +245,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 gap-2">
|
<div className="d-flex justify-content-start align-items-center text-nowrap 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>
|
||||||
@ -243,13 +253,16 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
|
|||||||
title="Payment Type"
|
title="Payment Type"
|
||||||
id="payment_type"
|
id="payment_type"
|
||||||
content={
|
content={
|
||||||
<p>
|
<div className=" w-50">
|
||||||
Choose whether the payment amount varies or remains fixed each cycle.
|
<p>
|
||||||
<br />
|
Choose whether the payment amount varies or remains fixed
|
||||||
<strong>Is Variable:</strong> Amount changes per cycle.
|
each cycle.
|
||||||
<br />
|
<br />
|
||||||
<strong>Fixed:</strong> Amount stays constant.
|
<strong>Is Variable:</strong> Amount changes per cycle.
|
||||||
</p>
|
<br />
|
||||||
|
<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>
|
||||||
@ -270,7 +283,10 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
|
|||||||
checked={field.value === true}
|
checked={field.value === true}
|
||||||
onChange={() => field.onChange(true)}
|
onChange={() => field.onChange(true)}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor="isVariableTrue" className="form-check-label">
|
<Label
|
||||||
|
htmlFor="isVariableTrue"
|
||||||
|
className="form-check-label"
|
||||||
|
>
|
||||||
Is Variable
|
Is Variable
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
@ -283,7 +299,10 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
|
|||||||
checked={field.value === false}
|
checked={field.value === false}
|
||||||
onChange={() => field.onChange(false)}
|
onChange={() => field.onChange(false)}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor="isVariableFalse" className="form-check-label">
|
<Label
|
||||||
|
htmlFor="isVariableFalse"
|
||||||
|
className="form-check-label"
|
||||||
|
>
|
||||||
Fixed
|
Fixed
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
@ -295,7 +314,6 @@ 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 */}
|
||||||
@ -391,11 +409,12 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
|
|||||||
id="frequency"
|
id="frequency"
|
||||||
content={
|
content={
|
||||||
<p>
|
<p>
|
||||||
Defines how often payments or billing occur, such as monthly, quarterly, or annually.
|
Defines how often payments or billing occur, such as
|
||||||
|
monthly, quarterly, or annually.
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<i className="bx bx-info-circle bx-sm text-muted cursor-pointer"></i>
|
<i className="bx bx-info-circle bx-xs text-muted cursor-pointer"></i>
|
||||||
</HoverPopup>
|
</HoverPopup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -444,10 +463,13 @@ 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 gap-2">
|
<div className="d-flex justify-content-start align-items-center text-nowrap gap-2">
|
||||||
<Label htmlFor="paymentBufferDays" className="form-label mb-0" required>
|
<Label
|
||||||
|
htmlFor="paymentBufferDays"
|
||||||
|
className="form-label mb-0 "
|
||||||
|
required
|
||||||
|
>
|
||||||
Payment Buffer Days
|
Payment Buffer Days
|
||||||
</Label>
|
</Label>
|
||||||
<HoverPopup
|
<HoverPopup
|
||||||
@ -455,11 +477,12 @@ 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 payment is considered late.
|
Number of extra days allowed after the due date before
|
||||||
|
payment is considered late.
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<i className="bx bx-info-circle bx-sm text-muted cursor-pointer"></i>
|
<i className="bx bx-info-circle bx-xs text-muted cursor-pointer"></i>
|
||||||
</HoverPopup>
|
</HoverPopup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -480,9 +503,8 @@ 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 gap-2">
|
<div className="d-flex justify-content-start align-items-center text-nowrap 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>
|
||||||
@ -495,7 +517,7 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
|
|||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<i className="bx bx-info-circle bx-sm text-muted cursor-pointer"></i>
|
<i className="bx bx-info-circle bx-xs text-muted cursor-pointer"></i>
|
||||||
</HoverPopup>
|
</HoverPopup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -510,10 +532,8 @@ 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>
|
||||||
@ -572,8 +592,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>
|
||||||
|
|||||||
@ -130,7 +130,7 @@ const JobList = ({ isArchive }) => {
|
|||||||
{isArchiveModalOpen && (
|
{isArchiveModalOpen && (
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
isOpen={isArchiveModalOpen}
|
isOpen={isArchiveModalOpen}
|
||||||
type={isArchiveAction ? "success" : "undo"}
|
type={isArchiveAction ? "archive" : "Un-archive"}
|
||||||
header={isArchiveAction ? "Archive Job" : "Restore Job"}
|
header={isArchiveAction ? "Archive Job" : "Restore Job"}
|
||||||
message={
|
message={
|
||||||
isArchiveAction
|
isArchiveAction
|
||||||
|
|||||||
@ -72,11 +72,11 @@ const Jobs = () => {
|
|||||||
>
|
>
|
||||||
{showArchive ? (
|
{showArchive ? (
|
||||||
<>
|
<>
|
||||||
<i className="bx bx-list-ul me-1 mt-1"></i> Show Active Jobs
|
<i className="bx bx-list-ul me-1 mt-1"></i> Show Active
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<i className="bx bx-archive me-1 mt-1"></i> Show Archived Jobs
|
<i className="bx bx-archive me-1 mt-1"></i> Show Archived
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -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 value={status.id}>{status.label}</option>
|
<option key={status.id} value={status.id}>{status.label}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
{errors?.statusId && (
|
{errors?.statusId && (
|
||||||
|
|||||||
@ -103,7 +103,7 @@ const ServiceBranch = () => {
|
|||||||
htmlFor="inactiveEmployeesCheckbox"
|
htmlFor="inactiveEmployeesCheckbox"
|
||||||
className="ms-2 text-xs"
|
className="ms-2 text-xs"
|
||||||
>
|
>
|
||||||
Show Deleted Branches
|
{!showInactive ? "Show Deleted" : "Hide Deleted"}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="d-flex justify-content-end">
|
<div className="d-flex justify-content-end">
|
||||||
|
|||||||
@ -31,7 +31,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 =
|
||||||
|
|||||||
@ -26,13 +26,6 @@ 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 = () => {
|
||||||
@ -43,10 +36,6 @@ 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({
|
||||||
@ -68,6 +57,8 @@ const ServiceProjectCard = ({ project, isCore = true }) => {
|
|||||||
DeleteProject(projectId, false);
|
DeleteProject(projectId, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
@ -138,14 +129,6 @@ 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={() =>
|
||||||
|
|||||||
@ -0,0 +1,209 @@
|
|||||||
|
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;
|
||||||
@ -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 handleRemove={handleRemove} employee={e} />
|
<EmployeeChip key={`${e.id}-emp`} handleRemove={handleRemove} employee={e} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,45 +1,44 @@
|
|||||||
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@ -19,7 +19,9 @@ const ConfirmModal = ({
|
|||||||
case "success":
|
case "success":
|
||||||
return <i className="bx bx-archive-in text-warning" style={{ fontSize: "60px" }}></i>;
|
return <i className="bx bx-archive-in text-warning" style={{ fontSize: "60px" }}></i>;
|
||||||
case "archive":
|
case "archive":
|
||||||
return <i className="bx bx-error-circle text-warning" style={{ fontSize: "60px" }}></i>;
|
return <i className="bx bx-archive-in text-warning" style={{ fontSize: "60px" }}></i>;
|
||||||
|
case "Un-archive":
|
||||||
|
return <i className="bx bx-archive-out text-warning" style={{ fontSize: "60px" }}></i>;
|
||||||
case "undo":
|
case "undo":
|
||||||
return <i className="bx bx-undo text-info" style={{ fontSize: "50px" }}></i>;
|
return <i className="bx bx-undo text-info" style={{ fontSize: "50px" }}></i>;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -14,7 +14,6 @@ const InputSuggessionField = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const dropdownRef = useRef(null);
|
const dropdownRef = useRef(null);
|
||||||
console.log(suggesstionList)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event) => {
|
const handleClickOutside = (event) => {
|
||||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
||||||
@ -61,7 +60,7 @@ console.log(suggesstionList)
|
|||||||
|
|
||||||
{open && !isLoading && (
|
{open && !isLoading && (
|
||||||
<ul
|
<ul
|
||||||
className="dropdown-menu w-100 shadow-sm show animate__fadeIn"
|
className="dropdown-menu w-100 shadow-sm show animate__fadeIn h-64 overflow-auto rounded overflow-x-hidden"
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "100%",
|
top: "100%",
|
||||||
|
|||||||
@ -3,6 +3,7 @@ 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",
|
||||||
@ -18,6 +19,7 @@ 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,
|
||||||
@ -34,22 +36,15 @@ const SelectEmployeeServerSide = ({
|
|||||||
return `${emp.firstName || ""} ${emp.lastName || ""}`.trim();
|
return `${emp.firstName || ""} ${emp.lastName || ""}`.trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
/** -----------------------------
|
|
||||||
* SELECTED OPTION (SINGLE)
|
|
||||||
* ----------------------------- */
|
|
||||||
let selectedSingle = null;
|
let selectedSingle = null;
|
||||||
|
|
||||||
if (!isMultiple) {
|
if (!isMultiple) {
|
||||||
if (isFullObject && value) selectedSingle = value;
|
if (isFullObject && value) selectedSingle = value;
|
||||||
else if (!isFullObject && value)
|
else if (!isFullObject && value)
|
||||||
selectedSingle = options.find((o) => o[valueKey] === value);
|
selectedSingle =
|
||||||
|
options.find((o) => o[valueKey] === value) || forcedSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** -----------------------------
|
|
||||||
* 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 {
|
||||||
@ -57,54 +52,61 @@ const SelectEmployeeServerSide = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Main button label */
|
|
||||||
const displayText = !isMultiple
|
const displayText = !isMultiple
|
||||||
? getDisplayName(selectedSingle) || placeholder
|
? getDisplayName(selectedSingle) || placeholder
|
||||||
: selectedList.length > 0
|
: selectedList.length > 0
|
||||||
? selectedList.map((e) => getDisplayName(e)).join(", ")
|
? selectedList.map((e) => getDisplayName(e)).join(", ")
|
||||||
: placeholder;
|
: placeholder;
|
||||||
|
|
||||||
/** -----------------------------
|
|
||||||
* HANDLE OUTSIDE CLICK
|
|
||||||
* ----------------------------- */
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (e) => {
|
const handleClickOutside = (e) => {
|
||||||
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/** -----------------------------
|
|
||||||
* HANDLE SELECT
|
|
||||||
* ----------------------------- */
|
|
||||||
const handleSelect = (option) => {
|
const handleSelect = (option) => {
|
||||||
if (!isMultiple) {
|
if (!isMultiple) {
|
||||||
// SINGLE SELECT
|
|
||||||
if (isFullObject) onChange(option);
|
if (isFullObject) onChange(option);
|
||||||
else onChange(option[valueKey]);
|
else onChange(option[valueKey]);
|
||||||
|
setOpen(false);
|
||||||
} else {
|
} else {
|
||||||
// MULTIPLE SELECT
|
|
||||||
let updated = [];
|
let updated = [];
|
||||||
|
|
||||||
const exists = selectedList.some((e) => e[valueKey] === option[valueKey]);
|
const exists = selectedList.some((e) => e[valueKey] === option[valueKey]);
|
||||||
|
updated = exists
|
||||||
if (exists) {
|
? selectedList.filter((e) => e[valueKey] !== option[valueKey])
|
||||||
// remove
|
: [...selectedList, option];
|
||||||
updated = selectedList.filter((e) => e[valueKey] !== option[valueKey]);
|
|
||||||
} else {
|
|
||||||
// add
|
|
||||||
updated = [...selectedList, option];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFullObject) onChange(updated);
|
if (isFullObject) onChange(updated);
|
||||||
else onChange(updated.map((x) => x[valueKey]));
|
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 (
|
return (
|
||||||
<div className="mb-3 position-relative" ref={dropdownRef}>
|
<div className="mb-3 position-relative" ref={dropdownRef}>
|
||||||
{label && (
|
{label && (
|
||||||
@ -126,7 +128,6 @@ const SelectEmployeeServerSide = ({
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* DROPDOWN */}
|
|
||||||
{open && (
|
{open && (
|
||||||
<ul
|
<ul
|
||||||
className="dropdown-menu w-100 shadow-sm show animate__fadeIn h-64 overflow-auto rounded"
|
className="dropdown-menu w-100 shadow-sm show animate__fadeIn h-64 overflow-auto rounded"
|
||||||
@ -137,10 +138,10 @@ const SelectEmployeeServerSide = ({
|
|||||||
zIndex: 1050,
|
zIndex: 1050,
|
||||||
marginTop: "4px",
|
marginTop: "4px",
|
||||||
borderRadius: "0.375rem",
|
borderRadius: "0.375rem",
|
||||||
overflow: "hidden",
|
padding: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="p-1">
|
<li className="p-1 sticky-top bg-white" style={{ zIndex: 10 }}>
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
value={searchText}
|
value={searchText}
|
||||||
@ -148,7 +149,7 @@ const SelectEmployeeServerSide = ({
|
|||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</li>
|
||||||
|
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<li className="dropdown-item text-muted text-center">Loading...</li>
|
<li className="dropdown-item text-muted text-center">Loading...</li>
|
||||||
@ -168,10 +169,12 @@ const SelectEmployeeServerSide = ({
|
|||||||
selectedSingle[valueKey] === option[valueKey];
|
selectedSingle[valueKey] === option[valueKey];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={option[valueKey]}>
|
<li key={option[valueKey]} className="px-1 rounded">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`dropdown-item ${isActive ? "active" : ""}`}
|
className={`dropdown-item rounded ${
|
||||||
|
isActive ? "active" : ""
|
||||||
|
}`}
|
||||||
onClick={() => handleSelect(option)}
|
onClick={() => handleSelect(option)}
|
||||||
>
|
>
|
||||||
{getDisplayName(option)}
|
{getDisplayName(option)}
|
||||||
@ -184,6 +187,7 @@ const SelectEmployeeServerSide = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SelectEmployeeServerSide;
|
export default SelectEmployeeServerSide;
|
||||||
|
|
||||||
export const SelectProjectField = ({
|
export const SelectProjectField = ({
|
||||||
@ -196,6 +200,7 @@ export const SelectProjectField = ({
|
|||||||
isFullObject = false,
|
isFullObject = false,
|
||||||
isMultiple = false,
|
isMultiple = false,
|
||||||
isAllProject = false,
|
isAllProject = false,
|
||||||
|
disabled
|
||||||
}) => {
|
}) => {
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const debounce = useDebounce(searchText, 300);
|
const debounce = useDebounce(searchText, 300);
|
||||||
@ -211,9 +216,6 @@ export const SelectProjectField = ({
|
|||||||
return `${project.name || ""}`.trim();
|
return `${project.name || ""}`.trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
/** -----------------------------
|
|
||||||
* SELECTED OPTION (SINGLE)
|
|
||||||
* ----------------------------- */
|
|
||||||
let selectedSingle = null;
|
let selectedSingle = null;
|
||||||
|
|
||||||
if (!isMultiple) {
|
if (!isMultiple) {
|
||||||
@ -222,9 +224,6 @@ export const SelectProjectField = ({
|
|||||||
selectedSingle = options.find((o) => o[valueKey] === value);
|
selectedSingle = options.find((o) => o[valueKey] === value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** -----------------------------
|
|
||||||
* SELECTED OPTION (MULTIPLE)
|
|
||||||
* ----------------------------- */
|
|
||||||
let selectedList = [];
|
let selectedList = [];
|
||||||
|
|
||||||
if (isMultiple && Array.isArray(value)) {
|
if (isMultiple && Array.isArray(value)) {
|
||||||
@ -297,6 +296,7 @@ export const SelectProjectField = ({
|
|||||||
open ? "show" : ""
|
open ? "show" : ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setOpen((prev) => !prev)}
|
onClick={() => setOpen((prev) => !prev)}
|
||||||
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<span className={`text-truncate ${!displayText ? "text-muted" : ""}`}>
|
<span className={`text-truncate ${!displayText ? "text-muted" : ""}`}>
|
||||||
{displayText}
|
{displayText}
|
||||||
@ -345,10 +345,12 @@ export const SelectProjectField = ({
|
|||||||
selectedSingle[valueKey] === option[valueKey];
|
selectedSingle[valueKey] === option[valueKey];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={option[valueKey]}>
|
<li key={option[valueKey]} className="px-1 rounded w-full">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`dropdown-item ${isActive ? "active" : ""}`}
|
className={`dropdown-item rounded d-block text-truncate w-100 ${
|
||||||
|
isActive ? "active" : ""
|
||||||
|
}`}
|
||||||
onClick={() => handleSelect(option)}
|
onClick={() => handleSelect(option)}
|
||||||
>
|
>
|
||||||
{getDisplayName(option)}
|
{getDisplayName(option)}
|
||||||
@ -514,7 +516,7 @@ export const SelectFieldSearch = ({
|
|||||||
{/* DROPDOWN */}
|
{/* DROPDOWN */}
|
||||||
{open && (
|
{open && (
|
||||||
<ul
|
<ul
|
||||||
className="dropdown-menu w-100 shadow-sm show animate__fadeIn h-64 overflow-auto rounded"
|
className="dropdown-menu w-100 shadow-sm show animate__fadeIn h-64 overflow-auto rounded overflow-x-hidden"
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "100%",
|
top: "100%",
|
||||||
|
|||||||
@ -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, // Optional: cache for 5 minutes
|
staleTime: 5 * 60 * 1000,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -20,14 +20,15 @@ export const useCurrentService = () => {
|
|||||||
|
|
||||||
// ------------------------------Query-------------------
|
// ------------------------------Query-------------------
|
||||||
|
|
||||||
export const useProjects = (pageSize, pageNumber) => {
|
export const useProjects = (pageSize, pageNumber,searchString) => {
|
||||||
const loggedUser = useSelector((store) => store.globalVariables.loginUser);
|
const loggedUser = useSelector((store) => store.globalVariables.loginUser);
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["ProjectsList", pageSize, pageNumber],
|
queryKey: ["ProjectsList", pageSize, pageNumber,searchString],
|
||||||
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;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -8,13 +8,14 @@ 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) => {
|
export const useServiceProjects = (pageSize, pageNumber, searchString) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["serviceProjects", pageSize, pageNumber],
|
queryKey: ["serviceProjects", pageSize, pageNumber, searchString],
|
||||||
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;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -11,14 +11,18 @@ 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 }) => {
|
const ServiceProjectDisplay = ({ listView, selectedStatuses, searchTerm }) => {
|
||||||
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)) {
|
||||||
@ -47,15 +51,20 @@ const ServiceProjectDisplay = ({ listView ,selectedStatuses }) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="">
|
||||||
|
<div className="row">
|
||||||
{listView ? (
|
{listView ? (
|
||||||
<p>List</p>
|
<ServiceProjectList data={filteredProjects}
|
||||||
) : (
|
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
|
||||||
@ -82,6 +91,7 @@ const ServiceProjectDisplay = ({ listView ,selectedStatuses }) => {
|
|||||||
</GlobalModel>
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -116,22 +116,23 @@ 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 mx-0">
|
<div className="row align-items-center gap-sm-2 gap-md-0 mx-0">
|
||||||
{/* Left side: Date Picker + Show Pending (stacked on mobile) */}
|
<div className="col-12 col-md-4 d-flex flex-column flex-md-row flex-wrap align-items-start">
|
||||||
<div className="col-12 col-md-6 d-flex flex-column flex-md-row flex-wrap align-items-start">
|
|
||||||
<div className="d-inline-flex border rounded-pill overflow-hidden shadow-none">
|
<div className="d-inline-flex border rounded-pill overflow-hidden shadow-none">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn px-2 py-1 rounded-0 text-tiny ${!showPending ? "btn-primary text-white" : ""
|
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||||
}`}
|
!showPending ? "btn-primary text-white" : ""
|
||||||
|
}`}
|
||||||
onClick={() => setShowPending(false)}
|
onClick={() => setShowPending(false)}
|
||||||
>
|
>
|
||||||
Show All
|
Show All
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn px-2 py-1 rounded-0 text-tiny ${showPending ? "btn-primary text-white" : ""
|
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||||
}`}
|
showPending ? "btn-primary text-white" : ""
|
||||||
|
}`}
|
||||||
onClick={() => setShowPending(true)}
|
onClick={() => setShowPending(true)}
|
||||||
>
|
>
|
||||||
Pending
|
Pending
|
||||||
@ -139,34 +140,36 @@ const CollectionPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right side: Search + Add Button */}
|
<div className="col-12 col-sm-8 d-block d-sm-flex justify-content-end ga-2 align-items-center gap-2">
|
||||||
<div className="col-12 col-sm-6 d-flex justify-content-end align-items-center gap-2">
|
|
||||||
<FormProvider {...methods}>
|
|
||||||
<DateRangePicker1 howManyDay={180} startField="fromDate"
|
|
||||||
endField="toDate" />
|
|
||||||
</FormProvider>
|
|
||||||
<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 w-auto"
|
className="form-control form-control-sm mt-2 mt-sm-0"
|
||||||
/>
|
/>
|
||||||
|
<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"
|
className="btn btn-sm btn-primary ms-sm-2"
|
||||||
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">
|
<span className="d-none d-md-inline-block">Collection</span>
|
||||||
Add New Collection
|
</button>
|
||||||
</span>
|
)}
|
||||||
</button>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -41,10 +41,11 @@ const ProjectPage = () => {
|
|||||||
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 [selectedStatuses, setSelectedStatuses] = useState(
|
const [selectedStatuses, setSelectedStatuses] = useState(
|
||||||
PROJECT_STATUS.map((s) => s.id)
|
PROJECT_STATUS.map((s) => s.id)
|
||||||
@ -64,13 +65,11 @@ const ProjectPage = () => {
|
|||||||
manageServiceProject,
|
manageServiceProject,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleToggleProject = (value) => {
|
const handleToggleProject = (value) => {
|
||||||
setCoreProjects(value);
|
setCoreProjects(value);
|
||||||
sessionStorage.setItem("whichProjectDisplay", String(value));
|
sessionStorage.setItem("whichProjectDisplay", String(value));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProjectContext.Provider value={contextDispatcher}>
|
<ProjectContext.Provider value={contextDispatcher}>
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
@ -90,8 +89,9 @@ const ProjectPage = () => {
|
|||||||
{/* Service Project Button */}
|
{/* Service Project Button */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn px-2 py-1 rounded-0 text-tiny ${!coreProjects ? "btn-primary text-white" : ""
|
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||||
}`}
|
!coreProjects ? "btn-primary text-white" : ""
|
||||||
|
}`}
|
||||||
onClick={() => handleToggleProject(false)}
|
onClick={() => handleToggleProject(false)}
|
||||||
>
|
>
|
||||||
Service Project
|
Service Project
|
||||||
@ -99,20 +99,18 @@ const ProjectPage = () => {
|
|||||||
{/* Organization Project Button */}
|
{/* Organization Project Button */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn px-2 py-1 rounded-0 text-tiny ${coreProjects ? "btn-primary text-white" : ""
|
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||||
}`}
|
coreProjects ? "btn-primary text-white" : ""
|
||||||
|
}`}
|
||||||
onClick={() => handleToggleProject(true)}
|
onClick={() => handleToggleProject(true)}
|
||||||
>
|
>
|
||||||
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
|
||||||
@ -131,7 +129,9 @@ 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"
|
||||||
>
|
>
|
||||||
@ -140,7 +140,9 @@ 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"
|
||||||
>
|
>
|
||||||
@ -180,10 +182,14 @@ const ProjectPage = () => {
|
|||||||
<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
|
() =>
|
||||||
? setMangeProject({ isOpen: true, Project: null }) // Organization Project → Infra
|
coreProjects
|
||||||
: setManageServiceProject({ isOpen: true, Project: null }) // Service Project
|
? setMangeProject({ isOpen: true, Project: null }) // Organization Project → Infra
|
||||||
|
: 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>
|
||||||
@ -195,11 +201,22 @@ const ProjectPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{coreProjects ? <ProjectsDisplay listView={listView}
|
{coreProjects ? (
|
||||||
searchTerm={searchTerm}
|
<ProjectsDisplay
|
||||||
selectedStatuses={selectedStatuses}
|
listView={listView}
|
||||||
handleStatusChange={handleStatusChange} /> : <ServiceProjectDisplay listView={listView}
|
searchTerm={searchTerm}
|
||||||
selectedStatuses={selectedStatuses} />}
|
selectedStatuses={selectedStatuses}
|
||||||
|
handleStatusChange={handleStatusChange}
|
||||||
|
currentPage={currentPage}
|
||||||
|
setCurrentPage={setCurrentPage}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ServiceProjectDisplay
|
||||||
|
listView={listView}
|
||||||
|
searchTerm={searchTerm}
|
||||||
|
selectedStatuses={selectedStatuses}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ProjectContext.Provider>
|
</ProjectContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -10,6 +10,7 @@ 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,
|
||||||
@ -26,8 +27,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);
|
const { data, isLoading, isError, error } = useProjects(ITEMS_PER_PAGE, 1, debouncedSearch);
|
||||||
|
|
||||||
const filteredProjects =
|
const filteredProjects =
|
||||||
data?.data?.filter((project) => {
|
data?.data?.filter((project) => {
|
||||||
@ -98,7 +99,7 @@ const ProjectsDisplay = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="">
|
||||||
{listView ? (
|
{listView ? (
|
||||||
<ProjectListView
|
<ProjectListView
|
||||||
data={projectList}
|
data={projectList}
|
||||||
|
|||||||
@ -11,12 +11,13 @@ 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) => {
|
getEmployeeName: (projectId, search, allEmployee,employeeId) => {
|
||||||
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}` : ""}`);
|
||||||
|
|||||||
@ -2,8 +2,8 @@ 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}`),
|
||||||
|
|
||||||
|
|||||||
@ -4,9 +4,9 @@ import { api } from "../utils/axiosClient";
|
|||||||
export const ServiceProjectRepository = {
|
export const ServiceProjectRepository = {
|
||||||
//#region Service Project
|
//#region Service Project
|
||||||
CreateServiceProject: (data) => api.post("/api/ServiceProject/create", data),
|
CreateServiceProject: (data) => api.post("/api/ServiceProject/create", data),
|
||||||
GetServiceProjects: (pageSize, pageNumber) =>
|
GetServiceProjects: (pageSize, pageNumber,searchString) =>
|
||||||
api.get(
|
api.get(
|
||||||
`/api/ServiceProject/list?pageSize=${pageSize}&pageNumber=${pageNumber}`
|
`/api/ServiceProject/list?pageSize=${pageSize}&pageNumber=${pageNumber}&searchString=${searchString}`
|
||||||
),
|
),
|
||||||
GetServiceProject: (id) => api.get(`/api/ServiceProject/details/${id}`),
|
GetServiceProject: (id) => api.get(`/api/ServiceProject/details/${id}`),
|
||||||
UpdateServiceProject: (id, data) =>
|
UpdateServiceProject: (id, data) =>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user