Merge branch 'Issues_Oct_4W_V1' into migrate_main

This commit is contained in:
pramod.mahajan 2025-11-07 15:17:58 +05:30
commit 7b2bb4d59a
41 changed files with 644 additions and 544 deletions

View File

@ -123,12 +123,15 @@ const AttendLogs = ({ Id }) => {
}, []); }, []);
return ( return (
<div className="table-responsive"> <div className="table-responsive">
<div className="text-start"> <div className="mb-3">
<h5 className="mb-4">Attendance Logs</h5>
{logs && !loading && ( {logs && !loading && (
<p> <p className="mb-0 text-start">
Attendance logs for{" "} Showing logs for{" "}
{logs[0]?.employee?.firstName + " " + logs[0]?.employee?.lastName}{" "} <strong>
on {formatUTCToLocalTime(logs[0]?.activityTime)} {logs[0]?.employee?.firstName + " " + logs[0]?.employee?.lastName}
</strong>{" "}
on <strong>{formatUTCToLocalTime(logs[0]?.activityTime)}</strong>
</p> </p>
)} )}
</div> </div>
@ -142,9 +145,9 @@ const AttendLogs = ({ Id }) => {
<table className="table table-sm mb-0"> <table className="table table-sm mb-0">
<thead> <thead>
<tr> <tr>
<th>Activity</th>
<th>Date</th> <th>Date</th>
<th>Time</th> <th>Time</th>
<th>Activity</th>
<th>Location</th> <th>Location</th>
<th>Recored By</th> <th>Recored By</th>
<th>Description</th> <th>Description</th>
@ -156,11 +159,16 @@ const AttendLogs = ({ Id }) => {
.sort((a, b) => b.id - a.id) .sort((a, b) => b.id - a.id)
.map((log, index) => ( .map((log, index) => (
<tr key={index}> <tr key={index}>
<td>{formatUTCToLocalTime(log.activityTime)}</td>
<td>{convertShortTime(log.activityTime)}</td>
<td> <td>
{whichActivityPerform(log.activity, log.activityTime)} {whichActivityPerform(log.activity, log.activityTime)}
</td> </td>
<td>
<div className="py-2">
{formatUTCToLocalTime(log.activityTime)}
</div>
</td>
<td>{convertShortTime(log.activityTime)}</td>
<td> <td>
{log?.latitude != 0 ? ( {log?.latitude != 0 ? (
<i <i
@ -179,9 +187,8 @@ const AttendLogs = ({ Id }) => {
)} )}
</td> </td>
<td className="text-wrap"> <td className="text-wrap">
{`${log?.updatedByEmployee?.firstName ?? ""} ${ {`${log?.updatedByEmployee?.firstName ?? ""} ${log?.updatedByEmployee?.lastName ?? ""
log?.updatedByEmployee?.lastName ?? "" }`}
}`}
</td> </td>
<td className="text-wrap" colSpan={3}> <td className="text-wrap" colSpan={3}>
{log?.comment?.length > 50 {log?.comment?.length > 50

View File

@ -11,6 +11,7 @@ import { useSelector } from "react-redux";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
import { SpinnerLoader } from "../common/Loader";
const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizationId, }) => { const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizationId, }) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -110,28 +111,41 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
return ( return (
<> <>
<div className="d-flex justify-content-between align-items-center py-2 px-2">
{/* Left side - Date */}
<div className="text-start">
<strong>Date: {formatUTCToLocalTime(todayDate)}</strong>
</div>
{/* Right side - Pending Attendance toggle */}
<div className="form-check form-switch m-0">
<input
type="checkbox"
className="form-check-input"
role="switch"
id="inactiveEmployeesCheckbox"
disabled={isFetching}
checked={ShowPending}
onChange={(e) => setShowPending(e.target.checked)}
/>
<label className="form-check-label" htmlFor="inactiveEmployeesCheckbox">
Pending Attendance
</label>
</div>
</div>
<div <div
className="table-responsive text-nowrap h-100" className="table-responsive modal-min-h text-nowrap h-100"
style={{ minHeight: "200px" }} // Ensures fixed height style={{ minHeight: "200px" }} // Ensures fixed height
> >
<div className="d-flex text-start align-items-center py-2">
<strong>Date : {formatUTCToLocalTime(todayDate)}</strong>
<div className="form-check form-switch text-start m-0 ms-5">
<input
type="checkbox"
className="form-check-input"
role="switch"
id="inactiveEmployeesCheckbox"
disabled={isFetching}
checked={ShowPending}
onChange={(e) => setShowPending(e.target.checked)}
/>
<label className="form-check-label ms-0">Pending Attendance</label>
</div>
</div>
{attLoading ? ( {attLoading ? (
<div>Loading...</div> <div
className="d-flex justify-content-center align-items-center"
style={{ minHeight: "50vh" }}
>
<SpinnerLoader />
</div>
) : currentItems?.length > 0 ? ( ) : currentItems?.length > 0 ? (
<> <>
<table className="table "> <table className="table ">
<thead> <thead>

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState, useMemo, useCallback } from "react"; import React, { useEffect, useState, useMemo, useCallback } from "react";
import moment from "moment"; import moment from "moment";
import Avatar from "../common/Avatar"; import Avatar from "../common/Avatar";
import { convertShortTime } from "../../utils/dateUtils"; import { convertShortTime, formatUTCToLocalTime } from "../../utils/dateUtils";
import RenderAttendanceStatus from "./RenderAttendanceStatus"; import RenderAttendanceStatus from "./RenderAttendanceStatus";
import { useSelector, useDispatch } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
import DateRangePicker from "../common/DateRangePicker"; import DateRangePicker from "../common/DateRangePicker";
@ -16,6 +16,7 @@ import { useAttendancesLogs } from "../../hooks/useAttendance";
import { queryClient } from "../../layouts/AuthLayout"; import { queryClient } from "../../layouts/AuthLayout";
import { ITEMS_PER_PAGE } from "../../utils/constants"; import { ITEMS_PER_PAGE } from "../../utils/constants";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { SpinnerLoader } from "../common/Loader";
const usePagination = (data, itemsPerPage) => { const usePagination = (data, itemsPerPage) => {
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
@ -174,49 +175,51 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
return ( return (
<> <>
<div <div
className="dataTables_length text-start py-2 d-flex flex-wrap justify-content-between" className="dataTables_length text-start py-2 d-flex flex-wrap justify-content-between align-items-center"
id="DataTables_Table_0_length" id="DataTables_Table_0_length"
> >
<div className="d-flex flex-wrap align-items-center gap-2 gap-md-3 my-0"> {/* Left Side - Date Picker */}
{/* Date Range Picker */} <div className="d-flex align-items-center">
<div className="flex-grow-1 flex-md-grow-0"> <DateRangePicker
<DateRangePicker onRangeChange={setDateRange}
onRangeChange={setDateRange} defaultStartDate={yesterday}
defaultStartDate={yesterday} />
/>
</div>
{/* Pending Attendance Switch */}
<div className="form-check form-switch text-start mb-0">
<input
type="checkbox"
className="form-check-input"
role="switch"
id="inactiveEmployeesCheckbox"
disabled={isFetching}
checked={showPending}
onChange={(e) => setShowPending(e.target.checked)}
/>
<label className="form-check-label ms-0 ms-md-0">
Pending Attendance
</label>
</div>
</div> </div>
{/* Right Side - Pending Attendance Switch */}
<div className="form-check form-switch d-flex align-items-center mb-2">
<input
type="checkbox"
className="form-check-input"
role="switch"
id="inactiveEmployeesCheckbox"
disabled={isFetching}
checked={showPending}
onChange={(e) => setShowPending(e.target.checked)}
/>
<label
className="form-check-label ms-2"
htmlFor="inactiveEmployeesCheckbox"
>
Pending Attendance
</label>
</div>
</div> </div>
<div <div
className="table-responsive text-nowrap" className="table-responsive modal-min-h text-nowrap"
style={{ minHeight: "200px" }} style={{ minHeight: "200px" }}
> >
{isLoading ? ( {isLoading ? (
<div <div
className="d-flex justify-content-center align-items-center" className="d-flex justify-content-center align-items-center"
style={{ height: "200px" }} style={{ minHeight: "50vh" }}
> >
<p className="text-secondary">Loading...</p> <SpinnerLoader />
</div> </div>
) : filteredSearchData?.length > 0 ? ( ) : filteredSearchData?.length > 0 ? (
<table className="table mb-0"> <table className="table mb-0">
<thead> <thead>
<tr> <tr>
@ -255,8 +258,8 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
className="table-row-header" className="table-row-header"
> >
<td colSpan={8} className="text-start"> <td colSpan={8} className="text-start">
<strong> <strong className="d-inline-block my-1 ms-2">
{moment(currentDate).format("DD-MM-YYYY")} {formatUTCToLocalTime(currentDate)}
</strong> </strong>
</td> </td>
</tr> </tr>

View File

@ -24,8 +24,7 @@ import { useProfile } from "../../hooks/useProfile";
import { refreshData, setProjectId } from "../../slices/localVariablesSlice"; import { refreshData, setProjectId } from "../../slices/localVariablesSlice";
import InfraTable from "../Project/Infrastructure/InfraTable"; import InfraTable from "../Project/Infrastructure/InfraTable";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
import Loader from "../common/Loader"; import { SpinnerLoader } from "../common/Loader";
const InfraPlanning = () => { const InfraPlanning = () => {
const { profile: LoggedUser, refetch: fetchData } = useProfile(); const { profile: LoggedUser, refetch: fetchData } = useProfile();
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -57,7 +56,14 @@ const InfraPlanning = () => {
} }
if (isLoading) { if (isLoading) {
return <Loader />; return (
<div
className="d-flex justify-content-center align-items-center"
style={{ height: "60vh" }}
>
<SpinnerLoader />
</div>
);
} }
if (isFetched && (!projectInfra || projectInfra.length === 0)) { if (isFetched && (!projectInfra || projectInfra.length === 0)) {

View File

@ -15,6 +15,7 @@ import {
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import Pagination from "../../components/common/Pagination"; import Pagination from "../../components/common/Pagination";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { SpinnerLoader } from "../common/Loader";
const Regularization = ({ const Regularization = ({
handleRequest, handleRequest,
@ -106,15 +107,15 @@ const Regularization = ({
return ( return (
<div> <div>
<div <div
className="table-responsive pt-3 text-nowrap pb-4" className="table-responsive modal-min-h pt-3 text-nowrap pb-4"
style={{ minHeight: "200px" }} style={{ minHeight: "200px" }}
> >
{loading ? ( {loading ? (
<div <div
className="d-flex justify-content-center align-items-center" className="d-flex justify-content-center align-items-center"
style={{ height: "200px" }} style={{ minHeight: "50vh" }}
> >
<p className="text-secondary">Loading...</p> <SpinnerLoader />
</div> </div>
) : currentItems?.length > 0 ? ( ) : currentItems?.length > 0 ? (
<table className="table mb-0"> <table className="table mb-0">

View File

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import ReactApexChart from "react-apexcharts"; import ReactApexChart from "react-apexcharts";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { SpinnerLoader } from "../common/Loader";
const HorizontalBarChart = ({ const HorizontalBarChart = ({
seriesData = [], seriesData = [],
@ -23,8 +24,12 @@ const HorizontalBarChart = ({
if (loading) { if (loading) {
return ( return (
<div className="w-full h-[380px] flex items-center justify-center bg-gray-100 rounded-xl"> <div className="w-full h-[380px] flex items-center justify-center bg-gray-100 rounded-xl">
<span className="text-gray-500">Loading chart...</span> <div
{/* Replace this with a skeleton or spinner if you prefer */} className="d-flex justify-content-center align-items-center"
style={{ minHeight: "50vh" }}
>
<SpinnerLoader />
</div>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import ReactApexChart from "react-apexcharts"; import ReactApexChart from "react-apexcharts";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { SpinnerLoader } from "../common/Loader";
const LineChart = ({ const LineChart = ({
seriesData = [], seriesData = [],
@ -9,24 +10,28 @@ const LineChart = ({
loading = false, loading = false,
lineChartCategoriesDates = [], lineChartCategoriesDates = [],
}) => { }) => {
const hasValidData = const hasValidData =
Array.isArray(seriesData) && Array.isArray(seriesData) &&
seriesData.length > 0 && seriesData.length > 0 &&
Array.isArray(categories) && Array.isArray(categories) &&
categories.length > 0; categories.length > 0;
if (loading) { if (loading) {
return ( return (
<div className="flex justify-center items-center h-[350px] text-gray-500"> <div className="flex justify-center items-center h-[350px] text-gray-500">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500 mr-2" /> <div
Loading chart... className="d-flex justify-content-center align-items-center"
</div> style={{ minHeight: "50vh" }}
); >
} <SpinnerLoader />
</div>
</div>
);
}
if (!hasValidData) { if (!hasValidData) {
return <div className="text-center text-gray-500">No data to display</div>; return <div className="text-center text-gray-500">No data to display</div>;
} }
const chartOptions = { const chartOptions = {
chart: { chart: {
@ -129,16 +134,16 @@ const LineChart = ({
}; };
LineChart.propTypes = { LineChart.propTypes = {
seriesData: PropTypes.arrayOf( seriesData: PropTypes.arrayOf(
PropTypes.shape({ PropTypes.shape({
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
data: PropTypes.arrayOf(PropTypes.number).isRequired data: PropTypes.arrayOf(PropTypes.number).isRequired
}) })
), ),
categories: PropTypes.arrayOf(PropTypes.string), categories: PropTypes.arrayOf(PropTypes.string),
colors: PropTypes.arrayOf(PropTypes.string), colors: PropTypes.arrayOf(PropTypes.string),
title: PropTypes.string, title: PropTypes.string,
loading: PropTypes.bool loading: PropTypes.bool
}; };
export default LineChart; export default LineChart;

View File

@ -4,6 +4,7 @@ import ReactApexChart from "react-apexcharts";
import { useAttendanceOverviewData } from "../../hooks/useDashboard_Data"; import { useAttendanceOverviewData } from "../../hooks/useDashboard_Data";
import flatColors from "../Charts/flatColor"; import flatColors from "../Charts/flatColor";
import ChartSkeleton from "../Charts/Skelton"; import ChartSkeleton from "../Charts/Skelton";
import { SpinnerLoader } from "../common/Loader";
const formatDate = (dateStr) => { const formatDate = (dateStr) => {
const date = new Date(dateStr); const date = new Date(dateStr);
@ -99,7 +100,7 @@ const AttendanceOverview = () => {
}; };
return ( return (
<div className="bg-white p-4 rounded shadow d-flex flex-column"> <div className="bg-white p-4 rounded shadow d-flex flex-column h-100">
{/* Header */} {/* Header */}
<div className="d-flex justify-content-between align-items-center mb-3"> <div className="d-flex justify-content-between align-items-center mb-3">
<div className="card-title mb-0 text-start"> <div className="card-title mb-0 text-start">
@ -117,18 +118,16 @@ const AttendanceOverview = () => {
<option value={30}>Last 30 Days</option> <option value={30}>Last 30 Days</option>
</select> </select>
<button <button
className={`btn btn-sm p-1 ${ className={`btn btn-sm p-1 ${view === "chart" ? "btn-primary" : "btn-outline-primary"
view === "chart" ? "btn-primary" : "btn-outline-primary" }`}
}`}
onClick={() => setView("chart")} onClick={() => setView("chart")}
title="Chart View" title="Chart View"
> >
<i className="bx bx-bar-chart-alt-2"></i> <i className="bx bx-bar-chart-alt-2"></i>
</button> </button>
<button <button
className={`btn btn-sm p-1 ${ className={`btn btn-sm p-1 ${view === "table" ? "btn-primary" : "btn-outline-primary"
view === "table" ? "btn-primary" : "btn-outline-primary" }`}
}`}
onClick={() => setView("table")} onClick={() => setView("table")}
title="Table View" title="Table View"
> >
@ -138,11 +137,14 @@ const AttendanceOverview = () => {
</div> </div>
{/* Content */} {/* Content */}
<div className="flex-grow-1 d-flex align-items-center justify-content-center"> <div className="flex-grow-1 d-flex align-items-center justify-content-center">
{loading ? ( {loading ? (
<ChartSkeleton /> <SpinnerLoader />
) : error ? ( ) : error ? (
<p className="text-danger">{error}</p> <p className="text-danger">{error}</p>
) : attendanceOverviewData.length === 0 ||
attendanceOverviewData.every((item) => item.present === 0) ? (
<div className="text-center text-dark">No data found</div>
) : view === "chart" ? ( ) : view === "chart" ? (
<div className="w-100"> <div className="w-100">
<ReactApexChart <ReactApexChart
@ -163,9 +165,7 @@ const AttendanceOverview = () => {
style={{ position: "sticky", top: 0, zIndex: 1 }} style={{ position: "sticky", top: 0, zIndex: 1 }}
> >
<tr> <tr>
<th style={{ background: "#f8f9fa", textTransform: "none" }}> <th style={{ background: "#f8f9fa", textTransform: "none" }}>Role</th>
Role
</th>
{dates.map((date, idx) => ( {dates.map((date, idx) => (
<th <th
key={idx} key={idx}
@ -176,15 +176,13 @@ const AttendanceOverview = () => {
))} ))}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{roles.map((role) => ( {roles.map((role) => (
<tr key={role}> <tr key={role}>
<td>{role}</td> <td>{role}</td>
{tableData.map((row, idx) => { {tableData.map((row, idx) => {
const value = row[role]; const value = row[role];
const cellStyle = const cellStyle = value > 0 ? { backgroundColor: "#d5d5d5" } : {};
value > 0 ? { backgroundColor: "#d5d5d5" } : {};
return ( return (
<td key={idx} style={cellStyle}> <td key={idx} style={cellStyle}>
{value} {value}

View File

@ -63,12 +63,12 @@ const Dashboard = () => {
</div> </div>
<div className="col-12 col-xl-4 col-md-6"> <div className="col-12 col-xl-4 col-md-6">
<div className="card "> <div className="card h-100">
<ExpenseStatus /> <ExpenseStatus />
</div> </div>
</div> </div>
{!isAllProjectsSelected && ( {!isAllProjectsSelected && (
<div className="col-12 col-md-6 mb-sm-0 mb-4 "> <div className="col-12 col-md-6 mb-sm-0 mb-4">
<AttendanceOverview /> <AttendanceOverview />
</div> </div>
)} )}

View File

@ -6,6 +6,7 @@ import { DateRangePicker1 } from "../common/DateRangePicker";
import { FormProvider, useForm } from "react-hook-form"; import { FormProvider, useForm } from "react-hook-form";
import { formatCurrency, localToUtc } from "../../utils/appUtils"; import { formatCurrency, localToUtc } from "../../utils/appUtils";
import { useProjectName } from "../../hooks/useProjects"; import { useProjectName } from "../../hooks/useProjects";
import { SpinnerLoader } from "../common/Loader";
const ExpenseAnalysis = () => { const ExpenseAnalysis = () => {
const projectId = useSelectedProject(); const projectId = useSelectedProject();
@ -78,86 +79,92 @@ const ExpenseAnalysis = () => {
return ( 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 w-100">
<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-start text-sm-end w-75"> <div className="card-header d-flex flex-column flex-sm-row justify-content-between align-items-start align-items-sm-center gap-2">
<FormProvider {...methods}> <div className="text-start w-100">
<DateRangePicker1 /> <h5 className="mb-1 card-title">Expense Breakdown</h5>
</FormProvider> {/* <p className="card-subtitle mb-0">Category Wise Expense Breakdown</p> */}
</div> <p className="card-subtitle m-0">{projectName}</p>
</div> </div>
{/* Card body */} <div className="text-start text-sm-end w-75">
<div className="card-body position-relative"> <FormProvider {...methods}>
{isLoading && ( <DateRangePicker1 />
<div </FormProvider>
className="d-flex justify-content-center align-items-center" </div>
style={{ height: "200px" }} </div>
>
<span>Loading...</span> {/* Card body */}
<div className="card-body position-relative">
{isLoading && (
<div
className="d-flex justify-content-center align-items-center"
style={{ minHeight: "50vh" }}
>
<SpinnerLoader />
</div>
)}
{!isLoading && report.length === 0 && (
<div
className="d-flex justify-content-center align-items-center text-muted"
style={{ height: "300px" }}
>
No data found
</div>
)}
{!isLoading && report.length > 0 && (
<>
{isFetching && (
<div className="position-absolute top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center bg-white bg-opacity-75">
<span>Loading...</span>
</div>
)}
<div className="d-flex justify-content-center mb-3">
<Chart
options={donutOptions}
series={series}
type="donut"
width="100%"
height={320}
/>
</div> </div>
)}
{!isLoading && report.length === 0 && ( <div className="mb-2 w-100">
<div className="text-center py-5 text-muted">No data found</div> <div className="row g-2">
)} {report.map((item, idx) => (
<div
{!isLoading && report.length > 0 && ( className="col-12 col-sm-6 d-flex align-items-start"
<> key={idx}
{isFetching && ( >
<div className="position-absolute top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center bg-white bg-opacity-75"> <div className="avatar me-2">
<span>Loading...</span> <span
</div> className="avatar-initial rounded-2"
)} style={{
backgroundColor:
<div className="d-flex justify-content-center mb-3"> donutOptions.colors[idx % donutOptions.colors.length],
<Chart }}
options={donutOptions} >
series={series} <i className="bx bx-receipt fs-4"></i>
type="donut" </span>
width="100%"
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>
</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 className="d-flex flex-column gap-1 text-start">
</div> <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> )}
</div>
{/* Header */} {/* Header */}
</> </>

View File

@ -7,6 +7,7 @@ import { formatCurrency } from "../../utils/appUtils";
import { formatDate_DayMonth } from "../../utils/dateUtils"; import { formatDate_DayMonth } from "../../utils/dateUtils";
import { useProjectName } from "../../hooks/useProjects"; import { useProjectName } from "../../hooks/useProjects";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
import { SpinnerLoader } from "../common/Loader";
const ExpenseByProject = () => { const ExpenseByProject = () => {
const projectId = useSelector((store) => store.localVariables.projectId); const projectId = useSelector((store) => store.localVariables.projectId);
@ -63,6 +64,12 @@ const ExpenseByProject = () => {
categories: chartData.categories, categories: chartData.categories,
labels: { style: { fontSize: "12px" }, rotate: -45 }, labels: { style: { fontSize: "12px" }, rotate: -45 },
}, },
yaxis: {
labels: {
formatter: (val) => formatCurrency(val),
style: { fontSize: "12px", colors: "#555" },
},
},
tooltip: { tooltip: {
y: { y: {
formatter: (val) => `${formatCurrency(val)} (${getSelectedTypeName()})`, formatter: (val) => `${formatCurrency(val)} (${getSelectedTypeName()})`,
@ -82,15 +89,15 @@ const ExpenseByProject = () => {
]; ];
return ( return (
<div className="card shadow-sm rounded "> <div className="card shadow-sm h-100 rounded ">
{/* Header */} {/* Header */}
<div className="card-header"> <div className="card-header">
<div className="d-flex justify-content-start align-items-center mb-3 mt-3"> <div className="d-flex justify-content-start align-items-center mb-1 mt-1">
<div className="text-start"> <div className="text-start">
<h5 className="mb-1 me-6 card-title">Monthly Expense -</h5> <h5 className="mb-1 me-6 card-title">Monthly Expense -</h5>
<p className="card-subtitle m-0">{projectName}</p> <p className="card-subtitle m-0">{projectName}</p>
</div> </div>
<div className="btn-group mb-4 ms-n8"> <div className="btn-group mb-5 ms-n8">
<button <button
className="btn btn-sm dropdown-toggle fs-5" className="btn btn-sm dropdown-toggle fs-5"
type="button" type="button"
@ -163,12 +170,15 @@ const ExpenseByProject = () => {
{/* Chart */} {/* Chart */}
<div className="card-body bg-white text-dark p-3 rounded" style={{ minHeight: "210px" }}> <div className="card-body bg-white text-dark p-3 rounded" style={{ minHeight: "210px" }}>
{isLoading ? ( {isLoading ? (
<p>Loading chart...</p> <div className="d-flex justify-content-center align-items-center py-5">
<SpinnerLoader />
</div>
) : !expenseApiData || expenseApiData.length === 0 ? ( ) : !expenseApiData || expenseApiData.length === 0 ? (
<div className="text-center text-muted py-5">No data found</div> <div className="text-center text-muted py-12">No data found</div>
) : ( ) : (
<Chart options={options} series={series} type="bar" height={235} /> <Chart options={options} series={series} type="bar" height={235} />
)} )}
</div> </div>
</div> </div>

View File

@ -24,7 +24,7 @@ const ProjectCompletionChart = () => {
<p className="card-subtitle">Projects Completion Status</p> <p className="card-subtitle">Projects Completion Status</p>
</div> </div>
</div> </div>
<div className="card-body"> <div className="card-body ms-n7">
<HorizontalBarChart <HorizontalBarChart
categories={projectNames} categories={projectNames}
seriesData={projectProgress} seriesData={projectProgress}

View File

@ -44,9 +44,9 @@ const TasksCard = () => {
</span> </span>
</div> </div>
) : ( ) : (
<div className="d-flex justify-content-around align-items-start flex-wrap mt-n2"> <div className="d-flex justify-content-around align-items-start mt-n2">
{/* Total Tasks */} {/* Total Tasks */}
<div className="text-center flex-fill p-2"> <div>
<h4 className="mb-0 fw-bold text-truncate"> <h4 className="mb-0 fw-bold text-truncate">
{formatFigure(tasksCardData?.totalTasks ?? 0, { {formatFigure(tasksCardData?.totalTasks ?? 0, {
notation: "compact", notation: "compact",
@ -56,7 +56,7 @@ const TasksCard = () => {
</div> </div>
{/* Completed Tasks */} {/* Completed Tasks */}
<div className="text-center flex-fill p-2"> <div>
<h4 className="mb-0 fw-bold text-truncate"> <h4 className="mb-0 fw-bold text-truncate">
{formatFigure(tasksCardData?.completedTasks ?? 0, { {formatFigure(tasksCardData?.completedTasks ?? 0, {
notation: "compact", notation: "compact",

View File

@ -61,7 +61,7 @@ const CardViewContact = ({
(contact?.name || "").trim().split(" ")[1]?.charAt(0) || "" (contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""
} }
/>{" "} />{" "}
<span className="text-heading fs-6"> {contact?.name}</span> <span className="text-heading fs-6 ms-2"> {contact?.name}</span>
</div> </div>
<div> <div>
{IsActive && ( {IsActive && (

View File

@ -87,7 +87,7 @@ const NoteCardDirectoryEditable = ({
/> />
<div> <div>
<div <div
className="d-flex ms-0 align-middle cursor-pointer" className="d-flex ms-3 align-middle cursor-pointer"
onClick={() => contactProfile(noteItem.contactId)} onClick={() => contactProfile(noteItem.contactId)}
> >
<span> <span>
@ -98,7 +98,7 @@ const NoteCardDirectoryEditable = ({
</span> </span>
</div> </div>
<div className="d-flex ms-0 align-middle"></div> <div className="d-flex ms-0 align-middle"></div>
<div className="d-flex ms-0 mt-2"> <div className="d-flex ms-3 mt-2">
<span className="text-muted"> <span className="text-muted">
by{" "} by{" "}
<span className="fw-bold "> <span className="fw-bold ">
@ -184,7 +184,7 @@ const NoteCardDirectoryEditable = ({
</> </>
) : ( ) : (
<div <div
className="mx-4 px-10 text-start" className="mx-4 px-11 text-start"
dangerouslySetInnerHTML={{ __html: noteItem.note }} dangerouslySetInnerHTML={{ __html: noteItem.note }}
/> />
)} )}

View File

@ -13,230 +13,236 @@ import { useParams } from "react-router-dom";
const DocumentFilterPanel = forwardRef( const DocumentFilterPanel = forwardRef(
({ entityTypeId, onApply, setFilterdata }, ref) => { ({ entityTypeId, onApply, setFilterdata }, ref) => {
const [resetKey, setResetKey] = useState(0); const [resetKey, setResetKey] = useState(0);
const { status } = useParams(); const { status } = useParams();
const { data, isError, isLoading, error } = const { data, isError, isLoading, error } =
useDocumentFilterEntities(entityTypeId); useDocumentFilterEntities(entityTypeId);
//changes useEffect(() => {
return () => {
closePanel();
};
}, []);
const dynamicDocumentFilterDefaultValues = useMemo(() => { //changes
return {
...DocumentFilterDefaultValues, const dynamicDocumentFilterDefaultValues = useMemo(() => {
uploadedByIds: DocumentFilterDefaultValues.uploadedByIds || [], return {
documentCategoryIds: DocumentFilterDefaultValues.documentCategoryIds || [], ...DocumentFilterDefaultValues,
documentTypeIds: DocumentFilterDefaultValues.documentTypeIds || [], uploadedByIds: DocumentFilterDefaultValues.uploadedByIds || [],
documentTagIds: DocumentFilterDefaultValues.documentTagIds || [], documentCategoryIds: DocumentFilterDefaultValues.documentCategoryIds || [],
startDate: DocumentFilterDefaultValues.startDate, documentTypeIds: DocumentFilterDefaultValues.documentTypeIds || [],
endDate: DocumentFilterDefaultValues.endDate, documentTagIds: DocumentFilterDefaultValues.documentTagIds || [],
startDate: DocumentFilterDefaultValues.startDate,
endDate: DocumentFilterDefaultValues.endDate,
};
}, [status]);
const methods = useForm({
resolver: zodResolver(DocumentFilterSchema),
defaultValues: dynamicDocumentFilterDefaultValues,
});
const { handleSubmit, reset, setValue, watch } = methods;
// Watch values from form
const isUploadedAt = watch("isUploadedAt");
const isVerified = watch("isVerified");
// Close the offcanvas (bootstrap specific)
const closePanel = () => {
document.querySelector(".offcanvas.show .btn-close")?.click();
}; };
}, [status]); useImperativeHandle(ref, () => ({
resetFieldValue: (name, value) => {
if (value !== undefined) {
setValue(name, value);
} else {
reset({ ...methods.getValues(), [name]: DocumentFilterDefaultValues[name] });
}
},
getValues: methods.getValues, // optional, to read current filter state
}));
const methods = useForm({ //changes
resolver: zodResolver(DocumentFilterSchema), useEffect(() => {
defaultValues: dynamicDocumentFilterDefaultValues, if (data && setFilterdata) {
}); setFilterdata(data);
const { handleSubmit, reset, setValue, watch } = methods;
// Watch values from form
const isUploadedAt = watch("isUploadedAt");
const isVerified = watch("isVerified");
// Close the offcanvas (bootstrap specific)
const closePanel = () => {
document.querySelector(".offcanvas.show .btn-close")?.click();
};
useImperativeHandle(ref, () => ({
resetFieldValue: (name, value) => {
if (value !== undefined) {
setValue(name, value);
} else {
reset({ ...methods.getValues(), [name]: DocumentFilterDefaultValues[name] });
} }
}, }, [data, setFilterdata]);
getValues: methods.getValues, // optional, to read current filter state
}));
//changes const onSubmit = (values) => {
useEffect(() => { onApply({
if (data && setFilterdata) { ...values,
setFilterdata(data); startDate: values.startDate
} ? moment.utc(values.startDate, "DD-MM-YYYY").toISOString()
}, [data, setFilterdata]); : null,
endDate: values.endDate
? moment.utc(values.endDate, "DD-MM-YYYY").toISOString()
: null,
});
// closePanel();
};
const onSubmit = (values) => { const onClear = () => {
onApply({ reset(DocumentFilterDefaultValues);
...values, setResetKey((prev) => prev + 1);
startDate: values.startDate onApply(DocumentFilterDefaultValues);
? moment.utc(values.startDate, "DD-MM-YYYY").toISOString() // closePanel();
: null, };
endDate: values.endDate
? moment.utc(values.endDate, "DD-MM-YYYY").toISOString()
: null,
});
// closePanel();
};
const onClear = () => { if (isLoading) return <div>Loading...</div>;
reset(DocumentFilterDefaultValues); if (isError)
setResetKey((prev) => prev + 1); return <div>Error: {error?.message || "Something went wrong!"}</div>;
onApply(DocumentFilterDefaultValues);
// closePanel();
};
if (isLoading) return <div>Loading...</div>; const {
if (isError) uploadedBy = [],
return <div>Error: {error?.message || "Something went wrong!"}</div>; documentCategory = [],
documentType = [],
const { documentTag = [],
uploadedBy = [], } = data?.data || {};
documentCategory = [],
documentType = [],
documentTag = [],
} = data?.data || {};
return ( return (
<FormProvider {...methods}> <FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
{/* Date Range Section */} {/* Date Range Section */}
<div className="mb-2"> <div className="mb-2">
<div className="text-start d-flex align-items-center my-1"> <div className="text-start d-flex align-items-center my-1">
<label className="form-label me-2 my-0">Choose Date:</label> <label className="form-label me-2 my-0">Choose Date:</label>
<div className="d-inline-flex border rounded-pill overflow-hidden shadow-none"> <div className="d-inline-flex border rounded-pill overflow-hidden shadow-none">
<button <button
type="button" type="button"
className={`btn px-2 py-1 rounded-0 text-tiny ${isUploadedAt ? "active btn-secondary text-white" : "" className={`btn px-2 py-1 rounded-0 text-tiny ${isUploadedAt ? "active btn-secondary text-white" : ""
}`} }`}
onClick={() => setValue("isUploadedAt", true)} onClick={() => setValue("isUploadedAt", true)}
> >
Uploaded On Uploaded On
</button> </button>
<button <button
type="button" type="button"
className={`btn px-2 py-1 rounded-0 text-tiny ${!isUploadedAt ? "active btn-secondary text-white" : "" className={`btn px-2 py-1 rounded-0 text-tiny ${!isUploadedAt ? "active btn-secondary text-white" : ""
}`} }`}
onClick={() => setValue("isUploadedAt", false)} onClick={() => setValue("isUploadedAt", false)}
> >
Updated On Updated On
</button> </button>
</div>
</div>
<DateRangePicker1
placeholder="DD-MM-YYYY To DD-MM-YYYY"
startField="startDate"
endField="endDate"
defaultRange={false}
resetSignal={resetKey}
maxDate={new Date()}
/>
</div>
{/* Dropdown Filters */}
<div className="row g-2 text-start">
<SelectMultiple
name="uploadedByIds"
label="Uploaded By:"
options={uploadedBy}
labelKey="name"
valueKey="id"
/>
<SelectMultiple
name="documentCategoryIds"
label="Document Category:"
options={documentCategory}
labelKey="name"
valueKey="id"
/>
<SelectMultiple
name="documentTypeIds"
label="Document Type:"
options={documentType}
labelKey="name"
valueKey="id"
/>
<SelectMultiple
name="documentTagIds"
label="Tags:"
options={documentTag}
labelKey="name"
valueKey="id"
/>
</div>
{/* Status Filter */}
<div className="text-start my-2">
<label className="form-label d-block mb-2">Choose Status:</label>
<div className="d-flex gap-4">
<label className="switch switch-sm">
<input
type="radio"
className="switch-input"
name="isVerified"
checked={isVerified === null}
onChange={() => setValue("isVerified", null)}
/>
<span className="switch-toggle-slider">
<span className="switch-on"></span>
<span className="switch-off"></span>
</span>
<span className="switch-label">All</span>
</label>
<label className="switch switch-sm">
<input
type="radio"
className="switch-input"
name="isVerified"
checked={isVerified === true}
onChange={() => setValue("isVerified", true)}
/>
<span className="switch-toggle-slider">
<span className="switch-on"></span>
<span className="switch-off"></span>
</span>
<span className="switch-label">Verified</span>
</label>
<label className="switch switch-sm">
<input
type="radio"
className="switch-input"
name="isVerified"
checked={isVerified === false}
onChange={() => setValue("isVerified", false)}
/>
<span className="switch-toggle-slider">
<span className="switch-on"></span>
<span className="switch-off"></span>
</span>
<span className="switch-label">Rejected</span>
</label>
</div> </div>
</div> </div>
<DateRangePicker1 {/* Footer Buttons */}
placeholder="DD-MM-YYYY To DD-MM-YYYY" <div className="d-flex justify-content-end py-3 gap-2">
startField="startDate" <button
endField="endDate" type="button"
defaultRange={false} className="btn btn-label-secondary btn-sm"
resetSignal={resetKey} onClick={onClear}
maxDate={new Date()} >
/> Clear
</div> </button>
<button type="submit" className="btn btn-primary btn-sm">
{/* Dropdown Filters */} Apply
<div className="row g-2 text-start"> </button>
<SelectMultiple
name="uploadedByIds"
label="Uploaded By:"
options={uploadedBy}
labelKey="name"
valueKey="id"
/>
<SelectMultiple
name="documentCategoryIds"
label="Document Category:"
options={documentCategory}
labelKey="name"
valueKey="id"
/>
<SelectMultiple
name="documentTypeIds"
label="Document Type:"
options={documentType}
labelKey="name"
valueKey="id"
/>
<SelectMultiple
name="documentTagIds"
label="Tags:"
options={documentTag}
labelKey="name"
valueKey="id"
/>
</div>
{/* Status Filter */}
<div className="text-start my-2">
<label className="form-label d-block mb-2">Choose Status:</label>
<div className="d-flex gap-4">
<label className="switch switch-sm">
<input
type="radio"
className="switch-input"
name="isVerified"
checked={isVerified === null}
onChange={() => setValue("isVerified", null)}
/>
<span className="switch-toggle-slider">
<span className="switch-on"></span>
<span className="switch-off"></span>
</span>
<span className="switch-label">All</span>
</label>
<label className="switch switch-sm">
<input
type="radio"
className="switch-input"
name="isVerified"
checked={isVerified === true}
onChange={() => setValue("isVerified", true)}
/>
<span className="switch-toggle-slider">
<span className="switch-on"></span>
<span className="switch-off"></span>
</span>
<span className="switch-label">Verified</span>
</label>
<label className="switch switch-sm">
<input
type="radio"
className="switch-input"
name="isVerified"
checked={isVerified === false}
onChange={() => setValue("isVerified", false)}
/>
<span className="switch-toggle-slider">
<span className="switch-on"></span>
<span className="switch-off"></span>
</span>
<span className="switch-label">Rejected</span>
</label>
</div> </div>
</div> </form>
</FormProvider>
{/* Footer Buttons */} );
<div className="d-flex justify-content-end py-3 gap-2"> });
<button
type="button"
className="btn btn-label-secondary btn-sm"
onClick={onClear}
>
Clear
</button>
<button type="submit" className="btn btn-primary btn-sm">
Apply
</button>
</div>
</form>
</FormProvider>
);
});
export default DocumentFilterPanel; export default DocumentFilterPanel;

View File

@ -74,7 +74,7 @@ const DocumentVersionList = ({
firstName={currentDoc.uploadedBy?.firstName} firstName={currentDoc.uploadedBy?.firstName}
lastName={currentDoc.uploadedBy?.lastName} lastName={currentDoc.uploadedBy?.lastName}
/> />
<span className="ms-1"> <span className="ms-3">
<small className="fw-normal" style={{ marginLeft: "-10px" }}> <small className="fw-normal" style={{ marginLeft: "-10px" }}>
{`${currentDoc.uploadedBy?.firstName ?? ""} ${currentDoc.uploadedBy?.lastName ?? ""}`.trim() || "N/A"} {`${currentDoc.uploadedBy?.firstName ?? ""} ${currentDoc.uploadedBy?.lastName ?? ""}`.trim() || "N/A"}
</small> </small>
@ -196,7 +196,7 @@ const DocumentVersionList = ({
firstName={document.uploadedBy?.firstName} firstName={document.uploadedBy?.firstName}
lastName={document.uploadedBy?.lastName} lastName={document.uploadedBy?.lastName}
/> />
<span className="ms-1"> <span className="ms-3">
<small className="fw-normal" style={{ marginLeft: "-10px" }}> <small className="fw-normal" style={{ marginLeft: "-10px" }}>
{`${document.uploadedBy?.firstName ?? ""} ${document.uploadedBy?.lastName ?? ""}`.trim() || "N/A"} {`${document.uploadedBy?.firstName ?? ""} ${document.uploadedBy?.lastName ?? ""}`.trim() || "N/A"}
</small> </small>
@ -216,7 +216,7 @@ const DocumentVersionList = ({
firstName={document.verifiedBy?.firstName} firstName={document.verifiedBy?.firstName}
lastName={document.verifiedBy?.lastName} lastName={document.verifiedBy?.lastName}
/> />
<span className="ms-1"> <span className="ms-3">
<small className="fw-normal" style={{ marginLeft: "-10px" }}> <small className="fw-normal" style={{ marginLeft: "-10px" }}>
{`${document.verifiedBy?.firstName ?? ""} ${document.verifiedBy?.lastName ?? ""}`.trim() || "N/A"} {`${document.verifiedBy?.firstName ?? ""} ${document.verifiedBy?.lastName ?? ""}`.trim() || "N/A"}
</small> </small>

View File

@ -12,13 +12,13 @@ const EmpActivities = ({ employee }) => {
const { const {
data, data,
isError, isError,
isLoading, isLoading,
error, error,
refetch, refetch,
} = useProjectTasksByEmployee(employee?.id,dateRange.startDate,dateRange.endDate); } = useProjectTasksByEmployee(employee?.id, dateRange.startDate, dateRange.endDate);
if(isLoading) return <div>Loading...</div> if (isLoading) return <div>Loading...</div>
return ( return (
<> <>
<div className="card h-100 mt-4"> <div className="card h-100 mt-4">
@ -31,28 +31,28 @@ error,
/> />
</div> </div>
<ul className="timeline mb-0 mt-5 text-start"> <ul className="timeline mb-0 mt-5 text-start">
{data?.map((activity)=>( {data?.map((activity) => (
<li className="timeline-item timeline-item-transparent"> <li className="timeline-item timeline-item-transparent">
<span className="timeline-point timeline-point-primary"></span> <span className="timeline-point timeline-point-primary"></span>
<div className="timeline-event"> <div className="timeline-event">
<div className="timeline-header mb-3"> <div className="timeline-header mb-3">
<h6 className="mb-0">{activity.projectName}</h6> <h6 className="mb-0">{activity.projectName}</h6>
<small className="text-body-secondary"> <small className="text-body-secondary">
{useFormattedDate(activity.assignmentDate, "dd-MMM-yyyy")} {useFormattedDate(activity.assignmentDate, "dd-MMM-yyyy")}
</small> </small>
</div>
<p className="mb-2"><span className="fw-semibold">Activity:</span>{activity.activityName}</p>
<p className="mb-2">
<span className="fw-semibold">Location:</span> {activity.location}
</p>
<p className="mb-2">
<span className="fw-semibold">Planned: {activity.plannedTask}</span>
<span className="ms-2">Completed : {activity.completedTask}</span>
</p>
</div> </div>
<p className="mb-2"><span className="fw-semibold">Activity:</span>{activity.activityName}</p> </li>
<p className="mb-2">
<span className="fw-semibold">Location:</span> {activity.location}
</p>
<p className="mb-2">
<span className="fw-semibold">Planned: {activity.plannedTask}</span>
<span className="ms-2">Completed : {activity.completedTask}</span>
</p>
</div>
</li>
))} ))}
{/* <li className="timeline-item timeline-item-transparent"> {/* <li className="timeline-item timeline-item-transparent">
<span className="timeline-point timeline-point-success"></span> <span className="timeline-point timeline-point-success"></span>

View File

@ -15,6 +15,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { localToUtc } from "../../utils/appUtils"; import { localToUtc } from "../../utils/appUtils";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { SpinnerLoader } from "../common/Loader";
const EmpAttendance = () => { const EmpAttendance = () => {
const { employeeId } = useParams(); const { employeeId } = useParams();
@ -82,21 +83,19 @@ const EmpAttendance = () => {
</FormProvider> </FormProvider>
</> </>
</div> </div>
<div className="col-md-2 m-0 text-end">
<i
className={`bx bx-refresh cursor-pointer fs-4 ${
isFetching ? "spin" : ""
}`}
data-toggle="tooltip"
title="Refresh"
onClick={() => refetch()}
/>
</div>
</div> </div>
<div className="table-responsive text-nowrap"> <div className="table-responsive text-nowrap">
{!loading && data.length === 0 && <span>No employee logs</span>} {!loading && data.length === 0 && (
<div className="text-center py-5">No employee logs</div>
)}
{isError && <div className="text-center">{error.message}</div>} {isError && <div className="text-center">{error.message}</div>}
{loading && !data && <div className="text-center">Loading...</div>} {loading && (
<div className="d-flex justify-content-center align-items-center" style={{ minHeight: "200px" }}>
<SpinnerLoader />
</div>
)}
{data && data.length > 0 && ( {data && data.length > 0 && (
<table className="table mb-0"> <table className="table mb-0">
<thead> <thead>
@ -179,9 +178,8 @@ const EmpAttendance = () => {
{[...Array(totalPages)].map((_, index) => ( {[...Array(totalPages)].map((_, index) => (
<li <li
key={index} key={index}
className={`page-item ${ className={`page-item ${currentPage === index + 1 ? "active" : ""
currentPage === index + 1 ? "active" : "" }`}
}`}
> >
<button <button
className="page-link " className="page-link "
@ -192,9 +190,8 @@ const EmpAttendance = () => {
</li> </li>
))} ))}
<li <li
className={`page-item ${ className={`page-item ${currentPage === totalPages ? "disabled" : ""
currentPage === totalPages ? "disabled" : "" }`}
}`}
> >
<button <button
className="page-link " className="page-link "

View File

@ -145,7 +145,7 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
handleGroupBy, handleGroupBy,
selectedGroup.id, selectedGroup.id,
appliedStatusId, appliedStatusId,
selectedProjectId, // Added dependency selectedProjectId,
]); ]);

View File

@ -124,7 +124,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
}, },
{ {
key: "expensesType", key: "expensesType",
label: "Expense Type", label: "Expense Category",
getValue: (e) => e.expensesType?.name || "N/A", getValue: (e) => e.expensesType?.name || "N/A",
align: "text-start", align: "text-start",
}, },
@ -178,8 +178,6 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
<span <span
className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary" className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary"
}`} }`}
className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary"
}`}
> >
{e.status?.name || "Unknown"} {e.status?.name || "Unknown"}
</span> </span>
@ -267,10 +265,10 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
<td colSpan={8} className="text-start"> <td colSpan={8} className="text-start">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
{" "} {" "}
<small className="fs-6 py-1"> <small className="fs-6 ms-2 py-1">
{displayField} :{" "} {displayField} :{" "}
</small>{" "} </small>{" "}
<small className="fs-6 ms-3"> <small className="fs-6 ms-3">
{IsGroupedByDate {IsGroupedByDate
? formatUTCToLocalTime(key) ? formatUTCToLocalTime(key)
: key} : key}

View File

@ -16,6 +16,7 @@ import { useParams } from "react-router-dom";
import ProgressBar from "../../common/ProgressBar"; import ProgressBar from "../../common/ProgressBar";
import { formatNumber } from "../../../utils/dateUtils"; import { formatNumber } from "../../../utils/dateUtils";
import { useServices } from "../../../hooks/masterHook/useMaster"; import { useServices } from "../../../hooks/masterHook/useMaster";
import { SpinnerLoader } from "../../common/Loader";
const WorkArea = ({ workArea, floor, forBuilding }) => { const WorkArea = ({ workArea, floor, forBuilding }) => {
const selectedProject = useSelectedProject() const selectedProject = useSelectedProject()
@ -120,9 +121,14 @@ const WorkArea = ({ workArea, floor, forBuilding }) => {
className="accordion-collapse collapse" className="accordion-collapse collapse"
aria-labelledby={`heading-${workArea.id}`} aria-labelledby={`heading-${workArea.id}`}
> >
<div className="accordion-body px-6"> <div className="accordion-body px-6">
{isLoading || ProjectTaskList === undefined ? ( {isLoading || ProjectTaskList === undefined ? (
<div className="text-center py-2 text-muted">Loading activities...</div> <div
className="d-flex justify-content-center align-items-center text-muted"
style={{ height: "120px" }}
>
<SpinnerLoader />
</div>
) : ProjectTaskList?.length === 0 ? ( ) : ProjectTaskList?.length === 0 ? (
<div className="text-center py-2 text-muted">No activities available for this work area.</div> <div className="text-center py-2 text-muted">No activities available for this work area.</div>
) : ProjectTaskList?.length > 0 ? ( ) : ProjectTaskList?.length > 0 ? (

View File

@ -29,6 +29,7 @@ import eventBus from "../../services/eventBus";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import GlobalModel from "../common/GlobalModel"; import GlobalModel from "../common/GlobalModel";
import { setService } from "../../slices/globalVariablesSlice"; import { setService } from "../../slices/globalVariablesSlice";
import { SpinnerLoader } from "../common/Loader";
const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => { const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
const projectId = useSelectedProject(); const projectId = useSelectedProject();
@ -186,18 +187,20 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
)} )}
</div> </div>
</div> </div>
<div className="row "> <div className="row modal-min-h">
{isLoading && <p>Loading....</p>} {isLoading ? (
{projectInfra && projectInfra?.length > 0 && ( <div className="d-flex justify-content-center align-items-center py-5">
<SpinnerLoader />
</div>
) : projectInfra && projectInfra.length > 0 ? (
<InfraTable <InfraTable
buildings={projectInfra} buildings={projectInfra}
projectId={projectId} projectId={projectId}
serviceId={selectedService} serviceId={selectedService}
/> />
)} ) : (
{!isLoading && projectInfra?.length == 0 && ( <div className="text-center py-5">
<div className="mt-5"> <p className="text-muted fs-6">No infrastructure data available.</p>
<p>No Infra Avaiable</p>
</div> </div>
)} )}
</div> </div>

View File

@ -2,6 +2,7 @@ import React from "react";
import { useProjectAssignedOrganizations } from "../../../hooks/useProjects"; import { useProjectAssignedOrganizations } from "../../../hooks/useProjects";
import { useSelectedProject } from "../../../slices/apiDataManager"; import { useSelectedProject } from "../../../slices/apiDataManager";
import { formatUTCToLocalTime } from "../../../utils/dateUtils"; import { formatUTCToLocalTime } from "../../../utils/dateUtils";
import { SpinnerLoader } from "../../common/Loader";
const ProjectAssignedOrgs = () => { const ProjectAssignedOrgs = () => {
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
@ -25,12 +26,12 @@ const ProjectAssignedOrgs = () => {
), ),
align: "text-start", align: "text-start",
}, },
{ {
key: "service", key: "service",
label: "Service Name", label: "Service Name",
getValue: (org) => ( getValue: (org) => (
<div className="d-flex gap-2 py-1 "> <div className="d-flex gap-2 py-1 ">
{org?.service?.name} {org?.service?.name}
</div> </div>
), ),
align: "text-start", align: "text-start",
@ -49,7 +50,7 @@ const ProjectAssignedOrgs = () => {
align: "text-center", align: "text-center",
}, },
{ {
key: "organizationType", key: "organizationType",
label: "Organization Type", label: "Organization Type",
getValue: (org) => ( getValue: (org) => (
@ -62,7 +63,7 @@ const ProjectAssignedOrgs = () => {
), ),
align: "text-center", align: "text-center",
}, },
{ {
key: "assignedDate", key: "assignedDate",
label: "Assigned Date", label: "Assigned Date",
getValue: (org) => ( getValue: (org) => (
@ -78,7 +79,12 @@ const ProjectAssignedOrgs = () => {
}, },
]; ];
if (isLoading) return <div>Loading...</div>; if (isLoading)
return (
<div className="d-flex justify-content-center align-items-center py-5">
<SpinnerLoader />
</div>
);
if (isError) return <div>{error.message}</div>; if (isError) return <div>{error.message}</div>;
return ( return (

View File

@ -21,7 +21,7 @@ const ProjectOrganizations = () => {
</div> </div>
</div> </div>
<div className="row"> <div className="row modal-min-h">
<ProjectAssignedOrgs /> <ProjectAssignedOrgs />
</div> </div>
</div> </div>

View File

@ -21,6 +21,7 @@ import {
import { useSelectedProject } from "../../../slices/apiDataManager"; import { useSelectedProject } from "../../../slices/apiDataManager";
import GlobalModel from "../../common/GlobalModel"; import GlobalModel from "../../common/GlobalModel";
import TeamAssignToProject from "./TeamAssignToProject"; import TeamAssignToProject from "./TeamAssignToProject";
import { SpinnerLoader } from "../../common/Loader";
const Teams = () => { const Teams = () => {
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
@ -158,16 +159,8 @@ const Teams = () => {
<div> <div>
{!servicesLoading && ( {!servicesLoading && (
<> <>
{(!assignedServices || assignedServices.length === 0) && (
<span className="badge bg-label-secondary">
Not Service Assigned
</span>
)}
{assignedServices?.length === 1 && ( {assignedServices?.length === 1 && (
<span className="badge bg-label-secondary"> <h5 className="mb-2">{assignedServices[0].name}</h5>
{assignedServices[0].name}
</span>
)} )}
{assignedServices?.length > 1 && ( {assignedServices?.length > 1 && (
@ -232,7 +225,11 @@ const Teams = () => {
</div> </div>
<div className="table-responsive text-nowrap modal-min-h"> <div className="table-responsive text-nowrap modal-min-h">
{employeeLodaing && <p>Loading..</p>} {employeeLodaing && (
<div className="d-flex justify-content-center align-items-center py-5" style={{ minHeight: "50vh" }}>
<SpinnerLoader />
</div>
)}
{projectEmployees && projectEmployees.length > 0 && ( {projectEmployees && projectEmployees.length > 0 && (
<table className="table "> <table className="table ">
<thead> <thead>
@ -290,8 +287,8 @@ const Teams = () => {
<td> <td>
{emp.reAllocationDate {emp.reAllocationDate
? moment(emp.reAllocationDate).format( ? moment(emp.reAllocationDate).format(
"DD-MMM-YYYY" "DD-MMM-YYYY"
) )
: "Present"} : "Present"}
</td> </td>
)} )}

View File

@ -20,16 +20,7 @@ export const newTenantSchema = z.object({
contactNumber: z.string().trim() contactNumber: z.string().trim()
.nonempty("Contact number is required") .nonempty("Contact number is required")
.regex(/^\+?[1-9]\d{7,14}$/, "Enter a valid contact number"), .regex(/^\+?[1-9]\d{7,14}$/, "Enter a valid contact number"),
onBoardingDate: z.preprocess((val) => { onBoardingDate: z.string().min(1, { message: "Date is required" }),
if (typeof val === "string" && val.includes("-")) {
const [day, month, year] = val.split("-");
return new Date(`${year}-${month}-${day}`);
}
return val;
}, z.date({
required_error: "Onboarding date is required",
invalid_type_error: "Invalid date format",
})),
organizationSize: z.string().nonempty("Organization size is required"), organizationSize: z.string().nonempty("Organization size is required"),
industryId: z.string().uuid("Invalid industry ID"), industryId: z.string().uuid("Invalid industry ID"),
reference: z.string().nonempty("Reference is required"), reference: z.string().nonempty("Reference is required"),

View File

@ -19,3 +19,13 @@ const Loader = () => {
export default Loader; export default Loader;
export const SpinnerLoader = () => {
return (
<div className="text-center">
<div className="spinner-border text-primary mb-3" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<p className="text-secondary mb-0">Loading data...</p>
</div>
)
}

View File

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { useImageGalleryFilter } from "../../hooks/useImageGallery"; import { useImageGalleryFilter } from "../../hooks/useImageGallery";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
import { FormProvider, useForm } from "react-hook-form"; import { FormProvider, useForm } from "react-hook-form";
@ -8,6 +8,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { defaultGalleryFilterValue, gallerySchema } from "./GallerySchema"; import { defaultGalleryFilterValue, gallerySchema } from "./GallerySchema";
import SelectMultiple from "../common/SelectMultiple"; import SelectMultiple from "../common/SelectMultiple";
import { localToUtc } from "../../utils/appUtils"; import { localToUtc } from "../../utils/appUtils";
import { useLocation } from "react-router-dom";
const GalleryFilterPanel = ({ onApply }) => { const GalleryFilterPanel = ({ onApply }) => {
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
@ -27,7 +28,7 @@ const GalleryFilterPanel = ({ onApply }) => {
const { const {
handleSubmit, handleSubmit,
register, register,
setValue,reset, setValue, reset,
formState: { errors }, formState: { errors },
} = methods; } = methods;
@ -40,12 +41,17 @@ const GalleryFilterPanel = ({ onApply }) => {
// closePanel() // closePanel()
}; };
const onClear=()=>{ const onClear = () => {
reset(defaultGalleryFilterValue); reset(defaultGalleryFilterValue);
setResetKey((prev) => prev + 1); setResetKey((prev) => prev + 1);
// closePanel() // closePanel()
} }
const location = useLocation();
useEffect(() => {
closePanel();
}, [location]);
if (isLoading) return <div>Loading....</div>; if (isLoading) return <div>Loading....</div>;
if (isError) return <div>{error.message}</div>; if (isError) return <div>{error.message}</div>;
return ( return (

View File

@ -7,8 +7,7 @@ import { useSelectedProject } from "../../slices/apiDataManager";
import { ITEMS_PER_PAGE } from "../../utils/constants"; import { ITEMS_PER_PAGE } from "../../utils/constants";
import Pagination from "../common/Pagination"; import Pagination from "../common/Pagination";
import { formatUTCToLocalTime } from "../../utils/dateUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils";
import Loader from "../common/Loader"; import { SpinnerLoader } from "../common/Loader";
const ImageGalleryListView = ({filter}) => { const ImageGalleryListView = ({filter}) => {
const [hoveredImage, setHoveredImage] = useState(null); const [hoveredImage, setHoveredImage] = useState(null);
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
@ -44,10 +43,10 @@ const ImageGalleryListView = ({filter}) => {
} }
if (isLoading) { if (isLoading) {
return ( return (
<div className="page-min-h d-flex justify-content-center align-items-center"> <div className="page-min-h d-flex justify-content-center align-items-center">
<Loader /> <SpinnerLoader />
</div> </div>
); );
} }

View File

@ -179,7 +179,7 @@ const AttendancePage = () => {
</div> </div>
{/* Search + Organization filter */} {/* Search + Organization filter */}
<div className="col-12 col-md-auto mt-2 mt-md-0 ms-md-auto"> <div className="col-12 col-md-auto pb-2 m-0 mt-md-0 ms-md-auto nav-tabs">
<div className="row g-2"> <div className="row g-2">
<div className="col-12 col-sm-6"> <div className="col-12 col-sm-6">
<select <select

View File

@ -11,8 +11,7 @@ import { defaultContactFilter } from "../../components/Directory/DirectorySchema
import { useDebounce } from "../../utils/appUtils"; import { useDebounce } from "../../utils/appUtils";
import Pagination from "../../components/common/Pagination"; import Pagination from "../../components/common/Pagination";
import ListViewContact from "../../components/Directory/ListViewContact"; import ListViewContact from "../../components/Directory/ListViewContact";
import Loader from "../../components/common/Loader"; import { SpinnerLoader } from "../../components/common/Loader";
// Utility for CSV export // Utility for CSV export
const formatExportData = (contacts) => { const formatExportData = (contacts) => {
return contacts.map((contact) => ({ return contacts.map((contact) => ({
@ -113,7 +112,14 @@ const ContactsPage = ({ projectId, searchText, onExport }) => {
{/* Grid / List View */} {/* Grid / List View */}
{gridView ? ( {gridView ? (
<> <>
{isLoading && <Loader />} {isLoading && (
<div
className="d-flex justify-content-center align-items-center"
style={{ height: "50vh" }}
>
<SpinnerLoader />
</div>
)}
{data?.data?.length === 0 && ( {data?.data?.length === 0 && (
<div className="py-4 text-center"> <div className="py-4 text-center">

View File

@ -44,7 +44,7 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
const [searchNote, setSearchNote] = useState(""); const [searchNote, setSearchNote] = useState("");
const [activeTab, setActiveTab] = useState("notes"); const [activeTab, setActiveTab] = useState("notes");
const { setActions } = useFab(); const { setActions } = useFab();
const [gridView, setGridView] = useState(false); const [gridView, setGridView] = useState(true);
const [isOpenBucket, setOpenBucket] = useState(false); const [isOpenBucket, setOpenBucket] = useState(false);
const [isManageContact, setManageContact] = useState({ const [isManageContact, setManageContact] = useState({
isOpen: false, isOpen: false,

View File

@ -115,7 +115,7 @@ const ExpensePage = () => {
{IsViewAll || IsViewSelf || IsCreatedAble ? ( {IsViewAll || IsViewSelf || IsCreatedAble ? (
<> <>
<div className="card my-3 px-sm-4 px-0"> <div className="card my-3 px-sm-4 px-0">
<div className="card-body py-2 px-3"> <div className="card-body py-2 px-3 me-n1">
<div className="row align-items-center"> <div className="row align-items-center">
<div className="col-6"> <div className="col-6">
<input <input

View File

@ -3,7 +3,7 @@ import { useProfile } from "../../hooks/useProfile";
import TenantDetails from "./TenantDetails"; import TenantDetails from "./TenantDetails";
import { VIEW_TENANTS } from "../../utils/constants"; import { VIEW_TENANTS } from "../../utils/constants";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import Loader from "../../components/common/Loader"; import { SpinnerLoader } from "../../components/common/Loader";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
const SelfTenantDetails = () => { const SelfTenantDetails = () => {
@ -19,7 +19,7 @@ const SelfTenantDetails = () => {
}, [isSelfTenantView, navigate]); }, [isSelfTenantView, navigate]);
if (loading || !tenantId) { if (loading || !tenantId) {
return <Loader/>; return <SpinnerLoader />;
} }
return ( return (

View File

@ -7,7 +7,7 @@ import { ComingSoonPage } from "../Misc/ComingSoonPage";
import GlobalModel from "../../components/common/GlobalModel"; import GlobalModel from "../../components/common/GlobalModel";
import EditProfile from "../../components/Tenant/EditProfile"; import EditProfile from "../../components/Tenant/EditProfile";
import SubScriptionHistory from "../../components/Tenant/SubScriptionHistory"; import SubScriptionHistory from "../../components/Tenant/SubScriptionHistory";
import Loader from "../../components/common/Loader"; import { SpinnerLoader } from "../../components/common/Loader";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { MANAGE_TENANTS, SUPPER_TENANT } from "../../utils/constants"; import { MANAGE_TENANTS, SUPPER_TENANT } from "../../utils/constants";
@ -71,8 +71,11 @@ const TenantDetails = ({
if (!activeTenantId) return <div className="my-4">No tenant selected.</div>; if (!activeTenantId) return <div className="my-4">No tenant selected.</div>;
if (isLoading) if (isLoading)
return ( return (
<div className="my-4"> <div
<Loader /> className="page-min-h d-flex justify-content-center align-items-center"
style={{ minHeight: "80vh" }}
>
<SpinnerLoader />
</div> </div>
); );
if (isError) if (isError)
@ -110,14 +113,14 @@ const TenantDetails = ({
data={ data={
iTSelf iTSelf
? [ ? [
{ label: "Home", link: "/dashboard" }, { label: "Home", link: "/dashboard" },
{ label: "Tenant Details", link: null }, { label: "Tenant Details", link: null },
] ]
: [ : [
{ label: "Home", link: "/dashboard" }, { label: "Home", link: "/dashboard" },
{ label: "Tenant", link: "/tenants" }, { label: "Tenant", link: "/tenants" },
{ label: "Tenant Details", link: null }, { label: "Tenant Details", link: null },
] ]
} }
/> />
)} )}
@ -128,9 +131,8 @@ const TenantDetails = ({
<li key={tab.id} className="nav-item"> <li key={tab.id} className="nav-item">
<button <button
type="button" type="button"
className={`nav-link d-flex align-items-center text-tiny gap-2 ${ className={`nav-link d-flex align-items-center text-tiny gap-2 ${index === 0 ? "active" : ""
index === 0 ? "active" : "" }`}
}`}
role="tab" role="tab"
data-bs-toggle="tab" data-bs-toggle="tab"
data-bs-target={`#${tab.id}`} data-bs-target={`#${tab.id}`}
@ -150,9 +152,8 @@ const TenantDetails = ({
{tabs.map((tab, index) => ( {tabs.map((tab, index) => (
<div <div
key={tab.id} key={tab.id}
className={`tab-pane fade ${ className={`tab-pane fade ${index === 0 ? "show active" : ""
index === 0 ? "show active" : "" } text-start`}
} text-start`}
id={tab.id} id={tab.id}
> >
{tab.content} {tab.content}

View File

@ -4,8 +4,7 @@ import { useAuthModal, useSelectTenant, useTenants } from "../../hooks/useAuth";
import { useProfile } from "../../hooks/useProfile"; import { useProfile } from "../../hooks/useProfile";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import AuthRepository from "../../repositories/AuthRepository"; import AuthRepository from "../../repositories/AuthRepository";
import Loader from "../../components/common/Loader"; import { SpinnerLoader } from "../../components/common/Loader";
const SwitchTenant = () => { const SwitchTenant = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { profile } = useProfile(); const { profile } = useProfile();
@ -99,7 +98,7 @@ const SwitchTenant = () => {
</div> </div>
</div> </div>
); );
return <Modal isOpen={isOpen} onClose={onClose} body={isLoading ? <Loader/>:contentBody} />; return <Modal isOpen={isOpen} onClose={onClose} body={isLoading ? <SpinnerLoader/>:contentBody} />;
}; };
export default SwitchTenant; export default SwitchTenant;

View File

@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
import { useTenants, useSelectTenant, useLogout } from "../../hooks/useAuth.jsx"; import { useTenants, useSelectTenant, useLogout } from "../../hooks/useAuth.jsx";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import Dashboard from "../../components/Dashboard/Dashboard.jsx"; import Dashboard from "../../components/Dashboard/Dashboard.jsx";
import Loader from "../../components/common/Loader.jsx"; import { SpinnerLoader } from "../../components/common/Loader.jsx";
const TenantSelectionPage = () => { const TenantSelectionPage = () => {
const [pendingTenant, setPendingTenant] = useState(null); const [pendingTenant, setPendingTenant] = useState(null);
@ -45,7 +45,7 @@ const TenantSelectionPage = () => {
isPending || isPending ||
(data?.data?.length === 1 && pendingTenant !== null) (data?.data?.length === 1 && pendingTenant !== null)
) { ) {
return <Loader />; return <SpinnerLoader />;
} }
if (!data?.data?.length) { if (!data?.data?.length) {

View File

@ -39,6 +39,7 @@ import { setProjectId } from "../../slices/localVariablesSlice";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import Pagination from "../../components/common/Pagination"; import Pagination from "../../components/common/Pagination";
import handleEmployeeExport from "../../components/Employee/handleEmployeeExport"; import handleEmployeeExport from "../../components/Employee/handleEmployeeExport";
import { SpinnerLoader } from "../../components/common/Loader";
const EmployeeList = () => { const EmployeeList = () => {
const selectedProjectId = useSelector( const selectedProjectId = useSelector(
@ -468,12 +469,15 @@ const EmployeeList = () => {
<tbody> <tbody>
{loading && ( {loading && (
<tr> <tr>
<td colSpan={8}> <td colSpan={8} className="text-center py-5">
<p>Loading...</p> <div className="d-flex justify-content-center align-items-center py-5" style={{ minHeight: "50vh" }}>
<SpinnerLoader />
</div>
</td> </td>
</tr> </tr>
)} )}
{!loading && {!loading &&
displayData?.length === 0 && displayData?.length === 0 &&
(!searchText) ? ( (!searchText) ? (
@ -649,16 +653,16 @@ const EmployeeList = () => {
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
{displayData?.length > 0 && ( {displayData?.length > 0 && (
<Pagination <Pagination
currentPage={currentPage} currentPage={currentPage}
totalPages={totalPages} totalPages={totalPages}
onPageChange={paginate} onPageChange={paginate}
/> />
)} )}
</div> </div>
) : ( ) : (
<div className="card"> <div className="card">

View File

@ -7,7 +7,7 @@ import AboutProject from "../../components/Project/AboutProject";
import ProjectNav from "../../components/Project/ProjectNav"; import ProjectNav from "../../components/Project/ProjectNav";
import Teams from "../../components/Project/Team/Teams"; import Teams from "../../components/Project/Team/Teams";
import ProjectInfra from "../../components/Project/ProjectInfra"; import ProjectInfra from "../../components/Project/ProjectInfra";
import Loader from "../../components/common/Loader"; import { SpinnerLoader } from "../../components/common/Loader";
import WorkPlan from "../../components/Project/WorkPlan"; import WorkPlan from "../../components/Project/WorkPlan";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
@ -20,7 +20,7 @@ import { setProjectId } from "../../slices/localVariablesSlice";
import ProjectDocuments from "../../components/Project/ProjectDocuments"; import ProjectDocuments from "../../components/Project/ProjectDocuments";
import ProjectSetting from "../../components/Project/ProjectSetting"; import ProjectSetting from "../../components/Project/ProjectSetting";
import DirectoryPage from "../Directory/DirectoryPage"; import DirectoryPage from "../Directory/DirectoryPage";
import { useProjectAccess } from "../../hooks/useProjectAccess"; import { useProjectAccess } from "../../hooks/useProjectAccess";
import "./ProjectDetails.css"; import "./ProjectDetails.css";
import ProjectOrganizations from "../../components/Project/ProjectOrganizations"; import ProjectOrganizations from "../../components/Project/ProjectOrganizations";
@ -65,7 +65,14 @@ const ProjectDetails = () => {
}; };
if (projectLoading || permsLoading || !projects_Details) { if (projectLoading || permsLoading || !projects_Details) {
return <Loader />; return (
<div
className="d-flex justify-content-center align-items-center"
style={{ height: "80vh" }}
>
<SpinnerLoader />
</div>
);
} }
const renderContent = () => { const renderContent = () => {

View File

@ -7,8 +7,8 @@ import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
import ProjectCardView from "../../components/Project/ProjectCardView"; import ProjectCardView from "../../components/Project/ProjectCardView";
import usePagination from "../../hooks/usePagination"; import usePagination from "../../hooks/usePagination";
import { useProjects } from "../../hooks/useProjects"; import { useProjects } from "../../hooks/useProjects";
import Loader from "../../components/common/Loader";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { SpinnerLoader } from "../../components/common/Loader";
const ProjectContext = createContext(); const ProjectContext = createContext();
export const useProjectContext = () => { export const useProjectContext = () => {
@ -95,8 +95,16 @@ const ProjectPage = () => {
} }
}, [data, isLoading, selectedStatuses]); }, [data, isLoading, selectedStatuses]);
if (isLoading)
return (
<div
className="page-min-h d-flex justify-content-center align-items-center"
style={{ minHeight: "80vh" }}
>
<SpinnerLoader />
</div>
);
if (isLoading) return <div className="page-min-h"><Loader /></div>
if (isError) return <div className="page-min-h d-flex justify-content-center align-items-center"><p>{error.message}</p></div> if (isError) return <div className="page-min-h d-flex justify-content-center align-items-center"><p>{error.message}</p></div>
return ( return (
<ProjectContext.Provider value={contextDispatcher}> <ProjectContext.Provider value={contextDispatcher}>