added Expensebstatus widget, fixed small bugs
This commit is contained in:
parent
3553a7b521
commit
78808ecac0
@ -9,4 +9,15 @@
|
|||||||
}
|
}
|
||||||
.table_header_border {
|
.table_header_border {
|
||||||
border-bottom:2px solid var(--bs-table-border-color) ;
|
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{
|
.text-red{
|
||||||
color:var(--bs-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 flatColors from "../Charts/flatColor";
|
||||||
import ChartSkeleton from "../Charts/Skelton";
|
import ChartSkeleton from "../Charts/Skelton";
|
||||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
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 AttendanceOverview = () => {
|
||||||
const [dayRange, setDayRange] = useState(7);
|
const [dayRange, setDayRange] = useState(7);
|
||||||
@ -35,7 +30,7 @@ const AttendanceOverview = () => {
|
|||||||
const map = new Map();
|
const map = new Map();
|
||||||
|
|
||||||
attendanceData.forEach((entry) => {
|
attendanceData.forEach((entry) => {
|
||||||
const date = formatDate(entry.date);
|
const date = formatDate_DayMonth(entry.date);
|
||||||
if (!map.has(date)) map.set(date, {});
|
if (!map.has(date)) map.set(date, {});
|
||||||
map.get(date)[entry.role.trim()] = entry.present;
|
map.get(date)[entry.role.trim()] = entry.present;
|
||||||
});
|
});
|
||||||
|
@ -17,6 +17,7 @@ import AttendanceOverview from "./AttendanceOverview";
|
|||||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
import { useProjectName } from "../../hooks/useProjects";
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
import ExpenseAnalysis from "./ExpenseAnalysis";
|
import ExpenseAnalysis from "./ExpenseAnalysis";
|
||||||
|
import ExpenseStatus from "./ExpenseStatus";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
// const { projectsCardData } = useDashboardProjectsCardData();
|
// const { projectsCardData } = useDashboardProjectsCardData();
|
||||||
@ -27,19 +28,30 @@ const Dashboard = () => {
|
|||||||
const projectId = useSelector((store) => store.localVariables.projectId);
|
const projectId = useSelector((store) => store.localVariables.projectId);
|
||||||
const isAllProjectsSelected = projectId === null;
|
const isAllProjectsSelected = projectId === null;
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container-fluid mt-5">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
<div className="row gy-4">
|
{/* <div className="col-xxl-6 col-lg-6">
|
||||||
<div className="col-xxl-6 col-lg-6">
|
|
||||||
<ExpenseAnalysis />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isAllProjectsSelected && (
|
{!isAllProjectsSelected && (
|
||||||
<div className="col-xxl-6 col-lg-6">
|
<div className="col-xxl-6 col-lg-6">
|
||||||
<AttendanceOverview />
|
<AttendanceOverview />
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,7 @@ import { useExpenseAnalysis } from "../../hooks/useDashboard_Data";
|
|||||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
import { DateRangePicker1 } from "../common/DateRangePicker";
|
import { DateRangePicker1 } from "../common/DateRangePicker";
|
||||||
import { FormProvider, useForm } from "react-hook-form";
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
import { localToUtc } from "../../utils/appUtils";
|
import { formatCurrency, localToUtc } from "../../utils/appUtils";
|
||||||
|
|
||||||
const ExpenseAnalysis = () => {
|
const ExpenseAnalysis = () => {
|
||||||
const projectId = useSelectedProject();
|
const projectId = useSelectedProject();
|
||||||
@ -19,11 +19,11 @@ const ExpenseAnalysis = () => {
|
|||||||
const { watch } = methods;
|
const { watch } = methods;
|
||||||
|
|
||||||
const [startDate, endDate] = watch(["startDate", "endDate"]);
|
const [startDate, endDate] = watch(["startDate", "endDate"]);
|
||||||
const { data, isLoading, isError, error, isFetching } = useExpenseAnalysis(
|
const { data, isLoading, isError, error, isFetching } = useExpenseAnalysis(
|
||||||
projectId,
|
projectId,
|
||||||
localToUtc(startDate),
|
localToUtc(startDate),
|
||||||
localToUtc(endDate)
|
localToUtc(endDate)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isError) return <div>{error.message}</div>;
|
if (isError) return <div>{error.message}</div>;
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ const ExpenseAnalysis = () => {
|
|||||||
|
|
||||||
const labels = report.map((item) => item.projectName);
|
const labels = report.map((item) => item.projectName);
|
||||||
const series = report.map((item) => item.totalApprovedAmount || 0);
|
const series = report.map((item) => item.totalApprovedAmount || 0);
|
||||||
const total = data?.totalAmount || 0;
|
const total = formatCurrency(data?.totalAmount || 0);
|
||||||
|
|
||||||
const donutOptions = {
|
const donutOptions = {
|
||||||
chart: { type: "donut" },
|
chart: { type: "donut" },
|
||||||
@ -57,84 +57,93 @@ const ExpenseAnalysis = () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if (data?.report === 0) {
|
if (data?.report === 0) {
|
||||||
return <div>No data found</div>;
|
return <div>No data found</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card shadow-sm" style={{ minHeight: "500px" }}>
|
<>
|
||||||
<div className="card-header d-flex justify-content-between align-items-center mt-3">
|
<div className="card-header d-flex justify-content-between align-items-center ">
|
||||||
<div>
|
<div>
|
||||||
<h5 className="mb-1 fw-bold">Expense Breakdown</h5>
|
<h5 className="mb-1 fw-bold">Expense Breakdown</h5>
|
||||||
<p className="card-subtitle me-3">Detailed project expenses</p>
|
<p className="card-subtitle me-3">Detailed project expenses</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-end">
|
<div className="text-end">
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<DateRangePicker1 />
|
<DateRangePicker1 />
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card-body position-relative">
|
<div className="card-body position-relative">
|
||||||
{/* Initial loading: show full loader */}
|
{/* Initial loading: show full loader */}
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="d-flex justify-content-center align-items-center" style={{ height: "200px" }}>
|
<div
|
||||||
<span>Loading...</span>
|
className="d-flex justify-content-center align-items-center"
|
||||||
</div>
|
style={{ height: "200px" }}
|
||||||
)}
|
>
|
||||||
|
<span>Loading...</span>
|
||||||
{/* 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 className="mb-2 w-100">
|
{/* Data display */}
|
||||||
<div className="row">
|
{!isLoading && report.length === 0 && (
|
||||||
{report.map((item, idx) => (
|
<div className="text-center text-muted py-5">No data found</div>
|
||||||
<div className="col-6 d-flex justify-content-start align-items-start mb-2" key={idx}>
|
)}
|
||||||
<div className="avatar me-2">
|
|
||||||
<span
|
{!isLoading && report.length > 0 && (
|
||||||
className="avatar-initial rounded-2"
|
<>
|
||||||
style={{
|
{/* Overlay spinner for refetch */}
|
||||||
backgroundColor: donutOptions.colors[idx % donutOptions.colors.length],
|
{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>
|
||||||
<i className="bx bx-receipt fs-4"></i>
|
</div>
|
||||||
</span>
|
)}
|
||||||
</div>
|
|
||||||
<div className="d-flex flex-column gap-1 text-start">
|
<div className="d-flex justify-content-center mb-8">
|
||||||
<small className="fw-bold fs-6">{item.projectName}</small>
|
<Chart
|
||||||
<span className="fw-bold text-muted ms-1">{item.totalApprovedAmount}</span>
|
options={donutOptions}
|
||||||
</div>
|
series={report.map((item) => item.totalApprovedAmount || 0)}
|
||||||
</div>
|
type="donut"
|
||||||
))}
|
width="320"
|
||||||
|
/>
|
||||||
</div>
|
</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 moment from "moment";
|
||||||
import { useExpenseFilter } from "../../hooks/useExpense";
|
import { useExpenseFilter } from "../../hooks/useExpense";
|
||||||
import { ExpenseFilterSkeleton } from "./ExpenseSkeleton";
|
import { ExpenseFilterSkeleton } from "./ExpenseSkeleton";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
|
|
||||||
const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
||||||
|
const { status } = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
const selectedProjectId = useSelector(
|
const selectedProjectId = useSelector(
|
||||||
(store) => store.localVariables.projectId
|
(store) => store.localVariables.projectId
|
||||||
);
|
);
|
||||||
@ -37,9 +39,22 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
|||||||
const [selectedGroup, setSelectedGroup] = useState(groupByList[0]);
|
const [selectedGroup, setSelectedGroup] = useState(groupByList[0]);
|
||||||
const [resetKey, setResetKey] = useState(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({
|
const methods = useForm({
|
||||||
resolver: zodResolver(SearchSchema),
|
resolver: zodResolver(SearchSchema),
|
||||||
defaultValues: defaultFilter,
|
defaultValues: dynamicDefaultFilter,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { control, handleSubmit, reset, setValue, watch } = methods;
|
const { control, handleSubmit, reset, setValue, watch } = methods;
|
||||||
@ -71,14 +86,49 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
|||||||
onApply(defaultFilter);
|
onApply(defaultFilter);
|
||||||
handleGroupBy(groupByList[0].id);
|
handleGroupBy(groupByList[0].id);
|
||||||
closePanel();
|
closePanel();
|
||||||
|
if (status) {
|
||||||
|
navigate("/expenses", { replace: true });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Close popup when navigating to another component
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
closePanel();
|
closePanel();
|
||||||
}, [location]);
|
}, [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 (isLoading || isFetching) return <ExpenseFilterSkeleton />;
|
||||||
if (isError && isFetched)
|
if (isError && isFetched)
|
||||||
return <div>Something went wrong Here- {error.message} </div>;
|
return <div>Something went wrong Here- {error.message} </div>;
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
EXPENSE_REJECTEDBY,
|
EXPENSE_REJECTEDBY,
|
||||||
ITEMS_PER_PAGE,
|
ITEMS_PER_PAGE,
|
||||||
} from "../../utils/constants";
|
} from "../../utils/constants";
|
||||||
import { getColorNameFromHex, useDebounce } from "../../utils/appUtils";
|
import { formatCurrency, getColorNameFromHex, useDebounce } from "../../utils/appUtils";
|
||||||
import { ExpenseTableSkeleton } from "./ExpenseSkeleton";
|
import { ExpenseTableSkeleton } from "./ExpenseSkeleton";
|
||||||
import ConfirmModal from "../common/ConfirmModal";
|
import ConfirmModal from "../common/ConfirmModal";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
@ -140,7 +140,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
|||||||
label: "Amount",
|
label: "Amount",
|
||||||
getValue: (e) => (
|
getValue: (e) => (
|
||||||
<>
|
<>
|
||||||
<i className="bx bx-rupee b-xs"></i> {e?.amount}
|
{formatCurrency(e?.amount)}
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
isAlwaysVisible: true,
|
isAlwaysVisible: true,
|
||||||
|
@ -111,9 +111,6 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
<h5 className="fw-semibold">Expense Details</h5>
|
<h5 className="fw-semibold">Expense Details</h5>
|
||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-start mb-2">
|
|
||||||
<div className="text-muted">{data?.description}</div>
|
|
||||||
</div>
|
|
||||||
{/* Row 1 */}
|
{/* Row 1 */}
|
||||||
<div className="col-md-6 mb-3">
|
<div className="col-md-6 mb-3">
|
||||||
<div className="d-flex">
|
<div className="d-flex">
|
||||||
@ -277,7 +274,7 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
className="form-label me-2 mb-0 fw-semibold"
|
className="form-label me-2 mb-0 fw-semibold"
|
||||||
style={{ minWidth: "130px" }}
|
style={{ minWidth: "130px" }}
|
||||||
>
|
>
|
||||||
Paid By:
|
Paid By :
|
||||||
</label>
|
</label>
|
||||||
<div className="d-flex align-items-center ">
|
<div className="d-flex align-items-center ">
|
||||||
<Avatar
|
<Avatar
|
||||||
@ -294,6 +291,11 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div className="col-12 text-start">
|
<div className="col-12 text-start">
|
||||||
|
@ -14,7 +14,7 @@ import { useProfile } from "../../hooks/useProfile";
|
|||||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
import Avatar from "../../components/common/Avatar";
|
import Avatar from "../../components/common/Avatar";
|
||||||
import { useChangePassword } from "../Context/ChangePasswordContext";
|
import { useChangePassword } from "../Context/ChangePasswordContext";
|
||||||
import { useProjectModal, useProjects } from "../../hooks/useProjects";
|
import { useProjects } from "../../hooks/useProjects";
|
||||||
import { useProjectName } from "../../hooks/useProjects";
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
@ -28,7 +28,6 @@ const Header = () => {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { openModal } = useProjectModal();
|
|
||||||
const { mutate: logout, isPending: logouting } = useLogout();
|
const { mutate: logout, isPending: logouting } = useLogout();
|
||||||
const { onOpen } = useAuthModal();
|
const { onOpen } = useAuthModal();
|
||||||
const { openChangePassword } = useChangePassword();
|
const { openChangePassword } = useChangePassword();
|
||||||
@ -232,16 +231,7 @@ const Header = () => {
|
|||||||
|
|
||||||
<ul className="navbar-nav flex-row align-items-center ms-md-auto">
|
<ul className="navbar-nav flex-row align-items-center ms-md-auto">
|
||||||
{/* {HasManageProjectPermission && ( */}
|
{/* {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">
|
<li className="nav-item navbar-dropdown dropdown-user dropdown">
|
||||||
<a
|
<a
|
||||||
|
@ -54,7 +54,7 @@ const ProjectCard = ({ project }) => {
|
|||||||
style={{ fontSize: "xx-large" }}
|
style={{ fontSize: "xx-large" }}
|
||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
<div className="me-2">
|
<div className="me-2 text-start">
|
||||||
<h5
|
<h5
|
||||||
className="mb-0 stretched-link text-heading text-start"
|
className="mb-0 stretched-link text-heading text-start"
|
||||||
onClick={handleViewProject}
|
onClick={handleViewProject}
|
||||||
|
@ -270,4 +270,13 @@ export const useExpenseAnalysis = (projectId, startDate, endDate) => {
|
|||||||
},
|
},
|
||||||
enabled:shouldFetch
|
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>
|
</button>
|
||||||
</div>
|
</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
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="form-check-input"
|
className="form-check-input"
|
||||||
|
@ -129,6 +129,9 @@ const ExpensePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-6 text-end mt-2 mt-sm-0">
|
<div className="col-6 text-end mt-2 mt-sm-0">
|
||||||
|
<button className="btn btn-sm" onClick={clearFilter}>
|
||||||
|
CLear
|
||||||
|
</button>
|
||||||
{IsCreatedAble && (
|
{IsCreatedAble && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-primary"
|
className="btn btn-sm btn-primary"
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
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 ProjectListView from "../../components/Project/ProjectListView";
|
||||||
import GlobalModel from "../../components/common/GlobalModel";
|
import GlobalModel from "../../components/common/GlobalModel";
|
||||||
import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
|
import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
|
||||||
@ -95,9 +99,18 @@ const ProjectPage = () => {
|
|||||||
}
|
}
|
||||||
}, [data, isLoading, selectedStatuses]);
|
}, [data, isLoading, selectedStatuses]);
|
||||||
|
|
||||||
|
if (isLoading)
|
||||||
if(isLoading) return <div className="page-min-h"><Loader/></div>
|
return (
|
||||||
if(isError) return <div className="page-min-h d-flex justify-content-center align-items-center"><p>{error.message}</p></div>
|
<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 (
|
return (
|
||||||
<ProjectContext.Provider value={contextDispatcher}>
|
<ProjectContext.Provider value={contextDispatcher}>
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
@ -181,7 +194,8 @@ const ProjectPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{/* {HasManageProject && ( <button
|
{/* {HasManageProject && ( */}
|
||||||
|
<button
|
||||||
className="btn btn-sm btn-primary"
|
className="btn btn-sm btn-primary"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@ -192,7 +206,8 @@ const ProjectPage = () => {
|
|||||||
<span className="d-none d-md-inline-block">
|
<span className="d-none d-md-inline-block">
|
||||||
Add New Project
|
Add New Project
|
||||||
</span>
|
</span>
|
||||||
</button>)} */}
|
</button>
|
||||||
|
{/* )} */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -209,7 +224,11 @@ const ProjectPage = () => {
|
|||||||
isLoading={isLoading}
|
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 );
|
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/task", element: <TaskPlannng /> },
|
||||||
{ path: "/activities/reports", element: <Reports /> },
|
{ path: "/activities/reports", element: <Reports /> },
|
||||||
{ path: "/gallary", element: <ComingSoonPage /> },
|
{ path: "/gallary", element: <ComingSoonPage /> },
|
||||||
{ path: "/expenses", element: <ExpensePage /> },
|
{ path: "/expenses/:status?", element: <ExpensePage /> },
|
||||||
{ path: "/masters", element: <MasterPage /> },
|
{ path: "/masters", element: <MasterPage /> },
|
||||||
{ path: "/tenants", element: <TenantPage /> },
|
{ path: "/tenants", element: <TenantPage /> },
|
||||||
{ path: "/tenants/new-tenant", element: <CreateTenant /> },
|
{ path: "/tenants/new-tenant", element: <CreateTenant /> },
|
||||||
|
@ -59,7 +59,7 @@ const attemptTokenRefresh = async (storedRefreshToken) => {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Token refresh failed:", error);
|
removeSession()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -87,6 +87,17 @@ export function localToUtc(dateString) {
|
|||||||
|
|
||||||
if (!day || !month || !year) return null;
|
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();
|
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 =
|
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}$/;
|
/^\/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 moment from "moment";
|
||||||
import { ActiveTenant } from "./constants";
|
import { ActiveTenant } from "./constants";
|
||||||
|
import {format} from 'date-fns'
|
||||||
|
|
||||||
export const getDateDifferenceInDays = (startDate, endDate) => {
|
export const getDateDifferenceInDays = (startDate, endDate) => {
|
||||||
if (!startDate || !endDate) {
|
if (!startDate || !endDate) {
|
||||||
@ -93,6 +94,11 @@ export const getCompletionPercentage = (completedWork, plannedWork)=> {
|
|||||||
return clamped.toFixed(2);
|
return clamped.toFixed(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const formatDate_DayMonth = (dateInput)=>{
|
||||||
|
const date = new Date(dateInput);
|
||||||
|
return format(date, "d MMM");
|
||||||
|
}
|
||||||
|
|
||||||
export const getTenantStatus =(statusId)=>{
|
export const getTenantStatus =(statusId)=>{
|
||||||
return ActiveTenant === statusId ? " bg-label-success":"bg-label-secondary"
|
return ActiveTenant === statusId ? " bg-label-success":"bg-label-secondary"
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user