Merge branch 'Service_Project_Managment' into Kartik_Bug#1505

This commit is contained in:
Vikas Nale 2025-11-17 13:03:42 +00:00
commit 2b1d3e5890
155 changed files with 5449 additions and 9129 deletions

View File

@ -456,3 +456,8 @@ font-weight: normal;
.fs-md-xlarge { font-size: 170% !important; }
.fs-md-xxlarge { font-size: calc(1.725rem + 5.7vw) !important; }
}
.me-16 {
/* margin-inline-end: -7.0625rem !important; */
margin-left: -7.0625rem !important;
}

View File

@ -76,6 +76,7 @@
--bs-dark-border-subtle: #bfc0c6;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 34, 48, 62;
--bs-font-roboto:"Segoe UI", Roboto, "sans-serif",
--bs-font-sans-serif: "Public Sans", -apple-system, blinkmacsystemfont,
"Segoe UI", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
"Helvetica Neue", sans-serif;
@ -88,7 +89,7 @@
);
--bs-root-font-size: 16px;
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 0.8375rem;
--bs-body-font-size: 0.875rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.375;
--bs-body-color: #646e78;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

BIN
public/img/hero/bg-01.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

BIN
public/img/hero/bg-02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

BIN
public/img/hero/bg-03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
public/img/hero/bg-04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

View File

Before

Width:  |  Height:  |  Size: 860 KiB

After

Width:  |  Height:  |  Size: 860 KiB

BIN
public/img/icons/ai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/img/icons/report.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

View File

@ -1,42 +0,0 @@
<svg width="25" viewBox="0 0 25 42" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path
d="M13.7918663,0.358365126 L3.39788168,7.44174259 C0.566865006,9.69408886 -0.379795268,12.4788597 0.557900856,15.7960551 C0.68998853,16.2305145 1.09562888,17.7872135 3.12357076,19.2293357 C3.8146334,19.7207684 5.32369333,20.3834223 7.65075054,21.2172976 L7.59773219,21.2525164 L2.63468769,24.5493413 C0.445452254,26.3002124 0.0884951797,28.5083815 1.56381646,31.1738486 C2.83770406,32.8170431 5.20850219,33.2640127 7.09180128,32.5391577 C8.347334,32.0559211 11.4559176,30.0011079 16.4175519,26.3747182 C18.0338572,24.4997857 18.6973423,22.4544883 18.4080071,20.2388261 C17.963753,17.5346866 16.1776345,15.5799961 13.0496516,14.3747546 L10.9194936,13.4715819 L18.6192054,7.984237 L13.7918663,0.358365126 Z"
id="path-1"></path>
<path
d="M5.47320593,6.00457225 C4.05321814,8.216144 4.36334763,10.0722806 6.40359441,11.5729822 C8.61520715,12.571656 10.0999176,13.2171421 10.8577257,13.5094407 L15.5088241,14.433041 L18.6192054,7.984237 C15.5364148,3.11535317 13.9273018,0.573395879 13.7918663,0.358365126 C13.5790555,0.511491653 10.8061687,2.3935607 5.47320593,6.00457225 Z"
id="path-3"></path>
<path
d="M7.50063644,21.2294429 L12.3234468,23.3159332 C14.1688022,24.7579751 14.397098,26.4880487 13.008334,28.506154 C11.6195701,30.5242593 10.3099883,31.790241 9.07958868,32.3040991 C5.78142938,33.4346997 4.13234973,34 4.13234973,34 C4.13234973,34 2.75489982,33.0538207 2.37032616e-14,31.1614621 C-0.55822714,27.8186216 -0.55822714,26.0572515 -4.05231404e-15,25.8773518 C0.83734071,25.6075023 2.77988457,22.8248993 3.3049379,22.52991 C3.65497346,22.3332504 5.05353963,21.8997614 7.50063644,21.2294429 Z"
id="path-4"></path>
<path
d="M20.6,7.13333333 L25.6,13.8 C26.2627417,14.6836556 26.0836556,15.9372583 25.2,16.6 C24.8538077,16.8596443 24.4327404,17 24,17 L14,17 C12.8954305,17 12,16.1045695 12,15 C12,14.5672596 12.1403557,14.1461923 12.4,13.8 L17.4,7.13333333 C18.0627417,6.24967773 19.3163444,6.07059163 20.2,6.73333333 C20.3516113,6.84704183 20.4862915,6.981722 20.6,7.13333333 Z"
id="path-5"></path>
</defs>
<g id="g-app-brand" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Brand-Logo" transform="translate(-27.000000, -15.000000)">
<g id="Icon" transform="translate(27.000000, 15.000000)">
<g id="Mask" transform="translate(0.000000, 8.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use fill="#696cff" xlink:href="#path-1"></use>
<g id="Path-3" mask="url(#mask-2)">
<use fill="#696cff" xlink:href="#path-3"></use>
<use fill-opacity="0.2" fill="#FFFFFF" xlink:href="#path-3"></use>
</g>
<g id="Path-4" mask="url(#mask-2)">
<use fill="#696cff" xlink:href="#path-4"></use>
<use fill-opacity="0.2" fill="#FFFFFF" xlink:href="#path-4"></use>
</g>
</g>
<g id="Triangle"
transform="translate(19.000000, 11.000000) rotate(-300.000000) translate(-19.000000, -11.000000) ">
<use fill="#696cff" xlink:href="#path-5"></use>
<use fill-opacity="0.2" fill="#FFFFFF" xlink:href="#path-5"></use>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

View File

@ -5,21 +5,24 @@ import { useAuthModal, useModal } from "./hooks/useAuth";
import SwitchTenant from "./pages/authentication/SwitchTenant";
import ChangePasswordPage from "./pages/authentication/ChangePassword";
import NewCollection from "./components/collections/ManageCollection";
import ServiceProjectTeamAllocation from "./components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamAllocation";
const ModalProvider = () => {
const { isOpen, onClose } = useOrganizationModal();
const { isOpen: isAuthOpen } = useAuthModal();
const {isOpen:isChangePass} = useModal("ChangePassword")
const {isOpen:isCollectionNew} = useModal("newCollection");
const { isOpen: isChangePass } = useModal("ChangePassword");
const { isOpen: isCollectionNew } = useModal("newCollection");
const { isOpen: isServiceTeamAllocation } = useModal("ServiceTeamAllocation");
return (
<>
{isOpen && <OrganizationModal />}
{isAuthOpen && <SwitchTenant />}
{isChangePass && <ChangePasswordPage /> }
{isCollectionNew && <NewCollection/>}
{isChangePass && <ChangePasswordPage />}
{isCollectionNew && <NewCollection />}
{isServiceTeamAllocation && <ServiceProjectTeamAllocation />}
</>
);
};
export default ModalProvider;
export default ModalProvider;

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -72,7 +72,7 @@
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-root-font-size: 16px;
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 0.9375rem;
--bs-body-font-size: 0.875rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.375;
--bs-body-color: #646e78;

View File

@ -100,9 +100,9 @@ const AttendanceOverview = () => {
};
return (
<div className="bg-white p-4 rounded shadow d-flex flex-column h-100">
<div className="bg-white px-4 rounded shadow d-flex flex-column h-100">
{/* Header */}
<div className="d-flex justify-content-between align-items-center mb-3">
<div className="d-flex mt-2 justify-content-between align-items-center mb-3">
<div className="card-title mb-0 text-start">
<h5 className="mb-1 fw-bold">Attendance Overview</h5>
<p className="card-subtitle">Role-wise present count</p>

View File

@ -12,11 +12,11 @@ import Teams from "./Teams";
import TasksCard from "./Tasks";
import ProjectCompletionChart from "./ProjectCompletionChart";
import ProjectProgressChart from "./ProjectProgressChart";
import ProjectOverview from "../Project/ProjectOverview";
import AttendanceOverview from "./AttendanceChart";
import AttendanceOverview from "./AttendanceOverview";
import ExpenseAnalysis from "./ExpenseAnalysis";
import ExpenseStatus from "./ExpenseStatus";
import ExpenseByProject from "./ExpenseByProject";
import ProjectStatistics from "../Project/ProjectStatistics";
const Dashboard = () => {
@ -29,16 +29,16 @@ const Dashboard = () => {
<div className="row gy-4">
{isAllProjectsSelected && (
<div className="col-sm-6 col-lg-4">
<Projects />
<Projects />
</div>
)}
<div className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"}`}>
<Teams />
<Teams />
</div>
<div className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4"}`}>
<TasksCard/>
<TasksCard />
</div>
{isAllProjectsSelected && (
@ -46,32 +46,31 @@ const Dashboard = () => {
<ProjectCompletionChart />
</div>
)}
{!isAllProjectsSelected && (
<div className="col-xxl-6 col-lg-6">
<ProjectOverview />
</div>
)}
<div className="col-xxl-6 col-lg-6">
<ProjectProgressChart />
</div>
<div className="col-12 col-xl-8">
<div className="card h-100">
<ExpenseAnalysis />
</div>
</div>
<div className="col-12 col-xl-4 col-md-6">
<div className="card h-100">
<ExpenseStatus />
</div>
</div>
{!isAllProjectsSelected && (
<div className="col-12 col-md-6 mb-sm-0 mb-4">
<AttendanceOverview />
</div>
)}
{!isAllProjectsSelected && (
<div className="col-xxl-4 col-lg-4">
<ProjectStatistics />
</div>
)}
<div className="col-12 col-xl-4 col-md-6">
<div className="card ">
<ExpenseStatus />
</div>
</div>
<div className="col-12 col-xl-8">
<div className="card h-100">
<ExpenseAnalysis />
</div>
</div>
<div className="col-12 col-md-6">
<ExpenseByProject />
</div>

View File

@ -7,11 +7,12 @@ import { FormProvider, useForm } from "react-hook-form";
import { formatCurrency, localToUtc } from "../../utils/appUtils";
import { useProjectName } from "../../hooks/useProjects";
import { SpinnerLoader } from "../common/Loader";
import flatColors from "../Charts/flatColor";
const ExpenseAnalysis = () => {
const projectId = useSelectedProject();
const [projectName, setProjectName] = useState("All Project");
const { projectNames, loading } = useProjectName();
const { projectNames } = useProjectName();
const methods = useForm({
defaultValues: { startDate: "", endDate: "" },
@ -50,7 +51,7 @@ const ExpenseAnalysis = () => {
labels,
legend: { show: false },
dataLabels: { enabled: true, formatter: (val) => `${val.toFixed(0)}%` },
colors: ["#7367F0", "#28C76F", "#FF9F43", "#EA5455", "#00CFE8", "#FF78B8"],
colors: flatColors,
plotOptions: {
pie: {
donut: {
@ -79,22 +80,19 @@ const ExpenseAnalysis = () => {
return (
<>
<div className="card-header d-flex flex-column flex-sm-row justify-content-between align-items-start align-items-sm-center gap-2">
<div className="text-start ">
<div className="text-start">
<h5 className="mb-1 card-title">Expense Breakdown</h5>
{/* <p className="card-subtitle mb-0">Category Wise Expense Breakdown</p> */}
<p className="card-subtitle m-0">{projectName}</p>
</div>
<div className="text-end text-sm-end ">
<div className="text-end text-sm-end">
<FormProvider {...methods}>
<DateRangePicker1 />
</FormProvider>
</div>
</div>
{/* Card body */}
<div className="card-body position-relative">
{isLoading && (
<div
@ -114,7 +112,6 @@ const ExpenseAnalysis = () => {
</div>
)}
{!isLoading && report.length > 0 && (
<>
{isFetching && (
@ -123,50 +120,59 @@ const ExpenseAnalysis = () => {
</div>
)}
<div className="d-flex justify-content-center mb-3">
<Chart
options={donutOptions}
series={series}
type="donut"
width="100%"
height={320}
/>
</div>
<div className="row">
{/* Chart Column */}
<div className="col-12 col-lg-6 d-flex justify-content-center mt-5 mb-3 mb-lg-0">
<Chart
options={donutOptions}
series={series}
type="donut"
width="70%"
height={320}
/>
</div>
<div className="mb-2 w-100">
<div className="row g-2">
{report.map((item, idx) => (
<div
className="col-12 col-sm-6 d-flex align-items-start"
key={idx}
>
<div className="avatar me-2">
<span
className="avatar-initial rounded-2"
style={{
backgroundColor:
donutOptions.colors[idx % donutOptions.colors.length],
}}
>
<i className="bx bx-receipt fs-4"></i>
</span>
{/* Data/Legend Column */}
<div className="col-12 mt-6 col-lg-6">
<div className="row g-4">
{report.map((item, idx) => (
<div
className="col-6"
key={idx}
style={{
borderLeft: `3px solid ${flatColors[idx % flatColors.length]}`,
}}
>
<div className="d-flex flex-column text-start">
<small
className="fw-semibold text-wrap text-dark"
style={{
fontSize: "0.8rem",
whiteSpace: "normal",
wordBreak: "break-word",
lineHeight: "1.2",
}}
>
{item.projectName}
</small>
<span
className="fw-semibold text-muted"
style={{
fontSize: "0.75rem",
}}
>
{formatCurrency(item.totalApprovedAmount)}
</span>
</div>
</div>
<div className="d-flex flex-column gap-1 text-start">
<small className="fw-semibold">{item.projectName}</small>
<span className="fw-semibold text-muted ms-1">
{formatCurrency(item.totalApprovedAmount)}
</span>
</div>
</div>
))}
))}
</div>
</div>
</div>
</>
)}
</div>
{/* Header */}
</>
);
};

View File

@ -92,7 +92,7 @@ const ExpenseByProject = () => {
<div className="card shadow-sm h-100 rounded ">
{/* Header */}
<div className="card-header">
<div className="d-flex justify-content-start align-items-center mb-1 mt-1">
<div className="d-flex justify-content-between align-items-center mb-1 mt-1">
<div className="text-start">
<h5 className="mb-1 me-6 card-title">Monthly Expense -</h5>
<p className="card-subtitle m-0">{projectName}</p>

View File

@ -103,7 +103,7 @@ const ExpenseStatus = () => {
</div>
<div>
<small
className={`text-royalblue ${countDigit(item?.count || 0) >= 3 ? "text-xl" : "text-2xl"
className={`text-royalblue ${countDigit(item?.count || 0) >= 3 ? "text-xl" : "text-xl"
} text-gray-500`}
>
{item?.count || 0}
@ -137,7 +137,7 @@ const ExpenseStatus = () => {
</div>
<div className="d-flex align-items-center gap-2">
<span
className={`text-end text-royalblue ${countDigit(data?.totalAmount || 0) > 3 ? "text-" : "text-3xl"
className={`text-end text-royalblue ${countDigit(data?.totalAmount || 0) > 3 ? "text-xl" : "text-3xl"
} text-md`}
>
{formatCurrency(data?.totalAmount || 0)}

View File

@ -1,11 +1,14 @@
import React from "react";
import React, { useState } from "react";
import HorizontalBarChart from "../Charts/HorizontalBarChart";
import { useProjects } from "../../hooks/useProjects";
import { ITEMS_PER_PAGE } from "../../utils/constants";
const ProjectCompletionChart = () => {
const { data: projects = [], isLoading: loading, isError, error } = useProjects();
const [currentPage, setCurrentPage] = useState(1);
const { data: projects, isLoading: loading, isError, error } = useProjects(currentPage, ITEMS_PER_PAGE);
console.log("Kartik", projects)
// Bar chart logic
const projectNames = projects?.map((p) => p.name) || [];
const projectProgress =

View File

@ -23,7 +23,7 @@ import Label from "../common/Label";
const ManageContact = ({ contactId, closeModal }) => {
// fetch master data
const { buckets, loading: bucketsLoaging } = useBuckets();
const { data:projects, loading: projectLoading } = useProjects();
const { data: projects, loading: projectLoading } = useProjects();
const { contactCategory, loading: contactCategoryLoading } =
useContactCategory();
const { organizationList } = useOrganization();
@ -205,12 +205,12 @@ const ManageContact = ({ contactId, closeModal }) => {
<Label htmlFor={"organization"} required>
Organization
</Label>
<InputSuggestions
organizationList={organizationList}
value={watch("organization") || ""}
onChange={(val) => setValue("organization", val, { shouldValidate: true })}
error={errors.organization?.message}
/>
<InputSuggestions
organizationList={organizationList}
value={watch("organization") || ""}
onChange={(val) => setValue("organization", val, { shouldValidate: true })}
error={errors.organization?.message}
/>
</div>
</div>
@ -408,6 +408,7 @@ const ManageContact = ({ contactId, closeModal }) => {
label="Tags"
options={contactTags}
isRequired={true}
placeholder="Enter Tag"
/>
{errors.tags && (
<small className="danger-text">{errors.tags.message}</small>
@ -482,7 +483,7 @@ const ManageContact = ({ contactId, closeModal }) => {
<button className="btn btn-sm btn-primary" type="submit" disabled={isPending}>
{isPending ? "Please Wait..." : "Submit"}
</button>
</div>
</form>
</FormProvider>

View File

@ -10,6 +10,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { defaultActionValues, ExpenseActionScheam } from "./ExpenseSchema";
import { useExpenseContext } from "../../pages/Expense/ExpensePage";
import {
calculateTDSPercentage,
formatCurrency,
formatFigure,
getColorNameFromHex,
@ -54,12 +55,22 @@ const ViewExpense = ({ ExpenseId }) => {
setValue,
reset,
control,
watch,
formState: { errors },
} = useForm({
resolver: zodResolver(ActionSchema),
defaultValues: defaultActionValues,
});
const baseAmount = Number(watch("baseAmount")) || 0;
const taxAmount = Number(watch("taxAmount")) || 0;
const tdsPercentage = Number(watch("tdsPercentage")) || 0;
const { grossAmount, tdsAmount, netPayable } = useMemo(() => {
return calculateTDSPercentage(baseAmount, taxAmount, tdsPercentage);
}, [baseAmount, taxAmount, tdsPercentage]);
const userPermissions = useSelector(
(state) => state?.globalVariables?.loginUser?.featurePermissions || []
);
@ -132,9 +143,8 @@ const ViewExpense = ({ ExpenseId }) => {
<span>{data?.expenseUId}</span>
</div>{" "}
<span
className={`badge bg-label-${
getColorNameFromHex(data?.status?.color) || "secondary"
}`}
className={`badge bg-label-${getColorNameFromHex(data?.status?.color) || "secondary"
}`}
t
>
{data?.status?.name}
@ -142,7 +152,7 @@ const ViewExpense = ({ ExpenseId }) => {
</div>
{/* Row 1 */}
<div className="row text-start">
<div className="row text-start">
<div className="col-md-6 mb-3">
<label
className="form-label me-2 mb-0 fw-semibold text-start"
@ -264,28 +274,28 @@ const ViewExpense = ({ ExpenseId }) => {
</div> */}
{/* Row 5 */}
<div className="row text-start">
<div className="row text-start">
<div className="col-md-6 mb-3">
<label
className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }}
>
Created At :
</label>
</div>
<div className="col-md-6 mb-3">
</div>
<div className="col-md-6 mb-3">
<small className="text-muted">
{formatUTCToLocalTime(data?.createdAt, true)}
</small>
</div>
</div>
</div>
{/* Created & Paid By */}
{data.createdBy && (
@ -307,9 +317,8 @@ const ViewExpense = ({ ExpenseId }) => {
lastName={data.createdBy?.lastName}
/>
<span className="text-muted">
{`${data.createdBy?.firstName ?? ""} ${
data.createdBy?.lastName ?? ""
}`.trim() || "N/A"}
{`${data.createdBy?.firstName ?? ""} ${data.createdBy?.lastName ?? ""
}`.trim() || "N/A"}
</span>
</div>
</div>
@ -337,31 +346,30 @@ const ViewExpense = ({ ExpenseId }) => {
</span>
</div>
</div> */}
<div className="row text-start">
<div className="col-md-6 text-start mb-3">
<label
className="form-label me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }}
>
Paid By :
</label>
</div>
<div className="col-md-6 text-start mb-3">
<div className="d-flex align-items-center">
<Avatar
size="xs"
classAvatar="m-0 me-1"
firstName={data.paidBy?.firstName}
lastName={data.paidBy?.lastName}
/>
<span className="text-muted">
{`${data.paidBy?.firstName ?? ""} ${
data.paidBy?.lastName ?? ""
<div className="row text-start">
<div className="col-md-6 text-start mb-3">
<label
className="form-label me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }}
>
Paid By :
</label>
</div>
<div className="col-md-6 text-start mb-3">
<div className="d-flex align-items-center">
<Avatar
size="xs"
classAvatar="m-0 me-1"
firstName={data.paidBy?.firstName}
lastName={data.paidBy?.lastName}
/>
<span className="text-muted">
{`${data.paidBy?.firstName ?? ""} ${data.paidBy?.lastName ?? ""
}`.trim() || "N/A"}
</span>
</div>
</span>
</div>
</div>
</div>
{/* Description */}
<div className="col-12 text-start mb-2">
@ -493,19 +501,7 @@ const ViewExpense = ({ ExpenseId }) => {
projectId={null}
/>
</div>
<div className="col-12 col-md-6 text-start">
<Label className="form-label">TDS Percentage</Label>
<input
type="text"
className="form-control form-control-sm"
{...register("tdsPercentage")}
/>
{errors.tdsPercentage && (
<small className="danger-text">
{errors.tdsPercentage.message}
</small>
)}
</div>
<div className="col-12 col-md-6 text-start">
<Label className="form-label" required>
Base Amount
@ -536,28 +532,53 @@ const ViewExpense = ({ ExpenseId }) => {
</small>
)}
</div>
<div className="col-12 col-md-6 text-start">
<Label className="form-label">TDS Percentage</Label>
<input
type="text"
className="form-control form-control-sm"
{...register("tdsPercentage")}
/>
{errors.tdsPercentage && (
<small className="danger-text">
{errors.tdsPercentage.message}
</small>
)}
</div>
<div className="col-12 d-flex align-items-center gap-4 mb-2 mt-1">
<div>
<span className="fw-semibold">TDS Amount: </span>
<span className="badge bg-label-secondary">{tdsAmount.toFixed(2)}</span>
</div>
<div>
<span className="fw-semibold">Net Payable: </span>
<span className="badge bg-label-secondary">{netPayable.toFixed(2)}</span>
</div>
</div>
</div>
)}
<div className="col-12 mb-3 text-start mt-1">
{((nextStatusWithPermission.length > 0 &&
!IsRejectedExpense) ||
(IsRejectedExpense && isCreatedBy)) && (
<>
<Label className="form-label me-2 mb-0" required>
Comment
</Label>
<textarea
className="form-control form-control-sm"
{...register("comment")}
rows="2"
/>
{errors.comment && (
<small className="danger-text">
{errors.comment.message}
</small>
)}
</>
)}
<>
<Label className="form-label me-2 mb-0" required>
Comment
</Label>
<textarea
className="form-control form-control-sm"
{...register("comment")}
rows="2"
/>
{errors.comment && (
<small className="danger-text">
{errors.comment.message}
</small>
)}
</>
)}
{nextStatusWithPermission?.length > 0 &&
(!IsRejectedExpense || isCreatedBy) && (

View File

@ -46,8 +46,12 @@ const Header = () => {
pathname
);
const isExpensePage = /^\/expenses$/.test(pathname);
const isPaymentRequest = /^\/payment-request$/.test(pathname);
const isRecurringExpense = /^\/recurring-payment$/.test(pathname);
const isAdvancePayment = /^\/advance-payment$/.test(pathname);
const isServiceProjectPage = /^\/service-projects\/[0-9a-fA-F-]{36}$/.test(pathname);
return !(isDirectoryPath || isProfilePage || isExpensePage);
return !(isDirectoryPath || isProfilePage || isExpensePage || isPaymentRequest || isRecurringExpense || isAdvancePayment ||isServiceProjectPage);
};
const allowedProjectStatusIds = [
"603e994b-a27f-4e5d-a251-f3d69b0498ba",
@ -59,11 +63,7 @@ const Header = () => {
if (!Array.isArray(roles)) return "User";
let role = roles.find((role) => role.id === joRoleId);
return role ? role.name : "User";
};
};
const handleProfilePage = () => {
navigate(`/employee/${profile?.employeeInfo?.id}`);

View File

@ -99,7 +99,7 @@ const MenuItem = (item) => {
className={`menu-link ${hasSubmenu ? "menu-toggle" : ""}`}
target={item.link?.includes("http") ? "_blank" : undefined}
>
<i className={`menu-icon tf-icons ${item.icon}`}></i>
{item.icon && <i className={`menu-icon tf-icons ${item.icon}`}></i>}
<div>{item.name}</div>
{item.available === false && (
<div className="badge bg-label-primary fs-tiny rounded-pill ms-auto">

View File

@ -135,6 +135,19 @@ const ManagOrg = () => {
)}
</div>
<div className="mb-1 text-start">
<Label htmlFor="gstNumber">
GST Number
</Label>
<input
className="form-control form-control-sm"
{...register("gstNumber")}
/>
{errors.gstNumber && (
<span className="danger-text">{errors.gstNumber.message}</span>
)}
</div>
<div className="mb-1 text-start">
<Label htmlFor="email" required>
Email Address
@ -199,8 +212,8 @@ const ManagOrg = () => {
{isCreating || isUpdating
? "Please Wait..."
: orgData
? "Update"
: "Submit"}
? "Update"
: "Submit"}
</button>
</div>
</div>

View File

@ -20,6 +20,7 @@ export const organizationSchema = z.object({
serviceIds: z
.array(z.string())
.min(1, { message: "Service isrequired" }),
gstNumber: z.string().optional(),
});
export const defaultOrganizationValues = {
@ -29,6 +30,7 @@ export const defaultOrganizationValues = {
address: "",
email: "",
serviceIds: [],
gstNumber : ""
};
export const assignedOrgToProject = z.object({

View File

@ -24,7 +24,7 @@ import { useNavigate } from "react-router-dom";
import { useForm } from "react-hook-form";
import { useSelector } from "react-redux";
import { usePaymentRequestContext } from "../../pages/PaymentRequest/PaymentRequestPage";
import { localToUtc } from "../../utils/appUtils";
import { calculateTDSPercentage, localToUtc } from "../../utils/appUtils";
import { usePaymentMode } from "../../hooks/masterHook/useMaster";
import Filelist from "../Expenses/Filelist";
@ -69,15 +69,10 @@ const ActionPaymentRequest = ({ requestId }) => {
const taxAmount = watch("taxAmount") || 0;
const tdsPercentage = watch("tdsPercentage") || 0;
const grossAmount = baseAmount + taxAmount;
const tdsAmount = useMemo(() => (baseAmount * tdsPercentage) / 100, [
baseAmount,
tdsPercentage,
]);
const netPayable = useMemo(() => grossAmount - tdsAmount, [grossAmount, tdsAmount]);
const { grossAmount, tdsAmount, netPayable } = useMemo(() => {
return calculateTDSPercentage(baseAmount, taxAmount, tdsPercentage);
}, [baseAmount, taxAmount, tdsPercentage]);
const userPermissions = useSelector(
(state) => state?.globalVariables?.loginUser?.featurePermissions || []
@ -428,6 +423,7 @@ const ActionPaymentRequest = ({ requestId }) => {
<span className="badge bg-label-secondary">{netPayable.toFixed(2)}</span>
</div>
</div>
</div>
)}

View File

@ -108,7 +108,7 @@ const ManageProjectInfo = ({ project, onClose }) => {
return (
<div className="p-sm-2 p-2">
<div className="text-center mb-2">
<h5 className="mb-2">{project ? "Edit Project" : "Create Project"}</h5>
<h5 className="mb-2">{project ? "Edit Infra Project" : "Create Infra Project"}</h5>
</div>
<form
className="row g-2 text-start"

View File

@ -104,7 +104,6 @@ const hasChanges =
permission: payloadPermissions,
};
console.log("Final payload:", payload);
updatePermission(payload);
};

View File

@ -9,7 +9,7 @@ import {
import ReactApexChart from "react-apexcharts";
import Chart from "react-apexcharts";
const ProjectOverview = ({ project }) => {
const ProjectStatistics = ({ project }) => {
const { data } = useProjects();
const [current_project, setCurrentProject] = useState(
data?.find((pro) => pro.id == project)
@ -133,10 +133,10 @@ const ProjectOverview = ({ project }) => {
},
},
stroke: {
lineCap: "round",
lineCap: "round",
},
labels: ["Progress"],
series: [percentage],
labels: ["Progress"],
series: [percentage],
};
};
const [radialPercentage, setRadialPercentage] = useState(75); // Initial percentage
@ -165,7 +165,7 @@ const ProjectOverview = ({ project }) => {
}, [selectedProject]);
return (
<div className="card" style={{ minHeight: "490px" }}>
<div className="card h-100">
<div className="card-header text-start">
<h5 className="card-action-title mb-0">
{" "}
@ -173,78 +173,78 @@ const ProjectOverview = ({ project }) => {
<span className="ms-2 fw-bold">Project Statistics</span>
</h5>
</div>
<div className="card-body">
<ul className="list-unstyled m-0 p-0">
<li className="d-flex flex-wrap">
<div className="w-100 d-flex flex-wrap">
{/* Centered Chart */}
<div className="w-100 d-flex justify-content-center mb-3">
<div >
<Chart
options={radialBarOptions}
series={radialBarOptions.series}
type="radialBar"
height="100%"
/>
</div>
</div>
{/* Info Section */}
<div className="mb-2" style={{ flex: "1 1 auto" }}>
<div>
{/* Tasks Planned */}
<div className="d-flex align-items-center mb-3">
<div className="avatar me-2">
<span className="avatar-initial rounded-2 bg-label-primary">
<i className="bx bx-check text-primary fs-4"></i>
</span>
<div className="card-body">
<ul className="list-unstyled m-0 p-0">
<li className="d-flex flex-wrap">
<div className="w-100 d-flex flex-wrap">
{/* Centered Chart */}
<div className="w-100 d-flex justify-content-center mb-3">
<div >
<Chart
options={radialBarOptions}
series={radialBarOptions.series}
type="radialBar"
height="100%"
/>
</div>
</div>
<div className="d-flex flex-column text-start">
<small className="fw-bold">Tasks Planned</small>
<h5 className="mb-0">
{FormattedNumber(current_project?.plannedWork)}
</h5>
{/* Info Section */}
<div className="mb-2" style={{ flex: "1 1 auto" }}>
<div>
{/* Tasks Planned */}
<div className="d-flex align-items-center mb-3">
<div className="avatar me-2">
<span className="avatar-initial rounded-2 bg-label-primary">
<i className="bx bx-check text-primary fs-4"></i>
</span>
</div>
<div className="d-flex flex-column text-start">
<small className="fw-bold">Tasks Planned</small>
<h5 className="mb-0">
{FormattedNumber(current_project?.plannedWork)}
</h5>
</div>
</div>
{/* Tasks Completed */}
<div className="d-flex align-items-center mb-3">
<div className="avatar me-2">
<span className="avatar-initial rounded-2 bg-label-info">
<i className="bx bx-star text-info fs-4"></i>
</span>
</div>
<div className="d-flex flex-column text-start">
<small className="fw-bold">Tasks Completed</small>
<h5 className="mb-0">
{FormattedNumber(current_project?.completedWork)}
</h5>
</div>
</div>
{/* Team Size */}
<div className="d-flex align-items-center">
<div className="avatar me-2">
<span className="avatar-initial rounded-2 bg-label-primary">
<i className="bx bx-group text-primary fs-4"></i>
</span>
</div>
<div className="d-flex flex-column text-start">
<small className="fw-bold">Current Team Size</small>
<h5 className="mb-0">
{FormattedNumber(current_project?.teamSize)}
</h5>
</div>
</div>
</div>
</div>
</div>
{/* Tasks Completed */}
<div className="d-flex align-items-center mb-3">
<div className="avatar me-2">
<span className="avatar-initial rounded-2 bg-label-info">
<i className="bx bx-star text-info fs-4"></i>
</span>
</div>
<div className="d-flex flex-column text-start">
<small className="fw-bold">Tasks Completed</small>
<h5 className="mb-0">
{FormattedNumber(current_project?.completedWork)}
</h5>
</div>
</div>
{/* Team Size */}
<div className="d-flex align-items-center">
<div className="avatar me-2">
<span className="avatar-initial rounded-2 bg-label-primary">
<i className="bx bx-group text-primary fs-4"></i>
</span>
</div>
<div className="d-flex flex-column text-start">
<small className="fw-bold">Current Team Size</small>
<h5 className="mb-0">
{FormattedNumber(current_project?.teamSize)}
</h5>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
</li>
</ul>
</div>
</div>
);
};
export default ProjectOverview;
export default ProjectStatistics;

View File

@ -134,7 +134,6 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => {
const StrikeDate = watch("strikeDate")
const onSubmit = (fromdata) => {
console.log(fromdata);
let payload = {
...fromdata,
strikeDate: fromdata.strikeDate

View File

@ -0,0 +1,100 @@
import SelectField from "../common/Forms/SelectField";
import { useJobStatus } from "../../hooks/masterHook/useMaster";
import { SpinnerLoader } from "../common/Loader";
import Error from "../common/Error";
import { z } from "zod";
import {
AppFormController,
AppFormProvider,
useAppForm,
} from "../../hooks/appHooks/useAppForm";
import { zodResolver } from "@hookform/resolvers/zod";
import { useDispatch, useSelector } from "react-redux";
import { closePopup } from "../../slices/localVariablesSlice";
import { useUpdateServiceProjectJob } from "../../hooks/useServiceProject";
export const ChangeStatusSchema = z.object({
statusId: z.string().min(1, { message: "Please select status" }),
});
const ChangeStatus = ({ statusId, projectId, jobId, popUpId }) => {
const { data, isLoading, isError, error } = useJobStatus(statusId, projectId);
const dispatch = useDispatch();
const methods = useAppForm({
resolver: zodResolver(ChangeStatusSchema),
defaultValues: { statusId: "" },
});
const {
control,
handleSubmit,
reset,
formState: { errors },
} = methods;
const { mutate: UpdateStatus, isPending } = useUpdateServiceProjectJob(() => {
// handleClose();
});
const onSubmit = (formData) => {
const payload =
[
{
op: "replace",
path: "/statusId",
value: formData.statusId,
},
];
UpdateStatus({ id: jobId, payload });
};
const handleClose = () => {
dispatch(closePopup(popUpId));
};
return (
<AppFormProvider {...methods}>
<form className="row text-start" onSubmit={handleSubmit(onSubmit)}>
<div className="mb-2">
<AppFormController
name="statusId"
control={control}
render={({ field }) => (
<SelectField
label="Select Status"
options={data ?? []}
placeholder="Choose a Status"
required
labelKeyKey="name"
valueKeyKey="id"
value={field.value}
onChange={field.onChange}
isLoading={isLoading}
className="m-0"
/>
)}
/>
{errors.statusId && (
<small className="danger-text">{errors.statusId.message}</small>
)}
</div>
<div className="d-flex flex-row justify-content-end gap-3">
<button
type="button"
onClick={handleClose}
className="btn btn-label-secondary btn-sm"
>
Cancel
</button>
<button type="submit" className="btn btn-primary btn-sm">
{isPending ? "Please wait" : "Save"}
</button>
</div>
</form>
</AppFormProvider>
);
};
export default ChangeStatus;

View File

@ -22,7 +22,7 @@ const JobComments = ({ data }) => {
formState: { errors },
} = useAppForm({
resolver: zodResolver(JobCommentSchema),
defaultValues:{ comment: "", attachments: [] }
defaultValues: { comment: "", attachments: [] }
});
const {
@ -94,7 +94,6 @@ const JobComments = ({ data }) => {
const newFiles = files.filter((_, i) => i !== index);
setValue("attachments", newFiles, { shouldValidate: true });
};
console.log(watch("attachments"))
return (
<div className="row">
<div className="sticky-section bg-white p-3">
@ -120,38 +119,49 @@ const JobComments = ({ data }) => {
</div>
</div>
<div className="d-flex justify-content-end gap-3 align-items-center text-end mt-3">
<div
onClick={() => document.getElementById("attachments").click()}
className="cursor-pointer"
>
<input
type="file"
accept=".pdf,.jpg,.jpeg,.png"
id="attachments"
multiple
className="d-none"
{...register("attachments")}
onChange={(e) => {
onFileChange(e);
e.target.value = "";
}}
/>
<i className="bx bx-paperclip"></i>
Add Attachment
<div className="d-flex flex-column flex-md-row justify-content-between align-items-start">
{/* LEFT SIDE → Uploaded Files */}
<div className="flex-grow-1 ms-10 mt-2">
{files?.length > 0 && (
<Filelist files={files} removeFile={removeFile} />
)}
</div>
{/* RIGHT SIDE → Add Attachment + Submit */}
<div className="d-flex gap-3 align-items-center text-end mt-3 ms-10 ms-md-0">
<div
onClick={() => document.getElementById("attachments").click()}
className="cursor-pointer"
style={{ whiteSpace: 'nowrap' }}
>
<input
type="file"
accept=".pdf,.jpg,.jpeg,.png"
id="attachments"
multiple
className="d-none"
{...register("attachments")}
onChange={(e) => {
onFileChange(e);
e.target.value = "";
}}
/>
<i className="bx bx-sm bx-paperclip mb-1 me-1"></i>
Add Attachment
</div>
<button
className="btn btn-primary btn-sm px-1 py-1" // smaller padding + slightly smaller font
type="submit"
disabled={!watch("comment")?.trim() || isPending}
>
<i className="bx bx-xs bx-send me-1"></i>
Submit
</button>
</div>
<button
className="btn btn-primary btn-sm px-3"
type="submit"
disabled={!watch("comment")?.trim() || isPending}
>
<i className="bx bx-send me-1"></i>
Submit
</button>
</div>
{files?.length > 0 && (
<Filelist files={files} removeFile={removeFile} />
)}
</form>
</div>
<div className="card-body p-0">

Some files were not shown because too many files have changed in this diff Show More