-
+
diff --git a/src/components/ServiceProject/ManageJobTicket.jsx b/src/components/ServiceProject/ManageJobTicket.jsx
new file mode 100644
index 00000000..3d422a83
--- /dev/null
+++ b/src/components/ServiceProject/ManageJobTicket.jsx
@@ -0,0 +1,71 @@
+import React from "react";
+import { useServiceProjectJobDetails } from "../../hooks/useServiceProject";
+import { SpinnerLoader } from "../common/Loader";
+import Error from "../common/Error";
+import { formatUTCToLocalTime } from "../../utils/dateUtils";
+import Avatar from "../common/Avatar";
+import EmployeeAvatarGroup from "../common/EmployeeAvatarGroup";
+
+const ManageJobTicket = ({ Job }) => {
+ const { data, isLoading, isError, error } = useServiceProjectJobDetails(
+ Job?.job
+ );
+ if (isLoading) return
;
+ if (isError)
+ return (
+
+
+
+ );
+ return (
+
+
+
{data?.title}
+
+
+ {" "}
+ {formatUTCToLocalTime(data?.createdAt, true)}
+
+
{data?.status?.name}
+
+
+
+
+ Start Date : {formatUTCToLocalTime(data?.startDate)}
+ {" "}
+ {" "}
+
+ Due To : {formatUTCToLocalTime(data?.startDate)}
+
+
+
+
+
+ Created By
+ {" "}
+
{" "}
+
{`${data?.createdBy?.firstName} ${data?.createdBy?.lastName}`}
+
+
+
+ Created By
+
+
+
+
+
+
+
+ );
+};
+
+export default ManageJobTicket;
diff --git a/src/components/ServiceProject/ServiceProjectSchema.jsx b/src/components/ServiceProject/ServiceProjectSchema.jsx
index 9f660f14..349e8070 100644
--- a/src/components/ServiceProject/ServiceProjectSchema.jsx
+++ b/src/components/ServiceProject/ServiceProjectSchema.jsx
@@ -31,6 +31,7 @@ export const defaultProjectValues = {
//#region JobSchema
+
export const TagSchema = z.object({
name: z.string().min(1, "Tag name is required"),
isActive: z.boolean().default(true),
diff --git a/src/components/common/Forms/SelectField.jsx b/src/components/common/Forms/SelectField.jsx
new file mode 100644
index 00000000..a08eaf8c
--- /dev/null
+++ b/src/components/common/Forms/SelectField.jsx
@@ -0,0 +1,94 @@
+import React, { useEffect, useRef, useState } from "react";
+import Label from "../Label";
+
+const SelectField = ({
+ label = "Select",
+ options = [],
+ placeholder = "Select Option",
+ required = false,
+ value,
+ onChange,
+ valueKey = "id",
+ labelKey = "name",
+ isLoading = false,
+}) => {
+ const [open, setOpen] = useState(false);
+ const dropdownRef = useRef(null);
+
+ useEffect(() => {
+ const handleClickOutside = (event) => {
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
+ setOpen(false);
+ }
+ };
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => document.removeEventListener("mousedown", handleClickOutside);
+ }, []);
+
+ const selectedOption = options.find((opt) => opt[valueKey] === value);
+
+ const displayText = selectedOption ? selectedOption[labelKey] : placeholder;
+
+ const handleSelect = (option) => {
+ onChange(option[valueKey]);
+ setOpen(false);
+ };
+
+ const toggleDropdown = () => setOpen((prev) => !prev);
+
+ return (
+
+ {label && (
+
+ )}
+
+
+
+ {open && !isLoading && (
+
+ {options.map((option, i) => (
+ -
+
+
+ ))}
+
+ )}
+
+ );
+};
+
+export default SelectField;
diff --git a/src/components/common/Forms/SelectFieldServerSide.jsx b/src/components/common/Forms/SelectFieldServerSide.jsx
new file mode 100644
index 00000000..c0746775
--- /dev/null
+++ b/src/components/common/Forms/SelectFieldServerSide.jsx
@@ -0,0 +1,98 @@
+import React, { useEffect, useRef, useState } from "react";
+import Label from "../Label";
+import { useDebounce } from "../../../utils/appUtils";
+
+const SelectFieldServerSide = ({
+ label = "Select",
+ options = [],
+ placeholder = "Select Option",
+ required = false,
+ value,
+ onChange,
+ valueKey = "id",
+ labelKey = "name",
+ isLoading = false,
+}) => {
+ const [searchText,setSeachText] = useState("")
+ const debounce = useDebounce(searchText,300);
+// const {} = use
+ const [open, setOpen] = useState(false);
+ const dropdownRef = useRef(null);
+
+ useEffect(() => {
+ const handleClickOutside = (event) => {
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
+ setOpen(false);
+ }
+ };
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => document.removeEventListener("mousedown", handleClickOutside);
+ }, []);
+
+ const selectedOption = options.find((opt) => opt[valueKey] === value);
+
+ const displayText = selectedOption ? selectedOption[labelKey] : placeholder;
+
+ const handleSelect = (option) => {
+ onChange(option[valueKey]);
+ setOpen(false);
+ };
+
+ const toggleDropdown = () => setOpen((prev) => !prev);
+
+ return (
+
+ {label && (
+
+ )}
+
+
+
+ {open && !isLoading && (
+
+ {options.map((option, i) => (
+ -
+
+
+ ))}
+
+ )}
+
+ );
+};
+
+export default SelectFieldServerSide;
diff --git a/src/components/common/OffcanvasComponent.jsx b/src/components/common/OffcanvasComponent.jsx
new file mode 100644
index 00000000..970785ae
--- /dev/null
+++ b/src/components/common/OffcanvasComponent.jsx
@@ -0,0 +1,67 @@
+import React, { useEffect, useRef } from "react";
+
+const OffcanvasComponent = ({
+ id = "globalOffcanvas",
+ title = "Offcanvas Title",
+ placement = "start", // start | end | top | bottom
+ children,
+ show = false,
+ onClose = () => {},
+}) => {
+ const offcanvasRef = useRef(null);
+ const bsInstance = useRef(null);
+
+ useEffect(() => {
+ if (!offcanvasRef.current) return;
+
+ // initialize once
+ bsInstance.current = bootstrap.Offcanvas.getOrCreateInstance(offcanvasRef.current);
+
+ const el = offcanvasRef.current;
+ const handleHide = () => onClose();
+
+ el.addEventListener("hidden.bs.offcanvas", handleHide);
+
+ return () => {
+ el.removeEventListener("hidden.bs.offcanvas", handleHide);
+ };
+ }, [onClose]);
+
+ // react to `show` changes
+ useEffect(() => {
+ if (!bsInstance.current) return;
+
+ if (show) bsInstance.current.show();
+ else bsInstance.current.hide();
+ }, [show]);
+
+ return (
+
+ );
+};
+
+export default OffcanvasComponent;
diff --git a/src/hooks/appHooks/useAppForm.js b/src/hooks/appHooks/useAppForm.js
new file mode 100644
index 00000000..ea751205
--- /dev/null
+++ b/src/hooks/appHooks/useAppForm.js
@@ -0,0 +1,6 @@
+import { useForm, Controller,FormProvider } from "react-hook-form";
+
+export const useAppForm = (config) => useForm(config);
+export const AppFormProvider = FormProvider;
+export const AppFormController = Controller;
+
diff --git a/src/hooks/useServiceProject.jsx b/src/hooks/useServiceProject.jsx
index 46f88f23..3e13b848 100644
--- a/src/hooks/useServiceProject.jsx
+++ b/src/hooks/useServiceProject.jsx
@@ -112,6 +112,16 @@ export const useServiceProjectJobs=(pageSize,pageNumber,isActive=true,project)=>
}
})
}
+export const useServiceProjectJobDetails = (job)=>{
+ return useQuery({
+ queryKey:['service-job',job],
+ queryFn:async() =>{
+ const resp = await ServiceProjectRepository.GetJobDetails(job);
+ return resp.data;
+ },
+ enabled:!!job
+ })
+}
export const useCreateServiceProjectJob = (onSuccessCallback) => {
const queryClient = useQueryClient();
diff --git a/src/repositories/ServiceProjectRepository.jsx b/src/repositories/ServiceProjectRepository.jsx
index 22fb4ba0..541d63de 100644
--- a/src/repositories/ServiceProjectRepository.jsx
+++ b/src/repositories/ServiceProjectRepository.jsx
@@ -19,4 +19,5 @@ export const ServiceProjectRepository = {
api.get(
`/api/ServiceProject/job/list?pageSize=${pageSize}&pageNumber=${pageNumber}&isActive=${isActive}&projectId=${projectId}`
),
+ GetJobDetails:(id)=>api.get(`/api/ServiceProject/job/details/${id}`)
};