Implementing edit api for branches.

This commit is contained in:
Kartik Sharma 2025-11-20 09:51:23 +05:30
parent e7fddd41c2
commit dd716187da
4 changed files with 255 additions and 143 deletions

View File

@ -4,6 +4,7 @@ import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import Label from "../../common/Label"; import Label from "../../common/Label";
import { import {
useBranchDetails,
useCreateBranch, useCreateBranch,
useServiceProjects, useServiceProjects,
useUpdateBranch, useUpdateBranch,
@ -13,7 +14,13 @@ import { useParams } from "react-router-dom";
import { BranchSchema, defaultBranches } from "../ServiceProjectSchema"; import { BranchSchema, defaultBranches } from "../ServiceProjectSchema";
const ManageBranch = ({ closeModal, BranchToEdit = null }) => { const ManageBranch = ({ closeModal, BranchToEdit = null }) => {
const { data } = {}; const {
data,
isLoading,
isError,
error: requestError,
} = useBranchDetails(BranchToEdit);
const { projectId } = useParams(); const { projectId } = useParams();
const schema = BranchSchema(); const schema = BranchSchema();
const { const {
@ -112,7 +119,7 @@ const ManageBranch = ({ closeModal, BranchToEdit = null }) => {
</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="branchType" className="form-label" required> <Label htmlFor="branchType" className="form-label" required>
Branch Type Branch Type
@ -127,7 +134,7 @@ const ManageBranch = ({ closeModal, BranchToEdit = null }) => {
<small className="danger-text">{errors.branchType.message}</small> <small className="danger-text">{errors.branchType.message}</small>
)} )}
</div> </div>
<div className="col-md-6"> <div className="col-md-6">
<Label htmlFor="googleMapUrl" className="form-label" required> <Label htmlFor="googleMapUrl" className="form-label" required>
Google Map URL Google Map URL
</Label> </Label>
@ -146,7 +153,7 @@ const ManageBranch = ({ closeModal, BranchToEdit = null }) => {
</div> </div>
<div className="row my-2 text-start"> <div className="row my-2 text-start">
<div className="col-12"> <div className="col-12">
<Label htmlFor="address" className="form-label" required> <Label htmlFor="address" className="form-label" required>
Address Address
@ -172,7 +179,7 @@ const ManageBranch = ({ closeModal, BranchToEdit = null }) => {
</button> </button>
<button type="submit" className="btn btn-primary btn-sm mt-3"> <button type="submit" className="btn btn-primary btn-sm mt-3">
{isPending ? "Please wait...":"Submit"} {isPending ? "Please wait..." : "Submit"}
</button> </button>
</div> </div>
</form> </form>

View File

@ -1,19 +1,24 @@
import React, { useState } from "react"; import React, { useState } from "react";
import GlobalModel from "../../common/GlobalModel"; import GlobalModel from "../../common/GlobalModel";
import ManageBranch from "./ManageBranch"; import ManageBranch from "./ManageBranch";
import { useBranches } from "../../../hooks/useServiceProject"; import { useBranches, useDeleteBranch } from "../../../hooks/useServiceProject";
import { ITEMS_PER_PAGE } from "../../../utils/constants"; import { ITEMS_PER_PAGE } from "../../../utils/constants";
import { useDebounce } from "../../../utils/appUtils"; import { useDebounce } from "../../../utils/appUtils";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import Pagination from "../../common/Pagination"; import Pagination from "../../common/Pagination";
import ConfirmModal from "../../common/ConfirmModal";
import { SpinnerLoader } from "../../common/Loader";
const ServiceBranch = () => { const ServiceBranch = () => {
const { projectId } = useParams(); const { projectId } = useParams();
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [showInactive, setShowInactive] = useState(false);
const [manageState, setManageState] = useState({ const [manageState, setManageState] = useState({
IsOpen: false, IsOpen: false,
branchId: null, branchId: null,
}); });
const { mutate: DeleteBranch, isPending } = useDeleteBranch();
const [deletingId, setDeletingId] = useState(null);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
@ -21,7 +26,8 @@ const ServiceBranch = () => {
const { data, isLoading, isError, error } = useBranches( const { data, isLoading, isError, error } = useBranches(
projectId, projectId,
true, // true,
!showInactive,
ITEMS_PER_PAGE - 10, ITEMS_PER_PAGE - 10,
currentPage, currentPage,
debouncedSearch debouncedSearch
@ -42,132 +48,222 @@ const ServiceBranch = () => {
}, },
]; ];
const handleDelete = (id) => {
setDeletingId(id);
DeleteBranch(
{ id, isActive: false },
{
onSettled: () => {
setDeletingId(null);
setIsDeleteModalOpen(false);
},
}
);
};
return ( return (
<div className="card h-100 table-responsive px-sm-4"> <>
<div className="card-datatable" id="payment-request-table"> {IsDeleteModalOpen && (
{/* Header Section */} <ConfirmModal
<div className="row align-items-center justify-content-between mt-3 mx-1"> isOpen={IsDeleteModalOpen}
<div className="col-md-6 col-sm-12 text-start"> type="delete"
<h5 className="mb-0"> header="Delete Expense"
<i className="bx bx-buildings text-primary me-2"></i> message="Are you sure you want delete?"
Branches onSubmit={handleDelete}
</h5> onClose={() => setIsDeleteModalOpen(false)}
loading={isPending}
paramData={deletingId}
/>
)}
<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 ms-n3 text-start ">
<h5 className="mb-0">
<i className="bx bx-buildings text-primary"></i>
<span className="ms-2 fw-bold">Branch</span>
</h5>
</div>
{/* Flex container for toggle + button */}
<div className="col-md-6 col-sm-12 text-end">
<div className="d-flex flex-column flex-md-row align-items-md-center justify-content-md-end gap-2">
{/* Toggle Switch */}
<div className="form-check form-switch d-inline-flex align-items-center">
<input
type="checkbox"
className="form-check-input mt-1"
id="inactiveEmployeesCheckbox"
checked={showInactive}
onChange={() => setShowInactive(!showInactive)}
/>
<label htmlFor="inactiveEmployeesCheckbox" className="ms-2 mt-1">
Show Deleted Branches
</label>
</div>
{/* Add Branch Button */}
<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> </div>
<div className="col-md-3 col-sm-12 text-end"> <div className="mx-2 mt-3">
<button <table className="table border-top text-nowrap align-middle table-borderless">
className="btn btn-sm btn-primary" <thead>
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> <tr>
<td colSpan={columns.length + 1} className="text-center py-5"> {columns.map((col) => (
Loading... <th key={col.key} className={col.align}>
</td> {col.label}
</th>
))}
<th className="text-center">Action</th>
</tr> </tr>
)} </thead>
{isError && ( <tbody>
<tr> {isLoading && (
<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> <tr>
<td <td
colSpan={columns.length + 1} colSpan={columns.length + 1}
className="text-center py-12" className="text-center py-5"
style={{
height: "200px",
verticalAlign: "middle",
}}
> >
No Branch Found <div className="d-flex justify-content-center align-items-center w-100 h-100">
<SpinnerLoader />
</div>
</td> </td>
</tr> </tr>
)} )}
</tbody>
</table>
{data?.data?.length > 0 && (
<Pagination {isError && (
currentPage={currentPage} <tr>
totalPages={data.totalPages} <td
onPageChange={paginate} 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">
<div className="dropdown z-2">
<button
type="button"
className="btn btn-xs btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0 m-0"
data-bs-toggle="dropdown"
>
<i className="bx bx-dots-vertical-rounded text-muted p-0"></i>
</button>
<ul className="dropdown-menu dropdown-menu-end w-auto">
{/* Modify */}
<li
onClick={() =>
setManageState({
IsOpen: true,
branchId: branch.id,
})
}
>
<a className="dropdown-item px-2 cursor-pointer py-1">
<i className="bx bx-edit text-primary bx-xs me-2"></i>
Modify
</a>
</li>
{/* Delete */}
<li
onClick={() => {
setIsDeleteModalOpen(true);
setDeletingId(branch.id);
}}
>
<a className="dropdown-item px-2 cursor-pointer py-1">
<i className="bx bx-trash text-danger bx-xs me-2"></i>
Delete
</a>
</li>
</ul>
</div>
</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"}
BranchToEdit={manageState.branchId}
closeModal={() =>
setManageState({ IsOpen: false, branchId: null })
}
/>
</GlobalModel>
)} )}
</div> </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>
</div> </>
); );
}; };

View File

@ -315,12 +315,12 @@ export const useBranches = (
}; };
export const useBranch = (id)=>{ export const useBranchDetails = (id) => {
return useQuery({ return useQuery({
queryKey: ["branch", id], queryKey: ["branch", id],
queryFn: async () => { queryFn: async () => {
const resp = await ServiceProjectRepository.GetBranchDetail(id); const resp = await ServiceProjectRepository.GetBranchDetail(id);
return resp.data ?? resp; return resp.data;
}, },
enabled: !!id enabled: !!id
}) })
@ -346,37 +346,42 @@ export const useCreateBranch = (onSuccessCallBack) => {
}, },
}); });
}; };
export const useUpdateBranch = (onSuccessCallBack) => {
export const useUpdateBranch = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async ({ id, payload }) => await ServiceProjectRepository.UpdateBranch(id, payload), mutationFn: async ({ id, payload }) =>
await ServiceProjectRepository.UpdateBranch(id, payload),
onSuccess: (_, variables) => { onSuccess: (_, variables) => {
// remove old single-branch cache
queryClient.removeQueries({ queryKey: ["branch", variables.id] });
// refresh list
queryClient.invalidateQueries({ queryKey: ["branches"] }); queryClient.invalidateQueries({ queryKey: ["branches"] });
queryClient.invalidateQueries({ queryKey: ["branch", variables.id] });
if (onSuccessCallback) onSuccessCallback(); showToast("Branch updated successfully", "success");
showToast("Branch Updated successfully", "success"); onSuccessCallBack?.();
}, },
onError: (error) => {
showToast( onError: () => {
error?.response?.data?.message || showToast("Something went wrong. Please try again later.", "error");
error.message ||
"Failed to update branch",
"error"
);
}, },
}) });
} };
export const useDeleteBranch = () => { export const useDeleteBranch = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async (id) => await ServiceProjectRepository.DeleteBranch(id), mutationFn: async ({ id, isActive}) =>
onSuccess: (_, variables) => { await ServiceProjectRepository.DeleteBranch(id, isActive),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["branches"] }); queryClient.invalidateQueries({ queryKey: ["branches"] });
if (onSuccessCallback) onSuccessCallback(); showToast("Branch deleted successfully", "success");
showToast("Branch Deleted successfully", "success");
}, },
onError: (error) => { onError: (error) => {
showToast( showToast(
error?.response?.data?.message || error?.response?.data?.message ||
@ -385,5 +390,5 @@ export const useDeleteBranch = () => {
"error" "error"
); );
}, },
}) });
} };

View File

@ -1,3 +1,4 @@
import { isAction } from "@reduxjs/toolkit";
import { api } from "../utils/axiosClient"; import { api } from "../utils/axiosClient";
export const ServiceProjectRepository = { export const ServiceProjectRepository = {
@ -43,14 +44,17 @@ export const ServiceProjectRepository = {
//#region Project Branch //#region Project Branch
CreateBranch: (data) => api.post(`/api/ServiceProject/branch/create`, data), CreateBranch: (data) => api.post(`/api/ServiceProject/branch/create`, data),
UpdateBranch: (id, data) => UpdateBranch: (id, data) =>
api.put("/api/ServiceProject/branch/edit/${id}", data), api.put(`/api/ServiceProject/branch/edit/${id}`, data),
GetBranchList: (projectId, isActive, pageSize, pageNumber, searchString) => { GetBranchList: (projectId, isActive, pageSize, pageNumber, searchString) => {
return api.get( return api.get(
`/api/ServiceProject/branch/list/${projectId}?isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&searchString=${searchString}` `/api/ServiceProject/branch/list/${projectId}?isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&searchString=${searchString}`
); );
}, },
GetBranchDetail: (id) => api.get(`/api/ServiceProject/branch/details/${id}`), GetBranchDetail: (id) => api.get(`/api/ServiceProject/branch/details/${id}`),
DeleteBranch: (id) => api.delete(`/api/ServiceProject/branch/delete/${id}`), DeleteBranch: (id, isActive = false) =>
api.delete(`/api/ServiceProject/branch/delete/${id}?isActive=${isActive}`),
}; };