added branch for projects
This commit is contained in:
parent
7e4dffff34
commit
e7fddd41c2
@ -40,7 +40,6 @@ const ActionPaymentRequest = ({ requestId }) => {
|
||||
error: PaymentModeError,
|
||||
} = usePaymentMode();
|
||||
|
||||
console.log("Kartik", data)
|
||||
|
||||
const IsReview = useHasUserPermission(REVIEW_EXPENSE);
|
||||
const [imageLoaded, setImageLoaded] = useState({});
|
||||
|
||||
@ -82,7 +82,6 @@ const EditActivityModal = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (!workItem) return;
|
||||
console.log(workItem)
|
||||
reset({
|
||||
activityID: String(
|
||||
workItem?.workItem?.activityId || workItem?.activityMaster?.id
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const BranchSchema = () =>
|
||||
z.object({
|
||||
projectId: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, { message: "Project is required" }),
|
||||
|
||||
branchName: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, { message: "Branch Name is required" }),
|
||||
|
||||
contactInformation: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, { message: "Contact Information is required" }),
|
||||
|
||||
address: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, { message: "Address is required" }),
|
||||
|
||||
branchType: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, { message: "Branch Type is required" }),
|
||||
|
||||
googleMapUrl: z
|
||||
.string()
|
||||
.trim()
|
||||
.url({ message: "Enter a valid Google Map URL" }),
|
||||
});
|
||||
|
||||
export const defaultBranches = {
|
||||
branchName: "",
|
||||
projectId: "",
|
||||
contactInformation: "",
|
||||
address: "",
|
||||
branchType: "",
|
||||
googleMapUrl: "",
|
||||
};
|
||||
@ -1,186 +0,0 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { useProjectName } from '../../hooks/useProjects';
|
||||
import { BranchSchema, defaultBranches } from './BranchSchema';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import Label from '../common/Label';
|
||||
import { useCreateBranch, useServiceProjects, useUpdateBranch } from '../../hooks/useServiceProject';
|
||||
import { useAppForm } from '../../hooks/appHooks/useAppForm';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
const ManageBranch = ({ closeModal, BranchToEdit = null }) => {
|
||||
const { data } = {}
|
||||
const { projectId } = useParams();
|
||||
const schema = BranchSchema();
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
watch,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = useAppForm({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
...defaultBranches,
|
||||
projectId: projectId || ""
|
||||
},
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
reset();
|
||||
closeModal();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (BranchToEdit && data) {
|
||||
reset({
|
||||
branchName: data.branchName || "",
|
||||
projectId: data.project?.id || projectId || "",
|
||||
contactInformation: data.contactInformation || "",
|
||||
address: data.address || "",
|
||||
branchType: data.branchType || "",
|
||||
googleMapUrl: data.googleMapUrl || "",
|
||||
});
|
||||
}
|
||||
}, [data, reset]);
|
||||
|
||||
const { mutate: CreateServiceBranch, isPending: createPending } =
|
||||
useCreateBranch(() => {
|
||||
handleClose();
|
||||
});
|
||||
const { mutate: ServiceBranchUpdate, isPending } =
|
||||
useUpdateBranch(() => handleClose());
|
||||
|
||||
const onSubmit = (fromdata) => {
|
||||
let payload = {
|
||||
...fromdata, projectId,
|
||||
};
|
||||
if (BranchToEdit) {
|
||||
const editPayload = { ...payload, id: data.id };
|
||||
ServiceBranchUpdate({ id: data.id, payload: editPayload });
|
||||
} else {
|
||||
CreateServiceBranch(payload);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container p-3">
|
||||
<h5 className="m-0">
|
||||
{BranchToEdit
|
||||
? "Update Branch"
|
||||
: "Create Branch"}
|
||||
</h5>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="row my-2 text-start">
|
||||
|
||||
<div className="col-md-6">
|
||||
<Label htmlFor="branchName" className="form-label" required>
|
||||
Branch Name
|
||||
</Label>
|
||||
<input
|
||||
type="text"
|
||||
id="branchName"
|
||||
className="form-control form-control-sm"
|
||||
{...register("branchName")}
|
||||
placeholder="Enter Branch"
|
||||
/>
|
||||
{errors.branchName && (
|
||||
<small className="danger-text">{errors.branchName.message}</small>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<Label htmlFor="contactInformation" className="form-label" required>
|
||||
Contact Information
|
||||
</Label>
|
||||
<input
|
||||
type="text"
|
||||
id="contactInformation"
|
||||
className="form-control form-control-sm"
|
||||
{...register("contactInformation")}
|
||||
|
||||
/>
|
||||
{errors.contactInformation && (
|
||||
<small className="danger-text">{errors.contactInformation.message}</small>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row my-2 text-start">
|
||||
|
||||
|
||||
<div className="col-md-6">
|
||||
<Label htmlFor="address" className="form-label" required>
|
||||
Address
|
||||
</Label>
|
||||
<input
|
||||
type="text"
|
||||
id="address"
|
||||
className="form-control form-control-sm"
|
||||
{...register("address")}
|
||||
|
||||
/>
|
||||
{errors.address && (
|
||||
<small className="danger-text">{errors.address.message}</small>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<Label htmlFor="branchType" className="form-label" required>
|
||||
Branch Type
|
||||
</Label>
|
||||
<input
|
||||
type="text"
|
||||
id="branchType"
|
||||
className="form-control form-control-sm"
|
||||
{...register("branchType")}
|
||||
/>
|
||||
{errors.branchType && (
|
||||
<small className="danger-text">{errors.branchType.message}</small>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="row my-2 text-start">
|
||||
|
||||
|
||||
<div className="col-md-6">
|
||||
<Label htmlFor="googleMapUrl" className="form-label" required>
|
||||
Google Map URL
|
||||
</Label>
|
||||
<input
|
||||
type="text"
|
||||
id="googleMapUrl"
|
||||
className="form-control form-control-sm"
|
||||
{...register("googleMapUrl")}
|
||||
|
||||
/>
|
||||
{errors.googleMapUrl && (
|
||||
<small className="danger-text">{errors.googleMapUrl.message}</small>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="d-flex justify-content-end gap-3">
|
||||
<button
|
||||
type="reset"
|
||||
onClick={handleClose}
|
||||
className="btn btn-label-secondary btn-sm mt-3"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
<button type="submit" className="btn btn-primary btn-sm mt-3">
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ManageBranch
|
||||
@ -41,7 +41,8 @@ const ManageJob = ({ Job }) => {
|
||||
control,
|
||||
watch,
|
||||
handleSubmit,
|
||||
reset,setValue,
|
||||
reset,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = methods;
|
||||
|
||||
@ -164,6 +165,7 @@ const ManageJob = ({ Job }) => {
|
||||
dueDate: JobData.dueDate ?? null,
|
||||
tags: JobData.tags ?? [],
|
||||
statusId: JobData.status.id,
|
||||
branchId : JobData?.projectBranch?.id
|
||||
});
|
||||
}, [JobData, Job, projectId]);
|
||||
return (
|
||||
@ -201,7 +203,7 @@ const ManageJob = ({ Job }) => {
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-6 mb-2 mb-md-4">
|
||||
<Label >Select Employee</Label>
|
||||
<Label>Select Employee</Label>
|
||||
<PmsEmployeeInputTag
|
||||
control={control}
|
||||
name="assignees"
|
||||
@ -240,21 +242,20 @@ 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]}
|
||||
hookParams={[projectId, true, 10, 1]}
|
||||
useFetchHook={useBranches}
|
||||
isMultiple={false}
|
||||
disabled={Job}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
|
||||
@ -124,10 +124,16 @@ const ManageJobTicket = ({ Job }) => {
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className="d-flex my-2">
|
||||
<span className="fw-semibold">
|
||||
{" "}
|
||||
<i className="bx bx-buildings me-1"></i>Branch Name :
|
||||
</span>
|
||||
</div>
|
||||
<div className="d-block mt-4 mb-3">
|
||||
<div className="row align-items-start align-items-md-start gap-2 mb-1">
|
||||
<div className="col-12 col-md-auto">
|
||||
<small className="fs-6 fw-medium">Created By</small>
|
||||
<small className="fs-6 ">Created By</small>
|
||||
</div>
|
||||
<div className="col d-flex flex-row align-items-center ">
|
||||
<Avatar
|
||||
@ -147,7 +153,7 @@ const ManageJobTicket = ({ Job }) => {
|
||||
{data?.assignees?.length > 0 && (
|
||||
<div className="row align-items-start align-items-md-start gap-2">
|
||||
<div className="col-12 col-md-auto">
|
||||
<small className="fs-6 fw-medium">Assigned To</small>
|
||||
<small className="fs-6">Assigned To</small>
|
||||
</div>
|
||||
|
||||
<div className="col">
|
||||
|
||||
@ -1,162 +0,0 @@
|
||||
import React, { useState } from 'react'
|
||||
import GlobalModel from '../common/GlobalModel';
|
||||
import ManageBranch from './ManageBranch';
|
||||
import { Pagination } from 'swiper/modules';
|
||||
import { useBranches } from '../../hooks/useServiceProject';
|
||||
import { ITEMS_PER_PAGE } from '../../utils/constants';
|
||||
import { useDebounce } from '../../utils/appUtils';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
const ServiceBranch = () => {
|
||||
|
||||
const [ManageServiceBranch, setManageServiceBranch] = useState({
|
||||
IsOpen: null,
|
||||
branchId: null,
|
||||
});
|
||||
|
||||
const { projectId } = useParams();
|
||||
|
||||
const [search, setSearch] = useState("");
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const debouncedSearch = useDebounce(search, 500);
|
||||
|
||||
const { data, isLoading, isError, error } = useBranches(
|
||||
projectId,
|
||||
true,
|
||||
ITEMS_PER_PAGE,
|
||||
currentPage,
|
||||
debouncedSearch,
|
||||
);
|
||||
const paginate = (page) => {
|
||||
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
|
||||
setCurrentPage(page);
|
||||
}
|
||||
};
|
||||
|
||||
const ServiceBranch = [
|
||||
{
|
||||
key: "branchName",
|
||||
label: "Branch Name",
|
||||
align: "text-start",
|
||||
getValue: (e) => e?.branchName || "N/A",
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="card h-100 table-responsive px-sm-4">
|
||||
<div className="card-datatable" id="payment-request-table">
|
||||
<div className="row align-items-center justify-content-end mt-3">
|
||||
<div className="col-md-4 col-sm-12 text-md-end text-end">
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setManageServiceBranch({
|
||||
IsOpen: true,
|
||||
branchId: null,
|
||||
})
|
||||
}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
<span className="d-none d-md-inline-block">
|
||||
Add Branch
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="mx-2">
|
||||
{/* {Array.isArray(filteredData) && filteredData.length > 0 && ( */}
|
||||
<table className="table border-top text-nowrap align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
{ServiceBranch.map((col) => (
|
||||
<th key={col.key} className={col.align}>
|
||||
{col.label}
|
||||
</th>
|
||||
))}
|
||||
<th className="text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{isLoading ? (
|
||||
<tr>
|
||||
<td colSpan={ServiceBranch.length + 1} className="text-center py-5">
|
||||
Loading...
|
||||
</td>
|
||||
</tr>
|
||||
) : isError ? (
|
||||
<tr>
|
||||
<td colSpan={ServiceBranch.length + 1} className="text-center py-5">
|
||||
{error?.message || "Error loading branches"}
|
||||
</td>
|
||||
</tr>
|
||||
) : data?.data?.length > 0 ? (
|
||||
data.data.map((branch) => (
|
||||
<tr key={branch.id}>
|
||||
{ServiceBranch.map((col) => (
|
||||
<td key={col.key} className={col.align}>
|
||||
{col.getValue(branch)}
|
||||
</td>
|
||||
))}
|
||||
<td className="text-center">
|
||||
<i
|
||||
className="bx bx-edit text-primary cursor-pointer"
|
||||
onClick={() =>
|
||||
setManageServiceBranch({
|
||||
IsOpen: true,
|
||||
branchId: branch.id,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={ServiceBranch.length + 1} className="text-center py-5">
|
||||
No Branch Found
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
{/* )} */}
|
||||
{/* {!filteredData ||
|
||||
filteredData.length === 0
|
||||
&& (
|
||||
<div className="d-flex justify-content-center align-items-center h-64">
|
||||
{isError ? (<p>{error.message}</p>) : (<p>No Recurring Expense Found</p>)}
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
|
||||
{ManageServiceBranch.IsOpen && (
|
||||
<GlobalModel
|
||||
isOpen
|
||||
size="lg"
|
||||
closeModal={() =>
|
||||
setManageServiceBranch({ IsOpen: null, branchId: null })
|
||||
}
|
||||
>
|
||||
<ManageBranch
|
||||
key={ManageServiceBranch.branchId ?? "new"}
|
||||
closeModal={() =>
|
||||
setManageServiceBranch({ IsOpen: null, branchId: null })
|
||||
}
|
||||
// requestToEdit={ManageServiceBranch.branchId}
|
||||
/>
|
||||
</GlobalModel>
|
||||
)}
|
||||
|
||||
{/* <Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={data?.totalPages}
|
||||
onPageChange={paginate}
|
||||
/> */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServiceBranch
|
||||
95
src/components/ServiceProject/ServiceProfile.jsx
Normal file
95
src/components/ServiceProject/ServiceProfile.jsx
Normal file
@ -0,0 +1,95 @@
|
||||
import React from 'react'
|
||||
import { formatUTCToLocalTime } from '../../utils/dateUtils'
|
||||
|
||||
const ServiceProfile = ({data,setIsOpenModal}) => {
|
||||
return (
|
||||
<div className="card mb-4 h-100">
|
||||
<div className="card-header text-start">
|
||||
<h5 className="card-action-title mb-0 ps-1">
|
||||
{" "}
|
||||
<i className="fa fa-building rounded-circle text-primary"></i>
|
||||
<span className="ms-2 fw-bold">Project Profile</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<ul className="list-unstyled my-3 ps-0 text-start">
|
||||
|
||||
<li className="d-flex mb-3">
|
||||
<div className="d-flex align-items-start" style={{ minWidth: "150px" }}>
|
||||
<i className="bx bx-cog"></i>
|
||||
<span className="fw-medium mx-2 text-nowrap">Name:</span>
|
||||
</div>
|
||||
|
||||
<div className="flex-grow-1 text-start text-wrap">
|
||||
{data.name}
|
||||
</div>
|
||||
</li>
|
||||
<li className="d-flex mb-3">
|
||||
<div className="d-flex align-items-center" style={{ width: '150px' }}>
|
||||
<i className="bx bx-fingerprint"></i>
|
||||
<span className="fw-medium mx-2">Nick Name:</span>
|
||||
</div>
|
||||
<span>{data.shortName}</span>
|
||||
</li>
|
||||
<li className="d-flex mb-3">
|
||||
<div className="d-flex align-items-center" style={{ width: '150px' }}>
|
||||
<i className="bx bx-check"></i>
|
||||
<span className="fw-medium mx-2">Assign Date:</span>
|
||||
</div>
|
||||
<span>
|
||||
{data.assignedDate ? formatUTCToLocalTime(data.assignedDate) : "NA"}
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li className="d-flex mb-3">
|
||||
<div className="d-flex align-items-center" style={{ width: '150px' }}>
|
||||
<i className="bx bx-trophy"></i>
|
||||
<span className="fw-medium mx-2">Status:</span>
|
||||
</div>
|
||||
<span>{data?.status.status}</span>
|
||||
</li>
|
||||
<li className="d-flex mb-3">
|
||||
<div className="d-flex align-items-center" style={{ width: '150px' }}>
|
||||
<i className="bx bx-user"></i>
|
||||
<span className="fw-medium mx-2">Contact:</span>
|
||||
</div>
|
||||
<span>{data.contactName}</span>
|
||||
</li>
|
||||
<li className="d-flex mb-3">
|
||||
{/* Label section with icon */}
|
||||
<div className="d-flex align-items-start" style={{ minWidth: "150px" }}>
|
||||
<i className="bx bx-flag mt-1"></i>
|
||||
<span className="fw-medium mx-2 text-nowrap">Address:</span>
|
||||
</div>
|
||||
|
||||
{/* Content section that wraps nicely */}
|
||||
<div className="flex-grow-1 text-start text-wrap">
|
||||
{data.address}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li className="d-flex justify-content-center mt-4">
|
||||
|
||||
<a className="d-flex justify-content-center mt-4">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-primary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#edit-project-modal"
|
||||
onClick={() => setIsOpenModal(true)}
|
||||
>
|
||||
Modify Details
|
||||
</button>
|
||||
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServiceProfile
|
||||
@ -0,0 +1,183 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useProjectName } from "../../../hooks/useProjects";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import Label from "../../common/Label";
|
||||
import {
|
||||
useCreateBranch,
|
||||
useServiceProjects,
|
||||
useUpdateBranch,
|
||||
} from "../../../hooks/useServiceProject";
|
||||
import { useAppForm } from "../../../hooks/appHooks/useAppForm";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { BranchSchema, defaultBranches } from "../ServiceProjectSchema";
|
||||
|
||||
const ManageBranch = ({ closeModal, BranchToEdit = null }) => {
|
||||
const { data } = {};
|
||||
const { projectId } = useParams();
|
||||
const schema = BranchSchema();
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
watch,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = useAppForm({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
...defaultBranches,
|
||||
projectId: projectId || "",
|
||||
},
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
reset();
|
||||
closeModal();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (BranchToEdit && data) {
|
||||
reset({
|
||||
branchName: data.branchName || "",
|
||||
projectId: data.project?.id || projectId || "",
|
||||
contactInformation: data.contactInformation || "",
|
||||
address: data.address || "",
|
||||
branchType: data.branchType || "",
|
||||
googleMapUrl: data.googleMapUrl || "",
|
||||
});
|
||||
}
|
||||
}, [data, reset]);
|
||||
|
||||
const { mutate: CreateServiceBranch, isPending: createPending } =
|
||||
useCreateBranch(() => {
|
||||
handleClose();
|
||||
});
|
||||
const { mutate: ServiceBranchUpdate, isPending } = useUpdateBranch(() =>
|
||||
handleClose()
|
||||
);
|
||||
|
||||
const onSubmit = (fromdata) => {
|
||||
let payload = {
|
||||
...fromdata,
|
||||
projectId,
|
||||
};
|
||||
if (BranchToEdit) {
|
||||
const editPayload = { ...payload, id: data.id };
|
||||
ServiceBranchUpdate({ id: data.id, payload: editPayload });
|
||||
} else {
|
||||
CreateServiceBranch(payload);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container p-3">
|
||||
<h5 className="m-0">
|
||||
{BranchToEdit ? "Update Branch" : "Create Branch"}
|
||||
</h5>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="row my-2 text-start">
|
||||
<div className="col-md-6">
|
||||
<Label htmlFor="branchName" className="form-label" required>
|
||||
Branch Name
|
||||
</Label>
|
||||
<input
|
||||
type="text"
|
||||
id="branchName"
|
||||
className="form-control form-control-sm"
|
||||
{...register("branchName")}
|
||||
placeholder="Enter Branch"
|
||||
/>
|
||||
{errors.branchName && (
|
||||
<small className="danger-text">{errors.branchName.message}</small>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<Label htmlFor="contactInformation" className="form-label" required>
|
||||
Contact Number
|
||||
</Label>
|
||||
<input
|
||||
type="text"
|
||||
id="contactInformation"
|
||||
className="form-control form-control-sm"
|
||||
{...register("contactInformation")}
|
||||
/>
|
||||
{errors.contactInformation && (
|
||||
<small className="danger-text">
|
||||
{errors.contactInformation.message}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row my-2 text-start">
|
||||
|
||||
<div className="col-md-6">
|
||||
<Label htmlFor="branchType" className="form-label" required>
|
||||
Branch Type
|
||||
</Label>
|
||||
<input
|
||||
type="text"
|
||||
id="branchType"
|
||||
className="form-control form-control-sm"
|
||||
{...register("branchType")}
|
||||
/>
|
||||
{errors.branchType && (
|
||||
<small className="danger-text">{errors.branchType.message}</small>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<Label htmlFor="googleMapUrl" className="form-label" required>
|
||||
Google Map URL
|
||||
</Label>
|
||||
<input
|
||||
type="text"
|
||||
id="googleMapUrl"
|
||||
className="form-control form-control-sm"
|
||||
{...register("googleMapUrl")}
|
||||
/>
|
||||
{errors.googleMapUrl && (
|
||||
<small className="danger-text">
|
||||
{errors.googleMapUrl.message}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row my-2 text-start">
|
||||
|
||||
<div className="col-12">
|
||||
<Label htmlFor="address" className="form-label" required>
|
||||
Address
|
||||
</Label>
|
||||
<textarea
|
||||
id="address"
|
||||
className="form-control form-control-sm"
|
||||
{...register("address")}
|
||||
/>
|
||||
{errors.address && (
|
||||
<small className="danger-text">{errors.address.message}</small>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="d-flex justify-content-end gap-3">
|
||||
<button
|
||||
type="reset"
|
||||
onClick={handleClose}
|
||||
className="btn btn-label-secondary btn-sm mt-3"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
<button type="submit" className="btn btn-primary btn-sm mt-3">
|
||||
{isPending ? "Please wait...":"Submit"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManageBranch;
|
||||
@ -0,0 +1,174 @@
|
||||
import React, { useState } from "react";
|
||||
import GlobalModel from "../../common/GlobalModel";
|
||||
import ManageBranch from "./ManageBranch";
|
||||
import { useBranches } from "../../../hooks/useServiceProject";
|
||||
import { ITEMS_PER_PAGE } from "../../../utils/constants";
|
||||
import { useDebounce } from "../../../utils/appUtils";
|
||||
import { useParams } from "react-router-dom";
|
||||
import Pagination from "../../common/Pagination";
|
||||
|
||||
const ServiceBranch = () => {
|
||||
const { projectId } = useParams();
|
||||
|
||||
const [manageState, setManageState] = useState({
|
||||
IsOpen: false,
|
||||
branchId: null,
|
||||
});
|
||||
|
||||
const [search, setSearch] = useState("");
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const debouncedSearch = useDebounce(search, 500);
|
||||
|
||||
const { data, isLoading, isError, error } = useBranches(
|
||||
projectId,
|
||||
true,
|
||||
ITEMS_PER_PAGE - 10,
|
||||
currentPage,
|
||||
debouncedSearch
|
||||
);
|
||||
|
||||
const paginate = (page) => {
|
||||
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
|
||||
setCurrentPage(page);
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
key: "branchName",
|
||||
label: "Name",
|
||||
align: "text-start",
|
||||
getValue: (e) => e?.branchName || "N/A",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="card h-100 table-responsive px-sm-4">
|
||||
<div className="card-datatable" id="payment-request-table">
|
||||
{/* Header Section */}
|
||||
<div className="row align-items-center justify-content-between mt-3 mx-1">
|
||||
<div className="col-md-6 col-sm-12 text-start">
|
||||
<h5 className="mb-0">
|
||||
<i className="bx bx-buildings text-primary me-2"></i>
|
||||
Branches
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<div className="col-md-3 col-sm-12 text-end">
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setManageState({
|
||||
IsOpen: true,
|
||||
branchId: null,
|
||||
})
|
||||
}
|
||||
>
|
||||
<i className="bx bx-sm bx-plus-circle me-2"></i>
|
||||
<span className="d-none d-md-inline-block">Add Branch</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-2 mt-3">
|
||||
<table className="table border-top text-nowrap align-middle table-borderless">
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.map((col) => (
|
||||
<th key={col.key} className={col.align}>
|
||||
{col.label}
|
||||
</th>
|
||||
))}
|
||||
<th className="text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{isLoading && (
|
||||
<tr>
|
||||
<td colSpan={columns.length + 1} className="text-center py-5">
|
||||
Loading...
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{isError && (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={columns.length + 1}
|
||||
className="text-center text-danger py-5"
|
||||
>
|
||||
{error?.message || "Error loading branches"}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{!isLoading &&
|
||||
!isError &&
|
||||
data?.data?.length > 0 &&
|
||||
data.data.map((branch) => (
|
||||
<tr key={branch.id}>
|
||||
{columns.map((col) => (
|
||||
<td key={col.key} className={`${col.align} py-3`}>
|
||||
{col.getValue(branch)}
|
||||
</td>
|
||||
))}
|
||||
<td className="text-center">
|
||||
<i
|
||||
className="bx bx-edit text-primary cursor-pointer"
|
||||
onClick={() =>
|
||||
setManageState({
|
||||
IsOpen: true,
|
||||
branchId: branch.id,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
{!isLoading &&
|
||||
!isError &&
|
||||
(!data?.data || data.data.length === 0) && (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={columns.length + 1}
|
||||
className="text-center py-12"
|
||||
>
|
||||
No Branch Found
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{data?.data?.length > 0 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={data.totalPages}
|
||||
onPageChange={paginate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{manageState.IsOpen && (
|
||||
<GlobalModel
|
||||
isOpen
|
||||
size="md"
|
||||
closeModal={() => setManageState({ IsOpen: false, branchId: null })}
|
||||
>
|
||||
<ManageBranch
|
||||
key={manageState.branchId ?? "new"}
|
||||
closeModal={() =>
|
||||
setManageState({ IsOpen: false, branchId: null })
|
||||
}
|
||||
/>
|
||||
</GlobalModel>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceBranch;
|
||||
@ -5,17 +5,26 @@ import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||
import ManageServiceProject from "./ManageServiceProject";
|
||||
import GlobalModel from "../common/GlobalModel";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
import ServiceBranch from "./ServiceBranch";
|
||||
import ServiceBranch from "./ServiceProjectBranch/ServiceBranch";
|
||||
import ServiceProfile from "./ServiceProfile";
|
||||
|
||||
const ServiceProjectProfile = () => {
|
||||
const { projectId } = useParams();
|
||||
const [IsOpenModal, setIsOpenModal] = useState(false);
|
||||
const { data, isLoading, isError, error } = useServiceProject(projectId);
|
||||
if (isLoading) return <div className="py-8"><SpinnerLoader/></div>
|
||||
if (isLoading)
|
||||
return (
|
||||
<div className="py-8">
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{IsOpenModal && (
|
||||
<GlobalModel isOpen={IsOpenModal} closeModal={() => setIsOpenModal(false)}>
|
||||
<GlobalModel
|
||||
isOpen={IsOpenModal}
|
||||
closeModal={() => setIsOpenModal(false)}
|
||||
>
|
||||
<ManageServiceProject
|
||||
serviceProjectId={projectId}
|
||||
onClose={() => setIsOpenModal(false)}
|
||||
@ -25,101 +34,12 @@ const ServiceProjectProfile = () => {
|
||||
|
||||
<div className="row py-2">
|
||||
<div className="col-md-6 col-lg-4 order-2 mb-6">
|
||||
<div className="card mb-4">
|
||||
<div className="card-header text-start">
|
||||
<h5 className="card-action-title mb-0 ps-1">
|
||||
{" "}
|
||||
<i className="fa fa-building rounded-circle text-primary"></i>
|
||||
<span className="ms-2 fw-bold">Project Profile</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<ul className="list-unstyled my-3 ps-0 text-start">
|
||||
|
||||
<li className="d-flex mb-3">
|
||||
<div className="d-flex align-items-start" style={{ minWidth: "150px" }}>
|
||||
<i className="bx bx-cog"></i>
|
||||
<span className="fw-medium mx-2 text-nowrap">Name:</span>
|
||||
</div>
|
||||
|
||||
{/* Content section that wraps nicely */}
|
||||
<div className="flex-grow-1 text-start text-wrap">
|
||||
{data.name}
|
||||
</div>
|
||||
</li>
|
||||
<li className="d-flex mb-3">
|
||||
<div className="d-flex align-items-center" style={{ width: '150px' }}>
|
||||
<i className="bx bx-fingerprint"></i>
|
||||
<span className="fw-medium mx-2">Nick Name:</span>
|
||||
</div>
|
||||
<span>{data.shortName}</span>
|
||||
</li>
|
||||
<li className="d-flex mb-3">
|
||||
<div className="d-flex align-items-center" style={{ width: '150px' }}>
|
||||
<i className="bx bx-check"></i>
|
||||
<span className="fw-medium mx-2">Assign Date:</span>
|
||||
</div>
|
||||
<span>
|
||||
{data.assignedDate ? formatUTCToLocalTime(data.assignedDate) : "NA"}
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li className="d-flex mb-3">
|
||||
<div className="d-flex align-items-center" style={{ width: '150px' }}>
|
||||
<i className="bx bx-trophy"></i>
|
||||
<span className="fw-medium mx-2">Status:</span>
|
||||
</div>
|
||||
<span>{data?.status.status}</span>
|
||||
</li>
|
||||
<li className="d-flex mb-3">
|
||||
<div className="d-flex align-items-center" style={{ width: '150px' }}>
|
||||
<i className="bx bx-user"></i>
|
||||
<span className="fw-medium mx-2">Contact:</span>
|
||||
</div>
|
||||
<span>{data.contactName}</span>
|
||||
</li>
|
||||
<li className="d-flex mb-3">
|
||||
{/* Label section with icon */}
|
||||
<div className="d-flex align-items-start" style={{ minWidth: "150px" }}>
|
||||
<i className="bx bx-flag mt-1"></i>
|
||||
<span className="fw-medium mx-2 text-nowrap">Address:</span>
|
||||
</div>
|
||||
|
||||
{/* Content section that wraps nicely */}
|
||||
<div className="flex-grow-1 text-start text-wrap">
|
||||
{data.address}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li className="d-flex justify-content-center mt-4"> {/* Added mt-4 for some top margin */}
|
||||
|
||||
<a className="d-flex justify-content-center mt-4"> {/* Added mt-4 for some top margin */}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-primary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#edit-project-modal"
|
||||
onClick={() => setIsOpenModal(true)}
|
||||
>
|
||||
Modify Details
|
||||
</button>
|
||||
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ServiceProfile data={data} setIsOpenModal={setIsOpenModal}/>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6 col-lg-8 order-2 mb-6">
|
||||
<ServiceBranch/>
|
||||
<ServiceBranch />
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -50,6 +50,10 @@ export const defaultProjectValues = {
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//#region JobSchema
|
||||
|
||||
export const TagSchema = z.object({
|
||||
@ -115,3 +119,54 @@ export const defaultJobValue = {
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//#region Branch
|
||||
|
||||
export const BranchSchema = () =>
|
||||
z.object({
|
||||
projectId: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, { message: "Project is required" }),
|
||||
|
||||
branchName: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, { message: "Branch Name is required" }),
|
||||
|
||||
contactInformation: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, { message: "Contact Information is required" }),
|
||||
|
||||
address: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, { message: "Address is required" }),
|
||||
|
||||
branchType: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, { message: "Branch Type is required" }),
|
||||
|
||||
googleMapUrl: z
|
||||
.string()
|
||||
.trim()
|
||||
.url({ message: "Enter a valid Google Map URL" }),
|
||||
});
|
||||
|
||||
export const defaultBranches = {
|
||||
branchName: "",
|
||||
projectId: "",
|
||||
contactInformation: "",
|
||||
address: "",
|
||||
branchType: "",
|
||||
googleMapUrl: "",
|
||||
};
|
||||
|
||||
|
||||
//#endregion
|
||||
@ -104,8 +104,7 @@ const SubScriptionHistory = ({ tenantId }) => {
|
||||
</button>
|
||||
<button
|
||||
className="dropdown-item py-1"
|
||||
onClick={() =>
|
||||
console.log("Download clicked for", item.id)
|
||||
onClick={() =>{}
|
||||
}
|
||||
>
|
||||
<i className="bx bx-cloud-download bx-sm"></i> Download
|
||||
|
||||
@ -93,11 +93,9 @@ const TenantForm = () => {
|
||||
};
|
||||
|
||||
const onSubmitTenant = (data) => {
|
||||
console.log("Tenant Data:", data);
|
||||
};
|
||||
|
||||
const onSubmitSubScription = (data) => {
|
||||
console.log("Subscription Data:", data);
|
||||
};
|
||||
|
||||
const newTenantConfig = [
|
||||
|
||||
@ -362,8 +362,6 @@ export const SelectProjectField = ({
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const SelectFieldSearch = ({
|
||||
label = "Select",
|
||||
placeholder = "Select ",
|
||||
@ -372,7 +370,7 @@ export const SelectFieldSearch = ({
|
||||
onChange,
|
||||
valueKey = "id",
|
||||
labelKey = "name",
|
||||
|
||||
disabled = false,
|
||||
isFullObject = false,
|
||||
isMultiple = false,
|
||||
hookParams,
|
||||
@ -381,7 +379,7 @@ export const SelectFieldSearch = ({
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const debounce = useDebounce(searchText, 300);
|
||||
|
||||
const { data, isLoading } = useFetchHook(...hookParams,debounce);
|
||||
const { data, isLoading } = useFetchHook(...hookParams, debounce);
|
||||
const options = data?.data ?? [];
|
||||
const [open, setOpen] = useState(false);
|
||||
const dropdownRef = useRef(null);
|
||||
@ -434,6 +432,36 @@ export const SelectFieldSearch = ({
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
// MERGED OPTIONS TO ENSURE SELECTED VALUE APPEARS EVEN IF NOT IN SEARCH RESULT
|
||||
const [mergedOptions, setMergedOptions] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
let finalList = [...options];
|
||||
|
||||
if (!isMultiple && value && !isFullObject) {
|
||||
// already selected option inside options?
|
||||
const exists = options.some((o) => o[valueKey] === value);
|
||||
|
||||
// if selected item not found, try to get from props (value) as fallback
|
||||
if (!exists && typeof value === "object") {
|
||||
finalList.unshift(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (isMultiple && Array.isArray(value)) {
|
||||
value.forEach((val) => {
|
||||
const id = isFullObject ? val[valueKey] : val;
|
||||
const exists = options.some((o) => o[valueKey] === id);
|
||||
|
||||
if (!exists && typeof val === "object") {
|
||||
finalList.unshift(val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setMergedOptions(finalList);
|
||||
}, [options, value]);
|
||||
|
||||
/** -----------------------------
|
||||
* HANDLE SELECT
|
||||
* ----------------------------- */
|
||||
@ -472,9 +500,10 @@ export const SelectFieldSearch = ({
|
||||
{/* MAIN BUTTON */}
|
||||
<button
|
||||
type="button"
|
||||
className={`select2-icons form-select d-flex align-items-center justify-content-between ${
|
||||
className={`select2-icons form-select d-flex align-items-center justify-content-between ${
|
||||
open ? "show" : ""
|
||||
}`}
|
||||
disabled={disabled}
|
||||
onClick={() => setOpen((prev) => !prev)}
|
||||
>
|
||||
<span className={`text-truncate ${!displayText ? "text-muted" : ""}`}>
|
||||
@ -503,6 +532,7 @@ export const SelectFieldSearch = ({
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
className="form-control form-control-sm"
|
||||
placeholder="Search..."
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@ -28,7 +28,6 @@ const CommentEditor = () => {
|
||||
const [value, setValue] = useState("");
|
||||
|
||||
const handleSubmit = () => {
|
||||
console.log("Comment:", value);
|
||||
// Submit or handle content
|
||||
};
|
||||
|
||||
|
||||
@ -411,7 +411,6 @@ export const useUpdateProject = (onSuccessCallback) => {
|
||||
},
|
||||
|
||||
onError: (error) => {
|
||||
console.log(error);
|
||||
showToast(error?.message || "Error while updating project", "error");
|
||||
},
|
||||
});
|
||||
|
||||
@ -308,7 +308,6 @@ export const useBranches = (
|
||||
pageNumber,
|
||||
searchString
|
||||
);
|
||||
console.log(resp)
|
||||
return resp.data;
|
||||
},
|
||||
enabled: !!projectId,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user