Creating new weidgets in Dashboard in main.

This commit is contained in:
Kartik Sharma 2025-10-14 17:14:06 +05:30
parent 12b632f087
commit a7f1ba97c3
12 changed files with 714 additions and 98 deletions

View File

@ -14,6 +14,9 @@ import ProjectCompletionChart from "./ProjectCompletionChart";
import ProjectProgressChart from "./ProjectProgressChart";
import ProjectOverview from "../Project/ProjectOverview";
import AttendanceOverview from "./AttendanceChart";
import ExpenseAnalysis from "./ExpenseAnalysis";
import ExpenseStatus from "./ExpenseStatus";
import ExpenseByProject from "./ExpenseByProject";
const Dashboard = () => {
const { projectsCardData } = useDashboardProjectsCardData();
@ -56,11 +59,25 @@ const Dashboard = () => {
<div className="col-xxl-6 col-lg-6">
<ProjectProgressChart />
</div>
<div className="col-12 col-xl-8">
<div className="card h-100">
<ExpenseAnalysis />
</div>
</div>
<div className="col-12 col-xl-4 col-md-6">
<div className="card ">
<ExpenseStatus />
</div>
</div>
{!isAllProjectsSelected && (
<div className="col-xxl-6 col-lg-6">
<AttendanceOverview /> {/* ✅ Removed unnecessary projectId prop */}
<div className="col-12 col-md-6 mb-sm-0 mb-4 ">
<AttendanceOverview />
</div>
)}
<div className="col-12 col-md-6">
<ExpenseByProject />
</div>
</div>
</div>
);

View File

@ -0,0 +1,154 @@
import React, { useEffect, useMemo } from "react";
import Chart from "react-apexcharts";
import { useExpenseAnalysis } from "../../hooks/useDashboard_Data";
import { useSelectedProject } from "../../slices/apiDataManager";
import { DateRangePicker1 } from "../common/DateRangePicker";
import { FormProvider, useForm } from "react-hook-form";
import { formatCurrency, localToUtc } from "../../utils/appUtils";
const ExpenseAnalysis = () => {
const projectId = useSelectedProject();
const methods = useForm({
defaultValues: { startDate: "", endDate: "" },
});
const { watch } = methods;
const [startDate, endDate] = watch(["startDate", "endDate"]);
const { data, isLoading, isError, error, isFetching } = useExpenseAnalysis(
projectId,
startDate ? localToUtc(startDate) : null,
endDate ? localToUtc(endDate) : null
);
if (isError) return <div>{error.message}</div>;
const report = data?.report ?? [];
const { labels, series, total } = useMemo(() => {
const labels = report.map((item) => item.projectName);
const series = report.map((item) => item.totalApprovedAmount || 0);
const total = formatCurrency(data?.totalAmount || 0);
return { labels, series, total };
}, [report, data?.totalAmount]);
const donutOptions = {
chart: { type: "donut" },
labels,
legend: { show: false },
dataLabels: { enabled: true, formatter: (val) => `${val.toFixed(0)}%` },
colors: ["#7367F0", "#28C76F", "#FF9F43", "#EA5455", "#00CFE8", "#FF78B8"],
plotOptions: {
pie: {
donut: {
size: "70%",
labels: {
show: true,
total: {
show: true,
label: "Total",
fontSize: "16px",
formatter: () => `${total}`,
},
},
},
},
},
responsive: [
{
breakpoint: 576, // mobile breakpoint
options: {
chart: { width: "100%" },
},
},
],
};
return (
<>
<div className="card">
<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>
</div>
<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={{ height: "200px" }}
>
<span>Loading...</span>
</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>
))}
</div>
</div>
</>
)}
</div>
</div>
{/* Header */}
</>
);
};
export default ExpenseAnalysis;

View File

@ -0,0 +1,164 @@
import React, { useState, useEffect } from "react";
import Chart from "react-apexcharts";
import { useExpenseType } from "../../hooks/masterHook/useMaster";
import { useSelector } from "react-redux";
import { useExpenseDataByProject } from "../../hooks/useDashboard_Data";
import { formatCurrency } from "../../utils/appUtils";
import { formatDate_DayMonth } from "../../utils/dateUtils";
const ExpenseByProject = () => {
const projectId = useSelector((store) => store.localVariables.projectId);
const [range, setRange] = useState("12M");
const [selectedType, setSelectedType] = useState("");
const [viewMode, setViewMode] = useState("Category");
const [chartData, setChartData] = useState({ categories: [], data: [] });
const { ExpenseTypes, loading: typeLoading } = useExpenseType();
const { data: expenseApiData, isLoading } = useExpenseDataByProject(
projectId,
selectedType,
range === "All" ? null : parseInt(range)
);
useEffect(() => {
if (expenseApiData) {
const categories = expenseApiData.map((item) =>
formatDate_DayMonth(item.monthName, item.year)
);
const data = expenseApiData.map((item) => item.total);
setChartData({ categories, data });
} else {
setChartData({ categories: [], data: [] });
}
}, [expenseApiData]);
const getSelectedTypeName = () => {
if (!selectedType) return "All Types";
const found = ExpenseTypes.find((t) => t.id === selectedType);
return found ? found.name : "All Types";
};
const options = {
chart: { type: "bar", toolbar: { show: false } },
plotOptions: {
bar: { horizontal: false, columnWidth: "55%", borderRadius: 4 },
},
dataLabels: { enabled: true, formatter: (val) => formatCurrency(val) },
xaxis: {
categories: chartData.categories,
labels: { style: { fontSize: "12px" }, rotate: -45 },
},
tooltip: {
y: {
formatter: (val) => `${formatCurrency(val)} (${getSelectedTypeName()})`,
},
},
annotations: { xaxis: [{ x: 0, strokeDashArray: 0 }] },
fill: { opacity: 1 },
colors: ["#2196f3"],
};
const series = [
{
name: getSelectedTypeName(),
data: chartData.data,
},
];
return (
<div className="card shadow-sm rounded ">
{/* Header */}
<div className="card-header">
<div className="d-flex justify-content-start align-items-center mb-3 mt-3">
<div>
<h5 className="mb-1 me-6 card-title">Monthly Expense -</h5>
<p className="card-subtitle me-5 mb-0">Detailed project expenses</p>
</div>
<div className="btn-group mb-4 ms-n8">
<button
className="btn btn-sm dropdown-toggle fs-5"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{viewMode}
</button>
<ul className="dropdown-menu dropdown-menu-end ">
<li>
<button
className="dropdown-item"
onClick={() => {
setViewMode("Category");
setSelectedType("");
}}
>
Category
</button>
</li>
<li>
<button
className="dropdown-item"
onClick={() => {
setViewMode("Project");
setSelectedType("");
}}
>
Project
</button>
</li>
</ul>
</div>
</div>
{/* Range Buttons + Expense Dropdown */}
<div className="d-flex align-items-center flex-wrap ">
{["1M", "3M", "6M", "12M", "All"].map((item) => (
<button
key={item}
className={`border-0 px-2 py-1 text-sm rounded ${range === item
? "text-white bg-primary"
: "text-body bg-transparent"
}`}
style={{ cursor: "pointer", transition: "all 0.2s ease" }}
onClick={() => setRange(item)}
>
{item}
</button>
))}
{viewMode === "Category" && (
<select
className="form-select form-select-sm ms-auto mb-3 mt-1 mt-sm-0"
value={selectedType}
onChange={(e) => setSelectedType(e.target.value)}
disabled={typeLoading}
style={{ maxWidth: "200px" }}
>
<option value="">All Types</option>
{ExpenseTypes.map((type) => (
<option key={type.id} value={type.id}>
{type.name}
</option>
))}
</select>
)}
</div>
</div>
{/* Chart */}
<div className="card-body bg-white text-dark p-3 rounded" style={{ minHeight: "210px" }}>
{isLoading ? (
<p>Loading chart...</p>
) : !expenseApiData || expenseApiData.length === 0 ? (
<div className="text-center text-muted py-5">No data found</div>
) : (
<Chart options={options} series={series} type="bar" height={235} />
)}
</div>
</div>
);
};
export default ExpenseByProject;

View File

@ -0,0 +1,157 @@
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 { countDigit, formatCurrency } from "../../utils/appUtils";
import { EXPENSE_MANAGE, EXPENSE_STATUS } from "../../utils/constants";
import { useNavigate } from "react-router-dom";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
const ExpenseStatus = () => {
const [projectName, setProjectName] = useState("All Project");
const selectedProject = useSelectedProject();
const { projectNames, loading } = useProjectName();
const { data, isPending, error } = useExpenseStatus(selectedProject);
const navigate = useNavigate();
const isManageExpense = useHasUserPermission(EXPENSE_MANAGE)
useEffect(() => {
if (selectedProject && projectNames?.length) {
const project = projectNames.find((p) => p.id === selectedProject);
setProjectName(project?.name || "All Project");
} else {
setProjectName("All Project");
}
}, [projectNames, selectedProject]);
const handleNavigate = (status) => {
if (selectedProject) {
navigate(`/expenses/${status}/${selectedProject}`);
} else {
navigate(`/expenses/${status}`);
}
};
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="report-list text-start">
{[
{
title: "Pending Payment",
count: data?.processPending?.count || 0,
amount: data?.processPending?.totalAmount || 0,
icon: "bx bx-rupee",
iconColor: "text-primary",
status: EXPENSE_STATUS.payment_pending,
},
{
title: "Pending Approve",
count: data?.approvePending?.count || 0,
amount: data?.approvePending?.totalAmount || 0,
icon: "fa-solid fa-check",
iconColor: "text-warning",
status: EXPENSE_STATUS.approve_pending,
},
{
title: "Pending Review",
count: data?.reviewPending?.count || 0,
amount: data?.reviewPending?.totalAmount || 0,
icon: "bx bx-search-alt-2",
iconColor: "text-secondary",
status: EXPENSE_STATUS.review_pending,
},
{
title: "Draft",
count: data?.draft?.count || 0,
amount: data?.draft?.totalAmount || 0,
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-1 cursor-pointer"
onClick={() => handleNavigate(item?.status)}
>
<div className="d-flex align-items-center">
<div className="report-list-icon shadow-xs me-2">
<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>
{item?.amount ? (
<small className="mb-0 text-primary">
{formatCurrency(item?.amount)}
</small>
) : (
<small className="mb-0 text-primary">{formatCurrency(0)}</small>
)}
</div>
<div>
<small
className={`text-royalblue ${countDigit(item?.count || 0) >= 3 ? "text-xl" : "text-2xl"
} text-gray-500`}
>
{item?.count || 0}
</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 className=" py-0 text-start mb-2">
{isManageExpense && (
<div
className="d-flex justify-content-between align-items-center cursor-pointer"
onClick={() => handleNavigate(EXPENSE_STATUS.process_pending)}
>
<div className="d-block">
<span
className={`fs-semibold d-block ${countDigit(data?.totalAmount || 0) > 3 ? "text-base" : "text-lg"
}`}
>
Project Spendings:
</span>{" "}
<small className="d-block text-xxs text-gary-80">
(All Processed Payments)
</small>
</div>
<div className="d-flex align-items-center gap-2">
<span
className={`text-end text-royalblue ${countDigit(data?.totalAmount || 0) > 3 ? "text-" : "text-3xl"
} text-md`}
>
{formatCurrency(data?.totalAmount || 0)}
</span>
<small className="text-muted fs-semibold text-royalblue text-md">
<i className="bx bx-chevron-right"></i>
</small>
</div>
</div>
)}
</div>
</div>
</>
);
};
export default ExpenseStatus;

View File

@ -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
);
@ -71,8 +73,14 @@ 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(() => {
@ -82,6 +90,7 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
if (isLoading || isFetching) return <ExpenseFilterSkeleton />;
if (isError && isFetched)
return <div>Something went wrong Here- {error.message} </div>;
return (
<>
<FormProvider {...methods}>
@ -92,8 +101,7 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
<div className="d-inline-flex border rounded-pill mb-1 overflow-hidden shadow-none">
<button
type="button"
className={`btn px-2 py-1 rounded-0 text-tiny ${
isTransactionDate ? "active btn-primary text-white" : ""
className={`btn px-2 py-1 rounded-0 text-tiny ${isTransactionDate ? "active btn-primary text-white" : ""
}`}
onClick={() => setValue("isTransactionDate", true)}
>
@ -101,8 +109,7 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
</button>
<button
type="button"
className={`btn px-2 py-1 rounded-0 text-tiny ${
!isTransactionDate ? "active btn-primary text-white" : ""
className={`btn px-2 py-1 rounded-0 text-tiny ${!isTransactionDate ? "active btn-primary text-white" : ""
}`}
onClick={() => setValue("isTransactionDate", false)}
>

View File

@ -15,6 +15,7 @@ import { ExpenseTableSkeleton } from "./ExpenseSkeleton";
import ConfirmModal from "../common/ConfirmModal";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
const [deletingId, setDeletingId] = useState(null);
@ -24,6 +25,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE);
const [currentPage, setCurrentPage] = useState(1);
const debouncedSearch = useDebounce(searchText, 500);
const navigate = useNavigate();
const { mutate: DeleteExpense, isPending } = useDeleteExpense();
const { data, isLoading, isError, isInitialLoading, error } = useExpenseList(
@ -67,8 +69,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
key = item.status?.displayName || "Unknown";
break;
case "submittedBy":
key = `${item.createdBy?.firstName ?? ""} ${
item.createdBy?.lastName ?? ""
key = `${item.createdBy?.firstName ?? ""} ${item.createdBy?.lastName ?? ""
}`.trim();
break;
case "project":
@ -110,11 +111,12 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
label: "Submitted By",
align: "text-start",
getValue: (e) =>
`${e.createdBy?.firstName ?? ""} ${
e.createdBy?.lastName ?? ""
`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? ""
}`.trim() || "N/A",
customRender: (e) => (
<div className="d-flex align-items-center">
<div className="d-flex align-items-center cursor-pointer"
onClick={() => navigate(`/employee/${e.createdBy?.id}`)}>
<Avatar
size="xs"
classAvatar="m-0"
@ -122,8 +124,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
lastName={e.createdBy?.lastName}
/>
<span className="text-truncate">
{`${e.createdBy?.firstName ?? ""} ${
e.createdBy?.lastName ?? ""
{`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? ""
}`.trim() || "N/A"}
</span>
</div>
@ -152,8 +153,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
align: "text-center",
getValue: (e) => (
<span
className={`badge bg-label-${
getColorNameFromHex(e?.status?.color) || "secondary"
className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary"
}`}
>
{e.status?.name || "Unknown"}

View File

@ -200,10 +200,10 @@ export const useAttendanceOverviewData = (projectId, days) => {
// })
// }
export const useDashboard_AttendanceData = (date,projectId)=>{
export const useDashboard_AttendanceData = (date, projectId) => {
return useQuery({
queryKey:["dashboardAttendances",date,projectId],
queryFn:async()=> {
queryKey: ["dashboardAttendances", date, projectId],
queryFn: async () => {
const resp = await await GlobalRepository.getDashboardAttendanceData(date, projectId)
return resp.data;
@ -211,10 +211,10 @@ export const useDashboard_AttendanceData = (date,projectId)=>{
})
}
export const useDashboardTeamsCardData =(projectId)=>{
export const useDashboardTeamsCardData = (projectId) => {
return useQuery({
queryKey:["dashboardTeams",projectId],
queryFn:async()=> {
queryKey: ["dashboardTeams", projectId],
queryFn: async () => {
const resp = await GlobalRepository.getDashboardTeamsCardData(projectId)
return resp.data;
@ -224,8 +224,8 @@ export const useDashboardTeamsCardData =(projectId)=>{
export const useDashboardTasksCardData = (projectId) => {
return useQuery({
queryKey:["dashboardTasks",projectId],
queryFn:async()=> {
queryKey: ["dashboardTasks", projectId],
queryFn: async () => {
const resp = await GlobalRepository.getDashboardTasksCardData(projectId)
return resp.data;
@ -245,11 +245,52 @@ export const useDashboardTasksCardData = (projectId) => {
export const useDashboardProjectsCardData = () => {
return useQuery({
queryKey:["dashboardProjects"],
queryFn:async()=> {
queryKey: ["dashboardProjects"],
queryFn: async () => {
const resp = await GlobalRepository.getDashboardProjectsCardData();
return resp.data;
}
})
}
export const useExpenseAnalysis = (projectId, startDate, endDate) => {
const hasBothDates = !!startDate && !!endDate;
const noDatesSelected = !startDate && !endDate;
const shouldFetch =
noDatesSelected ||
hasBothDates;
return useQuery({
queryKey: ["expenseAnalysis", projectId, startDate, endDate],
queryFn: async () => {
const resp = await GlobalRepository.getExpenseData(projectId, startDate, endDate);
return resp.data;
},
enabled: shouldFetch,
refetchOnWindowFocus: true, // refetch when you come back
refetchOnMount: "always", // always refetch on remount
staleTime: 0,
});
};
export const useExpenseStatus = (projectId) => {
return useQuery({
queryKey: ["expense_stauts", projectId],
queryFn: async () => {
const resp = await GlobalRepository.getExpenseStatus(projectId);
return resp.data;
}
})
}
export const useExpenseDataByProject = (projectId, categoryId, months) => {
return useQuery({
queryKey: ["expenseByProject", projectId, categoryId, months],
queryFn: async () => {
const resp = await GlobalRepository.getExpenseDataByProject(projectId, categoryId, months);
return resp.data;
},
});
};

View File

@ -3,12 +3,12 @@ import { api } from "../utils/axiosClient";
const GlobalRepository = {
getDashboardProgressionData: ({ days = '', FromDate = '', projectId = '' }) => {
let params;
if(projectId == null){
if (projectId == null) {
params = new URLSearchParams({
days: days.toString(),
FromDate,
});
}else{
} else {
params = new URLSearchParams({
days: days.toString(),
FromDate,
@ -19,10 +19,10 @@ const GlobalRepository = {
return api.get(`/api/Dashboard/Progression?${params.toString()}`);
},
getDashboardAttendanceData: ( date,projectId ) => {
getDashboardAttendanceData: (date, projectId) => {
return api.get(`/api/Dashboard/project-attendance/${projectId}?date=${date}`);
},
},
getDashboardProjectsCardData: () => {
return api.get(`/api/Dashboard/projects`);
},
@ -32,17 +32,59 @@ const GlobalRepository = {
? `/api/Dashboard/teams?projectId=${projectId}`
: `/api/Dashboard/teams`;
return api.get(url);
},
},
getDashboardTasksCardData: (projectId) => {
const url = projectId
? `/api/Dashboard/tasks?projectId=${projectId}`
: `/api/Dashboard/tasks`;
return api.get(url);
},
},
getExpenseData: (projectId, startDate, endDate) => {
let url = `api/Dashboard/expense/type`
const queryParams = [];
if (projectId) {
queryParams.push(`projectId=${projectId}`);
}
if (startDate) {
queryParams.push(`startDate=${startDate}`);
}
if (endDate) {
queryParams.push(`endDate=${endDate}`);
}
if (queryParams.length > 0) {
url += `?${queryParams.join("&")}`;
}
return api.get(url);
},
getExpenseStatus: (projectId) => api.get(`/api/Dashboard/expense/pendings${projectId ? `?projectId=${projectId}` : ""}`),
getExpenseDataByProject: (projectId, categoryId, months) => {
let url = `api/Dashboard/expense/monthly`
const queryParams = [];
if (projectId) {
queryParams.push(`projectId=${projectId}`);
}
if (categoryId) {
queryParams.push(`categoryId=${categoryId}`);
}
if (months) {
queryParams.push(`months=${months}`);
}
if (queryParams.length > 0) {
url += `?${queryParams.join("&")}`;
}
return api.get(url);
},
getAttendanceOverview: (projectId, days) => api.get(`/api/dashboard/attendance-overview/${projectId}?days=${days}`)
getAttendanceOverview:(projectId,days)=>api.get(`/api/dashboard/attendance-overview/${projectId}?days=${days}`)
};
export default GlobalRepository;

View File

@ -94,7 +94,7 @@ const router = createBrowserRouter(
{ path: "/activities/task", element: <TaskPlannng /> },
{ path: "/activities/reports", element: <Reports /> },
{ path: "/gallary", element: <ImageGalleryPage /> },
{ path: "/expenses", element: <ExpensePage /> },
{ path: "/expenses/:status?/:project?", element: <ExpensePage /> },
{ path: "/masters", element: <MasterPage /> },
{ path: "/tenants", element: <TenantPage /> },
{ path: "/tenants/new-tenant", element: <CreateTenant /> },

View File

@ -69,30 +69,6 @@ export const normalizeAllowedContentTypes = (allowedContentType) => {
return allowedContentType.split(",");
return [];
};
export function localToUtc(dateString) {
if (!dateString || typeof dateString !== "string") return null;
const parts = dateString.trim().split("-");
if (parts.length !== 3) return null;
let day, month, year;
if (parts[0].length === 4) {
// Format: yyyy-mm-dd
[year, month, day] = parts;
} else {
// Format: dd-mm-yyyy
[day, month, year] = parts;
}
if (!day || !month || !year) return null;
const date = new Date(
Date.UTC(Number(year), Number(month) - 1, Number(day), 0, 0, 0)
);
return isNaN(date.getTime()) ? null : date.toISOString();
}
/**
* Flexible number formatter for currency, numbers, or percentages.
@ -135,3 +111,42 @@ export const formatFigure = (
return new Intl.NumberFormat(locale, formatterOptions).format(amount);
};
export function localToUtc(dateString) {
if (!dateString || typeof dateString !== "string") return null;
const parts = dateString.trim().split("-");
if (parts.length !== 3) return null;
let day, month, year;
if (parts[0].length === 4) {
// Format: yyyy-mm-dd
[year, month, day] = parts;
} else {
// Format: dd-mm-yyyy
[day, month, year] = parts;
}
if (!day || !month || !year) return null;
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",
notation: "compact",
compactDisplay: "short",
currency: currency,
minimumFractionDigits: 0,
maximumFractionDigits: 2,
}).format(amount);
};
export const countDigit = (num) => {
return Math.abs(num).toString().length;
};

View File

@ -145,5 +145,14 @@ export const PROJECT_STATUS = [
label: "Completed",
},
];
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",
process_pending:"61578360-3a49-4c34-8604-7b35a3787b95"
}
export const DEFAULT_EMPTY_STATUS_ID = "00000000-0000-0000-0000-000000000000";

View File

@ -51,7 +51,7 @@ export const convertShortTime = (dateString) => {
};
export const timeElapsed = (checkInTime, timeElapsedInHours) => {
const checkInDate = new Date( checkInTime.split( "T" )[ 0 ] );
const checkInDate = new Date(checkInTime.split("T")[0]);
const currentTime = new Date();
@ -84,7 +84,7 @@ export const formatUTCToLocalTime = (datetime, timeRequired = false) => {
: moment.utc(datetime).local().format("DD MMM YYYY");
};
export const getCompletionPercentage = (completedWork, plannedWork)=> {
export const getCompletionPercentage = (completedWork, plannedWork) => {
if (!plannedWork || plannedWork === 0) return 0;
const percentage = (completedWork / plannedWork) * 100;
@ -93,6 +93,16 @@ export const getCompletionPercentage = (completedWork, plannedWork)=> {
return clamped.toFixed(2);
}
export const getTenantStatus =(statusId)=>{
return ActiveTenant === statusId ? " bg-label-success":"bg-label-secondary"
export const formatDate_DayMonth = (monthName, year) => {
if (!monthName || !year) return "";
try {
const shortMonth = monthName.substring(0, 3);
return `${shortMonth} ${year}`;
} catch {
return "";
}
};
export const getTenantStatus = (statusId) => {
return ActiveTenant === statusId ? " bg-label-success" : "bg-label-secondary"
}