added Expensebstatus widget, fixed small bugs
This commit is contained in:
parent
3553a7b521
commit
78808ecac0
@ -9,4 +9,15 @@
|
||||
}
|
||||
.table_header_border {
|
||||
border-bottom:2px solid var(--bs-table-border-color) ;
|
||||
}
|
||||
.text-gary-80 {
|
||||
color:var(--bs-gray-500)
|
||||
}
|
||||
|
||||
.text-royalblue{
|
||||
color: #1796e3;
|
||||
}
|
||||
|
||||
.text-md {
|
||||
font-size: 2rem;
|
||||
}
|
3
public/assets/vendor/css/core.css
vendored
3
public/assets/vendor/css/core.css
vendored
@ -32573,4 +32573,7 @@ body:not(.modal-open) .layout-content-navbar .layout-navbar {
|
||||
}
|
||||
.text-red{
|
||||
color:var(--bs-red)
|
||||
}
|
||||
.bg-gray {
|
||||
background:var(--bs-body-color)
|
||||
}
|
@ -5,14 +5,9 @@ import { useAttendanceOverviewData } from "../../hooks/useDashboard_Data";
|
||||
import flatColors from "../Charts/flatColor";
|
||||
import ChartSkeleton from "../Charts/Skelton";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import { formatDate_DayMonth } from "../../utils/dateUtils";
|
||||
|
||||
|
||||
const formatDate = (dateStr) => {
|
||||
const date = new Date(dateStr);
|
||||
return date.toLocaleDateString("en-GB", {
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
});
|
||||
};
|
||||
|
||||
const AttendanceOverview = () => {
|
||||
const [dayRange, setDayRange] = useState(7);
|
||||
@ -35,7 +30,7 @@ const AttendanceOverview = () => {
|
||||
const map = new Map();
|
||||
|
||||
attendanceData.forEach((entry) => {
|
||||
const date = formatDate(entry.date);
|
||||
const date = formatDate_DayMonth(entry.date);
|
||||
if (!map.has(date)) map.set(date, {});
|
||||
map.get(date)[entry.role.trim()] = entry.present;
|
||||
});
|
||||
|
@ -17,6 +17,7 @@ import AttendanceOverview from "./AttendanceOverview";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import { useProjectName } from "../../hooks/useProjects";
|
||||
import ExpenseAnalysis from "./ExpenseAnalysis";
|
||||
import ExpenseStatus from "./ExpenseStatus";
|
||||
|
||||
const Dashboard = () => {
|
||||
// const { projectsCardData } = useDashboardProjectsCardData();
|
||||
@ -27,19 +28,30 @@ const Dashboard = () => {
|
||||
const projectId = useSelector((store) => store.localVariables.projectId);
|
||||
const isAllProjectsSelected = projectId === null;
|
||||
|
||||
|
||||
return (
|
||||
<div className="container-fluid mt-5">
|
||||
<div className="row gy-4">
|
||||
<div className="col-xxl-6 col-lg-6">
|
||||
<ExpenseAnalysis />
|
||||
<div className="container-xxl flex-grow-1 container-p-y">
|
||||
{/* <div className="col-xxl-6 col-lg-6">
|
||||
|
||||
</div>
|
||||
|
||||
{!isAllProjectsSelected && (
|
||||
<div className="col-xxl-6 col-lg-6">
|
||||
<AttendanceOverview />
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
<div class="row mb-6 g-6">
|
||||
<div class="col-12 col-xl-8">
|
||||
<div class="card h-100">
|
||||
<ExpenseAnalysis />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xl-4 col-md-6">
|
||||
<div class="card h-100">
|
||||
<ExpenseStatus />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ import { useExpenseAnalysis } from "../../hooks/useDashboard_Data";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import { DateRangePicker1 } from "../common/DateRangePicker";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { localToUtc } from "../../utils/appUtils";
|
||||
import { formatCurrency, localToUtc } from "../../utils/appUtils";
|
||||
|
||||
const ExpenseAnalysis = () => {
|
||||
const projectId = useSelectedProject();
|
||||
@ -19,11 +19,11 @@ const ExpenseAnalysis = () => {
|
||||
const { watch } = methods;
|
||||
|
||||
const [startDate, endDate] = watch(["startDate", "endDate"]);
|
||||
const { data, isLoading, isError, error, isFetching } = useExpenseAnalysis(
|
||||
projectId,
|
||||
localToUtc(startDate),
|
||||
localToUtc(endDate)
|
||||
);
|
||||
const { data, isLoading, isError, error, isFetching } = useExpenseAnalysis(
|
||||
projectId,
|
||||
localToUtc(startDate),
|
||||
localToUtc(endDate)
|
||||
);
|
||||
|
||||
if (isError) return <div>{error.message}</div>;
|
||||
|
||||
@ -31,7 +31,7 @@ const ExpenseAnalysis = () => {
|
||||
|
||||
const labels = report.map((item) => item.projectName);
|
||||
const series = report.map((item) => item.totalApprovedAmount || 0);
|
||||
const total = data?.totalAmount || 0;
|
||||
const total = formatCurrency(data?.totalAmount || 0);
|
||||
|
||||
const donutOptions = {
|
||||
chart: { type: "donut" },
|
||||
@ -57,84 +57,93 @@ const ExpenseAnalysis = () => {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
if (data?.report === 0) {
|
||||
return <div>No data found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="card shadow-sm" style={{ minHeight: "500px" }}>
|
||||
<div className="card-header d-flex justify-content-between align-items-center mt-3">
|
||||
<div>
|
||||
<h5 className="mb-1 fw-bold">Expense Breakdown</h5>
|
||||
<p className="card-subtitle me-3">Detailed project expenses</p>
|
||||
</div>
|
||||
|
||||
<>
|
||||
<div className="card-header d-flex justify-content-between align-items-center ">
|
||||
<div>
|
||||
<h5 className="mb-1 fw-bold">Expense Breakdown</h5>
|
||||
<p className="card-subtitle me-3">Detailed project expenses</p>
|
||||
</div>
|
||||
|
||||
<div className="text-end">
|
||||
<FormProvider {...methods}>
|
||||
<DateRangePicker1 />
|
||||
<FormProvider {...methods}>
|
||||
<DateRangePicker1 />
|
||||
</FormProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card-body position-relative">
|
||||
{/* Initial loading: show full loader */}
|
||||
{isLoading && (
|
||||
<div className="d-flex justify-content-center align-items-center" style={{ height: "200px" }}>
|
||||
<span>Loading...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Data display */}
|
||||
{!isLoading && report.length === 0 && (
|
||||
<div className="text-center text-muted py-5">No data found</div>
|
||||
)}
|
||||
|
||||
{!isLoading && report.length > 0 && (
|
||||
<>
|
||||
{/* Overlay spinner for refetch */}
|
||||
{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-8">
|
||||
<Chart
|
||||
options={donutOptions}
|
||||
series={report.map((item) => item.totalApprovedAmount || 0)}
|
||||
type="donut"
|
||||
width="320"
|
||||
/>
|
||||
<div className="card-body position-relative">
|
||||
{/* Initial loading: show full loader */}
|
||||
{isLoading && (
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center"
|
||||
style={{ height: "200px" }}
|
||||
>
|
||||
<span>Loading...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-2 w-100">
|
||||
<div className="row">
|
||||
{report.map((item, idx) => (
|
||||
<div className="col-6 d-flex justify-content-start align-items-start mb-2" 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-bold fs-6">{item.projectName}</small>
|
||||
<span className="fw-bold text-muted ms-1">{item.totalApprovedAmount}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{/* Data display */}
|
||||
{!isLoading && report.length === 0 && (
|
||||
<div className="text-center text-muted py-5">No data found</div>
|
||||
)}
|
||||
|
||||
{!isLoading && report.length > 0 && (
|
||||
<>
|
||||
{/* Overlay spinner for refetch */}
|
||||
{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-8">
|
||||
<Chart
|
||||
options={donutOptions}
|
||||
series={report.map((item) => item.totalApprovedAmount || 0)}
|
||||
type="donut"
|
||||
width="320"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-2 w-100">
|
||||
<div className="row">
|
||||
{report.map((item, idx) => (
|
||||
<div
|
||||
className="col-6 d-flex justify-content-start align-items-start mb-2"
|
||||
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>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
116
src/components/Dashboard/ExpenseStatus.jsx
Normal file
116
src/components/Dashboard/ExpenseStatus.jsx
Normal file
@ -0,0 +1,116 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useExpense } from "../../hooks/useExpense";
|
||||
import { useExpenseStatus } from "../../hooks/useDashboard_Data";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import { useProjectName } from "../../hooks/useProjects";
|
||||
import { formatCurrency } from "../../utils/appUtils";
|
||||
import { EXPENSE_STATUS } from "../../utils/constants";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const ExpenseStatus = () => {
|
||||
const [projectName, setProjectName] = useState("All Project");
|
||||
const selectedProject = useSelectedProject();
|
||||
const { projectNames, loading } = useProjectName();
|
||||
const { data, isPending, error } = useExpenseStatus(selectedProject);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProject && projectNames?.length) {
|
||||
const project = projectNames.find((p) => p.id === selectedProject);
|
||||
setProjectName(project?.name || "All Project");
|
||||
} else {
|
||||
setProjectName("All Project");
|
||||
}
|
||||
}, [projectNames, selectedProject]);
|
||||
return (
|
||||
<>
|
||||
<div className="card-header d-flex justify-content-between text-start">
|
||||
<div className="m-0">
|
||||
<h5 className="card-title mb-1">Expense - By Status</h5>
|
||||
<p className="card-subtitle m-0 ">{projectName}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card-body ">
|
||||
<div className=" py-0 text-start mb-2">
|
||||
<div className="d-flex justify-content-between align-items-center">
|
||||
<span className="fs-5"> Project Spendings:</span>{" "}
|
||||
<span className="text-end text-royalblue text-md">
|
||||
{formatCurrency(data?.totalAmount)}
|
||||
</span>
|
||||
</div>
|
||||
<small className=" text-gary-80">{`(All Processed Payments)`}</small>
|
||||
</div>
|
||||
<div className="report-list text-start">
|
||||
{[
|
||||
{
|
||||
title: "Pending Payment",
|
||||
count: data?.approvePending?.count,
|
||||
amount: data?.approvePending?.totalAmount,
|
||||
icon: "bx bx-rupee",
|
||||
iconColor: "text-primary",
|
||||
status: EXPENSE_STATUS.payment_pending,
|
||||
},
|
||||
{
|
||||
title: "Pending Approver",
|
||||
count: data?.processPending?.count,
|
||||
amount: data?.processPending?.totalAmount,
|
||||
icon: "fa-solid fa-check",
|
||||
iconColor: "text-warning",
|
||||
status: EXPENSE_STATUS.approve_pending,
|
||||
},
|
||||
{
|
||||
title: "Pending Reviewer",
|
||||
count: data?.reviewPending?.count,
|
||||
amount: data?.reviewPending?.totalAmount,
|
||||
icon: "bx bx-file",
|
||||
iconColor: "text-secondary",
|
||||
status: EXPENSE_STATUS.review_pending,
|
||||
},
|
||||
{
|
||||
title: "Draft",
|
||||
count: data?.submited?.count,
|
||||
amount: data?.submited?.totalAmount,
|
||||
icon: "bx bx-file-blank",
|
||||
iconColor: "text-info",
|
||||
status: EXPENSE_STATUS.daft,
|
||||
},
|
||||
].map((item, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="report-list-item rounded-2 mb-4 bg-lighter px-2 py-3 cursor-pointer"
|
||||
onClick={() => navigate(`/expenses/${item.status}`)}
|
||||
>
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="report-list-icon shadow-xs me-4">
|
||||
<span className="d-inline-flex align-items-center justify-content-center rounded-circle border p-2">
|
||||
<i className={`${item.icon} ${item.iconColor} bx-lg`}></i>
|
||||
</span>
|
||||
</div>
|
||||
<div className="d-flex justify-content-between align-items-center w-100 flex-wrap gap-2">
|
||||
<div className="d-flex flex-column gap-2">
|
||||
<span className="fw-bold">{item.title}</span>
|
||||
<small className="mb-0 text-primary">
|
||||
{formatCurrency(item.amount)}
|
||||
</small>
|
||||
</div>
|
||||
<div className="">
|
||||
{" "}
|
||||
<small className="text-muted fs-semibold text-royalblue text-md">
|
||||
{item.count}{" "}
|
||||
</small>
|
||||
<small className="text-muted fs-semibold text-royalblue text-md">
|
||||
<i className="bx bx-chevron-right"></i>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExpenseStatus;
|
@ -13,9 +13,11 @@ import { useSelector } from "react-redux";
|
||||
import moment from "moment";
|
||||
import { useExpenseFilter } from "../../hooks/useExpense";
|
||||
import { ExpenseFilterSkeleton } from "./ExpenseSkeleton";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
||||
const { status } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const selectedProjectId = useSelector(
|
||||
(store) => store.localVariables.projectId
|
||||
);
|
||||
@ -37,9 +39,22 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
||||
const [selectedGroup, setSelectedGroup] = useState(groupByList[0]);
|
||||
const [resetKey, setResetKey] = useState(0);
|
||||
|
||||
const dynamicDefaultFilter = useMemo(() => {
|
||||
return {
|
||||
...defaultFilter,
|
||||
statusIds: status ? [status] : defaultFilter.statusIds || [],
|
||||
projectIds: defaultFilter.projectIds || [],
|
||||
createdByIds: defaultFilter.createdByIds || [],
|
||||
paidById: defaultFilter.paidById || [],
|
||||
isTransactionDate: defaultFilter.isTransactionDate ?? true,
|
||||
startDate: defaultFilter.startDate,
|
||||
endDate: defaultFilter.endDate,
|
||||
};
|
||||
}, [status]);
|
||||
|
||||
const methods = useForm({
|
||||
resolver: zodResolver(SearchSchema),
|
||||
defaultValues: defaultFilter,
|
||||
defaultValues: dynamicDefaultFilter,
|
||||
});
|
||||
|
||||
const { control, handleSubmit, reset, setValue, watch } = methods;
|
||||
@ -71,14 +86,49 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
||||
onApply(defaultFilter);
|
||||
handleGroupBy(groupByList[0].id);
|
||||
closePanel();
|
||||
if (status) {
|
||||
navigate("/expenses", { replace: true });
|
||||
}
|
||||
};
|
||||
|
||||
// Close popup when navigating to another component
|
||||
const location = useLocation();
|
||||
useEffect(() => {
|
||||
closePanel();
|
||||
}, [location]);
|
||||
|
||||
const [appliedStatusId, setAppliedStatusId] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!status) return;
|
||||
|
||||
if (status !== appliedStatusId && data) {
|
||||
const filterWithStatus = {
|
||||
...dynamicDefaultFilter,
|
||||
startDate: dynamicDefaultFilter.startDate
|
||||
? moment
|
||||
.utc(dynamicDefaultFilter.startDate, "DD-MM-YYYY")
|
||||
.toISOString()
|
||||
: undefined,
|
||||
endDate: dynamicDefaultFilter.endDate
|
||||
? moment.utc(dynamicDefaultFilter.endDate, "DD-MM-YYYY").toISOString()
|
||||
: undefined,
|
||||
};
|
||||
|
||||
onApply(filterWithStatus);
|
||||
handleGroupBy(selectedGroup.id);
|
||||
|
||||
setAppliedStatusId(status);
|
||||
}
|
||||
}, [
|
||||
status,
|
||||
data,
|
||||
dynamicDefaultFilter,
|
||||
onApply,
|
||||
handleGroupBy,
|
||||
selectedGroup.id,
|
||||
appliedStatusId,
|
||||
]);
|
||||
|
||||
if (isLoading || isFetching) return <ExpenseFilterSkeleton />;
|
||||
if (isError && isFetched)
|
||||
return <div>Something went wrong Here- {error.message} </div>;
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
EXPENSE_REJECTEDBY,
|
||||
ITEMS_PER_PAGE,
|
||||
} from "../../utils/constants";
|
||||
import { getColorNameFromHex, useDebounce } from "../../utils/appUtils";
|
||||
import { formatCurrency, getColorNameFromHex, useDebounce } from "../../utils/appUtils";
|
||||
import { ExpenseTableSkeleton } from "./ExpenseSkeleton";
|
||||
import ConfirmModal from "../common/ConfirmModal";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
@ -140,7 +140,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
||||
label: "Amount",
|
||||
getValue: (e) => (
|
||||
<>
|
||||
<i className="bx bx-rupee b-xs"></i> {e?.amount}
|
||||
{formatCurrency(e?.amount)}
|
||||
</>
|
||||
),
|
||||
isAlwaysVisible: true,
|
||||
|
@ -111,9 +111,6 @@ const ViewExpense = ({ ExpenseId }) => {
|
||||
<h5 className="fw-semibold">Expense Details</h5>
|
||||
<hr />
|
||||
</div>
|
||||
<div className="text-start mb-2">
|
||||
<div className="text-muted">{data?.description}</div>
|
||||
</div>
|
||||
{/* Row 1 */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<div className="d-flex">
|
||||
@ -277,7 +274,7 @@ const ViewExpense = ({ ExpenseId }) => {
|
||||
className="form-label me-2 mb-0 fw-semibold"
|
||||
style={{ minWidth: "130px" }}
|
||||
>
|
||||
Paid By:
|
||||
Paid By :
|
||||
</label>
|
||||
<div className="d-flex align-items-center ">
|
||||
<Avatar
|
||||
@ -294,6 +291,11 @@ const ViewExpense = ({ ExpenseId }) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-start my-1">
|
||||
<label className="fw-semibold form-label">Description : </label>
|
||||
<div className="text-muted">{data?.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-12 text-start">
|
||||
|
@ -14,7 +14,7 @@ import { useProfile } from "../../hooks/useProfile";
|
||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
import Avatar from "../../components/common/Avatar";
|
||||
import { useChangePassword } from "../Context/ChangePasswordContext";
|
||||
import { useProjectModal, useProjects } from "../../hooks/useProjects";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import { useProjectName } from "../../hooks/useProjects";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
@ -28,7 +28,6 @@ const Header = () => {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { openModal } = useProjectModal();
|
||||
const { mutate: logout, isPending: logouting } = useLogout();
|
||||
const { onOpen } = useAuthModal();
|
||||
const { openChangePassword } = useChangePassword();
|
||||
@ -232,16 +231,7 @@ const Header = () => {
|
||||
|
||||
<ul className="navbar-nav flex-row align-items-center ms-md-auto">
|
||||
{/* {HasManageProjectPermission && ( */}
|
||||
<li className="nav-item navbar-dropdown dropdown-user dropdown">
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
type="button"
|
||||
onClick={() => openModal(null)}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
<span className="d-none d-md-inline-block">Create Project</span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
{/* )} */}
|
||||
<li className="nav-item navbar-dropdown dropdown-user dropdown">
|
||||
<a
|
||||
|
@ -54,7 +54,7 @@ const ProjectCard = ({ project }) => {
|
||||
style={{ fontSize: "xx-large" }}
|
||||
></i>
|
||||
</div>
|
||||
<div className="me-2">
|
||||
<div className="me-2 text-start">
|
||||
<h5
|
||||
className="mb-0 stretched-link text-heading text-start"
|
||||
onClick={handleViewProject}
|
||||
|
@ -270,4 +270,13 @@ export const useExpenseAnalysis = (projectId, startDate, endDate) => {
|
||||
},
|
||||
enabled:shouldFetch
|
||||
});
|
||||
};
|
||||
};
|
||||
export const useExpenseStatus = (projectId)=>{
|
||||
return useQuery({
|
||||
queryKey:["expense_stauts",projectId],
|
||||
queryFn: async()=>{
|
||||
const resp = await GlobalRepository.getExpenseStatus(projectId);
|
||||
return resp.data;
|
||||
}
|
||||
})
|
||||
}
|
@ -206,7 +206,7 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="form-check form-switch d-flex align-items-center">
|
||||
<div className="form-check form-switch d-flex align-items-center m-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
|
@ -129,6 +129,9 @@ const ExpensePage = () => {
|
||||
</div>
|
||||
|
||||
<div className="col-6 text-end mt-2 mt-sm-0">
|
||||
<button className="btn btn-sm" onClick={clearFilter}>
|
||||
CLear
|
||||
</button>
|
||||
{IsCreatedAble && (
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
|
@ -1,6 +1,10 @@
|
||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import { ITEMS_PER_PAGE, MANAGE_PROJECT, PROJECT_STATUS } from "../../utils/constants";
|
||||
import {
|
||||
ITEMS_PER_PAGE,
|
||||
MANAGE_PROJECT,
|
||||
PROJECT_STATUS,
|
||||
} from "../../utils/constants";
|
||||
import ProjectListView from "../../components/Project/ProjectListView";
|
||||
import GlobalModel from "../../components/common/GlobalModel";
|
||||
import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
|
||||
@ -95,9 +99,18 @@ const ProjectPage = () => {
|
||||
}
|
||||
}, [data, isLoading, selectedStatuses]);
|
||||
|
||||
|
||||
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 (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}>
|
||||
<div className="container-fluid">
|
||||
@ -181,7 +194,8 @@ const ProjectPage = () => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{/* {HasManageProject && ( <button
|
||||
{/* {HasManageProject && ( */}
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
type="button"
|
||||
onClick={() =>
|
||||
@ -192,7 +206,8 @@ const ProjectPage = () => {
|
||||
<span className="d-none d-md-inline-block">
|
||||
Add New Project
|
||||
</span>
|
||||
</button>)} */}
|
||||
</button>
|
||||
{/* )} */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -209,7 +224,11 @@ const ProjectPage = () => {
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
) : (
|
||||
<ProjectCardView currentItems={currentItems} setCurrentPage={setCurrentPage} totalPages={totalPages} />
|
||||
<ProjectCardView
|
||||
currentItems={currentItems}
|
||||
setCurrentPage={setCurrentPage}
|
||||
totalPages={totalPages}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ------------------ */}
|
||||
|
@ -67,6 +67,7 @@ const GlobalRepository = {
|
||||
return api.get(url );
|
||||
},
|
||||
|
||||
getExpenseStatus:(projectId)=>api.get(`/api/Dashboard/expense/pendings${projectId ? `?projectId=${projectId}`:""}`)
|
||||
|
||||
};
|
||||
|
||||
|
@ -94,7 +94,7 @@ const router = createBrowserRouter(
|
||||
{ path: "/activities/task", element: <TaskPlannng /> },
|
||||
{ path: "/activities/reports", element: <Reports /> },
|
||||
{ path: "/gallary", element: <ComingSoonPage /> },
|
||||
{ path: "/expenses", element: <ExpensePage /> },
|
||||
{ path: "/expenses/:status?", element: <ExpensePage /> },
|
||||
{ path: "/masters", element: <MasterPage /> },
|
||||
{ path: "/tenants", element: <TenantPage /> },
|
||||
{ path: "/tenants/new-tenant", element: <CreateTenant /> },
|
||||
|
@ -59,7 +59,7 @@ const attemptTokenRefresh = async (storedRefreshToken) => {
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Token refresh failed:", error);
|
||||
removeSession()
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
@ -87,6 +87,17 @@ export function localToUtc(dateString) {
|
||||
|
||||
if (!day || !month || !year) return null;
|
||||
|
||||
const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), 0, 0, 0));
|
||||
const date = new Date(
|
||||
Date.UTC(Number(year), Number(month) - 1, Number(day), 0, 0, 0)
|
||||
);
|
||||
return isNaN(date.getTime()) ? null : date.toISOString();
|
||||
}
|
||||
|
||||
export const formatCurrency = (amount, currency = "INR", locale = "en-US") => {
|
||||
return new Intl.NumberFormat(locale, {
|
||||
style: "currency",
|
||||
currency: currency,
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
}).format(amount);
|
||||
};
|
||||
|
@ -147,6 +147,14 @@ export const PROJECT_STATUS = [
|
||||
];
|
||||
|
||||
|
||||
export const EXPENSE_STATUS = {
|
||||
daft:"297e0d8f-f668-41b5-bfea-e03b354251c8",
|
||||
review_pending:"6537018f-f4e9-4cb3-a210-6c3b2da999d7",
|
||||
payment_pending:"f18c5cfd-7815-4341-8da2-2c2d65778e27",
|
||||
approve_pending:"4068007f-c92f-4f37-a907-bc15fe57d4d8",
|
||||
|
||||
}
|
||||
|
||||
export const UUID_REGEX =
|
||||
/^\/employee\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import moment from "moment";
|
||||
import { ActiveTenant } from "./constants";
|
||||
import {format} from 'date-fns'
|
||||
|
||||
export const getDateDifferenceInDays = (startDate, endDate) => {
|
||||
if (!startDate || !endDate) {
|
||||
@ -93,6 +94,11 @@ export const getCompletionPercentage = (completedWork, plannedWork)=> {
|
||||
return clamped.toFixed(2);
|
||||
}
|
||||
|
||||
export const formatDate_DayMonth = (dateInput)=>{
|
||||
const date = new Date(dateInput);
|
||||
return format(date, "d MMM");
|
||||
}
|
||||
|
||||
export const getTenantStatus =(statusId)=>{
|
||||
return ActiveTenant === statusId ? " bg-label-success":"bg-label-secondary"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user