From eab23389ed4dbdf87a7abcb96b98591aaf5f9c8c Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Mon, 13 Oct 2025 12:51:42 +0530 Subject: [PATCH 01/41] Correction in Projects Completion Status in this weidget data cannot be shown. --- src/components/Charts/HorizontalBarChart.jsx | 2 +- src/components/Dashboard/ProjectCompletionChart.jsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/Charts/HorizontalBarChart.jsx b/src/components/Charts/HorizontalBarChart.jsx index 82608da6..5a02c98f 100644 --- a/src/components/Charts/HorizontalBarChart.jsx +++ b/src/components/Charts/HorizontalBarChart.jsx @@ -23,7 +23,7 @@ const HorizontalBarChart = ({ if (loading) { return (
- Loading chart... + Loading chart... {/* Replace this with a skeleton or spinner if you prefer */}
); diff --git a/src/components/Dashboard/ProjectCompletionChart.jsx b/src/components/Dashboard/ProjectCompletionChart.jsx index 8ce4b13a..decf7918 100644 --- a/src/components/Dashboard/ProjectCompletionChart.jsx +++ b/src/components/Dashboard/ProjectCompletionChart.jsx @@ -3,7 +3,8 @@ import HorizontalBarChart from "../Charts/HorizontalBarChart"; import { useProjects } from "../../hooks/useProjects"; const ProjectCompletionChart = () => { - const { projects, loading } = useProjects(); + const { data: projects = [], isLoading: loading, isError, error } = useProjects(); + // Bar chart logic const projectNames = projects?.map((p) => p.name) || []; @@ -11,7 +12,7 @@ const ProjectCompletionChart = () => { projects?.map((p) => { const completed = p.completedWork || 0; const planned = p.plannedWork || 1; - const percent = (completed / planned) * 100; + const percent = planned ? (completed / planned) * 100 : 0; return Math.min(Math.round(percent), 100); }) || []; -- 2.43.0 From 6649cab6a2ccfc7d4eb9e164351f5e7af461be26 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Mon, 13 Oct 2025 14:16:11 +0530 Subject: [PATCH 02/41] Added cursor-not-allowed when user can delete the organization. --- public/assets/css/core-extend.css | 4 ++++ src/components/Organization/OrganizationsList.jsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/public/assets/css/core-extend.css b/public/assets/css/core-extend.css index 2e9e51ef..8ec6df19 100644 --- a/public/assets/css/core-extend.css +++ b/public/assets/css/core-extend.css @@ -280,3 +280,7 @@ .w-8-xl{ width: 2rem; } .w-10-xl{ width: 2.5rem; } } + +.cursor-not-allowed{ + cursor: not-allowed; +} diff --git a/src/components/Organization/OrganizationsList.jsx b/src/components/Organization/OrganizationsList.jsx index dc2e977e..41da2edf 100644 --- a/src/components/Organization/OrganizationsList.jsx +++ b/src/components/Organization/OrganizationsList.jsx @@ -131,7 +131,7 @@ const OrganizationsList = ({searchText}) => {
onOpen({startStep:5,orgData:org.id,flowType:"view"})}> onOpen({startStep:4,orgData:org,flowType:"edit"})}> - +
-- 2.43.0 From d75296ffe80ff6c3f51f19d25b4f2402aec0d814 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Mon, 13 Oct 2025 14:51:47 +0530 Subject: [PATCH 03/41] Filter Sidebar Should Auto-Close When Navigating to Another Page --- .../Documents/DocumentFilterPanel.jsx | 23 +++++++++++++------ src/pages/Directory/ContactFilterPanel.jsx | 19 ++++++++++++--- src/pages/Directory/NoteFilterPanel.jsx | 11 +++++++-- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/components/Documents/DocumentFilterPanel.jsx b/src/components/Documents/DocumentFilterPanel.jsx index 15a2cbf1..581277e8 100644 --- a/src/components/Documents/DocumentFilterPanel.jsx +++ b/src/components/Documents/DocumentFilterPanel.jsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { useDocumentFilterEntities } from "../../hooks/useDocument"; import { FormProvider, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -9,9 +9,11 @@ import { import { DateRangePicker1 } from "../common/DateRangePicker"; import SelectMultiple from "../common/SelectMultiple"; import moment from "moment"; +import { useLocation } from "react-router-dom"; const DocumentFilterPanel = ({ entityTypeId, onApply }) => { const [resetKey, setResetKey] = useState(0); + const location = useLocation(); const { data, isError, isLoading, error } = useDocumentFilterEntities(entityTypeId); @@ -52,6 +54,13 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => { closePanel(); }; + // Close popup when navigating to another component + useEffect(() => { + return () => { + closePanel(); + }; + }, []); + if (isLoading) return
Loading...
; if (isError) return
Error: {error?.message || "Something went wrong!"}
; @@ -63,6 +72,8 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => { documentTag = [], } = data?.data || {}; + + return (
@@ -73,18 +84,16 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => {
{data?.isActive ? "Active" : "In-Active"}{" "} @@ -105,9 +104,101 @@ const VieworgDataanization = ({ orgId }) => {
{data?.address}
-
- {" "} - Projects And Services +
+ + + {/* remove "show" from className */} +
+ {data?.projects && data.projects.length > 0 ? ( + data.projects + .reduce((acc, curr) => { + const projectId = curr.project.id; + if (!acc.find((p) => p.id === projectId)) { + acc.push(curr.project); + } + return acc; + }, []) + .map((project) => ( +
+ + +
+ {data.projects + .filter((p) => p.project.id === project.id) + .map((p) => ( +
+ + {p.service.name} +
+ ))} +
+
+ )) + ) : ( +
No projects available
+ )} +
+
+ + {/* Services Section */} +
+ + + {/* collapse is closed initially */} +
+ {data?.services && data.services.length > 0 ? ( +
+ {data.services.map((service) => ( +
+
+
+
+ + {service.name} +
+

+ {service.description || "No description available."} +

+
+
+
+ ))} +
+ ) : ( +
No services available
+ )} +
); -- 2.43.0 From 80a974e3be8fc4e34ca2984c8623c436bc4203e1 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Mon, 13 Oct 2025 11:17:09 +0530 Subject: [PATCH 05/41] =?UTF-8?q?=E2=80=9CNA=E2=80=9D=20Should=20Be=20Disp?= =?UTF-8?q?layed=20When=20Employee=20Has=20No=20Email=20Instead=20of=20?= =?UTF-8?q?=E2=80=9C=E2=80=93=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Documents/Documents.jsx | 2 +- src/components/Employee/EmpAttendance.jsx | 1 - src/pages/employee/EmployeeList.jsx | 50 +++++++++++------------ 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/components/Documents/Documents.jsx b/src/components/Documents/Documents.jsx index 8210bf58..f3649664 100644 --- a/src/components/Documents/Documents.jsx +++ b/src/components/Documents/Documents.jsx @@ -149,7 +149,7 @@ const Documents = ({ Document_Entity, Entity }) => { -
+
{(isSelf || canUploadDocument) && (
- + {item.email ? ( {item.email} ) : ( - - - - + NA )} + @@ -567,9 +562,14 @@ const EmployeeList = () => { - - {moment(item.joiningDate)?.format("DD-MMM-YYYY")} + + {item.joiningDate ? ( + moment(item.joiningDate).format("DD-MMM-YYYY") + ) : ( + NA + )} + {showInactive ? ( Date: Mon, 13 Oct 2025 10:45:58 +0530 Subject: [PATCH 06/41] Incorrect Toggle Switch Text for Active/Inactive Employee in Project Teams --- src/components/Activities/Attendance.jsx | 2 +- src/components/Activities/AttendcesLogs.jsx | 2 +- src/components/Documents/Documents.jsx | 2 +- src/components/Project/Team/Teams.jsx | 2 +- src/pages/Directory/DirectoryPage.jsx | 153 +++++++++----------- src/pages/employee/EmployeeList.jsx | 2 +- 6 files changed, 70 insertions(+), 93 deletions(-) diff --git a/src/components/Activities/Attendance.jsx b/src/components/Activities/Attendance.jsx index 9a082976..c46feac3 100644 --- a/src/components/Activities/Attendance.jsx +++ b/src/components/Activities/Attendance.jsx @@ -126,7 +126,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat checked={ShowPending} onChange={(e) => setShowPending(e.target.checked)} /> - +
{attLoading ? ( diff --git a/src/components/Activities/AttendcesLogs.jsx b/src/components/Activities/AttendcesLogs.jsx index 8f8a55ac..0a917968 100644 --- a/src/components/Activities/AttendcesLogs.jsx +++ b/src/components/Activities/AttendcesLogs.jsx @@ -190,7 +190,7 @@ useEffect(() => { checked={showPending} onChange={(e) => setShowPending(e.target.checked)} /> - + diff --git a/src/components/Documents/Documents.jsx b/src/components/Documents/Documents.jsx index f3649664..d724c98a 100644 --- a/src/components/Documents/Documents.jsx +++ b/src/components/Documents/Documents.jsx @@ -144,7 +144,7 @@ const Documents = ({ Document_Entity, Entity }) => { - {isActive ? "Active" : "In-Active"} + {isActive ? "Active Document" : "In-Active Document"} diff --git a/src/components/Project/Team/Teams.jsx b/src/components/Project/Team/Teams.jsx index f6bdc526..31d27efa 100644 --- a/src/components/Project/Team/Teams.jsx +++ b/src/components/Project/Team/Teams.jsx @@ -201,7 +201,7 @@ const Teams = () => { className="form-check-label ms-2" htmlFor="activeEmployeeSwitch" > - {activeEmployee ? "Active Employees" : "Include Inactive Employees"} + {activeEmployee ? "Active Employees" : "In-active Employees"} diff --git a/src/pages/Directory/DirectoryPage.jsx b/src/pages/Directory/DirectoryPage.jsx index a2a3e1a5..e0871446 100644 --- a/src/pages/Directory/DirectoryPage.jsx +++ b/src/pages/Directory/DirectoryPage.jsx @@ -139,9 +139,8 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) { + - - diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index 9b1d1afa..2018afb2 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -306,7 +306,7 @@ const EmployeeList = () => { className="form-check-label ms-0" htmlFor="inactiveEmployeesCheckbox" > - Show Inactive Employees + In-active Employees -- 2.43.0 From 95921084721c5f0e19923c60349425e522e3a02d Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Mon, 13 Oct 2025 16:35:28 +0530 Subject: [PATCH 07/41] Removing extra margin-top on Project-details. --- src/pages/project/ProjectDetails.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/project/ProjectDetails.jsx b/src/pages/project/ProjectDetails.jsx index 8d2f8277..d6fbd55d 100644 --- a/src/pages/project/ProjectDetails.jsx +++ b/src/pages/project/ProjectDetails.jsx @@ -77,7 +77,7 @@ const ProjectDetails = () => { -
+
-- 2.43.0 From b80af5467cc4150849b06e73108c3f099304a8e3 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Tue, 14 Oct 2025 15:07:07 +0530 Subject: [PATCH 08/41] Changes in Infrastructure. --- src/components/Project/Infrastructure/WorkArea.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Project/Infrastructure/WorkArea.jsx b/src/components/Project/Infrastructure/WorkArea.jsx index 59f281be..c5925321 100644 --- a/src/components/Project/Infrastructure/WorkArea.jsx +++ b/src/components/Project/Infrastructure/WorkArea.jsx @@ -104,7 +104,7 @@ const WorkArea = ({ workArea, floor, forBuilding }) => {
-
+
Date: Tue, 14 Oct 2025 17:14:06 +0530 Subject: [PATCH 09/41] Creating new weidgets in Dashboard in main. --- src/components/Dashboard/Dashboard.jsx | 21 ++- src/components/Dashboard/ExpenseAnalysis.jsx | 154 ++++++++++++++++ src/components/Dashboard/ExpenseByProject.jsx | 164 ++++++++++++++++++ src/components/Dashboard/ExpenseStatus.jsx | 157 +++++++++++++++++ .../Expenses/ExpenseFilterPanel.jsx | 21 ++- src/components/Expenses/ExpenseList.jsx | 26 +-- src/hooks/useDashboard_Data.jsx | 97 ++++++++--- src/repositories/GlobalRepository.jsx | 76 ++++++-- src/router/AppRoutes.jsx | 2 +- src/utils/appUtils.js | 63 ++++--- src/utils/constants.jsx | 9 + src/utils/dateUtils.jsx | 22 ++- 12 files changed, 714 insertions(+), 98 deletions(-) create mode 100644 src/components/Dashboard/ExpenseAnalysis.jsx create mode 100644 src/components/Dashboard/ExpenseByProject.jsx create mode 100644 src/components/Dashboard/ExpenseStatus.jsx diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index 311fa763..cc22006e 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -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 = () => {
+
+
+ +
+
+ +
+
+ +
+
{!isAllProjectsSelected && ( -
- {/* ✅ Removed unnecessary projectId prop */} +
+
)} +
+ +
); diff --git a/src/components/Dashboard/ExpenseAnalysis.jsx b/src/components/Dashboard/ExpenseAnalysis.jsx new file mode 100644 index 00000000..78126d21 --- /dev/null +++ b/src/components/Dashboard/ExpenseAnalysis.jsx @@ -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
{error.message}
; + + 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 ( + <> +
+
+
+
Expense Breakdown
+

Category Wise Expense Breakdown

+
+ +
+ + + +
+
+ + {/* Card body */} +
+ {isLoading && ( +
+ Loading... +
+ )} + + {!isLoading && report.length === 0 && ( +
No data found
+ )} + + {!isLoading && report.length > 0 && ( + <> + {isFetching && ( +
+ Loading... +
+ )} + +
+ +
+ +
+
+ {report.map((item, idx) => ( +
+
+ + + +
+
+ {item.projectName} + + {formatCurrency(item.totalApprovedAmount)} + +
+
+ ))} +
+
+ + )} +
+
+ {/* Header */} + + + ); +}; + +export default ExpenseAnalysis; diff --git a/src/components/Dashboard/ExpenseByProject.jsx b/src/components/Dashboard/ExpenseByProject.jsx new file mode 100644 index 00000000..819824ca --- /dev/null +++ b/src/components/Dashboard/ExpenseByProject.jsx @@ -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 ( +
+ {/* Header */} +
+
+
+
Monthly Expense -
+

Detailed project expenses

+
+
+ +
    +
  • + +
  • +
  • + +
  • +
+
+
+ + {/* Range Buttons + Expense Dropdown */} +
+ {["1M", "3M", "6M", "12M", "All"].map((item) => ( + + ))} + {viewMode === "Category" && ( + + )} +
+
+ + {/* Chart */} +
+ {isLoading ? ( +

Loading chart...

+ ) : !expenseApiData || expenseApiData.length === 0 ? ( +
No data found
+ ) : ( + + )} +
+ +
+ ); +}; + +export default ExpenseByProject; diff --git a/src/components/Dashboard/ExpenseStatus.jsx b/src/components/Dashboard/ExpenseStatus.jsx new file mode 100644 index 00000000..d6fefe7d --- /dev/null +++ b/src/components/Dashboard/ExpenseStatus.jsx @@ -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 ( + <> +
+
+
Expense - By Status
+

{projectName}

+
+
+ +
+ +
+ {[ + { + 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) => ( +
handleNavigate(item?.status)} + > +
+
+ + + +
+
+
+ {item?.title} + {item?.amount ? ( + + {formatCurrency(item?.amount)} + + ) : ( + {formatCurrency(0)} + )} +
+
+ = 3 ? "text-xl" : "text-2xl" + } text-gray-500`} + > + {item?.count || 0} + + + + +
+
+
+
+ ))} +
+ +
+ {isManageExpense && ( +
handleNavigate(EXPENSE_STATUS.process_pending)} + > +
+ 3 ? "text-base" : "text-lg" + }`} + > + Project Spendings: + {" "} + + (All Processed Payments) + +
+
+ 3 ? "text-" : "text-3xl" + } text-md`} + > + {formatCurrency(data?.totalAmount || 0)} + + + + +
+
+ )} +
+
+ + ); +}; + +export default ExpenseStatus; diff --git a/src/components/Expenses/ExpenseFilterPanel.jsx b/src/components/Expenses/ExpenseFilterPanel.jsx index c04a0981..fffd746c 100644 --- a/src/components/Expenses/ExpenseFilterPanel.jsx +++ b/src/components/Expenses/ExpenseFilterPanel.jsx @@ -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 ; if (isError && isFetched) return
Something went wrong Here- {error.message}
; + return ( <> @@ -92,18 +101,16 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
diff --git a/src/pages/project/ProjectPage.jsx b/src/pages/project/ProjectPage.jsx index 4fdccc30..a85d6f58 100644 --- a/src/pages/project/ProjectPage.jsx +++ b/src/pages/project/ProjectPage.jsx @@ -96,8 +96,8 @@ const ProjectPage = () => { }, [data, isLoading, selectedStatuses]); - if(isLoading) return
- if(isError) return

{error.message}

+ if (isLoading) return
+ if (isError) return

{error.message}

return (
@@ -128,9 +128,8 @@ const ProjectPage = () => {
-
- {HasManageProject && ( )} -
+ {HasManageProject && ( +
+ +
+ )}
-- 2.43.0 From 0b025319095675d9173bc1175a390ec7c1f80d73 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 15 Oct 2025 11:33:09 +0530 Subject: [PATCH 12/41] Adding Table-respnsive in projectNav. --- src/components/Project/ProjectNav.jsx | 47 ++++++++++++++------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/components/Project/ProjectNav.jsx b/src/components/Project/ProjectNav.jsx index d8b2e8ed..c964efd3 100644 --- a/src/components/Project/ProjectNav.jsx +++ b/src/components/Project/ProjectNav.jsx @@ -40,31 +40,32 @@ const ProjectNav = ({ onPillClick, activePill }) => { label: "Directory", hidden: !(DirAdmin || DireManager || DirUser), }, - { key: "documents", icon: "bx bx-folder-open", label: "Documents",hidden:!(isViewDocuments || isModifyDocument || isUploadDocument) }, - { key: "organization", icon: "bx bx-buildings", label: "Organization"}, - { key: "setting", icon: "bx bxs-cog", label: "Setting",hidden:!isManageTeam }, + { key: "documents", icon: "bx bx-folder-open", label: "Documents", hidden: !(isViewDocuments || isModifyDocument || isUploadDocument) }, + { key: "organization", icon: "bx bx-buildings", label: "Organization" }, + { key: "setting", icon: "bx bxs-cog", label: "Setting", hidden: !isManageTeam }, ]; return ( -
- +
+
+ +
); }; -- 2.43.0 From 57d65a5fe75be7a772ee01a54db39bbe7cbf39b1 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 15 Oct 2025 12:46:47 +0530 Subject: [PATCH 13/41] Adding Chips in Document, Directory and Expense Page. --- .../Directory/ContactFilterChips.jsx | 56 +++++ src/components/Directory/NoteFilterChips.jsx | 79 ++++++++ .../Documents/DocumentFilterChips.jsx | 94 +++++++++ .../Documents/DocumentFilterPanel.jsx | 57 ++++-- src/components/Documents/Documents.jsx | 41 +++- src/components/Documents/DocumentsList.jsx | 20 +- .../Expenses/ExpenseFilterChips.jsx | 86 ++++++++ .../Expenses/ExpenseFilterPanel.jsx | 94 ++++++++- src/components/Expenses/ExpenseList.jsx | 191 ++++++++++++------ src/pages/Directory/ContactFilterPanel.jsx | 155 ++++++++------ src/pages/Directory/ContactsPage.jsx | 80 ++++++-- src/pages/Directory/NoteFilterPanel.jsx | 121 ++++++----- src/pages/Directory/NotesPage.jsx | 49 ++++- src/pages/Expense/ExpensePage.jsx | 82 ++++---- 14 files changed, 925 insertions(+), 280 deletions(-) create mode 100644 src/components/Directory/ContactFilterChips.jsx create mode 100644 src/components/Directory/NoteFilterChips.jsx create mode 100644 src/components/Documents/DocumentFilterChips.jsx create mode 100644 src/components/Expenses/ExpenseFilterChips.jsx diff --git a/src/components/Directory/ContactFilterChips.jsx b/src/components/Directory/ContactFilterChips.jsx new file mode 100644 index 00000000..d321afa3 --- /dev/null +++ b/src/components/Directory/ContactFilterChips.jsx @@ -0,0 +1,56 @@ +import React, { useMemo } from "react"; + +const ContactFilterChips = ({ filters, filterData, removeFilterChip, clearFilter }) => { + const data = filterData?.data || filterData || {}; + + const filterChips = useMemo(() => { + const chips = []; + + const addGroup = (ids, list, label, key) => { + if (!ids?.length) return; + const items = ids.map((id) => ({ + id, + name: list?.find((i) => i.id === id)?.name || id, + })); + chips.push({ key, label, items }); + }; + + addGroup(filters.bucketIds, data.buckets, "Buckets", "bucketIds"); + addGroup(filters.categoryIds, data.contactCategories, "Category", "categoryIds"); + + return chips; + }, [filters, filterData]); + + if (!filterChips.length) return null; + + return ( +
+ {filterChips.map((chipGroup) => ( +
+ {chipGroup.label}: + {chipGroup.items.map((item) => ( + + {item.name} +
+ ))} + +
+ ); +}; + +export default ContactFilterChips; \ No newline at end of file diff --git a/src/components/Directory/NoteFilterChips.jsx b/src/components/Directory/NoteFilterChips.jsx new file mode 100644 index 00000000..5569a460 --- /dev/null +++ b/src/components/Directory/NoteFilterChips.jsx @@ -0,0 +1,79 @@ +import React, { useMemo } from "react"; +import moment from "moment"; + +const NoteFilterChips = ({ filters, filterData, removeFilterChip }) => { + // Normalize data (in case it’s wrapped in .data) + const data = filterData?.data || filterData || {}; + + const filterChips = useMemo(() => { + const chips = []; + + const buildGroup = (ids, list, label, key) => { + if (!ids?.length) return; + const items = ids.map((id) => ({ + id, + name: list?.find((item) => item.id === id)?.name || id, + })); + chips.push({ key, label, items }); + }; + + // Build chips dynamically + buildGroup(filters.createdByIds, data.createdBy, "Created By", "createdByIds"); + buildGroup(filters.organizations, data.organizations, "Organization", "organizations"); + + // Example: Add date range if you ever add in future + if (filters.startDate || filters.endDate) { + const start = filters.startDate ? moment(filters.startDate).format("DD-MM-YYYY") : ""; + const end = filters.endDate ? moment(filters.endDate).format("DD-MM-YYYY") : ""; + chips.push({ + key: "dateRange", + label: "Date Range", + items: [{ id: "dateRange", name: `${start} - ${end}` }], + }); + } + + return chips; + }, [filters, filterData]); + + if (!filterChips.length) return null; + + return ( +
+
+
+ {filterChips.map((chip) => ( +
+ {chip.label}: +
+ {chip.items.map((item) => ( + + {item.name} +
+
+ ))} +
+
+
+ ); +}; + +export default NoteFilterChips; \ No newline at end of file diff --git a/src/components/Documents/DocumentFilterChips.jsx b/src/components/Documents/DocumentFilterChips.jsx new file mode 100644 index 00000000..e6b56d7c --- /dev/null +++ b/src/components/Documents/DocumentFilterChips.jsx @@ -0,0 +1,94 @@ +import React, { useMemo } from "react"; +import moment from "moment"; + +const DocumentFilterChips = ({ filters, filterData, removeFilterChip }) => { + // Normalize structure: handle both "filterData.data" and plain "filterData" + const data = filterData?.data || filterData || {}; + + const filterChips = useMemo(() => { + const chips = []; + + const buildGroup = (ids, list, label, key) => { + if (!ids?.length) return; + const items = ids.map((id) => ({ + id, + name: list?.find((item) => item.id === id)?.name || id, + })); + chips.push({ key, label, items }); + }; + + // Build chips using normalized data + buildGroup(filters.uploadedByIds, data.uploadedBy || [], "Uploaded By", "uploadedByIds"); + buildGroup(filters.documentCategoryIds, data.documentCategory || [], "Category", "documentCategoryIds"); + buildGroup(filters.documentTypeIds, data.documentType || [], "Type", "documentTypeIds"); + buildGroup(filters.documentTagIds, data.documentTag || [], "Tags", "documentTagIds"); + + if (filters.statusIds?.length) { + const items = filters.statusIds.map((status) => ({ + id: status, + name: + status === true + ? "Verified" + : status === false + ? "Rejected" + : "Pending", + })); + chips.push({ key: "statusIds", label: "Status", items }); + } + + if (filters.startDate || filters.endDate) { + const start = filters.startDate ? moment(filters.startDate).format("DD-MM-YYYY") : ""; + const end = filters.endDate ? moment(filters.endDate).format("DD-MM-YYYY") : ""; + chips.push({ + key: "dateRange", + label: "Date Range", + items: [{ id: "dateRange", name: `${start} - ${end}` }], + }); + } + + return chips; + }, [filters, filterData]); + + if (!filterChips.length) return null; + + + return ( +
+
+
+ {filterChips.map((chip) => ( +
+ {chip.label}: +
+ {chip.items.map((item) => ( + + {item.name} +
+
+ ))} +
+
+
+ ); +}; + +export default DocumentFilterChips; diff --git a/src/components/Documents/DocumentFilterPanel.jsx b/src/components/Documents/DocumentFilterPanel.jsx index 581277e8..b266c986 100644 --- a/src/components/Documents/DocumentFilterPanel.jsx +++ b/src/components/Documents/DocumentFilterPanel.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useMemo, useImperativeHandle, forwardRef } from "react"; import { useDocumentFilterEntities } from "../../hooks/useDocument"; import { FormProvider, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -9,18 +9,34 @@ import { import { DateRangePicker1 } from "../common/DateRangePicker"; import SelectMultiple from "../common/SelectMultiple"; import moment from "moment"; -import { useLocation } from "react-router-dom"; +import { useParams } from "react-router-dom"; -const DocumentFilterPanel = ({ entityTypeId, onApply }) => { +const DocumentFilterPanel = forwardRef( + ({ entityTypeId, onApply, setFilterdata }, ref) => { const [resetKey, setResetKey] = useState(0); - const location = useLocation(); + const { status } = useParams(); const { data, isError, isLoading, error } = useDocumentFilterEntities(entityTypeId); + //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: DocumentFilterDefaultValues, + defaultValues: dynamicDocumentFilterDefaultValues, }); const { handleSubmit, reset, setValue, watch } = methods; @@ -34,6 +50,24 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => { 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] }); + } + }, + getValues: methods.getValues, // optional, to read current filter state + })); + + //changes + useEffect(() => { + if (data && setFilterdata) { + setFilterdata(data); + } + }, [data, setFilterdata]); + const onSubmit = (values) => { onApply({ ...values, @@ -54,13 +88,6 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => { closePanel(); }; - // Close popup when navigating to another component - useEffect(() => { - return () => { - closePanel(); - }; - }, []); - if (isLoading) return
Loading...
; if (isError) return
Error: {error?.message || "Something went wrong!"}
; @@ -198,18 +225,18 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => {
-
); -}; +}); export default DocumentFilterPanel; diff --git a/src/components/Documents/Documents.jsx b/src/components/Documents/Documents.jsx index d724c98a..930ceb8c 100644 --- a/src/components/Documents/Documents.jsx +++ b/src/components/Documents/Documents.jsx @@ -1,4 +1,4 @@ -import React, { createContext, useContext, useEffect, useState } from "react"; +import React, { createContext, useContext, useEffect, useRef, useState } from "react"; import GlobalModel from "../common/GlobalModel"; import NewDocument from "./ManageDocument"; import { DOCUMENTS_ENTITIES, UPLOAD_DOCUMENT } from "../../utils/constants"; @@ -17,6 +17,7 @@ import ViewDocument from "./ViewDocument"; import DocumentViewerModal from "./DocumentViewerModal"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useProfile } from "../../hooks/useProfile"; +import DocumentFilterChips from "./DocumentFilterChips"; // Context export const DocumentContext = createContext(); @@ -51,12 +52,14 @@ const Documents = ({ Document_Entity, Entity }) => { const [isSelf, setIsSelf] = useState(false); const [searchText, setSearchText] = useState(""); const [isActive, setIsActive] = useState(true); - const [filters, setFilter] = useState(); + const [filters, setFilter] = useState(DocumentFilterDefaultValues); const [isRefetching, setIsRefetching] = useState(false); const [refetchFn, setRefetchFn] = useState(null); const [DocumentEntity, setDocumentEntity] = useState(Document_Entity); const { employeeId } = useParams(); const [OpenDocument, setOpenDocument] = useState(false); + const [filterData, setFilterdata] = useState(DocumentFilterDefaultValues); + const updatedRef = useRef(); const [ManageDoc, setManageDoc] = useState({ document: null, isOpen: false, @@ -92,7 +95,7 @@ const Documents = ({ Document_Entity, Entity }) => { setShowTrigger(true); setOffcanvasContent( "Document Filters", - + ); return () => { @@ -115,13 +118,35 @@ const Documents = ({ Document_Entity, Entity }) => { setDocumentEntity(Document_Entity); } }, [Document_Entity]); + + + const removeFilterChip = (key, id) => { + const updatedFilters = { ...filters }; + if (Array.isArray(updatedFilters[key])) { + updatedFilters[key] = updatedFilters[key].filter((v) => v !== id); + updatedRef.current?.resetFieldValue(key,updatedFilters[key]); + } + else if (key === "dateRange") { + updatedFilters.startDate = null; + updatedFilters.endDate = null; + updatedRef.current?.resetFieldValue("startDate",null); + updatedRef.current?.resetFieldValue("endDate",null); + } + else { + updatedFilters[key] = null; + } + setFilter(updatedFilters); + return updatedFilters; + }; + return ( -
-
+
+
+
{/* Search */} -
+
{" "} { - {isActive ? "Active Document" : "In-Active Document"} + {isActive ? "Active" : "In-Active"}
@@ -231,4 +256,4 @@ const Documents = ({ Document_Entity, Entity }) => { ); }; -export default Documents; +export default Documents; \ No newline at end of file diff --git a/src/components/Documents/DocumentsList.jsx b/src/components/Documents/DocumentsList.jsx index b56060ea..2121c9dd 100644 --- a/src/components/Documents/DocumentsList.jsx +++ b/src/components/Documents/DocumentsList.jsx @@ -82,9 +82,9 @@ const DocumentsList = ({ if (isLoading || isFetching) return ; if (isError) return
Error: {error?.message || "Something went wrong"}
; - if (isInitialEmpty) return
No documents found yet.
; - if (isSearchEmpty) return
No results found for "{debouncedSearch}"
; - if (isFilterEmpty) return
No documents match your filter.
; + if (isInitialEmpty) return
No documents found yet.
; + if (isSearchEmpty) return
No results found for "{debouncedSearch}"
; + if (isFilterEmpty) return
No documents match your filter.
; const handleDelete = () => { ActiveInActive( @@ -138,16 +138,14 @@ const DocumentsList = ({ lastName={e.uploadedBy?.lastName} /> - {`${e.uploadedBy?.firstName ?? ""} ${ - e.uploadedBy?.lastName ?? "" - }`.trim() || "N/A"} + {`${e.uploadedBy?.firstName ?? ""} ${e.uploadedBy?.lastName ?? "" + }`.trim() || "N/A"}
), getValue: (e) => - `${e.uploadedBy?.firstName ?? ""} ${ - e.uploadedBy?.lastName ?? "" - }`.trim() || "N/A", + `${e.uploadedBy?.firstName ?? ""} ${e.uploadedBy?.lastName ?? "" + }`.trim() || "N/A", }, { key: "uploadedAt", @@ -217,7 +215,7 @@ const DocumentsList = ({ } >
- {(isSelf || canModifyDocument) && ( + {(isSelf || canModifyDocument) && ( @@ -226,7 +224,7 @@ const DocumentsList = ({ > )} - {(isSelf || canDeleteDocument) && ( + {(isSelf || canDeleteDocument) && ( { diff --git a/src/components/Expenses/ExpenseFilterChips.jsx b/src/components/Expenses/ExpenseFilterChips.jsx new file mode 100644 index 00000000..66c8a376 --- /dev/null +++ b/src/components/Expenses/ExpenseFilterChips.jsx @@ -0,0 +1,86 @@ +import React, { useMemo } from "react"; + +const ExpenseFilterChips = ({ filters, filterData, removeFilterChip }) => { + // Build chips from filters + const filterChips = useMemo(() => { + const chips = []; + + const buildGroup = (ids, list, label, key) => { + if (!ids?.length) return; + const items = ids.map((id) => ({ + id, + name: list.find((item) => item.id === id)?.name || id, + })); + chips.push({ key, label, items }); + }; + + buildGroup(filters.projectIds, filterData.projects, "Project", "projectIds"); + buildGroup(filters.createdByIds, filterData.createdBy, "Submitted By", "createdByIds"); + buildGroup(filters.paidById, filterData.paidBy, "Paid By", "paidById"); + buildGroup(filters.statusIds, filterData.status, "Status", "statusIds"); + buildGroup(filters.ExpenseTypeIds, filterData.expensesType, "Category", "ExpenseTypeIds"); + + if (filters.startDate || filters.endDate) { + const start = filters.startDate + ? new Date(filters.startDate).toLocaleDateString() + : ""; + const end = filters.endDate + ? new Date(filters.endDate).toLocaleDateString() + : ""; + chips.push({ + key: "dateRange", + label: "Date Range", + items: [{ id: "dateRange", name: `${start} - ${end}` }], + }); + } + + return chips; + }, [filters, filterData]); + + if (!filterChips.length) return null; + + return ( +
+
+
+ {filterChips.map((chip) => ( +
+ {/* Chip Label */} + {chip.label}: + + {/* Chip Items */} +
+ {chip.items.map((item) => ( + + {item.name} +
+
+ ))} +
+
+
+ ); +}; + +export default ExpenseFilterChips; + + diff --git a/src/components/Expenses/ExpenseFilterPanel.jsx b/src/components/Expenses/ExpenseFilterPanel.jsx index fffd746c..0ec19ce6 100644 --- a/src/components/Expenses/ExpenseFilterPanel.jsx +++ b/src/components/Expenses/ExpenseFilterPanel.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useMemo } from "react"; +import React, { forwardRef, useEffect, useImperativeHandle, useState, useMemo } from "react"; import { FormProvider, useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { defaultFilter, SearchSchema } from "./ExpenseSchema"; @@ -15,7 +15,7 @@ import { useExpenseFilter } from "../../hooks/useExpense"; import { ExpenseFilterSkeleton } from "./ExpenseSkeleton"; import { useLocation, useNavigate, useParams } from "react-router-dom"; -const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { +const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }, ref) => { const { status } = useParams(); const navigate = useNavigate(); const selectedProjectId = useSelector( @@ -31,17 +31,31 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { { id: "submittedBy", name: "Submitted By" }, { id: "project", name: "Project" }, { id: "paymentMode", name: "Payment Mode" }, - { id: "expensesType", name: "Expense Type" }, + { id: "expensesType", name: "Expense Category" }, { id: "createdAt", name: "Submitted Date" }, ].sort((a, b) => a.name.localeCompare(b.name)); }, []); - const [selectedGroup, setSelectedGroup] = useState(groupByList[0]); + const [selectedGroup, setSelectedGroup] = useState(groupByList[6]); const [resetKey, setResetKey] = useState(0); + const dynamicDefaultFilter = useMemo(() => { + return { + ...defaultFilter, + statusIds: status ? [status] : defaultFilter.statusIds || [], + projectIds: selectedProjectId ? [selectedProjectId] : [], + createdByIds: defaultFilter.createdByIds || [], + paidById: defaultFilter.paidById || [], + ExpenseTypeIds: defaultFilter.ExpenseTypeIds || [], + isTransactionDate: defaultFilter.isTransactionDate ?? true, + startDate: defaultFilter.startDate, + endDate: defaultFilter.endDate, + }; + }, [status,selectedProjectId]); + const methods = useForm({ resolver: zodResolver(SearchSchema), - defaultValues: defaultFilter, + defaultValues: dynamicDefaultFilter, }); const { control, handleSubmit, reset, setValue, watch } = methods; @@ -51,11 +65,30 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { document.querySelector(".offcanvas.show .btn-close")?.click(); }; + // Change here + useEffect(() => { + if (data && setFilterdata) { + setFilterdata(data); + } + }, [data, setFilterdata]); + const handleGroupChange = (e) => { const group = groupByList.find((g) => g.id === e.target.value); if (group) setSelectedGroup(group); }; + useImperativeHandle(ref, () => ({ + resetFieldValue: (name, value) => { + // Reset specific field + if (value !== undefined) { + setValue(name, value); + } else { + reset({ ...methods.getValues(), [name]: defaultFilter[name] }); + } + }, + getValues: methods.getValues, // optional, to read current filter state + })); + const onSubmit = (formData) => { onApply({ ...formData, @@ -78,19 +111,51 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { } }; - - - - // 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, + projectIds: selectedProjectId ? [selectedProjectId] : [], // ✅ include project ID + 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, + selectedProjectId, // ✅ added dependency + ]); + + if (isLoading || isFetching) return ; if (isError && isFetched) return
Something went wrong Here- {error.message}
; + + return ( <> @@ -150,6 +215,13 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { labelKey={(item) => item.name} valueKey="id" /> + item.name} + valueKey="id" + />
@@ -221,6 +293,6 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { ); -}; +}); -export default ExpenseFilterPanel; +export default ExpenseFilterPanel; \ No newline at end of file diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index 8d96e121..7cccb5d3 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -10,23 +10,30 @@ 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"; import { useSelector } from "react-redux"; +import ExpenseFilterChips from "./ExpenseFilterChips"; +import { defaultFilter } from "./ExpenseSchema"; import { useNavigate } from "react-router-dom"; const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { const [deletingId, setDeletingId] = useState(null); const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const { setViewExpense, setManageExpenseModal } = useExpenseContext(); + const { setViewExpense, setManageExpenseModal, filterData, removeFilterChip } = useExpenseContext(); const IsExpenseEditable = useHasUserPermission(); 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( ITEMS_PER_PAGE, @@ -61,39 +68,60 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { const groupByField = (items, field) => { return items.reduce((acc, item) => { let key; + let displayField; + switch (field) { case "transactionDate": - key = item.transactionDate?.split("T")[0]; + key = item?.transactionDate?.split("T")[0]; + displayField = "Transaction Date"; break; case "status": - key = item.status?.displayName || "Unknown"; + key = item?.status?.displayName || "Unknown"; + displayField = "Status"; break; case "submittedBy": - key = `${item.createdBy?.firstName ?? ""} ${item.createdBy?.lastName ?? "" + key = `${item?.createdBy?.firstName ?? ""} ${item.createdBy?.lastName ?? "" }`.trim(); + displayField = "Submitted By"; break; case "project": - key = item.project?.name || "Unknown Project"; + key = item?.project?.name || "Unknown Project"; + displayField = "Project"; break; case "paymentMode": - key = item.paymentMode?.name || "Unknown Mode"; + key = item?.paymentMode?.name || "Unknown Mode"; + displayField = "Payment Mode"; break; case "expensesType": - key = item.expensesType?.name || "Unknown Type"; + key = item?.expensesType?.name || "Unknown Type"; + displayField = "Expense Category"; break; case "createdAt": - key = item.createdAt?.split("T")[0] || "Unknown Type"; + key = item?.createdAt?.split("T")[0] || "Unknown Date"; + displayField = "Created Date"; break; default: key = "Others"; + displayField = "Others"; } - if (!acc[key]) acc[key] = []; - acc[key].push(item); + + const groupKey = `${field}_${key}`; // unique key for object property + if (!acc[groupKey]) { + acc[groupKey] = { key, displayField, items: [] }; + } + + acc[groupKey].items.push(item); return acc; }, {}); }; const expenseColumns = [ + { + key: "expenseUId", + label: "Expense Id", + getValue: (e) => e.expenseUId || "N/A", + align: "text-start mx-2", + }, { key: "expensesType", label: "Expense Type", @@ -116,7 +144,6 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { customRender: (e) => (
navigate(`/employee/${e.createdBy?.id}`)}> - { { key: "amount", label: "Amount", - getValue: (e) => ( - <> - {e?.amount} - - ), + getValue: (e) => <>{formatCurrency(e?.amount)}, isAlwaysVisible: true, align: "text-end", }, @@ -162,27 +185,30 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { }, ]; - if (isInitialLoading) return ; - if (isError) return
{error.message}
; + if (isInitialLoading && !data) return ; + if (isError) return
{error?.message}
; const grouped = groupBy ? groupByField(data?.data ?? [], groupBy) : { All: data?.data ?? [] }; - const IsGroupedByDate = ["transactionDate", "createdAt"].includes(groupBy); + const IsGroupedByDate = [ + { key: "transactionDate", displayField: "Transaction Date" }, + { key: "createdAt", displayField: "created Date" }, + ]?.includes(groupBy); + const canEditExpense = (expense) => { return ( - (expense.status.id === EXPENSE_DRAFT || - EXPENSE_REJECTEDBY.includes(expense.status.id)) && - expense.createdBy?.id === SelfId + (expense?.status?.id === EXPENSE_DRAFT || + EXPENSE_REJECTEDBY.includes(expense?.status?.id)) && + expense?.createdBy?.id === SelfId ); }; const canDetetExpense = (expense) => { return ( - expense.status.id === EXPENSE_DRAFT && expense.createdBy.id === SelfId + expense?.status?.id === EXPENSE_DRAFT && expense?.createdBy?.id === SelfId ); }; - return ( <> {IsDeleteModalOpen && ( @@ -198,7 +224,14 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { /> )} -
+
+ {/* Filter Chips */} +
{ {Object.keys(grouped).length > 0 ? ( - Object.entries(grouped).map(([group, expenses]) => ( - + Object.values(grouped).map(({ key, displayField, items }) => ( + - - {IsGroupedByDate - ? formatUTCToLocalTime(group) - : group} - +
+ {" "} + + {displayField} :{" "} + {" "} + + {IsGroupedByDate + ? formatUTCToLocalTime(key) + : key} + +
- {expenses.map((expense) => ( + {items?.map((expense) => ( {expenseColumns.map( (col) => @@ -263,27 +302,61 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { }) } >
- {canEditExpense(expense) && ( - - setManageExpenseModal({ - IsOpen: true, - expenseId: expense.id, - }) - } - > - )} + {canDetetExpense(expense) && + canEditExpense(expense) && ( +
+ +
    + {canDetetExpense(expense) && ( +
  • + setManageExpenseModal({ + IsOpen: true, + expenseId: expense.id, + }) + } + > + + + + Modify + + +
  • + )} - {canDetetExpense(expense) && ( - { - setIsDeleteModalOpen(true); - setDeletingId(expense.id); - }} - > - )} + {canDetetExpense(expense) && ( +
  • { + setIsDeleteModalOpen(true); + setDeletingId(expense.id); + }} + > + + + + Delete + + +
  • + )} +
+
+ )}
@@ -292,13 +365,17 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { )) ) : ( - - No Expense Found + +
+

No Expense Found

+
)} +
+
{data?.data?.length > 0 && ( { onPageChange={paginate} /> )} -
-
); diff --git a/src/pages/Directory/ContactFilterPanel.jsx b/src/pages/Directory/ContactFilterPanel.jsx index 063a6268..e77c8469 100644 --- a/src/pages/Directory/ContactFilterPanel.jsx +++ b/src/pages/Directory/ContactFilterPanel.jsx @@ -1,5 +1,10 @@ import { zodResolver } from "@hookform/resolvers/zod"; -import React, { useEffect } from "react"; +import React, { + useEffect, + useImperativeHandle, + forwardRef, + useMemo, +} from "react"; import { FormProvider, useForm } from "react-hook-form"; import { contactsFilter, @@ -8,83 +13,101 @@ import { import { useContactFilter } from "../../hooks/useDirectory"; import { ExpenseFilterSkeleton } from "../../components/Expenses/ExpenseSkeleton"; import SelectMultiple from "../../components/common/SelectMultiple"; -import { useLocation } from "react-router-dom"; +import { useParams } from "react-router-dom"; -const ContactFilterPanel = ({ onApply, clearFilter }) => { - const { data, isError, isLoading, error, isFetched, isFetching } = - useContactFilter(); +const ContactFilterPanel = forwardRef( + ({ onApply, clearFilter, setFilterdata }, ref) => { + const { data, isError, isLoading, error, isFetched, isFetching } = + useContactFilter(); + const { status } = useParams(); - const location = useLocation(); + const dynamicdefaultContactFilter = useMemo(() => { + return { + ...defaultContactFilter, + bucketIds: defaultContactFilter.bucketIds || [], + categoryIds: defaultContactFilter.categoryIds || [], + }; + }, [status]); - const methods = useForm({ - resolver: zodResolver(contactsFilter), - defaultValues: defaultContactFilter, - }); + const methods = useForm({ + resolver: zodResolver(contactsFilter), + defaultValues: dynamicdefaultContactFilter, + }); - const closePanel = () => { - document.querySelector(".offcanvas.show .btn-close")?.click(); - }; + const { handleSubmit, reset, setValue, getValues } = methods; - const { register, handleSubmit, reset, watch } = methods; + useImperativeHandle(ref, () => ({ + resetFieldValue: (name, value) => { + if (value !== undefined) { + setValue(name, value); + } else { + reset({ ...getValues(), [name]: defaultContactFilter[name] }); + } + }, + getValues, // optional: allows parent to read current form values + })); - const onSubmit = (formData) => { - onApply(formData); - closePanel(); - }; + useEffect(() => { + if (data && setFilterdata) { + setFilterdata(data); + } + }, [data, setFilterdata]); - const handleClose = () => { - reset(defaultContactFilter); - onApply(defaultContactFilter); - closePanel(); - }; + const closePanel = () => { + document.querySelector(".offcanvas.show .btn-close")?.click(); + }; - - useEffect(() => { - return () => { + const onSubmit = (formData) => { + onApply(formData); closePanel(); }; - }, []); - if (isLoading || isFetching) return ; - if (isError && isFetched) - return
Something went wrong Here- {error.message}
; + const handleClose = () => { + reset(defaultContactFilter); + onApply(defaultContactFilter); + closePanel(); + }; + if (isLoading || isFetching) return ; + if (isError && isFetched) + return
Something went wrong — {error?.message}
; + return ( + +
+
+ + item.name} + valueKey="id" + /> +
- return ( - - -
- - item.name} - valueKey="id" - /> -
-
- - -
- -
- ); -}; +
+ + +
+ +
+ ); + } +); -export default ContactFilterPanel; +export default ContactFilterPanel; \ No newline at end of file diff --git a/src/pages/Directory/ContactsPage.jsx b/src/pages/Directory/ContactsPage.jsx index 670b3ef9..195ebb96 100644 --- a/src/pages/Directory/ContactsPage.jsx +++ b/src/pages/Directory/ContactsPage.jsx @@ -1,26 +1,30 @@ -import React, { useEffect, useState } from "react"; + +import React, { useEffect, useRef, useState } from "react"; import { useFab } from "../../Context/FabContext"; import { useContactList } from "../../hooks/useDirectory"; import { useDirectoryContext } from "./DirectoryPage"; import CardViewContact from "../../components/Directory/CardViewContact"; import { ITEMS_PER_PAGE } from "../../utils/constants"; import ContactFilterPanel from "./ContactFilterPanel"; +import ContactFilterChips from "../../components/Directory/ContactFilterChips"; import { defaultContactFilter } from "../../components/Directory/DirectorySchema"; import { useDebounce } from "../../utils/appUtils"; import Pagination from "../../components/common/Pagination"; import ListViewContact from "../../components/Directory/ListViewContact"; -import { CardViewContactSkeleton, ListViewContactSkeleton } from "../../components/Directory/DirectoryPageSkeleton"; +import Loader from "../../components/common/Loader"; -// Utility function to format contacts for CSV export +// Utility for CSV export const formatExportData = (contacts) => { - return contacts.map(contact => ({ - Email: contact.contactEmails?.map(e => e.emailAddress).join(", ") || "", - Phone: contact.contactPhones?.map(p => p.phoneNumber).join(", ") || "", - Created: contact.createdAt ? new Date(contact.createdAt).toLocaleString() : "", + return contacts.map((contact) => ({ + Email: contact.contactEmails?.map((e) => e.emailAddress).join(", ") || "", + Phone: contact.contactPhones?.map((p) => p.phoneNumber).join(", ") || "", + Created: contact.createdAt + ? new Date(contact.createdAt).toLocaleString() + : "", Location: contact.address || "", Organization: contact.organization || "", Category: contact.contactCategory?.name || "", - Tags: contact.tags?.map(t => t.name).join(", ") || "", + Tags: contact.tags?.map((t) => t.name).join(", ") || "", Buckets: contact.bucketIds?.join(", ") || "", })); }; @@ -28,8 +32,10 @@ const formatExportData = (contacts) => { const ContactsPage = ({ projectId, searchText, onExport }) => { const [currentPage, setCurrentPage] = useState(1); const [filters, setFilter] = useState(defaultContactFilter); + const [filterData, setFilterdata] = useState(null); const debouncedSearch = useDebounce(searchText, 500); const { showActive, gridView } = useDirectoryContext(); + const updatedRef = useRef(); const { data, isError, isLoading, error } = useContactList( showActive, projectId, @@ -40,13 +46,19 @@ const ContactsPage = ({ projectId, searchText, onExport }) => { ); const { setOffcanvasContent, setShowTrigger } = useFab(); + // clear filters const clearFilter = () => setFilter(defaultContactFilter); useEffect(() => { setShowTrigger(true); setOffcanvasContent( "Contacts Filters", - + ); return () => { @@ -55,7 +67,7 @@ const ContactsPage = ({ projectId, searchText, onExport }) => { }; }, []); - // 🔹 Format contacts for export + // export data useEffect(() => { if (data?.data && onExport) { onExport(formatExportData(data.data)); @@ -68,15 +80,54 @@ const ContactsPage = ({ projectId, searchText, onExport }) => { } }; + const handleRemoveChip = (key, id) => { + setFilter((prev) => { + const updated = { ...prev }; + + if (Array.isArray(updated[key])) { + updated[key] = updated[key].filter((v) => v !== id); + updatedRef.current?.resetFieldValue(key, updated[key]); + } else { + updated[key] = null; + updatedRef.current?.resetFieldValue(key, null); + } + + return updated; + }); + }; + if (isError) return
{error.message}
; - // if (isLoading) return gridView ? : ; return ( -
+
+ {/* Chips Section */} +
+ +
+ + {/* Grid / List View */} {gridView ? ( <> + {isLoading && } + + {data?.data?.length === 0 && ( +
+ {searchText + ? `No contact found for "${searchText}"` + : "No contacts found"} +
+ )} + {data?.data?.map((contact) => ( -
+
))} @@ -95,6 +146,7 @@ const ContactsPage = ({ projectId, searchText, onExport }) => {
{ ); }; -export default ContactsPage; +export default ContactsPage; \ No newline at end of file diff --git a/src/pages/Directory/NoteFilterPanel.jsx b/src/pages/Directory/NoteFilterPanel.jsx index 0b21fc46..6cc5bdbc 100644 --- a/src/pages/Directory/NoteFilterPanel.jsx +++ b/src/pages/Directory/NoteFilterPanel.jsx @@ -1,36 +1,38 @@ import { zodResolver } from "@hookform/resolvers/zod"; -import React, { useEffect } from "react"; +import React, { useEffect, useImperativeHandle, forwardRef, useMemo } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { defaultNotesFilter, notesFilter, } from "../../components/Directory/DirectorySchema"; -import { useContactFilter, useNoteFilter } from "../../hooks/useDirectory"; +import { useNoteFilter } from "../../hooks/useDirectory"; import { ExpenseFilterSkeleton } from "../../components/Expenses/ExpenseSkeleton"; import SelectMultiple from "../../components/common/SelectMultiple"; -import { useLocation } from "react-router-dom"; -const NoteFilterPanel = ({ onApply, clearFilter }) => { - const { data, isError, isLoading, error, isFetched, isFetching } = - useNoteFilter(); +const NoteFilterPanel = forwardRef(({ onApply, clearFilter, setFilterdata }, ref) => { + const { data, isError, isLoading, error, isFetched, isFetching } = useNoteFilter(); - useEffect(() => { - return () => { - closePanel(); + + //Add this for Filter chip remover + const dynamicdefaultNotesFilter = useMemo(() => { + return { + ...defaultNotesFilter, + bucketIds: defaultNotesFilter.bucketIds || [], + categoryIds: defaultNotesFilter.categoryIds || [], }; - }, []); + }, [status]); const methods = useForm({ resolver: zodResolver(notesFilter), - defaultValues: defaultNotesFilter, + defaultValues: dynamicdefaultNotesFilter, }); + const { handleSubmit, reset, setValue, getValues } = methods; + const closePanel = () => { document.querySelector(".offcanvas.show .btn-close")?.click(); }; - const { register, handleSubmit, reset, watch } = methods; - const onSubmit = (formData) => { onApply(formData); closePanel(); @@ -42,43 +44,70 @@ const NoteFilterPanel = ({ onApply, clearFilter }) => { closePanel(); }; - if (isLoading || isFetching) return ; - if (isError && isFetched) - return
Something went wrong Here- {error.message}
; + //Add this for Filter chip remover + useImperativeHandle(ref, () => ({ + resetFieldValue: (name, value) => { + setTimeout(() => { + if (value !== undefined) { + setValue(name, value); + } else { + reset({ ...getValues(), [name]: defaultNotesFilter[name] }); + } + }, 0); + }, + getValues, + })); + + + useEffect(() => { + if (data && setFilterdata) { + setFilterdata(data); + } + }, [data, setFilterdata]); + return (
-
- - item.name} - valueKey="id" - /> -
-
- - -
+ {isLoading || isFetching ? ( + + ) : isError && isFetched ? ( +
Something went wrong here: {error?.message}
+ ) : ( + <> +
+ + item.name} + valueKey="id" + /> +
+ +
+ + +
+ + )}
); -}; +}); -export default NoteFilterPanel; +export default NoteFilterPanel; \ No newline at end of file diff --git a/src/pages/Directory/NotesPage.jsx b/src/pages/Directory/NotesPage.jsx index 58790562..45abe8d3 100644 --- a/src/pages/Directory/NotesPage.jsx +++ b/src/pages/Directory/NotesPage.jsx @@ -1,5 +1,4 @@ -// NotesPage.jsx -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { useFab } from "../../Context/FabContext"; import { useNotes } from "../../hooks/useDirectory"; import NoteFilterPanel from "./NoteFilterPanel"; @@ -9,11 +8,14 @@ import { useDebounce } from "../../utils/appUtils"; import NoteCardDirectoryEditable from "../../components/Directory/NoteCardDirectoryEditable"; import Pagination from "../../components/common/Pagination"; import { NoteCardSkeleton } from "../../components/Directory/DirectoryPageSkeleton"; +import NoteFilterChips from "../../components/Directory/NoteFilterChips"; const NotesPage = ({ projectId, searchText, onExport }) => { const [filters, setFilter] = useState(defaultNotesFilter); const [currentPage, setCurrentPage] = useState(1); const debouncedSearch = useDebounce(searchText, 500); + const [filterData, setFilterdata] = useState(null); + const updatedRef = useRef(); const { data, isLoading, isError, error } = useNotes( projectId, @@ -33,7 +35,12 @@ const NotesPage = ({ projectId, searchText, onExport }) => { setShowTrigger(true); setOffcanvasContent( "Notes Filters", - + ); return () => { @@ -42,11 +49,27 @@ const NotesPage = ({ projectId, searchText, onExport }) => { }; }, []); - // 🔹 Format data for export + const handleRemoveChip = (key, id) => { + setFilter((prev) => { + const updated = { ...prev }; + + if (Array.isArray(updated[key])) { + updated[key] = updated[key].filter((v) => v !== id); + updatedRef.current?.resetFieldValue(key, updated[key]); //IMP + } else { + updated[key] = null; + updatedRef.current?.resetFieldValue(key, null); + } + + return updated; + }); + }; + + // Format data for export const formatExportData = (notes) => { return notes.map((n) => ({ ContactName: n.contactName || "", - Note: n.note ? n.note.replace(/<[^>]+>/g, "") : "", // strip HTML tags + Note: n.note ? n.note.replace(/<[^>]+>/g, "") : "", Organization: n.organizationName || "", CreatedBy: n.createdBy ? `${n.createdBy.firstName || ""} ${n.createdBy.lastName || ""}`.trim() @@ -59,7 +82,6 @@ const NotesPage = ({ projectId, searchText, onExport }) => { })); }; - // 🔹 Pass formatted notes to parent for export useEffect(() => { if (data?.data && onExport) { onExport(formatExportData(data.data)); @@ -77,6 +99,12 @@ const NotesPage = ({ projectId, searchText, onExport }) => { return (
+ + {data?.data?.length > 0 ? ( <> {data.data.map((noteItem) => ( @@ -96,7 +124,6 @@ const NotesPage = ({ projectId, searchText, onExport }) => {
) : ( - // Card for "No notes available"
{

{debouncedSearch ? `No notes found matching "${searchText}"` - : Object.keys(filters).some((k) => filters[k] && filters[k].length) - ? "No notes found for the applied filters." - : "No notes available."} + : Object.keys(filters).some((k) => filters[k]?.length) + ? "No notes found for the applied filters." + : "No notes available."}

)} @@ -114,4 +141,4 @@ const NotesPage = ({ projectId, searchText, onExport }) => { ); }; -export default NotesPage; \ No newline at end of file +export default NotesPage; diff --git a/src/pages/Expense/ExpensePage.jsx b/src/pages/Expense/ExpensePage.jsx index 5d2ef806..f5c3ea5d 100644 --- a/src/pages/Expense/ExpensePage.jsx +++ b/src/pages/Expense/ExpensePage.jsx @@ -1,17 +1,16 @@ -import React, { createContext, useContext, useState, useEffect } from "react"; -import { useForm } from "react-hook-form"; +import React, { createContext, useContext, useState, useEffect, useRef } from "react"; +import { useForm, useFormContext } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useSelector } from "react-redux"; -import ExpenseList from "../../components/Expenses/ExpenseList"; -import ViewExpense from "../../components/Expenses/ViewExpense"; import Breadcrumb from "../../components/common/Breadcrumb"; import GlobalModel from "../../components/common/GlobalModel"; -import PreviewDocument from "../../components/Expenses/PreviewDocument"; +import ExpenseList from "../../components/Expenses/ExpenseList"; +import ViewExpense from "../../components/Expenses/ViewExpense"; import ManageExpense from "../../components/Expenses/ManageExpense"; import ExpenseFilterPanel from "../../components/Expenses/ExpenseFilterPanel"; +import ExpenseFilterChips from "../../components/Expenses/ExpenseFilterChips"; -// Context & Hooks import { useFab } from "../../Context/FabContext"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { @@ -20,11 +19,8 @@ import { VIEW_SELF_EXPENSE, } from "../../utils/constants"; -// Schema & Defaults -import { - defaultFilter, - SearchSchema, -} from "../../components/Expenses/ExpenseSchema"; +import { defaultFilter, SearchSchema } from "../../components/Expenses/ExpenseSchema"; +import PreviewDocument from "../../components/Expenses/PreviewDocument"; // Context export const ExpenseContext = createContext(); @@ -41,10 +37,10 @@ const ExpensePage = () => { (store) => store.localVariables.projectId ); - const [filters, setFilter] = useState(); + const [filters, setFilters] = useState(defaultFilter); const [groupBy, setGroupBy] = useState("transactionDate"); const [searchText, setSearchText] = useState(""); - + const filterPanelRef = useRef(); const [ManageExpenseModal, setManageExpenseModal] = useState({ IsOpen: null, expenseId: null, @@ -63,19 +59,22 @@ const ExpensePage = () => { const IsCreatedAble = useHasUserPermission(CREATE_EXEPENSE); const IsViewAll = useHasUserPermission(VIEW_ALL_EXPNESE); const IsViewSelf = useHasUserPermission(VIEW_SELF_EXPENSE); - const { setOffcanvasContent, setShowTrigger } = useFab(); - - const methods = useForm({ - resolver: zodResolver(SearchSchema), - defaultValues: defaultFilter, - }); - - const { reset } = methods; - - const clearFilter = () => { - setFilter(defaultFilter); - reset(); + const [filterData, setFilterdata] = useState(defaultFilter); + const removeFilterChip = (key, id) => { + setFilters((prev) => { + const updated = { ...prev }; + if (Array.isArray(updated[key])) { + updated[key] = updated[key].filter((v) => v !== id); + filterPanelRef.current?.resetFieldValue(key, updated[key]); + } else if (key === "dateRange") { + updated.startDate = null; + updated.endDate = null; + filterPanelRef.current?.resetFieldValue("startDate", null); + filterPanelRef.current?.resetFieldValue("endDate", null); + } + return updated; + }); }; useEffect(() => { @@ -84,9 +83,10 @@ const ExpensePage = () => { setOffcanvasContent( "Expense Filters", ); } @@ -101,6 +101,8 @@ const ExpensePage = () => { setViewExpense, setManageExpenseModal, setDocumentView, + filterData, + removeFilterChip }; return ( @@ -115,20 +117,18 @@ const ExpensePage = () => {
-
-
- setSearchText(e.target.value)} - /> -
+
+ setSearchText(e.target.value)} + />
-
+
+ {IsCreatedAble && (
+ + {

- Access Denied: You don't have permission to perform this action ! + Access Denied: You don't have permission to perform this action!

)} -- 2.43.0 From bd6332fa615c050fa368334beb0c454228286816 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 15 Oct 2025 14:10:03 +0530 Subject: [PATCH 14/41] Changes in Filter panel. --- src/pages/Directory/ContactFilterPanel.jsx | 6 ++++++ src/pages/Directory/NoteFilterPanel.jsx | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/pages/Directory/ContactFilterPanel.jsx b/src/pages/Directory/ContactFilterPanel.jsx index e77c8469..7716d66e 100644 --- a/src/pages/Directory/ContactFilterPanel.jsx +++ b/src/pages/Directory/ContactFilterPanel.jsx @@ -21,6 +21,12 @@ const ContactFilterPanel = forwardRef( useContactFilter(); const { status } = useParams(); + useEffect(() => { + return () => { + closePanel(); + }; + }, []); + const dynamicdefaultContactFilter = useMemo(() => { return { ...defaultContactFilter, diff --git a/src/pages/Directory/NoteFilterPanel.jsx b/src/pages/Directory/NoteFilterPanel.jsx index 6cc5bdbc..88fb992e 100644 --- a/src/pages/Directory/NoteFilterPanel.jsx +++ b/src/pages/Directory/NoteFilterPanel.jsx @@ -12,7 +12,11 @@ import SelectMultiple from "../../components/common/SelectMultiple"; const NoteFilterPanel = forwardRef(({ onApply, clearFilter, setFilterdata }, ref) => { const { data, isError, isLoading, error, isFetched, isFetching } = useNoteFilter(); - + useEffect(() => { + return () => { + closePanel(); + }; + }, []); //Add this for Filter chip remover const dynamicdefaultNotesFilter = useMemo(() => { return { -- 2.43.0 From b23518f796f79b5f5e40a2cc7e6c82cefcf88275 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 15 Oct 2025 15:58:59 +0530 Subject: [PATCH 15/41] Changes in expense filter panel. --- .../Expenses/ExpenseFilterPanel.jsx | 13 +- src/components/Layout/Header.jsx | 138 +++++++++--------- src/router/AppRoutes.jsx | 1 + 3 files changed, 74 insertions(+), 78 deletions(-) diff --git a/src/components/Expenses/ExpenseFilterPanel.jsx b/src/components/Expenses/ExpenseFilterPanel.jsx index 0ec19ce6..3f0085c3 100644 --- a/src/components/Expenses/ExpenseFilterPanel.jsx +++ b/src/components/Expenses/ExpenseFilterPanel.jsx @@ -43,7 +43,7 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata } return { ...defaultFilter, statusIds: status ? [status] : defaultFilter.statusIds || [], - projectIds: selectedProjectId ? [selectedProjectId] : [], + projectIds: defaultFilter.projectIds || [], createdByIds: defaultFilter.createdByIds || [], paidById: defaultFilter.paidById || [], ExpenseTypeIds: defaultFilter.ExpenseTypeIds || [], @@ -51,7 +51,7 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata } startDate: defaultFilter.startDate, endDate: defaultFilter.endDate, }; - }, [status,selectedProjectId]); + }, [status]); const methods = useForm({ resolver: zodResolver(SearchSchema), @@ -119,12 +119,12 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata } const [appliedStatusId, setAppliedStatusId] = useState(null); useEffect(() => { - if (!status) return; + if (!status || !data) return; - if (status !== appliedStatusId && data) { + if (status !== appliedStatusId) { const filterWithStatus = { ...dynamicDefaultFilter, - projectIds: selectedProjectId ? [selectedProjectId] : [], // ✅ include project ID + projectIds: selectedProjectId ? [selectedProjectId] : dynamicDefaultFilter.projectIds || [], startDate: dynamicDefaultFilter.startDate ? moment.utc(dynamicDefaultFilter.startDate, "DD-MM-YYYY").toISOString() : undefined, @@ -135,7 +135,6 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata } onApply(filterWithStatus); handleGroupBy(selectedGroup.id); - setAppliedStatusId(status); } }, [ @@ -146,7 +145,7 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata } handleGroupBy, selectedGroup.id, appliedStatusId, - selectedProjectId, // ✅ added dependency + selectedProjectId, // ✅ Added dependency ]); diff --git a/src/components/Layout/Header.jsx b/src/components/Layout/Header.jsx index ae72921a..2a459b94 100644 --- a/src/components/Layout/Header.jsx +++ b/src/components/Layout/Header.jsx @@ -26,27 +26,23 @@ const Header = () => { const dispatch = useDispatch(); const { data, loading } = useMaster(); const navigate = useNavigate(); - const {onOpen} = useAuthModal() - const { onOpen:changePass } = useModal("ChangePassword"); + const { onOpen } = useAuthModal() + const { onOpen: changePass } = useModal("ChangePassword"); const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT); - const { mutate : logout,isPending:logouting} = useLogout() + const { mutate: logout, isPending: logouting } = useLogout() const isDashboardPath = /^\/dashboard$/.test(location.pathname) || /^\/$/.test(location.pathname); const isProjectPath = /^\/projects$/.test(location.pathname); const showProjectDropdown = (pathname) => { - const isDirectoryPath = /^\/directory$/.test(pathname); + // Paths where we DON'T want to show the project dropdown + const excludedPaths = ["/directory", "/expenses", "/employee"]; - // const isProfilePage = /^\/employee$/.test(location.pathname); - const isProfilePage = - /^\/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}$/.test( - pathname - ); - const isExpensePage = /^\/expenses$/.test(pathname); - - return !(isDirectoryPath || isProfilePage || isExpensePage); + // Check if pathname starts with any of the excluded paths + return !excludedPaths.some((path) => pathname.startsWith(path)); }; + const allowedProjectStatusIds = [ "603e994b-a27f-4e5d-a251-f3d69b0498ba", "cdad86aa-8a56-4ff4-b633-9c629057dfef", @@ -59,9 +55,9 @@ const Header = () => { return role ? role.name : "User"; }; - - + + const handleProfilePage = () => { navigate(`/employee/${profile?.employeeInfo?.id}`); @@ -74,8 +70,8 @@ const Header = () => { const projectsForDropdown = isDashboardPath ? projectNames : projectNames?.filter((project) => - allowedProjectStatusIds.includes(project.projectStatusId) - ); + allowedProjectStatusIds.includes(project.projectStatusId) + ); let currentProjectDisplayName; if (projectLoading) { @@ -191,63 +187,63 @@ const Header = () => { >
{showProjectDropdown(location.pathname) && ( -
- -
- {shouldShowDropdown ? ( - - ) : ( - - {currentProjectDisplayName} - - )} - - {shouldShowDropdown && - projectsForDropdown && - projectsForDropdown.length > 0 && ( -
    + +
    + {shouldShowDropdown ? ( + - - )} - {[...projectsForDropdown] - .sort((a, b) => a?.name?.localeCompare(b.name)) - .map((project) => ( -
  • + {currentProjectDisplayName} + + ) : ( + + {currentProjectDisplayName} + + )} + + {shouldShowDropdown && + projectsForDropdown && + projectsForDropdown.length > 0 && ( +
      + {isDashboardPath && ( +
    • - ))} -
    - )} + )} + {[...projectsForDropdown] + .sort((a, b) => a?.name?.localeCompare(b.name)) + .map((project) => ( +
  • + +
  • + ))} +
+ )} +
-
- )} + )}
diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index 025b05b8..37904f98 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -95,6 +95,7 @@ const router = createBrowserRouter( { path: "/activities/reports", element: }, { path: "/gallary", element: }, { path: "/expenses/:status?/:project?", element: }, + { path: "/expenses", element: }, { path: "/masters", element: }, { path: "/tenants", element: }, { path: "/tenants/new-tenant", element: }, -- 2.43.0 From cc2a82e3f09d8d6e8941ccdfe77e8da3d9518447 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 15 Oct 2025 16:56:18 +0530 Subject: [PATCH 16/41] Removing error in Contact Filter panel. --- src/pages/Directory/ContactFilterPanel.jsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/pages/Directory/ContactFilterPanel.jsx b/src/pages/Directory/ContactFilterPanel.jsx index 7716d66e..5f3c84de 100644 --- a/src/pages/Directory/ContactFilterPanel.jsx +++ b/src/pages/Directory/ContactFilterPanel.jsx @@ -44,13 +44,15 @@ const ContactFilterPanel = forwardRef( useImperativeHandle(ref, () => ({ resetFieldValue: (name, value) => { - if (value !== undefined) { - setValue(name, value); - } else { - reset({ ...getValues(), [name]: defaultContactFilter[name] }); - } + setTimeout(() => { + if (value !== undefined) { + setValue(name, value); + } else { + reset({ ...getValues(), [name]: defaultContactFilter[name] }); + } + }, 0); }, - getValues, // optional: allows parent to read current form values + getValues, })); useEffect(() => { -- 2.43.0 From 98c90f2a9b6f0dafbc274a18ecfec859decf260d Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Mon, 13 Oct 2025 17:11:25 +0530 Subject: [PATCH 17/41] Attendance "No record" message improvement --- src/components/Activities/AttendcesLogs.jsx | 247 ++++++++++---------- 1 file changed, 122 insertions(+), 125 deletions(-) diff --git a/src/components/Activities/AttendcesLogs.jsx b/src/components/Activities/AttendcesLogs.jsx index 0a917968..7a346f67 100644 --- a/src/components/Activities/AttendcesLogs.jsx +++ b/src/components/Activities/AttendcesLogs.jsx @@ -38,134 +38,134 @@ const usePagination = (data, itemsPerPage) => { }; const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => { -const selectedProject = useSelectedProject(); -const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" }); -const dispatch = useDispatch(); -const [loading, setLoading] = useState(false); -const [showPending, setShowPending] = useState(false); -const [isRefreshing, setIsRefreshing] = useState(false); + const selectedProject = useSelectedProject(); + const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" }); + const dispatch = useDispatch(); + const [loading, setLoading] = useState(false); + const [showPending, setShowPending] = useState(false); + const [isRefreshing, setIsRefreshing] = useState(false); -const today = new Date(); -today.setHours(0, 0, 0, 0); + const today = new Date(); + today.setHours(0, 0, 0, 0); -const yesterday = new Date(); -yesterday.setDate(yesterday.getDate() - 1); + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); -const isSameDay = (dateStr) => { - if (!dateStr) return false; - const d = new Date(dateStr); - d.setHours(0, 0, 0, 0); - return d.getTime() === today.getTime(); -}; + const isSameDay = (dateStr) => { + if (!dateStr) return false; + const d = new Date(dateStr); + d.setHours(0, 0, 0, 0); + return d.getTime() === today.getTime(); + }; -const isBeforeToday = (dateStr) => { - if (!dateStr) return false; - const d = new Date(dateStr); - d.setHours(0, 0, 0, 0); - return d.getTime() < today.getTime(); -}; + const isBeforeToday = (dateStr) => { + if (!dateStr) return false; + const d = new Date(dateStr); + d.setHours(0, 0, 0, 0); + return d.getTime() < today.getTime(); + }; -const sortByName = (a, b) => { - const nameA = (a.firstName + a.lastName).toLowerCase(); - const nameB = (b.firstName + b.lastName).toLowerCase(); - return nameA.localeCompare(nameB); -}; + const sortByName = (a, b) => { + const nameA = (a.firstName + a.lastName).toLowerCase(); + const nameB = (b.firstName + b.lastName).toLowerCase(); + return nameA.localeCompare(nameB); + }; -const { data = [], isLoading, error, refetch, isFetching } = useAttendancesLogs( - selectedProject, - dateRange.startDate, - dateRange.endDate, - organizationId -); - -const processedData = useMemo(() => { - const filteredData = showPending - ? data.filter((item) => item.checkOutTime === null) - : data; - - const group1 = filteredData.filter((d) => d.activity === 1 && isSameDay(d.checkInTime)).sort(sortByName); - const group2 = filteredData.filter((d) => d.activity === 4 && isSameDay(d.checkOutTime)).sort(sortByName); - const group3 = filteredData.filter((d) => d.activity === 1 && isBeforeToday(d.checkInTime)).sort(sortByName); - const group4 = filteredData.filter((d) => d.activity === 4 && isBeforeToday(d.checkOutTime)); - const group5 = filteredData.filter((d) => d.activity === 2 && isBeforeToday(d.checkOutTime)).sort(sortByName); - const group6 = filteredData.filter((d) => d.activity === 5).sort(sortByName); - - const sortedList = [...group1, ...group2, ...group3, ...group4, ...group5, ...group6]; - - const groupedByDate = sortedList.reduce((acc, item) => { - const date = (item.checkInTime || item.checkOutTime)?.split("T")[0]; - if (date) { - acc[date] = acc[date] || []; - acc[date].push(item); - } - return acc; - }, {}); - - const sortedDates = Object.keys(groupedByDate).sort((a, b) => new Date(b) - new Date(a)); - return sortedDates.flatMap((date) => groupedByDate[date]); -}, [data, showPending]); - -const filteredSearchData = useMemo(() => { - if (!searchTerm) return processedData; - - const lowercased = searchTerm.toLowerCase(); - return processedData.filter((item) => - `${item.firstName} ${item.lastName}`.toLowerCase().includes(lowercased) + const { data = [], isLoading, error, refetch, isFetching } = useAttendancesLogs( + selectedProject, + dateRange.startDate, + dateRange.endDate, + organizationId ); -}, [processedData, searchTerm]); -const { - currentPage, - totalPages, - currentItems: paginatedAttendances, - paginate, - resetPage, -} = usePagination(filteredSearchData, 20); + const processedData = useMemo(() => { + const filteredData = showPending + ? data.filter((item) => item.checkOutTime === null) + : data; -useEffect(() => { - resetPage(); -}, [filteredSearchData]); + const group1 = filteredData.filter((d) => d.activity === 1 && isSameDay(d.checkInTime)).sort(sortByName); + const group2 = filteredData.filter((d) => d.activity === 4 && isSameDay(d.checkOutTime)).sort(sortByName); + const group3 = filteredData.filter((d) => d.activity === 1 && isBeforeToday(d.checkInTime)).sort(sortByName); + const group4 = filteredData.filter((d) => d.activity === 4 && isBeforeToday(d.checkOutTime)); + const group5 = filteredData.filter((d) => d.activity === 2 && isBeforeToday(d.checkOutTime)).sort(sortByName); + const group6 = filteredData.filter((d) => d.activity === 5).sort(sortByName); -const handler = useCallback( - (msg) => { - const { startDate, endDate } = dateRange; - const checkIn = msg.response.checkInTime.substring(0, 10); + const sortedList = [...group1, ...group2, ...group3, ...group4, ...group5, ...group6]; - if (selectedProject === msg.projectId && startDate <= checkIn && checkIn <= endDate) { - queryClient.setQueriesData(["attendanceLogs"], (oldData) => { - if (!oldData) { - queryClient.invalidateQueries({ queryKey: ["attendanceLogs"] }); - return; - } - return oldData.map((record) => - record.id === msg.response.id ? { ...record, ...msg.response } : record - ); - }); - resetPage(); - } - }, - [selectedProject, dateRange, resetPage] -); + const groupedByDate = sortedList.reduce((acc, item) => { + const date = (item.checkInTime || item.checkOutTime)?.split("T")[0]; + if (date) { + acc[date] = acc[date] || []; + acc[date].push(item); + } + return acc; + }, {}); -useEffect(() => { - eventBus.on("attendance_log", handler); - return () => eventBus.off("attendance_log", handler); -}, [handler]); + const sortedDates = Object.keys(groupedByDate).sort((a, b) => new Date(b) - new Date(a)); + return sortedDates.flatMap((date) => groupedByDate[date]); + }, [data, showPending]); -const employeeHandler = useCallback( - (msg) => { - const { startDate, endDate } = dateRange; - if (data.some((item) => item.employeeId == msg.employeeId)) { - refetch(); - } - }, - [data, refetch] -); + const filteredSearchData = useMemo(() => { + if (!searchTerm) return processedData; -useEffect(() => { - eventBus.on("employee", employeeHandler); - return () => eventBus.off("employee", employeeHandler); -}, [employeeHandler]); + const lowercased = searchTerm.toLowerCase(); + return processedData.filter((item) => + `${item.firstName} ${item.lastName}`.toLowerCase().includes(lowercased) + ); + }, [processedData, searchTerm]); + + const { + currentPage, + totalPages, + currentItems: paginatedAttendances, + paginate, + resetPage, + } = usePagination(filteredSearchData, 20); + + useEffect(() => { + resetPage(); + }, [filteredSearchData]); + + const handler = useCallback( + (msg) => { + const { startDate, endDate } = dateRange; + const checkIn = msg.response.checkInTime.substring(0, 10); + + if (selectedProject === msg.projectId && startDate <= checkIn && checkIn <= endDate) { + queryClient.setQueriesData(["attendanceLogs"], (oldData) => { + if (!oldData) { + queryClient.invalidateQueries({ queryKey: ["attendanceLogs"] }); + return; + } + return oldData.map((record) => + record.id === msg.response.id ? { ...record, ...msg.response } : record + ); + }); + resetPage(); + } + }, + [selectedProject, dateRange, resetPage] + ); + + useEffect(() => { + eventBus.on("attendance_log", handler); + return () => eventBus.off("attendance_log", handler); + }, [handler]); + + const employeeHandler = useCallback( + (msg) => { + const { startDate, endDate } = dateRange; + if (data.some((item) => item.employeeId == msg.employeeId)) { + refetch(); + } + }, + [data, refetch] + ); + + useEffect(() => { + eventBus.on("employee", employeeHandler); + return () => eventBus.off("employee", employeeHandler); + }, [employeeHandler]); @@ -232,9 +232,9 @@ useEffect(() => { const previousAttendance = arr[index - 1]; const previousDate = previousAttendance ? moment( - previousAttendance.checkInTime || - previousAttendance.checkOutTime - ).format("YYYY-MM-DD") + previousAttendance.checkInTime || + previousAttendance.checkOutTime + ).format("YYYY-MM-DD") : null; if (!previousDate || currentDate !== previousDate) { @@ -297,8 +297,7 @@ useEffect(() => { ) : (
- No data available for the selected date range. Please Select - another date. + No attendance record found in selected date range.
)} @@ -326,9 +325,8 @@ useEffect(() => { (pageNumber) => (
  • -
  • - {[...Array(totalPages)].map((_, index) => ( -
  • - -
  • - ))} -
  • - -
  • - - - )} ) : (
    )}
    + {!loading && finalFilteredData.length > ITEMS_PER_PAGE && ( + + )} ); }; diff --git a/src/components/Activities/AttendcesLogs.jsx b/src/components/Activities/AttendcesLogs.jsx index 7a346f67..dea24b4e 100644 --- a/src/components/Activities/AttendcesLogs.jsx +++ b/src/components/Activities/AttendcesLogs.jsx @@ -171,29 +171,30 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => { return ( <> -
    -
    - -
    - setShowPending(e.target.checked)} - /> - -
    -
    -
    +
    +
    + +
    + setShowPending(e.target.checked)} + /> + +
    +
    +
    +
    )} - {/* {!loading && totalPages > 1 && ( - - )} */} - {totalPages > 0 && ( Date: Tue, 14 Oct 2025 14:42:33 +0530 Subject: [PATCH 24/41] Changes in Regularization tab. --- src/components/Activities/AttendcesLogs.jsx | 46 ++--- src/components/Activities/Regularization.jsx | 197 ++++++++++--------- 2 files changed, 123 insertions(+), 120 deletions(-) diff --git a/src/components/Activities/AttendcesLogs.jsx b/src/components/Activities/AttendcesLogs.jsx index dea24b4e..b8ec542c 100644 --- a/src/components/Activities/AttendcesLogs.jsx +++ b/src/components/Activities/AttendcesLogs.jsx @@ -171,29 +171,29 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => { return ( <> -
    -
    - -
    - setShowPending(e.target.checked)} - /> - -
    -
    -
    +
    +
    + +
    + setShowPending(e.target.checked)} + /> + +
    +
    +
    { - if(!regularizes) return - if(regularizes?.length) { - setregularizedList(regularizes); + if (!regularizes) return + if (regularizes?.length) { + setregularizedList(regularizes); } }, [regularizes]); @@ -102,101 +102,103 @@ const Regularization = ({ }, [employeeHandler]); return ( -
    - {loading ? ( -
    -

    Loading...

    -
    - ) : currentItems?.length > 0 ? ( - - - - - - - - - - - - - - - {currentItems?.map((att, index) => ( - - - - - - - - - - - - +
    +
    + {loading ? ( +
    +

    Loading...

    +
    + ) : currentItems?.length > 0 ? ( +
    NameDateOrganization - Check-In - - Check-Out - - Requested By - - Requested At - Action
    - - {moment(att.checkOutTime).format("DD-MMM-YYYY")}{att.organizationName || "--"}{convertShortTime(att.checkInTime)} - {att.requestedAt ? convertShortTime(att.checkOutTime) : "--"} - - {att.requestedBy ? ( ):(--)} - - {att?.requestedAt ? formatUTCToLocalTime(att.requestedAt,true) : "--"} - - -
    + + + + + + + + + + - ))} - -
    NameDateOrganization + Check-In + + Check-Out + + Requested By + + Requested At + Action
    - ) : ( -
    - - {searchTerm - ? "No results found for your search." - : "No Requests Found !"} - -
    - )} + + + {currentItems?.map((att, index) => ( + + + + + {moment(att.checkOutTime).format("DD-MMM-YYYY")} + + {att.organizationName || "--"} + + {convertShortTime(att.checkInTime)} + + {att.requestedAt ? convertShortTime(att.checkOutTime) : "--"} + + + + {att.requestedBy ? () : (--)} + + + {att?.requestedAt ? formatUTCToLocalTime(att.requestedAt, true) : "--"} + + + + + + ))} + + + ) : ( +
    + + {searchTerm + ? "No results found for your search." + : "No Requests Found !"} + +
    + )} +
    {totalPages > 0 && ( )} +
    ); }; -- 2.43.0 From dc4e48ad3bc6e5c32b041e60c1ed9838215db1c1 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Tue, 14 Oct 2025 14:51:57 +0530 Subject: [PATCH 25/41] Changes in Employee list at pagination and at mobile view then scrollbar is shown. --- .../DailyProgressRport/TaskReportList.jsx | 203 +++++++++--------- src/components/Directory/ListViewContact.jsx | 13 +- src/components/Expenses/ExpenseList.jsx | 34 ++- src/pages/employee/EmployeeList.jsx | 9 +- 4 files changed, 129 insertions(+), 130 deletions(-) diff --git a/src/components/DailyProgressRport/TaskReportList.jsx b/src/components/DailyProgressRport/TaskReportList.jsx index 78af8df3..c47f46c4 100644 --- a/src/components/DailyProgressRport/TaskReportList.jsx +++ b/src/components/DailyProgressRport/TaskReportList.jsx @@ -29,7 +29,7 @@ const TaskReportList = () => { const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK); const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK); - const { service, openModal, closeModal,filter } = useDailyProgrssContext(); + const { service, openModal, closeModal, filter } = useDailyProgrssContext(); const selectedProject = useSelectedProject(); const { projectNames } = useProjectName(); @@ -37,7 +37,7 @@ const TaskReportList = () => { selectedProject, ITEMS_PER_PAGE, currentPage, - service,filter + service, filter ); const ProgrssReportColumn = [ @@ -192,109 +192,114 @@ const TaskReportList = () => { if (isLoading) return ; if (isError) return
    Loading....
    ; return ( -
    - - - - - - - - - - - - - {groupedTasks.length === 0 && ( +
    +
    +
    Activity - - Total Pending{" "} - This shows the total pending tasks for each activity on that date.

    } - > - -
    -
    -
    - - Reported/Planned{" "} - This shows the reported versus planned tasks for each activity on that date.

    } - > - -
    -
    -
    Assign DateTeamActions
    + - + + + + + + - )} - - {groupedTasks.map(({ date, tasks }) => ( - - - + + {groupedTasks.length === 0 && ( + + - {tasks.map((task, idx) => ( - - - - - - - + - ))} - - ))} - -
    - No reports available - Activity + + Total Pending{" "} + This shows the total pending tasks for each activity on that date.

    } + > + +
    +
    +
    + + Reported/Planned{" "} + This shows the reported versus planned tasks for each activity on that date.

    } + > + +
    +
    +
    Assign DateTeamActions
    - {formatUTCToLocalTime(date)} +
    + No reports available
    -
    - {task.workItem.activityMaster?.activityName || "No Activity Name"} -
    -
    - {task.workItem.workArea?.floor?.building?.name} ›{" "} - {task.workItem.workArea?.floor?.floorName} ›{" "} - {task.workItem.workArea?.areaName} -
    -
    - {formatNumber(task.workItem.plannedWork)} - {`${formatNumber(task.completedTask)} / ${formatNumber(task.plannedTask)}`}{formatUTCToLocalTime(task.assignmentDate)}{renderTeamMembers(task, idx)} -
    - {ReportTaskRights && !task.reportedDate && ( - - )} - {ApprovedTaskRights && task.reportedDate && !task.approvedBy && ( - - )} - -
    + )} + + {groupedTasks.map(({ date, tasks }) => ( + +
    + {formatUTCToLocalTime(date)}
    - {data?.data?.length > 0 && ( - - )} -
    + {tasks.map((task, idx) => ( + + +
    + {task.workItem.activityMaster?.activityName || "No Activity Name"} +
    +
    + {task.workItem.workArea?.floor?.building?.name} ›{" "} + {task.workItem.workArea?.floor?.floorName} ›{" "} + {task.workItem.workArea?.areaName} +
    + + + {formatNumber(task.workItem.plannedWork)} + + {`${formatNumber(task.completedTask)} / ${formatNumber(task.plannedTask)}`} + {formatUTCToLocalTime(task.assignmentDate)} + {renderTeamMembers(task, idx)} + +
    + {ReportTaskRights && !task.reportedDate && ( + + )} + {ApprovedTaskRights && task.reportedDate && !task.approvedBy && ( + + )} + +
    + + + ))} + + ))} + + + +
    + { + data?.data?.length > 0 && ( + + ) + } +
    ); }; diff --git a/src/components/Directory/ListViewContact.jsx b/src/components/Directory/ListViewContact.jsx index 16fd9a14..f4f15531 100644 --- a/src/components/Directory/ListViewContact.jsx +++ b/src/components/Directory/ListViewContact.jsx @@ -160,8 +160,7 @@ const ListViewContact = ({ data, Pagination }) => {
    ) : ( { )} - {Pagination && ( -
    - {Pagination} -
    - )}
    + {Pagination && ( +
    + {Pagination} +
    + )}
    ); diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index c18d2650..d51063d7 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -67,9 +67,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { key = item.status?.displayName || "Unknown"; break; case "submittedBy": - key = `${item.createdBy?.firstName ?? ""} ${ - item.createdBy?.lastName ?? "" - }`.trim(); + key = `${item.createdBy?.firstName ?? ""} ${item.createdBy?.lastName ?? "" + }`.trim(); break; case "project": key = item.project?.name || "Unknown Project"; @@ -110,9 +109,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { label: "Submitted By", align: "text-start", getValue: (e) => - `${e.createdBy?.firstName ?? ""} ${ - e.createdBy?.lastName ?? "" - }`.trim() || "N/A", + `${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? "" + }`.trim() || "N/A", customRender: (e) => (
    { lastName={e.createdBy?.lastName} /> - {`${e.createdBy?.firstName ?? ""} ${ - e.createdBy?.lastName ?? "" - }`.trim() || "N/A"} + {`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? "" + }`.trim() || "N/A"}
    ), @@ -152,9 +149,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { align: "text-center", getValue: (e) => ( {e.status?.name || "Unknown"} @@ -299,15 +295,15 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { )} - {data?.data?.length > 0 && ( - - )}
    + {data?.data?.length > 0 && ( + + )}
    ); diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index 4d6d2f0c..10db6786 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -649,17 +649,16 @@ const EmployeeList = () => { ))} - - - {displayData?.length > 0 && ( + +
    +
    + {displayData?.length > 0 && ( )} -
    -
    ) : (
    -- 2.43.0 From c8273070aca68af5bb4898476aff36082cf9e9ac Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 15 Oct 2025 17:21:56 +0530 Subject: [PATCH 26/41] Changes in Attendance and Expenselist. --- src/components/Activities/AttendcesLogs.jsx | 9 ++++++++- src/components/Activities/Regularization.jsx | 19 ++++++++++++------- src/components/Expenses/ExpenseList.jsx | 5 ++++- src/pages/Activities/AttendancePage.jsx | 6 +++--- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/components/Activities/AttendcesLogs.jsx b/src/components/Activities/AttendcesLogs.jsx index b8ec542c..949ad7bd 100644 --- a/src/components/Activities/AttendcesLogs.jsx +++ b/src/components/Activities/AttendcesLogs.jsx @@ -15,6 +15,7 @@ import AttendanceRepository from "../../repositories/AttendanceRepository"; import { useAttendancesLogs } from "../../hooks/useAttendance"; import { queryClient } from "../../layouts/AuthLayout"; import { ITEMS_PER_PAGE } from "../../utils/constants"; +import { useNavigate } from "react-router-dom"; const usePagination = (data, itemsPerPage) => { const [currentPage, setCurrentPage] = useState(1); @@ -44,6 +45,7 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => { const [loading, setLoading] = useState(false); const [showPending, setShowPending] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); + const navigate = useNavigate(); const today = new Date(); today.setHours(0, 0, 0, 0); @@ -261,7 +263,12 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => { lastName={attendance.lastName} />
    - + + navigate(`/employee/${attendance.employeeId}?for=attendance`) + } + className="text-heading text-truncate cursor-pointer" + > {attendance.firstName} {attendance.lastName} diff --git a/src/components/Activities/Regularization.jsx b/src/components/Activities/Regularization.jsx index 6521cb65..954820c2 100644 --- a/src/components/Activities/Regularization.jsx +++ b/src/components/Activities/Regularization.jsx @@ -14,6 +14,7 @@ import { } from "../../slices/apiDataManager"; import { useQueryClient } from "@tanstack/react-query"; import Pagination from "../../components/common/Pagination"; +import { useNavigate } from "react-router-dom"; const Regularization = ({ handleRequest, @@ -26,6 +27,7 @@ const Regularization = ({ // var selectedProject = useSelector((store) => store.localVariables.projectId); const selectedProject = useSelectedProject(); const [regularizesList, setregularizedList] = useState([]); + const navigate = useNavigate(); const { regularizes, loading, error, refetch } = useRegularizationRequests( selectedProject, organizationId, @@ -104,7 +106,7 @@ const Regularization = ({ return (
    {loading ? ( @@ -142,12 +144,15 @@ const Regularization = ({
    - diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index d51063d7..29844402 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -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( @@ -112,7 +114,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { `${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? "" }`.trim() || "N/A", customRender: (e) => ( -
    +
    navigate(`/employee/${e.createdBy?.id}`)}> { {selectedProject ? ( <> {activeTab === "all" && ( -
    +
    {
    )} {activeTab === "logs" && ( -
    +
    {
    )} {activeTab === "regularization" && DoRegularized && ( -
    +
    Date: Thu, 16 Oct 2025 10:17:12 +0530 Subject: [PATCH 27/41] Changes in filter panel. --- src/components/Documents/DocumentFilterPanel.jsx | 4 ++-- src/components/Expenses/ExpenseList.jsx | 8 +++----- src/components/gallary/GalleryFilterPanel.jsx | 4 ++-- src/pages/Directory/ContactFilterPanel.jsx | 10 +++++----- src/pages/Directory/NoteFilterPanel.jsx | 10 +++++----- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/components/Documents/DocumentFilterPanel.jsx b/src/components/Documents/DocumentFilterPanel.jsx index b266c986..edc613e1 100644 --- a/src/components/Documents/DocumentFilterPanel.jsx +++ b/src/components/Documents/DocumentFilterPanel.jsx @@ -78,14 +78,14 @@ const DocumentFilterPanel = forwardRef( ? moment.utc(values.endDate, "DD-MM-YYYY").toISOString() : null, }); - closePanel(); + // closePanel(); }; const onClear = () => { reset(DocumentFilterDefaultValues); setResetKey((prev) => prev + 1); onApply(DocumentFilterDefaultValues); - closePanel(); + // closePanel(); }; if (isLoading) return
    Loading...
    ; diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index eadd0ac1..1ec3e71f 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -140,9 +140,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { align: "text-start", getValue: (e) => `${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? "" - }`.trim() || "N/A", - `${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? "" - }`.trim() || "N/A", + }`.trim() || "N/A", customRender: (e) => (
    navigate(`/employee/${e.createdBy?.id}`)}> @@ -380,8 +378,6 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { )} -
    -
    {data?.data?.length > 0 && ( { onPageChange={paginate} /> )} +
    +
    ); diff --git a/src/components/gallary/GalleryFilterPanel.jsx b/src/components/gallary/GalleryFilterPanel.jsx index fadcf135..d71625a3 100644 --- a/src/components/gallary/GalleryFilterPanel.jsx +++ b/src/components/gallary/GalleryFilterPanel.jsx @@ -37,13 +37,13 @@ const GalleryFilterPanel = ({ onApply }) => { startDate: localToUtc(formData.startDate), endDate: localToUtc(formData.endDate), }); - closePanel() + // closePanel() }; const onClear=()=>{ reset(defaultGalleryFilterValue); setResetKey((prev) => prev + 1); - closePanel() + // closePanel() } if (isLoading) return
    Loading....
    ; diff --git a/src/pages/Directory/ContactFilterPanel.jsx b/src/pages/Directory/ContactFilterPanel.jsx index 5f3c84de..5db00bd9 100644 --- a/src/pages/Directory/ContactFilterPanel.jsx +++ b/src/pages/Directory/ContactFilterPanel.jsx @@ -67,13 +67,13 @@ const ContactFilterPanel = forwardRef( const onSubmit = (formData) => { onApply(formData); - closePanel(); + // closePanel(); }; const handleClose = () => { reset(defaultContactFilter); onApply(defaultContactFilter); - closePanel(); + // closePanel(); }; if (isLoading || isFetching) return ; @@ -99,16 +99,16 @@ const ContactFilterPanel = forwardRef( valueKey="id" />
    -
    -
    diff --git a/src/pages/Directory/NoteFilterPanel.jsx b/src/pages/Directory/NoteFilterPanel.jsx index 88fb992e..5c24785c 100644 --- a/src/pages/Directory/NoteFilterPanel.jsx +++ b/src/pages/Directory/NoteFilterPanel.jsx @@ -39,13 +39,13 @@ const NoteFilterPanel = forwardRef(({ onApply, clearFilter, setFilterdata }, ref const onSubmit = (formData) => { onApply(formData); - closePanel(); + // closePanel(); }; const handleClose = () => { reset(defaultNotesFilter); onApply(defaultNotesFilter); - closePanel(); + // closePanel(); }; //Add this for Filter chip remover @@ -93,17 +93,17 @@ const NoteFilterPanel = forwardRef(({ onApply, clearFilter, setFilterdata }, ref labelKey={(item) => item.name} valueKey="id" /> -
    - +
    -
    -- 2.43.0 From 97525e3cb23fb2d9e4136ebb6a47ed60d79eb860 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Thu, 16 Oct 2025 12:51:03 +0530 Subject: [PATCH 28/41] Correction in Expense compoent at name column. --- src/components/Expenses/ExpenseList.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index 1ec3e71f..752ad7b4 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -152,9 +152,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { /> {`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? "" - }`.trim() || "N/A"} - {`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? "" - }`.trim() || "N/A"} + }`.trim() || "N/A"}
    ), -- 2.43.0 From 9355efa4aff2917ff083703e13f1354f50a9aa9f Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Fri, 24 Oct 2025 13:12:41 +0530 Subject: [PATCH 29/41] fixed search position of master search bar --- src/pages/master/MasterPage.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/master/MasterPage.jsx b/src/pages/master/MasterPage.jsx index f055fca5..31f14da4 100644 --- a/src/pages/master/MasterPage.jsx +++ b/src/pages/master/MasterPage.jsx @@ -186,8 +186,8 @@ const MasterPage = () => { )}
    -
    -
    +
    +
    Date: Fri, 24 Oct 2025 13:19:09 +0530 Subject: [PATCH 30/41] changed button size of resetbutton inside preview-document component --- src/components/Expenses/PreviewDocument.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Expenses/PreviewDocument.jsx b/src/components/Expenses/PreviewDocument.jsx index ce554ca9..c9000059 100644 --- a/src/components/Expenses/PreviewDocument.jsx +++ b/src/components/Expenses/PreviewDocument.jsx @@ -39,7 +39,7 @@ const PreviewDocument = ({ imageUrl }) => {
    + {/* Button Section */} +
    + +
    +
    +
    {" "} {errors.spridSearchText && ( @@ -124,7 +132,7 @@ const OrgPickerFromSPId = ({ title, placeholder }) => { No organization found for "{SPRID}"
    ) : null} -
    +
    Do not have SPRID or could not find organization ? -- 2.43.0 From 32f16092db7edba07677823f2f4131c4a8b5a889 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Fri, 24 Oct 2025 17:29:38 +0530 Subject: [PATCH 32/41] changed ui for org --- src/components/Dashboard/Dashboard.jsx | 3 +- .../Dashboard/DashboardSkeleton.jsx | 54 +++++++++++++++ src/components/Dashboard/Projects.jsx | 4 +- .../Organization/OrganizationsList.jsx | 11 ++-- src/components/Tenant/TenantsList.jsx | 2 +- src/components/collections/CollectionList.jsx | 2 +- src/components/gallary/ViewGallery.jsx | 1 - src/pages/Organization/OrganizationPage.jsx | 8 ++- src/pages/Tenant/TenantPage.jsx | 2 +- .../authentication/TenantSelectionPage.jsx | 65 ++++++++++--------- src/pages/collections/CollectionPage.jsx | 62 +++++++++++------- 11 files changed, 147 insertions(+), 67 deletions(-) create mode 100644 src/components/Dashboard/DashboardSkeleton.jsx diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index cc22006e..cd39fd82 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -19,7 +19,6 @@ import ExpenseStatus from "./ExpenseStatus"; import ExpenseByProject from "./ExpenseByProject"; const Dashboard = () => { - const { projectsCardData } = useDashboardProjectsCardData(); const { teamsCardData } = useDashboardTeamsCardData(); const { tasksCardData } = useDashboardTasksCardData(); @@ -32,7 +31,7 @@ const Dashboard = () => {
    {isAllProjectsSelected && (
    - +
    )} diff --git a/src/components/Dashboard/DashboardSkeleton.jsx b/src/components/Dashboard/DashboardSkeleton.jsx new file mode 100644 index 00000000..c62a6594 --- /dev/null +++ b/src/components/Dashboard/DashboardSkeleton.jsx @@ -0,0 +1,54 @@ +import React from "react"; + +const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => ( +
    +); + +const skeletonStyle = ` +@keyframes skeleton-loading { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} +`; + +export const ProjectCardSkeleton = () => { + return ( + <> + {/* Inject animation CSS once */} + + +
    + {/* Header */} +
    +
    + {" "} + Projects +
    +
    + + {/* Skeleton body */} +
    +
    + + +
    +
    + + +
    +
    +
    + + ); +}; + diff --git a/src/components/Dashboard/Projects.jsx b/src/components/Dashboard/Projects.jsx index 86958aa5..aaa27a06 100644 --- a/src/components/Dashboard/Projects.jsx +++ b/src/components/Dashboard/Projects.jsx @@ -1,6 +1,8 @@ import React, { useEffect } from "react"; import { useDashboardProjectsCardData } from "../../hooks/useDashboard_Data"; import eventBus from "../../services/eventBus"; +import ProjectInfra from "../Project/ProjectInfra"; +import { ProjectCardSkeleton } from "./DashboardSkeleton"; const Projects = () => { const { @@ -23,7 +25,7 @@ const Projects = () => { const totalProjects = projectsCardData?.totalProjects ?? 0; const ongoingProjects = projectsCardData?.ongoingProjects ?? 0; - + if(isLoading) return return (
    diff --git a/src/components/Organization/OrganizationsList.jsx b/src/components/Organization/OrganizationsList.jsx index 41da2edf..ceacbab5 100644 --- a/src/components/Organization/OrganizationsList.jsx +++ b/src/components/Organization/OrganizationsList.jsx @@ -93,10 +93,12 @@ const OrganizationsList = ({searchText}) => { if (isError) return
    {error?.message || "Something went wrong"}
    ; return ( -
    -
    -
    - +
    +
    +
    {organizationsColumns.map((col) => ( @@ -157,7 +159,6 @@ const OrganizationsList = ({searchText}) => { )} - ); }; diff --git a/src/components/Tenant/TenantsList.jsx b/src/components/Tenant/TenantsList.jsx index 6269a248..089ffe44 100644 --- a/src/components/Tenant/TenantsList.jsx +++ b/src/components/Tenant/TenantsList.jsx @@ -136,7 +136,7 @@ const TenantsList = ({ ); return ( <> -
    +
    diff --git a/src/components/collections/CollectionList.jsx b/src/components/collections/CollectionList.jsx index 6f64808d..eba51a1d 100644 --- a/src/components/collections/CollectionList.jsx +++ b/src/components/collections/CollectionList.jsx @@ -151,7 +151,7 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => { if (isError) return

    {error.message}

    ; return ( -
    +
    { const [loading, setLoading] = useState(true); const [currentIndex, setCurrentIndex] = useState(index); - console.log(batch); useEffect(() => { setCurrentIndex(index); }, [index, batch]); diff --git a/src/pages/Organization/OrganizationPage.jsx b/src/pages/Organization/OrganizationPage.jsx index 21a28263..95629d87 100644 --- a/src/pages/Organization/OrganizationPage.jsx +++ b/src/pages/Organization/OrganizationPage.jsx @@ -13,7 +13,7 @@ const OrganizationPage = () => { -
    +
    @@ -42,9 +42,13 @@ const OrganizationPage = () => {
    + +
    - +
    + +
    ); diff --git a/src/pages/Tenant/TenantPage.jsx b/src/pages/Tenant/TenantPage.jsx index 694ff557..3ab2015c 100644 --- a/src/pages/Tenant/TenantPage.jsx +++ b/src/pages/Tenant/TenantPage.jsx @@ -121,7 +121,7 @@ const TenantPage = () => { { label: "Tenant", link: null }, ]} /> -
    +
    {/* Super Tenant Actions */} {isSuperTenant && (
    diff --git a/src/pages/authentication/TenantSelectionPage.jsx b/src/pages/authentication/TenantSelectionPage.jsx index 18028902..b8a5398d 100644 --- a/src/pages/authentication/TenantSelectionPage.jsx +++ b/src/pages/authentication/TenantSelectionPage.jsx @@ -8,35 +8,43 @@ const TenantSelectionPage = () => { const [pendingTenant, setPendingTenant] = useState(null); const navigate = useNavigate(); - const { data, isLoading, isError, error } = useTenants(); + const { data, isLoading, isError } = useTenants(); const { mutate: chooseTenant, isPending } = useSelectTenant(() => { navigate("/dashboard"); }); - const handleTenantselection = (tenantId) => { + const { mutate: handleLogout, isPending: isLogouting } = useLogout(); + + const handleTenantSelection = (tenantId) => { setPendingTenant(tenantId); localStorage.setItem("ctnt", tenantId); chooseTenant(tenantId); }; - - const {mutate:handleLogout,isPending:isLogouting} = useLogout() - + // Auto-select if already stored useEffect(() => { - if (localStorage.getItem("ctnt")) { - chooseTenant(localStorage.getItem("ctnt")) + const storedTenant = localStorage.getItem("ctnt"); + if (storedTenant) { + chooseTenant(storedTenant); } - }, [navigate]); + }, []); + // Auto-select if only one tenant useEffect(() => { if (!isLoading && data?.data?.length === 1) { const tenant = data.data[0]; - handleTenantselection(tenant.id); + handleTenantSelection(tenant.id); } }, [isLoading, data]); - if (isLoading) return ; - - if (isLoading) { + // Show loader if: + // - initial loading + // - only one tenant (auto-selecting) + // - user manually selecting + if ( + isLoading || + isPending || + (data?.data?.length === 1 && pendingTenant !== null) + ) { return ; } @@ -48,7 +56,6 @@ const TenantSelectionPage = () => { ); } - return (
    {/* Logo */} @@ -65,20 +72,24 @@ const TenantSelectionPage = () => {

    Welcome

    - Please select which dashboard you want to explore!!! + Please select which dashboard you want to explore!

    -
    handleLogout()}> - {isLogouting ? "Please Wait...":SignOut} +
    handleLogout()}> + {isLogouting ? ( + "Please Wait..." + ) : ( + + Sign Out + + )}
    -
    - {/* Card Section */} -
    + {/* Tenant Cards */} +
    {data?.data.map((tenant) => (
    - {/* Image */}
    { {/* Content */}
    - {/* Title */}

    {tenant?.name}

    - {/* Industry */}

    Industry:

    @@ -108,21 +117,19 @@ const TenantSelectionPage = () => {

    - {/* Description */} {tenant?.description && (

    {tenant?.description}

    )} - {/* Button */}
    @@ -131,8 +138,8 @@ const TenantSelectionPage = () => { ))}
    - ); }; -export default TenantSelectionPage; \ No newline at end of file +export default TenantSelectionPage; + diff --git a/src/pages/collections/CollectionPage.jsx b/src/pages/collections/CollectionPage.jsx index aac80d26..298bde19 100644 --- a/src/pages/collections/CollectionPage.jsx +++ b/src/pages/collections/CollectionPage.jsx @@ -82,18 +82,29 @@ const CollectionPage = () => { const handleMarkedPayment = (payload) => { MarkedReceived(payload); }; - if (isAdmin === undefined || - canAddPayment === undefined || - canEditCollection === undefined || - canViewCollection === undefined || + if ( + isAdmin === undefined || + canAddPayment === undefined || + canEditCollection === undefined || + canViewCollection === undefined || canCreate === undefined -) { - return
    Checking access...
    ; -} + ) { + return
    Checking access...
    ; + } -if (!isAdmin && !canAddPayment && !canEditCollection && !canViewCollection && !canCreate) { - return ; -} + if ( + !isAdmin && + !canAddPayment && + !canEditCollection && + !canViewCollection && + !canCreate + ) { + return ( + + ); + } return (
    @@ -127,28 +138,31 @@ if (!isAdmin && !canAddPayment && !canEditCollection && !canViewCollection && !c
    -
    -
    +
    +
    {" "} setSearchText(e.target.value)} - placeholder="search Collection" + placeholder="Search Collection" className="form-control form-control-sm" />
    - { (canCreate || isAdmin) && ( - -)} - + {(canCreate || isAdmin) && ( + + )}
    -- 2.43.0 From a070d23304723e83f324166e77634c7b14e5c8b7 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Sat, 25 Oct 2025 10:26:41 +0530 Subject: [PATCH 33/41] added skeleton for dashboard card figures cards --- public/assets/css/core-extend.css | 6 ++ .../TaskReportFilterPanel.jsx | 6 +- src/components/Dashboard/Dashboard.jsx | 6 +- .../Dashboard/DashboardSkeleton.jsx | 56 +++++++++++++++++++ src/components/Dashboard/Projects.jsx | 29 ++++++---- src/components/Dashboard/Tasks.jsx | 35 +++++++----- src/components/Dashboard/Teams.jsx | 33 +++++++---- 7 files changed, 133 insertions(+), 38 deletions(-) diff --git a/public/assets/css/core-extend.css b/public/assets/css/core-extend.css index 7823d036..5b780c72 100644 --- a/public/assets/css/core-extend.css +++ b/public/assets/css/core-extend.css @@ -25,6 +25,12 @@ font-size: 2rem; .text-md-b { font-weight: normal; } +.cursor-wait{ + cursor:wait; +} +.cursor-notallowed { + cursor: not-allowed; +} .text-xxs { font-size: 0.55rem; } /* 8px */ .text-xs { font-size: 0.75rem; } /* 12px */ diff --git a/src/components/DailyProgressRport/TaskReportFilterPanel.jsx b/src/components/DailyProgressRport/TaskReportFilterPanel.jsx index 6b1d15dc..2af3636d 100644 --- a/src/components/DailyProgressRport/TaskReportFilterPanel.jsx +++ b/src/components/DailyProgressRport/TaskReportFilterPanel.jsx @@ -29,7 +29,9 @@ const TaskReportFilterPanel = ({ handleFilter }) => { handleSubmit, formState: { errors }, } = methods; - + const closePanel = () => { + document.querySelector(".offcanvas.show .btn-close")?.click(); + }; const onSubmit = (formData) => { const filterPayload = { ...formData, @@ -37,12 +39,14 @@ const TaskReportFilterPanel = ({ handleFilter }) => { dateTo: localToUtc(formData.dateTo), }; handleFilter(filterPayload); + closePanel(); }; const onClear = () => { setResetKey((prev) => prev + 1); handleFilter(TaskReportDefaultValue); reset(TaskReportDefaultValue); + closePanel(); }; return ( diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index cd39fd82..4c09056a 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -19,8 +19,6 @@ import ExpenseStatus from "./ExpenseStatus"; import ExpenseByProject from "./ExpenseByProject"; const Dashboard = () => { - const { teamsCardData } = useDashboardTeamsCardData(); - const { tasksCardData } = useDashboardTasksCardData(); // Get the selected project ID from Redux store const projectId = useSelector((store) => store.localVariables.projectId); @@ -36,11 +34,11 @@ const Dashboard = () => { )}
    - +
    - +
    {isAllProjectsSelected && ( diff --git a/src/components/Dashboard/DashboardSkeleton.jsx b/src/components/Dashboard/DashboardSkeleton.jsx index c62a6594..89d25636 100644 --- a/src/components/Dashboard/DashboardSkeleton.jsx +++ b/src/components/Dashboard/DashboardSkeleton.jsx @@ -52,3 +52,59 @@ export const ProjectCardSkeleton = () => { ); }; +export const TeamsSkeleton = () => { + return ( + <> + + +
    + {/* Header */} +
    +
    + Teams +
    +
    + + {/* Skeleton Body */} +
    +
    + + +
    +
    + + +
    +
    +
    + + ); +}; +export const TasksSkeleton = () => { + return ( + <> + + +
    + {/* Header */} +
    +
    + Tasks +
    +
    + + {/* Skeleton Body */} +
    +
    + + +
    +
    + + +
    +
    +
    + + ); +}; diff --git a/src/components/Dashboard/Projects.jsx b/src/components/Dashboard/Projects.jsx index aaa27a06..359b184c 100644 --- a/src/components/Dashboard/Projects.jsx +++ b/src/components/Dashboard/Projects.jsx @@ -10,6 +10,7 @@ const Projects = () => { isLoading, isError, error, + isFetching, refetch, } = useDashboardProjectsCardData(); @@ -25,7 +26,7 @@ const Projects = () => { const totalProjects = projectsCardData?.totalProjects ?? 0; const ongoingProjects = projectsCardData?.ongoingProjects ?? 0; - if(isLoading) return + if (isLoading) return ; return (
    @@ -35,15 +36,23 @@ const Projects = () => {
    - {isLoading ? ( -
    -
    - Loading... -
    -
    - ) : isError ? ( -
    - {error?.message || "Error loading data"} + {isError ? ( +
    + + + {error?.message || "Unable to load data at the moment."} + + + {" "} + Retry +
    ) : (
    diff --git a/src/components/Dashboard/Tasks.jsx b/src/components/Dashboard/Tasks.jsx index 56eb00f0..e4dcd791 100644 --- a/src/components/Dashboard/Tasks.jsx +++ b/src/components/Dashboard/Tasks.jsx @@ -1,6 +1,7 @@ import React from "react"; import { useSelectedProject } from "../../slices/apiDataManager"; import { useDashboardTasksCardData } from "../../hooks/useDashboard_Data"; +import { TasksSkeleton } from "./DashboardSkeleton"; const TasksCard = () => { const projectId = useSelectedProject(); @@ -10,8 +11,10 @@ const TasksCard = () => { isLoading, isError, error, + isFetching, + refetch, } = useDashboardTasksCardData(projectId); - + if (isLoading) return ; return (
    @@ -20,20 +23,26 @@ const TasksCard = () => {
    - {isLoading ? ( - // Loader while fetching -
    -
    - Loading... -
    -
    - ) : isError ? ( - // Show error -
    - {error?.message || "Error loading data"} + {isError ? ( +
    + + + + {error?.message || "Unable to load data at the moment."} + + + {" "} + Retry +
    ) : ( - // Show data

    diff --git a/src/components/Dashboard/Teams.jsx b/src/components/Dashboard/Teams.jsx index 9e9d31f9..bae3d553 100644 --- a/src/components/Dashboard/Teams.jsx +++ b/src/components/Dashboard/Teams.jsx @@ -4,16 +4,19 @@ import { useDashboardTeamsCardData } from "../../hooks/useDashboard_Data"; import eventBus from "../../services/eventBus"; import { useQueryClient } from "@tanstack/react-query"; import { useSelectedProject } from "../../slices/apiDataManager"; +import { TeamsSkeleton } from "./DashboardSkeleton"; const Teams = () => { const queryClient = useQueryClient(); - const projectId = useSelectedProject() + const projectId = useSelectedProject(); const { data: teamsCardData, isLoading, isError, error, + isFetching, + refetch, } = useDashboardTeamsCardData(projectId); // Handle real-time updates via eventBus @@ -40,6 +43,7 @@ const Teams = () => { const inToday = teamsCardData?.inToday ?? 0; const totalEmployees = teamsCardData?.totalEmployees ?? 0; + if (isLoading) return ; return (
    @@ -48,15 +52,24 @@ const Teams = () => {

    - {isLoading ? ( -
    -
    - Loading... -
    -
    - ) : isError ? ( -
    - {error?.message || "Error loading data"} + {isError ? ( +
    + + + + {error?.message || "Unable to load data at the moment."} + + + {" "} + Retry +
    ) : (
    -- 2.43.0 From b4f1c48293dbd9b41da6fb95a3f2f2946223b608 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Sat, 25 Oct 2025 10:27:21 +0530 Subject: [PATCH 34/41] removed debugger --- src/hooks/useAuth.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hooks/useAuth.jsx b/src/hooks/useAuth.jsx index ded67e13..e5a9a0d3 100644 --- a/src/hooks/useAuth.jsx +++ b/src/hooks/useAuth.jsx @@ -36,7 +36,6 @@ export const useSubscription = (frequency) => { return useQuery({ queryKey: ["subscriptionPlans", frequency], queryFn: async () => { - debugger const resp = await AuthRepository.getSubscription(frequency); return resp.data; }, -- 2.43.0 From d7caf47498723c51e53bdab465d08828d7cc1846 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Sat, 25 Oct 2025 10:45:52 +0530 Subject: [PATCH 35/41] added zoom functionality inside prevw documents --- src/components/Expenses/PreviewDocument.jsx | 132 +++++++++++++++----- 1 file changed, 103 insertions(+), 29 deletions(-) diff --git a/src/components/Expenses/PreviewDocument.jsx b/src/components/Expenses/PreviewDocument.jsx index c9000059..dd756893 100644 --- a/src/components/Expenses/PreviewDocument.jsx +++ b/src/components/Expenses/PreviewDocument.jsx @@ -1,54 +1,128 @@ -import { useState } from "react"; +import { useState, useRef } from "react"; const PreviewDocument = ({ imageUrl }) => { const [loading, setLoading] = useState(true); const [rotation, setRotation] = useState(0); + const [zoom, setZoom] = useState(1); + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [isDragging, setIsDragging] = useState(false); + const [startPos, setStartPos] = useState({ x: 0, y: 0 }); + const containerRef = useRef(null); + + // Zoom handlers + const handleZoomIn = () => setZoom((prev) => Math.min(prev + 0.2, 3)); + const handleZoomOut = () => setZoom((prev) => Math.max(prev - 0.2, 0.5)); + + // Mouse wheel zoom + const handleWheel = (e) => { + e.preventDefault(); + const delta = e.deltaY > 0 ? -0.1 : 0.1; + setZoom((prev) => Math.min(Math.max(prev + delta, 0.5), 3)); + }; + + const handleMouseDown = (e) => { + if (zoom <= 1) return; + setIsDragging(true); + setStartPos({ + x: e.clientX - position.x, + y: e.clientY - position.y, + }); + }; + + const handleMouseMove = (e) => { + if (!isDragging) return; + setPosition({ + x: e.clientX - startPos.x, + y: e.clientY - startPos.y, + }); + }; + + const handleMouseUp = () => setIsDragging(false); + const handleMouseLeave = () => setIsDragging(false); + + const handleReset = () => { + setRotation(0); + setZoom(1); + setPosition({ x: 0, y: 0 }); + }; return ( - <> -
    + <> +
    setRotation((prev) => prev + 90)} > + + +
    -
    - - {loading && ( -
    Loading...
    - )} -
    +
    1 ? (isDragging ? "grabbing" : "grab") : "default", + userSelect: "none", + position: "relative", + }} + > + {loading && ( +
    + Loading... +
    + )} Full View setLoading(false)} + style={{ + transform: `translate(${position.x}px, ${position.y}px) rotate(${rotation}deg) scale(${zoom})`, + transition: isDragging ? "none" : "transform 0.3s ease", + objectFit: "contain", + maxWidth: "100%", + maxHeight: "100%", + display: loading ? "none" : "block", + pointerEvents: "none", + }} />
    -
    + {/*
    -
    -
    - +
    */} + ); }; export default PreviewDocument; + + + -- 2.43.0 From 4238157fd4fc4ee35aba3d862cb7075dacb78e7e Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Sat, 25 Oct 2025 11:02:57 +0530 Subject: [PATCH 36/41] prevent error occurering due to zoom in -out --- src/components/Expenses/PreviewDocument.jsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/Expenses/PreviewDocument.jsx b/src/components/Expenses/PreviewDocument.jsx index dd756893..31f50991 100644 --- a/src/components/Expenses/PreviewDocument.jsx +++ b/src/components/Expenses/PreviewDocument.jsx @@ -1,4 +1,4 @@ -import { useState, useRef } from "react"; +import { useState, useRef ,useEffect} from "react"; const PreviewDocument = ({ imageUrl }) => { const [loading, setLoading] = useState(true); @@ -20,6 +20,16 @@ const PreviewDocument = ({ imageUrl }) => { setZoom((prev) => Math.min(Math.max(prev + delta, 0.5), 3)); }; + useEffect(() => { + const container = containerRef.current; + if (!container) return; + + container.addEventListener("wheel", handleWheel, { passive: false }); + + return () => { + container.removeEventListener("wheel", handleWheel); + }; + }, []); const handleMouseDown = (e) => { if (zoom <= 1) return; setIsDragging(true); @@ -73,12 +83,11 @@ const PreviewDocument = ({ imageUrl }) => {
    Date: Sat, 25 Oct 2025 11:20:57 +0530 Subject: [PATCH 37/41] added document files properly handling inside --- src/components/collections/ViewCollection.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/collections/ViewCollection.jsx b/src/components/collections/ViewCollection.jsx index 799033e5..77bc78fe 100644 --- a/src/components/collections/ViewCollection.jsx +++ b/src/components/collections/ViewCollection.jsx @@ -172,15 +172,15 @@ const ViewCollection = ({ onClose }) => {
    {data?.attachments?.map((doc) => { - const isImage = doc.contentType?.includes("image"); + const isImage = doc.contentType?.startsWith("image"); return (
    { if (isImage) { @@ -188,6 +188,8 @@ const ViewCollection = ({ onClose }) => { IsOpen: true, Image: doc.preSignedUrl, }); + } else { + window.open(doc.preSignedUrl, "_blank"); } }} > -- 2.43.0 From b38eb1bc4b099a19f65ffe9d13ad51691abd5a63 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Sat, 25 Oct 2025 11:37:23 +0530 Subject: [PATCH 38/41] handle view documents inside expense --- src/components/Expenses/ViewExpense.jsx | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/Expenses/ViewExpense.jsx b/src/components/Expenses/ViewExpense.jsx index 3feed000..cd608dfe 100644 --- a/src/components/Expenses/ViewExpense.jsx +++ b/src/components/Expenses/ViewExpense.jsx @@ -9,7 +9,11 @@ import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { defaultActionValues, ExpenseActionScheam } from "./ExpenseSchema"; import { useExpenseContext } from "../../pages/Expense/ExpensePage"; -import { getColorNameFromHex, getIconByFileType, localToUtc } from "../../utils/appUtils"; +import { + getColorNameFromHex, + getIconByFileType, + localToUtc, +} from "../../utils/appUtils"; import { ExpenseDetailsSkeleton } from "./ExpenseSkeleton"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { @@ -301,15 +305,15 @@ const ViewExpense = ({ ExpenseId }) => {
    {data?.documents?.map((doc) => { - const isImage = doc.contentType?.includes("image"); + const isImage = doc.contentType?.startsWith("image"); return (
    { if (isImage) { @@ -317,6 +321,8 @@ const ViewExpense = ({ ExpenseId }) => { IsOpen: true, Image: doc.preSignedUrl, }); + } else { + window.open(doc.preSignedUrl, "_blank"); } }} > @@ -332,7 +338,7 @@ const ViewExpense = ({ ExpenseId }) => {
    ); - })} + }) ?? "No Attachment"}
    @@ -418,7 +424,9 @@ const ViewExpense = ({ ExpenseId }) => { {((nextStatusWithPermission.length > 0 && !IsRejectedExpense) || (IsRejectedExpense && isCreatedBy)) && ( <> - +