diff --git a/src/components/ServiceProject/JobComments.jsx b/src/components/ServiceProject/JobComments.jsx index ff86577c..84d31665 100644 --- a/src/components/ServiceProject/JobComments.jsx +++ b/src/components/ServiceProject/JobComments.jsx @@ -22,7 +22,7 @@ const JobComments = ({ data }) => { formState: { errors }, } = useAppForm({ resolver: zodResolver(JobCommentSchema), - defaultValues: { comment: "", attachments: [] } + defaultValues: { comment: "", attachments: [] }, }); const { @@ -118,50 +118,41 @@ const JobComments = ({ data }) => { )} - -
- {/* LEFT SIDE → Uploaded Files */} -
- {files?.length > 0 && ( - - )} -
- - {/* RIGHT SIDE → Add Attachment + Submit */} -
-
document.getElementById("attachments").click()} - className="cursor-pointer" - style={{ whiteSpace: 'nowrap' }} - > - { - onFileChange(e); - e.target.value = ""; - }} - /> - - Add Attachment -
- - - -
+
+ {files?.length > 0 && ( + + )}
+
+
document.getElementById("attachments").click()} + className="cursor-pointer" + style={{ whiteSpace: "nowrap" }} + > + { + onFileChange(e); + e.target.value = ""; + }} + /> + + Add Attachment +
+ +
diff --git a/src/components/ServiceProject/JobList.jsx b/src/components/ServiceProject/JobList.jsx index fd0122ef..8a7f1639 100644 --- a/src/components/ServiceProject/JobList.jsx +++ b/src/components/ServiceProject/JobList.jsx @@ -1,5 +1,9 @@ import React, { useState } from "react"; -import { daysLeft, getNextBadgeColor } from "../../utils/appUtils"; +import { + daysLeft, + getJobStatusBadge, + getNextBadgeColor, +} from "../../utils/appUtils"; import { useServiceProjectJobs } from "../../hooks/useServiceProject"; import { ITEMS_PER_PAGE } from "../../utils/constants"; import EmployeeAvatarGroup from "../common/EmployeeAvatarGroup"; @@ -23,7 +27,7 @@ const JobList = () => { { key: "jobTicketUId", label: "Job Id", - getValue: (e) => e?.jobTicketUId || "N/A", + getValue: (e) => {e?.jobTicketUId || "N/A"}, align: "text-start", }, { @@ -56,22 +60,9 @@ const JobList = () => { key: "status", label: "Status", getValue: (e) => { - const statusName = e?.status?.displayName || "N/A"; - const statusColorMap = { - Assigned: "label-primary", - Pending: "label-warning", - Completed: "label-success", - Cancelled: "label-danger", - }; - - const badgeColor = statusColorMap[statusName] || "label-secondary"; - return ( - - {statusName} + + {e?.status?.displayName} ); }, @@ -85,7 +76,7 @@ const JobList = () => { const { days, color } = daysLeft(e.startDate, e.dueDate); return ( - + {days !== null ? `${days} days` : "N/A"} ); @@ -96,7 +87,7 @@ const JobList = () => { ]; return ( -
+
{ {Array.isArray(data?.data) && data.data.length > 0 ? ( data.data.map((row, i) => ( - + {jobGrid.map((col) => ( - ))} diff --git a/src/components/ServiceProject/JobStatusLog.jsx b/src/components/ServiceProject/JobStatusLog.jsx index 6e97f9e9..88ff3562 100644 --- a/src/components/ServiceProject/JobStatusLog.jsx +++ b/src/components/ServiceProject/JobStatusLog.jsx @@ -1,5 +1,6 @@ import React from "react"; import Avatar from "../common/Avatar"; +import { formatUTCToLocalTime } from "../../utils/dateUtils"; const JobStatusLog = ({ data }) => { return ( @@ -20,9 +21,9 @@ const JobStatusLog = ({ data }) => { - {/* - Level {item.nextStatus?.level ?? item.status?.level} - */} + + {formatUTCToLocalTime(item?.updatedAt,true)} +
{ const { projectId } = useParams(); @@ -45,33 +47,18 @@ const ManageJobTicket = ({ Job }) => { return (
-
{data?.title}
-
+
{data?.title}
+

Job Id : {data?.jobTicketUId || "N/A"}

- {data?.dueDate && - (() => { - const { days, color } = daysLeft( - data?.startDate, - data?.dueDate - ); - return ( - - Days Left: - - {days !== null ? `${days} days` : "N/A"} - - - ); - })()}
- - {data?.status?.name} + + {data?.status?.displayName} - { /> } > - - + }/> + }
- -

{data?.description || "N/A"}

@@ -106,7 +91,7 @@ const ManageJobTicket = ({ Job }) => {
-
+
Start Date :{" "} @@ -118,45 +103,73 @@ const ManageJobTicket = ({ Job }) => { {formatUTCToLocalTime(data?.startDate)}
+ {data?.dueDate && + (() => { + const { days, color } = daysLeft( + data?.startDate, + data?.dueDate + ); + return ( + + Days Left: + + {days !== null ? `${days} days` : "N/A"} + + + ); + })()}
-
- Created By{" "} - {" "} -
-

{`${data?.createdBy?.firstName} ${data?.createdBy?.lastName}`}

- - ({data?.createdBy?.jobRoleName}) - +
+
+ Created By +
+
+ {" "} +
+

{`${data?.createdBy?.firstName} ${data?.createdBy?.lastName}`}

+ + ({data?.createdBy?.jobRoleName}) + +
-
- Assigned By -
- {data?.assignees?.map((emp) => ( -
-
- -
- - {emp.firstName} {emp.lastName} - - - {emp.jobRoleName} - +
+
+ Assigned To +
+ +
+
+ {data?.assignees?.map((emp) => ( +
+
+ + +
+ + {emp.firstName} {emp.lastName} + + + {emp.jobRoleName} + +
-
- ))} + ))} +
diff --git a/src/components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamAllocation.jsx b/src/components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamAllocation.jsx index 50b834ac..37005288 100644 --- a/src/components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamAllocation.jsx +++ b/src/components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamAllocation.jsx @@ -123,12 +123,14 @@ const ServiceProjectTeamAllocation = () => {
- {!isAddEmployee && } + {!isAddEmployee && ( + + )}
{isAddEmployee && ( @@ -166,18 +168,18 @@ const ServiceProjectTeamAllocation = () => { )}
- { isAddEmployee && ( -
+ {isAddEmployee && ( +
{" "} - +
)}
-
- setSelectedJob({ showCanvas: true, job: row?.id }) - }> + + setSelectedJob({ showCanvas: true, job: row?.id }) + } + > {col.getValue(row)}
+
@@ -202,29 +204,30 @@ const ServiceProjectTeamAllocation = () => { Team?.map((emp) => ( + - @@ -233,18 +236,12 @@ const ServiceProjectTeamAllocation = () => { diff --git a/src/components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamList.jsx b/src/components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamList.jsx index ee98305a..ff2acdd7 100644 --- a/src/components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamList.jsx +++ b/src/components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamList.jsx @@ -15,14 +15,17 @@ const ServiceProjectTeamList = () => { { key: "employeName", label: "Name", - getValue: (e) => { - return ( -
- - {`${e.employee.firstName} ${e.employee.lastName}`} -
- ); - }, + getValue: (e) => ( +
+ {" "} + + {`${e.employee.firstName} ${e.employee.lastName}`} +
+ ), + align: "text-start", }, { key: "teamRole", @@ -30,66 +33,51 @@ const ServiceProjectTeamList = () => { getValue: (e) => { return (
- {`${e.teamRole.firstName}`} + {`${e.teamRole.name}`}
); }, + align: "text-start", }, { key: "assignedAt", label: "assigned Date", - getValue: (e) => { - return ( -
- {formatUTCToLocalTime(e.assignedAT)} -
- ); - }, + getValue: (e) => + formatUTCToLocalTime(e.assignedAT) + , + align: "text-center", }, ]; return ( -
-
Name
-
- {" "} +
- {`${emp?.employee?.firstName} ${emp?.employee?.lastName}`} + + {emp?.employee?.firstName} {emp?.employee?.lastName} +
{emp?.teamRole?.name} - {deletingEmp?.emplyee?.id === emp.id && isPending ? ( -
+ +
+ {deletingEmp?.employee?.id === emp.id && isPending ? ( +
) : ( - - - setSeletingEmp({ employee: emp, isOpen: true }) - } - > - + + setSeletingEmp({ employee: emp, isOpen: true }) + } + > )}
{isTeamLoading ? ( ) : ( -
- -

- {" "} - Please Add a Employee{" "} -

-
+

No Records Found

)}
+
+
{servceProjectColmen.map((col) => ( - + ))} {data?.length > 0 ? ( - data.map((emp) => ( - - - - - - + data.map((row) => ( + + {servceProjectColmen.map((col) => ( + + ))} )) ) : ( - diff --git a/src/components/common/Forms/InputFieldSuggesstion.jsx b/src/components/common/Forms/InputFieldSuggesstion.jsx new file mode 100644 index 00000000..881eb16c --- /dev/null +++ b/src/components/common/Forms/InputFieldSuggesstion.jsx @@ -0,0 +1,56 @@ +import React from 'react' + +const InputFieldSuggesstion = () => { + return ( +
+ setTimeout(() => setShowSuggestions(false), 150)} + onFocus={() => { + if (value) setShowSuggestions(true); + }} + disabled={disabled} + /> + {showSuggestions && filteredList.length > 0 && ( +
    + {filteredList.map((org) => ( +
  • handleSelectSuggestion(org)} + onMouseEnter={(e) => + (e.currentTarget.style.backgroundColor = "#f8f9fa") + } + onMouseLeave={(e) => + (e.currentTarget.style.backgroundColor = "transparent") + } + > + {org} +
  • + ))} +
+ )} + + {error && {error}} +
+ ) +} + +export default InputFieldSuggesstion diff --git a/src/components/common/Forms/SelectFieldServerSide.jsx b/src/components/common/Forms/SelectFieldServerSide.jsx index 5d398932..f7aa70eb 100644 --- a/src/components/common/Forms/SelectFieldServerSide.jsx +++ b/src/components/common/Forms/SelectFieldServerSide.jsx @@ -181,5 +181,173 @@ const SelectEmployeeServerSide = ({ ); }; - export default SelectEmployeeServerSide; + + +export const SelectProjectField = ()=>{ + const [searchText, setSearchText] = useState(""); + const debounce = useDebounce(searchText, 300); + + const { data, isLoading } = useEmployeesName( + projectId, + debounce, + isAllEmployee + ); + + const options = data?.data ?? []; + const [open, setOpen] = useState(false); + const dropdownRef = useRef(null); + + const getDisplayName = (emp) => { + if (!emp) return ""; + return `${emp.firstName || ""} ${emp.lastName || ""}`.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 ( +
+ {label && ( + + )} + + {/* MAIN BUTTON */} + + + {/* DROPDOWN */} + {open && ( +
    +
    + setSearchText(e.target.value)} + className="form-control form-control-sm" + placeholder="Search..." + /> +
    + + {isLoading && ( +
  • Loading...
  • + )} + + {!isLoading && options.length === 0 && ( +
  • No results found
  • + )} + + {!isLoading && + options.map((option) => { + const isActive = isMultiple + ? selectedList.some((x) => x[valueKey] === option[valueKey]) + : selectedSingle && + selectedSingle[valueKey] === option[valueKey]; + + return ( +
  • + +
  • + ); + })} +
+ )} +
+ ); +}; \ No newline at end of file diff --git a/src/components/common/OffcanvasComponent.jsx b/src/components/common/OffcanvasComponent.jsx index f9e2d1c1..88d3ba57 100644 --- a/src/components/common/OffcanvasComponent.jsx +++ b/src/components/common/OffcanvasComponent.jsx @@ -66,7 +66,7 @@ const OffcanvasComponent = ({ > -
{children}
+
{children}
); diff --git a/src/utils/appUtils.js b/src/utils/appUtils.js index 563520be..7daa6f5e 100644 --- a/src/utils/appUtils.js +++ b/src/utils/appUtils.js @@ -34,8 +34,24 @@ export const getColorNameFromHex = (hex) => { } } - return null; // + return null; }; +export const getJobStatusBadge = (statusId) => { + if (!statusId) return "bg-label-secondary"; + + const map = { + "32d76a02-8f44-4aa0-9b66-c3716c45a918": "bg-label-primary", // New + "cfa1886d-055f-4ded-84c6-42a2a8a14a66": "bg-label-info", // Assigned + "5a6873a5-fed7-4745-a52f-8f61bf3bd72d": "bg-label-warning", // In Progress + "aab71020-2fb8-44d9-9430-c9a7e9bf33b0": "bg-label-label-dark", // Review + "ed10ab57-dbaa-4ca5-8ecd-56745dcbdbd7": "bg-label-success", // Done + "3ddeefb5-ae3c-4e10-a922-35e0a452bb69": "bg-label-secondary", // Closed + "75a0c8b8-9c6a-41af-80bf-b35bab722eb2": "bg-label-danger", // On Hold + }; + + return map[statusId] || "bg-label-secondary"; +}; + export const useDebounce = (value, delay = 500) => { const [debouncedValue, setDebouncedValue] = useState(value); diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx index 434a833b..24121cb0 100644 --- a/src/utils/constants.jsx +++ b/src/utils/constants.jsx @@ -205,3 +205,9 @@ export const PAYEE_RECURRING_EXPENSE = [ label: "Paused", }, ]; + + +//#region Service Project and Jobs +export const STATUS_JOB_DONE = "ed10ab57-dbaa-4ca5-8ecd-56745dcbdbd7" + +//#endregion \ No newline at end of file
{col.label}{col.label}
-
- - - {`${emp?.employee?.firstName} ${emp?.employee?.lastName}`} - -
-
{emp?.teamRole?.name}{formatUTCToLocalTime(emp.assignedAt)}
{col.getValue(row)}
+ {isLoading ? ( ) : ( -
- -

Please Add an Employee

-
+
No Records Available
)}