Merge branch 'Issues_Oct_4W_V1' into migrate_main
This commit is contained in:
commit
7b2bb4d59a
@ -123,12 +123,15 @@ const AttendLogs = ({ Id }) => {
|
||||
}, []);
|
||||
return (
|
||||
<div className="table-responsive">
|
||||
<div className="text-start">
|
||||
<div className="mb-3">
|
||||
<h5 className="mb-4">Attendance Logs</h5>
|
||||
{logs && !loading && (
|
||||
<p>
|
||||
Attendance logs for{" "}
|
||||
{logs[0]?.employee?.firstName + " " + logs[0]?.employee?.lastName}{" "}
|
||||
on {formatUTCToLocalTime(logs[0]?.activityTime)}
|
||||
<p className="mb-0 text-start">
|
||||
Showing logs for{" "}
|
||||
<strong>
|
||||
{logs[0]?.employee?.firstName + " " + logs[0]?.employee?.lastName}
|
||||
</strong>{" "}
|
||||
on <strong>{formatUTCToLocalTime(logs[0]?.activityTime)}</strong>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@ -142,9 +145,9 @@ const AttendLogs = ({ Id }) => {
|
||||
<table className="table table-sm mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Activity</th>
|
||||
<th>Date</th>
|
||||
<th>Time</th>
|
||||
<th>Activity</th>
|
||||
<th>Location</th>
|
||||
<th>Recored By</th>
|
||||
<th>Description</th>
|
||||
@ -156,11 +159,16 @@ const AttendLogs = ({ Id }) => {
|
||||
.sort((a, b) => b.id - a.id)
|
||||
.map((log, index) => (
|
||||
<tr key={index}>
|
||||
<td>{formatUTCToLocalTime(log.activityTime)}</td>
|
||||
<td>{convertShortTime(log.activityTime)}</td>
|
||||
<td>
|
||||
{whichActivityPerform(log.activity, log.activityTime)}
|
||||
</td>
|
||||
<td>
|
||||
<div className="py-2">
|
||||
{formatUTCToLocalTime(log.activityTime)}
|
||||
</div>
|
||||
</td>
|
||||
<td>{convertShortTime(log.activityTime)}</td>
|
||||
|
||||
<td>
|
||||
{log?.latitude != 0 ? (
|
||||
<i
|
||||
@ -179,9 +187,8 @@ const AttendLogs = ({ Id }) => {
|
||||
)}
|
||||
</td>
|
||||
<td className="text-wrap">
|
||||
{`${log?.updatedByEmployee?.firstName ?? ""} ${
|
||||
log?.updatedByEmployee?.lastName ?? ""
|
||||
}`}
|
||||
{`${log?.updatedByEmployee?.firstName ?? ""} ${log?.updatedByEmployee?.lastName ?? ""
|
||||
}`}
|
||||
</td>
|
||||
<td className="text-wrap" colSpan={3}>
|
||||
{log?.comment?.length > 50
|
||||
|
||||
@ -11,6 +11,7 @@ import { useSelector } from "react-redux";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
|
||||
const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizationId, }) => {
|
||||
const queryClient = useQueryClient();
|
||||
@ -110,28 +111,41 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
|
||||
|
||||
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
|
||||
className="table-responsive text-nowrap h-100"
|
||||
className="table-responsive modal-min-h text-nowrap h-100"
|
||||
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 ? (
|
||||
<div>Loading...</div>
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center"
|
||||
style={{ minHeight: "50vh" }}
|
||||
>
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
) : currentItems?.length > 0 ? (
|
||||
|
||||
<>
|
||||
<table className="table ">
|
||||
<thead>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState, useMemo, useCallback } from "react";
|
||||
import moment from "moment";
|
||||
import Avatar from "../common/Avatar";
|
||||
import { convertShortTime } from "../../utils/dateUtils";
|
||||
import { convertShortTime, formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||
import RenderAttendanceStatus from "./RenderAttendanceStatus";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import DateRangePicker from "../common/DateRangePicker";
|
||||
@ -16,6 +16,7 @@ import { useAttendancesLogs } from "../../hooks/useAttendance";
|
||||
import { queryClient } from "../../layouts/AuthLayout";
|
||||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
|
||||
const usePagination = (data, itemsPerPage) => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
@ -174,49 +175,51 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
|
||||
return (
|
||||
<>
|
||||
<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"
|
||||
>
|
||||
<div className="d-flex flex-wrap align-items-center gap-2 gap-md-3 my-0">
|
||||
{/* Date Range Picker */}
|
||||
<div className="flex-grow-1 flex-md-grow-0">
|
||||
<DateRangePicker
|
||||
onRangeChange={setDateRange}
|
||||
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>
|
||||
{/* Left Side - Date Picker */}
|
||||
<div className="d-flex align-items-center">
|
||||
<DateRangePicker
|
||||
onRangeChange={setDateRange}
|
||||
defaultStartDate={yesterday}
|
||||
/>
|
||||
</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
|
||||
className="table-responsive text-nowrap"
|
||||
className="table-responsive modal-min-h text-nowrap"
|
||||
style={{ minHeight: "200px" }}
|
||||
>
|
||||
{isLoading ? (
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center"
|
||||
style={{ height: "200px" }}
|
||||
style={{ minHeight: "50vh" }}
|
||||
>
|
||||
<p className="text-secondary">Loading...</p>
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
) : filteredSearchData?.length > 0 ? (
|
||||
|
||||
<table className="table mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -255,8 +258,8 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
|
||||
className="table-row-header"
|
||||
>
|
||||
<td colSpan={8} className="text-start">
|
||||
<strong>
|
||||
{moment(currentDate).format("DD-MM-YYYY")}
|
||||
<strong className="d-inline-block my-1 ms-2">
|
||||
{formatUTCToLocalTime(currentDate)}
|
||||
</strong>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -24,8 +24,7 @@ import { useProfile } from "../../hooks/useProfile";
|
||||
import { refreshData, setProjectId } from "../../slices/localVariablesSlice";
|
||||
import InfraTable from "../Project/Infrastructure/InfraTable";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import Loader from "../common/Loader";
|
||||
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
const InfraPlanning = () => {
|
||||
const { profile: LoggedUser, refetch: fetchData } = useProfile();
|
||||
const dispatch = useDispatch();
|
||||
@ -57,7 +56,14 @@ const InfraPlanning = () => {
|
||||
}
|
||||
|
||||
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)) {
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import Pagination from "../../components/common/Pagination";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
|
||||
const Regularization = ({
|
||||
handleRequest,
|
||||
@ -106,15 +107,15 @@ const Regularization = ({
|
||||
return (
|
||||
<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" }}
|
||||
>
|
||||
{loading ? (
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center"
|
||||
style={{ height: "200px" }}
|
||||
style={{ minHeight: "50vh" }}
|
||||
>
|
||||
<p className="text-secondary">Loading...</p>
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
) : currentItems?.length > 0 ? (
|
||||
<table className="table mb-0">
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import ReactApexChart from "react-apexcharts";
|
||||
import PropTypes from "prop-types";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
|
||||
const HorizontalBarChart = ({
|
||||
seriesData = [],
|
||||
@ -23,8 +24,12 @@ const HorizontalBarChart = ({
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="w-full h-[380px] flex items-center justify-center bg-gray-100 rounded-xl">
|
||||
<span className="text-gray-500">Loading chart...</span>
|
||||
{/* Replace this with a skeleton or spinner if you prefer */}
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center"
|
||||
style={{ minHeight: "50vh" }}
|
||||
>
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import ReactApexChart from "react-apexcharts";
|
||||
import PropTypes from "prop-types";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
|
||||
const LineChart = ({
|
||||
seriesData = [],
|
||||
@ -9,24 +10,28 @@ const LineChart = ({
|
||||
loading = false,
|
||||
lineChartCategoriesDates = [],
|
||||
}) => {
|
||||
const hasValidData =
|
||||
Array.isArray(seriesData) &&
|
||||
seriesData.length > 0 &&
|
||||
Array.isArray(categories) &&
|
||||
categories.length > 0;
|
||||
const hasValidData =
|
||||
Array.isArray(seriesData) &&
|
||||
seriesData.length > 0 &&
|
||||
Array.isArray(categories) &&
|
||||
categories.length > 0;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<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" />
|
||||
Loading chart...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center h-[350px] text-gray-500">
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center"
|
||||
style={{ minHeight: "50vh" }}
|
||||
>
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasValidData) {
|
||||
return <div className="text-center text-gray-500">No data to display</div>;
|
||||
}
|
||||
if (!hasValidData) {
|
||||
return <div className="text-center text-gray-500">No data to display</div>;
|
||||
}
|
||||
|
||||
const chartOptions = {
|
||||
chart: {
|
||||
@ -129,16 +134,16 @@ const LineChart = ({
|
||||
};
|
||||
|
||||
LineChart.propTypes = {
|
||||
seriesData: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
data: PropTypes.arrayOf(PropTypes.number).isRequired
|
||||
})
|
||||
),
|
||||
categories: PropTypes.arrayOf(PropTypes.string),
|
||||
colors: PropTypes.arrayOf(PropTypes.string),
|
||||
title: PropTypes.string,
|
||||
loading: PropTypes.bool
|
||||
seriesData: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
data: PropTypes.arrayOf(PropTypes.number).isRequired
|
||||
})
|
||||
),
|
||||
categories: PropTypes.arrayOf(PropTypes.string),
|
||||
colors: PropTypes.arrayOf(PropTypes.string),
|
||||
title: PropTypes.string,
|
||||
loading: PropTypes.bool
|
||||
};
|
||||
|
||||
export default LineChart;
|
||||
|
||||
@ -4,6 +4,7 @@ import ReactApexChart from "react-apexcharts";
|
||||
import { useAttendanceOverviewData } from "../../hooks/useDashboard_Data";
|
||||
import flatColors from "../Charts/flatColor";
|
||||
import ChartSkeleton from "../Charts/Skelton";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
|
||||
const formatDate = (dateStr) => {
|
||||
const date = new Date(dateStr);
|
||||
@ -99,7 +100,7 @@ const AttendanceOverview = () => {
|
||||
};
|
||||
|
||||
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 */}
|
||||
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||
<div className="card-title mb-0 text-start">
|
||||
@ -117,18 +118,16 @@ const AttendanceOverview = () => {
|
||||
<option value={30}>Last 30 Days</option>
|
||||
</select>
|
||||
<button
|
||||
className={`btn btn-sm p-1 ${
|
||||
view === "chart" ? "btn-primary" : "btn-outline-primary"
|
||||
}`}
|
||||
className={`btn btn-sm p-1 ${view === "chart" ? "btn-primary" : "btn-outline-primary"
|
||||
}`}
|
||||
onClick={() => setView("chart")}
|
||||
title="Chart View"
|
||||
>
|
||||
<i className="bx bx-bar-chart-alt-2"></i>
|
||||
</button>
|
||||
<button
|
||||
className={`btn btn-sm p-1 ${
|
||||
view === "table" ? "btn-primary" : "btn-outline-primary"
|
||||
}`}
|
||||
className={`btn btn-sm p-1 ${view === "table" ? "btn-primary" : "btn-outline-primary"
|
||||
}`}
|
||||
onClick={() => setView("table")}
|
||||
title="Table View"
|
||||
>
|
||||
@ -138,11 +137,14 @@ const AttendanceOverview = () => {
|
||||
</div>
|
||||
|
||||
{/* 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 ? (
|
||||
<ChartSkeleton />
|
||||
<SpinnerLoader />
|
||||
) : error ? (
|
||||
<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" ? (
|
||||
<div className="w-100">
|
||||
<ReactApexChart
|
||||
@ -163,9 +165,7 @@ const AttendanceOverview = () => {
|
||||
style={{ position: "sticky", top: 0, zIndex: 1 }}
|
||||
>
|
||||
<tr>
|
||||
<th style={{ background: "#f8f9fa", textTransform: "none" }}>
|
||||
Role
|
||||
</th>
|
||||
<th style={{ background: "#f8f9fa", textTransform: "none" }}>Role</th>
|
||||
{dates.map((date, idx) => (
|
||||
<th
|
||||
key={idx}
|
||||
@ -176,15 +176,13 @@ const AttendanceOverview = () => {
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{roles.map((role) => (
|
||||
<tr key={role}>
|
||||
<td>{role}</td>
|
||||
{tableData.map((row, idx) => {
|
||||
const value = row[role];
|
||||
const cellStyle =
|
||||
value > 0 ? { backgroundColor: "#d5d5d5" } : {};
|
||||
const cellStyle = value > 0 ? { backgroundColor: "#d5d5d5" } : {};
|
||||
return (
|
||||
<td key={idx} style={cellStyle}>
|
||||
{value}
|
||||
|
||||
@ -63,12 +63,12 @@ const Dashboard = () => {
|
||||
</div>
|
||||
|
||||
<div className="col-12 col-xl-4 col-md-6">
|
||||
<div className="card ">
|
||||
<div className="card h-100">
|
||||
<ExpenseStatus />
|
||||
</div>
|
||||
</div>
|
||||
{!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 />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -6,6 +6,7 @@ import { DateRangePicker1 } from "../common/DateRangePicker";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { formatCurrency, localToUtc } from "../../utils/appUtils";
|
||||
import { useProjectName } from "../../hooks/useProjects";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
|
||||
const ExpenseAnalysis = () => {
|
||||
const projectId = useSelectedProject();
|
||||
@ -78,86 +79,92 @@ 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 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">
|
||||
<FormProvider {...methods}>
|
||||
<DateRangePicker1 />
|
||||
</FormProvider>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
{/* Card body */}
|
||||
<div className="card-body position-relative">
|
||||
{isLoading && (
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center"
|
||||
style={{ height: "200px" }}
|
||||
>
|
||||
<span>Loading...</span>
|
||||
<div className="text-start text-sm-end w-75">
|
||||
<FormProvider {...methods}>
|
||||
<DateRangePicker1 />
|
||||
</FormProvider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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>
|
||||
)}
|
||||
|
||||
{!isLoading && report.length === 0 && (
|
||||
<div className="text-center py-5 text-muted">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 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 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>
|
||||
<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 */}
|
||||
|
||||
</>
|
||||
|
||||
@ -7,6 +7,7 @@ import { formatCurrency } from "../../utils/appUtils";
|
||||
import { formatDate_DayMonth } from "../../utils/dateUtils";
|
||||
import { useProjectName } from "../../hooks/useProjects";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
|
||||
const ExpenseByProject = () => {
|
||||
const projectId = useSelector((store) => store.localVariables.projectId);
|
||||
@ -63,6 +64,12 @@ const ExpenseByProject = () => {
|
||||
categories: chartData.categories,
|
||||
labels: { style: { fontSize: "12px" }, rotate: -45 },
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: (val) => formatCurrency(val),
|
||||
style: { fontSize: "12px", colors: "#555" },
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: (val) => `${formatCurrency(val)} (${getSelectedTypeName()})`,
|
||||
@ -82,15 +89,15 @@ const ExpenseByProject = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="card shadow-sm rounded ">
|
||||
<div className="card shadow-sm h-100 rounded ">
|
||||
{/* 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">
|
||||
<h5 className="mb-1 me-6 card-title">Monthly Expense -</h5>
|
||||
<p className="card-subtitle m-0">{projectName}</p>
|
||||
</div>
|
||||
<div className="btn-group mb-4 ms-n8">
|
||||
<div className="btn-group mb-5 ms-n8">
|
||||
<button
|
||||
className="btn btn-sm dropdown-toggle fs-5"
|
||||
type="button"
|
||||
@ -163,12 +170,15 @@ const ExpenseByProject = () => {
|
||||
{/* Chart */}
|
||||
<div className="card-body bg-white text-dark p-3 rounded" style={{ minHeight: "210px" }}>
|
||||
{isLoading ? (
|
||||
<p>Loading chart...</p>
|
||||
<div className="d-flex justify-content-center align-items-center py-5">
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
) : !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} />
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@ -24,7 +24,7 @@ const ProjectCompletionChart = () => {
|
||||
<p className="card-subtitle">Projects Completion Status</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="card-body ms-n7">
|
||||
<HorizontalBarChart
|
||||
categories={projectNames}
|
||||
seriesData={projectProgress}
|
||||
|
||||
@ -44,9 +44,9 @@ const TasksCard = () => {
|
||||
</span>
|
||||
</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 */}
|
||||
<div className="text-center flex-fill p-2">
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold text-truncate">
|
||||
{formatFigure(tasksCardData?.totalTasks ?? 0, {
|
||||
notation: "compact",
|
||||
@ -56,7 +56,7 @@ const TasksCard = () => {
|
||||
</div>
|
||||
|
||||
{/* Completed Tasks */}
|
||||
<div className="text-center flex-fill p-2">
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold text-truncate">
|
||||
{formatFigure(tasksCardData?.completedTasks ?? 0, {
|
||||
notation: "compact",
|
||||
|
||||
@ -61,7 +61,7 @@ const CardViewContact = ({
|
||||
(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>
|
||||
{IsActive && (
|
||||
|
||||
@ -87,7 +87,7 @@ const NoteCardDirectoryEditable = ({
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
className="d-flex ms-0 align-middle cursor-pointer"
|
||||
className="d-flex ms-3 align-middle cursor-pointer"
|
||||
onClick={() => contactProfile(noteItem.contactId)}
|
||||
>
|
||||
<span>
|
||||
@ -98,7 +98,7 @@ const NoteCardDirectoryEditable = ({
|
||||
</span>
|
||||
</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">
|
||||
by{" "}
|
||||
<span className="fw-bold ">
|
||||
@ -184,7 +184,7 @@ const NoteCardDirectoryEditable = ({
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className="mx-4 px-10 text-start"
|
||||
className="mx-4 px-11 text-start"
|
||||
dangerouslySetInnerHTML={{ __html: noteItem.note }}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -13,230 +13,236 @@ import { useParams } from "react-router-dom";
|
||||
|
||||
const DocumentFilterPanel = forwardRef(
|
||||
({ entityTypeId, onApply, setFilterdata }, ref) => {
|
||||
const [resetKey, setResetKey] = useState(0);
|
||||
const { status } = useParams();
|
||||
const [resetKey, setResetKey] = useState(0);
|
||||
const { status } = useParams();
|
||||
|
||||
const { data, isError, isLoading, error } =
|
||||
useDocumentFilterEntities(entityTypeId);
|
||||
const { data, isError, isLoading, error } =
|
||||
useDocumentFilterEntities(entityTypeId);
|
||||
|
||||
//changes
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
closePanel();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const dynamicDocumentFilterDefaultValues = useMemo(() => {
|
||||
return {
|
||||
...DocumentFilterDefaultValues,
|
||||
uploadedByIds: DocumentFilterDefaultValues.uploadedByIds || [],
|
||||
documentCategoryIds: DocumentFilterDefaultValues.documentCategoryIds || [],
|
||||
documentTypeIds: DocumentFilterDefaultValues.documentTypeIds || [],
|
||||
documentTagIds: DocumentFilterDefaultValues.documentTagIds || [],
|
||||
startDate: DocumentFilterDefaultValues.startDate,
|
||||
endDate: DocumentFilterDefaultValues.endDate,
|
||||
//changes
|
||||
|
||||
const dynamicDocumentFilterDefaultValues = useMemo(() => {
|
||||
return {
|
||||
...DocumentFilterDefaultValues,
|
||||
uploadedByIds: DocumentFilterDefaultValues.uploadedByIds || [],
|
||||
documentCategoryIds: DocumentFilterDefaultValues.documentCategoryIds || [],
|
||||
documentTypeIds: DocumentFilterDefaultValues.documentTypeIds || [],
|
||||
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({
|
||||
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();
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
resetFieldValue: (name, value) => {
|
||||
if (value !== undefined) {
|
||||
setValue(name, value);
|
||||
} else {
|
||||
reset({ ...methods.getValues(), [name]: DocumentFilterDefaultValues[name] });
|
||||
//changes
|
||||
useEffect(() => {
|
||||
if (data && setFilterdata) {
|
||||
setFilterdata(data);
|
||||
}
|
||||
},
|
||||
getValues: methods.getValues, // optional, to read current filter state
|
||||
}));
|
||||
}, [data, setFilterdata]);
|
||||
|
||||
//changes
|
||||
useEffect(() => {
|
||||
if (data && setFilterdata) {
|
||||
setFilterdata(data);
|
||||
}
|
||||
}, [data, setFilterdata]);
|
||||
const onSubmit = (values) => {
|
||||
onApply({
|
||||
...values,
|
||||
startDate: values.startDate
|
||||
? moment.utc(values.startDate, "DD-MM-YYYY").toISOString()
|
||||
: null,
|
||||
endDate: values.endDate
|
||||
? moment.utc(values.endDate, "DD-MM-YYYY").toISOString()
|
||||
: null,
|
||||
});
|
||||
// closePanel();
|
||||
};
|
||||
|
||||
const onSubmit = (values) => {
|
||||
onApply({
|
||||
...values,
|
||||
startDate: values.startDate
|
||||
? moment.utc(values.startDate, "DD-MM-YYYY").toISOString()
|
||||
: null,
|
||||
endDate: values.endDate
|
||||
? moment.utc(values.endDate, "DD-MM-YYYY").toISOString()
|
||||
: null,
|
||||
});
|
||||
// closePanel();
|
||||
};
|
||||
const onClear = () => {
|
||||
reset(DocumentFilterDefaultValues);
|
||||
setResetKey((prev) => prev + 1);
|
||||
onApply(DocumentFilterDefaultValues);
|
||||
// closePanel();
|
||||
};
|
||||
|
||||
const onClear = () => {
|
||||
reset(DocumentFilterDefaultValues);
|
||||
setResetKey((prev) => prev + 1);
|
||||
onApply(DocumentFilterDefaultValues);
|
||||
// closePanel();
|
||||
};
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (isError)
|
||||
return <div>Error: {error?.message || "Something went wrong!"}</div>;
|
||||
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (isError)
|
||||
return <div>Error: {error?.message || "Something went wrong!"}</div>;
|
||||
|
||||
const {
|
||||
uploadedBy = [],
|
||||
documentCategory = [],
|
||||
documentType = [],
|
||||
documentTag = [],
|
||||
} = data?.data || {};
|
||||
const {
|
||||
uploadedBy = [],
|
||||
documentCategory = [],
|
||||
documentType = [],
|
||||
documentTag = [],
|
||||
} = data?.data || {};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{/* Date Range Section */}
|
||||
<div className="mb-2">
|
||||
<div className="text-start d-flex align-items-center my-1">
|
||||
<label className="form-label me-2 my-0">Choose Date:</label>
|
||||
<div className="d-inline-flex border rounded-pill overflow-hidden shadow-none">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn px-2 py-1 rounded-0 text-tiny ${isUploadedAt ? "active btn-secondary text-white" : ""
|
||||
}`}
|
||||
onClick={() => setValue("isUploadedAt", true)}
|
||||
>
|
||||
Uploaded On
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn px-2 py-1 rounded-0 text-tiny ${!isUploadedAt ? "active btn-secondary text-white" : ""
|
||||
}`}
|
||||
onClick={() => setValue("isUploadedAt", false)}
|
||||
>
|
||||
Updated On
|
||||
</button>
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{/* Date Range Section */}
|
||||
<div className="mb-2">
|
||||
<div className="text-start d-flex align-items-center my-1">
|
||||
<label className="form-label me-2 my-0">Choose Date:</label>
|
||||
<div className="d-inline-flex border rounded-pill overflow-hidden shadow-none">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn px-2 py-1 rounded-0 text-tiny ${isUploadedAt ? "active btn-secondary text-white" : ""
|
||||
}`}
|
||||
onClick={() => setValue("isUploadedAt", true)}
|
||||
>
|
||||
Uploaded On
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn px-2 py-1 rounded-0 text-tiny ${!isUploadedAt ? "active btn-secondary text-white" : ""
|
||||
}`}
|
||||
onClick={() => setValue("isUploadedAt", false)}
|
||||
>
|
||||
Updated On
|
||||
</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>
|
||||
|
||||
<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>
|
||||
{/* 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>
|
||||
</div>
|
||||
|
||||
{/* 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>
|
||||
);
|
||||
});
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
});
|
||||
|
||||
export default DocumentFilterPanel;
|
||||
|
||||
@ -74,7 +74,7 @@ const DocumentVersionList = ({
|
||||
firstName={currentDoc.uploadedBy?.firstName}
|
||||
lastName={currentDoc.uploadedBy?.lastName}
|
||||
/>
|
||||
<span className="ms-1">
|
||||
<span className="ms-3">
|
||||
<small className="fw-normal" style={{ marginLeft: "-10px" }}>
|
||||
{`${currentDoc.uploadedBy?.firstName ?? ""} ${currentDoc.uploadedBy?.lastName ?? ""}`.trim() || "N/A"}
|
||||
</small>
|
||||
@ -196,7 +196,7 @@ const DocumentVersionList = ({
|
||||
firstName={document.uploadedBy?.firstName}
|
||||
lastName={document.uploadedBy?.lastName}
|
||||
/>
|
||||
<span className="ms-1">
|
||||
<span className="ms-3">
|
||||
<small className="fw-normal" style={{ marginLeft: "-10px" }}>
|
||||
{`${document.uploadedBy?.firstName ?? ""} ${document.uploadedBy?.lastName ?? ""}`.trim() || "N/A"}
|
||||
</small>
|
||||
@ -216,7 +216,7 @@ const DocumentVersionList = ({
|
||||
firstName={document.verifiedBy?.firstName}
|
||||
lastName={document.verifiedBy?.lastName}
|
||||
/>
|
||||
<span className="ms-1">
|
||||
<span className="ms-3">
|
||||
<small className="fw-normal" style={{ marginLeft: "-10px" }}>
|
||||
{`${document.verifiedBy?.firstName ?? ""} ${document.verifiedBy?.lastName ?? ""}`.trim() || "N/A"}
|
||||
</small>
|
||||
|
||||
@ -12,13 +12,13 @@ const EmpActivities = ({ employee }) => {
|
||||
|
||||
const {
|
||||
data,
|
||||
isError,
|
||||
isLoading,
|
||||
error,
|
||||
isError,
|
||||
isLoading,
|
||||
error,
|
||||
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 (
|
||||
<>
|
||||
<div className="card h-100 mt-4">
|
||||
@ -31,28 +31,28 @@ error,
|
||||
/>
|
||||
</div>
|
||||
<ul className="timeline mb-0 mt-5 text-start">
|
||||
{data?.map((activity)=>(
|
||||
<li className="timeline-item timeline-item-transparent">
|
||||
<span className="timeline-point timeline-point-primary"></span>
|
||||
<div className="timeline-event">
|
||||
<div className="timeline-header mb-3">
|
||||
<h6 className="mb-0">{activity.projectName}</h6>
|
||||
<small className="text-body-secondary">
|
||||
{useFormattedDate(activity.assignmentDate, "dd-MMM-yyyy")}
|
||||
</small>
|
||||
{data?.map((activity) => (
|
||||
<li className="timeline-item timeline-item-transparent">
|
||||
<span className="timeline-point timeline-point-primary"></span>
|
||||
<div className="timeline-event">
|
||||
<div className="timeline-header mb-3">
|
||||
<h6 className="mb-0">{activity.projectName}</h6>
|
||||
<small className="text-body-secondary">
|
||||
{useFormattedDate(activity.assignmentDate, "dd-MMM-yyyy")}
|
||||
</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>
|
||||
<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>
|
||||
</li>
|
||||
</li>
|
||||
))}
|
||||
|
||||
|
||||
|
||||
{/* <li className="timeline-item timeline-item-transparent">
|
||||
<span className="timeline-point timeline-point-success"></span>
|
||||
|
||||
@ -15,6 +15,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import { localToUtc } from "../../utils/appUtils";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
|
||||
const EmpAttendance = () => {
|
||||
const { employeeId } = useParams();
|
||||
@ -82,21 +83,19 @@ const EmpAttendance = () => {
|
||||
</FormProvider>
|
||||
</>
|
||||
</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 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>}
|
||||
{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 && (
|
||||
<table className="table mb-0">
|
||||
<thead>
|
||||
@ -179,9 +178,8 @@ const EmpAttendance = () => {
|
||||
{[...Array(totalPages)].map((_, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={`page-item ${
|
||||
currentPage === index + 1 ? "active" : ""
|
||||
}`}
|
||||
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className="page-link "
|
||||
@ -192,9 +190,8 @@ const EmpAttendance = () => {
|
||||
</li>
|
||||
))}
|
||||
<li
|
||||
className={`page-item ${
|
||||
currentPage === totalPages ? "disabled" : ""
|
||||
}`}
|
||||
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className="page-link "
|
||||
|
||||
@ -145,7 +145,7 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
|
||||
handleGroupBy,
|
||||
selectedGroup.id,
|
||||
appliedStatusId,
|
||||
selectedProjectId, // ✅ Added dependency
|
||||
selectedProjectId,
|
||||
]);
|
||||
|
||||
|
||||
|
||||
@ -124,7 +124,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
||||
},
|
||||
{
|
||||
key: "expensesType",
|
||||
label: "Expense Type",
|
||||
label: "Expense Category",
|
||||
getValue: (e) => e.expensesType?.name || "N/A",
|
||||
align: "text-start",
|
||||
},
|
||||
@ -178,8 +178,6 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
||||
<span
|
||||
className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary"
|
||||
}`}
|
||||
className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary"
|
||||
}`}
|
||||
>
|
||||
{e.status?.name || "Unknown"}
|
||||
</span>
|
||||
@ -267,10 +265,10 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
||||
<td colSpan={8} className="text-start">
|
||||
<div className="d-flex align-items-center">
|
||||
{" "}
|
||||
<small className="fs-6 py-1">
|
||||
<small className="fs-6 ms-2 py-1">
|
||||
{displayField} :{" "}
|
||||
</small>{" "}
|
||||
<small className="fs-6 ms-3">
|
||||
<small className="fs-6 ms-3">
|
||||
{IsGroupedByDate
|
||||
? formatUTCToLocalTime(key)
|
||||
: key}
|
||||
|
||||
@ -16,6 +16,7 @@ import { useParams } from "react-router-dom";
|
||||
import ProgressBar from "../../common/ProgressBar";
|
||||
import { formatNumber } from "../../../utils/dateUtils";
|
||||
import { useServices } from "../../../hooks/masterHook/useMaster";
|
||||
import { SpinnerLoader } from "../../common/Loader";
|
||||
|
||||
const WorkArea = ({ workArea, floor, forBuilding }) => {
|
||||
const selectedProject = useSelectedProject()
|
||||
@ -120,9 +121,14 @@ const WorkArea = ({ workArea, floor, forBuilding }) => {
|
||||
className="accordion-collapse collapse"
|
||||
aria-labelledby={`heading-${workArea.id}`}
|
||||
>
|
||||
<div className="accordion-body px-6">
|
||||
<div className="accordion-body px-6">
|
||||
{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 ? (
|
||||
<div className="text-center py-2 text-muted">No activities available for this work area.</div>
|
||||
) : ProjectTaskList?.length > 0 ? (
|
||||
|
||||
@ -29,6 +29,7 @@ import eventBus from "../../services/eventBus";
|
||||
import { useParams } from "react-router-dom";
|
||||
import GlobalModel from "../common/GlobalModel";
|
||||
import { setService } from "../../slices/globalVariablesSlice";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
|
||||
const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
|
||||
const projectId = useSelectedProject();
|
||||
@ -186,18 +187,20 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="row ">
|
||||
{isLoading && <p>Loading....</p>}
|
||||
{projectInfra && projectInfra?.length > 0 && (
|
||||
<div className="row modal-min-h">
|
||||
{isLoading ? (
|
||||
<div className="d-flex justify-content-center align-items-center py-5">
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
) : projectInfra && projectInfra.length > 0 ? (
|
||||
<InfraTable
|
||||
buildings={projectInfra}
|
||||
projectId={projectId}
|
||||
serviceId={selectedService}
|
||||
/>
|
||||
)}
|
||||
{!isLoading && projectInfra?.length == 0 && (
|
||||
<div className="mt-5">
|
||||
<p>No Infra Avaiable</p>
|
||||
) : (
|
||||
<div className="text-center py-5">
|
||||
<p className="text-muted fs-6">No infrastructure data available.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -2,6 +2,7 @@ import React from "react";
|
||||
import { useProjectAssignedOrganizations } from "../../../hooks/useProjects";
|
||||
import { useSelectedProject } from "../../../slices/apiDataManager";
|
||||
import { formatUTCToLocalTime } from "../../../utils/dateUtils";
|
||||
import { SpinnerLoader } from "../../common/Loader";
|
||||
|
||||
const ProjectAssignedOrgs = () => {
|
||||
const selectedProject = useSelectedProject();
|
||||
@ -25,12 +26,12 @@ const ProjectAssignedOrgs = () => {
|
||||
),
|
||||
align: "text-start",
|
||||
},
|
||||
{
|
||||
{
|
||||
key: "service",
|
||||
label: "Service Name",
|
||||
getValue: (org) => (
|
||||
<div className="d-flex gap-2 py-1 ">
|
||||
{org?.service?.name}
|
||||
{org?.service?.name}
|
||||
</div>
|
||||
),
|
||||
align: "text-start",
|
||||
@ -49,7 +50,7 @@ const ProjectAssignedOrgs = () => {
|
||||
align: "text-center",
|
||||
},
|
||||
|
||||
{
|
||||
{
|
||||
key: "organizationType",
|
||||
label: "Organization Type",
|
||||
getValue: (org) => (
|
||||
@ -62,7 +63,7 @@ const ProjectAssignedOrgs = () => {
|
||||
),
|
||||
align: "text-center",
|
||||
},
|
||||
{
|
||||
{
|
||||
key: "assignedDate",
|
||||
label: "Assigned Date",
|
||||
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>;
|
||||
|
||||
return (
|
||||
|
||||
@ -21,7 +21,7 @@ const ProjectOrganizations = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="row modal-min-h">
|
||||
<ProjectAssignedOrgs />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
import { useSelectedProject } from "../../../slices/apiDataManager";
|
||||
import GlobalModel from "../../common/GlobalModel";
|
||||
import TeamAssignToProject from "./TeamAssignToProject";
|
||||
import { SpinnerLoader } from "../../common/Loader";
|
||||
|
||||
const Teams = () => {
|
||||
const selectedProject = useSelectedProject();
|
||||
@ -158,16 +159,8 @@ const Teams = () => {
|
||||
<div>
|
||||
{!servicesLoading && (
|
||||
<>
|
||||
{(!assignedServices || assignedServices.length === 0) && (
|
||||
<span className="badge bg-label-secondary">
|
||||
Not Service Assigned
|
||||
</span>
|
||||
)}
|
||||
|
||||
{assignedServices?.length === 1 && (
|
||||
<span className="badge bg-label-secondary">
|
||||
{assignedServices[0].name}
|
||||
</span>
|
||||
<h5 className="mb-2">{assignedServices[0].name}</h5>
|
||||
)}
|
||||
|
||||
{assignedServices?.length > 1 && (
|
||||
@ -232,7 +225,11 @@ const Teams = () => {
|
||||
</div>
|
||||
|
||||
<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 && (
|
||||
<table className="table ">
|
||||
<thead>
|
||||
@ -290,8 +287,8 @@ const Teams = () => {
|
||||
<td>
|
||||
{emp.reAllocationDate
|
||||
? moment(emp.reAllocationDate).format(
|
||||
"DD-MMM-YYYY"
|
||||
)
|
||||
"DD-MMM-YYYY"
|
||||
)
|
||||
: "Present"}
|
||||
</td>
|
||||
)}
|
||||
|
||||
@ -20,16 +20,7 @@ export const newTenantSchema = z.object({
|
||||
contactNumber: z.string().trim()
|
||||
.nonempty("Contact number is required")
|
||||
.regex(/^\+?[1-9]\d{7,14}$/, "Enter a valid contact number"),
|
||||
onBoardingDate: z.preprocess((val) => {
|
||||
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",
|
||||
})),
|
||||
onBoardingDate: z.string().min(1, { message: "Date is required" }),
|
||||
organizationSize: z.string().nonempty("Organization size is required"),
|
||||
industryId: z.string().uuid("Invalid industry ID"),
|
||||
reference: z.string().nonempty("Reference is required"),
|
||||
|
||||
@ -19,3 +19,13 @@ const 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>
|
||||
)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useImageGalleryFilter } from "../../hooks/useImageGallery";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
@ -8,6 +8,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { defaultGalleryFilterValue, gallerySchema } from "./GallerySchema";
|
||||
import SelectMultiple from "../common/SelectMultiple";
|
||||
import { localToUtc } from "../../utils/appUtils";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
const GalleryFilterPanel = ({ onApply }) => {
|
||||
const selectedProject = useSelectedProject();
|
||||
@ -27,7 +28,7 @@ const GalleryFilterPanel = ({ onApply }) => {
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
setValue,reset,
|
||||
setValue, reset,
|
||||
formState: { errors },
|
||||
} = methods;
|
||||
|
||||
@ -40,12 +41,17 @@ const GalleryFilterPanel = ({ onApply }) => {
|
||||
// closePanel()
|
||||
};
|
||||
|
||||
const onClear=()=>{
|
||||
const onClear = () => {
|
||||
reset(defaultGalleryFilterValue);
|
||||
setResetKey((prev) => prev + 1);
|
||||
setResetKey((prev) => prev + 1);
|
||||
// closePanel()
|
||||
}
|
||||
|
||||
const location = useLocation();
|
||||
useEffect(() => {
|
||||
closePanel();
|
||||
}, [location]);
|
||||
|
||||
if (isLoading) return <div>Loading....</div>;
|
||||
if (isError) return <div>{error.message}</div>;
|
||||
return (
|
||||
|
||||
@ -7,8 +7,7 @@ import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||
import Pagination from "../common/Pagination";
|
||||
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||
import Loader from "../common/Loader";
|
||||
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
const ImageGalleryListView = ({filter}) => {
|
||||
const [hoveredImage, setHoveredImage] = useState(null);
|
||||
const selectedProject = useSelectedProject();
|
||||
@ -44,10 +43,10 @@ const ImageGalleryListView = ({filter}) => {
|
||||
}
|
||||
|
||||
|
||||
if (isLoading) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="page-min-h d-flex justify-content-center align-items-center">
|
||||
<Loader />
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -179,7 +179,7 @@ const AttendancePage = () => {
|
||||
</div>
|
||||
|
||||
{/* 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="col-12 col-sm-6">
|
||||
<select
|
||||
|
||||
@ -11,8 +11,7 @@ import { defaultContactFilter } from "../../components/Directory/DirectorySchema
|
||||
import { useDebounce } from "../../utils/appUtils";
|
||||
import Pagination from "../../components/common/Pagination";
|
||||
import ListViewContact from "../../components/Directory/ListViewContact";
|
||||
import Loader from "../../components/common/Loader";
|
||||
|
||||
import { SpinnerLoader } from "../../components/common/Loader";
|
||||
// Utility for CSV export
|
||||
const formatExportData = (contacts) => {
|
||||
return contacts.map((contact) => ({
|
||||
@ -113,7 +112,14 @@ const ContactsPage = ({ projectId, searchText, onExport }) => {
|
||||
{/* Grid / List View */}
|
||||
{gridView ? (
|
||||
<>
|
||||
{isLoading && <Loader />}
|
||||
{isLoading && (
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center"
|
||||
style={{ height: "50vh" }}
|
||||
>
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data?.data?.length === 0 && (
|
||||
<div className="py-4 text-center">
|
||||
|
||||
@ -44,7 +44,7 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
||||
const [searchNote, setSearchNote] = useState("");
|
||||
const [activeTab, setActiveTab] = useState("notes");
|
||||
const { setActions } = useFab();
|
||||
const [gridView, setGridView] = useState(false);
|
||||
const [gridView, setGridView] = useState(true);
|
||||
const [isOpenBucket, setOpenBucket] = useState(false);
|
||||
const [isManageContact, setManageContact] = useState({
|
||||
isOpen: false,
|
||||
|
||||
@ -115,7 +115,7 @@ const ExpensePage = () => {
|
||||
{IsViewAll || IsViewSelf || IsCreatedAble ? (
|
||||
<>
|
||||
<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="col-6">
|
||||
<input
|
||||
|
||||
@ -3,7 +3,7 @@ import { useProfile } from "../../hooks/useProfile";
|
||||
import TenantDetails from "./TenantDetails";
|
||||
import { VIEW_TENANTS } from "../../utils/constants";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Loader from "../../components/common/Loader";
|
||||
import { SpinnerLoader } from "../../components/common/Loader";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
|
||||
const SelfTenantDetails = () => {
|
||||
@ -19,7 +19,7 @@ const SelfTenantDetails = () => {
|
||||
}, [isSelfTenantView, navigate]);
|
||||
|
||||
if (loading || !tenantId) {
|
||||
return <Loader/>;
|
||||
return <SpinnerLoader />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -7,7 +7,7 @@ import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||
import GlobalModel from "../../components/common/GlobalModel";
|
||||
import EditProfile from "../../components/Tenant/EditProfile";
|
||||
import SubScriptionHistory from "../../components/Tenant/SubScriptionHistory";
|
||||
import Loader from "../../components/common/Loader";
|
||||
import { SpinnerLoader } from "../../components/common/Loader";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
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 (isLoading)
|
||||
return (
|
||||
<div className="my-4">
|
||||
<Loader />
|
||||
<div
|
||||
className="page-min-h d-flex justify-content-center align-items-center"
|
||||
style={{ minHeight: "80vh" }}
|
||||
>
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
);
|
||||
if (isError)
|
||||
@ -110,14 +113,14 @@ const TenantDetails = ({
|
||||
data={
|
||||
iTSelf
|
||||
? [
|
||||
{ label: "Home", link: "/dashboard" },
|
||||
{ label: "Tenant Details", link: null },
|
||||
]
|
||||
{ label: "Home", link: "/dashboard" },
|
||||
{ label: "Tenant Details", link: null },
|
||||
]
|
||||
: [
|
||||
{ label: "Home", link: "/dashboard" },
|
||||
{ label: "Tenant", link: "/tenants" },
|
||||
{ label: "Tenant Details", link: null },
|
||||
]
|
||||
{ label: "Home", link: "/dashboard" },
|
||||
{ label: "Tenant", link: "/tenants" },
|
||||
{ label: "Tenant Details", link: null },
|
||||
]
|
||||
}
|
||||
/>
|
||||
)}
|
||||
@ -128,9 +131,8 @@ const TenantDetails = ({
|
||||
<li key={tab.id} className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link d-flex align-items-center text-tiny gap-2 ${
|
||||
index === 0 ? "active" : ""
|
||||
}`}
|
||||
className={`nav-link d-flex align-items-center text-tiny gap-2 ${index === 0 ? "active" : ""
|
||||
}`}
|
||||
role="tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target={`#${tab.id}`}
|
||||
@ -150,9 +152,8 @@ const TenantDetails = ({
|
||||
{tabs.map((tab, index) => (
|
||||
<div
|
||||
key={tab.id}
|
||||
className={`tab-pane fade ${
|
||||
index === 0 ? "show active" : ""
|
||||
} text-start`}
|
||||
className={`tab-pane fade ${index === 0 ? "show active" : ""
|
||||
} text-start`}
|
||||
id={tab.id}
|
||||
>
|
||||
{tab.content}
|
||||
|
||||
@ -4,8 +4,7 @@ import { useAuthModal, useSelectTenant, useTenants } from "../../hooks/useAuth";
|
||||
import { useProfile } from "../../hooks/useProfile";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import AuthRepository from "../../repositories/AuthRepository";
|
||||
import Loader from "../../components/common/Loader";
|
||||
|
||||
import { SpinnerLoader } from "../../components/common/Loader";
|
||||
const SwitchTenant = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { profile } = useProfile();
|
||||
@ -99,7 +98,7 @@ const SwitchTenant = () => {
|
||||
</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;
|
||||
@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
|
||||
import { useTenants, useSelectTenant, useLogout } from "../../hooks/useAuth.jsx";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import Dashboard from "../../components/Dashboard/Dashboard.jsx";
|
||||
import Loader from "../../components/common/Loader.jsx";
|
||||
import { SpinnerLoader } from "../../components/common/Loader.jsx";
|
||||
|
||||
const TenantSelectionPage = () => {
|
||||
const [pendingTenant, setPendingTenant] = useState(null);
|
||||
@ -45,7 +45,7 @@ const TenantSelectionPage = () => {
|
||||
isPending ||
|
||||
(data?.data?.length === 1 && pendingTenant !== null)
|
||||
) {
|
||||
return <Loader />;
|
||||
return <SpinnerLoader />;
|
||||
}
|
||||
|
||||
if (!data?.data?.length) {
|
||||
|
||||
@ -39,6 +39,7 @@ import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import Pagination from "../../components/common/Pagination";
|
||||
import handleEmployeeExport from "../../components/Employee/handleEmployeeExport";
|
||||
import { SpinnerLoader } from "../../components/common/Loader";
|
||||
|
||||
const EmployeeList = () => {
|
||||
const selectedProjectId = useSelector(
|
||||
@ -468,12 +469,15 @@ const EmployeeList = () => {
|
||||
<tbody>
|
||||
{loading && (
|
||||
<tr>
|
||||
<td colSpan={8}>
|
||||
<p>Loading...</p>
|
||||
<td colSpan={8} className="text-center py-5">
|
||||
<div className="d-flex justify-content-center align-items-center py-5" style={{ minHeight: "50vh" }}>
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
|
||||
{!loading &&
|
||||
displayData?.length === 0 &&
|
||||
(!searchText) ? (
|
||||
@ -649,16 +653,16 @@ const EmployeeList = () => {
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{displayData?.length > 0 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={paginate}
|
||||
/>
|
||||
)}
|
||||
{displayData?.length > 0 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={paginate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="card">
|
||||
|
||||
@ -7,7 +7,7 @@ import AboutProject from "../../components/Project/AboutProject";
|
||||
import ProjectNav from "../../components/Project/ProjectNav";
|
||||
import Teams from "../../components/Project/Team/Teams";
|
||||
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 Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
@ -20,7 +20,7 @@ import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
import ProjectDocuments from "../../components/Project/ProjectDocuments";
|
||||
import ProjectSetting from "../../components/Project/ProjectSetting";
|
||||
import DirectoryPage from "../Directory/DirectoryPage";
|
||||
import { useProjectAccess } from "../../hooks/useProjectAccess";
|
||||
import { useProjectAccess } from "../../hooks/useProjectAccess";
|
||||
|
||||
import "./ProjectDetails.css";
|
||||
import ProjectOrganizations from "../../components/Project/ProjectOrganizations";
|
||||
@ -65,7 +65,14 @@ const ProjectDetails = () => {
|
||||
};
|
||||
|
||||
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 = () => {
|
||||
|
||||
@ -7,8 +7,8 @@ import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
|
||||
import ProjectCardView from "../../components/Project/ProjectCardView";
|
||||
import usePagination from "../../hooks/usePagination";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import Loader from "../../components/common/Loader";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { SpinnerLoader } from "../../components/common/Loader";
|
||||
|
||||
const ProjectContext = createContext();
|
||||
export const useProjectContext = () => {
|
||||
@ -95,8 +95,16 @@ const ProjectPage = () => {
|
||||
}
|
||||
}, [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>
|
||||
return (
|
||||
<ProjectContext.Provider value={contextDispatcher}>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user