Merge branch 'Project_Branch_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Project_Branch_Management

This commit is contained in:
Kartik Sharma 2025-11-19 18:32:03 +05:30
commit e7a68aeab7
5 changed files with 205 additions and 5 deletions

View File

@ -4,6 +4,7 @@ import Label from "../common/Label";
import { zodResolver } from "@hookform/resolvers/zod";
import { defaultJobValue, jobSchema } from "./ServiceProjectSchema";
import {
useBranches,
useCreateServiceProjectJob,
useJobTags,
useServiceProjectJobDetails,
@ -25,6 +26,7 @@ import { useParams } from "react-router-dom";
import { useDispatch } from "react-redux";
import { useJobStatus } from "../../hooks/masterHook/useMaster";
import { useServiceProjectJobContext } from "./Jobs";
import { SelectFieldSearch } from "../common/Forms/SelectFieldServerSide";
const ManageJob = ({ Job }) => {
const { setManageJob, setSelectedJob } = useServiceProjectJobContext();
@ -39,7 +41,7 @@ const ManageJob = ({ Job }) => {
control,
watch,
handleSubmit,
reset,
reset,setValue,
formState: { errors },
} = methods;
@ -199,7 +201,7 @@ const ManageJob = ({ Job }) => {
/>
</div>
<div className="col-12 col-md-6 mb-2 mb-md-4">
<Label required>Select Employee</Label>
<Label >Select Employee</Label>
<PmsEmployeeInputTag
control={control}
name="assignees"
@ -238,7 +240,21 @@ const ManageJob = ({ Job }) => {
name="tags"
label="Tag"
placeholder="Enter Tag"
/>
</div>
<div className="col-12 col-md-6 mb-2 mb-md-4">
<SelectFieldSearch
label="Select Branch"
placeholder="Select Branch"
required
value={watch("branchId")}
onChange={(val) => setValue("branchId", val)}
valueKey="id"
labelKey="branchName"
hookParams={[projectId,true,10,1]}
useFetchHook={useBranches}
isMultiple={false}
/>
</div>
<div className="col-12">

View File

@ -70,6 +70,8 @@ export const jobSchema = z.object({
tags: z.array(TagSchema).optional().default([]),
statusId: z.string().optional().nullable(),
branchId: z.string().optional().nullable(),
});
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
@ -109,6 +111,7 @@ export const defaultJobValue = {
startDate: null,
dueDate: null,
tags: [],
branchId: null,
};
//#endregion

View File

@ -361,3 +361,182 @@ export const SelectProjectField = ({
</div>
);
};
export const SelectFieldSearch = ({
label = "Select",
placeholder = "Select ",
required = false,
value = null,
onChange,
valueKey = "id",
labelKey = "name",
isFullObject = false,
isMultiple = false,
hookParams,
useFetchHook,
}) => {
const [searchText, setSearchText] = useState("");
const debounce = useDebounce(searchText, 300);
const { data, isLoading } = useFetchHook(...hookParams,debounce);
const options = data?.data ?? [];
const [open, setOpen] = useState(false);
const dropdownRef = useRef(null);
const getDisplayName = (entity) => {
if (!entity) return "";
return `${entity[labelKey] || ""}`.trim();
};
/** -----------------------------
* SELECTED OPTION (SINGLE)
* ----------------------------- */
let selectedSingle = null;
if (!isMultiple) {
if (isFullObject && value) selectedSingle = value;
else if (!isFullObject && value)
selectedSingle = options.find((o) => o[valueKey] === value);
}
/** -----------------------------
* SELECTED OPTION (MULTIPLE)
* ----------------------------- */
let selectedList = [];
if (isMultiple && Array.isArray(value)) {
if (isFullObject) selectedList = value;
else {
selectedList = options.filter((opt) => value.includes(opt[valueKey]));
}
}
/** Main button label */
const displayText = !isMultiple
? getDisplayName(selectedSingle) || placeholder
: selectedList.length > 0
? selectedList.map((e) => getDisplayName(e)).join(", ")
: placeholder;
/** -----------------------------
* HANDLE OUTSIDE CLICK
* ----------------------------- */
useEffect(() => {
const handleClickOutside = (e) => {
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
setOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
/** -----------------------------
* HANDLE SELECT
* ----------------------------- */
const handleSelect = (option) => {
if (!isMultiple) {
// SINGLE SELECT
if (isFullObject) onChange(option);
else onChange(option[valueKey]);
} else {
// MULTIPLE SELECT
let updated = [];
const exists = selectedList.some((e) => e[valueKey] === option[valueKey]);
if (exists) {
// remove
updated = selectedList.filter((e) => e[valueKey] !== option[valueKey]);
} else {
// add
updated = [...selectedList, option];
}
if (isFullObject) onChange(updated);
else onChange(updated.map((x) => x[valueKey]));
}
};
return (
<div className="mb-3 position-relative" ref={dropdownRef}>
{label && (
<Label className="form-label" required={required}>
{label}
</Label>
)}
{/* MAIN BUTTON */}
<button
type="button"
className={`select2-icons form-select d-flex align-items-center justify-content-between ${
open ? "show" : ""
}`}
onClick={() => setOpen((prev) => !prev)}
>
<span className={`text-truncate ${!displayText ? "text-muted" : ""}`}>
{displayText}
</span>
</button>
{/* DROPDOWN */}
{open && (
<ul
className="dropdown-menu w-100 shadow-sm show animate__fadeIn h-64 overflow-auto rounded"
style={{
position: "absolute",
top: "100%",
left: 0,
zIndex: 1050,
marginTop: "2px",
borderRadius: "0.375rem",
overflow: "hidden",
}}
>
<div className="p-1">
<input
type="search"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
className="form-control form-control-sm"
placeholder="Search..."
/>
</div>
{isLoading && (
<li className="dropdown-item text-muted text-center">Loading...</li>
)}
{!isLoading && options.length === 0 && (
<li className="dropdown-item text-muted text-center">
No results found
</li>
)}
{!isLoading &&
options.map((option) => {
const isActive = isMultiple
? selectedList.some((x) => x[valueKey] === option[valueKey])
: selectedSingle &&
selectedSingle[valueKey] === option[valueKey];
return (
<li key={option[valueKey]}>
<button
type="button"
className={`dropdown-item ${isActive ? "active" : ""}`}
onClick={() => handleSelect(option)}
>
{getDisplayName(option)}
</button>
</li>
);
})}
</ul>
)}
</div>
);
};

View File

@ -308,13 +308,15 @@ export const useBranches = (
pageNumber,
searchString
);
console.log(resp)
return resp.data;
},
enabled: !!projectId,
});
};
export const useBranch = (id) => {
export const useBranch = (id)=>{
return useQuery({
queryKey: ["branch", id],
queryFn: async () => {

View File

@ -45,8 +45,8 @@ export const ServiceProjectRepository = {
UpdateBranch: (id, data) =>
api.put("/api/ServiceProject/branch/edit/${id}", data),
GetBranchList: (projectId, isActive, pageSize, pageNumber, searchString) => {
api.get(
`/api/ServiceProject/branch/list/${projectId}/?isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&searchString=${searchString}`
return api.get(
`/api/ServiceProject/branch/list/${projectId}?isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&searchString=${searchString}`
);
},